diff options
-rw-r--r-- | include/fs.h | 12 | ||||
-rw-r--r-- | include/spm.h | 7 | ||||
-rw-r--r-- | lib/fs.c | 127 | ||||
-rw-r--r-- | tests/test_fs_fslist.c | 85 | ||||
-rw-r--r-- | tests/test_fs_touch.c | 31 |
5 files changed, 258 insertions, 4 deletions
diff --git a/include/fs.h b/include/fs.h index c12fe83..796c350 100644 --- a/include/fs.h +++ b/include/fs.h @@ -18,9 +18,18 @@ typedef struct { size_t files_length; } FSTree; +typedef struct { + char *root; + struct dirent **record; + size_t records; + size_t _num_alloc; +} FSList; + int _fstree_compare(const FTSENT **a, const FTSENT **b); -void fstree_free(FSTree *fsdata); FSTree *fstree(const char *_path, char **filter_by, unsigned int filter_mode); +void fstree_free(FSTree *fsdata); +FSList *fslist(const char *path); +void fslist_free(FSList *fsdata); int exists(const char *filename); int rmdirs(const char *_path); long int get_file_size(const char *filename); @@ -31,6 +40,7 @@ int rsync(const char *_args, const char *_source, const char *_destination); char *human_readable_size(uint64_t n); char *expandpath(const char *_path); char *spm_mkdtemp(const char *name, const char *extended_path); +int touch(const char *path); #endif //SPM_FSTREE_H diff --git a/include/spm.h b/include/spm.h index 895354f..93f1947 100644 --- a/include/spm.h +++ b/include/spm.h @@ -7,8 +7,6 @@ #include <ctype.h> #include <dirent.h> #include <errno.h> -#include <fts.h> -#include <glob.h> #include <math.h> #include <stdio.h> #include <stdlib.h> @@ -16,14 +14,17 @@ #include <stdarg.h> #include <stdint.h> #include <string.h> -#include <unistd.h> #include <time.h> #include <sys/stat.h> #include <openssl/md5.h> #include <openssl/sha.h> #if !defined(_WIN32) +#include <fts.h> +#include <glob.h> +#include <unistd.h> #include <sys/utsname.h> +#include <utime.h> #endif #include "package.h" @@ -118,6 +118,80 @@ int _fstree_compare(const FTSENT **one, const FTSENT **two) { } /** + * Produce a zero-depth directory listing + * + * @param path absolute or relative path string (directory) + * @return error=NULL, success=`FSList` structure + */ +FSList *fslist(const char *path) { + FSList *tree = NULL; + DIR *dir = NULL; + struct dirent *d_ent = NULL; + + if (path == NULL) { + return NULL; + } + + tree = calloc(1, sizeof(FSList)); + if (tree == NULL) { + perror(path); + return NULL; + } + + tree->root = strdup(path); + tree->records = 0; + tree->_num_alloc = 1; + + tree->record = calloc(tree->_num_alloc, sizeof(struct dirent *)); + if (tree->record == NULL) { + perror("tree->record"); + goto failed; + } + + if ((dir = opendir(tree->root)) == NULL) { + perror(tree->root); + goto failed; + } + + errno = 0; // clear errno so perror prints the correct error + while ((d_ent = readdir(dir)) != NULL) { + struct dirent **tmp = NULL; + if (strcmp(d_ent->d_name, ".") == 0 || strcmp(d_ent->d_name, "..") == 0) { + continue; + } + + tree->record[tree->records] = calloc(1, sizeof(struct dirent)); + memcpy(tree->record[tree->records], d_ent, sizeof(struct dirent)); + + if (tree->record[tree->records] == NULL) { + perror("unable to allocate record for tree->files"); + goto failed; + } + + tree->records++; + tree->_num_alloc++; + + tmp = realloc(tree->record, tree->_num_alloc * sizeof(struct dirent *)); + if (tmp == NULL) { + perror("unable to reallocate tree->record"); + goto failed; + } + tree->record = tmp; + tree->record[tree->records] = NULL; + } + + if (errno) { + perror(path); +failed: // label + fslist_free(tree); + return NULL; + } + + closedir(dir); + return tree; +} + +/** * * @param _path * @return @@ -169,6 +243,23 @@ void fstree_free(FSTree *fsdata) { } } +void fslist_free(FSList *fsdata) { + if (fsdata == NULL) { + return; + } + + if (fsdata->root != NULL) { + free(fsdata->root); + } + if (fsdata->record != NULL) { + for (size_t i = 0; fsdata->record[i] != NULL; i++) { + free(fsdata->record[i]); + } + } + free(fsdata->record); + free(fsdata); +} + /** * Expand "~" to the user's home directory * @@ -508,3 +599,39 @@ char *spm_mkdtemp(const char *name, const char *extended_path) { return strdup(tmpdir); } + /** + * Create an empty file or update a file's modified timestamp + * @param path path to file + * @return error=-1, success=0 + */ + int touch(const char *path) { + FILE *fp = NULL; + struct stat st; + int path_stat = 0; + + if (path == NULL) { + return -1; + } + + path_stat = stat(path, &st); + if (path_stat < 0) { + if ((fp = fopen(path, "w+")) == NULL) { + perror(path); + return -1; + } + fclose(fp); + } else { + if ((S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) > 0) { + if (utime(path, NULL) < 0) { + // failed to set file time + perror(path); + return -1; + } + // updated file time + } else { + return -1; + } + } + + return 0; + } diff --git a/tests/test_fs_fslist.c b/tests/test_fs_fslist.c new file mode 100644 index 0000000..05bc2b7 --- /dev/null +++ b/tests/test_fs_fslist.c @@ -0,0 +1,85 @@ +#include "spm.h" +#include "framework.h" + +struct TestCase testCase[] = { + {.arg[0].sptr = "fslist/testdir", .arg[1].sptr = "testfile", .arg[2].sptr = "testlink"}, +}; +size_t numCases = sizeof(testCase) / sizeof(struct TestCase); + +int main(int argc, char *argv[]) { + for (size_t i = 0; i < numCases; i++) { + char *filename = NULL; + char *linkname = NULL; + char *dirnam = NULL; + FSList *listing = NULL; + + if (startswith(testCase[i].arg[0].sptr, DIRSEPS)) { + fprintf(stderr, "INSECURE TEST CASE: '%s' (starts with, or is, '%s')\n", testCase[i].arg[0].sptr, DIRSEPS); + exit(2); + } + + // Clean previous run + rmdirs(dirname(testCase[i].arg[0].sptr)); + + // Create test case directory + mkdirs(testCase[i].arg[0].sptr, 0755); + + // Render paths + filename = join((char *[]){testCase[i].arg[0].sptr, testCase[i].arg[1].sptr, NULL}, DIRSEPS); + linkname = join((char *[]){testCase[i].arg[0].sptr, testCase[i].arg[2].sptr, NULL}, DIRSEPS); + dirnam = join((char *[]){testCase[i].arg[0].sptr, basename(testCase[i].arg[0].sptr), NULL}, DIRSEPS); + + // Create a file + if (touch(filename) < 0) { + perror(filename); + exit(1); + } + + // Create a symlink (to file ^) + if (symlink(basename(filename), linkname) < 0) { + perror(linkname); + exit(1); + } + + // Create a directory + if (mkdir(dirnam, 0755) < 0) { + perror(dirnam); + exit(1); + } + + // Populate directory listing + listing = fslist(testCase[i].arg[0].sptr); + + // Check FSList structure + myassert(listing != NULL, "fslist() return NULL\n"); + myassert(strcmp(listing->root, testCase[i].arg[0].sptr) == 0, "listing->root points to '%s', instead of '%s'", listing->root, testCase[i].arg[0].sptr); + myassert(listing->records > 0, "listing->records should be: >0 (was: %zu)\n", listing->records); + myassert(listing->_num_alloc > listing->records, "listing->_num_alloc should be: >%zu (was: %zu)\n", listing->records, listing->_num_alloc); + + printf("root = %s\n", listing->root); + // If this for-loop segfaults then the test fails + for (size_t d = 0; d < listing->records; d++) { + char type[NAME_MAX]; + switch (listing->record[d]->d_type) { + case DT_DIR: + strcpy(type, "directory"); + break; + case DT_LNK: + strcpy(type, "symlink"); + break; + case DT_REG: + strcpy(type, "file"); + break; + default: + strcpy(type, "unknown"); + break; + } + printf("%s[%zu] = %s\n", type, d, listing->record[d]->d_name); + } + + // Clean up resources + rmdirs(dirname(testCase[i].arg[0].sptr)); + fslist_free(listing); + } + return 0; +}
\ No newline at end of file diff --git a/tests/test_fs_touch.c b/tests/test_fs_touch.c new file mode 100644 index 0000000..dfd7ddb --- /dev/null +++ b/tests/test_fs_touch.c @@ -0,0 +1,31 @@ +#include "spm.h" +#include "framework.h" + +#define FILENAME "touched_file" + +const char *testFmt = "case: '%s': returned '%d', expected '%d'\n"; +struct TestCase testCase[] = { + {.caseValue.sptr = FILENAME, .truthValue.signed_integer = 0}, // create file + {.caseValue.sptr = FILENAME, .truthValue.signed_integer = 0}, // update file + {.caseValue.sptr = FILENAME, .truthValue.signed_integer = 0}, // update file + {.caseValue.sptr = ".", .truthValue.signed_integer = -1}, +}; +size_t numCases = sizeof(testCase) / sizeof(struct TestCase); + +static void cleanup() { + if (access(FILENAME, F_OK) == 0) { + unlink(FILENAME); + } +} + +int main(int argc, char *argv[]) { + cleanup(); + + for (size_t i = 0; i < numCases; i++) { + int result = touch(testCase[i].caseValue.sptr); + myassert(result == testCase[i].truthValue.signed_integer, testFmt, testCase[i].caseValue.sptr, result, testCase[i].truthValue.signed_integer); + } + + cleanup(); + return 0; +}
\ No newline at end of file |