寻找反馈,bug,性能报告,和这个简单的文件观察者的一般代码审查。
基本算法: MacOS版本只设置要从CLI参数中观察的相关路径。Linux版本监视整个文件系统并筛选出不相关的路径。
#if !defined(__APPLE__)
#define _GNU_SOURCE// required for open_by_handle_at()
#include
#endif
#include
#include
#include
#include
#include
#include
#include
#if defined(__APPLE__)
#include
#include
char gPathBuffer[PATH_MAX];
static void callback(
ConstFSEventStreamRef streamRef,
void *clientCallBackInfo,
size_t numEvents,
void *eventPaths,
const FSEventStreamEventFlags eventFlags[],
const FSEventStreamEventId eventIds[]
) {
for (unsigned long i=numEvents;i--;) {
CFStringGetCString(CFArrayGetValueAtIndex(eventPaths,i),gPathBuffer,sizeof(gPathBuffer),kCFStringEncodingUTF8);
#ifdef DEBUG
printf("%u %s%s\n",(unsigned int)eventFlags[i],gPathBuffer,eventFlags[i]&kFSEventStreamEventFlagItemIsDir?"/":"");
#endif
fputs(gPathBuffer, stdout);
if (eventFlags[i] & kFSEventStreamEventFlagItemIsDir) putchar('/');
putchar('\n');
}
fflush(stdout);
}
#endif
void die(char *msg) { perror(msg); fprintf(stderr, " (%d)\n", errno); exit(1); }
int main(int argc, char *argv[]) {
// print help, if requested
for (int i = 1; i < argc; i++) {
if (0 == strcmp("--help", argv[i])) {
printf(
"jp-notify v0.8.0\n"
"\n"
"Usage:\n"
"jp-notify [PATH1] [PATH2] ...\n"
"\n"
"Description:\n"
"This command recursively watches the specified paths for changes to the files and/or directories they point to and their attributes. It prints the path when a change occurs. If no paths are specified it will use the current working directory.\n"
"\n"
"Note:\n"
"Must run as superuser on Linux systems. This is a limitation of the Linux kernel's fanotify system.\n"
);
return 0;
}
}
int paths_n = argc;// number of paths
char **paths = argv;// first path always ignored
#ifdef DEBUG
printf("argc=%d\n", argc);
#endif
// deal with no supplied args
if (1 == argc) {
paths_n = 2;
paths = (char **)malloc(sizeof(char *) * paths_n);
paths[1] = ".";
}
{
// convert all paths to absolute paths (so we can handle path args like "../../")
char p[PATH_MAX];// absolute path
for (int i = 1; i < paths_n; i++) {
(void)realpath(paths[i], p);// doesn't seem to return null ever
paths[i] = (char *)malloc(strlen(p) + 1);
strcpy(paths[i], p);
}
}
#ifdef DEBUG
for (int i = 1; i < paths_n; i++) printf("paths[%d]=%s\n", i, paths[i]);
fflush(stdout);
#endif
#if defined(__APPLE__)
// put paths params into Core Foundation (CF) Array
CFMutableArrayRef cf_paths = CFArrayCreateMutable(kCFAllocatorDefault, paths_n, &kCFTypeArrayCallBacks);
for (int i = 1; i < paths_n; i++) {
CFStringRef p = CFStringCreateWithCStringNoCopy(kCFAllocatorDefault, paths[i], kCFStringEncodingUTF8, kCFAllocatorDefault);
CFArrayAppendValue(cf_paths, p);
}
// monitor the path recursively (and also keep track of new files/folders created within it)
FSEventStreamRef stream = FSEventStreamCreate(
NULL,// memory allocator (NULL=default)
&callback,// FSEventStreamCallback
NULL,// context
cf_paths,// paths to watch
kFSEventStreamEventIdSinceNow,// since when
0,// latency (seconds)
kFSEventStreamCreateFlagUseCFTypes// The framework will invoke your callback function with CF types rather than raw C types (i.e., a CFArrayRef of CFStringRefs, rather than a raw C array of raw C string pointers). See FSEventStreamCallback.
| kFSEventStreamCreateFlagFileEvents// Request file-level notifications. Your stream will receive events about individual files in the hierarchy you're watching instead of only receiving directory level notifications. Use this flag with care as it will generate significantly more events than without it.
);
dispatch_queue_t d = dispatch_queue_create("jp-watch", NULL);
FSEventStreamSetDispatchQueue(stream, d);
FSEventStreamStart(stream);
#ifdef DEBUG
sleep(10);// sleep so can see the results of "leaks" for detecting memory leaks
#else
pause();// The pause function suspends program execution until a signal arrives whose action is either to execute a handler function, or to terminate the process.
#endif
// free memory and resources
// paths
for (int i = 0; i < paths_n - 1; i++) CFRelease(CFArrayGetValueAtIndex(cf_paths, i));
CFRelease(cf_paths);
// stream
dispatch_release(d);
FSEventStreamStop(stream);
FSEventStreamInvalidate(stream);
FSEventStreamRelease(stream);
#else
// fanotify init
int fd = fanotify_init(FAN_REPORT_FID, O_RDONLY);
if (-1 == fd) die("Fanotify init failed");
// fanotify mark
if (-1 == fanotify_mark(
fd,
FAN_MARK_ADD// add new marks
| FAN_MARK_FILESYSTEM// monitor the whole filesystem
, FAN_MODIFY
| FAN_ATTRIB// since Linux 5.1
| FAN_CREATE// since Linux 5.1
| FAN_DELETE// since Linux 5.1
| FAN_MOVE// since Linux 5.1, = FAN_MOVED_FROM | FAN_MOVED_TO
| FAN_ONDIR// require in order to create events when subdirectory entries are modified (i.e., mkdir/rmdir)
| FAN_EVENT_ON_CHILD// events for the immediate children of marked directories shall be created
, AT_FDCWD
, "/"// path to monitor
)) {
if (1 == errno) fputs("Must run as superuser on Linux systems.\n", stderr);
die("Fanotify mark failed");
};
// fanotify process events
char b[8192];// byte buffer (docs recommend a big one e.g. 4096)
char p[PATH_MAX];// /proc/self/fd/%d path
char f[PATH_MAX];// filename
ssize_t n;
while ((n = read(fd, &b, sizeof(b))) > 0) {
struct fanotify_event_metadata *m = (struct fanotify_event_metadata *)b;
while (FAN_EVENT_OK(m, n)) {
struct fanotify_event_info_fid *fid = (struct fanotify_event_info_fid *)(m + 1);
// get fd from handle
int event_fd = open_by_handle_at(AT_FDCWD, (struct file_handle *)fid->handle, O_RDONLY);
// get filename from fd
sprintf(p, "/proc/self/fd/%d", event_fd);
int l = readlink(p, f, PATH_MAX);
f[l] = '\0';
// filter by paths
int i;
for (i = 1; i < paths_n; i++) {
if (strncmp(f, paths[i], strlen(paths[i])) == 0) break;// matching prefix found, so stop comparing
}
if (paths_n != i) {// in the path set?
fputs(f, stdout);
if (m->mask & FAN_ONDIR) putchar('/');
putchar('\n');
fflush(stdout);
//if (m->mask & FAN_ACCESS) puts("FAN_ACCESS");
//if (m->mask & FAN_MODIFY) puts("FAN_MODIFY");
//if (m->mask & FAN_ATTRIB) puts("FAN_ATTRIB");
//if (m->mask & FAN_CREATE) puts("FAN_CREATE");
//if (m->mask & FAN_DELETE) puts("FAN_DELETE");
//if (m->mask & FAN_MOVE) puts("FAN_MOVE");
//if (m->mask & FAN_ONDIR) { puts("/"); puts("FAN_ONDIR"); } else { putchar('\n'); }
//if (m->mask & FAN_EVENT_ON_CHILD) puts("FAN_EVENT_ON_CHILD");
}
m = FAN_EVENT_NEXT(m, n);
}
}
die("Fanotify read failed");
#endif
if (1 == argc) free(paths);
return 0;
}发布于 2023-02-23 21:27:20
还不清楚为什么您认为这两个程序应该有相同的主程序,而不是它们在不同的平台上执行类似的功能。几乎所有代码都是在实现之间共享的。唯一常见的代码是命令行解析,并将要监视的路径转换为绝对路径。
有一点是很清楚的,那就是,这两个版本在main()中都做得太多了,而在main()调用的函数中却做得不够。在Linux版本中尤其如此,其中唯一被调用的本地函数是void die(char* msg)。
另一件很明显的事情是,这段代码实际上还没有准备好处理命令行参数,只有一个支持的标志,那就是--帮助标志。应该添加的函数之一是解析命令行。基于实现的一个标志的复杂性,我建议添加帮助命令行解析器的第二个函数,该函数可能应该称为usage。对于这两种不同的实现,使用应该是不同的,在Apple上运行时,不需要指出Linux系统上的超级用户。
注释掉的代码是没有准备好检查的代码,没有必要在文件中看到这一点。
//if (m->mask & FAN_ACCESS) puts("FAN_ACCESS");
//if (m->mask & FAN_MODIFY) puts("FAN_MODIFY");
//if (m->mask & FAN_ATTRIB) puts("FAN_ATTRIB");
//if (m->mask & FAN_CREATE) puts("FAN_CREATE");
//if (m->mask & FAN_DELETE) puts("FAN_DELETE");
//if (m->mask & FAN_MOVE) puts("FAN_MOVE");
//if (m->mask & FAN_ONDIR) { puts("/"); puts("FAN_ONDIR"); } else { putchar('\n'); }
//if (m->mask & FAN_EVENT_ON_CHILD) puts("FAN_EVENT_ON_CHILD");此代码应该是程序的Linux版本中的一个函数:
// fanotify process events
char b[8192];// byte buffer (docs recommend a big one e.g. 4096)
char p[PATH_MAX];// /proc/self/fd/%d path
char f[PATH_MAX];// filename
ssize_t n;
while ((n = read(fd, &b, sizeof(b))) > 0) {
struct fanotify_event_metadata* m = (struct fanotify_event_metadata*)b;
while (FAN_EVENT_OK(m, n)) {
struct fanotify_event_info_fid* fid = (struct fanotify_event_info_fid*)(m + 1);
// get fd from handle
int event_fd = open_by_handle_at(AT_FDCWD, (struct file_handle*)fid->handle, O_RDONLY);
// get filename from fd
sprintf(p, "/proc/self/fd/%d", event_fd);
int l = readlink(p, f, PATH_MAX);
f[l] = '\0';
// filter by paths
int i;
for (i = 1; i < paths_n; i++) {
if (strncmp(f, paths[i], strlen(paths[i])) == 0) break;// matching prefix found, so stop comparing
}
if (paths_n != i) {// in the path set?
fputs(f, stdout);
if (m->mask & FAN_ONDIR) putchar('/');
putchar('\n');
fflush(stdout);
//if (m->mask & FAN_ACCESS) puts("FAN_ACCESS");
//if (m->mask & FAN_MODIFY) puts("FAN_MODIFY");
//if (m->mask & FAN_ATTRIB) puts("FAN_ATTRIB");
//if (m->mask & FAN_CREATE) puts("FAN_CREATE");
//if (m->mask & FAN_DELETE) puts("FAN_DELETE");
//if (m->mask & FAN_MOVE) puts("FAN_MOVE");
//if (m->mask & FAN_ONDIR) { puts("/"); puts("FAN_ONDIR"); } else { putchar('\n'); }
//if (m->mask & FAN_EVENT_ON_CHILD) puts("FAN_EVENT_ON_CHILD");
}
m = FAN_EVENT_NEXT(m, n);
}
}注意,ssize_t不是可移植的。
可能的内存分配错误的
在现代高级语言(如C++ )中,内存分配错误会抛出程序员可以捕捉到的异常。在C编程语言中,情况并非如此。虽然这在现代计算机中很少见,因为内存太多,但内存分配可能会失败,特别是当代码在有限内存应用程序(如嵌入式控制系统)中工作时。在C编程语言中,当内存分配失败时,函数malloc()、calloc()和realloc()返回NULL。通过NULL指针引用任何内存地址将导致undefined行为 (UB)。
在这种情况下,可能的未知行为可能是内存页错误(在Unix中,这将是调用分段冲突),程序中的数据损坏,在非常旧的计算机中,它甚至可能导致计算机重新启动(堆栈指针损坏)。
为了防止这种undefined行为,最好的做法是始终遵循内存分配语句,测试返回的指针不是NULL。
C中使用内存分配时的K214约定
当在C中使用malloc()、calloc()或realloc()时,常见的约定是相当大的(*PTR)相当大的(PTR_TYPE),这使得代码更容易维护,并且不太容易出错,因为如果指针的类型发生变化,就需要更少的编辑。
不需要将malloc()的结果转换为适当的类型。
为遵循上述最佳做法而重写的当前代码:
// deal with no supplied args
if (1 == argc) {
paths_n = 2;
paths = malloc(sizeof(*paths) * paths_n);
if (!paths)
{
fprintf(stderr, "malloc of paths failed! %s exiting", argv[0]);
return EXIT_FAILURE;
}
paths[1] = ".";
}
{
// convert all paths to absolute paths (so we can handle path args like "../../")
char p[PATH_MAX];// absolute path
for (int i = 1; i < paths_n; i++) {
(void)realpath(paths[i], p);// doesn't seem to return null ever
paths[i] = malloc(strlen(p) + 1);
if (!paths[i])
{
fprintf(stderr, "malloc of paths[%d] failed! %s exiting", i, argv[0]);
return EXIT_FAILURE;
}
strcpy(paths[i], p);
}
}https://codereview.stackexchange.com/questions/283521
复制相似问题