xref: /openbsd-src/usr.bin/rdistd/filesys.c (revision 47911bd667ac77dc523b8a13ef40b012dbffa741)
1 /*	$OpenBSD: filesys.c,v 1.7 2001/11/19 19:02:15 mpech 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.7 2001/11/19 19:02:15 mpech 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 	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 	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 	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;
235 	struct mntinfo *newmi, *m;
236 	struct stat mntstat;
237 	mntent_t *mnt;
238 	int timeo = 310;
239 
240 	if (!(mfp = setmountent(MOUNTED_FILE, "r"))) {
241 		message(MT_NERROR, "%s: setmntent failed: %s",
242 			MOUNTED_FILE, SYSERR);
243 		return(NULL);
244 	}
245 
246 	(void) signal(SIGALRM, wakeup);
247 	(void) alarm(timeo);
248 	if (setjmp(env)) {
249 		message(MT_NERROR, "Timeout getting mount info");
250 		return(NULL);
251 	}
252 
253 	mntinfo = mi;
254 	while ((mnt = getmountent(mfp))) {
255 		debugmsg(DM_MISC, "mountent = '%s' (%s)",
256 			 mnt->me_path, mnt->me_type);
257 
258 		/*
259 		 * Make sure we don't already have it for some reason
260 		 */
261 		if (isdupmnt(mnt, mntinfo))
262 			continue;
263 
264 		/*
265 		 * Get stat info
266 		 */
267 		if (stat(mnt->me_path, &mntstat) != 0) {
268 			message(MT_WARNING, "%s: Cannot stat filesystem: %s",
269 				mnt->me_path, SYSERR);
270 			continue;
271 		}
272 
273 		/*
274 		 * Create new entry
275 		 */
276 		newmi = (struct mntinfo *) xcalloc(1, sizeof(struct mntinfo));
277 		newmi->mi_mnt = newmountent(mnt);
278 		newmi->mi_statb =
279 		    (struct stat *) xcalloc(1, sizeof(struct stat));
280 		bcopy((char *) &mntstat, (char *) newmi->mi_statb,
281 		      sizeof(struct stat));
282 
283 		/*
284 		 * Add entry to list
285 		 */
286 		if (mntinfo) {
287 			for (m = mntinfo; m->mi_nxt; m = m->mi_nxt);
288 			m->mi_nxt = newmi;
289 		} else
290 			mntinfo = newmi;
291 	}
292 
293 	(void) alarm(0);
294 	(void) endmountent(mfp);
295 
296 	return(mntinfo);
297 }
298 
299 /*
300  * Given a name like /usr/src/etc/foo.c returns the mntent
301  * structure for the file system it lives in.
302  *
303  * If "statbuf" is not NULL it is used as the stat buffer too avoid
304  * stat()'ing the file again back in server.c.
305  */
306 mntent_t *getmntpt(pathname, statbuf, isvalid)
307 	char *pathname;
308 	struct stat *statbuf;
309 	int *isvalid;
310 {
311 	static struct mntinfo *mntinfo = NULL;
312 	static struct stat filestat;
313 	struct stat *pstat;
314 	struct mntinfo *tmpmi;
315 	mntent_t *mnt;
316 
317 	/*
318 	 * Use the supplied stat buffer if not NULL or our own.
319 	 */
320 	if (statbuf)
321 		pstat = statbuf;
322 	else
323 		pstat = &filestat;
324 
325 	if (!find_file(pathname, pstat, isvalid))
326 	        return(NULL);
327 
328 	/*
329 	 * Make mntinfo if it doesn't exist.
330 	 */
331 	if (!mntinfo)
332 		mntinfo = makemntinfo(NULL);
333 
334 	/*
335 	 * Find the mnt that pathname is on.
336 	 */
337 	if ((mnt = findmnt(pstat, mntinfo)))
338 		return(mnt);
339 
340 	/*
341 	 * We failed to find correct mnt, so maybe it's a newly
342 	 * mounted filesystem.  We rebuild mntinfo and try again.
343 	 */
344 	if ((tmpmi = makemntinfo(mntinfo))) {
345 		mntinfo = tmpmi;
346 		if ((mnt = findmnt(pstat, mntinfo)))
347 			return(mnt);
348 	}
349 
350 	error("%s: Could not find mount point", pathname);
351 	return(NULL);
352 }
353 
354 #endif /* NFS_CHECK || RO_CHECK */
355 
356 #if	defined(NFS_CHECK)
357 /*
358  * Is "path" NFS mounted?  Return 1 if it is, 0 if not, or -1 on error.
359  */
360 int is_nfs_mounted(path, statbuf, isvalid)
361 	char *path;
362 	struct stat *statbuf;
363 	int *isvalid;
364 {
365 	mntent_t *mnt;
366 
367 	if ((mnt = (mntent_t *) getmntpt(path, statbuf, isvalid)) == NULL)
368 		return(-1);
369 
370 	/*
371 	 * We treat "cachefs" just like NFS
372 	 */
373 	if ((strcmp(mnt->me_type, METYPE_NFS) == 0) ||
374 	    (strcmp(mnt->me_type, "cachefs") == 0))
375 		return(1);
376 
377 	return(0);
378 }
379 #endif	/* NFS_CHECK */
380 
381 #if	defined(RO_CHECK)
382 /*
383  * Is "path" on a read-only mounted filesystem?
384  * Return 1 if it is, 0 if not, or -1 on error.
385  */
386 int is_ro_mounted(path, statbuf, isvalid)
387 	char *path;
388 	struct stat *statbuf;
389 	int *isvalid;
390 {
391 	mntent_t *mnt;
392 
393 	if ((mnt = (mntent_t *) getmntpt(path, statbuf, isvalid)) == NULL)
394 		return(-1);
395 
396 	if (mnt->me_flags & MEFLAG_READONLY)
397 		return(1);
398 
399 	return(0);
400 }
401 #endif	/* RO_CHECK */
402 
403 /*
404  * Is "path" a symlink?
405  * Return 1 if it is, 0 if not, or -1 on error.
406  */
407 int is_symlinked(path, statbuf, isvalid)
408 	/*ARGSUSED*/
409 	char *path;
410 	struct stat *statbuf;
411 	int *isvalid;
412 {
413 	static struct stat stb;
414 
415 	if (!(*isvalid)) {
416 		if (lstat(path, &stb) != 0)
417 			return(-1);
418 		statbuf = &stb;
419 	}
420 
421 	if (S_ISLNK(statbuf->st_mode))
422 		return(1);
423 
424 	return(0);
425 }
426 
427 /*
428  * Get filesystem information for "file".  Set freespace
429  * to the amount of free (available) space and number of free
430  * files (inodes) on the filesystem "file" resides on.
431  * Returns 0 on success or -1 on failure.
432  * Filesystem values < 0 indicate unsupported or unavailable
433  * information.
434  */
435 int getfilesysinfo(file, freespace, freefiles)
436 	char *file;
437 	long *freespace;
438 	long *freefiles;
439 {
440 #if	defined(STATFS_TYPE)
441 	static statfs_t statfsbuf;
442 	char *mntpt;
443 	int t, r;
444 
445 	/*
446 	 * Get the mount point of the file.
447 	 */
448 	mntpt = find_file(file, NULL, &t);
449 	if (!mntpt) {
450 		debugmsg(DM_MISC, "unknown mount point for `%s'", file);
451 		return(-1);
452 	}
453 
454 	/*
455 	 * Stat the filesystem (system specific)
456 	 */
457 #if	STATFS_TYPE == STATFS_SYSV
458 	r = statfs(mntpt, &statfsbuf, sizeof(statfs_t), 0);
459 #endif
460 #if	STATFS_TYPE == STATFS_BSD
461 	r = statfs(mntpt, &statfsbuf);
462 #endif
463 #if	STATFS_TYPE == STATFS_OSF1
464 	r = statfs(mntpt, &statfsbuf, sizeof(statfs_t));
465 #endif
466 
467 	if (r < 0) {
468 		error("%s: Cannot statfs filesystem: %s.", mntpt, SYSERR);
469 		return(-1);
470 	}
471 
472 	/*
473 	 * If values are < 0, then assume the value is unsupported
474 	 * or unavailable for that filesystem type.
475 	 */
476 	if (statfsbuf.f_bavail >= 0)
477 		*freespace = (statfsbuf.f_bavail * (statfsbuf.f_bsize / 512))
478 			      / 2;
479 
480 	/*
481 	 * BROKEN_STATFS means that statfs() does not set fields
482 	 * to < 0 if the field is unsupported for the filesystem type.
483 	 */
484 #if	defined(BROKEN_STATFS)
485 	if (statfsbuf.f_ffree > 0)
486 #else
487 	if (statfsbuf.f_ffree >= 0)
488 #endif 	/* BROKEN_STATFS */
489 		*freefiles = statfsbuf.f_ffree;
490 
491 #else	/* !STATFS_TYPE */
492 
493     	*freespace = *freefiles = -1;
494 
495 #endif	/* STATFS_TYPE */
496 
497 	return(0);
498 }
499