master xplshn/aruu / cmd / posix / tail.c
  1/* See LICENSE file for copyright and license details. */
  2
  3#include <sys/stat.h>
  4
  5#include <fcntl.h>
  6#include <stdint.h>
  7#include <stdio.h>
  8#include <stdlib.h>
  9#include <string.h>
 10#include <unistd.h>
 11
 12#include "utf.h"
 13#include "util.h"
 14
 15static char mode = 'n';
 16
 17static int
 18dropinit(int fd, const char *fname, size_t count)
 19{
 20  Rune    r;
 21  char    buf[BUFSIZ], *p;
 22  ssize_t n;
 23  int     nr;
 24
 25  if (count < 2)
 26    goto copy;
 27  count--; /* numbering starts at 1 */
 28  while (count && (n = read(fd, buf, sizeof(buf))) > 0) {
 29    switch (mode) {
 30        // ?man -n: print line numbers or counts
 31      case 'n': /* lines */
 32        for (p = buf; count && n > 0; p++, n--) {
 33          if (*p == '\n')
 34            count--;
 35        }
 36        break;
 37        // ?man -c: print count or perform stdout action
 38      case 'c': /* bytes */
 39        if (count > (size_t)n) {
 40          count -= n;
 41        } else {
 42          p = buf + count;
 43          n -= count;
 44          count = 0;
 45        }
 46        break;
 47        // ?man -m: specify mode or limit
 48      case 'm': /* runes */
 49        for (p = buf; count && n > 0; p += nr, n -= nr, count--) {
 50          nr = charntorune(&r, p, n);
 51          if (!nr) {
 52            /* we don't have a full rune, move
 53             * remaining data to beginning and read
 54             * again */
 55            memmove(buf, p, n);
 56            break;
 57          }
 58        }
 59        break;
 60    }
 61  }
 62  if (count) {
 63    if (n < 0)
 64      weprintf("read %s:", fname);
 65    if (n <= 0)
 66      return n;
 67  }
 68
 69  /* write the rest of the buffer */
 70  if (writeall(1, p, n) < 0)
 71    eprintf("write:");
 72copy:
 73  switch (concat(fd, fname, 1, "<stdout>")) {
 74    case -1: /* read error */
 75      return -1;
 76    case -2: /* write error */
 77      exit(1);
 78    default:
 79      return 0;
 80  }
 81}
 82
 83static int
 84taketail(int fd, const char *fname, size_t count)
 85{
 86  static char  *buf  = NULL;
 87  static size_t size = 0;
 88  char         *p;
 89  size_t        len = 0, left;
 90  ssize_t       n;
 91
 92  if (!count)
 93    return 0;
 94  for (;;) {
 95    if (len + BUFSIZ > size) {
 96      /* make sure we have at least BUFSIZ to read */
 97      size += 2 * BUFSIZ;
 98      buf = erealloc(buf, size);
 99    }
100    n = read(fd, buf + len, size - len);
101    if (n < 0) {
102      weprintf("read %s:", fname);
103      return -1;
104    }
105    if (n == 0)
106      break;
107    len += n;
108    switch (mode) {
109        // ?man -n: print line numbers or counts
110      case 'n': /* lines */
111        /* ignore the last character; if it is a newline, it
112         * ends the last line */
113        for (p = buf + len - 2, left = count; p >= buf; p--) {
114          if (*p != '\n')
115            continue;
116          left--;
117          if (!left) {
118            p++;
119            break;
120          }
121        }
122        break;
123        // ?man -c: print count or perform stdout action
124      case 'c': /* bytes */
125        p = count < len ? buf + len - count : buf;
126        break;
127        // ?man -m: specify mode or limit
128      case 'm': /* runes */
129        for (p = buf + len - 1, left = count; p >= buf; p--) {
130          /* skip utf-8 continuation bytes */
131          if (UTF8_POINT(*p))
132            continue;
133          left--;
134          if (!left)
135            break;
136        }
137        break;
138    }
139    if (p > buf) {
140      len -= p - buf;
141      memmove(buf, p, len);
142    }
143  }
144  if (writeall(1, buf, len) < 0)
145    eprintf("write:");
146  return 0;
147}
148
149static void
150usage(void)
151{
152  eprintf("usage: %s [-f] [-c num | -m num | -n num | -num] [file ...]\n", argv0);
153}
154
155// ?man tail: output last part of files
156// ?man arguments: file ...
157// ?man print the last lines or bytes of files
158int
159main(int argc, char *argv[])
160{
161  struct stat st1, st2;
162  int         fd;
163  size_t      n     = 10;
164  int         fflag = 0, ret = 0, newline = 0, many = 0;
165  char       *numstr;
166  int (*tail)(int, const char *, size_t) = taketail;
167
168  ARGBEGIN
169  {
170    // ?man -f: force the operation
171    case 'f':
172      fflag = 1;
173      break;
174    // ?man -c: print count or perform stdout action
175    case 'c':
176    // ?man -m: specify mode or limit
177    case 'm':
178    // ?man -n:num: print line numbers or counts
179    case 'n':
180      mode   = ARGC();
181      numstr = EARGF(usage());
182      n =
183          MIN((unsigned long long)llabs(estrtonum(
184                  numstr,
185                  LLONG_MIN + 1,
186                  MIN((unsigned long long)LLONG_MAX, (unsigned long long)SIZE_MAX)
187              )),
188              (unsigned long long)SIZE_MAX);
189      if (strchr(numstr, '+'))
190        tail = dropinit;
191      break;
192    // ?man ARGNUM: specify RGNUM option
193    ARGNUM:
194      n = ARGNUMF();
195      break;
196    default:
197      usage();
198  }
199  ARGEND
200
201  if (!argc) {
202    if (tail(0, "<stdin>", n) < 0)
203      ret = 1;
204  } else {
205    if ((many = argc > 1) && fflag)
206      usage();
207    for (newline = 0; *argv; argc--, argv++) {
208      if (!strcmp(*argv, "-")) {
209        *argv = "<stdin>";
210        fd    = 0;
211      } else if ((fd = open(*argv, O_RDONLY)) < 0) {
212        weprintf("open %s:", *argv);
213        ret = 1;
214        continue;
215      }
216      if (many)
217        printf("%s==> %s <==\n", newline ? "\n" : "", *argv);
218      if (fstat(fd, &st1) < 0)
219        eprintf("fstat %s:", *argv);
220      if (!(S_ISFIFO(st1.st_mode) || S_ISREG(st1.st_mode)))
221        fflag = 0;
222      newline = 1;
223      if (tail(fd, *argv, n) < 0) {
224        ret   = 1;
225        fflag = 0;
226      }
227
228      if (!fflag) {
229        if (fd != 0)
230          close(fd);
231        continue;
232      }
233      for (;;) {
234        if (concat(fd, *argv, 1, "<stdout>") < 0)
235          exit(1);
236        if (fstat(fd, &st2) < 0)
237          eprintf("fstat %s:", *argv);
238        if (st2.st_size < st1.st_size) {
239          fprintf(stderr, "%s: file truncated\n", *argv);
240          if (lseek(fd, SEEK_SET, 0) < 0)
241            eprintf("lseek:");
242        }
243        st1 = st2;
244        sleep(1);
245      }
246    }
247  }
248
249  return ret;
250}