about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--.gitignore4
-rw-r--r--configure.ac17
-rw-r--r--corepkgs/Makefile.am3
-rw-r--r--corepkgs/imported-drv-to-derivation.nix21
-rw-r--r--doc/manual/conf-file.xml15
-rw-r--r--doc/manual/release-notes.xml2
-rw-r--r--release.nix4
-rwxr-xr-xscripts/nix-build.in4
-rw-r--r--scripts/nix-profile.sh.in6
-rw-r--r--src/libexpr/attr-path.hh9
-rw-r--r--src/libexpr/common-opts.hh7
-rw-r--r--src/libexpr/eval-inline.hh7
-rw-r--r--src/libexpr/eval.hh6
-rw-r--r--src/libexpr/get-drvs.hh6
-rw-r--r--src/libexpr/names.hh11
-rw-r--r--src/libexpr/nixexpr.hh6
-rw-r--r--src/libexpr/primops.cc26
-rw-r--r--src/libexpr/symbol-table.hh5
-rw-r--r--src/libexpr/value-to-xml.hh5
-rw-r--r--src/libexpr/value.hh5
-rw-r--r--src/libmain/shared.hh6
-rw-r--r--src/libstore/build.cc7
-rw-r--r--src/libstore/derivations.hh6
-rw-r--r--src/libstore/gc.cc37
-rw-r--r--src/libstore/globals.hh6
-rw-r--r--src/libstore/local-store.cc32
-rw-r--r--src/libstore/local-store.hh17
-rw-r--r--src/libstore/misc.hh6
-rw-r--r--src/libstore/optimise-store.cc218
-rw-r--r--src/libstore/pathlocks.hh6
-rw-r--r--src/libstore/references.hh5
-rw-r--r--src/libstore/remote-store.hh6
-rw-r--r--src/libstore/store-api.hh6
-rw-r--r--src/libstore/worker-protocol.hh7
-rw-r--r--src/libutil/archive.hh6
-rw-r--r--src/libutil/hash.hh6
-rw-r--r--src/libutil/immutable.hh5
-rw-r--r--src/libutil/serialise.hh6
-rw-r--r--src/libutil/types.hh6
-rw-r--r--src/libutil/util.cc4
-rw-r--r--src/libutil/util.hh8
-rw-r--r--src/libutil/xml-writer.hh6
-rw-r--r--src/nix-env/profiles.hh6
-rw-r--r--src/nix-env/user-env.hh9
-rw-r--r--src/nix-store/dotgraph.hh5
-rw-r--r--src/nix-store/nix-store.cc10
-rw-r--r--src/nix-store/xmlgraph.hh5
-rw-r--r--tests/Makefile.am2
-rw-r--r--tests/optimise-store.sh26
-rw-r--r--version2
50 files changed, 341 insertions, 305 deletions
diff --git a/.gitignore b/.gitignore
index bcc7a79f8a30..9160fe43d1ec 100644
--- a/.gitignore
+++ b/.gitignore
@@ -79,13 +79,9 @@ Makefile.in
 /src/libexpr/parser-tab.cc
 /src/libexpr/parser-tab.hh
 /src/libexpr/parser-tab.output
-/src/libexpr/nixexpr-ast.hh
-/src/libexpr/nixexpr-ast.cc
 /src/libexpr/nix.tbl
 
 # /src/libstore/
-/src/libstore/derivations-ast.cc
-/src/libstore/derivations-ast.hh
 /src/libstore/schema.sql.hh
 
 # /src/nix-env/
diff --git a/configure.ac b/configure.ac
index 53d653a0eed6..21a87194f477 100644
--- a/configure.ac
+++ b/configure.ac
@@ -115,6 +115,23 @@ AC_CHECK_HEADERS([sys/mount.h], [], [],
 ])
 
 
+# Check for lutimes, optionally used for changing the mtime of
+# symlinks.
+AC_CHECK_FUNCS([lutimes])
+
+
+# Check whether the store optimiser can optimise symlinks.
+AC_MSG_CHECKING([whether it is possible to create a link to a symlink])
+ln -s bla tmp_link
+if ln tmp_link tmp_link2 2> /dev/null; then
+    AC_MSG_RESULT(yes)
+    AC_DEFINE(CAN_LINK_SYMLINK, 1, [Whether link() works on symlinks.])
+else
+    AC_MSG_RESULT(no)
+fi
+rm -f tmp_link tmp_link2
+
+
 # Check for <locale>.
 AC_LANG_PUSH(C++)
 AC_CHECK_HEADERS([locale])
diff --git a/corepkgs/Makefile.am b/corepkgs/Makefile.am
index 729d15e7b191..4b0b8860be68 100644
--- a/corepkgs/Makefile.am
+++ b/corepkgs/Makefile.am
@@ -1,6 +1,7 @@
 all-local: config.nix
 
-files = nar.nix buildenv.nix buildenv.pl unpack-channel.nix unpack-channel.sh derivation.nix fetchurl.nix
+files = nar.nix buildenv.nix buildenv.pl unpack-channel.nix unpack-channel.sh derivation.nix fetchurl.nix \
+	imported-drv-to-derivation.nix
 
 install-exec-local:
 	$(INSTALL) -d $(DESTDIR)$(datadir)/nix/corepkgs
diff --git a/corepkgs/imported-drv-to-derivation.nix b/corepkgs/imported-drv-to-derivation.nix
new file mode 100644
index 000000000000..bdb60169860a
--- /dev/null
+++ b/corepkgs/imported-drv-to-derivation.nix
@@ -0,0 +1,21 @@
+attrs @ { drvPath, outputs, ... }:
+
+let
+
+  commonAttrs = (builtins.listToAttrs outputsList) //
+    { all = map (x: x.value) outputsList;
+      inherit drvPath;
+      type = "derivation";
+    };
+
+  outputToAttrListElement = outputName:
+    { name = outputName;
+      value = commonAttrs // {
+        outPath = builtins.getAttr outputName attrs;
+        inherit outputName;
+      };
+    };
+    
+  outputsList = map outputToAttrListElement outputs;
+    
+in (builtins.head outputsList).value
diff --git a/doc/manual/conf-file.xml b/doc/manual/conf-file.xml
index 021b42ec63f3..cccee8d46202 100644
--- a/doc/manual/conf-file.xml
+++ b/doc/manual/conf-file.xml
@@ -357,7 +357,20 @@ build-use-chroot = /dev /proc /bin</programlisting>
     <literal>true</literal>.</para></listitem>
 
   </varlistentry>
-    
+
+  
+  <varlistentry><term><literal>auto-optimise-store</literal></term>
+
+    <listitem><para>If set to <literal>true</literal> (the default),
+    Nix automatically detects files in the store that have identical
+    contents, and replaces them with hard links to a single copy.
+    This saves disk space.  If set to <literal>false</literal>, you
+    can still run <command>nix-store --optimise</command> to get rid
+    of duplicate files.</para></listitem>
+
+  </varlistentry>
+
+
 </variablelist>
 
 </para>
