2013-01-25 19:07:27 -05:00
|
|
|
#define __STDC_CONSTANT_MACROS
|
|
|
|
#define __STDC_LIMIT_MACROS
|
|
|
|
|
2012-12-15 19:54:49 -05:00
|
|
|
#include <sys/display.h>
|
2012-05-23 11:01:53 -04:00
|
|
|
#include <sys/keycodes.h>
|
|
|
|
#include <sys/termmode.h>
|
|
|
|
#include <sys/wait.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <string.h>
|
2012-12-15 19:54:49 -05:00
|
|
|
#include <stdarg.h>
|
2012-05-23 11:01:53 -04:00
|
|
|
#include <stdint.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <error.h>
|
|
|
|
#include <errno.h>
|
|
|
|
#include <termios.h>
|
|
|
|
#include <readparamstring.h>
|
|
|
|
|
2012-12-15 19:54:49 -05:00
|
|
|
// TODO: Provide this using asprintf or somewhere sane in user-space.
|
|
|
|
char* Combine(size_t NumParameters, ...)
|
2012-05-23 11:01:53 -04:00
|
|
|
{
|
2012-12-15 19:54:49 -05:00
|
|
|
va_list param_pt;
|
|
|
|
|
|
|
|
va_start(param_pt, NumParameters);
|
|
|
|
|
|
|
|
// First calculate the string length.
|
|
|
|
size_t ResultLength = 0;
|
|
|
|
const char* TMP = 0;
|
|
|
|
|
|
|
|
for ( size_t I = 0; I < NumParameters; I++ )
|
|
|
|
{
|
|
|
|
TMP = va_arg(param_pt, const char*);
|
|
|
|
|
|
|
|
if ( TMP != NULL ) { ResultLength += strlen(TMP); }
|
|
|
|
}
|
|
|
|
|
|
|
|
// Allocate a string with the desired length.
|
|
|
|
char* Result = new char[ResultLength+1];
|
|
|
|
if ( !Result ) { return NULL; }
|
|
|
|
|
|
|
|
Result[0] = 0;
|
|
|
|
|
|
|
|
va_end(param_pt);
|
|
|
|
va_start(param_pt, NumParameters);
|
|
|
|
|
|
|
|
size_t ResultOffset = 0;
|
|
|
|
|
|
|
|
for ( size_t I = 0; I < NumParameters; I++ )
|
|
|
|
{
|
|
|
|
TMP = va_arg(param_pt, const char*);
|
|
|
|
|
|
|
|
if ( TMP )
|
|
|
|
{
|
|
|
|
size_t TMPLength = strlen(TMP);
|
|
|
|
strcpy(Result + ResultOffset, TMP);
|
|
|
|
ResultOffset += TMPLength;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return Result;
|
2012-05-23 11:01:53 -04:00
|
|
|
}
|
|
|
|
|
2012-12-15 19:54:49 -05:00
|
|
|
char* ActualGetDriverName(uint64_t driver_index)
|
2012-05-23 11:01:53 -04:00
|
|
|
{
|
2012-12-15 19:54:49 -05:00
|
|
|
struct dispmsg_get_driver_name msg;
|
|
|
|
msg.msgid = DISPMSG_GET_DRIVER_NAME;
|
|
|
|
msg.device = 0;
|
|
|
|
msg.driver_index = driver_index;
|
|
|
|
size_t guess = 32;
|
|
|
|
while ( true )
|
|
|
|
{
|
|
|
|
char* ret = new char[guess];
|
|
|
|
if ( !ret )
|
|
|
|
return NULL;
|
|
|
|
msg.name.byte_size = guess;
|
|
|
|
msg.name.str = ret;
|
|
|
|
if ( dispmsg_issue(&msg, sizeof(msg)) == 0 )
|
|
|
|
return ret;
|
|
|
|
delete[] ret;
|
|
|
|
if ( errno == ERANGE && guess < msg.name.byte_size )
|
|
|
|
{
|
|
|
|
guess = msg.name.byte_size;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
2012-05-23 11:01:53 -04:00
|
|
|
}
|
|
|
|
|
2012-12-15 19:54:49 -05:00
|
|
|
static char* StringOfCrtcMode(struct dispmsg_crtc_mode mode)
|
2012-05-23 11:01:53 -04:00
|
|
|
{
|
2012-12-15 19:54:49 -05:00
|
|
|
char bppstr[sizeof(mode.fb_format) * 3];
|
|
|
|
char xresstr[sizeof(mode.view_xres) * 3];
|
|
|
|
char yresstr[sizeof(mode.view_yres) * 3];
|
|
|
|
char magicstr[sizeof(mode.magic) * 3];
|
|
|
|
snprintf(bppstr, sizeof(bppstr), "%ju", (uintmax_t) mode.fb_format);
|
|
|
|
snprintf(xresstr, sizeof(xresstr), "%ju", (uintmax_t) mode.view_xres);
|
|
|
|
snprintf(yresstr, sizeof(yresstr), "%ju", (uintmax_t) mode.view_yres);
|
|
|
|
snprintf(magicstr, sizeof(magicstr), "%ju", (uintmax_t) mode.magic);
|
|
|
|
char* drivername = ActualGetDriverName(mode.driver_index);
|
|
|
|
if ( !drivername )
|
|
|
|
return NULL;
|
|
|
|
char* ret = Combine(10,
|
|
|
|
"driver=", drivername, ","
|
|
|
|
"bpp=", bppstr, ","
|
|
|
|
"width=", xresstr, ","
|
|
|
|
"height=", yresstr, ","
|
|
|
|
"modeid=", magicstr);
|
|
|
|
delete[] drivername;
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool SetCurrentMode(struct dispmsg_crtc_mode mode)
|
|
|
|
{
|
|
|
|
struct dispmsg_set_crtc_mode msg;
|
|
|
|
msg.msgid = DISPMSG_SET_CRTC_MODE;
|
|
|
|
msg.device = 0;
|
|
|
|
msg.connector = 0;
|
|
|
|
msg.mode = mode;
|
|
|
|
return dispmsg_issue(&msg, sizeof(msg)) == 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct dispmsg_crtc_mode GetCurrentMode()
|
|
|
|
{
|
|
|
|
struct dispmsg_set_crtc_mode msg;
|
|
|
|
msg.msgid = DISPMSG_GET_CRTC_MODE;
|
|
|
|
msg.device = 0;
|
|
|
|
msg.connector = 0;
|
|
|
|
dispmsg_issue(&msg, sizeof(msg));
|
|
|
|
return msg.mode;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct dispmsg_crtc_mode* GetAvailableModes(size_t* nummodesptr)
|
|
|
|
{
|
|
|
|
struct dispmsg_get_crtc_modes msg;
|
|
|
|
msg.msgid = DISPMSG_GET_CRTC_MODES;
|
|
|
|
msg.device = 0;
|
|
|
|
msg.connector = 0;
|
|
|
|
size_t guess = 0;
|
2012-05-23 11:01:53 -04:00
|
|
|
while ( true )
|
|
|
|
{
|
2012-12-15 19:54:49 -05:00
|
|
|
struct dispmsg_crtc_mode* ret = new struct dispmsg_crtc_mode[guess];
|
|
|
|
if ( !ret )
|
|
|
|
return NULL;
|
|
|
|
msg.modes_length = guess;
|
|
|
|
msg.modes = ret;
|
|
|
|
if ( dispmsg_issue(&msg, sizeof(msg)) == 0 )
|
|
|
|
{
|
|
|
|
*nummodesptr = guess;
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
delete[] ret;
|
|
|
|
if ( errno == ERANGE && guess < msg.modes_length )
|
2012-05-23 11:01:53 -04:00
|
|
|
{
|
2012-12-15 19:54:49 -05:00
|
|
|
guess = msg.modes_length;
|
|
|
|
continue;
|
2012-05-23 11:01:53 -04:00
|
|
|
}
|
2012-12-15 19:54:49 -05:00
|
|
|
return NULL;
|
2012-05-23 11:01:53 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-12-15 19:54:49 -05:00
|
|
|
void DrawMenu(size_t selection, struct dispmsg_crtc_mode* modes, size_t nummodes)
|
2012-05-23 11:01:53 -04:00
|
|
|
{
|
|
|
|
printf("\e[m\e[H\e[2K"); // Move to (0,0), clear screen.
|
|
|
|
printf("Please select one of these video modes or press ESC to abort.\n");
|
|
|
|
size_t off = 1; // Above line
|
|
|
|
struct winsize ws;
|
|
|
|
if ( tcgetwinsize(1, &ws) != 0 )
|
|
|
|
ws.ws_row = 25;
|
|
|
|
size_t amount = ws.ws_row-off;
|
|
|
|
size_t from = (selection / amount) * amount;
|
|
|
|
size_t howmanyavailable = nummodes - from;
|
|
|
|
size_t howmany = howmanyavailable < amount ? howmanyavailable : amount;
|
|
|
|
size_t to = from + howmany - 1;
|
|
|
|
for ( size_t i = from; i <= to; i++ )
|
|
|
|
{
|
|
|
|
size_t screenline = 1 + off + i - from;
|
|
|
|
const char* color = i == selection ? "\e[31m" : "\e[m";
|
2012-12-15 19:54:49 -05:00
|
|
|
char* desc = StringOfCrtcMode(modes[i]);
|
|
|
|
const char* print_desc = desc ? desc : "<unknown - error>";
|
|
|
|
printf("\e[%zuH%s\e[2K%zu\t%s", screenline, color, i, print_desc);
|
|
|
|
delete[] desc;
|
2012-05-23 11:01:53 -04:00
|
|
|
}
|
|
|
|
printf("\e[m\e[J");
|
|
|
|
fflush(stdout);
|
|
|
|
}
|
|
|
|
|
|
|
|
int GetKey(int fd)
|
|
|
|
{
|
|
|
|
unsigned int oldtermmode;
|
|
|
|
gettermmode(fd, &oldtermmode);
|
|
|
|
// Read the keyboard input from the user.
|
|
|
|
const unsigned termmode = TERMMODE_KBKEY
|
|
|
|
| TERMMODE_UNICODE
|
|
|
|
| TERMMODE_SIGNAL;
|
|
|
|
if ( settermmode(fd, termmode) ) { error(1, errno, "settermmode"); }
|
|
|
|
uint32_t codepoint;
|
|
|
|
ssize_t numbytes;
|
|
|
|
while ( 0 < (numbytes = read(0, &codepoint, sizeof(codepoint))) )
|
|
|
|
{
|
|
|
|
int kbkey = KBKEY_DECODE(codepoint);
|
|
|
|
if ( !kbkey ) { continue; }
|
|
|
|
return kbkey;
|
|
|
|
}
|
|
|
|
settermmode(fd, oldtermmode);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int GetKey(FILE* fp) { return GetKey(fileno(fp)); }
|
|
|
|
|
|
|
|
struct Filter
|
|
|
|
{
|
|
|
|
bool includeall;
|
|
|
|
bool includesupported;
|
|
|
|
bool includeunsupported;
|
|
|
|
bool includetext;
|
|
|
|
bool includegraphics;
|
|
|
|
size_t minbpp;
|
|
|
|
size_t maxbpp;
|
|
|
|
size_t minxres;
|
|
|
|
size_t maxxres;
|
|
|
|
size_t minyres;
|
|
|
|
size_t maxyres;
|
|
|
|
size_t minxchars;
|
|
|
|
size_t maxxchars;
|
|
|
|
size_t minychars;
|
|
|
|
size_t maxychars;
|
|
|
|
};
|
|
|
|
|
|
|
|
size_t ParseSizeT(const char* str, size_t def = 0)
|
|
|
|
{
|
|
|
|
if ( !str || !*str )
|
|
|
|
return def;
|
|
|
|
char* endptr;
|
|
|
|
size_t ret = strtoul(str, &endptr, 10);
|
|
|
|
if ( *endptr )
|
|
|
|
return def;
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool ParseBool(const char* str, bool def = false)
|
|
|
|
{
|
|
|
|
if ( !str || !*str )
|
|
|
|
return def;
|
|
|
|
bool isfalse = !strcmp(str, "0") || !strcmp(str, "false");
|
|
|
|
return !isfalse;
|
|
|
|
}
|
|
|
|
|
2012-12-15 19:54:49 -05:00
|
|
|
bool PassesFilter(struct dispmsg_crtc_mode mode, Filter* filt)
|
2012-05-23 11:01:53 -04:00
|
|
|
{
|
|
|
|
if ( filt->includeall ) { return true; }
|
2012-12-15 19:54:49 -05:00
|
|
|
size_t width = mode.view_xres;
|
|
|
|
size_t height = mode.view_yres;
|
|
|
|
size_t bpp = mode.fb_format;
|
|
|
|
bool unsupported = (mode.control & 1) == 0;
|
2012-05-23 11:01:53 -04:00
|
|
|
bool supported = !unsupported;
|
2012-12-15 19:54:49 -05:00
|
|
|
bool text = false; // TODO: How does <sys/display.h> tell this?
|
2012-05-23 11:01:53 -04:00
|
|
|
bool graphics = !text;
|
|
|
|
if ( unsupported && !filt->includeunsupported )
|
|
|
|
return false;
|
|
|
|
if ( supported && !filt->includesupported )
|
|
|
|
return false;
|
|
|
|
if ( text && !filt->includetext )
|
|
|
|
return false;
|
|
|
|
if ( graphics && !filt->includegraphics )
|
|
|
|
return false;
|
|
|
|
if ( graphics && (bpp < filt->minbpp || filt->maxbpp < bpp) )
|
|
|
|
return false;
|
|
|
|
if ( graphics && (width < filt->minxres || filt->maxxres < width) )
|
|
|
|
return false;
|
|
|
|
if ( graphics && (height < filt->minyres || filt->maxyres < height) )
|
|
|
|
return false;
|
|
|
|
// TODO: Support filtering text modes according to columns/rows.
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2012-12-15 19:54:49 -05:00
|
|
|
void FilterModes(struct dispmsg_crtc_mode* modes, size_t* nummodesptr, Filter* filt)
|
2012-05-23 11:01:53 -04:00
|
|
|
{
|
|
|
|
size_t innum = *nummodesptr;
|
|
|
|
size_t outnum = 0;
|
|
|
|
for ( size_t i = 0; i < innum; i++ )
|
|
|
|
{
|
|
|
|
if ( PassesFilter(modes[i], filt) )
|
|
|
|
modes[outnum++] = modes[i];
|
|
|
|
}
|
|
|
|
*nummodesptr = outnum;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Usage(FILE* fp, const char* argv0)
|
|
|
|
{
|
|
|
|
fprintf(fp, "Usage: %s [OPTION ...] [PROGRAM-TO-RUN [ARG ...]]\n", argv0);
|
|
|
|
fprintf(fp, "Changes the video mode and optionally runs a program\n");
|
|
|
|
fprintf(fp, "\n");
|
|
|
|
fprintf(fp, "Options supported by %s:\n", argv0);
|
|
|
|
fprintf(fp, " --help, --usage Display this help and exit\n");
|
|
|
|
fprintf(fp, " --version Output version information and exit\n");
|
|
|
|
fprintf(fp, "\n");
|
|
|
|
fprintf(fp, "Options for filtering modes:\n");
|
|
|
|
fprintf(fp, " --show-all BOOL\n");
|
|
|
|
fprintf(fp, " --show-supported BOOL, --show-unsupported BOOL\n");
|
|
|
|
fprintf(fp, " --show-text BOOL\n");
|
|
|
|
fprintf(fp, " --show-graphics BOOL\n");
|
|
|
|
fprintf(fp, " --bpp BPP, --min-bpp BPP, --max-bpp BPP\n");
|
|
|
|
fprintf(fp, " --width NUM, --min-width NUM, --max-width NUM\n");
|
|
|
|
fprintf(fp, " --height NUM, --min-height NUM, --max-height NUM\n");
|
|
|
|
fprintf(fp, "\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
void Help(FILE* fp, const char* argv0)
|
|
|
|
{
|
|
|
|
Usage(fp, argv0);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Version(FILE* fp, const char* argv0)
|
|
|
|
{
|
|
|
|
Usage(fp, argv0);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool SizeTParam(const char* name, const char* option,
|
|
|
|
const char* param, size_t* minvar, size_t* maxvar)
|
|
|
|
{
|
|
|
|
char opt[64]; stpcpy(stpcpy(opt, "--"), name);
|
|
|
|
char minopt[64]; stpcpy(stpcpy(minopt, "--min-"), name);
|
|
|
|
char maxopt[64]; stpcpy(stpcpy(maxopt, "--max-"), name);
|
|
|
|
if ( !strcmp(option, opt) )
|
|
|
|
*minvar = *maxvar = ParseSizeT(param);
|
|
|
|
else if ( !strcmp(option, minopt) )
|
|
|
|
*minvar = ParseBool(param);
|
|
|
|
else if ( !strcmp(option, maxopt) )
|
|
|
|
*maxvar = ParseBool(param);
|
|
|
|
else
|
|
|
|
return false;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool BoolParam(const char* name, const char* option,
|
|
|
|
const char* param, bool* var)
|
|
|
|
{
|
|
|
|
char opt[64]; stpcpy(stpcpy(opt, "--"), name);
|
|
|
|
if ( !strcmp(option, opt) )
|
|
|
|
*var = ParseBool(param);
|
|
|
|
else
|
|
|
|
return false;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
int main(int argc, char* argv[])
|
|
|
|
{
|
|
|
|
const char* argv0 = argv[0];
|
|
|
|
|
|
|
|
Filter filt;
|
|
|
|
filt.includeall = false;
|
|
|
|
filt.includesupported = true;
|
|
|
|
filt.includeunsupported = false;
|
|
|
|
filt.includetext = true;
|
|
|
|
filt.includegraphics = true;
|
|
|
|
// TODO: HACK: The kernel log printing requires either text mode or 32-bit
|
|
|
|
// graphics. For now, just filter away anything but 32-bit graphics.
|
|
|
|
filt.minbpp = 32;
|
|
|
|
filt.maxbpp = 32;
|
|
|
|
filt.minxres = 0;
|
|
|
|
filt.maxxres = SIZE_MAX;
|
|
|
|
filt.minyres = 0;
|
|
|
|
filt.maxyres = SIZE_MAX;
|
|
|
|
filt.minxchars = 0;
|
|
|
|
filt.maxxchars = SIZE_MAX;
|
|
|
|
filt.minychars = 0;
|
|
|
|
filt.maxychars = SIZE_MAX;
|
|
|
|
|
|
|
|
for ( int i = 1; i < argc; i++ )
|
|
|
|
{
|
|
|
|
const char* arg = argv[i];
|
|
|
|
if ( arg[0] != '-' ) { break; }
|
|
|
|
if ( !strcmp(arg, "--") ) { break; }
|
|
|
|
if ( !strcmp(arg, "--help") ) { Help(stdout, argv0); exit(0); }
|
|
|
|
if ( !strcmp(arg, "--usage") ) { Usage(stdout, argv0); exit(0); }
|
|
|
|
if ( !strcmp(arg, "--version") ) { Version(stdout, argv0); exit(0); }
|
|
|
|
if ( i == argc-1 )
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: missing parameter to %s\n", argv0, arg);
|
|
|
|
Usage(stderr, argv0);
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
const char* p = argv[++i]; argv[i] = NULL;
|
|
|
|
bool handled =
|
|
|
|
BoolParam("show-all", arg, p, &filt.includeall) ||
|
|
|
|
BoolParam("show-supported", arg, p, &filt.includesupported) ||
|
|
|
|
BoolParam("show-unsupported", arg, p, &filt.includeunsupported) ||
|
|
|
|
BoolParam("show-text", arg, p, &filt.includetext) ||
|
|
|
|
BoolParam("show-graphics", arg, p, &filt.includegraphics) ||
|
|
|
|
SizeTParam("bpp", arg, p, &filt.minbpp, &filt.maxbpp) ||
|
|
|
|
SizeTParam("width", arg, p, &filt.minxres, &filt.maxxres) ||
|
|
|
|
SizeTParam("height", arg, p, &filt.minyres, &filt.maxyres) ||
|
|
|
|
false;
|
|
|
|
if ( !handled )
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: no such option: %s\n", argv0, arg);
|
|
|
|
Usage(stderr, argv0);
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-12-15 19:54:49 -05:00
|
|
|
struct dispmsg_crtc_mode prevmode = GetCurrentMode();
|
2012-05-23 11:01:53 -04:00
|
|
|
#if 1
|
|
|
|
size_t nummodes = 0;
|
2012-12-15 19:54:49 -05:00
|
|
|
struct dispmsg_crtc_mode* modes = GetAvailableModes(&nummodes);
|
2012-05-23 11:01:53 -04:00
|
|
|
if ( !modes ) { perror("Unable to detect available video modes"); exit(1); }
|
|
|
|
|
|
|
|
if ( !nummodes )
|
|
|
|
{
|
|
|
|
fprintf(stderr, "No video modes are currently available.\n");
|
|
|
|
fprintf(stderr, "Try make sure a driver exists and is activated.\n");
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
FilterModes(modes, &nummodes, &filt);
|
|
|
|
if ( !nummodes )
|
|
|
|
{
|
|
|
|
fprintf(stderr, "\
|
|
|
|
No video mode remains after filtering away unwanted modes.\n");
|
|
|
|
fprintf(stderr, "\
|
|
|
|
Try make sure the desired driver is loaded and is configured correctly.\n");
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t selection = 0;
|
|
|
|
bool decided = false;
|
|
|
|
while ( !decided )
|
|
|
|
{
|
|
|
|
DrawMenu(selection, modes, nummodes);
|
|
|
|
switch ( GetKey(stdin) )
|
|
|
|
{
|
|
|
|
case KBKEY_ESC: printf("\n"); exit(10); break;
|
|
|
|
case KBKEY_UP: selection = (nummodes+selection-1) % nummodes; break;
|
|
|
|
case KBKEY_DOWN: selection = (selection+1) % nummodes; break;
|
|
|
|
case KBKEY_ENTER: decided = true; break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-12-15 19:54:49 -05:00
|
|
|
struct dispmsg_crtc_mode mode = modes[selection];
|
2012-05-23 11:01:53 -04:00
|
|
|
if ( !SetCurrentMode(mode) )
|
|
|
|
{
|
|
|
|
error(1, errno, "Unable to set video mode: %s", mode);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
if ( 1 < argc )
|
|
|
|
{
|
|
|
|
pid_t childpid = fork();
|
|
|
|
if ( childpid < 0 ) { perror("fork"); exit(1); }
|
|
|
|
if ( childpid )
|
|
|
|
{
|
|
|
|
int status;
|
|
|
|
waitpid(childpid, &status, 0);
|
|
|
|
if ( !SetCurrentMode(prevmode) )
|
|
|
|
{
|
|
|
|
error(1, errno, "Unable to restore video mode: %s", prevmode);
|
|
|
|
}
|
2012-09-09 06:21:39 -04:00
|
|
|
exit(WEXITSTATUS(status));
|
2012-05-23 11:01:53 -04:00
|
|
|
}
|
|
|
|
execvp(argv[1], argv + 1);
|
2012-12-15 16:13:25 -05:00
|
|
|
error(127, errno, "`%s'", argv[1]);
|
2012-05-23 11:01:53 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|