Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
att
GitHub Repository: att/ast
Path: blob/master/src/lib/libcmd/tail.c
1808 views
1
/***********************************************************************
2
* *
3
* This software is part of the ast package *
4
* Copyright (c) 1992-2012 AT&T Intellectual Property *
5
* and is licensed under the *
6
* Eclipse Public License, Version 1.0 *
7
* by AT&T Intellectual Property *
8
* *
9
* A copy of the License is available at *
10
* http://www.eclipse.org/org/documents/epl-v10.html *
11
* (with md5 checksum b35adb5213ca9657e911e9befb180842) *
12
* *
13
* Information and Software Systems Research *
14
* AT&T Research *
15
* Florham Park NJ *
16
* *
17
* Glenn Fowler <[email protected]> *
18
* David Korn <[email protected]> *
19
* *
20
***********************************************************************/
21
#pragma prototyped
22
23
/*
24
* print the tail of one or more files
25
*
26
* David Korn
27
* Glenn Fowler
28
*/
29
30
static const char usage[] =
31
"+[-?\n@(#)$Id: tail (AT&T Research) 2012-10-10 $\n]"
32
USAGE_LICENSE
33
"[+NAME?tail - output trailing portion of one or more files ]"
34
"[+DESCRIPTION?\btail\b copies one or more input files to standard output "
35
"starting at a designated point for each file. Copying starts "
36
"at the point indicated by the options and is unlimited in size.]"
37
"[+?By default a header of the form \b==> \b\afilename\a\b <==\b "
38
"is output before all but the first file but this can be changed "
39
"with the \b-q\b and \b-v\b options.]"
40
"[+?If no \afile\a is given, or if the \afile\a is \b-\b, \btail\b "
41
"copies from standard input. The start of the file is defined "
42
"as the current offset.]"
43
"[+?The option argument for \b-c\b can optionally be "
44
"followed by one of the following characters to specify a different "
45
"unit other than a single byte:]{"
46
"[+b?512 bytes.]"
47
"[+k?1 KiB.]"
48
"[+m?1 MiB.]"
49
"[+g?1 GiB.]"
50
"}"
51
"[+?For backwards compatibility, \b-\b\anumber\a is equivalent to "
52
"\b-n\b \anumber\a and \b+\b\anumber\a is equivalent to "
53
"\b-n -\b\anumber\a. \anumber\a may also have these option "
54
"suffixes: \bb c f g k l m r\b.]"
55
56
"[n:lines]:[lines:=10?Copy \alines\a lines from each file. A negative value "
57
"for \alines\a indicates an offset from the end of the file.]"
58
"[b:blocks?Copy units of 512 bytes.]"
59
"[c:bytes]:?[chars?Copy \achars\a bytes from each file. A negative value "
60
"for \achars\a indicates an offset from the end of the file.]"
61
"[f:forever|follow?Loop forever trying to read more characters as the "
62
"end of each file to copy new data. Ignored if reading from a pipe "
63
"or fifo.]"
64
"[h!:headers?Output filename headers.]"
65
"[l:lines?Copy units of lines. This is the default.]"
66
"[L:log?When a \b--forever\b file times out via \b--timeout\b, verify that "
67
"the curent file has not been renamed and replaced by another file "
68
"of the same name (a common log file practice) before giving up on "
69
"the file.]"
70
"[q:quiet?Don't output filename headers. For GNU compatibility.]"
71
"[r:reverse?Output lines in reverse order.]"
72
"[s:silent?Don't warn about timeout expiration and log file changes.]"
73
"[t:timeout?Stop checking after \atimeout\a elapses with no additional "
74
"\b--forever\b output. A separate elapsed time is maintained for "
75
"each file operand. There is no timeout by default. The default "
76
"\atimeout\a unit is seconds. \atimeout\a may be a catenation of 1 "
77
"or more integers, each followed by a 1 character suffix. The suffix "
78
"may be omitted from the last integer, in which case it is "
79
"interpreted as seconds. The supported suffixes are:]:[timeout]{"
80
"[+s?seconds]"
81
"[+m?minutes]"
82
"[+h?hours]"
83
"[+d?days]"
84
"[+w?weeks]"
85
"[+M?months]"
86
"[+y?years]"
87
"[+S?scores]"
88
"}"
89
"[v:verbose?Always ouput filename headers.]"
90
91
"\n"
92
"\n[file ...]\n"
93
"\n"
94
95
"[+EXIT STATUS?]{"
96
"[+0?All files copied successfully.]"
97
"[+>0?One or more files did not copy.]"
98
"}"
99
"[+SEE ALSO?\bcat\b(1), \bhead\b(1), \brev\b(1)]"
100
;
101
102
#include <cmd.h>
103
#include <ctype.h>
104
#include <ls.h>
105
#include <tv.h>
106
#include <rev.h>
107
108
#define COUNT (1<<0)
109
#define ERROR (1<<1)
110
#define FOLLOW (1<<2)
111
#define HEADERS (1<<3)
112
#define LINES (1<<4)
113
#define LOG (1<<5)
114
#define NEGATIVE (1<<6)
115
#define POSITIVE (1<<7)
116
#define REVERSE (1<<8)
117
#define SILENT (1<<9)
118
#define TIMEOUT (1<<10)
119
#define VERBOSE (1<<11)
120
121
#define NOW (unsigned long)time(NiL)
122
123
#define DEFAULT 10
124
125
#ifdef S_ISSOCK
126
#define FIFO(m) (S_ISFIFO(m)||S_ISSOCK(m))
127
#else
128
#define FIFO(m) S_ISFIFO(m)
129
#endif
130
131
struct Tail_s; typedef struct Tail_s Tail_t;
132
133
struct Tail_s
134
{
135
Tail_t* next;
136
char* name;
137
Sfio_t* sp;
138
Sfoff_t cur;
139
Sfoff_t end;
140
unsigned long expire;
141
long dev;
142
long ino;
143
int fifo;
144
};
145
146
static const char header_fmt[] = "\n==> %s <==\n";
147
148
/*
149
* if file is seekable, position file to tail location and return offset
150
* otherwise, return -1
151
*/
152
153
static Sfoff_t
154
tailpos(register Sfio_t* fp, register Sfoff_t number, int delim)
155
{
156
register size_t n;
157
register Sfoff_t offset;
158
register Sfoff_t first;
159
register Sfoff_t last;
160
register char* s;
161
register char* t;
162
struct stat st;
163
164
last = sfsize(fp);
165
if ((first = sfseek(fp, (Sfoff_t)0, SEEK_CUR)) < 0)
166
return last || fstat(sffileno(fp), &st) || st.st_size || FIFO(st.st_mode) ? -1 : 0;
167
if (delim < 0)
168
{
169
if ((offset = last - number) < first)
170
return first;
171
return offset;
172
}
173
for (;;)
174
{
175
if ((offset = last - SF_BUFSIZE) < first)
176
offset = first;
177
sfseek(fp, offset, SEEK_SET);
178
n = last - offset;
179
if (!(s = sfreserve(fp, n, SF_LOCKR)))
180
return -1;
181
t = s + n;
182
while (t > s)
183
if (*--t == delim && number-- <= 0)
184
{
185
sfread(fp, s, 0);
186
return offset + (t - s) + 1;
187
}
188
sfread(fp, s, 0);
189
if (offset == first)
190
break;
191
last = offset;
192
}
193
return first;
194
}
195
196
/*
197
* this code handles tail from a pipe without any size limits
198
*/
199
200
static void
201
pipetail(Sfio_t* infile, Sfio_t* outfile, Sfoff_t number, int delim)
202
{
203
register Sfio_t* out;
204
register Sfoff_t n;
205
register Sfoff_t nleft = number;
206
register size_t a = 2 * SF_BUFSIZE;
207
register int fno = 0;
208
Sfoff_t offset[2];
209
Sfio_t* tmp[2];
210
211
if (delim < 0 && a > number)
212
a = number;
213
out = tmp[0] = sftmp(a);
214
tmp[1] = sftmp(a);
215
offset[0] = offset[1] = 0;
216
while ((n = sfmove(infile, out, number, delim)) > 0)
217
{
218
offset[fno] = sftell(out);
219
if ((nleft -= n) <= 0)
220
{
221
out = tmp[fno= !fno];
222
sfseek(out, (Sfoff_t)0, SEEK_SET);
223
nleft = number;
224
}
225
}
226
if (nleft == number)
227
{
228
offset[fno] = 0;
229
fno= !fno;
230
}
231
sfseek(tmp[0], (Sfoff_t)0, SEEK_SET);
232
233
/*
234
* see whether both files are needed
235
*/
236
237
if (offset[fno])
238
{
239
sfseek(tmp[1], (Sfoff_t)0, SEEK_SET);
240
if ((n = number - nleft) > 0)
241
sfmove(tmp[!fno], NiL, n, delim);
242
if ((n = offset[!fno] - sftell(tmp[!fno])) > 0)
243
sfmove(tmp[!fno], outfile, n, -1);
244
}
245
else
246
fno = !fno;
247
sfmove(tmp[fno], outfile, offset[fno], -1);
248
sfclose(tmp[0]);
249
sfclose(tmp[1]);
250
}
251
252
/*
253
* (re)initialize a tail stream
254
*/
255
256
static int
257
init(Tail_t* tp, Sfoff_t number, int delim, int flags, const char** format)
258
{
259
Sfoff_t offset;
260
Sfio_t* op;
261
struct stat st;
262
263
tp->fifo = 0;
264
if (tp->sp)
265
{
266
offset = 0;
267
if (tp->sp == sfstdin)
268
tp->sp = 0;
269
}
270
else
271
offset = 1;
272
if (!tp->name || streq(tp->name, "-"))
273
{
274
tp->name = "/dev/stdin";
275
tp->sp = sfstdin;
276
}
277
else if (!(tp->sp = sfopen(tp->sp, tp->name, "r")))
278
{
279
error(ERROR_system(0), "%s: cannot open", tp->name);
280
return -1;
281
}
282
sfset(tp->sp, SF_SHARE, 0);
283
if (offset)
284
{
285
if (number < 0 || !number && (flags & POSITIVE))
286
{
287
sfset(tp->sp, SF_SHARE, !(flags & FOLLOW));
288
if (number < -1)
289
{
290
sfmove(tp->sp, NiL, -number - 1, delim);
291
offset = sfseek(tp->sp, (Sfoff_t)0, SEEK_CUR);
292
}
293
else
294
offset = 0;
295
}
296
else if ((offset = tailpos(tp->sp, number, delim)) >= 0)
297
sfseek(tp->sp, offset, SEEK_SET);
298
else if (fstat(sffileno(tp->sp), &st))
299
{
300
error(ERROR_system(0), "%s: cannot stat", tp->name);
301
goto bad;
302
}
303
else if (!FIFO(st.st_mode))
304
{
305
error(ERROR_SYSTEM|2, "%s: cannot position file to tail", tp->name);
306
goto bad;
307
}
308
else
309
{
310
tp->fifo = 1;
311
if (flags & (HEADERS|VERBOSE))
312
{
313
sfprintf(sfstdout, *format, tp->name);
314
*format = header_fmt;
315
}
316
op = (flags & REVERSE) ? sftmp(4*SF_BUFSIZE) : sfstdout;
317
pipetail(tp->sp ? tp->sp : sfstdin, op, number, delim);
318
if (flags & REVERSE)
319
{
320
sfseek(op, (Sfoff_t)0, SEEK_SET);
321
rev_line(op, sfstdout, (Sfoff_t)0);
322
sfclose(op);
323
}
324
}
325
}
326
tp->cur = tp->end = offset;
327
if (flags & LOG)
328
{
329
if (fstat(sffileno(tp->sp), &st))
330
{
331
error(ERROR_system(0), "%s: cannot stat", tp->name);
332
goto bad;
333
}
334
tp->dev = st.st_dev;
335
tp->ino = st.st_ino;
336
}
337
return 0;
338
bad:
339
if (tp->sp != sfstdin)
340
sfclose(tp->sp);
341
tp->sp = 0;
342
return -1;
343
}
344
345
/*
346
* convert number with validity diagnostics
347
*/
348
349
static intmax_t
350
num(register const char* s, char** e, int* f, int o)
351
{
352
intmax_t number;
353
char* t;
354
int c;
355
356
*f &= ~(ERROR|NEGATIVE|POSITIVE);
357
if ((c = *s) == '-')
358
{
359
*f |= NEGATIVE;
360
s++;
361
}
362
else if (c == '+')
363
{
364
*f |= POSITIVE;
365
s++;
366
}
367
while (*s == '0' && isdigit(*(s + 1)))
368
s++;
369
errno = 0;
370
number = strtonll(s, &t, NiL, 0);
371
if (t == s)
372
number = DEFAULT;
373
if (o && *t)
374
{
375
number = 0;
376
*f |= ERROR;
377
error(2, "-%c: %s: invalid numeric argument -- unknown suffix", o, s);
378
}
379
else if (errno)
380
{
381
*f |= ERROR;
382
if (o)
383
error(2, "-%c: %s: invalid numeric argument -- out of range", o, s);
384
else
385
error(2, "%s: invalid numeric argument -- out of range", s);
386
}
387
else
388
{
389
*f |= COUNT;
390
if (t > s && isalpha(*(t - 1)))
391
*f &= ~LINES;
392
if (c == '-')
393
number = -number;
394
}
395
if (e)
396
*e = t;
397
return number;
398
}
399
400
int
401
b_tail(int argc, char** argv, Shbltin_t* context)
402
{
403
register Sfio_t* ip;
404
register int n;
405
register int i;
406
int delim;
407
int flags = HEADERS|LINES;
408
int blocks = 0;
409
char* s;
410
char* t;
411
char* r;
412
char* file;
413
Sfoff_t offset;
414
Sfoff_t number = DEFAULT;
415
unsigned long timeout = 0;
416
struct stat st;
417
const char* format = header_fmt+1;
418
ssize_t z;
419
ssize_t w;
420
Sfio_t* op;
421
register Tail_t* fp;
422
register Tail_t* pp;
423
register Tail_t* hp;
424
Tail_t* files;
425
Tv_t tv;
426
427
cmdinit(argc, argv, context, ERROR_CATALOG, ERROR_NOTIFY);
428
for (;;)
429
{
430
switch (n = optget(argv, usage))
431
{
432
case 0:
433
if (!(flags & FOLLOW) && argv[opt_info.index] && (argv[opt_info.index][0] == '-' || argv[opt_info.index][0] == '+') && !argv[opt_info.index][1])
434
{
435
number = argv[opt_info.index][0] == '-' ? 10 : -10;
436
flags |= LINES;
437
opt_info.index++;
438
continue;
439
}
440
break;
441
case 'b':
442
blocks = 512;
443
flags &= ~LINES;
444
if (opt_info.option[0] == '+')
445
number = -number;
446
continue;
447
case 'c':
448
flags &= ~LINES;
449
if (opt_info.arg == argv[opt_info.index - 1])
450
{
451
strtol(opt_info.arg, &s, 10);
452
if (*s)
453
{
454
opt_info.index--;
455
t = "";
456
goto suffix;
457
}
458
}
459
else if (opt_info.arg && isalpha(*opt_info.arg))
460
{
461
t = opt_info.arg;
462
goto suffix;
463
}
464
/*FALLTHROUGH*/
465
case 'n':
466
flags |= COUNT;
467
if (s = opt_info.arg)
468
number = num(s, &s, &flags, n);
469
else
470
{
471
number = DEFAULT;
472
flags &= ~(ERROR|NEGATIVE|POSITIVE);
473
s = "";
474
}
475
if (n != 'n' && s && isalpha(*s))
476
{
477
t = s;
478
goto suffix;
479
}
480
if (flags & ERROR)
481
continue;
482
if (flags & (NEGATIVE|POSITIVE))
483
number = -number;
484
if (opt_info.option[0]=='+')
485
number = -number;
486
continue;
487
case 'f':
488
flags |= FOLLOW;
489
continue;
490
case 'h':
491
if (opt_info.num)
492
flags |= HEADERS;
493
else
494
flags &= ~HEADERS;
495
continue;
496
case 'l':
497
flags |= LINES;
498
if (opt_info.option[0] == '+')
499
number = -number;
500
continue;
501
case 'L':
502
flags |= LOG;
503
continue;
504
case 'q':
505
flags &= ~HEADERS;
506
continue;
507
case 'r':
508
flags |= REVERSE;
509
continue;
510
case 's':
511
flags |= SILENT;
512
continue;
513
case 't':
514
flags |= TIMEOUT;
515
timeout = strelapsed(opt_info.arg, &s, 1);
516
if (*s)
517
error(ERROR_exit(1), "%s: invalid elapsed time [%s]", opt_info.arg, s);
518
continue;
519
case 'v':
520
flags |= VERBOSE;
521
continue;
522
case ':':
523
/* handle old style arguments */
524
if (!(r = argv[opt_info.index]) || !opt_info.offset)
525
{
526
error(2, "%s", opt_info.arg);
527
break;
528
}
529
s = r + opt_info.offset - 1;
530
if (i = *(s - 1) == '-' || *(s - 1) == '+')
531
s--;
532
if ((number = num(s, &t, &flags, 0)) && i)
533
number = -number;
534
goto compatibility;
535
suffix:
536
r = 0;
537
if (opt_info.option[0] == '+')
538
number = -number;
539
compatibility:
540
for (;;)
541
{
542
switch (*t++)
543
{
544
case 0:
545
if (r)
546
opt_info.offset = t - r - 1;
547
break;
548
case 'c':
549
flags &= ~LINES;
550
continue;
551
case 'f':
552
flags |= FOLLOW;
553
continue;
554
case 'l':
555
flags |= LINES;
556
continue;
557
case 'r':
558
flags |= REVERSE;
559
continue;
560
default:
561
error(2, "%s: invalid suffix", t - 1);
562
if (r)
563
opt_info.offset = strlen(r);
564
break;
565
}
566
break;
567
}
568
continue;
569
case '?':
570
error(ERROR_usage(2), "%s", opt_info.arg);
571
break;
572
}
573
break;
574
}
575
argv += opt_info.index;
576
if (!*argv)
577
{
578
flags &= ~HEADERS;
579
if (fstat(0, &st))
580
error(ERROR_system(0), "/dev/stdin: cannot stat");
581
else if (FIFO(st.st_mode))
582
flags &= ~FOLLOW;
583
}
584
else if (!*(argv + 1))
585
flags &= ~HEADERS;
586
delim = (flags & LINES) ? '\n' : -1;
587
if (blocks)
588
number *= blocks;
589
if (flags & REVERSE)
590
{
591
if (delim < 0)
592
error(2, "--reverse requires line mode");
593
if (!(flags & COUNT))
594
number = -1;
595
flags &= ~FOLLOW;
596
}
597
if ((flags & (FOLLOW|TIMEOUT)) == TIMEOUT)
598
{
599
flags &= ~TIMEOUT;
600
timeout = 0;
601
error(ERROR_warn(0), "--timeout ignored for --noforever");
602
}
603
if ((flags & (LOG|TIMEOUT)) == LOG)
604
{
605
flags &= ~LOG;
606
error(ERROR_warn(0), "--log ignored for --notimeout");
607
}
608
if (error_info.errors)
609
error(ERROR_usage(2), "%s", optusage(NiL));
610
if (flags & FOLLOW)
611
{
612
if (!(fp = (Tail_t*)stakalloc(argc * sizeof(Tail_t))))
613
error(ERROR_system(1), "out of space");
614
files = 0;
615
s = *argv;
616
do
617
{
618
fp->name = s;
619
fp->sp = 0;
620
if (!init(fp, number, delim, flags, &format))
621
{
622
fp->expire = timeout ? (NOW + timeout + 1) : 0;
623
if (files)
624
pp->next = fp;
625
else
626
files = fp;
627
pp = fp;
628
fp++;
629
}
630
} while (s && (s = *++argv));
631
if (!files)
632
return error_info.errors != 0;
633
pp->next = 0;
634
hp = 0;
635
n = 1;
636
tv.tv_sec = 1;
637
tv.tv_nsec = 0;
638
while (fp = files)
639
{
640
if (n)
641
n = 0;
642
else if (sh_checksig(context) || tvsleep(&tv, NiL) && sh_checksig(context))
643
{
644
error_info.errors++;
645
break;
646
}
647
pp = 0;
648
while (fp)
649
{
650
if (fstat(sffileno(fp->sp), &st))
651
error(ERROR_system(0), "%s: cannot stat", fp->name);
652
else if (fp->fifo || fp->end < st.st_size)
653
{
654
n = 1;
655
if (timeout)
656
fp->expire = NOW + timeout;
657
z = fp->fifo ? SF_UNBOUND : st.st_size - fp->cur;
658
i = 0;
659
if ((s = sfreserve(fp->sp, z, SF_LOCKR)) || (z = sfvalue(fp->sp)) && (s = sfreserve(fp->sp, z, SF_LOCKR)) && (i = 1))
660
{
661
z = sfvalue(fp->sp);
662
for (r = s + z; r > s && *(r - 1) != '\n'; r--);
663
if ((w = r - s) || i && (w = z))
664
{
665
if ((flags & (HEADERS|VERBOSE)) && hp != fp)
666
{
667
hp = fp;
668
sfprintf(sfstdout, format, fp->name);
669
format = header_fmt;
670
}
671
fp->cur += w;
672
sfwrite(sfstdout, s, w);
673
}
674
else
675
w = 0;
676
sfread(fp->sp, s, w);
677
fp->end += w;
678
}
679
goto next;
680
}
681
else if (!timeout || fp->expire > NOW)
682
goto next;
683
else
684
{
685
if (flags & LOG)
686
{
687
i = 3;
688
while (--i && stat(fp->name, &st))
689
if (sh_checksig(context))
690
{
691
error_info.errors++;
692
goto done;
693
}
694
else
695
tvsleep(&tv, NiL);
696
if (i && (fp->dev != st.st_dev || fp->ino != st.st_ino) && !init(fp, 0, 0, flags, &format))
697
{
698
if (!(flags & SILENT))
699
error(ERROR_warn(0), "%s: log file change", fp->name);
700
fp->expire = NOW + timeout;
701
goto next;
702
}
703
}
704
if (!(flags & SILENT))
705
error(ERROR_warn(0), "%s: %s timeout", fp->name, fmtelapsed(timeout, 1));
706
}
707
if (fp->sp && fp->sp != sfstdin)
708
sfclose(fp->sp);
709
if (pp)
710
pp = pp->next = fp->next;
711
else
712
files = files->next;
713
fp = fp->next;
714
continue;
715
next:
716
pp = fp;
717
fp = fp->next;
718
}
719
if (sfsync(sfstdout))
720
error(ERROR_system(1), "write error");
721
}
722
done:
723
for (fp = files; fp; fp = fp->next)
724
if (fp->sp && fp->sp != sfstdin)
725
sfclose(fp->sp);
726
}
727
else
728
{
729
if (file = *argv)
730
argv++;
731
do
732
{
733
if (!file || streq(file, "-"))
734
{
735
file = "/dev/stdin";
736
ip = sfstdin;
737
}
738
else if (!(ip = sfopen(NiL, file, "r")))
739
{
740
error(ERROR_system(0), "%s: cannot open", file);
741
continue;
742
}
743
if (flags & (HEADERS|VERBOSE))
744
{
745
sfprintf(sfstdout, format, file);
746
format = header_fmt;
747
}
748
if (number < 0 || !number && (flags & POSITIVE))
749
{
750
sfset(ip, SF_SHARE, 1);
751
if (number < -1)
752
sfmove(ip, NiL, -number - 1, delim);
753
if (flags & REVERSE)
754
rev_line(ip, sfstdout, sfseek(ip, (Sfoff_t)0, SEEK_CUR));
755
else
756
sfmove(ip, sfstdout, SF_UNBOUND, -1);
757
}
758
else
759
{
760
sfset(ip, SF_SHARE, 0);
761
if ((offset = tailpos(ip, number, delim)) >= 0)
762
{
763
if (flags & REVERSE)
764
rev_line(ip, sfstdout, offset);
765
else
766
{
767
sfseek(ip, offset, SEEK_SET);
768
sfmove(ip, sfstdout, SF_UNBOUND, -1);
769
}
770
}
771
else
772
{
773
op = (flags & REVERSE) ? sftmp(4*SF_BUFSIZE) : sfstdout;
774
pipetail(ip, op, number, delim);
775
if (flags & REVERSE)
776
{
777
sfseek(op, (Sfoff_t)0, SEEK_SET);
778
rev_line(op, sfstdout, (Sfoff_t)0);
779
sfclose(op);
780
}
781
flags = 0;
782
}
783
}
784
if (ip != sfstdin)
785
sfclose(ip);
786
} while ((file = *argv++) && !sh_checksig(context));
787
}
788
return error_info.errors != 0;
789
}
790
791