/* ----------------------------------------------------------------- * * cfsadmin.c * * Cache FS admin utility. */ /* #pragma ident "@(#)cfsadmin.c 1.18 93/06/03 SMI" */ /* * (c) 1992 Sun Microsystems, Inc * All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "subr.h" char *cfsadmin_opts[] = { #define COPT_MAXBLOCKS 0 "maxblocks", #define COPT_MAXFILES 1 "maxfiles", #define COPT_HIBLOCKS 2 "hiblocks", #define COPT_LOWBLOCKS 3 "lowblocks", #define COPT_HIFILES 4 "hifiles", #define COPT_LOWFILES 5 "lowfiles", NULL }; /* size to make a buffer for holding a pathname */ #define XMAXPATH (PATH_MAX + MAXNAMELEN + 2) #define bad(val) ((val) == NULL || !isdigit(*(val))) /* numbers must be valid percentages ranging from 0 to 100 */ #define badpercent(val) \ ((val) == NULL || !isdigit(*(val)) || \ atoi((val)) < 0 || atoi((val)) > 100) char *Progname; ulong_t Fsid; /* forward references */ void usage(char *msg); int cfs_get_opts(char *oarg, struct user_values *uvp); int update_cachelabel(char *dirp, char *optionp); int cache_stats(char *dirp); int convert_old_cacheids(char *dir); int checkfid(const char *path, const struct stat *sbp, int filetype, struct FTW *ftwp) { checkfidarg_t cfarg; int status = 0; int fd; char fileheader[FILEHEADER_SIZE]; fileheader_t *fhp = (fileheader_t *)fileheader; /* * Do not process any files at the root level. */ if (ftwp->level == 0) { return(0); } switch (filetype) { case FTW_F: /* regular file */ /* * Read the file header. */ if (read_file_header(path, fileheader)) { cfarg.cf_fsid = Fsid; cfarg.cf_fid = fhp->fh_metadata.md_backfid; if (syssgi(SGI_CACHEFS_SYS, CACHEFSSYS_CHECKFID, &cfarg) == -1) { switch (errno) { case ESTALE: if ( unlink(path) == -1 ) { pr_err("Unable to remove file %s: %s", path, strerror(errno)); } else { pr_err("Stale file %s removed", path); } break; case EINTR: status = -1; break; default: pr_err("Unable to check file %s: %s", path, strerror(errno)); break; } } } break; case FTW_D: /* * Ignore directories */ break; case FTW_DP: break; case FTW_SLN: /* symlink -- there should be none of these */ if ( unlink(path) == -1 ) { pr_err("Symbolic link found but not removed: %s, error %s", path, strerror(errno)); } else { pr_err("Symbolic link found and removed: %s", path); } break; case FTW_DNR: /* unreadable directory -- log an error */ pr_err("Directory %s cannot be read", path); break; case FTW_NS: /* unsatable file/directory -- log an error */ pr_err("Stat failed for %s", path); break; default: /* invalid file type -- log an error */ pr_err("Invalid file type %d", filetype); } return(status); } void garbage_collect(char *mntdir, char *backmnt) { char *fsidp; char cacheid[PATH_MAX]; char *cp; fsidp = strrchr(backmnt, '/'); if (!fsidp) { pr_err("Invalid back mount path %s", backmnt); return; } strcpy(cacheid, fsidp + 1); strcat(cacheid, ":"); cp = &cacheid[strlen(cacheid)]; strcat(cacheid, mntdir); while ((cp = strpbrk(cp, "/")) != NULL) *cp = '_'; if (nftw(cacheid, checkfid, 5, FTW_PHYS) == -1) { if (errno != EINTR) { pr_err("nftw failed: %s", strerror(errno)); } } } /* ----------------------------------------------------------------- * * main * * Description: * Main routine for the cfsadmin program. * Arguments: * argc number of command line arguments * argv command line arguments * Returns: * Returns 0 for failure, > 0 for an error. * Preconditions: */ int main(int argc, char **argv) { struct statvfs svb; struct mntent mget; struct mntent mref; FILE *finp; struct user_values uv; int c; int xx; int lockid; char *cacheid; char *dir; int gflag; int cflag; int uflag; int dflag; int allflag; int lflag; char *optionp; int convert = 0; Progname = *argv; /* verify root running command */ if (getuid() != 0) { pr_err(gettext("must be run by root")); return (1); } (void) setlocale(LC_ALL, ""); #ifndef TEXT_DOMAIN #define TEXT_DOMAIN "SYS_TEST" #endif /* set defaults for command line options */ cflag = 0; uflag = 0; dflag = 0; allflag = 0; lflag = 0; gflag = 0; optionp = NULL; /* parse the command line arguments */ while ((c = getopt(argc, argv, "Cgcuo:d:l")) != EOF) { switch (c) { case 'C': convert = 1; break;; case 'c': /* create */ cflag = 1; break; case 'u': /* update */ uflag = 1; break; case 'd': /* delete */ dflag = 1; if (!strcmp(optarg, "all")) allflag = 1; else cacheid = optarg; break; case 'l': /* list cache ids */ lflag = 1; break; case 'o': /* options for update and create */ optionp = optarg; break; case 'g': /* garbage collection */ gflag = 1; break; default: usage(gettext("illegal option")); return (1); } } /* make sure dir is specified */ if (argc - 1 != optind) { usage(gettext("cache directory or mount point not specified")); return (1); } dir = argv[argc-1]; /* make sure a reasonable set of flags were specified */ if (convert + gflag + cflag + uflag + dflag + lflag != 1) { /* flags are mutually exclusive, at least one must be set */ usage(gettext( "exactly one of -C, -c, -u, -d, -l must be specified")); return (1); } /* make sure -o specified with -c or -u */ if (optionp && !(cflag|uflag)) { usage(gettext("-o can only be used with -c or -u")); return (1); } /* if creating a cache */ if (cflag) { /* get default cache paramaters */ user_values_defaults(&uv); /* parse the options if specified */ if (optionp) { xx = cfs_get_opts(optionp, &uv); if (xx) return (-1); } xx = create_cache(dir, &uv); if (xx != 0) { if (xx == -2) { /* remove a partially created cache dir */ (void) delete_all_cache(dir); } return (1); } } /* else if updating resource parameters */ else if (uflag) { /* lock the cache directory non-shared */ lockid = cachefs_dir_lock(dir, 0); if (lockid == -1) { /* quit if could not get the lock */ return (1); } xx = update_cachelabel(dir, optionp); cachefs_dir_unlock(lockid); if (xx != 0) { return (1); } } /* else if deleting a specific cacheID (or all caches) */ else if (dflag) { /* lock the cache directory non-shared */ lockid = cachefs_dir_lock(dir, 0); if (lockid == -1) { /* quit if could not get the lock */ return (1); } /* if the cache is in use */ if (cachefs_inuse(dir)) { pr_err(gettext("Cache %s is in use and cannot be modified."), dir); cachefs_dir_unlock(lockid); return (1); } if (allflag) xx = delete_all_cache(dir); else { xx = delete_cache(dir, cacheid); } cachefs_dir_unlock(lockid); if (xx != 0) { return (1); } } /* else if listing cache statistics */ else if (lflag) { xx = cache_stats(dir); if (xx != 0) return (1); } /* else if garbage collecting */ else if (gflag) { finp = setmntent(MOUNTED, "r"); if (finp) { bzero(&mref, sizeof(mref)); mref.mnt_dir = dir; mref.mnt_type = MNTTYPE_CACHEFS; xx = getmntany(finp, &mget, &mref); if (xx != -1) { if (statvfs(mget.mnt_fsname, &svb) == 0) { Fsid = svb.f_fsid; garbage_collect(dir, mget.mnt_fsname); } else { pr_err(gettext("Cannot stat %s: %s"), mget.mnt_fsname, strerror(errno)); } } else { pr_err(gettext("%s not mounted"), dir); } endmntent(finp); } } else if (convert) { return(convert_old_cacheids(dir)); } /* return success */ return (0); } /* ----------------------------------------------------------------- * * usage * * Description: * Prints a usage message for this utility. * Arguments: * msgp message to include with the usage message * Returns: * Preconditions: * precond(msgp) */ void usage(char *msgp) { fprintf(stderr, gettext("cfsadmin: %s\n"), msgp); fprintf(stderr, gettext( "usage: cfsadmin -[cu] [-o parameter-list] cachedir\n")); fprintf(stderr, gettext(" cfsadmin -d [CacheID|all] cachedir\n")); fprintf(stderr, gettext(" cfsadmin -l cachedir\n")); fprintf(stderr, gettext(" cfsadmin -g mntpoint\n")); } /* ----------------------------------------------------------------- * * cfs_get_opts * * Description: * Decodes cfs options specified with -o. * Only the fields referenced by the options are modified. * Arguments: * oarg options from -o option * uvp place to put options * Returns: * Returns 0 for success, -1 for an error. * Preconditions: * precond(oarg) * precond(uvp) */ int cfs_get_opts(char *oarg, struct user_values *uvp) { char *optstr, *opts, *val; char *saveopts; int badopt; /* make a copy of the options because getsubopt modifies it */ optstr = opts = strdup(oarg); if (opts == NULL) { pr_err(gettext("no memory")); return (-1); } /* process the options */ badopt = 0; while (*opts && !badopt) { saveopts = opts; switch (getsubopt(&opts, cfsadmin_opts, &val)) { case COPT_MAXBLOCKS: if (badpercent(val)) badopt = 1; else uvp->uv_maxblocks = atoi(val); break; case COPT_MAXFILES: if (badpercent(val)) badopt = 1; else uvp->uv_maxfiles = atoi(val); break; case COPT_HIBLOCKS: if (badpercent(val)) badopt = 1; else uvp->uv_hiblocks = atoi(val); break; case COPT_LOWBLOCKS: if (badpercent(val)) badopt = 1; else uvp->uv_lowblocks = atoi(val); break; case COPT_HIFILES: if (badpercent(val)) badopt = 1; else uvp->uv_hifiles = atoi(val); break; case COPT_LOWFILES: if (badpercent(val)) badopt = 1; else uvp->uv_lowfiles = atoi(val); break; default: /* if a bad option argument */ pr_err(gettext("Invalid option %s"), saveopts); return (-1); } } /* if a bad value for an option, display an error message */ if (badopt) { pr_err(gettext("invalid argument to option: \"%s\""), saveopts); } /* free the duplicated option string */ free(optstr); /* return the result */ return (badopt ? -1 : 0); } /* ----------------------------------------------------------------- * * update_cachelabel * * Description: * Changes the parameters of the cache_label. * If optionp is NULL then the cache_label is set to * default values. * Arguments: * dirp the name of the cache directory * optionp comma delimited options * Returns: * Returns 0 for success and -1 for an error. * Preconditions: * precond(dirp) */ int update_cachelabel(char *dirp, char *optionp) { char path[XMAXPATH]; struct cache_label clabel_new; struct cache_label clabel_orig; struct user_values uv_orig, uv_new; int xx; /* if the cache is in use */ if (cachefs_inuse(dirp)) { pr_err(gettext("Cache %s is in use and cannot be modified."), dirp); return (-1); } /* make sure we don't overwrite path */ if (strlen(dirp) > (size_t)PATH_MAX) { pr_err(gettext("name of label file %s is too long."), dirp); return (-1); } /* construct the pathname to the cach_label file */ sprintf(path, "%s/%s", dirp, CACHELABEL_NAME); /* read the current set of parameters */ xx = cachefs_label_file_get(path, &clabel_orig); if (xx == -1) { pr_err(gettext("reading %s failed"), path); return (-1); } clabel_new = clabel_orig; /* convert the cache_label to user values */ xx = convert_cl2uv(&clabel_orig, &uv_orig, dirp); if (xx) { return (-1); } /* if options were specified */ if (optionp) { /* start with the original values */ uv_new = uv_orig; /* parse the options */ xx = cfs_get_opts(optionp, &uv_new); if (xx) { return (-1); } /* verify options are reasonable */ xx = check_user_values_for_sanity(&uv_new); if (xx) { return (-1); } } /* else if options where not specified, get defaults */ else { user_values_defaults(&uv_new); } /* convert user values to a cache_label */ xx = convert_uv2cl(&uv_new, &clabel_new, dirp); if (xx) { return (-1); } /* write back the new values */ xx = cachefs_label_file_put(path, &clabel_new); if (xx == -1) { pr_err(gettext("writing %s failed"), path); return (-1); } /* put the new values in the duplicate cache label file also */ sprintf(path, "%s/%s.dup", dirp, CACHELABEL_NAME); xx = cachefs_label_file_put(path, &clabel_new); if (xx == -1) { pr_err(gettext("writing %s failed"), path); return (-1); } /* return status */ return (xx); } /* ----------------------------------------------------------------- * * cache_stats * * Description: * Show each cache in the directory, cache resource statistics, * and, for each fs in the cache, the name of the fs, and the * cache resource parameters. * Arguments: * dirp name of the cache directory * Returns: * Returns 0 for success, -1 for an error. * Errors: * Preconditions: */ int cache_stats(char *dirp) { DIR *dp; struct dirent *dep; char path[XMAXPATH]; struct stat statinfo; int ret; int xx; struct cache_label clabel; struct user_values uv; struct statvfs svb; /* make sure cache dir name is not too long */ if (strlen(dirp) > (size_t)PATH_MAX) { pr_err(gettext("path name %s is too long."), dirp); return (-1); } if (statvfs(dirp, &svb) == -1) { pr_err(gettext("statvfs failed for %s."), dirp); return (-1); } /* read the cache label file */ sprintf(path, "%s/%s", dirp, CACHELABEL_NAME); xx = cachefs_label_file_get(path, &clabel); if (xx == -1) { pr_err(gettext("Reading %s failed."), path); return (-1); } /* convert the cache_label to user values */ xx = convert_cl2uv(&clabel, &uv, dirp); if (xx) return (-1); /* display the parameters */ printf(gettext("cfsadmin: list cache FS information\n")); printf(gettext(" Version %3d %3d %3d\n"), (unsigned int)clabel.cl_cfsversion, (unsigned int)clabel.cl_cfslongsize, (unsigned int)clabel.cl_cfsmetasize); /* * report the block limits in 512 byte units for consistency with * df */ printf(gettext(" maxblocks %3d%% (%d blocks)\n"), uv.uv_maxblocks, clabel.cl_maxblks * (svb.f_frsize / 512)); printf(gettext(" hiblocks %3d%% (%d blocks)\n"), uv.uv_hiblocks, clabel.cl_blkhiwat * (svb.f_frsize / 512)); printf(gettext(" lowblocks %3d%% (%d blocks)\n"), uv.uv_lowblocks, clabel.cl_blklowat * (svb.f_frsize / 512)); printf(gettext(" maxfiles %3d%% (%d files)\n"), uv.uv_maxfiles, clabel.cl_maxfiles); printf(gettext(" hifiles %3d%% (%d files)\n"), uv.uv_hifiles, clabel.cl_filehiwat); printf(gettext(" lowfiles %3d%% (%d files)\n"), uv.uv_lowfiles, clabel.cl_filelowat); /* open the directory */ if ((dp = opendir(dirp)) == NULL) { pr_err(gettext("opendir %s failed: %s"), dirp, strerror(errno)); return (-1); } /* loop reading the contents of the directory */ ret = 0; while ((dep = readdir(dp)) != NULL) { /* ignore . and .. */ /* * all cache ID names begin with CACHEID_PREFIX */ if (strncmp(dep->d_name, CACHEID_PREFIX, CACHEID_PREFIX_LEN) == 0) { /* stat the file */ sprintf(path, "%s/%s", dirp, dep->d_name); if (lstat(path, &statinfo) == -1) { pr_err(gettext("lstat %s failed: %s"), path, strerror(errno)); closedir(dp); return (-1); } if (S_ISDIR(statinfo.st_mode)) { /* print the file system cache directory name */ printf(gettext(" %s\n"), dep->d_name); } } } closedir(dp); /* return status */ return (ret); } char * get_fsname(char *cacheid) { char *cp; char *last_colon; int colons; char *fsname = cacheid; /* * count the colons and point at the last one */ for (cp = cacheid, colons = 0, last_colon = NULL; cp = strchr(cp, ':'); last_colon = cp, colons++, cp++) ; switch (colons) { case 1: case 2: assert(last_colon); fsname = malloc(last_colon - cacheid + 1); if (!fsname) { return(NULL); } strncpy(fsname, cacheid, last_colon - cacheid); fsname[last_colon - cacheid] = '\0'; break; default: printf("Old cache IDs are of the form :\n"); printf("The fsname could not be ddetermined for %s.\n", cacheid); fsname = malloc(strlen(cacheid) + 1); if (!fsname) { return(NULL); } printf("Please enter the fsname: "); fsname = fgets(fsname, strlen(cacheid), stdin); } return(fsname); } int convert_old_cacheids(char *dirp) { int repeat_question; DIR *dp; struct dirent *dep; char newname[MAXNAMELEN]; char *fsname; struct stat statinfo; printf("Converting old cache IDs to new ones for cache %s.\n", dirp); if (chdir(dirp) == -1) { pr_err(gettext("Could not chdir to %s: %s"), dirp, strerror(errno)); return(-1); } /* open the directory */ if ((dp = opendir(".")) == NULL) { pr_err(gettext("opendir failed for %s: %s"), dirp, strerror(errno)); return(-1); } /* loop reading the contents of the directory */ while ((dep = readdir(dp)) != NULL) { /* * skip ., .., and BACKMNT_NAME */ if ((strcmp(dep->d_name, ".") == 0) || (strcmp(dep->d_name, "..") == 0) || (strcmp(dep->d_name, BACKMNT_NAME) == 0)) { continue; } /* * all cache ID names begin with CACHEID_PREFIX */ /* stat the file */ if (lstat(dep->d_name, &statinfo) == -1) { pr_err(gettext("lstat %s failed: %s"), dep->d_name, strerror(errno)); closedir(dp); return(-1); } if (S_ISDIR(statinfo.st_mode) && (strncmp(dep->d_name, CACHEID_PREFIX, CACHEID_PREFIX_LEN) != 0)) { fsname = get_fsname(dep->d_name); if (!fsname) { pr_err(gettext("Unable to get fsname for %s"), dep->d_name); closedir(dp); return(-1); } strcpy(newname, CACHEID_PREFIX); strcat(newname, fsname); if (rename(dep->d_name, newname) == -1) { if (errno == EEXIST) { printf("Failed to rename %s to %s\n", dep->d_name, newname); printf("%s exists and is not empty.\n", newname); do { repeat_question = 0; printf("Remove %s (y(es), n(o), or q(uit))? ", newname); switch (getchar()) { case 'y': case 'Y': if (delete_cache(dirp, newname) == -1) { closedir(dp); return(-1); } if (rename(dep->d_name, newname) == -1) { pr_err(gettext( "Unable to rename %s to %s: %s"), dep->d_name, newname, strerror(errno)); closedir(dp); return(-1); } else { printf("%s renamed to %s\n", dep->d_name, newname); } break; case 'n': case 'N': do { repeat_question = 0; printf("\nRemove %s (y(es) or n(o))? ", dep->d_name); switch (getchar()) { case 'y': case 'Y': if (delete_cache(dirp, dep->d_name) == -1) { closedir(dp); return(-1); } else { printf("%s removed\n", dep->d_name); } break; case 'n': case 'N': strcpy(newname, CACHEID_PREFIX); strcat(newname, dep->d_name); if (rename(dep->d_name, newname) == -1) { pr_err(gettext( "Unable to rename " "%s to %s: %s"), dep->d_name, newname, strerror(errno)); closedir(dp); return(-1); } else { printf("%s renamed to %s\n", dep->d_name, newname); } break; default: repeat_question = 1; } } while (repeat_question); break; case 'q': case 'Q': closedir(dp); return(0); default: repeat_question = 1; } putchar('\n'); } while (repeat_question); } else { pr_err(gettext("Unable to rename %s to %s: %s"), dep->d_name, newname, strerror(errno)); closedir(dp); return(-1); } } else { printf("%s renamed to %s\n", dep->d_name, newname); } rewinddir(dp); } } closedir(dp); return(0); }