1 /** 2 * Open $(B std.stdio.File) using a set of symbolic constants as a file access mode. 3 * Authors: 4 * $(LINK2 https://github.com/FreeSlave, Roman Chistokhodov) 5 * Copyright: 6 * Roman Chistokhodov, 2020 7 * License: 8 * $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0). 9 */ 10 11 module openfile; 12 13 public import std.stdio : File; 14 15 /// Flags for file open mode. 16 enum OpenMode 17 { 18 /// Open in read mode. 19 read = 1 << 0, 20 /** 21 * Open in write mode, dont' truncate. 22 * Create a file if `existingOnly` flag is not provided. 23 */ 24 update = 1 << 1, 25 /** 26 * Open in write mode, truncate if exists. 27 * Create a file if `existingOnly` flag is not provided. 28 */ 29 truncate = 1 << 2, 30 /** 31 * Open in write mode. Append to the end on writing. 32 * Create a file if `existingOnly` flag is not provided. 33 * 34 * Note that it has a special meaning on Posix: 35 * the file opened in append mode will have write operations 36 * happening at the end of the file regardless of manual seek position changing. 37 */ 38 append = 1 << 3, 39 /** 40 * Open in write mode. Create file only if it does not exist, error otherwise. 41 * The check for existence and the file creation is an atomical operation. 42 * Use this flag when need to ensure that the file with such name did not exist. 43 */ 44 createNew = 1 << 4, 45 /** 46 * Don't create file if it does not exist when opening in write mode. 47 * This flag is not necessary when opening a file in read-only mode. 48 * This flag can't be used together with `OpenMode.createNew` flag. 49 */ 50 existingOnly = 1 << 5, 51 } 52 53 private bool hasAnyWriteFlag(OpenMode openMode) @safe nothrow pure @nogc 54 { 55 with(OpenMode) return (openMode & (update | truncate | append | createNew)) != 0; 56 } 57 58 private string modezFromOpenMode(OpenMode openMode) @safe 59 { 60 const bool anyWrite = hasAnyWriteFlag(openMode); 61 if (openMode & OpenMode.read) 62 { 63 if (anyWrite) 64 { 65 if (openMode & OpenMode.append) 66 return "a+"; 67 else if (openMode & OpenMode.existingOnly) 68 return "r+"; 69 else 70 return "w+"; 71 } 72 else return "r"; 73 } 74 else 75 { 76 assert(anyWrite); 77 if (openMode & OpenMode.append) 78 return "a"; 79 else 80 return "w"; 81 } 82 } 83 84 version(Posix) 85 { 86 private: 87 import core.sys.posix.fcntl : O_RDWR, O_RDONLY, O_WRONLY, O_TRUNC, O_APPEND, O_EXCL, O_CREAT, mode_t; 88 89 mode_t unixModeFromOpenMode(OpenMode openMode) @safe @nogc nothrow pure 90 { 91 mode_t mode; 92 const bool anyWrite = hasAnyWriteFlag(openMode); 93 const bool existing = (openMode & OpenMode.existingOnly) != 0; 94 const bool hasRead = (openMode & OpenMode.read) != 0; 95 if (hasRead && anyWrite) 96 mode |= O_RDWR; 97 else if (hasRead) 98 mode |= O_RDONLY; 99 else if (anyWrite) 100 mode |= O_WRONLY; 101 102 if (anyWrite && !existing) 103 mode |= O_CREAT; 104 105 if (openMode & OpenMode.truncate) 106 mode |= O_TRUNC; 107 if (openMode & OpenMode.append) 108 mode |= O_APPEND; 109 if (openMode & OpenMode.createNew) 110 mode |= O_EXCL; 111 return mode; 112 } 113 114 unittest 115 { 116 assert(unixModeFromOpenMode(OpenMode.read | OpenMode.truncate) == (O_RDWR | O_TRUNC | O_CREAT)); 117 } 118 119 int openFd(string name, OpenMode openMode) @trusted 120 { 121 import std.exception : errnoEnforce; 122 import std.internal.cstring : tempCString; 123 import std.conv : octal; 124 static import core.sys.posix.fcntl; 125 126 mode_t mode = unixModeFromOpenMode(openMode); 127 128 auto namez = name.tempCString(); 129 int fd; 130 if (mode & O_CREAT) 131 fd = core.sys.posix.fcntl.open(namez, mode, octal!666); 132 else 133 fd = core.sys.posix.fcntl.open(namez, mode); 134 errnoEnforce(fd >= 0); 135 return fd; 136 } 137 } 138 139 version(Windows) 140 { 141 private: 142 import core.sys.windows.core : HANDLE; 143 144 HANDLE openHandle(string name, OpenMode openMode) @trusted 145 { 146 import std.utf : toUTF16z; 147 import std.windows.syserror : wenforce; 148 import core.sys.windows.core : FILE_ATTRIBUTE_NORMAL, FILE_FLAG_SEQUENTIAL_SCAN, 149 GENERIC_READ, GENERIC_WRITE, FILE_SHARE_READ, FILE_SHARE_WRITE, 150 TRUNCATE_EXISTING, OPEN_EXISTING, CREATE_ALWAYS, 151 CREATE_NEW, OPEN_ALWAYS, INVALID_HANDLE_VALUE, 152 FILE_END, INVALID_SET_FILE_POINTER, DWORD, 153 CreateFileW, SetFilePointer; 154 155 auto namez = name.toUTF16z; 156 const bool anyWrite = hasAnyWriteFlag(openMode); 157 const bool existing = (openMode & OpenMode.existingOnly) != 0; 158 const bool hasRead = (openMode & OpenMode.read) != 0; 159 160 DWORD desiredAccess = 0; 161 DWORD shareMode = 0; 162 DWORD creationDisposition = 0; 163 DWORD flags = FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN; 164 165 if (hasRead) 166 { 167 desiredAccess |= GENERIC_READ; 168 shareMode |= FILE_SHARE_READ; 169 } 170 if (anyWrite) 171 { 172 desiredAccess |= GENERIC_WRITE; 173 shareMode |= FILE_SHARE_WRITE; 174 } 175 176 if ((hasRead && !anyWrite) || existing) 177 { 178 if (openMode & OpenMode.truncate) 179 creationDisposition = TRUNCATE_EXISTING; 180 else 181 creationDisposition = OPEN_EXISTING; 182 } 183 else if (openMode & OpenMode.createNew) 184 { 185 creationDisposition = CREATE_NEW; 186 } 187 else if (openMode & OpenMode.truncate) 188 { 189 creationDisposition = CREATE_ALWAYS; 190 } 191 else if (anyWrite) 192 { 193 creationDisposition = OPEN_ALWAYS; 194 } 195 HANDLE h = CreateFileW(namez, desiredAccess, shareMode, null, creationDisposition, flags, HANDLE.init); 196 wenforce(h != INVALID_HANDLE_VALUE, name); 197 if (openMode & OpenMode.append) 198 wenforce(SetFilePointer(h, 0, null, FILE_END) != INVALID_SET_FILE_POINTER, name); 199 return h; 200 } 201 } 202 203 /** 204 * Open file using name and symbolic access mode. 205 * See_Also: $(D openfile.sopen) 206 */ 207 File openFile(string name, OpenMode mode) @trusted 208 { 209 import std.exception : enforce; 210 enforce((mode & OpenMode.read) != 0 || hasAnyWriteFlag(mode), 211 "read flag or some of write flags must be provided in open mode"); 212 enforce(!((mode & OpenMode.createNew) != 0 && (mode & OpenMode.existingOnly) != 0), 213 "createNew and existingOnly can't be used together in open mode"); 214 215 version(Posix) 216 { 217 import core.sys.posix.unistd : close; 218 int fd = openFd(name, mode); 219 scope(failure) close(fd); 220 File file; 221 file.fdopen(fd, modezFromOpenMode(mode)); 222 return file; 223 } 224 else version(Windows) 225 { 226 import core.sys.windows.core : CloseHandle; 227 HANDLE handle = openHandle(name, mode); 228 scope(failure) CloseHandle(handle); 229 File file; 230 file.windowsHandleOpen(handle, modezFromOpenMode(mode)); 231 return file; 232 } 233 } 234 235 /// Open file using name and symbolic access mode. Convenient function for UFCS. Calls $(B std.stdio.detach) before assigning a new file handle. 236 void sopen(ref scope File file, string name, OpenMode mode) @safe 237 { 238 file.detach(); 239 file = openFile(name, mode); 240 } 241 242 /// 243 unittest 244 { 245 static import std.file; 246 import std.path : buildPath; 247 import std.exception : assertThrown; 248 import std.process : thisProcessID; 249 import std.conv : to; 250 251 auto deleteme = buildPath(std.file.tempDir(), "deleteme.openfile.unittest.pid" ~ to!string(thisProcessID)); 252 scope(exit) std.file.remove(deleteme); 253 254 // bad set of flags 255 assertThrown(openFile(deleteme, OpenMode.createNew | OpenMode.existingOnly)); 256 assertThrown(openFile(deleteme, OpenMode.existingOnly)); 257 258 // opening non-existent file 259 assertThrown(openFile(deleteme, OpenMode.read)); 260 assertThrown(openFile(deleteme, OpenMode.update | OpenMode.existingOnly)); 261 262 File f = openFile(deleteme, OpenMode.read | OpenMode.truncate | OpenMode.createNew); 263 f.write("Hello"); 264 f.rewind(); 265 assert(f.readln() == "Hello"); 266 267 assertThrown(openFile(deleteme, OpenMode.createNew)); 268 269 f.sopen(deleteme, OpenMode.append | OpenMode.existingOnly); 270 f.write(" world"); 271 272 f.sopen(deleteme, OpenMode.update | OpenMode.existingOnly); 273 f.seek(6); 274 f.write("sco"); 275 276 f.sopen(deleteme, OpenMode.read); 277 assert(f.readln() == "Hello scold"); 278 279 f.sopen(deleteme, OpenMode.read | OpenMode.update | OpenMode.existingOnly); 280 f.write("Yo"); 281 f.rewind(); 282 assert(f.readln() == "Yollo scold"); 283 284 f.sopen(deleteme, OpenMode.read | OpenMode.append | OpenMode.existingOnly); 285 f.write("ing"); 286 f.rewind(); 287 assert(f.readln() == "Yollo scolding"); 288 289 auto deleteme2 = buildPath(std.file.tempDir(), "deleteme2.openfile.unittest.pid" ~ to!string(thisProcessID)); 290 scope(exit) std.file.remove(deleteme2); 291 292 assertThrown(f.sopen(deleteme2, OpenMode.truncate | OpenMode.existingOnly)); 293 294 f.sopen(deleteme2, OpenMode.read | OpenMode.update | OpenMode.createNew); 295 f.write("baz"); 296 f.rewind(); 297 assert(f.readln() == "baz"); 298 f.seek(3); 299 f.write("bar"); 300 f.rewind(); 301 assert(f.readln() == "bazbar"); 302 303 f.sopen(deleteme2, OpenMode.read | OpenMode.truncate | OpenMode.existingOnly); 304 f.write("some"); 305 f.rewind(); 306 assert(f.readln() == "some"); 307 308 f.close(); 309 }