diff --git a/src/lu5_cli_options.c b/src/lu5_cli_options.c index 2d9d9af0..f7a5b03e 100644 --- a/src/lu5_cli_options.c +++ b/src/lu5_cli_options.c @@ -5,10 +5,13 @@ #include "lu5_cli_options.h" #include "lu5_state.h" +#include "lu5_fs.h" #include #include +#include + #define LU5_SKETCH_BOILERPLATE \ "\n"\ "function setup()\n"\ @@ -40,6 +43,13 @@ lu5_option cli_options[LU5_OPTION_COUNT] = { .example = "lu5 sketch.lua --log 1", .handler = lu5_option_log, .arg_count = 1 + }, + { + .name = "install", + .description = "Install a lu5 sketch on the system globally", + .example = "lu5 --install sketch.lua", + .handler = lu5_option_install, + .arg_count = 1 } }; @@ -94,10 +104,10 @@ int lu5_option_init(int argc, char **argv, int idx, int cli_id, bool* defaultExe // first option argument is filename *filename = argv[arg_1]; - struct stat buffer; // If file exists - if (stat(*filename, &buffer) == 0) { + struct stat file_info; + if (stat(*filename, &file_info) == 0) { LU5_WARN( "It seems that \x1b[90m%s\x1b[0m already exists", @@ -124,9 +134,9 @@ int lu5_option_init(int argc, char **argv, int idx, int cli_id, bool* defaultExe } - FILE *sketch = fopen(argv[arg_1], "w"); + FILE *sketch = fopen(argv[arg_1], "wr"); - LU5_INFO("Overwriting %s", *filename); + LU5_INFO("Writing %s", *filename); // Write to file fprintf(sketch, LU5_SKETCH_BOILERPLATE); @@ -137,6 +147,148 @@ int lu5_option_init(int argc, char **argv, int idx, int cli_id, bool* defaultExe return 0; } +int lu5_option_install(int argc, char **argv, int idx, int cli_id, bool* defaultExec, char **filename) +{ +#ifdef _WIN32 + LU5_WARN("Feature not implemented for windows yet. exiting."); + + // avoid unused warnings + (void)lu5_read_file; + (void)lu5_write_file; + + return 0; +#else + // option position + nb of args must be smaller than argc + if (idx+cli_options[cli_id].arg_count >= argc) { + LU5_ERROR(LU5_FILE_NOT_SPECIFIED); + return 1; + } + + int arg_1 = idx+1; + + // first option argument is filename + *filename = argv[arg_1]; + + // If does not file exist + struct stat file_info; + if LU5_FILE_NOT_EXISTS(*filename, &file_info) { + + LU5_ERROR( + "Could not find \x1b[90m'%s'\x1b[0m", + argv[arg_1] + ); + + if (errno) LU5_ERROR("errno: %i", errno); + + return 1; + } + + int err; + + char *installed_path, + *first_line_str, + *sketch_source, + *sketch_name = lu5_get_file_name(*filename); + + int sketch_name_len = strlen(sketch_name); + + // Create a destination path + installed_path = malloc(sketch_name_len + 10); + if (!installed_path) { + LU5_ERROR("Not enough memory to allocate \x1b[90m'%s'\x1b[0m source in heap", *filename); + + free(sketch_name); + return 1; + } + + // Format destination path + sprintf(installed_path, "/usr/bin/%s", sketch_name); + + // Check if file at path exists + struct stat dest_file_info; + if (LU5_FILE_EXISTS(installed_path, &dest_file_info)) { + + LU5_ERROR( + "\x1b[90m'%s'\x1b[0m already exists, cancelling.", + installed_path + ); + + if (errno) LU5_ERROR("errno: %i", errno); + + free(installed_path); + return 1; + } + + LU5_INFO("Installing %s to %s", *filename, installed_path); + + // Read the source file + long sketch_length; + sketch_source = lu5_read_file(*filename, &sketch_length); + + // check for hashbang + const char *hashbang = "#!/usr/bin/lu5"; + const char *end_line = strchr(sketch_source, '\n'); + int first_line_length = (end_line) ? (end_line - sketch_source) : sketch_length; + + // + first_line_str = malloc(first_line_length+1); + if (!first_line_str) { + LU5_ERROR("Not enough memory to allocate \x1b[90m'%s'\x1b[0m source in heap", *filename); + + free(installed_path); + free(sketch_source); + return 1; + } + + strncpy(first_line_str, sketch_source, first_line_length); + if (strstr(first_line_str, hashbang) == NULL) { + LU5_ERROR("lu5 sketch \x1b[90m'%s'\x1b[0m must have a hashbang \x1b[90m'%s'\x1b[0m as the first line of the script.", *filename, hashbang); + + free(installed_path); + free(sketch_source); + free(first_line_str); + return 1; + } + + // Write to destination file + if (lu5_write_file(installed_path, sketch_source, sketch_length)) { + // Error handled in write file + free(installed_path); + free(sketch_source); + free(first_line_str); + return 1; + } + + // Set execute permitions for everyone + err = chmod( + installed_path, + S_IRUSR | S_IWUSR | S_IXUSR | + S_IRGRP | S_IXGRP | + S_IROTH | S_IXOTH + ); + + if (err) { + LU5_ERROR("Could not properly install \x1b[90m'%s'\x1b[0m err: %i, errno: %i", *filename, err, errno); + err = 1; + } + + // Clear allocated strings + if (installed_path != NULL) + free(installed_path); + + if (sketch_source != NULL) + free(sketch_source); + + if (first_line_str != NULL) + free(first_line_str); + + if (sketch_name != NULL) + free(sketch_name); + + return err; +#endif +} + int lu5_option_help(int argc, char **argv, int idx, int cli_id, bool* defaultExec, char **filename) { for (int i = 0; i < LU5_OPTION_COUNT; i++) { diff --git a/src/lu5_cli_options.h b/src/lu5_cli_options.h index 76902199..7e0df249 100644 --- a/src/lu5_cli_options.h +++ b/src/lu5_cli_options.h @@ -3,7 +3,7 @@ #include -#define LU5_OPTION_COUNT 3 +#define LU5_OPTION_COUNT 4 typedef struct { const char *name; @@ -17,6 +17,7 @@ extern lu5_option cli_options[LU5_OPTION_COUNT]; int lu5_option_log(int argc, char **argv, int idx, int cli_id, bool* defaultExec, char **filename); int lu5_option_init(int argc, char **argv, int idx, int cli_id, bool* defaultExec, char **filename); +int lu5_option_install(int argc, char **argv, int idx, int cli_id, bool* defaultExec, char **filename); int lu5_option_help(int argc, char **argv, int idx, int cli_id, bool* defaultExec, char **filename); #endif diff --git a/src/lu5_fs.c b/src/lu5_fs.c new file mode 100644 index 00000000..2b7402a1 --- /dev/null +++ b/src/lu5_fs.c @@ -0,0 +1,106 @@ +#include "lu5_fs.h" + +#include +#include +#include + +#include "lu5_logger.h" + +#include +#include + +char *lu5_get_file_name(const char *filename) { + // Find the last dot in the filename + char *dot = strrchr(filename, '.'); + if (!dot || dot == filename) { + return strdup(filename); + } + + // Get the position of the dot + size_t length = dot - filename; + + // Allocate memory for the new string + char *name_without_extention = (char *)malloc(length + 1); + if (!name_without_extention) { + LU5_ERROR("No memory left on the device"); + return strdup(filename); + } + + strncpy(name_without_extention, filename, length); + name_without_extention[length] = '\0'; + + return name_without_extention; +} + +int lu5_write_file(const char *path, const void *buffer, size_t len) { + // Open the file in binary write mode + FILE *file = fopen(path, "wb"); + if (file == NULL) { + switch(errno) { + case EACCES: { + LU5_ERROR("Permission denied when writing \x1b[90m'%s'\x1b[0m", path); + break; + } + default: { + LU5_ERROR("Something went wrong when writing \x1b[90m'%s'\x1b[0m, errno: %i", path, errno); + break; + } + } + return 1; + } + + // Write the buffer to the file + size_t written = fwrite(buffer, 1, len, file); + if (written != len) { + LU5_ERROR("Error writing to \x1b[90m'%s'\x1b[0m", path); + return 1; + } + + // Close the file + fclose(file); + return 0; +} + +/** + * Read a file entirely + * + * caller must free the returned buffer. + */ +char *lu5_read_file(const char* path, long *size) { + FILE *fp; + long length; + char *buffer; + + // Open the file + fp = fopen(path, "rb"); + if (fp == NULL) { + switch(errno) { + case EACCES: { + LU5_ERROR("No permission to read \x1b[90m'%s'\x1b[0m", path); + break; + } + default: { + LU5_ERROR("Could not open file: %s\n", path); + break; + } + } + return NULL; + } + + // Get the length of the file + fseek(fp, 0, SEEK_END); + length = ftell(fp); + fseek(fp, 0, SEEK_SET); + + // Read the file contents + buffer = (char*)malloc((length + 1) * sizeof(char)); + if (buffer) { + fread(buffer, sizeof(char), length, fp); + buffer[length] = '\0'; // Null-terminate the string + } + fclose(fp); + + *size = length; + + return buffer; +} diff --git a/src/lu5_fs.h b/src/lu5_fs.h new file mode 100644 index 00000000..7230f279 --- /dev/null +++ b/src/lu5_fs.h @@ -0,0 +1,22 @@ +#ifndef _LU5_FS_H_ +#define _LU5_FS_H_ + +#include + +#define LU5_FILE_EXISTS(filename, info) (stat(filename, info) == 0) +#define LU5_FILE_NOT_EXISTS(filename, info) (stat(filename, info) != 0) + +char *lu5_get_file_name(const char *filename); + + +int lu5_write_file(const char *path, const void *buffer, size_t len); + +/** + * Read a file entirely + * + * caller must free the returned buffer. + */ +char* lu5_read_file(const char* path, long *size); + + +#endif \ No newline at end of file