1 /*
2  * Copyright (c) 1990 Jan-Simon Pendry
3  * Copyright (c) 1990 Imperial College of Science, Technology & Medicine
4  * Copyright (c) 1990, 1993
5  *	The Regents of the University of California.  All rights reserved.
6  *
7  * This code is derived from software contributed to Berkeley by
8  * Jan-Simon Pendry at Imperial College, London.
9  *
10  * %sccs.include.redist.c%
11  *
12  *	@(#)mtab_file.c	8.1 (Berkeley) 06/06/93
13  *
14  * $Id: mtab_file.c,v 5.2.2.1 1992/02/09 15:10:42 jsp beta $
15  *
16  */
17 
18 #include "am.h"
19 
20 #ifdef READ_MTAB_FROM_FILE
21 
22 #ifdef USE_FCNTL
23 #include <fcntl.h>
24 #else
25 #include <sys/file.h>
26 #endif /* USE_FCNTL */
27 
28 #ifdef UPDATE_MTAB
29 
30 /*
31  * Do strict /etc/mtab locking
32  */
33 #define	MTAB_LOCKING
34 
35 /*
36  * Firewall mtab entries
37  */
38 #define	MTAB_STRIPNL
39 
40 #include <sys/stat.h>
41 static FILE *mnt_file;
42 
43 /*
44  * If the system is being trashed by something, then
45  * opening mtab may fail with ENFILE.  So, go to sleep
46  * for a second and try again. (Yes - this has happened to me.)
47  *
48  * Note that this *may* block the automounter, oh well.
49  * If we get to this state then things are badly wrong anyway...
50  *
51  * Give the system 10 seconds to recover but then give up.
52  * Hopefully something else will exit and free up some file
53  * table slots in that time.
54  */
55 #define	NFILE_RETRIES	10 /* seconds */
56 
57 #ifdef MTAB_LOCKING
58 #ifdef LOCK_FCNTL
59 static int lock(fd)
60 {
61 	int rc;
62 	struct flock lk;
63 
64 	lk.l_type = F_WRLCK;
65 	lk.l_whence = 0;
66 	lk.l_start = 0;
67 	lk.l_len = 0;
68 
69 again:
70 	rc = fcntl(fd, F_SETLKW, (caddr_t) &lk);
71 	if (rc < 0 && (errno == EACCES || errno == EAGAIN)) {
72 #ifdef DEBUG
73 		dlog("Blocked, trying to obtain exclusive mtab lock");
74 #endif /* DEBUG */
75 		sleep(1);
76 		goto again;
77 	}
78 	return rc;
79 }
80 #else
81 #define lock(fd) (flock((fd), LOCK_EX))
82 #endif /* LOCK_FCNTL */
83 #endif /* MTAB_LOCKING */
84 
85 static FILE *open_locked_mtab(mtab_file, mode, fs)
86 char *mtab_file;
87 char *mode;
88 char *fs;
89 {
90 	FILE *mfp = 0;
91 
92 #ifdef UPDATE_MTAB
93 	/*
94 	 * There is a possible race condition if two processes enter
95 	 * this routine at the same time.  One will be blocked by the
96 	 * exclusive lock below (or by the shared lock in setmntent)
97 	 * and by the time the second process has the exclusive lock
98 	 * it will be on the wrong underlying object.  To check for this
99 	 * the mtab file is stat'ed before and after all the locking
100 	 * sequence, and if it is a different file then we assume that
101 	 * it may be the wrong file (only "may", since there is another
102 	 * race between the initial stat and the setmntent).
103 	 *
104 	 * Simpler solutions to this problem are invited...
105 	 */
106 	int racing = 2;
107 #ifdef MTAB_LOCKING
108 	int rc;
109 	int retries = 0;
110 	struct stat st_before, st_after;
111 #endif /* MTAB_LOCKING */
112 
113 	if (mnt_file) {
114 #ifdef DEBUG
115 		dlog("Forced close on %s in read_mtab", mtab_file);
116 #endif /* DEBUG */
117 		endmntent(mnt_file);
118 		mnt_file = 0;
119 	}
120 
121 #ifdef MTAB_LOCKING
122 again:
123 	if (mfp) {
124 		endmntent(mfp);
125 		mfp = 0;
126 	}
127 
128 	clock_valid = 0;
129 	if (stat(mtab_file, &st_before) < 0) {
130 		plog(XLOG_ERROR, "%s: stat: %m", mtab_file);
131 		if (errno == ESTALE) {
132 			/* happens occasionally */
133 			sleep(1);
134 			goto again;
135 		}
136 		return 0;
137 	}
138 #endif /* MTAB_LOCKING */
139 #endif /* UPDATE_MTAB */
140 
141 eacces:
142 	mfp = setmntent(mtab_file, mode);
143 	if (!mfp) {
144 		/*
145 		 * Since setmntent locks the descriptor, it
146 		 * is possible it can fail... so retry if
147 		 * needed.
148 		 */
149 		if (errno == EACCES || errno == EAGAIN) {
150 #ifdef DEBUG
151 			dlog("Blocked, trying to obtain exclusive mtab lock");
152 #endif /* DEBUG */
153 			goto eacces;
154 		} else if (errno == ENFILE && retries++ < NFILE_RETRIES) {
155 			sleep(1);
156 			goto eacces;
157 		}
158 
159 		plog(XLOG_ERROR, "setmntent(\"%s\", \"%s\"): %m", mtab_file, mode);
160 		return 0;
161 	}
162 
163 #ifdef MTAB_LOCKING
164 #ifdef UPDATE_MTAB
165 	/*
166 	 * At this point we have an exclusive lock on the mount list,
167 	 * but it may be the wrong one so...
168 	 */
169 
170 	/*
171 	 * Need to get an exclusive lock on the current
172 	 * mount table until we have a new copy written
173 	 * out, when the lock is released in free_mntlist.
174 	 * flock is good enough since the mount table is
175 	 * not shared between machines.
176 	 */
177 	do
178 		rc = lock(fileno(mfp));
179 	while (rc < 0 && errno == EINTR);
180 	if (rc < 0) {
181 		plog(XLOG_ERROR, "Couldn't lock %s: %m", mtab_file);
182 		endmntent(mfp);
183 		return 0;
184 	}
185 	/*
186 	 * Now check whether the mtab file has changed under our feet
187 	 */
188 	if (stat(mtab_file, &st_after) < 0) {
189 		plog(XLOG_ERROR, "%s: stat", mtab_file);
190 		goto again;
191 	}
192 
193 	if (st_before.st_dev != st_after.st_dev ||
194 		st_before.st_ino != st_after.st_ino) {
195 			struct timeval tv;
196 			if (racing == 0) {
197 				/* Sometimes print a warning */
198 				plog(XLOG_WARNING,
199 					"Possible mount table race - retrying %s", fs);
200 			}
201 			racing = (racing+1) & 3;
202 			/*
203 			 * Take a nap.  From: Doug Kingston <dpk@morgan.com>
204 			 */
205 			tv.tv_sec = 0;
206 			tv.tv_usec = (mypid & 0x07) << 17;
207 			if (tv.tv_usec)
208 				if (select(0, (voidp) 0, (voidp) 0, (voidp) 0, &tv) < 0)
209 					plog(XLOG_WARNING, "mtab nap failed: %m");
210 
211 			goto again;
212 	}
213 #endif /* UPDATE_MTAB */
214 #endif /* MTAB_LOCKING */
215 
216 	return mfp;
217 }
218 
219 /*
220  * Unlock the mount table
221  */
222 void unlock_mntlist P((void));
223 void unlock_mntlist()
224 {
225 	/*
226 	 * Release file lock, by closing the file
227 	 */
228 	if (mnt_file) {
229 		endmntent(mnt_file);
230 		mnt_file = 0;
231 	}
232 }
233 
234 /*
235  * Write out a mount list
236  */
237 void rewrite_mtab(mp)
238 mntlist *mp;
239 {
240 	FILE *mfp;
241 	int error = 0;
242 
243 	/*
244 	 * Concoct a temporary name in the same
245 	 * directory as the target mount table
246 	 * so that rename() will work.
247 	 */
248 	char tmpname[64];
249 	int retries;
250 	int tmpfd;
251 	char *cp;
252 	char *mcp = mtab;
253 	cp = strrchr(mcp, '/');
254 	if (cp) {
255 		bcopy(mcp, tmpname, cp - mcp);
256 		tmpname[cp-mcp] = '\0';
257 	} else {
258 		plog(XLOG_WARNING, "No '/' in mtab (%s), using \".\" as tmp directory", mtab);
259 		tmpname[0] = '.'; tmpname[1] = '\0';
260 	}
261 	strcat(tmpname, "/mtabXXXXXX");
262 	mktemp(tmpname);
263 	retries = 0;
264 enfile1:
265 	if ((tmpfd = open(tmpname, O_RDWR|O_CREAT|O_TRUNC, 0644)) < 0) {
266 		if (errno == ENFILE && retries++ < NFILE_RETRIES) {
267 			sleep(1);
268 			goto enfile1;
269 		}
270 		plog(XLOG_ERROR, "%s: open: %m", tmpname);
271 		return;
272 	}
273 	if (close(tmpfd) < 0)
274 		plog(XLOG_ERROR, "Couldn't close tmp file descriptor: %m");
275 
276 	retries = 0;
277 enfile2:
278 	mfp = setmntent(tmpname, "w");
279 	if (!mfp) {
280 		if (errno == ENFILE && retries++ < NFILE_RETRIES) {
281 			sleep(1);
282 			goto enfile2;
283 		}
284 		plog(XLOG_ERROR, "setmntent(\"%s\", \"w\"): %m", tmpname);
285 		error = 1;
286 		goto out;
287 	}
288 
289 	while (mp) {
290 		if (mp->mnt) {
291 			if (addmntent(mfp, mp->mnt)) {
292 				plog(XLOG_ERROR, "Can't write entry to %s", tmpname);
293 				error = 1;
294 				goto out;
295 			}
296 		}
297 		mp = mp->mnext;
298 	}
299 
300 	/*
301 	 * SunOS 4.1 manuals say that the return code from entmntent()
302 	 * is always 1 and to treat as a void.  That means we need to
303 	 * call fflush() to make sure the new mtab file got written.
304 	 */
305 	if (fflush(mfp)) {
306 		plog(XLOG_ERROR, "flush new mtab file: %m");
307 		error = 1;
308 		goto out;
309 	}
310 
311 	(void) endmntent(mfp);
312 
313 	/*
314 	 * Rename temporary mtab to real mtab
315 	 */
316 	if (rename(tmpname, mtab) < 0) {
317 		plog(XLOG_ERROR, "rename %s to %s: %m", tmpname, mtab);
318 		error = 1;
319 		goto out;
320 	}
321 
322 out:
323 	if (error)
324 		(void) unlink(tmpname);
325 }
326 
327 #ifdef MTAB_STRIPNL
328 static void mtab_stripnl(s)
329 char *s;
330 {
331 	do {
332 		s = strchr(s, '\n');
333 		if (s)
334 			*s++ = ' ';
335 	} while (s);
336 }
337 #endif /* MTAB_STRIPNL */
338 
339 /*
340  * Append a mntent structure to the
341  * current mount table.
342  */
343 void write_mntent(mp)
344 struct mntent *mp;
345 {
346 	int retries = 0;
347 	FILE *mfp;
348 enfile:
349 	mfp = open_locked_mtab(mtab, "a", mp->mnt_dir);
350 	if (mfp) {
351 #ifdef MTAB_STRIPNL
352 		mtab_stripnl(mp->mnt_opts);
353 #endif /* MTAB_STRIPNL */
354 		if (addmntent(mfp, mp))
355 			plog(XLOG_ERROR, "Couldn't write %s: %m", mtab);
356 		if (fflush(mfp))
357 			plog(XLOG_ERROR, "Couldn't flush %s: %m", mtab);
358 		(void) endmntent(mfp);
359 	} else {
360 		if (errno == ENFILE && retries < NFILE_RETRIES) {
361 			sleep(1);
362 			goto enfile;
363 		}
364 		plog(XLOG_ERROR, "setmntent(\"%s\", \"a\"): %m", mtab);
365 	}
366 }
367 
368 #endif /* UPDATE_MTAB */
369 
370 static struct mntent *mnt_dup(mp)
371 struct mntent *mp;
372 {
373 	struct mntent *new_mp = ALLOC(mntent);
374 
375 	new_mp->mnt_fsname = strdup(mp->mnt_fsname);
376 	new_mp->mnt_dir = strdup(mp->mnt_dir);
377 	new_mp->mnt_type = strdup(mp->mnt_type);
378 	new_mp->mnt_opts = strdup(mp->mnt_opts);
379 
380 	new_mp->mnt_freq = mp->mnt_freq;
381 	new_mp->mnt_passno = mp->mnt_passno;
382 
383 #ifdef FIXUP_MNTENT_DUP
384 	/*
385 	 * Additional fields get dup'ed here
386 	 */
387 	FIXUP_MNTENT_DUP(new_mp, mp);
388 #endif
389 
390 	return new_mp;
391 }
392 
393 /*
394  * Read a mount table into memory
395  */
396 mntlist *read_mtab(fs)
397 char *fs;
398 {
399 	mntlist **mpp, *mhp;
400 
401 	struct mntent *mep;
402 	FILE *mfp = open_locked_mtab(mtab, "r+", fs);
403 
404 	if (!mfp)
405 		return 0;
406 
407 	mpp = &mhp;
408 
409 /*
410  * XXX - In SunOS 4 there is (yet another) memory leak
411  * which loses 1K the first time getmntent is called.
412  * (jsp)
413  */
414 	while (mep = getmntent(mfp)) {
415 		/*
416 		 * Allocate a new slot
417 		 */
418 		*mpp = ALLOC(mntlist);
419 
420 		/*
421 		 * Copy the data returned by getmntent
422 		 */
423 		(*mpp)->mnt = mnt_dup(mep);
424 
425 		/*
426 		 * Move to next pointer
427 		 */
428 		mpp = &(*mpp)->mnext;
429 	}
430 	*mpp = 0;
431 
432 #ifdef UPDATE_MTAB
433 	/*
434 	 * If we are not updating the mount table then we
435 	 * can free the resources held here, otherwise they
436 	 * must be held until the mount table update is complete
437 	 */
438 	mnt_file = mfp;
439 #else
440 	endmntent(mfp);
441 #endif /* UPDATE_MTAB */
442 
443 	return mhp;
444 }
445 
446 #endif /* READ_MTAB_FROM_FILE */
447