6ac5c70c5b118b104800a6342d144f4412df6063
[nfs-utils.git] / utils / mount / fstab.c
1 /* 1999-02-22 Arkadiusz Mi¬∂kiewicz <misiek@pld.ORG.PL>
2  * - added Native Language Support
3  * Sun Mar 21 1999 - Arnaldo Carvalho de Melo <acme@conectiva.com.br>
4  * - fixed strerr(errno) in gettext calls
5  *
6  * 2006-06-08 Amit Gud <agud@redhat.com>
7  * - Moved code to nfs-utils/support/nfs from util-linux/mount.
8  */
9
10 #include <errno.h>
11 #include <stdio.h>
12 #include <fcntl.h>
13 #include <unistd.h>
14 #include <string.h>
15 #include <sys/stat.h>
16 #include <mntent.h>
17
18 #include "fstab.h"
19 #include "xcommon.h"
20 #include "nfs_mntent.h"
21 #include "nfs_paths.h"
22 #include "nls.h"
23
24 #define LOCK_TIMEOUT    10
25 #define streq(s, t)     (strcmp ((s), (t)) == 0)
26 #define PROC_MOUNTS             "/proc/mounts"
27
28 extern int verbose;
29
30 /* Information about mtab. ------------------------------------*/
31 static int have_mtab_info = 0;
32 static int var_mtab_does_not_exist = 0;
33 static int var_mtab_is_a_symlink = 0;
34
35 static void
36 get_mtab_info(void) {
37         struct stat mtab_stat;
38
39         if (!have_mtab_info) {
40                 if (lstat(MOUNTED, &mtab_stat))
41                         var_mtab_does_not_exist = 1;
42                 else if (S_ISLNK(mtab_stat.st_mode))
43                         var_mtab_is_a_symlink = 1;
44                 have_mtab_info = 1;
45         }
46 }
47
48 int
49 mtab_does_not_exist(void) {
50         get_mtab_info();
51         return var_mtab_does_not_exist;
52 }
53
54 static int
55 mtab_is_a_symlink(void) {
56         get_mtab_info();
57         return var_mtab_is_a_symlink;
58 }
59
60 int
61 mtab_is_writable() {
62         int fd;
63
64         /* Should we write to /etc/mtab upon an update?
65            Probably not if it is a symlink to /proc/mounts, since that
66            would create a file /proc/mounts in case the proc filesystem
67            is not mounted. */
68         if (mtab_is_a_symlink())
69                 return 0;
70
71         fd = open(MOUNTED, O_RDWR | O_CREAT, 0644);
72         if (fd >= 0) {
73                 close(fd);
74                 return 1;
75         } else
76                 return 0;
77 }
78
79 /* Contents of mtab and fstab ---------------------------------*/
80
81 struct mntentchn mounttable;
82 static int got_mtab = 0;
83 struct mntentchn fstab;
84 static int got_fstab = 0;
85
86 static void read_mounttable(void);
87 static void read_fstab(void);
88
89 static struct mntentchn *
90 mtab_head(void)
91 {
92         if (!got_mtab)
93                 read_mounttable();
94         return &mounttable;
95 }
96
97 static struct mntentchn *
98 fstab_head(void)
99 {
100         if (!got_fstab)
101                 read_fstab();
102         return &fstab;
103 }
104
105 #if 0
106 static void
107 my_free(const void *s) {
108         if (s)
109                 free((void *) s);
110 }
111
112 static void
113 discard_mntentchn(struct mntentchn *mc0) {
114         struct mntentchn *mc, *mc1;
115
116         for (mc = mc0->nxt; mc && mc != mc0; mc = mc1) {
117                 mc1 = mc->nxt;
118                 my_free(mc->m.mnt_fsname);
119                 my_free(mc->m.mnt_dir);
120                 my_free(mc->m.mnt_type);
121                 my_free(mc->m.mnt_opts);
122                 free(mc);
123         }
124 }
125 #endif
126
127 static void
128 read_mntentchn(mntFILE *mfp, const char *fnam, struct mntentchn *mc0) {
129         struct mntentchn *mc = mc0;
130         struct mntent *mnt;
131
132         while ((mnt = nfs_getmntent(mfp)) != NULL) {
133                 if (!streq(mnt->mnt_type, MNTTYPE_IGNORE)) {
134                         mc->nxt = (struct mntentchn *) xmalloc(sizeof(*mc));
135                         mc->nxt->prev = mc;
136                         mc = mc->nxt;
137                         mc->m = *mnt;
138                         mc->nxt = mc0;
139                 }
140         }
141         mc0->prev = mc;
142         if (ferror(mfp->mntent_fp)) {
143                 int errsv = errno;
144                 nfs_error(_("warning: error reading %s: %s"),
145                       fnam, strerror (errsv));
146                 mc0->nxt = mc0->prev = NULL;
147         }
148         nfs_endmntent(mfp);
149 }
150
151 /*
152  * Read /etc/mtab.  If that fails, try /proc/mounts.
153  * This produces a linked list. The list head mounttable is a dummy.
154  * Return 0 on success.
155  */
156 static void
157 read_mounttable() {
158         mntFILE *mfp;
159         const char *fnam;
160         struct mntentchn *mc = &mounttable;
161
162         got_mtab = 1;
163         mc->nxt = mc->prev = NULL;
164
165         fnam = MOUNTED;
166         mfp = nfs_setmntent (fnam, "r");
167         if (mfp == NULL || mfp->mntent_fp == NULL) {
168                 int errsv = errno;
169                 fnam = PROC_MOUNTS;
170                 mfp = nfs_setmntent (fnam, "r");
171                 if (mfp == NULL || mfp->mntent_fp == NULL) {
172                         nfs_error(_("warning: can't open %s: %s"),
173                               MOUNTED, strerror (errsv));
174                         return;
175                 }
176                 if (verbose)
177                         printf (_("mount: could not open %s - "
178                                   "using %s instead\n"),
179                                 MOUNTED, PROC_MOUNTS);
180         }
181         read_mntentchn(mfp, fnam, mc);
182 }
183
184 static void
185 read_fstab()
186 {
187         mntFILE *mfp = NULL;
188         const char *fnam;
189         struct mntentchn *mc = &fstab;
190
191         got_fstab = 1;
192         mc->nxt = mc->prev = NULL;
193
194         fnam = _PATH_FSTAB;
195         mfp = nfs_setmntent (fnam, "r");
196         if (mfp == NULL || mfp->mntent_fp == NULL) {
197                 int errsv = errno;
198                 nfs_error(_("warning: can't open %s: %s"),
199                           _PATH_FSTAB, strerror (errsv));
200                 return;
201         }
202         read_mntentchn(mfp, fnam, mc);
203 }
204
205 /*
206  * Given the directory name NAME, and the place MCPREV we found it last time,
207  * try to find more occurrences.
208  */ 
209 struct mntentchn *
210 getmntdirbackward (const char *name, struct mntentchn *mcprev) {
211         struct mntentchn *mc, *mc0;
212
213         mc0 = mtab_head();
214         if (!mcprev)
215                 mcprev = mc0;
216         for (mc = mcprev->prev; mc && mc != mc0; mc = mc->prev)
217                 if (streq(mc->m.mnt_dir, name))
218                         return mc;
219         return NULL;
220 }
221
222 /*
223  * Given the device name NAME, and the place MCPREV we found it last time,
224  * try to find more occurrences.
225  */ 
226 struct mntentchn *
227 getmntdevbackward (const char *name, struct mntentchn *mcprev) {
228         struct mntentchn *mc, *mc0;
229
230         mc0 = mtab_head();
231         if (!mcprev)
232                 mcprev = mc0;
233         for (mc = mcprev->prev; mc && mc != mc0; mc = mc->prev)
234                 if (streq(mc->m.mnt_fsname, name))
235                         return mc;
236         return NULL;
237 }
238
239 /* Find the dir FILE in fstab.  */
240 struct mntentchn *
241 getfsfile (const char *file)
242 {
243         struct mntentchn *mc, *mc0;
244
245         mc0 = fstab_head();
246         for (mc = mc0->nxt; mc && mc != mc0; mc = mc->nxt)
247                 if (streq(mc->m.mnt_dir, file))
248                         return mc;
249         return NULL;
250 }
251
252 /* Find the device SPEC in fstab.  */
253 struct mntentchn *
254 getfsspec (const char *spec)
255 {
256         struct mntentchn *mc, *mc0;
257
258         mc0 = fstab_head();
259         for (mc = mc0->nxt; mc && mc != mc0; mc = mc->nxt)
260                 if (streq(mc->m.mnt_fsname, spec))
261                         return mc;
262         return NULL;
263 }
264
265 /* Updating mtab ----------------------------------------------*/
266
267 /* Flag for already existing lock file. */
268 static int we_created_lockfile = 0;
269 static int lockfile_fd = -1;
270
271 /* Flag to indicate that signals have been set up. */
272 static int signals_have_been_setup = 0;
273
274 /* Ensure that the lock is released if we are interrupted.  */
275 extern char *strsignal(int sig);        /* not always in <string.h> */
276
277 static void
278 handler (int sig) {
279      die(EX_USER, "%s", strsignal(sig));
280 }
281
282 static void
283 setlkw_timeout (int sig) {
284      /* nothing, fcntl will fail anyway */
285 }
286
287 /* Remove lock file.  */
288 void
289 unlock_mtab (void) {
290         if (we_created_lockfile) {
291                 close(lockfile_fd);
292                 lockfile_fd = -1;
293                 unlink (MOUNTED_LOCK);
294                 we_created_lockfile = 0;
295         }
296 }
297
298 /* Create the lock file.
299    The lock file will be removed if we catch a signal or when we exit. */
300 /* The old code here used flock on a lock file /etc/mtab~ and deleted
301    this lock file afterwards. However, as rgooch remarks, that has a
302    race: a second mount may be waiting on the lock and proceed as
303    soon as the lock file is deleted by the first mount, and immediately
304    afterwards a third mount comes, creates a new /etc/mtab~, applies
305    flock to that, and also proceeds, so that the second and third mount
306    now both are scribbling in /etc/mtab.
307    The new code uses a link() instead of a creat(), where we proceed
308    only if it was us that created the lock, and hence we always have
309    to delete the lock afterwards. Now the use of flock() is in principle
310    superfluous, but avoids an arbitrary sleep(). */
311
312 /* Where does the link point to? Obvious choices are mtab and mtab~~.
313    HJLu points out that the latter leads to races. Right now we use
314    mtab~.<pid> instead. Use 20 as upper bound for the length of %d. */
315 #define MOUNTLOCK_LINKTARGET            MOUNTED_LOCK "%d"
316 #define MOUNTLOCK_LINKTARGET_LTH        (sizeof(MOUNTED_LOCK)+20)
317
318 void
319 lock_mtab (void) {
320         int tries = 100000, i;
321         char linktargetfile[MOUNTLOCK_LINKTARGET_LTH];
322
323         at_die = unlock_mtab;
324
325         if (!signals_have_been_setup) {
326                 int sig = 0;
327                 struct sigaction sa;
328
329                 sa.sa_handler = handler;
330                 sa.sa_flags = 0;
331                 sigfillset (&sa.sa_mask);
332   
333                 while (sigismember (&sa.sa_mask, ++sig) != -1
334                        && sig != SIGCHLD) {
335                         if (sig == SIGALRM)
336                                 sa.sa_handler = setlkw_timeout;
337                         else
338                                 sa.sa_handler = handler;
339                         sigaction (sig, &sa, (struct sigaction *) 0);
340                 }
341                 signals_have_been_setup = 1;
342         }
343
344         sprintf(linktargetfile, MOUNTLOCK_LINKTARGET, getpid ());
345
346         i = open (linktargetfile, O_WRONLY|O_CREAT, 0);
347         if (i < 0) {
348                 int errsv = errno;
349                 /* linktargetfile does not exist (as a file)
350                    and we cannot create it. Read-only filesystem?
351                    Too many files open in the system?
352                    Filesystem full? */
353                 die (EX_FILEIO, _("can't create lock file %s: %s "
354                                                   "(use -n flag to override)"),
355                          linktargetfile, strerror (errsv));
356         }
357         close(i);
358         
359         /* Repeat until it was us who made the link */
360         while (!we_created_lockfile) {
361                 struct flock flock;
362                 int errsv, j;
363
364                 j = link(linktargetfile, MOUNTED_LOCK);
365                 errsv = errno;
366
367                 if (j == 0)
368                         we_created_lockfile = 1;
369
370                 if (j < 0 && errsv != EEXIST) {
371                         (void) unlink(linktargetfile);
372                         die (EX_FILEIO, _("can't link lock file %s: %s "
373                              "(use -n flag to override)"),
374                              MOUNTED_LOCK, strerror (errsv));
375                 }
376
377                 lockfile_fd = open (MOUNTED_LOCK, O_WRONLY);
378
379                 if (lockfile_fd < 0) {
380                         int errsv = errno;
381                         /* Strange... Maybe the file was just deleted? */
382                         if (errno == ENOENT && tries-- > 0) {
383                                 if (tries % 200 == 0)
384                                         usleep(30);
385                                 continue;
386                         }
387                         (void) unlink(linktargetfile);
388                         die (EX_FILEIO, _("can't open lock file %s: %s "
389                              "(use -n flag to override)"),
390                              MOUNTED_LOCK, strerror (errsv));
391                 }
392
393                 flock.l_type = F_WRLCK;
394                 flock.l_whence = SEEK_SET;
395                 flock.l_start = 0;
396                 flock.l_len = 0;
397
398                 if (j == 0) {
399                         /* We made the link. Now claim the lock. */
400                         if (fcntl (lockfile_fd, F_SETLK, &flock) == -1) {
401                                 if (verbose) {
402                                     int errsv = errno;
403                                     printf(_("Can't lock lock file %s: %s\n"),
404                                            MOUNTED_LOCK, strerror (errsv));
405                                 }
406                                 /* proceed anyway */
407                         }
408                         (void) unlink(linktargetfile);
409                 } else {
410                         static int tries = 0;
411
412                         /* Someone else made the link. Wait. */
413                         alarm(LOCK_TIMEOUT);
414                         if (fcntl (lockfile_fd, F_SETLKW, &flock) == -1) {
415                                 int errsv = errno;
416                                 (void) unlink(linktargetfile);
417                                 die (EX_FILEIO, _("can't lock lock file %s: %s"),
418                                      MOUNTED_LOCK, (errno == EINTR) ?
419                                      _("timed out") : strerror (errsv));
420                         }
421                         alarm(0);
422                         /* Limit the number of iterations - maybe there
423                            still is some old /etc/mtab~ */
424                         ++tries;
425                         if (tries % 200 == 0)
426                            usleep(30);
427                         if (tries > 100000) {
428                                 (void) unlink(linktargetfile);
429                                 close(lockfile_fd);
430                                 die (EX_FILEIO, _("Cannot create link %s\n"
431                                                   "Perhaps there is a stale lock file?\n"),
432                                          MOUNTED_LOCK);
433                         }
434                         close(lockfile_fd);
435                 }
436         }
437 }
438
439 /*
440  * Update the mtab.
441  *  Used by umount with null INSTEAD: remove the last DIR entry.
442  *  Used by mount upon a remount: update option part,
443  *   and complain if a wrong device or type was given.
444  *   [Note that often a remount will be a rw remount of /
445  *    where there was no entry before, and we'll have to believe
446  *    the values given in INSTEAD.]
447  */
448
449 void
450 update_mtab (const char *dir, struct mntent *instead)
451 {
452         mntFILE *mfp, *mftmp;
453         const char *fnam = MOUNTED;
454         struct mntentchn mtabhead;      /* dummy */
455         struct mntentchn *mc, *mc0, *absent = NULL;
456
457         if (mtab_does_not_exist() || !mtab_is_writable())
458                 return;
459
460         lock_mtab();
461
462         /* having locked mtab, read it again */
463         mc0 = mc = &mtabhead;
464         mc->nxt = mc->prev = NULL;
465
466         mfp = nfs_setmntent(fnam, "r");
467         if (mfp == NULL || mfp->mntent_fp == NULL) {
468                 int errsv = errno;
469                 nfs_error (_("cannot open %s (%s) - mtab not updated"),
470                        fnam, strerror (errsv));
471                 goto leave;
472         }
473
474         read_mntentchn(mfp, fnam, mc);
475
476         /* find last occurrence of dir */
477         for (mc = mc0->prev; mc && mc != mc0; mc = mc->prev)
478                 if (streq(mc->m.mnt_dir, dir))
479                         break;
480         if (mc && mc != mc0) {
481                 if (instead == NULL) {
482                         /* An umount - remove entry */
483                         if (mc && mc != mc0) {
484                                 mc->prev->nxt = mc->nxt;
485                                 mc->nxt->prev = mc->prev;
486                                 free(mc);
487                         }
488                 } else {
489                         /* A remount */
490                         mc->m.mnt_opts = instead->mnt_opts;
491                 }
492         } else if (instead) {
493                 /* not found, add a new entry */
494                 absent = xmalloc(sizeof(*absent));
495                 absent->m = *instead;
496                 absent->nxt = mc0;
497                 absent->prev = mc0->prev;
498                 mc0->prev = absent;
499                 if (mc0->nxt == NULL)
500                         mc0->nxt = absent;
501         }
502
503         /* write chain to mtemp */
504         mftmp = nfs_setmntent (MOUNTED_TEMP, "w");
505         if (mftmp == NULL || mftmp->mntent_fp == NULL) {
506                 int errsv = errno;
507                 nfs_error (_("cannot open %s (%s) - mtab not updated"),
508                        MOUNTED_TEMP, strerror (errsv));
509                 goto leave;
510         }
511
512         for (mc = mc0->nxt; mc && mc != mc0; mc = mc->nxt) {
513                 if (nfs_addmntent(mftmp, &(mc->m)) == 1) {
514                         int errsv = errno;
515                         die (EX_FILEIO, _("error writing %s: %s"),
516                              MOUNTED_TEMP, strerror (errsv));
517                 }
518         }
519
520 #if 0
521         /* the chain might have strings copied from 'instead',
522          * so we cannot safely free it.
523          * And there is no need anyway because we are going to exit
524          * shortly.  So just don't call discard_mntentchn....
525          */
526         discard_mntentchn(mc0);
527 #endif
528         if (fchmod (fileno (mftmp->mntent_fp),
529                     S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) < 0) {
530                 int errsv = errno;
531                 fprintf(stderr, _("error changing mode of %s: %s\n"),
532                         MOUNTED_TEMP, strerror (errsv));
533         }
534         nfs_endmntent (mftmp);
535
536         { /*
537            * If mount is setuid and some non-root user mounts sth,
538            * then mtab.tmp might get the group of this user. Copy uid/gid
539            * from the present mtab before renaming.
540            */
541             struct stat sbuf;
542             if (stat (MOUNTED, &sbuf) == 0)
543                 chown (MOUNTED_TEMP, sbuf.st_uid, sbuf.st_gid);
544         }
545
546         /* rename mtemp to mtab */
547         if (rename (MOUNTED_TEMP, MOUNTED) < 0) {
548                 int errsv = errno;
549                 fprintf(stderr, _("can't rename %s to %s: %s\n"),
550                         MOUNTED_TEMP, MOUNTED, strerror(errsv));
551         }
552
553  leave:
554         unlock_mtab();
555 }