1/*- 2 * Copyright (c) 2007 Pawel Jakub Dawidek <pjd@FreeBSD.org> 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND 15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE 18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 * SUCH DAMAGE. 25 */ 26 27#include <sys/cdefs.h> 28__FBSDID("$FreeBSD$"); 29 30#include <sys/param.h> 31 32#include <assert.h> 33#include <errno.h> 34#include <fcntl.h> 35#include <fsshare.h> 36#include <libutil.h> 37#include <pathnames.h> /* _PATH_MOUNTDPID */ 38#include <signal.h> 39#include <stdio.h> 40#include <string.h> 41#include <unistd.h> 42 43#define FILE_HEADER "# !!! DO NOT EDIT THIS FILE MANUALLY !!!\n\n" 44#define OPTSSIZE 1024 45#define MAXLINESIZE (PATH_MAX + OPTSSIZE) 46 47static void 48restart_mountd(void) 49{ 50 struct pidfh *pfh; 51 pid_t mountdpid; 52 53 pfh = pidfile_open(_PATH_MOUNTDPID, 0600, &mountdpid); 54 if (pfh != NULL) { 55 /* Mountd is not running. */ 56 pidfile_remove(pfh); 57 return; 58 } 59 if (errno != EEXIST) { 60 /* Cannot open pidfile for some reason. */ 61 return; 62 } 63 /* We have mountd(8) PID in mountdpid varible. */ 64 kill(mountdpid, SIGHUP); 65} 66 67/* 68 * Read one line from a file. Skip comments, empty lines and a line with a 69 * mountpoint specified in the 'skip' argument. 70 */ 71static char * 72getline(FILE *fd, const char *skip) 73{ 74 static char line[MAXLINESIZE]; 75 size_t len, skiplen; 76 char *s, last; 77 78 if (skip != NULL) 79 skiplen = strlen(skip); 80 for (;;) { 81 s = fgets(line, sizeof(line), fd); 82 if (s == NULL) 83 return (NULL); 84 /* Skip empty lines and comments. */ 85 if (line[0] == '\n' || line[0] == '#') 86 continue; 87 len = strlen(line); 88 if (line[len - 1] == '\n') 89 line[len - 1] = '\0'; 90 last = line[skiplen]; 91 /* Skip the given mountpoint. */ 92 if (skip != NULL && strncmp(skip, line, skiplen) == 0 && 93 (last == '\t' || last == ' ' || last == '\0')) { 94 continue; 95 } 96 break; 97 } 98 return (line); 99} 100 101/* 102 * Function translate options to a format acceptable by exports(5), eg. 103 * 104 * -ro -network=192.168.0.0 -mask=255.255.255.0 -maproot=0 freefall.freebsd.org 69.147.83.54 105 * 106 * Accepted input formats: 107 * 108 * ro,network=192.168.0.0,mask=255.255.255.0,maproot=0,freefall.freebsd.org 109 * ro network=192.168.0.0 mask=255.255.255.0 maproot=0 freefall.freebsd.org 110 * -ro,-network=192.168.0.0,-mask=255.255.255.0,-maproot=0,freefall.freebsd.org 111 * -ro -network=192.168.0.0 -mask=255.255.255.0 -maproot=0 freefall.freebsd.org 112 * 113 * Recognized keywords: 114 * 115 * ro, maproot, mapall, mask, network, sec, alldirs, public, webnfs, index, quiet 116 * 117 */ 118static const char *known_opts[] = { "ro", "maproot", "mapall", "mask", 119 "network", "sec", "alldirs", "public", "webnfs", "index", "quiet", NULL }; 120static char * 121translate_opts(const char *shareopts) 122{ 123 static char newopts[OPTSSIZE]; 124 char oldopts[OPTSSIZE]; 125 char *o, *s = NULL; 126 unsigned int i; 127 size_t len; 128 129 strlcpy(oldopts, shareopts, sizeof(oldopts)); 130 newopts[0] = '\0'; 131 s = oldopts; 132 while ((o = strsep(&s, "-, ")) != NULL) { 133 if (o[0] == '\0') 134 continue; 135 for (i = 0; known_opts[i] != NULL; i++) { 136 len = strlen(known_opts[i]); 137 if (strncmp(known_opts[i], o, len) == 0 && 138 (o[len] == '\0' || o[len] == '=')) { 139 strlcat(newopts, "-", sizeof(newopts)); 140 break; 141 } 142 } 143 strlcat(newopts, o, sizeof(newopts)); 144 strlcat(newopts, " ", sizeof(newopts)); 145 } 146 return (newopts); 147} 148 149static int 150fsshare_main(const char *file, const char *mountpoint, const char *shareopts, 151 int share) 152{ 153 char tmpfile[PATH_MAX]; 154 char *line; 155 FILE *newfd, *oldfd; 156 int fd, error; 157 158 newfd = oldfd = NULL; 159 error = 0; 160 161 /* 162 * Create temporary file in the same directory, so we can atomically 163 * rename it. 164 */ 165 if (strlcpy(tmpfile, file, sizeof(tmpfile)) >= sizeof(tmpfile)) 166 return (ENAMETOOLONG); 167 if (strlcat(tmpfile, ".XXXXXXXX", sizeof(tmpfile)) >= sizeof(tmpfile)) 168 return (ENAMETOOLONG); 169 fd = mkstemp(tmpfile); 170 if (fd == -1) 171 return (errno); 172 /* 173 * File name is random, so we don't really need file lock now, but it 174 * will be needed after rename(2). 175 */ 176 error = flock(fd, LOCK_EX); 177 assert(error == 0 || (error == -1 && errno == EOPNOTSUPP)); 178 newfd = fdopen(fd, "r+"); 179 assert(newfd != NULL); 180 /* Open old exports file. */ 181 oldfd = fopen(file, "r"); 182 if (oldfd == NULL) { 183 if (share) { 184 if (errno != ENOENT) { 185 error = errno; 186 goto out; 187 } 188 } else { 189 /* If there is no exports file, ignore the error. */ 190 if (errno == ENOENT) 191 errno = 0; 192 error = errno; 193 goto out; 194 } 195 } else { 196 error = flock(fileno(oldfd), LOCK_EX); 197 assert(error == 0 || (error == -1 && errno == EOPNOTSUPP)); 198 error = 0; 199 } 200 201 /* Place big, fat warning at the begining of the file. */ 202 fprintf(newfd, "%s", FILE_HEADER); 203 while (oldfd != NULL && (line = getline(oldfd, mountpoint)) != NULL) 204 fprintf(newfd, "%s\n", line); 205 if (oldfd != NULL && ferror(oldfd) != 0) { 206 error = ferror(oldfd); 207 goto out; 208 } 209 if (ferror(newfd) != 0) { 210 error = ferror(newfd); 211 goto out; 212 } 213 if (share) { 214 fprintf(newfd, "%s\t%s\n", mountpoint, 215 translate_opts(shareopts)); 216 } 217 218out: 219 if (error != 0) 220 unlink(tmpfile); 221 else { 222 if (rename(tmpfile, file) == -1) { 223 error = errno; 224 unlink(tmpfile); 225 } else { 226 fflush(newfd); 227 /* 228 * Send SIGHUP to mountd, but unlock exports file later. 229 */ 230 restart_mountd(); 231 } 232 } 233 if (oldfd != NULL) { 234 flock(fileno(oldfd), LOCK_UN); 235 fclose(oldfd); 236 } 237 if (newfd != NULL) { 238 flock(fileno(newfd), LOCK_UN); 239 fclose(newfd); 240 } 241 return (error); 242} 243 244/* 245 * Add the given mountpoint to the given exports file. 246 */ 247int 248fsshare(const char *file, const char *mountpoint, const char *shareopts) 249{ 250 251 return (fsshare_main(file, mountpoint, shareopts, 1)); 252} 253 254/* 255 * Remove the given mountpoint from the given exports file. 256 */ 257int 258fsunshare(const char *file, const char *mountpoint) 259{ 260 261 return (fsshare_main(file, mountpoint, NULL, 0)); 262} 263