Hello Teddy78,
Thanks for the question. Here's an answer that should do just fine. It
follows the suggested hints plus takes care of a few special cases:
- input file is empty (no output)
- invalid input parameter (indicate & print usage information)
- print usage information if no arguments provided
It certainly doesn't do everything that the "real" tail program does,
but does match the output for all the test cases I tried. The program
follows at the end. It builds without error nor warning with gcc.
--Maniac
/*
* tail.c - print the last few lines of a file
* similar to tail(1) for Unix
*/
#include <sys/types.h>
#include <sys/uio.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
static char *myname;
static char buffer[BUFSIZ];
static void usage ()
{
fprintf(stderr, "Usage: %s [-#] [file ...]\n", myname);
exit(EXIT_FAILURE);
}
static void dotail (char * name,
int lines)
{
int fd;
off_t nowat;
ssize_t chars;
ssize_t endbuf;
fd = open(name, O_RDONLY, 0);
if (fd < 0) {
fprintf(stderr, "%s: cannot open %s\n", myname, name);
perror(myname);
exit(EXIT_FAILURE);
}
/* get a buffer at the end */
nowat = lseek(fd, -BUFSIZ, SEEK_END);
if (nowat == -1) {
fprintf(stderr, "%s: seek in %s failed\n", myname, name);
perror(myname);
(void)close(fd);
exit(EXIT_FAILURE);
}
else if (nowat < 0) {
nowat = lseek(fd, 0, SEEK_SET);
}
/* read the last few characters */
chars = read(fd, &buffer, BUFSIZ);
endbuf = chars; /* remember current end of buffer */
if (chars < 0) {
fprintf(stderr, "%s: read failed in %s\n", myname, name);
perror(myname);
(void)close(fd);
exit(EXIT_FAILURE);
}
else if (chars == 0) {
(void)close(fd);
}
else {
/* scan backwards the number of lines */
lines++; /* bump to avoid one off */
while (lines > 0) {
if (buffer[--chars] == '\n') {
lines--;
}
else if (chars == 0) {
if (nowat == 0)
break; /* can't go back farther */
else if (nowat < BUFSIZ) {
chars = nowat-1; /* only get a few characters */
nowat = lseek(fd, 0, SEEK_SET);
chars = read(fd, &buffer, chars);
endbuf = chars;
}
else {
nowat = lseek(fd, nowat-BUFSIZ, SEEK_SET);
chars = read(fd, &buffer, BUFSIZ);
endbuf = chars;
/* note at this point - we have a full buffer */
}
}
/* otherwise, we keep moving back */
}
if ((nowat != 0) || ((nowat == 0) & (chars > 0)))
chars = chars+1; /* bump past \n if needed */
/*
* at this point, buffer[chars+1] is the first line
* to be output & we need to output characters
* in the remaining part of the buffer & any later
* buffers
*/
chars = write(fileno(stdout), buffer+chars, endbuf-chars);
while ((chars = read(fd, &buffer, BUFSIZ)) > 0) {
(void)write(fileno(stdout), &buffer, chars);
}
(void)close(fd); /* all done with this file */
}
/* fprintf(stderr, "File %s for %d lines\n", name, lines); */
}
int main (int argc,
char * argv[])
{
int curarg;
int lines = 10;
myname = argv[0];
if (argc < 2) {
usage();
}
/*
* for each argument, set number of lines to dump or
* get a file to process
*/
for (curarg = 1; curarg < argc; curarg++) {
if (argv[curarg][0]=='-') {
if (sscanf(argv[curarg], "-%d", &lines) == 0) {
fprintf(stderr, "%s: don't support %s\n", myname, argv[curarg]);
usage();
}
}
else {
dotail(argv[curarg], lines);
}
}
return EXIT_SUCCESS;
} |