/* * blogc: A blog compiler. * Copyright (C) 2014-2017 Rafael G. Martins * * This program can be distributed under the terms of the BSD License. * See the file LICENSE. */ #include #include #include #include #include #include #include #include #include "../common/error.h" #include "../common/file.h" #include "../common/utils.h" #include "ctx.h" #include "exec.h" #include "settings.h" char* bm_exec_find_binary(const char *argv0, const char *bin, const char *env) { // first try: env var const char *env_bin = getenv(env); if (env_bin != NULL) { return bc_shell_quote(env_bin); } // second try: same dir as current exec // we rely on some assumptions here: // // - if binary is called without a directory, that means location will // be resolved from $PATH, we don't care about doing a dir lookup. // - we *never* call chdir anywhere in the code, so we can assume // that relative paths will work as expected. // - windows path sep is not supported if (argv0 != NULL && (NULL != strchr(argv0, '/'))) { char *path = bc_strdup(argv0); char *dir = bc_strdup(dirname(path)); free(path); char *tmp = bc_strdup_printf("%s/%s", dir, bin); free(dir); if (0 == access(tmp, X_OK)) { char *rv = bc_shell_quote(tmp); free(tmp); return rv; } free(tmp); } // last try: $PATH return bc_strdup(bin); } int bm_exec_command(const char *cmd, const char *input, char **output, char **error, bc_error_t **err) { if (err == NULL || *err != NULL) return 3; int fd_in[2]; if (-1 == pipe(fd_in)) { *err = bc_error_new_printf(BLOGC_MAKE_ERROR_EXEC, "Failed to create stdin pipe: %s", strerror(errno)); return 3; } int fd_out[2]; if (-1 == pipe(fd_out)) { *err = bc_error_new_printf(BLOGC_MAKE_ERROR_EXEC, "Failed to create stdout pipe: %s", strerror(errno)); close(fd_in[0]); close(fd_in[1]); return 3; } int fd_err[2]; if (-1 == pipe(fd_err)) { *err = bc_error_new_printf(BLOGC_MAKE_ERROR_EXEC, "Failed to create stderr pipe: %s", strerror(errno)); close(fd_in[0]); close(fd_in[1]); close(fd_out[0]); close(fd_out[1]); return 3; } pid_t pid = fork(); if (pid == -1) { *err = bc_error_new_printf(BLOGC_MAKE_ERROR_EXEC, "Failed to fork: %s", strerror(errno)); close(fd_in[0]); close(fd_in[1]); close(fd_out[0]); close(fd_out[1]); close(fd_err[0]); close(fd_err[1]); return 3; } // child if (pid == 0) { close(fd_in[1]); close(fd_out[0]); close(fd_err[0]); dup2(fd_in[0], STDIN_FILENO); dup2(fd_out[1], STDOUT_FILENO); dup2(fd_err[1], STDERR_FILENO); char *const argv[] = { "/bin/sh", "-c", (char*) cmd, NULL, }; execv(argv[0], argv); exit(1); } // parent close(fd_in[0]); close(fd_out[1]); close(fd_err[1]); if (input != NULL) { if (-1 == write(fd_in[1], input, strlen(input))) { *err = bc_error_new_printf(BLOGC_MAKE_ERROR_EXEC, "Failed to write to stdin pipe: %s", strerror(errno)); close(fd_in[1]); close(fd_out[0]); close(fd_err[0]); return 3; } } close(fd_in[1]); char buffer[BC_FILE_CHUNK_SIZE]; ssize_t s; bc_string_t *out = NULL; while(0 != (s = read(fd_out[0], buffer, BC_FILE_CHUNK_SIZE))) { if (s == -1) { *err = bc_error_new_printf(BLOGC_MAKE_ERROR_EXEC, "Failed to read from stdout pipe: %s", strerror(errno)); close(fd_out[0]); close(fd_err[0]); bc_string_free(out, true); return 3; } if (out == NULL) { out = bc_string_new(); } bc_string_append_len(out, buffer, s); } if (out != NULL) { *output = bc_string_free(out, false); } close(fd_out[0]); out = NULL; while(0 != (s = read(fd_err[0], buffer, BC_FILE_CHUNK_SIZE))) { if (s == -1) { *err = bc_error_new_printf(BLOGC_MAKE_ERROR_EXEC, "Failed to read from stderr pipe: %s", strerror(errno)); close(fd_err[0]); bc_string_free(out, true); return 3; } if (out == NULL) out = bc_string_new(); bc_string_append_len(out, buffer, s); } if (out != NULL) { *error = bc_string_free(out, false); } close(fd_err[0]); int status; waitpid(pid, &status, 0); return WEXITSTATUS(status); } static void list_variables(const char *key, const char *value, bc_string_t *str) { char *tmp = bc_shell_quote(value); bc_string_append_printf(str, " -D %s=%s", key, tmp); free(tmp); } char* bm_exec_build_blogc_cmd(const char *blogc_bin, bm_settings_t *settings, bc_trie_t *variables, bool listing, const char *template, const char *output, bool sources_stdin) { bc_string_t *rv = bc_string_new(); const char *locale = NULL; if (settings != NULL) { locale = bc_trie_lookup(settings->settings, "locale"); } if (locale != NULL) { char *tmp = bc_shell_quote(locale); bc_string_append_printf(rv, "LC_ALL=%s ", tmp); free(tmp); } bc_string_append(rv, blogc_bin); if (settings != NULL) { bc_trie_foreach(settings->env, (bc_trie_foreach_func_t) list_variables, rv); } bc_trie_foreach(variables, (bc_trie_foreach_func_t) list_variables, rv); if (listing) { bc_string_append(rv, " -l"); } if (template != NULL) { char *tmp = bc_shell_quote(template); bc_string_append_printf(rv, " -t %s", tmp); free(tmp); } if (output != NULL) { char *tmp = bc_shell_quote(output); bc_string_append_printf(rv, " -o %s", tmp); free(tmp); } if (sources_stdin) { bc_string_append(rv, " -i"); } return bc_string_free(rv, false); } int bm_exec_blogc(bm_ctx_t *ctx, bc_trie_t *variables, bool listing, bm_filectx_t *template, bm_filectx_t *output, bc_slist_t *sources, bool only_first_source) { if (ctx == NULL) return 3; bc_string_t *input = bc_string_new(); for (bc_slist_t *l = sources; l != NULL; l = l->next) { bc_string_append_printf(input, "%s\n", ((bm_filectx_t*) l->data)->path); if (only_first_source) break; } char *cmd = bm_exec_build_blogc_cmd(ctx->blogc, ctx->settings, variables, listing, template->path, output->path, input->len > 0); if (ctx->verbose) printf("%s\n", cmd); else printf(" BLOGC %s\n", output->short_path); fflush(stdout); char *out = NULL; char *err = NULL; bc_error_t *error = NULL; int rv = bm_exec_command(cmd, input->str, &out, &err, &error); if (error != NULL) { bc_error_print(error, "blogc-make"); free(cmd); free(out); free(err); bc_string_free(input, true); bc_error_free(error); return 3; } if (rv != 0) { if (ctx->verbose) { fprintf(stderr, "blogc-make: error: Failed to execute command.\n" "\n" "STATUS CODE: %d\n", rv); if (input->len > 0) { fprintf(stderr, "\nSTDIN:\n" "----------------------------->8-----------------------------\n" "%s\n" "----------------------------->8-----------------------------\n", bc_str_strip(input->str)); } if (out != NULL) { fprintf(stderr, "\nSTDOUT:\n" "----------------------------->8-----------------------------\n" "%s\n" "----------------------------->8-----------------------------\n", bc_str_strip(out)); } if (err != NULL) { fprintf(stderr, "\nSTDERR:\n" "----------------------------->8-----------------------------\n" "%s\n" "----------------------------->8-----------------------------\n", bc_str_strip(err)); } } else { fprintf(stderr, "blogc-make: error: Failed to execute command, returned " "status code: %d\n", rv); } } bc_string_free(input, true); free(cmd); free(out); free(err); return rv == 127 ? 3 : rv; } int bm_exec_blogc_runserver(bm_ctx_t *ctx, const char *host, const char *port, const char *threads) { if (ctx == NULL) return 3; bc_string_t *cmd = bc_string_new(); bc_string_append(cmd, ctx->blogc_runserver); if (host != NULL) { char *tmp = bc_shell_quote(host); bc_string_append_printf(cmd, " -t %s", tmp); free(tmp); } if (port != NULL) { char *tmp = bc_shell_quote(port); bc_string_append_printf(cmd, " -p %s", tmp); free(tmp); } if (threads != NULL) { char *tmp = bc_shell_quote(threads); bc_string_append_printf(cmd, " -m %s", tmp); free(tmp); } char *tmp = bc_shell_quote(ctx->output_dir); bc_string_append_printf(cmd, " %s", tmp); free(tmp); if (ctx->verbose) printf("%s\n\n", cmd->str); else printf("\n"); fflush(stdout); // we don't need pipes to run blogc-runserver, because it is "interactive" int status = system(cmd->str); int rv = WEXITSTATUS(status); bc_string_free(cmd, true); if (rv != 0) { if (rv == 127) { fprintf(stderr, "blogc-make: error: blogc-runserver command not found. Maybe " "it is not installed?\n"); rv = 3; // blogc-make exists, so we should not return 127 } else { fprintf(stderr, "blogc-make: error: Failed to execute command, returned " "status code: %d\n", rv); } } return rv; }