Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,10 @@ ifdef QEMU_DEBUG
endif

clean:
rm -rf build/*
$(MAKE) -C core clean
$(MAKE) -C uefi clean
$(MAKE) -C cli clean

.PHONY: clean

Expand All @@ -52,7 +54,14 @@ build/efi: uefi/build/app.efi
mkdir -p $@/EFI/BOOT
cp -f $^ $@/EFI/BOOT/BOOTX64.efi

build: build/efi
build/cli:
$(MAKE) -C cli
@rm -rf $@
@cp -r cli/build $@

build: build/efi build/cli

.PHONY: build build/efi build/cli

qemu: build
$(QEMU) \
Expand Down
36 changes: 36 additions & 0 deletions cli/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
PLATFORM ?=

OBJS := app.o

OBJS += commands.o
OBJS += commands/help.o
OBJS += commands/store.o
OBJS += commands/switch.o

OBJS += lib/argtable/argtable3.o

CFLAGS := -Wall -Wextra -Werror \
-I $(ROOT_DIR)/shared/include/ \
-I. \
-I lib/argtable \
-D TRACE_DEBUG

LDFLAGS := -lm

APP_NAME := switch-os-cli

TARGETS :=
ifneq ($(PLATFORM),)
include makefiles/$(PLATFORM).mk
else
include makefiles/*.mk
endif

all: $(TARGETS)
.DEFAULT_GOAL := all

clean:
rm -rf obj/*
rm -rf build/*

.PHONY: all clean
56 changes: 56 additions & 0 deletions cli/app.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#include "app.h"

#include <argtable3.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>

#include "commands/help.h"
#include "commands/store.h"
#include "commands/switch.h"

unsigned long trace(const char* fmt, ...) {
va_list args;
va_start(args, fmt);
unsigned long res = vprintf(fmt, args);
va_end(args);
return res;
}

struct arg_lit* help_arg;
struct arg_lit* store_arg;
struct arg_lit* switch_arg;
struct arg_end* end_arg;

void register_cmd(const struct cmd* cmd, void* ctx) {
arg_cmd_register(cmd->name, cmd->handler, cmd->description, ctx);
}

int main(int argc, char* argv[]) {
int exitcode = EXIT_SUCCESS;

arg_set_module_name(APP_NAME);
arg_set_module_version(APP_VER_MAJOR, APP_VER_MINOR, APP_VER_PATCH, APP_VER_TAG);

arg_cmd_init();
register_cmd(&g_help_cmd, NULL);
register_cmd(&g_store_cmd, NULL);
register_cmd(&g_switch_cmd, NULL);

arg_dstr_t res = arg_dstr_create();

if (argc == 1 || arg_cmd_info(argv[1]) == NULL) {
arg_make_get_help_msg(res);
printf("%s", arg_dstr_cstr(res));
goto cleanup;
}

exitcode = arg_cmd_dispatch(argv[1], argc, argv, res);
printf("%s", arg_dstr_cstr(res));

cleanup:
arg_dstr_destroy(res);
arg_cmd_uninit();

return exitcode;
}
7 changes: 7 additions & 0 deletions cli/app.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#pragma once

#define APP_NAME "switch-os-cli"
#define APP_VER_MAJOR 1
#define APP_VER_MINOR 0
#define APP_VER_PATCH 0
#define APP_VER_TAG ""
17 changes: 17 additions & 0 deletions cli/commands.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#include "commands.h"

#include <argtable3.h>

#include "guid.h"
#include "osl.h"

err_t send_uefi_command(const wchar_t* name, void* data, size_t data_size) {
err_t err = SUCCESS;

struct guid commands_guid = EFI_COMMANDS_GUID;
CHECK_RETHROW_TRACE(set_uefi_variable(&commands_guid, name, data, data_size), "Failed to set UEFI variable %ls\n",
name);

cleanup:
return err;
}
30 changes: 30 additions & 0 deletions cli/commands.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#pragma once

#include <argtable3.h>
#include <wchar.h>

#include "error.h"

typedef int (*cmd_handler)(int argc, char* argv[], arg_dstr_t res, void* ctx);

// This struct describes each of the subcommands of the main application.
struct cmd {
/// The name of the subcommand.
const char* name;

/// The description of the subcommand. Shown when running the `help` subcommand, or when passing `--help` to the
/// subcommand.
const char* description;

/// The function executed when the subcommand is passed. The return value of the subcommand is the exitcode of the
/// application. Any output should be passed to the `res` dynamic string.
const cmd_handler handler;
};

/**
* Sends a command to the UEFI application via its `SetVariable` hook.
*
* This calls the `SetVariable` UEFI runtime service, with the magic GUID `EFI_COMMANDS_GUID` which identifies commands
* to the hook. Note that the value of `data` may change due to this call, depending on the command.
*/
err_t send_uefi_command(const wchar_t* name, void* data, size_t data_size);
79 changes: 79 additions & 0 deletions cli/commands/help.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#include "help.h"

#include <argtable3.h>
#include <stdint.h>
#include <stdlib.h>

#include "app.h"
#include "commands.h"
#include "utils.h"

