1 /** 2 * Copyright: Copyright Jason White, 2014-2016 3 * License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0). 4 * Authors: Jason White 5 */ 6 module io.file.flags; 7 8 public import io.stream : Access; 9 10 /** 11 * Specifies in what mode a file should be opened. These flags can be combined. 12 */ 13 enum Mode 14 { 15 /// Default mode. Not very useful. 16 none = 0, 17 18 /** 19 * Opens an existing file. Unless combined with create, fails if the file 20 * does not exist. 21 */ 22 open = 1 << 0, 23 24 /** 25 * Creates a new file. Fails if the file is opened without write access. 26 * Fails if the file already exists and not combined with truncate or open. 27 */ 28 create = 1 << 1, 29 30 /** 31 * Opens the file if it already exists or creates it if it does not. 32 */ 33 openOrCreate = open | create, 34 35 /** 36 * Allows only appending to the end of the file. Seek operations only affect 37 * subsequent reads. Upon writing, the file pointer gets set to the end of 38 * the file. Requires write access to the file. 39 */ 40 append = 1 << 2, 41 42 /** 43 * Truncates the file. This has no effect if the file has been created anew. 44 * Requires write access to the file. 45 */ 46 truncate = 1 << 3, 47 } 48 49 /** 50 * Specifies what other processes are allowed to do to the file. These flags can 51 * be combined. 52 * 53 * Windows_specific: 54 * Currently only used by Windows. 55 */ 56 enum Share 57 { 58 /// Forbids sharing of the file. 59 none = 0, 60 61 /// Allows others to read from the file. 62 read = 1 << 0, 63 64 /// Allows others to write to the file. 65 write = 1 << 1, 66 67 /// Allows others to either read or write to the file. 68 readWrite = read | write, 69 70 /// Allows the file to deleted. 71 remove = 1 << 2, 72 } 73 74 /** 75 * File flags that determine how a file stream is created and used. 76 * 77 * Since all methods in this struct are pure, the high-level configuration flags 78 * given here are converted to the platform-specific file flags at compile time. 79 * 80 * See_Also: 81 * $(D io.file.stream) 82 * 83 * Example: 84 * --- 85 * // Creates the file "foobar", truncates it, and opens it in write-only mode 86 * auto f = File("foobar", FileFlags.writeEmpty); 87 * --- 88 */ 89 struct FileFlags 90 { 91 static immutable 92 { 93 /** 94 * An existing file is opened with read access. This is likely the most 95 * commonly used set of flags. 96 */ 97 FileFlags readExisting = FileFlags(Mode.open, Access.read); 98 99 /** 100 * An existing file is opened with write access. 101 */ 102 FileFlags writeExisting = FileFlags(Mode.open, Access.write); 103 104 /** 105 * A new file is created with write access. Fails if the file already 106 * exists. 107 */ 108 FileFlags writeNew = FileFlags(Mode.create, Access.write); 109 110 /** 111 * A new file is either opened or created with write access. 112 */ 113 FileFlags writeAlways = FileFlags(Mode.openOrCreate, Access.write); 114 115 /** 116 * A new file is either opened or created, truncated if necessary, with 117 * write access. This ensures that an $(I empty) file is opened. 118 */ 119 FileFlags writeEmpty = FileFlags(Mode.openOrCreate | Mode.truncate, Access.write); 120 121 /** 122 * An existing file is opened with read/write access. 123 */ 124 FileFlags readWriteExisting = FileFlags(Mode.open, Access.readWrite); 125 126 /** 127 * A new file is created with read/write access. Fails if the file 128 * already exists. 129 */ 130 FileFlags readWriteNew = FileFlags(Mode.create, Access.readWrite); 131 132 /** 133 * A new file is either opened or created with read/write access. 134 */ 135 FileFlags readWriteAlways = FileFlags(Mode.openOrCreate, Access.readWrite); 136 137 /** 138 * A new file is either opened or created, truncated if necessary, with 139 * read/write access. This ensures that an $(I empty) file is opened. 140 */ 141 FileFlags readWriteEmpty = FileFlags(Mode.openOrCreate | Mode.truncate, Access.readWrite); 142 } 143 144 version (Posix) 145 { 146 import core.sys.posix.fcntl; 147 148 int flags; 149 150 alias flags this; 151 152 /** 153 * Constructs the $(D FileFlag) with the given mode, access, and share 154 * attributes. These high-level flags are converted to the 155 * equivalent platform-specific flags that are needed when opening the 156 * file. 157 * 158 * Params: 159 * mode = Mode the file should be opened in. That is, to open, create, 160 * append, or truncate the file. 161 * access = The permissions on the file stream. That is, read access, 162 * write access, or both. 163 * share = The sharing permissions other processes are allowed on the 164 * file stream when trying to open the same file. By default, 165 * other processes are prevented from opening the file if they 166 * request read, write, or delete access. If you wish to allow 167 * other processes to read the file while it is open in this 168 * process, set this to $(D Share.read). Currently only used 169 * by Windows. 170 * 171 * Windows_specific: 172 * The share parameter is currently only used by Windows. 173 * 174 * Example: 175 * --- 176 * immutable flags = FileFlags(Mode.open, Access.read); 177 * --- 178 */ 179 this(Mode mode, 180 Access access, 181 Share share = Share.init) pure nothrow 182 { 183 // Disable buffering. Buffering is handled by $(D io.buffered). 184 //flags |= O_DIRECT; // FIXME: O_DIRECT is not defined 185 186 if ((mode & Mode.openOrCreate) == Mode.openOrCreate) 187 flags |= O_CREAT; 188 else if (mode & Mode.create) 189 flags |= O_EXCL | O_CREAT; 190 // Mode.open by default 191 192 if (mode & Mode.truncate) 193 flags |= O_TRUNC; 194 195 if (mode & Mode.append) 196 flags |= O_APPEND; 197 198 if (access == Access.readWrite) 199 flags |= O_RDWR; 200 else if (access & Access.read) 201 flags |= O_RDONLY; 202 else if (access & Access.write) 203 flags |= O_WRONLY; 204 205 // Posix does not support Share flags. 206 } 207 208 unittest 209 { 210 with (FileFlags) 211 { 212 static assert(readExisting.flags == O_RDONLY); 213 static assert(writeExisting.flags == O_WRONLY); 214 static assert(writeNew.flags == (O_EXCL | O_CREAT | O_WRONLY)); 215 static assert(writeAlways.flags == (O_CREAT | O_WRONLY)); 216 static assert(writeEmpty.flags == (O_CREAT | O_TRUNC | O_WRONLY)); 217 static assert(readWriteExisting.flags == O_RDWR); 218 static assert(readWriteNew.flags == (O_CREAT | O_EXCL | O_RDWR)); 219 static assert(readWriteAlways.flags == (O_CREAT | O_RDWR)); 220 static assert(readWriteEmpty.flags == (O_CREAT | O_RDWR | O_TRUNC)); 221 } 222 } 223 } 224 else version (Windows) 225 { 226 import core.sys.windows.windows; 227 228 DWORD access, share, mode; 229 230 this(Mode mode, 231 Access access, 232 Share share = Share.init) pure nothrow 233 { 234 // Access flags 235 if (access & Access.read) this.access |= GENERIC_READ; 236 if (access & Access.write) this.access |= GENERIC_WRITE; 237 if (mode & Mode.append) this.access |= FILE_APPEND_DATA; 238 239 // Share flags 240 if (share & Share.read) this.share |= FILE_SHARE_READ; 241 if (share & Share.write) this.share |= FILE_SHARE_WRITE; 242 if (share & Share.remove) this.share |= FILE_SHARE_DELETE; 243 244 // Creation flags 245 if (mode & Mode.truncate) 246 { 247 if (mode & Mode.create) 248 this.mode = CREATE_ALWAYS; 249 else 250 this.mode = TRUNCATE_EXISTING; 251 } 252 else if ((mode & Mode.openOrCreate) == Mode.openOrCreate) 253 this.mode = OPEN_ALWAYS; 254 else if (mode & Mode.open) 255 this.mode = OPEN_EXISTING; 256 else if (mode & Mode.create) 257 this.mode = CREATE_NEW; 258 } 259 } 260 261 /** 262 * Constructs the file flags from a mode string. 263 * 264 * This simply calls $(LREF FileFlags.parse). 265 * 266 * See_Also: 267 * $(LREF FileFlags.parse) 268 */ 269 this(string mode) pure 270 { 271 this = parse(mode); 272 } 273 274 /// Ditto 275 void opAssign(string mode) pure 276 { 277 this = parse(mode); 278 } 279 280 /// 281 unittest 282 { 283 FileFlags ff = "wb+"; 284 assert(ff == FileFlags.readWriteEmpty); 285 assert(ff == FileFlags("wb+")); 286 } 287 288 /** 289 * Parses an $(D fopen)-style mode string such as "r+". All possible mode 290 * strings include: 291 * 292 * $(TABLE 293 * $(TR $(TH Mode String) $(TH Meaning)) 294 * $(TR $(TD $(D "wb")) $(TD Write truncated)) 295 * $(TR $(TD $(D "wb+")) $(TD Read/write truncated)) 296 * $(TR $(TD $(D "w+b")) $(TD Read/write truncated)) 297 * $(TR $(TD $(D "wbx")) $(TD Write new)) 298 * $(TR $(TD $(D "wb+x")) $(TD Read/write new)) 299 * $(TR $(TD $(D "w+bx")) $(TD Read/write new)) 300 * $(TR $(TD $(D "rb")) $(TD Read existing")) 301 * $(TR $(TD $(D "rb+")) $(TD Read/write existing")) 302 * $(TR $(TD $(D "r+b")) $(TD Read/write existing")) 303 * $(TR $(TD $(D "ab")) $(TD Append new")) 304 * $(TR $(TD $(D "ab+")) $(TD Append/read new")) 305 * $(TR $(TD $(D "a+b")) $(TD Append/read new")) 306 * ) 307 * 308 * The _mode strings accepted here differ from those accepted by $(D fopen). 309 * Here, file streams are never opened in text _mode -- only binary mode. 310 * Text handling functionality is built on top of low-level file streams. 311 * It does not make sense to distinguish between text and binary modes here. 312 * $(D fopen) opens all files in text _mode by default and the flag 'b' must 313 * be specified in order to open in binary _mode. Thus, an exception is 314 * thrown here if 'b' is omitted in the specified mode string. 315 * 316 * Note: It is not advisable to use fopen-style _mode strings. It is better 317 * to use one of the predefined file flag configurations such as 318 * $(LREF FileFlags.readExisting) for greater readability and intent of 319 * meaning. 320 */ 321 static FileFlags parse(string mode) pure 322 { 323 // There are only 12 possible permutations of mode strings that we care 324 // about. (There would be twice as many if the 'b' flag was optional.) 325 // Thus, it is easier to just check for all 12 possible flags rather 326 // than actually parsing the string. 327 switch (mode) 328 { 329 case "wb": return FileFlags.writeEmpty; 330 case "wb+": return FileFlags.readWriteEmpty; 331 case "w+b": return FileFlags.readWriteEmpty; 332 case "wbx": return FileFlags.writeNew; 333 case "wb+x": return FileFlags.readWriteNew; 334 case "w+bx": return FileFlags.readWriteNew; 335 case "rb": return FileFlags.readExisting; 336 case "rb+": return FileFlags.readWriteExisting; 337 case "r+b": return FileFlags.readWriteExisting; 338 case "ab": return FileFlags(Mode.openOrCreate | Mode.append, Access.write); 339 case "ab+": return FileFlags(Mode.openOrCreate | Mode.append, Access.readWrite); 340 case "a+b": return FileFlags(Mode.openOrCreate | Mode.append, Access.readWrite); 341 default: 342 throw new Exception("Invalid mode string"); 343 } 344 } 345 346 /// 347 unittest 348 { 349 static assert(FileFlags("wb") == FileFlags.writeEmpty); 350 static assert(FileFlags("wb+") == FileFlags.readWriteEmpty); 351 static assert(FileFlags("w+b") == FileFlags.readWriteEmpty); 352 static assert(FileFlags("wbx") == FileFlags.writeNew); 353 static assert(FileFlags("wb+x") == FileFlags.readWriteNew); 354 static assert(FileFlags("w+bx") == FileFlags.readWriteNew); 355 static assert(FileFlags("rb") == FileFlags.readExisting); 356 static assert(FileFlags("rb+") == FileFlags.readWriteExisting); 357 static assert(FileFlags("r+b") == FileFlags.readWriteExisting); 358 static assert(FileFlags("ab") == FileFlags(Mode.openOrCreate | Mode.append, Access.write)); 359 static assert(FileFlags("ab+") == FileFlags(Mode.openOrCreate | Mode.append, Access.readWrite)); 360 static assert(FileFlags("a+b") == FileFlags(Mode.openOrCreate | Mode.append, Access.readWrite)); 361 } 362 363 unittest 364 { 365 import std.exception : collectException; 366 367 immutable badModes = ["", "rw", "asdf", "+r", "b+", " r", "r+b "]; 368 foreach (m; badModes) 369 assert(collectException(FileFlags(m))); 370 } 371 }