#include "commands.h" #include "command.h" #include "instance.h" #include "l2su.h" #include "uuid/uuid.h" #include "macros.h" #include #include #include #include #include #include #include #include #include #define CMD_MSG_INSTANCE_LOAD_ERR "Error loading instances" #define CMD_MSG_INSTANCE_ADD_ERR "Error adding the instance" #define CMD_MSG_INSTANCE_SAVE_ERR "Error saving the instance" #define CMD_MSG_INSTANCE_DEL_ERR "Error removing the instance" #define CMD_MSG_INSTANCE_RENAME_ERR "Error renaming the instance" #define CMD_MSG_INSTANCE_INVALID_NAME "The instance name is invalid." #define CMD_MSG_INSTANCE_DUPLICATE "An instance named '%s' already exists." #define CMD_MSG_INSTANCE_DIR_CREATE_E "Error creating instance directory: %s" #define CMD_MSG_INSTANCE_DIR_CHECK_E "Error checking instance directory: %s" #define CMD_MSG_INSTANCE_DIR_DIRTY "The instance directory '%s' is not empty. If this is intentional, rerun the command with --force." #define CMD_MSG_INSTANCE_NAME_NOT_FOUND "An instance by the name '%s' could not be found." #define CMD_MSG_INSTANCE_UUID_NOT_FOUND "An instance by the UUID '%s' could not be found." #define CMD_MSG_INSTANCE_DELDIR_ERR(_msg) "Error deleting instance directory: " _msg #define CMD_INSTANCE_CHECK(_res, _v, _msg) do { \ switch (((_res) = (_v))) { \ case INSTANCE_SUCCESS: break; \ case INSTANCE_ERRNO: \ CMD_FATAL(_msg ": %s: %s", l2_instance_errormsg[(_res)], strerror(errno)); \ break; \ default: \ CMD_FATAL(_msg ": %s", l2_instance_errormsg[(_res)]); \ } \ } while (0) void cmd_instance__check_name(const char *instname); unsigned cmd_instance_add(struct l2_context_node *ctx, char **args) { int res; char *name = NULL; char *path = NULL; char *newpath = NULL; uuid_t uuid; char uuidstr[UUID_STRLEN+1]; bool flag_force = false; DIR *dir = NULL; for (char **cur = args; *cur; ++cur) { if (!strcmp(*cur, "--force")) { flag_force = true; } else { CMD_FATAL(CMD_MSG_UNKNOWN_ARGUMENT, *cur); } } CMD_INSTANCE_CHECK(res, l2_instance_load_all(), CMD_MSG_INSTANCE_LOAD_ERR); /* get the name and path (if it exists) */ l2_cmd_collect_args(ctx, 2, "name", &name, "#location", &path); /* make sure the name and path make sense (path should be an absolute path, name shouldn't be empty) */ cmd_instance__check_name(name); /* make sure there is not a profile with that name already */ if (l2_instance_find_by_name(name)) { CMD_FATAL(CMD_MSG_INSTANCE_DUPLICATE, name); } /* also find an unused UUID through rejection sampling */ while (true) { l2_uuid_random(&uuid); if (!l2_instance_find_by_uuid(&uuid)) break; } /* create the directory at "path" (if it isn't empty, complain) */ if (path) { newpath = realpath(path, NULL); if (!newpath) { CMD_FATAL0(CMD_MSG_ALLOCATION_FAILED); } } else if (!path) { newpath = strdup(l2_state.paths.data); if (!newpath) { newpath_append_fail: CMD_FATAL0(CMD_MSG_ALLOCATION_FAILED); } 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; path = newpath; } /* TODO: now create the directory for realsies */ if (l2_launcher_mkdir_parents(path) < 0) { CMD_FATAL(CMD_MSG_INSTANCE_DIR_CREATE_E, strerror(errno)); } if (!flag_force) { dir = opendir(path); if (!dir) { CMD_FATAL(CMD_MSG_INSTANCE_DIR_CHECK_E, strerror(errno)); } struct dirent *ent; errno = 0; /* readdir can return NULL on success */ while ((ent = readdir(dir))) { if (strcmp(ent->d_name, ".") && strcmp(ent->d_name, "..")) { CMD_FATAL(CMD_MSG_INSTANCE_DIR_DIRTY, path); } errno = 0; } if (errno) { CMD_FATAL(CMD_MSG_INSTANCE_DIR_CHECK_E, strerror(errno)); } closedir(dir); dir = NULL; } /* add the instance */ struct l2_instance inst; l2_uuid_copy(&inst.uuid, &uuid); inst.name = name; inst.path = path; CMD_INSTANCE_CHECK(res, l2_instance_add_instance(&inst), CMD_MSG_INSTANCE_ADD_ERR); CMD_INSTANCE_CHECK(res, l2_instance_save_all(), CMD_MSG_INSTANCE_SAVE_ERR); if (dir) closedir(dir); free(newpath); return CMD_RESULT_SUCCESS; } int cmd__delete_with_reckless_abandon(const char *fpath, const struct stat *sb, int typeflag, struct FTW *ftwbuf) { L2_UNUSED(sb); L2_UNUSED(ftwbuf); switch (typeflag) { case FTW_F: case FTW_SL: if (unlink(fpath) < 0) return errno; break; case FTW_DP: if (rmdir(fpath) < 0) return errno; break; /* unlink */ case FTW_NS: case FTW_DNR: return -2; /* error */ } return 0; } unsigned cmd_instance_remove(struct l2_context_node *ctx, char **args) { char *name = NULL; bool flag_remove = false, flag_byid = false; int res; uuid_t instid = UUID_NULL_INIT; struct l2_instance *to_remove = NULL; char *removepath = NULL; char instid_pretty[UUID_STRLEN + 1]; for (char **cur = args; *cur; ++cur) { if (!strcmp("--delete", *cur)) { flag_remove = true; } else if (!strcmp("--id", *cur)) { flag_byid = true; } else { CMD_FATAL(CMD_MSG_UNKNOWN_ARGUMENT, *cur); } } l2_cmd_collect_args(ctx, 1, "name", &name); if (flag_byid && !l2_uuid_from_string(&instid, name)) { CMD_FATAL(CMD_MSG_INVALID_UUID, name); } CMD_INSTANCE_CHECK(res, l2_instance_load_all(), CMD_MSG_INSTANCE_LOAD_ERR); /* find the thing */ if (flag_byid) { to_remove = l2_instance_find_by_uuid(&instid); if (!to_remove) { l2_uuid_to_string(&instid, instid_pretty); CMD_FATAL(CMD_MSG_INSTANCE_UUID_NOT_FOUND, instid_pretty); } } else { to_remove = l2_instance_find_by_name(name); if (!to_remove) CMD_FATAL(CMD_MSG_INSTANCE_NAME_NOT_FOUND, name); } if (flag_remove) { removepath = strdup(to_remove->path); if (!removepath) { CMD_FATAL0(CMD_MSG_ALLOCATION_FAILED); } } /* remove the thing */ CMD_INSTANCE_CHECK(res, l2_instance_del_instance(to_remove), CMD_MSG_INSTANCE_DEL_ERR); to_remove = NULL; /* del_instance frees the instance */ CMD_INSTANCE_CHECK(res, l2_instance_save_all(), CMD_MSG_INSTANCE_SAVE_ERR); /* delete the thing if it is necessary */ if (flag_remove) { res = nftw(removepath, &cmd__delete_with_reckless_abandon, 128, FTW_DEPTH | FTW_PHYS); switch (res) { case -2: CMD_FATAL0(CMD_MSG_INSTANCE_DELDIR_ERR("unreadable file/folder encountered")); case -1: CMD_FATAL0(CMD_MSG_INSTANCE_DELDIR_ERR("error walking file tree")); case 0: break; default: CMD_FATAL(CMD_MSG_INSTANCE_DELDIR_ERR("error removing a file: %s"), strerror(res)); } } if (removepath) free(removepath); return CMD_RESULT_SUCCESS; } unsigned cmd_instance_list(struct l2_context_node *ctx, char **args) { L2_UNUSED(ctx); int res; if (*args) { CMD_FATAL(CMD_MSG_UNKNOWN_ARGUMENT, *args); } CMD_INSTANCE_CHECK(res, l2_instance_load_all(), CMD_MSG_INSTANCE_LOAD_ERR); if (l2_state.instance_head) { char idstr[UUID_STRLEN + 1]; for (struct l2_instance *inst = l2_state.instance_head; inst; inst = inst->next) { l2_uuid_to_string(&inst->uuid, idstr); printf("- %s (%s) at '%s'\n", inst->name, idstr, inst->path); } } else { fputs("There are no instances.\n", stderr); } return CMD_RESULT_SUCCESS; } unsigned cmd_instance_rename(struct l2_context_node *ctx, char **args) { int res; char *oldname = NULL; char *newname = NULL; struct l2_instance *to_rename = NULL; if (*args) { CMD_FATAL(CMD_MSG_UNKNOWN_ARGUMENT, *args); } CMD_INSTANCE_CHECK(res, l2_instance_load_all(), CMD_MSG_INSTANCE_LOAD_ERR); l2_cmd_collect_args(ctx, 2, "oldname", &oldname, "newname", &newname); cmd_instance__check_name(newname); if (l2_instance_find_by_name(newname)) { CMD_FATAL(CMD_MSG_INSTANCE_DUPLICATE, newname); } if (!(to_rename = l2_instance_find_by_name(oldname))) { CMD_FATAL(CMD_MSG_INSTANCE_NAME_NOT_FOUND, oldname); } CMD_INSTANCE_CHECK(res, l2_instance_rename_instance(to_rename, newname), CMD_MSG_INSTANCE_RENAME_ERR); CMD_INSTANCE_CHECK(res, l2_instance_save_all(), CMD_MSG_INSTANCE_SAVE_ERR); return CMD_RESULT_SUCCESS; } void cmd_instance__check_name(const char *instname) { if (!*instname) goto failure; for (const char *cur = instname; *cur; ++cur) { if (*cur <= ' ') goto failure; /* not like you couldn't just use a unicode NBSP or something */ } return; failure: CMD_FATAL0(CMD_MSG_INSTANCE_INVALID_NAME); }