/* $Id: shrinkfile.c 6135 2003-01-19 01:15:40Z rra $ ** ** Shrink files on line boundaries. ** ** Written by Landon Curt Noll , and placed in the ** public domain. Rewritten for INN by Rich Salz. ** ** Usage: ** shrinkfile [-n] [-s size [-m maxsize]] [-v] file... ** -n No writes, exit 0 if any file is too large, 1 otherwise ** -s size Truncation size (0 default); suffix may be k, m, ** or g to scale. Must not be larger than 2^31 - 1. ** -m maxsize Maximum size allowed before truncation. If maxsize ** <= size, then it is reset to size. Default == size. ** -v Print status line. ** ** Files will be shrunk an end of line boundary. In no case will the ** file be longer than size bytes if it was longer than maxsize bytes. ** If the first line is longer than the absolute value of size, the file ** will be truncated to zero length. ** ** The -n flag may be used to determine of any file is too large. No ** files will be altered in this mode. */ #include "config.h" #include "clibrary.h" #include #include #include #include #include "inn/innconf.h" #include "inn/messages.h" #include "libinn.h" #define MAX_SIZE 0x7fffffffUL /* ** Open a safe unique temporary file that will go away when closed. */ static FILE * OpenTemp(void) { FILE *F; char *filename; int fd; filename = concatpath(innconf->pathtmp, "shrinkXXXXXX"); fd = mkstemp(filename); if (fd < 0) sysdie("cannot create temporary file"); F = fdopen(fd, "w+"); if (F == NULL) sysdie("cannot fdopen %s", filename); unlink(filename); free(filename); return F; } /* ** Does file end with \n? Assume it does on I/O error, to avoid doing I/O. */ static int EndsWithNewline(FILE *F) { int c; if (fseeko(F, 1, SEEK_END) < 0) { syswarn("cannot seek to end of file"); return true; } /* return the actual character or EOF */ if ((c = fgetc(F)) == EOF) { if (ferror(F)) syswarn("cannot read last byte"); return true; } return c == '\n'; } /* ** Add a newline to location of a file. */ static bool AppendNewline(char *name) { FILE *F; if ((F = xfopena(name)) == NULL) { syswarn("cannot add newline"); return false; } if (fputc('\n', F) == EOF || fflush(F) == EOF || ferror(F) || fclose(F) == EOF) { syswarn("cannot add newline"); return false; } return true; } /* ** Just check if it is too big */ static bool TooBig(FILE *F, off_t maxsize) { struct stat Sb; /* Get the file's size. */ if (fstat((int)fileno(F), &Sb) < 0) { syswarn("cannot fstat"); return false; } /* return true if too large */ return (maxsize > Sb.st_size ? false : true); } /* ** This routine does all the work. */ static bool Process(FILE *F, char *name, off_t size, off_t maxsize, bool *Changedp) { off_t len; FILE *tmp; struct stat Sb; char buff[BUFSIZ + 1]; int c; size_t i; bool err; /* Get the file's size. */ if (fstat((int)fileno(F), &Sb) < 0) { syswarn("cannot fstat"); return false; } len = Sb.st_size; /* Process a zero size request. */ if (size == 0 && len > maxsize) { if (len > 0) { fclose(F); if ((F = fopen(name, "w")) == NULL) { syswarn("cannot overwrite"); return false; } fclose(F); *Changedp = true; } return true; } /* See if already small enough. */ if (len <= maxsize) { /* Newline already present? */ if (EndsWithNewline(F)) { fclose(F); return true; } /* No newline, add it if it fits. */ if (len < size - 1) { fclose(F); *Changedp = true; return AppendNewline(name); } } else if (!EndsWithNewline(F)) { if (!AppendNewline(name)) { fclose(F); return false; } } /* We now have a file that ends with a newline that is bigger than * we want. Starting from {size} bytes from end, move forward * until we get a newline. */ if (fseeko(F, -size, SEEK_END) < 0) { syswarn("cannot fseeko"); fclose(F); return false; } while ((c = getc(F)) != '\n') if (c == EOF) { syswarn("cannot read"); fclose(F); return false; } /* Copy rest of file to temp. */ tmp = OpenTemp(); err = false; while ((i = fread(buff, 1, sizeof buff, F)) > 0) if (fwrite(buff, 1, i, tmp) != i) { err = true; break; } if (err) { syswarn("cannot copy to temporary file"); fclose(F); fclose(tmp); return false; } /* Now copy temp back to original file. */ fclose(F); if ((F = fopen(name, "w")) == NULL) { syswarn("cannot overwrite file"); fclose(tmp); return false; } fseeko(tmp, 0, SEEK_SET); while ((i = fread(buff, 1, sizeof buff, tmp)) > 0) if (fwrite(buff, 1, i, F) != i) { err = true; break; } if (err) { syswarn("cannot overwrite file"); fclose(F); fclose(tmp); return false; } fclose(F); fclose(tmp); *Changedp = true; return true; } /* ** Convert size argument to numeric value. Return -1 on error. */ static off_t ParseSize(char *p) { off_t scale; unsigned long str_num; char *q; /* Skip leading spaces */ while (ISWHITE(*p)) p++; if (*p == '\0') return -1; /* determine the scaling factor */ q = &p[strlen(p) - 1]; switch (*q) { default: return -1; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': scale = 1; break; case 'k': case 'K': scale = 1024; *q = '\0'; break; case 'm': case 'M': scale = 1024 * 1024; *q = '\0'; break; case 'g': case 'G': scale = 1024 * 1024 * 1024; *q = '\0'; break; } /* Convert string to number. */ if (sscanf(p, "%lud", &str_num) != 1) return -1; if (str_num > MAX_SIZE / scale) die("size is too big"); return scale * str_num; } /* ** Print usage message and exit. */ static void Usage(void) { fprintf(stderr, "Usage: shrinkfile [-n] [ -m maxsize ] [-s size] [-v] file..."); exit(1); } int main(int ac, char *av[]) { bool Changed; bool Verbose; bool no_op; FILE *F; char *p; int i; off_t size = 0; off_t maxsize = 0; /* First thing, set up our identity. */ message_program_name = "shrinkfile"; /* Set defaults. */ Verbose = false; no_op = false; umask(NEWSUMASK); if (!innconf_read(NULL)) exit(1); /* Parse JCL. */ while ((i = getopt(ac, av, "m:s:vn")) != EOF) switch (i) { default: Usage(); /* NOTREACHED */ case 'n': no_op = true; break; case 'm': if ((maxsize = ParseSize(optarg)) < 0) Usage(); break; case 's': if ((size = ParseSize(optarg)) < 0) Usage(); break; case 'v': Verbose = true; break; } if (maxsize < size) { maxsize = size; } ac -= optind; av += optind; if (ac == 0) Usage(); while ((p = *av++) != NULL) { if ((F = fopen(p, "r")) == NULL) { syswarn("cannot open %s", p); continue; } /* -n (no_op) or normal processing */ if (no_op) { /* check if too big and exit zero if it is */ if (TooBig(F, maxsize)) { if (Verbose) notice("%s is too large", p); exit(0); /* NOTREACHED */ } /* no -n, do some real work */ } else { Changed = false; if (!Process(F, p, size, maxsize, &Changed)) syswarn("cannot shrink %s", p); else if (Verbose && Changed) notice("shrunk %s", p); } } if (no_op && Verbose) { notice("did not find a file that was too large"); } /* if -n, then exit non-zero to indicate no file too big */ exit(no_op ? 1 : 0); /* NOTREACHED */ }