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