#!mkcmd # $Compile(*): mkcmd -n %F %f && %b -mCompile -d%s %F.c && rm %F.[ch] # $Compile: mkcmd -n %F %f && %b -mCompile %F.c && rm %F.[ch] comment "%c%kCompile(*): $%{cc-cc%} -o %%F %%f" from "" from "" from "" from "" from "" from "" from "" from "" from "" from "" from '"machine.h"' from '"sparse.h"' %i static char rcsid[] = "$Id: quot.m,v 1.7 2007/01/02 15:45:52 ksb Exp $"; static int iErrs = EX_OK; /* our exit code */ static void **ppvHistogram; /* -c's sparse space */ %% require "util_errno.m" require "std_help.m" "util_fts.m" "std_version.m" require "util_errno.m" basename "quot" "" before 6 "SetTimes();" boolean 'a' { named "fAllMounted" abort "exit(EX_UNAVAILABLE);" help "visit all mounted filesystems (not implemented)" } boolean 'c' { named "fHistogram" help "display a histogram of file sizes" } boolean 'f' { named "fCount" help "display number of files, in addition to total space" } boolean 'g' { named "fByGroup" help "display totals per group, rather than login" } # tends to under report on an ext2, by a lot. boolean 'H' { hidden named "fHeuristic" help "use the size of the file, rather than the count of physical blocks" } boolean 'k' { named "fInKB" help "report sizes in kb (1024byte blocks rather than 512byte blocks)" } boolean 'n' { named "fFilter" abort "exit(EX_UNAVAILABLE);" help "filter ncheck output for inode number to owner mapping (not implemented)" } boolean 'U' { named "fNoUser" init "1" help "do not include the nouser (unmapped ids) output line" } boolean 'v' { named "fIdle" help "output the idle space per login" } augment action 'V' { user "Version();" } boolean 'N' { named "fHeader" init "1" help "supress the header line" } boolean 'X' { named "fXdev" help "honor cross a filesystem boundries" } after 6 "MapPasswd();" key "fts_visit" 2 { "Summate" "%a->fts_statp" "%a->fts_info" } list { update "ReMap(%#, %@);" user '%ZK/ef;' param "files" help "file hierarchies or devices to quot" key 2 initialize { "u_fts_walk" "%a" "FTS_LOGICAL|FTS_PHYSICAL|(fXdev?FTS_XDEV:0)" } } exit { named "Output" abort "exit(iErrs);" } %c static void Version() { #if USE_MNTENT printf("%s: mounted filesystems read from \"%s\"\n", progname, acMounted); #endif #if USE_GETMNTINFO printf("%s: mounted filesystems getmntinfo(3)\n", progname); #endif } static time_t t30, t60, t90; /* times to compare against */ static void SetTimes() { auto time_t tNow; (void) time(& tNow); t30 = tNow - 30 * 24 * 60 * 60; t60 = tNow - 60 * 24 * 60 * 60; t90 = tNow - 90 * 24 * 60 * 60; } typedef struct QMnode { int muid; char *pclogin; unsigned long wcount; unsigned long w30, w60, w90, wtotal; } QUOT_MAP; static QUOT_MAP *pQMAll; static unsigned int *puiOrder; static unsigned int iLogins; static unsigned int iMaxLoginLen; /* for output */ /* set the order in the uid map to "by uid" (ksb) */ static int ByUid(const void *pvLeft, const void *pvRight) { return pQMAll[*((unsigned int *)pvLeft)].muid - pQMAll[*((unsigned int *)pvRight)].muid; } /* set the order in the uid map to "by idle disk" (ksb) */ static int BySpace(const void *pvLeft, const void *pvRight) { return pQMAll[*((unsigned int *)pvRight)].wtotal - pQMAll[*((unsigned int *)pvLeft)].wtotal; } /* map the password file in and build a sum node for each login (ksb) */ static void MapPasswd() { register char *pcPasswd, *pcEol, *pcLast; register int fdPasswd; static char acPasswd[] = "/etc/passwd"; static char acGroup[] = "/etc/group"; auto struct stat stSize; register unsigned int iLines, i, iCurLoginLen; if (fHistogram) { ppvHistogram = sp_init(SP_MAX_KEY); } if (-1 == (fdPasswd = open(fByGroup ? acGroup : acPasswd, O_RDONLY, 0))) { fprintf(stderr, "%s: open: %s: %s\n", progname, fByGroup ? acGroup : acPasswd, strerror(errno)); exit(EX_OSFILE); } if (0 != fstat(fdPasswd, & stSize)) { fprintf(stderr, "%s: fstat: %s\n", progname, strerror(errno)); exit(EX_OSFILE); } pcPasswd = (char *)mmap((void *)0, stSize.st_size, PROT_READ, MAP_SHARED, fdPasswd, (off_t)0); /* if the password file doesn't end in a '\n' we loose */ pcEol = (char *)0; /* stupid gcc */ for (pcLast = pcPasswd, iLines = 0; stSize.st_size > 1; ++iLines, pcLast = pcEol) { pcEol = strchr(pcLast, '\n'); pcEol++; stSize.st_size -= (pcEol - pcLast); pcLast = pcEol; } iLogins = iLines; pQMAll = (QUOT_MAP *)calloc(iLines+1, sizeof(QUOT_MAP)); puiOrder = (unsigned int *)calloc(iLines+1, sizeof(unsigned int)); if ((QUOT_MAP *)0 == pQMAll || (unsigned int *)0 == puiOrder) { fprintf(stderr, "%s: calloc: %d,%d: %s\n", progname, iLines, sizeof(QUOT_MAP), strerror(errno)); exit(EX_OSERR); } pcLast = pcPasswd; /* bin:x:1:1:bin:/bin:/sbin/nologin\n */ iCurLoginLen = 0; for (i = 0; i < iLines; ++i, pcLast = pcEol) { register char *pcUid; pcEol = strchr(pcLast, '\n'); pcEol++; pcUid = strchr(pcLast, ':')+1; if (pcUid - pcLast > iCurLoginLen) { iCurLoginLen = (pcUid-pcLast); } pcUid = strchr(pcUid, ':')+1; pQMAll[i].pclogin = pcLast; pQMAll[i].muid = atol(pcUid); pQMAll[i].wcount = 0UL; pQMAll[i].w30 = 0; pQMAll[i].w60 = 0; pQMAll[i].w90 = 0; pQMAll[i].wtotal = 0; } pQMAll[i].pclogin = fByGroup ? "-nogroup:\n" : "-nouser:\n"; pQMAll[i].muid = 0; pQMAll[i].wcount = 0UL; pQMAll[i].w30 = 0; pQMAll[i].w60 = 0; pQMAll[i].w90 = 0; pQMAll[i].wtotal = 0; for (i = 0; i < iLines; ++i) { puiOrder[i] = i; } puiOrder[i] = i; qsort(puiOrder, iLines, sizeof(unsigned int), ByUid); iMaxLoginLen = iCurLoginLen-1; } /* find a uid in the billing table (ksb) */ static QUOT_MAP * FindUid(int iUid) { register int uiHi, uiLow, uiCheck; register QUOT_MAP *pQM; uiHi = iLogins; /* 1 past the end */ uiLow = 0; /* we might look here */ while (uiHi != uiLow) { uiCheck = (uiLow+uiHi)/2; pQM = & pQMAll[puiOrder[uiCheck]]; if (iUid == pQM->muid) return pQM; if (iUid < pQM->muid) uiHi = uiCheck; else if (uiLow == uiCheck) break; else uiLow = uiCheck; } /* make the "unknown uid" be us, so we hit the cache */ pQM = & pQMAll[iLogins]; pQM->muid = iUid; return pQM; } /* Assuming we are in uid order (via puiOrder[]), find the given (ksb) * uid (st_uid) in the array then bill the owner for the space. */ static int Summate(struct stat *pst, int ftsInfo) { static QUOT_MAP *pQMCache = (QUOT_MAP *)0; register QUOT_MAP *pQM; register int iUid; register off_t wBill, wBlock; register time_t tNode; wBlock = 0; switch (ftsInfo) { case FTS_DOT: /* . (don't count me twice */ case FTS_DP: /* A directory being visited in post-order */ return 0; case FTS_D: /* A directory being visited in pre-order. */ case FTS_F: /* A regular file. */ case FTS_SLNONE: /* symlink points with lstat info, sum it */ case FTS_DEFAULT: wBlock = pst->st_blocks; iUid = fByGroup ? pst->st_gid : pst->st_uid; wBill = fHeuristic ? pst->st_size / 512 : pst->st_blocks; tNode = pst->st_atime; break; case FTS_SL: /* symbolic link might count as a file? */ iUid = fByGroup ? pst->st_gid : pst->st_uid; wBill = 0; tNode = t30+60; break; case FTS_DC: case FTS_DNR: /* dir not +r */ iErrs = EX_NOPERM; return 0; case FTS_NS: /* no stat info for this file, sigh */ case FTS_NSOK: /* no stat, but that's OK? Not here... */ case FTS_ERR: /* any other error */ default: iErrs = EX_OSERR; return 0; } if (fHistogram) { register void **ppvFound; if ((void **)0 == (ppvFound = sp_index(ppvHistogram, wBlock))) { fprintf(stderr, "%s: no memory?\n", progname); exit(EX_OSERR); } if ((QUOT_MAP *)0 == (pQM = (QUOT_MAP *)*ppvFound)) { pQM = calloc(1, sizeof(QUOT_MAP)); if ((QUOT_MAP *)0 == pQM) { fprintf(stderr, "%s: no memory?\n", progname); exit(EX_OSERR); } pQM->wtotal = 0; *ppvFound = (void *)pQM; } wBill = 1; } else if ((QUOT_MAP *)0 == (pQM = pQMCache) || iUid != pQM->muid) { pQMCache = pQM = FindUid(iUid); } pQM->wtotal += wBill; ++pQM->wcount; /* -v (time features) */ if (t30 < tNode) { /* nada, was accessed w/o 30 days */ } else if (t60 < tNode) { pQM->w30 += wBill; } else if (t90 < tNode) { pQM->w60 += wBill; } else { pQM->w90 += wBill; } return 0; } /* output the histogram for -c from the collected size data (ksb) */ static int OutHist(sp_key wBlocks, void *pvQM) { static sp_key wAgg = 0; register QUOT_MAP *pQM; pQM = (QUOT_MAP *)pvQM; wAgg += wBlocks * pQM->wcount; printf("%-10lu %-10lu ", wBlocks, wAgg); if (fIdle) { printf("%-10lu %-10lu %-10lu ", pQM->w30, pQM->w60, pQM->w90); } if (fCount) { printf(" %lu", pQM->wcount); } printf("\n"); return 0; } /* Output the table we were asked to build (ksb) */ static void Output() { register unsigned int i, iNumWidth; register int iLogWidth; register QUOT_MAP *pQM; auto char acSizeWidth[64]; /* longest base 10 rep of a size */ /* include unmapped space (for output) if we were asked to */ if (fNoUser) { puiOrder[iLogins] = iLogins; ++iLogins; } (void)qsort(puiOrder, iLogins, sizeof(unsigned int), BySpace); sprintf(acSizeWidth, "%ld", (long)pQMAll[puiOrder[0]].wtotal); iNumWidth = strlen(acSizeWidth); if (iNumWidth < 6) { iNumWidth = 6; } if (fHeader) { iNumWidth = 10; printf("%*s %-*s ", iNumWidth, "total", iMaxLoginLen, fHistogram ? "blocks" : fByGroup ? "group" : "login"); if (fIdle) { printf("%*s %*s %*s ", iNumWidth, "30days", iNumWidth, "60days", iNumWidth, "90days"); } if (fCount) { printf(" Count"); } printf("\n"); } if (fHistogram) { sp_walk(ppvHistogram, 0, SP_MAX_KEY, OutHist); } else for (i = 0; i < iLogins; ++i) { pQM = & pQMAll[puiOrder[i]]; if (fInKB) { pQM->w30 = (pQM->w30+1)/2; pQM->w60 = (pQM->w60+1)/2; pQM->w90 = (pQM->w90+1)/2; pQM->wtotal = (pQM->wtotal+1)/2; } if (0 == pQM->wtotal) break; for (iLogWidth = 0; ':' != pQM->pclogin[iLogWidth]; ++iLogWidth) { /* search for epass colon after login */ } printf("%*ld %-*.*s ", iNumWidth, (long)pQM->wtotal, iMaxLoginLen, iLogWidth, pQM->pclogin); if (fIdle) { printf("%*ld %*ld %*ld ", iNumWidth, (long)pQM->w30, iNumWidth, (long)pQM->w60, iNumWidth, (long) pQM->w90); } if (fCount) { printf(" %ld", (long)pQM->wcount); } printf("\n"); } if (fNoUser) { --iLogins; } } %% %i #if USE_MNTENT #include static char acMounted[] = "/etc/mtab"; #endif #if USE_GETMNTINFO #include #include #include #endif %% %c /* remap any device nodes to the present mount point (ksb) */ static void ReMap(int argc, char **argv) { register char *pcThis; auto struct stat stDev; #if USE_MNTENT register FILE *fpMtab; register struct mntent *psm; fpMtab = (FILE *)0; for (/* argv */; (char *)0 != (pcThis = *argv); ++argv) { if (-1 == stat(pcThis, & stDev)) { fprintf(stderr, "%s: stat: %s: %s\n", progname, pcThis, strerror(errno)); exit(EX_OSERR); } if (S_ISDIR(stDev.st_mode)) { continue; } if ((FILE *)0 != fpMtab) { rewind(fpMtab); } else if ((FILE *)0 == (fpMtab = setmntent(acMounted, "r"))) { fprintf(stderr, "%s: setmentent: %s: %s\n", progname, acMounted, strerror(errno)); exit(EX_OSERR); } while ((struct mntent *)0 != (psm = getmntent(fpMtab))) { if (0 == strcmp(pcThis, psm->mnt_fsname)) break; } if ((struct mntent *)0 == psm) { fprintf(stderr, "%s: %s: not a directory or presently mounted device\n", progname, pcThis); exit(EX_NOINPUT); } *argv = strdup(psm->mnt_dir); } if ((FILE *)0 != fpMtab) { endmntent(fpMtab); } #endif #if USE_GETMNTINFO auto struct statfs *pSFMounted; register int iMounted, i; iMounted = 0; for (/* argv */; (char *)0 != (pcThis = *argv); ++argv) { if (-1 == stat(pcThis, & stDev)) { fprintf(stderr, "%s: stat: %s: %s\n", progname, pcThis, strerror(errno)); exit(EX_OSERR); } if (S_ISDIR(stDev.st_mode)) { continue; } if (0 != iMounted) { /* already cached */ } else if (-1 == (iMounted = getmntinfo(& pSFMounted, MNT_LOCAL))) { fprintf(stderr, "%s: getmntinfo: %s\n", progname, strerror(errno)); exit(EX_NOINPUT); } for(i = iMounted; 0 <= --i; /* nada */) { if (0 == strcmp(pcThis, pSFMounted[i].f_mntfromname)) break; } if (-1 == i) { fprintf(stderr, "%s: %s: not a directory or presently mounted device\n", progname, pcThis); exit(EX_NOINPUT); } *argv = strdup(pSFMounted[i].f_mntonname); } #endif /* US_GETMNTINFO */ }