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