#!mkcmd # $Id: since.m,v 1.26 2012/07/27 15:18:58 ksb Exp $ # $Compile: mkcmd %f && ${cc-gcc} -Wall -g -o prog prog.c # from '' from '' from '' from '' from '' from '' from '' basename "since" "" require "sstamp.m" require "std_help.m" "std_version.m" require "util_home.m" %i static char rcsid[] = "$Id: since.m,v 1.26 2012/07/27 15:18:58 ksb Exp $"; static char acSinceDB[MAXPATHLEN]; /* Major/minor should be in types.h, on most UNIX systems -- ksb */ #if !defined(major) #define major(x) (((unsigned)(x)>>16)&0xffff) #endif #if !defined(minor) #define minor(x) ((x)&0xffff) #endif #if USE_64_API #define stat stat64 #define fstat fstat64 #define open open64 #define lseek lseek64 #define off_t off64_t #endif %% char* 'F' { named "pcSinceDB" "pcTempDB" param "db" help "since time stamp meta file" late } exclude "RZLS" int 'R' { named "iReplay" track "iGaveReplay" init "0" param "lines" help "number of lines to replay before previous position" } boolean 'Z' { named "fFromZero" init "0" help "start the file over at the first byte" } boolean 'S' { named "fSync" init "0" help "move the current position to the end-of-file for each target" } boolean 'L' { named "fListOnly" init "0" help "list present status of each file (only)" } boolean 'd' { named "fDelete" init "0" help "remove the database entry for each target, after processing" } init 3 '%rFU = 1;%rFN = acSinceDB;(void)util_home(acSinceDB, (char *)0);(void)strcat(acSinceDB, "/.");(void)strcat(acSinceDB, %b);' augment action 'V' { user 'printf("%%s: default time stamp meta file: %%s\\n", %b, acSinceDB);' } char* named "pcTarget" { } letter 'c' { named "cOutSep" init "'\\n'" param "sep" help "output separator for filenames under -d" } every { update "Since(%a);" local param "targets" help "file to display" } zero { named "SinceFd" update 'SinceFd("stdin", fileno(stdin));' aborts 'exit(EX_OK);' } %c /* Rewind the file by lines until we hit the beginning, in which (petef,ksb) * case we return the number of lines we couldn't rewind. */ static int SkipBack(int iFd, int iOrig) { ssize_t wCur; unsigned int iRead, iCursor, iTake, iLeft; auto char acBlock[1024]; iLeft = iOrig; wCur = lseek(iFd, 0, SEEK_CUR); while (wCur > 0) { iTake = sizeof(acBlock); if (iTake > wCur) { iTake = wCur; } wCur -= iTake; #if USE_PREAD iRead = pread(iFd, acBlock, iTake, wCur); #else if (wCur != lseek(iFd, wCur, SEEK_SET)) { return iLeft; } iRead = read(iFd, acBlock, iTake); #endif if (iRead != iTake) { return iOrig; } for (iCursor = iRead; iCursor-- > 0;) { if ('\n' != acBlock[iCursor]) { continue; } if (0 == iLeft--) { wCur += iCursor + 1; goto done; } } } done: if (wCur != lseek(iFd, wCur, SEEK_SET)) { return iOrig; } return iLeft; } /* do the deed for a directory (ksb) * We output like "ls", skipping "." and "..", of course, * show file that have changed by (st_ctime) since we last looked, or * since the epoch if we never looked. */ static void SinceDir(char *pcDir, struct stat *pstArg, char *pcCallme) { register int fNewFile, i; register off_t lNew; register SINCE_STAMP *pSS; register struct dirent *pDE; register DIR *pDR; register size_t w; auto time_t wLast; auto struct stat stItem; auto char acItem[MAXPATHLEN+4]; pSS = since_from_log((SINCE_STAMP *)0, pcSinceDB, pstArg); fNewFile = (SINCE_STAMP *)0 == pSS; if (fNewFile) { pSS = (SINCE_STAMP *)calloc(1, sizeof(SINCE_STAMP)); pSS->wlength = 0L; wLast = pSS->wwhen = (time_t)0; pSS->wnode = pstArg->st_ino; pSS->wdevice = pstArg->st_dev; } else { wLast = pSS->wwhen; } if (fFromZero) { pSS->wwhen = (time_t)0; } if (fSync) { pSS->wwhen = time((time_t *)0); } /* Check for common errors with the input, the kernel * should enforce maxpathlen, but we'll cross-check anyway. */ if ((DIR *)0 == (pDR = opendir(pcDir))) { fprintf(stderr, "%s: opendir: %s: %s\n", progname, pcDir, strerror(errno)); return; } if (MAXPATHLEN < (w = strlen(pcDir))) { fprintf(stderr, "%s: path length limit: %s\n", progname, strerror(ENAMETOOLONG)); return; } (void)strncpy(acItem, pcDir, sizeof(acItem)); if (0 == w || '/' != acItem[w-1]) { acItem[w++] = '/'; } lNew = 0L; /* We don't do -R here, and I don't think we can -- ksb */ for (i = 0; (struct dirent *)0 != (pDE = readdir(pDR)); /*nada*/) { if ('.' == pDE->d_name[0] && ('\000' == pDE->d_name[1] || ('.' == pDE->d_name[1] && '\000' == pDE->d_name[2]))) continue; ++i; if (w + strlen(pDE->d_name) > MAXPATHLEN) { fprintf(stderr, "%s: path lenght limit exceeded: %s/%s\n", progname, pcDir, pDE->d_name); continue; } (void)strcpy(acItem+w, pDE->d_name); if (-1 == stat(acItem, &stItem)) { fprintf(stderr, "%s: stat: %s: %s\n", progname, acItem, strerror(errno)); continue; } if (stItem.st_ctime <= pSS->wwhen) { continue; } if (pstArg->st_ctime < stItem.st_ctime) { pstArg->st_ctime = stItem.st_ctime; } if (fListOnly) { ++lNew; continue; } printf("%s%c", pDE->d_name, cOutSep); } closedir(pDR); pSS->istamp = 1; if (fListOnly) { static int fHeader = 1; auto char acDevInode[256], acNumber[64]; if (fHeader) { printf("%-5s %16s %16s %12s %s\n", "Mod", "Last-count", "Count", "Device/inode", "Name"); fHeader = 0; } #if HAVE_SNPRINTF snprintf(acNumber, sizeof(acNumber), #if USE_64_API "%lld", #else "%ld", #endif lNew); snprintf(acDevInode, sizeof(acDevInode), "%d,%d/%d", major(pSS->wdevice), minor(pSS->wdevice), pSS->wnode); #else sprintf(acNumber, #if USE_64_API "%lld", #else "%ld", #endif (long int)lNew); sprintf(acDevInode, "%ld,%ld/%ld", (long)major(pSS->wdevice), (long)minor(pSS->wdevice), (long)pSS->wnode); #endif printf("%-5s " #if USE_64_API "%16lld" #else "%16ld" #endif "%16ld %12s %s\n", fNewFile ? "new" : 0L != lNew ? acNumber : "no", (long)pSS->wlength, (long)i, acDevInode, pcCallme); /* Put the record back as it was in the db, if it existed. */ if (fNewFile) { return; } if (!fDelete) { pSS->wwhen = wLast; since_to_log(pSS, pcSinceDB); } return; } pSS->wwhen = pstArg->st_ctime; pSS->wlength = i; /* Record our new state, if we are supposed to, in the db file */ if (!fDelete) since_to_log(pSS, pcSinceDB); } /* do the deed for a file (petef,ksb) */ static void SinceFile(char *pcFile, struct stat *pstArg, int iFd) { register int iRet, iBlockSize, fNewFile = 0; register SINCE_STAMP *pSS; register char *pcBuf; /* Fetch our state from the sincedb, write what we've seen since * last time, and write our state back out */ pSS = since_from_log((SINCE_STAMP *)0, pcSinceDB, pstArg); if (fListOnly) { static int fHeader = 1; auto char acDevInode[256]; auto off_t wCur = lseek(iFd, 0l, SEEK_END); if (fHeader) { printf("%-3s %16s %16s %12s %s\n", "Mod", "Last-end", "Present-size", "Device/inode", "Name"); fHeader = 0; } if ((SINCE_STAMP *)0 == pSS) { printf("new %16s " #if USE_64_API "%16lld" #else "%16ld" #endif "16lld %12s %s\n", "~", (long)lseek(iFd, 0l, SEEK_END), "~/~", pcFile); return; } #if HAVE_SNPRINTF snprintf(acDevInode, sizeof(acDevInode), "%d,%d/%d", major(pSS->wdevice), minor(pSS->wdevice), pSS->wnode); #else sprintf(acDevInode, "%ld,%ld/%ld", (long)major(pSS->wdevice), (long)minor(pSS->wdevice), (long)pSS->wnode); #endif printf("%-3s " #if USE_64_API "%16lld %16lld" #else "%16ld %16ld" #endif " %12s %s\n", (pSS->wlength != wCur || pstArg->st_ino != pSS->wnode || pstArg->st_dev != pSS->wdevice) ? "yes" : "no", (long)pSS->wlength, (long)wCur, acDevInode, pcFile); if (!fDelete) { pSS->istamp = 1; since_to_log(pSS, pcSinceDB); } return; } if ((SINCE_STAMP *)0 == pSS) { fNewFile = 1; pSS = (SINCE_STAMP *)calloc(1, sizeof(SINCE_STAMP)); pSS->wlength = 0; pSS->wnode = pstArg->st_ino; pSS->wdevice = pstArg->st_dev; } iBlockSize = pstArg->st_blksize; pcBuf = (char *)calloc(iBlockSize, sizeof(char)); lseek(iFd, pSS->wlength, SEEK_SET); /* Do we need to rewind to show previous lines? * If we can't find all the lines we pad with blanks ones, this * makes the caller's invar. code path way easier. */ if (fFromZero && 0L != lseek(iFd, 0l, SEEK_SET)) { fprintf(stderr, "%s: lseek: %s: %s\n", progname, pcFile, strerror(errno)); /* If we can't rewind the file delete the db entry, * this is the correct thing to do most of the time. */ return; } if (fSync) { lseek(iFd, 0l, SEEK_END); } if (iGaveReplay) { iReplay = SkipBack(iFd, iReplay); while (iReplay-- > 0) { write(1, "\n", 1); } } /* Pump out all the data we have at present */ while ((iRet = read(iFd, pcBuf, iBlockSize)) > 0) { if (iRet != write(1, pcBuf, iRet)) break; } if (-1 == iRet) { fprintf(stderr, "%s: read: %s: %s\n", progname, pcFile, strerror(iRet)); exit(EX_OSERR); } /* Record our new state, if we are supposed to, in the db file */ pSS->wlength = lseek(iFd, 0l, SEEK_CUR); if (!fDelete) since_to_log(pSS, pcSinceDB); } /* read the data from a file the called has open (maybe flockd) (ksb) */ static void SinceFd(char *pcName, int iFd) { auto struct stat stFd; auto int iWasIn; if (isatty(iFd)) { fprintf(stderr, "%s: no targets given\n", progname); exit(EX_USAGE); } if (-1 == fstat(iFd, & stFd)) { fprintf(stderr, "%s: fstat: %d: %s\n", progname, iFd, strerror(errno)); exit(EX_OSERR); } if (S_ISREG(stFd.st_mode)) { SinceFile(pcName, &stFd, iFd); } else if (!S_ISDIR(stFd.st_mode)) { fprintf(stderr, "%s: %s: must be either a plain file or a directory\n", progname, pcName); exit(EX_DATAERR); } if (-1 == (iWasIn = open(".", O_RDONLY, 0711))) { fprintf(stderr, "%s: open: %s: %s\n", progname, ".", strerror(errno)); exit(EX_NOINPUT); } if (-1 == fchdir(iFd)) { fprintf(stderr, "%s: fchdir: %d: %s\n", progname, iFd, strerror(errno)); return; } SinceDir(".", &stFd, pcName); if (-1 == fchdir(iWasIn)) { fprintf(stderr, "%s: fchdir: %s: %s\n", progname, "back", strerror(errno)); exit(EX_NOINPUT); } close(iWasIn); } /* find out which way to since the input node (ksb) */ static void Since(char *pcTarget) { auto struct stat stArg; register int iFd; if ('-' == pcTarget[0] && '\000' == pcTarget[1]) { SinceFd(pcTarget, 0); return; } if (-1 == stat(pcTarget, & stArg)) { fprintf(stderr, "%s: stat: %s: %s\n", progname, pcTarget, strerror(errno)); exit(EX_OSERR); } if (S_ISREG(stArg.st_mode)) { /* nada, we like plain files */; } else if (S_ISDIR(stArg.st_mode)) { SinceDir(pcTarget, &stArg, pcTarget); return; } else { fprintf(stderr, "%s: %s: not a plain file or directory\n", progname, pcTarget); exit(EX_DATAERR); } if (-1 == (iFd = open(pcTarget, O_RDONLY, 0))) { fprintf(stderr, "%s: open: %s: %s\n", progname, pcTarget, strerror(errno)); exit(EX_NOINPUT); } SinceFile(pcTarget, &stArg, iFd); close(iFd); } %%