diff --git a/doc/manual/release-notes.xml b/doc/manual/release-notes.xml
index 4dfcb6b477ca..7c78122f0267 100644
--- a/doc/manual/release-notes.xml
+++ b/doc/manual/release-notes.xml
@@ -8,7 +8,7 @@
 
 <!--==================================================================-->
 
-<section xml:id="ssec-relnotes-1.1"><title>Release 1.1 (TBA)</title>
+<section xml:id="ssec-relnotes-1.1"><title>Release 1.1 (July 18, 2012)</title>
 
 <para>This release has the following improvements:</para>
 
diff --git a/release.nix b/release.nix
index f2d48691d4fe..0b382fff31b2 100644
--- a/release.nix
+++ b/release.nix
@@ -156,10 +156,6 @@ let
     rpm_fedora13x86_64 = makeRPM_x86_64 (diskImageFunsFun: diskImageFunsFun.fedora13x86_64) 50;
     rpm_fedora16i386 = makeRPM_i686 (diskImageFuns: diskImageFuns.fedora16i386) 50;
     rpm_fedora16x86_64 = makeRPM_x86_64 (diskImageFunsFun: diskImageFunsFun.fedora16x86_64) 50;
-    
-    rpm_opensuse103i386 = makeRPM_i686 (diskImageFuns: diskImageFuns.opensuse103i386) 40;
-    rpm_opensuse110i386 = makeRPM_i686 (diskImageFuns: diskImageFuns.opensuse110i386) 50;
-    rpm_opensuse110x86_64 = makeRPM_x86_64 (diskImageFuns: diskImageFuns.opensuse110x86_64) 50;
 
     
     deb_debian60i386 = makeDeb_i686 (diskImageFuns: diskImageFuns.debian60i386) 50;
diff --git a/scripts/nix-build.in b/scripts/nix-build.in
index 13404f1dcb53..aa3f4661aeee 100755
--- a/scripts/nix-build.in
+++ b/scripts/nix-build.in
@@ -123,6 +123,10 @@ EOF
     elsif ($arg eq "--show-trace") {
         push @instArgs, $arg;
     }
+
+    elsif ($arg eq "-") {
+        @exprs = ("-");
+    }
     
     elsif ($arg eq "--verbose" or substr($arg, 0, 2) eq "-v") {
         push @buildArgs, $arg;
diff --git a/scripts/nix-profile.sh.in b/scripts/nix-profile.sh.in
index d343385cc1e5..bc3dc719eb6f 100644
--- a/scripts/nix-profile.sh.in
+++ b/scripts/nix-profile.sh.in
@@ -19,9 +19,9 @@ fi
 
 export PATH="$HOME/.nix-profile/bin:$PATH"
 
-# Subscribe the root user to the NixOS channel by default.
+# Subscribe the root user to the Nixpkgs channel by default.
 if [ "$USER" = root -a ! -e $HOME/.nix-channels ]; then
-    echo "http://nixos.org/releases/nixos/channels/nixos-unstable nixos" > $HOME/.nix-channels
+    echo "http://nixos.org/releases/nixos/channels/nixpkgs-unstable nixpkgs" > $HOME/.nix-channels
 fi
 
 # Create the per-user garbage collector roots directory.
@@ -43,7 +43,7 @@ fi
 
 # Set up secure multi-user builds: non-root users build through the
 # Nix daemon.
-if test "$USER" != root; then
+if [ "$USER" != root -a -e @localstatedir@/nix/daemon-socket/socket ]; then
     export NIX_REMOTE=daemon
 else
     unset NIX_REMOTE
diff --git a/src/libexpr/attr-path.hh b/src/libexpr/attr-path.hh
index b106da5ef817..d3aad746116a 100644
--- a/src/libexpr/attr-path.hh
+++ b/src/libexpr/attr-path.hh
@@ -1,20 +1,13 @@
-#ifndef __ATTR_PATH_H
-#define __ATTR_PATH_H
+#pragma once
 
 #include "eval.hh"
 
 #include <string>
 #include <map>
 
-
 namespace nix {
 
-    
 void findAlongAttrPath(EvalState & state, const string & attrPath,
     Bindings & autoArgs, Expr * e, Value & v);
 
-    
 }
-
-
-#endif /* !__ATTR_PATH_H */
diff --git a/src/libexpr/common-opts.hh b/src/libexpr/common-opts.hh
index c28641e9015d..e2e3fe77171c 100644
--- a/src/libexpr/common-opts.hh
+++ b/src/libexpr/common-opts.hh
@@ -1,9 +1,7 @@
-#ifndef __COMMON_OPTS_H
-#define __COMMON_OPTS_H
+#pragma once
 
 #include "eval.hh"
 
-
 namespace nix {
 
 /* Some common option parsing between nix-env and nix-instantiate. */
@@ -17,6 +15,3 @@ bool parseSearchPathArg(const string & arg, Strings::iterator & i,
 Path lookupFileArg(EvalState & state, string s);
 
 }
-
-
-#endif /* !__COMMON_OPTS_H */
diff --git a/src/libexpr/eval-inline.hh b/src/libexpr/eval-inline.hh
index 6026a7d11015..57a9e4c63c21 100644
--- a/src/libexpr/eval-inline.hh
+++ b/src/libexpr/eval-inline.hh
@@ -1,5 +1,4 @@
-#ifndef __EVAL_INLINE_H
-#define __EVAL_INLINE_H
+#pragma once
 
 #include "eval.hh"
 
@@ -8,7 +7,6 @@
 
 namespace nix {
 
-
 LocalNoInlineNoReturn(void throwEvalError(const char * s))
 {
     throw EvalError(s);
@@ -55,7 +53,4 @@ inline void EvalState::forceList(Value & v)
         throwTypeError("value is %1% while a list was expected", showType(v));
 }
 
-
 }
-
-#endif /* !__EVAL_INLINE_H */
diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh
index bab9303b08eb..5103ae8cefe9 100644
--- a/src/libexpr/eval.hh
+++ b/src/libexpr/eval.hh
@@ -1,5 +1,4 @@
-#ifndef __EVAL_H
-#define __EVAL_H
+#pragma once
 
 #include "value.hh"
 #include "nixexpr.hh"
@@ -256,6 +255,3 @@ string showType(const Value & v);
 
 
 }
-
-
-#endif /* !__EVAL_H */
diff --git a/src/libexpr/get-drvs.hh b/src/libexpr/get-drvs.hh
index 1e5d0a817cca..25d8baa559b2 100644
--- a/src/libexpr/get-drvs.hh
+++ b/src/libexpr/get-drvs.hh
@@ -1,5 +1,4 @@
-#ifndef __GET_DRVS_H
-#define __GET_DRVS_H
+#pragma once
 
 #include "eval.hh"
 
@@ -83,6 +82,3 @@ void getDerivations(EvalState & state, Value & v, const string & pathPrefix,
 
  
 }
-
-
-#endif /* !__GET_DRVS_H */
diff --git a/src/libexpr/names.hh b/src/libexpr/names.hh
index e189302d6d94..ebe113e82ac1 100644
--- a/src/libexpr/names.hh
+++ b/src/libexpr/names.hh
@@ -1,12 +1,9 @@
-#ifndef __NAMES_H
-#define __NAMES_H
+#pragma once
 
 #include "types.hh"
 
-
 namespace nix {
 
-
 struct DrvName
 {
     string fullName;
@@ -19,15 +16,9 @@ struct DrvName
     bool matches(DrvName & n);
 };
 
-
 typedef list<DrvName> DrvNames;
 
-
 int compareVersions(const string & v1, const string & v2);
 DrvNames drvNamesFromArgs(const Strings & opArgs);
 
-
 }
-
-
-#endif /* !__NAMES_H */
diff --git a/src/libexpr/nixexpr.hh b/src/libexpr/nixexpr.hh
index 6eb771a726b4..4c1a0bb2d5fb 100644
--- a/src/libexpr/nixexpr.hh
+++ b/src/libexpr/nixexpr.hh
@@ -1,5 +1,4 @@
-#ifndef __NIXEXPR_H
-#define __NIXEXPR_H
+#pragma once
 
 #include "value.hh"
 #include "symbol-table.hh"
@@ -290,6 +289,3 @@ struct StaticEnv
 
 
 }
