Line data Source code
1 : // Copyright (c) 2011-2022 Made to Order Software Corp. All Rights Reserved
2 : //
3 : // https://snapwebsites.org/project/snapdev
4 : // contact@m2osw.com
5 : //
6 : // This program is free software; you can redistribute it and/or modify
7 : // it under the terms of the GNU General Public License as published by
8 : // the Free Software Foundation; either version 2 of the License, or
9 : // (at your option) any later version.
10 : //
11 : // This program is distributed in the hope that it will be useful,
12 : // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : // GNU General Public License for more details.
15 : //
16 : // You should have received a copy of the GNU General Public License along
17 : // with this program; if not, write to the Free Software Foundation, Inc.,
18 : // 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 : #pragma once
20 :
21 : // self
22 : //
23 : #include <snapdev/mkdir_p.h>
24 :
25 :
26 : // C++ lib
27 : //
28 : #include <fstream>
29 : #include <iostream>
30 : #include <ios>
31 :
32 :
33 : // C lib
34 : //
35 : #include <unistd.h>
36 :
37 :
38 :
39 : namespace snapdev
40 : {
41 :
42 :
43 :
44 : class file_contents
45 : {
46 : public:
47 : /** \brief Define the way to determine the file size.
48 : *
49 : * The size of some files can't be determined ahead of time
50 : * (sockets, FIFO, /proc files, etc.) and thus attempting to
51 : * get the size of such files is not going to work.
52 : *
53 : * Before calling read_all(), you can change the size mode
54 : * to one where the size will be determined once the file
55 : * is read.
56 : */
57 : enum size_mode_t
58 : {
59 : /** \brief Seek to the end to determine the size of the file.
60 : *
61 : * This mode is the default. It tells the read_all() command
62 : * to seek to the end of the file, get the position, which
63 : * represents the size, then seek back to the start of the
64 : * file. This works with all regular files.
65 : */
66 : SIZE_MODE_SEEK,
67 :
68 : /** \brief Read the file, avoid the seek().
69 : *
70 : * This mode has to be used with any file which size can't be
71 : * determine with a seek() function. It will read as much as it
72 : * can and then return that contents. There is no real way to
73 : * know whether the entire file was read in this mode.
74 : *
75 : * Although it is possible ot use this mode to read a regular
76 : * file, it is not a good idea since (1) it will be slower and
77 : * (2) it may re-allocate the contents buffer multiple times
78 : * so the data fits in there.
79 : */
80 : SIZE_MODE_READ,
81 : };
82 :
83 :
84 : /** \brief Initialize a content file.
85 : *
86 : * The constructor initialize the file content object with a filename.
87 : * The filename is used by the read_all() and write_all() functions.
88 : *
89 : * If the file_content is setup to be a temporary file, then the
90 : * destructor also makes use of the filename to delete the file
91 : * at that time. By default a file is not marked as temporary.
92 : *
93 : * \exception std::invalid_argument
94 : * The \p filename parameter cannot be an empty string.
95 : *
96 : * \exception std::ios::failure
97 : * The function checks whether all the folders exist. If not then the
98 : * file can't be create or read so there is no valid file_content()
99 : * possible with that path. This exception only occurs if the
100 : * \p create_missing_directories is true and the creation of any
101 : * of the directories fails.
102 : *
103 : * \param[in] filename The name of the file to read and write.
104 : * \param[in] create_missing_directories Whether to create missing directories
105 : * as found in the path (see mkdir_p()).
106 : * \param[in] temporary Whether the file is temporary.
107 : *
108 : * \sa snap::mkdir_p()
109 : */
110 15 : file_contents(
111 : std::string const & filename
112 : , bool create_missing_directories = false
113 : , bool temporary = false)
114 15 : : f_filename(filename)
115 17 : , f_temporary(temporary)
116 : {
117 15 : if(f_filename.empty())
118 : {
119 1 : throw std::invalid_argument("snapdev::file_contents: the filename of a file_contents object cannot be the empty string.");
120 : }
121 :
122 14 : if(create_missing_directories)
123 : {
124 3 : int const r(mkdir_p(f_filename, true));
125 3 : if(r != 0)
126 : {
127 1 : throw std::ios::failure("snapdev::file_contents: the full path to filename for a file_contents object could not be created");
128 : }
129 : }
130 13 : }
131 :
132 :
133 : /** \brief Clean up as required.
134 : *
135 : * If the file_content was marked as temporary, then the destructor
136 : * deletes the file on disk before returning.
137 : *
138 : * If the file does not exist and it was marked as temporary, the
139 : * deletion will fail silently. Otherwise you get a warning in the
140 : * logs with more details about the error (i.e. permission, bad
141 : * filename, etc.)
142 : */
143 13 : ~file_contents()
144 13 : {
145 13 : if(f_temporary)
146 : {
147 2 : if(unlink(f_filename.c_str()) != 0)
148 : {
149 1 : if(errno != ENOENT)
150 : {
151 : // TODO: what should we do here?!
152 : }
153 : }
154 : }
155 13 : }
156 :
157 :
158 : /** \brief Retrieve the filename.
159 : *
160 : * This function returns a refrence to the filename used by this
161 : * object. Note that the filename cannot be modified.
162 : *
163 : * \return A reference to this file content filename.
164 : */
165 : std::string const & filename() const
166 : {
167 : return f_filename;
168 : }
169 :
170 :
171 : /** \brief Check whether this file exists.
172 : *
173 : * This function checks whether the file exists on disk. If the
174 : * read_all() function returns false and this function returns true,
175 : * then you probably do not have permissions to read the file or it
176 : * is a directory.
177 : *
178 : * \return true if the file exists and can be read.
179 : *
180 : * \sa read_all()
181 : */
182 : bool exists() const
183 : {
184 : return access(f_filename.c_str(), R_OK) == 0;
185 : }
186 :
187 :
188 : /** \brief Change the size mode.
189 : *
190 : * Certain files do not have a size that can be read using the seek()
191 : * function and tell(). This function can be used to change the mode
192 : * to instead read the entire file little by little until the EOF is
193 : * reached.
194 : *
195 : * By default, we expect you to be using a regular file so the mode
196 : * is set to size_mode_t::SIZE_MODE_SEEK.
197 : *
198 : * \param[in] mode The new size mode.
199 : */
200 : void size_mode(size_mode_t mode)
201 : {
202 : f_size_mode = mode;
203 : }
204 :
205 :
206 : /** \brief Retrieve the current size mode.
207 : *
208 : * This function returns the current size mode for this file. The
209 : * mode is used to know whether we can use the seek() function or
210 : * not. If not, we just read the file up to EOF and return from
211 : * the read_all() function.
212 : *
213 : * \return The current size mode of this file_contents object
214 : * This function returns the current size mode for this file. The
215 : * mode is used to know whether we can use the seek() function or
216 : * not. If not, we just read the file up to EOF and return from
217 : * the read_all() function.
218 : *
219 : * \return The current size mode of this file_contents object.
220 : */
221 4 : size_mode_t size_mode() const
222 : {
223 4 : return f_size_mode;
224 : }
225 :
226 :
227 : /** \brief Read the entire file in a buffer.
228 : *
229 : * This function reads the entire file in memory. It saves the data
230 : * in a buffer which you can get with the contents() function.
231 : * The file can be binary in which case remember that c_str()
232 : * and other similar function will not work right.
233 : *
234 : * The function may return false if the file could not be opened or
235 : * read in full.
236 : *
237 : * \return true if the file could be read, false otherwise.
238 : *
239 : * \sa write_all()
240 : * \sa contents() const
241 : * \sa last_error()
242 : */
243 6 : bool read_all()
244 : {
245 : // try to open the file
246 : //
247 12 : std::ifstream in;
248 6 : in.open(f_filename, std::ios::in | std::ios::binary);
249 6 : if(!in.is_open())
250 : {
251 4 : f_error = "could not open file \""
252 4 : + f_filename
253 6 : + "\" for reading.";
254 2 : return false;
255 : }
256 :
257 : // get size
258 : //
259 4 : std::ifstream::pos_type size(0);
260 4 : size_mode_t mode(size_mode());
261 4 : if(mode == size_mode_t::SIZE_MODE_SEEK)
262 : {
263 4 : in.seekg(0, std::ios::end);
264 4 : size = in.tellg();
265 4 : in.seekg(0, std::ios::beg);
266 4 : if(std::ifstream::pos_type(-1) == size)
267 : {
268 : // try again in READ mode
269 : //
270 1 : mode = size_mode_t::SIZE_MODE_READ;
271 1 : in.clear();
272 : }
273 : }
274 :
275 4 : if(mode == size_mode_t::SIZE_MODE_READ)
276 : {
277 : // on certain files, the "get size" fails (i.e. "/proc/...",
278 : // FIFO, socket, etc.)
279 : //
280 1 : f_contents.clear();
281 1 : char buf[1024 * 16];
282 0 : do
283 : {
284 1 : in.read(buf, sizeof(buf));
285 1 : ssize_t sz(in.gcount());
286 1 : if(sz <= 0)
287 : {
288 0 : break;
289 : }
290 1 : f_contents.insert(f_contents.end(), buf, buf + sz);
291 : }
292 1 : while(in.good());
293 : }
294 : else
295 : {
296 : // resize the buffer accordingly
297 : //
298 : try
299 : {
300 3 : f_contents.resize(size, '\0');
301 : }
302 : catch(std::bad_alloc const & e) // LCOV_EXCL_LINE
303 : {
304 : f_error = "cannot allocate buffer of " // LCOV_EXCL_LINE
305 : + std::to_string(size) // LCOV_EXCL_LINE
306 : + " bytes to read file."; // LCOV_EXCL_LINE
307 : f_contents.clear(); // LCOV_EXCL_LINE
308 : return false; // LCOV_EXCL_LINE
309 : }
310 :
311 : // read the data
312 : //
313 3 : in.read(f_contents.data(), size);
314 : }
315 :
316 4 : if(in.bad()) // eof() always true, fail() may be true too (in case we can't gather the size() above)
317 : {
318 : f_error = "an I/O error occurred reading \"" // LCOV_EXCL_LINE
319 : + f_filename // LCOV_EXCL_LINE
320 : + "\"."; // LCOV_EXCL_LINE
321 : return false; // LCOV_EXCL_LINE
322 : }
323 :
324 4 : f_error.clear();
325 :
326 4 : return true;
327 : }
328 :
329 :
330 : /** \brief Write the contents to the file.
331 : *
332 : * This function writes the file contents data to the file. If a new
333 : * filename is specified, the contents is saved there instead. Note
334 : * that the filename does not get modified, but it allows for creating
335 : * a backup before making changes and save the new file:
336 : *
337 : * \code
338 : * file_content fc("filename.here");
339 : * fc.read_all();
340 : * fc.write_all("filename.bak"); // create backup
341 : * std::string content(fc.contents());
342 : * ... // make changes on 'contents'
343 : * fc.contents(contents);
344 : * fc.write_all();
345 : * \endcode
346 : *
347 : * \warning
348 : * If you marked the file_contents object as managing a temporary file
349 : * and specify a filename here which is not exactly equal to the
350 : * filename passed to the constructor, then the file you are writing
351 : * now will not be deleted automatically.
352 : *
353 : * \param[in] filename The name to use or an empty string (or nothing)
354 : * to use the filename defined in the constructor.
355 : *
356 : * \return true if the file was saved successfully.
357 : *
358 : * \sa contents()
359 : * \sa contents(std::string const & new_contents)
360 : * \sa read_all()
361 : * \sa last_error()
362 : */
363 6 : bool write_all(std::string const & filename = std::string())
364 : {
365 : // select filename
366 : //
367 12 : std::string const name(filename.empty() ? f_filename : filename);
368 :
369 : // try to open the file
370 : //
371 12 : std::ofstream out;
372 6 : out.open(name, std::ios::trunc | std::ios::out | std::ios::binary);
373 6 : if(!out.is_open())
374 : {
375 2 : f_error = "could not open file \""
376 2 : + name
377 3 : + "\" for writing.";
378 1 : return false;
379 : }
380 :
381 : // write the data
382 : //
383 5 : out.write(f_contents.data(), f_contents.length());
384 :
385 5 : if(out.fail())
386 : {
387 : f_error = "could not write " // LCOV_EXCL_LINE
388 : + std::to_string(f_contents.length()) // LCOV_EXCL_LINE
389 : + " bytes to \"" + name + "\"."; // LCOV_EXCL_LINE
390 : return false; // LCOV_EXCL_LINE
391 : }
392 :
393 5 : f_error.clear();
394 :
395 5 : return true;
396 : }
397 :
398 :
399 : /** \brief Change the contents with \p new_contents.
400 : *
401 : * This function replaces the current file contents with
402 : * \p new_contents.
403 : *
404 : * If \p new_contents is empty, then the file will become empty on a
405 : * write_all(). If you instead wanted to delete the file on
406 : * destruction of the function, set the temporary flag of the
407 : * construction to true to do that.
408 : *
409 : * \param[in] new_contents The new contents of the file.
410 : *
411 : * \sa contents()
412 : * \sa write_all()
413 : */
414 6 : void contents(std::string const & new_contents)
415 : {
416 6 : f_contents = new_contents;
417 6 : }
418 :
419 :
420 : /** \brief Get a constant reference to the contents.
421 : *
422 : * This function gives you read access to the existing contents of the
423 : * file.
424 : *
425 : * The string represents the contents of the file only if you called
426 : * the read_all() function first. It is not mandatory to do so in case
427 : * you are creating a new file.
428 : *
429 : * \return A constant reference to this file contents.
430 : *
431 : * \sa contents(std::string const & new_contents)
432 : * \sa read_all()
433 : */
434 2 : std::string const & contents() const
435 : {
436 2 : return f_contents;
437 : }
438 :
439 :
440 : /** \brief Get a reference to the contents.
441 : *
442 : * This function gives you direct read/write access to the existing
443 : * contents of the file.
444 : *
445 : * The string represents the contents of the file only if you called
446 : * the read_all() function first. It is not mandatory to do so in case
447 : * you are creating a new file.
448 : *
449 : * \return A constant reference to this file contents.
450 : *
451 : * \sa contents() const
452 : * \sa contents(std::string const & new_contents)
453 : * \sa read_all()
454 : */
455 4 : std::string & contents()
456 : {
457 4 : return f_contents;
458 : }
459 :
460 :
461 : /** \brief Retrieve the last error message.
462 : *
463 : * This function returns a copy of the last error message generated
464 : * by one of read_all() or write_all().
465 : *
466 : * \return The last error generated.
467 : */
468 1 : std::string const & last_error() const
469 : {
470 1 : return f_error;
471 : }
472 :
473 : private:
474 : std::string f_filename = std::string();
475 : std::string f_contents = std::string();
476 : std::string f_error = std::string();
477 : size_mode_t f_size_mode = size_mode_t::SIZE_MODE_SEEK;
478 : bool f_temporary = false;
479 : };
480 :
481 :
482 :
483 : } // namespace snapdev
484 : // vim: ts=4 sw=4 et
|