This patch adds a 'koji-helper' setuid program which implements the
following methods:

* koji-helper rmrf <dir>
    removes everything under <dir>, inclusive <dir>.  It does not cross
    filesystem borders

* koji-helper rmtree <dir>
    removes everything under <dir>, but not <dir> itself. It does not cross
    filesystem borders


Methods above are implemented to replace the python 'safe_rmtree()' method
which was never safe, nor will work when 'kojid' is running as non-root.

Signed-off-by: Enrico Scholz <[EMAIL PROTECTED]>
---

 Makefile          |   15 ++-
 builder/kojid     |   53 +++--------
 koji.spec         |    3 -
 src/koji-helper.c |  260 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 286 insertions(+), 45 deletions(-)

diff --git a/Makefile b/Makefile
index cd88d4b..2a239fd 100644
--- a/Makefile
+++ b/Makefile
@@ -2,6 +2,8 @@ NAME=koji
 SPECFILE = $(firstword $(wildcard *.spec))
 SUBDIRS = hub builder koji cli docs util www
 
+sbindir = /usr/sbin
+
 ifdef DIST
 DIST_DEFINES := --define "dist $(DIST)"
 endif
@@ -52,11 +54,14 @@ ifndef TAG
 TAG=$(NAME)-$(VERSION)-$(RELEASE)
 endif
 
-_default:
-       @echo "read the makefile"
+all:   src/koji-helper
+
+src/koji-helper:       src/koji-helper.c
+       $(CC) $(CFLAGS) $< -o $@
 
 clean:
        rm -f *.o *.so *.pyc *~ koji*.bz2 koji*.src.rpm
+       rm -f src/koji-helper
        rm -rf koji-$(VERSION)
        for d in $(SUBDIRS); do make -s -C $$d clean; done
 
@@ -100,14 +105,16 @@ force-tag::
 #      @$(MAKE) tag TAG_OPTS="-F $(TAG_OPTS)"
 
 DESTDIR ?= /
-install:
+install:       all
        @if [ "$(DESTDIR)" = "" ]; then \
                echo " "; \
                echo "ERROR: A destdir is required"; \
                exit 1; \
        fi
 
-       mkdir -p $(DESTDIR)
+       mkdir -p $(DESTDIR)$(sbindir)
+
+       install -p -m0710 src/koji-helper $(DESTDIR)$(sbindir)
 
        for d in $(SUBDIRS); do make DESTDIR=`cd $(DESTDIR); pwd` \
                -C $$d install; [ $$? = 0 ] || exit 1; done