-
-
-#endif /* !__NIXEXPR_H */
diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc
index f20c2f2879ab..5c011c43e31c 100644
--- a/src/libexpr/primops.cc
+++ b/src/libexpr/primops.cc
@@ -65,7 +65,31 @@ static void prim_import(EvalState & state, Value * * args, Value & v)
             }
     }
 
-    state.evalFile(path, v);
+    if (isStorePath(path) && store->isValidPath(path) && isDerivation(path)) {
+        Derivation drv = parseDerivation(readFile(path));
+        Value & w = *state.allocValue();
+        state.mkAttrs(w, 1 + drv.outputs.size());
+        mkString(*state.allocAttr(w, state.sDrvPath), path, singleton<PathSet>("=" + path));
+        state.mkList(*state.allocAttr(w, state.symbols.create("outputs")), drv.outputs.size());
+        unsigned int outputs_index = 0;
+
+        Value * outputsVal = w.attrs->find(state.symbols.create("outputs"))->value;
+        foreach (DerivationOutputs::iterator, i, drv.outputs) {
+            mkString(*state.allocAttr(w, state.symbols.create(i->first)),
+                i->second.path, singleton<PathSet>("!" + i->first + "!" + path));
+            mkString(*(outputsVal->list.elems[outputs_index++] = state.allocValue()),
+                i->first);
+        }
+        w.attrs->sort();
+        Value fun;
+        state.mkThunk_(fun,
+            state.parseExprFromFile(state.findFile("nix/imported-drv-to-derivation.nix")));
+        state.forceFunction(fun);
+        mkApp(v, fun, w);
+        state.forceAttrs(v);
+    } else {
+        state.evalFile(path, v);
+    }
 }
 
 
diff --git a/src/libexpr/symbol-table.hh b/src/libexpr/symbol-table.hh
index 976117a20a46..143fc495b04a 100644
--- a/src/libexpr/symbol-table.hh
+++ b/src/libexpr/symbol-table.hh
@@ -1,5 +1,4 @@
-#ifndef __SYMBOL_TABLE_H
-#define __SYMBOL_TABLE_H
+#pragma once
 
 #include "config.h"
 
@@ -88,5 +87,3 @@ public:
 };
 
 }
-
-#endif /* !__SYMBOL_TABLE_H */
diff --git a/src/libexpr/value-to-xml.hh b/src/libexpr/value-to-xml.hh
index 3ebc989ffa26..97657327edba 100644
--- a/src/libexpr/value-to-xml.hh
+++ b/src/libexpr/value-to-xml.hh
@@ -1,5 +1,4 @@
-#ifndef __VALUE_TO_XML_H
-#define __VALUE_TO_XML_H
+#pragma once
 
 #include "nixexpr.hh"
 #include "eval.hh"
@@ -13,5 +12,3 @@ void printValueAsXML(EvalState & state, bool strict, bool location,
     Value & v, std::ostream & out, PathSet & context);
     
 }
-
-#endif /* !__VALUE_TO_XML_H */
diff --git a/src/libexpr/value.hh b/src/libexpr/value.hh
index 41512d40b1e0..c9ec236c470d 100644
--- a/src/libexpr/value.hh
+++ b/src/libexpr/value.hh
@@ -1,5 +1,4 @@
-#ifndef __VALUE_H
-#define __VALUE_H
+#pragma once
 
 #include "symbol-table.hh"
 
@@ -151,5 +150,3 @@ void mkPath(Value & v, const char * s);
 
 
 }
-
-#endif /* !__VALUE_H */
diff --git a/src/libmain/shared.hh b/src/libmain/shared.hh
index d198df70d150..7849e10e3641 100644
--- a/src/libmain/shared.hh
+++ b/src/libmain/shared.hh
@@ -1,5 +1,4 @@
-#ifndef __SHARED_H
-#define __SHARED_H
+#pragma once
 
 #include "util.hh"
 
@@ -54,6 +53,3 @@ extern int exitCode;
 extern char * * argvSaved;
 
 }
-
-
-#endif /* !__SHARED_H */
diff --git a/src/libstore/build.cc b/src/libstore/build.cc
index 4bef6f02dc00..c57a63db69dd 100644
--- a/src/libstore/build.cc
+++ b/src/libstore/build.cc
@@ -1533,7 +1533,7 @@ void DerivationGoal::startBuilder()
 
     /* Create a temporary directory where the build will take
        place. */
-    tmpDir = createTempDir("", "nix-build-" + baseNameOf(drvPath), false, false);
+    tmpDir = createTempDir("", "nix-build-" + baseNameOf(drvPath), false, false, 0700);
 
     /* For convenience, set an environment pointing to the top build
        directory. */
@@ -2099,6 +2099,8 @@ void DerivationGoal::computeClosure()
                 if (allowed.find(*i) == allowed.end())
                     throw BuildError(format("output is not allowed to refer to path `%1%'") % *i);
         }
