Rethinking fsinfo()
Linux has long supported the statfs() system call (usually seen from user space as statvfs()) as a way of obtaining information about mounted filesystems. As has happened so often, though, the designers of statfs() made a list of all the filesystem attributes they thought might be interesting and limited the call to those attributes; there is no way to extend it with new attributes. Filesystem designers, though, have stubbornly refused to stop designing new features in the decades since statfs() was set in stone, so there is now a lot of relevant information that cannot be obtained from statfs(). Such details include mount options, timestamp granularity, associated labels and UUIDs, and whether the filesystem supports features like extended attributes, access-control lists, and case-insensitive lookups.
As it happens, the kernel does make much of that information available now by way of the /proc/mounts virtual file. The problem with /proc/mounts, beyond the fact that some information is still missing, is that it is inefficient to access. Reading the contents of that file requires the kernel to query every mounted filesystem for the relevant information; on systems with a lot of mounted filesystems, that can get expensive. Systems running containerized workloads, in particular, can have vast numbers of mounts — thousands in some cases — so reading /proc/mounts can be painful indeed. For extra fun, the only way to know about newly mounted filesystems with current kernels is to poll /proc/mounts and look for new entries.
David Howells proposes to solve the polling problem with a new notification mechanism, but that mechanism, in turn, relies on fsinfo(), the 21st revision of which was posted on August 3. Howells requested that both notifications and fsinfo() be pulled during the 5.9 merge window, but that did not happen. Instead, the request resulted in yet another discussion about whether fsinfo() makes sense in its current form.
fsinfo()
The API for fsinfo() is comprehensive and extensible; there should never be a need for an fsinfo2() to add new attributes in the future. But it is also complex. On the surface, the interface looks like this:
int fsinfo(int dfd, const char *pathname, const struct fsinfo_params *params, size_t params_size, void *result_buffer, size_t result_buf_size);
Where the params structure is defined as:
struct fsinfo_params { __u64 resolve_flags; /* RESOLVE_* flags */ __u32 at_flags; /* AT_* flags */ __u32 flags; /* Flags controlling fsinfo() specifically */ __u32 request; /* ID of requested attribute */ __u32 Nth; /* Instance of it (some may have multiple) */ __u32 Mth; /* Subinstance of Nth instance */ };
There are four different ways to use dfd, pathname, and params->at_flags to specify which filesystem should be queried; see this patch changelog for details. The rest of the params structure describes the actual information request; the results end up in result_buffer.
There are numerous possibilities for params->request, including:
- FSINFO_ATTR_STATFS returns more-or-less the same information that would be obtained from statfs().
- FSINFO_ATTR_LIMITS returns various limits of the filesystem, including maximum file size, inode number, user ID number, hard links to a file, file-name length, etc. These are returned in an fsinfo_limits structure.
- FSINFO_ATTR_TIMESTAMP_INFO yields information about timestamps on files as a set of binary structures; this information includes the maximum values and granularity of timestamps expressed in a unique (to the kernel) mantissa-and-exponent format.
- FSINFO_ATTR_MOUNT_POINT generates a string showing where the filesystem is mounted.
- FSINFO_ATTR_MOUNT_CHILDREN gives an array of structures identifying the filesystems mounted below the filesystem being queried.
The full list of possible requests is rather longer than the above. Each returns data in a different format, usually a specific binary structure for the information requested. For some attributes, a query might return an arbitrary number of elements; in this case, the Nth and Mth fields in the fsinfo_params structure can be used to identify which should be returned. This patch contains a sample program that exercises a number of fsinfo() features to produce a listing showing the mount topography of the current system.
Complaints and alternatives
There are a couple of points of resistance to the fsinfo()
proposal, starting with whether it is needed at all. Linus Torvalds called
it "engineering for its own sake, rather than responding to
actual user concerns
" and wondered why it was needed now after Linux
has done without it for so many years. Torvalds tends to worry about
adding system calls that end up being used by nobody, so it is not unusual
for him to push for justification for the addition of new interfaces. It didn't take
long for potential users to make their needs clear; Steven Whitehouse described
it this way:
Karel Zak, maintainer of the util-linux package, described the needs of systems with thousands of mount points. Lennart Poettering provided a long list of attributes he would like to learn about filesystems and why they would be useful. The end result of all this discussion is that the need for some sort of filesystem-information system call is not really in doubt.
The complexity of fsinfo() still gives some developers something to worry about, though; to them, it looks like yet another multiplexer system call that tries to do a large number of things. But it's not entirely clear what an alternative would look like. There was a brief digression in which Torvalds suggested an API where attributes of a file could be opened as if that file were actually a directory; so, for example, opening (with a special flag) foo/max_file_size would allow the reading of the maximum file size supported by the filesystem hosting the plain file foo. This idea strongly resembles the controversial approach to metadata implemented by the reiser4 filesystem back in 2004, though nobody seemed to think it was politic to point that out in the discussion.
What was pointed out was that there are numerous practical difficulties associated with implementing this sort of mechanism. Even precisely defining its semantics turns out to be hard. So this idea was put aside; it will languish until somebody else surely suggests it again several years from now.
That leaves open the question of what a new API for obtaining filesystem
information should look like. Torvalds called
fsinfo() "confusing and over-engineered
" and asked: "Can we just make a simple extended statfs() and be done
with it, instead of this hugely complex thing that does five different
things with the same interface and makes it really odd as a result?
"
He further suggested
that a number of the binary structures used by fsinfo() could be
replaced by ASCII data. He pointed
out that a number of filesystem interfaces use ASCII for the more
complex attributes already and expressed hope that a kernel interface
exporting information in ASCII would make life easier for code that is
parsing that information out of /proc/mounts now.
So the end result of this discussion is likely to be an attempt to redesign
fsinfo() along those lines. There is a problem here, though: the
information needed is, like the systems it is representing, inherently
complex. By the time a statfs()-like API that can represent all
of this information and which can be extended in the future is designed,
chances are that this design will start to look a lot like what
fsinfo() is now. Replacing a few binary structures with ASCII
seems unlikely to change the picture significantly. The end result of this
whole exercise may be something that strongly resembles the current design.
Index entries for this article | |
---|---|
Kernel | System calls |
Posted Aug 21, 2020 17:55 UTC (Fri)
by zyga (subscriber, #81533)
[Link] (29 responses)
- A few bugs, over time, in a rather small and well tested code.
Perhaps it would be worth recognizing that the format is neither simple nor common. Space escaping rules differ from file to file. There is no libkernel, with reference parsers, everyone rolls their own.
Could the kernel, just maybe, adopt something (whatever) standard and simple. So that parsing the next "simple" text file, is done from a library function available in all the modern programming languages? Could we just use JSON or something of the kind?
If the counter argument is that parsing JSON is hard, I will only say that there are a few high-quality implementations, including the one that everyone reading this comment is using at the time. I don't think we need to invent a new format for fsinfo.
Posted Aug 21, 2020 18:09 UTC (Fri)
by Cyberax (✭ supporter ✭, #52523)
[Link] (7 responses)
Posted Aug 22, 2020 12:57 UTC (Sat)
by ibukanov (subscriber, #3942)
[Link]
Posted Aug 22, 2020 15:31 UTC (Sat)
by dezgeg (subscriber, #92243)
[Link]
Posted Aug 24, 2020 15:45 UTC (Mon)
by SEJeff (guest, #51588)
[Link] (4 responses)
Posted Aug 24, 2020 18:29 UTC (Mon)
by josh (subscriber, #17465)
[Link] (3 responses)
Posted Aug 24, 2020 19:07 UTC (Mon)
by Cyberax (✭ supporter ✭, #52523)
[Link] (2 responses)
Posted Aug 24, 2020 20:15 UTC (Mon)
by excors (subscriber, #95769)
[Link] (1 responses)
When decoding strings or byte arrays, it can read directly from the stream into the decoded message struct (if the field is configured with a fixed max size) or into a malloced buffer (if configured with variable size) or can pass a substream object representing the value into a callback function. Using the callback interface would let the kernel copy directly from the userspace input buffer into the appropriate internal kernel struct.
It looks like FlatBuffers can't do that, because verification (to avoid out-of-bounds reads etc) is a separate operation from reading fields. You'd have to memcpy the whole buffer from userspace to kernel memory before verifying and then copying strings again into kernel structs.
For kernel-to-userspace messages, you don't need to worry about race conditions and you probably don't need the verification step (since you have to trust the kernel anyway), so FlatBuffers could work better there.
For userspace-to-kernel in both protocols, if you really don't want to force the user to pack all their data into a single buffer, you could always encode userspace pointers as integers (like a "fixed64" in protobuf) to point to raw data or encoded messages at other addresses, and the kernel can traverse those pointers manually like it does today. You'd still get the benefit of automatic marshalling for the majority of structs and fields.
Posted Aug 27, 2020 16:50 UTC (Thu)
by esemwy (guest, #83963)
[Link]
http://h30266.www3.hpe.com/odl/axpos/opsys/vmsos84/5841/5...
Posted Aug 21, 2020 22:59 UTC (Fri)
by chris_se (subscriber, #99706)
[Link]
macOS: 1 direct system call, 5 LOC with error handling
Linux: have to parse /sys/devices/system/cpu/cpuX/topology/thread_siblings_list, X starting at 0, as well as checking /sys/devices/system/cpu/cpuX/online to see whether the core was actually active. I wrote my first implementation on an Intel system, where the siblings list of a 2-core system with hyperthreading would be {"0,2", "1,3", "0,2", "1,3"}, because Intel puts all of their hyperthreaded cores _after_ all of the physical cores. Tried that a year later on an AMD system with SMT, and because AMD groups their cores differently, the contents are {"0-1", "0-1", "2-3", "2-3"}. My code couldn't interpret that because it didn't consider the fact that the cores could be specified as ranges. So I had to go back and change it. Sure, if you read the documentation _really_ carefully there is an indication how to parse the format, so yeah, that was my fault. But in the end, my (now correct) implementation is > 20 LOC just for parsing the thread_siblings_list file, not including the logic to actually obtain the result that I actually want. A simple system call to obtain information about the CPUs in the current system as some kind of struct would have made my life a _lot_ easier in that case.
I have to do a lot of low-level OS programming on various OS on a daily basis -- and in general I like the Linux APIs a lot better than the APIs of most other operating systems -- but as soon as I have to parse or generate ASCII, I start to hate it with a vengeance. (Thankfully this is not the case for most things I have to do.) I consider the proposed fsinfo() system call interface to be VASTLY superior to any ASCII-based interface. The underlying user-space code would be a LOT less error-prone for me. For me as a user-space developer, I could see myself using the proposed fsinfo() call (with the structs) in the future to obtain some information about the filesystem, but unless I desperately needed a piece of information for some reason I would completely avoid this system call if it required me to parse an ASCII string. (Though, to be fair, I'm definitely not one of the people who is the main target of this functionality at all; for my software this currently falls more into the "nice to have" category.)
Posted Aug 22, 2020 15:03 UTC (Sat)
by vadim (subscriber, #35271)
[Link]
The worst thing about the text formats is that they are brittle. They're prone to failure when some unexpected character sneaks in, to ad-hoc implementations that are based on a programmer looking at 'cat /proc/whatever' and writing whatever comes to mind ("oh, so this is a list with one entry per line, with elements separated by spaces") and to being inflexible for expansion (can you add anything to /proc/partitions and not break a lot of stuff?)
The second worst is that different philosophies are being followed.
/proc/swaps might as well be the output of a command like 'df'
/proc/meminfo is half-written for human consumption, with amounts in kB and aligned columns, and half written for machine consumption with obscure labels like "Committed_AS".
/proc/mounts is very machine oriented.
Some files explicitly tell you the units (and it can be unclear whether another unit could ever be used). Some files have column names. Some like /proc/ioports have meaningful identation.
Many are uncomfortable for machine parsing. Eg, in /proc/cpuinfo you get "address sizes : 39 bits physical, 48 bits virtual". -- you have to parse that comma, and it's unclear whether a third thing could ever be there. Many files are lacking in unique identifiers, eg, my /proc/ioports has 3 levels of "0000-0000 : pnp 00:00", one under another.
I'm amazed that the computing industry is pretty old by now, we have plentiful RAM, storage and CPU power, and the issues of escaping data, representing arrays and trees, and allowing for adding extra info without breaking existing software are still with us, despite there being things like JSON that solve the vast majority of them.
Posted Aug 22, 2020 15:26 UTC (Sat)
by dezgeg (subscriber, #92243)
[Link] (12 responses)
Posted Aug 22, 2020 16:36 UTC (Sat)
by zyga (subscriber, #81533)
[Link]
Posted Aug 22, 2020 18:50 UTC (Sat)
by NYKevin (subscriber, #129325)
[Link]
Or, if you think letting people create files with ridiculous names was a bad idea to begin with, you could simply declare non-UTF-8 paths unsupported and spit out invalid JSON if the user tries to create one. Much userspace software already does something like that anyway (see for example Python 3's surrogateescape hack). But then a lot of parsers will work just fine the vast majority of the time, and break on an obscure condition that the average engineer may not even realize is possible. So that's probably not ideal...
Posted Aug 23, 2020 15:22 UTC (Sun)
by abo (subscriber, #77288)
[Link] (9 responses)
Surrogate escapes can be used to encode arbitrary bytes in JSON: The JSON looks like this:
Posted Aug 23, 2020 18:05 UTC (Sun)
by excors (subscriber, #95769)
[Link] (8 responses)
> the ABNF in this specification allows member names and string values to contain bit sequences that cannot encode Unicode characters; for example, "\uDEAD" (a single unpaired UTF-16 surrogate). Instances of this have been observed, for example, when a library truncates a UTF-16 string without checking whether the truncation split a surrogate pair. The behavior of software that receives JSON texts containing such values is unpredictable; for example, implementations might return different values for the length of a string value or even suffer fatal runtime exceptions.
so it seems a bad idea to rely on unpaired surrogates (like surrogateescape) if you're choosing JSON specifically for its interoperability.
Surely the simplest way to encode Linux's 8-bit paths in JSON is to map the bytes 0x00..0xFF onto U+0000..U+00FF and then proceed as normal. When decoding, treat any element >=U+0100 as a syntax error. That should be interoperable between all JSON implementations, and very easy to handle in both Unicode-aware and -unaware applications.
If the application wants to display the path to a user, do a potentially-lossy UTF-8 decode in the UI layer, which is about the best you can ever do with Linux paths regardless of how they're encoded for transport. For all non-display-related processing of paths (which I think is more common and more important than displaying paths), keep them in the simple lossless U+0000..U+00FF representation.
(Windows' 16-bit paths are more complicated, if you want to handle them pedantically correctly: they can contain unpaired surrogates so you can't simply interpret them as JSON-compatible Unicode strings. In that case it's probably safer to treat them as binary data and encode with base64.)
Posted Aug 23, 2020 19:08 UTC (Sun)
by NYKevin (subscriber, #129325)
[Link] (1 responses)
So, basically, pretend we have LC_ALL="[whatever].ISO-8859-1" at both ends, and then require userspace to clean up the mess if LC_ALL is actually set to a different value (which, on modern systems, is typically the case). The problem, of course, is that if you ever try to decode that JSON with a naive implementation, you will get mojibake since they will skip the "clean up the mess" step. So you still need non-naive implementations, which makes me wonder, why bother with JSON in the first place?
> If the application wants to display the path to a user, do a potentially-lossy UTF-8 decode in the UI layer, which is about the best you can ever do with Linux paths regardless of how they're encoded for transport.
Strictly, you should be consulting the locale information rather than just assuming UTF-8. UTF-8 is the most common encoding, but its use in pathnames is not required by any standard that I'm aware of. Now, you can't use something too weird such as UTF-16 (null bytes not allowed), but legacy 8-bit encodings are very much legal and valid on some older systems.
Posted Aug 23, 2020 22:44 UTC (Sun)
by shemminger (subscriber, #5739)
[Link]
Message based interfaces like netlink are more slightly more difficult to program but offer opportunity for expansion.
Posted Aug 24, 2020 4:51 UTC (Mon)
by mathstuf (subscriber, #69389)
[Link] (5 responses)
In what endianness do you treat the incoming 16bit data? Big? Little? Native? Native is easy, but it means you need to know what the host system is before archiving the raw data. Little is easy, but then can be confusing in the raw data viewers (which could render backwards). BOM is ok? But you could also start a filename with a BOM and…blah.
Posted Aug 24, 2020 12:30 UTC (Mon)
by excors (subscriber, #95769)
[Link] (4 responses)
Most code that processes the path should treat it as an opaque blob or decode it to wchar_t*, and wouldn't need to care about Unicode or surrogates etc.
When you want to display the path to a user, you'd need to do a lossy UTF-16LE decode to get a real Unicode string to pass into your UI system. (Lossy because the path might contain unpaired surrogates which you can't decode safely). (If you're using the Win32 UI APIs, that decoding will probably happen implicitly inside the API implementation; otherwise you might need to do it in the application). The important thing is to avoid trying to decode into a real Unicode string in any context where the lossiness will cause worse than a cosmetic glitch. (So you shouldn't try to store Windows paths directly in JSON, because interoperable JSON requires real Unicode strings, hence the base64 encoding.)
(Linux is the same except 8-bit instead of 16-bit, and probably UTF-8 (or the user's current locale, as NYKevin mentioned, though of course they might have files created under a different locale and there's no way to be sure what they were meant to be) instead of almost always UTF-16LE, and you can encode arbitrary 8-bit strings as JSON strings much more easily than encoding arbitrary 16-bit strings (where you need base64 etc). On both platforms it's a mistake to think that a path is simply an encoded Unicode string, and that you can decode/encode at the edge and do all your internal processing with Unicode.)
Posted Aug 25, 2020 19:02 UTC (Tue)
by mathstuf (subscriber, #69389)
[Link] (3 responses)
I agree that just stuffing paths into binary storage is the best solution. However, usually paths need displayed or the storage you're using has a human caring about it at some point in its lifetime. Especially if you're using a container format like JSON. It's nice and all, but a way to store arbitrary binary data without having to figure out how to encode it so that it is Unicode safe would have been much appreciated. (No, BSON don't fix this; they just change the window dressing from `{:"",}` into type-and-length-prefixed fields or type-and-NUL-terminated sequences). CBOR has binary data, but then library support is more widely lacking.
FWIW, I've spent a lot of time thinking about how to stuff paths into JSON: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p...
Posted Aug 26, 2020 19:43 UTC (Wed)
by unilynx (guest, #114305)
[Link] (2 responses)
Distributions might slowly make that option the default for new systems, sysadmins can opt-in faster themselves, unless they really have to deal with those few applications (which will hopefully disappear or become obsolete fast) that really, really want to create weird filenames.
Posted Aug 27, 2020 4:48 UTC (Thu)
by neilbrown (subscriber, #359)
[Link]
Posted Aug 29, 2020 11:29 UTC (Sat)
by flussence (guest, #85566)
[Link]
Posted Aug 26, 2020 14:09 UTC (Wed)
by flussence (guest, #85566)
[Link] (5 responses)
Anything that requires more than about half a kb of bash-builtins parsing code to deal with is too complicated, IMHO.
Posted Aug 26, 2020 19:36 UTC (Wed)
by unilynx (guest, #114305)
[Link]
Posted Aug 27, 2020 12:08 UTC (Thu)
by zyga (subscriber, #81533)
[Link] (3 responses)
As for parsing, please show me a correct /proc/self/mountinfo parser in shell. I'll wait. As another poster commented, jq handles that for shell scripts in a single-line correct and simple manner. The moment we step out of custom formats the kernel forces on us, the moment we start to have really rich set of tools for processing data.
And it doesn't have to be JSON. It should just not be ad-hoc, per-file convention with custom, brittle parser.
Posted Aug 27, 2020 13:10 UTC (Thu)
by kpfleming (subscriber, #23250)
[Link] (1 responses)
Posted Aug 28, 2020 13:13 UTC (Fri)
by mathstuf (subscriber, #69389)
[Link]
Don't forget that some parsers have baked in the extension proposals(!) in, so you have to pay attention to things like accidentally generating merge keys ("<<").
Posted Aug 28, 2020 12:13 UTC (Fri)
by mvdwege (guest, #113583)
[Link]
Best solution: discard the dogmatic adherence to 'it must be text'. If the primary consumer of the information is not a sysadmin at the console, use binary data and cut out the redundant parsing step. Yes, 'everything is an ASCII stream' makes things easily readable for humans, and it is great if you have text tools to write ad-hoc parsers for it. The problem is that you keep writing ad-hoc parsers. A little more pragmatism on this old UNIX dogma would be appreciated. I'm speaking as a sysadmin/developer who ran into this when I wanted to verify if my local MTA had successfully sent (aka received a 250 reply) an email. The only way to do that was to parse fscking syslog. In 2018. When tools like D-Bus notification already had existed for over a decade.
Rethinking fsinfo()
- Edge case that affects extremely common and battle-tested implementations (mount/systemd)
- Had to venture into the belly of the kernel to understand the precise implementation, and escaping rules
Rethinking fsinfo()
Rethinking fsinfo()
Rethinking fsinfo()
Rethinking fsinfo()
Rethinking fsinfo()
Rethinking fsinfo()
Rethinking fsinfo()
Rethinking fsinfo()
Rethinking fsinfo()
Windows: 2 low-level API calls (that probably translate directly into system calls? not sure) + some struct processing in a loop, 12 LOC with error handling
Rethinking fsinfo()
Rethinking fsinfo()
Rethinking fsinfo()
Rethinking fsinfo()
Rethinking fsinfo()
>>> b = bytes(range(256))
>>> u = b.decode("UTF-8", errors="surrogateescape")
>>> import json
>>> j = json.dumps(u)
>>> uin = json.loads(j)
>>> bin = uin.encode("UTF-8", errors="surrogateescape")
>>> [n for n in bin]
[0, 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]
>>> bin == b
True
"\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\b\t\n\u000b\f\r\u000e\u000f\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001a\u001b\u001c\u001d\u001e\u001f !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u007f\udc80\udc81\udc82\udc83\udc84\udc85\udc86\udc87\udc88\udc89\udc8a\udc8b\udc8c\udc8d\udc8e\udc8f\udc90\udc91\udc92\udc93\udc94\udc95\udc96\udc97\udc98\udc99\udc9a\udc9b\udc9c\udc9d\udc9e\udc9f\udca0\udca1\udca2\udca3\udca4\udca5\udca6\udca7\udca8\udca9\udcaa\udcab\udcac\udcad\udcae\udcaf\udcb0\udcb1\udcb2\udcb3\udcb4\udcb5\udcb6\udcb7\udcb8\udcb9\udcba\udcbb\udcbc\udcbd\udcbe\udcbf\udcc0\udcc1\udcc2\udcc3\udcc4\udcc5\udcc6\udcc7\udcc8\udcc9\udcca\udccb\udccc\udccd\udcce\udccf\udcd0\udcd1\udcd2\udcd3\udcd4\udcd5\udcd6\udcd7\udcd8\udcd9\udcda\udcdb\udcdc\udcdd\udcde\udcdf\udce0\udce1\udce2\udce3\udce4\udce5\udce6\udce7\udce8\udce9\udcea\udceb\udcec\udced\udcee\udcef\udcf0\udcf1\udcf2\udcf3\udcf4\udcf5\udcf6\udcf7\udcf8\udcf9\udcfa\udcfb\udcfc\udcfd\udcfe\udcff"
Rethinking fsinfo()
Rethinking fsinfo()
Rethinking fsinfo()
Rethinking fsinfo()
Rethinking fsinfo()
Rethinking fsinfo()
Rethinking fsinfo()
Rethinking fsinfo()
Excluding end-of-line characters is probably justifiable too. (or any control char ... I don't think we need TAB or DEL).
Anything else is parochial.
When I'm choosing a name to save my document from my GUI, why should I care about your inability to write safe shell scripts, or even have any understanding that "the shell" exists.
It is bad enough that I cannot put a '/' in my file names, why would you prevent me using '$'??
Rethinking fsinfo()
Rethinking fsinfo()
Rethinking fsinfo()
Rethinking fsinfo()
Rethinking fsinfo()
Rethinking fsinfo()
Rethinking fsinfo()