[PATCH v2] server: support inotify file change notifications on FreeBSD

Damjan Jovanovic damjan.jov at gmail.com
Thu Dec 2 22:05:50 CST 2021


The inotify code uses the Linux-specific /proc/self/fd/<FD> symlinks
to translate
file descriptors to filesystem paths. On FreeBSD, do this translation using
its own sysctl instead.

Signed-off-by: Damjan Jovanovic <damjan.jov at gmail.com>
---
 server/change.c | 132 ++++++++++++++++++++++++++++++++++++++++--------
 1 file changed, 111 insertions(+), 21 deletions(-)
-------------- next part --------------
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 <errno.h>
 #include <unistd.h>
 #include <poll.h>
+#ifdef HAVE_SYS_SYSCTL_H
+# include <sys/sysctl.h>
+#endif
+#ifdef HAVE_SYS_USER_H
+# define thread __unix_thread
+# include <sys/user.h>
+# undef thread
+#endif
+
 #ifdef HAVE_SYS_INOTIFY_H
 #include <sys/inotify.h>
 #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


More information about the wine-devel mailing list