user/wc.c
About this file
wc (“word count”) counts the lines, words and bytes of each file named on its command
line, or of standard input if there are none, and prints them in that order followed by
the name: wc README prints 48 336 2441 README in a freshly built xv6, and
echo hi there | wc prints 1 2 9 (the name is empty for standard input).
It reads input with the same loop as user/cat.c, but instead of writing each chunk
out it scans it byte by byte. Lines are counted by newlines. Words are counted with a
two-state machine: “inside a word” or “between words”; a word starts each time the state
changes from “between” to “inside”.
Read before: user/cat.c. Read next: user/grep.c, which also processes its
input line by line.
Headers and the input buffer
The same headers as the other utilities (kernel/fcntl.h for O_RDONLY). buf is a
512-byte global chunk buffer, as in user/cat.c: its size only affects how many
read system calls are made, never the counts.
wc(): the counters
l, w and c count lines, words and characters (bytes, really: xv6 has no notion
of multi-byte characters). inword is the state of the word machine: 1 while the
previous byte was part of a word, 0 otherwise. It starts at 0, so the first non-space
byte of the input begins a word.
All four are set once per file, outside the read loop. That matters for inword:
read can split the input at any byte, including in the middle of a word. Because
the state carries over from one chunk to the next, a word cut in two by the chunk
boundary is still counted once.
Counts start at zero for each file.
Start “between words”.
Scan every byte with the word state machine
Every byte counts as a character. Every \n ends a line, so a last line without a
newline is not counted as a line (the same rule as Unix wc).
The word machine has two states and two kinds of input:
| state | next byte | action | new state |
|---|---|---|---|
| between words | space | none | between words |
| between words | other | count a word | inside a word |
| inside a word | space | none | between words |
| inside a word | other | none | inside a word |
“Space” here means one of the five characters in " \r\t\n\v": space, carriage
return, tab, newline, vertical tab. (Form feed, \f, is missing from the list, so
wc treats it as part of a word.) The test uses strchr, which returns a
pointer to the byte if it is in the string and 0 otherwise. xv6’s strchr stops at
the string’s terminating NUL without comparing it, so a NUL byte in the input counts
as a word character; the standard C strchr would find the terminator and treat
NUL as a space.
Example: in hi there\n the bytes h and t each move the machine from “between”
to “inside”, giving 2 words; the line count is 1 and the byte count 9.
Read the next chunk (sys_read) until end of input or error.
Visit each of the n bytes actually read (not sizeof(buf)).
Every byte is a character.
A newline ends a line.
A whitespace byte: the machine moves to (or stays in) “between words”.
A non-whitespace byte while between words: a new word starts here.
Count it.
Now inside a word; further non-space bytes do not count again.
Report a read error, or print the counts
As in user/cat.c, the read loop ends on 0 (end of input) or -1 (error). An error
prints a message and exits with status 1 (exit status), but with printf, so
the message goes to standard output rather than
standard error.
Otherwise one line of results goes to standard output: lines, words, bytes, name.
Unlike Unix wc, there is no total line when several files are given.
The loop ended because read failed.
Error message, on standard output.
Lines, words, bytes, name.
main(): standard input, or each named file
The same shape as main in user/cat.c: no arguments means count descriptor 0
(with an empty name, so the output line ends in a space); otherwise open each file
read-only (sys_open), count it, close it. A file that cannot be opened ends
the program with status 1, and that message also goes to standard output.
No file names.
Count standard input; the empty name leaves a trailing space in the output.
Open each file read-only.
Error on standard output, then stop with status 1.
Count this file and print its line.
Free the descriptor for the next file (sys_close).