1/* $NetBSD: mtab_file.c,v 1.1.1.3 2015/01/17 16:34:16 christos Exp $ */ 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_file.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 50#ifdef MOUNT_TABLE_ON_FILE 51 52static FILE *mnt_file; 53 54 55/* 56 * If the system is being trashed by something, then 57 * opening mtab may fail with ENFILE. So, go to sleep 58 * for a second and try again. (Yes - this has happened to me.) 59 * 60 * Note that this *may* block the automounter, oh well. 61 * If we get to this state then things are badly wrong anyway... 62 * 63 * Give the system 10 seconds to recover but then give up. 64 * Hopefully something else will exit and free up some file 65 * table slots in that time. 66 */ 67# ifdef HAVE_FLOCK 68# define lock(fd) (flock((fd), LOCK_EX)) 69# else /* not HAVE_FLOCK */ 70static int 71lock(int fd) 72{ 73 int rc; 74 struct flock lk; 75 76 lk.l_type = F_WRLCK; 77 lk.l_whence = 0; 78 lk.l_start = 0; 79 lk.l_len = 0; 80 81again: 82 rc = fcntl(fd, F_SETLKW, (caddr_t) & lk); 83 if (rc < 0 && (errno == EACCES || errno == EAGAIN)) { 84# ifdef DEBUG 85 dlog("Blocked, trying to obtain exclusive mtab lock"); 86# endif /* DEBUG */ 87 sleep(1); 88 goto again; 89 } 90 return rc; 91} 92# endif /* not HAVE_FLOCK */ 93 94 95static FILE * 96open_locked_mtab(const char *mnttabname, char *mode, char *fs) 97{ 98 FILE *mfp = NULL; 99 100 /* 101 * There is a possible race condition if two processes enter 102 * this routine at the same time. One will be blocked by the 103 * exclusive lock below (or by the shared lock in setmntent) 104 * and by the time the second process has the exclusive lock 105 * it will be on the wrong underlying object. To check for this 106 * the mtab file is stat'ed before and after all the locking 107 * sequence, and if it is a different file then we assume that 108 * it may be the wrong file (only "may", since there is another 109 * race between the initial stat and the setmntent). 110 * 111 * Simpler solutions to this problem are invited... 112 */ 113 int racing = 2; 114 int rc; 115 int retries = 0; 116 struct stat st_before, st_after; 117 118 if (mnt_file) { 119# ifdef DEBUG 120 dlog("Forced close on %s in read_mtab", mnttabname); 121# endif /* DEBUG */ 122 endmntent(mnt_file); 123 mnt_file = NULL; 124 } 125again: 126 if (mfp) { 127 endmntent(mfp); 128 mfp = NULL; 129 } 130 if (stat(mnttabname, &st_before) < 0) { 131 plog(XLOG_ERROR, "%s: stat: %m", mnttabname); 132 if (errno == ESTALE) { 133 /* happens occasionally */ 134 sleep(1); 135 goto again; 136 } 137 /* 138 * If 'mnttabname' file does not exist give setmntent() a 139 * chance to create it (depending on the mode). 140 * Otherwise, bail out. 141 */ 142 else if (errno != ENOENT) { 143 return 0; 144 } 145 } 146 147eacces: 148 mfp = setmntent((char *)mnttabname, mode); 149 if (!mfp) { 150 /* 151 * Since setmntent locks the descriptor, it 152 * is possible it can fail... so retry if 153 * needed. 154 */ 155 if (errno == EACCES || errno == EAGAIN) { 156# ifdef DEBUG 157 dlog("Blocked, trying to obtain exclusive mtab lock"); 158# endif /* DEBUG */ 159 goto eacces; 160 } else if (errno == ENFILE && retries++ < NFILE_RETRIES) { 161 sleep(1); 162 goto eacces; 163 } 164 plog(XLOG_ERROR, "setmntent(\"%s\", \"%s\"): %m", mnttabname, mode); 165 return 0; 166 } 167 /* 168 * At this point we have an exclusive lock on the mount list, 169 * but it may be the wrong one so... 170 */ 171 172 /* 173 * Need to get an exclusive lock on the current 174 * mount table until we have a new copy written 175 * out, when the lock is released in free_mntlist. 176 * flock is good enough since the mount table is 177 * not shared between machines. 178 */ 179 do 180 rc = lock(fileno(mfp)); 181 while (rc < 0 && errno == EINTR); 182 if (rc < 0) { 183 plog(XLOG_ERROR, "Couldn't lock %s: %m", mnttabname); 184 endmntent(mfp); 185 return 0; 186 } 187 /* 188 * Now check whether the mtab file has changed under our feet 189 */ 190 if (stat(mnttabname, &st_after) < 0) { 191 plog(XLOG_ERROR, "%s: stat: %m", mnttabname); 192 goto again; 193 } 194 if (st_before.st_dev != st_after.st_dev || 195 st_before.st_ino != st_after.st_ino) { 196 struct timeval tv; 197 if (racing == 0) { 198 /* Sometimes print a warning */ 199 plog(XLOG_WARNING, 200 "Possible mount table race - retrying %s", fs); 201 } 202 racing = (racing + 1) & 3; 203 /* 204 * Take a nap. From: Doug Kingston <dpk@morgan.com> 205 */ 206 tv.tv_sec = 0; 207 tv.tv_usec = (am_mypid & 0x07) << 17; 208 if (tv.tv_usec) 209 if (select(0, (voidp) 0, (voidp) 0, (voidp) 0, &tv) < 0) 210 plog(XLOG_WARNING, "mtab nap failed: %m"); 211 212 goto again; 213 } 214 215 return mfp; 216} 217 218 219/* 220 * Unlock the mount table 221 */ 222void 223unlock_mntlist(void) 224{ 225 /* 226 * Release file lock, by closing the file 227 */ 228 if (mnt_file) { 229 dlog("unlock_mntlist: releasing"); 230 endmntent(mnt_file); 231 mnt_file = NULL; 232 } 233} 234 235 236/* 237 * Write out a mount list 238 */ 239void 240rewrite_mtab(mntlist *mp, const char *mnttabname) 241{ 242 FILE *mfp; 243 int error = 0; 244 245 /* 246 * Concoct a temporary name in the same directory as the target mount 247 * table so that rename() will work. 248 */ 249 char tmpname[64]; 250 int retries; 251 int tmpfd; 252 char *cp; 253 char mcp[128]; 254 255 xstrlcpy(mcp, mnttabname, sizeof(mcp)); 256 cp = strrchr(mcp, '/'); 257 if (cp) { 258 memmove(tmpname, mcp, cp - mcp); 259 tmpname[cp - mcp] = '\0'; 260 } else { 261 plog(XLOG_WARNING, "No '/' in mtab (%s), using \".\" as tmp directory", mnttabname); 262 tmpname[0] = '.'; 263 tmpname[1] = '\0'; 264 } 265 xstrlcat(tmpname, "/mtabXXXXXX", sizeof(tmpname)); 266 retries = 0; 267enfile1: 268#ifdef HAVE_MKSTEMP 269 tmpfd = mkstemp(tmpname); 270 fchmod(tmpfd, 0644); 271#else /* not HAVE_MKSTEMP */ 272 mktemp(tmpname); 273 tmpfd = open(tmpname, O_RDWR | O_CREAT | O_TRUNC, 0644); 274#endif /* not HAVE_MKSTEMP */ 275 if (tmpfd < 0) { 276 if (errno == ENFILE && retries++ < NFILE_RETRIES) { 277 sleep(1); 278 goto enfile1; 279 } 280 plog(XLOG_ERROR, "%s: open: %m", tmpname); 281 return; 282 } 283 if (close(tmpfd) < 0) 284 plog(XLOG_ERROR, "Couldn't close tmp file descriptor: %m"); 285 286 retries = 0; 287enfile2: 288 mfp = setmntent(tmpname, "w"); 289 if (!mfp) { 290 if (errno == ENFILE && retries++ < NFILE_RETRIES) { 291 sleep(1); 292 goto enfile2; 293 } 294 plog(XLOG_ERROR, "setmntent(\"%s\", \"w\"): %m", tmpname); 295 error = 1; 296 goto out; 297 } 298 while (mp) { 299 if (mp->mnt) { 300 if (addmntent(mfp, mp->mnt)) { 301 plog(XLOG_ERROR, "Can't write entry to %s", tmpname); 302 error = 1; 303 goto out; 304 } 305 } 306 mp = mp->mnext; 307 } 308 309 /* 310 * SunOS 4.1 manuals say that the return code from entmntent() 311 * is always 1 and to treat as a void. That means we need to 312 * call fflush() to make sure the new mtab file got written. 313 */ 314 if (fflush(mfp)) { 315 plog(XLOG_ERROR, "flush new mtab file: %m"); 316 error = 1; 317 goto out; 318 } 319 (void) endmntent(mfp); 320 321 /* 322 * Rename temporary mtab to real mtab 323 */ 324 if (rename(tmpname, mnttabname) < 0) { 325 plog(XLOG_ERROR, "rename %s to %s: %m", tmpname, mnttabname); 326 error = 1; 327 goto out; 328 } 329out: 330 if (error) 331 (void) unlink(tmpname); 332} 333 334 335static void 336mtab_stripnl(char *s) 337{ 338 do { 339 s = strchr(s, '\n'); 340 if (s) 341 *s++ = ' '; 342 } while (s); 343} 344 345 346/* 347 * Append a mntent structure to the 348 * current mount table. 349 */ 350void 351write_mntent(mntent_t *mp, const char *mnttabname) 352{ 353 int retries = 0; 354 FILE *mfp; 355enfile: 356 mfp = open_locked_mtab(mnttabname, "a", mp->mnt_dir); 357 if (mfp) { 358 mtab_stripnl(mp->mnt_opts); 359 if (addmntent(mfp, mp)) 360 plog(XLOG_ERROR, "Couldn't write %s: %m", mnttabname); 361 if (fflush(mfp)) 362 plog(XLOG_ERROR, "Couldn't flush %s: %m", mnttabname); 363 (void) endmntent(mfp); 364 } else { 365 if (errno == ENFILE && retries < NFILE_RETRIES) { 366 sleep(1); 367 goto enfile; 368 } 369 plog(XLOG_ERROR, "setmntent(\"%s\", \"a\"): %m", mnttabname); 370 } 371} 372 373#endif /* MOUNT_TABLE_ON_FILE */ 374 375 376static mntent_t * 377mnt_dup(mntent_t *mp) 378{ 379 mntent_t *new_mp = ALLOC(mntent_t); 380 381 new_mp->mnt_fsname = xstrdup(mp->mnt_fsname); 382 new_mp->mnt_dir = xstrdup(mp->mnt_dir); 383 new_mp->mnt_type = xstrdup(mp->mnt_type); 384 new_mp->mnt_opts = xstrdup(mp->mnt_opts); 385 386 new_mp->mnt_freq = mp->mnt_freq; 387 new_mp->mnt_passno = mp->mnt_passno; 388 389#ifdef HAVE_MNTENT_T_MNT_TIME 390# ifdef HAVE_MNTENT_T_MNT_TIME_STRING 391 new_mp->mnt_time = xstrdup(mp->mnt_time); 392# else /* not HAVE_MNTENT_T_MNT_TIME_STRING */ 393 new_mp->mnt_time = mp->mnt_time; 394# endif /* not HAVE_MNTENT_T_MNT_TIME_STRING */ 395#endif /* HAVE_MNTENT_T_MNT_TIME */ 396 397#ifdef HAVE_MNTENT_T_MNT_CNODE 398 new_mp->mnt_cnode = mp->mnt_cnode; 399#endif /* HAVE_MNTENT_T_MNT_CNODE */ 400 401 return new_mp; 402} 403 404 405/* 406 * Read a mount table into memory 407 */ 408mntlist * 409read_mtab(char *fs, const char *mnttabname) 410{ 411 mntlist **mpp, *mhp; 412 413 mntent_t *mep; 414 FILE *mfp = open_locked_mtab(mnttabname, "r+", fs); 415 416 if (!mfp) 417 return 0; 418 419 mpp = &mhp; 420 421/* 422 * XXX - In SunOS 4 there is (yet another) memory leak 423 * which loses 1K the first time getmntent is called. 424 * (jsp) 425 */ 426 while ((mep = getmntent(mfp))) { 427 /* 428 * Allocate a new slot 429 */ 430 *mpp = ALLOC(struct mntlist); 431 432 /* 433 * Copy the data returned by getmntent 434 */ 435 (*mpp)->mnt = mnt_dup(mep); 436 437 /* 438 * Move to next pointer 439 */ 440 mpp = &(*mpp)->mnext; 441 } 442 *mpp = NULL; 443 444#ifdef MOUNT_TABLE_ON_FILE 445 /* 446 * If we are not updating the mount table then we 447 * can free the resources held here, otherwise they 448 * must be held until the mount table update is complete 449 */ 450 mnt_file = mfp; 451#else /* not MOUNT_TABLE_ON_FILE */ 452 endmntent(mfp); 453#endif /* not MOUNT_TABLE_ON_FILE */ 454 455 return mhp; 456} 457