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