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