1 /* Example of Outcome used with constructors
2 (C) 2017-2019 Niall Douglas <http://www.nedproductions.biz/> (5 commits)
3 
4 
5 Boost Software License - Version 1.0 - August 17th, 2003
6 
7 Permission is hereby granted, free of charge, to any person or organization
8 obtaining a copy of the software and accompanying documentation covered by
9 this license (the "Software") to use, reproduce, display, distribute,
10 execute, and transmit the Software, and to prepare derivative works of the
11 Software, and to permit third-parties to whom the Software is furnished to
12 do so, all subject to the following:
13 
14 The copyright notices in the Software and this entire statement, including
15 the above license grant, this restriction and the following disclaimer,
16 must be included in all copies of the Software, in whole or in part, and
17 all derivative works of the Software, unless such copies or derivative
18 works are solely in the form of machine-executable object code generated by
19 a source language processor.
20 
21 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
24 SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
25 FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
26 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27 DEALINGS IN THE SOFTWARE.
28 */
29 
30 #ifdef _WIN32
31 #define _CRT_SECURE_NO_WARNINGS
32 #define _CRT_NONSTDC_NO_WARNINGS
33 #include <fcntl.h>
34 #include <io.h>
35 #else
36 #define file_handle linux_file_handle
37 #include <fcntl.h>
38 #include <unistd.h>
39 #undef file_handle
40 #endif
41 #include <cstring>  // for strerror
42 #include <sys/stat.h>
43 #include <sys/types.h>
44 
45 #include "../../../include/boost/outcome.hpp"
46 #include <experimental/filesystem>
47 
48 namespace outcome = BOOST_OUTCOME_V2_NAMESPACE;
49 namespace filesystem = std::experimental::filesystem;
50 
51 //! [file_handle]
52 class file_handle
53 {
54   int _fd{-1};  // file descriptor
55   struct stat _stat
56   {
57     0
58   };  // stat of the fd at open
59 
60   // Phase 1 private constexpr constructor
file_handle()61   constexpr file_handle() {}
62 
63 public:
64   using path_type = filesystem::path;
65 
66   //! The behaviour of the handle: does it read, read and write, or atomic append?
67   enum class mode : unsigned char  // bit 0 set means writable
68   {
69     unchanged = 0,
70     none = 2,        //!< No ability to read or write anything, but can synchronise (SYNCHRONIZE or 0)
71     attr_read = 4,   //!< Ability to read attributes (FILE_READ_ATTRIBUTES|SYNCHRONIZE or O_RDONLY)
72     attr_write = 5,  //!< Ability to read and write attributes (FILE_READ_ATTRIBUTES|FILE_WRITE_ATTRIBUTES|SYNCHRONIZE or O_RDONLY)
73     read = 6,        //!< Ability to read (READ_CONTROL|FILE_READ_DATA|FILE_READ_ATTRIBUTES|FILE_READ_EA|SYNCHRONISE or O_RDONLY)
74     write = 7,       //!< Ability to read and write (READ_CONTROL|FILE_READ_DATA|FILE_READ_ATTRIBUTES|FILE_READ_EA|FILE_WRITE_DATA|FILE_WRITE_ATTRIBUTES|FILE_WRITE_EA|FILE_APPEND_DATA|SYNCHRONISE or O_RDWR)
75     append = 9       //!< All mainstream OSs and CIFS guarantee this is atomic with respect to all other appenders (FILE_APPEND_DATA|SYNCHRONISE or O_APPEND)
76   };
77 
78   // Moves but not copies permitted
79   file_handle(const file_handle &) = delete;
file_handle(file_handle && o)80   file_handle(file_handle &&o) noexcept : _fd(o._fd) { o._fd = -1; }
81   file_handle &operator=(const file_handle &) = delete;
operator =(file_handle && o)82   file_handle &operator=(file_handle &&o) noexcept
83   {
84     this->~file_handle();
85     new(this) file_handle(std::move(o));
86     return *this;
87   }
88   // Destruction closes the handle
~file_handle()89   ~file_handle()
90   {
91     if(_fd != -1)
92     {
93       if(-1 == ::close(_fd))
94       {
95         int e = errno;
96         std::cerr << "FATAL: Closing the fd during destruction failed due to " << strerror(e) << std::endl;
97         std::terminate();
98       }
99       _fd = -1;
100     }
101   }
102 
103   // Phase 2 static member constructor function, which cannot throw
104   static inline outcome::result<file_handle> file(path_type path, mode mode = mode::read) noexcept;
105 };
106 //! [file_handle]
107 
108 //! [file]
109 // Phase 2 static member constructor function, which cannot throw
file(file_handle::path_type path,file_handle::mode mode)110 inline outcome::result<file_handle> file_handle::file(file_handle::path_type path, file_handle::mode mode) noexcept
111 {
112   // Perform phase 1 of object construction
113   file_handle ret;
114 
115   // Perform phase 2 of object construction
116   int flags = 0;
117   switch(mode)
118   {
119   case mode::attr_read:
120   case mode::read:
121     flags = O_RDONLY;
122     break;
123   case mode::attr_write:
124   case mode::write:
125     flags = O_RDWR;
126     break;
127   case mode::append:
128     flags = O_APPEND;
129     break;
130   default:
131     return std::errc::invalid_argument;
132   }
133   ret._fd = ::open(path.u8string().c_str(), flags);
134   if(-1 == ret._fd)
135   {
136     // Note that if we bail out here, ~file_handle() will correctly not call ::close()
137     return {errno, std::system_category()};
138   }
139   if(-1 == ::fstat(ret._fd, &ret._stat))
140   {
141     // Note that if we bail out here, ~file_handle() will correctly call ::close()
142     return {errno, std::system_category()};
143   }
144 
145   // Returning ret directly is an area full of compiler specific behaviour quirks,
146   // so be explicit by wrapping into an initialiser list with embedded move.
147   return {std::move(ret)};
148 }
149 //! [file]
150 
151 //! [construct-declaration]
152 template <class T> struct make
153 {
operator ()make154   outcome::result<T> operator()() const noexcept
155   {                                            //
156     static_assert(!std::is_same<T, T>::value,  //
157                   "make<T>() was not specialised for the type T supplied");
158   }
159 };
160 //! [construct-declaration]
161 
162 //! [construct-specialisation]
163 template <> struct make<file_handle>
164 {
165   file_handle::path_type _path;
166   file_handle::mode _mode{file_handle::mode::read};
167   // Any other args, default initialised if necessary, follow here ...
168 
operator ()make169   outcome::result<file_handle> operator()() const noexcept  //
170   {
171     return file_handle::file(std::move(_path));
172   }
173 };
174 //! [construct-specialisation]
175 
main()176 int main()
177 {
178   //! [static-use]
179   outcome::result<file_handle> fh1 = file_handle::file("hello" /*, file_handle::mode::read */);
180   if(!fh1)
181   {
182     std::cerr << "Opening file 'hello' failed with " << fh1.error().message() << std::endl;
183   }
184   //! [static-use]
185   //! [construct-use]
186   outcome::result<file_handle> fh2 = make<file_handle>{"hello" /*, file_handle::mode::read */}();
187   if(!fh2)
188   {
189     std::cerr << "Opening file 'hello' failed with " << fh2.error().message() << std::endl;
190   }
191   //! [construct-use]
192   return 0;
193 }