4.4BSD/usr/src/contrib/news/inn/backends/shrinkfile.c

Compare this file to the similar file:
Show the results in this format:

/*  $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 */
}