Commit | Line | Data |
---|---|---|
36457566 LC |
1 | #include "config.h" |
2 | ||
3 | #include <cerrno> | |
4 | #include <algorithm> | |
5 | #include <vector> | |
6 | ||
7 | #define _XOPEN_SOURCE 600 | |
8 | #include <sys/types.h> | |
9 | #include <sys/stat.h> | |
10 | #include <unistd.h> | |
11 | #include <dirent.h> | |
12 | #include <fcntl.h> | |
13 | ||
14 | #include "archive.hh" | |
15 | #include "util.hh" | |
16 | ||
17 | ||
18 | namespace nix { | |
19 | ||
20 | ||
21 | static string archiveVersion1 = "nix-archive-1"; | |
22 | ||
23 | ||
24 | PathFilter defaultPathFilter; | |
25 | ||
26 | ||
27 | static void dump(const string & path, Sink & sink, PathFilter & filter); | |
28 | ||
29 | ||
30 | static void dumpEntries(const Path & path, Sink & sink, PathFilter & filter) | |
31 | { | |
32 | Strings names = readDirectory(path); | |
33 | vector<string> names2(names.begin(), names.end()); | |
34 | sort(names2.begin(), names2.end()); | |
35 | ||
36 | for (vector<string>::iterator i = names2.begin(); | |
37 | i != names2.end(); ++i) | |
38 | { | |
39 | Path entry = path + "/" + *i; | |
40 | if (filter(entry)) { | |
41 | writeString("entry", sink); | |
42 | writeString("(", sink); | |
43 | writeString("name", sink); | |
44 | writeString(*i, sink); | |
45 | writeString("node", sink); | |
46 | dump(entry, sink, filter); | |
47 | writeString(")", sink); | |
48 | } | |
49 | } | |
50 | } | |
51 | ||
52 | ||
53 | static void dumpContents(const Path & path, size_t size, | |
54 | Sink & sink) | |
55 | { | |
56 | writeString("contents", sink); | |
57 | writeLongLong(size, sink); | |
58 | ||
59 | AutoCloseFD fd = open(path.c_str(), O_RDONLY); | |
60 | if (fd == -1) throw SysError(format("opening file `%1%'") % path); | |
61 | ||
62 | unsigned char buf[65536]; | |
63 | size_t left = size; | |
64 | ||
65 | while (left > 0) { | |
66 | size_t n = left > sizeof(buf) ? sizeof(buf) : left; | |
67 | readFull(fd, buf, n); | |
68 | left -= n; | |
69 | sink(buf, n); | |
70 | } | |
71 | ||
72 | writePadding(size, sink); | |
73 | } | |
74 | ||
75 | ||
76 | static void dump(const Path & path, Sink & sink, PathFilter & filter) | |
77 | { | |
78 | struct stat st; | |
79 | if (lstat(path.c_str(), &st)) | |
80 | throw SysError(format("getting attributes of path `%1%'") % path); | |
81 | ||
82 | writeString("(", sink); | |
83 | ||
84 | if (S_ISREG(st.st_mode)) { | |
85 | writeString("type", sink); | |
86 | writeString("regular", sink); | |
87 | if (st.st_mode & S_IXUSR) { | |
88 | writeString("executable", sink); | |
89 | writeString("", sink); | |
90 | } | |
91 | dumpContents(path, (size_t) st.st_size, sink); | |
92 | } | |
93 | ||
94 | else if (S_ISDIR(st.st_mode)) { | |
95 | writeString("type", sink); | |
96 | writeString("directory", sink); | |
97 | dumpEntries(path, sink, filter); | |
98 | } | |
99 | ||
100 | else if (S_ISLNK(st.st_mode)) { | |
101 | writeString("type", sink); | |
102 | writeString("symlink", sink); | |
103 | writeString("target", sink); | |
104 | writeString(readLink(path), sink); | |
105 | } | |
106 | ||
15ddeff5 | 107 | else throw Error(format("file `%1%' has an unsupported type") % path); |
36457566 LC |
108 | |
109 | writeString(")", sink); | |
110 | } | |
111 | ||
112 | ||
113 | void dumpPath(const Path & path, Sink & sink, PathFilter & filter) | |
114 | { | |
115 | writeString(archiveVersion1, sink); | |
116 | dump(path, sink, filter); | |
117 | } | |
118 | ||
119 | ||
120 | static SerialisationError badArchive(string s) | |
121 | { | |
122 | return SerialisationError("bad archive: " + s); | |
123 | } | |
124 | ||
125 | ||
126 | static void skipGeneric(Source & source) | |
127 | { | |
128 | if (readString(source) == "(") { | |
129 | while (readString(source) != ")") | |
130 | skipGeneric(source); | |
131 | } | |
132 | } | |
133 | ||
134 | ||
135 | static void parse(ParseSink & sink, Source & source, const Path & path); | |
136 | ||
137 | ||
138 | ||
139 | static void parseEntry(ParseSink & sink, Source & source, const Path & path) | |
140 | { | |
141 | string s, name; | |
142 | ||
143 | s = readString(source); | |
144 | if (s != "(") throw badArchive("expected open tag"); | |
145 | ||
146 | while (1) { | |
147 | checkInterrupt(); | |
148 | ||
149 | s = readString(source); | |
150 | ||
151 | if (s == ")") { | |
152 | break; | |
153 | } else if (s == "name") { | |
154 | name = readString(source); | |
155 | } else if (s == "node") { | |
156 | if (s == "") throw badArchive("entry name missing"); | |
157 | parse(sink, source, path + "/" + name); | |
158 | } else { | |
159 | throw badArchive("unknown field " + s); | |
160 | skipGeneric(source); | |
161 | } | |
162 | } | |
163 | } | |
164 | ||
165 | ||
166 | static void parseContents(ParseSink & sink, Source & source, const Path & path) | |
167 | { | |
168 | unsigned long long size = readLongLong(source); | |
169 | ||
170 | sink.preallocateContents(size); | |
171 | ||
172 | unsigned long long left = size; | |
173 | unsigned char buf[65536]; | |
174 | ||
175 | while (left) { | |
176 | checkInterrupt(); | |
177 | unsigned int n = sizeof(buf); | |
178 | if ((unsigned long long) n > left) n = left; | |
179 | source(buf, n); | |
180 | sink.receiveContents(buf, n); | |
181 | left -= n; | |
182 | } | |
183 | ||
184 | readPadding(size, source); | |
185 | } | |
186 | ||
187 | ||
188 | static void parse(ParseSink & sink, Source & source, const Path & path) | |
189 | { | |
190 | string s; | |
191 | ||
192 | s = readString(source); | |
193 | if (s != "(") throw badArchive("expected open tag"); | |
194 | ||
195 | enum { tpUnknown, tpRegular, tpDirectory, tpSymlink } type = tpUnknown; | |
196 | ||
197 | while (1) { | |
198 | checkInterrupt(); | |
199 | ||
200 | s = readString(source); | |
201 | ||
202 | if (s == ")") { | |
203 | break; | |
204 | } | |
205 | ||
206 | else if (s == "type") { | |
207 | if (type != tpUnknown) | |
208 | throw badArchive("multiple type fields"); | |
209 | string t = readString(source); | |
210 | ||
211 | if (t == "regular") { | |
212 | type = tpRegular; | |
213 | sink.createRegularFile(path); | |
214 | } | |
215 | ||
216 | else if (t == "directory") { | |
217 | sink.createDirectory(path); | |
218 | type = tpDirectory; | |
219 | } | |
220 | ||
221 | else if (t == "symlink") { | |
222 | type = tpSymlink; | |
223 | } | |
224 | ||
225 | else throw badArchive("unknown file type " + t); | |
226 | ||
227 | } | |
228 | ||
229 | else if (s == "contents" && type == tpRegular) { | |
230 | parseContents(sink, source, path); | |
231 | } | |
232 | ||
233 | else if (s == "executable" && type == tpRegular) { | |
234 | readString(source); | |
235 | sink.isExecutable(); | |
236 | } | |
237 | ||
238 | else if (s == "entry" && type == tpDirectory) { | |
239 | parseEntry(sink, source, path); | |
240 | } | |
241 | ||
242 | else if (s == "target" && type == tpSymlink) { | |
243 | string target = readString(source); | |
244 | sink.createSymlink(path, target); | |
245 | } | |
246 | ||
247 | else { | |
248 | throw badArchive("unknown field " + s); | |
249 | skipGeneric(source); | |
250 | } | |
251 | } | |
252 | } | |
253 | ||
254 | ||
255 | void parseDump(ParseSink & sink, Source & source) | |
256 | { | |
257 | string version; | |
258 | try { | |
259 | version = readString(source); | |
260 | } catch (SerialisationError & e) { | |
261 | /* This generally means the integer at the start couldn't be | |
262 | decoded. Ignore and throw the exception below. */ | |
263 | } | |
264 | if (version != archiveVersion1) | |
265 | throw badArchive("input doesn't look like a Nix archive"); | |
266 | parse(sink, source, ""); | |
267 | } | |
268 | ||
269 | ||
270 | struct RestoreSink : ParseSink | |
271 | { | |
272 | Path dstPath; | |
273 | AutoCloseFD fd; | |
274 | ||
275 | void createDirectory(const Path & path) | |
276 | { | |
277 | Path p = dstPath + path; | |
278 | if (mkdir(p.c_str(), 0777) == -1) | |
279 | throw SysError(format("creating directory `%1%'") % p); | |
280 | }; | |
281 | ||
282 | void createRegularFile(const Path & path) | |
283 | { | |
284 | Path p = dstPath + path; | |
285 | fd.close(); | |
286 | fd = open(p.c_str(), O_CREAT | O_EXCL | O_WRONLY, 0666); | |
287 | if (fd == -1) throw SysError(format("creating file `%1%'") % p); | |
288 | } | |
289 | ||
290 | void isExecutable() | |
291 | { | |
292 | struct stat st; | |
293 | if (fstat(fd, &st) == -1) | |
294 | throw SysError("fstat"); | |
295 | if (fchmod(fd, st.st_mode | (S_IXUSR | S_IXGRP | S_IXOTH)) == -1) | |
296 | throw SysError("fchmod"); | |
297 | } | |
298 | ||
299 | void preallocateContents(unsigned long long len) | |
300 | { | |
301 | #if HAVE_POSIX_FALLOCATE | |
302 | if (len) { | |
303 | errno = posix_fallocate(fd, 0, len); | |
304 | /* Note that EINVAL may indicate that the underlying | |
305 | filesystem doesn't support preallocation (e.g. on | |
306 | OpenSolaris). Since preallocation is just an | |
307 | optimisation, ignore it. */ | |
308 | if (errno && errno != EINVAL) | |
309 | throw SysError(format("preallocating file of %1% bytes") % len); | |
310 | } | |
311 | #endif | |
312 | } | |
313 | ||
314 | void receiveContents(unsigned char * data, unsigned int len) | |
315 | { | |
316 | writeFull(fd, data, len); | |
317 | } | |
318 | ||
319 | void createSymlink(const Path & path, const string & target) | |
320 | { | |
321 | Path p = dstPath + path; | |
322 | nix::createSymlink(target, p); | |
323 | } | |
324 | }; | |
325 | ||
326 | ||
327 | void restorePath(const Path & path, Source & source) | |
328 | { | |
329 | RestoreSink sink; | |
330 | sink.dstPath = path; | |
331 | parseDump(sink, source); | |
332 | } | |
333 | ||
334 | ||
335 | } |