]> pd.if.org Git - pdutils/blob - posix/tail/tail.c
implemented pwd
[pdutils] / posix / tail / tail.c
1 /*
2  * tail.c - copy the last part of a file
3  *
4  * Version: 2008-1.01
5  * Build:   c89 -o tail tail.c
6  * Source:  <http://pdcore.sourceforge.net/>
7  * Spec:    <http://www.opengroup.org/onlinepubs/9699919799/utilities/tail.html>
8  *
9  * This is free and unencumbered software released into the public domain,
10  * provided "as is", without warranty of any kind, express or implied. See the
11  * file UNLICENSE and the website <http://unlicense.org> for further details.
12  */
13
14
15 #define _POSIX_SOURCE
16
17 #include <ctype.h>
18 #include <errno.h>
19 #include <fcntl.h>
20 #include <locale.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <sys/stat.h>
25 #include <sys/types.h>
26 #include <unistd.h>
27
28 #define USAGE     "usage: tail [-f] [-c number] [-n number] [file]\n"
29 #define NOMALLOC  "Unable to allocate memory for read buffer"
30 #define NOFOLLOW  "tail: Option [-f] ignored; can only follow regular files and FIFOs\n"
31 #define BADBYTES  "Option [-c] requires non-zero byte offset"
32 #define BADLINES  "Option [-n] requires integer line offset"
33 #define CNMUTEXCL "Options [-c] and [-n] are mutually exclusive"
34 #define FVANISH   "Followed file has vanished"
35 #define FSHRUNK   "Followed file has shrunk"
36 #define SYSERR    1
37 #define APPERR    0
38
39 static int isdecint(char *s);
40 static void tailfile(int fd, char *fn);
41 static void tailbyteoffset(int fd, char *fn, int isregfile, ssize_t len);
42 static void taillineoffset(int fd, char *fn, int isregfile, ssize_t len);
43 static void copytoeof(int fd, char *fn);
44 static void fatal(int errtype, char *s);
45
46 static unsigned char* buf;
47 static ssize_t bufsize, offset;
48 static int optf, optc, optn;
49
50
51 int main(int argc, char **argv)
52 {
53     extern int opterr, optind;
54     extern char *optarg;
55     int c, fd;
56     char *fn;
57
58     setlocale(LC_ALL, "");
59     opterr = 0;
60
61     /*
62      * try to get a buffer _twice_ the size mandated by the standard as in
63      * later processing we may be paging through the file in _half_ buffer
64      * increments; this way we guarentee POSIX buffer size conformance
65      */
66     bufsize = sysconf(_SC_LINE_MAX) * 20;   /* POSIX says [{LINE_MAX)*10] */
67     bufsize = bufsize < 4096 ? 4096 : bufsize;
68
69     if ((buf = malloc(bufsize)) == NULL)
70         fatal(APPERR, NOMALLOC);
71
72     while ((c = getopt(argc, argv, "fc:n:")) != -1)
73         switch (c)
74         {
75         case 'f':   /* follow file */
76             optf = 1;
77             break;
78
79         case 'c':   /* offset in bytes */
80             optc = 1;
81             offset = 0;
82             if (*optarg && isdecint(optarg))
83             {
84                 offset = atol(optarg);
85                 offset = isdigit(*optarg) ? -offset : offset;
86             }
87
88             if (offset == 0)
89                 fatal(APPERR, BADBYTES);
90             else
91                 break;
92
93         case 'n':   /* offset in lines */
94             optn = 1;
95             if (*optarg && isdecint(optarg))
96             {
97                 offset = atol(optarg);
98                 offset = isdigit(*optarg) ? -offset : offset;
99
100                 /* the standard says [-n 0] is OK, but [-n +0] isn't &_& */
101                 if (offset != 0 || (offset == 0 && *optarg != '+'))
102                     break;
103             }
104             fatal(APPERR, BADLINES);
105
106         default:
107             fprintf(stderr, USAGE);
108             exit(1);
109         }
110
111     if (! optc && ! optn)   /* if none, set the default options */
112     {
113         optn = 1;
114         offset = -10;
115     }
116
117     if (optc && optn)
118         fatal(APPERR, CNMUTEXCL);
119
120     if (optind >= argc)
121         tailfile(STDIN_FILENO, "stdin");
122     else
123     {
124         fn = argv[optind++];
125
126         if (strcmp(fn, "-") == 0)
127             tailfile(STDIN_FILENO, "stdin");
128         else
129             if ((fd = open(fn, O_RDONLY)) == -1)
130                 fatal(SYSERR, fn);
131             else
132             {
133                 tailfile(fd, fn);
134                 if (close(fd) == -1)
135                     fatal(SYSERR, fn);
136             }
137     }
138
139     return(0);
140 }
141
142
143 int isdecint(char *s)
144 {
145     int badch = 0;
146     char *c = s;
147
148     if (! (isdigit(*c) || *c == '+' || *c == '-'))
149         badch = 1;
150     else
151         for (c++; *c; c++)
152             if (! isdigit(*c))
153                 badch = 1;
154
155     return (! badch);
156 }
157
158
159 void tailfile(int fd, char *fn)
160 {
161     struct stat sbuf;
162     int isregfile;
163     ssize_t n, len;
164
165     if (fstat(fd, &sbuf) == -1)
166         fatal(SYSERR, fn);
167
168     if (S_ISREG(sbuf.st_mode))
169     {
170         isregfile = 1;
171         len = sbuf.st_size;
172     }
173     else
174         isregfile = len = 0;
175
176     if (offset != 0)   /* [-n 0] is permitted; but no point processing that */
177     {
178         if (optc)
179             tailbyteoffset(fd, fn, isregfile, len);
180         else
181             taillineoffset(fd, fn, isregfile, len);
182     }
183
184     if (optf)
185     {
186         if (fd == STDIN_FILENO)   /* can't follow; warn then exit no error */
187         {
188             fprintf(stderr, NOFOLLOW);
189             exit(0);
190         }
191
192         while(1)   /* follow forever */
193         {
194             sleep(1);
195
196             if (isregfile)   /* if we can, be polite to the user */
197             {
198                 if (stat(fn, &sbuf) == -1)
199                     fatal(APPERR, FVANISH);
200                 else if (sbuf.st_size < len)
201                     fatal(APPERR, FSHRUNK);
202
203                 len = sbuf.st_size;
204             }
205
206             while ((n = read(fd, buf, bufsize)) > 0)
207                 if (write(STDOUT_FILENO, buf, n) != n)
208                     fatal(SYSERR, "stdout");
209
210             if (n < 0)
211                 fatal(SYSERR, fn);
212         }
213     }
214 }
215
216
217 void tailbyteoffset(int fd, char *fn, int isregfile, ssize_t len)
218 {
219     ssize_t n, halfbuf;
220
221     if (isregfile)   /* should be seekable, so we'll do so; it's fastest */
222     {
223         if (offset > 0)
224             offset = (offset - 1) > len ? len : offset - 1;
225         else
226             offset = (len + offset) > 0 ? len + offset : 0;
227
228         if (lseek(fd, (off_t)offset, SEEK_SET) == (off_t)-1)
229             fatal(SYSERR, fn);
230
231         copytoeof(fd, fn);
232     }
233
234     else   /* possibly non-seekable */
235     {
236         if (offset > 0)   /* forwards through file */
237         {
238             offset--;
239
240             while(1)
241             {
242                 if ((n = read(fd, buf, bufsize)) < 0)
243                     fatal(SYSERR, fn);
244
245                 if (n == 0)
246                     offset = 0;
247
248                 if (offset <= n)
249                     break;
250                 else
251                     offset -= n;
252             }
253
254             if (write(STDOUT_FILENO, buf + offset, n - offset) != n - offset)
255                 fatal(SYSERR, "stdout");
256
257             copytoeof(fd, fn);
258         }
259
260         else   /* backwards through file; remember that offset is negative */
261         {
262             halfbuf = bufsize / 2;
263
264             if ((n = read(fd, buf, bufsize)) < 0)
265                 fatal(SYSERR, fn);
266
267             if (n < bufsize)   /* we've got the whole file */
268             {
269                 offset = (n + offset) < 0 ? 0 : n + offset;
270                 len = n - offset;
271             }
272
273             else   /* we haven't got the whole file */
274             {
275                 while(1)   /* page through the file, half a buffer at a time */
276                 {
277                     memcpy(buf, buf + halfbuf, halfbuf);
278
279                     if ((n = read(fd, buf + halfbuf, halfbuf)) < 0)
280                         fatal(SYSERR, fn);
281                     else if (n < halfbuf)
282                         break;
283                 }
284
285                 offset = (halfbuf + n + offset) < 0 ? 0 : halfbuf + n + offset;
286                 len = halfbuf + n - offset;
287             }
288
289             if (write(STDOUT_FILENO, buf + offset, len) != len)
290                 fatal(SYSERR, "stdout");
291         }
292     }
293 }
294
295
296 void taillineoffset(int fd, char *fn, int isregfile, ssize_t len)
297 {
298     ssize_t n, i, halfbuf;
299
300     if (offset > 0)   /* forwards through file */
301     {
302         offset--;
303
304         while(1)
305         {
306             if ((n = read(fd, buf, bufsize)) < 0)
307                 fatal(SYSERR, fn);
308
309             if (n == 0)
310             {
311                 offset = 0;
312                 break;
313             }
314
315             for (i = 0; i < n && offset > 0; i++)
316                 if (buf[i] == '\n')
317                     offset--;
318
319             if (offset == 0)
320             {
321                 offset = i;
322                 break;
323             }
324         }
325
326         if (write(STDOUT_FILENO, buf + offset, n - offset) != n - offset)
327             fatal(SYSERR, "stdout");
328
329         copytoeof(fd, fn);
330     }
331
332     else   /* backwards through file; remember that offset is negative */
333     {
334         if (isregfile && len > 0)   /* should be seekable, so we'll do so */
335         {
336             n = (len - bufsize) < 0 ? 0 : len - bufsize;
337
338             if (lseek(fd, (off_t)n, SEEK_SET) == (off_t)-1)
339                 fatal(SYSERR, fn);
340
341             if ((n = read(fd, buf, bufsize)) < 0)
342                 fatal(SYSERR, fn);
343
344             if (buf[n-1] == '\n')
345                 offset--;
346
347             for (i = n - 1; i >= 0 && offset < 0; i--)
348                 if (buf[i] == '\n')
349                     offset++;
350
351             if (offset == 0)
352                 offset = i + 2;
353             else
354                 offset = 0;
355
356             len = n - offset;
357
358             if (write(STDOUT_FILENO, buf + offset, len) != len)
359                 fatal(SYSERR, "stdout");
360         }
361
362         else
363         {
364             halfbuf = bufsize / 2;
365
366             if ((n = read(fd, buf, bufsize)) < 0)
367                 fatal(SYSERR, fn);
368
369             if (n < bufsize)   /* we've got the whole file */
370             {
371                 if (n == 0)
372                     offset = 0;
373                 else
374                 {
375                     if (buf[n-1] == '\n')
376                         offset--;
377
378                     for (i = n - 1; i >= 0 && offset < 0; i--)
379                         if (buf[i] == '\n')
380                             offset++;
381
382                     if (offset == 0)
383                         offset = i + 2;
384                     else
385                         offset = 0;
386                 }
387
388                 len = n - offset;
389             }
390
391             else   /* we haven't got the whole file */
392             {
393                 while(1)   /* page through the file, half a buffer at a time */
394                 {
395                     memcpy(buf, buf + halfbuf, halfbuf);
396
397                     if ((n = read(fd, buf + halfbuf, halfbuf)) < 0)
398                         fatal(SYSERR, fn);
399                     else if (n < halfbuf)
400                         break;
401                 }
402
403                 if (buf[halfbuf+n-1] == '\n')
404                     offset--;
405
406                 for (i = halfbuf + n - 1; i >= 0 && offset < 0; i--)
407                     if (buf[i] == '\n')
408                         offset++;
409
410                 if (offset == 0)
411                     offset = i + 2;
412                 else
413                     offset = 0;
414
415                 len = halfbuf + n - offset;
416             }
417
418             if (write(STDOUT_FILENO, buf + offset, len) != len)
419                 fatal(SYSERR, "stdout");
420         }
421     }
422 }
423
424
425 void copytoeof(int fd, char *fn)
426 {
427     ssize_t n;
428
429     while ((n = read(fd, buf, bufsize)) > 0)
430         if (write(STDOUT_FILENO, buf, n) != n)
431             fatal(SYSERR, "stdout");
432
433     if (n < 0)
434         fatal(SYSERR, fn);
435 }
436
437
438 void fatal(int errtype, char *s)
439 {
440     if (errtype == SYSERR)
441         fprintf(stderr, "tail: %s: %s\n", s, strerror(errno));
442     else
443         fprintf(stderr, "tail: %s\n", s);
444
445     exit(1);
446 }