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 }