+
+        worker.store.optimisePath(path); // FIXME: combine with scanForReferences()
     }
 
     /* Register each output path as valid, and register the sets of
@@ -2182,6 +2184,7 @@ void DerivationGoal::deleteTmpDir(bool force)
                 % drvPath % tmpDir);
             if (buildUser.enabled() && !amPrivileged())
                 getOwnership(tmpDir);
+            chmod(tmpDir.c_str(), 0755);
         }
         else
             deletePathWrapped(tmpDir);
@@ -2562,6 +2565,8 @@ void SubstitutionGoal::finished()
 
     HashResult hash = hashPath(htSHA256, storePath);
     
+    worker.store.optimisePath(storePath); // FIXME: combine with hashPath()
+    
     ValidPathInfo info2;
     info2.path = storePath;
     info2.hash = hash.first;
diff --git a/src/libstore/derivations.hh b/src/libstore/derivations.hh
index 27e471d885cc..8f22b4afa6a1 100644
--- a/src/libstore/derivations.hh
+++ b/src/libstore/derivations.hh
@@ -1,5 +1,4 @@
-#ifndef __DERIVATIONS_H
-#define __DERIVATIONS_H
+#pragma once
 
 #include <map>
 
@@ -81,6 +80,3 @@ typedef std::map<Path, Hash> DrvHashes;
 extern DrvHashes drvHashes;
 
 }
-
-
-#endif /* !__DERIVATIONS_H */
diff --git a/src/libstore/gc.cc b/src/libstore/gc.cc
index f6ed7dd2264e..874efe4d32d9 100644
--- a/src/libstore/gc.cc
+++ b/src/libstore/gc.cc
@@ -436,6 +436,8 @@ bool LocalStore::tryToDelete(GCState & state, const Path & path)
 {
     checkInterrupt();
 
+    if (path == linksDir) return true;
+
     struct stat st;
     if (lstat(path.c_str(), &st)) {
         if (errno == ENOENT) return true;
@@ -569,6 +571,37 @@ bool LocalStore::tryToDelete(GCState & state, const Path & path)
 }
 
 
+/* Unlink all files in /nix/store/.links that have a link count of 1,
+   which indicates that there are no other links and so they can be
+   safely deleted.  FIXME: race condition with optimisePath(): we
+   might see a link count of 1 just before optimisePath() increases
+   the link count. */
+void LocalStore::removeUnusedLinks()
+{
+    AutoCloseDir dir = opendir(linksDir.c_str());
+    if (!dir) throw SysError(format("opening directory `%1%'") % linksDir);
+
+    struct dirent * dirent;
+    while (errno = 0, dirent = readdir(dir)) {
+        checkInterrupt();
+        string name = dirent->d_name;
+        if (name == "." || name == "..") continue;
+        Path path = linksDir + "/" + name;
+
+        struct stat st;
+        if (lstat(path.c_str(), &st) == -1)
+            throw SysError(format("statting `%1%'") % path);
+
+        if (st.st_nlink != 1) continue;
+
+        printMsg(lvlTalkative, format("deleting unused link `%1%'") % path);
+
+        if (unlink(path.c_str()) == -1)
+            throw SysError(format("deleting `%1%'") % path);
+    }
+}
+
+
 void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
 {
     GCState state(results);
@@ -682,6 +715,10 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
        released. */
     foreach (PathSet::iterator, i, state.invalidated)
         deleteGarbage(state, *i);
+
+    /* Clean up the links directory. */
+    printMsg(lvlError, format("deleting unused links..."));
+    removeUnusedLinks();
 }
 
 
diff --git a/src/libstore/globals.hh b/src/libstore/globals.hh
index 12a9b9ca15c0..1c0877a5e1e9 100644
--- a/src/libstore/globals.hh
+++ b/src/libstore/globals.hh
@@ -1,5 +1,4 @@
-#ifndef __GLOBALS_H
-#define __GLOBALS_H
+#pragma once
 
 #include "types.hh"
 
@@ -118,6 +117,3 @@ void setDefaultsFromEnvironment();
 
 
 }
-
-
-#endif /* !__GLOBALS_H */
diff --git a/src/libstore/local-store.cc b/src/libstore/local-store.cc
index aa21774df4a7..ebfcc946716a 100644
--- a/src/libstore/local-store.cc
+++ b/src/libstore/local-store.cc
@@ -12,6 +12,7 @@
 
 #include <sys/types.h>
 #include <sys/stat.h>
+#include <sys/time.h>
 #include <unistd.h>
 #include <utime.h>
 #include <fcntl.h>
@@ -208,6 +209,7 @@ LocalStore::LocalStore(bool reserveSpace)
 
     /* Create missing state directories if they don't already exist. */
     createDirs(nixStore);
+    createDirs(linksDir = nixStore + "/.links");
     Path profilesDir = nixStateDir + "/profiles";
     createDirs(nixStateDir + "/profiles");
     createDirs(nixStateDir + "/temproots");
@@ -444,7 +446,7 @@ void canonicalisePathMetaData(const Path & path, bool recurse)
             throw SysError(format("changing owner of `%1%' to %2%")
                 % path % geteuid());
     }
-    
+
     if (!S_ISLNK(st.st_mode)) {
 
         /* Mask out all type related bits. */
@@ -458,14 +460,20 @@ void canonicalisePathMetaData(const Path & path, bool recurse)
                 throw SysError(format("changing mode of `%1%' to %2$o") % path % mode);
         }
 
