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