ref: 2b99422480d596ebc26921c87c6bb81a07949f3e
dir: /ch3.ms/
.so tmacs .BC 3 Files .BS 2 "Input/Output .ix "I/O .LP It is important to know how to use files. In Plan 9, this is even more important. The abstractions provided by Plan 9 can be used through a file interface. If you know how to use the file interface, you also know how to use the interface for most of the abstractions that Plan 9 provides. .PP You already know a lot about files. In the past, we have been using .CW print to write messages. And, before this course, you used the library of your programming language to open, read, write, and close files. We are going to learn now how to do the same, but using the interface provided by the operating system. This is what your programming language library uses to do its job regarding input/output. .PP Consider .CW print , it is a convenience routine to print formatted messages. It writes to a file, by calling .ix "formatted output .CW write . .ix [write] Look at this program: .so progs/write.c.ms .ix [write.c] .LP This is what it does. It does the same that .CW print would do given the same string. .P1 ; 8.write hello .P2 .LP The function .CW write writes bytes into a file. Isn't it a surprise? To find out the declaration for this function, we can use .CW sig ⁱ. .ix [sig] .FS ⁱ Remember that this program looks at the source of the manual pages, in section 2, to find a function with the given name in any SYNOPSIS section of any manual page. Very convenient to get a quick reminder of which arguments receives a system function, and what does it return. .FE .P1 ; sig write long write(int fd, void *buf, long nbytes) .P2 .LP The bytes written to the file come from .CW buf , which was .CW msg in our example program. The number of bytes to write is specified by the third parameter, .CW nbytes , which was the length of the string in .CW msg . And the file were to write was specified by the first parameter, which was just .CW 1 for us. .PP Files have names, as we learned. We can use a full path, absolute or relative, to name a file. Files being used by a particular process have “names” as well. The names are called \fBfile descriptors\fP .ix "file descriptor .ix "file descriptor table and are small integers. You know from your programming courses that to read/write a file you must open it. Once open, you may read and write it until the file is closed. To identify an open file you use a small integer, its file descriptor. This integer is used by the operating system as an index in a table of open files for your process, to know which file to use for reading or writing. See figure [[!standard file descriptors!]]. .LS .PS right reset boxht=.2 boxwid=1 circle rad .4 "Process" spline -> right 1 then down "File descriptor" "table" D: [ down [ right box invis "0" ; F: box ] D0: last [].F [ right box invis "1" ; F: box ] D1: last [].F [ right box invis "2" ; F: box ] D2: last [].F [ right box invis "3" ; box invis "..."] [ right box invis "n" ; F: box ] ] spline -> from D.D0 right 1 then up then right ; box "Standard" "input" ht boxht*2 arrow from D.D1 right 1 then right ; box "Standard" "output" ht boxht*2 spline -> from D.D2 right 1 then down then right ; box "Standard" "error" ht boxht*2 reset .PE .LE F File descriptors point to files used for standard input, standard output, and standard error. .PP All processes have three files open right from the start, by convention, even if they do not open a single file. These open files have the file descriptors 0, 1, and 2. As you could see, file descriptor 1 is used for data output and is called .B "standard output" , File descriptor 0 is used for data input and is called .B "standard input" , File descriptor 2 is used for diagnostic (messages) output and is called .B "standard error" . .PP To read an open file, you may call .CW read . .ix [read] Here is the function declaration: .P1 ; sig read long read(int fd, void *buf, long nbytes) .P2 .LP It reads bytes from file descriptor .CW fd a maximum of .CW nbytes bytes and places the bytes read at the address pointed to by .CW buf . The number of bytes read is the value returned. Read does not guarantee that we would get as many bytes as we want, it reads what it can and lets us know. This program reads some bytes from standard input and later writes them to standard output. .so progs/read.c.ms .ix [read.c] .LP And here is how it works: .P1 ; 8.read from stdin, to stdout! \fI If you type this \fP from stdin, to stdout! \fI the program writes this\fP .P2 .LP When you run the program it calls .CW read , which awaits until there is something to read. When you type a line and press return, the window gives the characters you typed to the program. They are stored by .CW read at .CW buffer , and the number of bytes that it could read is returned and stored at .CW nr . Later, the program uses .CW write to write so many bytes into standard output, echoing what we wrote. .PP Many of the Plan 9 programs that accept file names as arguments work with their standard input when given no arguments. Try running .CW cat . .P1 ; cat .I "...it waits until you type something .P2 .LP It reads what you type and writes a copy to its standard output .ix [cat] .P1 ; cat from stdin, to stdout! \fI If you type this \fP from stdin, to stdout! \fI cat writes this\fP and again and again \fBcontrol-d\fP ; .P2 .LP until reaching the end of the file. The end of file for a keyboard? There is no such thing, but you can pretend there is. When you type a .I control-d by pressing the .CW d key while holding down .I Control , .ix "control-d" the program reading from the terminal gets an end of file. .PP Which file is standard input? And output? Most of the times, standard input, standard output, and standard error go to .CW /dev/cons . .ix console .ix "standard input .ix "standard output .ix "standard error This file represents the .I console for your program. Like many other files in Plan 9, this is not a real (disk) file. It is the interface to use the device that is known as the console, which corresponds to your terminal. When you read this file, you obtain the text you type in the keyboard. When you write this file, the text is printed in the screen. .PP When used within the window system, .CW /dev/cons .ix [/dev/cons] .ix "window corresponds to a fake console invented just for your window. The window system takes the real console for itself, and provides each window with a virtual console, that can be accessed via the file .CW /dev/cons within each window. We can rewrite the previous program, but opening this file ourselves. .so progs/read2.c.ms .ix [read.c] .LP This program behaves exactly like the previous one. You are invited to try. To open a file, you must call .CW open .ix [open] .ix "file name .ix path .ix "open mode specifying the file name (or its path) and what do you want to do with the open file. The integer constant .CW ORDWR . ix "[ORDWR] open~mode means to open the file for both reading and writing. This function returns a new file descriptor to let you call .CW read .ix [read] or .CW write .ix [write] for the newly open file. The descriptor is a small integer that we store into .CW fd , to use it later with .CW read and .CW write . Figure [[!descriptors opening!]] shows the file descriptors for the process running this program after the call to .CW open . It assumes that the file descriptor for the new open file was 3. .LS .PS right boxwid=1 boxht=.2 circlerad=.5 circle "Process" spline -> right 1 then down "File descriptor" "table" D: [ down [ right box invis "0" ; F: box ] D0: last [].F [ right box invis "1" ; F: box ] D1: last [].F [ right box invis "2" ; F: box ] D2: last [].F [ right box invis "3" ; F: box ] DN: last [].F [ right box invis ; box invis "..."] [ right box invis "n" ; F: box ] ] move right 2 ; C: box "\f(CW/dev/cons\fP" CC: circle invis at C spline -> from D.D1 right 1 then to CC chop spline -> from D.D0 right 1 then to CC chop spline -> from D.D2 right 1 then to CC chop spline -> from D.DN right 1 then to CC chop reset .PE .LE F File descriptors for the program after opening \f(CW/dev/cons\fP. .PP When the file is no longer useful for the program, it can be closed. This is achieved by calling .CW close , .ix [close] which releases the file descriptor. In our program, we could have open .CW /dev/cons several times, one for reading and one for writing .P1 infd = open("/dev/cons", OREAD); outfd = open("/dev/cons", OWRITE); .P2 .LP using the integer constants .CW OREAD and .CW OWRITE , .ix "[OREAD] open~mode .ix "[OWRITE] open~mode that specify that the file is to be open only for reading or writing. But it seemed better to open the file just once. .PP The file interface provided for each process in Plan 9 has a file that provides the list of open file descriptors for the process. For example, to know which file descriptors are open in the shell we are using we can do this. .ix "process [fd] file .ix "file descriptor .ix [$pid] .P1 .ps -2 ; cat /proc/$pid/fd /usr/nemo 0 r M 94 (0000000000000001 0 00) 8192 18 /dev/cons 1 w M 94 (0000000000000001 0 00) 8192 2 /dev/cons 2 w M 94 (0000000000000001 0 00) 8192 2 /dev/cons 3 r c 0 (0000000000000002 0 00) 0 0 /dev/cons 4 w c 0 (0000000000000002 0 00) 0 0 /dev/cons 5 w c 0 (0000000000000002 0 00) 0 0 /dev/cons 6 rw | 0 (0000000000000241 0 00) 65536 38 #|/data 7 rw | 0 (0000000000000242 0 00) 65536 81320369 #|/data1 8 rw | 0 (0000000000000281 0 00) 65536 0 #|/data 9 rw | 0 (0000000000000282 0 00) 65536 0 #|/data1 10 r M 10 (00003b49000035b0 13745 00) 8168 512 /rc/lib/rcmain 11 r M 94 (0000000000000001 0 00) 8192 18 /dev/cons ; .ps +2 .P2 .LP The first line reports the current working directory for the process. .ix "current directory Each other line reports a file descriptor open by the process. Its number is listed on the left. As you could see, our shell has descriptors 0, 1, and 2 open (among others). All these descriptors refer to the file .CW /dev/cons , whose name is listed on the right for each descriptor. Another interesting information is that the descriptor 0 is open just for reading (\f(CWOREAD\fP), because there is an .ix "[OREAD] open~mode .CW r listed right after the descriptor number. And as you can see, both standard output and error are open just for writing (\f(CWOWRITE\fP), because there is a .CW w .ix "[OWRITE] open~mode printed after the descriptor number. The .CW /proc/$pid/fd file is a useful information to track bugs related to file descriptor problems. Which descriptors has the typical process open? If you are skeptic, this program might help. .so progs/sleep.c.ms .ix [sleep.c] .ix [sleep] .LP It prints its PID, and hangs around for one hour. After running this program .P1 ; 8.sleep process pid is 1413. have fun. .I "...and it hangs around for one hour." .P2 .LP we can use another window to inspect the file descriptors for the process. .P1 .ps -2 ; cat /proc/1413/fd /usr/nemo/9intro 0 r M 94 (0000000000000001 0 00) 8192 87 /dev/cons 1 w M 94 (0000000000000001 0 00) 8192 936 /dev/cons 2 w M 94 (0000000000000001 0 00) 8192 936 /dev/cons 3 r c 0 (0000000000000002 0 00) 0 0 /dev/cons 4 w c 0 (0000000000000002 0 00) 0 0 /dev/cons 5 w c 0 (0000000000000002 0 00) 0 0 /dev/cons 6 rw | 0 (0000000000000241 0 00) 65536 38 #|/data 7 rw | 0 (0000000000000242 0 00) 65536 85044698 #|/data1 8 rw | 0 (0000000000000281 0 00) 65536 0 #|/data 9 rw | 0 (0000000000000282 0 00) 65536 0 #|/data1 .ps +2 .P2 .LP Your process has descriptors 0, 1, and 2 open, as they should be. However, it seems that there are many other ones open as well. That is why you cannot assume that the first file you open in your program is going to obtain the file descriptor number 3. It might already be open. You better be aware. .PP There is one legitimate question still pending. After we open a file, how does .CW read know from where in the file it should read? The function knows how many bytes we would like to read at most. But its parameters tell nothing about the .I offset in the file where to start reading. And the same question applies to .CW write as well. .PP The answer comes from .CW open , Each time you open a file, the system keeps track of a .B "file offset" for that open file, to know the offset in the file where to start working at the next .CW read or .CW write . Initially, this file offset is zero. When you write, the offset is advanced the number of bytes you write. When you read, the offset is also advanced the number of bytes you read. Therefore, a series of writes would store bytes .I sequentially , .ix "sequential access one write at a time, each one right after the previous one. And the same happens while reading. .PP The offset for a file descriptor can be changed using the .CW seek .ix [seek] system call. Its second parameter can be 0, 1, or 2 to let you change the offset to an absolute position, to a relative one counting from the old value, and to a relative one counting from the size of the file. For example, this sets the offset in .CW fd to be 10: .P1 seek(fd, 10, 0); .P2 .LP This advances the offset 5 bytes ahead: .P1 seek(fd, 5, 1); .P2 .LP And this moves the offset to the end of the file: .P1 seek(fd, 0, 2); .P2 .LP We did not use the return value from .CW seek , but it is useful to know that it returns the new offset for the file descriptor. .ix offset .BS 2 "Write games .LP This program is a variant of the first one in this chapter, but writes the salutation to a regular file, and not to the console .so progs/fhello.c.ms .ix [fhello.c] .LP We can create a file to play with by copying .CW /NOTICE .ix [/NOTICE] to .CW afile , and then run this program to see what happens. .P1 ; cp /NOTICE afile ; 8.fhello .P2 .LP This is what was at .CW /NOTICE : .P1 ; cat /NOTICE Copyright © 2002 Lucent Technologies Inc. All Rights Reserved ; .P2 .LP and this is what is in .CW afile : .P1 ; cat afile hello ght © 2002 Lucent Technologies Inc. All Rights Reserved .P2 .LP At first sight, it seems that something weird happen. The file has one .ix "new line extra line. However, part of the original text has been lost. These two things seem contradictory but they are not. Using .CW xd may reveal what happen: .P1 ; xd -c afile 0000000 h e l l o \en g h t c2 a9 2 0 0 0000010 2 L u c e n t T e c h n o l 0000020 o g i e s I n c . \en A l l R 0000030 i g h t s R e s e r v e d \en 000003f ; xd -c /NOTICE 0000000 C o p y r i g h t c2 a9 2 0 0 0000010 2 L u c e n t T e c h n o l 0000020 o g i e s I n c . \en A l l R 0000030 i g h t s R e s e r v e d \en 000003f .P2 .LP Our program opened .CW afile , which was a copy of .CW /NOTICE , and then it wrote “\f(CWhello\en\fP”. After the call to .CW open , .ix [open] the file offset for the new open file was set zero. This means that .CW write .ix [write] wrote 6 bytes into .CW afile starting at offset 0. The first six bytes in the file, which contained “\f(CWCopyri\fP”, have been overwritten by our program. But .CW write did just what it was expected to do. Write 6 bytes into the file starting at the file offset (0). Nothing more, nothing less. It does not truncate the file (it shouldn't!). It does not .I insert . It just writes. .PP If we change the program above, adding a second call to .CW write , so that it executes this code .P1 write(fd, "hello\en"); write(fd, "there\en"); .P2 .LP we can see what is inside .CW afile after running the program. .P1 ; cat afile hello there 2002 Lucent Technologies Inc. All Rights Reserved .P2 .P1 ; xd -c afile 0000000 h e l l o \en t h e r e \en 2 0 0 0000010 2 L u c e n t T e c h n o l 0000020 o g i e s I n c . \en A l l R 0000030 i g h t s R e s e r v e d \en 000003f .P2 .ix [xd] .LP After the first call to .CW write , the file offset was 6. Therefore, the second write happen .ix "file offset starting at offset 6 in the file. And it wrote six more bytes. Once more, it did just its job, write bytes. The file length is the same. The number of lines changed because the number of newline characters in the file changed. The console advances one line each time it encounters a newline, but it is just a single byte. .PP Figure [[!file offset!]] shows the elements involved in writing this file, after the first call to .CW write , and before the second call. The file descriptor, which we assume was 3, points to a data structure containing information about the open file. This data structure keeps the file offset, to be used for the following .CW read or .CW write operation, and record what the file was open for, e.g., .CW OWRITE . .ix "[OWRITE] open~mode Plan 9 calls this data structure a .CW Chan (Channel), .ix [Chan] and there is one per file in use in the system. Besides the offset and the open mode, it contains all the information needed to let the kernel reach the file server and perform operations on the file. Indeed, a Chan is just something used by Plan 9 to speak to a server regarding a file. This may require doing remote .ix 9P .ix "file server .ix "channel procedure calls across the network, but that is up to your kernel, and you can forget it. .LS .PS .CW down boxwid=.2 boxht=.2 circle rad .4 "\fRProcess\fP" line -> down " \fRFile descriptor\fP" ljust " \fRtable\fP" ljust D: [ down [ right box invis "0" ; F: box wid 1 ] D0: last [].F [ right box invis "1" ; F: box wid 1 ] D1: last [].F [ right box invis "2" ; F: box wid 1 ] D2: last [].F [ right box invis "3" ; F: box wid 1 ] D3: last [].F [ right box invis ; box invis wid 1 "..."] [ right box invis "n" ; F: box wid 1 ] ] arrow -> from D.D3 right 1 C: box wid 1.5 ht 3*boxht down X: [ down O: box invis "offset: 6" ljust box invis "mode: OWRITE " ljust F: box invis "file: " ljust ] with .nw at C.nw line invis from X.F.e right .5 A: [ spline -> right then down then left then down ] with .nw at last line.e H: [ right .R box "h" box "e" box "l" box "l" box "o" box "\en" S: box wid .6 "..." .R ] with .nw at A.sw box invis wid .75 "afile" line invis from X.O.e right 1; spline -> right then right then to H.S.nw dotted box invis "\fRChan\fP" with .sw at C.nw .R reset .PE .LE F The file offset for next operations is kept separate from the file descriptor. .PP We can use .CW seek .ix [seek] to write at a particular offset in the file. For example, the following code writes starting at offset 10 into our original version of .CW afile . .P1 int fd; fd = open("afile", OWRITE); seek(fd, 10, 0); write(fd, "hello\en", 6); close(fd); .P2 .LP The contents of .CW afile have six bytes changed, as it could be expected. .ix [xd] .P1 ; xd -c afile 0000000 C o p y r i g h t h e l l o \en 0000010 2 L u c e n t T e c h n o l 0000020 o g i e s I n c . \en A l l R 0000030 i g h t s R e s e r v e d \en 000003f .P2 .LP How can we write new contents into .CW afile , getting rid of anything that could be in the file before we write? Simply by specifying to .CW open that we want to .B truncate the file besides opening it. To do so, we can do a bit-or of the desired open mode and .CW OTRUNC , .ix "[OTRUNC] open~mode a flag that requests file truncation. This program does so, and writes a new string into our file. .so progs/thello.c.ms .ix [thello.c] .LP After running this program, .CW afile contains just the 6 bytes we wrote: .P1 ; 8.thello ; cat afile hello ; .P2 .LP The call to .CW open , caused the file .CW afile to be truncated. If was empty, open for writing on it, and the offset for the next file operation was zero. Then, .CW write wrote 6 bytes, at offset zero. At last, we closed the file. .PP What would the following program do to our new version of .CW afile ? .so progs/seekhello.c.ms .ix [seek] .ix [seekhello.c] .LP All system calls are very obedient. They do just what they are asked to do. The call to .CW seek changes the file offset to 32. Therefore, .CW write must write six bytes at offset 32. This is the output for .CW ls and .CW xd on the new file after running this program: .P1 ; 8.seekhello ; ls -l afile --r--r--r-- M 19 nemo nemo 38 Jul 9 18:14 afile ; xd -c afile 0000000 h e l l o \en 00 00 00 00 00 00 00 00 00 00 0000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0000020 t h e r e \en 0000026 .P2 .LP The size is 38 bytes. That is the offset before .CW write , .ix [write] 32, plus the six bytes we wrote. In the contents you see how all the bytes that we did not write were set to zero by Plan 9. And we know a new thing: The size of a file corresponds to the highest file offset ever written on it. .PP A variant of this program can be used to create files of a given size. To create a 1 Gigabyte file you do not need to write that many bytes. A single write suffices with just one byte. Of course, that write must be performed at an offset of 1 Gigabyte (minus 1 byte). .PP Creating large files in this way is different from writing all the zeroes yourself. First, it takes less time to create the file, because you make just a couple of system calls. Second, it can be that your new file does .I not consume all its space in the disk until you really use it. Because Plan 9 knows .ix "disk space the new size of the file, and it knows you never did write most of it, it can just record the new size and allocate disk space only for the things you really wrote. Reading other parts of the file yield just zeroes. There is no need to store all those zero bytes in the disk. .PP This kind of file (i.e., one created using .CW seek and .CW write ), is called a .B "file with holes". The name comes from considering that the file has “holes” on it, where you did never write anything. Of course, the holes are not really stored in a disk. It is funny to be able to store files for a total amount of bytes that exceeds the disk capacity, but now you know that this can happen. .PP To append some data to a file, we can use .CW seek to set the offset at the end of the file before calling write, like in .P1 fd = open("afile", OWRITE); seek(fd, 0, 2); // move to the end write(fd, bytes, nbytes); .P2 .LP For some files, like log files used to append diagnostic messages, or mail folders, used to append mail messages, writing should always happen at the end of the file. In this case, it is more appropriate to use an .B "append only" permission bit supported by the Plan 9 file server: .ix [chmod] .ix "[chmod] flag~[+a] .P1 .ps -1 ; chmod +a /sys/log/diagnostics ; ls -l /sys/log/diagnostics a-rw-r--r-- M 19 nemo nemo 0 Jul 10 01:11 /sys/log/diagnostics .ps +1 .P2 .LP This guarantees that any write will happen at the end of existing data, no matter what the offset is. Doing a .CW seek in all programs using this file might not suffice. If there are multiple machines writing to this file, each machine would keep its own offset for the file. Therefore, there is some risk of overwriting some data in the file. However, using the .CW +a permission bit fixes this problem once and for all. .BS 2 "Read games .LP To read a file it does not suffice to call .CW read once. This point may be missed when using this function for the first few times. The problem is that .CW read does no guarantee that all the bytes in the file could be read in the first call. For example, early in this chapter we did read from the console. Before typing a line, there is no way for .CW read to obtain its characters. The result in that when reading from the console our program did read one line at a time. If we change the program to read from a file on a disk, it will probably read as much as it fits in the buffer we supply for reading. .PP Usually, we are supposed to call .CW read until there is nothing more to read. That happens when the number of bytes read is zero. For example, this program reads the whole file .CW /NOTICE , and prints what it can read each time. The program is unrealistic, because usually you should employ a much larger read buffer. Memory is cheap these days. .so progs/nread.c.ms .ix [nread.c] .LP Although we did not check out error conditions in most of the programs in this chapter. This program does so. When .CW open fails , it returns .CW -1 . The program issues a diagnostic and terminates if that is the case. Also, after calling .CW read , it does not just check for .CW "nr == 0" , which means that there is nothing more to read. Instead, it checks for .CW "nr <= 0" , because .CW read returns .CW -1 when it fails. The call to .CW write might fail as well. It returns the number of bytes that could be written, and it is considered an error when this number differs from the one you specified. .BS 2 "Creating and removing files .LP The .CW create .ix [create] .ix "file creation system call creates one file. It is very similar to .CW open . .ix [open] After creating the file, it returns an open file descriptor for the new file, using the specified mode. It accepts the same parameters used for open, plus an extra one used to specify permissions for the new file encoded as a single integer. .PP This program creates its own version of .CW afile , without placing on us the burden of creating it. It does not check errors, because it is just an example. .so progs/create.c.ms .ix [create.c] .LP To test it, we remove our previous version for .CW afile , run this program, and ask .CW ls and .CW cat to print information about the file and its contents. .P1 ; rm afile ; ls afile ls: afile: 'afile' file does not exist ; 8.create ; ls -l afile --rw-r--r-- M 19 nemo nemo 11 Jul 9 18:39 afile ; cat afile a new file .P2 .LP In fact, there was no need to remove .CW afile before running the program. If the file being created exists, .CW create .ix "truncate truncates it. If it does not exist, the file is created. In either case, we obtain a new file descriptor for the file. .PP Directories can be created by doing a bit-or of the integer constant .ix "directory creation .ix [DMDIR] .CW DMDIR with the rest of the permissions given to .CW create . This sets a bit (called DMDIR) in the integer used to specify permissions, and the system creates a directory instead of a file. .P1 fd = create("adir", OREAD, DMDIR|0775); .P2 .LP You cannot write into directories. That would be dangerous. Instead, when you create and remove files within the directory, Plan 9 updates the contents of the directory file for you. If you modify the previous program to try to create a directory, you must remove the line calling .CW write . But you should still close the file descriptor. .PP Removing a file is simple. The system call .CW remove .ix [remove] .ix "file deletion removes the named file. This program is similar to .CW rm . .ix [rm] .so progs/rm.c.ms .ix [rm.c] .LP It can be used like the standard .I rm (1) tool, to get rid of multiple files. When .CW remove fails it alerts the user of the problem. .P1 ; 8.rm rm.8 x.c afile 8.rm: 'x.c' file does not exist .P2 .LP Like other calls, .CW remove returns .CW -1 .ix "system call error when it fails. In this case we print the program name (\f(CWargv[0]\fP) and the error string. That suffices to let the user know what happen and .ix "error string take any appropriate action. Note how the program iterates through command line arguments starting at 1. Otherwise, it would remove itself! .PP A directory that is not empty, and contains other files, cannot be removed using .ix "empty directory .CW remove . To remove it, you must remove its contents first. Plan 9 could remove the whole file tree rooted at the directory, but it would be utterly dangerous. Think about .CW "rm /" . The system command .CW rm accepts option .CW -r .ix "[rm] flag~[-r] to recursively descend the named file and remove it and all of its contents. It must be used with extreme caution. When a file is removed, it is gone. There is nothing you can do to bring it back to life. Plan 9 does not have a .I wastebasket . .ix wastebasket If you are not sure about removing a file, just don't do it. Or move it to .CW /tmp or to some other place where it does not gets in your way. .PP Now that we can create and remove files, it is interesting to see if a file does exist. This could be done by opening the file just to see if we can. However, it is more appropriate to use a system call intended just to check if we can access a file. It is called, perhaps surprisingly, .CW access . .ix [access] .ix "checking~for access For example, this code excerpt aborts the execution of its program when the file name in .CW fname does not exist: .P1 if (access(fname, AEXIST) < 0) sysfatal("%s does not exist", fname); .P2 .LP .ix "[AEXIST] access~mode The second parameter is an integer constant that indicates what do you want .CW access to check the file for. For example, .CW AWRITE .ix "[AWRITE] access~mode checks that you could open the file for writing, .CW AREAD .ix "[AREAD] access~mode does the same for reading, and .CW AEXEC .ix "[AEXEC] access~mode does the same for executing it. .BS 2 "Directory entries .LP Files have data. There are many examples above using .CW cat and .CW xd to retrieve the data stored in a file. Besides, files have .B metadata , i.e., data about the data. File metadata is simply what the system needs to know about the file to be able to implement it. File metadata includes the file name, the file size, the time for the last modification to the file, the time for the last access to the file, and other attributes for the file. Thus, file metadata is also known as .B "file attributes" . .PP Plan 9 stores attributes for a file in the directory that contains the file. Thus, the data structure that contains file metadata is known as a .B "directory entry" . A directory contains just a sequence of entries, each one providing the attributes for a file contained in it. Let's see this in action: .P1 ; lc ; cat . ; .P2 .LP An empty directory is an empty file. .P1 ; touch onefile ; xd -c . 0000000 B 00 M 00 13 00 00 00 00 00 00 00 00 bf a1 01 0000010 00 00 00 00 00 a4 01 00 00 \er I b1 D \er I b1 0000020 D 00 00 00 00 00 00 00 00 07 00 o n e f i 0000030 l e 04 00 n e m o 04 00 n e m o 04 00 0000040 n e m o 0000044 .P2 .LP After creating .CW onefile in this empty directory, we see a whole bunch of bytes in the directory. Nothing that we could understand by looking at them, although you can see how there are several strings, including .CW nemo and .CW onefile within the data kept in the directory. .PP For each file in the directory, there is an entry in the directory to describe the file. The format is independent of the architecture used, which means that the format .ix "architecture independent .ix "network format is the same no matter the machine that stored the file. Because the machine using the directory (e.g., your terminal) may differ from the machine keeping the file (e.g., your file server), this is important. Each machine could use a different format to encode integers, strings, and other data types. .PP We can double-check our belief by creating a second file in our directory. After doing so, the directory has twice the size: .P1 ; touch another ; xd -c . 0000000 B 00 M 00 13 00 00 00 00 00 00 00 00 c0 a1 01 0000010 00 00 00 00 00 a4 01 00 00 ! I b1 D ! I b1 0000020 D 00 00 00 00 00 00 00 00 07 00 a n o t h 0000030 e r 04 00 n e m o 04 00 n e m o 04 00 0000040 n e m o B 00 M 00 13 00 00 00 00 00 00 00 0000050 00 bf a1 01 00 00 00 00 00 a4 01 00 00 \er I b1 0000060 D \er I b1 D 00 00 00 00 00 00 00 00 07 00 o 0000070 n e f i l e 04 00 n e m o 04 00 n e 0000080 m o 04 00 n e m o 0000088 .P2 .LP When programming in C, there are convenience functions that convert this portable (but not amenable) data structure into a C structure. The C data type declared in .CW libc.h .ix [libc.h] .ix "C library that describes a directory entry is as follows: .P1 typedef struct Dir { /* system-modified data */ ushort type; /* server type */ uint dev; /* server subtype */ /* file data */ Qid qid; /* unique id from server */ ulong mode; /* permissions */ ulong atime; /* last read time */ ulong mtime; /* last write time */ vlong length; /* file length */ char *name; /* last element of path */ char *uid; /* owner name */ char *gid; /* group name */ char *muid; /* last modifier name */ } Dir; .P2 .ix [Dir] .LP From the shell, we can use .CW ls .ix [ls] to obtain most of this information. For example, .P1 ; ls -lm onefile [nemo] --rw-r--r-- M 19 nemo nemo 0 Jul 9 19:24 onefile .P2 .IP • The file name is .CW onefile . The field .CW name .ix "file name within the directory entry is a string with the name. Just with the name. An absolute path to refer to this file would include all the names from that of the root directory down to the file; each component separated by a slash. But the file name is just .CW onefile . .IP • The times for the last access and for the last modification of the file (this one printed by .CW ls ) are kept at .CW atime .ix "file access time and .CW mtime .ix [mtime] .ix "file modification time respectively. These dates are codified in seconds since the epoch, as we saw for .CW /dev/time . .ix [/dev/time] .IP • The length for the file is zero. This is stored at field .CW length .ix "file length in the directory entry. The file is owned by user .CW nemo .ix "file owner .ix "file group .ix "permissions and belongs to the group .CW nemo . These values are stored as string, using the fields .CW uid .ix [uid] .ix "user id (user id) and .CW gid .ix [gid] .ix "group id (group id) respectively. .IP • The field .CW mode .ix "file mode records the file permissions, also known as the mode (that is why .CW chmod .ix [chmod] has that name, for “change mode”). Permissions are encoded in a single integer, as we saw. For .ix "octal permissions this file mode would be .CW 0644 . .IP • The file was last modified by user .CW nemo , and this value is encoded as a string in the directory entry, using field .CW muid (modification user id). .ix "modification user id .IP • The fields .CW type , .CW dev , and .CW qid .ix QID identify the file. They deserve a separate explanation on their own that we defer by now. .LP To obtain the directory entry for a file, i.e., its attributes, we can use .CW dirstat . .ix [dirstat] This function uses the actual system call, .CW stat , .ix [stat] to read the data, and returns a .CW Dir .ix [Dir] structure that is more convenient to use in C programs. This structure is stored in dynamic memory allocated with .CW malloc .ix [malloc] by .CW dirstat , and the caller is responsible for calling .CW free .ix [free] on it. .PP The following program gives some information about .CW /NOTICE , nothing that .CW ls could not do, and produces this output when run: .P1 ; 8.stat file name: NOTICE file mode: 0444 file size: 63 bytes ; .P2 .so progs/stat.c.ms .ix [stat.c] .ix [stat] .LP Note that the program called .CW free only once, for the whole .CW Dir . .ix [Dir] The strings pointed to by fields in the structure are stored along with the structure itself in the same .CW malloc -allocated memory. Calling .CW free once suffices. .PP An alternative to using this function is using .CW dirfstat , .ix [dirfstat] which receives a file descriptor instead of a file name. This function calls .CW fstat , .ix [fstat] which is another system call similar to .CW stat .ix [stat] (but receiving a file descriptor instead of a file name). Which one to use depends on what do you have at hand, a name, or a file descriptor. .PP Because directories contain directory entries, reading from a directory is very similar to what we have just done. The function .CW read .ix "directory read can be used to read directories as well as files. The only difference is that the system will read only an integral number of directory entries. If one more entry does not fit in the buffer you supply to .CW read , it will have to wait until you read again. .PP The entries are stored in the directory in a portable, machine independent, and not amenable, format. Therefore, instead of using .CW read , it is more convenient to use .CW dirread . .ix [dirread] This function calls .CW read to read the data stored in the directory. But before returning to the caller, it .I unpacks .ix "network format them into a, more convenient, array of .CW Dir structures. .PP As an example, the next program lists the current directory, using .CW dirread to obtain the entries in it. .PP Running the program yields the following output. As you can see, the directory was being used to keep a few C programs and compile them. .P1 ; 8.lsdot 8.lsdot create.8 create.c lsdot.8 lsdot.c ; .P2 .so progs/lsdot.c.ms .ix [lsdot.c] .LP The array of directory entries is returned from .CW dirread using a pointer parameter passed by reference (We know, C passes all parameters by value; The function receives a pointer to the pointer). Such array is allocated by .CW dirread using .CW malloc , like before. Therefore, the caller must call .CW free (once) to release this memory. The number of entries in the array is the return value for the function. Like .CW read would do, when there are no more entries to be read, the function returns zero. .PP Sometimes it is useful to change file attributes. For example, changing the length to zero may truncate the file. A rename within the same directory can be achieved by changing the name in the directory entry. Permissions can be changed by updating the mode in the directory entry. Some of the attributes cannot be updated. For example, it is illegal to change the modification type, or any of the .CW type , .CW dev , and .CW qid fields. .PP The function .CW dirwstat .ix [dirwstat] is the counterpart of .CW dirstat . .ix [dirstat] It works in a similar way, but instead of reading the attributes, it updates them. New values for the update are taken from a .CW Dir structure given as a parameter. However, the function ignores any field set to a null value, to allow you to change just one attribute, or a few ones. Beware that zero is not a null value for some of the fields, because it would be a perfectly legal value for them. The function .CW nulldir is to be used to null all of the fields in a given .CW Dir . .PP Here is an example. The next program is similar to .CW chgrp (1), change group, .ix [chgrp] and can be used to change the group for a file. The .CW main function iterates through the file name(s) and calls a .CW chgrp function to do the actual work for each file. .so progs/chgrp.c.ms .ix [chgrp.c] .LP The interesting part is the implementation of the .CW chgrp function. It is quite simple. Internally, .CW dirwstat .I packs the structure into the portable format, and calls .CW wstat .ix [wstat] (the actual system call). As a remark, there is also a .CW dirfwstat .ix [dirfwstat] variant, that receives a file descriptor instead of a file name. It is the counterpart of .CW dirfstat and uses the .CW fwstat .ix [fwstat] system call. Other attributes in the directory entry can be updated as done above for the group id. .LP The resulting program can be used like the real .I chgrp (1) .P1 ; 8.chgrp planb chgrp.c chgrp.8 ; ls -l chgrp.c chgrp.8 --rw-r--r-- M 19 nemo planb 1182 Jul 10 12:09 chgrp.8 --rw-r--r-- M 19 nemo planb 377 Jul 10 12:08 chgrp.c ; .P2 .BS 2 "Listing files in the shell .LP It may be a surprise to find out that there is now a section with this title. You know all about listing files. It is a matter of using .CW ls .ix [ls] .ix [lc] .ix "file list .ix "directory list and other related tools. Well, there is something else. The shell on its own knows how to list files, to help you type names. Look at this session: .P1 ; cd $home ; lc bin lib tmp ; echo * bin lib tmp .P2 .LP First, we used .CW lc to list our home. Later, we used just the shell. It is clear that .CW echo is simply echoing its arguments. It knows nothing about listing files. Therefore, the shell had to supply .CW bin , .CW lib , and .CW tmp , as the arguments for .CW echo (instead of supplying the “\f(CW*\fP”). It could be either the shell or echo the one responsible for this behavior. There is no magic, and no other program was involved on this command line. .PP The shell gives special meaning to certain characters (we already saw two: “\f(CW$\fP”, and “\f(CW'\fP”). One of them is “\f(CW*\fP”. When the a command line contains a word that is “\f(CW*\fP”, it is replaced with the names for all the files in the current directory. Indeed, “\f(CW*\fP” works for all directories: .P1 ; lc bin 386 rc ; echo bin/* bin/386 bin/rc ; .P2 .LP .ix [echo] .ix "shell variable .ix "environment variable .ix "variable expansion In this case, the shell replaced .CW bin/* with two names before running echo: .CW bin/386 and .CW bin/rc . This is called .B globbing , and it works as follows. When the shell reads a command line, it looks for .B "file name patterns" . A pattern is an expression that describes file names. It can be just a file name, but useful patterns can include special characters like “\f(CW*\fP”. The shell replaces the pattern with all file names .B matching the pattern. .PP For example, .CW * .ix "[*] pattern matches with any sequence of characters not containing “\f(CW/\fP”. Therefore, in this directory .P1 ; lc bin book lib tmp .P2 .LP the pattern .CW * matches with .CW bin , .CW book , .CW lib , and .CW tmp : .P1 ; echo * bin book lib tmp .P2 .LP The pattern .CW b* matches with any file name that has an initial “\f(CWb\fP” followed by “\f(CW*\fP”, i.e, followed by anything. This means .P1 ; echo b* bin book .P2 .LP The pattern .CW *i* matches with anything, then an .CW i , and then anything: .P1 ; echo *i* bin lib .P2 .LP Another example .P1 ; echo *b* bin book lib .P2 .LP showing that the part of the name matched by .CW * can be also an empty string! Patterns like this one mean .I "the file name has a .CW b .I "in it" . .PP Patterns may appear within path names, to match against .ix "file name different levels in the file tree. For example, we might want to search for the file containing .CW ls , and this would be a brute force approach: .P1 ; ls /ls ls: /ls: '/ls' file does not exist .P2 .LP Not there. Let's try one level down .P1 ; ls /*/ls /bin/ls .P2 .LP Found! But let's assume it was not there either. .ix "file searching .P1 ; ls /*/*/ls .P2 .LP It might be at .CW /usr/bin/ls . Not in a Plan 9 system, but we did not know. Each .CW * in the pattern .CW /*/*/ls matches with any file name. Therefore, this patterns means .I "any file named .CW ls , .I "inside any directory, which is inside any directory that .I "is found at .CW / . .PP This mechanism is very powerful. For example, this directory contains a lot of source and object files. We can use a pattern to remove just the object files. .P1 ; lc 8.out echo.c err.c open.c echo.8 err.8 open.8 sleep.c ; rm *.8 .P2 .LP The shell replaced the pattern .CW *.8 with any file name terminated with .CW .8 . .ix [rm] Therefore, .CW rm received as arguments all the names for object files. .P1 ; lc 8.out echo.c err.c open.c sleep.c .P2 .LP Patterns may contain a “\f(CW?\fP”, which matches a single character. .ix "[?] pattern For example, we know that the linkers generate output files named .CW 8.out , .CW 5.out , etc. This removes any temporary binary that we might have in the directory: .P1 ; rm ?.out .P2 .LP Any file name containing a single character, and then .CW .out , matches this pattern. The shell replaces the pattern with appropriate file names, and then executes the command line. If no file name matches the pattern, the pattern itself is untouched by the shell and used as the command argument. After the previous command, if we try again .P1 ; rm ?.out rm: ?.out: '?.out' file does not exist .P2 .LP Another expression that may be used in a pattern is a series of characters between square brackets. It matches any single character within the brackets. For example, .ix "character range pattern instead of using .CW ?.out we might have used .CW [58].out in the command line above. The only file names matching this expression are .CW 5.out and .CW 8.out , which were the names we meant. .PP Another example. This lists any C source file (any string followed by a single dot, and then either a .CW c or an .CW h ). .P1 ; lc *.[ch] .P2 As a shorthand, consecutive letters or numbers within the brackets may be abbreviated by using a .CW - between just the first and the last ones. An example is .CW [0-9] , which matches again any single digit. .PP The directory .ix "file dump .ix "file archive .ix [/n/dump] .CW /n/dump keeps a file tree that uses names reflecting dates, to keep a copy of files in the system for each date. For example, .CW /n/dump/2002/0217 is the path for the dump (copy) made in February 17th, 2002. The command below uses a pattern to list directories for dumps made the 17th of any month not after June, in a year beyond 2000, but ending in 2 (i.e., just 2002 as of today). .P1 ; ls /n/dump/2*2/0[1-6]17 /n/dump/2002/0117 /n/dump/2002/0217 /n/dump/2002/0317 /n/dump/2002/0417 /n/dump/2002/0517 /n/dump/2002/0617 .P2 .LP In general, you concoct patterns to match on file names that may be of interest for you. The shell knows nothing about the meaning of the file names. However, you can exploit patterns in file names using file name patterns. Confusing? .PP To ask the shell not to touch a single character in a word that might be otherwise considered a pattern, the word must be quoted. For example, .ix "quoting .P1 ; lc bin lib tmp ; touch '*' ; echo * * bin lib tmp .P2 .LP Because the .CW * for .CW touch was quoted, the shell took it verbatim. It was not interpreted as a pattern. However, in the next command line it was used unquoted and taken as a pattern. Removing the funny file we just created is left as an exercise. But be careful. Remember what .ix [rm] .P1 ; rm * .P2 would do! .BS 2 "Buffered Input/Output .ix "buffered I/O .LP The interface provided by .CW open , .CW close , .CW read , and .CW write .ix [open] .ix [close] .ix [read] .ix [write] suffices many times to do the task at hand. Also, in many cases, it is just the more convenient interface for doing I/O to files. For example, .CW cat .ix [cat] must just write what it reads. It is just fine to use .CW read and .CW write for implementing such a tool. But, what if our program had to read one byte at a time? or one line at a time? We can experiment using the program below. It is a simple .CW cp , .ix [cp] .ix "file copy that copies one file into another, but using the size for the buffer that we supply as a parameter. .so progs/bcp.c.ms .ix [bcp.c] .LP We are going to test our new program using a file created just for this test. To create the file, we use .CW dd . This is a tool that is useful to copy bytes in a controlled way from one place to another (its name stands for .I "device to device" ). Using this command .ix "device to~device .ix [dd] .P1 ; dd -if /dev/zero -of /tmp/sfile -bs 1024 -count 1024 1024+0 records in 1024+0 records out ; ls -l /tmp/sfile --rw-r--r-- M 19 nemo nemo 1048576 Jul 29 16:20 /tmp/sfile .P2 .LP we create a file with 1 Mbyte of bytes, all of them zero. The option .ix "file creation .CW -if lets you specify the input file for .CW dd , i.e., where to read bytes from. In this case, we used .CW /dev/zero , which a (fake!) file that seems to be an unlimited sequence of zeroes. Reading it would just return as many zeroes as bytes you tried to read, and it would never give an end of file indication. The option .CW -of lets you specify which file to use as the output. In this case, we created the file .CW /tmp/sfile , which we are going to use for our experiment. .PP This tool, .CW dd , reads from the input .ix "file block file one block of bytes after another, and writes each block read to the output file. A block is also known as a .I record , as the output from the program shows. In our case, we used .CW -bs (block size) to ask .CW dd to read blocks of 1024 bytes. We asked .CW dd to copy just 1024 blocks, using its .CW -count option. The result is that .CW /tmp/sfile has 1024 blocks of 1024 bytes each (therefore 1 Mbyte) copied from .CW /dev/zero . .ix [/dev/zero] .PP We are using a relic that comes from ancient times! Times when tapes and even more weird .ix tape artifacts were very common. Many of such devices required programs to read (or write) one record at a time. Using .CW dd was very convenient to duplicate one tape onto another and similar things. Because it was not common to read or write partial records, the diagnostics printed by .CW dd show how many entire records were read (\f(CW1024\fP here), and how many bytes were read from a last but partial record (\f(CW+0\fP in our case). And the same for writing. Today, it is very common to see always \f(CW+0\fP for both the data read in, and the data written out. By the way, for our little experiment we could have used just .CW dd , instead of writing our own dumb version for it, but it seemed more appropriate to let you read the code to review file I/O once more. .PP So, what would happen when we copy our file using our default buffer size of 8Kbytes? .ix buffer .ix [time] .ix "performance .P1 ; time 8.bcp /tmp/sfile /tmp/dfile 0.01u 0.01s 0.40r 8.bcp /tmp/sfile /tmp/dfile .P2 .LP Using the command .CW time , to measure the time it takes for a command to run, we see that using a 8Kbyte buffer it takes 0.4 seconds of real time (\f(CW0.40r\fP) to copy a 1Mbyte file. As an aside, .CW time reports also that .CW 8.bcp spent 0.01 seconds executing its own code (\f(CW0.01u\fP) and 0.01 seconds executing inside the operating system (\f(CW0.01s\fP), .ix "user time .ix "system time .ix "elapsed time e.g., doing system calls. The remaining 0.38 seconds, until the total of 0.4 seconds, the system was doing something else (perhaps executing other programs or waiting for the disk to read or write). .PP What would happen reading one byte at a time? (and writing it, of course). .P1 ; time 8.bcp -b 1 /tmp/sfile /tmp/dfile 9.01u 56.48s 755.31r 8.bcp -b 1 /tmp/sfile /tmp/dfile .P2 .LP Our program is .I "amazingly slow" ! It took 755.31 seconds to complete. That is 12.6 minutes, which is an eon for a computer. But it is the same program, we did not change anything. Just this time, we read one byte at a time and then wrote that byte to the output file. Before, we did the same but for a more reasonable buffer size. .PP Let's continue the experiment. What would happen if our program reads one line at a time? The source file does not have lines, but we can pretend that all lines have 80 characters of one byte each. .P1 ; time 8.bcp -b 80 /tmp/sfile /tmp/dfile 0.11u 0.74s 10.38r 8.bcp -b 80 /tmp/sfile /tmp/dfile .P2 .LP Things improved, but nevertheless we still need 10.38 seconds just to copy 1 Mbyte. What happens is that making a system call is not so cheap, at least it seems very expensive when compared to making a procedure call. For a few calls, it does not matter at all. However, in this experiment it does. Using a buffer of just one byte means making 2,097,152 system calls! (1,048,576 to read bytes and 1,048,576 to write them). Using an 8Kbyte buffer requires just 128 calls (.e., 1,048,576 / 8,192). You can compare for yourself. In the intermediate experiment, reading one line at a time, it meant 26,214 system calls. Not as many as 2,097,152, but still a lot. .PP How to overcome this difficulty when we really need to write an algorithm that reads/writes a few bytes at a time? The answer, as you probably know, is just to use buffering. It does not matter if your algorithm reads one byte at a time. It does matter if you are making a system call for each byte you read. .PP The .I bio (2) .ix [bio] .ix "buffered I/O library in Plan 9 provides buffered input/output. This is an abstraction that, although not provided by the underlying Plan 9, is so common that you really must know how it works. The idea is that your program creates a Bio buffer for reading or writing, called a .CW Biobuf . .ix [Biobuf] You program reads from the .CW Biobuf , by calling a library function, and the library will call .CW read .ix [read] only to refill the buffer each time you exhaust its contents. This is our (in)famous program, but this time we use Bio. .so progs/biocp.c.ms .ix [biocp.c] .LP The first change you notice is that to use Bio the header .CW bio.h .ix [bio.h] must be included. The data structure representing the Bio buffer is a .CW Biobuf . The program obtains two ones, one for reading the input file and one for writing the output file. The function .CW Bopen .ix [Bopen] is similar to .CW open , but returns a pointer to a .CW Biobuf instead of returning a file descriptor. .P1 ; sig Bopen Biobuf* Bopen(char *file, int mode) .P2 .LP Of course, .CW Bopen .I must call .CW open to open a new file. But the descriptor returned by the underlying call to .CW open is kept inside the .CW Biobuf , because only routines from .I bio (2) should use that descriptor. You are supposed to read and write from the .CW Biobuf . .PP To read from .CW bin , our input buffer, the program calls .CW Bread . This function is exactly like .CW read , but reads bytes from the buffer when it can, without calling .CW read . Therefore, .CW Bread does not receive a file descriptor as its first parameter, it receives a pointer to the .CW Biobuf used for reading. .P1 ; sig Bread long Bread(Biobufhdr *bp, void *addr, long nbytes) .P2 .LP The actual system call, .CW read , is used by .CW Bread .ix [read] .ix [Bread] only when there are no more bytes to be read from the buffer, e.g., because you already read it all. .PP To write bytes to a .CW BIobuf , the program uses .CW Bwrite . .ix [Bwrite] This is to .CW write what .CW Bread is to .CW read . .P1 ; sig Bwrite long Bwrite(Biobufhdr *bp, void *addr, long nbytes) .P2 .LP The call to .CW Bterm .ix [Bterm] releases a .CW Biobuf , .ix "[Biobuf] termination including the memory for the data structure. This closes the file descriptor used to reach the file, after writing any pending byte still sitting in the buffer. .P1 ; sig Bterm int Bterm(Biobufhdr *bp) .P2 .LP As you can see, both .CW Bterm and .CW Bflush .ix [Bflush] .ix "[Biobuf] flushing return an integer. That is how they report errors. They can fail because it can be that the file cannot really be written (e.g., because the disk is full), but you will only know when you try to write the file, which does not necessarily happen in .CW Bwrite . .PP How will our new program behave, now that it uses buffered input/output? Let's try it. .P1 ; time 8.biocp /tmp/sfile /tmp/dfile 0.00u 0.03s 0.38r 8.bcp /tmp/sfile /tmp/dfile ; time 8.biocp -b 1 /tmp/sfile /tmp/dfile 0.00u 0.13s 0.31r 8.bcp -b 1 /tmp/sfile /tmp/dfile ; time 8.biocp -b 80 /tmp/sfile /tmp/dfile 0.00u 0.02s 0.20r 8.bcp -b 80 /tmp/sfile /tmp/dfile .P2 .LP Always the same!. Well, not exactly the same because there is always some uncertainty in every measurement. In this case, give or take 2/10th of a second. But in any case, reading one byte at a time is far from taking 12.6 minutes. Bio took care of using a reasonable buffer size, and calling .CW read only when necessary, as we did by ourselves when using 8Kbyte buffers. .PP One word of caution. After calling .CW write , it is very likely that our bytes are already in the file, because there is probably no buffering between your program and the actual file. However, after a call to .CW Bwrite it is almost for sure that your bytes are .I not in the file. They will be sitting in the .CW Biobuf , waiting for more bytes to be written, until a moment when it seems reasonable for a Bio routine to do the actual call to .CW write . This can happen either when you fill the buffer, or when you call .CW Bterm , which terminates the buffering. If you really want to flush your buffer, i.e., to send all the bytes in it to the file, you may call .CW Bflush . .P1 ; sig Bflush int Bflush(Biobufhdr *bp) .P2 .LP To play with this, and see a couple of other tools provided by Bio, we are going to reimplement our little .CW cat program but using Bio this time. .so progs/biocat.c.ms .ix [cat] .ix [biocat.c] .LP This program uses two .CW Biobufs , like the previous one. However, we now want one for reading from standard input, and another to write to standard output. Because we already have file descriptors 0 and 1 open, it is not necessary to call .CW Bopen . The function .CW Binit .ix [Binit] .ix "[Biobuf] file descriptor initializes a .CW Biobuf for an already open file descriptor. .P1 ; sig Binit int Binit(Biobuf *bp, int fd, int mode) .P2 .LP You must declare your own .CW Biobuf . Note that this time .CW bin and .CW bout are .I not pointers, they are the actual .CW Biobufs used. Once we have our .CW bin and .CW bout buffers, we might use any other Bio function on them, like before. The call to .CW Bterm terminates the buffering, and flushes any pending data to the underlying file. However, because Bio did not open the file descriptor for the buffer, it will not close it either. .PP Unlike the previous program, this one reads one line at a time, because we plan to use it with the console. The function .CW Brdline .ix [Brdline] .ix "read line reads bytes from the buffer until the end-of-line delimiter specified by its second parameter. .P1 ; sig Brdline void* Brdline(Biobufhdr *bp, int delim) .P2 .LP We used .CW '\en' , which is the end of line character in Plan 9. The function returns a pointer to the bytes read, or zero if no more data could be read. Each time the program reads a line, it writes the line to its standard output through .CW bout . The .CW line returned by .CW Brdline is not a C string. There is not a final null byte after the line. We could have used .CW Brdstr , .ix [Brdstr] .ix "string read which returns the line read in dynamic memory (allocated with .CW malloc ), and terminates the line with a final null byte. But we did not. Thus, how many bytes must we write to standard output? The function .CW Blinelen .ix [Blinelen] .ix "line length returns the number of bytes in the last line read with .CW Brdline . .P1 ; sig Blinelen int Blinelen(Biobufhdr *bp) .P2 .LP And that explains the body of the .CW while in our program. Let's now play with our cat. .P1 ; 8.biocat !!one little !!cat was walking. \fBcontrol-d\fP one little cat was walking. ; .P2 .LP No line was written to standard output until we typed .I control-d . The program did call .CW Bwrite , but this function kept the bytes in the buffer. When .CW Brdline returned an EOF indication, the call to .CW Bterm terminated the output buffer and its contents were written to the underlying file. If we modify this program to add a call to .P1 Bflush(&bout); .P2 after the one to .CW Bwrite , this is what happens. .P1 ; 8.biocat !!Another little cat Another little cat !!did follow did follow \fBcontrol-d\fP ; .P2 .LP The call to .CW Bflush flushes the buffer. Of course, it is now a waste to use .ix "buffer flushing .CW bout at all. If we are flushing the buffer after each write, we could have used just .CW write , and forget about .CW bout . .SH Problems .IP 1 Use the debugger, .CW acid , to see that a program reading from standard input in a window is indeed waiting inside .CW read while the system is waiting for you to type a line in the window. .IP .I Hint : Use .CW ps to find out which process is running your program. .IP 2 Implement the .I cat (1) utility without looking at the source code for the one in your system. .IP 3 Compare your program from the previous problem with the one in the system. Locate the one in the system using a command. Discuss the differences between both programs. .IP 4 Implement a version of .I chmod (1) that accepts an octal number representing a new set of permissions, and one or more files. The program is to be used like in .P1 ; 8.out 0775 file1 file2 file3 .P2 .IP 5 Implement your own program for doing a long listing like .P1 ; ls -l .P2 .IP would do. .IP 6 Write a program that prints all the files contained in a directory (hierarchy) along with the total number of bytes consumed by each file. If a file is a directory, its reported size must include that of the files found inside. Compare with .I du (1). .ds CH .bp \c