Wednesday, April 29, 2009

Today we're going to take a look at a relatively simple concept that's relatively simple to get confused; especially if you're relatively new to Unix or Linux. Your experience may vary. It's all relative (who didn't see that one coming from around the corner and down the block? ;)

The concept we'll be looking at is the dead-zone on Linux or Unix. Most people refer to it as /dev/null or the "bit bucket." And, of course, this prompts the question of "why is that so interesting?" I suppose, in and of itself, it's not. Don't get too excited ;)

Where it gets interesting is when we introduce the /dev/zero file (technically, like /dev/null, a pseudo-file). Most folks, at least initially, consider /dev/null and /dev/zero to be virtually equivalent. And, even though the names make that conclusion seem self-evident, the truth is actually quite the opposite. The two files differ quite drastically, although not in every aspect.

1. Writing to /dev/null and /dev/zero: You can write to both /dev/null and /dev/zero in exactly the same way. For instance, if you want to dump off output from a command, either one will do. That is to say that:

host # echo hi >/dev/null

and

host # echo hi >/dev/zero

will both send your output to "never never land." Executing either of the above commands will satisfy your requirements if you just want to "dump" output to "nowhere." They should both be character (or raw) devices, have identical major device numbers and only differ at the minor device number level. These numbers will differ from OS to OS, but the basic definitions above should hold relatively true.

2. Reading from /dev/null and /dev/zero: This is where the difference between the two files becomes apparent. The most significant difference is exposed in the "reading" since this action highlights the major way in which the two differ.

/dev/null is, essentially, a black hole. Writes to it (as noted above), basically go down the drain. They go nowhere, stay there and you can't get them back. When you "read" from /dev/null, the same rule holds true. /dev/null is virtually "nothing," and all reads from it produce no output whatsoever. For instance, this output from Solaris' truss (you can get the same from Linux's "strace" and similar utilities) shows what happens when /dev/null is read from (e.g. "cat /dev/null") - below, what you'd see at the command line, followed by a snippet of truss output from the almost-immediate end of the command's execution:

literally, the device file is opened, read from (which produces nothing) and is closed, since an EOF is sent immediately after opening.

/dev/zero, on the other hand is not the black hole that it appears to be when "writing to it." When you "read" from /dev/zero, you get a much different result than when you read from /dev/null. This is most specifically because /dev/zero returns zero's until the cows come home (or you stop reading from it ;) and "does not" return an EOF like /dev/null. It actually returns the ASCII null character (0x00) ad infinitum (or "ad naseum" depending upon your stomach for this sort of thing ;)

Below, we'll take a look at what you'd see at the command line, followed by a snippet of truss output from this command's execution:

host # cat /dev/zero^Chost #

<-- note that we had to type ctl-C (send an interrupt signal) to the return from "cat /dev/zero" to get it to return to the shell prompt

And that, pretty basically, is the difference between the two files. It should be noted, however, that there are two things you can do with /dev/zero that you can't do with /dev/null (one practical and one just downright malicious if you know what you're doing ;) and one very useful thing you can use /dev/null for on a regular basis.

1./dev/null can be easily used to create a zero byte file. This comes in handy when you want to create a placeholder file, etc:

of course, the same results can be achieved (in most shells) in a more efficient manner. The underlying mechanism is the same, but the typing's a bit speedier (and you're actually redirecting STDOUT, but we'll leave that distinction to the academics ;)

2. With /dev/zero, the good thing you can do is to create a zeroed out file on which you can, later, create a filesystem. Using a simple command like "dd" you can set /dev/zero as your input file ("if") and your block device (or regular file) as the output file ("of") and then make a filesystem on it. This is a very quick way to setup additional file system space:

host # dd if=/dev/zero of=mynewfile <-- the block size - "bs" - and count - "count" - arguments will differ depending upon your fs and what your particular needs arehost # mkfs ARGS mynewfile <-- Again, this part (creating the new filesystem) can have many variables. The most relevant command line component, in this instance, would be the name of the zeroed out file we create with "dd."

3. With /dev/zero, the "bad" thing you can do (or can do by mistake, if you're not careful or aren't aware of /dev/zero's behaviour) is related to the "good" thing. Just like you can create a zeroed out file system, you can also completely fill a partition with ASCII 0x00 characters and, possibly, cause your system to crash or go into a frenzy as it loses disk resources, like so (of course, you should not have the permission to do this as a regular user):

so, in about 100 seconds, you can cripple /var (4 GB of space on this host). That makes it difficult to do simple things, like copying over a relatively small file onto the partition you just filled up (not to mention the other adverse effects filling up /var can have on most Linux and Unix OS's):