qLibc
qfile.c
Go to the documentation of this file.
1 /******************************************************************************
2  * qLibc
3  *
4  * Copyright (c) 2010-2015 Seungyoung Kim.
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions are met:
9  *
10  * 1. Redistributions of source code must retain the above copyright notice,
11  * this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright notice,
13  * this list of conditions and the following disclaimer in the documentation
14  * and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
20  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26  * POSSIBILITY OF SUCH DAMAGE.
27  *****************************************************************************/
28 
29 /**
30  * @file qfile.c File handling APIs.
31  */
32 
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <stdbool.h>
36 #include <string.h>
37 #include <ctype.h>
38 #include <unistd.h>
39 #include <libgen.h>
40 #include <fcntl.h>
41 #include <limits.h>
42 #include <errno.h>
43 #include <sys/types.h>
44 #include <sys/stat.h>
45 #include <sys/file.h>
46 #include "qinternal.h"
47 #include "utilities/qstring.h"
48 #include "utilities/qfile.h"
49 
50 /**
51  * Lock file
52  *
53  * @param fd file descriptor
54  *
55  * @return true if successful, otherwise returns false.
56  *
57  * @code
58  * // for file descriptor
59  * int fd = open(...);
60  * if(qfile_lock(fd) == true) {
61  * (...atomic file access...)
62  * qfile_unlock(fd);
63  * }
64  *
65  * // for FILE stream object
66  * FILE *fp = fopen(...);
67  * int fd = fileno(fp);
68  * if(qfile_lock(fd) == true) {
69  * (...atomic file access...)
70  * qfile_unlock(fd);
71  * }
72  * @endcode
73  */
74 bool qfile_lock(int fd) {
75 #ifdef _WIN32
76  return false;
77 #else
78  struct flock lock;
79  memset((void *) &lock, 0, sizeof(flock));
80  lock.l_type = F_WRLCK;
81  lock.l_whence = SEEK_SET;
82  lock.l_start = 0;
83  lock.l_len = 0;
84  int ret = fcntl(fd, F_SETLK, &lock);
85  if (ret == 0)
86  return true;
87  return false;
88 #endif
89 }
90 
91 /**
92  * Unlock file which is locked by qfile_lock()
93  *
94  * @param fd file descriptor
95  *
96  * @return true if successful, otherwise returns false.
97  */
98 bool qfile_unlock(int fd) {
99 #ifdef _WIN32
100  return false;
101 #else
102  struct flock lock;
103  memset((void *) &lock, 0, sizeof(flock));
104  lock.l_type = F_UNLCK;
105  lock.l_whence = SEEK_SET;
106  lock.l_start = 0;
107  lock.l_len = 0;
108  int ret = fcntl(fd, F_SETLK, &lock);
109  if (ret == 0)
110  return true;
111  return false;
112 #endif
113 }
114 
115 /**
116  * Check file existence.
117  *
118  * @param filepath file or directory path
119  *
120  * @return true if exists, otherwise returns false.
121  */
122 bool qfile_exist(const char *filepath) {
123  if (access(filepath, F_OK) == 0)
124  return true;
125  return false;
126 }
127 
128 /**
129  * Load file into memory.
130  *
131  * @param filepath file path
132  * @param nbytes has two purpost, one is to set how many bytes are readed.
133  * the other is actual the number loaded bytes will be stored.
134  * nbytes must be point 0 or NULL to read entire file.
135  *
136  * @return allocated memory pointer if successful, otherwise returns NULL.
137  *
138  * @code
139  * // loading text file
140  * char *text = (char *)qfile_load("/tmp/text.txt", NULL);
141  *
142  * // loading binary file
143  * int binlen = 0;
144  * char *bin = (char *)qfile_load("/tmp/binary.bin", &binlen);
145  *
146  * // loading partial
147  * int binlen = 10;
148  * char *bin = (char *)qfile_load("/tmp/binary.bin", &binlen);
149  * @endcode
150  *
151  * @note
152  * This method actually allocates memory more than 1 bytes than filesize then
153  * append NULL character at the end. For example, when the file size is 10
154  * bytes long, 10+1 bytes will allocated and the last byte is always NULL
155  * character. So you can load text file and use without appending NULL
156  * character. By the way, the actual file size 10 will be returned at nbytes
157  * variable.
158  */
159 void *qfile_load(const char *filepath, size_t *nbytes) {
160  int fd;
161  if ((fd = open(filepath, O_RDONLY, 0)) < 0)
162  return NULL;
163 
164  struct stat fs;
165  if (fstat(fd, &fs) < 0) {
166  close(fd);
167  return NULL;
168  }
169 
170  size_t size = fs.st_size;
171  if (nbytes != NULL && *nbytes > 0 && *nbytes < fs.st_size)
172  size = *nbytes;
173 
174  void *buf = malloc(size + 1);
175  if (buf == NULL) {
176  close(fd);
177  return NULL;
178  }
179 
180  ssize_t count = read(fd, buf, size);
181  close(fd);
182 
183  if (count != size) {
184  free(buf);
185  return NULL;
186  }
187 
188  ((char *) buf)[count] = '\0';
189 
190  if (nbytes != NULL)
191  *nbytes = count;
192  return buf;
193 }
194 
195 /**
196  * Read data from a file stream.
197  *
198  * @param fp FILE pointer
199  * @param nbytes has two purpose, one is to set bytes to read.
200  * the other is to return actual number of bytes loaded.
201  * 0 or NULL can be set to read file until the end.
202  *
203  * @return allocated memory pointer if successful, otherwise returns NULL.
204  *
205  * @code
206  * int binlen = 0;
207  * char *bin = (char *)qfile_read(fp, &binlen);
208  * @endcode
209  *
210  * @note
211  * This method append NULL character at the end of stream. but nbytes only
212  * counts actual readed bytes.
213  */
214 void *qfile_read(FILE *fp, size_t *nbytes) {
215  size_t memsize = 1024;
216  size_t size = 0;
217 
218  if (nbytes != NULL && *nbytes > 0) {
219  memsize = *nbytes;
220  size = *nbytes;
221  }
222 
223  int c;
224  size_t c_count;
225  char *data = NULL;
226  for (c_count = 0; (c = fgetc(fp)) != EOF;) {
227  if (size > 0 && c_count == size)
228  break;
229 
230  if (c_count == 0) {
231  data = (char *) malloc(sizeof(char) * memsize);
232  if (data == NULL) {
233  DEBUG("Memory allocation failed.");
234  return NULL;
235  }
236  } else if (c_count == memsize - 1) {
237  memsize *= 2;
238 
239  /* Here, we don't use realloc(). Sometimes it is unstable. */
240  char *datatmp = (char *) malloc(sizeof(char) * (memsize + 1));
241  if (datatmp == NULL) {
242  DEBUG("Memory allocation failed.");
243  free(data);
244  return NULL;
245  }
246  memcpy(datatmp, data, c_count);
247  free(data);
248  data = datatmp;
249  }
250  data[c_count++] = (char) c;
251  }
252 
253  if (c_count == 0 && c == EOF)
254  return NULL;
255  data[c_count] = '\0';
256 
257  if (nbytes != NULL)
258  *nbytes = c_count;
259 
260  return (void *) data;
261 }
262 
263 /**
264  * Save data into file.
265  *
266  * @param filepath file path
267  * @param buf data
268  * @param size the number of bytes to save
269  * @param append false for new(if exists truncate), true for appending
270  *
271  * @return the number of bytes written if successful, otherwise returns -1.
272  *
273  * @code
274  * // save text
275  * char *text = "hello";
276  * qfile_save("/tmp/text.txt", (void*)text, strlen(text), false);
277  *
278  * // save binary
279  * int integer1 = 75;
280  * qfile_save("/tmp/integer.bin, (void*)&integer, sizeof(int));
281  * @endcode
282  */
283 ssize_t qfile_save(const char *filepath, const void *buf, size_t size,
284  bool append) {
285  int fd;
286 
287  if (append == false) {
288  fd = open(filepath, O_CREAT | O_WRONLY | O_TRUNC,
289  (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH));
290  } else {
291  fd = open(filepath, O_CREAT | O_WRONLY | O_APPEND,
292  (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH));
293  }
294  if (fd < 0)
295  return -1;
296 
297  ssize_t count = write(fd, buf, size);
298  close(fd);
299 
300  return count;
301 }
302 
303 /**
304  * Attempts to create a directory recursively.
305  *
306  * @param dirpath directory path
307  * @param mode permissions to use
308  * @param recursive whether or not to create parent directories automatically
309  *
310  * @return true if successful, otherwise returns false.
311  */
312 bool qfile_mkdir(const char *dirpath, mode_t mode, bool recursive) {
313  DEBUG("try to create directory %s", dirpath);
314  if (mkdir(dirpath, mode) == 0)
315  return true;
316 
317  bool ret = false;
318  if (recursive == true && errno == ENOENT) {
319  char *parentpath = qfile_get_dir(dirpath);
320  if (qfile_mkdir(parentpath, mode, true) == true
321  && mkdir(dirpath, mode) == 0) {
322  ret = true;
323  }
324  free(parentpath);
325  }
326 
327  return ret;
328 }
329 
330 /**
331  * Check path string contains invalid characters.
332  *
333  * @param path path string
334  *
335  * @return true if ok, otherwise returns false.
336  */
337 bool qfile_check_path(const char *path) {
338  if (path == NULL)
339  return false;
340 
341  int nLen = strlen(path);
342  if (nLen == 0 || nLen >= PATH_MAX)
343  return false;
344  else if (strpbrk(path, "\\:*?\"<>|") != NULL)
345  return false;
346  return true;
347 }
348 
349 /**
350  * Get filename from filepath
351  *
352  * @param filepath file or directory path
353  *
354  * @return malloced filename string
355  */
356 char *qfile_get_name(const char *filepath) {
357  char *path = strdup(filepath);
358  char *bname = basename(path);
359  char *filename = strdup(bname);
360  free(path);
361  return filename;
362 }
363 
364 /**
365  * Get directory suffix from filepath
366  *
367  * @param filepath file or directory path
368  *
369  * @return malloced filepath string
370  */
371 char *qfile_get_dir(const char *filepath) {
372  char *path = strdup(filepath);
373  char *dname = dirname(path);
374  char *dir = strdup(dname);
375  free(path);
376  return dir;
377 }
378 
379 /**
380  * Get extension from filepath.
381  *
382  * @param filepath file or directory path
383  *
384  * @return malloced extension string which is converted to lower case.
385  */
386 char *qfile_get_ext(const char *filepath) {
387 #define MAX_EXTENSION_LENGTH (8)
388  char *filename = qfile_get_name(filepath);
389  char *p = strrchr(filename, '.');
390  char *ext = NULL;
391  if (p != NULL && strlen(p + 1) <= MAX_EXTENSION_LENGTH
392  && qstrtest(isalnum, p + 1) == true) {
393  ext = strdup(p + 1);
394  qstrlower(ext);
395  } else {
396  ext = strdup("");
397  }
398 
399  free(filename);
400  return ext;
401 }
402 
403 /**
404  * Get file size.
405  *
406  * @param filepath file or directory path
407  *
408  * @return the file size if exists, otherwise returns -1.
409  */
410 off_t qfile_get_size(const char *filepath) {
411  struct stat finfo;
412  if (stat(filepath, &finfo) < 0)
413  return -1;
414  return finfo.st_size;
415 }
416 
417 /**
418  * Correct path string.
419  *
420  * @param path path string
421  *
422  * @return path string pointer
423  *
424  * @note
425  * This modify path argument itself.
426  *
427  * @code
428  * "/hello//my/../world" => "/hello/world"
429  * "././././hello/./world" => "./hello/world"
430  * "/../hello//world" => "/hello/world"
431  * "/../hello//world/" => "/hello/world"
432  * @endcode
433  */
434 char *qfile_correct_path(char *path) {
435  if (path == NULL)
436  return NULL;
437 
438  // take off heading & tailing white spaces
439  qstrtrim(path);
440 
441  while (true) {
442  // take off double slashes
443  if (strstr(path, "//") != NULL) {
444  qstrreplace("sr", path, "//", "/");
445  continue;
446  }
447 
448  if (strstr(path, "/./") != NULL) {
449  qstrreplace("sr", path, "/./", "/");
450  continue;
451  }
452 
453  if (strstr(path, "/../") != NULL) {
454  char *pszTmp = strstr(path, "/../");
455  if (pszTmp == path) {
456  // /../hello => /hello
457  strcpy(path, pszTmp + 3);
458  } else {
459  // /hello/../world => /world
460  *pszTmp = '\0';
461  char *pszNewPrefix = qfile_get_dir(path);
462  strcpy(path, pszNewPrefix);
463  strcat(path, pszTmp + 3);
464  free(pszNewPrefix);
465  }
466  continue;
467  }
468 
469  // take off tailing slash
470  size_t nLen = strlen(path);
471  if (nLen > 1) {
472  if (path[nLen - 1] == '/') {
473  path[nLen - 1] = '\0';
474  continue;
475  }
476  }
477 
478  // take off tailing /.
479  if (nLen > 2) {
480  if (!strcmp(path + (nLen - 2), "/.")) {
481  path[nLen - 2] = '\0';
482  continue;
483  }
484  }
485 
486  // take off tailing /.
487  if (nLen > 2) {
488  if (!strcmp(path + (nLen - 2), "/.")) {
489  path[nLen - 2] = '\0';
490  continue;
491  }
492  }
493 
494  // take off tailing /.
495  if (nLen > 3) {
496  if (!strcmp(path + (nLen - 3), "/..")) {
497  path[nLen - 3] = '\0';
498  char *pszNewPath = qfile_get_dir(path);
499  strcpy(path, pszNewPath);
500  free(pszNewPath);
501  continue;
502  }
503  }
504 
505  break;
506  }
507 
508  return path;
509 }
510 
511 /**
512  * Make full absolute system path string.
513  *
514  * @param buf buffer string pointer
515  * @param bufsize buffer size
516  * @param path path string
517  *
518  * @return buffer pointer if successful, otherwise returns NULL.
519  *
520  * @code
521  * char buf[PATH_MAX];
522  * chdir("/usr/local");
523  * qfile_abspath(buf, sizeof(buf), "."); // returns "/usr/local"
524  * qfile_abspath(buf, sizeof(buf), ".."); // returns "/usr"
525  * qfile_abspath(buf, sizeof(buf), "etc"); // returns "/usr/local/etc"
526  * qfile_abspath(buf, sizeof(buf), "/etc"); // returns "/etc"
527  * qfile_abspath(buf, sizeof(buf), "/etc/"); // returns "/etc/"
528  * @endcode
529  */
530 char *qfile_abspath(char *buf, size_t bufsize, const char *path) {
531  if (bufsize == 0)
532  return NULL;
533 
534  if (path[0] == '/') {
535  qstrcpy(buf, bufsize, path);
536  } else {
537  if (getcwd(buf, bufsize) == NULL)
538  return NULL;
539  strcat(buf, "/");
540  strcat(buf, path);
541  }
542  qfile_correct_path(buf);
543 
544  return buf;
545 }
char * qfile_get_name(const char *filepath)
Get filename from filepath.
Definition: qfile.c:356
char * qfile_correct_path(char *path)
Correct path string.
Definition: qfile.c:434
char * qfile_abspath(char *buf, size_t bufsize, const char *path)
Make full absolute system path string.
Definition: qfile.c:530
bool qfile_exist(const char *filepath)
Check file existence.
Definition: qfile.c:122
off_t qfile_get_size(const char *filepath)
Get file size.
Definition: qfile.c:410
bool qfile_mkdir(const char *dirpath, mode_t mode, bool recursive)
Attempts to create a directory recursively.
Definition: qfile.c:312
char * qfile_get_dir(const char *filepath)
Get directory suffix from filepath.
Definition: qfile.c:371
char * qstrtrim(char *str)
Remove white spaces(including CR, LF) from head and tail of the string.
Definition: qstring.c:55
char * qfile_get_ext(const char *filepath)
Get extension from filepath.
Definition: qfile.c:386
char * qstrcpy(char *dst, size_t size, const char *src)
Copy src string to dst.
Definition: qstring.c:320
bool qfile_lock(int fd)
Lock file.
Definition: qfile.c:74
ssize_t qfile_save(const char *filepath, const void *buf, size_t size, bool append)
Save data into file.
Definition: qfile.c:283
void * qfile_load(const char *filepath, size_t *nbytes)
Load file into memory.
Definition: qfile.c:159
bool qstrtest(int(*testfunc)(int), const char *str)
Test for an alpha-numeric string.
Definition: qstring.c:745
bool qfile_unlock(int fd)
Unlock file which is locked by qfile_lock()
Definition: qfile.c:98
char * qstrreplace(const char *mode, char *srcstr, const char *tokstr, const char *word)
Replace string or tokens as word from source string with given mode.
Definition: qstring.c:236
char * qstrlower(char *str)
Convert character to lower character.
Definition: qstring.c:536
void * qfile_read(FILE *fp, size_t *nbytes)
Read data from a file stream.
Definition: qfile.c:214
bool qfile_check_path(const char *path)
Check path string contains invalid characters.
Definition: qfile.c:337