summary refs log tree commit diff
path: root/src/libutil
diff options
context:
space:
mode:
Diffstat (limited to 'src/libutil')
-rw-r--r--src/libutil/Makefile.am4
-rw-r--r--src/libutil/immutable.cc67
-rw-r--r--src/libutil/immutable.hh19
-rw-r--r--src/libutil/util.cc3
4 files changed, 91 insertions, 2 deletions
diff --git a/src/libutil/Makefile.am b/src/libutil/Makefile.am
index 98f32633b8..a326060cfe 100644
--- a/src/libutil/Makefile.am
+++ b/src/libutil/Makefile.am
@@ -1,12 +1,12 @@
 pkglib_LTLIBRARIES = libutil.la
 
 libutil_la_SOURCES = util.cc hash.cc serialise.cc \
-  archive.cc xml-writer.cc
+  archive.cc xml-writer.cc immutable.cc
 
 libutil_la_LIBADD = ../boost/format/libformat.la ${aterm_lib} ${sqlite_lib}
 
 pkginclude_HEADERS = util.hh hash.hh serialise.hh \
-  archive.hh xml-writer.hh types.hh
+  archive.hh xml-writer.hh types.hh immutable.hh
 
 if !HAVE_OPENSSL
 libutil_la_SOURCES += \
diff --git a/src/libutil/immutable.cc b/src/libutil/immutable.cc
new file mode 100644
index 0000000000..f72f856254
--- /dev/null
+++ b/src/libutil/immutable.cc
@@ -0,0 +1,67 @@
+#include "config.h"
+
+#include "immutable.hh"
+#include "util.hh"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#if HAVE_LINUX_FS_H
+#include <linux/fs.h>
+#include <sys/ioctl.h>
+#include <errno.h>
+#endif
+
+namespace nix {
+
+
+void changeMutable(const Path & path, bool mut)
+{
+#if defined(FS_IOC_SETFLAGS) && defined(FS_IOC_GETFLAGS) && defined(FS_IMMUTABLE_FL)
+
+    /* Don't even try if we're not root.  One day we should support
+       the CAP_LINUX_IMMUTABLE capability. */
+    if (getuid() != 0) return;
+
+    /* The O_NOFOLLOW is important to prevent us from changing the
+       mutable bit on the target of a symlink (which would be a
+       security hole). */
+    AutoCloseFD fd = open(path.c_str(), O_RDONLY | O_NOFOLLOW);
+    if (fd == -1) {
+        if (errno == ELOOP) return; // it's a symlink
+        throw SysError(format("opening file `%1%'") % path);
+    }
+
+    unsigned int flags = 0, old;
+
+    /* Silently ignore errors getting/setting the immutable flag so
+       that we work correctly on filesystems that don't support it. */
+    if (ioctl(fd, FS_IOC_GETFLAGS, &flags)) return;
+
+    old = flags;
+    
+    if (mut) flags &= ~FS_IMMUTABLE_FL;
+    else flags |= FS_IMMUTABLE_FL;
+
+    if (old == flags) return;
+
+    if (ioctl(fd, FS_IOC_SETFLAGS, &flags)) return;
+    
+#endif
+}
+
+
+void makeImmutable(const Path & path)
+{
+    changeMutable(path, false);
+}
+
+
+void makeMutable(const Path & path)
+{
+    changeMutable(path, true);
+}
+
+
+}
diff --git a/src/libutil/immutable.hh b/src/libutil/immutable.hh
new file mode 100644
index 0000000000..5a42a46107
--- /dev/null
+++ b/src/libutil/immutable.hh
@@ -0,0 +1,19 @@
+#ifndef __IMMUTABLE_H
+#define __IMMUTABLE_H
+
+#include <types.hh>
+
+namespace nix {
+
+/* Make the given path immutable, i.e., prevent it from being modified
+   in any way, even by root.  This is a no-op on platforms that do not
+   support this, or if the calling user is not privileged.  On Linux,
+   this is implemented by doing the equivalent of ‘chattr +i path’. */
+void makeImmutable(const Path & path);
+
+/* Make the given path mutable. */
+void makeMutable(const Path & path);
+
+}
+
+#endif /* !__IMMUTABLE_H */
diff --git a/src/libutil/util.cc b/src/libutil/util.cc
index 42e5519b48..31322f9c48 100644
--- a/src/libutil/util.cc
+++ b/src/libutil/util.cc
@@ -12,6 +12,7 @@
 #include <limits.h>
 
 #include "util.hh"
+#include "immutable.hh"
 
 
 extern char * * environ;
@@ -304,6 +305,8 @@ static void _deletePath(const Path & path, unsigned long long & bytesFreed,
 
     struct stat st = lstat(path);
 
+    if (S_ISDIR(st.st_mode) || S_ISREG(st.st_mode)) makeMutable(path);
+
     if (!S_ISDIR(st.st_mode) && st.st_nlink == 1) {
         bytesFreed += st.st_size;
         blocksFreed += st.st_blocks;