From 6d1d4d0a5506dce90c833ef8dd141058fea3c33a Mon Sep 17 00:00:00 2001 From: bigfoot547 Date: Mon, 25 Dec 2023 06:05:40 -0600 Subject: [WIP] Add instances still need to do the following: - check if the folder exists - try and find some way to canonicalize the path name --- meson.build | 1 + sample-instance.json | 6 ++ src/cmd-instance.c | 120 +++++++++++++++++++++++++++++++- src/instance.c | 193 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/instance.h | 22 ++++-- src/l2su.c | 131 +++++++++------------------------- src/l2su.h | 25 ++++++- src/launcherutil.c | 157 +++++++++++++++++++++++++++++++++++++++++ src/meson.build | 2 +- src/uuid/uuid.c | 23 +++--- src/uuid/uuid.h | 5 ++ 11 files changed, 566 insertions(+), 119 deletions(-) create mode 100644 sample-instance.json create mode 100644 src/instance.c create mode 100644 src/launcherutil.c diff --git a/meson.build b/meson.build index 073c348..dc8284b 100644 --- a/meson.build +++ b/meson.build @@ -1,4 +1,5 @@ project('l2su', 'c') +add_global_arguments('-D_POSIX_C_SOURCE=200809L', language : 'c') curl_dep = dependency('libcurl') jansson_dep = dependency('jansson') diff --git a/sample-instance.json b/sample-instance.json new file mode 100644 index 0000000..cb00472 --- /dev/null +++ b/sample-instance.json @@ -0,0 +1,6 @@ +{ + "e9ddec2a-4cfe-4be5-85ee-4689c95bfa8d": { + "name": "somename", + "path": "/home/bigfoot/.local/share/l2su/instances/e9ddec2a-4cfe-4be5-85ee-4689c95bfa8d" + } +} diff --git a/src/cmd-instance.c b/src/cmd-instance.c index 9e346e2..89494af 100644 --- a/src/cmd-instance.c +++ b/src/cmd-instance.c @@ -1,11 +1,126 @@ #include "commands.h" -#include "src/command.h" +#include "command.h" +#include "instance.h" +#include "l2su.h" + #include +#include +#include +#include +#include unsigned cmd_instance_add(struct l2_context_node *ctx, char **args) { + int res = l2_instance_load_all(); + char *name = NULL; + char *path = NULL; + char *newpath = NULL; + uuid_t uuid; + char uuidstr[UUID_STRLEN+1]; + + if (res != INSTANCE_SUCCESS) { + if (res == INSTANCE_ERRNO) { + fprintf(stderr, "fatal: Error loading instances: %s", strerror(errno)); + } else { + fprintf(stderr, "fatal: Error loading instances (%d)\n", res); + } + return CMD_RESULT_FAIL; + } + + /* get the name and path (if it exists) */ + for (struct l2_context_node *cur = ctx; cur; cur = cur->next) { + if (cur->node->type != CMD_NODE_TYPE_ARGUMENT) continue; + if (!strcmp("name", cur->node->name)) { + name = cur->value; + } else if (!strcmp("location", cur->node->name)) { + path = cur->value; + } + } + + /* make sure the name and path make sense (path should be an absolute path, name shouldn't be empty) */ + if (!name || !*name) { + fputs("fatal: The specified name is invalid\n", stderr); + return CMD_RESULT_FAIL; + } + + for (char *cur = name; *cur; ++cur) { + if (*cur < ' ') { + fputs("fatal: The specified name contains invalid characters\n", stderr); + return CMD_RESULT_FAIL; + } + } + + /* make sure there is not a profile with that name already */ + for (struct l2_instance *cur = l2_state.instance_head; cur; cur = cur->next) { + if (!strcmp(cur->name, name)) { + fprintf(stderr, "fatal: An instance by the name '%s' already exists.\n", cur->name); + return CMD_RESULT_FAIL; + } + } + + /* also find an unused UUID through rejection sampling */ + bool found; + do { + found = false; + l2_uuid_random(&uuid); + for (struct l2_instance *cur = l2_state.instance_head; cur; cur = cur->next) { + if (!l2_uuid_compare(&cur->uuid, &uuid)) { + found = true; + break; + } + } + } while (found); + + /* create the directory at "path" (if it isn't empty, complain) */ + if (path && *path != '/') { + fputs("fatal: Specified path is not an absolute path\n", stderr); + return CMD_RESULT_FAIL; + } else if (!path) { + newpath = strdup(l2_state.paths.data); + if (!newpath) { + newpath_append_fail: + free(newpath); + fprintf(stderr, "fatal: Allocation failed: %s\n", strerror(errno)); + return CMD_RESULT_FAIL; + } + + newpath = l2_launcher_strapp(newpath, "/instances/"); + if (!newpath) goto newpath_append_fail; + + l2_uuid_to_string(&uuid, uuidstr); + newpath = l2_launcher_strapp(newpath, uuidstr); + if (!newpath) goto newpath_append_fail; + } + + /* TODO: now create the directory for realsies */ + + /* add the instance */ + + struct l2_instance inst; + + inst.uuid = uuid; + inst.name = name; + inst.path = path ? path : newpath; + + if ((res = l2_instance_add_instance(&inst)) != INSTANCE_SUCCESS) { + fprintf(stderr, "fatal: Failed to add instance (%d)\n", res); + res = CMD_RESULT_FAIL; + goto cleanup; + } + + if ((res = l2_instance_save_all()) != INSTANCE_SUCCESS) { + fprintf(stderr, "fatal: Failed to add instance (%d)\n", res); + res = CMD_RESULT_FAIL; + goto cleanup; + } + printf("add command yay\n"); - return CMD_RESULT_SUCCESS; + + res = CMD_RESULT_SUCCESS; + +cleanup: + free(newpath); + return res; } unsigned cmd_instance_remove(struct l2_context_node *ctx, char **args) @@ -22,4 +137,3 @@ unsigned cmd_instance_rename(struct l2_context_node *ctx, char **args) { return CMD_RESULT_SUCCESS; } - diff --git a/src/instance.c b/src/instance.c new file mode 100644 index 0000000..20c0f59 --- /dev/null +++ b/src/instance.c @@ -0,0 +1,193 @@ +#include "instance.h" +#include "l2su.h" +#include "uuid/uuid.h" + +#include +#include +#include +#include +#include +#include + +#include + +int l2_instance__load_all_from_object(json_t *obj); +json_t *l2_instance__save_all_to_object(void); + +/* da main functionality */ + +int l2_instance_load_all(void) +{ + /* open + openat because I am too lazy to concatenate the path with the filename */ + int instfd = l2_launcher_open_config("instance.json", O_RDONLY, 0); + if (instfd < 0) { + return errno == ENOENT ? INSTANCE_SUCCESS : INSTANCE_ERRNO; + } + + json_error_t jserr; + json_t *js = json_loadfd(instfd, JSON_REJECT_DUPLICATES, &jserr); + + if (!js) { + fprintf(stderr, "error: JSON error in instance.json: %s (%s:%d:%d)\n", jserr.text, jserr.source, jserr.line, jserr.column); + return INSTANCE_EJSON; + } + + if (!json_is_object(js)) { + fprintf(stderr, "error: instances.json value is not an object (is actually %d)\n", json_typeof(js)); + json_decref(js); + return INSTANCE_EFORMAT; + } + + int res = l2_instance__load_all_from_object(js); + json_decref(js); + return res; +} + +int l2_instance_save_all(void) +{ + json_t *js = NULL; + int instfd = l2_launcher_open_config("instance.json", O_WRONLY | O_CREAT, 0644); + if (instfd < 0) return INSTANCE_ERRNO; + + int retval = INSTANCE_SUCCESS; + + js = l2_instance__save_all_to_object(); + if (!js) { + retval = INSTANCE_EJSON; + goto save_done; + } + + if (json_dumpfd(js, instfd, JSON_INDENT(4)) < 0) { + retval = INSTANCE_EJSON; + } + +save_done: + close(instfd); + json_decref(js); + return retval; +} + +/* public utilities */ + +int l2_instance_add_instance(const struct l2_instance *inst) +{ + char *path = strdup(inst->path); + if (!path) return INSTANCE_ERRNO; + + char *name = strdup(inst->name); + if (!name) { + free(path); + return INSTANCE_ERRNO; + } + + struct l2_instance *newinst = calloc(1, sizeof(struct l2_instance)); + if (!newinst) { + free(path); + free(name); + return INSTANCE_ERRNO; + } + + newinst->uuid = inst->uuid; + newinst->path = path; + newinst->name = name; + + if (l2_state.instance_head && l2_state.instance_tail) { + l2_state.instance_tail->next = newinst; + newinst->prev = l2_state.instance_tail; + l2_state.instance_tail = newinst; + } else { + l2_state.instance_head = l2_state.instance_tail = newinst; + } + + return INSTANCE_SUCCESS; +} + +int l2_instance_del_instance(struct l2_instance *inst) +{ + if (inst->next) { + inst->next->prev = inst->prev; + } else { + l2_state.instance_tail = inst->prev; + } + + if (inst->prev) { + inst->prev->next = inst->next; + } else { + l2_state.instance_head = inst->next; + } + + return INSTANCE_SUCCESS; +} + +/* internal utilities */ + +int l2_instance__load_all_from_object(json_t *obj) +{ + const char *key; + json_t *val; + json_t *tempval; + int res; + + struct l2_instance inst; + + json_object_foreach(obj, key, val) { + if (!json_is_object(val)) { + return INSTANCE_EFORMAT; + } + + if (!l2_uuid_from_string(&inst.uuid, key)) { + return INSTANCE_EFORMAT; /* invalid UUID */ + } + + tempval = json_object_get(val, "name"); + if (!json_is_string(tempval)) { + return INSTANCE_EFORMAT; + } + + /* it's okay to discard const here because add_instance duplicates the string */ + inst.name = (char *)json_string_value(tempval); + + tempval = json_object_get(val, "path"); + if (!json_is_string(tempval)) { + return INSTANCE_EFORMAT; + } + + inst.path = (char *)json_string_value(tempval); + + if ((res = l2_instance_add_instance(&inst)) != INSTANCE_SUCCESS) { + return res; + } + } + + return INSTANCE_SUCCESS; +} + +json_t *l2_instance__save_all_to_object(void) +{ + json_t *obj = json_object(); + json_t *jinst; + if (!obj) return NULL; + + char uuidbuf[UUID_STRLEN + 1]; + + for (struct l2_instance *inst = l2_state.instance_head; inst; inst = inst->next) { + l2_uuid_to_string(&inst->uuid, uuidbuf); + if (json_object_set_new(obj, uuidbuf, jinst = json_object()) < 0) { + goto save_err; /* jinst is decref'd anyway (undocumented) */ + } + + if (json_object_set_new(jinst, "name", json_string(inst->name)) < 0) { + goto save_err; + } + + if (json_object_set_new(jinst, "path", json_string(inst->path)) < 0) { + goto save_err; + } + } + + return obj; + +save_err: + json_decref(obj); + return NULL; +} diff --git a/src/instance.h b/src/instance.h index b0be665..e7e5943 100644 --- a/src/instance.h +++ b/src/instance.h @@ -5,12 +5,26 @@ struct l2_instance { uuid_t uuid; - char *name; - char *path; + const char *name; + const char *path; + + struct l2_instance *next; + struct l2_instance *prev; +}; + +enum { + INSTANCE_SUCCESS, /* instance operation succeeded */ + INSTANCE_ERRNO, /* instance operation failed (error code in errno) */ + INSTANCE_EJSON, /* instance operation failed (JSON error) */ + INSTANCE_EFORMAT /* instance file is malformed */ }; -void l2_instance_load(void); +/* load all instances (must be called after initialization) */ +int l2_instance_load_all(void); + +int l2_instance_save_all(void); -void l2_instance_save(void); +int l2_instance_add_instance(const struct l2_instance *inst); +int l2_instance_del_instance(struct l2_instance *inst); #endif /* include guard */ diff --git a/src/l2su.c b/src/l2su.c index bd22987..c9a100b 100644 --- a/src/l2su.c +++ b/src/l2su.c @@ -1,135 +1,68 @@ #include "l2su.h" +#include "command.h" #include "macros.h" #include #include #include +#include -char *l2_launcher__strdup(const char *); -char *l2_launcher__strapp(char *, const char *); - -char *l2_launcher__find_config_path(void) -{ - /* check for $L2SU_CONFIG */ - char *config = getenv(PROJECT_NAME_UPPER "_CONFIG"); - if (config) { - return l2_launcher__strdup(config); - } - - /* check for $XDG_CONFIG_HOME */ - config = getenv("XDG_CONFIG_HOME"); - if (config) { - /* want to append '"/" PROJECT_NAME' to our string */ - char *ret = l2_launcher__strdup(config); - if (!ret) return NULL; - - return l2_launcher__strapp(ret, "/" PROJECT_NAME); - } - - /* check for $HOME/.config */ - config = getenv("HOME"); - if (config) { - char *ret = l2_launcher__strdup(config); - if (!ret) return NULL; - - return l2_launcher__strapp(ret, "/.config/" PROJECT_NAME); - } - - /* fail (do NOT attempt to find home directory from passwd */ - return NULL; -} - -char *l2_launcher__find_data_path(void) -{ - /* check for $L2SU_DATA */ - char *config = getenv(PROJECT_NAME_UPPER "_DATA"); - if (config) { - return l2_launcher__strdup(config); - } - - /* check for $XDG_DATA_HOME */ - config = getenv("XDG_DATA_HOME"); - if (config) { - char *ret = l2_launcher__strdup(config); - if (!ret) return NULL; - - return l2_launcher__strapp(ret, "/" PROJECT_NAME); - } - - /* check for $HOME/.local/share */ - config = getenv("HOME"); - if (config) { - char *ret = l2_launcher__strdup(config); - if (!ret) return NULL; - - return l2_launcher__strapp(ret, "/.local/share/" PROJECT_NAME); - } - - return NULL; -} +struct tag_l2_state_t l2_state = { 0 }; int l2_launcher_init(void) { /* find paths (set up l2_state.paths) */ /* create folders if they don't exist */ - printf("project is " PROJECT_NAME "...\n"); - /* look for config path */ - if (!(l2_state.paths.config = l2_launcher__find_config_path())) { + if (!(l2_state.paths.config = l2_launcher_find_config_path())) { fputs("fatal: Could not decide on a configuration directory! Please define " PROJECT_NAME_UPPER "_CONFIG to a sensible value.\n", stderr); return 1; } - if (!(l2_state.paths.data = l2_launcher__find_data_path())) { + if (!(l2_state.paths.data = l2_launcher_find_data_path())) { fputs("fatal: Could not decide on a data directory! Please define " PROJECT_NAME_UPPER "_DATA to a sensible value.\n", stderr); return 1; } - printf("config path: %s\n", l2_state.paths.config); - printf("data path: %s\n", l2_state.paths.data); + printf("Using configuration path: %s\n", l2_state.paths.config); + printf("Using data path: %s\n", l2_state.paths.data); - return 0; -} + if (l2_launcher_mkdir_parents(l2_state.paths.config) < 0) { + fprintf(stderr, "fatal: Could not create config directory: %s\n", strerror(errno)); + return 1; + } -int main(void) -{ - int status = l2_launcher_init(); - if (status != 0) return status; + if (l2_launcher_mkdir_parents(l2_state.paths.data) < 0) { + fprintf(stderr, "fatal: Could not create data directory: %s\n", strerror(errno)); + return 1; + } return 0; } -/* handcoded string functions - * - * NOTE: I am aware that this is inefficient but since these are used only a handful of times - * during initialization, I don't think performance is really a huge deal here. - * - * remember that this is a Minecraft launcher, so the PC has to meet the minimum specs of that, - * which are well above the minimum specs of this launcher */ - -char *l2_launcher__strdup(const char *in) +int main(int argc, char **argv) { - if (!in) return NULL; + int status = l2_launcher_init(); + if (status != 0) return EXIT_FAILURE; - size_t inlen = strlen(in); - char *ret = calloc(1, inlen + 1); + if (argc == 0 || !argv || !*argv) { + fputs("weird: no program name!\n", stderr); + return EXIT_FAILURE; + } - if (ret) memcpy(ret, in, inlen); + struct l2_parseinfo parseinfo; + unsigned parseres = l2_cmd_parse_command(argv + 1, &parseinfo); + if (parseres != CMD_PARSE_SUCCESS) { + fputs("failed to parse command\n", stderr); + return EXIT_FAILURE; + } - return ret; -} + unsigned res = (*parseinfo.proc)(parseinfo.ctx, parseinfo.argv); -char *l2_launcher__strapp(char *buf, const char *src) -{ - size_t buflen = strlen(buf); - size_t srclen = strlen(src); + l2_cmd_free_ctx(parseinfo.ctx); - char *ret = realloc(buf, buflen + srclen + 1); - if (!ret) return NULL; + return res == CMD_RESULT_SUCCESS ? EXIT_SUCCESS : EXIT_FAILURE; +} - memcpy(ret + buflen, src, srclen); - ret[buflen + srclen] = '\0'; - return ret; -} diff --git a/src/l2su.h b/src/l2su.h index 13df852..1746a53 100644 --- a/src/l2su.h +++ b/src/l2su.h @@ -4,14 +4,33 @@ #include "command.h" #include "instance.h" +#include + struct l2_user_paths { const char *config; const char *data; }; -struct { +struct tag_l2_state_t { struct l2_user_paths paths; - struct l2_instance *instances; -} l2_state; + + /* eventually instances and other stuff will be stored in a hash table or something, + * but for now a doubly-linked list accomplishes our goals */ + struct l2_instance *instance_head; + struct l2_instance *instance_tail; +}; + +extern struct tag_l2_state_t l2_state; + +/* homecooked string methods */ +char *l2_launcher_strapp(char *target, const char *src); + +/* launcher utilities */ +char *l2_launcher_find_config_path(void); +char *l2_launcher_find_data_path(void); + +int l2_launcher_open_config(const char *path, int flags, mode_t mode); + +int l2_launcher_mkdir_parents(const char *path); #endif /* include guard */ diff --git a/src/launcherutil.c b/src/launcherutil.c new file mode 100644 index 0000000..8df4a71 --- /dev/null +++ b/src/launcherutil.c @@ -0,0 +1,157 @@ +#include "macros.h" +#include "l2su.h" + +#include +#include +#include +#include +#include +#include + +/* handcoded string functions + * + * NOTE: I am aware that this is inefficient but since these are used only a handful of times + * during initialization, I don't think performance is really a huge deal here. + * + * remember that this is a Minecraft launcher, so the PC has to meet the minimum specs of that, + * which are well above the minimum specs of this launcher */ + +char *l2_launcher_strapp(char *buf, const char *src) +{ + size_t buflen = strlen(buf); + size_t srclen = strlen(src); + + char *ret = realloc(buf, buflen + srclen + 1); + if (!ret) return NULL; + + memcpy(ret + buflen, src, srclen); + ret[buflen + srclen] = '\0'; /* realloc does not initialize like calloc does */ + + return ret; +} + +char *l2_launcher_find_config_path(void) +{ + /* check for $L2SU_CONFIG */ + char *config = getenv(PROJECT_NAME_UPPER "_CONFIG"); + if (config) { + return strdup(config); + } + + /* check for $XDG_CONFIG_HOME */ + config = getenv("XDG_CONFIG_HOME"); + if (config) { + /* want to append '"/" PROJECT_NAME' to our string */ + char *ret = strdup(config); + if (!ret) return NULL; + + return l2_launcher_strapp(ret, "/" PROJECT_NAME); + } + + /* check for $HOME/.config */ + config = getenv("HOME"); + if (config) { + char *ret = strdup(config); + if (!ret) return NULL; + + return l2_launcher_strapp(ret, "/.config/" PROJECT_NAME); + } + + /* fail (do NOT attempt to find home directory from passwd */ + return NULL; +} + +char *l2_launcher_find_data_path(void) +{ + /* check for $L2SU_DATA */ + char *config = getenv(PROJECT_NAME_UPPER "_DATA"); + if (config) { + return strdup(config); + } + + /* check for $XDG_DATA_HOME */ + config = getenv("XDG_DATA_HOME"); + if (config) { + char *ret = strdup(config); + if (!ret) return NULL; + + return l2_launcher_strapp(ret, "/" PROJECT_NAME); + } + + /* check for $HOME/.local/share */ + config = getenv("HOME"); + if (config) { + char *ret = strdup(config); + if (!ret) return NULL; + + return l2_launcher_strapp(ret, "/.local/share/" PROJECT_NAME); + } + + return NULL; +} + +int l2_launcher_open_config(const char *path, int flags, mode_t mode) +{ + int conffd = open(l2_state.paths.config, O_RDONLY | O_DIRECTORY); + if (conffd < 0) return INSTANCE_ERRNO; + + int instfd = openat(conffd, path, flags, mode); + int en = errno; /* back up errno because close can fail */ + + close(conffd); + + errno = en; + return instfd; +} + +/* NOTE: There's no portable (or otherwise - see open(2) BUGS) way to do this without race conditions. */ +int l2_launcher_mkdir_parents(const char *path) +{ + if (*path != '/') return -1; + + int reserrno = 0; + + char *pathbuf = strdup(path); + char *pcurelem = pathbuf; + if (!pathbuf) return -1; + + struct stat stbuf = { 0 }; + + do { /* strtok is off-limits because it smells bad */ + *pcurelem = '/'; + pcurelem = strchr(pcurelem + 1, '/'); + + if (pcurelem) { + *pcurelem = '\0'; + } + + /* now pathbuf contains our truncated path name which may or may not exist */ + if (mkdir(pathbuf, 0755) < 0) { + if (errno == EEXIST) { + /* racy: stat the file and continue if it is a directory */ + if (stat(pathbuf, &stbuf) < 0) { + reserrno = errno; + goto mdcleanup; + } + + if (!S_ISDIR(stbuf.st_mode)) { + reserrno = ENOTDIR; + goto mdcleanup; + } + } else { + reserrno = errno; + goto mdcleanup; + } + } + } while (pcurelem); + +mdcleanup: + + free(pathbuf); + if (reserrno != 0) { + errno = reserrno; + return -1; + } + + return 0; +} diff --git a/src/meson.build b/src/meson.build index 2f675ea..000bf6b 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,4 +1,4 @@ -launcher_srcs = files('l2su.c', 'command.c', 'cmd-instance.c', 'uuid/uuid.c') +launcher_srcs = files('l2su.c', 'command.c', 'cmd-instance.c', 'uuid/uuid.c', 'launcherutil.c', 'instance.c') configure_file(input : 'config.h.in', output : 'config.h', configuration : config_data) config_include_dir = include_directories('.') diff --git a/src/uuid/uuid.c b/src/uuid/uuid.c index 0d49f0b..0dfcfbd 100644 --- a/src/uuid/uuid.c +++ b/src/uuid/uuid.c @@ -11,8 +11,8 @@ void l2_uuid__set_version(uuid_t *id, unsigned version) { version &= 0x0F; - id->halves[1] &= ~(UINT64_C(0x0F) << 12); - id->halves[1] |= (uint64_t)version << 12; + id->uuid_ms &= ~(UINT64_C(0x0F) << 12); + id->uuid_ms |= (uint64_t)version << 12; } void l2_uuid__set_variant(uuid_t *id, unsigned variant, unsigned bits) @@ -21,8 +21,8 @@ void l2_uuid__set_variant(uuid_t *id, unsigned variant, unsigned bits) uint64_t lvariant = (uint64_t)variant << (64 - bits); lvariant &= ~mask; - id->halves[0] &= mask; - id->halves[0] |= lvariant; + id->uuid_ls &= mask; + id->uuid_ls |= lvariant; } void l2_uuid_random(uuid_t *id) @@ -35,11 +35,11 @@ void l2_uuid_random(uuid_t *id) void l2_uuid_to_string(const uuid_t *id, char *out) { snprintf(out, UUID_STRLEN + 1, "%08" PRIx64 "-%04" PRIx64 "-%04" PRIx64 "-%04" PRIx64 "-%012" PRIx64, - id->halves[1] >> 32, /* time-low */ - id->halves[1] >> 16 & 0xffff, /* time-mid */ - id->halves[1] & 0xffff, /* time-high-and-version */ - id->halves[0] >> 48, /* clock-seq-and-reserved, clock-seq-low */ - id->halves[0] & UINT64_C(0xffffffffffff)); /* node */ + id->uuid_ms >> 32, /* time-low */ + id->uuid_ms >> 16 & 0xffff, /* time-mid */ + id->uuid_ms & 0xffff, /* time-high-and-version */ + id->uuid_ls >> 48, /* clock-seq-and-reserved, clock-seq-low */ + id->uuid_ls & UINT64_C(0xffffffffffff)); /* node */ } void l2_uuid_to_string_short(const uuid_t *id, char *out) @@ -78,6 +78,11 @@ bool l2_uuid_from_string_short(uuid_t *id, const char *str) return true; } +int l2_uuid_compare(const uuid_t *c1, const uuid_t *c2) +{ + return memcmp(c1, c2, sizeof(uuid_t)); +} + /* This function exists because there's not a portable way to do this. * Don't suggest strtoull, because uint64_t may not be a ulonglong. */ bool l2__str_to_uint64(uint64_t *out, const char *str) diff --git a/src/uuid/uuid.h b/src/uuid/uuid.h index 740fa17..7ddeeb4 100644 --- a/src/uuid/uuid.h +++ b/src/uuid/uuid.h @@ -9,6 +9,9 @@ typedef union tag_uuid_t { uint8_t bytes[16]; uint64_t halves[2]; + +#define uuid_ls halves[0] +#define uuid_ms halves[1] } uuid_t; #define UUID_NULL_INIT { { 0 } } @@ -24,4 +27,6 @@ void l2_uuid_to_string_short(const uuid_t *id, char *out); bool l2_uuid_from_string(uuid_t *id, const char *str); bool l2_uuid_from_string_short(uuid_t *id, const char *str); +int l2_uuid_compare(const uuid_t *c1, const uuid_t *c2); + #endif /* include guard */ -- cgit v1.2.3-70-g09d2