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