Compare commits
1 commit
d95cba36a6
...
55f6887bb9
| Author | SHA1 | Date | |
|---|---|---|---|
| 55f6887bb9 |
8 changed files with 446 additions and 18 deletions
12
examples/hello.asmsh
Normal file
12
examples/hello.asmsh
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
mov $0x0a6f6c6c, %rax
|
||||
shl $(8*2), %rax
|
||||
or $0x6548, %rax
|
||||
push %rax
|
||||
mov $1, %rax
|
||||
mov %rax, %rdi
|
||||
mov %rsp, %rsi
|
||||
mov $6, %rdx
|
||||
syscall
|
||||
mov $60, %rax
|
||||
xor %rdi, %rdi
|
||||
syscall
|
||||
13
examples/hello2.asmsh
Normal file
13
examples/hello2.asmsh
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
.syntax intel
|
||||
mov %rax, 0x0a6f6c6c
|
||||
shl %rax, 16
|
||||
or %rax, 0x6548
|
||||
push %rax
|
||||
mov %rax, 1
|
||||
mov %rdi, %rax
|
||||
mov %rsi, %rsp
|
||||
mov %rdx, 6
|
||||
syscall
|
||||
mov %rax, 60
|
||||
xor %rdi, %rdi
|
||||
syscall
|
||||
|
|
@ -207,7 +207,7 @@ int asmsh_env_step(asmsh_env_t *env, int *status)
|
|||
err_wstatus:
|
||||
if(WIFEXITED(env->status))
|
||||
{
|
||||
dprintf(2, "Child exited with status %d\n", WEXITSTATUS(env->status));
|
||||
asmsh_log_debug("Child exited with status %d", WEXITSTATUS(env->status));
|
||||
}
|
||||
else if(WIFSIGNALED(env->status))
|
||||
{
|
||||
|
|
@ -431,7 +431,7 @@ static int _asmsh_env_spawn(asmsh_env_t *env)
|
|||
err_wstatus:
|
||||
if(WIFEXITED(wstatus))
|
||||
{
|
||||
dprintf(2, "Child exited with status %d\n", WEXITSTATUS(wstatus));
|
||||
asmsh_log_debug("Child exited with status %d", WEXITSTATUS(wstatus));
|
||||
}
|
||||
else if(WIFSIGNALED(wstatus))
|
||||
{
|
||||
|
|
|
|||
48
src/asmsh.c
48
src/asmsh.c
|
|
@ -79,11 +79,6 @@ int main(int argc, char *argv[], char *envp[])
|
|||
return 0;
|
||||
}
|
||||
}
|
||||
if(optind < argc)
|
||||
{
|
||||
dprintf(2, "File execution not supported yet\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
|
||||
asmsh_logger_t *logger;
|
||||
|
|
@ -102,7 +97,21 @@ int main(int argc, char *argv[], char *envp[])
|
|||
asmsh_logger_dprint(2, logger);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int argleft = argc - optind;
|
||||
if(argleft > 1)
|
||||
{
|
||||
help(argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* file execution */
|
||||
if(argleft > 0)
|
||||
{
|
||||
return asmsh_file_exec(&sh, argv[optind], logger);
|
||||
}
|
||||
|
||||
/* interactive prompt */
|
||||
#ifdef HAVE_LIBREADLINE
|
||||
rl_special_prefixes="";
|
||||
rl_basic_word_break_characters=" \t\n";
|
||||
|
|
@ -112,7 +121,8 @@ int main(int argc, char *argv[], char *envp[])
|
|||
load_history(hpath, add_history);
|
||||
#endif
|
||||
|
||||
while(1)
|
||||
char main_loop=1;
|
||||
while(main_loop)
|
||||
{
|
||||
#ifdef HAVE_LIBREADLINE
|
||||
char *cmd;
|
||||
|
|
@ -142,19 +152,27 @@ int main(int argc, char *argv[], char *envp[])
|
|||
cmd[readed] = '\0';
|
||||
|
||||
#endif
|
||||
ret = asmsh_exec(&sh, cmd);
|
||||
asmsh_logger_dprint_fmt(2, logger, asmsh_log_lvl_fmt);
|
||||
if(ret > 0)
|
||||
const char *split_cmd;
|
||||
asmsh_cmdline_t cmdline;
|
||||
asmsh_cmdline_init(&cmdline, cmd);
|
||||
while((split_cmd = asmsh_cmdline_iter(&cmdline)))
|
||||
{
|
||||
//unrecoverable error or exit (if ret == 1)
|
||||
ret--; // exit status
|
||||
if(ret)
|
||||
ret = asmsh_exec(&sh, split_cmd);
|
||||
|
||||
asmsh_logger_dprint_fmt(2, logger, asmsh_log_lvl_fmt);
|
||||
if(ret > 0)
|
||||
{
|
||||
dprintf(2, "Exit with status %d\n", ret);
|
||||
//unrecoverable error or exit (if ret == 1)
|
||||
ret--; // exit status
|
||||
if(ret)
|
||||
{
|
||||
dprintf(2, "Exit with status %d\n", ret);
|
||||
}
|
||||
main_loop=0;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
//else if (ret < 0) {} //recoverable errors
|
||||
}
|
||||
//else if (ret < 0) {} //recoverable errors
|
||||
#ifdef HAVE_LIBREADLINE
|
||||
add_history(cmd);
|
||||
#endif
|
||||
|
|
|
|||
231
src/shell.c
231
src/shell.c
|
|
@ -135,6 +135,236 @@ int asmsh_exec(asmsh_t *sh, const char *_cmd)
|
|||
return ret;
|
||||
}
|
||||
|
||||
|
||||
int asmsh_file_exec(asmsh_t *sh, const char *filename, asmsh_logger_t *logger)
|
||||
{
|
||||
asmsh_file_t file;
|
||||
int retval = 0;
|
||||
|
||||
if(asmsh_file_open(&file, filename))
|
||||
{
|
||||
asmsh_logger_dprint_fmt(2, logger, asmsh_log_lvl_fmt);
|
||||
return -1;
|
||||
}
|
||||
|
||||
const char *cmd;
|
||||
while((cmd = asmsh_file_iter(&file)))
|
||||
{
|
||||
int ret = asmsh_exec(sh, cmd);
|
||||
asmsh_logger_dprint_fmt(2, logger, asmsh_log_lvl_fmt);
|
||||
if(ret > 0)
|
||||
{
|
||||
//unrecoverable error
|
||||
ret--;
|
||||
if(ret)
|
||||
{
|
||||
asmsh_log_fatal("Child exited with satus %d",
|
||||
ret);
|
||||
retval = ret;
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(file.cmdline.err)
|
||||
{
|
||||
asmsh_log_fatal("Error while reading file : %s",
|
||||
file.cmdline.err);
|
||||
retval = -1;
|
||||
}
|
||||
|
||||
cleanup:
|
||||
asmsh_file_close(&file);
|
||||
asmsh_logger_dprint_fmt(2, logger, asmsh_log_lvl_fmt);
|
||||
return retval;
|
||||
}
|
||||
|
||||
int asmsh_file_open(asmsh_file_t *file, const char *filename)
|
||||
{
|
||||
return _asmsh_file_open(file, filename, 0);
|
||||
}
|
||||
|
||||
int _asmsh_file_open(asmsh_file_t *file, const char *filename, size_t bufsize)
|
||||
{
|
||||
bzero(file, sizeof(*file));
|
||||
if(!(file->filename = strdup(filename)))
|
||||
{
|
||||
asmsh_log_perror("Unable to strdup filename");
|
||||
return -1;
|
||||
}
|
||||
if((file->fd = open(file->filename, O_RDONLY)) < 0)
|
||||
{
|
||||
asmsh_log_fatal("Unable to open file '%s' : %s",
|
||||
filename, strerror(errno));
|
||||
goto err_open;
|
||||
}
|
||||
file->bufsize = bufsize ? bufsize : 4096;
|
||||
if(!(file->buf = malloc(file->bufsize)))
|
||||
{
|
||||
asmsh_log_perror("Unable to allocate file read buffer");
|
||||
goto err_malloc;
|
||||
}
|
||||
|
||||
if(read(file->fd, file->buf, file->bufsize) == -1)
|
||||
{
|
||||
asmsh_log_fatal("Unable to read '%s' : %s",
|
||||
file->filename, strerror(errno));
|
||||
goto err_read;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_read:
|
||||
free(file->buf);
|
||||
err_malloc:
|
||||
close(file->fd);
|
||||
file->fd = -1;
|
||||
err_open:
|
||||
free(file->filename);
|
||||
return -1;
|
||||
}
|
||||
|
||||
const char* asmsh_file_iter(asmsh_file_t *file)
|
||||
{
|
||||
if(!file->cmdline.cmd)
|
||||
{
|
||||
asmsh_cmdline_init(&file->cmdline, file->buf);
|
||||
}
|
||||
const char *ret = asmsh_cmdline_iter(&file->cmdline);
|
||||
if(!ret || !file->cmdline.prev_end)
|
||||
{
|
||||
if(file->eof)
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
/* no new command returned or end of buffer after
|
||||
returned command : attempt to read file again */
|
||||
if(file->cmdline.prev_end && file->cmdline.cmd)
|
||||
{
|
||||
file->cmdline.cmd[file->cmdline.stop] = file->cmdline.prev_end;
|
||||
}
|
||||
size_t left_sz;
|
||||
if(ret)
|
||||
{
|
||||
left_sz = strlen(&file->buf[file->cmdline.start]);
|
||||
}
|
||||
else
|
||||
{
|
||||
left_sz = 0;
|
||||
}
|
||||
memmove(file->buf, &file->buf[file->cmdline.start], left_sz);
|
||||
file->buf[left_sz] = '\0';
|
||||
|
||||
size_t readed = read(file->fd, &file->buf[left_sz],
|
||||
file->bufsize - left_sz);
|
||||
if(readed == -1)
|
||||
{
|
||||
asmsh_log_fatal("Unable to read '%s' : %s",
|
||||
file->filename, strerror(errno));
|
||||
goto clean_exit;
|
||||
}
|
||||
if (!readed)
|
||||
{
|
||||
file->eof = 1;
|
||||
if(!ret)
|
||||
{
|
||||
goto clean_exit;
|
||||
}
|
||||
}
|
||||
file->buf[readed+left_sz] = '\0';
|
||||
asmsh_cmdline_init(&file->cmdline, file->buf);
|
||||
return asmsh_file_iter(file);
|
||||
}
|
||||
return ret;
|
||||
|
||||
clean_exit:
|
||||
close(file->fd);
|
||||
free(file->buf);
|
||||
bzero(file, sizeof(*file));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void asmsh_file_close(asmsh_file_t *file)
|
||||
{
|
||||
if(file->fd)
|
||||
{
|
||||
close(file->fd);
|
||||
}
|
||||
if(file->buf)
|
||||
{
|
||||
free(file->buf);
|
||||
}
|
||||
bzero(file, sizeof(*file));
|
||||
}
|
||||
|
||||
void asmsh_cmdline_init(asmsh_cmdline_t *cmdline, char *cmd)
|
||||
{
|
||||
cmdline->cmd = cmd;
|
||||
cmdline->start = cmdline->stop = 0;
|
||||
cmdline->prev_end = '\0';
|
||||
cmdline->err = 0;
|
||||
}
|
||||
|
||||
const char* asmsh_cmdline_iter(asmsh_cmdline_t *cmdline)
|
||||
{
|
||||
if(cmdline->prev_end && cmdline->stop)
|
||||
{
|
||||
cmdline->cmd[cmdline->stop] = cmdline->prev_end;
|
||||
}
|
||||
if(!cmdline->cmd[cmdline->stop])
|
||||
{
|
||||
goto clean_exit;
|
||||
}
|
||||
if(cmdline->start || cmdline->stop)
|
||||
{
|
||||
cmdline->stop++;
|
||||
cmdline->start = cmdline->stop;
|
||||
if(!cmdline->cmd[cmdline->stop])
|
||||
{
|
||||
goto clean_exit;
|
||||
}
|
||||
}
|
||||
char chr;
|
||||
while((chr = cmdline->cmd[cmdline->stop]))
|
||||
{
|
||||
if(chr == '\\')
|
||||
{
|
||||
cmdline->stop++;
|
||||
}
|
||||
else if(chr == '"' || chr == '\'')
|
||||
{
|
||||
char closing = chr;
|
||||
do
|
||||
{
|
||||
if(chr == '\\') { cmdline->stop++; }
|
||||
cmdline->stop++;
|
||||
chr = cmdline->cmd[cmdline->stop];
|
||||
}while(chr && chr != closing);
|
||||
if(!chr)
|
||||
{
|
||||
cmdline->err = "Unterminated quoted string";
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
else if(strchr(ASMSH_IFS, (int)chr))
|
||||
{
|
||||
break;
|
||||
}
|
||||
cmdline->stop++;
|
||||
}
|
||||
if(cmdline->stop == cmdline->start)
|
||||
{
|
||||
return asmsh_cmdline_iter(cmdline);
|
||||
}
|
||||
cmdline->prev_end = cmdline->cmd[cmdline->stop];
|
||||
cmdline->cmd[cmdline->stop] = '\0';
|
||||
return &cmdline->cmd[cmdline->start];
|
||||
clean_exit:
|
||||
cmdline->cmd = NULL;
|
||||
cmdline->err = NULL;
|
||||
cmdline->start = cmdline->stop = 0;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int _compile_step(asmsh_t *sh, const char *cmd)
|
||||
{
|
||||
int ret, status;
|
||||
|
|
@ -299,4 +529,3 @@ size_t asmsh_parse_labels(asmsh_t *sh, char preffix, const char *cmd,
|
|||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
|
|
|
|||
67
src/shell.h
67
src/shell.h
|
|
@ -25,8 +25,13 @@
|
|||
#include "compile.h"
|
||||
|
||||
typedef struct asmsh_s asmsh_t;
|
||||
typedef struct asmsh_file_s asmsh_file_t;
|
||||
typedef struct asmsh_cmdline_s asmsh_cmdline_t;
|
||||
#include "shell_sym.h"
|
||||
|
||||
#define ASMSH_IFS "\n;"
|
||||
|
||||
|
||||
/** Structure storing all information needed to run a shell
|
||||
* @todo Make it handle command history
|
||||
* @todo Make it handle "modes" for function writing etc.
|
||||
|
|
@ -52,6 +57,27 @@ struct asmsh_s
|
|||
|
||||
};
|
||||
|
||||
struct asmsh_cmdline_s
|
||||
{
|
||||
char *cmd;
|
||||
size_t start;
|
||||
size_t stop;
|
||||
char prev_end;
|
||||
const char *err;
|
||||
};
|
||||
|
||||
struct asmsh_file_s
|
||||
{
|
||||
char *filename;
|
||||
int fd;
|
||||
short eof;
|
||||
char *buf;
|
||||
char cmd_end;
|
||||
size_t bufsize;
|
||||
asmsh_cmdline_t cmdline;
|
||||
size_t lineno;
|
||||
};
|
||||
|
||||
/** Initialize a shell environment
|
||||
* @param asmsh_t* Pointer on the shell environment
|
||||
* @param const char* The child process executable path (NULL is recommended
|
||||
|
|
@ -69,6 +95,47 @@ void asmsh_cleanup(asmsh_t *sh);
|
|||
* @return <0 on error 0 on ok 1 on exit */
|
||||
int asmsh_exec(asmsh_t *sh, const char *cmd);
|
||||
|
||||
/** Execute a file with a shell
|
||||
* @param asmsh_t* Pointer on the shell environment
|
||||
* @param const char* The path of the file to exec
|
||||
* @param asmsh_logger_t* A logger object
|
||||
*/
|
||||
int asmsh_file_exec(asmsh_t *sh, const char *filename, asmsh_logger_t *logger);
|
||||
|
||||
/** Initialize given asmsh_file_t*
|
||||
* @param asmsh_file_t* Pointer on the struct to initialize
|
||||
* @param const char* The filename to initialize with
|
||||
* @return 0 if no error
|
||||
*/
|
||||
int asmsh_file_open(asmsh_file_t *file, const char *filename);
|
||||
int _asmsh_file_open(asmsh_file_t *file, const char *filename, size_t bufsize);
|
||||
|
||||
/** Return the next command in a file
|
||||
* @param asmsh_file_t* The file to iter on, initialized by
|
||||
* @ref asmsh_file_open()
|
||||
* @return A reference on the next command or NULL for EOF or error
|
||||
*/
|
||||
const char* asmsh_file_iter(asmsh_file_t *file);
|
||||
|
||||
/** File cleanup
|
||||
* @param asmsh_file_t* the file to cleanup
|
||||
*/
|
||||
void asmsh_file_close(asmsh_file_t *file);
|
||||
|
||||
/** Initialize a command line iteration struct
|
||||
* @param asmsh_cmdline_t* the structure to initialize
|
||||
* @param char* the command line to iter on
|
||||
*/
|
||||
void asmsh_cmdline_init(asmsh_cmdline_t *cmdline, char *cmd);
|
||||
|
||||
/**
|
||||
* @param asmsh_cmdline_t* The command line to iter on
|
||||
* @return A reference on the next command in the command line
|
||||
* or NULL on end or error
|
||||
* @note On error @ref struct asmsh_cmdline_s::err is set to a string
|
||||
*/
|
||||
const char* asmsh_cmdline_iter(asmsh_cmdline_t *cmdline);
|
||||
|
||||
/** Replace all labels found in given command by their relative offset
|
||||
* @param asmsh_t* The shell
|
||||
* @param char The prefix character before a symbol name
|
||||
|
|
|
|||
4
tests/samples/sample_file_iter.txt
Normal file
4
tests/samples/sample_file_iter.txt
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
cmd1;cmd2
|
||||
cmd3
|
||||
|
||||
cmd 4
|
||||
|
|
@ -8,6 +8,8 @@
|
|||
#include "asmsh_check.h"
|
||||
#include "shell.h"
|
||||
|
||||
#define SAMPLE_FILE "samples/sample_file_iter.txt"
|
||||
|
||||
START_TEST(test_init)
|
||||
{
|
||||
asmsh_t sh;
|
||||
|
|
@ -57,10 +59,93 @@ START_TEST(test_embed)
|
|||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(test_cmdline_iter)
|
||||
{
|
||||
asmsh_cmdline_t cmdline;
|
||||
char *cmd = strdup("Hello world!");
|
||||
asmsh_cmdline_init(&cmdline, cmd);
|
||||
ck_assert_str_eq(asmsh_cmdline_iter(&cmdline), "Hello world!");
|
||||
ck_assert_ptr_eq(asmsh_cmdline_iter(&cmdline), NULL);
|
||||
ck_assert_ptr_eq(cmdline.cmd, NULL);
|
||||
ck_assert_ptr_eq(cmdline.err, NULL);
|
||||
free(cmd);
|
||||
|
||||
cmd = strdup("Hello \\\nworld !");
|
||||
asmsh_cmdline_init(&cmdline, cmd);
|
||||
ck_assert_str_eq(asmsh_cmdline_iter(&cmdline), "Hello \\\nworld !");
|
||||
ck_assert_ptr_eq(asmsh_cmdline_iter(&cmdline), NULL);
|
||||
ck_assert_ptr_eq(cmdline.cmd, NULL);
|
||||
ck_assert_ptr_eq(cmdline.err, NULL);
|
||||
free(cmd);
|
||||
|
||||
cmd = strdup("cmd1;cmd2\ncmd 3;cmd4\\;;cmd 5;;");
|
||||
asmsh_cmdline_init(&cmdline, cmd);
|
||||
ck_assert_str_eq(asmsh_cmdline_iter(&cmdline), "cmd1");
|
||||
ck_assert_str_eq(asmsh_cmdline_iter(&cmdline), "cmd2");
|
||||
ck_assert_str_eq(asmsh_cmdline_iter(&cmdline), "cmd 3");
|
||||
ck_assert_str_eq(asmsh_cmdline_iter(&cmdline), "cmd4\\;");
|
||||
ck_assert_str_eq(asmsh_cmdline_iter(&cmdline), "cmd 5");
|
||||
ck_assert_ptr_eq(asmsh_cmdline_iter(&cmdline), NULL);
|
||||
ck_assert_ptr_eq(cmdline.cmd, NULL);
|
||||
ck_assert_ptr_eq(cmdline.err, NULL);
|
||||
free(cmd);
|
||||
|
||||
cmd = strdup("cmd1 \"arg\n;1\"; cmd2 'arg2\\'';");
|
||||
asmsh_cmdline_init(&cmdline, cmd);
|
||||
ck_assert_str_eq(asmsh_cmdline_iter(&cmdline), "cmd1 \"arg\n;1\"");
|
||||
ck_assert_str_eq(asmsh_cmdline_iter(&cmdline), " cmd2 'arg2\\''");
|
||||
ck_assert_ptr_eq(asmsh_cmdline_iter(&cmdline), NULL);
|
||||
ck_assert_ptr_eq(cmdline.cmd, NULL);
|
||||
ck_assert_ptr_eq(cmdline.err, NULL);
|
||||
free(cmd);
|
||||
|
||||
cmd = strdup("cmd1 \"foobar err;\n");
|
||||
asmsh_cmdline_init(&cmdline, cmd);
|
||||
ck_assert_ptr_eq(asmsh_cmdline_iter(&cmdline), NULL);
|
||||
ck_assert_str_eq(cmdline.err, "Unterminated quoted string");
|
||||
ck_assert_ptr_ne(cmdline.cmd, NULL);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(test_file_iter)
|
||||
{
|
||||
asmsh_file_t file;
|
||||
const char *cmd;
|
||||
ck_assert_int_eq(_asmsh_file_open(&file, SAMPLE_FILE, 0), 0);
|
||||
ck_assert_str_eq(asmsh_file_iter(&file), "cmd1");
|
||||
ck_assert_str_eq(asmsh_file_iter(&file), "cmd2");
|
||||
ck_assert_str_eq(asmsh_file_iter(&file), "cmd3");
|
||||
ck_assert_str_eq(asmsh_file_iter(&file), "cmd 4");
|
||||
ck_assert_ptr_eq(asmsh_file_iter(&file), NULL);
|
||||
ck_assert_int_eq(file.fd, 0);
|
||||
ck_assert_ptr_eq(file.buf, NULL);
|
||||
|
||||
for(size_t i=6; i<15; i++)
|
||||
{
|
||||
dprintf(2, "========= %ld ========\n", i);
|
||||
ck_assert_int_eq(_asmsh_file_open(&file, SAMPLE_FILE, i), 0);
|
||||
ck_assert_str_eq(asmsh_file_iter(&file), "cmd1");
|
||||
ck_assert_str_eq(asmsh_file_iter(&file), "cmd2");
|
||||
ck_assert_str_eq(asmsh_file_iter(&file), "cmd3");
|
||||
ck_assert_str_eq(asmsh_file_iter(&file), "cmd 4");
|
||||
const char *foo = asmsh_file_iter(&file);
|
||||
if(foo) {
|
||||
dprintf(2, "DEBIG %d FOO='%s'\n", i, foo);
|
||||
}
|
||||
ck_assert_ptr_eq(foo, NULL);
|
||||
//ck_assert_ptr_eq(asmsh_file_iter(&file), NULL);
|
||||
ck_assert_int_eq(file.fd, 0);
|
||||
ck_assert_ptr_eq(file.buf, NULL);
|
||||
}
|
||||
}
|
||||
END_TEST
|
||||
|
||||
ASMSH_CHECK_START("Testing shell", "testing shell init/exec functions")
|
||||
ASMSH_ADD_TEST(test_init);
|
||||
ASMSH_ADD_TEST(test_exit);
|
||||
ASMSH_ADD_TEST(test_cmd);
|
||||
ASMSH_ADD_TEST(test_embed);
|
||||
ASMSH_ADD_TEST(test_cmdline_iter);
|
||||
ASMSH_ADD_TEST(test_file_iter);
|
||||
ASMSH_CHECK_END
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue