| /* |
| * |
| * Utility to check disk quotas |
| * |
| * Some parts of this utility are copied from old quotacheck by |
| * Marco van Wieringen <mvw@planets.elm.net> and Edvard Tuinder <ed@elm.net> |
| * |
| * New quota format implementation - Jan Kara <jack@suse.cz> - Sponsored by SuSE CR |
| */ |
| |
| #include "config.h" |
| |
| #include <dirent.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <stdarg.h> |
| #include <getopt.h> |
| #include <limits.h> |
| #include <unistd.h> |
| #include <stdlib.h> |
| #include <errno.h> |
| |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <sys/file.h> |
| #include <sys/statfs.h> |
| #include <sys/ioctl.h> |
| #include <sys/mount.h> |
| #include <sys/utsname.h> |
| |
| #ifdef EXT2_DIRECT |
| #include <linux/types.h> |
| #include <ext2fs/ext2fs.h> |
| #endif |
| |
| #include "pot.h" |
| #include "common.h" |
| #include "quotaio.h" |
| #include "quotasys.h" |
| #include "mntopt.h" |
| #include "bylabel.h" |
| #include "quotacheck.h" |
| #include "quotaops.h" |
| |
| #define LINKSHASHSIZE 16384 /* Size of hashtable for hardlinked inodes */ |
| #define DQUOTHASHSIZE 32768 /* Size of hashtable for dquots from file */ |
| |
| struct dlinks { |
| ino_t i_num; |
| struct dlinks *next; |
| }; |
| |
| struct dirs { |
| char *dir_name; |
| struct dirs *next; |
| }; |
| |
| #define BITS_SIZE 4 /* sizeof(bits) == 5 */ |
| #define BLIT_RATIO 10 /* Blit in just 1/10 of blit() calls */ |
| |
| static dev_t cur_dev; /* Device we are working on */ |
| static int files_done, dirs_done; |
| int flags, fmt = -1, cfmt; /* Options from command line; Quota format to use spec. by user; Actual format to check */ |
| static int uwant, gwant, ucheck, gcheck; /* Does user want to check user/group quota; Do we check user/group quota? */ |
| static char *mntpoint; /* Mountpoint to check */ |
| char *progname; |
| struct util_dqinfo old_info[MAXQUOTAS]; /* Loaded infos */ |
| |
| static char extensions[MAXQUOTAS + 2][20] = INITQFNAMES; /* Extensions depending on quota type */ |
| static char *basenames[] = INITQFBASENAMES; /* Names of quota files */ |
| |
| #ifdef DEBUG_MALLOC |
| static size_t malloc_mem = 0; |
| static size_t free_mem = 0; |
| #endif |
| |
| static struct dquot *dquot_hash[MAXQUOTAS][DQUOTHASHSIZE]; |
| static struct dlinks *links_hash[MAXQUOTAS][DQUOTHASHSIZE]; |
| |
| /* |
| * Ok check each memory allocation. |
| */ |
| void *xmalloc(size_t size) |
| { |
| void *ptr; |
| |
| #ifdef DEBUG_MALLOC |
| malloc_mem += size; |
| #endif |
| ptr = malloc(size); |
| if (!ptr) |
| die(3, _("Not enough memory.\n")); |
| memset(ptr, 0, size); |
| return (ptr); |
| } |
| |
| void debug(int df, char *fmtstr, ...) |
| { |
| va_list args; |
| |
| if (!(flags & df)) |
| return; |
| |
| fprintf(stderr, "%s: ", progname); |
| va_start(args, fmtstr); |
| vfprintf(stderr, fmtstr, args); |
| va_end(args); |
| } |
| |
| /* Compute hashvalue for given inode number */ |
| static inline uint hash_ino(uint i_num) |
| { |
| return ((i_num ^ (i_num << 16)) * 997) & (LINKSHASHSIZE - 1); |
| } |
| |
| /* |
| * Store a hardlinked inode as we don't want to count it more then once. |
| */ |
| static int store_dlinks(int type, ino_t i_num) |
| { |
| struct dlinks *lptr; |
| uint hash = hash_ino(i_num); |
| |
| debug(FL_DEBUG, _("Adding hardlink for inode %llu\n"), (unsigned long long)i_num); |
| |
| for (lptr = links_hash[type][hash]; lptr; lptr = lptr->next) |
| if (lptr->i_num == i_num) |
| return 1; |
| |
| lptr = (struct dlinks *)xmalloc(sizeof(struct dlinks)); |
| |
| lptr->i_num = i_num; |
| lptr->next = links_hash[type][hash]; |
| links_hash[type][hash] = lptr; |
| return 0; |
| } |
| |
| /* Hash given id */ |
| static inline uint hash_dquot(uint id) |
| { |
| return ((id ^ (id << 16)) * 997) & (DQUOTHASHSIZE - 1); |
| } |
| |
| /* |
| * Do a lookup of a type of quota for a specific id. Use short cut with |
| * most recently used dquot struct pointer. |
| */ |
| struct dquot *lookup_dquot(qid_t id, int type) |
| { |
| struct dquot *lptr; |
| uint hash = hash_dquot(id); |
| |
| for (lptr = dquot_hash[type][hash]; lptr != NODQUOT; lptr = lptr->dq_next) |
| if (lptr->dq_id == id) |
| return lptr; |
| return NODQUOT; |
| } |
| |
| /* |
| * Add a new dquot for a new id to the list. |
| */ |
| struct dquot *add_dquot(qid_t id, int type) |
| { |
| struct dquot *lptr; |
| uint hash = hash_dquot(id); |
| |
| debug(FL_DEBUG, _("Adding dquot structure type %s for %d\n"), type2name(type), (int)id); |
| |
| lptr = (struct dquot *)xmalloc(sizeof(struct dquot)); |
| |
| lptr->dq_id = id; |
| lptr->dq_next = dquot_hash[type][hash]; |
| dquot_hash[type][hash] = lptr; |
| lptr->dq_dqb.dqb_btime = lptr->dq_dqb.dqb_itime = (time_t) 0; |
| |
| return lptr; |
| } |
| |
| /* |
| * Add a number of blocks and inodes to a quota. |
| */ |
| static void add_to_quota(int type, ino_t i_num, uid_t i_uid, gid_t i_gid, mode_t i_mode, |
| nlink_t i_nlink, loff_t i_space, int need_remember) |
| { |
| qid_t wanted; |
| struct dquot *lptr; |
| |
| if (type == USRQUOTA) |
| wanted = i_uid; |
| else |
| wanted = i_gid; |
| |
| if ((lptr = lookup_dquot(wanted, type)) == NODQUOT) |
| lptr = add_dquot(wanted, type); |
| |
| if (i_nlink != 1 && need_remember) |
| if (store_dlinks(type, i_num)) /* Did we already count this inode? */ |
| return; |
| lptr->dq_dqb.dqb_curinodes++; |
| lptr->dq_dqb.dqb_curspace += i_space;; |
| } |
| |
| /* |
| * Clean up all list from a previous run. |
| */ |
| static void remove_list(void) |
| { |
| int cnt; |
| uint i; |
| struct dquot *dquot, *dquot_free; |
| struct dlinks *dlink, *dlink_free; |
| |
| for (cnt = 0; cnt < MAXQUOTAS; cnt++) { |
| for (i = 0; i < DQUOTHASHSIZE; i++) { |
| dquot = dquot_hash[cnt][i]; |
| while (dquot != NODQUOT) { |
| dquot_free = dquot; |
| dquot = dquot->dq_next; |
| #ifdef DEBUG_MALLOC |
| free_mem += sizeof(struct dquot); |
| #endif |
| free(dquot_free); |
| } |
| dquot_hash[cnt][i] = NODQUOT; |
| } |
| for (i = 0; i < LINKSHASHSIZE; i++) { |
| dlink = links_hash[cnt][i]; |
| while (dlink) { |
| dlink_free = dlink; |
| dlink = dlink->next; |
| #ifdef DEBUG_MALLOC |
| free_mem += sizeof(struct dlinks); |
| #endif |
| free(dlink_free); |
| } |
| links_hash[cnt][i] = NULL; |
| } |
| } |
| } |
| |
| /* Get size used by file */ |
| static loff_t getqsize(const char *fname, struct stat *st) |
| { |
| static char ioctl_fail_warn; |
| int fd; |
| loff_t size; |
| |
| if (S_ISLNK(st->st_mode)) /* There's no way to do ioctl() on links... */ |
| return st->st_blocks << 9; |
| if (!S_ISDIR(st->st_mode) && !S_ISREG(st->st_mode)) |
| return st->st_blocks << 9; |
| if ((fd = open(fname, O_RDONLY)) == -1) |
| die(2, _("Cannot open file %s: %s\n"), fname, strerror(errno)); |
| if (ioctl(fd, FIOQSIZE, &size) == -1) { |
| size = st->st_blocks << 9; |
| if (!ioctl_fail_warn) { |
| ioctl_fail_warn = 1; |
| fputs(_("Cannot get exact used space... Results might be inaccurate.\n"), stderr); |
| } |
| } |
| close(fd); |
| return size; |
| } |
| |
| /* |
| * Show a blitting cursor as means of visual progress indicator. |
| */ |
| static inline void blit(const char *msg) |
| { |
| static int bitc = 0; |
| static const char bits[] = "|/-\\"; |
| static int slow_down; |
| |
| if (flags & FL_VERYVERBOSE && msg) { |
| int len = strlen(msg); |
| |
| putchar('\r'); |
| printf("%.70s", msg); |
| if (len > 70) |
| fputs("...", stdout); |
| else |
| printf("%*s",73-len, ""); |
| } |
| if (flags & FL_VERYVERBOSE || ++slow_down >= BLIT_RATIO) { |
| putchar(bits[bitc]); |
| putchar('\b'); |
| fflush(stdout); |
| bitc++; |
| bitc %= BITS_SIZE; |
| slow_down = 0; |
| } |
| } |
| |
| static void usage(void) |
| { |
| printf(_("Utility for checking and repairing quota files.\n%s [-gucbfinvdmMR] [-F <quota-format>] filesystem|-a\n\n\ |
| -u, --user check user files\n\ |
| -g, --group check group files\n\ |
| -c, --create-files create new quota files\n\ |
| -b, --backup create backups of old quota files\n\ |
| -f, --force force check even if quotas are enabled\n\ |
| -i, --interactive interactive mode\n\ |
| -n, --use-first-dquot use the first copy of duplicated structure\n\ |
| -v, --verbose print more information\n\ |
| -d, --debug print even more messages\n\ |
| -m, --no-remount do not remount filesystem read-only\n\ |
| -M, --try-remount try remounting filesystem read-only,\n\ |
| continue even if it fails\n\ |
| -R, --exclude-root exclude root when checking all filesystems\n\ |
| -F, --format=formatname check quota files of specific format\n\ |
| -a, --all check all filesystems\n\ |
| -h, --help display this message and exit\n\ |
| -V, --version display version information and exit\n\n"), progname); |
| printf(_("Bugs to %s\n"), PACKAGE_BUGREPORT); |
| exit(1); |
| } |
| |
| static void parse_options(int argcnt, char **argstr) |
| { |
| int ret; |
| struct option long_opts[] = { |
| { "version", 0, NULL, 'V' }, |
| { "help", 0, NULL, 'h' }, |
| { "backup", 0, NULL, 'b' }, |
| { "create-files", 0, NULL, 'c' }, |
| { "verbose", 0, NULL, 'v' }, |
| { "debug", 0, NULL, 'd' }, |
| { "user", 0, NULL, 'u' }, |
| { "group", 0, NULL, 'g' }, |
| { "interactive", 0, NULL, 'i' }, |
| { "use-first-dquot", 0, NULL, 'n' }, |
| { "force", 0, NULL, 'f' }, |
| { "format", 1, NULL, 'F' }, |
| { "no-remount", 0, NULL, 'm' }, |
| { "try-remount", 0, NULL, 'M' }, |
| { "exclude-root", 0, NULL, 'R' }, |
| { "all", 0, NULL, 'a' }, |
| { NULL, 0, NULL, 0 } |
| }; |
| |
| while ((ret = getopt_long(argcnt, argstr, "VhbcvugidnfF:mMRa", long_opts, NULL)) != -1) { |
| switch (ret) { |
| case 'b': |
| flags |= FL_BACKUPS; |
| break; |
| case 'g': |
| gwant = 1; |
| break; |
| case 'u': |
| uwant = 1; |
| break; |
| case 'd': |
| flags |= FL_DEBUG; |
| setlinebuf(stderr); |
| break; |
| case 'v': |
| if (flags & FL_VERBOSE) |
| flags |= FL_VERYVERBOSE; |
| else |
| flags |= FL_VERBOSE; |
| break; |
| case 'f': |
| flags |= FL_FORCE; |
| break; |
| case 'i': |
| flags |= FL_INTERACTIVE; |
| break; |
| case 'n': |
| flags |= FL_GUESSDQ; |
| break; |
| case 'c': |
| flags |= FL_NEWFILE; |
| break; |
| case 'V': |
| version(); |
| exit(0); |
| case 'M': |
| flags |= FL_FORCEREMOUNT; |
| break; |
| case 'm': |
| flags |= FL_NOREMOUNT; |
| break; |
| case 'a': |
| flags |= FL_ALL; |
| break; |
| case 'R': |
| flags |= FL_NOROOT; |
| break; |
| case 'F': |
| if ((fmt = name2fmt(optarg)) == QF_ERROR) |
| exit(1); |
| break; |
| default: |
| usage(); |
| } |
| } |
| if (!(uwant | gwant)) |
| uwant = 1; |
| if ((argcnt == optind && !(flags & FL_ALL)) || (argcnt > optind && flags & FL_ALL)) { |
| fputs(_("Bad number of arguments.\n"), stderr); |
| usage(); |
| } |
| if (flags & FL_VERBOSE && flags & FL_DEBUG) |
| flags &= ~FL_VERBOSE; |
| if (!(flags & FL_ALL)) |
| mntpoint = argstr[optind]; |
| else |
| mntpoint = NULL; |
| } |
| |
| #if defined(EXT2_DIRECT) |
| static int ext2_direct_scan(const char *device) |
| { |
| ext2_ino_t i_num; |
| ext2_filsys fs; |
| errcode_t error; |
| ext2_inode_scan scan; |
| struct ext2_inode inode; |
| int inode_buffer_blocks = 0; |
| ext2fs_inode_bitmap inode_used_map; |
| ext2fs_inode_bitmap inode_dir_map; |
| uid_t uid; |
| gid_t gid; |
| |
| if ((error = ext2fs_open(device, 0, 0, 0, unix_io_manager, &fs))) { |
| errstr(_("error (%d) while opening %s\n"), (int)error, device); |
| return -1; |
| } |
| |
| if ((error = ext2fs_allocate_inode_bitmap(fs, "in-use inode map", &inode_used_map))) { |
| errstr(_("error (%d) while allocating file inode bitmap\n"), (int)error); |
| return -1; |
| } |
| |
| if ((error = ext2fs_allocate_inode_bitmap(fs, "directory inode map", &inode_dir_map))) { |
| errstr(_("errstr (%d) while allocating directory inode bitmap\n"), (int)error); |
| return -1; |
| } |
| |
| if ((error = ext2fs_open_inode_scan(fs, inode_buffer_blocks, &scan))) { |
| errstr(_("error (%d) while opening inode scan\n"), (int)error); |
| return -1; |
| } |
| |
| if ((error = ext2fs_get_next_inode(scan, &i_num, &inode))) { |
| errstr(_("error (%d) while starting inode scan\n"), (int)error); |
| return -1; |
| } |
| |
| while (i_num) { |
| if ((i_num == EXT2_ROOT_INO || |
| i_num >= EXT2_FIRST_INO(fs->super)) && |
| inode.i_links_count) { |
| debug(FL_DEBUG, _("Found i_num %ld, blocks %ld\n"), (long)i_num, (long)inode.i_blocks); |
| if (flags & FL_VERBOSE) |
| blit(NULL); |
| uid = inode.i_uid | (inode.i_uid_high << 16); |
| gid = inode.i_gid | (inode.i_gid_high << 16); |
| if (inode.i_uid_high | inode.i_gid_high) |
| debug(FL_DEBUG, _("High uid detected.\n")); |
| if (ucheck) |
| add_to_quota(USRQUOTA, i_num, uid, gid, |
| inode.i_mode, inode.i_links_count, |
| ((loff_t)inode.i_blocks) << 9, 0); |
| if (gcheck) |
| add_to_quota(GRPQUOTA, i_num, uid, gid, |
| inode.i_mode, inode.i_links_count, |
| ((loff_t)inode.i_blocks) << 9, 0); |
| if (S_ISDIR(inode.i_mode)) |
| dirs_done++; |
| else |
| files_done++; |
| } |
| |
| if ((error = ext2fs_get_next_inode(scan, &i_num, &inode))) { |
| errstr(_("Something weird happened while scanning. Error %d\n"), (int)error); |
| return -1; |
| } |
| } |
| return 0; |
| } |
| #endif |
| |
| /* |
| * Scan a directory with the readdir systemcall. Stat the files and add the sizes |
| * of the files to the appropriate quotas. When we find a dir we recursivly call |
| * ourself to scan that dir. |
| */ |
| static int scan_dir(const char *pathname) |
| { |
| struct dirs *dir_stack = NULL; |
| struct dirs *new_dir; |
| struct dirent *de; |
| struct stat st; |
| loff_t qspace; |
| DIR *dp; |
| int ret; |
| |
| if (lstat(pathname, &st) == -1) { |
| errstr(_("Cannot stat directory %s: %s\n"), pathname, strerror(errno)); |
| goto out; |
| } |
| qspace = getqsize(pathname, &st); |
| if (ucheck) |
| add_to_quota(USRQUOTA, st.st_ino, st.st_uid, st.st_gid, st.st_mode, |
| st.st_nlink, qspace, 0); |
| if (gcheck) |
| add_to_quota(GRPQUOTA, st.st_ino, st.st_uid, st.st_gid, st.st_mode, |
| st.st_nlink, qspace, 0); |
| |
| if (chdir(pathname) == -1) { |
| errstr(_("Cannot chdir to %s: %s\n"), pathname, strerror(errno)); |
| goto out; |
| } |
| |
| if ((dp = opendir(".")) == (DIR *) NULL) |
| die(2, _("\nCannot open directory %s: %s\n"), |
| pathname, strerror(errno)); |
| |
| if (flags & FL_VERYVERBOSE) |
| blit(pathname); |
| while ((de = readdir(dp)) != (struct dirent *)NULL) { |
| if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) |
| continue; |
| if (flags & FL_VERBOSE) |
| blit(NULL); |
| |
| if ((lstat(de->d_name, &st)) == -1) { |
| errstr(_("lstat: Cannot stat `%s/%s': %s\nGuess you'd better run fsck first !\nexiting...\n"), |
| pathname, de->d_name, strerror(errno)); |
| goto out; |
| } |
| |
| if (S_ISDIR(st.st_mode)) { |
| if (st.st_dev != cur_dev) |
| continue; |
| /* |
| * Add this to the directory stack and check this later on. |
| */ |
| debug(FL_DEBUG, _("pushd %s/%s\n"), pathname, de->d_name); |
| new_dir = xmalloc(sizeof(struct dirs)); |
| |
| new_dir->dir_name = xmalloc(strlen(pathname) + strlen(de->d_name) + 2); |
| sprintf(new_dir->dir_name, "%s/%s", pathname, de->d_name); |
| new_dir->next = dir_stack; |
| dir_stack = new_dir; |
| } |
| else { |
| qspace = getqsize(de->d_name, &st); |
| if (ucheck) |
| add_to_quota(USRQUOTA, st.st_ino, st.st_uid, st.st_gid, st.st_mode, |
| st.st_nlink, qspace, 1); |
| if (gcheck) |
| add_to_quota(GRPQUOTA, st.st_ino, st.st_uid, st.st_gid, st.st_mode, |
| st.st_nlink, qspace, 1); |
| debug(FL_DEBUG, _("\tAdding %s size %lld ino %d links %d uid %u gid %u\n"), de->d_name, |
| (long long)st.st_size, (int)st.st_ino, (int)st.st_nlink, (int)st.st_uid, (int)st.st_gid); |
| files_done++; |
| } |
| } |
| closedir(dp); |
| |
| /* |
| * Traverse the directory stack, and check it. |
| */ |
| debug(FL_DEBUG, _("Scanning stored directories from directory stack\n")); |
| while (dir_stack != (struct dirs *)NULL) { |
| new_dir = dir_stack; |
| dir_stack = dir_stack->next; |
| debug(FL_DEBUG, _("popd %s\nEntering directory %s\n"), new_dir->dir_name, |
| new_dir->dir_name); |
| ret = scan_dir(new_dir->dir_name); |
| dirs_done++; |
| #ifdef DEBUG_MALLOC |
| free_mem += sizeof(struct dirs) + strlen(new_dir->dir_name) + 1; |
| #endif |
| free(new_dir->dir_name); |
| free(new_dir); |
| if (ret < 0) /* Error while scanning? */ |
| goto out; |
| } |
| debug(FL_DEBUG, _("Leaving %s\n"), pathname); |
| return 0; |
| out: |
| for (new_dir = dir_stack; new_dir; new_dir = dir_stack) { |
| dir_stack = dir_stack->next; |
| #ifdef DEBUG_MALLOC |
| free_mem += sizeof(struct dirs) + strlen(new_dir->dir_name) + 1; |
| #endif |
| free(new_dir->dir_name); |
| free(new_dir); |
| } |
| return -1; |
| } |
| |
| /* Ask user y/n question */ |
| int ask_yn(char *q, int def) |
| { |
| char a[10]; /* Users answer */ |
| |
| printf("%s [%c]: ", q, def ? 'y' : 'n'); |
| fflush(stdout); |
| while (fgets(a, sizeof(a)-1, stdin)) { |
| if (a[0] == '\n') |
| return def; |
| if (!strcasecmp(a, "y\n")) |
| return 1; |
| if (!strcasecmp(a, "n\n")) |
| return 0; |
| printf("Illegal answer. Please answer y/n: "); |
| fflush(stdout); |
| } |
| return def; |
| } |
| |
| /* Do checks and buffer quota file into memory */ |
| static int process_file(struct mount_entry *mnt, int type) |
| { |
| char *qfname = NULL; |
| int fd = -1, ret; |
| |
| debug(FL_DEBUG, _("Going to check %s quota file of %s\n"), _(type2name(type)), |
| mnt->me_dir); |
| |
| if (kern_quota_on(mnt, type, cfmt) >= 0) { /* Is quota enabled? */ |
| if (!(flags & FL_FORCE)) { |
| if (flags & FL_INTERACTIVE) { |
| printf(_("Quota for %ss is enabled on mountpoint %s so quotacheck might damage the file.\n"), _(type2name(type)), mnt->me_dir); |
| if (!ask_yn(_("Should I continue?"), 0)) { |
| printf(_("As you wish... Canceling check of this file.\n")); |
| return -1; |
| } |
| } |
| else |
| die(6, _("Quota for %ss is enabled on mountpoint %s so quotacheck might damage the file.\n\ |
| Please turn quotas off or use -f to force checking.\n"), |
| type2name(type), mnt->me_dir); |
| } |
| /* At least sync quotas so damage will be smaller */ |
| if (quotactl(QCMD((kernel_iface == IFACE_GENERIC)? Q_SYNC : Q_6_5_SYNC, type), |
| mnt->me_devname, 0, NULL) < 0) |
| die(4, _("Error while syncing quotas on %s: %s\n"), mnt->me_devname, strerror(errno)); |
| } |
| |
| if (!(flags & FL_NEWFILE)) { /* Need to buffer file? */ |
| if (get_qf_name(mnt, type, cfmt, 0, &qfname) < 0) { |
| errstr(_("Cannot get quotafile name for %s\n"), mnt->me_devname); |
| return -1; |
| } |
| if ((fd = open(qfname, O_RDONLY)) < 0) { |
| if (errno != ENOENT) { |
| errstr(_("Cannot open quotafile %s: %s\n"), |
| qfname, strerror(errno)); |
| free(qfname); |
| return -1; |
| } |
| /* When file was not found, just skip it */ |
| flags |= FL_NEWFILE; |
| free(qfname); |
| qfname = NULL; |
| } |
| } |
| |
| ret = 0; |
| memset(old_info + type, 0, sizeof(old_info[type])); |
| if (is_tree_qfmt(cfmt)) |
| ret = v2_buffer_file(qfname, fd, type, cfmt); |
| else |
| ret = v1_buffer_file(qfname, fd, type); |
| |
| if (!(flags & FL_NEWFILE)) { |
| free(qfname); |
| close(fd); |
| } |
| return ret; |
| } |
| |
| /* Backup old quotafile and rename new one to right name */ |
| static int rename_files(struct mount_entry *mnt, int type) |
| { |
| char *filename, newfilename[PATH_MAX]; |
| struct stat st; |
| mode_t mode = S_IRUSR | S_IWUSR; |
| #ifdef EXT2_DIRECT |
| long ext2_flags = -1; |
| int fd; |
| #endif |
| |
| if (cfmt == QF_XFS) /* No renaming for xfs/gfs2 */ |
| return 0; |
| |
| debug(FL_DEBUG, _("Renaming new files to proper names.\n")); |
| if (get_qf_name(mnt, type, cfmt, 0, &filename) < 0) |
| die(2, _("Cannot get name of old quotafile on %s.\n"), mnt->me_dir); |
| if (stat(filename, &st) < 0) { /* File doesn't exist? */ |
| if (errno == ENOENT) { |
| debug(FL_DEBUG | FL_VERBOSE, _("Old file not found.\n")); |
| goto rename_new; |
| } |
| errstr(_("Error while searching for old quota file %s: %s\n"), |
| filename, strerror(errno)); |
| free(filename); |
| return -1; |
| } |
| mode = st.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO); |
| #ifdef EXT2_DIRECT |
| if ((fd = open(filename, O_RDONLY)) < 0) { |
| if (errno == ENOENT) { |
| debug(FL_DEBUG | FL_VERBOSE, _("Old file found removed during check!\n")); |
| goto rename_new; |
| } |
| errstr(_("Error while opening old quota file %s: %s\n"), |
| filename, strerror(errno)); |
| free(filename); |
| return -1; |
| } |
| if (ioctl(fd, EXT2_IOC_GETFLAGS, &ext2_flags) < 0) |
| debug(FL_DEBUG, _("EXT2_IOC_GETFLAGS failed: %s\n"), strerror(errno)); |
| else if (ext2_flags & EXT2_IMMUTABLE_FL) { |
| /* IMMUTABLE flag set probably because system crashed and quota |
| * was not properly turned off */ |
| debug(FL_DEBUG | FL_VERBOSE, _("Quota file %s has IMMUTABLE flag set. Clearing.\n"), filename); |
| ext2_flags &= ~EXT2_IMMUTABLE_FL; |
| if (ioctl(fd, EXT2_IOC_SETFLAGS, &ext2_flags) < 0) { |
| errstr(_("Failed to remove IMMUTABLE flag from quota file %s: %s\n"), filename, strerror(errno)); |
| free(filename); |
| close(fd); |
| return -1; |
| } |
| } |
| close(fd); |
| #endif |
| if (flags & FL_BACKUPS) { |
| debug(FL_DEBUG, _("Renaming old quotafile to %s~\n"), filename); |
| /* Backup old file */ |
| strcpy(newfilename, filename); |
| /* Make backingup safe */ |
| sstrncat(newfilename, "~", PATH_MAX); |
| if (newfilename[strlen(newfilename) - 1] != '~') |
| die(8, _("Name of quota file too long. Contact %s.\n"), PACKAGE_BUGREPORT); |
| if (rename(filename, newfilename) < 0) { |
| errstr(_("Cannot rename old quotafile %s to %s: %s\n"), |
| filename, newfilename, strerror(errno)); |
| free(filename); |
| return -1; |
| } |
| } |
| debug(FL_DEBUG, _("Renaming new quotafile\n")); |
| rename_new: |
| /* Rename new file to right name */ |
| strcpy(newfilename, filename); |
| sstrncat(newfilename, ".new", PATH_MAX); |
| if (rename(newfilename, filename) < 0) { |
| errstr(_("Cannot rename new quotafile %s to name %s: %s\n"), |
| newfilename, filename, strerror(errno)); |
| free(filename); |
| return -1; |
| } |
| if (chmod(filename, mode) < 0) { |
| errstr(_("Cannot change permission of %s: %s\n"), filename, strerror(errno)); |
| free(filename); |
| return -1; |
| } |
| #ifdef EXT2_DIRECT |
| if (ext2_flags != -1) { |
| if ((fd = open(filename, O_RDONLY)) < 0) { |
| errstr(_("Cannot open new quota file %s: %s\n"), filename, strerror(errno)); |
| free(filename); |
| return -1; |
| } |
| if (ioctl(fd, EXT2_IOC_SETFLAGS, &ext2_flags) < 0) |
| errstr(_("Warning: Cannot set EXT2 flags on %s: %s\n"), filename, strerror(errno)); |
| close(fd); |
| } |
| #endif |
| free(filename); |
| return 0; |
| } |
| |
| /* |
| * Dump the quota info that we have in memory now to the appropriate |
| * quota file. As quotafiles doesn't account to quotas we don't have to |
| * bother about accounting new blocks for quota file |
| */ |
| static int dump_to_file(struct mount_entry *mnt, int type) |
| { |
| struct dquot *dquot; |
| uint i; |
| struct quota_handle *h; |
| unsigned int commit = 0; |
| |
| debug(FL_DEBUG, _("Dumping gathered data for %ss.\n"), _(type2name(type))); |
| if (cfmt == QF_XFS) { |
| if (!(h = init_io(mnt, type, cfmt, IOI_READONLY))) { |
| errstr(_("Cannot initialize IO on xfs/gfs2 quotafile: %s\n"), |
| strerror(errno)); |
| return -1; |
| } |
| } else { |
| if (!(h = new_io(mnt, type, cfmt))) { |
| errstr(_("Cannot initialize IO on new quotafile: %s\n"), |
| strerror(errno)); |
| return -1; |
| } |
| if (!(flags & FL_NEWFILE)) { |
| h->qh_info.dqi_bgrace = old_info[type].dqi_bgrace; |
| h->qh_info.dqi_igrace = old_info[type].dqi_igrace; |
| if (is_tree_qfmt(cfmt)) |
| v2_merge_info(&h->qh_info, old_info + type); |
| mark_quotafile_info_dirty(h); |
| } |
| } |
| for (i = 0; i < DQUOTHASHSIZE; i++) |
| for (dquot = dquot_hash[type][i]; dquot; dquot = dquot->dq_next) { |
| dquot->dq_h = h; |
| /* For XFS/GFS2, we don't bother with actually checking |
| * what the usage value is in the internal quota file. |
| * We simply attempt to update the usage for every quota |
| * we find in the fs scan. The filesystem decides in the |
| * quotactl handler whether to update the usage in the |
| * quota file or not. |
| */ |
| commit = cfmt == QF_XFS ? COMMIT_USAGE : COMMIT_ALL; |
| update_grace_times(dquot); |
| h->qh_ops->commit_dquot(dquot, commit); |
| } |
| if (end_io(h) < 0) { |
| errstr(_("Cannot finish IO on new quotafile: %s\n"), strerror(errno)); |
| return -1; |
| } |
| debug(FL_DEBUG, _("Data dumped.\n")); |
| /* Moving of quota files doesn't apply to GFS2 or XFS */ |
| if (cfmt == QF_XFS) |
| return 0; |
| if (kern_quota_on(mnt, type, cfmt) >= 0) { /* Quota turned on? */ |
| char *filename; |
| |
| if (get_qf_name(mnt, type, cfmt, NF_FORMAT, &filename) < 0) |
| errstr(_("Cannot find checked quota file for %ss on %s!\n"), _(type2name(type)), mnt->me_devname); |
| else { |
| if (quotactl(QCMD((kernel_iface == IFACE_GENERIC) ? Q_QUOTAOFF : Q_6_5_QUOTAOFF, type), |
| mnt->me_devname, 0, NULL) < 0) |
| errstr(_("Cannot turn %s quotas off on %s: %s\nKernel won't know about changes quotacheck did.\n"), |
| _(type2name(type)), mnt->me_devname, strerror(errno)); |
| else { |
| int ret; |
| |
| /* Rename files - if it fails we cannot do anything better than just turn on quotas again */ |
| rename_files(mnt, type); |
| |
| if (kernel_iface == IFACE_GENERIC) |
| ret = quotactl(QCMD(Q_QUOTAON, type), mnt->me_devname, util2kernfmt(cfmt), filename); |
| else |
| ret = quotactl(QCMD(Q_6_5_QUOTAON, type), mnt->me_devname, 0, filename); |
| if (ret < 0) |
| errstr(_("Cannot turn %s quotas on on %s: %s\nKernel won't know about changes quotacheck did.\n"), |
| _(type2name(type)), mnt->me_devname, strerror(errno)); |
| } |
| free(filename); |
| } |
| } |
| else |
| if (rename_files(mnt, type) < 0) |
| return -1; |
| return 0; |
| } |
| |
| /* Substract space used by old quota file from usage. |
| * Return non-zero in case of failure, zero otherwise. */ |
| static int sub_quota_file(struct mount_entry *mnt, int qtype, int ftype) |
| { |
| char *filename; |
| struct stat st; |
| loff_t qspace; |
| struct dquot *d; |
| qid_t id; |
| |
| /* GFS2 and XFS do not have quota files. */ |
| if (cfmt == QF_XFS) |
| return 0; |
| |
| debug(FL_DEBUG, _("Substracting space used by old %s quota file.\n"), _(type2name(ftype))); |
| if (get_qf_name(mnt, ftype, cfmt, 0, &filename) < 0) { |
| debug(FL_VERBOSE | FL_DEBUG, _("Old %s file name could not been determined. Usage will not be subtracted.\n"), _(type2name(ftype))); |
| return 0; |
| } |
| |
| if (stat(filename, &st) < 0) { |
| debug(FL_VERBOSE | FL_DEBUG, _("Cannot stat old %s quota file %s: %s. Usage will not be subtracted.\n"), _(type2name(ftype)), filename, strerror(errno)); |
| free(filename); |
| return 0; |
| } |
| qspace = getqsize(filename, &st); |
| free(filename); |
| |
| if (qtype == USRQUOTA) |
| id = st.st_uid; |
| else |
| id = st.st_gid; |
| if ((d = lookup_dquot(id, qtype)) == NODQUOT) { |
| errstr(_("Quota structure for %s owning quota file not present! Something is really wrong...\n"), _(type2name(qtype))); |
| return -1; |
| } |
| d->dq_dqb.dqb_curinodes--; |
| d->dq_dqb.dqb_curspace -= qspace; |
| debug(FL_DEBUG, _("Substracted %lu bytes.\n"), (unsigned long)qspace); |
| return 0; |
| } |
| |
| /* Buffer quotafile, run filesystem scan, dump quotafiles. |
| * Return non-zero value in case of failure, zero otherwise. */ |
| static int check_dir(struct mount_entry *mnt) |
| { |
| struct stat st; |
| int remounted = 0; |
| int failed = 0; |
| |
| if (lstat(mnt->me_dir, &st) < 0) |
| die(2, _("Cannot stat mountpoint %s: %s\n"), mnt->me_dir, strerror(errno)); |
| if (!S_ISDIR(st.st_mode)) |
| die(2, _("Mountpoint %s is not a directory?!\n"), mnt->me_dir); |
| cur_dev = st.st_dev; |
| files_done = dirs_done = 0; |
| /* |
| * For gfs2, we scan the fs first and then tell the kernel about the new usage. |
| * So, there's no need to load any information. We also don't remount the |
| * filesystem read-only because for a clustering filesystem it won't stop |
| * modifications from other nodes anyway. |
| */ |
| if (cfmt == QF_XFS) |
| goto start_scan; |
| if (ucheck) |
| if (process_file(mnt, USRQUOTA) < 0) |
| ucheck = 0; |
| if (gcheck) |
| if (process_file(mnt, GRPQUOTA) < 0) |
| gcheck = 0; |
| if (!ucheck && !gcheck) /* Nothing to check? */ |
| return 0; |
| if (!(flags & FL_NOREMOUNT)) { |
| /* Now we try to remount fs read-only to prevent races when scanning filesystem */ |
| if (mount |
| (NULL, mnt->me_dir, mnt->me_type, MS_MGC_VAL | MS_REMOUNT | MS_RDONLY, |
| NULL) < 0 && !(flags & FL_FORCEREMOUNT)) { |
| if (flags & FL_INTERACTIVE) { |
| printf(_("Cannot remount filesystem mounted on %s read-only. Counted values might not be right.\n"), mnt->me_dir); |
| if (!ask_yn(_("Should I continue?"), 0)) { |
| printf(_("As you wish... Canceling check of this file.\n")); |
| failed = -1; |
| goto out; |
| } |
| } |
| else { |
| errstr(_("Cannot remount filesystem mounted on %s read-only so counted values might not be right.\n\ |
| Please stop all programs writing to filesystem or use -m flag to force checking.\n"), mnt->me_dir); |
| failed = -1; |
| goto out; |
| } |
| } |
| else |
| remounted = 1; |
| debug(FL_DEBUG, _("Filesystem remounted read-only\n")); |
| } |
| start_scan: |
| debug(FL_VERBOSE | FL_DEBUG, _("Scanning %s [%s] "), mnt->me_devname, mnt->me_dir); |
| #if defined(EXT2_DIRECT) |
| if (!strcmp(mnt->me_type, MNTTYPE_EXT2) || |
| !strcmp(mnt->me_type, MNTTYPE_EXT3) || |
| !strcmp(mnt->me_type, MNTTYPE_NEXT3) || |
| !strcmp(mnt->me_type, MNTTYPE_EXT4)) { |
| if ((failed = ext2_direct_scan(mnt->me_devname)) < 0) |
| goto out; |
| } |
| else { |
| #else |
| if (mnt->me_dir) { |
| #endif |
| if (flags & FL_VERYVERBOSE) |
| putchar('\n'); |
| if ((failed = scan_dir(mnt->me_dir)) < 0) |
| goto out; |
| } |
| dirs_done++; |
| if (flags & FL_VERBOSE || flags & FL_DEBUG) |
| fputs(_("done\n"), stdout); |
| if (ucheck) { |
| failed |= sub_quota_file(mnt, USRQUOTA, USRQUOTA); |
| failed |= sub_quota_file(mnt, USRQUOTA, GRPQUOTA); |
| } |
| if (gcheck) { |
| failed |= sub_quota_file(mnt, GRPQUOTA, USRQUOTA); |
| failed |= sub_quota_file(mnt, GRPQUOTA, GRPQUOTA); |
| } |
| debug(FL_DEBUG | FL_VERBOSE, _("Checked %d directories and %d files\n"), dirs_done, |
| files_done); |
| if (remounted) { |
| if (mount(NULL, mnt->me_dir, mnt->me_type, MS_MGC_VAL | MS_REMOUNT, NULL) < 0) |
| die(4, _("Cannot remount filesystem %s read-write. cannot write new quota files.\n"), mnt->me_dir); |
| debug(FL_DEBUG, _("Filesystem remounted RW.\n")); |
| } |
| if (ucheck) |
| failed |= dump_to_file(mnt, USRQUOTA); |
| if (gcheck) |
| failed |= dump_to_file(mnt, GRPQUOTA); |
| out: |
| remove_list(); |
| return failed; |
| } |
| |
| /* Detect quota format from filename of present files */ |
| static int detect_filename_format(struct mount_entry *mnt, int type) |
| { |
| char *option; |
| struct stat statbuf; |
| char namebuf[PATH_MAX]; |
| int journal = 0; |
| int fmt; |
| |
| if (strcmp(mnt->me_type, MNTTYPE_XFS) == 0 || |
| strcmp(mnt->me_type, MNTTYPE_GFS2) == 0) |
| return QF_XFS; |
| |
| if (type == USRQUOTA) { |
| if ((option = str_hasmntopt(mnt->me_opts, MNTOPT_USRQUOTA))) |
| option += strlen(MNTOPT_USRQUOTA); |
| else if ((option = str_hasmntopt(mnt->me_opts, MNTOPT_USRJQUOTA))) { |
| journal = 1; |
| option += strlen(MNTOPT_USRJQUOTA); |
| } |
| else if ((option = str_hasmntopt(mnt->me_opts, MNTOPT_QUOTA))) |
| option += strlen(MNTOPT_QUOTA); |
| } |
| else { |
| if ((option = str_hasmntopt(mnt->me_opts, MNTOPT_GRPQUOTA))) |
| option += strlen(MNTOPT_GRPQUOTA); |
| else if ((option = str_hasmntopt(mnt->me_opts, MNTOPT_GRPJQUOTA))) { |
| journal = 1; |
| option += strlen(MNTOPT_GRPJQUOTA); |
| } |
| } |
| if (!option) |
| die(2, _("Cannot find quota option on filesystem %s with quotas!\n"), mnt->me_dir); |
| if (journal) { |
| char fmtbuf[64], *space; |
| |
| if (!(option = str_hasmntopt(mnt->me_opts, MNTOPT_JQFMT))) { |
| jquota_err: |
| errstr(_("Cannot detect quota format for journalled quota on %s\n"), mnt->me_dir); |
| return -1; |
| } |
| option += strlen(MNTOPT_JQFMT); |
| if (*option != '=') |
| goto jquota_err; |
| space = strchr(option, ','); |
| if (!space) |
| space = option + strlen(option); |
| if (space-option > sizeof(fmtbuf)) |
| goto jquota_err; |
| sstrncpy(fmtbuf, option+1, space-option); |
| fmt = name2fmt(fmtbuf); |
| if (fmt == QF_ERROR) |
| goto jquota_err; |
| return fmt; |
| } |
| else if (*option == '=') /* If the file name is specified we can't detect quota format from it... */ |
| return -1; |
| snprintf(namebuf, PATH_MAX, "%s/%s.%s", mnt->me_dir, basenames[QF_VFSV0], extensions[type]); |
| if (!stat(namebuf, &statbuf)) { |
| int fd = open(namebuf, O_RDONLY); |
| if (fd < 0) |
| return -1; |
| fmt = v2_detect_version(namebuf, fd, type); |
| close(fd); |
| return fmt; |
| |
| } |
| if (errno != ENOENT) |
| return -1; |
| snprintf(namebuf, PATH_MAX, "%s/%s.%s", mnt->me_dir, basenames[QF_VFSOLD], extensions[type]); |
| if (!stat(namebuf, &statbuf)) |
| return QF_VFSOLD; |
| /* Old quota files don't exist, just create VFSv0 format if available */ |
| if (kern_qfmt_supp(QF_VFSV0)) |
| return QF_VFSV0; |
| if (kern_qfmt_supp(QF_VFSOLD)) |
| return QF_VFSOLD; |
| return -1; |
| } |
| |
| static int compatible_fs_qfmt(char *fstype, int fmt) |
| { |
| /* We never check XFS, NFS, and filesystems supporting VFS metaformat */ |
| if (!strcmp(fstype, MNTTYPE_XFS) || nfs_fstype(fstype) || |
| meta_qf_fstype(fstype)) |
| return 0; |
| /* In all other cases we can pick a format... */ |
| if (fmt == -1) |
| return 1; |
| /* XFS format is supported only by GFS2 */ |
| if (fmt == QF_XFS) |
| return !strcmp(fstype, MNTTYPE_GFS2); |
| /* Anything but GFS2 supports all other formats */ |
| return !!strcmp(fstype, MNTTYPE_GFS2); |
| } |
| |
| /* Parse kernel version and warn if not using journaled quotas */ |
| static void warn_if_jquota_supported(void) |
| { |
| struct utsname stats; |
| int v; |
| char *errch; |
| |
| if (uname(&stats) < 0) { |
| errstr(_("Cannot get system info: %s\n"), strerror(errno)); |
| return; |
| } |
| if (strcmp(stats.sysname, "Linux")) |
| return; |
| |
| v = strtol(stats.release, &errch, 10); |
| if (v < 2) |
| return; |
| if (v >= 3) |
| goto warn; |
| if (*errch != '.') |
| return; |
| v = strtol(errch + 1, &errch, 10); |
| if (*errch != '.' || v < 6) |
| return; |
| v = strtol(errch + 1, &errch, 10); |
| if (v < 11) |
| return; |
| warn: |
| errstr(_("Your kernel probably supports journaled quota but you are " |
| "not using it. Consider switching to journaled quota to avoid" |
| " running quotacheck after an unclean shutdown.\n")); |
| } |
| |
| /* Return 0 in case of success, non-zero otherwise. */ |
| static int check_all(void) |
| { |
| struct mount_entry *mnt; |
| int checked = 0; |
| static int warned; |
| int failed = 0; |
| |
| if (init_mounts_scan((flags & FL_ALL) ? 0 : 1, &mntpoint, 0) < 0) |
| die(2, _("Cannot initialize mountpoint scan.\n")); |
| while ((mnt = get_next_mount())) { |
| if (flags & FL_ALL && flags & FL_NOROOT && !strcmp(mnt->me_dir, "/")) |
| continue; |
| if (!compatible_fs_qfmt(mnt->me_type, fmt)) { |
| debug(FL_DEBUG | FL_VERBOSE, _("Skipping %s [%s]\n"), mnt->me_devname, mnt->me_dir); |
| continue; |
| } |
| cfmt = fmt; |
| if (uwant && me_hasquota(mnt, USRQUOTA)) |
| ucheck = 1; |
| else |
| ucheck = 0; |
| if (gwant && me_hasquota(mnt, GRPQUOTA)) |
| gcheck = 1; |
| else |
| gcheck = 0; |
| if (!ucheck && !gcheck) |
| continue; |
| if (cfmt == -1) { |
| cfmt = detect_filename_format(mnt, ucheck ? USRQUOTA : GRPQUOTA); |
| if (cfmt == -1) { |
| errstr(_("Cannot guess format from filename on %s. Please specify format on commandline.\n"), |
| mnt->me_devname); |
| failed = -1; |
| continue; |
| } |
| debug(FL_DEBUG, _("Detected quota format %s\n"), fmt2name(cfmt)); |
| } |
| |
| if (flags & (FL_VERBOSE | FL_DEBUG) && |
| !str_hasmntopt(mnt->me_opts, MNTOPT_USRJQUOTA) && |
| !str_hasmntopt(mnt->me_opts, MNTOPT_GRPJQUOTA) && |
| !warned && |
| (!strcmp(mnt->me_type, MNTTYPE_EXT3) || |
| !strcmp(mnt->me_type, MNTTYPE_EXT4) || |
| !strcmp(mnt->me_type, MNTTYPE_NEXT3) || |
| !strcmp(mnt->me_type, MNTTYPE_EXT4DEV) || |
| !strcmp(mnt->me_type, MNTTYPE_REISER))) { |
| warned = 1; |
| warn_if_jquota_supported(); |
| } |
| |
| checked++; |
| failed |= check_dir(mnt); |
| } |
| end_mounts_scan(); |
| if (!checked && (!(flags & FL_ALL) || flags & (FL_VERBOSE | FL_DEBUG))) { |
| errstr(_("Cannot find filesystem to check or filesystem not mounted with quota option.\n")); |
| failed = -1; |
| } |
| return failed; |
| } |
| |
| int main(int argc, char **argv) |
| { |
| int failed; |
| |
| gettexton(); |
| progname = basename(argv[0]); |
| |
| parse_options(argc, argv); |
| init_kernel_interface(); |
| |
| failed = check_all(); |
| #ifdef DEBUG_MALLOC |
| errstr(_("Allocated %d bytes memory\nFree'd %d bytes\nLost %d bytes\n"), |
| malloc_mem, free_mem, malloc_mem - free_mem); |
| #endif |
| return (failed ? EXIT_FAILURE : EXIT_SUCCESS); |
| } |