diff options
Diffstat (limited to 'usr.bin/vim/unix.c')
-rw-r--r-- | usr.bin/vim/unix.c | 2883 |
1 files changed, 2883 insertions, 0 deletions
diff --git a/usr.bin/vim/unix.c b/usr.bin/vim/unix.c new file mode 100644 index 00000000000..bdb7bf55124 --- /dev/null +++ b/usr.bin/vim/unix.c @@ -0,0 +1,2883 @@ +/* $OpenBSD: unix.c,v 1.1 1996/09/07 21:40:24 downsj Exp $ */ +/* vi:set ts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * OS/2 port by Paul Slootman + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + */ + +/* + * unix.c -- code for all flavors of Unix (BSD, SYSV, SVR4, POSIX, ...) + * Also for OS/2, using the excellent EMX package!!! + * + * A lot of this file was originally written by Juergen Weigert and later + * changed beyond recognition. + */ + +/* + * Some systems have a prototype for select() that has (int *) instead of + * (fd_set *), which is wrong. This define removes that prototype. We include + * our own prototype in osdef.h. + */ +#define select select_declared_wrong + +#include "vim.h" +#include "globals.h" +#include "option.h" +#include "proto.h" + +#ifdef HAVE_FCNTL_H +# include <fcntl.h> +#endif + +#include "unixunix.h" /* unix includes for unix.c only */ + +/* + * Use this prototype for select, some include files have a wrong prototype + */ +#undef select + +#if defined(HAVE_SELECT) +extern int select __ARGS((int, fd_set *, fd_set *, fd_set *, struct timeval *)); +#endif + +/* + * end of autoconf section. To be extended... + */ + +/* Are the following #ifdefs still required? And why? Is that for X11? */ + +#if defined(ESIX) || defined(M_UNIX) && !defined(SCO) +# ifdef SIGWINCH +# undef SIGWINCH +# endif +# ifdef TIOCGWINSZ +# undef TIOCGWINSZ +# endif +#endif + +#if defined(SIGWINDOW) && !defined(SIGWINCH) /* hpux 9.01 has it */ +# define SIGWINCH SIGWINDOW +#endif + +#if defined(HAVE_X11) && defined(WANT_X11) +# include <X11/Xlib.h> +# include <X11/Xutil.h> +# include <X11/Xatom.h> + +Window x11_window = 0; +Display *x11_display = NULL; +int got_x_error = FALSE; + +static int get_x11_windis __ARGS((void)); +static void set_x11_title __ARGS((char_u *)); +static void set_x11_icon __ARGS((char_u *)); +#endif + +static int get_x11_title __ARGS((int)); +static int get_x11_icon __ARGS((int)); + +static void may_core_dump __ARGS((void)); + +static int Read __ARGS((char_u *, long)); +static int WaitForChar __ARGS((long)); +static int RealWaitForChar __ARGS((int, long)); +static void fill_inbuf __ARGS((int)); + +#if defined(SIGWINCH) +static RETSIGTYPE sig_winch __ARGS(SIGPROTOARG); +#endif +#if defined(SIGALRM) && defined(HAVE_X11) && defined(WANT_X11) +static RETSIGTYPE sig_alarm __ARGS(SIGPROTOARG); +#endif +static RETSIGTYPE deathtrap __ARGS(SIGPROTOARG); + +static void catch_signals __ARGS((RETSIGTYPE (*func)())); +#ifndef __EMX__ +static int have_wildcard __ARGS((int, char_u **)); +static int have_dollars __ARGS((int, char_u **)); +#endif + +static int do_resize = FALSE; +static char_u *oldtitle = NULL; +static char_u *fixedtitle = (char_u *)"Thanks for flying Vim"; +static char_u *oldicon = NULL; +#ifndef __EMX__ +static char_u *extra_shell_arg = NULL; +static int show_shell_mess = TRUE; +#endif +static int core_dump = FALSE; /* core dump in mch_windexit() */ + +#ifdef SYS_SIGLIST_DECLARED +/* + * I have seen + * extern char *_sys_siglist[NSIG]; + * on Irix, Linux, NetBSD and Solaris. It contains a nice list of strings + * that describe the signals. That is nearly what we want here. But + * autoconf does only check for sys_siglist (without the underscore), I + * do not want to change everything today.... jw. + * This is why AC_DECL_SYS_SIGLIST is commented out in configure.in + */ +#endif + +static struct +{ + int sig; /* Signal number, eg. SIGSEGV etc */ + char *name; /* Signal name (not char_u!). */ + int dump; /* Should this signal cause a core dump? */ +} signal_info[] = +{ +#ifdef SIGHUP + {SIGHUP, "HUP", FALSE}, +#endif +#ifdef SIGINT + {SIGINT, "INT", FALSE}, +#endif +#ifdef SIGQUIT + {SIGQUIT, "QUIT", TRUE}, +#endif +#ifdef SIGILL + {SIGILL, "ILL", TRUE}, +#endif +#ifdef SIGTRAP + {SIGTRAP, "TRAP", TRUE}, +#endif +#ifdef SIGABRT + {SIGABRT, "ABRT", TRUE}, +#endif +#ifdef SIGEMT + {SIGEMT, "EMT", TRUE}, +#endif +#ifdef SIGFPE + {SIGFPE, "FPE", TRUE}, +#endif +#ifdef SIGBUS + {SIGBUS, "BUS", TRUE}, +#endif +#ifdef SIGSEGV + {SIGSEGV, "SEGV", TRUE}, +#endif +#ifdef SIGSYS + {SIGSYS, "SYS", TRUE}, +#endif +#ifdef SIGALRM + {SIGALRM, "ALRM", FALSE}, +#endif +#ifdef SIGTERM + {SIGTERM, "TERM", FALSE}, +#endif +#ifdef SIGVTALRM + {SIGVTALRM, "VTALRM", FALSE}, +#endif +#ifdef SIGPROF + {SIGPROF, "PROF", FALSE}, +#endif +#ifdef SIGXCPU + {SIGXCPU, "XCPU", TRUE}, +#endif +#ifdef SIGXFSZ + {SIGXFSZ, "XFSZ", TRUE}, +#endif +#ifdef SIGUSR1 + {SIGUSR1, "USR1", FALSE}, +#endif +#ifdef SIGUSR2 + {SIGUSR2, "USR2", FALSE}, +#endif + {-1, "Unknown!", -1} +}; + + void +mch_write(s, len) + char_u *s; + int len; +{ +#ifdef USE_GUI + if (gui.in_use && !gui.dying) + { + gui_write(s, len); + if (p_wd) + gui_mch_wait_for_chars(p_wd); + } + else +#endif + { + write(1, (char *)s, len); + if (p_wd) /* Unix is too fast, slow down a bit more */ + RealWaitForChar(0, p_wd); + } +} + +/* + * mch_inchar(): low level input funcion. + * Get a characters from the keyboard. + * Return the number of characters that are available. + * If wtime == 0 do not wait for characters. + * If wtime == n wait a short time for characters. + * If wtime == -1 wait forever for characters. + */ + int +mch_inchar(buf, maxlen, wtime) + char_u *buf; + int maxlen; + long wtime; /* don't use "time", MIPS cannot handle it */ +{ + int len; + +#ifdef USE_GUI + if (gui.in_use) + { + if (!gui_mch_wait_for_chars(wtime)) + return 0; + return Read(buf, (long)maxlen); + } +#endif + + if (wtime >= 0) + { + while (WaitForChar(wtime) == 0) /* no character available */ + { + if (!do_resize) /* return if not interrupted by resize */ + return 0; + set_winsize(0, 0, FALSE); + do_resize = FALSE; + } + } + else /* wtime == -1 */ + { + /* + * If there is no character available within 'updatetime' seconds + * flush all the swap files to disk + * Also done when interrupted by SIGWINCH. + */ + if (WaitForChar(p_ut) == 0) + updatescript(0); + } + + for (;;) /* repeat until we got a character */ + { + if (do_resize) /* window changed size */ + { + set_winsize(0, 0, FALSE); + do_resize = FALSE; + } + /* + * we want to be interrupted by the winch signal + */ + WaitForChar(-1L); + if (do_resize) /* interrupted by SIGWINCHsignal */ + continue; + + /* + * For some terminals we only get one character at a time. + * We want the get all available characters, so we could keep on + * trying until none is available + * For some other terminals this is quite slow, that's why we don't do + * it. + */ + len = Read(buf, (long)maxlen); + if (len > 0) + { +#ifdef OS2 + int i; + + for (i = 0; i < len; i++) + if (buf[i] == 0) + buf[i] = K_NUL; +#endif + return len; + } + } +} + +/* + * return non-zero if a character is available + */ + int +mch_char_avail() +{ +#ifdef USE_GUI + if (gui.in_use) + { + gui_mch_update(); + return !is_input_buf_empty(); + } +#endif + return WaitForChar(0L); +} + + long +mch_avail_mem(special) + int special; +{ +#ifdef __EMX__ + return ulimit(3, 0L); /* always 32MB? */ +#else + return 0x7fffffff; /* virtual memory eh */ +#endif +} + + void +mch_delay(msec, ignoreinput) + long msec; + int ignoreinput; +{ + if (ignoreinput) +#ifndef HAVE_SELECT + poll(NULL, 0, (int)msec); +#else +# ifdef __EMX__ + _sleep2(msec); +# else + { + struct timeval tv; + + tv.tv_sec = msec / 1000; + tv.tv_usec = (msec % 1000) * 1000; + select(0, NULL, NULL, NULL, &tv); + } +# endif /* __EMX__ */ +#endif /* HAVE_SELECT */ + else +#ifdef USE_GUI + if (gui.in_use) + gui_mch_wait_for_chars(msec); + else +#endif + WaitForChar(msec); +} + +#if defined(SIGWINCH) +/* + * We need correct potatotypes, otherwise mean compilers will barf when the + * second argument to signal() is ``wrong''. + * Let me try it with a few tricky defines from my own osdef.h (jw). + */ + static RETSIGTYPE +sig_winch SIGDEFARG(sigarg) +{ + /* this is not required on all systems, but it doesn't hurt anybody */ + signal(SIGWINCH, (RETSIGTYPE (*)())sig_winch); + do_resize = TRUE; + SIGRETURN; +} +#endif + +#if defined(SIGALRM) && defined(HAVE_X11) && defined(WANT_X11) +/* + * signal function for alarm(). + */ + static RETSIGTYPE +sig_alarm SIGDEFARG(sigarg) +{ + /* doesn't do anything, just to break a system call */ + SIGRETURN; +} +#endif + + void +mch_resize() +{ + do_resize = TRUE; +} + +/* + * This function handles deadly signals. + * It tries to preserve any swap file and exit properly. + * (partly from Elvis). + */ + static RETSIGTYPE +deathtrap SIGDEFARG(sigarg) +{ + static int entered = 0; +#ifdef SIGHASARG + int i; + + for (i = 0; signal_info[i].dump != -1; i++) + { + if (sigarg == signal_info[i].sig) + { + if (signal_info[i].dump) + core_dump = TRUE; + break; + } + } +#endif + + /* + * If something goes wrong after entering here, we may get here again. + * When this happens, give a message and try to exit nicely (resetting the + * terminal mode, etc.) + * When this happens twice, just exit, don't even try to give a message, + * stack may be corrupt or something weird. + */ + if (entered == 2) + { + may_core_dump(); + exit(7); + } + if (entered) + { + OUTSTR("Vim: Double signal, exiting\n"); + flushbuf(); + getout(1); + } + ++entered; + + sprintf((char *)IObuff, "Vim: Caught %s %s\n", +#ifdef SIGHASARG + "deadly signal", signal_info[i].name); +#else + "some", "deadly signal"); +#endif + + preserve_exit(); /* preserve files and exit */ + + SIGRETURN; +} + +/* + * If the machine has job control, use it to suspend the program, + * otherwise fake it by starting a new shell. + * When running the GUI iconify the window. + */ + void +mch_suspend() +{ +#ifdef USE_GUI + if (gui.in_use) + { + gui_mch_iconify(); + return; + } +#endif +#ifdef SIGTSTP + flushbuf(); /* needed to make cursor visible on some systems */ + settmode(0); + flushbuf(); /* needed to disable mouse on some systems */ + kill(0, SIGTSTP); /* send ourselves a STOP signal */ + + /* + * Set oldtitle to NULL, so the current title is obtained again. + */ + if (oldtitle != fixedtitle) + { + vim_free(oldtitle); + oldtitle = NULL; + } + settmode(1); +#else + MSG_OUTSTR("new shell started\n"); + (void)call_shell(NULL, SHELL_COOKED); +#endif + need_check_timestamps = TRUE; +} + + void +mch_windinit() +{ + Columns = 80; + Rows = 24; + + flushbuf(); + + (void)mch_get_winsize(); + +#if defined(SIGWINCH) + /* + * WINDOW CHANGE signal is handled with sig_winch(). + */ + signal(SIGWINCH, (RETSIGTYPE (*)())sig_winch); +#endif + + /* + * We want the STOP signal to work, to make mch_suspend() work + */ +#ifdef SIGTSTP + signal(SIGTSTP, SIG_DFL); +#endif + + /* + * We want to ignore breaking of PIPEs. + */ +#ifdef SIGPIPE + signal(SIGPIPE, SIG_IGN); +#endif + + /* + * Arrange for other signals to gracefully shutdown Vim. + */ + catch_signals(deathtrap); +} + + static void +catch_signals(func) + RETSIGTYPE (*func)(); +{ + int i; + + for (i = 0; signal_info[i].dump != -1; i++) + signal(signal_info[i].sig, func); +} + + void +reset_signals() +{ + catch_signals(SIG_DFL); +} + +/* + * Check_win checks whether we have an interactive window. + */ + int +mch_check_win(argc, argv) + int argc; + char **argv; +{ + if (isatty(1)) + return OK; + return FAIL; +} + + int +mch_check_input() +{ + if (isatty(0)) + return OK; + return FAIL; +} + +#if defined(HAVE_X11) && defined(WANT_X11) +/* + * X Error handler, otherwise X just exits! (very rude) -- webb + */ + static int +x_error_handler(dpy, error_event) + Display *dpy; + XErrorEvent *error_event; +{ + XGetErrorText(dpy, error_event->error_code, (char *)IObuff, IOSIZE); + STRCAT(IObuff, "\nVim: Got X error\n"); + +#if 1 + preserve_exit(); /* preserve files and exit */ +#else + printf(IObuff); /* print error message and continue */ + /* Makes my system hang */ +#endif + + return 0; /* NOTREACHED */ +} + +/* + * Another X Error handler, just used to check for errors. + */ + static int +x_error_check(dpy, error_event) + Display *dpy; + XErrorEvent *error_event; +{ + got_x_error = TRUE; + return 0; +} + +/* + * try to get x11 window and display + * + * return FAIL for failure, OK otherwise + */ + static int +get_x11_windis() +{ + char *winid; + XTextProperty text_prop; + int (*old_handler)(); + static int result = -1; + static int x11_display_opened_here = FALSE; + + /* X just exits if it finds an error otherwise! */ + XSetErrorHandler(x_error_handler); + +#ifdef USE_GUI_X11 + if (gui.in_use) + { + /* + * If the X11 display was opened here before, for the window where Vim + * was started, close that one now to avoid a memory leak. + */ + if (x11_display_opened_here && x11_display != NULL) + { + XCloseDisplay(x11_display); + x11_display = NULL; + x11_display_opened_here = FALSE; + } + return gui_get_x11_windis(&x11_window, &x11_display); + } +#endif + + if (result != -1) /* Have already been here and set this */ + return result; /* Don't do all these X calls again */ + + /* + * If WINDOWID not set, should try another method to find out + * what the current window number is. The only code I know for + * this is very complicated. + * We assume that zero is invalid for WINDOWID. + */ + if (x11_window == 0 && (winid = getenv("WINDOWID")) != NULL) + x11_window = (Window)atol(winid); + if (x11_window != 0 && x11_display == NULL) + { +#ifdef SIGALRM + RETSIGTYPE (*sig_save)(); + + /* + * Opening the Display may hang if the DISPLAY setting is wrong, or + * the network connection is bad. Set an alarm timer to get out. + */ + sig_save = (RETSIGTYPE (*)())signal(SIGALRM, + (RETSIGTYPE (*)())sig_alarm); + alarm(2); +#endif + x11_display = XOpenDisplay(NULL); +#ifdef SIGALRM + alarm(0); + signal(SIGALRM, (RETSIGTYPE (*)())sig_save); +#endif + if (x11_display != NULL) + { + /* + * Try to get the window title. I don't actually want it yet, so + * there may be a simpler call to use, but this will cause the + * error handler x_error_check() to be called if anything is wrong, + * such as the window pointer being invalid (as can happen when the + * user changes his DISPLAY, but not his WINDOWID) -- webb + */ + old_handler = XSetErrorHandler(x_error_check); + got_x_error = FALSE; + if (XGetWMName(x11_display, x11_window, &text_prop)) + XFree((void *)text_prop.value); + XSync(x11_display, False); + if (got_x_error) + { + /* Maybe window id is bad */ + x11_window = 0; + XCloseDisplay(x11_display); + x11_display = NULL; + } + else + x11_display_opened_here = TRUE; + XSetErrorHandler(old_handler); + } + } + if (x11_window == 0 || x11_display == NULL) + return (result = FAIL); + return (result = OK); +} + +/* + * Determine original x11 Window Title + */ + static int +get_x11_title(test_only) + int test_only; +{ + XTextProperty text_prop; + int retval = FALSE; + + if (get_x11_windis() == OK) + { + /* Get window name if any */ + if (XGetWMName(x11_display, x11_window, &text_prop)) + { + if (text_prop.value != NULL) + { + retval = TRUE; + if (!test_only) + oldtitle = strsave((char_u *)text_prop.value); + } + XFree((void *)text_prop.value); + } + } + if (oldtitle == NULL && !test_only) /* could not get old title */ + oldtitle = fixedtitle; + + return retval; +} + +/* + * Determine original x11 Window icon + */ + + static int +get_x11_icon(test_only) + int test_only; +{ + XTextProperty text_prop; + int retval = FALSE; + + if (get_x11_windis() == OK) + { + /* Get icon name if any */ + if (XGetWMIconName(x11_display, x11_window, &text_prop)) + { + if (text_prop.value != NULL) + { + retval = TRUE; + if (!test_only) + oldicon = strsave((char_u *)text_prop.value); + } + XFree((void *)text_prop.value); + } + } + + /* could not get old icon, use terminal name */ + if (oldicon == NULL && !test_only) + { + if (STRNCMP(term_strings[KS_NAME], "builtin_", 8) == 0) + oldicon = term_strings[KS_NAME] + 8; + else + oldicon = term_strings[KS_NAME]; + } + + return retval; +} + +/* + * Set x11 Window Title + * + * get_x11_windis() must be called before this and have returned OK + */ + static void +set_x11_title(title) + char_u *title; +{ +#if XtSpecificationRelease >= 4 + XTextProperty text_prop; + + text_prop.value = title; + text_prop.nitems = STRLEN(title); + text_prop.encoding = XA_STRING; + text_prop.format = 8; + XSetWMName(x11_display, x11_window, &text_prop); +#else + XStoreName(x11_display, x11_window, (char *)title); +#endif + XFlush(x11_display); +} + +/* + * Set x11 Window icon + * + * get_x11_windis() must be called before this and have returned OK + */ + static void +set_x11_icon(icon) + char_u *icon; +{ +#if XtSpecificationRelease >= 4 + XTextProperty text_prop; + + text_prop.value = icon; + text_prop.nitems = STRLEN(icon); + text_prop.encoding = XA_STRING; + text_prop.format = 8; + XSetWMIconName(x11_display, x11_window, &text_prop); +#else + XSetIconName(x11_display, x11_window, (char *)icon); +#endif + XFlush(x11_display); +} + +#else /* HAVE_X11 && WANT_X11 */ + + static int +get_x11_title(test_only) + int test_only; +{ + if (!test_only) + oldtitle = fixedtitle; + return FALSE; +} + + static int +get_x11_icon(test_only) + int test_only; +{ + if (!test_only) + { + if (STRNCMP(term_strings[KS_NAME], "builtin_", 8) == 0) + oldicon = term_strings[KS_NAME] + 8; + else + oldicon = term_strings[KS_NAME]; + } + return FALSE; +} + +#endif /* HAVE_X11 && WANT_X11 */ + + int +mch_can_restore_title() +{ +#ifdef USE_GUI + /* + * If GUI is (going to be) used, we can always set the window title. + * Saves a bit of time, because the X11 display server does not need to be + * contacted. + */ + if (gui.starting || gui.in_use) + return TRUE; +#endif + return get_x11_title(TRUE); +} + + int +mch_can_restore_icon() +{ +#ifdef USE_GUI + /* + * If GUI is (going to be) used, we can always set the icon name. + * Saves a bit of time, because the X11 display server does not need to be + * contacted. + */ + if (gui.starting || gui.in_use) + return TRUE; +#endif + return get_x11_icon(TRUE); +} + +/* + * Set the window title and icon. + * Currently only works for x11. + */ + void +mch_settitle(title, icon) + char_u *title; + char_u *icon; +{ + int type = 0; + + if (term_strings[KS_NAME] == NULL) /* no terminal name (yet) */ + return; + if (title == NULL && icon == NULL) /* nothing to do */ + return; + +/* + * if the window ID and the display is known, we may use X11 calls + */ +#if defined(HAVE_X11) && defined(WANT_X11) + if (get_x11_windis() == OK) + type = 1; +#endif + + /* + * Note: if terminal is xterm, title is set with escape sequence rather + * than x11 calls, because the x11 calls don't always work + * Check only if the start of the terminal name is "xterm", also catch + * "xterms". + */ + if (is_xterm(term_strings[KS_NAME])) + type = 2; + + if (is_iris_ansi(term_strings[KS_NAME])) + type = 3; + + if (type) + { + if (title != NULL) + { + if (oldtitle == NULL) /* first call, save title */ + (void)get_x11_title(FALSE); + + switch(type) + { +#if defined(HAVE_X11) && defined(WANT_X11) + case 1: set_x11_title(title); /* x11 */ + break; +#endif + case 2: outstrn((char_u *)"\033]2;"); /* xterm */ + outstrn(title); + outchar(Ctrl('G')); + flushbuf(); + break; + + case 3: outstrn((char_u *)"\033P1.y"); /* iris-ansi */ + outstrn(title); + outstrn((char_u *)"\234"); + flushbuf(); + break; + } + } + + if (icon != NULL) + { + if (oldicon == NULL) /* first call, save icon */ + get_x11_icon(FALSE); + + switch(type) + { +#if defined(HAVE_X11) && defined(WANT_X11) + case 1: set_x11_icon(icon); /* x11 */ + break; +#endif + case 2: outstrn((char_u *)"\033]1;"); /* xterm */ + outstrn(icon); + outchar(Ctrl('G')); + flushbuf(); + break; + + case 3: outstrn((char_u *)"\033P3.y"); /* iris-ansi */ + outstrn(icon); + outstrn((char_u *)"\234"); + flushbuf(); + break; + } + } + } +} + + int +is_xterm(name) + char_u *name; +{ + if (name == NULL) + return FALSE; + return (vim_strnicmp(name, (char_u *)"xterm", (size_t)5) == 0 || + STRCMP(name, "builtin_xterm") == 0); +} + + int +is_iris_ansi(name) + char_u *name; +{ + if (name == NULL) + return FALSE; + return (vim_strnicmp(name, (char_u *)"iris-ansi", (size_t)9) == 0 || + STRCMP(name, "builtin_iris-ansi") == 0); +} + +/* + * Return TRUE if "name" is a terminal for which 'ttyfast' should be set. + * This should include all windowed terminal emulators. + */ + int +is_fastterm(name) + char_u *name; +{ + if (name == NULL) + return FALSE; + if (is_xterm(name) || is_iris_ansi(name)) + return TRUE; + return (vim_strnicmp(name, (char_u *)"hpterm", (size_t)6) == 0 || + vim_strnicmp(name, (char_u *)"sun-cmd", (size_t)7) == 0 || + vim_strnicmp(name, (char_u *)"screen", (size_t)6) == 0 || + vim_strnicmp(name, (char_u *)"dtterm", (size_t)6) == 0); +} + +/* + * Restore the window/icon title. + * which is one of: + * 1 Just restore title + * 2 Just restore icon + * 3 Restore title and icon + */ + void +mch_restore_title(which) + int which; +{ + mch_settitle((which & 1) ? oldtitle : NULL, (which & 2) ? oldicon : NULL); +} + +/* + * Insert user name in s[len]. + * Return OK if a name found. + */ + int +mch_get_user_name(s, len) + char_u *s; + int len; +{ +#if defined(HAVE_PWD_H) && defined(HAVE_GETPWUID) + struct passwd *pw; +#endif + uid_t uid; + + uid = getuid(); +#if defined(HAVE_PWD_H) && defined(HAVE_GETPWUID) + if ((pw = getpwuid(uid)) != NULL && + pw->pw_name != NULL && *pw->pw_name != NUL) + { + STRNCPY(s, pw->pw_name, len); + return OK; + } +#endif + sprintf((char *)s, "%d", (int)uid); /* assumes s is long enough */ + return FAIL; /* a number is not a name */ +} + +/* + * Insert host name is s[len]. + */ + +#ifdef HAVE_SYS_UTSNAME_H + void +mch_get_host_name(s, len) + char_u *s; + int len; +{ + struct utsname vutsname; + + uname(&vutsname); + STRNCPY(s, vutsname.nodename, len); +} +#else /* HAVE_SYS_UTSNAME_H */ + +# ifdef HAVE_SYS_SYSTEMINFO_H +# define gethostname(nam, len) sysinfo(SI_HOSTNAME, nam, len) +# endif + + void +mch_get_host_name(s, len) + char_u *s; + int len; +{ + gethostname((char *)s, len); +} +#endif /* HAVE_SYS_UTSNAME_H */ + +/* + * return process ID + */ + long +mch_get_pid() +{ + return (long)getpid(); +} + +#if !defined(HAVE_STRERROR) && defined(USE_GETCWD) +static char *strerror __ARGS((int)); + + static char * +strerror(err) + int err; +{ + extern int sys_nerr; + extern char *sys_errlist[]; + static char er[20]; + + if (err > 0 && err < sys_nerr) + return (sys_errlist[err]); + sprintf(er, "Error %d", err); + return er; +} +#endif + +/* + * Get name of current directory into buffer 'buf' of length 'len' bytes. + * Return OK for success, FAIL for failure. + */ + int +mch_dirname(buf, len) + char_u *buf; + int len; +{ +#if defined(USE_GETCWD) + if (getcwd((char *)buf, len) == NULL) + { + STRCPY(buf, strerror(errno)); + return FAIL; + } + return OK; +#else + return (getwd((char *)buf) != NULL ? OK : FAIL); +#endif +} + +#ifdef __EMX__ +/* + * Replace all slashes by backslashes. + */ + static void +slash_adjust(p) + char_u *p; +{ + while (*p) + { + if (*p == '/') + *p = '\\'; + ++p; + } +} +#endif + +/* + * Get absolute filename into buffer 'buf' of length 'len' bytes. + * + * return FAIL for failure, OK for success + */ + int +FullName(fname, buf, len, force) + char_u *fname, *buf; + int len; + int force; /* also expand when already absolute path name */ +{ + int l; +#ifdef OS2 + int only_drive; /* only a drive letter is specified in file name */ +#endif +#ifdef HAVE_FCHDIR + int fd = -1; + static int dont_fchdir = FALSE; /* TRUE when fchdir() doesn't work */ +#endif + char_u olddir[MAXPATHL]; + char_u *p; + char_u c; + int retval = OK; + + if (fname == NULL) /* always fail */ + { + *buf = NUL; + return FAIL; + } + + *buf = 0; + if (force || !isFullName(fname)) /* if forced or not an absolute path */ + { + /* + * If the file name has a path, change to that directory for a moment, + * and then do the getwd() (and get back to where we were). + * This will get the correct path name with "../" things. + */ +#ifdef OS2 + only_drive = 0; + if (((p = vim_strrchr(fname, '/')) != NULL) || + ((p = vim_strrchr(fname, '\\')) != NULL) || + (((p = vim_strchr(fname, ':')) != NULL) && ++only_drive)) +#else + if ((p = vim_strrchr(fname, '/')) != NULL) +#endif + { +#ifdef HAVE_FCHDIR + /* + * Use fchdir() if possible, it's said to be faster and more + * reliable. But on SunOS 4 it might not work. Check this by + * doing a fchdir() right now. + */ + if (!dont_fchdir) + { + fd = open(".", O_RDONLY | O_EXTRA); + if (fd >= 0 && fchdir(fd) < 0) + { + close(fd); + fd = -1; + dont_fchdir = TRUE; /* don't try again */ + } + } +#endif + if ( +#ifdef HAVE_FCHDIR + fd < 0 && +#endif + mch_dirname(olddir, MAXPATHL) == FAIL) + { + p = NULL; /* can't get current dir: don't chdir */ + retval = FAIL; + } + else + { +#ifdef OS2 + /* + * compensate for case where ':' from "D:" was the only + * path separator detected in the file name; the _next_ + * character has to be removed, and then restored later. + */ + if (only_drive) + p++; +#endif + c = *p; + *p = NUL; + if (vim_chdir((char *)fname)) + retval = FAIL; + else + fname = p + 1; + *p = c; +#ifdef OS2 + if (only_drive) + { + p--; + if (retval != FAIL) + fname--; + } +#endif + } + } + if (mch_dirname(buf, len) == FAIL) + { + retval = FAIL; + *buf = NUL; + } + l = STRLEN(buf); + if (l && buf[l - 1] != '/') + STRCAT(buf, "/"); + if (p != NULL) + { +#ifdef HAVE_FCHDIR + if (fd >= 0) + { + fchdir(fd); + close(fd); + } + else +#endif + vim_chdir((char *)olddir); + } + } + STRCAT(buf, fname); +#ifdef OS2 + slash_adjust(buf); +#endif + return retval; +} + +/* + * return TRUE is fname is an absolute path name + */ + int +isFullName(fname) + char_u *fname; +{ +#ifdef __EMX__ + return _fnisabs(fname); +#else + return (*fname == '/' || *fname == '~'); +#endif +} + +/* + * get file permissions for 'name' + */ + long +getperm(name) + char_u *name; +{ + struct stat statb; + + if (stat((char *)name, &statb)) + return -1; + return statb.st_mode; +} + +/* + * set file permission for 'name' to 'perm' + * + * return FAIL for failure, OK otherwise + */ + int +setperm(name, perm) + char_u *name; + int perm; +{ + return (chmod((char *)name, (mode_t)perm) == 0 ? OK : FAIL); +} + +/* + * return TRUE if "name" is a directory + * return FALSE if "name" is not a directory + * return FALSE for error + */ + int +mch_isdir(name) + char_u *name; +{ + struct stat statb; + + if (stat((char *)name, &statb)) + return FALSE; +#ifdef _POSIX_SOURCE + return (S_ISDIR(statb.st_mode) ? TRUE : FALSE); +#else + return ((statb.st_mode & S_IFMT) == S_IFDIR ? TRUE : FALSE); +#endif +} + + void +mch_windexit(r) + int r; +{ + settmode(0); + exiting = TRUE; + mch_settitle(oldtitle, oldicon); /* restore xterm title */ + stoptermcap(); + flushbuf(); + ml_close_all(TRUE); /* remove all memfiles */ + may_core_dump(); + exit(r); +} + + static void +may_core_dump() +{ +#ifdef SIGQUIT + signal(SIGQUIT, SIG_DFL); + if (core_dump) + kill(getpid(), SIGQUIT); /* force a core dump */ +#endif +} + +static int curr_tmode = 0; /* contains current raw/cooked mode (0 = cooked) */ + + void +mch_settmode(raw) + int raw; +{ + static int first = TRUE; + + /* Why is NeXT excluded here (and not in unixunix.h)? */ +#if defined(ECHOE) && defined(ICANON) && (defined(HAVE_TERMIO_H) || defined(HAVE_TERMIOS_H)) && !defined(__NeXT__) + /* for "new" tty systems */ +# ifdef HAVE_TERMIOS_H + static struct termios told; + struct termios tnew; +# else + static struct termio told; + struct termio tnew; +# endif + +# ifdef TIOCLGET + static unsigned long tty_local; +# endif + + if (raw) + { + if (first) + { + first = FALSE; +# ifdef TIOCLGET + ioctl(0, TIOCLGET, &tty_local); +# endif +# if defined(HAVE_TERMIOS_H) + tcgetattr(0, &told); +# else + ioctl(0, TCGETA, &told); +# endif + } + tnew = told; + /* + * ICRNL enables typing ^V^M + */ + tnew.c_iflag &= ~ICRNL; + tnew.c_lflag &= ~(ICANON | ECHO | ISIG | ECHOE +# if defined(IEXTEN) && !defined(MINT) + | IEXTEN /* IEXTEN enables typing ^V on SOLARIS */ + /* but it breaks function keys on MINT */ +# endif + ); + tnew.c_cc[VMIN] = 1; /* return after 1 char */ + tnew.c_cc[VTIME] = 0; /* don't wait */ +# if defined(HAVE_TERMIOS_H) + tcsetattr(0, TCSANOW, &tnew); +# else + ioctl(0, TCSETA, &tnew); +# endif + } + else + { +# if defined(HAVE_TERMIOS_H) + tcsetattr(0, TCSANOW, &told); +# else + ioctl(0, TCSETA, &told); +# endif +# ifdef TIOCLGET + ioctl(0, TIOCLSET, &tty_local); +# endif + } +#else +# ifndef TIOCSETN +# define TIOCSETN TIOCSETP /* for hpux 9.0 */ +# endif + /* for "old" tty systems */ + static struct sgttyb ttybold; + struct sgttyb ttybnew; + + if (raw) + { + if (first) + { + first = FALSE; + ioctl(0, TIOCGETP, &ttybold); + } + ttybnew = ttybold; + ttybnew.sg_flags &= ~(CRMOD | ECHO); + ttybnew.sg_flags |= RAW; + ioctl(0, TIOCSETN, &ttybnew); + } + else + ioctl(0, TIOCSETN, &ttybold); +#endif + curr_tmode = raw; +} + +/* + * Try to get the code for "t_kb" from the stty setting + * + * Even if termcap claims a backspace key, the user's setting *should* + * prevail. stty knows more about reality than termcap does, and if + * somebody's usual erase key is DEL (which, for most BSD users, it will + * be), they're going to get really annoyed if their erase key starts + * doing forward deletes for no reason. (Eric Fischer) + */ + void +get_stty() +{ + char_u buf[2]; + char_u *p; + + /* Why is NeXT excluded here (and not in unixunix.h)? */ +#if defined(ECHOE) && defined(ICANON) && (defined(HAVE_TERMIO_H) || defined(HAVE_TERMIOS_H)) && !defined(__NeXT__) + /* for "new" tty systems */ +# ifdef HAVE_TERMIOS_H + struct termios keys; +# else + struct termio keys; +# endif + +# if defined(HAVE_TERMIOS_H) + if (tcgetattr(0, &keys) != -1) +# else + if (ioctl(0, TCGETA, &keys) != -1) +# endif + { + buf[0] = keys.c_cc[VERASE]; +#else + /* for "old" tty systems */ + struct sgttyb keys; + + if (ioctl(0, TIOCGETP, &keys) != -1) + { + buf[0] = keys.sg_erase; +#endif + buf[1] = NUL; + add_termcode((char_u *)"kb", buf); + + /* + * If <BS> and <DEL> are now the same, redefine <DEL>. + */ + p = find_termcode((char_u *)"kD"); + if (p != NULL && p[0] == buf[0] && p[1] == buf[1]) + do_fixdel(); + } +#if 0 + } /* to keep cindent happy */ +#endif +} + +#ifdef USE_MOUSE +/* + * set mouse clicks on or off (only works for xterms) + */ + void +mch_setmouse(on) + int on; +{ + static int ison = FALSE; + + if (on == ison) /* return quickly if nothing to do */ + return; + + if (is_xterm(term_strings[KS_NAME])) + { + if (on) + outstrn((char_u *)"\033[?1000h"); /* xterm: enable mouse events */ + else + outstrn((char_u *)"\033[?1000l"); /* xterm: disable mouse events */ + } + ison = on; +} +#endif + +/* + * set screen mode, always fails. + */ + int +mch_screenmode(arg) + char_u *arg; +{ + EMSG("Screen mode setting not supported"); + return FAIL; +} + +/* + * Try to get the current window size: + * 1. with an ioctl(), most accurate method + * 2. from the environment variables LINES and COLUMNS + * 3. from the termcap + * 4. keep using the old values + */ + int +mch_get_winsize() +{ + int old_Rows = Rows; + int old_Columns = Columns; + char_u *p; + +#ifdef USE_GUI + if (gui.in_use) + return gui_mch_get_winsize(); +#endif + + Columns = 0; + Rows = 0; + +/* + * For OS/2 use _scrsize(). + */ +# ifdef __EMX__ + { + int s[2]; + _scrsize(s); + Columns = s[0]; + Rows = s[1]; + } +# endif + +/* + * 1. try using an ioctl. It is the most accurate method. + * + * Try using TIOCGWINSZ first, some systems that have it also define TIOCGSIZE + * but don't have a struct ttysize. + */ +# ifdef TIOCGWINSZ + { + struct winsize ws; + + if (ioctl(0, TIOCGWINSZ, &ws) == 0) + { + Columns = ws.ws_col; + Rows = ws.ws_row; + } + } +# else /* TIOCGWINSZ */ +# ifdef TIOCGSIZE + { + struct ttysize ts; + + if (ioctl(0, TIOCGSIZE, &ts) == 0) + { + Columns = ts.ts_cols; + Rows = ts.ts_lines; + } + } +# endif /* TIOCGSIZE */ +# endif /* TIOCGWINSZ */ + +/* + * 2. get size from environment + */ + if (Columns == 0 || Rows == 0) + { + if ((p = (char_u *)getenv("LINES"))) + Rows = atoi((char *)p); + if ((p = (char_u *)getenv("COLUMNS"))) + Columns = atoi((char *)p); + } + +#ifdef HAVE_TGETENT +/* + * 3. try reading the termcap + */ + if (Columns == 0 || Rows == 0) + getlinecol(); /* get "co" and "li" entries from termcap */ +#endif + +/* + * 4. If everything fails, use the old values + */ + if (Columns <= 0 || Rows <= 0) + { + Columns = old_Columns; + Rows = old_Rows; + return FAIL; + } + + check_winsize(); + +/* if size changed: screenalloc will allocate new screen buffers */ + return OK; +} + + void +mch_set_winsize() +{ + char_u string[10]; + +#ifdef USE_GUI + if (gui.in_use) + { + gui_mch_set_winsize(); + return; + } +#endif + + /* try to set the window size to Rows and Columns */ + if (is_iris_ansi(term_strings[KS_NAME])) + { + sprintf((char *)string, "\033[203;%ld;%ld/y", Rows, Columns); + outstrn(string); + flushbuf(); + screen_start(); /* don't know where cursor is now */ + } +} + + int +call_shell(cmd, options) + char_u *cmd; + int options; /* SHELL_FILTER if called by do_filter() */ + /* SHELL_COOKED if term needs cooked mode */ + /* SHELL_EXPAND if called by ExpandWildCards() */ +{ +#ifdef USE_SYSTEM /* use system() to start the shell: simple but slow */ + + int x; +#ifndef __EMX__ + char_u newcmd[1024]; /* only needed for unix */ +#else /* __EMX__ */ + /* + * Set the preferred shell in the EMXSHELL environment variable (but + * only if it is different from what is already in the environment). + * Emx then takes care of whether to use "/c" or "-c" in an + * intelligent way. Simply pass the whole thing to emx's system() call. + * Emx also starts an interactive shell if system() is passed an empty + * string. + */ + char_u *p, *old; + + if (((old = getenv("EMXSHELL")) == NULL) || strcmp(old, p_sh)) + { + /* should check HAVE_SETENV, but I know we don't have it. */ + p = alloc(10 + strlen(p_sh)); + if (p) + { + sprintf(p, "EMXSHELL=%s", p_sh); + putenv(p); /* don't free the pointer! */ + } + } +#endif + + flushbuf(); + + if (options & SHELL_COOKED) + settmode(0); /* set to cooked mode */ + +#ifdef __EMX__ + if (cmd == NULL) + x = system(""); /* this starts an interactive shell in emx */ + else + x = system(cmd); + if (x == -1) /* system() returns -1 when error occurs in starting shell */ + { + MSG_OUTSTR("\nCannot execute shell "); + msg_outstr(p_sh); + msg_outchar('\n'); + } +#else /* not __EMX__ */ + if (cmd == NULL) + x = system(p_sh); + else + { + sprintf(newcmd, "%s %s -c \"%s\"", p_sh, + extra_shell_arg == NULL ? "" : (char *)extra_shell_arg, + (char *)cmd); + x = system(newcmd); + } + if (x == 127) + { + MSG_OUTSTR("\nCannot execute shell sh\n"); + } +#endif /* __EMX__ */ + else if (x && !expand_interactively) + { + msg_outchar('\n'); + msg_outnum((long)x); + MSG_OUTSTR(" returned\n"); + } + + settmode(1); /* set to raw mode */ +#ifdef OS2 + /* external command may change the window size in OS/2, so check it */ + mch_get_winsize(); +#endif + resettitle(); + return (x ? FAIL : OK); + +#else /* USE_SYSTEM */ /* don't use system(), use fork()/exec() */ + +#define EXEC_FAILED 122 /* Exit code when shell didn't execute. Don't use + 127, some shell use that already */ + + char_u newcmd[1024]; + int pid; +#ifdef HAVE_UNION_WAIT + union wait status; +#else + int status = -1; +#endif + int retval = FAIL; + char **argv = NULL; + int argc; + int i; + char_u *p; + int inquote; +#ifdef USE_GUI + int pty_master_fd = -1; /* for pty's */ + int pty_slave_fd = -1; + char *tty_name; + int fd_toshell[2]; /* for pipes */ + int fd_fromshell[2]; + int pipe_error = FALSE; +# ifdef HAVE_SETENV + char envbuf[50]; +# else + static char envbuf_Rows[20]; + static char envbuf_Columns[20]; +# endif +#endif + int did_settmode = FALSE; /* TRUE when settmode(1) called */ + + flushbuf(); + if (options & SHELL_COOKED) + settmode(0); /* set to cooked mode */ + + /* + * 1: find number of arguments + * 2: separate them and built argv[] + */ + STRCPY(newcmd, p_sh); + for (i = 0; i < 2; ++i) + { + p = newcmd; + inquote = FALSE; + argc = 0; + for (;;) + { + if (i == 1) + argv[argc] = (char *)p; + ++argc; + while (*p && (inquote || (*p != ' ' && *p != TAB))) + { + if (*p == '"') + inquote = !inquote; + ++p; + } + if (*p == NUL) + break; + if (i == 1) + *p++ = NUL; + p = skipwhite(p); + } + if (i == 0) + { + argv = (char **)alloc((unsigned)((argc + 4) * sizeof(char *))); + if (argv == NULL) /* out of memory */ + goto error; + } + } + if (cmd != NULL) + { + if (extra_shell_arg != NULL) + argv[argc++] = (char *)extra_shell_arg; + argv[argc++] = "-c"; + argv[argc++] = (char *)cmd; + } + argv[argc] = NULL; + +#ifdef tower32 + /* + * reap lost children (seems necessary on NCR Tower, + * although I don't have a clue why...) (Slootman) + */ + while (wait(&status) != 0 && errno != ECHILD) + ; /* do it again, if necessary */ +#endif + +#ifdef USE_GUI +/* + * First try at using a pseudo-tty to get the stdin/stdout of the executed + * command into the current window for the GUI. + */ + + if (gui.in_use && show_shell_mess) + { + /* + * Try to open a master pty. + * If this works, open the slave pty. + * If the slave can't be opened, close the master pty. + */ + if (p_guipty) + { + pty_master_fd = OpenPTY(&tty_name); /* open pty */ + if (pty_master_fd >= 0 && ((pty_slave_fd = + open(tty_name, O_RDWR | O_EXTRA)) < 0)) + { + close(pty_master_fd); + pty_master_fd = -1; + } + } + /* + * If opening a pty didn't work, try using pipes. + */ + if (pty_master_fd < 0) + { + pipe_error = (pipe(fd_toshell) < 0); + if (!pipe_error) /* pipe create OK */ + { + pipe_error = (pipe(fd_fromshell) < 0); + if (pipe_error) /* pipe create failed */ + { + close(fd_toshell[0]); + close(fd_toshell[1]); + } + } + if (pipe_error) + { + MSG_OUTSTR("\nCannot create pipes\n"); + flushbuf(); + } + } + } + + if (!pipe_error) /* pty or pipe opened or not used */ +#endif + + { + if ((pid = fork()) == -1) /* maybe we should use vfork() */ + { + MSG_OUTSTR("\nCannot fork\n"); +#ifdef USE_GUI + if (gui.in_use && show_shell_mess) + { + if (pty_master_fd >= 0) /* close the pseudo tty */ + { + close(pty_master_fd); + close(pty_slave_fd); + } + else /* close the pipes */ + { + close(fd_toshell[0]); + close(fd_toshell[1]); + close(fd_fromshell[0]); + close(fd_fromshell[1]); + } + } +#endif + } + else if (pid == 0) /* child */ + { + reset_signals(); /* handle signals normally */ + if (!show_shell_mess) + { + int fd; + + /* + * Don't want to show any message from the shell. Can't just + * close stdout and stderr though, because some systems will + * break if you try to write to them after that, so we must + * use dup() to replace them with something else -- webb + */ + fd = open("/dev/null", O_WRONLY | O_EXTRA); + fclose(stdout); + fclose(stderr); + + /* + * If any of these open()'s and dup()'s fail, we just continue + * anyway. It's not fatal, and on most systems it will make + * no difference at all. On a few it will cause the execvp() + * to exit with a non-zero status even when the completion + * could be done, which is nothing too serious. If the open() + * or dup() failed we'd just do the same thing ourselves + * anyway -- webb + */ + if (fd >= 0) + { + /* To replace stdout (file descriptor 1) */ + dup(fd); + + /* To replace stderr (file descriptor 2) */ + dup(fd); + + /* Don't need this now that we've duplicated it */ + close(fd); + } + } +#ifdef USE_GUI + else if (gui.in_use) + { + +#ifdef HAVE_SETSID + (void)setsid(); +#endif +#ifdef TIOCSCTTY + /* try to become controlling tty (probably doesn't work, + * unless run by root) */ + ioctl(pty_slave_fd, TIOCSCTTY, (char *)NULL); +#endif + /* Simulate to have a dumb terminal (for now) */ +#ifdef HAVE_SETENV + setenv("TERM", "dumb", 1); + sprintf((char *)envbuf, "%ld", Rows); + setenv("ROWS", (char *)envbuf, 1); + sprintf((char *)envbuf, "%ld", Columns); + setenv("COLUMNS", (char *)envbuf, 1); +#else + /* + * Putenv does not copy the string, it has to remain valid. + * Use a static array to avoid loosing allocated memory. + */ + putenv("TERM=dumb"); + sprintf(envbuf_Rows, "ROWS=%ld", Rows); + putenv(envbuf_Rows); + sprintf(envbuf_Columns, "COLUMNS=%ld", Columns); + putenv(envbuf_Columns); +#endif + + if (pty_master_fd >= 0) + { + close(pty_master_fd); /* close master side of pty */ + + /* set up stdin/stdout/stderr for the child */ + close(0); + dup(pty_slave_fd); + close(1); + dup(pty_slave_fd); + close(2); + dup(pty_slave_fd); + + close(pty_slave_fd); /* has been dupped, close it now */ + } + else + { + /* set up stdin for the child */ + close(fd_toshell[1]); + close(0); + dup(fd_toshell[0]); + close(fd_toshell[0]); + + /* set up stdout for the child */ + close(fd_fromshell[0]); + close(1); + dup(fd_fromshell[1]); + close(fd_fromshell[1]); + + /* set up stderr for the child */ + close(2); + dup(1); + } + } +#endif + /* + * There is no type cast for the argv, because the type may be + * different on different machines. This may cause a warning + * message with strict compilers, don't worry about it. + */ + execvp(argv[0], argv); + exit(EXEC_FAILED); /* exec failed, return failure code */ + } + else /* parent */ + { + /* + * While child is running, ignore terminating signals. + */ + catch_signals(SIG_IGN); + +#ifdef USE_GUI + + /* + * For the GUI we redirect stdin, stdout and stderr to our window. + */ + if (gui.in_use && show_shell_mess) + { +#define BUFLEN 100 /* length for buffer, pseudo tty limit is 128 */ + char_u buffer[BUFLEN]; + int len; + int p_more_save; + int old_State; + int read_count; + int c; + int toshell_fd; + int fromshell_fd; + + if (pty_master_fd >= 0) + { + close(pty_slave_fd); /* close slave side of pty */ + fromshell_fd = pty_master_fd; + toshell_fd = dup(pty_master_fd); + } + else + { + close(fd_toshell[0]); + close(fd_fromshell[1]); + toshell_fd = fd_toshell[1]; + fromshell_fd = fd_fromshell[0]; + } + + /* + * Write to the child if there are typed characters. + * Read from the child if there are characters available. + * Repeat the reading a few times if more characters are + * available. Need to check for typed keys now and then, but + * not too often (delays when no chars are available). + * This loop is quit if no characters can be read from the pty + * (WaitForChar detected special condition), or there are no + * characters available and the child has exited. + * Only check if the child has exited when there is no more + * output. The child may exit before all the output has + * been printed. + * + * Currently this busy loops! + * This can probably dead-lock when the write blocks! + */ + p_more_save = p_more; + p_more = FALSE; + old_State = State; + State = EXTERNCMD; /* don't redraw at window resize */ + + for (;;) + { + /* + * Check if keys have been typed, write them to the child + * if there are any. Don't do this if we are expanding + * wild cards (would eat typeahead). + */ + if (!(options & SHELL_EXPAND) && + (len = mch_inchar(buffer, BUFLEN - 1, 10)) != 0) + { + /* + * For pipes: + * Check for CTRL-C: sent interrupt signal to child. + * Check for CTRL-D: EOF, close pipe to child. + */ + if (len == 1 && (pty_master_fd < 0 || cmd != NULL)) + { +#ifdef SIGINT + if (buffer[0] == Ctrl('C')) + { + /* Use both kill() and killpg(), in case one + * of the two fails */ + kill(pid, SIGINT); +# ifdef HAVE_KILLPG + killpg(0, SIGINT); +# endif + } +#endif + if (pty_master_fd < 0 && toshell_fd >= 0 && + buffer[0] == Ctrl('D')) + { + close(toshell_fd); + toshell_fd = -1; + } + } + + /* replace K_BS by <BS> and K_DEL by <DEL> */ + for (i = 0; i < len; ++i) + { + if (buffer[i] == CSI && len - i > 2) + { + c = TERMCAP2KEY(buffer[i + 1], buffer[i + 2]); + if (c == K_DEL || c == K_BS) + { + vim_memmove(buffer + i + 1, buffer + i + 3, + (size_t)(len - i - 2)); + if (c == K_DEL) + buffer[i] = DEL; + else + buffer[i] = Ctrl('H'); + len -= 2; + } + } + else if (buffer[i] == '\r') + buffer[i] = '\n'; + } + + /* + * For pipes: echo the typed characters. + * For a pty this does not seem to work. + */ + if (pty_master_fd < 0) + { + for (i = 0; i < len; ++i) + if (buffer[i] == '\n' || buffer[i] == '\b') + msg_outchar(buffer[i]); + else + msg_outtrans_len(buffer + i, 1); + windgoto(msg_row, msg_col); + flushbuf(); + } + + /* + * Write the characters to the child, unless EOF has + * been typed for pipes. Ignore errors. + */ + if (toshell_fd >= 0) + write(toshell_fd, (char *)buffer, (size_t)len); + } + + /* + * Check if the child has any characters to be printed. + * Read them and write them to our window. + * Repeat this a few times as long as there is something + * to do, avoid the 10ms wait for mch_inchar(). + * TODO: This should handle escape sequences. + */ + for (read_count = 0; read_count < 10 && + RealWaitForChar(fromshell_fd, 10); ++read_count) + { + len = read(fromshell_fd, (char *)buffer, + (size_t)BUFLEN); + if (len == 0) /* end of file */ + goto finished; + buffer[len] = NUL; + msg_outstr(buffer); + windgoto(msg_row, msg_col); + cursor_on(); + flushbuf(); + } + + /* + * Check if the child still exists when we finished + * outputting all characters. + */ + if (read_count == 0 && +#ifdef __NeXT__ + wait4(pid, &status, WNOHANG, (struct rusage *) 0) && +#else + waitpid(pid, &status, WNOHANG) && +#endif + WIFEXITED(status)) + break; + } +finished: + p_more = p_more_save; + State = old_State; + if (toshell_fd >= 0) + close(toshell_fd); + close(fromshell_fd); + } +#endif /* USE_GUI */ + + /* + * Wait until child has exited. + */ +#ifdef ECHILD + /* Don't stop waiting when a signal (e.g. SIGWINCH) is received. */ + while (wait(&status) == -1 && errno != ECHILD) + ; +#else + wait(&status); +#endif + /* + * Set to raw mode right now, otherwise a CTRL-C after + * catch_signals will kill Vim. + */ + settmode(1); + did_settmode = TRUE; + catch_signals(deathtrap); + + /* + * Check the window size, in case it changed while executing the + * external command. + */ + mch_get_winsize(); + + if (WIFEXITED(status)) + { + i = WEXITSTATUS(status); + if (i) + { + if (i == EXEC_FAILED) + { + MSG_OUTSTR("\nCannot execute shell "); + msg_outtrans(p_sh); + msg_outchar('\n'); + } + else if (!expand_interactively) + { + msg_outchar('\n'); + msg_outnum((long)i); + MSG_OUTSTR(" returned\n"); + } + } + else + retval = OK; + } + else + MSG_OUTSTR("\nCommand terminated\n"); + } + } + vim_free(argv); + +error: + if (!did_settmode) + settmode(1); /* always set to raw mode */ + resettitle(); + + return retval; + +#endif /* USE_SYSTEM */ +} + +/* + * The input characters are buffered to be able to check for a CTRL-C. + * This should be done with signals, but I don't know how to do that in + * a portable way for a tty in RAW mode. + */ + +/* + * Internal typeahead buffer. Includes extra space for long key code + * descriptions which would otherwise overflow. The buffer is considered full + * when only this extra space (or part of it) remains. + */ +#define INBUFLEN 250 + +static char_u inbuf[INBUFLEN + MAX_KEY_CODE_LEN]; +static int inbufcount = 0; /* number of chars in inbuf[] */ + +/* + * is_input_buf_full(), is_input_buf_empty(), add_to_input_buf(), and + * trash_input_buf() are functions for manipulating the input buffer. These + * are used by the gui_* calls when a GUI is used to handle keyboard input. + * + * NOTE: These functions will be identical in msdos.c etc, and should probably + * be taken out and put elsewhere, but at the moment inbuf is only local. + */ + + int +is_input_buf_full() +{ + return (inbufcount >= INBUFLEN); +} + + int +is_input_buf_empty() +{ + return (inbufcount == 0); +} + +/* Add the given bytes to the input buffer */ + void +add_to_input_buf(s, len) + char_u *s; + int len; +{ + if (inbufcount + len > INBUFLEN + MAX_KEY_CODE_LEN) + return; /* Shouldn't ever happen! */ + + while (len--) + inbuf[inbufcount++] = *s++; +} + +/* Remove everything from the input buffer. Called when ^C is found */ + void +trash_input_buf() +{ + inbufcount = 0; +} + + static int +Read(buf, maxlen) + char_u *buf; + long maxlen; +{ + if (inbufcount == 0) /* if the buffer is empty, fill it */ + fill_inbuf(TRUE); + if (maxlen > inbufcount) + maxlen = inbufcount; + vim_memmove(buf, inbuf, (size_t)maxlen); + inbufcount -= maxlen; + if (inbufcount) + vim_memmove(inbuf, inbuf + maxlen, (size_t)inbufcount); + return (int)maxlen; +} + + void +mch_breakcheck() +{ +#ifdef USE_GUI + if (gui.in_use) + { + gui_mch_update(); + return; + } +#endif /* USE_GUI */ + +/* + * Check for CTRL-C typed by reading all available characters. + * In cooked mode we should get SIGINT, no need to check. + */ + if (curr_tmode && RealWaitForChar(0, 0L)) /* if characters available */ + fill_inbuf(FALSE); +} + + static void +fill_inbuf(exit_on_error) + int exit_on_error; +{ + int len; + int try; + +#ifdef USE_GUI + if (gui.in_use) + { + gui_mch_update(); + return; + } +#endif + if (is_input_buf_full()) + return; + /* + * Fill_inbuf() is only called when we really need a character. + * If we can't get any, but there is some in the buffer, just return. + * If we can't get any, and there isn't any in the buffer, we give up and + * exit Vim. + */ + for (try = 0; try < 100; ++try) + { + len = read(0, (char *)inbuf + inbufcount, + (size_t)(INBUFLEN - inbufcount)); + if (len > 0) + break; + if (!exit_on_error) + return; + } + if (len <= 0) + { + windgoto((int)Rows - 1, 0); + fprintf(stderr, "Vim: Error reading input, exiting...\n"); + ml_sync_all(FALSE, TRUE); /* preserve all swap files */ + getout(1); + } + while (len-- > 0) + { + /* + * if a CTRL-C was typed, remove it from the buffer and set got_int + */ + if (inbuf[inbufcount] == 3) + { + /* remove everything typed before the CTRL-C */ + vim_memmove(inbuf, inbuf + inbufcount, (size_t)(len + 1)); + inbufcount = 0; + got_int = TRUE; + } + ++inbufcount; + } +} + +/* + * Wait "msec" msec until a character is available from the keyboard or from + * inbuf[]. msec == -1 will block forever. + * When a GUI is being used, this will never get called -- webb + */ + + static int +WaitForChar(msec) + long msec; +{ + if (inbufcount) /* something in inbuf[] */ + return 1; + return RealWaitForChar(0, msec); +} + +/* + * Wait "msec" msec until a character is available from file descriptor "fd". + * Time == -1 will block forever. + * When a GUI is being used, this will not be used for input -- webb + */ + static int +RealWaitForChar(fd, msec) + int fd; + long msec; +{ +#ifndef HAVE_SELECT + struct pollfd fds; + + fds.fd = fd; + fds.events = POLLIN; + return (poll(&fds, 1, (int)msec) > 0); /* is this correct when fd != 0?? */ +#else + struct timeval tv; + fd_set rfds, efds; + +# ifdef __EMX__ + /* don't check for incoming chars if not in raw mode, because select() + * always returns TRUE then (in some version of emx.dll) */ + if (curr_tmode == 0) + return 0; +# endif + + if (msec >= 0) + { + tv.tv_sec = msec / 1000; + tv.tv_usec = (msec % 1000) * (1000000/1000); + } + + /* + * Select on ready for reading and exceptional condition (end of file). + */ + FD_ZERO(&rfds); /* calls bzero() on a sun */ + FD_ZERO(&efds); + FD_SET(fd, &rfds); + FD_SET(fd, &efds); + return (select(fd + 1, &rfds, NULL, &efds, (msec >= 0) ? &tv : NULL) > 0); +#endif +} + +/* + * ExpandWildCards() - this code does wild-card pattern matching using the shell + * + * return OK for success, FAIL for error (you may lose some memory) and put + * an error message in *file. + * + * num_pat is number of input patterns + * pat is array of pointers to input patterns + * num_file is pointer to number of matched file names + * file is pointer to array of pointers to matched file names + * On Unix we do not check for files only yet + * list_notfound is ignored + */ + +extern char *mktemp __ARGS((char *)); +#ifndef SEEK_SET +# define SEEK_SET 0 +#endif +#ifndef SEEK_END +# define SEEK_END 2 +#endif + + int +ExpandWildCards(num_pat, pat, num_file, file, files_only, list_notfound) + int num_pat; + char_u **pat; + int *num_file; + char_u ***file; + int files_only; + int list_notfound; +{ + int i; + size_t len; + char_u *p; +#ifdef __EMX__ +# define EXPL_ALLOC_INC 16 + char_u **expl_files; + size_t files_alloced, files_free; + + *num_file = 0; /* default: no files found */ + files_alloced = EXPL_ALLOC_INC; /* how much space is allocated */ + files_free = EXPL_ALLOC_INC; /* how much space is not used */ + *file = (char_u **) alloc(sizeof(char_u **) * files_alloced); + if (!*file) + { + emsg(e_outofmem); + return FAIL; + } + + for (; num_pat > 0; num_pat--, pat++) + { + expl_files = NULL; + if (vim_strchr(*pat, '$') || vim_strchr(*pat, '~')) + { + /* expand environment var or home dir */ + char_u *buf = alloc(1024); + if (!buf) + { + emsg(e_outofmem); + return FAIL; + } + expand_env(*pat, buf, 1024); + if (mch_has_wildcard(buf)) /* still wildcards in there? */ + { + expl_files = (char_u **)_fnexplode(buf); + } + if (expl_files == NULL) + { + /* + * If no wildcard still remaining, simply add + * the pattern to the results. + * If wildcard did not match, add the pattern to + * the list of results anyway. This way, doing + * :n exist.c notexist* + * will at least edits exist.c and then say + * notexist* [new file] + */ + expl_files = (char_u **)alloc(sizeof(char_u **) * 2); + expl_files[0] = strsave(buf); + expl_files[1] = NULL; + } + vim_free(buf); + } + else + { + expl_files = (char_u **)_fnexplode(*pat); + if (expl_files == NULL) + { + /* see above for explanation */ + expl_files = (char_u **)alloc(sizeof(char_u **) * 2); + expl_files[0] = strsave(*pat); + expl_files[1] = NULL; + } + } + if (!expl_files) + { + /* Can't happen */ + char_u msg[128]; + sprintf(msg, "%s (unix.c:%d)", e_internal, __LINE__); + emsg(msg); + *file = (char_u **)""; + *num_file = 0; + return OK; + } + /* + * Count number of names resulting from expansion, + * At the same time add a backslash to the end of names that happen to be + * directories, and replace slashes with backslashes. + */ + for (i = 0; (p = expl_files[i]) != NULL; i++, (*num_file)++) + { + if (--files_free == 0) + { + /* need more room in table of pointers */ + files_alloced += EXPL_ALLOC_INC; + *file = (char_u **) realloc(*file, + sizeof(char_u **) * files_alloced); + files_free = EXPL_ALLOC_INC; + } + slash_adjust(p); + if (mch_isdir(p)) + { + len = strlen(p); + p = realloc(p, len + 2); + if (!p) + { + emsg(e_outofmem); + return FAIL; + } + (*file)[*num_file] = p; + p += len; + *p++ = '\\'; + *p = 0; + } + else + { + (*file)[*num_file] = strsave(p); + } + } + _fnexplodefree(expl_files); + } + return OK; + +#else /* __EMX__ */ + + int dir; + char_u tmpname[TMPNAMELEN]; + char_u *command; + FILE *fd; + char_u *buffer; + int use_glob = FALSE; + + *num_file = 0; /* default: no files found */ + *file = (char_u **)""; + + /* + * If there are no wildcards, just copy the names to allocated memory. + * Saves a lot of time, because we don't have to start a new shell. + */ + if (!have_wildcard(num_pat, pat)) + { + *file = (char_u **)alloc(num_pat * sizeof(char_u *)); + if (*file == NULL) + { + *file = (char_u **)""; + return FAIL; + } + for (i = 0; i < num_pat; i++) + (*file)[i] = strsave(pat[i]); + *num_file = num_pat; + return OK; + } + +/* + * get a name for the temp file + */ + STRCPY(tmpname, TMPNAME2); + if (*mktemp((char *)tmpname) == NUL) + { + emsg(e_notmp); + return FAIL; + } + +/* + * let the shell expand the patterns and write the result into the temp file + * If we use csh, glob will work better than echo. + */ + if ((len = STRLEN(p_sh)) >= 3 && STRCMP(p_sh + len - 3, "csh") == 0) + use_glob = TRUE; + + len = TMPNAMELEN + 11; + for (i = 0; i < num_pat; ++i) /* count the length of the patterns */ + len += STRLEN(pat[i]) + 3; + command = alloc(len); + if (command == NULL) + return FAIL; + if (use_glob) + STRCPY(command, "glob >"); /* build the shell command */ + else + STRCPY(command, "echo >"); /* build the shell command */ + STRCAT(command, tmpname); + for (i = 0; i < num_pat; ++i) + { +#ifdef USE_SYSTEM + STRCAT(command, " \""); /* need extra quotes because we */ + STRCAT(command, pat[i]); /* start the shell twice */ + STRCAT(command, "\""); +#else + STRCAT(command, " "); + STRCAT(command, pat[i]); +#endif + } + if (expand_interactively) + show_shell_mess = FALSE; + /* + * If we use -f then shell variables set in .cshrc won't get expanded. + * vi can do it, so we will too, but it is only necessary if there is a "$" + * in one of the patterns, otherwise we can still use the fast option. + */ + if (use_glob && !have_dollars(num_pat, pat)) /* Use csh fast option */ + extra_shell_arg = (char_u *)"-f"; + i = call_shell(command, SHELL_EXPAND); /* execute it */ + extra_shell_arg = NULL; + show_shell_mess = TRUE; + vim_free(command); + if (i == FAIL) /* call_shell failed */ + { + vim_remove(tmpname); + /* + * With interactive completion, the error message is not printed. + * However with USE_SYSTEM, I don't know how to turn off error messages + * from the shell, so screen may still get messed up -- webb. + */ +#ifndef USE_SYSTEM + if (!expand_interactively) +#endif + { + must_redraw = CLEAR; /* probably messed up screen */ + msg_outchar('\n'); /* clear bottom line quickly */ + cmdline_row = Rows - 1; /* continue on last line */ + } + return FAIL; + } + +/* + * read the names from the file into memory + */ + fd = fopen((char *)tmpname, "r"); + if (fd == NULL) + { + emsg2(e_notopen, tmpname); + return FAIL; + } + fseek(fd, 0L, SEEK_END); + len = ftell(fd); /* get size of temp file */ + fseek(fd, 0L, SEEK_SET); + buffer = alloc(len + 1); + if (buffer == NULL) + { + vim_remove(tmpname); + fclose(fd); + return FAIL; + } + i = fread((char *)buffer, 1, len, fd); + fclose(fd); + vim_remove(tmpname); + if (i != len) + { + emsg2(e_notread, tmpname); + vim_free(buffer); + return FAIL; + } + + if (use_glob) /* file names are separated with NUL */ + { + buffer[len] = NUL; /* make sure the buffers ends in NUL */ + i = 0; + for (p = buffer; p < buffer + len; ++p) + if (*p == NUL) /* count entry */ + ++i; + if (len) + ++i; /* count last entry */ + } + else /* file names are separated with SPACE */ + { + buffer[len] = '\n'; /* make sure the buffers ends in NL */ + p = buffer; + for (i = 0; *p != '\n'; ++i) /* count number of entries */ + { + while (*p != ' ' && *p != '\n') /* skip entry */ + ++p; + p = skipwhite(p); /* skip to next entry */ + } + } + if (i == 0) + { + /* + * Can happen when using /bin/sh and typing ":e $NO_SUCH_VAR^I". + * /bin/sh will happily expand it to nothing rather than returning an + * error; and hey, it's good to check anyway -- webb. + */ + vim_free(buffer); + *file = (char_u **)""; + return FAIL; + } + *num_file = i; + *file = (char_u **)alloc(sizeof(char_u *) * i); + if (*file == NULL) + { + vim_free(buffer); + *file = (char_u **)""; + return FAIL; + } + + /* + * Isolate the individual file names. + */ + p = buffer; + for (i = 0; i < *num_file; ++i) + { + (*file)[i] = p; + if (use_glob) + { + while (*p && p < buffer + len) /* skip entry */ + ++p; + ++p; /* skip NUL */ + } + else + { + while (*p != ' ' && *p != '\n') /* skip entry */ + ++p; + if (*p == '\n') /* last entry */ + *p = NUL; + else + { + *p++ = NUL; + p = skipwhite(p); /* skip to next entry */ + } + } + } + + /* + * Move the file names to allocated memory. + */ + for (i = 0; i < *num_file; ++i) + { + /* Require the files to exist. Helps when using /bin/sh */ + if (expand_interactively) + { + struct stat st; + int j; + + if (stat((char *)((*file)[i]), &st) < 0) + { + for (j = i; j + 1 < *num_file; ++j) + (*file)[j] = (*file)[j + 1]; + --*num_file; + --i; + continue; + } + } + + /* if file doesn't exist don't add '/' */ + dir = (mch_isdir((*file)[i])); + p = alloc((unsigned)(STRLEN((*file)[i]) + 1 + dir)); + if (p) + { + STRCPY(p, (*file)[i]); + if (dir) + STRCAT(p, "/"); + } + (*file)[i] = p; + } + vim_free(buffer); + + if (*num_file == 0) /* rejected all entries */ + { + vim_free(*file); + *file = (char_u **)""; + return FAIL; + } + + return OK; + +#endif /* __EMX__ */ +} + + int +mch_has_wildcard(p) + char_u *p; +{ + for ( ; *p; ++p) + { + if (*p == '\\' && p[1] != NUL) + ++p; + else if (vim_strchr((char_u *)"*?[{`~$", *p) != NULL) + return TRUE; + } + return FALSE; +} + +#ifndef __EMX__ + static int +have_wildcard(num, file) + int num; + char_u **file; +{ + register int i; + + for (i = 0; i < num; i++) + if (mch_has_wildcard(file[i])) + return 1; + return 0; +} + + static int +have_dollars(num, file) + int num; + char_u **file; +{ + register int i; + + for (i = 0; i < num; i++) + if (vim_strchr(file[i], '$') != NULL) + return TRUE; + return FALSE; +} +#endif /* ifndef __EMX__ */ + +#ifndef HAVE_RENAME +/* + * Scaled-down version of rename, which is missing in Xenix. + * This version can only move regular files and will fail if the + * destination exists. + */ + int +rename(src, dest) + const char *src, *dest; +{ + struct stat st; + + if (stat(dest, &st) >= 0) /* fail if destination exists */ + return -1; + if (link(src, dest) != 0) /* link file to new name */ + return -1; + if (vim_remove(src) == 0) /* delete link to old name */ + return 0; + return -1; +} +#endif /* !HAVE_RENAME */ |