1/* $NetBSD$ */ 2 3/* 4 * Copyright (c) 1997-2014 Erez Zadok 5 * Copyright (c) 1990 Jan-Simon Pendry 6 * Copyright (c) 1990 Imperial College of Science, Technology & Medicine 7 * Copyright (c) 1990 The Regents of the University of California. 8 * All rights reserved. 9 * 10 * This code is derived from software contributed to Berkeley by 11 * Jan-Simon Pendry at Imperial College, London. 12 * 13 * Redistribution and use in source and binary forms, with or without 14 * modification, are permitted provided that the following conditions 15 * are met: 16 * 1. Redistributions of source code must retain the above copyright 17 * notice, this list of conditions and the following disclaimer. 18 * 2. Redistributions in binary form must reproduce the above copyright 19 * notice, this list of conditions and the following disclaimer in the 20 * documentation and/or other materials provided with the distribution. 21 * 3. Neither the name of the University nor the names of its contributors 22 * may be used to endorse or promote products derived from this software 23 * without specific prior written permission. 24 * 25 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 26 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 27 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 28 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 29 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 30 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 31 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 32 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 33 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 34 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 35 * SUCH DAMAGE. 36 * 37 * 38 * File: am-utils/conf/mtab/mtab_mach3.c 39 * 40 */ 41 42#ifdef HAVE_CONFIG_H 43# include <config.h> 44#endif /* HAVE_CONFIG_H */ 45#include <am_defs.h> 46#include <amu.h> 47 48#define NFILE_RETRIES 10 /* number of retries (seconds) */ 49 50static FILE *mnt_file; 51 52 53/* 54 * If the system is being trashed by something, then 55 * opening mtab may fail with ENFILE. So, go to sleep 56 * for a second and try again. (Yes - this has happened to me.) 57 * 58 * Note that this *may* block the automounter, oh well. 59 * If we get to this state then things are badly wrong anyway... 60 * 61 * Give the system 10 seconds to recover but then give up. 62 * Hopefully something else will exit and free up some file 63 * table slots in that time. 64 */ 65#ifdef HAVE_FCNTL_H 66static int 67lock(int fd) 68{ 69 int rc; 70 struct flock lk; 71 72 lk.l_type = F_WRLCK; 73 lk.l_whence = 0; 74 lk.l_start = 0; 75 lk.l_len = 0; 76 77again: 78 rc = fcntl(fd, F_SETLKW, (caddr_t) & lk); 79 if (rc < 0 && (errno == EACCES || errno == EAGAIN)) { 80# ifdef DEBUG 81 dlog("Blocked, trying to obtain exclusive mtab lock"); 82# endif /* DEBUG */ 83 sleep(1); 84 goto again; 85 } 86 return rc; 87} 88#else /* not HAVE_FCNTL_H */ 89# define lock(fd) (flock((fd), LOCK_EX)) 90#endif /* not HAVE_FCNTL_H */ 91 92 93static FILE * 94open_locked_mtab(char *mnttabname, char *mode, char *fs) 95{ 96 FILE *mfp = NULL; 97 98 /* 99 * There is a possible race condition if two processes enter 100 * this routine at the same time. One will be blocked by the 101 * exclusive lock below (or by the shared lock in setmntent) 102 * and by the time the second process has the exclusive lock 103 * it will be on the wrong underlying object. To check for this 104 * the mtab file is stat'ed before and after all the locking 105 * sequence, and if it is a different file then we assume that 106 * it may be the wrong file (only "may", since there is another 107 * race between the initial stat and the setmntent). 108 * 109 * Simpler solutions to this problem are invited... 110 */ 111 int racing = 2; 112 int rc; 113 int retries = 0; 114 struct stat st_before, st_after; 115 116 if (mnt_file) { 117#ifdef DEBUG 118 dlog("Forced close on %s in read_mtab", mnttabname); 119#endif /* DEBUG */ 120 endmntent(mnt_file); 121 mnt_file = NULL; 122 } 123again: 124 if (mfp) { 125 endmntent(mfp); 126 mfp = NULL; 127 } 128 if (stat(mnttabname, &st_before) < 0) { 129 plog(XLOG_ERROR, "%s: stat: %m", mnttabname); 130 if (errno == ESTALE) { 131 /* happens occasionally */ 132 sleep(1); 133 goto again; 134 } 135 /* 136 * If 'mnttabname' file does not exist give setmntent() a 137 * chance to create it (depending on the mode). 138 * Otherwise, bail out. 139 */ 140 else if (errno != ENOENT) { 141 return 0; 142 } 143 } 144eacces: 145 mfp = setmntent(mnttabname, mode); 146 if (!mfp) { 147 /* 148 * Since setmntent locks the descriptor, it 149 * is possible it can fail... so retry if 150 * needed. 151 */ 152 if (errno == EACCES || errno == EAGAIN) { 153#ifdef DEBUG 154 dlog("Blocked, trying to obtain exclusive mtab lock"); 155#endif /* DEBUG */ 156 goto eacces; 157 } else if (errno == ENFILE && retries++ < NFILE_RETRIES) { 158 sleep(1); 159 goto eacces; 160 } 161 plog(XLOG_ERROR, "setmntent(\"%s\", \"%s\"): %m", mnttabname, mode); 162 return 0; 163 } 164 /* 165 * At this point we have an exclusive lock on the mount list, 166 * but it may be the wrong one so... 167 */ 168 169 /* 170 * Need to get an exclusive lock on the current 171 * mount table until we have a new copy written 172 * out, when the lock is released in free_mntlist. 173 * flock is good enough since the mount table is 174 * not shared between machines. 175 */ 176 do 177 rc = lock(fileno(mfp)); 178 while (rc < 0 && errno == EINTR); 179 if (rc < 0) { 180 plog(XLOG_ERROR, "Couldn't lock %s: %m", mnttabname); 181 endmntent(mfp); 182 return 0; 183 } 184 /* 185 * Now check whether the mtab file has changed under our feet 186 */ 187 if (stat(mnttabname, &st_after) < 0) { 188 plog(XLOG_ERROR, "%s: stat", mnttabname); 189 goto again; 190 } 191 if (st_before.st_dev != st_after.st_dev || 192 st_before.st_ino != st_after.st_ino) { 193 struct timeval tv; 194 if (racing == 0) { 195 /* Sometimes print a warning */ 196 plog(XLOG_WARNING, 197 "Possible mount table race - retrying %s", fs); 198 } 199 racing = (racing + 1) & 3; 200 /* 201 * Take a nap. From: Doug Kingston <dpk@morgan.com> 202 */ 203 tv.tv_sec = 0; 204 tv.tv_usec = (am_mypid & 0x07) << 17; 205 if (tv.tv_usec) 206 if (select(0, (voidp) 0, (voidp) 0, (voidp) 0, &tv) < 0) 207 plog(XLOG_WARNING, "mtab nap failed: %m"); 208 209 goto again; 210 } 211 return mfp; 212} 213 214 215/* 216 * Unlock the mount table 217 */ 218void 219unlock_mntlist(void) 220{ 221 /* 222 * Release file lock, by closing the file 223 */ 224 if (mnt_file) { 225 dlog("unlock_mntlist: releasing"); 226 endmntent(mnt_file); 227 mnt_file = NULL; 228 } 229} 230 231 232/* 233 * routine to convert notation "/@honeydew" to the notation 234 * honeydew:/ and vice versa (honeydew:/ to /@honeydew) 235 * This lets you put /@honeydew in /etc/fstab without changing 236 * fstab.c and it lets you use EITHER notation on the command line! 237 */ 238static char * 239convert(register char *s, char bad, char good) 240{ 241 char *index(); 242 register char *t, *p; 243 register int len1, len2, i; 244 char *ptr; 245 246 if ((p = index(s, bad)) == NULL) { 247 return (s); 248 } 249 ptr = t = (char *) xzalloc(MAXPATHLEN * sizeof(char)); 250 len1 = p - s; 251 len2 = strlen(s) - len1 - 1; 252 p++; 253 for (i = 0; i < len2; i++) 254 *t++ = p[i]; 255 *t++ = good; 256 for (i = 0; i < len1; i++) 257 *t++ = s[i]; 258 return (ptr); 259} 260 261 262static 263mntprtent3(FILE *mnttabp, register mntent_t *mnt) 264{ 265 char *cvtd = convert(mnt->mnt_fsname, ':', '@'); 266 267 dlog("%x:%s:%s:%s:%d:%d:%s:%s:\n", 268 mnttabp, 269 (cvtd ? cvtd : ""), 270 (mnt->mnt_dir ? mnt->mnt_dir : ""), 271 (mnt->mnt_opts ? mnt->mnt_opts : ""), 272 mnt->mnt_freq, 273 mnt->mnt_passno, 274 (mnt->mnt_type ? mnt->mnt_type : ""), 275 (mnt->mnt_opts2 ? mnt->mnt_opts2 : "") 276 ); 277 fprintf(mnttabp, "%s:%s:%s:%d:%d:%s:%s:\n", 278 (cvtd ? cvtd : ""), 279 (mnt->mnt_dir ? mnt->mnt_dir : ""), 280 (mnt->mnt_opts ? mnt->mnt_opts : ""), 281 mnt->mnt_freq, 282 mnt->mnt_passno, 283 (mnt->mnt_type ? mnt->mnt_type : ""), 284 (mnt->mnt_opts2 ? mnt->mnt_opts2 : "") 285 ); 286 XFREE(cvtd); 287 cvtd = NULL; 288 return (0); 289} 290 291 292addmntent3(FILE *mnttabp, register mntent_t *mnt) 293{ 294 if (fseek(mnttabp, 0, 2) < 0) { 295 return (1); 296 } 297 mntprtent3(mnttabp, mnt); 298 return (0); 299} 300 301 302/* 303 * Write out a mount list 304 */ 305void 306rewrite_mtab(mntlist *mp, const char *mnttabname) 307{ 308 FILE *mfp; 309 int error = 0; 310 /* 311 * Concoct a temporary name in the same directory as the target mount 312 * table so that rename() will work. 313 */ 314 char tmpname[64]; 315 int retries; 316 int tmpfd; 317 char *cp; 318 char *mcp = mnttabname; 319 320 cp = strrchr(mcp, '/'); 321 if (cp) { 322 memmove(tmpname, mcp, cp - mcp); 323 tmpname[cp - mcp] = '\0'; 324 } else { 325 plog(XLOG_WARNING, "No '/' in mtab (%s), using \".\" as tmp directory", mnttabname); 326 tmpname[0] = '.'; 327 tmpname[1] = '\0'; 328 } 329 xstrlcat(tmpname, "/mtabXXXXXX", sizeof(tmpname)); 330 retries = 0; 331enfile1: 332#ifdef HAVE_MKSTEMP 333 tmpfd = mkstemp(tmpname); 334 fchmod(tmpfd, 0644); 335#else /* not HAVE_MKSTEMP */ 336 mktemp(tmpname); 337 tmpfd = open(tmpname, O_RDWR | O_CREAT | O_TRUNC, 0644); 338#endif /* not HAVE_MKSTEMP */ 339 if (tmpfd < 0) { 340 if (errno == ENFILE && retries++ < NFILE_RETRIES) { 341 sleep(1); 342 goto enfile1; 343 } 344 plog(XLOG_ERROR, "%s: open: %m", tmpname); 345 return; 346 } 347 if (close(tmpfd) < 0) 348 plog(XLOG_ERROR, "Couldn't close tmp file descriptor: %m"); 349 350 retries = 0; 351enfile2: 352 mfp = setmntent(tmpname, "w"); 353 if (!mfp) { 354 if (errno == ENFILE && retries++ < NFILE_RETRIES) { 355 sleep(1); 356 goto enfile2; 357 } 358 plog(XLOG_ERROR, "setmntent(\"%s\", \"w\"): %m", tmpname); 359 error = 1; 360 goto out; 361 } 362 while (mp) { 363 if (mp->mnt) { 364 if (addmntent3(mfp, mp->mnt)) { 365 plog(XLOG_ERROR, "Can't write entry to %s", tmpname); 366 error = 1; 367 goto out; 368 } 369 } 370 mp = mp->mnext; 371 } 372 373 /* 374 * SunOS 4.1 manuals say that the return code from endmntent() 375 * is always 1 and to treat as a void. That means we need to 376 * call fflush() to make sure the new mtab file got written. 377 */ 378 if (fflush(mfp)) { 379 plog(XLOG_ERROR, "flush new mtab file: %m"); 380 error = 1; 381 goto out; 382 } 383 (void) endmntent(mfp); 384 385 /* 386 * Rename temporary mtab to real mtab 387 */ 388 if (rename(tmpname, mnttabname) < 0) { 389 plog(XLOG_ERROR, "rename %s to %s: %m", tmpname, mnttabname); 390 error = 1; 391 goto out; 392 } 393out: 394 if (error) 395 (void) unlink(tmpname); 396} 397 398 399static void 400mtab_stripnl(char *s) 401{ 402 do { 403 s = strchr(s, '\n'); 404 if (s) 405 *s++ = ' '; 406 } while (s); 407} 408 409 410/* 411 * Append a mntent structure to the 412 * current mount table. 413 */ 414void 415write_mntent(mntent_t *mp, const char *mnttabname) 416{ 417 int retries = 0; 418 FILE *mfp; 419enfile: 420 mfp = open_locked_mtab(mnttabname, "a", mp->mnt_dir); 421 if (mfp) { 422 mtab_stripnl(mp->mnt_opts); 423 if (addmntent3(mfp, mp)) 424 plog(XLOG_ERROR, "Couldn't write %s: %m", mnttabname); 425 if (fflush(mfp)) 426 plog(XLOG_ERROR, "Couldn't flush %s: %m", mnttabname); 427 (void) endmntent(mfp); 428 } else { 429 if (errno == ENFILE && retries < NFILE_RETRIES) { 430 sleep(1); 431 goto enfile; 432 } 433 plog(XLOG_ERROR, "setmntent(\"%s\", \"a\"): %m", mnttabname); 434 } 435} 436 437 438static mntent_t * 439mnt_dup(mntent_t *mp) 440{ 441 mntent_t *new_mp = ALLOC(mntent_t); 442 443 new_mp->mnt_fsname = convert(mp->mnt_fsname, '@', ':'); 444 445 new_mp->mnt_dir = xstrdup(mp->mnt_dir); 446 new_mp->mnt_type = xstrdup(mp->mnt_type); 447 new_mp->mnt_opts = xstrdup(mp->mnt_opts); 448 449 new_mp->mnt_freq = mp->mnt_freq; 450 new_mp->mnt_passno = mp->mnt_passno; 451 452 return new_mp; 453} 454 455 456/* 457 * Read a mount table into memory 458 */ 459mntlist * 460read_mtab(char *fs, const char *mnttabname) 461{ 462 mntlist **mpp, *mhp; 463 464 mntent_t *mep; 465 FILE *mfp = open_locked_mtab(mnttabname, "r+", fs); 466 467 if (!mfp) 468 return 0; 469 470 mpp = &mhp; 471 472/* 473 * XXX - In SunOS 4 there is (yet another) memory leak 474 * which loses 1K the first time getmntent is called. 475 * (jsp) 476 */ 477 while (mep = getmntent(mfp)) { 478 /* 479 * Allocate a new slot 480 */ 481 *mpp = ALLOC(struct mntlist); 482 483 /* 484 * Copy the data returned by getmntent 485 */ 486 (*mpp)->mnt = mnt_dup(mep); 487 488 /* 489 * Move to next pointer 490 */ 491 mpp = &(*mpp)->mnext; 492 } 493 *mpp = NULL; 494 495 /* 496 * If we are not updating the mount table then we 497 * can free the resources held here, otherwise they 498 * must be held until the mount table update is complete 499 */ 500 mnt_file = mfp; 501 502 return mhp; 503} 504