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