/*
 * makehole: copy a file, making holes in all the blank spaces
 */

static const char rcsid[] = "Mastodon $Id: s.makehole.c 1.2 99/05/01 19:01:32-00:00 orc $";

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>

static long isize=0, osize=0;
static unsigned char getch_bfr[5120];
static int getch_sz = 1, getch_ptr = 1;

int
getch(int fd)
{

    if (getch_sz <= 0)
	return EOF;

    if (getch_ptr >= getch_sz) {
	getch_sz = read(fd, getch_bfr, sizeof getch_bfr);
	if (getch_sz > 0) {
	    isize += getch_sz;
	    getch_ptr = 0;
	}
	else
	    return EOF;
    }
    return 0xff & getch_bfr[getch_ptr++];
}

static off_t blank = 0;
static char writebuf[51200];
static int fill = 0;
static off_t opos = 0;
static int dangerous=0;

int
putch(int fd, int c)
{
    if (c == 0) {
	if (fill > 0) {
	    write(fd, writebuf, fill);
	    fill = 0;
	}
	blank++;
    }
    else {
	if (blank > 0) {
	    if (blank > 4096)
		printf("%ld byte hole at %ld\n", blank, opos);
	    lseek(fd, opos, SEEK_SET);
	    blank = 0;
	}
	if (fill == sizeof writebuf) {
	    printf("write %ld bytes\n", sizeof writebuf);
	    write(fd, writebuf, fill);
	    fill = 0;
	}
	writebuf[fill++] = c;
	osize ++;
    }
    opos++;
}


void
makehole(char *file)
{
    int input, output;
    register c;
    char *tmp, *bak;
    char *base;
    struct stat modes;
    
    getch_sz = 1;
    getch_ptr = 1;
    isize = 0;
    osize = 0;
    blank = 0;
    fill  = 0;
    opos  = 0;

    tmp = alloca(strlen(file)+20);
    strcpy(tmp, file);
    strcat(tmp, ".TMP");
    bak = alloca(strlen(file)+20);
    strcpy(bak, file);
    strcat(bak, ".BAK");

    if ((input = open(file, O_RDONLY)) == -1) {
	perror(file);
	return;
    }
    fstat(input, &modes);
    if (modes.st_nlink > 1) {
	close(input);
	fprintf(stderr, "%s: too many links\n", file);
	return;
    }
    if ((output = open(tmp, O_WRONLY|O_EXCL|O_CREAT, 0666)) == -1) {
	perror(tmp);
	close(input);
	return;
    }
    while ((c = getch(input)) != EOF)
	putch(output, c);

    if (fill > 0) {
	write(output, writebuf, fill);
    }
    if (blank > 0) {
	if (blank > 1)
	    lseek(output, opos-1, SEEK_SET);
	writebuf[0] = 0;
	write(output, writebuf, 1);
    }

    fchown(output, modes.st_uid, modes.st_gid);
    fchmod(output, modes.st_mode);
    
    close(output);
    close(input);

    if (!dangerous) {
	unlink(bak);
	if (link(file, bak) != 0) {
	    perror(bak);
	    return;
	}
    }
    if (unlink(file) != 0) {
	perror(file);
	return;
    }
    if (link(tmp, file) != 0) {
	perror(tmp);
	return;
    }
    unlink(tmp);

    if (dangerous)
	printf("%s: %ld (%ld) byte%s\n", file, opos, osize, (opos!=1)?"s":"");
    else {
	printf("%s -> %s: %ld byte%s\n", file, bak, isize, (isize!=1)?"s":"");
	printf("%s -> %s: %ld (%ld) byte%s\n", tmp, file, opos, osize, (opos!=1)?"s":"");
    }
} /* makehole */


main(int argc, char** argv)
{
    int x;

    if ((argc > 1) && strcmp(argv[1], "-d") == 0) {
	dangerous=1;
	argc--, argv++;
    }

    if (argc < 2) {
	fprintf(stderr, "usage: makehole [-d] file [...]\n");
	exit(1);
    }

    for (x=1; x < argc; x++)
	makehole(argv[x]);
    exit(0);
}
