#include #include #include #include #include "lesstest.h" #define RD 0 #define WR 1 extern int verbose; extern char* lt_screen; extern char* lt_screen_opts; static const int run_less = 1; // Make 2 specified file descriptors be stdin and stdout. static void dup_std(int fd0, int fd1) { if (fd0 >= 0) dup2(fd0, 0); if (fd1 >= 0) dup2(fd1, 1); } static const char* basename(const char* path) { const char* slash = strrchr(path, '/'); if (slash == NULL) return path; return slash+1; } // Exec an instance of less in the current process. static void become_child_less(char* less, int argc, char* const* argv, char* const* envp, const char* tempfile, int less_in_pipe[2], int screen_in_pipe[2]) { if (verbose) fprintf(stderr, "less child: in %d, out %d, close %d,%d\n", less_in_pipe[RD], screen_in_pipe[WR], less_in_pipe[WR], screen_in_pipe[RD]); close(less_in_pipe[WR]); close(screen_in_pipe[RD]); dup_std(less_in_pipe[RD], screen_in_pipe[WR]); char** less_argv = malloc(sizeof(char*) * (argc + 6)); int less_argc = 0; less_argv[less_argc++] = less; less_argv[less_argc++] = "--tty"; less_argv[less_argc++] = "/dev/stdin"; while (--argc > 0) { char* arg = *++argv; less_argv[less_argc++] = (argc > 1 || tempfile == NULL) ? arg : (char*) tempfile; } less_argv[less_argc] = NULL; if (verbose) { print_strings("less argv", less_argv); print_strings("less envp", envp); } execve(less, less_argv, envp); fprintf(stderr, "cannot exec %s: %s\n", less, strerror(errno)); exit(1); } // Exec an instance of lt_screen in the current process. static void become_child_screen(char* lt_screen, int screen_width, int screen_height, int screen_in_pipe[2], int screen_out_pipe[2]) { if (verbose) fprintf(stderr, "screen child: in %d, out %d, close %d\n", screen_in_pipe[RD], screen_out_pipe[WR], screen_out_pipe[RD]); close(screen_out_pipe[RD]); dup_std(screen_in_pipe[RD], screen_out_pipe[WR]); char* screen_argv[10]; int screen_argc = 0; char sw[16]; char sh[16]; screen_argv[screen_argc++] = lt_screen; if (screen_width >= 0) { snprintf(sw, sizeof(sw), "%d", screen_width); screen_argv[screen_argc++] = "-w"; screen_argv[screen_argc++] = sw; } if (screen_height >= 0) { snprintf(sh, sizeof(sh), "%d", screen_height); screen_argv[screen_argc++] = "-h"; screen_argv[screen_argc++] = sh; } if (lt_screen_opts != NULL) { screen_argv[screen_argc++] = lt_screen_opts; } if (1) screen_argv[screen_argc++] = "-q"; screen_argv[screen_argc] = NULL; if (verbose) print_strings("screen argv", screen_argv); char* const screen_envp[] = { NULL }; execve(lt_screen, screen_argv, screen_envp); fprintf(stderr, "cannot exec %s: %s\n", lt_screen, strerror(errno)); exit(1); } // Create an empty LessPipeline. static LessPipeline* new_pipeline(void) { LessPipeline* pipeline = malloc(sizeof(LessPipeline)); pipeline->less_in_pipe[RD] = pipeline->less_in_pipe[WR] = -1; pipeline->screen_in_pipe[RD] = pipeline->screen_in_pipe[WR] = -1; pipeline->screen_out_pipe[RD] = pipeline->screen_out_pipe[WR] = -1; pipeline->less_in = pipeline->screen_out = -1; pipeline->tempfile = NULL; pipeline->screen_pid = 0; pipeline->screen_width = pipeline->screen_height = 0; return pipeline; } // Create a LessPipeline. LessPipeline* create_less_pipeline(char* const* argv, int argc, char* const* envp) { // If textfile contains a slash, create a temporary link from // the named text file to its basename, and run less on the link. LessPipeline* pipeline = new_pipeline(); const char* textfile = argv[argc-1]; const char* textbase = basename(textfile); if (textbase != textfile) { pipeline->tempfile = textbase; if (link(textfile, textbase) < 0) { fprintf(stderr, "cannot link %s to %s: %s\n", textfile, textbase, strerror(errno)); return NULL; } textfile = textbase; } if (pipe(pipeline->screen_in_pipe) < 0) { destroy_less_pipeline(pipeline); return NULL; } const char* w = get_envp(envp, "COLUMNS"); const char* h = get_envp(envp, "LINES"); if (w != NULL) pipeline->screen_width = atoi(w); if (h != NULL) pipeline->screen_height = atoi(h); if (verbose) fprintf(stderr, "less out pipe %d,%d\n", pipeline->screen_in_pipe[0], pipeline->screen_in_pipe[1]); if (run_less) { if (pipe(pipeline->less_in_pipe) < 0) { destroy_less_pipeline(pipeline); return 0; } if (verbose) fprintf(stderr, "less in pipe %d,%d\n", pipeline->less_in_pipe[RD], pipeline->less_in_pipe[WR]); char* less = argv[0]; if (verbose) fprintf(stderr, "testing %s on %s\n", less, textfile); pipeline->less_pid = fork(); if (pipeline->less_pid < 0) { destroy_less_pipeline(pipeline); return NULL; } if (!pipeline->less_pid) become_child_less(less, argc, argv, envp, pipeline->tempfile, pipeline->less_in_pipe, pipeline->screen_in_pipe); if (verbose) fprintf(stderr, "less child %ld\n", (long) pipeline->less_pid); close(pipeline->less_in_pipe[RD]); pipeline->less_in_pipe[RD] = -1; close(pipeline->screen_in_pipe[WR]); pipeline->screen_in_pipe[WR] = -1; } if (pipe(pipeline->screen_out_pipe) < 0) { destroy_less_pipeline(pipeline); return NULL; } if (verbose) fprintf(stderr, "screen out pipe %d,%d\n", pipeline->screen_out_pipe[RD], pipeline->screen_out_pipe[WR]); pipeline->screen_pid = fork(); if (!pipeline->screen_pid) // child: lt_screen become_child_screen(lt_screen, pipeline->screen_width, pipeline->screen_height, pipeline->screen_in_pipe, pipeline->screen_out_pipe); if (verbose) fprintf(stderr, "screen child %ld\n", (long) pipeline->screen_pid); close(pipeline->screen_out_pipe[WR]); pipeline->screen_out_pipe[WR] = -1; close(pipeline->screen_in_pipe[RD]); pipeline->screen_in_pipe[RD] = -1; pipeline->less_in = run_less ? pipeline->less_in_pipe[WR] : pipeline->screen_in_pipe[WR]; pipeline->screen_out = pipeline->screen_out_pipe[RD]; if (verbose) fprintf(stderr, "less in %d, screen out %d, pid %ld\n", pipeline->less_in, pipeline->screen_out, (long) pipeline->screen_pid); return pipeline; } void destroy_less_pipeline(LessPipeline* pipeline) { close(pipeline->less_in); close(pipeline->screen_out); close(pipeline->less_in_pipe[RD]); close(pipeline->less_in_pipe[WR]); close(pipeline->screen_in_pipe[RD]); close(pipeline->screen_in_pipe[WR]); close(pipeline->screen_out_pipe[RD]); close(pipeline->screen_out_pipe[WR]); if (pipeline->tempfile != NULL) unlink(pipeline->tempfile); free(pipeline); }