-        if (st.st_mtime != mtimeStore) {
-            struct utimbuf utimbuf;
-            utimbuf.actime = st.st_atime;
-            utimbuf.modtime = mtimeStore;
-            if (utime(path.c_str(), &utimbuf) == -1) 
-                throw SysError(format("changing modification time of `%1%'") % path);
-        }
-
+    }
+    
+    if (st.st_mtime != mtimeStore) {
+        struct timeval times[2];
+        times[0].tv_sec = st.st_atime;
+        times[0].tv_usec = 0;
+        times[1].tv_sec = mtimeStore;
+        times[1].tv_usec = 0;
+#if HAVE_LUTIMES
+        if (lutimes(path.c_str(), times) == -1)
+#else
+        if (!S_ISLNK(st.st_mode) && utimes(path.c_str(), times) == -1)
+#endif                
+            throw SysError(format("changing modification time of `%1%'") % path);
     }
 
     if (recurse && S_ISDIR(st.st_mode)) {
@@ -1134,6 +1142,8 @@ Path LocalStore::addToStoreFromDump(const string & dump, const string & name,
                 hash.second = dump.size();
             } else
                 hash = hashPath(htSHA256, dstPath);
+
+            optimisePath(dstPath); // FIXME: combine with hashPath()
             
             ValidPathInfo info;
             info.path = dstPath;
@@ -1188,6 +1198,8 @@ Path LocalStore::addTextToStore(const string & name, const string & s,
             canonicalisePathMetaData(dstPath);
 
             HashResult hash = hashPath(htSHA256, dstPath);
+
+            optimisePath(dstPath);
             
             ValidPathInfo info;
             info.path = dstPath;
@@ -1423,6 +1435,8 @@ Path LocalStore::importPath(bool requireSignature, Source & source)
             /* !!! if we were clever, we could prevent the hashPath()
                here. */
             HashResult hash = hashPath(htSHA256, dstPath);
+
+            optimisePath(dstPath); // FIXME: combine with hashPath()
             
             ValidPathInfo info;
             info.path = dstPath;
diff --git a/src/libstore/local-store.hh b/src/libstore/local-store.hh
index 07d8198ecaa5..15dff1d02052 100644
--- a/src/libstore/local-store.hh
+++ b/src/libstore/local-store.hh
@@ -1,5 +1,4 @@
-#ifndef __LOCAL_STORE_H
-#define __LOCAL_STORE_H
+#pragma once
 
 #include <string>
 
@@ -86,6 +85,8 @@ private:
 
     typedef std::map<Path, RunningSubstituter> RunningSubstituters;
     RunningSubstituters runningSubstituters;
+
+    Path linksDir;
     
 public:
 
@@ -168,8 +169,11 @@ public:
 
     /* Optimise the disk space usage of the Nix store by hard-linking
        files with the same contents. */
-    void optimiseStore(bool dryRun, OptimiseStats & stats);
+    void optimiseStore(OptimiseStats & stats);
 
+    /* Optimise a single store path. */
+    void optimisePath(const Path & path);
+    
     /* Check the integrity of the Nix store. */
     void verifyStore(bool checkContents);
 
@@ -260,6 +264,8 @@ private:
         
     int openGCLock(LockType lockType);
     
+    void removeUnusedLinks();
+
     void startSubstituter(const Path & substituter,
         RunningSubstituter & runningSubstituter);
 
@@ -268,6 +274,8 @@ private:
     Path importPath(bool requireSignature, Source & source);
     
     void checkDerivationOutputs(const Path & drvPath, const Derivation & drv);
+
+    void optimisePath_(OptimiseStats & stats, const Path & path);
 };
 
 
@@ -302,6 +310,3 @@ void deletePathWrapped(const Path & path,
 void deletePathWrapped(const Path & path);
  
 }
-
-
-#endif /* !__LOCAL_STORE_H */
diff --git a/src/libstore/misc.hh b/src/libstore/misc.hh
index 850c12af4ebc..fe0bbdd799d7 100644
--- a/src/libstore/misc.hh
+++ b/src/libstore/misc.hh
@@ -1,5 +1,4 @@
-#ifndef __MISC_H
-#define __MISC_H
+#pragma once
 
 #include "derivations.hh"
 
@@ -35,6 +34,3 @@ void queryMissing(StoreAPI & store, const PathSet & targets,
 
 
 }
-
-
-#endif /* !__MISC_H */
diff --git a/src/libstore/optimise-store.cc b/src/libstore/optimise-store.cc
index a486e66ef59e..84a72604bba2 100644
--- a/src/libstore/optimise-store.cc
+++ b/src/libstore/optimise-store.cc
@@ -1,6 +1,7 @@
 #include "util.hh"
 #include "local-store.hh"
 #include "immutable.hh"
+#include "globals.hh"
 
 #include <sys/types.h>
 #include <sys/stat.h>
@@ -12,9 +13,6 @@
 namespace nix {
 
 
-typedef std::map<Hash, std::pair<Path, ino_t> > HashToPath;
-
-
 static void makeWritable(const Path & path)
 {
     struct stat st;
@@ -51,140 +49,152 @@ struct MakeImmutable
 };
 
 
-static void hashAndLink(bool dryRun, HashToPath & hashToPath,
-    OptimiseStats & stats, const Path & path)
+void LocalStore::optimisePath_(OptimiseStats & stats, const Path & path)
 {
     struct stat st;
     if (lstat(path.c_str(), &st))
 	throw SysError(format("getting attributes of path `%1%'") % path);
 
+    if (S_ISDIR(st.st_mode)) {
+        Strings names = readDirectory(path);
+	foreach (Strings::iterator, i, names)
+	    optimisePath_(stats, path + "/" + *i);
+        return;
+    }
+    
+    /* We can hard link regular files and maybe symlinks. */
+    if (!S_ISREG(st.st_mode)
+#if CAN_LINK_SYMLINK
+        x
+        && !S_ISLNK(st.st_mode)
+#endif
+        ) return;
+        
     /* Sometimes SNAFUs can cause files in the Nix store to be
        modified, in particular when running programs as root under
        NixOS (example: $fontconfig/var/cache being modified).  Skip
-       those files. */
+       those files.  FIXME: check the modification time. */
     if (S_ISREG(st.st_mode) && (st.st_mode & S_IWUSR)) {
         printMsg(lvlError, format("skipping suspicious writable file `%1%'") % path);
         return;
     }
 
-    /* We can hard link regular files and symlinks. */
-    if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) {
+    /* Hash the file.  Note that hashPath() returns the hash over the
+       NAR serialisation, which includes the execute bit on the file.
+       Thus, executable and non-executable files with the same
+       contents *won't* be linked (which is good because otherwise the
+       permissions would be screwed up).
 
-        /* Hash the file.  Note that hashPath() returns the hash over
-           the NAR serialisation, which includes the execute bit on
-           the file.  Thus, executable and non-executable files with
-           the same contents *won't* be linked (which is good because
-           otherwise the permissions would be screwed up).
+       Also note that if `path' is a symlink, then we're hashing the
+       contents of the symlink (i.e. the result of readlink()), not
+       the contents of the target (which may not even exist). */
+    Hash hash = hashPath(htSHA256, path).first;
+    stats.totalFiles++;
+    printMsg(lvlDebug, format("`%1%' has hash `%2%'") % path % printHash(hash));
 
-           Also note that if `path' is a symlink, then we're hashing
-           the contents of the symlink (i.e. the result of
-           readlink()), not the contents of the target (which may not
-           even exist). */
-        Hash hash = hashPath(htSHA256, path).first;
-        stats.totalFiles++;
-        printMsg(lvlDebug, format("`%1%' has hash `%2%'") % path % printHash(hash));
+    /* Check if this is a known hash. */
+    Path linkPath = linksDir + "/" + printHash32(hash);
 
-        std::pair<Path, ino_t> prevPath = hashToPath[hash];
-        
-        if (prevPath.first == "") {
-            hashToPath[hash] = std::pair<Path, ino_t>(path, st.st_ino);
-            return;
-        }
-            
-        /* Yes!  We've seen a file with the same contents.  Replace
-           the current file with a hard link to that file. */
-        stats.sameContents++;
-        if (prevPath.second == st.st_ino) {
-            printMsg(lvlDebug, format("`%1%' is already linked to `%2%'") % path % prevPath.first);
-            return;
-        }
+    if (!pathExists(linkPath)) {
+        /* Nope, create a hard link in the links directory. */
+        makeMutable(path);
+        MakeImmutable mk1(path);
+
+        if (link(path.c_str(), linkPath.c_str()) == -1)
+            throw SysError(format("cannot link `%1%' to `%2%'") % linkPath % path);
+
+        return;
+    }
+
+    /* Yes!  We've seen a file with the same contents.  Replace the
+       current file with a hard link to that file. */
+    struct stat stLink;
+    if (lstat(linkPath.c_str(), &stLink))
+	throw SysError(format("getting attributes of path `%1%'") % linkPath);
+    
+    stats.sameContents++;
+    if (st.st_ino == stLink.st_ino) {
+        printMsg(lvlDebug, format("`%1%' is already linked to `%2%'") % path % linkPath);
+        return;
+    }
         
-        if (!dryRun) {
-            
-            printMsg(lvlTalkative, format("linking `%1%' to `%2%'") % path % prevPath.first);
+    printMsg(lvlTalkative, format("linking `%1%' to `%2%'") % path % linkPath);
 
-            Path tempLink = (format("%1%.tmp-%2%-%3%")
-                % path % getpid() % rand()).str();
+    Path tempLink = (format("%1%/.tmp-link-%2%-%3%")
+        % nixStore % getpid() % rand()).str();
 
-            /* Make the containing directory writable, but only if
-               it's not the store itself (we don't want or need to
-               mess with its permissions). */
-            bool mustToggle = !isStorePath(path);
-            if (mustToggle) makeWritable(dirOf(path));
+    /* Make the containing directory writable, but only if it's not
+       the store itself (we don't want or need to mess with its
+       permissions). */
+    bool mustToggle = !isStorePath(path);
+    if (mustToggle) makeWritable(dirOf(path));
             
-            /* When we're done, make the directory read-only again and
-               reset its timestamp back to 0. */
-            MakeReadOnly makeReadOnly(mustToggle ? dirOf(path) : "");
-
-            /* If ‘prevPath’ is immutable, we can't create hard links
-               to it, so make it mutable first (and make it immutable
-               again when we're done).  We also have to make ‘path’
-               mutable, otherwise rename() will fail to delete it. */
-            makeMutable(prevPath.first);
-            MakeImmutable mk1(prevPath.first);
-            
-            makeMutable(path);
-            MakeImmutable mk2(path);
-
-            if (link(prevPath.first.c_str(), tempLink.c_str()) == -1) {
-                if (errno == EMLINK) {
-                    /* Too many links to the same file (>= 32000 on
-                       most file systems).  This is likely to happen
-                       with empty files.  Just start over, creating
-                       links to the current file. */
-                    printMsg(lvlInfo, format("`%1%' has maximum number of links") % prevPath.first);
-                    hashToPath[hash] = std::pair<Path, ino_t>(path, st.st_ino);
-                    return;
-                }
-                throw SysError(format("cannot link `%1%' to `%2%'")
-                    % tempLink % prevPath.first);
-            }
-
-            /* Atomically replace the old file with the new hard link. */
-            if (rename(tempLink.c_str(), path.c_str()) == -1) {
-                if (errno == EMLINK) {
-                    /* Some filesystems generate too many links on the
-                       rename, rather than on the original link.
-                       (Probably it temporarily increases the st_nlink
-                       field before decreasing it again.) */
-                    printMsg(lvlInfo, format("`%1%' has maximum number of links") % prevPath.first);
-                    hashToPath[hash] = std::pair<Path, ino_t>(path, st.st_ino);
-
-                    /* Unlink the temp link. */
-                    if (unlink(tempLink.c_str()) == -1)
-                        printMsg(lvlError, format("unable to unlink `%1%'") % tempLink);
-                    return;
-                }
-                throw SysError(format("cannot rename `%1%' to `%2%'")
-                    % tempLink % path);
-            }
-        } else
-            printMsg(lvlTalkative, format("would link `%1%' to `%2%'") % path % prevPath.first);
-        
-        stats.filesLinked++;
-        stats.bytesFreed += st.st_size;
-        stats.blocksFreed += st.st_blocks;
+    /* When we're done, make the directory read-only again and reset
+       its timestamp back to 0. */
+    MakeReadOnly makeReadOnly(mustToggle ? dirOf(path) : "");
+
+    /* If ‘linkPath’ is immutable, we can't create hard links to it,
+       so make it mutable first (and make it immutable again when
+       we're done).  We also have to make ‘path’ mutable, otherwise
+       rename() will fail to delete it. */
+    makeMutable(linkPath);
+    MakeImmutable mk1(linkPath);
+
+    makeMutable(path);
+    MakeImmutable mk2(path);
+
+    if (link(linkPath.c_str(), tempLink.c_str()) == -1) {
+        if (errno == EMLINK) {
+            /* Too many links to the same file (>= 32000 on most file
+               systems).  This is likely to happen with empty files.
+               Just shrug and ignore. */
+            printMsg(lvlInfo, format("`%1%' has maximum number of links") % linkPath);
+            return;
+        }
+        throw SysError(format("cannot link `%1%' to `%2%'") % tempLink % linkPath);
     }
 
-    if (S_ISDIR(st.st_mode)) {
-        Strings names = readDirectory(path);
-	foreach (Strings::iterator, i, names)
-	    hashAndLink(dryRun, hashToPath, stats, path + "/" + *i);
+    /* Atomically replace the old file with the new hard link. */
+    if (rename(tempLink.c_str(), path.c_str()) == -1) {
+        if (errno == EMLINK) {
+            /* Some filesystems generate too many links on the rename,
+               rather than on the original link.  (Probably it
+               temporarily increases the st_nlink field before
+               decreasing it again.) */
+            printMsg(lvlInfo, format("`%1%' has maximum number of links") % linkPath);
+
+            /* Unlink the temp link. */
+            if (unlink(tempLink.c_str()) == -1)
+                printMsg(lvlError, format("unable to unlink `%1%'") % tempLink);
+            return;
+        }
+        throw SysError(format("cannot rename `%1%' to `%2%'") % tempLink % path);
     }
+
+    stats.filesLinked++;
+    stats.bytesFreed += st.st_size;
+    stats.blocksFreed += st.st_blocks;
 }
 
 
-void LocalStore::optimiseStore(bool dryRun, OptimiseStats & stats)
+void LocalStore::optimiseStore(OptimiseStats & stats)
 {
-    HashToPath hashToPath;
-
     PathSet paths = queryAllValidPaths();
 
     foreach (PathSet::iterator, i, paths) {
         addTempRoot(*i);
         if (!isValidPath(*i)) continue; /* path was GC'ed, probably */
         startNest(nest, lvlChatty, format("hashing files in `%1%'") % *i);
-        hashAndLink(dryRun, hashToPath, stats, *i);
+        optimisePath_(stats, *i);
+    }
+}
+
+
+void LocalStore::optimisePath(const Path & path)
+{
+    if (queryBoolSetting("auto-optimise-store", true)) {
+        OptimiseStats stats;
+        optimisePath_(stats, path);
     }
 }
 
diff --git a/src/libstore/pathlocks.hh b/src/libstore/pathlocks.hh
index 57ca1584a6d6..8a6b1450da2a 100644
--- a/src/libstore/pathlocks.hh
+++ b/src/libstore/pathlocks.hh
@@ -1,5 +1,4 @@
-#ifndef __PATHLOCKS_H
-#define __PATHLOCKS_H
+#pragma once
 
 #include "types.hh"
 
@@ -44,6 +43,3 @@ bool pathIsLockedByMe(const Path & path);
 
 
 }
-
-
-#endif /* !__PATHLOCKS_H */
diff --git a/src/libstore/references.hh b/src/libstore/references.hh
index 158c08a77646..013809d122f3 100644
--- a/src/libstore/references.hh
+++ b/src/libstore/references.hh
@@ -1,5 +1,4 @@
-#ifndef __REFERENCES_H
-#define __REFERENCES_H
+#pragma once
 
 #include "types.hh"
 #include "hash.hh"
@@ -10,5 +9,3 @@ PathSet scanForReferences(const Path & path, const PathSet & refs,
     HashResult & hash);
     
 }
-
-#endif /* !__REFERENCES_H */
diff --git a/src/libstore/remote-store.hh b/src/libstore/remote-store.hh
index 68db0640ae70..ae4c48dad6ab 100644
--- a/src/libstore/remote-store.hh
+++ b/src/libstore/remote-store.hh
@@ -1,5 +1,4 @@
-#ifndef __REMOTE_STORE_H
-#define __REMOTE_STORE_H
+#pragma once
 
 #include <string>
 
@@ -103,6 +102,3 @@ private:
 
 
 }
