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 */
74bool 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 */
98bool 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 */
122bool 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 */
159void *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 */
214void *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 */
283ssize_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 */
312bool 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 */
337bool 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 */
356char *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 */
371char *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 */
386char *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 */
410off_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 */
434char *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 */
530char *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 }
543
544 return buf;
545}
void * qfile_load(const char *filepath, size_t *nbytes)
Load file into memory.
Definition qfile.c:159
char * qfile_correct_path(char *path)
Correct path string.
Definition qfile.c:434
char * qfile_get_name(const char *filepath)
Get filename from filepath.
Definition qfile.c:356
void * qfile_read(FILE *fp, size_t *nbytes)
Read data from a file stream.
Definition qfile.c:214
bool qfile_unlock(int fd)
Unlock file which is locked by qfile_lock()
Definition qfile.c:98
char * qfile_abspath(char *buf, size_t bufsize, const char *path)
Make full absolute system path string.
Definition qfile.c:530
ssize_t qfile_save(const char *filepath, const void *buf, size_t size, bool append)
Save data into file.
Definition qfile.c:283
char * qfile_get_ext(const char *filepath)
Get extension from filepath.
Definition qfile.c:386
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
bool qfile_lock(int fd)
Lock file.
Definition qfile.c:74
bool qfile_exist(const char *filepath)
Check file existence.
Definition qfile.c:122
bool qfile_check_path(const char *path)
Check path string contains invalid characters.
Definition qfile.c:337
off_t qfile_get_size(const char *filepath)
Get file size.
Definition qfile.c:410
char * qstrcpy(char *dst, size_t size, const char *src)
Copy src string to dst.
Definition qstring.c:325
char * qstrtrim(char *str)
Remove white spaces(including CR, LF) from head and tail of the string.
Definition qstring.c:55
bool qstrtest(int(*testfunc)(int), const char *str)
Test for an alpha-numeric string.
Definition qstring.c:752
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:543