fileutils.c
Go to the documentation of this file.
1 /* ========================================================================== */
2 /*! \file
3  * \brief Generic file handling and path checking functions
4  *
5  * Copyright (c) 2012-2021 by the developers. See the LICENSE file for details.
6  *
7  * If nothing else is specified, functions return zero to indicate success
8  * and a negative value to indicate an error.
9  */
10 
11 
12 /* ========================================================================== */
13 /* Include headers */
14 
15 #include "posix.h" /* Include this first because of feature test macros */
16 
17 #include <ctype.h>
18 #include <stdio.h>
19 #include <string.h>
20 
21 #include "config.h"
22 #include "fileutils.h"
23 #include "main.h"
24 
25 
26 /* ========================================================================== */
27 /* Constants */
28 
29 /*! \brief Message prefix for FILEUTILS module */
30 #define MAIN_ERR_PREFIX "FUTIL: "
31 
32 
33 /* ========================================================================== */
34 /*! \defgroup FILEUTILS FUTIL: POSIX file handling
35  *
36  * This is a wrapper for the POSIX file handling stuff. It mainly does some
37  * things for convenience and is not a real abstraction layer.
38  *
39  * \note
40  * It can't be used from C++ code because the used POSIX API doesn't has a C++
41  * binding.
42  */
43 /*! @{ */
44 
45 
46 /* ========================================================================== */
47 /* Dummy compare function for 'scandir()'
48  *
49  * \return
50  * - Always 0 so that the sort order will be undefined.
51  */
52 
53 static int fu_compar(const struct_posix_dirent** a,
54  const struct_posix_dirent** b)
55 {
56  return(0);
57 }
58 
59 
60 /* ========================================================================== */
61 /*! \brief Check path
62  *
63  * \param[in] path String with path or pathname to verify
64  *
65  * Accept only characters from the POSIX portable filename character set and '/'
66  * for security reasons. Especially accept no space and '%' in the path.
67  *
68  * \return
69  * - Zero on success
70  * - Negative value on error
71  */
72 
73 int fu_check_path(const char* path)
74 {
75  int res = 0;
76  size_t i;
77 
78  for(i = 0; i < strlen(path); ++i)
79  {
80  /* Check for alphanumeric characters */
81  if('a' <= path[i] && 'z' >= path[i]) continue;
82  if('A' <= path[i] && 'Z' >= path[i]) continue;
83  /* Check for digits */
84  if('0' <= path[i] && '9' >= path[i]) continue;
85  /* Check for other allowed characters */
86  if('.' == path[i]) continue;
87  if('_' == path[i]) continue;
88  if('-' == path[i]) continue;
89  /* Check for slash */
90  if('/' == path[i]) continue;
91 
92  /* Do not accept other characters */
93  PRINT_ERROR("Path or pathname contains forbidden characters");
94  res = -1;
95  break;
96  }
97 
98  return(res);
99 }
100 
101 
102 /* ========================================================================== */
103 /*! \brief Create path
104  *
105  * \param[in] path String with path to create
106  * \param[in] perm Permissions for directories to create
107  *
108  * \attention \e path must not be \c NULL and must be an absolute path.
109  *
110  * \c mkdir() is called for every component of the path. If \e path does not
111  * end with a slash, the last component is not treated as a directory to
112  * create.
113  *
114  * \return
115  * - Zero on success
116  * - Negative value on error
117  */
118 
119 int fu_create_path(const char* path, posix_mode_t perm)
120 {
121  int res = 0;
122  size_t pos = 1;
123  char* p;
124  char* pcomp = NULL;
125  size_t len;
126  int rv;
127 
128  if(NULL == path || '/' != path[0])
129  {
130  goto error;
131  }
132  /* printf("Path to create: %s\n", path); */
133 
134  while (0 != path[pos])
135  {
136  /* Extract next path component */
137  p = strchr(&path[pos], (int) '/');
138  if(NULL != p)
139  {
140  len = (size_t) (p - &path[pos]);
141  pos += len + (size_t) 1; /* New position after slash */
142  }
143  else
144  {
145  /* Last component without trailing slash */
146  len = (size_t) (&path[strlen(path)] - &path[pos]);
147  pos += len; /* New position on NUL termination */
148  /* Verify NUL termination to ensure that loop terminates */
149  if(0 != path[pos])
150  {
151  PRINT_ERROR("Infinite loop detected (bug)");
152  goto error;
153  }
154  }
155 
156  pcomp = (char*) posix_malloc(pos + (size_t) 1);
157  if(NULL == pcomp)
158  {
159  PRINT_ERROR("Can't allocate memory for path component");
160  goto error;
161  }
162  memcpy((void*) pcomp, (void*) path, pos);
163  pcomp[pos] = 0;
164 
165  /* Remove potential trailing slash */
166  /* Note: According to POSIX.1 it should work with the slash too */
167  if('/' == pcomp[pos - (size_t) 1])
168  {
169  pcomp[pos - (size_t) 1] = 0;
170  }
171  /* printf("Directory to create: %s\n", pcomp); */
172 
173  /* Create configuration directory, if it doesn't exist */
174  rv = posix_mkdir(pcomp, perm);
175  if(!(0 == rv || (-1 == rv && POSIX_EEXIST == posix_errno)))
176  {
177  goto error;
178  }
179 
180  posix_free((void*) pcomp);
181  }
182 
183 exit:
184  return(res);
185 
186 error:
187  PRINT_ERROR("Cannot create directory for path");
188  res = -1;
189  goto exit;
190 }
191 
192 
193 /* ========================================================================== */
194 /*! \brief Check whether file exist
195  *
196  * \param[in] pathname Pathname of file to open
197  * \param[out] state Pointer to status buffer
198  *
199  * This is a wrapper for the POSIX \c stat() system call.
200  *
201  * This function check for a file from \e pathname and try to get its status.
202  * If \e stat is \c NULL the status is thrown away.
203  *
204  * On success, status is written to location pointed to by \e stat
205  *
206  * \return
207  * - Zero on success
208  * - Negative value on error
209  */
210 
211 int fu_check_file(const char* pathname, struct_posix_stat* state)
212 {
213  int res = -1;
214  struct_posix_stat stat_dummy;
215 
216  if(NULL == state) { res = posix_stat(pathname, &stat_dummy); }
217  else { res = posix_stat(pathname, state); }
218 
219  return(res);
220 }
221 
222 
223 /* ========================================================================== */
224 /*! \brief Open file
225  *
226  * \param[in] pathname Pathname of file to open
227  * \param[out] filedesc Pointer to file descriptor
228  * \param[in] mode Mode for file to open
229  * \param[in] perm Permissions for file to open (if it must be created)
230  *
231  * This is a wrapper for the POSIX \c open() system call that automatically
232  * retry if the system call was interrupted by a signal.
233  *
234  * This function opens a file from \e pathname with the mode \e mode .
235  * If a new file is created, the permissions from \e perm are used, otherwise
236  * \e perm is ignored.
237  *
238  * \return
239  * - Zero on success
240  * - Negative value on error
241  */
242 
243 int fu_open_file(const char* pathname, int* filedesc, int mode,
244  posix_mode_t perm)
245 {
246  int res = 0;
247  int rv;
248 
249  /* Open file */
250  do { *filedesc = posix_open(pathname, mode, perm); }
251  while(-1 == *filedesc && POSIX_EINTR == posix_errno);
252  if (-1 == *filedesc)
253  {
254  /* PRINT_ERROR("Cannot open file"); */
255  res = -1;
256  }
257  else
258  {
259  /* Set 'FD_CLOEXEC' flag */
260  do { rv = posix_fcntl(*filedesc, POSIX_F_SETFD, POSIX_FD_CLOEXEC); }
261  while(-1 == rv && POSIX_EINTR == posix_errno);
262  if(-1 == rv)
263  {
264  PRINT_ERROR("fcntl() failed");
265  posix_close(*filedesc);
266  res = -1;
267  }
268  }
269 
270  return(res);
271 }
272 
273 
274 /* ========================================================================== */
275 /*! \brief Close file (and potentially associated I/O stream)
276  *
277  * \param[in] filedesc Pointer to filedescriptor of file to close
278  * \param[in] stream Pointer to stream associated with \e filedesc
279  *
280  * This function first close the stream pointed to by \e stream and then the
281  * filedescriptor pointed to by \e filedesc.
282  *
283  * \note
284  * Both, \e filedesc and \e stream may be \c NULL and are ignored in this case.
285  * If \e filedesc points to a location with value \c -1 it is ignored.
286  * If \e stream points to a location containing a \c NULL pointer, it is
287  * ignored.
288  */
289 
290 void fu_close_file(int* filedesc, FILE** stream)
291 {
292  /* Check for stream */
293  if (NULL != stream)
294  {
295  if (NULL != *stream) { fclose(*stream); }
296  *stream = NULL;
297  }
298 
299  /* Touch filedescriptor only if there was no stream */
300  if (NULL == stream && NULL != filedesc)
301  {
302  if (-1 != *filedesc) { posix_close(*filedesc); }
303  }
304  if (NULL != filedesc) { *filedesc = -1; }
305 
306  /*
307  * Attention:
308  * Potential EINTR and EIO errors must be ignored
309  * POSIX specifies that both, the stream pointer for fclose() and the
310  * filedescriptor for close() are in undefined state after such an error.
311  * Therefore it is not allowed to retry the fclose()/close() calls.
312  */
313 }
314 
315 
316 /* ========================================================================== */
317 /*! \brief Lock file for writing
318  *
319  * \param[in] filedesc Descriptor of file to lock
320  *
321  * The file must be open for writing for this function to work.
322  *
323  * \return
324  * - Zero on success
325  * - Negative value on error
326  */
327 
328 int fu_lock_file(int filedesc)
329 {
330  int res = -1;
331  struct_posix_flock fl;
332 
333  fl.l_type = POSIX_F_WRLCK;
334  fl.l_whence = POSIX_SEEK_SET;
335  fl.l_start = 0;
336  fl.l_len = 0;
337  res = posix_fcntl(filedesc, POSIX_F_SETLK, &fl);
338  if(-1 == res) { PRINT_ERROR("Cannot lock file"); }
339  else res = 0;
340 
341  return(res);
342 }
343 
344 
345 /* ========================================================================== */
346 /*! \brief Unlink file
347  *
348  * \param[in] pathname Pathname of file to unlink
349  *
350  * \return
351  * - Zero on success
352  * - Negative value on error
353  */
354 
355 int fu_unlink_file(const char* pathname)
356 {
357  return(posix_unlink(pathname));
358 }
359 
360 
361 /* ========================================================================== */
362 /*! \brief Assign I/O stream to open file
363  *
364  * \param[in] filedesc Filedescriptor to which the stream should be assigned
365  * \param[out] stream File stream that was assigned to file descriptor
366  * \param[in] mode Access mode of the new stream
367  *
368  * \return
369  * - Zero on success
370  * - Negative value on error
371  */
372 
373 int fu_assign_stream(int filedesc, FILE** stream, const char* mode)
374 {
375  int res = 0;
376 
377  *stream = posix_fdopen(filedesc, mode);
378  if(NULL == *stream)
379  {
380  PRINT_ERROR("Cannot assign I/O stream to file");
381  res = -1;
382  }
383 
384  return(res);
385 }
386 
387 
388 /* ========================================================================== */
389 /*! \brief Flush buffers of file
390  *
391  * \param[in] filedesc Filedescriptor of file
392  * \param[out] stream File stream assigned to file descriptor
393  *
394  * The filedescriptor \e filedesc must be valid, \e stream may be NULL if there
395  * is no stream assigned to the filedescriptor.
396  *
397  * \return
398  * - Zero on success
399  * - Negative value on error
400  */
401 
402 int fu_sync(int filedesc, FILE* stream)
403 {
404  int res = 0;
405 
406  if(NULL != stream)
407  {
408  /* Flush user space buffers */
409  do { res = fflush(stream); }
410  while(EOF == res && POSIX_EINTR == posix_errno);
411  }
412 
413  if(!res)
414  {
415  /* Flush kernel buffers */
416  do { res = posix_fsync(filedesc); }
417  while(-1 == res && POSIX_EINTR == posix_errno);
418  }
419 
420  return(res);
421 }
422 
423 
424 /* ========================================================================== */
425 /*! \brief Read text file content and store it into memory buffer
426  *
427  * \param[in] filedesc Filedescriptor of file
428  * \param[out] buffer Pointer to data buffer
429  * \param[out] len Pointer to buffer size
430  *
431  * The filedescriptor \e filedesc must be valid.
432  * The data is terminated with a \c NUL character, therefore the file must be
433  * a text file without NUL characters in the content.
434  *
435  * \attention
436  * The value written to \e len is the buffer size, not the data length!
437  *
438  * On success that caller is responsible to free the allocated buffer.
439  *
440  * \return
441  * - Zero on success
442  * - Negative value on error
443  */
444 
445 int fu_read_whole_file(int filedesc, char** buffer, size_t* len)
446 {
447  int res = -1;
448  size_t i = 0;
449  char* p;
450  posix_ssize_t rv;
451 
452  *buffer = NULL;
453  *len = 0;
454  while(1)
455  {
456  if(i + (size_t) 1 >= *len) /* At least one additional termination byte */
457  {
458  /* Allocate more memory in exponentially increasing chunks */
459  if(!*len) { *len = 256; }
460  p = posix_realloc((void*) *buffer, *len *= (size_t) 2);
461  if(NULL == p) { break; }
462  else { *buffer = p; }
463  }
464  /* Read data from file */
465  rv = posix_read(filedesc, &(*buffer)[i], *len - i - (size_t) 1);
466  if(-1 == rv && POSIX_EINTR == posix_errno) { continue; }
467  if(-1 == rv) { break; }
468  else { i += (size_t) rv; }
469  /* Check for EOF */
470  if(!rv) { (*buffer)[i] = 0; res = 0; break; }
471  }
472 
473  /* Check for error */
474  if(res)
475  {
476  posix_free((void*) *buffer);
477  *buffer = NULL;
478  *len = 0;
479  }
480 
481  return(res);
482 }
483 
484 
485 /* ========================================================================== */
486 /*! \brief Read data block to filedescriptor
487  *
488  * \param[in] filedesc Filedescriptor of file
489  * \param[out] buffer Pointer to data buffer
490  * \param[in,out] len Pointer to data length in octets
491  *
492  * The filedescriptor \e filedesc must be valid.
493  *
494  * \attention
495  * The caller must set the value \e len points to to the size of the buffer.
496  * On success the value is overwritten with the number of octets read.
497  * A buffer size of zero is treated as error.
498  *
499  * \return
500  * - 0 on success (at least one octet was written to \e buffer)
501  * - Nonzero on error (or EOF before the first octet was read)
502  */
503 
504 int fu_read_from_filedesc(int filedesc, char* buffer, size_t* len)
505 {
506  int res = -1;
507  posix_ssize_t rv = -1;
508  size_t i = 0;
509 
510  while(i < *len)
511  {
512  rv = posix_read(filedesc, (void*) &buffer[i], *len - i);
513  if((posix_ssize_t) -1 == rv && POSIX_EINTR == posix_errno) { continue; }
514  if(!rv) { break; } /* EOF */
515  if((posix_ssize_t) -1 != rv) { i += (size_t) rv; } else { break; }
516  }
517  if(-1 == rv) { PRINT_ERROR("Error while reading from FD"); }
518  if(i)
519  {
520  *len = i;
521  res = 0;
522  }
523 
524  return(res);
525 }
526 
527 
528 /* ========================================================================== */
529 /*! \brief Write data block to filedescriptor
530  *
531  * \param[in] filedesc Filedescriptor of file
532  * \param[in] buffer Pointer to data buffer
533  * \param[in] len Data length in octets
534  *
535  * The filedescriptor \e filedesc must be valid.
536  *
537  * \return
538  * - Zero on success
539  * - Negative value on error
540  */
541 
542 int fu_write_to_filedesc(int filedesc, const char* buffer, size_t len)
543 {
544  int res = -1;
545  posix_ssize_t rv;
546  size_t i = 0;
547 
548  while(i < len)
549  {
550  rv = posix_write(filedesc, (void*) &buffer[i], len - i);
551  if((posix_ssize_t) -1 == rv && POSIX_EINTR == posix_errno) { continue; }
552  if((posix_ssize_t) -1 != rv) { i += (size_t) rv; } else { break; }
553  }
554  if(len == i) { res = 0; }
555  if(res) { PRINT_ERROR("Writing data block to FD failed"); }
556 
557  return(res);
558 }
559 
560 
561 /* ========================================================================== */
562 /*! \brief Delete directory tree
563  *
564  * \param[in] dir Pathname of toplevel directory (to delete with all content)
565  *
566  * The parameter \e dir must point to a string with the absolute path.
567  * A trailing slash is allowed and ignored.
568  *
569  * \attention
570  * If the directory tree \e dir contains symbolic links, this function only
571  * works if compiled to use the X/Open XSI extension of the operating system.
572  *
573  * \return
574  * - Zero on success
575  * - Negative value on error
576  */
577 
578 int fu_delete_tree(const char* dir)
579 {
580  int res = -1;
581  int rv;
582  int num;
583  size_t len;
584  char* path;
585  struct_posix_dirent** content;
586  struct_posix_stat state;
587  const char* entry;
588  char* pathname;
589  size_t i;
590 
591  if(NULL != dir)
592  {
593  /* Check for absolute path */
594  len = strlen(dir);
595  if(len && '/' == dir[0])
596  {
597  /* Strip potential trailing slash of path */
598  path = (char*) posix_malloc(len + (size_t) 1);
599  if(NULL == path)
600  {
601  PRINT_ERROR("Can't allocate memory for path");
602  }
603  else
604  {
605  strcpy(path, dir);
606  if('/' == path[len - (size_t) 1]) { path[len - (size_t) 1] = 0; }
607  /* Delete directory */
608  num = posix_scandir(path, &content, NULL, fu_compar);
609  if(0 > num) { PRINT_ERROR("Cannot scan directory"); }
610  else
611  {
612  /* Init result to success. Report errors, but continue */
613  res = 0;
614  for(i = 0; i < (size_t) num; ++i)
615  {
616  entry = content[i]->d_name;
617  /*
618  * A directory containing only "." and ".." is empty in terms
619  * of 'rmdir()' which means this function can delete it.
620  */
621  if(!strcmp(".", entry)) { continue; }
622  if(!strcmp("..", entry)) { continue; }
623  pathname = (char*) posix_malloc(strlen(path) + strlen(entry)
624  + (size_t) 2);
625  if(NULL == pathname)
626  {
627  PRINT_ERROR("Cannot allocate memory for path");
628  res = -1;
629  continue;
630  }
631  strcpy(pathname, path);
632  strcat(pathname, "/");
633  strcat(pathname, entry);
634  /* printf("Pathname: %s\n", pathname); */
635 #if CFG_USE_POSIX_API >= 200112 || CFG_USE_XSI
636  /* This is the desired operation (not follow symlinks) */
637  rv = posix_lstat(pathname, &state);
638 #else /* CFG_USE_POSIX_API >= 200112 || CFG_USE_XSI */
639  /* Fallback that only works if there are no symlinks */
640  rv = posix_stat(pathname, &state);
641 #endif /* CFG_USE_POSIX_API >= 200112 || CFG_USE_XSI */
642  if(rv)
643  {
644  PRINT_ERROR("Cannot stat file");
645  res = -1;
646  }
647  else
648  {
649  /* Check whether entry is a directory */
650  if(POSIX_S_ISDIR(state.st_mode))
651  {
652  /* Yes => Recursively delete it */
653  rv = fu_delete_tree(pathname);
654  if(rv) { res = -1; }
655  }
656  else
657  {
658  /* No => Delete file */
659  /* printf("Unlink file: %s\n", pathname); */
660  rv = fu_unlink_file(pathname);
661  if(rv) { res = -1; }
662  }
663  }
664  posix_free((void*) pathname);
665  }
666  /* printf("Delete dir : %s\n", path); */
667  rv = posix_rmdir(path);
668  if(rv) { res = -1; }
669  /* Free memory allocated by scandir() */
670  while(num--) { posix_free((void*) content[num]); }
671  posix_free((void*) content);
672  }
673  posix_free((void*) path);
674  }
675  }
676  }
677 
678  return(res);
679 }
680 
681 
682 /*! @} */
683 
684 /* EOF */
fu_write_to_filedesc
int fu_write_to_filedesc(int filedesc, const char *buffer, size_t len)
Write data block to filedescriptor.
Definition: fileutils.c:542
fu_assign_stream
int fu_assign_stream(int filedesc, FILE **stream, const char *mode)
Assign I/O stream to open file.
Definition: fileutils.c:373
fu_read_from_filedesc
int fu_read_from_filedesc(int filedesc, char *buffer, size_t *len)
Read data block to filedescriptor.
Definition: fileutils.c:504
fu_lock_file
int fu_lock_file(int filedesc)
Lock file for writing.
Definition: fileutils.c:328
fu_create_path
int fu_create_path(const char *path, posix_mode_t perm)
Create path.
Definition: fileutils.c:119
fu_unlink_file
int fu_unlink_file(const char *pathname)
Unlink file.
Definition: fileutils.c:355
PRINT_ERROR
#define PRINT_ERROR(s)
Prepend module prefix and print error message.
Definition: main.h:19
fu_delete_tree
int fu_delete_tree(const char *dir)
Delete directory tree.
Definition: fileutils.c:578
fu_sync
int fu_sync(int filedesc, FILE *stream)
Flush buffers of file.
Definition: fileutils.c:402
fu_close_file
void fu_close_file(int *filedesc, FILE **stream)
Close file (and potentially associated I/O stream)
Definition: fileutils.c:290
fu_check_path
int fu_check_path(const char *path)
Check path.
Definition: fileutils.c:73
fu_read_whole_file
int fu_read_whole_file(int filedesc, char **buffer, size_t *len)
Read text file content and store it into memory buffer.
Definition: fileutils.c:445
fu_check_file
int fu_check_file(const char *pathname, struct_posix_stat *state)
Check whether file exist.
Definition: fileutils.c:211
fu_open_file
int fu_open_file(const char *pathname, int *filedesc, int mode, posix_mode_t perm)
Open file.
Definition: fileutils.c:243

Generated at 2024-04-27 using  doxygen