1 #define _XOPEN_SOURCE 600
10 #include <strings.h> // for strcasecmp
12 #include <sys/types.h>
32 static string archiveVersion1
= "nix-archive-1";
34 static string caseHackSuffix
= "~nix~case~hack~";
36 PathFilter defaultPathFilter
;
39 static void dumpContents(const Path
& path
, size_t size
,
42 writeString("contents", sink
);
43 writeLongLong(size
, sink
);
45 AutoCloseFD fd
= open(path
.c_str(), O_RDONLY
);
46 if (fd
== -1) throw SysError(format("opening file `%1%'") % path
);
48 unsigned char buf
[65536];
52 size_t n
= left
> sizeof(buf
) ? sizeof(buf
) : left
;
58 writePadding(size
, sink
);
62 static void dump(const Path
& path
, Sink
& sink
, PathFilter
& filter
)
65 if (lstat(path
.c_str(), &st
))
66 throw SysError(format("getting attributes of path `%1%'") % path
);
68 writeString("(", sink
);
70 if (S_ISREG(st
.st_mode
)) {
71 writeString("type", sink
);
72 writeString("regular", sink
);
73 if (st
.st_mode
& S_IXUSR
) {
74 writeString("executable", sink
);
75 writeString("", sink
);
77 dumpContents(path
, (size_t) st
.st_size
, sink
);
80 else if (S_ISDIR(st
.st_mode
)) {
81 writeString("type", sink
);
82 writeString("directory", sink
);
84 /* If we're on a case-insensitive system like Mac OS X, undo
85 the case hack applied by restorePath(). */
86 std::map
<string
, string
> unhacked
;
87 for (auto & i
: readDirectory(path
))
90 size_t pos
= i
.name
.find(caseHackSuffix
);
91 if (pos
!= string::npos
) {
92 printMsg(lvlDebug
, format("removing case hack suffix from `%1%'") % (path
+ "/" + i
.name
));
95 if (unhacked
.find(name
) != unhacked
.end())
96 throw Error(format("file name collision in between `%1%' and `%2%'")
97 % (path
+ "/" + unhacked
[name
]) % (path
+ "/" + i
.name
));
98 unhacked
[name
] = i
.name
;
100 unhacked
[i
.name
] = i
.name
;
102 for (auto & i
: unhacked
)
103 if (filter(path
+ "/" + i
.first
)) {
104 writeString("entry", sink
);
105 writeString("(", sink
);
106 writeString("name", sink
);
107 writeString(i
.first
, sink
);
108 writeString("node", sink
);
109 dump(path
+ "/" + i
.second
, sink
, filter
);
110 writeString(")", sink
);
114 else if (S_ISLNK(st
.st_mode
)) {
115 writeString("type", sink
);
116 writeString("symlink", sink
);
117 writeString("target", sink
);
118 writeString(readLink(path
), sink
);
121 else throw Error(format("file `%1%' has an unsupported type") % path
);
123 writeString(")", sink
);
127 void dumpPath(const Path
& path
, Sink
& sink
, PathFilter
& filter
)
129 writeString(archiveVersion1
, sink
);
130 dump(path
, sink
, filter
);
134 static SerialisationError
badArchive(string s
)
136 return SerialisationError("bad archive: " + s
);
141 static void skipGeneric(Source
& source
)
143 if (readString(source
) == "(") {
144 while (readString(source
) != ")")
151 static void parseContents(ParseSink
& sink
, Source
& source
, const Path
& path
)
153 unsigned long long size
= readLongLong(source
);
155 sink
.preallocateContents(size
);
157 unsigned long long left
= size
;
158 unsigned char buf
[65536];
162 unsigned int n
= sizeof(buf
);
163 if ((unsigned long long) n
> left
) n
= left
;
165 sink
.receiveContents(buf
, n
);
169 readPadding(size
, source
);
173 struct CaseInsensitiveCompare
175 bool operator() (const string
& a
, const string
& b
) const
177 return strcasecmp(a
.c_str(), b
.c_str()) < 0;
182 static void parse(ParseSink
& sink
, Source
& source
, const Path
& path
)
186 s
= readString(source
);
187 if (s
!= "(") throw badArchive("expected open tag");
189 enum { tpUnknown
, tpRegular
, tpDirectory
, tpSymlink
} type
= tpUnknown
;
191 std::map
<Path
, int, CaseInsensitiveCompare
> names
;
196 s
= readString(source
);
202 else if (s
== "type") {
203 if (type
!= tpUnknown
)
204 throw badArchive("multiple type fields");
205 string t
= readString(source
);
207 if (t
== "regular") {
209 sink
.createRegularFile(path
);
212 else if (t
== "directory") {
213 sink
.createDirectory(path
);
217 else if (t
== "symlink") {
221 else throw badArchive("unknown file type " + t
);
225 else if (s
== "contents" && type
== tpRegular
) {
226 parseContents(sink
, source
, path
);
229 else if (s
== "executable" && type
== tpRegular
) {
234 else if (s
== "entry" && type
== tpDirectory
) {
235 string name
, prevName
;
237 s
= readString(source
);
238 if (s
!= "(") throw badArchive("expected open tag");
243 s
= readString(source
);
247 } else if (s
== "name") {
248 name
= readString(source
);
249 if (name
.empty() || name
== "." || name
== ".." || name
.find('/') != string::npos
|| name
.find((char) 0) != string::npos
)
250 throw Error(format("NAR contains invalid file name `%1%'") % name
);
251 if (name
<= prevName
)
252 throw Error("NAR directory is not sorted");
255 auto i
= names
.find(name
);
256 if (i
!= names
.end()) {
257 printMsg(lvlDebug
, format("case collision between `%1%' and `%2%'") % i
->first
% name
);
258 name
+= caseHackSuffix
;
259 name
+= int2String(++i
->second
);
263 } else if (s
== "node") {
264 if (s
.empty()) throw badArchive("entry name missing");
265 parse(sink
, source
, path
+ "/" + name
);
267 throw badArchive("unknown field " + s
);
271 else if (s
== "target" && type
== tpSymlink
) {
272 string target
= readString(source
);
273 sink
.createSymlink(path
, target
);
277 throw badArchive("unknown field " + s
);
282 void parseDump(ParseSink
& sink
, Source
& source
)
286 version
= readString(source
);
287 } catch (SerialisationError
& e
) {
288 /* This generally means the integer at the start couldn't be
289 decoded. Ignore and throw the exception below. */
291 if (version
!= archiveVersion1
)
292 throw badArchive("input doesn't look like a Nix archive");
293 parse(sink
, source
, "");
297 struct RestoreSink
: ParseSink
302 void createDirectory(const Path
& path
)
304 Path p
= dstPath
+ path
;
305 if (mkdir(p
.c_str(), 0777) == -1)
306 throw SysError(format("creating directory `%1%'") % p
);
309 void createRegularFile(const Path
& path
)
311 Path p
= dstPath
+ path
;
313 fd
= open(p
.c_str(), O_CREAT
| O_EXCL
| O_WRONLY
, 0666);
314 if (fd
== -1) throw SysError(format("creating file `%1%'") % p
);
320 if (fstat(fd
, &st
) == -1)
321 throw SysError("fstat");
322 if (fchmod(fd
, st
.st_mode
| (S_IXUSR
| S_IXGRP
| S_IXOTH
)) == -1)
323 throw SysError("fchmod");
326 void preallocateContents(unsigned long long len
)
328 #if HAVE_POSIX_FALLOCATE
330 errno
= posix_fallocate(fd
, 0, len
);
331 /* Note that EINVAL may indicate that the underlying
332 filesystem doesn't support preallocation (e.g. on
333 OpenSolaris). Since preallocation is just an
334 optimisation, ignore it. */
335 if (errno
&& errno
!= EINVAL
)
336 throw SysError(format("preallocating file of %1% bytes") % len
);
341 void receiveContents(unsigned char * data
, unsigned int len
)
343 writeFull(fd
, data
, len
);
346 void createSymlink(const Path
& path
, const string
& target
)
348 Path p
= dstPath
+ path
;
349 nix::createSymlink(target
, p
);
354 void restorePath(const Path
& path
, Source
& source
)
358 parseDump(sink
, source
);