/*
 * support/export/xtab.c
 *
 * Interface to the etab/exports file.
 *
 * Copyright (C) 1995, 1996 Olaf Kirch <okir@monad.swb.de>
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <libgen.h>

#include "nfslib.h"
#include "exportfs.h"
#include "xio.h"
#include "xlog.h"
#include "v4root.h"
#include "misc.h"

static char state_base_dirname[PATH_MAX] = NFS_STATEDIR;
struct state_paths etab;

int v4root_needed;
static void cond_rename(char *newfile, char *oldfile);

static int
xtab_read(char *xtab, char *lockfn, int is_export)
{
    /* is_export == 0  => reading /proc/fs/nfs/exports - we know these things are exported to kernel
     * is_export == 1  => reading /var/lib/nfs/etab - these things are allowed to be exported
     */
	struct exportent	*xp;
	nfs_export		*exp;
	int			lockid;

	if ((lockid = xflock(lockfn, "r")) < 0)
		return 0;
	setexportent(xtab, "r");
	if (is_export == 1)
		v4root_needed = 1;
	while ((xp = getexportent(is_export==0, 0)) != NULL) {
		if (!(exp = export_lookup(xp->e_hostname, xp->e_path, is_export != 1)) &&
		    !(exp = export_create(xp, is_export!=1))) {
                        if(xp->e_hostname) {
                            free(xp->e_hostname);
                            xp->e_hostname=NULL;
                        }
                        if(xp->e_uuid) {
                            free(xp->e_uuid);
                            xp->e_uuid=NULL;
                        }
			continue;
		}
		switch (is_export) {
		case 0:
			exp->m_exported = 1;
			break;
		case 1:
			exp->m_xtabent = 1;
			exp->m_mayexport = 1;
			if ((xp->e_flags & NFSEXP_FSID) && xp->e_fsid == 0)
				v4root_needed = 0;
			break;
		}  
                if(xp->e_hostname) {
                    free(xp->e_hostname);
                    xp->e_hostname=NULL;
                }
                if(xp->e_uuid) {
                    free(xp->e_uuid);
                    xp->e_uuid=NULL;
                }

	}
	endexportent();
	xfunlock(lockid);

	return 0;
}

int
xtab_export_read(void)
{
	return xtab_read(etab.statefn, etab.lockfn, 1);
}

/*
 * mountd now keeps an open fd for the etab at all times to make sure that the
 * inode number changes when the xtab_export_write is done. If you change the
 * routine below such that the files are edited in place, then you'll need to
 * fix the auth_reload logic as well...
 */
static int
xtab_write(char *xtab, char *xtabtmp, char *lockfn, int is_export)
{
	struct exportent	xe;
	nfs_export		*exp;
	int			lockid, i;

	if ((lockid = xflock(lockfn, "w")) < 0) {
		xlog(L_ERROR, "can't lock %s for writing", xtab);
		return 0;
	}
	setexportent(xtabtmp, "w");

	for (i = 0; i < MCL_MAXTYPES; i++) {
		for (exp = exportlist[i].p_head; exp; exp = exp->m_next) {
			if (is_export && !exp->m_xtabent)
				continue;
			if (!is_export && ! exp->m_exported)
				continue;

			/* write out the export entry using the FQDN */
			xe = exp->m_export;
			xe.e_hostname = exp->m_client->m_hostname;
			putexportent(&xe);
		}
	}
	endexportent();

	cond_rename(xtabtmp, xtab);

	xfunlock(lockid);

	return 1;
}

int
xtab_export_write(void)
{
	return xtab_write(etab.statefn, etab.tmpfn, etab.lockfn, 1);
}

/*
 * rename newfile onto oldfile unless
 * they are identical
 */
static void cond_rename(char *newfile, char *oldfile)
{
	int nfd, ofd;
	char nbuf[4096], obuf[4096];
	int ncnt, ocnt;

	nfd = open(newfile, 0);
	if (nfd < 0)
		return;
	ofd = open(oldfile, 0);
	if (ofd < 0) {
		close(nfd);
		rename(newfile, oldfile);
		return;
	}

	do {
		ncnt = read(nfd, nbuf, sizeof(nbuf));
		if (ncnt < 0)
			break;
		ocnt = read(ofd, obuf, sizeof(obuf));
		if (ocnt < 0)
			break;
		if (ncnt != ocnt)
			break;
		if (ncnt == 0) {
			close(nfd);
			close(ofd);
			unlink(newfile);
			return;
		}
	} while (memcmp(obuf, nbuf, ncnt) == 0);

	/* some mis-match */
	close(nfd);
	close(ofd);
	rename(newfile, oldfile);
	return;
}

/*
 * Returns a dynamically allocated, '\0'-terminated buffer
 * containing an appropriate pathname, or NULL if an error
 * occurs.  Caller must free the returned result with free(3).
 */
static char *
state_make_pathname(const char *tabname)
{
	return generic_make_pathname(state_base_dirname, tabname);
}

/**
 * state_setup_basedir - set up basedir
 * @progname: C string containing name of program, for error messages
 * @parentdir: C string containing pathname to on-disk state, or NULL
 *
 * This runs before logging is set up, so error messages are directed
 * to stderr.
 *
 * Returns true and sets up our basedir, if @parentdir was valid
 * and usable; otherwise false is returned.
 */
_Bool
state_setup_basedir(const char *progname, const char *parentdir)
{
	return generic_setup_basedir(progname, parentdir, state_base_dirname,
				     PATH_MAX);
}

int
setup_state_path_names(const char *progname, const char *statefn,
		      const char *tmpfn, const char *lockfn,
		      struct state_paths *paths)
{
	paths->statefn = state_make_pathname(statefn);
	if (!paths->statefn) {
		fprintf(stderr, "%s: state_make_pathname(%s) failed\n",
			progname, statefn);
		goto out_err;
	}
	paths->tmpfn = state_make_pathname(tmpfn);
	if (!paths->tmpfn) {
		fprintf(stderr, "%s: state_make_pathname(%s) failed\n",
			progname, tmpfn);
		goto out_free_statefn;
	}
	paths->lockfn = state_make_pathname(lockfn);
	if (!paths->lockfn) {
		fprintf(stderr, "%s: state_make_pathname(%s) failed\n",
			progname, lockfn);
		goto out_free_tmpfn;
	}
	return 1;

out_free_tmpfn:
	free(paths->tmpfn);
out_free_statefn:
	free(paths->statefn);
out_err:
	return 0;

}

void
free_state_path_names(struct state_paths *paths)
{
	free(paths->statefn);
	free(paths->tmpfn);
	free(paths->lockfn);
}
