"Fossies" - the Fresh Open Source Software Archive

Member "quotactl-1.00/quotause/quotause.c" (9 Oct 2005, 22293 Bytes) of package /linux/privat/old/quotactl-1.00.tgz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "quotause.c" see the Fossies "Dox" file reference documentation.

    1 #define _GNU_SOURCE
    2 /*
    3 
    4     By Bryan Henderson, San Jose CA 05.08.14.
    5 
    6     Bryan's contributions to this code are contributed to the public domain
    7     by Bryan.
    8 
    9     Much of this program is copied from quotacheck.c, which is part of
   10     the quota-tools package, on 05.08.14.  File was dated 05.06.01.
   11   
   12     Some parts of this utility are copied from old quotacheck by
   13     Marco van Wieringen <mvw@planets.elm.net> and Edvard Tuinder <ed@elm.ent>
   14    
   15     New quota format implementation - Jan Kara <jack@suse.cz> -
   16     Sponsored by SuSE CR
   17 
   18 */
   19 
   20 #include <stdbool.h>
   21 #include <assert.h>
   22 #include <dirent.h>
   23 #include <stdio.h>
   24 #include <string.h>
   25 #include <limits.h>
   26 #include <unistd.h>
   27 #include <stdlib.h>
   28 #include <errno.h>
   29 #include <time.h>
   30 
   31 #include <sys/stat.h>
   32 #include <sys/types.h>
   33 #include <sys/file.h>
   34 #include <sys/ioctl.h>
   35 
   36 #include <giraffe/fatal.h>
   37 #include <giraffe/girstring.h>
   38 #include <giraffe/mallocvar.h>
   39 #include <giraffe/cmdline_parser.h>
   40 
   41 #include "quotaio.h"
   42 #include "quotacheck.h"
   43 #include "dquothash.h"
   44 #include "quotause_v1.h"
   45 #include "quotause_v2.h"
   46 #include "misc.h"
   47 
   48 struct cmdlineInfo {
   49     const char * userinfile;
   50     const char * useroutfile;
   51     const char * groupinfile;
   52     const char * groupoutfile;
   53     const char * mountpoint;
   54 };
   55 
   56 
   57 #ifndef HAVE_EXT2_INO_T
   58 typedef ino_t ext2_ino_t;
   59 #endif
   60 
   61 struct dirs {
   62     const char * dir_name;
   63     struct dirs * next;
   64 };
   65 
   66 #define BITS_SIZE 4     /* sizeof(bits) == 5 */
   67 #define BLIT_RATIO 10       /* Blit in just 1/10 of blit() calls */
   68 
   69 dev_t cur_dev;          /* Device we are working on */
   70 int files_done, dirs_done;
   71 int flags, fmt = -1;
   72     /* Options from command line; Quota format to use spec. by user;
   73        Actual format to check 
   74     */
   75 int uwant, gwant;
   76     /* Does user want to check user/group quota;
   77     */
   78 struct util_dqinfo old_info[MAXQUOTAS]; /* Loaded infos */
   79 
   80 const char *basenames[] = INITQFBASENAMES;    /* Names of quota files */
   81 
   82 static bool const debugMalloc = true;
   83 
   84 
   85 
   86 static void
   87 parseCommandLine(int                  const argc,
   88                  char **              const argv,
   89                  struct cmdlineInfo * const cmdlineP,
   90                  const char **        const errorP) {
   91 
   92     cmdlineParser const cp = cmd_createOptionParser();
   93 
   94     const char * error;
   95 
   96     cmd_defineOption(cp, "userinfile",          OPTTYPE_STRING);
   97     cmd_defineOption(cp, "useroutfile",         OPTTYPE_STRING);
   98     cmd_defineOption(cp, "groupinfile",         OPTTYPE_STRING);
   99     cmd_defineOption(cp, "groupoutfile",        OPTTYPE_STRING);
  100 
  101     *errorP = NULL;
  102 
  103     cmd_processOptions(cp, argc, argv, &error);
  104 
  105     if (error) {
  106         casprintf(errorP, "Command syntax error.  %s", error);
  107         strfree(error);
  108     } else {
  109         cmdlineP->userinfile   = cmd_getOptionValueString(cp, "userinfile");
  110         cmdlineP->useroutfile  = cmd_getOptionValueString(cp, "useroutfile");
  111         cmdlineP->groupinfile  = cmd_getOptionValueString(cp, "groupinfile");
  112         cmdlineP->groupoutfile = cmd_getOptionValueString(cp, "groupoutfile");
  113 
  114         if (cmdlineP->userinfile) {
  115             if (!cmdlineP->useroutfile)
  116                 casprintf(errorP, "If you specify -userinfile, you must "
  117                           "also specify -useroutfile");
  118         } else {
  119             if (cmdlineP->useroutfile)
  120                 casprintf(errorP, "You cannot specify -useroutfile unless "
  121                           "you also specify -userinfile");
  122         }
  123         if (!*errorP) {
  124             if (cmdlineP->groupinfile) {
  125                 if (!cmdlineP->groupoutfile)
  126                     casprintf(errorP, "If you specify -groupinfile, you must "
  127                               "also specify -groupoutfile");
  128             }
  129         } else {
  130             if (cmdlineP->groupoutfile)
  131                 casprintf(errorP, "You cannot specify -groupoutfile unless "
  132                           "you also specify -groupinfile");
  133         }
  134         if (!*errorP) {
  135             if (cmd_argumentCount(cp) < 1)
  136                 casprintf(errorP, "You must specify an argument: a mountpoint "
  137                           "of the filesystem to which the quotas apply.");
  138             else {
  139                 cmdlineP->mountpoint = cmd_getArgument(cp, 0);
  140                 if (cmd_argumentCount(cp) > 2)
  141                     casprintf(errorP,
  142                               "Extra arguments.  There is only 1 argument.  "
  143                               "You specified %u", cmd_argumentCount(cp));
  144             }
  145         }
  146     }
  147     cmd_destroyOptionParser(cp);
  148 }
  149 
  150 
  151 
  152 static void
  153 freeCmdline(struct cmdlineInfo const cmdline) {
  154 
  155     if (cmdline.userinfile) {
  156         strfree(cmdline.userinfile);
  157         strfree(cmdline.useroutfile);
  158     }
  159     if (cmdline.groupinfile) {
  160         strfree(cmdline.groupinfile);
  161         strfree(cmdline.groupoutfile);
  162     }
  163     strfree(cmdline.mountpoint);
  164 }
  165 
  166 
  167 
  168 /* Forward declaration for recursion */
  169 static int
  170 scanDirectory(const char * const pathname,
  171               bool         const needUser,
  172               bool         const needGroup);
  173 
  174 
  175 static struct dquot *
  176 getDquot(enum quotaType  const type,
  177          qid_t           const id) {
  178 
  179     struct dquot * dquotP;
  180 
  181     dquotP = lookup_dquot(id, type);
  182     if (!dquotP) {
  183         /* We have to start a new quota record for this id. */
  184 
  185         dquotP = create_dquot(id);
  186 
  187         add_dquot(dquotP, type);
  188     }
  189     return dquotP;
  190 }
  191 
  192 
  193 
  194 static void
  195 addToQuota(enum quotaType const type,
  196            ino_t          const i_num,
  197            qid_t          const id,
  198            nlink_t        const i_nlink,
  199            loff_t         const i_space,
  200            int            const isDirectory) {
  201 /*----------------------------------------------------------------------------
  202    Add a number of blocks and inodes to a quota.
  203 -----------------------------------------------------------------------------*/
  204     bool mustCount;
  205     struct dquot * dquotP;
  206 
  207     dquotP = getDquot(type, id);
  208 
  209     if (i_nlink > 1 && !isDirectory) {
  210         /* Filesystem object will be encountered multiple times in a scan
  211            of the filesystem, so make sure we don't count it twice.
  212         */
  213         if (inode_has_been_processed(type, i_num)) {
  214             /* We already counted this inode */
  215             mustCount = false;
  216         } else {
  217             mustCount = true;
  218             remember_inode(type, i_num);
  219         }
  220     } else
  221         mustCount = true;
  222         
  223     if (mustCount) {
  224         ++dquotP->dq_dqb.dqb_curinodes;
  225         dquotP->dq_dqb.dqb_curspace += i_space;;
  226     }
  227 }
  228 
  229 
  230 
  231 /* Get size used by file */
  232 static loff_t
  233 getqsize(const char *  const fname,
  234          struct stat * const st) {
  235 
  236     static char ioctl_fail_warn;
  237     int fd;
  238     loff_t size;
  239 
  240     if (S_ISLNK(st->st_mode))   /* There's no way to do ioctl() on links... */
  241         return st->st_blocks << 9;
  242     if (!S_ISDIR(st->st_mode) && !S_ISREG(st->st_mode))
  243         return st->st_blocks << 9;
  244     if ((fd = open(fname, O_RDONLY)) == -1)
  245         fatal("Cannot open file '%s'.  errno=%d (%s)",
  246               fname, errno, strerror(errno));
  247     if (ioctl(fd, FIOQSIZE, &size) == -1) {
  248         size = st->st_blocks << 9;
  249         if (!ioctl_fail_warn) {
  250             ioctl_fail_warn = 1;
  251             fputs("Cannot get exact used space... "
  252                   "Results might be inaccurate.", stderr);
  253         }
  254     }
  255     close(fd);
  256     return size;
  257 }
  258 
  259 
  260 
  261 /*
  262  * Show a blitting cursor as means of visual progress indicator.
  263  */
  264 static inline void
  265 blit(const char * const msg) {
  266     static int bitc = 0;
  267     static const char bits[] = "|/-\\";
  268     static int slow_down;
  269 
  270     if (flags & FL_VERYVERBOSE && msg) {
  271         putchar('\r');
  272         printf("%-70s ", msg);
  273     }
  274     if (flags & FL_VERYVERBOSE || ++slow_down >= BLIT_RATIO) {
  275         putchar(bits[bitc]);
  276         putchar('\b');
  277         fflush(stdout);
  278         ++bitc;
  279         bitc %= BITS_SIZE;
  280         slow_down = 0;
  281     }
  282 }
  283 
  284 
  285 
  286 static void
  287 processDirEntry(struct dirent * const de,
  288                 const char *    const parentName,
  289                 struct dirs **  const dirStackP,
  290                 bool            const needUser,
  291                 bool            const needGroup,
  292                 bool *          const errorP) {
  293 /*----------------------------------------------------------------------------
  294    Do one directory entry in a scan for usages.
  295    
  296    If it's a directory, add it to the stack *dirStackP and don't do anything
  297    else with it.
  298 
  299    If it's not, add its usages to the quota cache quota_hash[][].
  300 
  301    *de is the directory entry.  It is in the current directory, whose
  302    full name is 'parentName'.
  303 -----------------------------------------------------------------------------*/
  304     struct stat st;
  305     int rc;
  306 
  307     rc = lstat(de->d_name, &st);
  308     if (rc == -1) {
  309         msg("lstat Cannot stat '%s', a name we got from directory '%s'.  "
  310             "errno=%d (%s).  "
  311             "Guess you'd better run Fsck!",
  312             de->d_name, parentName, errno, strerror(errno));
  313         *errorP = true;
  314     } else {
  315         if (S_ISDIR(st.st_mode)) {
  316             if (st.st_dev == cur_dev) {
  317                 /* We're still within our filesystem image.
  318                    Add this to the directory stack to be checked later on.
  319                 */
  320                 struct dirs * newDirP;
  321             
  322                 debug("pushd %s/%s", parentName, de->d_name);
  323                 MALLOCVAR(newDirP);
  324                 if (newDirP == NULL)
  325                     *errorP = true;
  326                 else {
  327                     *errorP = false;
  328                     casprintf(&newDirP->dir_name, "%s/%s",
  329                               parentName, de->d_name);
  330                     newDirP->next = *dirStackP;
  331                     *dirStackP = newDirP;
  332                 }
  333             }
  334         } else {
  335             loff_t const qspace = getqsize(de->d_name, &st);
  336 
  337             if (needUser)
  338                 addToQuota(USRQUOTA,
  339                            st.st_ino, st.st_uid, st.st_nlink, qspace, 1);
  340             if (needGroup)
  341                 addToQuota(GRPQUOTA,
  342                            st.st_ino, st.st_gid, st.st_nlink, qspace, 1);
  343             debug("    Adding %s size %Ld ino %d links %d uid %u gid %u",
  344                   de->d_name,
  345                   (long long)st.st_size, (int)st.st_ino, (int)st.st_nlink,
  346                   (int)st.st_uid, (int)st.st_gid);
  347             ++files_done;
  348 
  349             *errorP = false;
  350         }
  351     }
  352 }
  353 
  354 
  355 
  356 static void
  357 freeDirectoryList(struct dirs * const dirList) {
  358 
  359     struct dirs * nextDirP;
  360 
  361     nextDirP = dirList;
  362 
  363     while (nextDirP) {
  364         struct dirs * const dirP = nextDirP;
  365         nextDirP = dirP->next;  /* remove from list */
  366         strfree(dirP->dir_name);
  367         free(dirP);
  368     }
  369 }
  370 
  371 
  372 
  373 static void
  374 doChildren2(const char * const pathname,
  375             DIR *        const dp,
  376             bool         const needUser,
  377             bool         const needGroup,
  378             bool *       const errorP) {
  379 /*----------------------------------------------------------------------------
  380    Same as doChildren(), except directory is open with handle 'dp'
  381    and it is also the current directory.
  382 -----------------------------------------------------------------------------*/
  383     struct dirs * dirStack;
  384     struct dirent * de;
  385     struct dirs * dirP;
  386     
  387     dirStack = NULL;  /* initial value: empty list */
  388         
  389     while ((de = readdir(dp))) {
  390         if (streq(de->d_name, ".") || streq(de->d_name, "..")) {
  391             /* Fake non-tree entries.  Ignore. */
  392         } else {
  393             if (flags & FL_VERBOSE)
  394                 blit(NULL);
  395             
  396             processDirEntry(de, pathname, &dirStack, needUser, needGroup,
  397                             errorP);
  398         }
  399     }
  400     
  401     *errorP = false;  /* initial assumption */
  402     
  403     /* Do the directories from the stack generated above */
  404     debug("Scanning deferred directories from directory stack");
  405     for (dirP = dirStack; dirP && !*errorP; dirP = dirP->next) {
  406         int rc;
  407         debug("popd %s; Entering directory %s",
  408               dirP->dir_name, dirP->dir_name);
  409         rc = scanDirectory(dirP->dir_name, needUser, needGroup);
  410         *errorP = (rc < 0);
  411         ++dirs_done;
  412     }
  413     freeDirectoryList(dirStack);
  414 }
  415 
  416 
  417 
  418 static void
  419 doChildren(const char * const pathname,
  420            bool         const needUser,
  421            bool         const needGroup,
  422            bool *       const errorP) {
  423 /*----------------------------------------------------------------------------
  424    Add the usages of descendants of directory 'pathname' to the quota
  425    cache.
  426 -----------------------------------------------------------------------------*/
  427     int rc;
  428 
  429     rc = chdir(pathname);
  430     if (rc != 0) {
  431         msg("Cannot chdir to directory '%s'.  errno=%d (%s)",
  432             pathname, errno, strerror(errno));
  433         *errorP = true;
  434     } else {
  435         DIR * dp;
  436         dp = opendir(pathname);
  437         if (dp == NULL) {
  438             msg("Cannot open directory '%s'.  errno=%d (%s)",
  439                 pathname, errno, strerror(errno));
  440             *errorP = true;
  441         } else {
  442             doChildren2(pathname, dp, needUser, needGroup, errorP);
  443             closedir(dp);
  444         }
  445     }
  446 }
  447 
  448 
  449 
  450 static int
  451 scanDirectory(const char * const pathname,
  452               bool         const needUser,
  453               bool         const needGroup) {
  454 /*----------------------------------------------------------------------------
  455   Add the usages of directory 'pathname' and all its descendants to the
  456   quota cache.
  457 -----------------------------------------------------------------------------*/
  458     struct stat st;
  459     bool errorRet;
  460 
  461     if (flags & FL_VERYVERBOSE)
  462         blit(pathname);
  463         
  464     if (lstat(pathname, &st) == -1) {
  465         msg("Cannot stat directory '%s'.  errno=%d (%s)",
  466             pathname, errno, strerror(errno));
  467         errorRet = true;
  468     } else {
  469         loff_t const qspace = getqsize(pathname, &st);
  470 
  471         assert(S_ISDIR(st.st_mode));
  472 
  473         if (needUser)
  474             addToQuota(USRQUOTA, st.st_ino, st.st_uid,
  475                        st.st_nlink, qspace, 0);
  476         if (needGroup)
  477             addToQuota(GRPQUOTA, st.st_ino, st.st_gid,
  478                        st.st_nlink, qspace, 0);
  479 
  480         doChildren(pathname, needUser, needGroup, &errorRet);
  481 
  482         debug("Leaving %s", pathname);
  483     }
  484     return errorRet ? -1 : 0;
  485 }
  486 
  487 
  488 
  489 static void
  490 readQuotaFile(const char *      const qfname,
  491               int               const type,
  492               enum quotaFileFmt const fmt,
  493               const char **     const errorP) {
  494 /*----------------------------------------------------------------------------
  495    Read the quota file named 'qfname' into the quota cache
  496    dquot_hash[][].
  497 
  498    Also do some integrity checks on it.
  499 -----------------------------------------------------------------------------*/
  500     int fd;
  501 
  502     debug("Going to check %s quota file '%s'", type2name(type), qfname);
  503 
  504     *errorP = NULL; // initial assumption
  505 
  506     fd = -1;  // no file open
  507 
  508     if (!(flags & FL_NEWFILE)) {    /* Need to buffer file? */
  509         int rc;
  510         rc = open(qfname, O_RDONLY);
  511         if (rc < 0)
  512             casprintf(errorP, "Cannot open quotafile '%s'.  errno=%d (%s)",
  513                       qfname, errno, strerror(errno));
  514         else
  515             fd = rc;
  516     }
  517 
  518     if (!*errorP) {
  519         memset(&old_info[type], 0, sizeof(old_info[type]));
  520 
  521         switch (fmt) {
  522         case QF_VFSOLD: {
  523             int rc;
  524             rc = v1_buffer_file(qfname, fd, type);
  525             if (rc != 0)
  526                 casprintf(errorP, "v1_buffer_file() rc = %d", rc);
  527         }
  528         break;
  529         case QF_VFSV0: {
  530             int rc;
  531             rc = v2_buffer_file(qfname, fd, type);
  532             if (rc != 0)
  533                 casprintf(errorP, "v2_buffer_file() rc = %d", rc);
  534         }
  535         break;
  536         case QF_XFS:
  537             casprintf(errorP, "We don't know how to interpret the XFS "
  538                       "quota file format.");
  539         }
  540     }
  541     if (fd >= 0)
  542         close(fd);
  543 }
  544 
  545 
  546 
  547 /*
  548  * Set grace time if needed
  549  */
  550 static void
  551 update_grace_times(struct dquot *     const q,
  552                    struct util_dqinfo const info) {
  553 
  554     time_t now;
  555 
  556     time(&now);
  557 
  558     if (q->dq_dqb.dqb_bsoftlimit &&
  559         toqb(q->dq_dqb.dqb_curspace) > q->dq_dqb.dqb_bsoftlimit) {
  560 
  561         if (!q->dq_dqb.dqb_btime)
  562             q->dq_dqb.dqb_btime = now + info.dqi_bgrace;
  563     } else
  564         q->dq_dqb.dqb_btime = 0;
  565 
  566     if (q->dq_dqb.dqb_isoftlimit &&
  567         q->dq_dqb.dqb_curinodes > q->dq_dqb.dqb_isoftlimit) {
  568 
  569         if (!q->dq_dqb.dqb_itime)
  570             q->dq_dqb.dqb_itime = now + info.dqi_igrace;
  571     } else
  572         q->dq_dqb.dqb_itime = 0;
  573 }
  574 
  575 
  576 
  577 static void
  578 dumpAQuota(struct dquot * const dquotP,
  579            void *         const argP) {
  580     
  581     struct quota_handle * const quotaFileP = argP;
  582 
  583     update_grace_times(dquotP, quotaFileP->qh_info);
  584     quotaFileP->qh_ops->write_dquot(quotaFileP, dquotP);
  585 }
  586 
  587 
  588 
  589 /*
  590  * Dump the quota info that we have in memory now to the appropriate
  591  * quota file. As quotafiles doesn't account to quotas we don't have to
  592  * bother about accounting new blocks for quota file
  593  */
  594 static void
  595 dumpToFile(const char *      const qfname,
  596            int               const type,
  597            enum quotaFileFmt const fmt,
  598            const char **     const errorP) {
  599 
  600     struct quota_handle * quotaFileP;
  601     const char * error;
  602 
  603     debug("Dumping gathered data for %ss.", type2name(type));
  604     new_io(OPEN_NEW, qfname, type, fmt, &quotaFileP, &error);
  605 
  606     if (error) {
  607         casprintf(errorP, "Cannot initialize IO on new quotafile.  %s", error);
  608     } else {
  609         int rc;
  610 
  611         if (!(flags & FL_NEWFILE)) {
  612             quotaFileP->qh_info.dqi_bgrace = old_info[type].dqi_bgrace;
  613             quotaFileP->qh_info.dqi_igrace = old_info[type].dqi_igrace;
  614             if (fmt == QF_VFSV0)
  615                 v2_merge_info(&quotaFileP->qh_info, old_info + type);
  616             mark_quotafile_info_dirty(quotaFileP);
  617         }
  618 
  619         do_for_each_quota(type, &dumpAQuota, quotaFileP);
  620 
  621         rc = end_io(quotaFileP);
  622 
  623         if (rc < 0) {
  624             casprintf(errorP, "Cannot finish IO on new quotafile.  "
  625                       "errno=%d (%s)",
  626                       errno, strerror(errno));
  627         } else {
  628             debug("Data dumped.");
  629     
  630             *errorP = NULL;
  631         }
  632     }
  633 }
  634 
  635 
  636 
  637 static void
  638 readQuotaFiles(bool              const needUser,
  639                bool              const needGroup,
  640                const char *      const oldUserFileName,
  641                const char *      const oldGroupFileName,
  642                enum quotaFileFmt const fmt,
  643                const char **     const errorP) {
  644                
  645     *errorP = NULL;  /* initial value */
  646 
  647     if (needUser) {
  648         const char * error;
  649         readQuotaFile(oldUserFileName, USRQUOTA, fmt, &error);
  650         if (error) {
  651             casprintf(errorP, "Invalid user quota file '%s'.  %s",
  652                       oldUserFileName, error);
  653             strfree(error);
  654         }
  655     }
  656     if (!*errorP) {
  657         if (needGroup) {
  658             const char * error;
  659             readQuotaFile(oldGroupFileName, GRPQUOTA, fmt, &error);
  660             if (error) {
  661                 casprintf(errorP, "Invalid group quota file '%s'.  %s",
  662                           oldGroupFileName, error);
  663                 strfree(error);
  664             }
  665         }
  666     }
  667 }
  668 
  669 
  670 
  671 /* Buffer quotafile, run filesystem scan, dump quotafiles */
  672 static void
  673 doFilesystem(const char *      const mountpoint,
  674              const char *      const oldUserFileName,
  675              const char *      const newUserFileName,
  676              const char *      const oldGroupFileName,
  677              const char *      const newGroupFileName,
  678              enum quotaFileFmt const fmt) {
  679 /*----------------------------------------------------------------------------
  680    Scan the filesystem mounted at 'mountpoint' and update input quota
  681    files with usage information to create output quota files.
  682 
  683    If 'oldUserFileName' is null, don't do user quotas.
  684    If 'oldGroupFileName' is null, don't do group quotas.
  685 -----------------------------------------------------------------------------*/
  686     bool const needUser  = oldUserFileName  != NULL;
  687     bool const needGroup = oldGroupFileName != NULL;
  688 
  689     const char * error;
  690 
  691     struct stat st;
  692     int rc;
  693 
  694     if (lstat(mountpoint, &st) < 0)
  695         fatal("Cannot stat mountpoint '%s'.  errno=%d (%s)",
  696               mountpoint, errno, strerror(errno));
  697     if (!S_ISDIR(st.st_mode))
  698         fatal("Mountpoint '%s' isn't a directory?!", mountpoint);
  699     cur_dev = st.st_dev;
  700     files_done = dirs_done = 0;
  701 
  702     readQuotaFiles(needUser, needGroup, oldUserFileName, oldGroupFileName,
  703                    fmt, &error);
  704     if (error)
  705         fatal("%s", error);
  706 
  707     msg("Scanning mountpoint '%s'", mountpoint);
  708     if (flags & FL_VERYVERBOSE)
  709         putchar('\n');
  710     rc = scanDirectory(mountpoint, needUser, needGroup);
  711     if (rc < 0)
  712         fatal("Failed to scan mountpoint '%s'", mountpoint);
  713     
  714     ++dirs_done;
  715     
  716     if (flags & FL_VERBOSE)
  717         fputs("done", stdout);
  718     msg("Checked %u directories and %u files", dirs_done, files_done);
  719     if (needUser)
  720         dumpToFile(newUserFileName, USRQUOTA, fmt, &error);
  721     if (!error) {
  722         if (needGroup)
  723             dumpToFile(newGroupFileName, GRPQUOTA, fmt, &error);
  724     }
  725     if (error) {
  726         fatal("After gathering all the data from the filesystem, "
  727               "I am unable to generate the quota file containing it.  %s",
  728               error);
  729         strfree(error);
  730     }
  731     remove_list();
  732 }
  733 
  734 
  735 
  736 int
  737 main(int argc, char **argv) {
  738 
  739     struct cmdlineInfo cmdline;
  740     const char * error;
  741 
  742     progname = basename(argv[0]);
  743     malloc_mem = 0;
  744     free_mem = 0;
  745 
  746     parseCommandLine(argc, argv, &cmdline, &error);
  747 
  748     if (error)
  749         fatal("Invalid options/arguments.  %s", error);
  750 
  751     doFilesystem(cmdline.mountpoint,
  752                  cmdline.userinfile,
  753                  cmdline.useroutfile,
  754                  cmdline.groupinfile,
  755                  cmdline.groupoutfile,
  756                  QF_VFSV0);
  757 
  758     freeCmdline(cmdline);
  759 
  760     if (debugMalloc)
  761         msg("Allocated %d bytes memory; Freed %d bytes; Lost %d bytes",
  762             malloc_mem, free_mem, malloc_mem - free_mem);
  763 
  764     return 0;
  765 }