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