1364 lines
31 KiB
C
1364 lines
31 KiB
C
/* ----------------------------------------------------------------------- *
|
|
*
|
|
* Copyright 2008-2011 Gene Cumm - All Rights Reserved
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, Inc., 53 Temple Place Ste 330,
|
|
* Boston MA 02111-1307, USA; either version 2 of the License, or
|
|
* (at your option) any later version; incorporated herein by reference.
|
|
*
|
|
* ----------------------------------------------------------------------- */
|
|
|
|
/*
|
|
* rosh.c
|
|
*
|
|
* Read-Only shell; Simple shell system designed for SYSLINUX-derivitives.
|
|
* Provides minimal commands utilizing the console via stdout/stderr as the
|
|
* sole output devices. Designed to compile for Linux for testing/debugging.
|
|
*/
|
|
|
|
/*
|
|
* ToDos:
|
|
* prompt: Allow left/right arrow, home/end and more?
|
|
* commands Break into argv/argc-like array
|
|
* rosh_cfg: allow -s <file> to change config
|
|
* rosh_ls(): sorted; then multiple columns
|
|
* prompt: Possibly honor timeout on initial entry for usage as UI
|
|
* Also possibly honor totaltimeout
|
|
*/
|
|
|
|
/*#define DO_DEBUG 1
|
|
//*/
|
|
/* Uncomment the above line for debugging output; Comment to remove */
|
|
/*#define DO_DEBUG2 1
|
|
//*/
|
|
/* Uncomment the above line for super-debugging output; Must have regular
|
|
* debugging enabled; Comment to remove.
|
|
*/
|
|
#include "rosh.h"
|
|
#include "version.h"
|
|
|
|
#define APP_LONGNAME "Read-Only Shell"
|
|
#define APP_NAME "rosh"
|
|
#define APP_AUTHOR "Gene Cumm"
|
|
#define APP_YEAR "2010"
|
|
#define APP_VER "beta-b090"
|
|
|
|
/* Print version information to stdout
|
|
*/
|
|
void rosh_version(int vtype)
|
|
{
|
|
char env[256];
|
|
env[0] = 0;
|
|
printf("%s v %s; (c) %s %s.\n\tFrom Syslinux %s, %s\n", APP_LONGNAME, APP_VER, APP_YEAR, APP_AUTHOR, VERSION_STR, DATE);
|
|
switch (vtype) {
|
|
case 1:
|
|
rosh_get_env_ver(env, 256);
|
|
printf("\tRunning on %s\n", env);
|
|
}
|
|
}
|
|
|
|
/* Print beta message and if DO_DEBUG/DO_DEBUG2 are active
|
|
*/
|
|
void print_beta(void)
|
|
{
|
|
puts(rosh_beta_str);
|
|
ROSH_DEBUG("DO_DEBUG active\n");
|
|
ROSH_DEBUG2("DO_DEBUG2 active\n");
|
|
}
|
|
|
|
/* Search a string for first non-space (' ') character, starting at ipos
|
|
* istr input string to parse
|
|
* ipos input position to start at
|
|
*/
|
|
int rosh_search_nonsp(const char *istr, const int ipos)
|
|
{
|
|
int curpos;
|
|
char c;
|
|
|
|
curpos = ipos;
|
|
c = istr[curpos];
|
|
while (c && isspace(c))
|
|
c = istr[++curpos];
|
|
return curpos;
|
|
}
|
|
|
|
/* Search a string for space (' '), returning the position of the next space
|
|
* or the '\0' at end of string
|
|
* istr input string to parse
|
|
* ipos input position to start at
|
|
*/
|
|
int rosh_search_sp(const char *istr, const int ipos)
|
|
{
|
|
int curpos;
|
|
char c;
|
|
|
|
curpos = ipos;
|
|
c = istr[curpos];
|
|
while (c && !(isspace(c)))
|
|
c = istr[++curpos];
|
|
return curpos;
|
|
}
|
|
|
|
/* Parse a string for the first non-space string, returning the end position
|
|
* from src
|
|
* dest string to contain the first non-space string
|
|
* src string to parse
|
|
* ipos Position to start in src
|
|
*/
|
|
int rosh_parse_sp_1(char *dest, const char *src, const int ipos)
|
|
{
|
|
int bpos, epos; /* beginning and ending position of source string
|
|
to copy to destination string */
|
|
|
|
bpos = 0;
|
|
epos = 0;
|
|
/* //HERE-error condition checking */
|
|
bpos = rosh_search_nonsp(src, ipos);
|
|
epos = rosh_search_sp(src, bpos);
|
|
if (epos > bpos) {
|
|
memcpy(dest, src + bpos, epos - bpos);
|
|
if (dest[epos - bpos] != 0)
|
|
dest[epos - bpos] = 0;
|
|
} else {
|
|
epos = strlen(src);
|
|
dest[0] = 0;
|
|
}
|
|
return epos;
|
|
}
|
|
|
|
/*
|
|
* parse_args1: Try 1 at parsing a string to an argc/argv pair. use free_args1 to free memory malloc'd
|
|
*
|
|
* Derived from com32/lib/sys/argv.c:__parse_argv()
|
|
* Copyright 2004-2009 H. Peter Anvin - All Rights Reserved
|
|
* Copyright 2009 Intel Corporation; author: H. Peter Anvin
|
|
*/
|
|
int parse_args1(char ***iargv, const char *istr)
|
|
{
|
|
int argc = 0;
|
|
const char *p;
|
|
char *q, *r, *args, **arg;
|
|
int sp = 1; //, qt = 0; /* Was a space; inside a quote */
|
|
|
|
/* Scan 1: Length */
|
|
/* I could eliminate this if I knew a max length, like strncpy() */
|
|
int len = strlen(istr);
|
|
|
|
/* Scan 2: Copy, nullify and make argc */
|
|
if (!(args = malloc(len + 1)))
|
|
goto fail_args;
|
|
q = args;
|
|
for (p = istr;; p++) {
|
|
if (*p <= ' ') {
|
|
if (!sp) {
|
|
sp = 1;
|
|
*q++ = '\0';
|
|
}
|
|
} else {
|
|
if (sp) {
|
|
argc++;
|
|
sp = 0;
|
|
}
|
|
*q++ = *p;
|
|
}
|
|
if (!*p)
|
|
break;
|
|
}
|
|
|
|
q--; /* Point q to final null */
|
|
/* Scan 3: Build array of pointers */
|
|
if (!(*iargv = malloc((argc + 1) * sizeof(char *))))
|
|
goto fail_args_ptr;
|
|
arg = *iargv;
|
|
arg[argc] = NULL; /* Nullify the last pointer */
|
|
if (*args != '\0')
|
|
*arg++ = args;
|
|
for (r = args; r < q ; r++) {
|
|
if (*r == '\0') {
|
|
*arg++ = r + 1;
|
|
}
|
|
}
|
|
|
|
fail_args:
|
|
return argc;
|
|
fail_args_ptr:
|
|
free(args);
|
|
return 0;
|
|
}
|
|
|
|
/* Free argv created by parse_args1()
|
|
* argv Argument Values
|
|
*/
|
|
void free_args1(char ***argv)
|
|
{
|
|
char *s;
|
|
s = **argv;
|
|
free(*argv);
|
|
free(s);
|
|
}
|
|
|
|
/* Convert a string to an argc/argv pair
|
|
* str String to parse
|
|
* argv Argument Values
|
|
* returns Argument Count
|
|
*/
|
|
int rosh_str2argv(char ***argv, const char *str)
|
|
{
|
|
return parse_args1(argv, str);
|
|
}
|
|
|
|
/* Free an argv created by rosh_str2argv()
|
|
* argv Argument Values to free
|
|
*/
|
|
void rosh_free_argv(char ***argv)
|
|
{
|
|
free_args1(argv);
|
|
}
|
|
|
|
/* Print the contents of an argc/argv pair
|
|
* argc Argument Count
|
|
* argv Argument Values
|
|
*/
|
|
void rosh_pr_argv(int argc, char *argv[])
|
|
{
|
|
int i;
|
|
for (i = 0; i < argc; i++) {
|
|
printf("%s%s", argv[i], (i < argc)? " " : "");
|
|
}
|
|
puts("");
|
|
}
|
|
|
|
/* Print the contents of an argc/argv pair verbosely
|
|
* argc Argument Count
|
|
* argv Argument Values
|
|
*/
|
|
void rosh_pr_argv_v(int argc, char *argv[])
|
|
{
|
|
int i;
|
|
for (i = 0; i < argc; i++) {
|
|
printf("%4d '%s'\n", i, argv[i]);
|
|
}
|
|
}
|
|
|
|
/* Reset the getopt() environment
|
|
*/
|
|
void rosh_getopt_reset(void)
|
|
{
|
|
optind = 0;
|
|
optopt = 0;
|
|
}
|
|
|
|
/* Display help
|
|
* type Help type
|
|
* cmdstr Command for which help is requested
|
|
*/
|
|
void rosh_help(int type, const char *cmdstr)
|
|
{
|
|
switch (type) {
|
|
case 2:
|
|
if ((cmdstr == NULL) || (strcmp(cmdstr, "") == 0)) {
|
|
rosh_version(0);
|
|
puts(rosh_help_str2);
|
|
} else {
|
|
switch (cmdstr[0]) {
|
|
case 'c':
|
|
puts(rosh_help_cd_str);
|
|
break;
|
|
case 'l':
|
|
puts(rosh_help_ls_str);
|
|
break;
|
|
default:
|
|
printf(rosh_help_str_adv, cmdstr);
|
|
}
|
|
}
|
|
break;
|
|
case 1:
|
|
default:
|
|
if (cmdstr)
|
|
printf("%s: %s: unknown command\n", APP_NAME, cmdstr);
|
|
rosh_version(0);
|
|
puts(rosh_help_str1);
|
|
}
|
|
}
|
|
|
|
/* Handle most/all errors
|
|
* ierrno Input Error number
|
|
* cmdstr Command being executed to cause error
|
|
* filestr File/parameter causing error
|
|
*/
|
|
void rosh_error(const int ierrno, const char *cmdstr, const char *filestr)
|
|
{
|
|
printf("--ERROR: %s '%s': ", cmdstr, filestr);
|
|
switch (ierrno) {
|
|
case 0:
|
|
puts("NO ERROR");
|
|
break;
|
|
case ENOENT:
|
|
puts("not found");
|
|
/* SYSLinux-3.72 COM32 API returns this for a
|
|
directory or empty file */
|
|
ROSH_COM32(" (COM32) could be a directory or empty file\n");
|
|
break;
|
|
case EIO:
|
|
puts("I/O Error");
|
|
break;
|
|
case EBADF:
|
|
puts("Bad File Descriptor");
|
|
break;
|
|
case EACCES:
|
|
puts("Access DENIED");
|
|
break;
|
|
case ENOTDIR:
|
|
puts("not a directory");
|
|
ROSH_COM32(" (COM32) could be directory\n");
|
|
break;
|
|
case EISDIR:
|
|
puts("IS a directory");
|
|
break;
|
|
case ENOSYS:
|
|
puts("not implemented");
|
|
break;
|
|
default:
|
|
printf("returns error; errno=%d\n", ierrno);
|
|
}
|
|
} /* rosh_error */
|
|
|
|
/* Concatenate command line arguments into one string
|
|
* cmdstr Output command string
|
|
* cmdlen Length of cmdstr
|
|
* argc Argument Count
|
|
* argv Argument Values
|
|
* barg Beginning Argument
|
|
*/
|
|
int rosh_argcat(char *cmdstr, const int cmdlen, const int argc, char *argv[],
|
|
const int barg)
|
|
{
|
|
int i, arglen, curpos; /* index, argument length, current position
|
|
in cmdstr */
|
|
curpos = 0;
|
|
cmdstr[0] = '\0'; /* Nullify string just to be sure */
|
|
for (i = barg; i < argc; i++) {
|
|
arglen = strlen(argv[i]);
|
|
/* Theoretically, this should never be met in SYSLINUX */
|
|
if ((curpos + arglen) > (cmdlen - 1))
|
|
arglen = (cmdlen - 1) - curpos;
|
|
memcpy(cmdstr + curpos, argv[i], arglen);
|
|
curpos += arglen;
|
|
if (curpos >= (cmdlen - 1)) {
|
|
/* Hopefully, curpos should not be greater than
|
|
(cmdlen - 1) */
|
|
/* Still need a '\0' at the last character */
|
|
cmdstr[(cmdlen - 1)] = 0;
|
|
break; /* Escape out of the for() loop;
|
|
We can no longer process anything more */
|
|
} else {
|
|
cmdstr[curpos] = ' ';
|
|
curpos += 1;
|
|
cmdstr[curpos] = 0;
|
|
}
|
|
}
|
|
/* If there's a ' ' at the end, remove it. This is normal unless
|
|
the maximum length is met/exceeded. */
|
|
if (cmdstr[curpos - 1] == ' ')
|
|
cmdstr[--curpos] = 0;
|
|
return curpos;
|
|
} /* rosh_argcat */
|
|
|
|
/*
|
|
* Prints a lot of the data in a struct termios
|
|
*/
|
|
/*
|
|
void rosh_print_tc(struct termios *tio)
|
|
{
|
|
printf(" -- termios: ");
|
|
printf(".c_iflag=%04X ", tio->c_iflag);
|
|
printf(".c_oflag=%04X ", tio->c_oflag);
|
|
printf(".c_cflag=%04X ", tio->c_cflag);
|
|
printf(".c_lflag=%04X ", tio->c_lflag);
|
|
printf(".c_cc[VTIME]='%d' ", tio->c_cc[VTIME]);
|
|
printf(".c_cc[VMIN]='%d'", tio->c_cc[VMIN]);
|
|
printf("\n");
|
|
}
|
|
*/
|
|
|
|
/*
|
|
* Attempts to get a single key from the console
|
|
* returns key pressed
|
|
*/
|
|
int rosh_getkey(void)
|
|
{
|
|
int inc;
|
|
|
|
inc = KEY_NONE;
|
|
while (inc == KEY_NONE)
|
|
inc = get_key(stdin, 6000);
|
|
return inc;
|
|
} /* rosh_getkey */
|
|
|
|
/*
|
|
* Qualifies a filename relative to the working directory
|
|
* filestr Filename to qualify
|
|
* pwdstr working directory
|
|
* returns qualified file name string
|
|
*/
|
|
void rosh_qualify_filestr(char *filestr, const char *ifilstr,
|
|
const char *pwdstr)
|
|
{
|
|
int filepos = 0;
|
|
if ((filestr) && (pwdstr) && (ifilstr)) {
|
|
if (ifilstr[0] != SEP) {
|
|
strcpy(filestr, pwdstr);
|
|
filepos = strlen(pwdstr);
|
|
if (filestr[filepos - 1] != SEP)
|
|
filestr[filepos++] = SEP;
|
|
}
|
|
strcpy(filestr + filepos, ifilstr);
|
|
ROSH_DEBUG("--'%s'\n", filestr);
|
|
}
|
|
}
|
|
|
|
/* Concatenate multiple files to stdout
|
|
* argc Argument Count
|
|
* argv Argument Values
|
|
*/
|
|
void rosh_cat(int argc, char *argv[])
|
|
{
|
|
FILE *f;
|
|
char buf[ROSH_BUF_SZ];
|
|
int i, numrd;
|
|
|
|
for (i = 0; i < argc; i++) {
|
|
printf("--File = '%s'\n", argv[i]);
|
|
errno = 0;
|
|
f = fopen(argv[i], "r");
|
|
if (f != NULL) {
|
|
numrd = fread(buf, 1, ROSH_BUF_SZ, f);
|
|
while (numrd > 0) {
|
|
fwrite(buf, 1, numrd, stdout);
|
|
numrd = fread(buf, 1, ROSH_BUF_SZ, f);
|
|
}
|
|
fclose(f);
|
|
} else {
|
|
rosh_error(errno, "cat", argv[i]);
|
|
errno = 0;
|
|
}
|
|
}
|
|
} /* rosh_cat */
|
|
|
|
/* Change PWD (Present Working Directory)
|
|
* argc Argument count
|
|
* argv Argument values
|
|
* ipwdstr Initial PWD
|
|
*/
|
|
void rosh_cd(int argc, char *argv[], const char *ipwdstr)
|
|
{
|
|
int rv = 0;
|
|
#ifdef DO_DEBUG
|
|
char filestr[ROSH_PATH_SZ];
|
|
#endif /* DO_DEBUG */
|
|
ROSH_DEBUG("CMD: \n");
|
|
ROSH_DEBUG_ARGV_V(argc, argv);
|
|
errno = 0;
|
|
if (argc == 2)
|
|
rv = chdir(argv[1]);
|
|
else if (argc == 1)
|
|
rv = chdir(ipwdstr);
|
|
else
|
|
rosh_help(2, argv[0]);
|
|
if (rv != 0) {
|
|
if (argc == 2)
|
|
rosh_error(errno, "cd", argv[1]);
|
|
else
|
|
rosh_error(errno, "cd", ipwdstr);
|
|
errno = 0;
|
|
} else {
|
|
#ifdef DO_DEBUG
|
|
if (getcwd(filestr, ROSH_PATH_SZ))
|
|
ROSH_DEBUG(" %s\n", filestr);
|
|
#endif /* DO_DEBUG */
|
|
}
|
|
} /* rosh_cd */
|
|
|
|
/* Print the syslinux config file name
|
|
*/
|
|
void rosh_cfg(void)
|
|
{
|
|
printf("CFG: '%s'\n", syslinux_config_file());
|
|
} /* rosh_cfg */
|
|
|
|
/* Echo a string back to the screen
|
|
* cmdstr command string to process
|
|
*/
|
|
void rosh_echo(const char *cmdstr)
|
|
{
|
|
int bpos = 0;
|
|
ROSH_DEBUG("CMD: '%s'\n", cmdstr);
|
|
bpos = rosh_search_nonsp(cmdstr, rosh_search_sp(cmdstr, 0));
|
|
if (bpos > 1) {
|
|
ROSH_DEBUG(" bpos=%d\n", bpos);
|
|
printf("'%s'\n", cmdstr + bpos);
|
|
} else {
|
|
puts("");
|
|
}
|
|
} /* rosh_echo */
|
|
|
|
/* Process argc/argv to optarr
|
|
* argc Argument count
|
|
* argv Argument values
|
|
* optarr option array to populate
|
|
*/
|
|
void rosh_ls_arg_opt(int argc, char *argv[], int optarr[])
|
|
{
|
|
int rv = 0;
|
|
|
|
optarr[0] = -1;
|
|
optarr[1] = -1;
|
|
optarr[2] = -1;
|
|
rosh_getopt_reset();
|
|
while (rv != -1) {
|
|
ROSH_DEBUG2("getopt optind=%d rv=%d\n", optind, rv);
|
|
rv = getopt(argc, argv, rosh_ls_opt_str);
|
|
switch (rv) {
|
|
case 'l':
|
|
case 0:
|
|
optarr[0] = 1;
|
|
break;
|
|
case 'F':
|
|
case 1:
|
|
optarr[1] = 1;
|
|
break;
|
|
case 'i':
|
|
case 2:
|
|
optarr[2] = 1;
|
|
break;
|
|
case '?':
|
|
case -1:
|
|
default:
|
|
ROSH_DEBUG2("getopt optind=%d rv=%d\n", optind, rv);
|
|
break;
|
|
}
|
|
}
|
|
ROSH_DEBUG2(" end getopt optind=%d rv=%d\n", optind, rv);
|
|
ROSH_DEBUG2("\tIn rosh_ls_arg_opt() opt[0]=%d\topt[1]=%d\topt[2]=%d\n", optarr[0], optarr[1],
|
|
optarr[2]);
|
|
} /* rosh_ls_arg_opt */
|
|
|
|
/* Retrieve the size of a file argument
|
|
* filestr directory name of directory entry
|
|
* de directory entry
|
|
*/
|
|
int rosh_ls_de_size(const char *filestr, struct dirent *de)
|
|
{
|
|
int de_size;
|
|
char filestr2[ROSH_PATH_SZ];
|
|
int fd2, file2pos;
|
|
struct stat fdstat;
|
|
|
|
filestr2[0] = 0;
|
|
file2pos = -1;
|
|
if (filestr) {
|
|
file2pos = strlen(filestr);
|
|
memcpy(filestr2, filestr, file2pos);
|
|
filestr2[file2pos] = '/';
|
|
}
|
|
strcpy(filestr2 + file2pos + 1, de->d_name);
|
|
fd2 = open(filestr2, O_RDONLY);
|
|
fstat(fd2, &fdstat);
|
|
fd2 = close(fd2);
|
|
de_size = (int)fdstat.st_size;
|
|
return de_size;
|
|
} /* rosh_ls_de_size */
|
|
|
|
/* Retrieve the size and mode of a file
|
|
* filestr directory name of directory entry
|
|
* de directory entry
|
|
*/
|
|
int rosh_ls_de_size_mode(const char *filestr, struct dirent *de, mode_t * st_mode)
|
|
{
|
|
int de_size;
|
|
char filestr2[ROSH_PATH_SZ];
|
|
int file2pos;
|
|
struct stat fdstat;
|
|
int status;
|
|
|
|
filestr2[0] = 0;
|
|
file2pos = -1;
|
|
memset(&fdstat, 0, sizeof fdstat);
|
|
ROSH_DEBUG2("ls:dsm(%s, %s) ", filestr, de->d_name);
|
|
if (filestr) {
|
|
/* FIXME: prevent string overflow */
|
|
file2pos = strlen(filestr);
|
|
memcpy(filestr2, filestr, file2pos);
|
|
if (( filestr2[file2pos - 1] == SEP )) {
|
|
file2pos--;
|
|
} else {
|
|
filestr2[file2pos] = SEP;
|
|
}
|
|
}
|
|
strcpy(filestr2 + file2pos + 1, de->d_name);
|
|
errno = 0;
|
|
ROSH_DEBUG2("stat(%s) ", filestr2);
|
|
status = stat(filestr2, &fdstat);
|
|
(void)status;
|
|
ROSH_DEBUG2("\t--stat()=%d\terr=%d\n", status, errno);
|
|
if (errno) {
|
|
rosh_error(errno, "ls:szmd.stat", de->d_name);
|
|
errno = 0;
|
|
}
|
|
de_size = (int)fdstat.st_size;
|
|
*st_mode = fdstat.st_mode;
|
|
return de_size;
|
|
} /* rosh_ls_de_size_mode */
|
|
|
|
/* Returns the Inode number if fdstat contains it
|
|
* fdstat struct to extract inode from if not COM32, for now
|
|
*/
|
|
long rosh_ls_d_ino(struct stat *fdstat)
|
|
{
|
|
long de_ino;
|
|
#ifdef __COM32__
|
|
if (fdstat)
|
|
de_ino = -1;
|
|
else
|
|
de_ino = 0;
|
|
#else /* __COM32__ */
|
|
de_ino = fdstat->st_ino;
|
|
#endif /* __COM32__ */
|
|
return de_ino;
|
|
}
|
|
|
|
/* Convert a d_type to a single char in human readable format
|
|
* d_type d_type to convert
|
|
* returns human readable single character; a space if other
|
|
*/
|
|
char rosh_d_type2char_human(unsigned char d_type)
|
|
{
|
|
char ret;
|
|
switch (d_type) {
|
|
case DT_UNKNOWN:
|
|
ret = 'U';
|
|
break; /* Unknown */
|
|
case DT_FIFO:
|
|
ret = 'F';
|
|
break; /* FIFO */
|
|
case DT_CHR:
|
|
ret = 'C';
|
|
break; /* Char Dev */
|
|
case DT_DIR:
|
|
ret = 'D';
|
|
break; /* Directory */
|
|
case DT_BLK:
|
|
ret = 'B';
|
|
break; /* Block Dev */
|
|
case DT_REG:
|
|
ret = 'R';
|
|
break; /* Regular File */
|
|
case DT_LNK:
|
|
ret = 'L';
|
|
break; /* Link, Symbolic */
|
|
case DT_SOCK:
|
|
ret = 'S';
|
|
break; /* Socket */
|
|
case DT_WHT:
|
|
ret = 'W';
|
|
break; /* UnionFS Whiteout */
|
|
default:
|
|
ret = ' ';
|
|
}
|
|
return ret;
|
|
} /* rosh_d_type2char_human */
|
|
|
|
/* Convert a d_type to a single char by ls's prefix standards for -l
|
|
* d_type d_type to convert
|
|
* returns ls style single character; a space if other
|
|
*/
|
|
char rosh_d_type2char_lspre(unsigned char d_type)
|
|
{
|
|
char ret;
|
|
switch (d_type) {
|
|
case DT_FIFO:
|
|
ret = 'p';
|
|
break;
|
|
case DT_CHR:
|
|
ret = 'c';
|
|
break;
|
|
case DT_DIR:
|
|
ret = 'd';
|
|
break;
|
|
case DT_BLK:
|
|
ret = 'b';
|
|
break;
|
|
case DT_REG:
|
|
ret = '-';
|
|
break;
|
|
case DT_LNK:
|
|
ret = 'l';
|
|
break;
|
|
case DT_SOCK:
|
|
ret = 's';
|
|
break;
|
|
default:
|
|
ret = '?';
|
|
}
|
|
return ret;
|
|
} /* rosh_d_type2char_lspre */
|
|
|
|
/* Convert a d_type to a single char by ls's classify (-F) suffix standards
|
|
* d_type d_type to convert
|
|
* returns ls style single character; a space if other
|
|
*/
|
|
char rosh_d_type2char_lssuf(unsigned char d_type)
|
|
{
|
|
char ret;
|
|
switch (d_type) {
|
|
case DT_FIFO:
|
|
ret = '|';
|
|
break;
|
|
case DT_DIR:
|
|
ret = '/';
|
|
break;
|
|
case DT_LNK:
|
|
ret = '@';
|
|
break;
|
|
case DT_SOCK:
|
|
ret = '=';
|
|
break;
|
|
default:
|
|
ret = ' ';
|
|
}
|
|
return ret;
|
|
} /* rosh_d_type2char_lssuf */
|
|
|
|
/* Converts data in the "other" place of st_mode to a ls-style string
|
|
* st_mode Mode in other to analyze
|
|
* st_mode_str string to hold converted string
|
|
*/
|
|
void rosh_st_mode_am2str(mode_t st_mode, char *st_mode_str)
|
|
{
|
|
st_mode_str[0] = ((st_mode & S_IROTH) ? 'r' : '-');
|
|
st_mode_str[1] = ((st_mode & S_IWOTH) ? 'w' : '-');
|
|
st_mode_str[2] = ((st_mode & S_IXOTH) ? 'x' : '-');
|
|
}
|
|
|
|
/* Converts st_mode to an ls-style string
|
|
* st_mode mode to convert
|
|
* st_mode_str string to hold converted string
|
|
*/
|
|
void rosh_st_mode2str(mode_t st_mode, char *st_mode_str)
|
|
{
|
|
st_mode_str[0] = rosh_d_type2char_lspre(IFTODT(st_mode));
|
|
rosh_st_mode_am2str((st_mode & S_IRWXU) >> 6, st_mode_str + 1);
|
|
rosh_st_mode_am2str((st_mode & S_IRWXG) >> 3, st_mode_str + 4);
|
|
rosh_st_mode_am2str(st_mode & S_IRWXO, st_mode_str + 7);
|
|
st_mode_str[10] = 0;
|
|
} /* rosh_st_mode2str */
|
|
|
|
/* Output a single entry
|
|
* filestr directory name to list
|
|
* de directory entry
|
|
* optarr Array of options
|
|
*/
|
|
void rosh_ls_arg_dir_de(const char *filestr, struct dirent *de, const int *optarr)
|
|
{
|
|
int de_size;
|
|
mode_t st_mode;
|
|
char st_mode_str[11];
|
|
st_mode = 0;
|
|
ROSH_DEBUG2("+");
|
|
if (optarr[2] > -1)
|
|
printf("%10d ", (int)(de->d_ino));
|
|
if (optarr[0] > -1) {
|
|
de_size = rosh_ls_de_size_mode(filestr, de, &st_mode);
|
|
rosh_st_mode2str(st_mode, st_mode_str);
|
|
ROSH_DEBUG2("%04X ", st_mode);
|
|
printf("%s %10d ", st_mode_str, de_size);
|
|
}
|
|
ROSH_DEBUG("'");
|
|
printf("%s", de->d_name);
|
|
ROSH_DEBUG("'");
|
|
if (optarr[1] > -1)
|
|
printf("%c", rosh_d_type2char_lssuf(de->d_type));
|
|
printf("\n");
|
|
} /* rosh_ls_arg_dir_de */
|
|
|
|
/* Output listing of a regular directory
|
|
* filestr directory name to list
|
|
* d the open DIR
|
|
* optarr Array of options
|
|
NOTE:This is where I could use qsort
|
|
*/
|
|
void rosh_ls_arg_dir(const char *filestr, DIR * d, const int *optarr)
|
|
{
|
|
struct dirent *de;
|
|
int filepos;
|
|
|
|
filepos = 0;
|
|
errno = 0;
|
|
while ((de = readdir(d))) {
|
|
filepos++;
|
|
rosh_ls_arg_dir_de(filestr, de, optarr);
|
|
}
|
|
if (errno) {
|
|
rosh_error(errno, "ls:arg_dir", filestr);
|
|
errno = 0;
|
|
} else { if (filepos == 0)
|
|
ROSH_DEBUG("0 files found");
|
|
}
|
|
} /* rosh_ls_arg_dir */
|
|
|
|
/* Simple directory listing for one argument (file/directory) based on
|
|
* filestr and pwdstr
|
|
* ifilstr input filename/directory name to list
|
|
* pwdstr Present Working Directory string
|
|
* optarr Option Array
|
|
*/
|
|
void rosh_ls_arg(const char *filestr, const int *optarr)
|
|
{
|
|
struct stat fdstat;
|
|
int status;
|
|
// char filestr[ROSH_PATH_SZ];
|
|
// int filepos;
|
|
DIR *d;
|
|
struct dirent de;
|
|
|
|
/* Initialization; make filestr based on leading character of ifilstr
|
|
and pwdstr */
|
|
// rosh_qualify_filestr(filestr, ifilstr, pwdstr);
|
|
fdstat.st_mode = 0;
|
|
fdstat.st_size = 0;
|
|
ROSH_DEBUG("\topt[0]=%d\topt[1]=%d\topt[2]=%d\n", optarr[0], optarr[1],
|
|
optarr[2]);
|
|
|
|
/* Now, the real work */
|
|
errno = 0;
|
|
status = stat(filestr, &fdstat);
|
|
if (status == 0) {
|
|
if (S_ISDIR(fdstat.st_mode)) {
|
|
ROSH_DEBUG("PATH '%s' is a directory\n", filestr);
|
|
if ((d = opendir(filestr))) {
|
|
rosh_ls_arg_dir(filestr, d, optarr);
|
|
closedir(d);
|
|
} else {
|
|
rosh_error(errno, "ls", filestr);
|
|
errno = 0;
|
|
}
|
|
} else {
|
|
de.d_ino = rosh_ls_d_ino(&fdstat);
|
|
de.d_type = (IFTODT(fdstat.st_mode));
|
|
strcpy(de.d_name, filestr);
|
|
if (S_ISREG(fdstat.st_mode)) {
|
|
ROSH_DEBUG("PATH '%s' is a regular file\n", filestr);
|
|
} else {
|
|
ROSH_DEBUG("PATH '%s' is some other file\n", filestr);
|
|
}
|
|
rosh_ls_arg_dir_de(NULL, &de, optarr);
|
|
/* if (ifilstr[0] == SEP)
|
|
rosh_ls_arg_dir_de(NULL, &de, optarr);
|
|
else
|
|
rosh_ls_arg_dir_de(pwdstr, &de, optarr);*/
|
|
}
|
|
} else {
|
|
rosh_error(errno, "ls", filestr);
|
|
errno = 0;
|
|
}
|
|
return;
|
|
} /* rosh_ls_arg */
|
|
|
|
/* Parse options that may be present in the cmdstr
|
|
* filestr Possible option string to parse
|
|
* optstr Current options
|
|
* returns 1 if filestr does not begin with '-' else 0
|
|
*/
|
|
int rosh_ls_parse_opt(const char *filestr, char *optstr)
|
|
{
|
|
int ret;
|
|
if (filestr[0] == '-') {
|
|
ret = 0;
|
|
if (optstr)
|
|
strcat(optstr, filestr + 1);
|
|
} else {
|
|
ret = 1;
|
|
}
|
|
ROSH_DEBUG("ParseOpt: '%s'\n\topt: '%s'\n\tret: %d\n", filestr, optstr,
|
|
ret);
|
|
return ret;
|
|
} /* rosh_ls_parse_opt */
|
|
|
|
/* List Directory
|
|
* argc Argument count
|
|
* argv Argument values
|
|
*/
|
|
void rosh_ls(int argc, char *argv[])
|
|
{
|
|
int optarr[3];
|
|
int i;
|
|
|
|
rosh_ls_arg_opt(argc, argv, optarr);
|
|
ROSH_DEBUG2("In ls()\n");
|
|
ROSH_DEBUG2_ARGV_V(argc, argv);
|
|
#ifdef DO_DEBUG
|
|
optarr[0] = 2;
|
|
#endif /* DO_DEBUG */
|
|
ROSH_DEBUG2(" argc=%d; optind=%d\n", argc, optind);
|
|
if (optind >= argc)
|
|
rosh_ls_arg(".", optarr);
|
|
for (i = optind; i < argc; i++) {
|
|
rosh_ls_arg(argv[i], optarr);
|
|
}
|
|
} /* rosh_ls */
|
|
|
|
/* Simple directory listing; calls rosh_ls()
|
|
* argc Argument count
|
|
* argv Argument values
|
|
*/
|
|
void rosh_dir(int argc, char *argv[])
|
|
{
|
|
ROSH_DEBUG(" dir implemented as ls\n");
|
|
rosh_ls(argc, argv);
|
|
} /* rosh_dir */
|
|
|
|
/* Page through a buffer string
|
|
* buf Buffer to page through
|
|
*/
|
|
void rosh_more_buf(char *buf, int buflen, int rows, int cols, char *scrbuf)
|
|
{
|
|
char *bufp, *bufeol, *bufeol2; /* Pointer to current and next
|
|
end-of-line position in buffer */
|
|
int bufpos, bufcnt; /* current position, count characters */
|
|
int inc;
|
|
int i, numln; /* Index, Number of lines */
|
|
int elpl; /* Extra lines per line read */
|
|
|
|
(void)cols;
|
|
|
|
bufpos = 0;
|
|
bufp = buf + bufpos;
|
|
bufeol = bufp;
|
|
numln = rows - 1;
|
|
ROSH_DEBUG("--(%d)\n", buflen);
|
|
while (bufpos < buflen) {
|
|
for (i = 0; i < numln; i++) {
|
|
bufeol2 = strchr(bufeol, '\n');
|
|
if (bufeol2 == NULL) {
|
|
bufeol = buf + buflen;
|
|
i = numln;
|
|
} else {
|
|
elpl = ((bufeol2 - bufeol - 1) / cols);
|
|
if (elpl < 0)
|
|
elpl = 0;
|
|
i += elpl;
|
|
ROSH_DEBUG2(" %d/%d ", elpl, i+1);
|
|
/* If this will not push too much, use it */
|
|
/* but if it's the first line, use it */
|
|
/* //HERE: We should probably snip the line off */
|
|
if ((i < numln) || (i == elpl))
|
|
bufeol = bufeol2 + 1;
|
|
}
|
|
}
|
|
ROSH_DEBUG2("\n");
|
|
bufcnt = bufeol - bufp;
|
|
printf("--(%d/%d @%d)\n", bufcnt, buflen, bufpos);
|
|
memcpy(scrbuf, bufp, bufcnt);
|
|
scrbuf[bufcnt] = 0;
|
|
printf("%s", scrbuf);
|
|
bufp = bufeol;
|
|
bufpos += bufcnt;
|
|
if (bufpos == buflen)
|
|
break;
|
|
inc = rosh_getkey();
|
|
numln = 1;
|
|
switch (inc) {
|
|
case KEY_CTRL('c'):
|
|
case 'q':
|
|
case 'Q':
|
|
bufpos = buflen;
|
|
break;
|
|
case ' ':
|
|
numln = rows - 1;
|
|
}
|
|
}
|
|
} /* rosh_more_buf */
|
|
|
|
/* Page through a single file using the open file stream
|
|
* fd File Descriptor
|
|
*/
|
|
void rosh_more_fd(int fd, int rows, int cols, char *scrbuf)
|
|
{
|
|
struct stat fdstat;
|
|
char *buf;
|
|
int bufpos;
|
|
int numrd;
|
|
FILE *f;
|
|
|
|
fstat(fd, &fdstat);
|
|
if (S_ISREG(fdstat.st_mode)) {
|
|
buf = malloc((int)fdstat.st_size);
|
|
if (buf != NULL) {
|
|
f = fdopen(fd, "r");
|
|
bufpos = 0;
|
|
numrd = fread(buf, 1, (int)fdstat.st_size, f);
|
|
while (numrd > 0) {
|
|
bufpos += numrd;
|
|
numrd = fread(buf + bufpos, 1,
|
|
((int)fdstat.st_size - bufpos), f);
|
|
}
|
|
fclose(f);
|
|
rosh_more_buf(buf, bufpos, rows, cols, scrbuf);
|
|
}
|
|
} else {
|
|
}
|
|
|
|
} /* rosh_more_fd */
|
|
|
|
/* Page through a file like the more command
|
|
* argc Argument Count
|
|
* argv Argument Values
|
|
*/
|
|
void rosh_more(int argc, char *argv[])
|
|
{
|
|
int fd, i;
|
|
/* char filestr[ROSH_PATH_SZ];
|
|
int cmdpos;*/
|
|
int rows, cols;
|
|
char *scrbuf;
|
|
int ret;
|
|
|
|
ROSH_DEBUG_ARGV_V(argc, argv);
|
|
ret = getscreensize(1, &rows, &cols);
|
|
if (ret) {
|
|
ROSH_DEBUG("getscreensize() fail(%d); fall back\n", ret);
|
|
ROSH_DEBUG("\tROWS='%d'\tCOLS='%d'\n", rows, cols);
|
|
/* If either fail, go under normal size, just in case */
|
|
if (!rows)
|
|
rows = 20;
|
|
if (!cols)
|
|
cols = 75;
|
|
}
|
|
ROSH_DEBUG("\tUSE ROWS='%d'\tCOLS='%d'\n", rows, cols);
|
|
/* 32 bit align beginning of row and over allocate */
|
|
scrbuf = malloc(rows * ((cols+3)&(INT_MAX - 3)));
|
|
if (!scrbuf)
|
|
return;
|
|
|
|
if (argc) {
|
|
/* There is no need to mess up the console if we don't have a
|
|
file */
|
|
rosh_console_raw();
|
|
for (i = 0; i < argc; i++) {
|
|
printf("--File = '%s'\n", argv[i]);
|
|
errno = 0;
|
|
fd = open(argv[i], O_RDONLY);
|
|
if (fd != -1) {
|
|
rosh_more_fd(fd, rows, cols, scrbuf);
|
|
close(fd);
|
|
} else {
|
|
rosh_error(errno, "more", argv[i]);
|
|
errno = 0;
|
|
}
|
|
}
|
|
rosh_console_std();
|
|
}
|
|
free(scrbuf);
|
|
} /* rosh_more */
|
|
|
|
/* Page a file with rewind
|
|
* argc Argument Count
|
|
* argv Argument Values
|
|
*/
|
|
void rosh_less(int argc, char *argv[])
|
|
{
|
|
printf(" less implemented as more (for now)\n");
|
|
rosh_more(argc, argv);
|
|
} /* rosh_less */
|
|
|
|
/* Show PWD
|
|
*/
|
|
void rosh_pwd(void)
|
|
{
|
|
char pwdstr[ROSH_PATH_SZ];
|
|
errno = 0;
|
|
if (getcwd(pwdstr, ROSH_PATH_SZ)) {
|
|
printf("%s\n", pwdstr);
|
|
} else {
|
|
rosh_error(errno, "pwd", "");
|
|
errno = 0;
|
|
}
|
|
} /* rosh_pwd */
|
|
|
|
/* Reboot; use warm reboot if one of certain options set
|
|
* argc Argument count
|
|
* argv Argument values
|
|
*/
|
|
void rosh_reboot(int argc, char *argv[])
|
|
{
|
|
int rtype = 0;
|
|
if (argc) {
|
|
/* For now, just use the first */
|
|
switch (argv[0][0]) {
|
|
case '1':
|
|
case 's':
|
|
case 'w':
|
|
rtype = 1;
|
|
break;
|
|
case '-':
|
|
switch (argv[0][1]) {
|
|
case '1':
|
|
case 's':
|
|
case 'w':
|
|
rtype = 1;
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
syslinux_reboot(rtype);
|
|
} /* rosh_reboot */
|
|
|
|
/* Run a boot string, calling syslinux_run_command
|
|
* argc Argument count
|
|
* argv Argument values
|
|
*/
|
|
void rosh_run(int argc, char *argv[])
|
|
{
|
|
char cmdstr[ROSH_CMD_SZ];
|
|
int len;
|
|
|
|
len = rosh_argcat(cmdstr, ROSH_CMD_SZ, argc, argv, 0);
|
|
if (len) {
|
|
printf("--run: '%s'\n", cmdstr);
|
|
syslinux_run_command(cmdstr);
|
|
} else {
|
|
printf(APP_NAME ":run: No arguments\n");
|
|
}
|
|
} /* rosh_run */
|
|
|
|
/* Process an argc/argv pair and call handling function
|
|
* argc Argument count
|
|
* argv Argument values
|
|
* ipwdstr Initial Present Working Directory string
|
|
* returns Whether to exit prompt
|
|
*/
|
|
char rosh_command(int argc, char *argv[], const char *ipwdstr)
|
|
{
|
|
char do_exit = false;
|
|
int tlen;
|
|
tlen = strlen(argv[0]);
|
|
ROSH_DEBUG_ARGV_V(argc, argv);
|
|
switch (argv[0][0]) {
|
|
case 'e':
|
|
case 'E':
|
|
case 'q':
|
|
case 'Q':
|
|
switch (argv[0][1]) {
|
|
case 0:
|
|
case 'x':
|
|
case 'X':
|
|
case 'u':
|
|
case 'U':
|
|
if ((strncasecmp("exit", argv[0], tlen) == 0) ||
|
|
(strncasecmp("quit", argv[0], tlen) == 0))
|
|
do_exit = true;
|
|
else
|
|
rosh_help(1, argv[0]);
|
|
break;
|
|
case 'c':
|
|
case 'C':
|
|
if (strncasecmp("echo", argv[0], tlen) == 0)
|
|
rosh_pr_argv(argc - 1, &argv[1]);
|
|
else
|
|
rosh_help(1, argv[0]);
|
|
break;
|
|
default:
|
|
rosh_help(1, argv[0]);
|
|
}
|
|
break;
|
|
case 'c':
|
|
case 'C': /* run 'cd' 'cat' 'cfg' */
|
|
switch (argv[0][1]) {
|
|
case 'a':
|
|
case 'A':
|
|
if (strncasecmp("cat", argv[0], tlen) == 0)
|
|
rosh_cat(argc - 1, &argv[1]);
|
|
else
|
|
rosh_help(1, argv[0]);
|
|
break;
|
|
case 'd':
|
|
case 'D':
|
|
if (strncasecmp("cd", argv[0], tlen) == 0)
|
|
rosh_cd(argc, argv, ipwdstr);
|
|
else
|
|
rosh_help(1, argv[0]);
|
|
break;
|
|
case 'f':
|
|
case 'F':
|
|
if (strncasecmp("cfg", argv[0], tlen) == 0)
|
|
rosh_cfg();
|
|
else
|
|
rosh_help(1, argv[0]);
|
|
break;
|
|
default:
|
|
rosh_help(1, argv[0]);
|
|
}
|
|
break;
|
|
case 'd':
|
|
case 'D': /* run 'dir' */
|
|
if (strncasecmp("dir", argv[0], tlen) == 0)
|
|
rosh_dir(argc - 1, &argv[1]);
|
|
else
|
|
rosh_help(1, argv[0]);
|
|
break;
|
|
case 'h':
|
|
case 'H':
|
|
case '?':
|
|
if ((strncasecmp("help", argv[0], tlen) == 0) || (tlen == 1))
|
|
rosh_help(2, argv[1]);
|
|
else
|
|
rosh_help(1, NULL);
|
|
break;
|
|
case 'l':
|
|
case 'L': /* run 'ls' 'less' */
|
|
switch (argv[0][1]) {
|
|
case 0:
|
|
case 's':
|
|
case 'S':
|
|
if (strncasecmp("ls", argv[0], tlen) == 0)
|
|
rosh_ls(argc, argv);
|
|
else
|
|
rosh_help(1, argv[0]);
|
|
break;
|
|
case 'e':
|
|
case 'E':
|
|
if (strncasecmp("less", argv[0], tlen) == 0)
|
|
rosh_less(argc - 1, &argv[1]);
|
|
else
|
|
rosh_help(1, argv[0]);
|
|
break;
|
|
default:
|
|
rosh_help(1, argv[0]);
|
|
}
|
|
break;
|
|
case 'm':
|
|
case 'M':
|
|
switch (argv[0][1]) {
|
|
case 'a':
|
|
case 'A':
|
|
if (strncasecmp("man", argv[0], tlen) == 0)
|
|
rosh_help(2, argv[1]);
|
|
else
|
|
rosh_help(1, argv[0]);
|
|
break;
|
|
case 'o':
|
|
case 'O':
|
|
if (strncasecmp("more", argv[0], tlen) == 0)
|
|
rosh_more(argc - 1, &argv[1]);
|
|
else
|
|
rosh_help(1, argv[0]);
|
|
break;
|
|
default:
|
|
rosh_help(1, argv[0]);
|
|
}
|
|
break;
|
|
case 'p':
|
|
case 'P': /* run 'pwd' */
|
|
if (strncasecmp("pwd", argv[0], tlen) == 0)
|
|
rosh_pwd();
|
|
else
|
|
rosh_help(1, argv[0]);
|
|
break;
|
|
case 'r':
|
|
case 'R': /* run 'run' */
|
|
switch (argv[0][1]) {
|
|
case 0:
|
|
case 'e':
|
|
case 'E':
|
|
if (strncasecmp("reboot", argv[0], tlen) == 0)
|
|
rosh_reboot(argc - 1, &argv[1]);
|
|
else
|
|
rosh_help(1, argv[0]);
|
|
break;
|
|
case 'u':
|
|
case 'U':
|
|
if (strncasecmp("run", argv[0], tlen) == 0)
|
|
rosh_run(argc - 1, &argv[1]);
|
|
else
|
|
rosh_help(1, argv[0]);
|
|
break;
|
|
default:
|
|
rosh_help(1, argv[0]);
|
|
}
|
|
break;
|
|
case 'v':
|
|
case 'V':
|
|
if (strncasecmp("version", argv[0], tlen) == 0)
|
|
rosh_version(1);
|
|
else
|
|
rosh_help(1, argv[0]);
|
|
break;
|
|
case 0:
|
|
case '\n':
|
|
break;
|
|
default:
|
|
rosh_help(1, argv[0]);
|
|
} /* switch(argv[0][0]) */
|
|
return do_exit;
|
|
} /* rosh_command */
|
|
|
|
/* Process the prompt for commands as read from stdin and call rosh_command
|
|
* to process command line string
|
|
* icmdstr Initial command line string
|
|
* returns Exit status
|
|
*/
|
|
int rosh_prompt(int iargc, char *iargv[])
|
|
{
|
|
int rv;
|
|
char cmdstr[ROSH_CMD_SZ];
|
|
char ipwdstr[ROSH_PATH_SZ];
|
|
char do_exit;
|
|
char **argv;
|
|
int argc;
|
|
|
|
rv = 0;
|
|
do_exit = false;
|
|
if (!getcwd(ipwdstr, ROSH_PATH_SZ))
|
|
strcpy(ipwdstr, "./");
|
|
if (iargc > 1)
|
|
do_exit = rosh_command(iargc - 1, &iargv[1], ipwdstr);
|
|
while (!(do_exit)) {
|
|
/* Extra preceeding newline */
|
|
printf("\nrosh: ");
|
|
/* Read a line from console */
|
|
if (fgets(cmdstr, ROSH_CMD_SZ, stdin)) {
|
|
argc = rosh_str2argv(&argv, cmdstr);
|
|
do_exit = rosh_command(argc, argv, ipwdstr);
|
|
rosh_free_argv(&argv);
|
|
} else {
|
|
do_exit = false;
|
|
}
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
int rv;
|
|
|
|
/* Initialization */
|
|
rv = 0;
|
|
rosh_console_std();
|
|
if (argc == 1) {
|
|
rosh_version(0);
|
|
print_beta();
|
|
} else {
|
|
#ifdef DO_DEBUG
|
|
char cmdstr[ROSH_CMD_SZ];
|
|
rosh_argcat(cmdstr, ROSH_CMD_SZ, argc, argv, 1);
|
|
ROSH_DEBUG("arg='%s'\n", cmdstr);
|
|
#endif
|
|
}
|
|
rv = rosh_prompt(argc, argv);
|
|
printf("--Exiting '" APP_NAME "'\n");
|
|
return rv;
|
|
}
|