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

Generated at 2026-01-27 using  doxygen