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 }