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