1#include <err.h>
2#include <errno.h>
3#include <pthread.h>
4#include <signal.h>
5#include <stdio.h>
6#include <stdlib.h>
7#include <string.h>
8#include <unistd.h>
9
10#include <sys/wait.h>
11#include <sys/types.h>
12
13static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
14
15static void
16sigchld(int num)
17{
18 int status, wstatus;
19
20 (void)num;
21
22 status = EXIT_SUCCESS;
23 while (waitpid((pid_t)-1, &wstatus, WNOHANG) > 0) {
24 if (WIFEXITED(wstatus) && status == EXIT_SUCCESS)
25 status = WEXITSTATUS(wstatus);
26 }
27
28 if (status != EXIT_SUCCESS)
29 exit(status);
30}
31
32static void
33sethandler(void)
34{
35 struct sigaction act;
36
37 act.sa_flags = SA_RESTART;
38 act.sa_handler = sigchld;
39 if (sigemptyset(&act.sa_mask) == -1)
40 err(EXIT_FAILURE, "sigemptyset failed");
41 if (sigaction(SIGCHLD, &act, NULL))
42 err(EXIT_FAILURE, "sigaction failed");
43}
44
45/* thread-safe version of basename(dirname(fp)) */
46static ssize_t
47servername(char **dest, char *fp)
48{
49 char *p, *last, *prev;
50
51 last = prev = NULL;
52 p = &fp[strlen(fp)];
53
54 while (p-- != fp) {
55 if (*p != '/')
56 continue;
57
58 if (!last) {
59 last = p;
60 } else if (!prev) {
61 prev = p;
62 break;
63 }
64 }
65
66 if (!last || !prev)
67 return -1;
68
69 *dest = ++prev;
70 return last - prev;
71}
72
73static void
74readlines(char *fp, FILE *stream)
75{
76 size_t llen, nlen;
77 char *nickstart, *line, *name;
78
79 line = NULL;
80 llen = 0;
81
82 name = NULL;
83 if ((nlen = servername(&name, fp)) <= 0)
84 return;
85
86 while (getline(&line, &llen, stream) != -1) {
87 if (!(nickstart = strchr(line, '[')))
88 continue;
89 if ((errno = pthread_mutex_lock(&mtx)))
90 err(EXIT_FAILURE, "pthread_mutex_lock failed");
91
92 printf("%.*s[%.*s %s", (int)(nickstart - line), line,
93 (int)nlen, name, nickstart + 1);
94
95 fflush(stdout);
96 if ((errno = pthread_mutex_unlock(&mtx)))
97 err(EXIT_FAILURE, "pthread_mutex_unlock failed");
98 }
99
100 if (ferror(stream))
101 errx(EXIT_FAILURE, "ferror failed");
102 free(line);
103}
104
105static void*
106tail(void *arg)
107{
108 FILE *stream;
109 int p[2];
110 char *fp;
111
112 fp = arg;
113 if (pipe(p))
114 err(EXIT_FAILURE, "pipe failed");
115
116 switch (fork()) {
117 case 0:
118 close(p[0]); /* close unused read-end */
119 close(STDOUT_FILENO);
120 dup(p[1]);
121 close(p[1]);
122
123 execlp("tail", "tail", "-f", fp, (char*)NULL);
124 err(EXIT_FAILURE, "execlp failed");
125 case -1:
126 err(EXIT_FAILURE, "fork failed");
127 default:
128 close(p[1]); /* close unused write-end */
129 if (!(stream = fdopen(p[0], "r")))
130 err(EXIT_FAILURE, "fdopen failed");
131
132 readlines(fp, stream);
133 if (fclose(stream) == EOF)
134 err(EXIT_FAILURE, "fclose failed");
135 }
136
137 return NULL;
138}
139
140int
141main(int argc, char **argv)
142{
143 int i, nthrs;
144 pthread_t *thrs;
145
146#ifdef __OpenBSD__
147 if (pledge("stdio proc exec", NULL) == -1)
148 err(EXIT_FAILURE, "pledge failed");
149#endif
150
151 if (argc <= 1) {
152 fprintf(stderr, "USAGE: %s FILE...\n", argv[0]);
153 return EXIT_FAILURE;
154 }
155
156 /* Setup SIGCHLD handler */
157 sethandler();
158
159 nthrs = argc - 1;
160 if (!(thrs = malloc(sizeof(pthread_t) * nthrs)))
161 err(EXIT_FAILURE, "malloc failed");
162
163 for (i = 0; i < nthrs; i++)
164 if ((errno = pthread_create(&thrs[i], NULL, tail, argv[i + 1])))
165 err(EXIT_FAILURE, "pthread_create failed");
166 for (i = 0; i < nthrs; i++)
167 if ((errno = pthread_join(thrs[i], NULL)))
168 err(EXIT_FAILURE, "pthread_join failed");
169
170 return EXIT_SUCCESS;
171}