CFEngine: Issue when reading /proc or /sys files with readfile()
Introduction
A first glance, reading files located in /proc or /sys directories on GNU/Linux systems is not very different from reading plain files.
But you may encounter surprises while doing so through CFEngine and its readfile() function. I will try to explain why, the current state with stable CFEngine versions, and a workaround.
TL;DR Workaround
Using CFEngine’s readfile()
Because of the “everything is a file” approach of all Unix derivatives, /proc or /sys files allow an usual I/O interface. In CFEngine, the readfile() function lets you reading an external file into a variable:
|
|
Looks sane:
|
|
Fun starts here
So reading /proc/meminfo should be straighforward, isn’t it?
|
|
Surprise, nothing!
|
|
What happened? The answer lies on internal readfile() implementation, which is roughly:
- Getting the file size with stat()
- Preparing a buffer to read up to this size
- Retrieving the file’s content into the buffer, up to the determinated file size.
Nothing is read from /proc/meminfo. Try to make an educated guess:
|
|
/proc/meminfo appears to stat() as an empty file, therefore readfile() tries to read… 0 bytes.
However readfile() is not totally to blame. If the known file size is zero, nothing has to be read, which is a correct behavior for a plain file.
The discussion around the related bug report and the fix has led to some interesting findings.
I will use the stat(1) command to retrieve the file size given by its inode, and pv(1) to read the file and display how many bytes were really read.
Let’s test with a 4 bytes plain file:
|
|
Both stat(1) and v(1) agree that the file is 4 bytes long (the 4th byte is the invisible line feed 0x0a character)
Trying with /proc/meminfo:
|
|
Here, plain file I/O semantics are broken. /proc/meminfo which appears to be empty, holds data. Another way to check is a copy to a plain file:
|
|
But surprises do not stop here:
|
|
After non-empty files that appears as empty ones, here is one which appears to have 4096 bytes, but only holds 5 bytes.
It becomes difficult to cope with plain files which exhibit a mix between plain and FIFOs I/O semantics !
Current situation in CFEngine
Upcoming CFEngine-3.6
maxsize parameter of readfile() has to be set to 0 when reading /sys or /proc files to ensure a reliable read. (Documented here, will be part of CFEngine-3.6 reference manual)
Note there is a glitch: because of strings internal limits, readfile() can only read up to 4096 bytes. (any file, not just /proc or /sys ones)
CFEngine-3.5.2
|
|
=> Empty result, does not work.
CFEngine-3.4.5
|
|
=> Does not work.
CFEngine-3.3.9
|
|
=> Does not work.
Workaround
Using the execresult() function to catch the output of cat /proc/meminfo (first seen here) is a reliable way to read /proc or /sys files across CFEngine’s 3.x versions.
Tested with the following bundle, on 3.3.9, 3.4.5, 3.5.2 and upcoming 3.6 versions.
|
|
Note that execresult() trick will also work with CFEngine-3.6
|
|
If you plan to work with “large” strings variables (> 4KB), beware, you may hit an internal strings limit (bug report) and the file will not be fully read.
Strings limits will be discussed in a further blog post.