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 }