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