1168404Spjd/*-
2168404Spjd * Copyright (c) 2007 Pawel Jakub Dawidek <pjd@FreeBSD.org>
3168404Spjd * All rights reserved.
4168404Spjd *
5168404Spjd * Redistribution and use in source and binary forms, with or without
6168404Spjd * modification, are permitted provided that the following conditions
7168404Spjd * are met:
8168404Spjd * 1. Redistributions of source code must retain the above copyright
9168404Spjd *    notice, this list of conditions and the following disclaimer.
10168404Spjd * 2. Redistributions in binary form must reproduce the above copyright
11168404Spjd *    notice, this list of conditions and the following disclaimer in the
12168404Spjd *    documentation and/or other materials provided with the distribution.
13168404Spjd *
14168404Spjd * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
15168404Spjd * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16168404Spjd * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17168404Spjd * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
18168404Spjd * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19168404Spjd * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20168404Spjd * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21168404Spjd * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22168404Spjd * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23168404Spjd * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24168404Spjd * SUCH DAMAGE.
25168404Spjd */
26168404Spjd
27168404Spjd#include <sys/cdefs.h>
28168404Spjd__FBSDID("$FreeBSD$");
29168404Spjd
30168404Spjd#include <sys/param.h>
31219089Spjd
32219089Spjd#include <assert.h>
33219089Spjd#include <errno.h>
34168404Spjd#include <fcntl.h>
35219089Spjd#include <fsshare.h>
36168404Spjd#include <libutil.h>
37168404Spjd#include <pathnames.h>	/* _PATH_MOUNTDPID */
38219089Spjd#include <signal.h>
39219089Spjd#include <stdio.h>
40219089Spjd#include <string.h>
41219089Spjd#include <unistd.h>
42168404Spjd
43168404Spjd#define	FILE_HEADER	"# !!! DO NOT EDIT THIS FILE MANUALLY !!!\n\n"
44168404Spjd#define	OPTSSIZE	1024
45168404Spjd#define	MAXLINESIZE	(PATH_MAX + OPTSSIZE)
46168404Spjd
47168404Spjdstatic void
48168404Spjdrestart_mountd(void)
49168404Spjd{
50168404Spjd	struct pidfh *pfh;
51168404Spjd	pid_t mountdpid;
52168404Spjd
53168404Spjd	pfh = pidfile_open(_PATH_MOUNTDPID, 0600, &mountdpid);
54168404Spjd	if (pfh != NULL) {
55168404Spjd		/* Mountd is not running. */
56168404Spjd		pidfile_remove(pfh);
57168404Spjd		return;
58168404Spjd	}
59168404Spjd	if (errno != EEXIST) {
60168404Spjd		/* Cannot open pidfile for some reason. */
61168404Spjd		return;
62168404Spjd	}
63168404Spjd	/* We have mountd(8) PID in mountdpid varible. */
64168404Spjd	kill(mountdpid, SIGHUP);
65168404Spjd}
66168404Spjd
67168404Spjd/*
68168404Spjd * Read one line from a file. Skip comments, empty lines and a line with a
69168404Spjd * mountpoint specified in the 'skip' argument.
70168404Spjd */
71168404Spjdstatic char *
72168404Spjdgetline(FILE *fd, const char *skip)
73168404Spjd{
74168404Spjd	static char line[MAXLINESIZE];
75168404Spjd	size_t len, skiplen;
76168404Spjd	char *s, last;
77168404Spjd
78168404Spjd	if (skip != NULL)
79168404Spjd		skiplen = strlen(skip);
80168404Spjd	for (;;) {
81168404Spjd		s = fgets(line, sizeof(line), fd);
82168404Spjd		if (s == NULL)
83168404Spjd			return (NULL);
84168404Spjd		/* Skip empty lines and comments. */
85168404Spjd		if (line[0] == '\n' || line[0] == '#')
86168404Spjd			continue;
87168404Spjd		len = strlen(line);
88168404Spjd		if (line[len - 1] == '\n')
89168404Spjd			line[len - 1] = '\0';
90168404Spjd		last = line[skiplen];
91168404Spjd		/* Skip the given mountpoint. */
92168404Spjd		if (skip != NULL && strncmp(skip, line, skiplen) == 0 &&
93168404Spjd		    (last == '\t' || last == ' ' || last == '\0')) {
94168404Spjd			continue;
95168404Spjd		}
96168404Spjd		break;
97168404Spjd	}
98168404Spjd	return (line);
99168404Spjd}
100168404Spjd
101168404Spjd/*
102168404Spjd * Function translate options to a format acceptable by exports(5), eg.
103168404Spjd *
104168929Spjd *	-ro -network=192.168.0.0 -mask=255.255.255.0 -maproot=0 freefall.freebsd.org 69.147.83.54
105168404Spjd *
106168404Spjd * Accepted input formats:
107168404Spjd *
108168929Spjd *	ro,network=192.168.0.0,mask=255.255.255.0,maproot=0,freefall.freebsd.org
109168929Spjd *	ro network=192.168.0.0 mask=255.255.255.0 maproot=0 freefall.freebsd.org
110168929Spjd *	-ro,-network=192.168.0.0,-mask=255.255.255.0,-maproot=0,freefall.freebsd.org
111168929Spjd *	-ro -network=192.168.0.0 -mask=255.255.255.0 -maproot=0 freefall.freebsd.org
112168929Spjd *
113168929Spjd * Recognized keywords:
114168929Spjd *
115209757Smm *	ro, maproot, mapall, mask, network, sec, alldirs, public, webnfs, index, quiet
116168929Spjd *
117168404Spjd */
118168929Spjdstatic const char *known_opts[] = { "ro", "maproot", "mapall", "mask",
119209757Smm    "network", "sec", "alldirs", "public", "webnfs", "index", "quiet", NULL };
120168404Spjdstatic char *
121168404Spjdtranslate_opts(const char *shareopts)
122168404Spjd{
123168404Spjd	static char newopts[OPTSSIZE];
124168929Spjd	char oldopts[OPTSSIZE];
125168404Spjd	char *o, *s = NULL;
126168929Spjd	unsigned int i;
127168929Spjd	size_t len;
128168404Spjd
129168404Spjd	strlcpy(oldopts, shareopts, sizeof(oldopts));
130168404Spjd	newopts[0] = '\0';
131168404Spjd	s = oldopts;
132168404Spjd	while ((o = strsep(&s, "-, ")) != NULL) {
133168404Spjd		if (o[0] == '\0')
134168404Spjd			continue;
135168929Spjd		for (i = 0; known_opts[i] != NULL; i++) {
136168929Spjd			len = strlen(known_opts[i]);
137168929Spjd			if (strncmp(known_opts[i], o, len) == 0 &&
138168929Spjd			    (o[len] == '\0' || o[len] == '=')) {
139168929Spjd				strlcat(newopts, "-", sizeof(newopts));
140168929Spjd				break;
141168929Spjd			}
142168929Spjd		}
143168929Spjd		strlcat(newopts, o, sizeof(newopts));
144168929Spjd		strlcat(newopts, " ", sizeof(newopts));
145168404Spjd	}
146168404Spjd	return (newopts);
147168404Spjd}
148168404Spjd
149168404Spjdstatic int
150168404Spjdfsshare_main(const char *file, const char *mountpoint, const char *shareopts,
151168404Spjd    int share)
152168404Spjd{
153168404Spjd	char tmpfile[PATH_MAX];
154168404Spjd	char *line;
155168404Spjd	FILE *newfd, *oldfd;
156168404Spjd	int fd, error;
157168404Spjd
158168404Spjd	newfd = oldfd = NULL;
159168404Spjd	error = 0;
160168404Spjd
161168404Spjd	/*
162168404Spjd	 * Create temporary file in the same directory, so we can atomically
163168404Spjd	 * rename it.
164168404Spjd	 */
165168404Spjd	if (strlcpy(tmpfile, file, sizeof(tmpfile)) >= sizeof(tmpfile))
166168404Spjd		return (ENAMETOOLONG);
167168404Spjd	if (strlcat(tmpfile, ".XXXXXXXX", sizeof(tmpfile)) >= sizeof(tmpfile))
168168404Spjd		return (ENAMETOOLONG);
169168404Spjd	fd = mkstemp(tmpfile);
170168404Spjd	if (fd == -1)
171168404Spjd		return (errno);
172168404Spjd	/*
173168404Spjd	 * File name is random, so we don't really need file lock now, but it
174168404Spjd	 * will be needed after rename(2).
175168404Spjd	 */
176168404Spjd	error = flock(fd, LOCK_EX);
177168404Spjd	assert(error == 0 || (error == -1 && errno == EOPNOTSUPP));
178168404Spjd	newfd = fdopen(fd, "r+");
179168404Spjd	assert(newfd != NULL);
180168404Spjd	/* Open old exports file. */
181168404Spjd	oldfd = fopen(file, "r");
182168404Spjd	if (oldfd == NULL) {
183168404Spjd		if (share) {
184168404Spjd			if (errno != ENOENT) {
185168404Spjd				error = errno;
186168404Spjd				goto out;
187168404Spjd			}
188168404Spjd		} else {
189168404Spjd			/* If there is no exports file, ignore the error. */
190168404Spjd			if (errno == ENOENT)
191168404Spjd				errno = 0;
192168404Spjd			error = errno;
193168404Spjd			goto out;
194168404Spjd		}
195168404Spjd	} else {
196168404Spjd		error = flock(fileno(oldfd), LOCK_EX);
197168404Spjd		assert(error == 0 || (error == -1 && errno == EOPNOTSUPP));
198168404Spjd		error = 0;
199168404Spjd	}
200168404Spjd
201168404Spjd	/* Place big, fat warning at the begining of the file. */
202168404Spjd	fprintf(newfd, "%s", FILE_HEADER);
203168404Spjd	while (oldfd != NULL && (line = getline(oldfd, mountpoint)) != NULL)
204168404Spjd		fprintf(newfd, "%s\n", line);
205168404Spjd	if (oldfd != NULL && ferror(oldfd) != 0) {
206168404Spjd		error = ferror(oldfd);
207168404Spjd		goto out;
208168404Spjd	}
209168404Spjd	if (ferror(newfd) != 0) {
210168404Spjd		error = ferror(newfd);
211168404Spjd		goto out;
212168404Spjd	}
213168404Spjd	if (share) {
214168404Spjd		fprintf(newfd, "%s\t%s\n", mountpoint,
215168404Spjd		    translate_opts(shareopts));
216168404Spjd	}
217168404Spjd
218168404Spjdout:
219168404Spjd	if (error != 0)
220168404Spjd		unlink(tmpfile);
221168404Spjd	else {
222168404Spjd		if (rename(tmpfile, file) == -1) {
223168404Spjd			error = errno;
224168404Spjd			unlink(tmpfile);
225168404Spjd		} else {
226222313Swill			fflush(newfd);
227168404Spjd			/*
228168404Spjd			 * Send SIGHUP to mountd, but unlock exports file later.
229168404Spjd			 */
230168404Spjd			restart_mountd();
231168404Spjd		}
232168404Spjd	}
233168404Spjd	if (oldfd != NULL) {
234168404Spjd		flock(fileno(oldfd), LOCK_UN);
235168404Spjd		fclose(oldfd);
236168404Spjd	}
237168404Spjd	if (newfd != NULL) {
238168404Spjd		flock(fileno(newfd), LOCK_UN);
239168404Spjd		fclose(newfd);
240168404Spjd	}
241168404Spjd	return (error);
242168404Spjd}
243168404Spjd
244168404Spjd/*
245168404Spjd * Add the given mountpoint to the given exports file.
246168404Spjd */
247168404Spjdint
248168404Spjdfsshare(const char *file, const char *mountpoint, const char *shareopts)
249168404Spjd{
250168404Spjd
251168404Spjd	return (fsshare_main(file, mountpoint, shareopts, 1));
252168404Spjd}
253168404Spjd
254168404Spjd/*
255168404Spjd * Remove the given mountpoint from the given exports file.
256168404Spjd */
257168404Spjdint
258168404Spjdfsunshare(const char *file, const char *mountpoint)
259168404Spjd{
260168404Spjd
261168404Spjd	return (fsshare_main(file, mountpoint, NULL, 0));
262168404Spjd}
263