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