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