aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoseph Hunkeler <jhunkeler@users.noreply.github.com>2020-04-14 01:31:46 -0400
committerGitHub <noreply@github.com>2020-04-14 01:31:46 -0400
commitb266c1a943c3aee2d3aea05141fb2f21f9ea168b (patch)
tree99eecfeb2b63f34dc1b3d279264eca49d9b16043
parentf04a13fd4b7c665dfecdeaef82e2ab628d6f402d (diff)
parent9dca29e8cc93aef68f638a06c19f4165dfc32e3d (diff)
downloadspmc-b266c1a943c3aee2d3aea05141fb2f21f9ea168b.tar.gz
Merge pull request #23 from jhunkeler/touch-fslist
Add functions w/ tests
-rw-r--r--include/fs.h12
-rw-r--r--include/spm.h7
-rw-r--r--lib/fs.c127
-rw-r--r--tests/test_fs_fslist.c85
-rw-r--r--tests/test_fs_touch.c31
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"
diff --git a/lib/fs.c b/lib/fs.c
index 79c04ff..76dfea8 100644
--- a/lib/fs.c
+++ b/lib/fs.c
@@ -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