diff --git a/builder/kojid b/builder/kojid
index 6e973fe..5940954 100755
--- a/builder/kojid
+++ b/builder/kojid
@@ -150,35 +150,17 @@ def log_output(path, args, outfile, uploadpath, cwd=None, 
logerror=0, append=0,
                     outfd.close()
                 return status[1]
 
-def safe_rmtree(path, unmount=False, strict=True):
+def safe_rmtree(path, strict=True, op='rmrf'):
     logger = logging.getLogger("koji.build")
-    #safe remove: with -xdev the find cmd will not cross filesystems
-    #             (though it will cross bind mounts from the same filesystem)
-    if not os.path.exists(path):
-        logger.debug("No such path: %s" % path)
-        return
-    if unmount:
-        umount_all(path)
-    #first rm -f non-directories
-    logger.debug('Scrubbing files in %s' % path)
-    rv = os.system("find '%s' -xdev \\! -type d -print0 |xargs -0 rm -f" % 
path)
-    msg = 'file removal failed (code %r) for %s' % (rv,path)
-    if rv != 0:
-        logger.warn(msg)
-        if strict:
-            raise koji.GenericError, msg
-        else:
-            return rv
-    #them rmdir directories
-    #with -depth, we start at the bottom and work up
-    logger.debug('Scrubbing directories in %s' % path)
-    rv = os.system("find '%s' -xdev -depth -type d -print0 |xargs -0 rmdir" % 
path)
-    msg = 'dir removal failed (code %r) for %s' % (rv,path)
-    if rv != 0:
-        logger.warn(msg)
-        if strict:
-            raise koji.GenericError, msg
-    return rv
+    rc     = os.spawnvp(os.P_WAIT, '/usr/sbin/koji-helper', 
['/usr/sbin/koji-helper', op, path])
+    if rc!=0:
+       msg = "directory removal failed (code %r) for %s" % (rc,path)
+       logger.warn(msg)
+       if strict:
+               raise koji.GenericError, msg
+       else:
+               return rc
+    return rc
 
 def umount_all(topdir):
     "Unmount every mount under topdir"
@@ -635,7 +617,7 @@ class TaskManager(object):
                 if age > 3600*24:
                     #dir untouched for a day
                     self.logger.info("Removing buildroot: %s" % desc)
-                    if topdir and safe_rmtree(topdir, unmount=True, 
strict=False) != 0:
+                    if topdir and safe_rmtree(topdir, strict=False) != 0:
                         continue
                     #also remove the config
                     try:
@@ -644,15 +626,7 @@ class TaskManager(object):
                         self.logger.warn("%s: can't remove config: %s" % 
(desc, e))
                 elif age > 120:
                     if rootdir:
-                        try:
-                            flist = os.listdir(rootdir)
-                        except OSError, e:
-                            self.logger.warn("%s: can't list rootdir: %s" % 
(desc, e))
-                            continue
-                        if flist:
-                            self.logger.info("%s: clearing rootdir" % desc)
-                        for fn in flist:
-                            safe_rmtree("%s/%s" % (rootdir,fn), unmount=True, 
strict=False)
+                        safe_rmtree(rootdir, strict=False, op='rmtree')
                 else:
                     self.logger.debug("Recent buildroot: %s: %i seconds" % 
(desc,age))
         self.logger.debug("Local buildroots: %d" % len(local_br))
@@ -1211,8 +1185,7 @@ class BaseTaskHandler(object):
     def removeWorkdir(self):
         if self.workdir is None:
             return
-        safe_rmtree(self.workdir, unmount=False, strict=True)
-        #os.spawnvp(os.P_WAIT, 'rm', ['rm', '-rf', self.workdir])
+        os.spawnvp(os.P_WAIT, 'rm', ['rm', '-rf', self.workdir])
 
     def wait(self, subtasks=None, all=False, failany=False):
         """Wait on subtasks
diff --git a/koji.spec b/koji.spec
index 13d3bf0..f14bb6e 100644
--- a/koji.spec
+++ b/koji.spec
@@ -16,7 +16,6 @@ Group: Applications/System
 URL: http://hosted.fedoraproject.org/projects/koji
 Source: %{name}-%{PACKAGE_VERSION}.tar.bz2
 BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
-BuildArch: noarch
 Requires: python-krbV >= 1.0.13
 Requires: rpm-python
 Requires: pyOpenSSL
@@ -89,6 +88,7 @@ koji-web is a web UI to the Koji system.
 %setup -q
 
 %build
+make CFLAGS="$CFLAGS" CC="%__cc" all
 
 %install
 rm -rf $RPM_BUILD_ROOT
@@ -125,6 +125,7 @@ rm -rf $RPM_BUILD_ROOT
 
 %files builder
 %defattr(-,root,root)
+%attr(4710,root,kojibuilder) %_sbindir/koji-helper
 %{_sbindir}/kojid
 %{_initrddir}/kojid
 %config(noreplace) %{_sysconfdir}/sysconfig/kojid
diff --git a/src/koji-helper.c b/src/koji-helper.c
new file mode 100644
index 0000000..a3d0921
--- /dev/null
+++ b/src/koji-helper.c
@@ -0,0 +1,260 @@
+/*     --*- c -*--
+ * Copyright (C) 2007 Enrico Scholz <[EMAIL PROTECTED]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define _GNU_SOURCE
+
+#ifndef MOCK_ROOT
+#  define MOCK_ROOT    "/var/lib/mock"
+#endif
+
+#include <sys/stat.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <stdbool.h>
+
+static int __attribute__((__nonnull__(1, 2)))
+safe_chdir(char const *path, struct stat const *exp_st) 
+{
+       struct stat             cur_st;
+       
+       if (strchr(path, '/')) {
+               fprintf(stderr, "safe_chdir(): invalid char in path '%s'\n", 
path);
+               return -1;
+       }
+
+       if (strcmp(path, "..")==0) {
+               fprintf(stderr, "safe_chdir(): parent dir referred\n");
+               return -1;
+       }
+
+       if (chdir(path) < 0) {
+               fprintf(stderr, "chdir(%s): %s\n", path, strerror(errno));
+               return -1;
+       }
+
+       if (stat(".", &cur_st) < 0) {
+               fprintf(stderr, "stat(%s): %s\n", path, strerror(errno));
+               return -2;
+       }
+
+       if (cur_st.st_dev != exp_st->st_dev ||
+           cur_st.st_ino != exp_st->st_ino) {
+               fprintf(stderr, "RACE: path '%s' changed before chdir()\n", 
path);
+               return -2;
+       }
+
+       return 0;
+}
+
+static int
+rmrf_cwd(struct stat *cwd_st)
+{
+       DIR             *cwd = opendir(".");
+       int             rc   = -1;
+
+       if (!cwd) {
+               perror("opendir()");
+               return -1;
+       }
+
+       for (;;) {
+               struct dirent   *ent = readdir(cwd);
+               struct stat     st;
+
+               if (!ent)
+                       break;
+
+               if (ent->d_name[0] == '.' &&
+                   (ent->d_name[1] == '\0'||
+                    (ent->d_name[1] == '.' && ent->d_name[2] == '\0')))
+                       continue;       /* skip '.' and '..' */
+
+               if (lstat(ent->d_name, &st) < 0) {
+                       fprintf(stderr, "rmrf_cwd: lstat(%s): %s\n",
+                               ent->d_name, strerror(errno));
+                       continue;
+               }
+
+               if (cwd_st && cwd_st->st_dev != st.st_dev)
+                       continue;       /* do not cross devices */
+               else if (S_ISDIR(st.st_mode)) {
+                       switch (safe_chdir(ent->d_name, &st)) {
+                       case -1: continue;
+                       case -2: break;
+                       default: rmrf_cwd(&st); break;
+                       }
+
+                       if (fchdir(dirfd(cwd)) < 0) {
+                               perror("rmrf_cwd: fchdir()");
+                               goto err;
+                       }
+
+                       if (rmdir(ent->d_name) < 0) {
+                               fprintf(stderr, "rmrf_cwd: rmdir(%s): %s\n",
+                                       ent->d_name, strerror(errno));
+                               continue;
+                       }
+               } else if (unlink(ent->d_name) < 0) {
+                       fprintf(stderr, "rmrf_cwd: unlink(%s): %s\n",
+                               ent->d_name, strerror(errno));
+                       continue;
+               }
+       }
+
+       rc = 0;
+err:
+       closedir(cwd);
+       return rc;
+}
+
+static int
+safe_chdir_subpath(char const *path_c, size_t path_len)
+{
+       char            path[path_len+1];
+       char            *ptr = path;
+       int             rc = 0;
+
+       if (path_len == 0)
+               return 0;
+
+       strncpy(path, path_c, path_len);
+       path[path_len] = '\0';
+
+       while (ptr) {
+               char            *new_ptr = strsep(&ptr, "/");
+               struct stat     st;
+
+               if (*new_ptr == '\0')
+                       continue;       /* skip empty path components
+                                        * (e.g. double /) */
+               
+               if (lstat(new_ptr, &st) < 0) {
+                       fprintf(stderr, "stat(%s): %s\n",
+                               new_ptr, strerror(errno));
+                       rc = -1;
+               } else if (!S_ISDIR(st.st_mode) || S_ISLNK(st.st_mode)) {
+                       fprintf(stderr, "safe_chdir_subpath(): invalid mode of 
'%s': %04x\n",
+                               new_ptr, st.st_mode);
+                       rc = -1;
+               } else
+                       rc = safe_chdir(new_ptr, &st);
+
+               if (rc < 0)
+                       break;
+       }
+
+       return rc;
+}
+
+/* Usage: rmrf <dir> */
+static int
+do_rmrf(int argc, char *argv[], bool remove_parent_dir)
+{
+       char const      *dir;
+       size_t          dir_len;
+       char const      *last_path;
+       int             parent_fd;
+       
+       if (argc != 2) {
+               fprintf(stderr, "wrong number of parameters for 'rmrf' 
operation\n");
+               return EXIT_FAILURE;
+       }
+
+       dir       = argv[1];
+
+       /* strip leading MOCK_ROOT; it's a little bit hacky but required to
+        * keep backward compatibility */
+       if (strncmp(dir, MOCK_ROOT, sizeof(MOCK_ROOT)-1) == 0)
+               dir += sizeof(MOCK_ROOT)-1;
+
+       while (*dir == '/')
+               ++dir;                  /* strip leading '/' */
+       
+       dir_len   = strlen(dir);
+       while (dir_len>0 && dir[dir_len-1] == '/')
+               --dir_len;              /* strip trailing '/' */
+
+       last_path = dir + dir_len;
+       while (last_path > dir && last_path[-1] != '/')
+               --last_path;
+
+       if (dir_len == 0) {
+               fprintf(stderr, "do_rmrf(): empty path\n");
+               return EXIT_FAILURE;
+       }
+       if (dir_len > 255) {
+               fprintf(stderr, "pathname too long\n");
+               return EXIT_FAILURE;
+       }
+
+       
+       /* real work begins here... */
+
+       if (chdir(MOCK_ROOT) < 0) {
+               perror("chdir(<MOCK_ROOT>)");
+               return EXIT_FAILURE;
+       }
+
+       if (last_path > dir &&          /* else, it would be a noop */
+           safe_chdir_subpath(dir, last_path - dir) < 0)
+               return EXIT_FAILURE;
+
+       parent_fd = open(".", O_RDONLY|O_DIRECTORY);
+       if (parent_fd < 0) {
+               perror("open(<MOCK_ROOT>)");
+               return EXIT_FAILURE;
+       }
+
+       if (safe_chdir_subpath(last_path, dir+dir_len - last_path + 1) < 0)
+               return EXIT_FAILURE;
+
+       /* we are now *in* the given path */
+       if (rmrf_cwd(NULL) < 0)
+               return EXIT_FAILURE;
+
+       if (fchdir(parent_fd) < 0) {
+               perror("fchdir(<parent>)");
+               return EXIT_FAILURE;
+       }
+
+       if (remove_parent_dir &&
+           rmdir(last_path) < 0)
+               return EXIT_FAILURE;
+
+       return EXIT_SUCCESS;
+}
+
+int main(int argc, char *argv[])
+{
+       if (argc < 2) {
+               fprintf(stderr, "not enough parameters\n");
+               return EXIT_FAILURE;
+       }
+
+       if (strcmp(argv[1], "rmrf") == 0)
+               return do_rmrf(argc-1, argv+1, true);
+       else if (strcmp(argv[1], "rmtree") == 0)
+               return do_rmrf(argc-1, argv+1, false);
+       else
+               fprintf(stderr, "unknown argument\n");
+
+       return EXIT_FAILURE;
+}

--
Fedora-buildsys-list mailing list
[email protected]
https://www.redhat.com/mailman/listinfo/fedora-buildsys-list

Reply via email to