/* view.c: Display a file if it fits on screen, otherwise use an external viewer. Jason Hood, 11 November, 1999. Public Domain. 021017: handle screen wrap and tabs; use a buffer instead of re-reading the file (which also allows NUL). v1.00, 10 to 15 October, 2003: fix problems with buffer overrun; use VIEW-ER environment variable to specify viewer; added Win32 and 16-bit DOS ports; use the viewer if redirected; recognise character 255 (dumb EOF) and backspace. v1.01, 9, 10 & 12 January, 2004: Win32: recognise screen lines, not buffer lines; handle pipe better (use PeekNamedPipe); use spawn & P_WAIT instead of exec (P_OVERLAY); quote spawned arguments containing spaces. v1.02, 12 June, 2004: Win32: poll the pipe for data (instead of sleeping); remove "Unable to peek pipe" message (just assume empty); prefix error messages with "view: ". v1.03, 28 to 31 July, 2004: Win32: handle the pipe separately; use the viewer if input is redirected and a file is given; return 2 for out of memory, 3 for unable to execute, 4 for pipe problems; recognise BEL; fix more buffer overflow problems (caused by CR & BS). v1.04, 5 October, 2005: Win32: work around XP console bug with backspace; allow VIEW-ER to have an argument. */ #define PVERS "1.04" #define PDATE "5 October, 2005" #include #include #include #include #if defined( __DJGPP__ ) # include # include // isatty for djgpp #else # include // isatty for everyone else # if defined( _WIN32 ) # include # define CR // djgpp & BC strip all CRs # elif defined( __TURBOC__ ) extern unsigned _heaplen = 10240; extern unsigned _stklen = 1024; # endif #endif #define VIEWER_ENV "VIEW-ER" // "VIEWER" might be too common #define VIEWER "less" #define TAB_SIZE 8 #ifdef __GNUC__ void help( void ) __attribute__((noreturn)); void viewer( int, char* [] ) __attribute__((noreturn)); void ErrorExit( int, const char* , const char* ) __attribute__((noreturn)); # ifdef _WIN32 void viewpipe( char*, int ) __attribute__((noreturn)); # endif #endif void help( void ) { puts( "\ View by Jason Hood .\n\ Version "PVERS" ("PDATE"). Public Domain.\n\ http://misc.adoxa.cjb.net/\n\ \n\ Display a file (or standard input if none) to the screen if it fits,\n\ otherwise use a viewer. Having more than one file or redirecting output will\n\ also use the viewer. The default viewer is \"less\"; the environment variable\n\ \"VIEW-ER\" can be set to use something else (eg: \"set VIEW-ER=more\").\ " ); exit( 0 ); } void ErrorExit( int rc, const char* msg1, const char* msg2 ) { fprintf( stderr, "view: %s%s.\n", msg1, msg2 ); exit( rc ); } void viewer( int argc, char* argv[] ) { int rc; char* arg; #ifndef __DJGPP__ char** qargv; int len; #endif argv[0] = getenv( VIEWER_ENV ); if (argv[0] == NULL) argv[0] = VIEWER; arg = strchr( argv[0], ' ' ); if (arg != NULL) { char** vargv = malloc( (argc + 2) * sizeof(char*) ); vargv[0] = strdup( argv[0] ); arg += vargv[0] - argv[0]; *arg = '\0'; vargv[1] = arg + 1; memcpy( vargv + 2, argv + 1, (argc + 1) * sizeof(char*) ); argv = vargv; ++argc; } #ifndef __DJGPP__ qargv = malloc( (argc + 1) * sizeof(char*) ); qargv[0] = argv[0]; qargv[argc] = NULL; while (--argc > 0) { if (strchr( argv[argc], ' ' ) == NULL) { qargv[argc] = argv[argc]; } else { len = strlen( argv[argc] ); qargv[argc] = malloc( len + 3 ); memcpy( qargv[argc] + 1, argv[argc], len ); qargv[argc][0] = qargv[argc][len+1] = '\"'; qargv[argc][len+2] = '\0'; } } argv = qargv; #endif rc = spawnvp( P_WAIT, argv[0], #ifdef __MINGW32__ (const char**) #endif argv ); if (rc == -1) ErrorExit( 3, argv[0], ": unable to execute" ); exit( rc ); } #ifdef _WIN32 void viewpipe( char* buf, int size ) { char* vwr; int ch, len; DWORD rc, wrote; BOOL wait; // Taken directly from the Win32 Programmer's Reference ("Creating a Child // Process with Redirected Input and Output"). HANDLE hChildStdinRd, hChildStdinWr, hChildStdinWrDup, hSaveStdin; SECURITY_ATTRIBUTES saAttr; PROCESS_INFORMATION piProcInfo; STARTUPINFO siStartInfo; saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); saAttr.bInheritHandle = TRUE; saAttr.lpSecurityDescriptor = NULL; hSaveStdin = GetStdHandle( STD_INPUT_HANDLE ); if (!CreatePipe( &hChildStdinRd, &hChildStdinWr, &saAttr, 0 )) ErrorExit( 4, "Stdin pipe creation failed", "" ); if (!SetStdHandle( STD_INPUT_HANDLE, hChildStdinRd )) ErrorExit( 4, "Redirecting Stdin failed", "" ); if (!DuplicateHandle( GetCurrentProcess(), hChildStdinWr, GetCurrentProcess(), &hChildStdinWrDup, 0, FALSE, DUPLICATE_SAME_ACCESS )) ErrorExit( 4, "DuplicateHandle failed", "" ); CloseHandle( hChildStdinWr ); vwr = getenv( VIEWER_ENV ); if (vwr == NULL) vwr = VIEWER; ZeroMemory( &siStartInfo, sizeof(STARTUPINFO) ); siStartInfo.cb = sizeof(STARTUPINFO); if (!CreateProcess( NULL, vwr, NULL, NULL, TRUE, 0, NULL, NULL, &siStartInfo, &piProcInfo )) ErrorExit( 3, vwr, ": unable to execute" ); if (!SetStdHandle( STD_INPUT_HANDLE, hSaveStdin )) ErrorExit( 4, "Re-redirecting Stdin failed", "" ); rc = PIPE_NOWAIT; SetNamedPipeHandleState( hChildStdinWrDup, &rc, NULL, NULL ); rc = STILL_ACTIVE; len = size; do { wait = FALSE; while (len > 0) { if (!WriteFile( hChildStdinWrDup, buf, len, &wrote, NULL )) goto done; if (wrote == 0) { if (wait) { GetExitCodeProcess( piProcInfo.hProcess, &rc ); if (rc != STILL_ACTIVE) goto done; Sleep( 10 ); } else { wait = TRUE; } } else { len -= wrote; } } do { ch = getchar(); if (ch == EOF) break; buf[len++] = ch; } while (ch != '\n' && len < size); } while (len > 0); done: if (!CloseHandle( hChildStdinWrDup )) ErrorExit( 4, "Close pipe failed", "" ); if (rc == STILL_ACTIVE) { WaitForSingleObject( piProcInfo.hProcess, INFINITE ); GetExitCodeProcess( piProcInfo.hProcess, &rc ); } exit( rc ); } #endif int main( int argc, char* argv[] ) { int lines, cols; FILE* file; char* buf; int max, out; int ch, col, len; #ifdef _WIN32 // Use two buffers: one for writing to screen (where newlines are removed // for lines of screen width) and one for the pipe (keeping the newlines). BOOL pipe; char* pbuf; int pout; CONSOLE_SCREEN_BUFFER_INFO csbi; #else # define pipe 0 #endif if ((argc == 1 && isatty( 0 )) || (argc == 2 && (strcmp( argv[1], "/?" ) == 0 || strcmp( argv[1], "-?" ) == 0 || strcmp( argv[1], "--help" ) == 0))) help(); if (argc > 2 || !isatty( 1 ) || (argc == 2 && !isatty( 0 ))) viewer( argc, argv ); if (argc == 1) { file = stdin; } else if ((file = fopen( argv[1], "r" )) == NULL) ErrorExit( 1, argv[1], ": unable to open" ); #if defined ( __DJGPP__ ) lines = ScreenRows(); cols = ScreenCols(); #elif defined( _WIN32 ) GetConsoleScreenBufferInfo( GetStdHandle( STD_OUTPUT_HANDLE ), &csbi ); lines = csbi.srWindow.Bottom - csbi.srWindow.Top + 1; cols = csbi.dwSize.X; //csbi.srWindow.Right - csbi.srWindow.Left + 1; #else // 16-bit DOS lines = *(unsigned char far*)0x0484 + 1; if (lines == 1) lines = 25; cols = *(unsigned int far*)0x044a; #endif lines -= 2; // Blank line and prompt max = (lines + 1) * (cols + 1); // cols characters per line, plus LF #ifdef _WIN32 pipe = (GetFileType( GetStdHandle( STD_INPUT_HANDLE ) ) == FILE_TYPE_PIPE); if (pipe) max *= 2; #endif buf = malloc( max ); if (buf == NULL) ErrorExit( 2, "not enough memory", "" ); #ifdef _WIN32 pbuf = buf; pout = 0; if (pipe) { max /= 2; pbuf += max; } #endif col = len = 0; out = 0; while ((ch = fgetc( file )) != EOF && out < max) { buf[out++] = ch; #ifdef _WIN32 pbuf[pout++] = ch; #endif if (ch == '\t') { int spc = TAB_SIZE - (col % TAB_SIZE); col += spc; len += spc; } else if (ch == '\b') { if (col > 0) --col, --len; } #ifdef CR else if (ch == '\r') { col = 0; len = (len / cols) * cols; } #endif else if (ch != '\n' && ch != '\a') ++col, ++len; if (ch == '\n' || col >= cols) { // If the line is exactly the width of the screen, the screen has // already wrapped, so remove the newline. if (ch == '\n' && len > 0 && (len % cols) == 0) --out; else if (--lines < 0) break; col = 0; if (ch == '\n') len = 0; } } if (file != stdin) fclose( file ); if (lines < 0 || out >= max) { #ifdef _WIN32 if (pipe) viewpipe( pbuf, pout ); #endif if (file == stdin) rewind( stdin ); free( buf ); viewer( argc, argv ); } #ifdef _WIN32 // XP console bug: doesn't handle backspace correctly when it's not alone. while (out--) putchar( *buf++ ); #else fwrite( buf, 1, out, stdout ); #endif return 0; }