chiark / gitweb /
path-lookup: rearrange things so that the system path order follows the user path...
[elogind.git] / src / cgroup-util.c
index 090573bd31ee7d0167e47f7ef3603ee0c6082623..904d30095226a6f027f1d527634cb4e9cc311b24 100644 (file)
@@ -27,6 +27,7 @@
 #include <dirent.h>
 #include <sys/stat.h>
 #include <sys/types.h>
+#include <ftw.h>
 
 #include "cgroup-util.h"
 #include "log.h"
@@ -153,17 +154,38 @@ int cg_read_subgroup(DIR *d, char **fn) {
         return 0;
 }
 
-int cg_rmdir(const char *controller, const char *path) {
+int cg_rmdir(const char *controller, const char *path, bool honour_sticky) {
         char *p;
         int r;
 
-        if ((r = cg_get_path(controller, path, NULL, &p)) < 0)
+        r = cg_get_path(controller, path, NULL, &p);
+        if (r < 0)
                 return r;
 
+        if (honour_sticky) {
+                char *tasks;
+
+                /* If the sticky bit is set don't remove the directory */
+
+                tasks = strappend(p, "/tasks");
+                if (!tasks) {
+                        free(p);
+                        return -ENOMEM;
+                }
+
+                r = file_is_priv_sticky(tasks);
+                free(tasks);
+
+                if (r > 0) {
+                        free(p);
+                        return 0;
+                }
+        }
+
         r = rmdir(p);
         free(p);
 
-        return r < 0 ? -errno : 0;
+        return (r < 0 && errno != ENOENT) ? -errno : 0;
 }
 
 int cg_kill(const char *controller, const char *path, int sig, bool sigcont, bool ignore_self, Set *s) {
@@ -302,7 +324,7 @@ int cg_kill_recursive(const char *controller, const char *path, int sig, bool si
                 ret = r;
 
         if (rem)
-                if ((r = cg_rmdir(controller, path)) < 0) {
+                if ((r = cg_rmdir(controller, path, true)) < 0) {
                         if (ret >= 0 &&
                             r != -ENOENT &&
                             r != -EBUSY)
@@ -466,7 +488,7 @@ int cg_migrate_recursive(const char *controller, const char *from, const char *t
                 ret = r;
 
         if (rem)
-                if ((r = cg_rmdir(controller, from)) < 0) {
+                if ((r = cg_rmdir(controller, from, true)) < 0) {
                         if (ret >= 0 &&
                             r != -ENOENT &&
                             r != -EBUSY)
@@ -482,13 +504,26 @@ finish:
 
 int cg_get_path(const char *controller, const char *path, const char *suffix, char **fs) {
         const char *p;
-        char *mp;
-        int r;
+        char *t;
         static __thread bool good = false;
 
         assert(controller);
         assert(fs);
 
+        if (_unlikely_(!good)) {
+                int r;
+
+                r = path_is_mount_point("/sys/fs/cgroup", false);
+                if (r <= 0)
+                        return r < 0 ? r : -ENOENT;
+
+                /* Cache this to save a few stat()s */
+                good = true;
+        }
+
+        if (isempty(controller))
+                return -EINVAL;
+
         /* This is a very minimal lookup from controller names to
          * paths. Since we have mounted most hierarchies ourselves
          * should be kinda safe, but eventually we might want to
@@ -502,50 +537,88 @@ int cg_get_path(const char *controller, const char *path, const char *suffix, ch
         else
                 p = controller;
 
-        if (asprintf(&mp, "/sys/fs/cgroup/%s", p) < 0)
+        if (path && suffix)
+                t = join("/sys/fs/cgroup/", p, "/", path, "/", suffix, NULL);
+        else if (path)
+                t = join("/sys/fs/cgroup/", p, "/", path, NULL);
+        else if (suffix)
+                t = join("/sys/fs/cgroup/", p, "/", suffix, NULL);
+        else
+                t = join("/sys/fs/cgroup/", p, NULL);
+
+        if (!t)
                 return -ENOMEM;
 
-        if (!good) {
-                if ((r = path_is_mount_point(mp)) <= 0) {
-                        free(mp);
-                        return r < 0 ? r : -ENOENT;
-                }
+        path_kill_slashes(t);
 
-                /* Cache this to save a few stat()s */
-                good = true;
-        }
+        *fs = t;
+        return 0;
+}
 
-        if (path && suffix)
-                r = asprintf(fs, "%s/%s/%s", mp, path, suffix);
-        else if (path)
-                r = asprintf(fs, "%s/%s", mp, path);
-        else if (suffix)
-                r = asprintf(fs, "%s/%s", mp, suffix);
-        else {
-                path_kill_slashes(mp);
-                *fs = mp;
+static int trim_cb(const char *path, const struct stat *sb, int typeflag, struct FTW *ftwbuf) {
+        char *p;
+        bool is_sticky;
+
+        if (typeflag != FTW_DP)
                 return 0;
+
+        if (ftwbuf->level < 1)
+                return 0;
+
+        p = strappend(path, "/tasks");
+        if (!p) {
+                errno = ENOMEM;
+                return 1;
         }
 
-        free(mp);
-        path_kill_slashes(*fs);
-        return r < 0 ? -ENOMEM : 0;
+        is_sticky = file_is_priv_sticky(p) > 0;
+        free(p);
+
+        if (is_sticky)
+                return 0;
+
+        rmdir(path);
+        return 0;
 }
 
 int cg_trim(const char *controller, const char *path, bool delete_root) {
         char *fs;
-        int r;
+        int r = 0;
 
         assert(controller);
         assert(path);
 
-        if ((r = cg_get_path(controller, path, NULL, &fs)) < 0)
+        r = cg_get_path(controller, path, NULL, &fs);
+        if (r < 0)
                 return r;
 
-        r = rm_rf(fs, true, delete_root);
+        errno = 0;
+        if (nftw(fs, trim_cb, 64, FTW_DEPTH|FTW_MOUNT|FTW_PHYS) < 0)
+                r = errno ? -errno : -EIO;
+
+        if (delete_root) {
+                bool is_sticky;
+                char *p;
+
+                p = strappend(fs, "/tasks");
+                if (!p) {
+                        free(fs);
+                        return -ENOMEM;
+                }
+
+                is_sticky = file_is_priv_sticky(p) > 0;
+                free(p);
+
+                if (!is_sticky)
+                        if (rmdir(fs) < 0 && errno != ENOENT) {
+                                if (r == 0)
+                                        r = -errno;
+                        }
+        }
+
         free(fs);
 
-        return r == -ENOENT ? 0 : r;
+        return r;
 }
 
 int cg_delete(const char *controller, const char *path) {
@@ -639,7 +712,11 @@ int cg_set_group_access(const char *controller, const char *path, mode_t mode, u
         assert(controller);
         assert(path);
 
-        if ((r = cg_get_path(controller, path, NULL, &fs)) < 0)
+        if (mode != (mode_t) -1)
+                mode &= 0777;
+
+        r = cg_get_path(controller, path, NULL, &fs);
+        if (r < 0)
                 return r;
 
         r = chmod_and_chown(fs, mode, uid, gid);
@@ -648,16 +725,47 @@ int cg_set_group_access(const char *controller, const char *path, mode_t mode, u
         return r;
 }
 
-int cg_set_task_access(const char *controller, const char *path, mode_t mode, uid_t uid, gid_t gid) {
+int cg_set_task_access(const char *controller, const char *path, mode_t mode, uid_t uid, gid_t gid, int sticky) {
         char *fs;
         int r;
 
         assert(controller);
         assert(path);
 
-        if ((r = cg_get_path(controller, path, "tasks", &fs)) < 0)
+        if (mode == (mode_t) -1 && uid == (uid_t) -1 && gid == (gid_t) -1 && sticky < 0)
+                return 0;
+
+        if (mode != (mode_t) -1)
+                mode &= 0666;
+
+        r = cg_get_path(controller, path, "tasks", &fs);
+        if (r < 0)
                 return r;
 
+        if (sticky >= 0 && mode != (mode_t) -1)
+                /* Both mode and sticky param are passed */
+                mode |= (sticky ? S_ISVTX : 0);
+        else if ((sticky >= 0 && mode == (mode_t) -1) ||
+                 (mode != (mode_t) -1 && sticky < 0)) {
+                struct stat st;
+
+                /* Only one param is passed, hence read the current
+                 * mode from the file itself */
+
+                r = lstat(fs, &st);
+                if (r < 0) {
+                        free(fs);
+                        return -errno;
+                }
+
+                if (mode == (mode_t) -1)
+                        /* No mode set, we just shall set the sticky bit */
+                        mode = (st.st_mode & ~S_ISVTX) | (sticky ? S_ISVTX : 0);
+                else
+                        /* Only mode set, leave sticky bit untouched */
+                        mode = (st.st_mode & ~0777) | mode;
+        }
+
         r = chmod_and_chown(fs, mode, uid, gid);
         free(fs);