chiark / gitweb /
path: avoid an allocation in path_spec_watch
[elogind.git] / src / core / path.c
index dcb3b1ff60a7a3d7e4f4aa9af3f62bdfd16cefd0..295e5cdf0e83096e73898a5051eaab783b5c290b 100644 (file)
@@ -53,8 +53,7 @@ int path_spec_watch(PathSpec *s, Unit *u) {
         };
 
         bool exists = false;
-        char _cleanup_free_ *k = NULL;
-        char *slash;
+        char *slash, *oldslash = NULL;
         int r;
 
         assert(u);
@@ -62,10 +61,6 @@ int path_spec_watch(PathSpec *s, Unit *u) {
 
         path_spec_unwatch(s, u);
 
-        k = strdup(s->path);
-        if (!k)
-                return -ENOMEM;
-
         s->inotify_fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC);
         if (s->inotify_fd < 0) {
                 r = -errno;
@@ -76,28 +71,71 @@ int path_spec_watch(PathSpec *s, Unit *u) {
         if (r < 0)
                 goto fail;
 
-        s->primary_wd = inotify_add_watch(s->inotify_fd, k, flags_table[s->type]);
-        if (s->primary_wd >= 0)
-                exists = true;
+        /* This assumes the path was passed through path_kill_slashes()! */
 
-        do {
+        for (slash = strchr(s->path, '/'); ; slash = strchr(slash+1, '/')) {
+                char *cut = NULL;
                 int flags;
+                char tmp;
+
+                if (slash) {
+                        cut = slash + (slash == s->path);
+                        tmp = *cut;
+                        *cut = '\0';
+
+                        flags = IN_MOVE_SELF | IN_DELETE_SELF | IN_ATTRIB | IN_CREATE | IN_MOVED_TO;
+                } else
+                        flags = flags_table[s->type];
+
+                r = inotify_add_watch(s->inotify_fd, s->path, flags);
+                if (r < 0) {
+                        if (errno == EACCES || errno == ENOENT) {
+                                if (cut)
+                                        *cut = tmp;
+                                break;
+                        }
+
+                        log_warning("Failed to add watch on %s: %m", s->path);
+                        r = -errno;
+                        if (cut)
+                                *cut = tmp;
+                        goto fail;
+                } else {
+                        exists = true;
 
-                /* This assumes the path was passed through path_kill_slashes()! */
-                slash = strrchr(k, '/');
-                if (!slash)
-                        break;
+                        /* Path exists, we don't need to watch parent
+                           too closely. */
+                        if (oldslash) {
+                                char *cut2 = oldslash + (oldslash == s->path);
+                                char tmp2 = *cut2;
+                                *cut2 = '\0';
 
-                /* Trim the path at the last slash. Keep the slash if it's the root dir. */
-                slash[slash == k] = 0;
+                                inotify_add_watch(s->inotify_fd, s->path, IN_MOVE_SELF);
+                                /* Error is ignored, the worst can happen is
+                                   we get spurious events. */
 
-                flags = IN_MOVE_SELF;
-                if (!exists)
-                        flags |= IN_DELETE_SELF | IN_ATTRIB | IN_CREATE | IN_MOVED_TO;
+                                *cut2 = tmp2;
+                        }
+                }
 
-                if (inotify_add_watch(s->inotify_fd, k, flags) >= 0)
-                        exists = true;
-        } while (slash != k);
+                if (cut)
+                        *cut = tmp;
+
+                if (slash)
+                        oldslash = slash;
+                else {
+                        /* whole path has been iterated over */
+                        s->primary_wd = r;
+                        break;
+                }
+        }
+
+        if (!exists) {
+                log_error("Failed to add watch on any of the components of %s: %m",
+                          s->path);
+                r = -errno; /* either EACCESS or ENOENT */
+                goto fail;
+        }
 
         return 0;