| 1 | /* $NetBSD: file.c,v 1.32 2019/01/05 16:54:00 christos Exp $ */ |
| 2 | |
| 3 | /*- |
| 4 | * Copyright (c) 1980, 1991, 1993 |
| 5 | * The Regents of the University of California. All rights reserved. |
| 6 | * |
| 7 | * Redistribution and use in source and binary forms, with or without |
| 8 | * modification, are permitted provided that the following conditions |
| 9 | * are met: |
| 10 | * 1. Redistributions of source code must retain the above copyright |
| 11 | * notice, this list of conditions and the following disclaimer. |
| 12 | * 2. Redistributions in binary form must reproduce the above copyright |
| 13 | * notice, this list of conditions and the following disclaimer in the |
| 14 | * documentation and/or other materials provided with the distribution. |
| 15 | * 3. Neither the name of the University nor the names of its contributors |
| 16 | * may be used to endorse or promote products derived from this software |
| 17 | * without specific prior written permission. |
| 18 | * |
| 19 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND |
| 20 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| 21 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| 22 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE |
| 23 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| 24 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
| 25 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
| 26 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
| 27 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
| 28 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
| 29 | * SUCH DAMAGE. |
| 30 | */ |
| 31 | |
| 32 | #include <sys/cdefs.h> |
| 33 | #ifndef lint |
| 34 | #if 0 |
| 35 | static char sccsid[] = "@(#)file.c 8.2 (Berkeley) 3/19/94" ; |
| 36 | #else |
| 37 | __RCSID("$NetBSD: file.c,v 1.32 2019/01/05 16:54:00 christos Exp $" ); |
| 38 | #endif |
| 39 | #endif /* not lint */ |
| 40 | |
| 41 | #ifdef FILEC |
| 42 | |
| 43 | #include <sys/ioctl.h> |
| 44 | #include <sys/param.h> |
| 45 | #include <sys/stat.h> |
| 46 | #include <sys/tty.h> |
| 47 | |
| 48 | #include <dirent.h> |
| 49 | #include <pwd.h> |
| 50 | #include <termios.h> |
| 51 | #include <stdarg.h> |
| 52 | #include <stdlib.h> |
| 53 | #include <unistd.h> |
| 54 | |
| 55 | #ifndef SHORT_STRINGS |
| 56 | #include <string.h> |
| 57 | #endif /* SHORT_STRINGS */ |
| 58 | |
| 59 | #include "csh.h" |
| 60 | #include "extern.h" |
| 61 | |
| 62 | /* |
| 63 | * Tenex style file name recognition, .. and more. |
| 64 | * History: |
| 65 | * Author: Ken Greer, Sept. 1975, CMU. |
| 66 | * Finally got around to adding to the Cshell., Ken Greer, Dec. 1981. |
| 67 | */ |
| 68 | |
| 69 | #define ON 1 |
| 70 | #define OFF 0 |
| 71 | #ifndef TRUE |
| 72 | #define TRUE 1 |
| 73 | #endif |
| 74 | #ifndef FALSE |
| 75 | #define FALSE 0 |
| 76 | #endif |
| 77 | |
| 78 | #define ESC '\033' |
| 79 | |
| 80 | typedef enum { |
| 81 | LIST, RECOGNIZE |
| 82 | } COMMAND; |
| 83 | |
| 84 | static void setup_tty(int); |
| 85 | static void back_to_col_1(void); |
| 86 | static int pushback(Char *); |
| 87 | static void catn(Char *, Char *, size_t); |
| 88 | static void copyn(Char *, Char *, size_t); |
| 89 | static Char filetype(Char *, Char *); |
| 90 | static void print_by_column(Char *, Char *[], size_t); |
| 91 | static Char *tilde(Char *, Char *); |
| 92 | static void retype(void); |
| 93 | static void beep(void); |
| 94 | static void print_recognized_stuff(Char *); |
| 95 | static void extract_dir_and_name(Char *, Char *, Char *); |
| 96 | static Char *getentry(DIR *, int); |
| 97 | static void free_items(Char **, size_t); |
| 98 | static size_t tsearch(Char *, COMMAND, size_t); |
| 99 | static int recognize(Char *, Char *, size_t, size_t); |
| 100 | static int is_prefix(Char *, Char *); |
| 101 | static int is_suffix(Char *, Char *); |
| 102 | static int ignored(Char *); |
| 103 | |
| 104 | /* |
| 105 | * Put this here so the binary can be patched with adb to enable file |
| 106 | * completion by default. Filec controls completion, nobeep controls |
| 107 | * ringing the terminal bell on incomplete expansions. |
| 108 | */ |
| 109 | int filec = 0; |
| 110 | |
| 111 | static void |
| 112 | setup_tty(int on) |
| 113 | { |
| 114 | struct termios tchars; |
| 115 | |
| 116 | (void)tcgetattr(SHIN, &tchars); |
| 117 | |
| 118 | if (on) { |
| 119 | tchars.c_cc[VEOL] = ESC; |
| 120 | if (tchars.c_lflag & ICANON) |
| 121 | on = TCSADRAIN; |
| 122 | else { |
| 123 | tchars.c_lflag |= ICANON; |
| 124 | on = TCSAFLUSH; |
| 125 | } |
| 126 | } |
| 127 | else { |
| 128 | tchars.c_cc[VEOL] = _POSIX_VDISABLE; |
| 129 | on = TCSADRAIN; |
| 130 | } |
| 131 | |
| 132 | (void)tcsetattr(SHIN, on, &tchars); |
| 133 | } |
| 134 | |
| 135 | /* |
| 136 | * Move back to beginning of current line |
| 137 | */ |
| 138 | static void |
| 139 | back_to_col_1(void) |
| 140 | { |
| 141 | struct termios tty, tty_normal; |
| 142 | sigset_t nsigset, osigset; |
| 143 | |
| 144 | sigemptyset(&nsigset); |
| 145 | (void)sigaddset(&nsigset, SIGINT); |
| 146 | (void)sigprocmask(SIG_BLOCK, &nsigset, &osigset); |
| 147 | (void)tcgetattr(SHOUT, &tty); |
| 148 | tty_normal = tty; |
| 149 | tty.c_iflag &= ~INLCR; |
| 150 | tty.c_oflag &= ~ONLCR; |
| 151 | (void)tcsetattr(SHOUT, TCSADRAIN, &tty); |
| 152 | (void)write(SHOUT, "\r" , 1); |
| 153 | (void)tcsetattr(SHOUT, TCSADRAIN, &tty_normal); |
| 154 | (void)sigprocmask(SIG_SETMASK, &osigset, NULL); |
| 155 | } |
| 156 | |
| 157 | /* |
| 158 | * Push string contents back into tty queue |
| 159 | */ |
| 160 | static int |
| 161 | pushback(Char *string) |
| 162 | { |
| 163 | struct termios tty, tty_normal; |
| 164 | char buf[64], svchars[sizeof(buf)]; |
| 165 | sigset_t nsigset, osigset; |
| 166 | Char *p; |
| 167 | size_t bufidx, i, len_str, nbuf, nsv, onsv, retrycnt; |
| 168 | char c; |
| 169 | |
| 170 | nsv = 0; |
| 171 | sigemptyset(&nsigset); |
| 172 | (void)sigaddset(&nsigset, SIGINT); |
| 173 | (void)sigprocmask(SIG_BLOCK, &nsigset, &osigset); |
| 174 | (void)tcgetattr(SHOUT, &tty); |
| 175 | tty_normal = tty; |
| 176 | tty.c_lflag &= ~(ECHOKE | ECHO | ECHOE | ECHOK | ECHONL | ECHOPRT | ECHOCTL); |
| 177 | /* FIONREAD works only in noncanonical mode. */ |
| 178 | tty.c_lflag &= ~ICANON; |
| 179 | tty.c_cc[VMIN] = 0; |
| 180 | (void)tcsetattr(SHOUT, TCSADRAIN, &tty); |
| 181 | |
| 182 | for (retrycnt = 5; ; retrycnt--) { |
| 183 | /* |
| 184 | * Push back characters. |
| 185 | */ |
| 186 | for (p = string; (c = (char)*p) != '\0'; p++) |
| 187 | (void)ioctl(SHOUT, TIOCSTI, (ioctl_t) &c); |
| 188 | for (i = 0; i < nsv; i++) |
| 189 | (void)ioctl(SHOUT, TIOCSTI, (ioctl_t) &svchars[i]); |
| 190 | |
| 191 | if (retrycnt == 0) |
| 192 | break; /* give up salvaging characters */ |
| 193 | |
| 194 | len_str = (size_t)(p - string); |
| 195 | |
| 196 | if (ioctl(SHOUT, FIONREAD, (ioctl_t) &nbuf) || |
| 197 | nbuf <= len_str + nsv || /* The string fit. */ |
| 198 | nbuf > sizeof(buf)) /* For future binary compatibility |
| 199 | (and safety). */ |
| 200 | break; |
| 201 | |
| 202 | /* |
| 203 | * User has typed characters before the pushback finished. |
| 204 | * Salvage the characters. |
| 205 | */ |
| 206 | |
| 207 | /* This read() should be in noncanonical mode. */ |
| 208 | if (read(SHOUT, &buf, nbuf) != (ssize_t)nbuf) |
| 209 | continue; /* hangup? */ |
| 210 | |
| 211 | onsv = nsv; |
| 212 | for (bufidx = 0, i = 0; bufidx < nbuf; bufidx++, i++) { |
| 213 | c = buf[bufidx]; |
| 214 | if ((i < len_str) ? c != (char)string[i] : |
| 215 | (i < len_str + onsv) ? c != svchars[i - len_str] : 1) { |
| 216 | /* Salvage a character. */ |
| 217 | if (nsv < (int)(sizeof svchars / sizeof svchars[0])) { |
| 218 | svchars[nsv++] = c; |
| 219 | i--; /* try this comparison with the next char */ |
| 220 | } else |
| 221 | break; /* too many */ |
| 222 | } |
| 223 | } |
| 224 | } |
| 225 | |
| 226 | #if 1 |
| 227 | /* |
| 228 | * XXX Is this a bug or a feature of kernel tty driver? |
| 229 | * |
| 230 | * FIONREAD in canonical mode does not return correct byte count |
| 231 | * in tty input queue, but this is required to avoid unwanted echo. |
| 232 | */ |
| 233 | tty.c_lflag |= ICANON; |
| 234 | (void)tcsetattr(SHOUT, TCSADRAIN, &tty); |
| 235 | (void)ioctl(SHOUT, FIONREAD, (ioctl_t) &i); |
| 236 | #endif |
| 237 | (void)tcsetattr(SHOUT, TCSADRAIN, &tty_normal); |
| 238 | (void)sigprocmask(SIG_SETMASK, &osigset, NULL); |
| 239 | |
| 240 | return (int)nsv; |
| 241 | } |
| 242 | |
| 243 | /* |
| 244 | * Concatenate src onto tail of des. |
| 245 | * Des is a string whose maximum length is count. |
| 246 | * Always null terminate. |
| 247 | */ |
| 248 | static void |
| 249 | catn(Char *des, Char *src, size_t count) |
| 250 | { |
| 251 | while (count-- > 0 && *des) |
| 252 | des++; |
| 253 | while (count-- > 0) |
| 254 | if ((*des++ = *src++) == 0) |
| 255 | return; |
| 256 | *des = '\0'; |
| 257 | } |
| 258 | |
| 259 | /* |
| 260 | * Like strncpy but always leave room for trailing \0 |
| 261 | * and always null terminate. |
| 262 | */ |
| 263 | static void |
| 264 | copyn(Char *des, Char *src, size_t count) |
| 265 | { |
| 266 | while (count-- > 0) |
| 267 | if ((*des++ = *src++) == 0) |
| 268 | return; |
| 269 | *des = '\0'; |
| 270 | } |
| 271 | |
| 272 | static Char |
| 273 | filetype(Char *dir, Char *file) |
| 274 | { |
| 275 | struct stat statb; |
| 276 | Char path[MAXPATHLEN]; |
| 277 | |
| 278 | catn(Strcpy(path, dir), file, sizeof(path) / sizeof(Char)); |
| 279 | if (lstat(short2str(path), &statb) == 0) { |
| 280 | switch (statb.st_mode & S_IFMT) { |
| 281 | case S_IFDIR: |
| 282 | return ('/'); |
| 283 | case S_IFLNK: |
| 284 | if (stat(short2str(path), &statb) == 0 && /* follow it out */ |
| 285 | S_ISDIR(statb.st_mode)) |
| 286 | return ('>'); |
| 287 | else |
| 288 | return ('@'); |
| 289 | case S_IFSOCK: |
| 290 | return ('='); |
| 291 | default: |
| 292 | if (statb.st_mode & 0111) |
| 293 | return ('*'); |
| 294 | } |
| 295 | } |
| 296 | return (' '); |
| 297 | } |
| 298 | |
| 299 | static struct winsize win; |
| 300 | |
| 301 | /* |
| 302 | * Print sorted down columns |
| 303 | */ |
| 304 | static void |
| 305 | print_by_column(Char *dir, Char *items[], size_t count) |
| 306 | { |
| 307 | size_t c, columns, i, maxwidth, r, rows; |
| 308 | |
| 309 | maxwidth = 0; |
| 310 | |
| 311 | if (ioctl(SHOUT, TIOCGWINSZ, (ioctl_t) & win) < 0 || win.ws_col == 0) |
| 312 | win.ws_col = 80; |
| 313 | for (i = 0; i < count; i++) |
| 314 | maxwidth = maxwidth > (r = Strlen(items[i])) ? maxwidth : r; |
| 315 | maxwidth += 2; /* for the file tag and space */ |
| 316 | columns = win.ws_col / maxwidth; |
| 317 | if (columns == 0) |
| 318 | columns = 1; |
| 319 | rows = (count + (columns - 1)) / columns; |
| 320 | for (r = 0; r < rows; r++) { |
| 321 | for (c = 0; c < columns; c++) { |
| 322 | i = c * rows + r; |
| 323 | if (i < count) { |
| 324 | size_t w; |
| 325 | |
| 326 | (void)fprintf(cshout, "%s" , vis_str(items[i])); |
| 327 | (void)fputc(dir ? filetype(dir, items[i]) : ' ', cshout); |
| 328 | if (c < columns - 1) { /* last column? */ |
| 329 | w = Strlen(items[i]) + 1; |
| 330 | for (; w < maxwidth; w++) |
| 331 | (void) fputc(' ', cshout); |
| 332 | } |
| 333 | } |
| 334 | } |
| 335 | (void)fputc('\r', cshout); |
| 336 | (void)fputc('\n', cshout); |
| 337 | } |
| 338 | } |
| 339 | |
| 340 | /* |
| 341 | * Expand file name with possible tilde usage |
| 342 | * ~person/mumble |
| 343 | * expands to |
| 344 | * home_directory_of_person/mumble |
| 345 | */ |
| 346 | static Char * |
| 347 | tilde(Char *new, Char *old) |
| 348 | { |
| 349 | static Char person[40]; |
| 350 | struct passwd *pw; |
| 351 | Char *o, *p; |
| 352 | |
| 353 | if (old[0] != '~') |
| 354 | return (Strcpy(new, old)); |
| 355 | |
| 356 | for (p = person, o = &old[1]; *o && *o != '/'; *p++ = *o++) |
| 357 | continue; |
| 358 | *p = '\0'; |
| 359 | if (person[0] == '\0') |
| 360 | (void)Strcpy(new, value(STRhome)); |
| 361 | else { |
| 362 | pw = getpwnam(short2str(person)); |
| 363 | if (pw == NULL) |
| 364 | return (NULL); |
| 365 | (void)Strcpy(new, str2short(pw->pw_dir)); |
| 366 | } |
| 367 | (void)Strcat(new, o); |
| 368 | return (new); |
| 369 | } |
| 370 | |
| 371 | /* |
| 372 | * Cause pending line to be printed |
| 373 | */ |
| 374 | static void |
| 375 | retype(void) |
| 376 | { |
| 377 | struct termios tty; |
| 378 | |
| 379 | (void)tcgetattr(SHOUT, &tty); |
| 380 | tty.c_lflag |= PENDIN; |
| 381 | (void)tcsetattr(SHOUT, TCSADRAIN, &tty); |
| 382 | } |
| 383 | |
| 384 | static void |
| 385 | beep(void) |
| 386 | { |
| 387 | if (adrof(STRnobeep) == 0) |
| 388 | (void)write(SHOUT, "\007" , 1); |
| 389 | } |
| 390 | |
| 391 | /* |
| 392 | * Erase that silly ^[ and |
| 393 | * print the recognized part of the string |
| 394 | */ |
| 395 | static void |
| 396 | print_recognized_stuff(Char *recognized_part) |
| 397 | { |
| 398 | /* An optimized erasing of that silly ^[ */ |
| 399 | (void)fputc('\b', cshout); |
| 400 | (void)fputc('\b', cshout); |
| 401 | switch (Strlen(recognized_part)) { |
| 402 | case 0: /* erase two Characters: ^[ */ |
| 403 | (void)fputc(' ', cshout); |
| 404 | (void)fputc(' ', cshout); |
| 405 | (void)fputc('\b', cshout); |
| 406 | (void)fputc('\b', cshout); |
| 407 | break; |
| 408 | case 1: /* overstrike the ^, erase the [ */ |
| 409 | (void)fprintf(cshout, "%s" , vis_str(recognized_part)); |
| 410 | (void)fputc(' ', cshout); |
| 411 | (void)fputc('\b', cshout); |
| 412 | break; |
| 413 | default: /* overstrike both Characters ^[ */ |
| 414 | (void)fprintf(cshout, "%s" , vis_str(recognized_part)); |
| 415 | break; |
| 416 | } |
| 417 | (void)fflush(cshout); |
| 418 | } |
| 419 | |
| 420 | /* |
| 421 | * Parse full path in file into 2 parts: directory and file names |
| 422 | * Should leave final slash (/) at end of dir. |
| 423 | */ |
| 424 | static void |
| 425 | extract_dir_and_name(Char *path, Char *dir, Char *name) |
| 426 | { |
| 427 | Char *p; |
| 428 | |
| 429 | p = Strrchr(path, '/'); |
| 430 | if (p == NULL) { |
| 431 | copyn(name, path, MAXNAMLEN); |
| 432 | dir[0] = '\0'; |
| 433 | } |
| 434 | else { |
| 435 | copyn(name, ++p, MAXNAMLEN); |
| 436 | copyn(dir, path, (size_t)(p - path)); |
| 437 | } |
| 438 | } |
| 439 | |
| 440 | static Char * |
| 441 | getentry(DIR *dir_fd, int looking_for_lognames) |
| 442 | { |
| 443 | struct dirent *dirp; |
| 444 | struct passwd *pw; |
| 445 | |
| 446 | if (looking_for_lognames) { |
| 447 | if ((pw = getpwent()) == NULL) |
| 448 | return (NULL); |
| 449 | return (str2short(pw->pw_name)); |
| 450 | } |
| 451 | if ((dirp = readdir(dir_fd)) != NULL) |
| 452 | return (str2short(dirp->d_name)); |
| 453 | return (NULL); |
| 454 | } |
| 455 | |
| 456 | static void |
| 457 | free_items(Char **items, size_t numitems) |
| 458 | { |
| 459 | size_t i; |
| 460 | |
| 461 | for (i = 0; i < numitems; i++) |
| 462 | free(items[i]); |
| 463 | free(items); |
| 464 | } |
| 465 | |
| 466 | #define FREE_ITEMS(items, numitems) { \ |
| 467 | sigset_t nsigset, osigset;\ |
| 468 | \ |
| 469 | sigemptyset(&nsigset);\ |
| 470 | (void) sigaddset(&nsigset, SIGINT);\ |
| 471 | (void) sigprocmask(SIG_BLOCK, &nsigset, &osigset);\ |
| 472 | free_items(items, numitems);\ |
| 473 | (void) sigprocmask(SIG_SETMASK, &osigset, NULL);\ |
| 474 | } |
| 475 | |
| 476 | /* |
| 477 | * Perform a RECOGNIZE or LIST command on string "word". |
| 478 | */ |
| 479 | static size_t |
| 480 | tsearch(Char *word, COMMAND command, size_t max_word_length) |
| 481 | { |
| 482 | Char dir[MAXPATHLEN + 1], extended_name[MAXNAMLEN + 1]; |
| 483 | Char name[MAXNAMLEN + 1], tilded_dir[MAXPATHLEN + 1]; |
| 484 | DIR *dir_fd; |
| 485 | Char *entry; |
| 486 | int ignoring, looking_for_lognames; |
| 487 | size_t name_length, nignored, numitems; |
| 488 | Char **items = NULL; |
| 489 | size_t maxitems = 0; |
| 490 | |
| 491 | numitems = 0; |
| 492 | ignoring = TRUE; |
| 493 | nignored = 0; |
| 494 | |
| 495 | looking_for_lognames = (*word == '~') && (Strchr(word, '/') == NULL); |
| 496 | if (looking_for_lognames) { |
| 497 | (void)setpwent(); |
| 498 | copyn(name, &word[1], MAXNAMLEN); /* name sans ~ */ |
| 499 | dir_fd = NULL; |
| 500 | } |
| 501 | else { |
| 502 | extract_dir_and_name(word, dir, name); |
| 503 | if (tilde(tilded_dir, dir) == 0) |
| 504 | return (0); |
| 505 | dir_fd = opendir(*tilded_dir ? short2str(tilded_dir) : "." ); |
| 506 | if (dir_fd == NULL) |
| 507 | return (0); |
| 508 | } |
| 509 | |
| 510 | again: /* search for matches */ |
| 511 | name_length = Strlen(name); |
| 512 | for (numitems = 0; (entry = getentry(dir_fd, looking_for_lognames)) != NULL;) { |
| 513 | if (!is_prefix(name, entry)) |
| 514 | continue; |
| 515 | /* Don't match . files on null prefix match */ |
| 516 | if (name_length == 0 && entry[0] == '.' && |
| 517 | !looking_for_lognames) |
| 518 | continue; |
| 519 | if (command == LIST) { |
| 520 | if ((size_t)numitems >= maxitems) { |
| 521 | maxitems += 1024; |
| 522 | if (items == NULL) |
| 523 | items = xmalloc(sizeof(*items) * maxitems); |
| 524 | else |
| 525 | items = xrealloc(items, sizeof(*items) * maxitems); |
| 526 | } |
| 527 | items[numitems] = xmalloc((size_t) (Strlen(entry) + 1) * |
| 528 | sizeof(Char)); |
| 529 | copyn(items[numitems], entry, MAXNAMLEN); |
| 530 | numitems++; |
| 531 | } |
| 532 | else { /* RECOGNIZE command */ |
| 533 | if (ignoring && ignored(entry)) |
| 534 | nignored++; |
| 535 | else if (recognize(extended_name, |
| 536 | entry, name_length, ++numitems)) |
| 537 | break; |
| 538 | } |
| 539 | } |
| 540 | if (ignoring && numitems == 0 && nignored > 0) { |
| 541 | ignoring = FALSE; |
| 542 | nignored = 0; |
| 543 | if (looking_for_lognames) |
| 544 | (void)setpwent(); |
| 545 | else |
| 546 | rewinddir(dir_fd); |
| 547 | goto again; |
| 548 | } |
| 549 | |
| 550 | if (looking_for_lognames) |
| 551 | (void)endpwent(); |
| 552 | else |
| 553 | (void)closedir(dir_fd); |
| 554 | if (numitems == 0) |
| 555 | return (0); |
| 556 | if (command == RECOGNIZE) { |
| 557 | if (looking_for_lognames) |
| 558 | copyn(word, STRtilde, 1); |
| 559 | else |
| 560 | /* put back dir part */ |
| 561 | copyn(word, dir, max_word_length); |
| 562 | /* add extended name */ |
| 563 | catn(word, extended_name, max_word_length); |
| 564 | return (numitems); |
| 565 | } |
| 566 | else { /* LIST */ |
| 567 | qsort(items, numitems, sizeof(items[0]), |
| 568 | (int (*) (const void *, const void *)) sortscmp); |
| 569 | print_by_column(looking_for_lognames ? NULL : tilded_dir, |
| 570 | items, numitems); |
| 571 | if (items != NULL) |
| 572 | FREE_ITEMS(items, numitems); |
| 573 | } |
| 574 | return (0); |
| 575 | } |
| 576 | |
| 577 | /* |
| 578 | * Object: extend what user typed up to an ambiguity. |
| 579 | * Algorithm: |
| 580 | * On first match, copy full entry (assume it'll be the only match) |
| 581 | * On subsequent matches, shorten extended_name to the first |
| 582 | * Character mismatch between extended_name and entry. |
| 583 | * If we shorten it back to the prefix length, stop searching. |
| 584 | */ |
| 585 | static int |
| 586 | recognize(Char *extended_name, Char *entry, size_t name_length, size_t numitems) |
| 587 | { |
| 588 | if (numitems == 1) /* 1st match */ |
| 589 | copyn(extended_name, entry, MAXNAMLEN); |
| 590 | else { /* 2nd & subsequent matches */ |
| 591 | Char *ent, *x; |
| 592 | size_t len = 0; |
| 593 | |
| 594 | x = extended_name; |
| 595 | for (ent = entry; *x && *x == *ent++; x++, len++) |
| 596 | continue; |
| 597 | *x = '\0'; /* Shorten at 1st Char diff */ |
| 598 | if (len == name_length) /* Ambiguous to prefix? */ |
| 599 | return (-1); /* So stop now and save time */ |
| 600 | } |
| 601 | return (0); |
| 602 | } |
| 603 | |
| 604 | /* |
| 605 | * Return true if check matches initial Chars in template. |
| 606 | * This differs from PWB imatch in that if check is null |
| 607 | * it matches anything. |
| 608 | */ |
| 609 | static int |
| 610 | is_prefix(Char *check, Char *template) |
| 611 | { |
| 612 | do |
| 613 | if (*check == 0) |
| 614 | return (TRUE); |
| 615 | while (*check++ == *template++); |
| 616 | return (FALSE); |
| 617 | } |
| 618 | |
| 619 | /* |
| 620 | * Return true if the Chars in template appear at the |
| 621 | * end of check, I.e., are its suffix. |
| 622 | */ |
| 623 | static int |
| 624 | is_suffix(Char *check, Char *template) |
| 625 | { |
| 626 | Char *c, *t; |
| 627 | |
| 628 | for (c = check; *c++;) |
| 629 | continue; |
| 630 | for (t = template; *t++;) |
| 631 | continue; |
| 632 | for (;;) { |
| 633 | if (t == template) |
| 634 | return 1; |
| 635 | if (c == check || *--t != *--c) |
| 636 | return 0; |
| 637 | } |
| 638 | } |
| 639 | |
| 640 | ssize_t |
| 641 | tenex(Char *inputline, size_t inputline_size) |
| 642 | { |
| 643 | char tinputline[BUFSIZE]; |
| 644 | ssize_t num_read; |
| 645 | size_t numitems; |
| 646 | |
| 647 | setup_tty(ON); |
| 648 | |
| 649 | while ((num_read = read(SHIN, tinputline, BUFSIZE)) > 0) { |
| 650 | size_t i, nr = (size_t) num_read; |
| 651 | |
| 652 | |
| 653 | static Char delims[] = {' ', '\'', '"', '\t', ';', '&', '<', |
| 654 | '>', '(', ')', '|', '^', '%', '\0'}; |
| 655 | Char *str_end, *word_start, last_Char, should_retype; |
| 656 | size_t space_left; |
| 657 | COMMAND command; |
| 658 | |
| 659 | for (i = 0; i < nr; i++) |
| 660 | inputline[i] = (unsigned char) tinputline[i]; |
| 661 | last_Char = inputline[nr - 1] & ASCII; |
| 662 | |
| 663 | if (last_Char == '\n' || nr == inputline_size) |
| 664 | break; |
| 665 | command = (last_Char == ESC) ? RECOGNIZE : LIST; |
| 666 | if (command == LIST) |
| 667 | (void)fputc('\n', cshout); |
| 668 | str_end = &inputline[nr]; |
| 669 | if (last_Char == ESC) |
| 670 | --str_end; /* wipeout trailing cmd Char */ |
| 671 | *str_end = '\0'; |
| 672 | /* |
| 673 | * Find LAST occurence of a delimiter in the inputline. The word start |
| 674 | * is one Character past it. |
| 675 | */ |
| 676 | for (word_start = str_end; word_start > inputline; --word_start) |
| 677 | if (Strchr(delims, word_start[-1])) |
| 678 | break; |
| 679 | space_left = inputline_size - (size_t)(word_start - inputline) - 1; |
| 680 | numitems = tsearch(word_start, command, space_left); |
| 681 | |
| 682 | if (command == RECOGNIZE) { |
| 683 | /* print from str_end on */ |
| 684 | print_recognized_stuff(str_end); |
| 685 | if (numitems != 1) /* Beep = No match/ambiguous */ |
| 686 | beep(); |
| 687 | } |
| 688 | |
| 689 | /* |
| 690 | * Tabs in the input line cause trouble after a pushback. tty driver |
| 691 | * won't backspace over them because column positions are now |
| 692 | * incorrect. This is solved by retyping over current line. |
| 693 | */ |
| 694 | should_retype = FALSE; |
| 695 | if (Strchr(inputline, '\t')) { /* tab Char in input line? */ |
| 696 | back_to_col_1(); |
| 697 | should_retype = TRUE; |
| 698 | } |
| 699 | if (command == LIST) /* Always retype after a LIST */ |
| 700 | should_retype = TRUE; |
| 701 | if (pushback(inputline)) |
| 702 | should_retype = TRUE; |
| 703 | if (should_retype) { |
| 704 | if (command == RECOGNIZE) |
| 705 | (void) fputc('\n', cshout); |
| 706 | printprompt(); |
| 707 | } |
| 708 | if (should_retype) |
| 709 | retype(); |
| 710 | } |
| 711 | setup_tty(OFF); |
| 712 | return num_read; |
| 713 | } |
| 714 | |
| 715 | static int |
| 716 | ignored(Char *entry) |
| 717 | { |
| 718 | struct varent *vp; |
| 719 | Char **cp; |
| 720 | |
| 721 | if ((vp = adrof(STRfignore)) == NULL || (cp = vp->vec) == NULL) |
| 722 | return (FALSE); |
| 723 | for (; *cp != NULL; cp++) |
| 724 | if (is_suffix(entry, *cp)) |
| 725 | return (TRUE); |
| 726 | return (FALSE); |
| 727 | } |
| 728 | #endif /* FILEC */ |
| 729 | |