user/cat.c
About this file
cat (“concatenate”) copies files to standard output, one after another. cat README
prints the file; cat a b > c joins two files into a third; plain cat with no
arguments copies its standard input to its standard output, which makes it useful at
either end of a pipeline.
It is the smallest complete example of the Unix I/O model seen from user space: open a
file by name to get a file descriptor, read from it in chunks until read
returns 0 (end of file), write each chunk to descriptor 1, close the descriptor.
Because read and write work the same way on files, the console and
pipes, cat never needs to know which kind of object it is copying.
Each call goes through a stub in user/usys.S that executes ecall, and lands in the
kernel in sys_open, sys_read, sys_write or
sys_close (kernel/sysfile.c).
Read before: user/user.h (the system-call declarations). Read next:
user/echo.c, then user/grep.c and user/wc.c, which follow the same pattern.
Headers
kernel/types.h defines uint and friends, which user/user.h uses.
kernel/fcntl.h supplies O_RDONLY, the open mode used on line 35. user/user.h
declares the system calls (read, write, open, …) and the small user library
(fprintf, strlen, …). There is no standard C library in xv6; these headers are
all a user program gets.
The copy buffer
A 512-byte buffer for moving data from input to output. It is a global, so it lives in
the program’s .bss section section rather than on the small user stack. 512 is a
reasonable chunk: each read is one system call, so a bigger buffer means fewer
trips into the kernel, but the size does not affect correctness.
cat(): copy one descriptor to standard output
The loop is the classic Unix copy: read up to 512 bytes, then write exactly as many
bytes as were read. read returns how many bytes it delivered, which can be fewer
than asked: at the end of a file, or from the console, where consoleread
returns after each line you type. That is why line 13 writes n bytes and not
sizeof(buf).
read returns 0 at end of file (or when all write ends of a pipe are closed, or when
you type Ctrl-D at the console) and -1 on error. Both end the loop; line 18 tells
them apart.
Errors go to descriptor 2, standard error, so that they still reach the screen when standard output is redirected to a file. Both errors exit with status 1 (exit status).
Read up to 512 bytes from fd into buf. The stub in user/usys.S traps into the
kernel, where sys_read finds the open file and calls fileread;
that function dispatches to a pipe, a device driver or readi for a file on
disk, and advances the file’s offset. The loop continues while read returns a
positive count.
Write the n bytes just read to descriptor 1 (sys_write →
filewrite). A short or failed write means the output could not take the data
(for example, the reading end of a pipe was closed), and continuing would silently
lose data, so cat stops.
fprintf(2, ...) is the user library’s formatted print to descriptor 2
(fprintf); printf would go to descriptor 1.
exit(1) ends the process with a non-zero status: failure. It never returns
(sys_exit → kexit).
The loop ended with n == 0 (end of file, normal) or n < 0 (an error, for example a
bad descriptor). Only the second is reported.
main(): standard input, or each named file in turn
argv[0] is the program name, so argc <= 1 means “no file names”. Then
cat copies descriptor 0, standard input, which the shell
has already connected to the console, a file (cat < f) or a pipe (ls | cat).
cat does not open or close it; it was inherited.
Otherwise each argument is opened, copied and closed in order. Closing matters: a
process has only 16 descriptor slots (NOFILE), so cat on more than about
13 files would run out if it never closed them. Since sys_open always
returns the lowest free descriptor (fdalloc), each file here gets descriptor
3 again after the previous one is closed.
A file that cannot be opened stops cat immediately with status 1; files named after
it are not printed. (GNU cat reports the error and carries on with the next file.)
No arguments other than the program name.
Copy standard input to standard output.
Open the file read-only. sys_open looks the path up (namei),
allocates an open file (struct file) and a descriptor, and returns the descriptor, or -1 if the
file does not exist or the tables are full. Opening a directory read-only succeeds, so
cat . prints the directory’s raw 16-byte entries: the file names show up, mixed with
unprintable bytes (the 2-byte inode numbers and the NUL padding).
Report which file failed, on standard error.
Copy this file’s contents to standard output.
Release the descriptor (sys_close → fileclose) so the slot can
be reused for the next file.
Every file was copied: exit with status 0, success.