/*************************************************************************\
* Copyright (C) Michael Kerrisk, 2022. *
* *
* This program is free software. You may use, modify, and redistribute it *
* under the terms of the GNU General Public License as published by the *
* Free Software Foundation, either version 3 or (at your option) any *
* later version. This program is distributed without any warranty. See *
* the file COPYING.gpl-v3 for details. *
\*************************************************************************/
/* Supplementary program for Chapter 19 */
/* inotify_dtree.c
This is an example application to demonstrate the robust use of the
inotify API.
The goal of the application is to maintain an internal representation
("a cache") of the directory trees named on its command line. To keep
the application shorter, just the directories are represented, not the
files, but dealing with the latter is simpler in any case.
As directories are added, removed, and renamed in the subtrees, the
resulting inotify events are used to maintain an internal representation
of the directory trees that remains consistent with the filesystem.
The program also provides a command-line interface that allows the user
to perform tasks such as dumping the current state of the cache and
running a consistency check of the cache against the current state of
the directory tree(s).
Testing of this program is ongoing, and bug reports (to mtk@man7.org)
are welcome.
The rand_dtree.c program can be used to stress test the operation
of this program.
See also the article
"Filesystem notification, part 2: A deeper investigation of inotify"
July 2014
https://lwn.net/Articles/605128/
*/
/* Known limitations
- Pathnames longer than PATH_MAX are not handled.
*/
#define _GNU_SOURCE
#include <sys/select.h>
#include <sys/stat.h>
#include <limits.h>
#include <sys/select.h>
#include <sys/inotify.h>
#include <fcntl.h>
#include <ftw.h>
#include <signal.h>
#include <stdarg.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#define errExit(msg) do { perror(msg); exit(EXIT_FAILURE); \
} while (0)
/* logMessage() flags */
#define VB_BASIC 1 /* Basic messages */
#define VB_NOISY 2 /* Verbose messages */
static int verboseMask;
static int checkCache;
static int dumpCache;
static int readBufferSize = 0;
static char *stopFile;
static int abortOnCacheProblem;
static FILE *logfp = NULL;
static int inotifyReadCnt = 0; /* Counts number of read()s from
inotify file descriptor */
static const int INOTIFY_READ_BUF_LEN =
(100 * (sizeof(struct inotify_event) + NAME_MAX + 1));
static void dumpCacheToLog(void);
/* Something went badly wrong. Create a 'stop' file to signal the
'rand_dtree' processes to stop, dump a copy of the cache to the
log file, and abort. */
__attribute__ ((__noreturn__))
static void
createStopFileAndAbort(void)
{
open(stopFile, O_CREAT | O_RDWR, 0600);
dumpCacheToLog();
abort();
}
/* Write a log message. The message is sent to none, either, or both of
stderr and the log file, depending on 'vb_mask' and whether a log file
has been specified via command-line options . */
static void
logMessage(int vb_mask, const char *format, ...)
{
va_list argList;
/* Write message to stderr if 'vb_mask' is zero, or matches one
of the bits in 'verboseMask' */
if ((vb_mask == 0) || (vb_mask & verboseMask)) {
va_start(argList, format);
vfprintf(stderr, format, argList);
va_end(argList);
}
/* If we have a log file, write the message there */
if (logfp != NULL) {
va_start(argList, format);
vfprintf(logfp, format, argList);
va_end(argList);
}
}
/***********************************************************************/
/* Display some information about an inotify event. (Used when
when we are doing verbose logging.) */
static void
displayInotifyEvent(struct inotify_event *ev)
{
logMessage(VB_NOISY, "==> wd = %d; ", ev->wd);
if (ev->cookie > 0)
logMessage(VB_NOISY, "cookie = %4d; ", ev->cookie);
logMessage(VB_NOISY, "mask = ");
if (ev->mask & IN_ISDIR)
logMessage(VB_NOISY, "IN_ISDIR ");
if (ev->mask & IN_CREATE)
logMessage(VB_NOISY, "IN_CREATE ");
if (ev->mask & IN_DELETE_SELF)
logMessage(VB_NOISY, "IN_DELETE_SELF ");
if (ev->mask & IN_MOVE_SELF)
logMessage(VB_NOISY, "IN_MOVE_SELF ");
if (ev->mask & IN_MOVED_FROM)
logMessage(VB_NOISY, "IN_MOVED_FROM ");
if (ev->mask & IN_MOVED_TO)
logMessage(VB_NOISY, "IN_MOVED_TO ");
if (ev->mask & IN_IGNORED)
logMessage(VB_NOISY, "IN_IGNORED ");
if (ev->mask & IN_Q_OVERFLOW)
logMessage(VB_NOISY, "IN_Q_OVERFLOW ");
if (ev->mask & IN_UNMOUNT)
logMessage(VB_NOISY, "IN_UNMOUNT ");
logMessage(VB_NOISY, "\n");
if (ev->len > 0)
logMessage(VB_NOISY, " name = %s\n", ev->name);
}
/***********************************************************************/
/* Data structures and functions for the watch list cache */
/* We use a very simple data structure for caching watched directory
paths: a dynamically sized array that is searched linearly. Not
efficient, but our main goal is to demonstrate the use of inotify. */
struct watch {
int wd; /* Watch descriptor (-1 if slot unused) */
char path[PATH_MAX]; /* Cached pathname */
};
struct watch *wlCache = NULL; /* Array of cached items */
static int cacheSize = 0; /* Current size of the array */
/* Deallocate the watch cache */
static void
freeCache(void)
{
free(wlCache);
cacheSize = 0;
wlCache = NULL;
}
/* Check that all pathnames in the cache are valid, and refer
to directories */
static void
checkCacheConsistency(void)
{
struct stat sb;
int failures = 0;
for (int j = 0; j < cacheSize; j++) {
if (wlCache[j].wd >= 0) {
if (lstat(wlCache[j].path, &sb) == -1) {
logMessage(0,
"checkCacheConsistency: stat: "
"[slot = %d; wd = %d] %s: %s\n",
j, wlCache[j].wd, wlCache[j].path, strerror(errno));
failures++;
}
} else if (!S_ISDIR(sb.st_mode)) {
logMessage(0, "checkCacheConsistency: %s is not a directory\n",
wlCache[j].path);
exit(EXIT_FAILURE);
}
}
if (failures > 0)
logMessage(VB_NOISY, "checkCacheConsistency: %d failures\n",
failures);
}
/* Check whether the cache contains the watch descriptor 'wd'.
If found, return the slot number, otherwise return -1. */
static int
findWatch(int wd)
{
for (int j = 0; j < cacheSize; j++)
if (wlCache[j].wd == wd)
return j;
return -1;
}
/* Find and return the cache slot for the watch descriptor 'wd'.
The caller expects this watch descriptor to exist. If it does not,
there is a problem, which is signaled by the -1 return. */
static int
findWatchChecked(int wd)
{
int slot = findWatch(wd);
if (slot >= 0)
return slot;
logMessage(0, "Could not find watch %d\n", wd);
/* With multiple renamers there are still rare cases where
the cache is missing entries after a 'Could not find watch'
event. It looks as though this is because of races with nftw(),
since the cache is (occasionally) re-created with fewer
entries than there are objects in the tree(s). Returning
-1 to our caller identifies that there's a problem, and the
评论0