summaryrefslogtreecommitdiff
path: root/src/file.c
diff options
context:
space:
mode:
authorRyo Nakamura <upa@haeena.net>2022-10-16 22:12:03 +0900
committerRyo Nakamura <upa@haeena.net>2022-10-16 22:12:03 +0900
commitdef9cfeba74357ca98fc25469d4285139ecd42ce (patch)
tree077c5de04728a1342392d37d95c3c0a7fbe455a3 /src/file.c
parent579629dea21dbbb26cc9c42c4f1f1022c7270144 (diff)
implement fill_file
Diffstat (limited to 'src/file.c')
-rw-r--r--src/file.c281
1 files changed, 281 insertions, 0 deletions
diff --git a/src/file.c b/src/file.c
new file mode 100644
index 0000000..88614ec
--- /dev/null
+++ b/src/file.c
@@ -0,0 +1,281 @@
+#include <stdlib.h>
+#include <stdbool.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <limits.h>
+
+#include <ssh.h>
+#include <util.h>
+#include <file.h>
+
+bool file_has_hostname(char *path)
+{
+ char *p;
+
+ p = strchr(path, ':');
+ if (p) {
+ if (p == path || ((p > path) && *(p - 1) == '\\')) {
+ /* first byte is colon or escaped colon, skip */
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+char *file_find_hostname(char *path)
+{
+ char *dup, *p;
+
+ dup = strdup(path);
+ if (!dup) {
+ pr_err("%s", strerrno());
+ return NULL;
+ }
+
+ p = strchr(dup, ':');
+ if (p) {
+ if (p == dup || ((p > dup) && *(p - 1) == '\\')) {
+ /* first byte is colon or escaped colon, skip */
+ free(dup);
+ } else {
+ /* handle this as remote hostname (with username) */
+ *p = '\0';
+ return dup;
+ }
+ }
+
+ return NULL;
+}
+
+static char *file_find_path(char *path)
+{
+ char *p;
+
+ p = strchr(path, ':');
+ if (p) {
+ if (p == path || ((p > path) && *(p - 1) == '\\')) {
+ /* first byte is colon or escaped colon, skip */
+ return path;
+ } else {
+ return p + 1;
+ }
+ }
+
+ return path;
+}
+
+/* return 1 when path is directory, 0 is not directory, and -1 on error */
+int file_is_directory(char *path, sftp_session sftp)
+{
+ int ret = 0;
+
+ if (sftp) {
+ char *remote_path = file_find_path(path);
+ sftp_attributes attr;
+
+ char *p = *remote_path == '\0' ? "." : remote_path;
+ attr = sftp_stat(sftp, p);
+ if (!attr) {
+ pr_err("file %s: %s\n", p,
+ ssh_get_error(sftp_ssh(sftp)));
+ ret = -1;
+ } else if (attr->type == SSH_FILEXFER_TYPE_DIRECTORY)
+ ret = 1;
+ sftp_attributes_free(attr);
+ } else {
+ struct stat statbuf;
+ if (stat(path, &statbuf) < 0) {
+ pr_err("file %s: %s\n", path, strerrno());
+ ret = -1;
+ } else if ((statbuf.st_mode & S_IFMT) == S_IFDIR)
+ ret = 1;
+ }
+
+ return ret;
+}
+
+static struct file *file_allocate(char *path, size_t size, bool remote)
+{
+ struct file *f;
+
+ f = malloc(sizeof(*f));
+ if (!f) {
+ pr_err("%s\n", strerrno());
+ return NULL;
+ }
+ memset(f, 0, sizeof(*f));
+
+ f->path = strdup(path);
+ if (!f->path) {
+ pr_err("%s\n", strerrno());
+ free(f);
+ return NULL;
+ }
+
+ f->size = size;
+ f->remote = remote;
+
+ return f;
+}
+
+static bool file_should_skip(char *path)
+{
+ int len = strlen(path);
+ if ((len == 1 && strncmp(path, ".", 1) == 0) ||
+ (len == 2 && strncmp(path, "..", 2) == 0)) {
+ return true;
+ }
+ return false;
+}
+
+
+static int file_fill_local_recursive(char *path, struct list_head *head)
+{
+ char child[PATH_MAX];
+ struct stat statbuf;
+ struct dirent *de;
+ DIR *dir;
+ int ret;
+
+ ret = file_is_directory(path, NULL);
+ if (ret < 0)
+ return -1;
+
+ if (ret == 1) {
+ if ((dir = opendir(path)) == NULL) {
+ pr_err("opend to open dir %s: %s\n", path, strerrno());
+ return -1;
+ }
+
+ while ((de = readdir(dir)) != NULL) {
+ if (file_should_skip(de->d_name))
+ continue;
+ snprintf(child, sizeof(child), "%s/%s", path, de->d_name);
+ ret = file_fill_local_recursive(child, head);
+ if (ret < 0)
+ return ret;
+ }
+ } else {
+ /* path is file */
+ if (stat(path, &statbuf) < 0) {
+ pr_err("file %s: %s\n", path, strerrno());
+ return -1;
+ }
+
+ if ((statbuf.st_mode & S_IFMT) == S_IFREG ||
+ (statbuf.st_mode & S_IFMT) == S_IFLNK) {
+ struct file *f = file_allocate(path, statbuf.st_size, false);
+ if (!f) {
+ pr_err("%s\n", strerrno());
+ return -1;
+ }
+ list_add(&f->list, head);
+ }
+ }
+
+ return 0;
+}
+
+static int file_fill_remote_recursive(char *path, sftp_session sftp,
+ struct list_head *head)
+{
+ char child[PATH_MAX];
+ sftp_attributes attr;
+ sftp_dir dir;
+ int ret;
+
+ ret = file_is_directory(path, sftp);
+ if (ret < 0)
+ return -1;
+
+ if (ret == 1) {
+ dir = sftp_opendir(sftp, path);
+ if (!dir) {
+ pr_err("failed to open dir %s: %s\n", path,
+ ssh_get_error(sftp_ssh(sftp)));
+ return -1;
+ }
+
+ while ((attr = sftp_readdir(sftp, dir)) != NULL) {
+ if (file_should_skip(attr->name))
+ continue;
+
+ snprintf(child, sizeof(child), "%s/%s", path, attr->name);
+ ret = file_fill_remote_recursive(child, sftp, head);
+ if (ret < 0)
+ return ret;
+ sftp_attributes_free(attr);
+ }
+
+ if (!sftp_dir_eof(dir)) {
+ pr_err("can't list directory %s: %s\n", path,
+ ssh_get_error(sftp_ssh(sftp)));
+ return -1;
+ }
+
+ if (sftp_closedir(dir) != SSH_OK) {
+ pr_err("can't close directory %s: %s\n", path,
+ ssh_get_error(sftp_ssh(sftp)));
+ return -1;
+ }
+
+ } else {
+ /* path is file */
+ attr = sftp_stat(sftp, path);
+ if (!attr) {
+ pr_err("failed to get stat for %s: %s\n",
+ path, ssh_get_error(sftp_ssh(sftp)));
+ return -1;
+ }
+
+ /* skip special and unknown files */
+ if (attr->type == SSH_FILEXFER_TYPE_REGULAR ||
+ attr->type == SSH_FILEXFER_TYPE_SYMLINK) {
+ struct file *f = file_allocate(path, attr->size, true);
+ if (!f) {
+ pr_err("%s\n", strerrno());
+ return -1;
+ }
+ list_add(&f->list, head);
+ sftp_attributes_free(attr);
+ }
+ }
+
+ return 0;
+}
+
+int file_fill(sftp_session sftp, struct list_head *head, char **src_array, int count)
+{
+ char *src, *path;
+ int ret, n;
+
+ for (n = 0; n < count; n++) {
+ src = *(src_array + n);
+ path = file_find_path(src);
+ path = *path == '\0' ? "." : path;
+ if (file_has_hostname(src))
+ ret = file_fill_remote_recursive(path, sftp, head);
+ else
+ ret = file_fill_local_recursive(path, head);
+ if (ret < 0)
+ return -1;
+ }
+
+ return 0;
+}
+
+
+#ifdef DEBUG
+void file_dump(struct list_head *head)
+{
+ struct file *f;
+
+ list_for_each_entry(f, head, list) {
+ pr_debug("%s %s %lu-byte\n", f->path,
+ f->remote ? "(remote)" : "(local)", f->size);
+ }
+}
+#endif