-
-
-#endif /* !__REMOTE_STORE_H */
diff --git a/src/libstore/store-api.hh b/src/libstore/store-api.hh
index 5d8c09f5a526..324d802dc450 100644
--- a/src/libstore/store-api.hh
+++ b/src/libstore/store-api.hh
@@ -1,5 +1,4 @@
-#ifndef __STOREAPI_H
-#define __STOREAPI_H
+#pragma once
 
 #include "hash.hh"
 #include "serialise.hh"
@@ -362,6 +361,3 @@ MakeError(BuildError, Error) /* denotes a permanent build failure */
 
 
 }
-
-
-#endif /* !__STOREAPI_H */
diff --git a/src/libstore/worker-protocol.hh b/src/libstore/worker-protocol.hh
index d4ac0ea16fed..9677a46c2896 100644
--- a/src/libstore/worker-protocol.hh
+++ b/src/libstore/worker-protocol.hh
@@ -1,6 +1,4 @@
-#ifndef __WORKER_PROTOCOL_H
-#define __WORKER_PROTOCOL_H
-
+#pragma once
 
 namespace nix {
 
@@ -67,6 +65,3 @@ template<class T> T readStorePaths(Source & from);
 
     
 }
-
-
-#endif /* !__WORKER_PROTOCOL_H */
diff --git a/src/libutil/archive.hh b/src/libutil/archive.hh
index fff62031397c..ccac92074d54 100644
--- a/src/libutil/archive.hh
+++ b/src/libutil/archive.hh
@@ -1,5 +1,4 @@
-#ifndef __ARCHIVE_H
-#define __ARCHIVE_H
+#pragma once
 
 #include "types.hh"
 #include "serialise.hh"
