|
#include <assert.h>
#include <errno.h>
#include <getopt.h>
#include <libgen.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <time.h>
#include <JavaScriptCore/JavaScript.h>
#include "linenoise.h"
#include "zip.h"
#define CONSOLE_LOG_BUF_SIZE 1000
char console_log_buf[CONSOLE_LOG_BUF_SIZE];
int exit_value = 0;
JSStringRef to_string(JSContextRef ctx, JSValueRef val);
JSValueRef evaluate_script(JSContextRef ctx, char *script, char *source);
char *value_to_c_string(JSContextRef ctx, JSValueRef val);
JSValueRef evaluate_source(JSContextRef ctx, char *type, char *source_value, bool expression, char *set_ns);
char *munge(char *s);
void bootstrap(JSContextRef ctx, char *deps_file_path, char *goog_base_path);
JSObjectRef get_function(JSContextRef ctx, char *namespace, char *name);
char* get_contents(char *path, time_t *last_modified);
void write_contents(char *path, char *contents);
int mkdir_p(char *path);
#ifdef DEBUG
#define debug_print_value(prefix, ctx, val) print_value(prefix ": ", ctx, val)
#else
#define debug_print_value(prefix, ctx, val)
#endif
void print_value(char *prefix, JSContextRef ctx, JSValueRef val) {
if (val != NULL) {
JSStringRef str = to_string(ctx, val);
char *ex_str = value_to_c_string(ctx, JSValueMakeString(ctx, str));
printf("%s%s\n", prefix, ex_str);
free(ex_str);
}
}
JSValueRef function_console_log(JSContextRef ctx, JSObjectRef function, JSObjectRef this_object,
size_t argc, const JSValueRef args[], JSValueRef* exception) {
for (int i = 0; i < argc; i++) {
if (i > 0) {
fprintf(stdout, " ");
}
JSStringRef str = to_string(ctx, args[i]);
JSStringGetUTF8CString(str, console_log_buf, CONSOLE_LOG_BUF_SIZE);
fprintf(stdout, "%s", console_log_buf);
}
fprintf(stdout, "\n");
return JSValueMakeUndefined(ctx);
}
JSValueRef function_console_error(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject,
size_t argc, const JSValueRef args[], JSValueRef* exception) {
for (int i = 0; i < argc; i++) {
if (i > 0) {
fprintf(stderr, " ");
}
JSStringRef str = to_string(ctx, args[i]);
JSStringGetUTF8CString(str, console_log_buf, CONSOLE_LOG_BUF_SIZE);
fprintf(stderr, "%s", console_log_buf);
}
fprintf(stderr, "\n");
return JSValueMakeUndefined(ctx);
}
JSValueRef function_read_file(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject,
size_t argc, const JSValueRef args[], JSValueRef* exception) {
// TODO: implement fully
if (argc == 1 && JSValueGetType(ctx, args[0]) == kJSTypeString) {
char path[100];
JSStringRef path_str = JSValueToStringCopy(ctx, args[0], NULL);
assert(JSStringGetLength(path_str) < 100);
JSStringGetUTF8CString(path_str, path, 100);
JSStringRelease(path_str);
// debug_print_value("read_file", ctx, args[0]);
char full_path[150];
// TODO: should not load from here?
snprintf(full_path, 150, "%s/%s", "out", path);
time_t last_modified = 0;
char *contents = get_contents(full_path, &last_modified);
if (contents != NULL) {
JSStringRef contents_str = JSStringCreateWithUTF8CString(contents);
free(contents);
JSValueRef res[2];
res[0] = JSValueMakeString(ctx, contents_str);
res[1] = JSValueMakeNumber(ctx, last_modified);
return JSObjectMakeArray(ctx, 2, res, NULL);
}
}
return JSValueMakeNull(ctx);
}
JSValueRef function_load(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject,
size_t argc, const JSValueRef args[], JSValueRef* exception) {
// TODO: implement fully
if (argc == 1 && JSValueGetType(ctx, args[0]) == kJSTypeString) {
char path[100];
JSStringRef path_str = JSValueToStringCopy(ctx, args[0], NULL);
assert(JSStringGetLength(path_str) < 100);
JSStringGetUTF8CString(path_str, path, 100);
JSStringRelease(path_str);
// debug_print_value("load", ctx, args[0]);
char full_path[150];
// TODO: should not load from here?
snprintf(full_path, 150, "%s/%s", "out", path);
JSValueRef contents_val = NULL;
time_t last_modified = 0;
char *contents = get_contents(full_path, &last_modified);
if (contents != NULL) {
JSStringRef contents_str = JSStringCreateWithUTF8CString(contents);
free(contents);
JSValueRef res[2];
res[0] = JSValueMakeString(ctx, contents_str);;
res[1] = JSValueMakeNumber(ctx, last_modified);
return JSObjectMakeArray(ctx, 2, res, NULL);
}
}
return JSValueMakeNull(ctx);
}
JSValueRef function_load_deps_cljs_files(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject,
size_t argc, const JSValueRef args[], JSValueRef* exception) {
// TODO: not implemented
fprintf(stderr, "WARN: %s: stub\n", __func__);
return JSObjectMakeArray(ctx, 0, NULL, NULL);
}
JSValueRef function_cache(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject,
size_t argc, const JSValueRef args[], JSValueRef* exception) {
if (argc == 4 &&
JSValueGetType (ctx, args[0]) == kJSTypeString &&
JSValueGetType (ctx, args[1]) == kJSTypeString &&
(JSValueGetType (ctx, args[2]) == kJSTypeString
|| JSValueGetType (ctx, args[2]) == kJSTypeNull) &&
(JSValueGetType (ctx, args[3]) == kJSTypeString
|| JSValueGetType (ctx, args[3]) == kJSTypeNull)) {
debug_print_value("cache", ctx, args[0]);
char *cache_prefix = value_to_c_string(ctx, args[0]);
char *source = value_to_c_string(ctx, args[1]);
char *cache = value_to_c_string(ctx, args[2]);
char *sourcemap = value_to_c_string(ctx, args[3]);
char *suffix = NULL;
int max_suffix_len = 20;
int prefix_len = strlen(cache_prefix);
char *path = malloc((prefix_len + max_suffix_len) * sizeof(char));
memset(path, 0, prefix_len + max_suffix_len);
suffix = ".js";
strcpy(path, cache_prefix);
strcat(path, suffix);
write_contents(path, source);
suffix = ".cache.json";
strcpy(path, cache_prefix);
strcat(path, suffix);
write_contents(path, cache);
suffix = ".js.map.json";
strcpy(path, cache_prefix);
strcat(path, suffix);
write_contents(path, sourcemap);
free(cache_prefix);
free(source);
free(cache);
free(sourcemap);
free(path);
}
return JSValueMakeNull(ctx);
}
JSValueRef function_eval(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject,
size_t argc, const JSValueRef args[], JSValueRef* exception) {
JSValueRef val = NULL;
if (argc == 2
&& JSValueGetType(ctx, args[0]) == kJSTypeString
&& JSValueGetType(ctx, args[1]) == kJSTypeString) {
// debug_print_value("eval", ctx, args[0]);
JSStringRef sourceRef = JSValueToStringCopy(ctx, args[0], NULL);
JSStringRef pathRef = JSValueToStringCopy(ctx, args[1], NULL);
JSEvaluateScript(ctx, sourceRef, NULL, pathRef, 0, &val);
JSStringRelease(pathRef);
JSStringRelease(sourceRef);
}
return val != NULL ? val : JSValueMakeNull(ctx);
}
JSValueRef function_get_term_size(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject,
size_t argc, const JSValueRef args[], JSValueRef* exception) {
// TODO: not implemented
fprintf(stderr, "WARN: %s: stub\n", __func__);
return JSValueMakeNull(ctx);
}
JSValueRef function_print_fn(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject,
size_t argc, const JSValueRef args[], JSValueRef* exception) {
if (argc == 1 && JSValueIsString(ctx, args[0])) {
char *str = value_to_c_string(ctx, args[0]);
fprintf(stdout, "%s", str);
fflush(stdout);
free(str);
}
return JSValueMakeNull(ctx);
}
JSValueRef function_print_err_fn(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject,
size_t argc, const JSValueRef args[], JSValueRef* exception) {
if (argc == 1 && JSValueIsString(ctx, args[0])) {
char *str = value_to_c_string(ctx, args[0]);
fprintf(stderr, "%s", str);
fflush(stderr);
free(str);
}
return JSValueMakeNull(ctx);
}
JSValueRef function_set_exit_value(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject,
size_t argc, const JSValueRef args[], JSValueRef* exception) {
if (argc == 1 && JSValueGetType (ctx, args[0]) == kJSTypeNumber) {
exit_value = JSValueToNumber(ctx, args[0], NULL);
}
return JSValueMakeNull(ctx);
}
JSValueRef function_import_script(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject,
size_t argc, const JSValueRef args[], JSValueRef* exception) {
if (argc == 1 && JSValueGetType(ctx, args[0]) == kJSTypeString) {
JSStringRef path_str_ref = JSValueToStringCopy(ctx, args[0], NULL);
char path[100];
path[0] = '\0';
JSStringGetUTF8CString(path_str_ref, path, 100);
JSStringRelease(path_str_ref);
char full_path[150];
snprintf(full_path, 150, "%s/%s", "out", path);
char *buf = get_contents(full_path, NULL);
if (buf == NULL) {
goto err;
}
evaluate_script(ctx, buf, full_path);
free(buf);
}
return JSValueMakeUndefined(ctx);
err:
// TODO: Fill exception with error from errno
return JSValueMakeUndefined(ctx);
}
void register_global_function(JSContextRef ctx, char *name, JSObjectCallAsFunctionCallback handler) {
JSObjectRef global_obj = JSContextGetGlobalObject(ctx);
JSStringRef fn_name = JSStringCreateWithUTF8CString(name);
JSObjectRef fn_obj = JSObjectMakeFunctionWithCallback(ctx, fn_name, handler);
JSObjectSetProperty(ctx, global_obj, fn_name, fn_obj, kJSPropertyAttributeNone, NULL);
}
void usage(char *program_name) {
printf("%s [flags]\n", program_name);
printf("\n");
printf(
" -h, --help Display this help message\n"
" -v, --verbose Print verbose diagnostics\n"
" -r, --repl Whether to start a REPL\n"
" -s, --static-fns Whether to use the :static-fns compiler option\n"
" -k, --cache The directory to cache compiler results in\n"
" -j, --javascript Whether to run a JavaScript REPL\n"
" -e, --eval Evaluate the given expression\n"
);
}
bool verbose = false;
bool repl = false;
bool static_fns = false;
char *cache_path = NULL;
bool javascript = false;
int main(int argc, char **argv) {
int num_eval_args = 0;
char **eval_args = NULL;
struct option long_options[] = {
{"help", no_argument, NULL, 'h'},
{"verbose", no_argument, NULL, 'v'},
{"repl", no_argument, NULL, 'r'},
{"static-fns", no_argument, NULL, 's'},
{"cache", required_argument, NULL, 'k'},
{"javascript", no_argument, NULL, 'j'},
{"eval", required_argument, NULL, 'e'},
{0, 0, 0, 0}
};
int opt, option_index;
while ((opt = getopt_long(argc, argv, "hvrsk:je:", long_options, &option_index)) != -1) {
switch (opt) {
case 'h':
usage(argv[0]);
exit(0);
case 'v':
verbose = true;
break;
case 'r':
repl = true;
break;
case 's':
static_fns = true;
break;
case 'k':
cache_path = argv[optind - 1];
break;
case 'j':
javascript = true;
break;
case 'e':
num_eval_args += 1;
eval_args = realloc(eval_args, num_eval_args * sizeof(char*));
eval_args[num_eval_args - 1] = argv[optind - 1];
break;
case '?':
usage(argv[0]);
exit(1);
default:
printf("unhandled argument: %c\n", opt);
}
}
int num_rest_args = 0;
char **rest_args = NULL;
if (optind < argc) {
num_rest_args = argc - optind;
rest_args = malloc((argc - optind) * sizeof(char*));
int i = 0;
while (optind < argc) {
rest_args[i++] = argv[optind++];
}
}
if (num_rest_args == 0 && num_eval_args == 0) {
repl = true;
}
JSGlobalContextRef ctx = JSGlobalContextCreate(NULL);
JSStringRef nameRef = JSStringCreateWithUTF8CString("jsc-test");
JSGlobalContextSetName(ctx, nameRef);
JSObjectRef global_obj = JSContextGetGlobalObject(ctx);
evaluate_script(ctx, "var global = this;", "<init>");
register_global_function(ctx, "IMPORT_SCRIPT", function_import_script);
bootstrap(ctx, "out/main.js", "out/goog/base.js");
register_global_function(ctx, "CONSOLE_LOG", function_console_log);
register_global_function(ctx, "CONSOLE_ERROR", function_console_error);
evaluate_script(ctx, "var console = {};"\
"console.log = CONSOLE_LOG;"\
"console.error = CONSOLE_ERROR;", "<init>");
// require app namespaces
evaluate_script(ctx, "goog.require('planck.repl');", "<init>");
// without this things won't work
evaluate_script(ctx, "var window = global;", "<init>");
register_global_function(ctx, "PLANCK_READ_FILE", function_read_file);
register_global_function(ctx, "PLANCK_LOAD", function_load);
register_global_function(ctx, "PLANCK_LOAD_DEPS_CLJS_FILES", function_load_deps_cljs_files);
register_global_function(ctx, "PLANCK_CACHE", function_cache);
register_global_function(ctx, "PLANCK_EVAL", function_eval);
register_global_function(ctx, "PLANCK_GET_TERM_SIZE", function_get_term_size);
register_global_function(ctx, "PLANCK_PRINT_FN", function_print_fn);
register_global_function(ctx, "PLANCK_PRINT_ERR_FN", function_print_err_fn);
register_global_function(ctx, "PLANCK_SET_EXIT_VALUE", function_set_exit_value);
evaluate_script(ctx, "cljs.core.set_print_fn_BANG_.call(null,PLANCK_PRINT_FN);", "<init>");
evaluate_script(ctx, "cljs.core.set_print_err_fn_BANG_.call(null,PLANCK_PRINT_ERR_FN);", "<init>");
{
JSValueRef arguments[4];
arguments[0] = JSValueMakeBoolean(ctx, repl);
arguments[1] = JSValueMakeBoolean(ctx, verbose);
JSStringRef cache_path_str = JSStringCreateWithUTF8CString(".planck_cache");
arguments[2] = JSValueMakeString(ctx, cache_path_str);
arguments[3] = JSValueMakeBoolean(ctx, static_fns);
JSValueRef ex = NULL;
JSObjectCallAsFunction(ctx, get_function(ctx, "planck.repl", "init"), JSContextGetGlobalObject(ctx), 4, arguments, &ex);
debug_print_value("planck.repl/init", ctx, ex);
}
if (repl) {
evaluate_source(ctx, "text", "(require '[planck.repl :refer-macros [apropos dir find-doc doc source pst]])", true, "cljs.user");
}
evaluate_script(ctx, "goog.provide('cljs.user');", "<init>");
evaluate_script(ctx, "goog.require('cljs.core');", "<init>");
evaluate_script(ctx, "cljs.core._STAR_assert_STAR_ = true;", "<init>");
for (int i = 0; i < num_eval_args; i++) {
evaluate_source(ctx, "text", eval_args[i], true, "cljs.user");
}
if (repl) {
char *prompt = javascript ? " > " : " => ";
char *line;
while ((line = linenoise(prompt)) != NULL) {
JSValueRef res = NULL;
if (javascript) {
res = evaluate_script(ctx, line, "<stdin>");
} else {
res = evaluate_source(ctx, "text", line, true, "cljs.user");
}
free(line);
print_value("", ctx, res);
}
}
return exit_value;
}
JSStringRef to_string(JSContextRef ctx, JSValueRef val) {
if (JSValueIsUndefined(ctx, val)) {
return JSStringCreateWithUTF8CString("undefined");
} else if (JSValueIsNull(ctx, val)) {
return JSStringCreateWithUTF8CString("null");
} else {
JSStringRef to_string_name = JSStringCreateWithUTF8CString("toString");
JSObjectRef obj = JSValueToObject(ctx, val, NULL);
JSValueRef to_string = JSObjectGetProperty(ctx, obj, to_string_name, NULL);
JSObjectRef to_string_obj = JSValueToObject(ctx, to_string, NULL);
JSValueRef obj_val = JSObjectCallAsFunction(ctx, to_string_obj, obj, 0, NULL, NULL);
return JSValueToStringCopy(ctx, obj_val, NULL);
}
}
JSValueRef evaluate_script(JSContextRef ctx, char *script, char *source) {
JSStringRef script_ref = JSStringCreateWithUTF8CString(script);
JSStringRef source_ref = NULL;
if (source != NULL) {
source_ref = JSStringCreateWithUTF8CString(source);
}
JSValueRef ex = NULL;
JSValueRef val = JSEvaluateScript(ctx, script_ref, NULL, source_ref, 0, &ex);
JSStringRelease(script_ref);
if (source != NULL) {
JSStringRelease(source_ref);
}
debug_print_value("evaluate_script", ctx, ex);
return val;
}
char *value_to_c_string(JSContextRef ctx, JSValueRef val) {
if (!JSValueIsString(ctx, val)) {
#ifdef DEBUG
fprintf(stderr, "WARN: not a string\n");
#endif
return NULL;
}
JSStringRef str_ref = JSValueToStringCopy(ctx, val, NULL);
size_t len = JSStringGetLength(str_ref) + 1;
char *str = malloc(len * sizeof(char));
memset(str, 0, len);
JSStringGetUTF8CString(str_ref, str, len);
JSStringRelease(str_ref);
return str;
}
JSValueRef get_value_on_object(JSContextRef ctx, JSObjectRef obj, char *name) {
JSStringRef name_str = JSStringCreateWithUTF8CString(name);
JSValueRef val = JSObjectGetProperty(ctx, obj, name_str, NULL);
JSStringRelease(name_str);
return val;
}
JSValueRef get_value(JSContextRef ctx, char *namespace, char *name) {
JSValueRef ns_val = NULL;
// printf("get_value: '%s'\n", namespace);
int len = strlen(namespace) + 1;
char *ns_tmp = malloc(len * sizeof(char));
char **ns_tmp_start = &ns_tmp;
strncpy(ns_tmp, namespace, len);
char *ns_part = strtok(ns_tmp, ".");
ns_tmp = NULL;
while (ns_part != NULL) {
char *munged_ns_part = munge(ns_part);
if (ns_val) {
ns_val = get_value_on_object(ctx, JSValueToObject(ctx, ns_val, NULL), munged_ns_part);
} else {
ns_val = get_value_on_object(ctx, JSContextGetGlobalObject(ctx), munged_ns_part);
}
free(munged_ns_part); // TODO: Use a fixed buffer for this? (Which would restrict namespace part length...)
ns_part = strtok(NULL, ".");
}
//free(ns_tmp);
return get_value_on_object(ctx, JSValueToObject(ctx, ns_val, NULL), name);
}
JSObjectRef get_function(JSContextRef ctx, char *namespace, char *name) {
JSValueRef val = get_value(ctx, namespace, name);
assert(!JSValueIsUndefined(ctx, val));
return JSValueToObject(ctx, val, NULL);
}
JSValueRef evaluate_source(JSContextRef ctx, char *type, char *source, bool expression, char *set_ns) {
JSValueRef args[7];
int num_args = 7;
{
JSValueRef source_args[2];
JSStringRef type_str = JSStringCreateWithUTF8CString(type);
source_args[0] = JSValueMakeString(ctx, type_str);
JSStringRef source_str = JSStringCreateWithUTF8CString(source);
source_args[1] = JSValueMakeString(ctx, source_str);
args[0] = JSObjectMakeArray(ctx, 2, source_args, NULL);
}
args[1] = JSValueMakeBoolean(ctx, expression);
args[2] = JSValueMakeBoolean(ctx, false);
args[3] = JSValueMakeBoolean(ctx, false);
JSStringRef set_ns_str = JSStringCreateWithUTF8CString(set_ns);
args[4] = JSValueMakeString(ctx, set_ns_str);
JSStringRef theme_str = JSStringCreateWithUTF8CString("dumb");
args[5] = JSValueMakeString(ctx, theme_str);
args[6] = JSValueMakeNumber(ctx, 0);
JSObjectRef execute_fn = get_function(ctx, "planck.repl", "execute");
JSObjectRef global_obj = JSContextGetGlobalObject(ctx);
JSValueRef ex = NULL;
JSValueRef val = JSObjectCallAsFunction(ctx, execute_fn, global_obj, num_args, args, &ex);
debug_print_value("planck.repl/execute", ctx, ex);
return ex != NULL ? ex : val;
}
char *munge(char *s) {
int len = strlen(s);
int new_len = 0;
for (int i = 0; i < len; i++) {
switch (s[i]) {
case '!':
new_len += 6; // _BANG_
break;
case '?':
new_len += 7; // _QMARK_
break;
default:
new_len += 1;
}
}
char *ms = malloc((new_len+1) * sizeof(char));
int j = 0;
for (int i = 0; i < len; i++) {
switch (s[i]) {
case '-':
ms[j++] = '_';
break;
case '!':
ms[j++] = '_';
ms[j++] = 'B';
ms[j++] = 'A';
ms[j++] = 'N';
ms[j++] = 'G';
ms[j++] = '_';
break;
case '?':
ms[j++] = '_';
ms[j++] = 'Q';
ms[j++] = 'M';
ms[j++] = 'A';
ms[j++] = 'R';
ms[j++] = 'K';
ms[j++] = '_';
break;
default:
ms[j++] = s[i];
}
}
ms[new_len] = '\0';
return ms;
}
void bootstrap(JSContextRef ctx, char *deps_file_path, char *goog_base_path) {
char source[] = "<bootstrap>";
// Setup CLOSURE_IMPORT_SCRIPT
evaluate_script(ctx, "CLOSURE_IMPORT_SCRIPT = function(src) { IMPORT_SCRIPT('goog/' + src); return true; }", source);
// Load goog base
char *base_script_str = get_contents(goog_base_path, NULL);
if (base_script_str == NULL) {
fprintf(stderr, "The goog base JavaScript text could not be loaded");
exit(1);
}
evaluate_script(ctx, base_script_str, "<bootstrap:base>");
free(base_script_str);
// Load the deps file
char *deps_script_str = get_contents(deps_file_path, NULL);
if (deps_script_str == NULL) {
fprintf(stderr, "The goog base JavaScript text could not be loaded");
exit(1);
}
evaluate_script(ctx, deps_script_str, "<bootstrap:deps>");
free(deps_script_str);
evaluate_script(ctx, "goog.isProvided_ = function(x) { return false; };", source);
evaluate_script(ctx, "goog.require = function (name) { return CLOSURE_IMPORT_SCRIPT(goog.dependencies_.nameToPath[name]); };", source);
evaluate_script(ctx, "goog.require('cljs.core');", source);
// redef goog.require to track loaded libs
evaluate_script(ctx, "cljs.core._STAR_loaded_libs_STAR_ = cljs.core.into.call(null, cljs.core.PersistentHashSet.EMPTY, [\"cljs.core\"]);\n"
"goog.require = function (name, reload) {\n"
" if(!cljs.core.contains_QMARK_(cljs.core._STAR_loaded_libs_STAR_, name) || reload) {\n"
" var AMBLY_TMP = cljs.core.PersistentHashSet.EMPTY;\n"
" if (cljs.core._STAR_loaded_libs_STAR_) {\n"
" AMBLY_TMP = cljs.core._STAR_loaded_libs_STAR_;\n"
" }\n"
" cljs.core._STAR_loaded_libs_STAR_ = cljs.core.into.call(null, AMBLY_TMP, [name]);\n"
" CLOSURE_IMPORT_SCRIPT(goog.dependencies_.nameToPath[name]);\n"
" }\n"
"};", source);
}
char *get_contents(char *path, time_t *last_modified) {
/*#ifdef DEBUG
printf("get_contents(\"%s\")\n", path);
#endif*/
char *err_prefix;
FILE *f = fopen(path, "r");
if (f == NULL) {
err_prefix = "fopen";
goto err;
}
struct stat f_stat;
if (fstat(fileno(f), &f_stat) < 0) {
err_prefix = "fstat";
goto err;
}
if (last_modified != NULL) {
*last_modified = f_stat.st_mtime;
}
char *buf = malloc(f_stat.st_size + 1);
memset(buf, 0, f_stat.st_size);
fread(buf, f_stat.st_size, 1, f);
buf[f_stat.st_size] = '\0';
if (ferror(f)) {
err_prefix = "fread";
free(buf);
goto err;
}
if (fclose(f) < 0) {
err_prefix = "fclose";
goto err;
}
return buf;
err:
//printf("get_contents(\"%s\"): %s: %s\n", path, err_prefix, strerror(errno));
return NULL;
}
void write_contents(char *path, char *contents) {
char *err_prefix;
char *path_copy = strdup(path);
char *dir = dirname(path_copy);
if (mkdir_p(dir) < 0) {
err_prefix = "mkdir_p";
free(path_copy);
goto err;
}
free(path_copy);
FILE *f = fopen(path, "w");
if (f == NULL) {
err_prefix = "fopen";
goto err;
}
int len = strlen(contents);
int offset = 0;
do {
int res = fwrite(contents+offset, 1, len-offset, f);
if (res < 0) {
err_prefix = "fwrite";
goto err;
}
offset += res;
} while (offset < len);
if (fclose(f) < 0) {
err_prefix = "fclose";
goto err;
}
return;
err:
printf("write_contents(\"%s\", ...): %s: %s\n", path, err_prefix, strerror(errno));
return;
}
int mkdir_p(char *path) {
int res = mkdir(path, 0755);
if (res < 0 && errno == EEXIST) {
return 0;
}
return res;
}
|