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