projects/guix·blob python-team · refs

410 lines · 11.1 KiB

1#define _XOPEN_SOURCE 600
2
3#include "config.h"
4
5#include <cerrno>
6#include <map>
7#include <format>
8
9#include <strings.h> // for strcasecmp
10
11#include <sys/types.h>
12#include <sys/stat.h>
13#include <unistd.h>
14#include <dirent.h>
15#include <fcntl.h>
16
17#include "archive.hh"
18#include "util.hh"
19
20
21namespace nix {
22
23static string archiveVersion1 = "nix-archive-1";
24
25static string caseHackSuffix = "~nix~case~hack~";
26
27
28static void dumpContents(const Path & path, size_t size,
29 Sink & sink)
30{
31 writeString("contents", sink);
32 writeLongLong(size, sink);
33
34 AutoCloseFD fd = open(path.c_str(), O_RDONLY);
35 if (fd == -1) throw SysError(std::format("opening file `{}'", path));
36
37 unsigned char buf[65536];
38 size_t left = size;
39
40 while (left > 0) {
41 size_t n = left > sizeof(buf) ? sizeof(buf) : left;
42 readFull(fd, buf, n);
43 left -= n;
44 sink(buf, n);
45 }
46
47 writePadding(size, sink);
48}
49
50
51static void dump(const Path & path, Sink & sink, PathFilter & filter)
52{
53 struct stat st;
54 if (lstat(path.c_str(), &st))
55 throw SysError(std::format("getting attributes of path `{}'", path));
56
57 writeString("(", sink);
58
59 if (S_ISREG(st.st_mode)) {
60 writeString("type", sink);
61 writeString("regular", sink);
62 if (st.st_mode & S_IXUSR) {
63 writeString("executable", sink);
64 writeString("", sink);
65 }
66 dumpContents(path, (size_t) st.st_size, sink);
67 }
68
69 else if (S_ISDIR(st.st_mode)) {
70 writeString("type", sink);
71 writeString("directory", sink);
72
73 /* If we're on a case-insensitive system like Mac OS X, undo
74 the case hack applied by restorePath(). */
75 std::map<string, string> unhacked;
76 for (auto & i : readDirectory(path))
77 unhacked[i.name] = i.name;
78
79 for (auto & i : unhacked)
80 if (filter(path + "/" + i.first)) {
81 writeString("entry", sink);
82 writeString("(", sink);
83 writeString("name", sink);
84 writeString(i.first, sink);
85 writeString("node", sink);
86 dump(path + "/" + i.second, sink, filter);
87 writeString(")", sink);
88 }
89 }
90
91 else if (S_ISLNK(st.st_mode)) {
92 writeString("type", sink);
93 writeString("symlink", sink);
94 writeString("target", sink);
95 writeString(readLink(path), sink);
96 }
97
98 else throw Error(std::format("file `{}' has an unsupported type", path));
99
100 writeString(")", sink);
101}
102
103
104void dumpPath(const Path & path, Sink & sink, PathFilter & filter)
105{
106 writeString(archiveVersion1, sink);
107 dump(path, sink, filter);
108}
109
110
111static SerialisationError badArchive(string s)
112{
113 return SerialisationError("bad archive: " + s);
114}
115
116
117#if 0
118static void skipGeneric(Source & source)
119{
120 if (readString(source) == "(") {
121 while (readString(source) != ")")
122 skipGeneric(source);
123 }
124}
125#endif
126
127
128static void parseContents(ParseSink & sink, Source & source, const Path & path)
129{
130 unsigned long long size = readLongLong(source);
131
132 sink.preallocateContents(size);
133
134 unsigned long long left = size;
135 unsigned char buf[65536];
136
137 while (left) {
138 checkInterrupt();
139 unsigned int n = sizeof(buf);
140 if ((unsigned long long) n > left) n = left;
141 source(buf, n);
142 sink.receiveContents(buf, n);
143 left -= n;
144 }
145
146 readPadding(size, source);
147}
148
149
150struct CaseInsensitiveCompare
151{
152 bool operator() (const string & a, const string & b) const
153 {
154 return strcasecmp(a.c_str(), b.c_str()) < 0;
155 }
156};
157
158/* Used to place a consistent upper bound on recursion depth */
159#define DIRECTORY_NESTING_LIMIT 256
160
161static void parse(ParseSink & sink, Source & source, const Path & path,
162 int nestLimit)
163{
164 if (nestLimit <= 0) throw Error("nar directory nesting limit reached");
165 string s;
166
167 s = readString(source);
168 if (s != "(") throw badArchive("expected open tag");
169
170 s = readString(source);
171 if (s != "type") throw badArchive("expected type tag");
172
173 s = readString(source);
174
175 if (s == "regular") {
176 bool executable = false;
177 s = readString(source);
178 if (s == "executable") {
179 executable = true;
180 readString(source);
181 s = readString(source);
182 }
183
184 if (s != "contents") throw badArchive("expected contents tag");
185
186 sink.createRegularFile(path);
187 if (executable) sink.isExecutable();
188 parseContents(sink, source, path);
189 s = readString(source);
190 if (s != ")") throw badArchive("expected close tag");
191 }
192 else if (s == "symlink") {
193 s = readString(source);
194 if (s != "target") throw badArchive("expected target tag");
195 s = readString(source);
196 sink.createSymlink(path, s);
197 s = readString(source);
198 if (s != ")") throw badArchive("expected close tag");
199 }
200 else if (s == "directory") {
201 string prevName;
202 sink.createDirectory(path);
203 while (true) {
204 checkInterrupt();
205 s = readString(source);
206 if (s == ")") break;
207 if (s != "entry") throw badArchive("expected entry tag");
208
209 s = readString(source);
210 if (s != "(") throw badArchive("expected entry open tag");
211
212 s = readString(source);
213 if (s != "name") throw badArchive("expected name tag");
214
215 s = readString(source);
216 string name = s;
217 if (name.empty() || name == "." || name == ".."
218 || name.find('/') != string::npos
219 || name.find((char) 0) != string::npos)
220 throw Error(std::format("NAR contains invalid file name `{}'", name));
221 if (!prevName.empty() && name <= prevName)
222 throw Error("NAR directory is not sorted");
223
224 s = readString(source);
225 if (s != "node") throw badArchive("expected node tag");
226
227 parse(sink, source, path + "/" + name, nestLimit - 1);
228
229 s = readString(source);
230 if (s != ")") throw badArchive("expected entry close tag");
231 prevName = name;
232 }
233 }
234 else throw badArchive("unknown type");
235}
236
237/* Unbounded variant that doesn't recurse, uses path as its stack */
238static void parse_unbounded(ParseSink & sink, Source & source, Path & path)
239{
240 string prevName;
241 string s;
242start:
243 s = readString(source);
244 if (s != "(") throw badArchive("expected open tag");
245
246 s = readString(source);
247 if (s != "type") throw badArchive("expected type tag");
248
249 s = readString(source);
250
251 if (s == "regular") {
252 bool executable = false;
253 s = readString(source);
254 if (s == "executable") {
255 executable = true;
256 readString(source);
257 s = readString(source);
258 }
259
260 if (s != "contents") throw badArchive("expected contents tag");
261
262 sink.createRegularFile(path);
263 if (executable) sink.isExecutable();
264 parseContents(sink, source, path);
265 s = readString(source);
266 if (s != ")") throw badArchive("expected close tag");
267 }
268 else if (s == "symlink") {
269 s = readString(source);
270 if (s != "target") throw badArchive("expected target tag");
271 s = readString(source);
272 sink.createSymlink(path, s);
273 s = readString(source);
274 if (s != ")") throw badArchive("expected close tag");
275 }
276 else if (s == "directory") {
277 sink.createDirectory(path);
278 while (true) {
279 checkInterrupt();
280 s = readString(source);
281 if (s == ")") break;
282 if (s != "entry") throw badArchive("expected entry tag");
283
284 s = readString(source);
285 if (s != "(") throw badArchive("expected entry open tag");
286
287 s = readString(source);
288 if (s != "name") throw badArchive("expected name tag");
289
290 s = readString(source);
291 {
292 string name = s;
293 if (name.empty() || name == "." || name == ".."
294 || name.find('/') != string::npos
295 || name.find((char) 0) != string::npos)
296 throw Error(std::format("NAR contains invalid file name `{}'", name));
297 if (!prevName.empty() && name <= prevName)
298 throw Error("NAR directory is not sorted");
299
300 s = readString(source);
301 if (s != "node") throw badArchive("expected node tag");
302
303 /* parse(sink, source, path + "/" + name); */
304 path.append("/" + name);
305 prevName = "";
306 goto start;
307 }
308
309 continue_directory:
310 s = readString(source);
311 if (s != ")") throw badArchive("expected entry close tag");
312 }
313 }
314 else throw badArchive("unknown type");
315
316 string::size_type slash_pos = path.rfind("/");
317 if (slash_pos != string::npos) {
318 prevName = string(path, slash_pos+1);
319 path.erase(slash_pos);
320 goto continue_directory;
321 }
322}
323
324
325void parseDump(ParseSink & sink, Source & source)
326{
327 string version;
328 try {
329 version = readString(source);
330 } catch (SerialisationError & e) {
331 /* This generally means the integer at the start couldn't be
332 decoded. Ignore and throw the exception below. */
333 }
334 if (version != archiveVersion1)
335 throw badArchive("input doesn't look like a normalized archive");
336
337 parse(sink, source, "", DIRECTORY_NESTING_LIMIT);
338 /*
339 string file = "";
340 parse_unbounded(sink, source, file);
341 */
342}
343
344
345struct RestoreSink : ParseSink
346{
347 Path dstPath;
348 AutoCloseFD fd;
349
350 void createDirectory(const Path & path)
351 {
352 Path p = dstPath + path;
353 if (mkdir(p.c_str(), 0777) == -1)
354 throw SysError(std::format("creating directory `{}'", p));
355 };
356
357 void createRegularFile(const Path & path)
358 {
359 Path p = dstPath + path;
360 fd.close();
361 fd = open(p.c_str(), O_CREAT | O_EXCL | O_WRONLY, 0666);
362 if (fd == -1) throw SysError(std::format("creating file `{}'", p));
363 }
364
365 void isExecutable()
366 {
367 struct stat st;
368 if (fstat(fd, &st) == -1)
369 throw SysError("fstat");
370 if (fchmod(fd, st.st_mode | (S_IXUSR | S_IXGRP | S_IXOTH)) == -1)
371 throw SysError("fchmod");
372 }
373
374 void preallocateContents(unsigned long long len)
375 {
376#if HAVE_POSIX_FALLOCATE
377 if (len) {
378 errno = posix_fallocate(fd, 0, len);
379 /* Note that EINVAL may indicate that the underlying
380 filesystem doesn't support preallocation (e.g. on
381 OpenSolaris). Since preallocation is just an
382 optimisation, ignore it. */
383 if (errno && errno != EINVAL)
384 throw SysError(std::format("preallocating file of {} bytes", len));
385 }
386#endif
387 }
388
389 void receiveContents(unsigned char * data, unsigned int len)
390 {
391 writeFull(fd, data, len);
392 }
393
394 void createSymlink(const Path & path, const string & target)
395 {
396 Path p = dstPath + path;
397 nix::createSymlink(target, p);
398 }
399};
400
401
402void restorePath(const Path & path, Source & source)
403{
404 RestoreSink sink;
405 sink.dstPath = path;
406 parseDump(sink, source);
407}
408
409
410}