1 /**
2 	File handling.
3 
4 	Copyright: © 2012 rejectedsoftware e.K.
5 	License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
6 	Authors: Sönke Ludwig
7 */
8 module dub.internal.vibecompat.core.file;
9 
10 public import dub.internal.vibecompat.inet.path;
11 
12 import dub.internal.logging;
13 
14 import std.conv;
15 import core.stdc.stdio;
16 import std.datetime;
17 import std.exception;
18 import std.file;
19 import std.path;
20 import std.stdio;
21 import std.string;
22 import std.utf;
23 
24 
25 /// Writes `buffer` to a file
26 public void writeFile(NativePath path, const void[] buffer)
27 {
28 	std.file.write(path.toNativeString(), buffer);
29 }
30 
31 /// Returns the content of a file
32 public ubyte[] readFile(NativePath path)
33 {
34 	return cast(ubyte[]) std.file.read(path.toNativeString());
35 }
36 
37 /**
38 	Moves or renames a file.
39 */
40 void moveFile(NativePath from, NativePath to)
41 {
42 	moveFile(from.toNativeString(), to.toNativeString());
43 }
44 /// ditto
45 void moveFile(string from, string to)
46 {
47 	std.file.rename(from, to);
48 }
49 
50 /**
51 	Copies a file.
52 
53 	Note that attributes and time stamps are currently not retained.
54 
55 	Params:
56 		from = NativePath of the source file
57 		to = NativePath for the destination file
58 		overwrite = If true, any file existing at the destination path will be
59 			overwritten. If this is false, an exception will be thrown should
60 			a file already exist at the destination path.
61 
62 	Throws:
63 		An Exception if the copy operation fails for some reason.
64 */
65 void copyFile(NativePath from, NativePath to, bool overwrite = false)
66 {
67 	enforce(existsFile(from), "Source file does not exist.");
68 
69 	if (existsFile(to)) {
70 		enforce(overwrite, "Destination file already exists.");
71 		// remove file before copy to allow "overwriting" files that are in
72 		// use on Linux
73 		removeFile(to);
74 	}
75 
76 	static if (is(PreserveAttributes))
77 	{
78 		.copy(from.toNativeString(), to.toNativeString(), PreserveAttributes.yes);
79 	}
80 	else
81 	{
82 		.copy(from.toNativeString(), to.toNativeString());
83 		// try to preserve ownership/permissions in Posix
84 		version (Posix) {
85 			import core.sys.posix.sys.stat;
86 			import core.sys.posix.unistd;
87 			import std.utf;
88 			auto cspath = toUTFz!(const(char)*)(from.toNativeString());
89 			auto cdpath = toUTFz!(const(char)*)(to.toNativeString());
90 			stat_t st;
91 			enforce(stat(cspath, &st) == 0, "Failed to get attributes of source file.");
92 			if (chown(cdpath, st.st_uid, st.st_gid) != 0)
93 				st.st_mode &= ~(S_ISUID | S_ISGID);
94 			chmod(cdpath, st.st_mode);
95 		}
96 	}
97 }
98 /// ditto
99 void copyFile(string from, string to)
100 {
101 	copyFile(NativePath(from), NativePath(to));
102 }
103 
104 version (Windows) extern(Windows) int CreateHardLinkW(in wchar* to, in wchar* from, void* attr=null);
105 
106 // guess whether 2 files are identical, ignores filename and content
107 private bool sameFile(NativePath a, NativePath b)
108 {
109 	version (Posix) {
110 		auto st_a = std.file.DirEntry(a.toNativeString).statBuf;
111 		auto st_b = std.file.DirEntry(b.toNativeString).statBuf;
112 		return st_a == st_b;
113 	} else {
114 		static assert(__traits(allMembers, FileInfo)[0] == "name");
115 		return getFileInfo(a).tupleof[1 .. $] == getFileInfo(b).tupleof[1 .. $];
116 	}
117 }
118 
119 private bool isWritable(NativePath name)
120 {
121 	version (Windows)
122 	{
123 		import core.sys.windows.windows;
124 
125 		return (name.toNativeString.getAttributes & FILE_ATTRIBUTE_READONLY) == 0;
126 	}
127 	else version (Posix)
128 	{
129 		import core.sys.posix.sys.stat;
130 
131 		return (name.toNativeString.getAttributes & S_IWUSR) != 0;
132 	}
133 	else
134 		static assert(false, "Needs implementation.");
135 }
136 
137 private void makeWritable(NativePath name)
138 {
139 	makeWritable(name.toNativeString);
140 }
141 
142 private void makeWritable(string name)
143 {
144 	version (Windows)
145 	{
146 		import core.sys.windows.windows;
147 
148 		name.setAttributes(name.getAttributes & ~FILE_ATTRIBUTE_READONLY);
149 	}
150 	else version (Posix)
151 	{
152 		import core.sys.posix.sys.stat;
153 
154 		name.setAttributes(name.getAttributes | S_IWUSR);
155 	}
156 	else
157 		static assert(false, "Needs implementation.");
158 }
159 
160 /**
161 	Creates a hardlink if possible, a copy otherwise.
162 
163 	If `from` is read-only and `overwrite` is true, then a copy is made instead
164 	and `to` is made writable; so that repeating the command will not fail.
165 */
166 void hardLinkFile(NativePath from, NativePath to, bool overwrite = false)
167 {
168 	if (existsFile(to)) {
169 		enforce(overwrite, "Destination file already exists.");
170 		if (auto fe = collectException!FileException(removeFile(to))) {
171 			if (sameFile(from, to)) return;
172 			throw fe;
173 		}
174 	}
175 	const writeAccessChangeRequired = overwrite && !isWritable(from);
176 	if (!writeAccessChangeRequired)
177 	{
178 		version (Windows)
179 		{
180 			alias cstr = toUTFz!(const(wchar)*);
181 			if (CreateHardLinkW(cstr(to.toNativeString), cstr(from.toNativeString)))
182 				return;
183 		}
184 		else
185 		{
186 			import core.sys.posix.unistd : link;
187 			alias cstr = toUTFz!(const(char)*);
188 			if (!link(cstr(from.toNativeString), cstr(to.toNativeString)))
189 				return;
190 		}
191 	}
192 	// fallback to copy
193 	copyFile(from, to, overwrite);
194 	if (writeAccessChangeRequired)
195 		to.makeWritable;
196 }
197 
198 /**
199 	Removes a file
200 */
201 void removeFile(NativePath path)
202 {
203 	removeFile(path.toNativeString());
204 }
205 /// ditto
206 void removeFile(string path) {
207 	std.file.remove(path);
208 }
209 
210 /**
211 	Checks if a file exists
212 */
213 bool existsFile(NativePath path) {
214 	return existsFile(path.toNativeString());
215 }
216 /// ditto
217 bool existsFile(string path)
218 {
219 	return std.file.exists(path);
220 }
221 
222 /// Checks if a directory exists
223 bool existsDirectory(NativePath path) {
224 	if( !existsFile(path) ) return false;
225 	auto fi = getFileInfo(path);
226 	return fi.isDirectory;
227 }
228 
229 /** Stores information about the specified file/directory into 'info'
230 
231 	Returns false if the file does not exist.
232 */
233 FileInfo getFileInfo(NativePath path)
234 {
235 	auto ent = std.file.DirEntry(path.toNativeString());
236 	return makeFileInfo(ent);
237 }
238 /// ditto
239 FileInfo getFileInfo(string path)
240 {
241 	return getFileInfo(NativePath(path));
242 }
243 
244 /**
245 	Creates a new directory.
246 */
247 void ensureDirectory(NativePath path)
248 {
249 	if (!existsDirectory(path))
250 		mkdirRecurse(path.toNativeString());
251 }
252 
253 /**
254 	Enumerates all files in the specified directory.
255 */
256 void listDirectory(NativePath path, scope bool delegate(FileInfo info) del)
257 {
258 	foreach( DirEntry ent; dirEntries(path.toNativeString(), SpanMode.shallow) )
259 		if( !del(makeFileInfo(ent)) )
260 			break;
261 }
262 /// ditto
263 void listDirectory(string path, scope bool delegate(FileInfo info) del)
264 {
265 	listDirectory(NativePath(path), del);
266 }
267 /// ditto
268 int delegate(scope int delegate(ref FileInfo)) iterateDirectory(NativePath path)
269 {
270 	int iterator(scope int delegate(ref FileInfo) del){
271 		int ret = 0;
272 		listDirectory(path, (fi){
273 			ret = del(fi);
274 			return ret == 0;
275 		});
276 		return ret;
277 	}
278 	return &iterator;
279 }
280 /// ditto
281 int delegate(scope int delegate(ref FileInfo)) iterateDirectory(string path)
282 {
283 	return iterateDirectory(NativePath(path));
284 }
285 
286 
287 /**
288 	Returns the current working directory.
289 */
290 NativePath getWorkingDirectory()
291 {
292 	return NativePath(std.file.getcwd());
293 }
294 
295 
296 /** Contains general information about a file.
297 */
298 struct FileInfo {
299 	/// Name of the file (not including the path)
300 	string name;
301 
302 	/// Size of the file (zero for directories)
303 	ulong size;
304 
305 	/// Time of the last modification
306 	SysTime timeModified;
307 
308 	/// Time of creation (not available on all operating systems/file systems)
309 	SysTime timeCreated;
310 
311 	/// True if this is a symlink to an actual file
312 	bool isSymlink;
313 
314 	/// True if this is a directory or a symlink pointing to a directory
315 	bool isDirectory;
316 }
317 
318 /**
319 	Specifies how a file is manipulated on disk.
320 */
321 enum FileMode {
322 	/// The file is opened read-only.
323 	read,
324 	/// The file is opened for read-write random access.
325 	readWrite,
326 	/// The file is truncated if it exists and created otherwise and the opened for read-write access.
327 	createTrunc,
328 	/// The file is opened for appending data to it and created if it does not exist.
329 	append
330 }
331 
332 /**
333 	Accesses the contents of a file as a stream.
334 */
335 
336 private FileInfo makeFileInfo(DirEntry ent)
337 {
338 	FileInfo ret;
339 	ret.name = baseName(ent.name);
340 	if( ret.name.length == 0 ) ret.name = ent.name;
341 	assert(ret.name.length > 0);
342 	ret.isSymlink = ent.isSymlink;
343 	try {
344 		ret.isDirectory = ent.isDir;
345 		ret.size = ent.size;
346 		ret.timeModified = ent.timeLastModified;
347 		version(Windows) ret.timeCreated = ent.timeCreated;
348 		else ret.timeCreated = ent.timeLastModified;
349 	} catch (Exception e) {
350 		logDiagnostic("Failed to get extended file information for %s: %s", ret.name, e.msg);
351 	}
352 	return ret;
353 }