The tee command is used to read content from standard input and displays it not only in standard output but also saves to other files simultaneously. The source code of tee
in OpenBSD
is very simple, and I want to give it an analysis:
(1) tee
leverages Singlely-linked List
defined in sys/queue.h to manage outputted files (including standard output):
struct list {
SLIST_ENTRY(list) next;
int fd;
char *name;
};
SLIST_HEAD(, list) head;
......
static void
add(int fd, char *name)
{
struct list *p;
......
SLIST_INSERT_HEAD(&head, p, next);
}
int
main(int argc, char *argv[])
{
struct list *p;
......
SLIST_INIT(&head);
......
SLIST_FOREACH(p, &head, next) {
......
}
}
To understand it easily, I extract the macros from sys/queue.h
and created a file which utilizes the marcos:
#define SLIST_HEAD(name, type) \
struct name { \
struct type *slh_first; /* first element */ \
}
#define SLIST_ENTRY(type) \
struct { \
struct type *sle_next; /* next element */ \
}
#define SLIST_FIRST(head) ((head)->slh_first)
#define SLIST_END(head) NULL
#define SLIST_EMPTY(head) (SLIST_FIRST(head) == SLIST_END(head))
#define SLIST_NEXT(elm, field) ((elm)->field.sle_next)
#define SLIST_FOREACH(var, head, field) \
for((var) = SLIST_FIRST(head); \
(var) != SLIST_END(head); \
(var) = SLIST_NEXT(var, field))
#define SLIST_INIT(head) { \
SLIST_FIRST(head) = SLIST_END(head); \
}
#define SLIST_INSERT_HEAD(head, elm, field) do { \
(elm)->field.sle_next = (head)->slh_first; \
(head)->slh_first = (elm); \
} while (0)
struct list {
SLIST_ENTRY(list) next;
int fd;
char *name;
};
SLIST_HEAD(, list) head;
int
main(int argc, char *argv[])
{
struct list *p;
SLIST_INIT(&head);
SLIST_INSERT_HEAD(&head, p, next);
SLIST_FOREACH(p, &head, next) {
}
}
Then employed gcc
‘s pre-processing function:
# gcc -E slist.c
# 1 "slist.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "slist.c"
# 30 "slist.c"
struct list {
struct { struct list *sle_next; } next;
int fd;
char *name;
};
struct { struct list *slh_first; } head;
int
main(int argc, char *argv[])
{
struct list *p;
{ ((&head)->slh_first) = NULL; };
do { (p)->next.sle_next = (&head)->slh_first; (&head)->slh_first = (p); } while (0);
for((p) = ((&head)->slh_first); (p) != NULL; (p) = ((p)->next.sle_next)) {
}
}
It becomes clear now! The head node in list contains only 1
member: slh_first
, which points to the first valid node. For the elements in the list, it is embedded with next
struct which uses sle_next
to refer to next buddy.
(2) By default, tee
will overwrite the output files. If you want to append it, use -a
option, and the code is as following:
while (*argv) {
if ((fd = open(*argv, O_WRONLY | O_CREAT |
(append ? O_APPEND : O_TRUNC), DEFFILEMODE)) == -1) {
......
}
......
}
(3) The next part is the skeleton of saving content to files:
while ((rval = read(STDIN_FILENO, buf, sizeof(buf))) > 0) {
SLIST_FOREACH(p, &head, next) {
n = rval;
bp = buf;
do {
if ((wval = write(p->fd, bp, n)) == -1) {
......
}
bp += wval;
} while (n -= wval);
}
}
We need to iterates every opened file descriptor and write contents into it.
(4) Normally, theinterrupt
signal will cause tee
exit:
# tee
fdkfkdfjk
fdkfkdfjk
^C
#
To disable this feature, use -i
option:
# tee -i
fdhfhd
fdhfhd
^C^C
The corresponding code is like this:
......
case 'i':
(void)signal(SIGINT, SIG_IGN);
break;
Nice post! Keep writing about OpenBSD please!