/**
* Lists all the subcommands of this application along with their descriptions into the `res` dynamic string.
*/
static void help_list_commands(arg_dstr_t res) {
arg_dstr_cat(res, "Usage:\n");
arg_dstr_catf(res, " %s <command> [options] [args]\n\n", APP_NAME);
arg_dstr_cat(res, "Available commands:\n");

arg_cmd_itr_t itr = arg_cmd_itr_create();
do {
arg_cmd_info_t* cmd_info = arg_cmd_itr_value(itr);
arg_dstr_catf(res, " %-23s %s\n", cmd_info->name, cmd_info->description);
} while (arg_cmd_itr_advance(itr));
arg_cmd_itr_destroy(itr);
}

/**
* Retrieves the detailed help information of a specific subcommand by calling its handler with `--help`, and filling
* the dynamic string `res` with the result.
*/
static int help_command(arg_dstr_t res, const char* cmd_name) {
if (arg_cmd_info(cmd_name) == NULL) {
arg_dstr_catf(res, "Unknown command: %s\n", cmd_name);
arg_make_get_help_msg(res);
return EXIT_FAILURE;
}

arg_cmd_info_t* cmd_info = arg_cmd_info(cmd_name);
char* cmd_argv[] = {APP_NAME, cmd_info->name, "--help"};
return cmd_info->proc(ARRAY_SIZE(cmd_argv), cmd_argv, res, NULL);
}

static int help_handler(int argc, char* argv[], arg_dstr_t res, UNUSED_PARAM void* ctx) {
int exitcode = 0;

arg_str_t* cmd = arg_str1(NULL, NULL, g_help_cmd.name, NULL);
arg_str_t* cmd_name = arg_str0(NULL, NULL, "<command>", NULL);
arg_lit_t* help = arg_lit0("h", "help", "Output usage information");
arg_end_t* end = arg_end(20);
void* argtable[] = {cmd, cmd_name, help, end};

if (arg_nullcheck(argtable) != 0) {
exitcode = EXIT_FAILURE;
goto cleanup;
}

int nerrors = arg_parse(argc, argv, argtable);
if (arg_make_syntax_err_help_msg(res, g_help_cmd.name, help->count, nerrors, argtable, end, &exitcode)) {
goto cleanup;
}

// The help command either receives another subcommand and prints specific information about it, or lists all the
// subcommands with their descriptions.
if (cmd_name->count == 0) {
help_list_commands(res);
} else {
exitcode = help_command(res, cmd_name->sval[0]);
}

cleanup:
arg_freetable(argtable, ARRAY_SIZE(argtable));
return exitcode;
}

struct cmd g_help_cmd = {
.name = "help",
.description = "Output usage information",
.handler = help_handler,
};
5 changes: 5 additions & 0 deletions cli/commands/help.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#pragma once

#include "commands.h"

extern struct cmd g_help_cmd;
75 changes: 75 additions & 0 deletions cli/commands/store.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
#include "store.h"

#include <argtable3.h>
#include <stdint.h>
#include <stdlib.h>

#include "commands.h"
#include "core/header.h"
#include "osl.h"
#include "utils.h"

/**
* Performs the store operation. This updates the `core_header->action` value to `CORE_ACTION_STORE` so next time an S3
* occurs, core will store the current state of the OS to the disk.
*
* @param is_lazy If false, the function will immediately enter S3 after updating `core_header->action` to switch the
* OS's state. If true, the S3 doesn't occur, so the switch will happen next time the user enters S3.
*/
static err_t execute_store_op(bool is_lazy) {
err_t err = SUCCESS;

CHECK_RETHROW(enable_uefi_privileges());
CHECK_RETHROW(enable_sleep_privileges());

enum core_action action = CORE_ACTION_STORE;
CHECK_RETHROW(send_uefi_command(L"SET_CORE_ACTION", &action, sizeof(action)));

if (!is_lazy) {
CHECK_RETHROW_TRACE(enter_sleep_s3(), "Failed to enter sleep S3\n");
}

cleanup:
return err;
}

static int store_handler(int argc, char* argv[], arg_dstr_t res, UNUSED_PARAM void* ctx) {
int exitcode = 0;

arg_str_t* cmd = arg_str1(NULL, NULL, g_store_cmd.name, NULL);
arg_str_t* cmd_name = arg_str0(NULL, NULL, "<command>", NULL);
arg_lit_t* help = arg_lit0("h", "help", "Output usage information");
arg_lit_t* lazy = arg_lit0(
NULL, "lazy",
"Don't immediately enter suspend mode. The store will happen the next time the user enters suspend themselves");
arg_end_t* end = arg_end(20);
void* argtable[] = {cmd, cmd_name, help, lazy, end};

if (arg_nullcheck(argtable) != 0) {
exitcode = 1;
goto cleanup;
}

int nerrors = arg_parse(argc, argv, argtable);
if (arg_make_syntax_err_help_msg(res, g_store_cmd.name, help->count, nerrors, argtable, end, &exitcode) != 0) {
goto cleanup;
}

bool is_lazy = lazy->count > 0;
if (!IS_SUCCESS(execute_store_op(is_lazy))) {
exitcode = EXIT_FAILURE;
goto cleanup;
}

cleanup:
arg_freetable(argtable, ARRAY_SIZE(argtable));
return exitcode;
}

struct cmd g_store_cmd = {
.name = "store",
.description =
"Store the current state of the OS to the disk. This command immediately enters sleep (S3) unless the --lazy flag "
"is passed.",
.handler = store_handler,
};
5 changes: 5 additions & 0 deletions cli/commands/store.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#pragma once

#include "commands.h"

extern struct cmd g_store_cmd;
Loading