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