/**********************************************************************
 *  modify.c -- functions to modify files
 *
 * Copyright 1994 by the Massachusetts Institute of Technology
 * For copying and distribution information, please see the file
 * <mit-copyright.h>.
 **********************************************************************/
#include <mit-copyright.h>

#include <AL/AL.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <errno.h>
#include <stdlib.h>

int ALbeginFileUpdate(const char *filename, const char *lockfilename,
		      FILE **fp, FILE **tempfp, sigset_t *mask)
{
    int count, fd;
    struct stat statbuf;
    sigset_t block;

    /* Block alarms and tty-generated stopping signals.  Also block
     * SIGCHLDs so that the application's SIGCHLD handler (if it has
     * one) doesn't clean up our pwd_mkdb (under BSD 4.4) before we get
     * a chance to. */
    sigemptyset(&block);
    sigaddset(&block, SIGHUP);
    sigaddset(&block, SIGINT);
    sigaddset(&block, SIGQUIT);
    sigaddset(&block, SIGTSTP);
    sigaddset(&block, SIGALRM);
    sigaddset(&block, SIGCHLD);
    sigprocmask(SIG_BLOCK, &block, mask);

    for (count = 10; count >= 0; count--) {
	if ((fd = open(lockfilename, O_WRONLY|O_CREAT|O_EXCL, 0644)) > 0)
	    break;
	sleep(1);
    }
    if (fd < 0) {
	sigprocmask(SIG_SETMASK, mask, NULL);
	return -1;
    }
    *tempfp = fdopen(fd, "w");

    *fp = fopen(filename, "r");
    if (!*fp) {
	close(fd);
	unlink(lockfilename);
	sigprocmask(SIG_SETMASK, mask, NULL);
	return -1;
    }

    /* Set lock file mode to existing file mode. */
    if (stat(filename, &statbuf) == 0)
	chmod(lockfilename, statbuf.st_mode);

    return 0;
}

int ALfinishFileUpdate(const char *filename, const char *lockfilename,
		       FILE *fp, FILE *tempfp, sigset_t *mask, int ispasswd)
{
    int status = 0;

    fclose(fp);
    fclose(tempfp);

#ifdef BSD4_4
    if (ispasswd) {
	int pstat;
	pid_t pid;

	pid = vfork();
	if (pid == 0) {
	    execl(_PATH_PWD_MKDB, "pwd_mkdb", "-p", lockfilename, NULL);
	    exit(1);
	}
	pid = waitpid(pid, &pstat, 0);
	if (pid == -1 || !WIFEXITED(pstat) || WEXITSTATUS(pstat) != 0)
	    status = -1;
    } else {
	status = rename(lockfilename, filename);
    }
#else
    status = rename(lockfilename, filename);
#endif
    if (status < 0)
	unlink(lockfilename);
    sigprocmask(SIG_SETMASK, mask, NULL);
    return status;
}

int ALabortFileUpdate(const char *filename, const char *lockfilename,
		      FILE *fp, FILE *tempfp, sigset_t *mask)
{
    int status;

    fclose(fp);
    fclose(tempfp);
    status = unlink(lockfilename);
    sigprocmask(SIG_SETMASK, mask, NULL);
    return status;
}

int ALgetLine(FILE *fp, char **buf, int *bufsize)
{
    char *newbuf;
    int offset = 0, len;

    if (*bufsize == 0) {
	*buf = malloc(128);
	if (!*buf)
	    return 0;
	*bufsize = 128;
    }

    while (1) {
	if (!fgets(*buf + offset, *bufsize - offset, fp))
	    return (offset != 0);
	len = offset + strlen(*buf + offset);
	if ((*buf)[len - 1] == '\n') {
	    (*buf)[len - 1] = 0;
	    return 1;
	}
	offset = len;

	/* Allocate more space. */
	newbuf = realloc(*buf, *bufsize * 2);
	if (!newbuf)
	    return 0;
	*buf = newbuf;
	*bufsize *= 2;
    }
}

/* Remove the line staring with prefix; add line at the end. */
int ALchangeFile(char *filename, char *lockfilename, char *prefix,
		 char *line, int ispasswd)
{
    FILE *file, *tempfile, *shadowfile;
    char *buf = NULL;
    int bufsize = 0, buflen = 0, len = (prefix) ? strlen(prefix) : 0;
    sigset_t mask;

    if (ALbeginFileUpdate(filename, lockfilename, &file, &tempfile, &mask) < 0)
	return -1;

    while (ALgetLine(file, &buf, &buflen)) {
	if (!prefix || strncmp(buf, prefix, len) != 0) {
	    fputs(buf, tempfile);
	    putc('\n', tempfile);
	}
    }
    if (line) {
	fputs(line, tempfile);
	putc('\n', tempfile);
    }

    if (buf)
	free(buf);

    return ALfinishFileUpdate(filename, lockfilename, file, tempfile, &mask,
			      ispasswd);
}

