2020-12-07 04:46:37 +00:00
|
|
|
#ifdef HAVE_CONFIG_H
|
2020-12-01 19:55:16 +00:00
|
|
|
#include "config.h"
|
2020-12-07 04:46:37 +00:00
|
|
|
#endif
|
2020-12-01 19:55:16 +00:00
|
|
|
|
2022-05-24 23:01:51 +00:00
|
|
|
#include <kernaux/assert.h>
|
2020-12-01 19:55:16 +00:00
|
|
|
#include <kernaux/cmdline.h>
|
2022-06-30 20:07:10 +00:00
|
|
|
#include <kernaux/macro.h>
|
2022-06-07 05:35:14 +00:00
|
|
|
|
2022-06-27 23:13:40 +00:00
|
|
|
#include <stddef.h>
|
|
|
|
#include <string.h>
|
2020-12-06 10:16:15 +00:00
|
|
|
|
2020-12-04 02:41:45 +00:00
|
|
|
enum State {
|
|
|
|
INITIAL,
|
|
|
|
FINAL,
|
|
|
|
WHITESPACE,
|
|
|
|
TOKEN,
|
2021-12-12 15:47:22 +00:00
|
|
|
BACKSLASH,
|
|
|
|
QUOTE,
|
2021-12-12 17:10:56 +00:00
|
|
|
QUOTE_BACKSLASH,
|
2020-12-04 02:41:45 +00:00
|
|
|
};
|
|
|
|
|
2022-06-27 14:46:29 +00:00
|
|
|
static bool kernaux_cmdline_common(
|
|
|
|
const char *cmdline,
|
|
|
|
char *error_msg,
|
|
|
|
size_t *argc,
|
|
|
|
char arg_terminator,
|
|
|
|
char **argv,
|
|
|
|
char *buffer,
|
2022-06-27 16:30:47 +00:00
|
|
|
size_t *arg_idxs,
|
|
|
|
size_t arg_count_max,
|
|
|
|
size_t buffer_size
|
2022-06-27 14:46:29 +00:00
|
|
|
);
|
|
|
|
|
2022-06-27 23:13:40 +00:00
|
|
|
static bool kernaux_cmdline_iter(
|
|
|
|
char cur,
|
|
|
|
enum State *state,
|
|
|
|
size_t *buffer_or_file_pos,
|
|
|
|
char *error_msg,
|
|
|
|
size_t *argc,
|
|
|
|
char arg_terminator,
|
|
|
|
char **argv,
|
|
|
|
char *buffer,
|
|
|
|
size_t *arg_idxs,
|
|
|
|
size_t arg_count_max,
|
|
|
|
size_t buffer_size
|
|
|
|
);
|
|
|
|
|
|
|
|
/*******************************
|
|
|
|
* Implementations: public API *
|
|
|
|
*******************************/
|
2022-06-27 14:46:29 +00:00
|
|
|
|
2022-01-21 21:13:54 +00:00
|
|
|
bool kernaux_cmdline(
|
2020-12-01 20:22:38 +00:00
|
|
|
const char *const cmdline,
|
2022-01-22 22:32:52 +00:00
|
|
|
char *const error_msg,
|
2021-12-16 15:26:16 +00:00
|
|
|
size_t *const argc,
|
2022-01-22 22:32:52 +00:00
|
|
|
char **const argv,
|
|
|
|
char *const buffer,
|
2022-06-27 16:30:47 +00:00
|
|
|
const size_t arg_count_max,
|
2022-01-21 21:42:54 +00:00
|
|
|
const size_t buffer_size
|
2020-12-01 20:22:38 +00:00
|
|
|
) {
|
2022-06-13 16:28:00 +00:00
|
|
|
KERNAUX_ASSERT(cmdline);
|
|
|
|
KERNAUX_ASSERT(error_msg);
|
|
|
|
KERNAUX_ASSERT(argc);
|
|
|
|
KERNAUX_ASSERT(argv);
|
2022-06-27 16:30:47 +00:00
|
|
|
KERNAUX_ASSERT(arg_count_max > 0);
|
2022-06-13 16:28:00 +00:00
|
|
|
KERNAUX_ASSERT(buffer_size > 0);
|
2020-12-01 20:22:38 +00:00
|
|
|
|
2022-06-27 14:46:29 +00:00
|
|
|
return kernaux_cmdline_common(
|
|
|
|
cmdline,
|
|
|
|
error_msg,
|
|
|
|
argc,
|
|
|
|
'\0', // arg_terminator
|
|
|
|
argv,
|
|
|
|
buffer,
|
2022-06-27 16:30:47 +00:00
|
|
|
NULL,
|
|
|
|
arg_count_max,
|
|
|
|
buffer_size
|
2022-06-27 14:46:29 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-06-27 23:13:40 +00:00
|
|
|
/*********************************
|
|
|
|
* Implementation: main function *
|
|
|
|
*********************************/
|
2022-06-27 14:46:29 +00:00
|
|
|
|
2022-06-27 16:30:47 +00:00
|
|
|
#define CLEAR do { \
|
|
|
|
*argc = 0; \
|
|
|
|
if (argv) memset(argv, 0, sizeof(char*) * arg_count_max); \
|
|
|
|
if (buffer) memset(buffer, '\0', buffer_size); \
|
|
|
|
if (arg_idxs) memset(arg_idxs, 0, sizeof(size_t) * arg_count_max); \
|
|
|
|
} while (0)
|
|
|
|
|
2022-06-27 14:46:29 +00:00
|
|
|
bool kernaux_cmdline_common(
|
|
|
|
const char *const cmdline,
|
|
|
|
char *const error_msg,
|
|
|
|
size_t *const argc,
|
|
|
|
char arg_terminator,
|
|
|
|
char **const argv,
|
|
|
|
char *const buffer,
|
2022-06-27 16:30:47 +00:00
|
|
|
size_t *const arg_idxs,
|
|
|
|
const size_t arg_count_max,
|
|
|
|
const size_t buffer_size
|
2022-06-27 14:46:29 +00:00
|
|
|
) {
|
|
|
|
KERNAUX_ASSERT(cmdline);
|
|
|
|
KERNAUX_ASSERT(error_msg);
|
|
|
|
KERNAUX_ASSERT(argc);
|
2022-06-27 16:30:47 +00:00
|
|
|
(void)arg_idxs;
|
2022-06-27 14:46:29 +00:00
|
|
|
|
2021-12-15 10:45:40 +00:00
|
|
|
memset(error_msg, '\0', KERNAUX_CMDLINE_ERROR_MSG_SIZE_MAX);
|
2022-06-27 16:30:47 +00:00
|
|
|
CLEAR;
|
2020-12-01 20:22:38 +00:00
|
|
|
|
2022-01-21 21:42:54 +00:00
|
|
|
if (cmdline[0] == '\0') return true;
|
2020-12-01 20:55:09 +00:00
|
|
|
|
2020-12-04 02:41:45 +00:00
|
|
|
enum State state = INITIAL;
|
2022-06-27 16:30:47 +00:00
|
|
|
size_t buffer_or_file_pos = 0;
|
2020-12-01 20:55:09 +00:00
|
|
|
|
2022-06-27 23:13:40 +00:00
|
|
|
for (size_t index = 0; state != FINAL; ++index) {
|
|
|
|
const bool result = kernaux_cmdline_iter(
|
|
|
|
cmdline[index],
|
|
|
|
&state,
|
|
|
|
&buffer_or_file_pos,
|
|
|
|
error_msg,
|
|
|
|
argc,
|
|
|
|
arg_terminator,
|
|
|
|
argv,
|
|
|
|
buffer,
|
|
|
|
arg_idxs,
|
|
|
|
arg_count_max,
|
|
|
|
buffer_size
|
|
|
|
);
|
|
|
|
if (!result) goto fail;
|
2020-12-01 20:55:09 +00:00
|
|
|
}
|
|
|
|
|
2020-12-06 10:16:15 +00:00
|
|
|
return true;
|
2020-12-01 21:54:18 +00:00
|
|
|
|
|
|
|
fail:
|
2022-06-27 16:30:47 +00:00
|
|
|
CLEAR;
|
2020-12-06 10:16:15 +00:00
|
|
|
return false;
|
2020-12-01 20:22:38 +00:00
|
|
|
}
|
2022-06-27 23:13:40 +00:00
|
|
|
|
|
|
|
/**************************************
|
|
|
|
* Implementation: iteration function *
|
|
|
|
**************************************/
|
|
|
|
|
|
|
|
#define FAIL(msg) do { \
|
|
|
|
strcpy(error_msg, msg); \
|
|
|
|
return false; \
|
|
|
|
} while (0)
|
|
|
|
|
|
|
|
#define PUT_CHAR(char) do { \
|
|
|
|
if (buffer_size && *buffer_or_file_pos >= buffer_size) { \
|
|
|
|
FAIL("EOF or buffer overflow"); \
|
|
|
|
} \
|
|
|
|
if (buffer) { \
|
|
|
|
buffer[*buffer_or_file_pos] = char; \
|
|
|
|
} \
|
|
|
|
++(*buffer_or_file_pos); \
|
|
|
|
} while (0)
|
|
|
|
|
|
|
|
#define PUT_ARG do { \
|
|
|
|
if (arg_count_max && *argc >= arg_count_max) { \
|
|
|
|
FAIL("too many args"); \
|
|
|
|
} \
|
|
|
|
if (argv && buffer) { \
|
|
|
|
argv[*argc] = &buffer[*buffer_or_file_pos]; \
|
|
|
|
} \
|
|
|
|
if (arg_idxs) { \
|
|
|
|
arg_idxs[*argc] = *buffer_or_file_pos; \
|
|
|
|
} \
|
|
|
|
++(*argc); \
|
|
|
|
} while (0)
|
|
|
|
|
|
|
|
#define PUT_ARG_AND_CHAR(char) do { \
|
|
|
|
if (arg_count_max && *argc >= arg_count_max) { \
|
|
|
|
FAIL("too many args"); \
|
|
|
|
} \
|
|
|
|
if (buffer_size && *buffer_or_file_pos >= buffer_size) { \
|
|
|
|
FAIL("EOF or buffer overflow"); \
|
|
|
|
} \
|
|
|
|
if (argv && buffer) { \
|
|
|
|
argv[*argc] = &buffer[*buffer_or_file_pos]; \
|
|
|
|
buffer[*buffer_or_file_pos] = char; \
|
|
|
|
} \
|
|
|
|
if (arg_idxs) { \
|
|
|
|
arg_idxs[*argc] = *buffer_or_file_pos; \
|
|
|
|
} \
|
|
|
|
++(*argc); \
|
|
|
|
++(*buffer_or_file_pos); \
|
|
|
|
} while (0)
|
|
|
|
|
|
|
|
bool kernaux_cmdline_iter(
|
|
|
|
const char cur,
|
|
|
|
enum State *const state,
|
|
|
|
size_t *const buffer_or_file_pos,
|
|
|
|
char *const error_msg,
|
|
|
|
size_t *const argc,
|
|
|
|
char arg_terminator,
|
|
|
|
char **const argv,
|
|
|
|
char *const buffer,
|
|
|
|
size_t *const arg_idxs,
|
|
|
|
const size_t arg_count_max,
|
|
|
|
const size_t buffer_size
|
|
|
|
) {
|
|
|
|
switch (*state) {
|
|
|
|
case FINAL:
|
|
|
|
break;
|
|
|
|
|
|
|
|
case INITIAL:
|
|
|
|
if (cur == '\0') {
|
|
|
|
*state = FINAL;
|
|
|
|
} else if (cur == ' ') {
|
|
|
|
*state = WHITESPACE;
|
|
|
|
} else if (cur == '\\') {
|
|
|
|
*state = BACKSLASH;
|
|
|
|
PUT_ARG;
|
|
|
|
} else if (cur == '"') {
|
|
|
|
*state = QUOTE;
|
|
|
|
PUT_ARG;
|
|
|
|
} else {
|
|
|
|
*state = TOKEN;
|
|
|
|
PUT_ARG_AND_CHAR(cur);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case WHITESPACE:
|
|
|
|
if (cur == '\0') {
|
|
|
|
*state = FINAL;
|
|
|
|
} else if (cur == ' ') {
|
|
|
|
// do nothing
|
|
|
|
} else if (cur == '\\') {
|
|
|
|
*state = BACKSLASH;
|
|
|
|
PUT_ARG;
|
|
|
|
} else if (cur == '"') {
|
|
|
|
*state = QUOTE;
|
|
|
|
PUT_ARG;
|
|
|
|
} else {
|
|
|
|
*state = TOKEN;
|
|
|
|
PUT_ARG_AND_CHAR(cur);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case TOKEN:
|
|
|
|
if (cur == '\0') {
|
|
|
|
*state = FINAL;
|
|
|
|
PUT_CHAR(arg_terminator);
|
|
|
|
} else if (cur == ' ') {
|
|
|
|
*state = WHITESPACE;
|
|
|
|
PUT_CHAR(arg_terminator);
|
|
|
|
} else if (cur == '\\') {
|
|
|
|
*state = BACKSLASH;
|
|
|
|
} else if (cur == '"') {
|
|
|
|
FAIL("unescaped quotation mark");
|
|
|
|
} else {
|
|
|
|
PUT_CHAR(cur);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case BACKSLASH:
|
|
|
|
if (cur == '\0') {
|
|
|
|
FAIL("EOL after backslash");
|
|
|
|
} else {
|
|
|
|
*state = TOKEN;
|
|
|
|
PUT_CHAR(cur);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case QUOTE:
|
|
|
|
if (cur == '\0') {
|
|
|
|
FAIL("EOL inside quote");
|
|
|
|
} else if (cur == '\\') {
|
|
|
|
*state = QUOTE_BACKSLASH;
|
|
|
|
} else if (cur == '"') {
|
|
|
|
*state = WHITESPACE;
|
|
|
|
PUT_CHAR(arg_terminator);
|
|
|
|
} else {
|
|
|
|
PUT_CHAR(cur);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case QUOTE_BACKSLASH:
|
|
|
|
if (cur == '\0') {
|
|
|
|
FAIL("EOL after backslash inside quote");
|
|
|
|
} else {
|
|
|
|
*state = QUOTE;
|
|
|
|
PUT_CHAR(cur);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|