xref: /openbsd-src/usr.bin/rdistd/filesys.c (revision 62a742911104f98b9185b2c6b6007d9b1c36396c)
1 /*	$OpenBSD: filesys.c,v 1.5 1998/06/26 21:20:48 millert Exp $	*/
2 
3 /*
4  * Copyright (c) 1983 Regents of the University of California.
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. All advertising materials mentioning features or use of this software
16  *    must display the following acknowledgement:
17  *	This product includes software developed by the University of
18  *	California, Berkeley and its contributors.
19  * 4. Neither the name of the University nor the names of its contributors
20  *    may be used to endorse or promote products derived from this software
21  *    without specific prior written permission.
22  *
23  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33  * SUCH DAMAGE.
34  */
35 
36 #ifndef lint
37 #if 0
38 static char RCSid[] =
39 "$From: filesys.c,v 6.24 1996/01/30 01:57:07 mcooper Exp $";
40 #else
41 static char RCSid[] =
42 "$OpenBSD: filesys.c,v 1.5 1998/06/26 21:20:48 millert Exp $";
43 #endif
44 
45 static char sccsid[] = "@(#)filesys.c";
46 
47 static char copyright[] =
48 "@(#) Copyright (c) 1983 Regents of the University of California.\n\
49  All rights reserved.\n";
50 #endif /* not lint */
51 
52 /*
53  * This file contains functions dealing with getting info
54  * about mounted filesystems.
55  */
56 
57 #include "defs.h"
58 #include "filesys.h"
59 
60 jmp_buf env;
61 
62 /*
63  * Given a pathname, find the fullest component that exists.
64  * If statbuf is not NULL, set it to point at our stat buffer.
65  */
66 char *find_file(pathname, statbuf, isvalid)
67 	char *pathname;
68 	struct stat *statbuf;
69 	int *isvalid;
70 {
71 	static char last_pathname[MAXPATHLEN];
72 	static char file[MAXPATHLEN + 3];
73 	static struct stat filestat;
74 	register char *p;
75 
76 	/*
77 	 * Mark the statbuf as invalid to start with.
78 	 */
79 	*isvalid = 0;
80 
81 	/*
82 	 * If this is the same pathname as the last time, and
83 	 * the file buffer is valid and we're doing the same stat()
84 	 * or lstat(), then set statbuf to the last filestat and
85 	 * return the last file we found.
86 	 */
87 	if (strcmp(pathname, last_pathname) == 0 && file[0]) {
88 		if (statbuf)
89 			statbuf = &filestat;
90 		if (strcmp(pathname, file) == 0)
91 			*isvalid = 1;
92 		return(file);
93 	}
94 
95 	if ((int)strlen(pathname) > sizeof(file)+3) {
96 		error("%s: Name to large for buffer.", pathname);
97 	        return(NULL);
98 	}
99 
100 	/*
101 	 * Save for next time
102 	 */
103 	(void) strcpy(last_pathname, pathname);
104 
105 	if (*pathname == '/')
106 	        (void) strcpy(file, pathname);
107 	else {
108 		/*
109 		 * Ensure we have a directory (".") in our path
110 		 * so we have something to stat in case the file
111 		 * does not exist.
112 		 */
113 	        (void) strcpy(file, "./");
114 		(void) strcat(file, pathname);
115 	}
116 
117 	while (lstat(file, &filestat) != 0) {
118 		/*
119 		 * Trim the last part of the pathname to try next level up
120 		 */
121 		if (errno == ENOENT) {
122 			/*
123 			 * Trim file name to get directory name.
124 			 * Normally we want to change /dir1/dir2/file
125 			 * into "/dir1/dir2/."
126 			 */
127 			if ((p = (char *) strrchr(file, '/'))) {
128 				if (strcmp(p, "/.") == 0) {
129 				    *p = CNULL;
130 				} else {
131 				    *++p = '.';
132 				    *++p = CNULL;
133 				}
134 			} else {
135 				/*
136 				 * Couldn't find anything, so give up.
137 				 */
138 				debugmsg(DM_MISC, "Cannot find dir of `%s'",
139 					 pathname);
140 				return(NULL);
141 			}
142 			continue;
143 		} else {
144 			error("%s: lstat failed: %s", pathname, SYSERR);
145 			return(NULL);
146 		}
147 	}
148 
149 	if (statbuf)
150 		bcopy((char *) &filestat, (char *) statbuf, sizeof(filestat));
151 
152 	/*
153 	 * Trim the "/." that we added.
154 	 */
155 	p = &file[strlen(file) - 1];
156 	if (*p == '.')
157 		*p-- = CNULL;
158 	for ( ; p && *p && *p == '/' && p != file; --p)
159 		*p = CNULL;
160 
161 	/*
162 	 * If this file is a symlink we really want the parent directory
163 	 * name in case the symlink points to another filesystem.
164 	 */
165 	if (S_ISLNK(filestat.st_mode))
166 		if ((p = (char *) strrchr(file, '/')) && *p+1) {
167 			/* Is this / (root)? */
168 			if (p == file)
169 				file[1] = CNULL;
170 			else
171 				*p = CNULL;
172 		}
173 
174 	if (strcmp(pathname, file) == 0)
175 		*isvalid = 1;
176 
177 	return((file && *file) ? file : NULL);
178 }
179 
180 #if defined(NFS_CHECK) || defined(RO_CHECK)
181 
182 /*
183  * Find the device that "filest" is on in the "mntinfo" linked list.
184  */
185 mntent_t *findmnt(filest, mntinfo)
186 	struct stat *filest;
187 	struct mntinfo *mntinfo;
188 {
189 	register struct mntinfo *mi;
190 
191 	for (mi = mntinfo; mi; mi = mi->mi_nxt) {
192 		if (mi->mi_mnt->me_flags & MEFLAG_IGNORE)
193 			continue;
194 		if (filest->st_dev == mi->mi_statb->st_dev)
195 			return(mi->mi_mnt);
196 	}
197 
198 	return(NULL);
199 }
200 
201 /*
202  * Is "mnt" a duplicate of any of the mntinfo->mi_mnt elements?
203  */
204 int isdupmnt(mnt, mntinfo)
205 	mntent_t *mnt;
206 	struct mntinfo *mntinfo;
207 {
208 	register struct mntinfo *m;
209 
210 	for (m = mntinfo; m; m = m->mi_nxt)
211 		if (strcmp(m->mi_mnt->me_path, mnt->me_path) == 0)
212 			return(1);
213 
214 	return(0);
215 }
216 
217 /*
218  * Alarm clock
219  */
220 void wakeup()
221 {
222 	debugmsg(DM_CALL, "wakeup() in filesys.c called");
223 	longjmp(env, 1);
224 }
225 
226 /*
227  * Make a linked list of mntinfo structures.
228  * Use "mi" as the base of the list if it's non NULL.
229  */
230 struct mntinfo *makemntinfo(mi)
231 	struct mntinfo *mi;
232 {
233 	FILE *mfp;
234 	static struct mntinfo *mntinfo, *newmi, *m;
235 	struct stat mntstat;
236 	mntent_t *mnt;
237 	int timeo = 310;
238 
239 	if (!(mfp = setmountent(MOUNTED_FILE, "r"))) {
240 		message(MT_NERROR, "%s: setmntent failed: %s",
241 			MOUNTED_FILE, SYSERR);
242 		return(NULL);
243 	}
244 
245 	(void) signal(SIGALRM, wakeup);
246 	(void) alarm(timeo);
247 	if (setjmp(env)) {
248 		message(MT_NERROR, "Timeout getting mount info");
249 		return(NULL);
250 	}
251 
252 	mntinfo = mi;
253 	while ((mnt = getmountent(mfp))) {
254 		debugmsg(DM_MISC, "mountent = '%s' (%s)",
255 			 mnt->me_path, mnt->me_type);
256 
257 		/*
258 		 * Make sure we don't already have it for some reason
259 		 */
260 		if (isdupmnt(mnt, mntinfo))
261 			continue;
262 
263 		/*
264 		 * Get stat info
265 		 */
266 		if (stat(mnt->me_path, &mntstat) != 0) {
267 			message(MT_WARNING, "%s: Cannot stat filesystem: %s",
268 				mnt->me_path, SYSERR);
269 			continue;
270 		}
271 
272 		/*
273 		 * Create new entry
274 		 */
275 		newmi = (struct mntinfo *) xcalloc(1, sizeof(struct mntinfo));
276 		newmi->mi_mnt = newmountent(mnt);
277 		newmi->mi_statb =
278 		    (struct stat *) xcalloc(1, sizeof(struct stat));
279 		bcopy((char *) &mntstat, (char *) newmi->mi_statb,
280 		      sizeof(struct stat));
281 
282 		/*
283 		 * Add entry to list
284 		 */
285 		if (mntinfo) {
286 			for (m = mntinfo; m && m->mi_nxt; m = m->mi_nxt);
287 			m->mi_nxt = newmi;
288 		} else
289 			mntinfo = newmi;
290 	}
291 
292 	(void) alarm(0);
293 	(void) endmountent(mfp);
294 
295 	return(mntinfo);
296 }
297 
298 /*
299  * Given a name like /usr/src/etc/foo.c returns the mntent
300  * structure for the file system it lives in.
301  *
302  * If "statbuf" is not NULL it is used as the stat buffer too avoid
303  * stat()'ing the file again back in server.c.
304  */
305 mntent_t *getmntpt(pathname, statbuf, isvalid)
306 	char *pathname;
307 	struct stat *statbuf;
308 	int *isvalid;
309 {
310 	static struct mntinfo *mntinfo = NULL;
311 	static struct stat filestat;
312 	struct stat *pstat;
313 	struct mntinfo *tmpmi;
314 	register mntent_t *mnt;
315 
316 	/*
317 	 * Use the supplied stat buffer if not NULL or our own.
318 	 */
319 	if (statbuf)
320 		pstat = statbuf;
321 	else
322 		pstat = &filestat;
323 
324 	if (!find_file(pathname, pstat, isvalid))
325 	        return(NULL);
326 
327 	/*
328 	 * Make mntinfo if it doesn't exist.
329 	 */
330 	if (!mntinfo)
331 		mntinfo = makemntinfo(NULL);
332 
333 	/*
334 	 * Find the mnt that pathname is on.
335 	 */
336 	if ((mnt = findmnt(pstat, mntinfo)))
337 		return(mnt);
338 
339 	/*
340 	 * We failed to find correct mnt, so maybe it's a newly
341 	 * mounted filesystem.  We rebuild mntinfo and try again.
342 	 */
343 	if ((tmpmi = makemntinfo(mntinfo))) {
344 		mntinfo = tmpmi;
345 		if ((mnt = findmnt(pstat, mntinfo)))
346 			return(mnt);
347 	}
348 
349 	error("%s: Could not find mount point", pathname);
350 	return(NULL);
351 }
352 
353 #endif /* NFS_CHECK || RO_CHECK */
354 
355 #if	defined(NFS_CHECK)
356 /*
357  * Is "path" NFS mounted?  Return 1 if it is, 0 if not, or -1 on error.
358  */
359 int is_nfs_mounted(path, statbuf, isvalid)
360 	char *path;
361 	struct stat *statbuf;
362 	int *isvalid;
363 {
364 	mntent_t *mnt;
365 
366 	if ((mnt = (mntent_t *) getmntpt(path, statbuf, isvalid)) == NULL)
367 		return(-1);
368 
369 	/*
370 	 * We treat "cachefs" just like NFS
371 	 */
372 	if ((strcmp(mnt->me_type, METYPE_NFS) == 0) ||
373 	    (strcmp(mnt->me_type, "cachefs") == 0))
374 		return(1);
375 
376 	return(0);
377 }
378 #endif	/* NFS_CHECK */
379 
380 #if	defined(RO_CHECK)
381 /*
382  * Is "path" on a read-only mounted filesystem?
383  * Return 1 if it is, 0 if not, or -1 on error.
384  */
385 int is_ro_mounted(path, statbuf, isvalid)
386 	char *path;
387 	struct stat *statbuf;
388 	int *isvalid;
389 {
390 	mntent_t *mnt;
391 
392 	if ((mnt = (mntent_t *) getmntpt(path, statbuf, isvalid)) == NULL)
393 		return(-1);
394 
395 	if (mnt->me_flags & MEFLAG_READONLY)
396 		return(1);
397 
398 	return(0);
399 }
400 #endif	/* RO_CHECK */
401 
402 /*
403  * Is "path" a symlink?
404  * Return 1 if it is, 0 if not, or -1 on error.
405  */
406 int is_symlinked(path, statbuf, isvalid)
407 	/*ARGSUSED*/
408 	char *path;
409 	struct stat *statbuf;
410 	int *isvalid;
411 {
412 	static struct stat stb;
413 
414 	if (!(*isvalid)) {
415 		if (lstat(path, &stb) != 0)
416 			return(-1);
417 		statbuf = &stb;
418 	}
419 
420 	if (S_ISLNK(statbuf->st_mode))
421 		return(1);
422 
423 	return(0);
424 }
425 
426 /*
427  * Get filesystem information for "file".  Set freespace
428  * to the amount of free (available) space and number of free
429  * files (inodes) on the filesystem "file" resides on.
430  * Returns 0 on success or -1 on failure.
431  * Filesystem values < 0 indicate unsupported or unavailable
432  * information.
433  */
434 int getfilesysinfo(file, freespace, freefiles)
435 	char *file;
436 	long *freespace;
437 	long *freefiles;
438 {
439 #if	defined(STATFS_TYPE)
440 	static statfs_t statfsbuf;
441 	char *mntpt;
442 	int t, r;
443 
444 	/*
445 	 * Get the mount point of the file.
446 	 */
447 	mntpt = find_file(file, NULL, &t);
448 	if (!mntpt) {
449 		debugmsg(DM_MISC, "unknown mount point for `%s'", file);
450 		return(-1);
451 	}
452 
453 	/*
454 	 * Stat the filesystem (system specific)
455 	 */
456 #if	STATFS_TYPE == STATFS_SYSV
457 	r = statfs(mntpt, &statfsbuf, sizeof(statfs_t), 0);
458 #endif
459 #if	STATFS_TYPE == STATFS_BSD
460 	r = statfs(mntpt, &statfsbuf);
461 #endif
462 #if	STATFS_TYPE == STATFS_OSF1
463 	r = statfs(mntpt, &statfsbuf, sizeof(statfs_t));
464 #endif
465 
466 	if (r < 0) {
467 		error("%s: Cannot statfs filesystem: %s.", mntpt, SYSERR);
468 		return(-1);
469 	}
470 
471 	/*
472 	 * If values are < 0, then assume the value is unsupported
473 	 * or unavailable for that filesystem type.
474 	 */
475 	if (statfsbuf.f_bavail >= 0)
476 		*freespace = (statfsbuf.f_bavail * (statfsbuf.f_bsize / 512))
477 			      / 2;
478 
479 	/*
480 	 * BROKEN_STATFS means that statfs() does not set fields
481 	 * to < 0 if the field is unsupported for the filesystem type.
482 	 */
483 #if	defined(BROKEN_STATFS)
484 	if (statfsbuf.f_ffree > 0)
485 #else
486 	if (statfsbuf.f_ffree >= 0)
487 #endif 	/* BROKEN_STATFS */
488 		*freefiles = statfsbuf.f_ffree;
489 
490 #else	/* !STATFS_TYPE */
491 
492     	*freespace = *freefiles = -1;
493 
494 #endif	/* STATFS_TYPE */
495 
496 	return(0);
497 }
498