1 /*
2 *
3 *	Copyright (c) 2001 Fredrik Sjoholm <fredrik@sjoholm.com>
4 *	All rights reserved.
5 *	License: GPL - The GNU General Public License
6 *
7 */
8 
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <unistd.h>
12 #include <errno.h>
13 #include <sys/types.h>
14 #include <sys/stat.h>
15 #include <fcntl.h>
16 #include <signal.h>
17 #include <time.h>
18 #include <string.h>
19 
20 
21 
22 struct {
23   char* data;
24   int used;
25   int size;
26 } buf;
27 
28 struct FileInfo {
29   char *name;
30   int fd;
31   struct stat stat;	// remember info about file so we can know if it's been rotated
32 } out;
33 
34 struct {
35   char timestamp;	// { 0 | 1 }
36   char * time_format;
37   int max_len;
38   int zap_if_disk_full;
39 } conf;
40 
41 #define DEFAULT_TIME_FORMAT "%Y%m%d;%T: "
42 
43 volatile int gotHUP;
44 
45 void growbuf(int size);
46 int openfile (struct FileInfo* file);
47 int filechanged (struct FileInfo* file);
48 void catchHUP (int sig);
49 void handleHUP ();
50 int dotime (char* prepend_to);
51 
main(int argc,char ** argv)52 int main(int argc, char** argv)
53 {
54   char *eol, *pos;
55   int done = 0;
56   int opt = 0;
57   int totalwn = 0;
58   int disk_is_full = 0;
59 
60   // look for switches
61   conf.time_format = NULL;
62   while (++opt < argc) {
63     if (!strcmp(argv[opt], "-t")) {
64       conf.timestamp = 1;
65       conf.time_format = DEFAULT_TIME_FORMAT;
66     }
67     else if (!strcmp(argv[opt], "-T")) {
68       opt++;
69       if (opt < argc) {
70         conf.timestamp = 1;
71         conf.time_format = argv[opt];
72       }
73     }
74     else if (!strcmp(argv[opt], "-l")) {
75       opt++;
76       if (opt < argc) {
77 	conf.max_len = atoi (argv[opt]);
78       }
79     }
80     else if (!strcmp(argv[opt], "-z")) {
81       conf.zap_if_disk_full = 1;
82     }
83     else {
84       break;
85     }
86   }
87 
88   if (opt >= argc) {
89     fprintf (stderr,
90 	"Usage: pipeline| %s [options] {logfile|-}  # SIGHUP will reopen logfile (v1.8)\n"
91 	"	-t	     prepend each line with \"YYYYMMDD;HH:MM:SS: \"\n"
92 	"	-T <format>  prepend each line with specified strftime(3) format\n"
93 	"	-l <number>  log file length limit (force truncation)\n"
94 	"	-z           zap (truncate) log if disk gets full (default: grow buffer)\n",
95 	argv[0]);
96     exit(1);
97   }
98 
99   out.name = argv[opt];
100   openfile (&out);
101 
102   signal (SIGHUP, catchHUP);
103 
104   growbuf (4096);
105 
106   while (!done) {
107     int size;
108 
109     if (buf.used >= buf.size) growbuf (buf.size*2);
110 
111     size = read (0, buf.data+buf.used, buf.size-buf.used);
112     if (size > 0) {
113       buf.used += size;
114     }
115     else if (size == 0) {
116       done = 1;
117     }
118     else { }
119 
120     for (;;) {
121 
122       if (gotHUP) {
123 	handleHUP();
124 	signal (SIGHUP, catchHUP);
125 	gotHUP = 0;
126       }
127 
128       pos = buf.data;
129       eol = (char*) memchr(buf.data, '\n', buf.used);
130       if (eol == 0) break;
131       eol ++;
132 
133       if (conf.timestamp && !disk_is_full) {
134 	pos -= dotime (pos);	// don't prepend time again if we already did it before disk got full.
135       }
136 
137       while (pos < eol) {
138 	int wn = write(out.fd, pos, eol-pos);
139 	if (wn > 0) {
140 	  pos += wn;
141 	  totalwn += wn;
142 	  disk_is_full = 0;
143 	}
144 	else if (errno == ENOSPC) {
145 	  if (conf.zap_if_disk_full) {
146 	    char str[256];
147 	    if (ftruncate(out.fd,0)) {
148 	      fprintf(stderr, "truncating %s: %d %s\n", out.name, errno, strerror(errno));
149 	      exit(1);
150 	    }
151 	    write (out.fd, str, snprintf(str, sizeof(str), "Device full: truncating %s\n\n", out.name));
152 	  }
153 	  else {
154 	    disk_is_full = 1;
155 	    break;	// give up trying to write to disk until next input
156 	  }
157 	}
158 	else {
159 	  fprintf(stderr, "write %s: %d %s\n", out.name, errno, strerror(errno));
160 	  exit(1);
161 	}
162 	if (conf.max_len && (totalwn > conf.max_len)) {
163 	  char str[256];
164 	  if (ftruncate(out.fd,0)) {
165 	    fprintf(stderr, "truncating %s: %d %s\n", out.name, errno, strerror(errno));
166 	    exit(1);
167 	  }
168 	  write (out.fd, str, snprintf(str, sizeof(str), "File size limit: truncating %s\n\n", out.name));
169 	  totalwn = 0;
170 	}
171       }
172 
173       buf.used = buf.data + buf.used - pos;
174       memmove (buf.data, pos, buf.used);
175       if (disk_is_full) { break; }
176     }
177 
178   }
179 
180 
181 
182   exit(0);
183 }
184 
185 
186 
growbuf(int size)187 void growbuf (int size)
188 {
189   if (size > buf.size) {
190     // pad beginning of buffer with 100 bytes used by dotime()
191     // pad end with the same, so the memmove() at end of loop above is always safe
192     // even in case of a full disk while writing a prepended timestamp.
193     buf.data = (char*) realloc (buf.data ? buf.data-100 : 0, size+100*2) + 100;
194     buf.size = size;
195   }
196 }
197 
198 
199 
200 
openfile(struct FileInfo * file)201 int openfile (struct FileInfo* file)
202 {
203   int fd;
204   if (strcmp(file->name,"-") != 0) {
205 #ifdef O_LARGEFILE
206     fd = open (file->name, O_CREAT|O_APPEND|O_WRONLY, 0666);
207 #else
208     fd = open (file->name, O_CREAT|O_APPEND|O_WRONLY, 0666);
209 #endif
210   } else {
211     fd = 2;
212   }
213   if (fd < 0) {
214     fprintf (stderr, "open write %s: %s\n", file->name, strerror(errno));
215     exit(1);
216   }
217   file->fd = fd;
218   stat (file->name, &file->stat);	// update stat buffer
219   return fd;
220 }
221 
222 
reopenfile(struct FileInfo * file)223 int reopenfile (struct FileInfo* file)
224 {
225   if (strcmp(file->name,"-") != 0) {
226     close (file->fd);
227     return openfile (file);
228   } else {
229     return file->fd;
230   }
231 }
232 
233 
234 
235 
236 
filechanged(struct FileInfo * file)237 int filechanged (struct FileInfo* file)
238 {
239   struct stat stat2;
240 
241   if (strcmp(file->name,"-") == 0) { return 0; }
242 
243   if (stat (file->name, &stat2) < 0) {
244     return 1;	// file removed or something
245   }
246 
247   if (stat2.st_ino != file->stat.st_ino || stat2.st_dev != file->stat.st_dev) {
248     // file changed or was moved to a different device
249     return 1;
250   }
251 
252   return 0;
253 }
254 
255 
256 
catchHUP(int sig)257 void catchHUP (int sig)
258 {
259   gotHUP = 1;
260 }
261 
262 
263 
handleHUP()264 void handleHUP ()
265 {
266   char str[256];
267   if (filechanged(&out)) {
268     // only reopen file and print msg if the file on disk was changed or removed
269     write (out.fd, str, snprintf (str, sizeof(str), "Caught SIGHUP: Reopening %s (closed)\n\n", out.name));
270     reopenfile (&out);
271     write (out.fd, str, snprintf (str, sizeof(str), "Caught SIGHUP: Reopening %s (opened)\n\n", out.name));
272   }
273 }
274 
275 
276 
277 // format: "YYYYMMDD;HH.MM.SS: "
278 // make sure it's safe to prepend before the prepend_to pointer.
dotime(char * prepend_to)279 int dotime (char* prepend_to)
280 {
281   time_t now;
282   struct tm *t;
283   static char buf[100];
284   int len;
285 
286   time (&now);
287   t = localtime (&now);
288   len = strftime(buf, sizeof(buf), conf.time_format, t);
289   memcpy (prepend_to-len, buf, len);
290   return len;
291 }
292 
293 
294 
295 
296