BSD 4_4_Lite2 development
[unix-history] / usr / src / contrib / news / inn / backends / shrinkfile.c
/* $Revision: 1.4 $
** Shrink files on line boundaries.
** Written by Landon Curt Noll <chongo@toad.com>, and placed in the
** public domain. Rewritten for INN by Rich Salz.
** Usage:
** shrinkfile [-s size] [-v] file...
** -s size Truncation size (0 default); suffix may be k, m,
** or g to scale. Must not be larger than 2^31 - 1.
** -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 the first line is longer than
** the absolute value of size, the file will be truncated to zero
** length.
*/
#include "configdata.h"
#include <stdio.h>
#include <ctype.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "libinn.h"
#include "clibrary.h"
#include "macros.h"
#define MAX_SIZE (OFFSET_T)0x7fffffff
/*
** Open a safe unique temporary file that will go away when closed.
*/
STATIC FILE *
OpenTemp()
{
FILE *F;
char *p;
char buff[SMBUF];
int i;
/* Get filename. */
if ((p = getenv("TMPDIR")) == NULL)
p = "/usr/tmp";
(void)sprintf(buff, "%s/shrinkXXXXXX", p);
(void)mktemp(buff);
/* Open the file. */
if ((i = open(buff, O_RDWR | O_CREAT | O_EXCL | O_TRUNC, 0600)) < 0) {
(void)fprintf(stderr, "Can't make temporary file, %s\n",
strerror(errno));
exit(1);
}
if ((F = fdopen(i, "w+")) == NULL) {
(void)fprintf(stderr, "Can't fdopen %d, %s\n", i, strerror(errno));
exit(1);
}
(void)unlink(buff);
return F;
}
/*
** Does file end with \n? Assume it does on I/O error, to avoid doing I/O.
*/
STATIC int
EndsWithNewline(F)
FILE *F;
{
int c;
if (fseek(F, (OFFSET_T)1, SEEK_END) < 0) {
(void)fprintf(stderr, "Can't seek to end of file, %s\n",
strerror(errno));
return TRUE;
}
/* return the actual character or EOF */
if ((c = fgetc(F)) == EOF) {
if (ferror(F))
(void)fprintf(stderr, "Can't read last byte, %s\n",
strerror(errno));
return TRUE;
}
return c == '\n';
}
/*
** Add a newline to location of a file.
*/
STATIC BOOL
AppendNewline(name)
char *name;
{
FILE *F;
if ((F = xfopena(name)) == NULL) {
(void)fprintf(stderr, "Can't add newline, %s\n", strerror(errno));
return FALSE;
}
if (fputc('\n', F) == EOF
|| fflush(F) == EOF
|| ferror(F)
|| fclose(F) == EOF) {
(void)fprintf(stderr, "Can't add newline, %s\n", strerror(errno));
return FALSE;
}
return TRUE;
}
/*
** This routine does all the work.
*/
STATIC BOOL
Process(F, name, size, Changedp)
FILE *F;
char *name;
OFFSET_T size;
BOOL *Changedp;
{
OFFSET_T len;
FILE *tmp;
struct stat Sb;
char buff[BUFSIZ + 1];
int c;
int i;
/* Get the file's size. */
if (fstat((int)fileno(F), &Sb) < 0) {
(void)fprintf(stderr, "Can't fstat, %s\n", strerror(errno));
return FALSE;
}
len = Sb.st_size;
/* Process a zero size request. */
if (size == 0) {
if (len > 0) {
(void)fclose(F);
if ((F = fopen(name, "w")) == NULL) {
(void)fprintf(stderr, "Can't overwrite, %s\n", strerror(errno));
return FALSE;
}
(void)fclose(F);
*Changedp = TRUE;
}
return TRUE;
}
/* See if already small enough. */
if (len < size) {
/* Newline already present? */
if (EndsWithNewline(F)) {
(void)fclose(F);
return TRUE;
}
/* No newline, add it if it fits. */
if (len < size - 1) {
(void)fclose(F);
*Changedp = TRUE;
return AppendNewline(name);
}
}
else if (!EndsWithNewline(F)) {
if (!AppendNewline(name)) {
(void)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 (fseek(F, -size, SEEK_END) < 0) {
(void)fprintf(stderr, "Can't fseek, %s\n", strerror(errno));
(void)fclose(F);
return FALSE;
}
while ((c = getc(F)) != '\n')
if (c == EOF) {
(void)fprintf(stderr, "Can't read, %s\n", strerror(errno));
(void)fclose(F);
return FALSE;
}
/* Copy rest of file to temp. */
tmp = OpenTemp();
while ((i = fread((POINTER)buff, (SIZE_T)1, (SIZE_T)sizeof buff, F)) > 0)
if (fwrite((POINTER)buff, (SIZE_T)1, (SIZE_T)i, tmp) != i) {
i = -1;
break;
}
if (i < 0) {
(void)fprintf(stderr, "Can't copy to temp file, %s\n",
strerror(errno));
(void)fclose(F);
(void)fclose(tmp);
return FALSE;
}
/* Now copy temp back to original file. */
(void)fclose(F);
if ((F = fopen(name, "w")) == NULL) {
(void)fprintf(stderr, "Can't overwrite, %s\n", strerror(errno));
(void)fclose(tmp);
return FALSE;
}
(void)fseek(tmp, (OFFSET_T)0, SEEK_SET);
while ((i = fread((POINTER)buff, (SIZE_T)1, (SIZE_T)sizeof buff, tmp)) > 0)
if (fwrite((POINTER)buff, (SIZE_T)1, (SIZE_T)i, F) != i) {
i = -1;
break;
}
if (i < 0) {
(void)fprintf(stderr, "Can't overwrite file, %s\n", strerror(errno));
(void)fclose(F);
(void)fclose(tmp);
return FALSE;
}
(void)fclose(F);
(void)fclose(tmp);
*Changedp = TRUE;
return TRUE;
}
/*
** Convert size argument to numeric value. Return -1 on error.
*/
STATIC OFFSET_T
ParseSize(p)
char *p;
{
OFFSET_T scale;
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, "%ld", &str_num) != 1)
return -1;
if (str_num > MAX_SIZE / scale) {
(void)fprintf(stderr, "Size is too big\n");
exit(1);
}
return scale * str_num;
}
/*
** Print usage message and exit.
*/
STATIC NORETURN
Usage()
{
(void)fprintf(stderr, "Usage: shrinkfile [-s size] [-v] file...\n");
exit(1);
}
int
main(ac, av)
int ac;
char *av[];
{
BOOL Changed;
BOOL Verbose;
FILE *F;
char *p;
int i;
OFFSET_T size;
/* Set defaults. */
Verbose = FALSE;
(void)umask(NEWSUMASK);
/* Parse JCL. */
while ((i = getopt(ac, av, "s:v")) != EOF)
switch (i) {
default:
Usage();
/* NOTREACHED */
case 's':
if ((size = ParseSize(optarg)) < 0)
Usage();
break;
case 'v':
Verbose = TRUE;
break;
}
ac -= optind;
av += optind;
if (ac == 0)
Usage();
while ((p = *av++) != NULL) {
if ((F = fopen(p, "r")) == NULL) {
(void)fprintf(stderr, "Can't open %s, %s\n", p, strerror(errno));
continue;
}
Changed = FALSE;
if (!Process(F, p, size, &Changed))
(void)fprintf(stderr, "Can't shrink %s\n", p);
else if (Verbose && Changed)
(void)printf("Shrunk %s\n", p);
}
exit(0);
/* NOTREACHED */
}