1 1.36 rillig /* $NetBSD: edit.c,v 1.36 2024/10/03 20:14:01 rillig Exp $ */ 2 1.2 tls 3 1.1 jtc /* 4 1.1 jtc * Command line editing - common code 5 1.1 jtc * 6 1.1 jtc */ 7 1.11 agc #include <sys/cdefs.h> 8 1.11 agc 9 1.11 agc #ifndef lint 10 1.36 rillig __RCSID("$NetBSD: edit.c,v 1.36 2024/10/03 20:14:01 rillig Exp $"); 11 1.11 agc #endif 12 1.11 agc 13 1.32 kamil #include <stdbool.h> 14 1.1 jtc 15 1.1 jtc #include "config.h" 16 1.1 jtc #ifdef EDIT 17 1.1 jtc 18 1.1 jtc #include "sh.h" 19 1.1 jtc #include "tty.h" 20 1.1 jtc #define EXTERN 21 1.1 jtc #include "edit.h" 22 1.1 jtc #undef EXTERN 23 1.14 mycroft #include <sys/ioctl.h> 24 1.31 kamil #include <sys/stat.h> 25 1.1 jtc #include <ctype.h> 26 1.1 jtc 27 1.1 jtc 28 1.1 jtc #if defined(TIOCGWINSZ) 29 1.1 jtc static RETSIGTYPE x_sigwinch ARGS((int sig)); 30 1.1 jtc static int got_sigwinch; 31 1.1 jtc static void check_sigwinch ARGS((void)); 32 1.1 jtc #endif /* TIOCGWINSZ */ 33 1.1 jtc 34 1.1 jtc static int x_file_glob ARGS((int flags, const char *str, int slen, 35 1.1 jtc char ***wordsp)); 36 1.1 jtc static int x_command_glob ARGS((int flags, const char *str, int slen, 37 1.1 jtc char ***wordsp)); 38 1.1 jtc static int x_locate_word ARGS((const char *buf, int buflen, int pos, 39 1.1 jtc int *startp, int *is_command)); 40 1.1 jtc 41 1.1 jtc static char vdisable_c; 42 1.1 jtc 43 1.1 jtc 44 1.1 jtc /* Called from main */ 45 1.1 jtc void 46 1.1 jtc x_init() 47 1.1 jtc { 48 1.5 hubertf /* set to -2 to force initial binding */ 49 1.1 jtc edchars.erase = edchars.kill = edchars.intr = edchars.quit 50 1.5 hubertf = edchars.eof = -2; 51 1.1 jtc /* default value for deficient systems */ 52 1.1 jtc edchars.werase = 027; /* ^W */ 53 1.1 jtc 54 1.1 jtc #ifdef TIOCGWINSZ 55 1.1 jtc # ifdef SIGWINCH 56 1.1 jtc if (setsig(&sigtraps[SIGWINCH], x_sigwinch, SS_RESTORE_ORIG|SS_SHTRAP)) 57 1.1 jtc sigtraps[SIGWINCH].flags |= TF_SHELL_USES; 58 1.1 jtc # endif /* SIGWINCH */ 59 1.2 tls got_sigwinch = 1; /* force initial check */ 60 1.1 jtc check_sigwinch(); 61 1.1 jtc #endif /* TIOCGWINSZ */ 62 1.1 jtc 63 1.1 jtc #ifdef EMACS 64 1.1 jtc x_init_emacs(); 65 1.1 jtc #endif /* EMACS */ 66 1.1 jtc 67 1.1 jtc /* Bizarreness to figure out how to disable 68 1.1 jtc * a struct termios.c_cc[] char 69 1.1 jtc */ 70 1.1 jtc #ifdef _POSIX_VDISABLE 71 1.1 jtc if (_POSIX_VDISABLE >= 0) 72 1.1 jtc vdisable_c = (char) _POSIX_VDISABLE; 73 1.1 jtc else 74 1.1 jtc /* `feature not available' */ 75 1.1 jtc vdisable_c = (char) 0377; 76 1.1 jtc #else 77 1.1 jtc # if defined(HAVE_PATHCONF) && defined(_PC_VDISABLE) 78 1.1 jtc vdisable_c = fpathconf(tty_fd, _PC_VDISABLE); 79 1.1 jtc # else 80 1.1 jtc vdisable_c = (char) 0377; /* default to old BSD value */ 81 1.1 jtc # endif 82 1.1 jtc #endif /* _POSIX_VDISABLE */ 83 1.1 jtc } 84 1.1 jtc 85 1.1 jtc #if defined(TIOCGWINSZ) 86 1.1 jtc static RETSIGTYPE 87 1.1 jtc x_sigwinch(sig) 88 1.1 jtc int sig; 89 1.1 jtc { 90 1.1 jtc got_sigwinch = 1; 91 1.1 jtc return RETSIGVAL; 92 1.1 jtc } 93 1.1 jtc 94 1.1 jtc static void 95 1.1 jtc check_sigwinch ARGS((void)) 96 1.1 jtc { 97 1.1 jtc if (got_sigwinch) { 98 1.1 jtc struct winsize ws; 99 1.1 jtc 100 1.1 jtc got_sigwinch = 0; 101 1.1 jtc if (procpid == kshpid && ioctl(tty_fd, TIOCGWINSZ, &ws) >= 0) { 102 1.1 jtc struct tbl *vp; 103 1.1 jtc 104 1.1 jtc /* Do NOT export COLUMNS/LINES. Many applications 105 1.1 jtc * check COLUMNS/LINES before checking ws.ws_col/row, 106 1.1 jtc * so if the app is started with C/L in the environ 107 1.1 jtc * and the window is then resized, the app won't 108 1.1 jtc * see the change cause the environ doesn't change. 109 1.1 jtc */ 110 1.1 jtc if (ws.ws_col) { 111 1.1 jtc x_cols = ws.ws_col < MIN_COLS ? MIN_COLS 112 1.1 jtc : ws.ws_col; 113 1.1 jtc 114 1.1 jtc if ((vp = typeset("COLUMNS", 0, 0, 0, 0))) 115 1.1 jtc setint(vp, (long) ws.ws_col); 116 1.1 jtc } 117 1.1 jtc if (ws.ws_row 118 1.1 jtc && (vp = typeset("LINES", 0, 0, 0, 0))) 119 1.1 jtc setint(vp, (long) ws.ws_row); 120 1.1 jtc } 121 1.1 jtc } 122 1.1 jtc } 123 1.1 jtc #endif /* TIOCGWINSZ */ 124 1.1 jtc 125 1.1 jtc /* 126 1.1 jtc * read an edited command line 127 1.1 jtc */ 128 1.1 jtc int 129 1.1 jtc x_read(buf, len) 130 1.1 jtc char *buf; 131 1.1 jtc size_t len; 132 1.1 jtc { 133 1.1 jtc int i; 134 1.1 jtc 135 1.32 kamil x_mode(true); 136 1.1 jtc #ifdef EMACS 137 1.1 jtc if (Flag(FEMACS) || Flag(FGMACS)) 138 1.1 jtc i = x_emacs(buf, len); 139 1.1 jtc else 140 1.1 jtc #endif 141 1.1 jtc #ifdef VI 142 1.1 jtc if (Flag(FVI)) 143 1.1 jtc i = x_vi(buf, len); 144 1.1 jtc else 145 1.1 jtc #endif 146 1.1 jtc i = -1; /* internal error */ 147 1.32 kamil x_mode(false); 148 1.17 christos #if defined(TIOCGWINSZ) 149 1.17 christos if (got_sigwinch) 150 1.17 christos check_sigwinch(); 151 1.17 christos #endif /* TIOCGWINSZ */ 152 1.17 christos 153 1.1 jtc return i; 154 1.1 jtc } 155 1.1 jtc 156 1.1 jtc /* tty I/O */ 157 1.1 jtc 158 1.1 jtc int 159 1.1 jtc x_getc() 160 1.1 jtc { 161 1.1 jtc char c; 162 1.1 jtc int n; 163 1.1 jtc 164 1.1 jtc while ((n = blocking_read(0, &c, 1)) < 0 && errno == EINTR) 165 1.1 jtc if (trap) { 166 1.32 kamil x_mode(false); 167 1.1 jtc runtraps(0); 168 1.32 kamil x_mode(true); 169 1.1 jtc } 170 1.1 jtc if (n != 1) 171 1.1 jtc return -1; 172 1.1 jtc return (int) (unsigned char) c; 173 1.1 jtc } 174 1.1 jtc 175 1.1 jtc void 176 1.1 jtc x_flush() 177 1.1 jtc { 178 1.1 jtc shf_flush(shl_out); 179 1.1 jtc } 180 1.1 jtc 181 1.1 jtc void 182 1.1 jtc x_putc(c) 183 1.1 jtc int c; 184 1.1 jtc { 185 1.1 jtc shf_putc(c, shl_out); 186 1.1 jtc } 187 1.1 jtc 188 1.1 jtc void 189 1.1 jtc x_puts(s) 190 1.1 jtc const char *s; 191 1.1 jtc { 192 1.1 jtc while (*s != 0) 193 1.1 jtc shf_putc(*s++, shl_out); 194 1.1 jtc } 195 1.1 jtc 196 1.32 kamil bool 197 1.34 joerg x_mode(bool onoff) 198 1.1 jtc { 199 1.32 kamil static bool x_cur_mode; 200 1.32 kamil bool prev; 201 1.1 jtc 202 1.1 jtc if (x_cur_mode == onoff) 203 1.1 jtc return x_cur_mode; 204 1.1 jtc prev = x_cur_mode; 205 1.1 jtc x_cur_mode = onoff; 206 1.1 jtc 207 1.1 jtc if (onoff) { 208 1.1 jtc TTY_state cb; 209 1.1 jtc X_chars oldchars; 210 1.1 jtc 211 1.1 jtc oldchars = edchars; 212 1.1 jtc cb = tty_state; 213 1.1 jtc 214 1.1 jtc #if defined(HAVE_TERMIOS_H) || defined(HAVE_TERMIO_H) 215 1.1 jtc edchars.erase = cb.c_cc[VERASE]; 216 1.1 jtc edchars.kill = cb.c_cc[VKILL]; 217 1.1 jtc edchars.intr = cb.c_cc[VINTR]; 218 1.1 jtc edchars.quit = cb.c_cc[VQUIT]; 219 1.1 jtc edchars.eof = cb.c_cc[VEOF]; 220 1.1 jtc # ifdef VWERASE 221 1.1 jtc edchars.werase = cb.c_cc[VWERASE]; 222 1.1 jtc # endif 223 1.1 jtc # ifdef _CRAY2 /* brain-damaged terminal handler */ 224 1.1 jtc cb.c_lflag &= ~(ICANON|ECHO); 225 1.1 jtc /* rely on print routine to map '\n' to CR,LF */ 226 1.1 jtc # else 227 1.1 jtc cb.c_iflag &= ~(INLCR|ICRNL); 228 1.1 jtc # ifdef _BSD_SYSV /* need to force CBREAK instead of RAW (need CRMOD on output) */ 229 1.1 jtc cb.c_lflag &= ~(ICANON|ECHO); 230 1.1 jtc # else 231 1.1 jtc # ifdef SWTCH /* need CBREAK to handle swtch char */ 232 1.1 jtc cb.c_lflag &= ~(ICANON|ECHO); 233 1.1 jtc cb.c_lflag |= ISIG; 234 1.1 jtc cb.c_cc[VINTR] = vdisable_c; 235 1.1 jtc cb.c_cc[VQUIT] = vdisable_c; 236 1.1 jtc # else 237 1.1 jtc cb.c_lflag &= ~(ISIG|ICANON|ECHO); 238 1.1 jtc # endif 239 1.1 jtc # endif 240 1.1 jtc # ifdef VLNEXT 241 1.1 jtc /* osf/1 processes lnext when ~icanon */ 242 1.1 jtc cb.c_cc[VLNEXT] = vdisable_c; 243 1.1 jtc # endif /* VLNEXT */ 244 1.1 jtc # ifdef VDISCARD 245 1.1 jtc /* sunos 4.1.x & osf/1 processes discard(flush) when ~icanon */ 246 1.1 jtc cb.c_cc[VDISCARD] = vdisable_c; 247 1.1 jtc # endif /* VDISCARD */ 248 1.1 jtc cb.c_cc[VTIME] = 0; 249 1.1 jtc cb.c_cc[VMIN] = 1; 250 1.1 jtc # endif /* _CRAY2 */ 251 1.1 jtc #else 252 1.1 jtc /* Assume BSD tty stuff. */ 253 1.1 jtc edchars.erase = cb.sgttyb.sg_erase; 254 1.1 jtc edchars.kill = cb.sgttyb.sg_kill; 255 1.1 jtc cb.sgttyb.sg_flags &= ~ECHO; 256 1.1 jtc cb.sgttyb.sg_flags |= CBREAK; 257 1.1 jtc # ifdef TIOCGATC 258 1.1 jtc edchars.intr = cb.lchars.tc_intrc; 259 1.1 jtc edchars.quit = cb.lchars.tc_quitc; 260 1.1 jtc edchars.eof = cb.lchars.tc_eofc; 261 1.1 jtc edchars.werase = cb.lchars.tc_werasc; 262 1.1 jtc cb.lchars.tc_suspc = -1; 263 1.1 jtc cb.lchars.tc_dsuspc = -1; 264 1.1 jtc cb.lchars.tc_lnextc = -1; 265 1.1 jtc cb.lchars.tc_statc = -1; 266 1.1 jtc cb.lchars.tc_intrc = -1; 267 1.1 jtc cb.lchars.tc_quitc = -1; 268 1.1 jtc cb.lchars.tc_rprntc = -1; 269 1.1 jtc # else 270 1.1 jtc edchars.intr = cb.tchars.t_intrc; 271 1.1 jtc edchars.quit = cb.tchars.t_quitc; 272 1.1 jtc edchars.eof = cb.tchars.t_eofc; 273 1.1 jtc cb.tchars.t_intrc = -1; 274 1.1 jtc cb.tchars.t_quitc = -1; 275 1.1 jtc # ifdef TIOCGLTC 276 1.1 jtc edchars.werase = cb.ltchars.t_werasc; 277 1.1 jtc cb.ltchars.t_suspc = -1; 278 1.1 jtc cb.ltchars.t_dsuspc = -1; 279 1.1 jtc cb.ltchars.t_lnextc = -1; 280 1.1 jtc cb.ltchars.t_rprntc = -1; 281 1.1 jtc # endif 282 1.1 jtc # endif /* TIOCGATC */ 283 1.1 jtc #endif /* HAVE_TERMIOS_H || HAVE_TERMIO_H */ 284 1.1 jtc 285 1.1 jtc set_tty(tty_fd, &cb, TF_WAIT); 286 1.1 jtc 287 1.5 hubertf /* Convert unset values to internal `unset' value */ 288 1.5 hubertf if (edchars.erase == vdisable_c) 289 1.5 hubertf edchars.erase = -1; 290 1.5 hubertf if (edchars.kill == vdisable_c) 291 1.5 hubertf edchars.kill = -1; 292 1.5 hubertf if (edchars.intr == vdisable_c) 293 1.5 hubertf edchars.intr = -1; 294 1.5 hubertf if (edchars.quit == vdisable_c) 295 1.5 hubertf edchars.quit = -1; 296 1.5 hubertf if (edchars.eof == vdisable_c) 297 1.5 hubertf edchars.eof = -1; 298 1.5 hubertf if (edchars.werase == vdisable_c) 299 1.5 hubertf edchars.werase = -1; 300 1.1 jtc if (memcmp(&edchars, &oldchars, sizeof(edchars)) != 0) { 301 1.1 jtc #ifdef EMACS 302 1.1 jtc x_emacs_keys(&edchars); 303 1.1 jtc #endif 304 1.1 jtc } 305 1.5 hubertf } else { 306 1.1 jtc /* TF_WAIT doesn't seem to be necessary when leaving xmode */ 307 1.1 jtc set_tty(tty_fd, &tty_state, TF_NONE); 308 1.5 hubertf } 309 1.1 jtc 310 1.1 jtc return prev; 311 1.1 jtc } 312 1.1 jtc 313 1.1 jtc /* NAME: 314 1.1 jtc * promptlen - calculate the length of PS1 etc. 315 1.1 jtc * 316 1.1 jtc * DESCRIPTION: 317 1.1 jtc * This function is based on a fix from guy (at) demon.co.uk 318 1.14 mycroft * It fixes a bug in that if PS1 contains '!', the length 319 1.1 jtc * given by strlen() is probably wrong. 320 1.1 jtc * 321 1.1 jtc * RETURN VALUE: 322 1.1 jtc * length 323 1.1 jtc */ 324 1.1 jtc int 325 1.1 jtc promptlen(cp, spp) 326 1.1 jtc const char *cp; 327 1.1 jtc const char **spp; 328 1.1 jtc { 329 1.1 jtc int count = 0; 330 1.1 jtc const char *sp = cp; 331 1.2 tls char delimiter = 0; 332 1.2 tls int indelimit = 0; 333 1.2 tls 334 1.2 tls /* Undocumented AT&T ksh feature: 335 1.2 tls * If the second char in the prompt string is \r then the first char 336 1.2 tls * is taken to be a non-printing delimiter and any chars between two 337 1.2 tls * instances of the delimiter are not considered to be part of the 338 1.2 tls * prompt length 339 1.2 tls */ 340 1.2 tls if (*cp && cp[1] == '\r') { 341 1.2 tls delimiter = *cp; 342 1.2 tls cp += 2; 343 1.2 tls } 344 1.1 jtc 345 1.2 tls for (; *cp; cp++) { 346 1.2 tls if (indelimit && *cp != delimiter) 347 1.2 tls ; 348 1.2 tls else if (*cp == '\n' || *cp == '\r') { 349 1.1 jtc count = 0; 350 1.2 tls sp = cp + 1; 351 1.1 jtc } else if (*cp == '\t') { 352 1.1 jtc count = (count | 7) + 1; 353 1.1 jtc } else if (*cp == '\b') { 354 1.1 jtc if (count > 0) 355 1.1 jtc count--; 356 1.2 tls } else if (*cp == delimiter) 357 1.2 tls indelimit = !indelimit; 358 1.1 jtc else 359 1.1 jtc count++; 360 1.1 jtc } 361 1.1 jtc if (spp) 362 1.1 jtc *spp = sp; 363 1.1 jtc return count; 364 1.1 jtc } 365 1.1 jtc 366 1.1 jtc void 367 1.1 jtc set_editmode(ed) 368 1.1 jtc const char *ed; 369 1.1 jtc { 370 1.1 jtc static const enum sh_flag edit_flags[] = { 371 1.1 jtc #ifdef EMACS 372 1.1 jtc FEMACS, FGMACS, 373 1.1 jtc #endif 374 1.1 jtc #ifdef VI 375 1.1 jtc FVI, 376 1.1 jtc #endif 377 1.1 jtc }; 378 1.36 rillig const char *rcp; 379 1.22 lukem size_t i; 380 1.14 mycroft 381 1.1 jtc if ((rcp = ksh_strrchr_dirsep(ed))) 382 1.1 jtc ed = ++rcp; 383 1.1 jtc for (i = 0; i < NELEM(edit_flags); i++) 384 1.16 christos if (strstr(ed, goptions[(int) edit_flags[i]].name)) { 385 1.1 jtc change_flag(edit_flags[i], OF_SPECIAL, 1); 386 1.1 jtc return; 387 1.1 jtc } 388 1.1 jtc } 389 1.1 jtc 390 1.1 jtc /* ------------------------------------------------------------------------- */ 391 1.1 jtc /* Misc common code for vi/emacs */ 392 1.1 jtc 393 1.1 jtc /* Handle the commenting/uncommenting of a line. 394 1.1 jtc * Returns: 395 1.1 jtc * 1 if a carriage return is indicated (comment added) 396 1.1 jtc * 0 if no return (comment removed) 397 1.1 jtc * -1 if there is an error (not enough room for comment chars) 398 1.1 jtc * If successful, *lenp contains the new length. Note: cursor should be 399 1.1 jtc * moved to the start of the line after (un)commenting. 400 1.1 jtc */ 401 1.1 jtc int 402 1.1 jtc x_do_comment(buf, bsize, lenp) 403 1.1 jtc char *buf; 404 1.1 jtc int bsize; 405 1.1 jtc int *lenp; 406 1.1 jtc { 407 1.1 jtc int i, j; 408 1.1 jtc int len = *lenp; 409 1.1 jtc 410 1.1 jtc if (len == 0) 411 1.1 jtc return 1; /* somewhat arbitrary - it's what at&t ksh does */ 412 1.1 jtc 413 1.1 jtc /* Already commented? */ 414 1.1 jtc if (buf[0] == '#') { 415 1.1 jtc int saw_nl = 0; 416 1.1 jtc 417 1.1 jtc for (j = 0, i = 1; i < len; i++) { 418 1.1 jtc if (!saw_nl || buf[i] != '#') 419 1.1 jtc buf[j++] = buf[i]; 420 1.1 jtc saw_nl = buf[i] == '\n'; 421 1.1 jtc } 422 1.1 jtc *lenp = j; 423 1.1 jtc return 0; 424 1.1 jtc } else { 425 1.1 jtc int n = 1; 426 1.1 jtc 427 1.1 jtc /* See if there's room for the #'s - 1 per \n */ 428 1.1 jtc for (i = 0; i < len; i++) 429 1.1 jtc if (buf[i] == '\n') 430 1.1 jtc n++; 431 1.1 jtc if (len + n >= bsize) 432 1.1 jtc return -1; 433 1.1 jtc /* Now add them... */ 434 1.1 jtc for (i = len, j = len + n; --i >= 0; ) { 435 1.1 jtc if (buf[i] == '\n') 436 1.1 jtc buf[--j] = '#'; 437 1.1 jtc buf[--j] = buf[i]; 438 1.1 jtc } 439 1.1 jtc buf[0] = '#'; 440 1.1 jtc *lenp += n; 441 1.1 jtc return 1; 442 1.1 jtc } 443 1.1 jtc } 444 1.1 jtc 445 1.1 jtc /* ------------------------------------------------------------------------- */ 446 1.1 jtc /* Common file/command completion code for vi/emacs */ 447 1.1 jtc 448 1.1 jtc 449 1.16 christos static char *add_glob ARGS((const char *, int)); 450 1.16 christos static void glob_table ARGS((const char *, XPtrV *, struct table *)); 451 1.16 christos static void glob_path ARGS((int, const char *, XPtrV *, const char *)); 452 1.1 jtc 453 1.1 jtc void 454 1.1 jtc x_print_expansions(nwords, words, is_command) 455 1.1 jtc int nwords; 456 1.1 jtc char *const *words; 457 1.1 jtc int is_command; 458 1.1 jtc { 459 1.1 jtc int use_copy = 0; 460 1.1 jtc int prefix_len; 461 1.1 jtc XPtrV l; 462 1.1 jtc 463 1.20 christos l.beg = NULL; 464 1.20 christos 465 1.1 jtc /* Check if all matches are in the same directory (in this 466 1.8 provos * case, we want to omit the directory name) 467 1.1 jtc */ 468 1.1 jtc if (!is_command 469 1.1 jtc && (prefix_len = x_longest_prefix(nwords, words)) > 0) 470 1.1 jtc { 471 1.1 jtc int i; 472 1.1 jtc 473 1.1 jtc /* Special case for 1 match (prefix is whole word) */ 474 1.1 jtc if (nwords == 1) 475 1.1 jtc prefix_len = x_basename(words[0], (char *) 0); 476 1.1 jtc /* Any (non-trailing) slashes in non-common word suffixes? */ 477 1.1 jtc for (i = 0; i < nwords; i++) 478 1.1 jtc if (x_basename(words[i] + prefix_len, (char *) 0) 479 1.1 jtc > prefix_len) 480 1.1 jtc break; 481 1.1 jtc /* All in same directory? */ 482 1.1 jtc if (i == nwords) { 483 1.1 jtc while (prefix_len > 0 484 1.1 jtc && !ISDIRSEP(words[0][prefix_len - 1])) 485 1.1 jtc prefix_len--; 486 1.1 jtc use_copy = 1; 487 1.1 jtc XPinit(l, nwords + 1); 488 1.1 jtc for (i = 0; i < nwords; i++) 489 1.1 jtc XPput(l, words[i] + prefix_len); 490 1.1 jtc XPput(l, (char *) 0); 491 1.1 jtc } 492 1.1 jtc } 493 1.1 jtc 494 1.1 jtc /* 495 1.1 jtc * Enumerate expansions 496 1.1 jtc */ 497 1.1 jtc x_putc('\r'); 498 1.1 jtc x_putc('\n'); 499 1.8 provos pr_list(use_copy ? (char **) XPptrv(l) : words); 500 1.1 jtc 501 1.1 jtc if (use_copy) 502 1.1 jtc XPfree(l); /* not x_free_words() */ 503 1.1 jtc } 504 1.1 jtc 505 1.1 jtc /* 506 1.1 jtc * Do file globbing: 507 1.1 jtc * - appends * to (copy of) str if no globbing chars found 508 1.1 jtc * - does expansion, checks for no match, etc. 509 1.1 jtc * - sets *wordsp to array of matching strings 510 1.1 jtc * - returns number of matching strings 511 1.1 jtc */ 512 1.1 jtc static int 513 1.1 jtc x_file_glob(flags, str, slen, wordsp) 514 1.1 jtc int flags; 515 1.1 jtc const char *str; 516 1.1 jtc int slen; 517 1.1 jtc char ***wordsp; 518 1.1 jtc { 519 1.1 jtc char *toglob; 520 1.1 jtc char **words; 521 1.6 jdolecek int nwords, i, idx, escaping; 522 1.1 jtc XPtrV w; 523 1.1 jtc struct source *s, *sold; 524 1.1 jtc 525 1.1 jtc if (slen < 0) 526 1.1 jtc return 0; 527 1.1 jtc 528 1.1 jtc toglob = add_glob(str, slen); 529 1.1 jtc 530 1.6 jdolecek /* remove all escaping backward slashes */ 531 1.6 jdolecek escaping = 0; 532 1.6 jdolecek for(i = 0, idx = 0; toglob[i]; i++) { 533 1.6 jdolecek if (toglob[i] == '\\' && !escaping) { 534 1.6 jdolecek escaping = 1; 535 1.6 jdolecek continue; 536 1.6 jdolecek } 537 1.6 jdolecek 538 1.6 jdolecek toglob[idx] = toglob[i]; 539 1.6 jdolecek idx++; 540 1.6 jdolecek if (escaping) escaping = 0; 541 1.6 jdolecek } 542 1.6 jdolecek toglob[idx] = '\0'; 543 1.6 jdolecek 544 1.1 jtc /* 545 1.1 jtc * Convert "foo*" (toglob) to an array of strings (words) 546 1.1 jtc */ 547 1.1 jtc sold = source; 548 1.1 jtc s = pushs(SWSTR, ATEMP); 549 1.1 jtc s->start = s->str = toglob; 550 1.1 jtc source = s; 551 1.1 jtc if (yylex(ONEWORD) != LWORD) { 552 1.1 jtc source = sold; 553 1.1 jtc internal_errorf(0, "fileglob: substitute error"); 554 1.1 jtc return 0; 555 1.1 jtc } 556 1.1 jtc source = sold; 557 1.1 jtc XPinit(w, 32); 558 1.1 jtc expand(yylval.cp, &w, DOGLOB|DOTILDE|DOMARKDIRS); 559 1.1 jtc XPput(w, NULL); 560 1.1 jtc words = (char **) XPclose(w); 561 1.1 jtc 562 1.1 jtc for (nwords = 0; words[nwords]; nwords++) 563 1.1 jtc ; 564 1.1 jtc if (nwords == 1) { 565 1.1 jtc struct stat statb; 566 1.1 jtc 567 1.1 jtc /* Check if globbing failed (returned glob pattern), 568 1.1 jtc * but be careful (E.g. toglob == "ab*" when the file 569 1.1 jtc * "ab*" exists is not an error). 570 1.1 jtc * Also, check for empty result - happens if we tried 571 1.1 jtc * to glob something which evaluated to an empty 572 1.1 jtc * string (e.g., "$FOO" when there is no FOO, etc). 573 1.1 jtc */ 574 1.1 jtc if ((strcmp(words[0], toglob) == 0 575 1.1 jtc && stat(words[0], &statb) < 0) 576 1.1 jtc || words[0][0] == '\0') 577 1.1 jtc { 578 1.1 jtc x_free_words(nwords, words); 579 1.19 christos words = NULL; 580 1.1 jtc nwords = 0; 581 1.1 jtc } 582 1.1 jtc } 583 1.1 jtc afree(toglob, ATEMP); 584 1.1 jtc 585 1.18 christos if (nwords) { 586 1.18 christos *wordsp = words; 587 1.19 christos } else if (words) { 588 1.18 christos x_free_words(nwords, words); 589 1.18 christos *wordsp = NULL; 590 1.18 christos } 591 1.1 jtc return nwords; 592 1.1 jtc } 593 1.1 jtc 594 1.1 jtc /* Data structure used in x_command_glob() */ 595 1.1 jtc struct path_order_info { 596 1.1 jtc char *word; 597 1.1 jtc int base; 598 1.1 jtc int path_order; 599 1.1 jtc }; 600 1.1 jtc 601 1.14 mycroft static int path_order_cmp(const void *aa, const void *bb); 602 1.14 mycroft 603 1.1 jtc /* Compare routine used in x_command_glob() */ 604 1.1 jtc static int 605 1.1 jtc path_order_cmp(aa, bb) 606 1.1 jtc const void *aa; 607 1.1 jtc const void *bb; 608 1.1 jtc { 609 1.1 jtc const struct path_order_info *a = (const struct path_order_info *) aa; 610 1.1 jtc const struct path_order_info *b = (const struct path_order_info *) bb; 611 1.1 jtc int t; 612 1.1 jtc 613 1.1 jtc t = FILECMP(a->word + a->base, b->word + b->base); 614 1.1 jtc return t ? t : a->path_order - b->path_order; 615 1.1 jtc } 616 1.1 jtc 617 1.1 jtc static int 618 1.1 jtc x_command_glob(flags, str, slen, wordsp) 619 1.1 jtc int flags; 620 1.1 jtc const char *str; 621 1.1 jtc int slen; 622 1.1 jtc char ***wordsp; 623 1.1 jtc { 624 1.1 jtc char *toglob; 625 1.1 jtc char *pat; 626 1.1 jtc char *fpath; 627 1.1 jtc int nwords; 628 1.1 jtc XPtrV w; 629 1.1 jtc struct block *l; 630 1.1 jtc 631 1.1 jtc if (slen < 0) 632 1.1 jtc return 0; 633 1.1 jtc 634 1.1 jtc toglob = add_glob(str, slen); 635 1.1 jtc 636 1.1 jtc /* Convert "foo*" (toglob) to a pattern for future use */ 637 1.1 jtc pat = evalstr(toglob, DOPAT|DOTILDE); 638 1.1 jtc afree(toglob, ATEMP); 639 1.1 jtc 640 1.1 jtc XPinit(w, 32); 641 1.1 jtc 642 1.1 jtc glob_table(pat, &w, &keywords); 643 1.1 jtc glob_table(pat, &w, &aliases); 644 1.1 jtc glob_table(pat, &w, &builtins); 645 1.1 jtc for (l = e->loc; l; l = l->next) 646 1.1 jtc glob_table(pat, &w, &l->funs); 647 1.1 jtc 648 1.1 jtc glob_path(flags, pat, &w, path); 649 1.1 jtc if ((fpath = str_val(global("FPATH"))) != null) 650 1.1 jtc glob_path(flags, pat, &w, fpath); 651 1.1 jtc 652 1.1 jtc nwords = XPsize(w); 653 1.1 jtc 654 1.1 jtc if (!nwords) { 655 1.1 jtc *wordsp = (char **) 0; 656 1.1 jtc XPfree(w); 657 1.1 jtc return 0; 658 1.1 jtc } 659 1.1 jtc 660 1.1 jtc /* Sort entries */ 661 1.1 jtc if (flags & XCF_FULLPATH) { 662 1.1 jtc /* Sort by basename, then path order */ 663 1.1 jtc struct path_order_info *info; 664 1.1 jtc struct path_order_info *last_info = 0; 665 1.1 jtc char **words = (char **) XPptrv(w); 666 1.1 jtc int path_order = 0; 667 1.1 jtc int i; 668 1.1 jtc 669 1.1 jtc info = (struct path_order_info *) 670 1.1 jtc alloc(sizeof(struct path_order_info) * nwords, ATEMP); 671 1.1 jtc for (i = 0; i < nwords; i++) { 672 1.1 jtc info[i].word = words[i]; 673 1.1 jtc info[i].base = x_basename(words[i], (char *) 0); 674 1.1 jtc if (!last_info || info[i].base != last_info->base 675 1.1 jtc || FILENCMP(words[i], 676 1.1 jtc last_info->word, info[i].base) != 0) 677 1.1 jtc { 678 1.1 jtc last_info = &info[i]; 679 1.1 jtc path_order++; 680 1.1 jtc } 681 1.1 jtc info[i].path_order = path_order; 682 1.1 jtc } 683 1.1 jtc qsort(info, nwords, sizeof(struct path_order_info), 684 1.1 jtc path_order_cmp); 685 1.1 jtc for (i = 0; i < nwords; i++) 686 1.1 jtc words[i] = info[i].word; 687 1.1 jtc afree((void *) info, ATEMP); 688 1.1 jtc } else { 689 1.1 jtc /* Sort and remove duplicate entries */ 690 1.1 jtc char **words = (char **) XPptrv(w); 691 1.1 jtc int i, j; 692 1.1 jtc 693 1.1 jtc qsortp(XPptrv(w), (size_t) nwords, xstrcmp); 694 1.1 jtc 695 1.1 jtc for (i = j = 0; i < nwords - 1; i++) { 696 1.1 jtc if (strcmp(words[i], words[i + 1])) 697 1.1 jtc words[j++] = words[i]; 698 1.1 jtc else 699 1.1 jtc afree(words[i], ATEMP); 700 1.1 jtc } 701 1.1 jtc words[j++] = words[i]; 702 1.1 jtc nwords = j; 703 1.1 jtc w.cur = (void **) &words[j]; 704 1.1 jtc } 705 1.1 jtc 706 1.1 jtc XPput(w, NULL); 707 1.1 jtc *wordsp = (char **) XPclose(w); 708 1.1 jtc 709 1.1 jtc return nwords; 710 1.1 jtc } 711 1.1 jtc 712 1.8 provos #define IS_WORDC(c) !( ctype(c, C_LEX1) || (c) == '\'' || (c) == '"' \ 713 1.8 provos || (c) == '`' || (c) == '=' || (c) == ':' ) 714 1.1 jtc 715 1.1 jtc static int 716 1.1 jtc x_locate_word(buf, buflen, pos, startp, is_commandp) 717 1.1 jtc const char *buf; 718 1.1 jtc int buflen; 719 1.1 jtc int pos; 720 1.1 jtc int *startp; 721 1.1 jtc int *is_commandp; 722 1.1 jtc { 723 1.1 jtc int p; 724 1.1 jtc int start, end; 725 1.1 jtc 726 1.1 jtc /* Bad call? Probably should report error */ 727 1.1 jtc if (pos < 0 || pos > buflen) { 728 1.1 jtc *startp = pos; 729 1.1 jtc *is_commandp = 0; 730 1.1 jtc return 0; 731 1.1 jtc } 732 1.5 hubertf /* The case where pos == buflen happens to take care of itself... */ 733 1.1 jtc 734 1.1 jtc start = pos; 735 1.1 jtc /* Keep going backwards to start of word (has effect of allowing 736 1.1 jtc * one blank after the end of a word) 737 1.1 jtc */ 738 1.6 jdolecek for (; (start > 0 && IS_WORDC(buf[start - 1])) 739 1.6 jdolecek || (start > 1 && buf[start-2] == '\\'); start--) 740 1.1 jtc ; 741 1.1 jtc /* Go forwards to end of word */ 742 1.6 jdolecek for (end = start; end < buflen && IS_WORDC(buf[end]); end++) { 743 1.12 wiz if (buf[end] == '\\' && (end+1) < buflen) 744 1.6 jdolecek end++; 745 1.6 jdolecek } 746 1.1 jtc 747 1.1 jtc if (is_commandp) { 748 1.1 jtc int iscmd; 749 1.1 jtc 750 1.1 jtc /* Figure out if this is a command */ 751 1.4 christos for (p = start - 1; p >= 0 && isspace((unsigned char)buf[p]); p--) 752 1.1 jtc ; 753 1.8 provos iscmd = p < 0 || strchr(";|&()`", buf[p]); 754 1.1 jtc if (iscmd) { 755 1.1 jtc /* If command has a /, path, etc. is not searched; 756 1.1 jtc * only current directory is searched, which is just 757 1.1 jtc * like file globbing. 758 1.1 jtc */ 759 1.1 jtc for (p = start; p < end; p++) 760 1.1 jtc if (ISDIRSEP(buf[p])) 761 1.1 jtc break; 762 1.1 jtc iscmd = p == end; 763 1.1 jtc } 764 1.1 jtc *is_commandp = iscmd; 765 1.1 jtc } 766 1.1 jtc 767 1.1 jtc *startp = start; 768 1.1 jtc 769 1.1 jtc return end - start; 770 1.1 jtc } 771 1.1 jtc 772 1.1 jtc int 773 1.1 jtc x_cf_glob(flags, buf, buflen, pos, startp, endp, wordsp, is_commandp) 774 1.1 jtc int flags; 775 1.1 jtc const char *buf; 776 1.1 jtc int buflen; 777 1.1 jtc int pos; 778 1.1 jtc int *startp; 779 1.1 jtc int *endp; 780 1.1 jtc char ***wordsp; 781 1.1 jtc int *is_commandp; 782 1.1 jtc { 783 1.1 jtc int len; 784 1.1 jtc int nwords; 785 1.1 jtc char **words; 786 1.1 jtc int is_command; 787 1.1 jtc 788 1.1 jtc len = x_locate_word(buf, buflen, pos, startp, &is_command); 789 1.1 jtc if (!(flags & XCF_COMMAND)) 790 1.1 jtc is_command = 0; 791 1.1 jtc /* Don't do command globing on zero length strings - it takes too 792 1.1 jtc * long and isn't very useful. File globs are more likely to be 793 1.1 jtc * useful, so allow these. 794 1.1 jtc */ 795 1.1 jtc if (len == 0 && is_command) 796 1.1 jtc return 0; 797 1.1 jtc 798 1.1 jtc nwords = (is_command ? x_command_glob : x_file_glob)(flags, 799 1.1 jtc buf + *startp, len, &words); 800 1.1 jtc if (nwords == 0) { 801 1.1 jtc *wordsp = (char **) 0; 802 1.1 jtc return 0; 803 1.1 jtc } 804 1.1 jtc 805 1.1 jtc if (is_commandp) 806 1.1 jtc *is_commandp = is_command; 807 1.1 jtc *wordsp = words; 808 1.1 jtc *endp = *startp + len; 809 1.1 jtc 810 1.1 jtc return nwords; 811 1.1 jtc } 812 1.1 jtc 813 1.1 jtc /* Given a string, copy it and possibly add a '*' to the end. The 814 1.1 jtc * new string is returned. 815 1.1 jtc */ 816 1.1 jtc static char * 817 1.1 jtc add_glob(str, slen) 818 1.1 jtc const char *str; 819 1.1 jtc int slen; 820 1.1 jtc { 821 1.1 jtc char *toglob; 822 1.1 jtc char *s; 823 1.32 kamil bool saw_slash = false; 824 1.1 jtc 825 1.1 jtc if (slen < 0) 826 1.1 jtc return (char *) 0; 827 1.1 jtc 828 1.1 jtc toglob = str_nsave(str, slen + 1, ATEMP); /* + 1 for "*" */ 829 1.1 jtc toglob[slen] = '\0'; 830 1.1 jtc 831 1.1 jtc /* 832 1.14 mycroft * If the pathname contains a wildcard (an unquoted '*', 833 1.25 sjg * '?', or '['), or a ~username 834 1.14 mycroft * with no trailing slash, then it is globbed based on that 835 1.14 mycroft * value (i.e., without the appended '*'). 836 1.1 jtc */ 837 1.1 jtc for (s = toglob; *s; s++) { 838 1.1 jtc if (*s == '\\' && s[1]) 839 1.1 jtc s++; 840 1.25 sjg else if (*s == '*' || *s == '[' || *s == '?' 841 1.1 jtc || (s[1] == '(' /*)*/ && strchr("*+?@!", *s))) 842 1.1 jtc break; 843 1.2 tls else if (ISDIRSEP(*s)) 844 1.32 kamil saw_slash = true; 845 1.1 jtc } 846 1.2 tls if (!*s && (*toglob != '~' || saw_slash)) { 847 1.1 jtc toglob[slen] = '*'; 848 1.1 jtc toglob[slen + 1] = '\0'; 849 1.1 jtc } 850 1.1 jtc 851 1.1 jtc return toglob; 852 1.1 jtc } 853 1.1 jtc 854 1.1 jtc /* 855 1.1 jtc * Find longest common prefix 856 1.1 jtc */ 857 1.1 jtc int 858 1.1 jtc x_longest_prefix(nwords, words) 859 1.1 jtc int nwords; 860 1.1 jtc char *const *words; 861 1.1 jtc { 862 1.1 jtc int i, j; 863 1.1 jtc int prefix_len; 864 1.1 jtc char *p; 865 1.1 jtc 866 1.1 jtc if (nwords <= 0) 867 1.1 jtc return 0; 868 1.1 jtc 869 1.1 jtc prefix_len = strlen(words[0]); 870 1.1 jtc for (i = 1; i < nwords; i++) 871 1.1 jtc for (j = 0, p = words[i]; j < prefix_len; j++) 872 1.15 rillig if (FILECHCONV((unsigned char)p[j]) 873 1.15 rillig != FILECHCONV((unsigned char)words[0][j])) { 874 1.1 jtc prefix_len = j; 875 1.1 jtc break; 876 1.1 jtc } 877 1.1 jtc return prefix_len; 878 1.1 jtc } 879 1.1 jtc 880 1.1 jtc void 881 1.1 jtc x_free_words(nwords, words) 882 1.1 jtc int nwords; 883 1.1 jtc char **words; 884 1.1 jtc { 885 1.1 jtc int i; 886 1.1 jtc 887 1.1 jtc for (i = 0; i < nwords; i++) 888 1.1 jtc if (words[i]) 889 1.1 jtc afree(words[i], ATEMP); 890 1.1 jtc afree(words, ATEMP); 891 1.1 jtc } 892 1.1 jtc 893 1.1 jtc /* Return the offset of the basename of string s (which ends at se - need not 894 1.1 jtc * be null terminated). Trailing slashes are ignored. If s is just a slash, 895 1.1 jtc * then the offset is 0 (actually, length - 1). 896 1.1 jtc * s Return 897 1.1 jtc * /etc 1 898 1.1 jtc * /etc/ 1 899 1.1 jtc * /etc// 1 900 1.1 jtc * /etc/fo 5 901 1.1 jtc * foo 0 902 1.1 jtc * /// 2 903 1.1 jtc * 0 904 1.1 jtc */ 905 1.1 jtc int 906 1.1 jtc x_basename(s, se) 907 1.1 jtc const char *s; 908 1.1 jtc const char *se; 909 1.1 jtc { 910 1.1 jtc const char *p; 911 1.1 jtc 912 1.1 jtc if (se == (char *) 0) 913 1.1 jtc se = s + strlen(s); 914 1.1 jtc if (s == se) 915 1.1 jtc return 0; 916 1.1 jtc 917 1.1 jtc /* Skip trailing slashes */ 918 1.1 jtc for (p = se - 1; p > s && ISDIRSEP(*p); p--) 919 1.1 jtc ; 920 1.1 jtc for (; p > s && !ISDIRSEP(*p); p--) 921 1.1 jtc ; 922 1.1 jtc if (ISDIRSEP(*p) && p + 1 < se) 923 1.1 jtc p++; 924 1.1 jtc 925 1.1 jtc return p - s; 926 1.1 jtc } 927 1.1 jtc 928 1.1 jtc /* 929 1.1 jtc * Apply pattern matching to a table: all table entries that match a pattern 930 1.1 jtc * are added to wp. 931 1.1 jtc */ 932 1.1 jtc static void 933 1.1 jtc glob_table(pat, wp, tp) 934 1.1 jtc const char *pat; 935 1.1 jtc XPtrV *wp; 936 1.1 jtc struct table *tp; 937 1.1 jtc { 938 1.1 jtc struct tstate ts; 939 1.1 jtc struct tbl *te; 940 1.1 jtc 941 1.35 kamil for (ksh_twalk(&ts, tp); (te = tnext(&ts)); ) { 942 1.32 kamil if (gmatch(te->name, pat, false)) 943 1.1 jtc XPput(*wp, str_save(te->name, ATEMP)); 944 1.1 jtc } 945 1.1 jtc } 946 1.1 jtc 947 1.1 jtc static void 948 1.16 christos glob_path(flags, pat, wp, xpath) 949 1.1 jtc int flags; 950 1.1 jtc const char *pat; 951 1.1 jtc XPtrV *wp; 952 1.16 christos const char *xpath; 953 1.1 jtc { 954 1.1 jtc const char *sp, *p; 955 1.1 jtc char *xp; 956 1.8 provos int staterr; 957 1.1 jtc int pathlen; 958 1.1 jtc int patlen; 959 1.1 jtc int oldsize, newsize, i, j; 960 1.1 jtc char **words; 961 1.1 jtc XString xs; 962 1.1 jtc 963 1.1 jtc patlen = strlen(pat) + 1; 964 1.16 christos sp = xpath; 965 1.1 jtc Xinit(xs, xp, patlen + 128, ATEMP); 966 1.1 jtc while (sp) { 967 1.1 jtc xp = Xstring(xs, xp); 968 1.1 jtc if (!(p = strchr(sp, PATHSEP))) 969 1.1 jtc p = sp + strlen(sp); 970 1.1 jtc pathlen = p - sp; 971 1.1 jtc if (pathlen) { 972 1.1 jtc /* Copy sp into xp, stuffing any MAGIC characters 973 1.1 jtc * on the way 974 1.1 jtc */ 975 1.1 jtc const char *s = sp; 976 1.1 jtc 977 1.1 jtc XcheckN(xs, xp, pathlen * 2); 978 1.1 jtc while (s < p) { 979 1.1 jtc if (ISMAGIC(*s)) 980 1.1 jtc *xp++ = MAGIC; 981 1.1 jtc *xp++ = *s++; 982 1.1 jtc } 983 1.1 jtc *xp++ = DIRSEP; 984 1.1 jtc pathlen++; 985 1.1 jtc } 986 1.1 jtc sp = p; 987 1.1 jtc XcheckN(xs, xp, patlen); 988 1.1 jtc memcpy(xp, pat, patlen); 989 1.1 jtc 990 1.1 jtc oldsize = XPsize(*wp); 991 1.8 provos glob_str(Xstring(xs, xp), wp, 1); /* mark dirs */ 992 1.1 jtc newsize = XPsize(*wp); 993 1.1 jtc 994 1.1 jtc /* Check that each match is executable... */ 995 1.1 jtc words = (char **) XPptrv(*wp); 996 1.1 jtc for (i = j = oldsize; i < newsize; i++) { 997 1.8 provos staterr = 0; 998 1.8 provos if ((search_access(words[i], X_OK, &staterr) >= 0) 999 1.8 provos || (staterr == EISDIR)) { 1000 1.1 jtc words[j] = words[i]; 1001 1.1 jtc if (!(flags & XCF_FULLPATH)) 1002 1.1 jtc memmove(words[j], words[j] + pathlen, 1003 1.1 jtc strlen(words[j] + pathlen) + 1); 1004 1.1 jtc j++; 1005 1.1 jtc } else 1006 1.1 jtc afree(words[i], ATEMP); 1007 1.1 jtc } 1008 1.1 jtc wp->cur = (void **) &words[j]; 1009 1.1 jtc 1010 1.1 jtc if (!*sp++) 1011 1.1 jtc break; 1012 1.1 jtc } 1013 1.1 jtc Xfree(xs, xp); 1014 1.1 jtc } 1015 1.1 jtc 1016 1.6 jdolecek /* 1017 1.6 jdolecek * if argument string contains any special characters, they will 1018 1.6 jdolecek * be escaped and the result will be put into edit buffer by 1019 1.6 jdolecek * keybinding-specific function 1020 1.6 jdolecek */ 1021 1.6 jdolecek int 1022 1.6 jdolecek x_escape(s, len, putbuf_func) 1023 1.6 jdolecek const char *s; 1024 1.6 jdolecek size_t len; 1025 1.24 plunky int (*putbuf_func) ARGS((const char *, size_t)); 1026 1.6 jdolecek { 1027 1.6 jdolecek size_t add, wlen; 1028 1.6 jdolecek const char *ifs = str_val(local("IFS", 0)); 1029 1.6 jdolecek int rval=0; 1030 1.6 jdolecek 1031 1.6 jdolecek for (add = 0, wlen = len; wlen - add > 0; add++) { 1032 1.21 cbiere if (strchr("\\$(){}[]?*&;#|<>\"'`", s[add]) || strchr(ifs, s[add])) { 1033 1.6 jdolecek if (putbuf_func(s, add) != 0) { 1034 1.6 jdolecek rval = -1; 1035 1.6 jdolecek break; 1036 1.6 jdolecek } 1037 1.6 jdolecek 1038 1.6 jdolecek putbuf_func("\\", 1); 1039 1.6 jdolecek putbuf_func(&s[add], 1); 1040 1.6 jdolecek 1041 1.6 jdolecek add++; 1042 1.6 jdolecek wlen -= add; 1043 1.6 jdolecek s += add; 1044 1.6 jdolecek add = -1; /* after the increment it will go to 0 */ 1045 1.6 jdolecek } 1046 1.6 jdolecek } 1047 1.6 jdolecek if (wlen > 0 && rval == 0) 1048 1.6 jdolecek rval = putbuf_func(s, wlen); 1049 1.6 jdolecek 1050 1.6 jdolecek return (rval); 1051 1.6 jdolecek } 1052 1.1 jtc #endif /* EDIT */ 1053