#!mkcmd # $Id: kruft.m,v 1.28 2010/12/15 19:08:40 ksb Exp $ # # A simpler version of mm's crut program. No state in the file system (ksb) # and we qsort based on ctime/mtime/atime (-c,-m,-a). # We honor the space percent (e.g.: -S 78) or the files percent (-I 60). # We honor -n,-v, -h, -V of course. # from '' from '' from '' from '' from '' from '' from '' require "util_errno.m" require "std_help.m" "std_version.m" "std_control.m" require "heap.m" from '"machine.h"' basename "kruft" "" %i static char rcsid[] = "$Id: kruft.m,v 1.28 2010/12/15 19:08:40 ksb Exp $"; %% augment action 'V' { user "Version();" } boolean 'a' 'm' 'c' 'b' 'r' { named "cWhichTime" init "'c'" update "%n = %w;" help "change which inode time to honor (r for most recent)" } boolean 'T' { named "fTruncate" init "0" help "truncate files rather than unlinking them" } type "seconds" 'A' { named "tMaxAge" "pcOldest" track "fGaveAge" init '"23h"' exclude "SI" param "oldest" help "purge by an absolute time (seconds in the past)" } accum [":"] 'E' { named "pcPriv" after "if ((char *)0 == %n) {%n = \"index.html\";}" param "exclude" help "never unlink a file in this colon separated list" } integer 'S' { local named "iSpace" param "percent" init "-1" help "prune by space left on device" } integer 'I' { local named "iFiles" param "percent" init "-1" help "prune by files left on device" } type "bytes" 'd' { hidden named "lDirSize" "pcDirSize" exclude "SIA" init '"0"' help "specify a hard disk limit for the instance" } after "if (-1 == %rSn && -1 == %rIn) {%rSn = 90;}" boolean 's' { named "fSync" help "sync the filesystem after every unlink to defeat softupdates" } boolean '0' { hidden named "fZeroFmt" help "output names to delete on stdout in find's -print0 format" } list { named "Mitosis" help "" } every type "dir" { param "dirs" named "pDEKruft" "pcTarget" user "Kruft(%n, %N, %rSn, %rIn);" help "directory to prune files from" } exit { update "if (-1 != %rSn) {DeKruft(%rSn);}" user "exit(EX_OK);" } %c /* nada yet (ksb) */ static void Version() { #if HAVE_BIRTHTIME printf("%s: native brithtime support\n", progname); #else printf("%s: brithtime aliases to modify time\n", progname); #endif } /* never remove a file with the name (in any directory) (ksb) * (see -E) */ static int IsPriv(pcName) char *pcName; { register char *pcCur, *pc, *pcNext; register int iCmp; for (pcCur = pcPriv; (char *)0 != pcCur && '\000' != *pcCur; pcCur = pcNext) { if ((char *)0 == (pc = strchr(pcCur, ':'))) { iCmp = strlen(pcCur); pcNext = (char *)0; } else { iCmp = pc - pcCur; pcNext = pc+1; } if (0 == strncmp(pcCur, pcName, iCmp)) { return 1; } } return 0; } /* Decide how much we hate this file. Higher numbers make it (ksb) * much more likely we'll delete the file. * * If space is a factor larger older files get scorned more * if number of files is a factor older smaller files get scorned * dirs get excluded by link count. */ static int Weight(pst, iSpace, iFiles) struct stat *pst; int iSpace, iFiles; { register time_t *piCheck, tAge; static time_t tNow = 0; if (0 == tNow) { time(& tNow); } switch (cWhichTime) { case 'b': #if HAVE_BIRTHTIME #if __BSD_VISIBLE piCheck = (time_t *)& pst->st_birthtimespec; #else piCheck = (time_t *)& pst->st_birthtimespec; #endif break; #endif /* no birth date for inode, replace with mtime */ /*fallthrough*/ case 'm': piCheck = & pst->st_mtime; break; case 'c': piCheck = & pst->st_ctime; break; case 'a': piCheck = & pst->st_atime; break; default: case 'r': /* most recent time */ piCheck = & pst->st_mtime; if (*piCheck < pst->st_ctime) piCheck = & pst->st_ctime; if (*piCheck < pst->st_atime) piCheck = & pst->st_atime; break; } /* space and files factors are assumed to be -1, or 0 to 100 */ if (1 != pst->st_nlink) { return 0; } tAge = tNow - *piCheck; if (fGaveAge) { return (tAge > tMaxAge) ? tAge : 0; } tAge *= pst->st_blocks * (-1 == iSpace ? 0 : iSpace); return tAge + (-1 == iFiles ? 0 : iFiles); } /* emulate df, as best we can with statfs(2) (ksb) */ void KruftDF(char *pcDir, double *pdCurSpace, double *pdCurFiles) { register double dUsed; #if USE_STATVFS auto struct statvfs sfDir; #else auto struct statfs sfDir; #endif #if USE_STATVFS if (-1 == statvfs(pcDir, & sfDir)) { fprintf(stderr, "%s: statvfs: %s: %s\n", progname, pcDir, strerror(errno)); exit(EX_OSERR); } #else if (-1 == statfs(pcDir, & sfDir)) { fprintf(stderr, "%s: statfs: %s: %s\n", progname, pcDir, strerror(errno)); exit(EX_OSERR); } #endif dUsed = (double)(sfDir.f_blocks - sfDir.f_bfree); if (0.0 == sfDir.f_bavail + dUsed) *pdCurSpace = 100.00; else *pdCurSpace = dUsed*100.0/(double)(sfDir.f_bavail + dUsed); if (0 == sfDir.f_ffree) *pdCurFiles = 100.00; else *pdCurFiles = (double)(sfDir.f_files - sfDir.f_ffree)*100.0/(double)sfDir.f_ffree; } /* Queue cruft files to remove, by some metric (ksb) */ static void Kruft(pDIR, pcDir, iSpace, iFiles) DIR *pDIR; char *pcDir; int iSpace, iFiles; { register struct dirent *pDE; register int i; auto double dCurSpace, dCurFiles; auto struct stat stCand; auto char acName[MAXPATHLEN+4]; if ((char *)0 == pcDir || (DIR *)0 == pDIR) { return; } KruftDF(pcDir, & dCurSpace, & dCurFiles); if (fVerbose) { printf("%s: %s: by %ctime" , progname, pcDir, cWhichTime); if (fGaveAge) { printf(" (oldest %ld second%s)", (long)tMaxAge, 1 == tMaxAge ? "" : "s"); } else { if (-1 != iSpace) { if (-1 != iFiles) { printf(" (space %d%%, files %d%%)", iSpace, iFiles); } else { printf(" (space %d%%)", iSpace); } } else if (-1 != iFiles) { printf(" (files %d%%)", iFiles); } } printf("\n%s: presently at %5.2f space and %5.2f files\n", progname, dCurSpace, dCurFiles); } if (-1 != iSpace && dCurSpace < (double)iSpace) { iSpace = -1; } if (-1 != iFiles && dCurFiles < (double)iFiles) { iFiles = -1; } if (!fGaveAge && -1 == iSpace && -1 == iFiles) { if (fVerbose) { printf("%s: %s: nothing to clean\n", progname, pcDir); } return; } /* OK we have some work to do. We'll build a heap of the files * we might unlink, then pull off the heap to remove the correct * ones. If we run out of RAM while building the heap we fail. (ksb) */ while ((struct dirent *)0 != (pDE = readdir(pDIR))) { if (IsPriv(pDE->d_name)) { continue; } sprintf(acName, "%s/%s", pcDir, pDE->d_name); if (-1 == lstat(acName, & stCand)) { fprintf(stderr, "%s: lstat: %s: %s\n", progname, acName, strerror(errno)); continue; } if (S_IFREG != (S_IFMT & stCand.st_mode)) { continue; } if (0 >= (i = Weight(& stCand, iSpace, iFiles))) { continue; } HeapInsert(pcDir, pDE->d_name, i); } } /* Now we have a list of crufty files, pick some to purge. (ksb) */ static void DeKruft(iSpace) int iSpace; { auto char *pcDir, *pcFile; auto int iWeight; auto double dCurSpace, dCurFiles; auto char acRm[MAXPATHLEN+4]; while (HeapDequeue(&pcDir, &pcFile, &iWeight)) { if (!fGaveAge) { KruftDF(pcDir, & dCurSpace, & dCurFiles); if (dCurSpace < (double)iSpace) { return; } } sprintf(acRm, "%s/%s", pcDir, pcFile); if (fVerbose) { printf("%s %s\n", fTruncate ? ">" : "rm", acRm); } if (fExec && -1 == (fTruncate ? truncate(acRm, 0L) : unlink(acRm))) { fprintf(stderr, "%s: %s: %s: %s\n", progname, fTruncate ? "truncate" : "unlink", acRm, strerror(errno)); } if (fExec && fSync) { sync(); } } if (fGaveAge) { exit(EX_OK); } if (fVerbose) { printf("%s: heap empty\n", progname); } /* we failed */ exit(EX_TEMPFAIL); } /* like a funky long jump (ksb) */ static int ImAKid(piRet) int *piRet; { register int iPid, iWait; auto int wExit; switch (iPid = fork()) { case -1: fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno)); exit(EX_OSERR); case 0: /* I am the kid */ return 1; default: break; } while (iPid != (iWait = wait(& wExit))) { if (-1 == iWait) { *piRet = EX_OSERR; return 0; } } if (EX_OK == *piRet && 0 != wExit) { *piRet = EX_NOPERM; /* a guess */ } return 0; } /* given 2 directories, sort by device (fsid would be better) (ksb) */ static int fDiffDevs = 0; static int DevCmp(ppcLeft, ppcRight) const void *ppcLeft, *ppcRight; { auto struct stat stLeft, stRight; if (-1 == stat(*(const char **)ppcLeft, & stLeft)) { fprintf(stderr, "%s: stat: %s: %s\n", progname, *(char **)ppcLeft, strerror(errno)); exit(EX_OSERR); } if (-1 == stat(*(const char **)ppcRight, & stRight)) { fprintf(stderr, "%s: stat: %s: %s\n", progname, *(char **)ppcRight, strerror(errno)); exit(EX_OSERR); } fDiffDevs |= stLeft.st_dev != stRight.st_dev; return stLeft.st_dev - stRight.st_dev; } /* I could put a list in here to fork a kruft for each unique fs (ksb) * the forked kids would do the work, exit for me and I'd return * (or exit) w/o doing any of the work, else if there is only * one fs I'd just do it. This is cool, but hard to follow code. */ static void Mitosis(argc, argv) int argc; char **argv; { register int i, iFlip, iStart; auto struct stat astCursor[2]; auto int iRet = EX_OK; register char **ppcCopy; if (0 == argc) { return; } /* Copy-out args and sort them: * if all the dirs are on the same filesystem just return to do them. */ ppcCopy = calloc(argc+1, sizeof(char *)); if ((char **)0 == ppcCopy) { fprintf(stderr, "%s: calloc: out of memory\n", progname); exit(EX_UNAVAILABLE); } for (i = 0; i <= argc; ++i) { ppcCopy[i] = argv[i]; } (void)qsort(ppcCopy, argc, sizeof(char *), DevCmp); if (! fDiffDevs) { return; } /* They are not all on the same filesystem, we'll fork a process * for each unique filesystem. We zero out argv and copy back * only the directories we want for each child. The mkcmd parser * doesn't see a changed argc -- but the code skips (char *)0 * slots in the every list (for just this kinda thing). */ iFlip = 1; if (-1 == stat(ppcCopy[0], & astCursor[0])) { fprintf(stderr, "%s: stat: %s: %s\n", progname, ppcCopy[0], strerror(errno)); exit(EX_OSERR); } argv[0] = ppcCopy[0]; for (i = 1; i <= argc; ++i) { argv[i] = (char *)0; } for (iStart = i = 1; i < argc; ++i) { if (-1 != stat(ppcCopy[i], & astCursor[iFlip])) { /* nada */; } else if (ENOENT == errno) { continue; } else { fprintf(stderr, "%s: stat: %s: %s\n", progname, ppcCopy[i], strerror(errno)); exit(EX_OSERR); } /* When we are at a file not on the present device then * we fork-return the list we have (argv[0] .. argv[iStart-1]) * to the called in another process and build a new list. */ if (astCursor[0].st_dev != astCursor[1].st_dev) { if (ImAKid(& iRet)) { return; } while (iStart > 0) { argv[--iStart] = (char *)0; } iFlip ^= 1; } argv[iStart++] = ppcCopy[i]; } /* send the last list back to the caller */ if (ImAKid(& iRet)) { return; } /* tell the Customer all the return codes we saw (or'd together) */ exit(iRet); }