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