chiark / gitweb /
close idle connections and spot unresponsive ones
[innduct.git] / backends / shrinkfile.c
1 /*  $Id: shrinkfile.c 6135 2003-01-19 01:15:40Z rra $
2 **
3 **  Shrink files on line boundaries.
4 **
5 **  Written by Landon Curt Noll <chongo@toad.com>, and placed in the
6 **  public domain.  Rewritten for INN by Rich Salz.
7 **
8 **  Usage:
9 **      shrinkfile [-n] [-s size [-m maxsize]] [-v] file...
10 **      -n              No writes, exit 0 if any file is too large, 1 otherwise
11 **      -s size         Truncation size (0 default); suffix may be k, m,
12 **                      or g to scale.  Must not be larger than 2^31 - 1.
13 **      -m maxsize      Maximum size allowed before truncation.  If maxsize
14 **                      <= size, then it is reset to size.  Default == size.
15 **      -v              Print status line.
16 **
17 **  Files will be shrunk an end of line boundary.  In no case will the
18 **  file be longer than size bytes if it was longer than maxsize bytes.  
19 **  If the first line is longer than the absolute value of size, the file 
20 **  will be truncated to zero length.
21 **
22 **  The -n flag may be used to determine of any file is too large.  No
23 **  files will be altered in this mode.
24 */
25
26 #include "config.h"
27 #include "clibrary.h"
28 #include <ctype.h>
29 #include <errno.h>
30 #include <fcntl.h>
31 #include <sys/stat.h>
32
33 #include "inn/innconf.h"
34 #include "inn/messages.h"
35 #include "libinn.h"
36
37 #define MAX_SIZE        0x7fffffffUL
38
39
40 /*
41 **  Open a safe unique temporary file that will go away when closed.
42 */
43 static FILE *
44 OpenTemp(void)
45 {
46     FILE        *F;
47     char        *filename;
48     int         fd;
49
50     filename = concatpath(innconf->pathtmp, "shrinkXXXXXX");
51     fd = mkstemp(filename);
52     if (fd < 0)
53         sysdie("cannot create temporary file");
54     F = fdopen(fd, "w+");
55     if (F == NULL)
56         sysdie("cannot fdopen %s", filename);
57     unlink(filename);
58     free(filename);
59     return F;
60 }
61
62
63 /*
64 **  Does file end with \n?  Assume it does on I/O error, to avoid doing I/O.
65 */
66 static int
67 EndsWithNewline(FILE *F)
68 {
69     int         c;
70
71     if (fseeko(F, 1, SEEK_END) < 0) {
72         syswarn("cannot seek to end of file");
73         return true;
74     }
75
76     /* return the actual character or EOF */
77     if ((c = fgetc(F)) == EOF) {
78         if (ferror(F))
79             syswarn("cannot read last byte");
80         return true;
81     }
82     return c == '\n';
83 }
84
85
86 /*
87 **  Add a newline to location of a file.
88 */
89 static bool
90 AppendNewline(char *name)
91 {
92     FILE        *F;
93
94     if ((F = xfopena(name)) == NULL) {
95         syswarn("cannot add newline");
96         return false;
97     }
98
99     if (fputc('\n', F) == EOF
100      || fflush(F) == EOF
101      || ferror(F)
102      || fclose(F) == EOF) {
103         syswarn("cannot add newline");
104         return false;
105     }
106
107     return true;
108 }
109
110 /*
111 **  Just check if it is too big
112 */
113 static bool
114 TooBig(FILE *F, off_t maxsize)
115 {
116     struct stat Sb;
117
118     /* Get the file's size. */
119     if (fstat((int)fileno(F), &Sb) < 0) {
120         syswarn("cannot fstat");
121         return false;
122     }
123
124     /* return true if too large */
125     return (maxsize > Sb.st_size ? false : true);
126 }
127
128 /*
129 **  This routine does all the work.
130 */
131 static bool
132 Process(FILE *F, char *name, off_t size, off_t maxsize, bool *Changedp)
133 {
134     off_t       len;
135     FILE        *tmp;
136     struct stat Sb;
137     char        buff[BUFSIZ + 1];
138     int         c;
139     size_t      i;
140     bool        err;
141
142     /* Get the file's size. */
143     if (fstat((int)fileno(F), &Sb) < 0) {
144         syswarn("cannot fstat");
145         return false;
146     }
147     len = Sb.st_size;
148
149     /* Process a zero size request. */
150     if (size == 0 && len > maxsize) {
151         if (len > 0) {
152             fclose(F);
153             if ((F = fopen(name, "w")) == NULL) {
154                 syswarn("cannot overwrite");
155                 return false;
156             }
157             fclose(F);
158             *Changedp = true;
159         }
160         return true;
161     }
162
163     /* See if already small enough. */
164     if (len <= maxsize) {
165         /* Newline already present? */
166         if (EndsWithNewline(F)) {
167             fclose(F);
168             return true;
169         }
170
171         /* No newline, add it if it fits. */
172         if (len < size - 1) {
173             fclose(F);
174             *Changedp = true;
175             return AppendNewline(name);
176         }
177     }
178     else if (!EndsWithNewline(F)) {
179         if (!AppendNewline(name)) {
180             fclose(F);
181             return false;
182         }
183     }
184
185     /* We now have a file that ends with a newline that is bigger than
186      * we want.  Starting from {size} bytes from end, move forward
187      * until we get a newline. */
188     if (fseeko(F, -size, SEEK_END) < 0) {
189         syswarn("cannot fseeko");
190         fclose(F);
191         return false;
192     }
193
194     while ((c = getc(F)) != '\n')
195         if (c == EOF) {
196             syswarn("cannot read");
197             fclose(F);
198             return false;
199         }
200
201     /* Copy rest of file to temp. */
202     tmp = OpenTemp();
203     err = false;
204     while ((i = fread(buff, 1, sizeof buff, F)) > 0)
205         if (fwrite(buff, 1, i, tmp) != i) {
206             err = true;
207             break;
208         }
209     if (err) {
210         syswarn("cannot copy to temporary file");
211         fclose(F);
212         fclose(tmp);
213         return false;
214     }
215
216     /* Now copy temp back to original file. */
217     fclose(F);
218     if ((F = fopen(name, "w")) == NULL) {
219         syswarn("cannot overwrite file");
220         fclose(tmp);
221         return false;
222     }
223     fseeko(tmp, 0, SEEK_SET);
224
225     while ((i = fread(buff, 1, sizeof buff, tmp)) > 0)
226         if (fwrite(buff, 1, i, F) != i) {
227             err = true;
228             break;
229         }
230     if (err) {
231         syswarn("cannot overwrite file");
232         fclose(F);
233         fclose(tmp);
234         return false;
235     }
236
237     fclose(F);
238     fclose(tmp);
239     *Changedp = true;
240     return true;
241 }
242
243
244 /*
245 **  Convert size argument to numeric value.  Return -1 on error.
246 */
247 static off_t
248 ParseSize(char *p)
249 {
250     off_t       scale;
251     unsigned long       str_num;
252     char        *q;
253
254     /* Skip leading spaces */
255     while (ISWHITE(*p))
256         p++;
257     if (*p == '\0')
258         return -1;
259
260     /* determine the scaling factor */
261     q = &p[strlen(p) - 1];
262     switch (*q) {
263     default:
264         return -1;
265     case '0': case '1': case '2': case '3': case '4':
266     case '5': case '6': case '7': case '8': case '9':
267         scale = 1;
268         break;
269     case 'k': case 'K':
270         scale = 1024;
271         *q = '\0';
272         break;
273     case 'm': case 'M':
274         scale = 1024 * 1024;
275         *q = '\0';
276         break;
277     case 'g': case 'G':
278         scale = 1024 * 1024 * 1024;
279         *q = '\0';
280         break;
281     }
282
283     /* Convert string to number. */
284     if (sscanf(p, "%lud", &str_num) != 1)
285         return -1;
286     if (str_num > MAX_SIZE / scale)
287         die("size is too big");
288
289     return scale * str_num;
290 }
291
292
293 /*
294 **  Print usage message and exit.
295 */
296 static void
297 Usage(void)
298 {
299     fprintf(stderr,
300             "Usage: shrinkfile [-n] [ -m maxsize ] [-s size] [-v] file...");
301     exit(1);
302 }
303
304
305 int
306 main(int ac, char *av[])
307 {
308     bool        Changed;
309     bool        Verbose;
310     bool        no_op;
311     FILE        *F;
312     char        *p;
313     int         i;
314     off_t       size = 0;
315     off_t       maxsize = 0;
316
317     /* First thing, set up our identity. */
318     message_program_name = "shrinkfile";
319
320     /* Set defaults. */
321     Verbose = false;
322     no_op = false;
323     umask(NEWSUMASK);
324
325     if (!innconf_read(NULL))
326         exit(1);
327
328     /* Parse JCL. */
329     while ((i = getopt(ac, av, "m:s:vn")) != EOF)
330         switch (i) {
331         default:
332             Usage();
333             /* NOTREACHED */
334         case 'n':
335             no_op = true;
336             break;
337         case 'm':
338             if ((maxsize = ParseSize(optarg)) < 0)
339                 Usage();
340             break;
341         case 's':
342             if ((size = ParseSize(optarg)) < 0)
343                 Usage();
344             break;
345         case 'v':
346             Verbose = true;
347             break;
348         }
349     if (maxsize < size) {
350         maxsize = size;
351     }
352     ac -= optind;
353     av += optind;
354     if (ac == 0)
355         Usage();
356
357     while ((p = *av++) != NULL) {
358         if ((F = fopen(p, "r")) == NULL) {
359             syswarn("cannot open %s", p);
360             continue;
361         }
362
363         /* -n (no_op) or normal processing */
364         if (no_op) {
365
366             /* check if too big and exit zero if it is */
367             if (TooBig(F, maxsize)) {
368                 if (Verbose)
369                     notice("%s is too large", p);
370                 exit(0);
371                 /* NOTREACHED */
372             }
373
374         /* no -n, do some real work */
375         } else {
376             Changed = false;
377             if (!Process(F, p, size, maxsize, &Changed))
378                 syswarn("cannot shrink %s", p);
379             else if (Verbose && Changed)
380                 notice("shrunk %s", p);
381         }
382     }
383     if (no_op && Verbose) {
384         notice("did not find a file that was too large");
385     }
386
387     /* if -n, then exit non-zero to indicate no file too big */
388     exit(no_op ? 1 : 0);
389     /* NOTREACHED */
390 }