No longer trap fatal signals
[notion/jeffpc.git] / utils / ion-completefile / ion-completefile.c
blobab7278d6ce715efd2fa4da351a1447f56a5a069a
1 /*
2 * ion/share/ion-completefile/ion-completefile.c
3 */
5 /****************************************************************************/
6 /* */
7 /* Copyright 1992 Simmule Turner and Rich Salz. All rights reserved. */
8 /* */
9 /* This software is not subject to any license of the American Telephone */
10 /* and Telegraph Company or of the Regents of the University of California. */
11 /* */
12 /* Permission is granted to anyone to use this software for any purpose on */
13 /* any computer system, and to alter it and redistribute it freely, subject */
14 /* to the following restrictions: */
15 /* 1. The authors are not responsible for the consequences of use of this */
16 /* software, no matter how awful, even if they arise from flaws in it. */
17 /* 2. The origin of this software must not be misrepresented, either by */
18 /* explicit claim or by omission. Since few users ever read sources, */
19 /* credits must appear in the documentation. */
20 /* 3. Altered versions must be plainly marked as such, and must not be */
21 /* misrepresented as being the original software. Since few users */
22 /* ever read sources, credits must appear in the documentation. */
23 /* 4. This notice may not be removed or altered. */
24 /* */
25 /****************************************************************************/
26 /* */
27 /* This is a line-editing library, it can be linked into almost any */
28 /* program to provide command-line editing and recall. */
29 /* */
30 /* Posted to comp.sources.misc Sun, 2 Aug 1992 03:05:27 GMT */
31 /* by rsalz@osf.org (Rich $alz) */
32 /* */
33 /****************************************************************************/
34 /* */
35 /* The version contained here has some modifications by awb@cstr.ed.ac.uk */
36 /* (Alan W Black) in order to integrate it with the Edinburgh Speech Tools */
37 /* library and Scheme-in-one-defun in particular. All modifications to */
38 /* to this work are continued with the same copyright above. That is */
39 /* This version editline does not have the the "no commercial use" */
40 /* restriction that some of the rest of the EST library may have */
41 /* awb Dec 30 1998 */
42 /* */
43 /****************************************************************************/
44 /* $Revision: 1.2 $
46 ** History and file completion functions for editline library.
51 * Adapted for use with Ion and tilde-expansion added by
52 * Tuomo Valkonen <tuomov@cc.tut.fi>, 2000-08-23.
55 #include <sys/param.h>
56 #include <sys/types.h>
57 #include <stdio.h>
58 #include <stdlib.h>
59 #include <dirent.h>
60 #include <string.h>
61 #include <unistd.h>
62 #include <sys/stat.h>
63 #include <pwd.h>
64 /* needed for NGROUPS_MAX on Solaris 10 */
65 #include <limits.h>
67 #include <libtu/util.h>
68 #include <libtu/types.h>
69 #include <libtu/misc.h>
70 #include <libtu/output.h>
72 #define STRDUP(X) scopy(X)
73 #define DISPOSE(X) free(X)
74 #define NEW(T, X) ALLOC_N(T, X)
75 #define STATIC static
76 #define EL_CONST const
77 #define SIZE_T int
78 #define MEM_INC 64
79 #define COPYFROMTO(new, p, len) \
80 (void)memcpy((char *)(new), (char *)(p), (int)(len))
82 #ifndef NGROUPS
83 /* Hopefully one of these is defined... */
84 #define NGROUPS NGROUPS_MAX
85 #endif
87 typedef struct dirent DIRENTRY;
89 static void el_add_slash(char *path,char *p)
91 struct stat sb;
93 if (stat(path, &sb) >= 0)
94 (void)strcat(p, S_ISDIR(sb.st_mode) ? "/" : " ");
97 static int el_is_directory(char *path)
99 struct stat sb;
101 if ((stat(path, &sb) >= 0) && S_ISDIR(sb.st_mode))
102 return 1;
103 else
104 return 0;
107 static int el_is_executable(char *path)
109 unsigned int t_uid = geteuid();
110 gid_t t_gids[NGROUPS];
111 int groupcount;
112 struct stat sb;
113 int i;
115 groupcount = getgroups(NGROUPS, t_gids);
117 if((stat(path, &sb) >= 0) && S_ISREG(sb.st_mode)) {
118 /* Normal file, see if we can execute it. */
120 if (sb.st_mode & S_IXOTH) { /* All can execute */
121 return (1);
123 if (sb.st_uid == t_uid && (sb.st_mode & S_IXUSR)) {
124 return (1);
126 if (sb.st_mode & S_IXGRP) {
127 for (i = 0; i < groupcount; i++) {
128 if (sb.st_gid == t_gids[i]) {
129 return (1);
134 return (0);
139 ** strcmp-like sorting predicate for qsort.
141 /*STATIC int compare(EL_CONST void *p1,EL_CONST void *p2)
143 EL_CONST char **v1;
144 EL_CONST char **v2;
146 v1 = (EL_CONST char **)p1;
147 v2 = (EL_CONST char **)p2;
148 return strcmp(*v1, *v2);
152 ** Fill in *avp with an array of names that match file, up to its length.
153 ** Ignore . and .. .
155 static int FindMatches(char *dir,char *file,char ***avp)
157 char **av;
158 char **new;
159 char *p;
160 DIR *dp;
161 DIRENTRY *ep;
162 SIZE_T ac;
163 SIZE_T len;
165 if(*dir=='\0')
166 dir=".";
168 if ((dp = opendir(dir)) == NULL)
169 return 0;
171 av = NULL;
172 ac = 0;
173 len = strlen(file);
174 while ((ep = readdir(dp)) != NULL) {
175 p = ep->d_name;
176 if (p[0] == '.' && (p[1] == '\0' || (p[1] == '.' && p[2] == '\0')))
177 continue;
178 if (len && strncmp(p, file, len) != 0)
179 continue;
181 if ((ac % MEM_INC) == 0) {
182 if ((new = NEW(char*, ac + MEM_INC)) == NULL)
183 break;
184 if (ac) {
185 COPYFROMTO(new, av, ac * sizeof (char **));
186 DISPOSE(av);
188 *avp = av = new;
191 if ((av[ac] = STRDUP(p)) == NULL) {
192 if (ac == 0)
193 DISPOSE(av);
194 break;
196 ac++;
199 /* Clean up and return. */
200 (void)closedir(dp);
201 /* if (ac)
202 qsort(av, ac, sizeof (char **), compare);*/
203 return ac;
208 ** Slight modification on FindMatches, to search through current PATH
211 static int FindFullPathMatches(char *file,char ***avp)
213 char **av;
214 char **new;
215 char *p;
216 char *path = getenv("PATH");
217 char t_tmp[1024];
218 char *t_path;
219 char *t_t_path;
220 DIR *dp;
221 DIRENTRY*ep;
222 SIZE_T ac;
223 SIZE_T len;
225 t_path = strdup(path);
226 t_t_path = t_path;
228 av = NULL;
229 ac = 0;
231 t_t_path = strtok(t_path, ":\n\0");
232 while (t_t_path) {
233 if ((dp = opendir(t_t_path)) == NULL) {
234 t_t_path = strtok(NULL, ":\n\0");
235 continue;
238 len = strlen(file);
239 while ((ep = readdir(dp)) != NULL) {
240 p = ep->d_name;
241 if (p[0] == '.' && (p[1] == '\0' || (p[0] == '.' &&
242 p[1] == '.' &&
243 p[2] == '\0'))) {
244 continue;
246 if (len && strncmp(p, file, len) != 0) {
247 continue;
250 snprintf(t_tmp, 1024, "%s/%s", t_t_path, p);
251 if(!el_is_executable(t_tmp)) {
252 continue;
255 if ((ac % MEM_INC) == 0) {
256 if ((new = NEW(char*, ac + MEM_INC)) == NULL) {
257 break;
259 if (ac) {
260 COPYFROMTO(new, av, ac * sizeof (char **));
261 DISPOSE(av);
263 *avp = av = new;
265 if ((av[ac] = STRDUP(p)) == NULL) {
266 if (ac == 0)
267 DISPOSE(av);
268 break;
270 ac++;
272 (void)closedir(dp);
273 t_t_path = strtok(NULL, ":\n\0");
276 } /* t_path-while */
278 /* Clean up and return. */
280 /*if (ac)
281 qsort(av, ac, sizeof (char **), compare); */
282 free(t_path);
284 return ac;
289 ** Split a pathname into allocated directory and trailing filename parts.
291 STATIC int SplitPath(const char *path,char **dirpart,char **filepart)
293 static char DOT[] = "./";
294 char *dpart;
295 char *fpart;
297 if ((fpart = strrchr(path, '/')) == NULL) {
298 /* No slashes in path */
299 if ((dpart = STRDUP(DOT)) == NULL)
300 return -1;
301 if ((fpart = STRDUP(path)) == NULL) {
302 DISPOSE(dpart);
303 return -1;
305 }else{
306 if ((dpart = STRDUP(path)) == NULL)
307 return -1;
308 /* Include the slash -- Tuomo */
309 dpart[fpart - path + 1] = '\0';
310 if ((fpart = STRDUP(++fpart)) == NULL) {
311 DISPOSE(dpart);
312 return -1;
314 /* Root no longer a special case due above -- Tuomo
315 if (dpart[0] == '\0')
317 dpart[0] = '/';
318 dpart[1] = '\0';
322 *dirpart = dpart;
323 *filepart = fpart;
324 return 0;
328 ** Split a pathname into allocated directory and trailing filename parts.
330 STATIC int SplitRelativePath(const char *path,char **dirpart,char **filepart)
332 static char DOT[] = "./";
333 static char EOL[] = "\0";
334 char *dpart;
335 char *fpart;
337 if ((fpart = strrchr(path, '/')) == NULL) {
338 /* No slashes in path */
339 if ((dpart = STRDUP(EOL)) == NULL)
340 return -1;
341 if ((fpart = STRDUP(path)) == NULL) {
342 DISPOSE(dpart);
343 return -1;
346 else {
347 if ((dpart = STRDUP(path)) == NULL)
348 return -1;
349 /* Include the slash -- Tuomo */
350 dpart[fpart - path + 1] = '\0';
351 if ((fpart = STRDUP(++fpart)) == NULL) {
352 DISPOSE(dpart);
353 return -1;
355 /* Root no longer a special case due above -- Tuomo
356 if (dpart[0] == '\0')
358 dpart[0] = '/';
359 dpart[1] = '\0';
363 *dirpart = dpart;
364 *filepart = fpart;
365 return 0;
369 ** Attempt to complete the pathname, returning an allocated copy.
370 ** Fill in *unique if we completed it, or set it to 0 if ambiguous.
372 #if 0
373 static char *el_complete(char *pathname,int *unique)
375 char **av;
376 char *dir;
377 char *file;
378 char *new;
379 char *p;
380 SIZE_T ac;
381 SIZE_T end;
382 SIZE_T i;
383 SIZE_T j;
384 SIZE_T len;
386 if (SplitPath(pathname, &dir, &file) < 0)
387 return NULL;
389 if ((ac = FindMatches(dir, file, &av)) == 0) {
390 DISPOSE(dir);
391 DISPOSE(file);
392 return NULL;
395 p = NULL;
396 len = strlen(file);
397 if (ac == 1) {
398 /* Exactly one match -- finish it off. */
399 *unique = 1;
400 j = strlen(av[0]) - len + 2;
401 if ((p = NEW(char, j + 1)) != NULL) {
402 COPYFROMTO(p, av[0] + len, j);
403 if ((new = NEW(char, strlen(dir) + strlen(av[0]) + 2)) != NULL) {
404 (void)strcpy(new, dir);
405 (void)strcat(new, "/");
406 (void)strcat(new, av[0]);
407 el_add_slash(new, p);
408 DISPOSE(new);
412 else {
413 *unique = 0;
414 if (len) {
415 /* Find largest matching substring. */
416 for (i = len, end = strlen(av[0]); i < end; i++)
417 for (j = 1; j < ac; j++)
418 if (av[0][i] != av[j][i])
419 goto breakout;
420 breakout:
421 if (i > len) {
422 j = i - len + 1;
423 if ((p = NEW(char, j)) != NULL) {
424 COPYFROMTO(p, av[0] + len, j);
425 p[j - 1] = '\0';
431 /* Clean up and return. */
432 DISPOSE(dir);
433 DISPOSE(file);
434 for (i = 0; i < ac; i++)
435 DISPOSE(av[i]);
436 DISPOSE(av);
437 return p;
439 #endif
441 static int complete_homedir(const char *username, char ***cp_ret, char **beg)
443 struct passwd *pw;
444 char *name;
445 char **cp;
446 int n=0, l=strlen(username);
448 *cp_ret=NULL;
450 for(pw=getpwent(); pw!=NULL; pw=getpwent()){
451 name=pw->pw_name;
453 if(l && strncmp(name, username, l))
454 continue;
456 name=scat3("~", name, "/");
458 if(name==NULL){
459 warn_err();
460 continue;
463 cp=REALLOC_N(*cp_ret, char*, n, n+1);
465 if(cp==NULL){
466 warn_err();
467 free(name);
468 if(*cp_ret!=NULL)
469 free(*cp_ret);
470 }else{
471 cp[n]=name;
472 n++;
473 *cp_ret=cp;
477 endpwent();
479 if(n==1){
480 name=**cp_ret;
481 name[strlen(name)-1]='\0';
482 pw=getpwnam(name+1);
483 if(pw!=NULL && pw->pw_dir!=NULL){
484 name=scat(pw->pw_dir, "/");
485 if(name!=NULL){
486 free(**cp_ret);
487 **cp_ret=name;
492 return n;
496 * ret: 0 not a home directory,
497 * 1 home directory (repath set)
498 * 2 someone's home directory
500 static int tilde_complete(char *path, char **retpath)
502 char *home;
503 char *p;
504 struct passwd *pw;
506 if(*path!='~')
507 return 0;
509 if(*(path+1)!='/' && *(path+1)!='\0'){
510 p=strchr(path, '/');
512 if(p==NULL)
513 return 2;
515 *p='\0';
516 pw=getpwnam(path+1);
517 *p='/';
519 if(pw==NULL)
520 return 0;
522 home=pw->pw_dir;
523 }else{
524 p=path+1;
525 home=getenv("HOME");
528 if(home!=NULL){
529 if(*p=='\0')
530 *retpath=scat3(home, p, "/");
531 else
532 *retpath=scat(home, p);
535 return (*retpath!=NULL);
540 ** Return all possible completions.
542 int do_complete_file(char *pathname, char ***avp, char **beg,
543 void *unused)
545 char *dir;
546 char *file, *path=NULL, *tt;
547 int ac=0, i;
549 switch(tilde_complete(pathname, &path)){
550 case 0:
551 i=SplitPath(pathname, &dir, &file);
552 break;
553 case 2:
554 return complete_homedir(pathname+1, avp, beg);
555 default:
556 i=SplitPath(path, &dir, &file);
559 if(i<0)
560 return 0;
562 ac=FindMatches(dir, file, avp);
564 DISPOSE(file);
566 if(ac==0 && path!=NULL){
567 *avp=ALLOC(char*);
568 if(*avp==NULL)
569 return 0;
570 **avp=path;
571 return 1;
572 }else if(path!=NULL){
573 free(path);
576 /* Identify directories with trailing / */
577 for(i=0; i<ac; i++){
578 path = NEW(char,strlen(dir)+strlen((*avp)[i])+3);
579 sprintf(path,"%s/%s",dir,(*avp)[i]);
580 if(el_is_directory(path)){
581 tt = NEW(char,strlen((*avp)[i])+2);
582 sprintf(tt,"%s/",(*avp)[i]);
583 DISPOSE((*avp)[i]);
584 (*avp)[i] = tt;
586 DISPOSE(path);
589 *beg=dir;
591 return ac;
596 ** Return all possible completions.
598 int do_complete_file_with_path(char *pathname, char ***avp, char **beg,
599 void *unused)
601 char *dir;
602 char *file, *path=NULL, *tt;
603 int ac=0, i, dcomp=FALSE;
605 switch(tilde_complete(pathname, &path)){
606 case 0:
607 i=SplitRelativePath(pathname, &dir, &file);
608 break;
609 case 2:
610 return complete_homedir(pathname+1, avp, beg);
611 default:
612 i=SplitPath(path, &dir, &file);
615 if(i<0)
616 return 0;
618 if(*dir=='\0')
619 ac=FindFullPathMatches(file, avp); /* No slashes in path so far. */
621 if(ac==0){
622 if(*dir=='\0'){
623 dir=scopy("./");
624 if(dir==NULL){
625 warn_err();
626 return 0;
629 ac=FindMatches(dir, file, avp);
630 dcomp=TRUE;
633 DISPOSE(file);
635 if(ac==0 && path!=NULL){
636 *avp=ALLOC(char*);
637 if(*avp==NULL)
638 return 0;
639 **avp=path;
640 return 1;
641 }else if(path!=NULL){
642 free(path);
645 /* Identify directories with trailing / */
646 if(dcomp){
647 for (i = 0; i < ac; i++) {
648 path = NEW(char,strlen(dir)+strlen((*avp)[i])+3);
649 sprintf(path,"%s/%s",dir,(*avp)[i]);
650 if (el_is_directory(path)) {
651 tt = NEW(char,strlen((*avp)[i])+2);
652 sprintf(tt,"%s/",(*avp)[i]);
653 DISPOSE((*avp)[i]);
654 (*avp)[i] = tt;
656 DISPOSE(path);
660 *beg=dir;
662 return ac;
667 int main(int argc, char *argv[])
669 char **avp=NULL;
670 char *beg=NULL;
671 bool wp=FALSE;
672 int i, j, n;
674 libtu_init(argv[0]);
676 for(i=1; i<argc; i++){
677 if(strcmp(argv[i], "-h")==0){
678 printf("Usage: ion-completefile [-help] [-wp] [to_complete...]\n");
679 return EXIT_SUCCESS;
683 for(i=1; i<argc; i++){
684 if(strcmp(argv[i], "-wp")==0){
685 wp=TRUE;
686 }else{
687 if(wp){
688 n=do_complete_file_with_path(argv[i], &avp, &beg, NULL);
689 }else{
690 n=do_complete_file(argv[i], &avp, &beg, NULL);
693 if(beg){
694 printf("%s\n", beg);
695 free(beg);
696 beg=NULL;
697 }else{
698 printf("\n");
702 if(avp){
703 for(j=0; j<n; j++){
704 printf("%s\n", avp[j]);
705 free(avp[j]);
707 free(avp);
708 avp=NULL;
713 return EXIT_SUCCESS;