@@ -74,6 +73,3 @@ void restorePath(const Path & path, Source & source);
 
  
 }
-
-
-#endif /* !__ARCHIVE_H */
diff --git a/src/libutil/hash.hh b/src/libutil/hash.hh
index e0b6478cc418..781f517428d0 100644
--- a/src/libutil/hash.hh
+++ b/src/libutil/hash.hh
@@ -1,5 +1,4 @@
-#ifndef __HASH_H
-#define __HASH_H
+#pragma once
 
 #include "types.hh"
 #include "serialise.hh"
@@ -109,6 +108,3 @@ public:
 
 
 }
-
-
-#endif /* !__HASH_H */
diff --git a/src/libutil/immutable.hh b/src/libutil/immutable.hh
index 5a42a4610736..8af41900490f 100644
--- a/src/libutil/immutable.hh
+++ b/src/libutil/immutable.hh
@@ -1,5 +1,4 @@
-#ifndef __IMMUTABLE_H
-#define __IMMUTABLE_H
+#pragma once
 
 #include <types.hh>
 
@@ -15,5 +14,3 @@ void makeImmutable(const Path & path);
 void makeMutable(const Path & path);
 
 }
-
-#endif /* !__IMMUTABLE_H */
diff --git a/src/libutil/serialise.hh b/src/libutil/serialise.hh
index ded4b12a046e..42dd271176db 100644
--- a/src/libutil/serialise.hh
+++ b/src/libutil/serialise.hh
@@ -1,5 +1,4 @@
-#ifndef __SERIALISE_H
-#define __SERIALISE_H
+#pragma once
 
 #include "types.hh"
 
@@ -130,6 +129,3 @@ MakeError(SerialisationError, Error)
 
 
 }
-
-
-#endif /* !__SERIALISE_H */
diff --git a/src/libutil/types.hh b/src/libutil/types.hh
index 844ad6f76a13..165a46fa28e9 100644
--- a/src/libutil/types.hh
+++ b/src/libutil/types.hh
@@ -1,5 +1,4 @@
-#ifndef __TYPES_H
-#define __TYPES_H
+#pragma once
 
 #include <string>
 #include <list>
@@ -74,6 +73,3 @@ typedef enum {
 
 
 }
