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