1 /* 2 * Copyright (C) 1984-2026 Mark Nudelman 3 * 4 * You may distribute under the terms of either the GNU General Public 5 * License or the Less License, as specified in the README file. 6 * 7 * For more information, see the README file. 8 */ 9 10 11 /* 12 * Routines to execute other programs. 13 * Necessarily very OS dependent. 14 */ 15 16 #include "less.h" 17 #include <signal.h> 18 #include "position.h" 19 20 #if MSDOS_COMPILER 21 #include <dos.h> 22 #if MSDOS_COMPILER==WIN32C && defined(__MINGW32__) 23 #include <direct.h> 24 #define setdisk(n) _chdrive((n)+1) 25 #else 26 #ifdef _MSC_VER 27 #include <direct.h> 28 #define setdisk(n) _chdrive((n)+1) 29 #else 30 #include <dir.h> 31 #endif 32 #endif 33 #endif 34 35 extern int sigs; 36 extern IFILE curr_ifile; 37 38 39 #if HAVE_SYSTEM 40 41 /* 42 * Pass the specified command to a shell to be executed. 43 * Like plain "system()", but handles resetting terminal modes, etc. 44 */ 45 public void lsystem(constant char *cmd, constant char *donemsg) 46 { 47 int inp; 48 #if HAVE_SHELL 49 constant char *shell; 50 char *p; 51 #endif 52 IFILE save_ifile; 53 #if MSDOS_COMPILER && MSDOS_COMPILER!=WIN32C 54 char cwd[FILENAME_MAX+1]; 55 #endif 56 57 /* 58 * Print the command which is to be executed, 59 * unless the command starts with a "-". 60 */ 61 if (cmd[0] == '-') 62 cmd++; 63 else 64 { 65 clear_bot(); 66 putstr("!"); 67 putstr(cmd); 68 putstr("\n"); 69 } 70 71 #if MSDOS_COMPILER 72 #if MSDOS_COMPILER==WIN32C 73 if (*cmd == '\0') 74 cmd = getenv("COMSPEC"); 75 #else 76 /* 77 * Working directory is global on MSDOS. 78 * The child might change the working directory, so we 79 * must save and restore CWD across calls to "system", 80 * or else we won't find our file when we return and 81 * try to "reedit_ifile" it. 82 */ 83 getcwd(cwd, FILENAME_MAX); 84 #endif 85 #endif 86 87 /* 88 * Close the current input file. 89 */ 90 save_ifile = save_curr_ifile(); 91 (void) edit_ifile(NULL_IFILE); 92 93 /* 94 * De-initialize the terminal and take out of raw mode. 95 */ 96 term_deinit(); 97 flush(); /* Make sure the deinit chars get out */ 98 raw_mode(0); 99 #if MSDOS_COMPILER==WIN32C 100 close_getchr(); 101 #endif 102 103 /* 104 * Restore signals to their defaults. 105 */ 106 init_signals(0); 107 108 #if HAVE_DUP 109 /* 110 * Force standard input to be the user's terminal 111 * (the normal standard input), even if less's standard input 112 * is coming from a pipe. 113 */ 114 inp = dup(0); 115 close(0); 116 #if !MSDOS_COMPILER 117 if (open_tty() < 0) 118 #endif 119 dup(inp); 120 #endif 121 122 /* 123 * Pass the command to the system to be executed. 124 * If we have a SHELL environment variable, use 125 * <$SHELL -c "command"> instead of just <command>. 126 * If the command is empty, just invoke a shell. 127 */ 128 #if HAVE_SHELL 129 p = NULL; 130 if ((shell = lgetenv("SHELL")) != NULL && *shell != '\0') 131 { 132 if (*cmd == '\0') 133 p = save(shell); 134 else 135 { 136 char *esccmd = shell_quote(cmd); 137 if (esccmd != NULL) 138 { 139 constant char *copt = shell_coption(); 140 size_t len = strlen(shell) + strlen(esccmd) + strlen(copt) + 3; 141 p = (char *) ecalloc(len, sizeof(char)); 142 SNPRINTF3(p, len, "%s %s %s", shell, copt, esccmd); 143 free(esccmd); 144 } 145 } 146 } 147 if (p == NULL) 148 { 149 if (*cmd == '\0') 150 p = save("sh"); 151 else 152 p = save(cmd); 153 } 154 system(p); 155 free(p); 156 #else 157 #if MSDOS_COMPILER==DJGPPC 158 /* 159 * Make stdin of the child be in cooked mode. 160 */ 161 setmode(0, O_TEXT); 162 /* 163 * We don't need to catch signals of the child (it 164 * also makes trouble with some DPMI servers). 165 */ 166 __djgpp_exception_toggle(); 167 system(cmd); 168 __djgpp_exception_toggle(); 169 #else 170 system(cmd); 171 #endif 172 #endif 173 174 #if HAVE_DUP 175 /* 176 * Restore standard input, reset signals, raw mode, etc. 177 */ 178 close(0); 179 dup(inp); 180 close(inp); 181 #endif 182 183 #if MSDOS_COMPILER==WIN32C 184 open_getchr(); 185 #endif 186 init_signals(1); 187 raw_mode(1); 188 if (donemsg != NULL) 189 { 190 putstr(donemsg); 191 putstr(" (press RETURN)"); 192 get_return(); 193 putchr('\n'); 194 flush(); 195 } 196 term_init(); 197 screen_trashed(); 198 199 #if MSDOS_COMPILER && MSDOS_COMPILER!=WIN32C 200 /* 201 * Restore the previous directory (possibly 202 * changed by the child program we just ran). 203 */ 204 chdir(cwd); 205 #if MSDOS_COMPILER != DJGPPC 206 /* 207 * Some versions of chdir() don't change to the drive 208 * which is part of CWD. (DJGPP does this in chdir.) 209 */ 210 if (cwd[1] == ':') 211 { 212 if (cwd[0] >= 'a' && cwd[0] <= 'z') 213 setdisk(cwd[0] - 'a'); 214 else if (cwd[0] >= 'A' && cwd[0] <= 'Z') 215 setdisk(cwd[0] - 'A'); 216 } 217 #endif 218 #endif 219 220 /* 221 * Reopen the current input file. 222 */ 223 reedit_ifile(save_ifile); 224 225 /* 226 * Since we were ignoring window change signals while we executed 227 * the system command, we must assume the window changed. 228 * Warning: this leaves a signal pending (in "sigs"), 229 * so psignals() should be called soon after lsystem(). 230 */ 231 sigs |= S_WINCH; 232 } 233 234 #endif 235 236 #if PIPEC 237 238 /* 239 * Pipe a section of the input file into the given shell command. 240 * The section to be piped is the section "between" the current 241 * position and the position marked by the given letter. 242 * 243 * If the mark is after the current screen, the section between 244 * the top line displayed and the mark is piped. 245 * If the mark is before the current screen, the section between 246 * the mark and the bottom line displayed is piped. 247 * If the mark is on the current screen, or if the mark is ".", 248 * the whole current screen is piped. 249 */ 250 public int pipe_mark(char c, constant char *cmd) 251 { 252 POSITION mpos, tpos, bpos; 253 254 /* 255 * mpos = the marked position. 256 * tpos = top of screen. 257 * bpos = bottom of screen. 258 */ 259 mpos = markpos(c); 260 if (mpos == NULL_POSITION) 261 return (-1); 262 tpos = position(TOP); 263 if (tpos == NULL_POSITION) 264 tpos = ch_zero(); 265 bpos = position(BOTTOM); 266 267 if (c == '.') 268 return (pipe_data(cmd, tpos, bpos)); 269 else if (mpos <= tpos) 270 return (pipe_data(cmd, mpos, bpos)); 271 else if (bpos == NULL_POSITION) 272 return (pipe_data(cmd, tpos, bpos)); 273 else 274 return (pipe_data(cmd, tpos, mpos)); 275 } 276 277 /* 278 * Create a pipe to the given shell command. 279 * Feed it the file contents between the positions spos and epos. 280 */ 281 public int pipe_data(constant char *cmd, POSITION spos, POSITION epos) 282 { 283 FILE *f; 284 int c; 285 286 /* 287 * This is structured much like lsystem(). 288 * Since we're running a shell program, we must be careful 289 * to perform the necessary deinitialization before running 290 * the command, and reinitialization after it. 291 */ 292 if (ch_seek(spos) != 0) 293 { 294 error("Cannot seek to start position", NULL_PARG); 295 return (-1); 296 } 297 298 if ((f = popen(cmd, "w")) == NULL) 299 { 300 error("Cannot create pipe", NULL_PARG); 301 return (-1); 302 } 303 clear_bot(); 304 putstr("!"); 305 putstr(cmd); 306 putstr("\n"); 307 308 term_deinit(); 309 flush(); 310 raw_mode(0); 311 init_signals(0); 312 #if MSDOS_COMPILER==WIN32C 313 close_getchr(); 314 #endif 315 #ifdef SIGPIPE 316 LSIGNAL(SIGPIPE, SIG_IGN); 317 #endif 318 319 c = EOI; 320 while (epos == NULL_POSITION || spos++ <= epos) 321 { 322 /* 323 * Read a character from the file and give it to the pipe. 324 */ 325 c = ch_forw_get(); 326 if (c == EOI) 327 break; 328 if (putc(c, f) == EOF) 329 break; 330 } 331 332 /* 333 * Finish up the last line. 334 */ 335 while (c != '\n' && c != EOI ) 336 { 337 c = ch_forw_get(); 338 if (c == EOI) 339 break; 340 if (putc(c, f) == EOF) 341 break; 342 } 343 344 pclose(f); 345 346 #ifdef SIGPIPE 347 LSIGNAL(SIGPIPE, SIG_DFL); 348 #endif 349 #if MSDOS_COMPILER==WIN32C 350 open_getchr(); 351 #endif 352 init_signals(1); 353 raw_mode(1); 354 term_init(); 355 screen_trashed(); 356 #if defined(SIGWINCH) || defined(SIGWIND) 357 /* {{ Probably don't need this here. }} */ 358 lwinch(0); 359 #endif 360 return (0); 361 } 362 363 #endif 364