Skip to content

Instantly share code, notes, and snippets.

@walkie
Last active November 7, 2023 18:14
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save walkie/6282157 to your computer and use it in GitHub Desktop.
Save walkie/6282157 to your computer and use it in GitHub Desktop.
Patched version of Amit Singh's fslogger utility, which logs file system events in OS X.
/*
* fslogger.c
*
* A patched version of Amit Singh's fslogger utility, which logs file system
* events in OS X.
*
* This version fixes a small bug where four characters were missing from
* the beginning of each file path. It also eliminates a compiler warning.
*
* To compile:
* (adapted from http://www.jitsc.co.uk/blog/programming/bash-pipe-weirdness-using-fslogger/)
*
* > mkdir fslogger
* > cd fslogger
* > curl -O https://gist.github.com/walkie/6282157/raw/fslogger.c
* > git clone https://github.com/anatol/xnu.git xnu
* > gcc -I./xnu/bsd -Wall -o fslogger fslogger.c
*
*
* Original file header:
*
* Copyright (c) 2008 Amit Singh (osxbook.com).
* http://osxbook.com/software/fslogger/
*
* Source released under the GNU GENERAL PUBLIC LICENSE (GPL) Version 2.0.
* See http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt for details.
*
* Compile (Mac OS X 10.5.x only) as follows:
*
* gcc -I/path/to/xnu/bsd -Wall -o fslogger fslogger.c
*
*/
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/sysctl.h>
#include <sys/fsevents.h>
#include <pwd.h>
#include <grp.h>
#define PROGNAME "fslogger"
#define PROGVERS "2.1"
#define DEV_FSEVENTS "/dev/fsevents" // the fsevents pseudo-device
#define FSEVENT_BUFSIZ 131072 // buffer for reading from the device
#define EVENT_QUEUE_SIZE 4096 // limited by MAX_KFS_EVENTS
// an event argument
typedef struct kfs_event_arg {
u_int16_t type; // argument type
u_int16_t len; // size of argument data that follows this field
union {
struct vnode *vp;
char *str;
void *ptr;
int32_t int32;
dev_t dev;
ino_t ino;
int32_t mode;
uid_t uid;
gid_t gid;
uint64_t timestamp;
} data;
} kfs_event_arg_t;
#define KFS_NUM_ARGS FSE_MAX_ARGS
// an event
typedef struct kfs_event {
int32_t type; // event type
pid_t pid; // pid of the process that performed the operation
kfs_event_arg_t args[KFS_NUM_ARGS]; // event arguments
} kfs_event;
// event names
static const char *kfseNames[] = {
"FSE_CREATE_FILE",
"FSE_DELETE",
"FSE_STAT_CHANGED",
"FSE_RENAME",
"FSE_CONTENT_MODIFIED",
"FSE_EXCHANGE",
"FSE_FINDER_INFO_CHANGED",
"FSE_CREATE_DIR",
"FSE_CHOWN",
"FSE_XATTR_MODIFIED",
"FSE_XATTR_REMOVED",
};
// argument names
static const char *kfseArgNames[] = {
"FSE_ARG_UNKNOWN", "FSE_ARG_VNODE", "FSE_ARG_STRING", "FSE_ARGPATH",
"FSE_ARG_INT32", "FSE_ARG_INT64", "FSE_ARG_RAW", "FSE_ARG_INO",
"FSE_ARG_UID", "FSE_ARG_DEV", "FSE_ARG_MODE", "FSE_ARG_GID",
"FSE_ARG_FINFO",
};
// for pretty-printing of vnode types
enum vtype {
VNON, VREG, VDIR, VBLK, VCHR, VLNK, VSOCK, VFIFO, VBAD, VSTR, VCPLX
};
enum vtype iftovt_tab[] = {
VNON, VFIFO, VCHR, VNON, VDIR, VNON, VBLK, VNON,
VREG, VNON, VLNK, VNON, VSOCK, VNON, VNON, VBAD,
};
static const char *vtypeNames[] = {
"VNON", "VREG", "VDIR", "VBLK", "VCHR", "VLNK",
"VSOCK", "VFIFO", "VBAD", "VSTR", "VCPLX",
};
#define VTYPE_MAX (sizeof(vtypeNames)/sizeof(char *))
static char *
get_proc_name(pid_t pid)
{
size_t len = sizeof(struct kinfo_proc);
static int name[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, 0 };
static struct kinfo_proc kp;
name[3] = pid;
kp.kp_proc.p_comm[0] = '\0';
if (sysctl((int *)name, sizeof(name)/sizeof(*name), &kp, &len, NULL, 0))
return "?";
if (kp.kp_proc.p_comm[0] == '\0')
return "exited?";
return kp.kp_proc.p_comm;
}
int
main(int argc, char **argv)
{
int32_t arg_id;
int fd, clonefd = -1;
int i, j, eoff, off, ret;
kfs_event_arg_t *kea;
struct fsevent_clone_args fca;
char buffer[FSEVENT_BUFSIZ];
struct passwd *p;
struct group *g;
mode_t va_mode;
u_int32_t va_type;
u_int32_t is_fse_arg_vnode = 0;
char fileModeString[11 + 1];
int8_t event_list[] = { // action to take for each event
FSE_REPORT, // FSE_CREATE_FILE,
FSE_REPORT, // FSE_DELETE,
FSE_REPORT, // FSE_STAT_CHANGED,
FSE_REPORT, // FSE_RENAME,
FSE_REPORT, // FSE_CONTENT_MODIFIED,
FSE_REPORT, // FSE_EXCHANGE,
FSE_REPORT, // FSE_FINDER_INFO_CHANGED,
FSE_REPORT, // FSE_CREATE_DIR,
FSE_REPORT, // FSE_CHOWN,
FSE_REPORT, // FSE_XATTR_MODIFIED,
FSE_REPORT, // FSE_XATTR_REMOVED,
};
if (argc != 1) {
fprintf(stderr, "%s (%s)\n", PROGNAME, PROGVERS);
fprintf(stderr, "Copyright (c) 2008 Amit Singh. "
"All Rights Reserved.\n");
fprintf(stderr, "File system change logger for Mac OS X. Usage:\n");
fprintf(stderr, "\n\t%s\n\n", PROGNAME);
fprintf(stderr, "%s does not take any arguments. "
"It must be run as root.\n\n", PROGNAME);
printf("Please report bugs using the following contact information:\n"
"<URL:http://www.osxbook.com/software/bugs/>\n");
exit(1);
exit(1);
}
if (geteuid() != 0) {
fprintf(stderr, "You must be root to run %s. Try again using 'sudo'.\n",
PROGNAME);
exit(1);
}
setbuf(stdout, NULL);
if ((fd = open(DEV_FSEVENTS, O_RDONLY)) < 0) {
perror("open");
exit(1);
}
fca.event_list = (int8_t *)event_list;
fca.num_events = sizeof(event_list)/sizeof(int8_t);
fca.event_queue_depth = EVENT_QUEUE_SIZE;
fca.fd = &clonefd;
if ((ret = ioctl(fd, FSEVENTS_CLONE, (char *)&fca)) < 0) {
perror("ioctl");
close(fd);
exit(1);
}
close(fd);
printf("fsevents device cloned (fd %d)\nfslogger ready\n", clonefd);
if ((ret = ioctl(clonefd, FSEVENTS_WANT_EXTENDED_INFO, NULL)) < 0) {
perror("ioctl");
close(clonefd);
exit(1);
}
while (1) { // event processing loop
if ((ret = read(clonefd, buffer, FSEVENT_BUFSIZ)) > 0)
printf("=> received %d bytes\n", ret);
off = 0;
while (off < ret) { // process one or more events received
struct kfs_event *kfse = (struct kfs_event *)((char *)buffer + off);
off += sizeof(int32_t) + sizeof(pid_t); // type + pid
if (kfse->type == FSE_EVENTS_DROPPED) { // special event
printf("# Event\n");
printf(" %-14s = %s\n", "type", "EVENTS DROPPED");
printf(" %-14s = %d\n", "pid", kfse->pid);
off += sizeof(u_int16_t); // FSE_ARG_DONE: sizeof(type)
continue;
}
int32_t atype = kfse->type & FSE_TYPE_MASK;
uint32_t aflags = FSE_GET_FLAGS(kfse->type);
if ((atype < FSE_MAX_EVENTS) && (atype >= -1)) {
printf("# Event\n");
printf(" %-14s = %s", "type", kfseNames[atype]);
if (aflags & FSE_COMBINED_EVENTS) {
printf("%s", ", combined events");
}
if (aflags & FSE_CONTAINS_DROPPED_EVENTS) {
printf("%s", ", contains dropped events");
}
printf("\n");
} else { // should never happen
printf("This may be a program bug (type = %d).\n", atype);
exit(1);
}
printf(" %-14s = %d (%s)\n", "pid", kfse->pid,
get_proc_name(kfse->pid));
printf(" # Details\n # %-14s%4s %s\n", "type", "len", "data");
kea = kfse->args;
i = 0;
//while ((off < ret) && (i <= FSE_MAX_ARGS)) { // process arguments
while (off < ret) {
i++;
if (kea->type == FSE_ARG_DONE) { // no more arguments
printf(" %s (%#x)\n", "FSE_ARG_DONE", kea->type);
off += sizeof(u_int16_t);
break;
}
eoff = sizeof(kea->type) + sizeof(kea->len) + kea->len;
off += eoff;
arg_id = (kea->type > FSE_MAX_ARGS) ? 0 : kea->type;
printf(" %-16s%4hd ", kfseArgNames[arg_id], kea->len);
switch (kea->type) { // handle based on argument type
case FSE_ARG_VNODE: // a vnode (string) pointer
is_fse_arg_vnode = 1;
printf("%-6s = %s\n", "path", (char *)&(kea->data.vp));
break;
case FSE_ARG_STRING: // a string pointer
printf("%-6s = %s\n", "string", (char *)&(kea->data.str)-4);
break;
case FSE_ARG_INT32:
printf("%-6s = %d\n", "int32", kea->data.int32);
break;
case FSE_ARG_RAW: // a void pointer
printf("%-6s = ", "ptr");
for (j = 0; j < kea->len; j++)
printf("%02x ", ((char *)kea->data.ptr)[j]);
printf("\n");
break;
case FSE_ARG_INO: // an inode number
printf("%-6s = %d\n", "ino", (int)kea->data.ino);
break;
case FSE_ARG_UID: // a user ID
p = getpwuid(kea->data.uid);
printf("%-6s = %d (%s)\n", "uid", kea->data.uid,
(p) ? p->pw_name : "?");
break;
case FSE_ARG_DEV: // a file system ID or a device number
if (is_fse_arg_vnode) {
printf("%-6s = %#08x\n", "fsid", kea->data.dev);
is_fse_arg_vnode = 0;
} else {
printf("%-6s = %#08x (major %u, minor %u)\n",
"dev", kea->data.dev,
major(kea->data.dev), minor(kea->data.dev));
}
break;
case FSE_ARG_MODE: // a combination of file mode and file type
va_mode = (kea->data.mode & 0x0000ffff);
va_type = (kea->data.mode & 0xfffff000);
strmode(va_mode, fileModeString);
va_type = iftovt_tab[(va_type & S_IFMT) >> 12];
printf("%-6s = %s (%#08x, vnode type %s)", "mode",
fileModeString, kea->data.mode,
(va_type < VTYPE_MAX) ? vtypeNames[va_type] : "?");
if (kea->data.mode & FSE_MODE_HLINK) {
printf("%s", ", hard link");
}
if (kea->data.mode & FSE_MODE_LAST_HLINK) {
printf("%s", ", link count zero now");
}
printf("\n");
break;
case FSE_ARG_GID: // a group ID
g = getgrgid(kea->data.gid);
printf("%-6s = %d (%s)\n", "gid", kea->data.gid,
(g) ? g->gr_name : "?");
break;
case FSE_ARG_INT64: // timestamp
printf("%-6s = %llu\n", "tstamp", kea->data.timestamp);
break;
default:
printf("%-6s = ?\n", "unknown");
break;
}
kea = (kfs_event_arg_t *)((char *)kea + eoff); // next
} // for each argument
} // for each event
} // forever
close(clonefd);
exit(0);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment