Impl. --generate-dir=<DIR>
All checks were successful
Run Unit Tests / build-and-run-unit-tests (push) Successful in 22s

Also added flag `--generate-enable-overwrite`.

Resolves #11
This commit is contained in:
Stephen Seo 2024-11-10 20:39:49 +09:00
parent 0d552d2cb0
commit de2d15033e
4 changed files with 199 additions and 0 deletions

View file

@ -44,6 +44,8 @@ void print_usage(void) {
puts(" --enable-cache-dir=<DIR>"); puts(" --enable-cache-dir=<DIR>");
puts(" --cache-entry-lifetime-seconds=<SECONDS>"); puts(" --cache-entry-lifetime-seconds=<SECONDS>");
puts(" --enable-static-dir=<DIR>"); puts(" --enable-static-dir=<DIR>");
puts(" --generate-dir=<DIR>");
puts(" --generate-enable-overwrite");
} }
Args parse_args(int32_t argc, char **argv) { 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); printf("Directory \"%s\" exists.\n", args.static_dir);
} }
closedir(d); 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 { } else {
fprintf(stderr, "ERROR: Invalid args!\n"); fprintf(stderr, "ERROR: Invalid args!\n");
print_usage(); print_usage();

View file

@ -28,6 +28,7 @@ typedef struct Args {
// xxxx xxx1 - disable peer addr print. // xxxx xxx1 - disable peer addr print.
// xxxx xx0x - disable listen on config file for reloading. // xxxx xx0x - disable listen on config file for reloading.
// xxxx xx1x - enable 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 flags;
uint16_t port; uint16_t port;
// Does not need to be free'd, this should point to a string in argv. // 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. // 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. // Does not need to be free'd since it points to a string in argv.
const char *static_dir; 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; } Args;
void print_usage(void); void print_usage(void);

View 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 /// 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 /// key "PATH" is the path it represents. The "key" value should match the
/// mentioned value for "PATH". /// 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 { union {
SDArchiverHashMap *paths; SDArchiverHashMap *paths;
SDArchiverHashMap *hash_map; SDArchiverHashMap *hash_map;

View file

@ -21,7 +21,10 @@
#include <stdint.h> #include <stdint.h>
// Linux/Unix includes. // Linux/Unix includes.
#include <libgen.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <sys/types.h>
#include <dirent.h>
#include <netinet/in.h> #include <netinet/in.h>
#include <unistd.h> #include <unistd.h>
#include <signal.h> #include <signal.h>
@ -33,11 +36,14 @@
// Third party includes. // Third party includes.
#include <SimpleArchiver/src/helpers.h> #include <SimpleArchiver/src/helpers.h>
#include <SimpleArchiver/src/data_structures/hash_map.h>
#include <SimpleArchiver/src/data_structures/linked_list.h>
// Local includes. // Local includes.
#include "arg_parse.h" #include "arg_parse.h"
#include "big_endian.h" #include "big_endian.h"
#include "config.h" #include "config.h"
#include "http_template.h"
#include "tcp_socket.h" #include "tcp_socket.h"
#include "signal_handling.h" #include "signal_handling.h"
#include "globals.h" #include "globals.h"
@ -326,6 +332,139 @@ int c_simple_http_manage_connections(void *data, void *ud) {
return 1; 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) { int main(int argc, char **argv) {
__attribute__((cleanup(c_simple_http_free_args))) __attribute__((cleanup(c_simple_http_free_args)))
Args args = parse_args(argc, argv); Args args = parse_args(argc, argv);
@ -348,6 +487,22 @@ int main(int argc, char **argv) {
return 5; 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 = __attribute__((cleanup(cleanup_tcp_socket))) int tcp_socket =
create_tcp_socket(args.port); create_tcp_socket(args.port);
if (tcp_socket == -1) { if (tcp_socket == -1) {