-
-
-#endif /* !__TYPES_H */
diff --git a/src/libutil/util.cc b/src/libutil/util.cc
index b188a9fc0e79..689fc543af31 100644
--- a/src/libutil/util.cc
+++ b/src/libutil/util.cc
@@ -380,7 +380,7 @@ static Path tempName(Path tmpRoot, const Path & prefix, bool includePid,
 
 
 Path createTempDir(const Path & tmpRoot, const Path & prefix,
-    bool includePid, bool useGlobalCounter)
+    bool includePid, bool useGlobalCounter, mode_t mode)
 {
     static int globalCounter = 0;
     int localCounter = 0;
@@ -389,7 +389,7 @@ Path createTempDir(const Path & tmpRoot, const Path & prefix,
     while (1) {
         checkInterrupt();
 	Path tmpDir = tempName(tmpRoot, prefix, includePid, counter);
-	if (mkdir(tmpDir.c_str(), 0777) == 0) {
+	if (mkdir(tmpDir.c_str(), mode) == 0) {
 	    /* Explicitly set the group of the directory.  This is to
 	       work around around problems caused by BSD's group
 	       ownership semantics (directories inherit the group of
diff --git a/src/libutil/util.hh b/src/libutil/util.hh
index ee0f3862a872..9b8656f70485 100644
--- a/src/libutil/util.hh
+++ b/src/libutil/util.hh
@@ -1,5 +1,4 @@
-#ifndef __UTIL_H
-#define __UTIL_H
+#pragma once
 
 #include "types.hh"
 
@@ -89,7 +88,7 @@ void makePathReadOnly(const Path & path);
 
 /* Create a temporary directory. */
 Path createTempDir(const Path & tmpRoot = "", const Path & prefix = "nix",
-    bool includePid = true, bool useGlobalCounter = true);
+    bool includePid = true, bool useGlobalCounter = true, mode_t mode = 0755);
 
 /* Create a directory and all its parents, if necessary.  Returns the
    list of created directories, in order of creation. */
@@ -333,6 +332,3 @@ void ignoreException();
 
 
 }
-
-
-#endif /* !__UTIL_H */
diff --git a/src/libutil/xml-writer.hh b/src/libutil/xml-writer.hh
index e5cc5f8c5417..fee2eb495eaf 100644
--- a/src/libutil/xml-writer.hh
+++ b/src/libutil/xml-writer.hh
@@ -1,5 +1,4 @@
-#ifndef __XML_WRITER_H
-#define __XML_WRITER_H
+#pragma once
 
 #include <iostream>
 #include <string>
@@ -70,6 +69,3 @@ public:
 
  
 }
-
-
-#endif /* !__XML_WRITER_H */
diff --git a/src/nix-env/profiles.hh b/src/nix-env/profiles.hh
index a64258dae224..30d2376d998c 100644
--- a/src/nix-env/profiles.hh
+++ b/src/nix-env/profiles.hh
@@ -1,5 +1,4 @@
-#ifndef __PROFILES_H
-#define __PROFILES_H
+#pragma once
 
 #include "types.hh"
 #include "pathlocks.hh"
@@ -54,6 +53,3 @@ void lockProfile(PathLocks & lock, const Path & profile);
 string optimisticLockProfile(const Path & profile);
 
 }
-
-
-#endif /* !__PROFILES_H */
diff --git a/src/nix-env/user-env.hh b/src/nix-env/user-env.hh
index 4125d821732f..f188efe9b4a9 100644
--- a/src/nix-env/user-env.hh
+++ b/src/nix-env/user-env.hh
@@ -1,5 +1,4 @@
-#ifndef __USER_ENV_H
-#define __USER_ENV_H
+#pragma once
 
 #include "get-drvs.hh"
 
@@ -12,9 +11,3 @@ bool createUserEnv(EvalState & state, DrvInfos & elems,
     const string & lockToken);
 
 }
-
-#endif /* !__USER_ENV_H */
-
-
-
-
diff --git a/src/nix-store/dotgraph.hh b/src/nix-store/dotgraph.hh
index 2318e2fde48d..68410d84156d 100644
--- a/src/nix-store/dotgraph.hh
+++ b/src/nix-store/dotgraph.hh
@@ -1,5 +1,4 @@
-#ifndef __DOTGRAPH_H
-#define __DOTGRAPH_H
+#pragma once
 
 #include "types.hh"
 
@@ -8,5 +7,3 @@ namespace nix {
 void printDotGraph(const PathSet & roots);
 
 }
-
-#endif /* !__DOTGRAPH_H */
diff --git a/src/nix-store/nix-store.cc b/src/nix-store/nix-store.cc
index 47c76693f1dc..941301d2e7a1 100644
--- a/src/nix-store/nix-store.cc
+++ b/src/nix-store/nix-store.cc
@@ -746,18 +746,12 @@ static void showOptimiseStats(OptimiseStats & stats)
    files with the same contents. */
 static void opOptimise(Strings opFlags, Strings opArgs)
 {
-    if (!opArgs.empty())
+    if (!opArgs.empty() || !opFlags.empty())
         throw UsageError("no arguments expected");
 
-    bool dryRun = false;
-
-    foreach (Strings::iterator, i, opFlags)
-        if (*i == "--dry-run") dryRun = true;
-        else throw UsageError(format("unknown flag `%1%'") % *i);
-
     OptimiseStats stats;
     try {
-        ensureLocalStore().optimiseStore(dryRun, stats);
+        ensureLocalStore().optimiseStore(stats);
     } catch (...) {
         showOptimiseStats(stats);
         throw;
diff --git a/src/nix-store/xmlgraph.hh b/src/nix-store/xmlgraph.hh
index 2f9908c43665..c2216c5a4627 100644
--- a/src/nix-store/xmlgraph.hh
+++ b/src/nix-store/xmlgraph.hh
@@ -1,5 +1,4 @@
-#ifndef __XMLGRAPH_H
-#define __XMLGRAPH_H
+#pragma once
 
 #include "types.hh"
 
@@ -8,5 +7,3 @@ namespace nix {
 void printXmlGraph(const PathSet & roots);
 
 }
-
-#endif /* !__XMLGRAPH_H */
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 517c382b1960..a562db52bc57 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -9,7 +9,7 @@ TESTS = init.sh hash.sh lang.sh add.sh simple.sh dependencies.sh \
   gc-runtime.sh install-package.sh check-refs.sh filter-source.sh \
   remote-store.sh export.sh export-graph.sh negative-caching.sh \
   binary-patching.sh timeout.sh secure-drv-outputs.sh nix-channel.sh \
-  multiple-outputs.sh import-derivation.sh fetchurl.sh
+  multiple-outputs.sh import-derivation.sh fetchurl.sh optimise-store.sh
 
 XFAIL_TESTS =
 
diff --git a/tests/optimise-store.sh b/tests/optimise-store.sh
new file mode 100644
index 000000000000..4d8077997d3e
--- /dev/null
+++ b/tests/optimise-store.sh
@@ -0,0 +1,26 @@
+source common.sh
+
+clearStore
+
+outPath1=$(echo 'with import ./config.nix; mkDerivation { name = "foo1"; builder = builtins.toFile "builder" "mkdir $out; echo hello > $out/foo"; }' | nix-build - --no-out-link)
+outPath2=$(echo 'with import ./config.nix; mkDerivation { name = "foo2"; builder = builtins.toFile "builder" "mkdir $out; echo hello > $out/foo"; }' | nix-build - --no-out-link)
+
+inode1="$(perl -e "print ((lstat('$outPath1/foo'))[1])")"
+inode2="$(perl -e "print ((lstat('$outPath2/foo'))[1])")"
+if [ "$inode1" != "$inode2" ]; then
+    echo "inodes do not match"
+    exit 1
+fi
+
+nlink="$(perl -e "print ((lstat('$outPath1/foo'))[3])")"
+if [ "$nlink" != 3 ]; then
+    echo "link count incorrect"
+    exit 1
+fi
+
+nix-store --gc
+
+if [ -n "$(ls $NIX_STORE_DIR/.links)" ]; then
+    echo ".links directory not empty after GC"
+    exit 1
+fi
diff --git a/version b/version
index b123147e2a16..ea710abb95bc 100644
--- a/version
+++ b/version
@@ -1 +1 @@
-1.1
\ No newline at end of file
+1.2
\ No newline at end of file