]> git.seodisparate.com - c_simple_http/commitdiff
Impl. `--generate-dir=<DIR>`
authorStephen Seo <seo.disparate@gmail.com>
Sun, 10 Nov 2024 11:39:49 +0000 (20:39 +0900)
committerStephen Seo <seo.disparate@gmail.com>
Sun, 10 Nov 2024 11:39:49 +0000 (20:39 +0900)
Also added flag `--generate-enable-overwrite`.

Resolves https://git.seodisparate.com/stephenseo/c_simple_http/issues/11

src/arg_parse.c
src/arg_parse.h
src/config.h
src/main.c

index 5fea90a3538995cfa71650f0e496164b6319b3f7..87a539eae3d308d1af1db3f602604c779d5693c5 100644 (file)
@@ -44,6 +44,8 @@ void print_usage(void) {
   puts("  --enable-cache-dir=<DIR>");
   puts("  --cache-entry-lifetime-seconds=<SECONDS>");
   puts("  --enable-static-dir=<DIR>");
+  puts("  --generate-dir=<DIR>");
+  puts("  --generate-enable-overwrite");
 }
 
 Args parse_args(int32_t argc, char **argv) {
@@ -152,6 +154,39 @@ Args parse_args(int32_t argc, char **argv) {
         printf("Directory \"%s\" exists.\n", args.static_dir);
       }
       closedir(d);
+    } else if (strncmp(argv[0], "--generate-dir=", 15) == 0) {
+      args.generate_dir = argv[0] + 15;
+      // Check if it actually is an existing directory.
+      DIR *d = opendir(args.generate_dir);
+      if (d == NULL) {
+        if (errno == ENOENT) {
+          printf(
+            "Directory \"%s\" doesn't exist, creating it...\n",
+            args.generate_dir);
+          int ret = mkdir(
+              args.generate_dir,
+              S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
+          if (ret == -1) {
+            fprintf(
+              stderr,
+              "ERROR Failed to create new directory (errno %d)\n",
+              errno);
+            exit(1);
+          }
+        } else {
+          fprintf(
+            stderr,
+            "ERROR Failed to open directory \"%s\" (errno %d)!\n",
+            args.generate_dir,
+            errno);
+          exit(1);
+        }
+      } else {
+        printf("Directory \"%s\" exists.\n", args.generate_dir);
+      }
+      closedir(d);
+    } else if (strcmp(argv[0], "--generate-enable-overwrite") == 0) {
+      args.flags |= 4;
     } else {
       fprintf(stderr, "ERROR: Invalid args!\n");
       print_usage();
index 7595f726f371e489c1b02fc637cd91f3e3277afd..1a4a510d828c7b6d2665bce33d954e2df85b6766 100644 (file)
@@ -28,6 +28,7 @@ typedef struct Args {
   // xxxx xxx1 - disable peer addr print.
   // xxxx xx0x - disable listen on config file for reloading.
   // xxxx xx1x - enable listen on config file for reloading.
+  // xxxx x1xx - enable overwrite on generate.
   uint16_t flags;
   uint16_t port;
   // Does not need to be free'd, this should point to a string in argv.
@@ -41,6 +42,9 @@ typedef struct Args {
   // Non-NULL if static-dir is specified and files in the dir are to be served.
   // Does not need to be free'd since it points to a string in argv.
   const char *static_dir;
+  // Non-NULL if generate-dir is specified.
+  // Does not need to be free'd since it points to a string in argv.
+  const char *generate_dir;
 } Args;
 
 void print_usage(void);
index a194d28ebd48fdb0b0d1335fb55663262a16af99..9d7c9e61a81646e237b04d89a4c856a0b6690943 100644 (file)
@@ -25,6 +25,11 @@ typedef struct C_SIMPLE_HTTP_ParsedConfig {
   /// Each entry in this data structure is a hash map where its value for the
   /// key "PATH" is the path it represents. The "key" value should match the
   /// mentioned value for "PATH".
+  ///
+  /// An example mapping for this structure (based on current example config):
+  /// KEY: "/", VALUE: HashMapWrapper struct
+  /// KEY: "/inner", VALUE: HashMapWrapper struct
+  /// KEY: "/inner/further", VALUE: HashMapWrapper struct
   union {
     SDArchiverHashMap *paths;
     SDArchiverHashMap *hash_map;
index 325663f864316cd1cfacbb3cf1a4181837a4f40a..cf1ce8f31ccda487a03b1d622391b151c081a7ac 100644 (file)
 #include <stdint.h>
 
 // Linux/Unix includes.
+#include <libgen.h>
 #include <sys/socket.h>
+#include <sys/types.h>
+#include <dirent.h>
 #include <netinet/in.h>
 #include <unistd.h>
 #include <signal.h>
 
 // Third party includes.
 #include <SimpleArchiver/src/helpers.h>
+#include <SimpleArchiver/src/data_structures/hash_map.h>
+#include <SimpleArchiver/src/data_structures/linked_list.h>
 
 // Local includes.
 #include "arg_parse.h"
 #include "big_endian.h"
 #include "config.h"
+#include "http_template.h"
 #include "tcp_socket.h"
 #include "signal_handling.h"
 #include "globals.h"
@@ -326,6 +332,139 @@ int c_simple_http_manage_connections(void *data, void *ud) {
   return 1;
 }
 
+int generate_paths_fn(const void *key,
+                      size_t key_size,
+                      const void *value,
+                      void *ud) {
+  const char *path = key;
+  const ConnectionContext *ctx = ud;
+  const char *generate_dir = ctx->args->generate_dir;
+
+  const unsigned long path_len = strlen(path);
+  const unsigned long generate_dir_len = strlen(generate_dir);
+
+  __attribute__((cleanup(simple_archiver_list_free)))
+  SDArchiverLinkedList *string_parts = simple_archiver_list_init();
+
+  // Add generate_dir as first path of paths to join.
+  c_simple_http_add_string_part(string_parts, generate_dir, 0);
+
+  // Ensure next character after generate_dir contains a '/' if generate_dir
+  // didn't contain one at the end.
+  if (generate_dir_len > 0 && generate_dir[generate_dir_len - 1] != '/') {
+    c_simple_http_add_string_part(string_parts, "/", 0);
+  }
+
+  // Append the path.
+  if (strcmp("path", "/") != 0) {
+    // Is not root.
+    uint32_t idx = 0;
+    while (idx <= path_len && path[idx] == '/') {
+      ++idx;
+    }
+    c_simple_http_add_string_part(string_parts, path + idx, 0);
+  }
+
+  // Add the final '/'.
+  if (path_len > 0 && path[path_len - 1] != '/') {
+    c_simple_http_add_string_part(string_parts, "/", 0);
+  }
+
+  // Add the ending "index.html".
+  c_simple_http_add_string_part(string_parts, "index.html", 0);
+
+  // Get the combined string.
+  __attribute__((cleanup(simple_archiver_helper_cleanup_c_string)))
+  char *generated_path = c_simple_http_combine_string_parts(string_parts);
+  if (!generated_path) {
+    fprintf(stderr, "ERROR Failed to get generated path (path: %s)!\n", path);
+    return 1;
+  }
+
+  if ((ctx->args->flags & 4) == 0) {
+    // Overwrite not enabled, check if file already exists.
+    FILE *fd = fopen(generated_path, "rb");
+    if (fd) {
+      fclose(fd);
+      fprintf(
+        stderr,
+        "WARNING Path \"%s\" exists and \"--generate-enable-overwrite\" not "
+          "specified, skipping!\n",
+        generated_path);
+      return 0;
+    }
+  }
+
+  // Ensure the required dirs exist.
+  __attribute__((cleanup(simple_archiver_helper_cleanup_c_string)))
+  char *generated_path_dup = strdup(generated_path);
+
+  uint_fast8_t did_make_generated_path_dir = 0;
+  char *generated_path_dir = dirname(generated_path_dup);
+  if (generated_path_dir) {
+    DIR *fd = opendir(generated_path_dir);
+    if (!fd) {
+      if (errno == ENOENT) {
+        c_simple_http_helper_mkdir_tree(generated_path_dir);
+        did_make_generated_path_dir = 1;
+      } else {
+        fprintf(stderr,
+                "ERROR opendir on path dirname failed unexpectedly (path: %s)!"
+                  "\n",
+                path);
+        return 1;
+      }
+    } else {
+      // Directory already exists.
+      closedir(fd);
+    }
+  } else {
+    fprintf(stderr,
+            "ERROR Failed to get dirname of generated path dir (path: %s)"
+              "!\n",
+            path);
+    return 1;
+  }
+
+  // Generate the html.
+  size_t html_buf_size = 0;
+  __attribute__((cleanup(simple_archiver_helper_cleanup_c_string)))
+  char *html_buf = c_simple_http_path_to_generated(path,
+                                                   ctx->parsed,
+                                                   &html_buf_size,
+                                                   NULL);
+  if (!html_buf || html_buf_size == 0) {
+    fprintf(stderr,
+            "WARNING Failed to generate html for generate (path: %s), "
+              "skipping!\n",
+            path);
+    if (did_make_generated_path_dir) {
+      if (rmdir(generated_path_dir) == -1) {
+        fprintf(stderr,
+                "WARNING rmdir on generated_path_dir failed, errno: %d\n",
+                errno);
+      }
+    }
+    return 0;
+  }
+
+  // Save the html.
+  FILE *fd = fopen(generated_path, "wb");
+  unsigned long fwrite_ret = fwrite(html_buf, 1, html_buf_size, fd);
+  if (fwrite_ret < html_buf_size) {
+    fclose(fd);
+    unlink(generated_path);
+    fprintf(stderr,
+            "ERROR Unable to write entirely to \"%s\"!\n",
+            generated_path);
+    return 1;
+  } else {
+    fclose(fd);
+  }
+
+  return 0;
+}
+
 int main(int argc, char **argv) {
   __attribute__((cleanup(c_simple_http_free_args)))
   Args args = parse_args(argc, argv);
@@ -348,6 +487,22 @@ int main(int argc, char **argv) {
     return 5;
   }
 
+  // If generate-dir is specified, the program must stop after generating or
+  // failure.
+  if (args.generate_dir) {
+    ConnectionContext ctx;
+    ctx.args = &args;
+    ctx.parsed = &parsed_config;
+    if (simple_archiver_hash_map_iter(parsed_config.paths,
+                                      generate_paths_fn,
+                                      &ctx)) {
+      fprintf(stderr, "ERROR during generating!\n");
+      return 1;
+    }
+    puts("Finished generating.");
+    return 0;
+  }
+
   __attribute__((cleanup(cleanup_tcp_socket))) int tcp_socket =
     create_tcp_socket(args.port);
   if (tcp_socket == -1) {