1 : #include "util.hh"
2 : #include "local-store.hh"
3 :
4 : #include <sys/types.h>
5 : #include <sys/stat.h>
6 : #include <unistd.h>
7 : #include <errno.h>
8 :
9 :
10 : namespace nix {
11 :
12 :
13 : typedef std::map<Hash, std::pair<Path, ino_t> > HashToPath;
14 :
15 :
16 0 : static void makeWritable(const Path & path)
17 : {
18 : struct stat st;
19 0 : if (lstat(path.c_str(), &st))
20 0 : throw SysError(format("getting attributes of path `%1%'") % path);
21 0 : if (chmod(path.c_str(), st.st_mode | S_IWUSR) == -1)
22 0 : throw SysError(format("changing writability of `%1%'") % path);
23 0 : }
24 :
25 :
26 : struct MakeReadOnly
27 : {
28 : Path path;
29 0 : MakeReadOnly(const Path & path) : path(path) { }
30 0 : ~MakeReadOnly()
31 : {
32 : try {
33 0 : if (path != "") canonicalisePathMetaData(path, false);
34 0 : } catch (...) {
35 0 : ignoreException();
36 : }
37 0 : }
38 : };
39 :
40 :
41 : static void hashAndLink(bool dryRun, HashToPath & hashToPath,
42 0 : OptimiseStats & stats, const Path & path)
43 : {
44 : struct stat st;
45 0 : if (lstat(path.c_str(), &st))
46 0 : throw SysError(format("getting attributes of path `%1%'") % path);
47 :
48 : /* Sometimes SNAFUs can cause files in the Nix store to be
49 : modified, in particular when running programs as root under
50 : NixOS (example: $fontconfig/var/cache being modified). Skip
51 : those files. */
52 0 : if (S_ISREG(st.st_mode) && (st.st_mode & S_IWUSR)) {
53 0 : printMsg(lvlError, format("skipping suspicious writable file `%1%'") % path);
54 0 : return;
55 : }
56 :
57 : /* We can hard link regular files and symlinks. */
58 0 : if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) {
59 :
60 : /* Hash the file. Note that hashPath() returns the hash over
61 : the NAR serialisation, which includes the execute bit on
62 : the file. Thus, executable and non-executable files with
63 : the same contents *won't* be linked (which is good because
64 : otherwise the permissions would be screwed up).
65 :
66 : Also note that if `path' is a symlink, then we're hashing
67 : the contents of the symlink (i.e. the result of
68 : readlink()), not the contents of the target (which may not
69 : even exist). */
70 0 : Hash hash = hashPath(htSHA256, path);
71 0 : stats.totalFiles++;
72 0 : printMsg(lvlDebug, format("`%1%' has hash `%2%'") % path % printHash(hash));
73 :
74 0 : std::pair<Path, ino_t> prevPath = hashToPath[hash];
75 :
76 0 : if (prevPath.first == "") {
77 0 : hashToPath[hash] = std::pair<Path, ino_t>(path, st.st_ino);
78 0 : return;
79 : }
80 :
81 : /* Yes! We've seen a file with the same contents. Replace
82 : the current file with a hard link to that file. */
83 0 : stats.sameContents++;
84 0 : if (prevPath.second == st.st_ino) {
85 0 : printMsg(lvlDebug, format("`%1%' is already linked to `%2%'") % path % prevPath.first);
86 : return;
87 : }
88 :
89 0 : if (!dryRun) {
90 :
91 0 : printMsg(lvlTalkative, format("linking `%1%' to `%2%'") % path % prevPath.first);
92 :
93 : Path tempLink = (format("%1%.tmp-%2%-%3%")
94 0 : % path % getpid() % rand()).str();
95 :
96 : /* Make the containing directory writable, but only if
97 : it's not the store itself (we don't want or need to
98 : mess with its permissions). */
99 0 : bool mustToggle = !isStorePath(path);
100 0 : if (mustToggle) makeWritable(dirOf(path));
101 :
102 : /* When we're done, make the directory read-only again and
103 : reset its timestamp back to 0. */
104 0 : MakeReadOnly makeReadOnly(mustToggle ? dirOf(path) : "");
105 :
106 0 : if (link(prevPath.first.c_str(), tempLink.c_str()) == -1) {
107 0 : if (errno == EMLINK) {
108 : /* Too many links to the same file (>= 32000 on
109 : most file systems). This is likely to happen
110 : with empty files. Just start over, creating
111 : links to the current file. */
112 0 : printMsg(lvlInfo, format("`%1%' has maximum number of links") % prevPath.first);
113 0 : hashToPath[hash] = std::pair<Path, ino_t>(path, st.st_ino);
114 : return;
115 : }
116 : throw SysError(format("cannot link `%1%' to `%2%'")
117 0 : % tempLink % prevPath.first);
118 : }
119 :
120 : /* Atomically replace the old file with the new hard link. */
121 0 : if (rename(tempLink.c_str(), path.c_str()) == -1)
122 : throw SysError(format("cannot rename `%1%' to `%2%'")
123 0 : % tempLink % path);
124 : } else
125 0 : printMsg(lvlTalkative, format("would link `%1%' to `%2%'") % path % prevPath.first);
126 :
127 0 : stats.filesLinked++;
128 0 : stats.bytesFreed += st.st_size;
129 0 : stats.blocksFreed += st.st_blocks;
130 : }
131 :
132 0 : if (S_ISDIR(st.st_mode)) {
133 0 : Strings names = readDirectory(path);
134 0 : for (Strings::iterator i = names.begin(); i != names.end(); ++i)
135 0 : hashAndLink(dryRun, hashToPath, stats, path + "/" + *i);
136 : }
137 : }
138 :
139 :
140 0 : void LocalStore::optimiseStore(bool dryRun, OptimiseStats & stats)
141 : {
142 0 : HashToPath hashToPath;
143 :
144 0 : PathSet paths = queryValidPaths();
145 :
146 0 : for (PathSet::iterator i = paths.begin(); i != paths.end(); ++i) {
147 0 : addTempRoot(*i);
148 0 : if (!isValidPath(*i)) continue; /* path was GC'ed, probably */
149 0 : startNest(nest, lvlChatty, format("hashing files in `%1%'") % *i);
150 0 : hashAndLink(dryRun, hashToPath, stats, *i);
151 0 : }
152 0 : }
153 :
154 0 :
155 : }
|