qLibc
qfile.c
Go to the documentation of this file.
1/******************************************************************************
2 * qLibc
3 *
4 * Copyright (c) 2010-2026 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 on success, otherwise 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 on success, otherwise 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 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 a file into memory.
130 *
131 * @param filepath file path
132 * @param nbytes has two purposes. You can set it to limit how many bytes
133 * are read, and the actual number of loaded bytes is stored
134 * back through the same pointer. Set it to 0 or NULL to read
135 * the entire file.
136 *
137 * @return allocated memory on success, or NULL on failure.
138 *
139 * @code
140 * // loading text file
141 * char *text = (char *)qfile_load("/tmp/text.txt", NULL);
142 *
143 * // loading binary file
144 * int binlen = 0;
145 * char *bin = (char *)qfile_load("/tmp/binary.bin", &binlen);
146 *
147 * // loading partial
148 * int binlen = 10;
149 * char *bin = (char *)qfile_load("/tmp/binary.bin", &binlen);
150 * @endcode
151 *
152 * @note
153 * This function allocates one extra byte after the file size and appends a
154 * NULL character at the end. For example, if the file size is 10 bytes,
155 * 11 bytes are allocated and the last byte is always NULL. This makes it
156 * safe to use the loaded data as a text string. The actual file size is
157 * returned through `nbytes`.
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 purposes. You can set it to limit how many bytes
200 * are read, and the actual number of loaded bytes is stored
201 * back through the same pointer. Set it to 0 or NULL to read
202 * until the end of the stream.
203 *
204 * @return allocated memory on success, or NULL on failure.
205 *
206 * @code
207 * int binlen = 0;
208 * char *bin = (char *)qfile_read(fp, &binlen);
209 * @endcode
210 *
211 * @note
212 * This function appends a NULL character at the end of the stream, but
213 * `nbytes` only counts the actual number of bytes read.
214 */
215void *qfile_read(FILE *fp, size_t *nbytes) {
216 size_t memsize = 1024;
217 size_t size = 0;
218
219 if (nbytes != NULL && *nbytes > 0) {
220 memsize = *nbytes;
221 size = *nbytes;
222 }
223
224 int c;
225 size_t c_count;
226 char *data = NULL;
227 for (c_count = 0; (c = fgetc(fp)) != EOF;) {
228 if (size > 0 && c_count == size)
229 break;
230
231 if (c_count == 0) {
232 data = (char *) malloc(sizeof(char) * memsize);
233 if (data == NULL) {
234 DEBUG("Memory allocation failed.");
235 return NULL;
236 }
237 } else if (c_count == memsize - 1) {
238 memsize *= 2;
239
240 /* Here, we don't use realloc(). Sometimes it is unstable. */
241 char *datatmp = (char *) malloc(sizeof(char) * (memsize + 1));
242 if (datatmp == NULL) {
243 DEBUG("Memory allocation failed.");
244 free(data);
245 return NULL;
246 }
247 memcpy(datatmp, data, c_count);
248 free(data);
249 data = datatmp;
250 }
251 data[c_count++] = (char) c;
252 }
253
254 if (c_count == 0 && c == EOF)
255 return NULL;
256 data[c_count] = '\0';
257
258 if (nbytes != NULL)
259 *nbytes = c_count;
260
261 return (void *) data;
262}
263
264/**
265 * Save data into file.
266 *
267 * @param filepath file path
268 * @param buf data
269 * @param size the number of bytes to save
270 * @param append false for new(if exists truncate), true for appending
271 *
272 * @return the number of bytes written if successful, otherwise returns -1.
273 *
274 * @code
275 * // save text
276 * char *text = "hello";
277 * qfile_save("/tmp/text.txt", (void*)text, strlen(text), false);
278 *
279 * // save binary
280 * int integer1 = 75;
281 * qfile_save("/tmp/integer.bin", (void*)&integer1, sizeof(int), false);
282 * @endcode
283 */
284ssize_t qfile_save(const char *filepath, const void *buf, size_t size,
285 bool append) {
286 int fd;
287
288 if (append == false) {
289 fd = open(filepath, O_CREAT | O_WRONLY | O_TRUNC,
290 (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH));
291 } else {
292 fd = open(filepath, O_CREAT | O_WRONLY | O_APPEND,
293 (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH));
294 }
295 if (fd < 0)
296 return -1;
297
298 ssize_t count = write(fd, buf, size);
299 close(fd);
300
301 return count;
302}
303
304/**
305 * Attempts to create a directory recursively.
306 *
307 * @param dirpath directory path
308 * @param mode permissions to use
309 * @param recursive whether or not to create parent directories automatically
310 *
311 * @return true on success, otherwise false.
312 */
313bool qfile_mkdir(const char *dirpath, mode_t mode, bool recursive) {
314 DEBUG("try to create directory %s", dirpath);
315 if (mkdir(dirpath, mode) == 0)
316 return true;
317
318 bool ret = false;
319 if (recursive == true && errno == ENOENT) {
320 char *parentpath = qfile_get_dir(dirpath);
321 if (qfile_mkdir(parentpath, mode, true) == true
322 && mkdir(dirpath, mode) == 0) {
323 ret = true;
324 }
325 free(parentpath);
326 }
327
328 return ret;
329}
330
331/**
332 * Check path string contains invalid characters.
333 *
334 * @param path path string
335 *
336 * @return true if ok, otherwise false.
337 */
338bool qfile_check_path(const char *path) {
339 if (path == NULL)
340 return false;
341
342 int nLen = strlen(path);
343 if (nLen == 0 || nLen >= PATH_MAX)
344 return false;
345 else if (strpbrk(path, "\\:*?\"<>|") != NULL)
346 return false;
347 return true;
348}
349
350/**
351 * Get the file name from a path.
352 *
353 * @param filepath file or directory path
354 *
355 * @return allocated file name string.
356 */
357char *qfile_get_name(const char *filepath) {
358 char *path = strdup(filepath);
359 char *bname = basename(path);
360 char *filename = strdup(bname);
361 free(path);
362 return filename;
363}
364
365/**
366 * Get the directory part of a path.
367 *
368 * @param filepath file or directory path
369 *
370 * @return allocated directory string.
371 */
372char *qfile_get_dir(const char *filepath) {
373 char *path = strdup(filepath);
374 char *dname = dirname(path);
375 char *dir = strdup(dname);
376 free(path);
377 return dir;
378}
379
380/**
381 * Get the file extension from a path.
382 *
383 * @param filepath file or directory path
384 *
385 * @return allocated extension string in lower case.
386 */
387char *qfile_get_ext(const char *filepath) {
388#define MAX_EXTENSION_LENGTH (8)
389 char *filename = qfile_get_name(filepath);
390 char *p = strrchr(filename, '.');
391 char *ext = NULL;
392 if (p != NULL && strlen(p + 1) <= MAX_EXTENSION_LENGTH
393 && qstrtest(isalnum, p + 1) == true) {
394 ext = strdup(p + 1);
395 qstrlower(ext);
396 } else {
397 ext = strdup("");
398 }
399
400 free(filename);
401 return ext;
402}
403
404/**
405 * Get file size.
406 *
407 * @param filepath file or directory path
408 *
409 * @return the file size if exists, otherwise returns -1.
410 */
411off_t qfile_get_size(const char *filepath) {
412 struct stat finfo;
413 if (stat(filepath, &finfo) < 0)
414 return -1;
415 return finfo.st_size;
416}
417
418/**
419 * Correct path string.
420 *
421 * @param path path string
422 *
423 * @return path string pointer
424 *
425 * @note
426 * This function modifies path argument itself.
427 *
428 * @code
429 * "/hello//my/../world" => "/hello/world"
430 * "././././hello/./world" => "./hello/world"
431 * "/../hello//world" => "/hello/world"
432 * "/../hello//world/" => "/hello/world"
433 * @endcode
434 */
435char *qfile_correct_path(char *path) {
436 if (path == NULL)
437 return NULL;
438
439 // take off leading & trailing white spaces
440 qstrtrim(path);
441
442 while (true) {
443 // take off double slashes
444 if (strstr(path, "//") != NULL) {
445 qstrreplace("sr", path, "//", "/");
446 continue;
447 }
448
449 if (strstr(path, "/./") != NULL) {
450 qstrreplace("sr", path, "/./", "/");
451 continue;
452 }
453
454 if (strstr(path, "/../") != NULL) {
455 char *pszTmp = strstr(path, "/../");
456 if (pszTmp == path) {
457 // /../hello => /hello
458 strcpy(path, pszTmp + 3);
459 } else {
460 // /hello/../world => /world
461 *pszTmp = '\0';
462 char *pszNewPrefix = qfile_get_dir(path);
463 strcpy(path, pszNewPrefix);
464 strcat(path, pszTmp + 3);
465 free(pszNewPrefix);
466 }
467 continue;
468 }
469
470 // take off trailing slash
471 size_t nLen = strlen(path);
472 if (nLen > 1) {
473 if (path[nLen - 1] == '/') {
474 path[nLen - 1] = '\0';
475 continue;
476 }
477 }
478
479 // take off trailing /.
480 if (nLen > 2) {
481 if (!strcmp(path + (nLen - 2), "/.")) {
482 path[nLen - 2] = '\0';
483 continue;
484 }
485 }
486
487 // take off trailing /.
488 if (nLen > 2) {
489 if (!strcmp(path + (nLen - 2), "/.")) {
490 path[nLen - 2] = '\0';
491 continue;
492 }
493 }
494
495 // take off trailing /.
496 if (nLen > 3) {
497 if (!strcmp(path + (nLen - 3), "/..")) {
498 path[nLen - 3] = '\0';
499 char *pszNewPath = qfile_get_dir(path);
500 strcpy(path, pszNewPath);
501 free(pszNewPath);
502 continue;
503 }
504 }
505
506 break;
507 }
508
509 return path;
510}
511
512/**
513 * Make full absolute system path string.
514 *
515 * @param buf buffer string pointer
516 * @param bufsize buffer size
517 * @param path path string
518 *
519 * @return buffer pointer on success, or NULL on failure.
520 *
521 * @code
522 * char buf[PATH_MAX];
523 * chdir("/usr/local");
524 * qfile_abspath(buf, sizeof(buf), "."); // returns "/usr/local"
525 * qfile_abspath(buf, sizeof(buf), ".."); // returns "/usr"
526 * qfile_abspath(buf, sizeof(buf), "etc"); // returns "/usr/local/etc"
527 * qfile_abspath(buf, sizeof(buf), "/etc"); // returns "/etc"
528 * qfile_abspath(buf, sizeof(buf), "/etc/"); // returns "/etc/"
529 * @endcode
530 */
531char *qfile_abspath(char *buf, size_t bufsize, const char *path) {
532 if (bufsize == 0)
533 return NULL;
534
535 if (path[0] == '/') {
536 qstrcpy(buf, bufsize, path);
537 } else {
538 if (getcwd(buf, bufsize) == NULL)
539 return NULL;
540 strcat(buf, "/");
541 strcat(buf, path);
542 }
544
545 return buf;
546}
void * qfile_load(const char *filepath, size_t *nbytes)
Load a file into memory.
Definition qfile.c:159
char * qfile_correct_path(char *path)
Correct path string.
Definition qfile.c:435
char * qfile_get_name(const char *filepath)
Get the file name from a path.
Definition qfile.c:357
void * qfile_read(FILE *fp, size_t *nbytes)
Read data from a file stream.
Definition qfile.c:215
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:531
ssize_t qfile_save(const char *filepath, const void *buf, size_t size, bool append)
Save data into file.
Definition qfile.c:284
char * qfile_get_ext(const char *filepath)
Get the file extension from a path.
Definition qfile.c:387
bool qfile_mkdir(const char *dirpath, mode_t mode, bool recursive)
Attempts to create a directory recursively.
Definition qfile.c:313
char * qfile_get_dir(const char *filepath)
Get the directory part of a path.
Definition qfile.c:372
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:338
off_t qfile_get_size(const char *filepath)
Get file size.
Definition qfile.c:411
char * qstrcpy(char *dst, size_t size, const char *src)
Copy src string to dst.
Definition qstring.c:298
char * qstrtrim(char *str)
Remove whitespace, including CR and LF, from both ends of a string.
Definition qstring.c:55
bool qstrtest(int(*testfunc)(int), const char *str)
Test whether a string matches a character rule.
Definition qstring.c:722
char * qstrreplace(const char *mode, char *srcstr, const char *tokstr, const char *word)
Replace tokens or strings in a source string using the given mode.
Definition qstring.c:209
char * qstrlower(char *str)
Convert character to lower character.
Definition qstring.c:516