Merge commit a1dd396cc02922372314c35c8035a38bfeea08df of branch 'nix'.
[jackhill/guix/guix.git] / nix / libutil / archive.cc
CommitLineData
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
18namespace nix {
19
20
21static string archiveVersion1 = "nix-archive-1";
22
23
24PathFilter defaultPathFilter;
25
26
27static void dump(const string & path, Sink & sink, PathFilter & filter);
28
29
30static 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
53static 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
76static 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
113void dumpPath(const Path & path, Sink & sink, PathFilter & filter)
114{
115 writeString(archiveVersion1, sink);
116 dump(path, sink, filter);
117}
118
119
120static SerialisationError badArchive(string s)
121{
122 return SerialisationError("bad archive: " + s);
123}
124
125
126static void skipGeneric(Source & source)
127{
128 if (readString(source) == "(") {
129 while (readString(source) != ")")
130 skipGeneric(source);
131 }
132}
133
134
135static void parse(ParseSink & sink, Source & source, const Path & path);
136
137
138
139static 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
166static 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
188static 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
255void 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
270struct 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
327void restorePath(const Path & path, Source & source)
328{
329 RestoreSink sink;
330 sink.dstPath = path;
331 parseDump(sink, source);
332}
333
334
335}