From: Damjan Jovanovic Subject: [PATCH v2] server: support inotify file change notifications on FreeBSD Message-Id: Date: Fri, 3 Dec 2021 06:05:50 +0200 The inotify code uses the Linux-specific /proc/self/fd/ symlinks to translate file descriptors to filesystem paths. On FreeBSD, do this translation using its own sysctl instead. Signed-off-by: Damjan Jovanovic --- server/change.c | 132 ++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 111 insertions(+), 21 deletions(-) diff --git a/server/change.c b/server/change.c index a01d6894151..147bec4c8f8 100644 --- a/server/change.c +++ b/server/change.c @@ -33,6 +33,15 @@ #include #include #include +#ifdef HAVE_SYS_SYSCTL_H +# include +#endif +#ifdef HAVE_SYS_USER_H +# define thread __unix_thread +# include +# undef thread +#endif + #ifdef HAVE_SYS_INOTIFY_H #include #endif @@ -164,6 +173,69 @@ enum dir_cache_state DIR_CACHE_STATE_RELEASED }; +#ifdef __linux__ + +static char *fd_path( int unix_fd, const char *suffix ) +{ + char *buffer; + static const char *proc_path = "/proc/self/fd/%u%s"; + + buffer = malloc( sizeof(proc_path) + 6 + strlen( suffix ) + 1); + if (buffer) sprintf( buffer, proc_path, unix_fd, suffix ); + return buffer; +} + +#elif defined(__FreeBSD__) + +static char *fd_path( int unix_fd, const char *suffix ) +{ + static int mib[4]; + size_t i, len; + char *buffer = NULL; + struct kinfo_file *kf; + char *path = NULL; + + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_FILEDESC; + mib[3] = getpid(); + len = 0; + if (sysctl( mib, sizeof(mib)/sizeof(mib[0]), NULL, &len, NULL, 0 )) + goto end; + len *= 2; + buffer = malloc( len ); + if (!buffer) + goto end; + if (sysctl( mib, sizeof(mib)/sizeof(mib[0]), buffer, &len, NULL, 0 )) + goto end; + + for (i = 0; i < len; ) + { + kf = (struct kinfo_file*) &buffer[i]; + if (kf->kf_structsize == 0) + break; + i += kf->kf_structsize; + + if (kf->kf_fd == unix_fd) + { + if (kf->kf_path[0]) + { + path = malloc( strlen( kf->kf_path ) + strlen( suffix ) + 1 ); + if (path) sprintf( path, "%s%s", kf->kf_path, suffix ); + } + break; + } + } + +end: + free( buffer ); + return path; +} + +#else +#error No fd_path() function for this platform +#endif + /* return an array of cache entries that can be freed on the client side */ static int *get_free_dir_cache_entries( struct process *process, data_size_t *size ) { @@ -734,9 +806,7 @@ static char *inode_get_path( struct inode *inode, int sz ) if (head) { int unix_fd = get_unix_fd( LIST_ENTRY( head, struct dir, in_entry )->fd ); - path = malloc ( 32 + sz ); - if (path) - sprintf( path, "/proc/self/fd/%u/", unix_fd ); + path = fd_path( unix_fd, "/" ); return path; } @@ -964,7 +1034,7 @@ static int inotify_adjust_changes( struct dir *dir ) unsigned int filter; struct inode *inode; struct stat st; - char path[32]; + char *path; int wd, unix_fd; if (!inotify_fd) @@ -990,8 +1060,10 @@ static int inotify_adjust_changes( struct dir *dir ) filter = filter_from_inode( inode, 0 ); - sprintf( path, "/proc/self/fd/%u", unix_fd ); - wd = inotify_add_dir( path, filter ); + path = fd_path( unix_fd, "" ); + wd = -1; + if (path) wd = inotify_add_dir( path, filter ); + free( path ); if (wd == -1) return 0; inode_set_wd( inode, wd ); @@ -1011,7 +1083,16 @@ static char *get_basename( const char *link ) r = readlink( link, buffer, n ); if (r < 0) + { + if (errno == EINVAL) /* not a symlink, normal for non-/proc paths */ + { + free( buffer ); + buffer = name = strdup( link ); + if (buffer) + r = strlen( buffer ); + } break; + } if (r < n) { @@ -1042,25 +1123,29 @@ static int dir_add_to_existing_notify( struct dir *dir ) struct inode *inode, *parent; unsigned int filter = 0; struct stat st, st_new; - char link[35], *name; - int wd, unix_fd; + char *link = NULL, *name = NULL; + int wd, unix_fd, ret = 0; if (!inotify_fd) - return 0; + goto end; unix_fd = get_unix_fd( dir->fd ); /* check if it's in the list of inodes we want to watch */ if (-1 == fstat( unix_fd, &st_new )) - return 0; + goto end; inode = find_inode( st_new.st_dev, st_new.st_ino ); if (inode) - return 0; + goto end; /* lookup the parent */ - sprintf( link, "/proc/self/fd/%u/..", unix_fd ); + link = fd_path( unix_fd, "/.." ); + if (!link) + goto end; if (-1 == stat( link, &st )) - return 0; + goto end; + free( link ); + link = NULL; /* * If there's no parent, stop. We could keep going adding @@ -1070,23 +1155,24 @@ static int dir_add_to_existing_notify( struct dir *dir ) */ parent = find_inode( st.st_dev, st.st_ino ); if (!parent) - return 0; + goto end; if (parent->wd == -1) - return 0; + goto end; filter = filter_from_inode( parent, 1 ); if (!filter) - return 0; + goto end; - sprintf( link, "/proc/self/fd/%u", unix_fd ); + link = fd_path( unix_fd, "" ); + if (!link) + goto end; name = get_basename( link ); if (!name) - return 0; + goto end; inode = inode_add( parent, st_new.st_dev, st_new.st_ino, name ); - free( name ); if (!inode) - return 0; + goto end; /* Couldn't find this inode at the start of the function, must be new */ assert( inode->wd == -1 ); @@ -1094,8 +1180,12 @@ static int dir_add_to_existing_notify( struct dir *dir ) wd = inotify_add_dir( link, filter ); if (wd != -1) inode_set_wd( inode, wd ); + ret = 1; - return 1; +end: + free( name ); + free( link ); + return ret; } #else