]> git.decadent.org.uk Git - ion3.git/blob - utils/ion-completefile/ion-completefile.c
[svn-inject] Installing original source of ion3
[ion3.git] / utils / ion-completefile / ion-completefile.c
1 /*
2  * ion/share/ion-completefile/ion-completefile.c
3  */
4
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 $
45  **
46  **  History and file completion functions for editline library.
47  */
48
49
50 /*
51  * Adapted for use with Ion and tilde-expansion added by
52  * Tuomo Valkonen <tuomov@cc.tut.fi>, 2000-08-23.
53  */
54
55 #include <sys/param.h>
56 #include <sys/types.h>
57 #include <stdlib.h>
58 #include <dirent.h>
59 #include <string.h>
60 #include <unistd.h>
61 #include <sys/stat.h>
62 #include <pwd.h>
63
64 #include <libtu/util.h>
65 #include <libtu/types.h>
66 #include <libtu/misc.h>
67 #include <libtu/output.h>
68
69 #define STRDUP(X) scopy(X)
70 #define DISPOSE(X) free(X)
71 #define NEW(T, X)  ALLOC_N(T, X)
72 #define STATIC static
73 #define EL_CONST const
74 #define SIZE_T int
75 #define MEM_INC 64
76 #define COPYFROMTO(new, p, len) \
77         (void)memcpy((char *)(new), (char *)(p), (int)(len))
78
79 #ifndef NGROUPS
80 /* Hopefully one of these is defined... */
81 #define NGROUPS NGROUPS_MAX
82 #endif
83
84 typedef struct dirent DIRENTRY;
85
86 static void el_add_slash(char *path,char *p)
87 {
88         struct stat sb;
89         
90         if (stat(path, &sb) >= 0)
91                 (void)strcat(p, S_ISDIR(sb.st_mode) ? "/" : " ");
92 }
93
94 static int el_is_directory(char *path)
95 {
96         struct stat sb;
97         
98         if ((stat(path, &sb) >= 0) && S_ISDIR(sb.st_mode))
99                 return 1;
100         else
101                 return 0;
102 }
103
104 static int el_is_executable(char *path)
105 {
106         unsigned int t_uid = geteuid();
107         gid_t t_gids[NGROUPS];
108         int groupcount;
109         struct stat sb;
110         int i;
111         
112         groupcount = getgroups(NGROUPS, t_gids);
113
114         if((stat(path, &sb) >= 0) && S_ISREG(sb.st_mode)) {
115                 /* Normal file, see if we can execute it. */
116                 
117                 if (sb.st_mode & S_IXOTH) { /* All can execute */
118                         return (1);
119                 }
120                 if (sb.st_uid == t_uid && (sb.st_mode & S_IXUSR)) {
121                         return (1);
122                 }
123                 if (sb.st_mode & S_IXGRP) {
124                         for (i = 0; i < groupcount; i++) {
125                             if (sb.st_gid == t_gids[i]) {
126                                 return (1);
127                             }
128                         }
129                 }
130         }
131         return (0);
132 }
133
134
135 /*
136  **  strcmp-like sorting predicate for qsort.
137  */
138 /*STATIC int compare(EL_CONST void *p1,EL_CONST void *p2)
139  {
140  EL_CONST char  **v1;
141  EL_CONST char  **v2;
142  * 
143  v1 = (EL_CONST char **)p1;
144  v2 = (EL_CONST char **)p2;
145  return strcmp(*v1, *v2);
146  }*/
147
148 /*
149  **  Fill in *avp with an array of names that match file, up to its length.
150  **  Ignore . and .. .
151  */
152 static int FindMatches(char *dir,char *file,char ***avp)
153 {
154     char        **av;
155     char        **new;
156     char        *p;
157     DIR         *dp;
158     DIRENTRY    *ep;
159     SIZE_T      ac;
160     SIZE_T      len;
161         
162         if(*dir=='\0')
163                 dir=".";
164         
165     if ((dp = opendir(dir)) == NULL)
166                 return 0;
167         
168     av = NULL;
169     ac = 0;
170     len = strlen(file);
171     while ((ep = readdir(dp)) != NULL) {
172                 p = ep->d_name;
173                 if (p[0] == '.' && (p[1] == '\0' || (p[1] == '.' && p[2] == '\0')))
174                         continue;
175                 if (len && strncmp(p, file, len) != 0)
176                         continue;
177                 
178                 if ((ac % MEM_INC) == 0) {
179                         if ((new = NEW(char*, ac + MEM_INC)) == NULL)
180                                 break;
181                         if (ac) {
182                                 COPYFROMTO(new, av, ac * sizeof (char **));
183                                 DISPOSE(av);
184                         }
185                         *avp = av = new;
186                 }
187                 
188                 if ((av[ac] = STRDUP(p)) == NULL) {
189                         if (ac == 0)
190                                 DISPOSE(av);
191                         break;
192                 }
193                 ac++;
194     }
195         
196     /* Clean up and return. */
197     (void)closedir(dp);
198         /*    if (ac)
199          qsort(av, ac, sizeof (char **), compare);*/
200     return ac;
201 }
202
203
204 /*
205  ** Slight modification on FindMatches, to search through current PATH
206  ** 
207  */
208 static int FindFullPathMatches(char *file,char ***avp)
209 {
210     char        **av;
211     char        **new;
212     char        *p;
213     char    *path = getenv("PATH");
214     char    t_tmp[1024];
215     char    *t_path;
216     char    *t_t_path;
217     DIR         *dp;
218     DIRENTRY*ep;
219     SIZE_T      ac;
220     SIZE_T      len;
221         
222     t_path = strdup(path);
223     t_t_path = t_path;
224         
225     av = NULL;
226     ac = 0;
227         
228     t_t_path = strtok(t_path, ":\n\0");
229     while (t_t_path) {
230                 if ((dp = opendir(t_t_path)) == NULL) {
231                         t_t_path = strtok(NULL, ":\n\0");
232                         continue;
233                 }
234                 
235                 len = strlen(file);
236                 while ((ep = readdir(dp)) != NULL) {
237                         p = ep->d_name;
238                         if (p[0] == '.' && (p[1] == '\0' || (p[0] == '.' &&
239                                                                                                  p[1] == '.' &&
240                                                                                                  p[2] == '\0'))) {
241                                 continue;
242                         }
243                         if (len && strncmp(p, file, len) != 0) {
244                                 continue;
245                         }
246                         
247                         snprintf(t_tmp, 1024, "%s/%s", t_t_path, p);
248                         if(!el_is_executable(t_tmp)) {
249                                 continue;
250                         }
251                         
252                         if ((ac % MEM_INC) == 0) {
253                                 if ((new = NEW(char*, ac + MEM_INC)) == NULL) {
254                                         break;
255                                 }
256                                 if (ac) {
257                                         COPYFROMTO(new, av, ac * sizeof (char **));
258                                         DISPOSE(av);
259                                 }
260                                 *avp = av = new;
261                         }
262                         if ((av[ac] = STRDUP(p)) == NULL) {
263                                 if (ac == 0)
264                                         DISPOSE(av);
265                                 break;
266                         }
267                         ac++;
268                 }
269                 (void)closedir(dp);
270                 t_t_path = strtok(NULL, ":\n\0");
271                 
272                 
273     } /* t_path-while */
274         
275     /* Clean up and return. */
276         
277     /*if (ac)
278          qsort(av, ac, sizeof (char **), compare); */
279     free(t_path);
280         
281     return ac;
282 }
283
284
285 /*
286  **  Split a pathname into allocated directory and trailing filename parts.
287  */
288 STATIC int SplitPath(const char *path,char **dirpart,char **filepart)
289 {
290     static char DOT[] = "./";
291     char        *dpart;
292     char        *fpart;
293         
294     if ((fpart = strrchr(path, '/')) == NULL) {
295                 /* No slashes in path */
296                 if ((dpart = STRDUP(DOT)) == NULL)
297                         return -1;
298                 if ((fpart = STRDUP(path)) == NULL) {
299                         DISPOSE(dpart);
300                         return -1;
301                 }
302     }else{
303                 if ((dpart = STRDUP(path)) == NULL)
304                         return -1;
305                 /* Include the slash -- Tuomo */
306                 dpart[fpart - path + 1] = '\0';
307                 if ((fpart = STRDUP(++fpart)) == NULL) {
308                         DISPOSE(dpart);
309                         return -1;
310                 }
311                 /* Root no longer a special case due above -- Tuomo
312                  if (dpart[0] == '\0')
313                  {
314                  dpart[0] = '/';
315                  dpart[1] = '\0';
316                  }
317                  */
318     }
319     *dirpart = dpart;
320     *filepart = fpart;
321     return 0;
322 }
323
324 /*
325  **  Split a pathname into allocated directory and trailing filename parts.
326  */
327 STATIC int SplitRelativePath(const char *path,char **dirpart,char **filepart)
328 {
329     static char DOT[] = "./";
330     static char EOL[] = "\0";
331     char *dpart;
332     char *fpart;
333         
334     if ((fpart = strrchr(path, '/')) == NULL) {
335                 /* No slashes in path */
336                 if ((dpart = STRDUP(EOL)) == NULL)
337                         return -1;
338                 if ((fpart = STRDUP(path)) == NULL) {
339                         DISPOSE(dpart);
340                         return -1;
341                 }
342     }
343     else {
344                 if ((dpart = STRDUP(path)) == NULL)
345                         return -1;
346                 /* Include the slash -- Tuomo */
347                 dpart[fpart - path + 1] = '\0';
348                 if ((fpart = STRDUP(++fpart)) == NULL) {
349                         DISPOSE(dpart);
350                         return -1;
351                 }
352                 /* Root no longer a special case due above -- Tuomo
353                  if (dpart[0] == '\0')
354                  {
355                  dpart[0] = '/';
356                  dpart[1] = '\0';
357                  }
358                  */
359     }
360     *dirpart = dpart;
361     *filepart = fpart;
362     return 0;
363 }
364
365 /*
366  **  Attempt to complete the pathname, returning an allocated copy.
367  **  Fill in *unique if we completed it, or set it to 0 if ambiguous.
368  */
369 #if 0
370 static char *el_complete(char *pathname,int *unique)
371 {
372     char        **av;
373     char        *dir;
374     char        *file;
375     char        *new;
376     char        *p;
377     SIZE_T      ac;
378     SIZE_T      end;
379     SIZE_T      i;
380     SIZE_T      j;
381     SIZE_T      len;
382         
383     if (SplitPath(pathname, &dir, &file) < 0)
384                 return NULL;
385         
386     if ((ac = FindMatches(dir, file, &av)) == 0) {
387                 DISPOSE(dir);
388                 DISPOSE(file);
389                 return NULL;
390     }
391         
392     p = NULL;
393     len = strlen(file);
394     if (ac == 1) {
395                 /* Exactly one match -- finish it off. */
396                 *unique = 1;
397                 j = strlen(av[0]) - len + 2;
398                 if ((p = NEW(char, j + 1)) != NULL) {
399                         COPYFROMTO(p, av[0] + len, j);
400                         if ((new = NEW(char, strlen(dir) + strlen(av[0]) + 2)) != NULL) {
401                                 (void)strcpy(new, dir);
402                                 (void)strcat(new, "/");
403                                 (void)strcat(new, av[0]);
404                                 el_add_slash(new, p);
405                                 DISPOSE(new);
406                         }
407                 }
408     }
409     else {
410                 *unique = 0;
411                 if (len) {
412                         /* Find largest matching substring. */
413                         for (i = len, end = strlen(av[0]); i < end; i++)
414                         for (j = 1; j < ac; j++)
415                     if (av[0][i] != av[j][i])
416                                 goto breakout;
417                 breakout:
418                         if (i > len) {
419                                 j = i - len + 1;
420                                 if ((p = NEW(char, j)) != NULL) {
421                                         COPYFROMTO(p, av[0] + len, j);
422                                         p[j - 1] = '\0';
423                                 }
424                         }
425                 }
426     }
427         
428     /* Clean up and return. */
429     DISPOSE(dir);
430     DISPOSE(file);
431     for (i = 0; i < ac; i++)
432                 DISPOSE(av[i]);
433     DISPOSE(av);
434     return p;
435 }
436 #endif
437
438 static int complete_homedir(const char *username, char ***cp_ret, char **beg)
439 {
440         struct passwd *pw;
441         char *name;
442         char **cp;
443         int n=0, l=strlen(username);
444         
445         *cp_ret=NULL;
446         
447         for(pw=getpwent(); pw!=NULL; pw=getpwent()){
448                 name=pw->pw_name;
449                 
450                 if(l && strncmp(name, username, l))
451                         continue;
452                 
453                 name=scat3("~", name, "/");
454                 
455                 if(name==NULL){
456                         warn_err();
457                         continue;
458                 }
459                 
460                 cp=REALLOC_N(*cp_ret, char*, n, n+1);
461                 
462                 if(cp==NULL){
463                         warn_err();
464                         free(name);
465                         if(*cp_ret!=NULL)
466                         free(*cp_ret);
467                 }else{
468                         cp[n]=name;
469                         n++;
470                         *cp_ret=cp;
471                 }
472         }
473         
474         endpwent();
475         
476         if(n==1){
477                 name=**cp_ret;
478                 name[strlen(name)-1]='\0';
479                 pw=getpwnam(name+1);
480                 if(pw!=NULL && pw->pw_dir!=NULL){
481                         name=scat(pw->pw_dir, "/");
482                         if(name!=NULL){
483                                 free(**cp_ret);
484                                 **cp_ret=name;
485                         }
486                 }
487         }
488         
489         return n;
490 }
491
492 /* 
493  * ret: 0 not a home directory,
494  *              1 home directory (repath set)
495  *              2 someone's home directory
496  */
497 static int tilde_complete(char *path, char **retpath)
498 {
499         char *home;
500         char *p;
501         struct passwd *pw;
502         
503         if(*path!='~')
504                 return 0;
505         
506         if(*(path+1)!='/' && *(path+1)!='\0'){
507                 p=strchr(path, '/');
508                 
509                 if(p==NULL)
510                         return 2;
511                 
512                 *p='\0';
513                 pw=getpwnam(path+1);
514                 *p='/';
515                 
516                 if(pw==NULL)
517                         return 0;
518                 
519                 home=pw->pw_dir;
520         }else{
521                 p=path+1;
522                 home=getenv("HOME");
523         }
524         
525         if(home!=NULL){
526                 if(*p=='\0')
527                         *retpath=scat3(home, p, "/");
528                 else
529                         *retpath=scat(home, p);
530         }
531         
532         return (*retpath!=NULL);
533 }
534
535
536 /*
537  **  Return all possible completions.
538  */
539 int do_complete_file(char *pathname, char ***avp, char **beg,
540                                          void *unused)
541 {
542     char        *dir;
543     char        *file, *path=NULL, *tt;
544     int         ac=0, i;
545     
546     switch(tilde_complete(pathname, &path)){
547     case 0:
548                 i=SplitPath(pathname, &dir, &file);
549                 break;
550     case 2:
551                 return complete_homedir(pathname+1, avp, beg);
552     default:
553                 i=SplitPath(path, &dir, &file);
554     }
555     
556     if(i<0)
557                 return 0;
558     
559     ac=FindMatches(dir, file, avp);
560     
561     DISPOSE(file);
562     
563     if(ac==0 && path!=NULL){
564                 *avp=ALLOC(char*);
565                 if(*avp==NULL)
566                         return 0;
567                 **avp=path;
568                         return 1;
569     }else if(path!=NULL){
570                 free(path);
571     }
572     
573     /* Identify directories with trailing / */
574     for(i=0; i<ac; i++){
575                 path = NEW(char,strlen(dir)+strlen((*avp)[i])+3);
576                 sprintf(path,"%s/%s",dir,(*avp)[i]);
577                 if(el_is_directory(path)){
578                         tt = NEW(char,strlen((*avp)[i])+2);
579                         sprintf(tt,"%s/",(*avp)[i]);
580                         DISPOSE((*avp)[i]);
581                         (*avp)[i] = tt;
582                 }
583                 DISPOSE(path);
584     }
585     
586     *beg=dir;
587     
588     return ac;
589 }
590
591
592 /*
593  **  Return all possible completions.
594  */
595 int do_complete_file_with_path(char *pathname, char ***avp, char **beg,
596                                                            void *unused)
597 {
598     char        *dir;
599     char        *file, *path=NULL, *tt;
600     int         ac=0, i, dcomp=FALSE;
601     
602     switch(tilde_complete(pathname, &path)){
603     case 0:
604                 i=SplitRelativePath(pathname, &dir, &file);
605                 break;
606     case 2:
607                 return complete_homedir(pathname+1, avp, beg);
608     default:
609                 i=SplitPath(path, &dir, &file);
610     }
611     
612     if(i<0)
613                 return 0;
614     
615     if(*dir=='\0')
616                 ac=FindFullPathMatches(file, avp); /* No slashes in path so far. */
617         
618         if(ac==0){
619                 if(*dir=='\0'){
620                         dir=scopy("./");
621                         if(dir==NULL){
622                                 warn_err();
623                                 return 0;
624                         }
625                 }
626                 ac=FindMatches(dir, file, avp);
627                 dcomp=TRUE;
628         }
629     
630     DISPOSE(file);
631     
632     if(ac==0 && path!=NULL){
633                 *avp=ALLOC(char*);
634                 if(*avp==NULL)
635                         return 0;
636                 **avp=path;
637                         return 1;
638     }else if(path!=NULL){
639                 free(path);
640     }
641     
642     /* Identify directories with trailing / */
643         if(dcomp){
644             for (i = 0; i < ac; i++) {
645                         path = NEW(char,strlen(dir)+strlen((*avp)[i])+3);
646                         sprintf(path,"%s/%s",dir,(*avp)[i]);
647                         if (el_is_directory(path)) {
648                                 tt = NEW(char,strlen((*avp)[i])+2);
649                                 sprintf(tt,"%s/",(*avp)[i]);
650                                 DISPOSE((*avp)[i]);
651                                 (*avp)[i] = tt;
652                         }
653                         DISPOSE(path);
654             }
655         }
656     
657     *beg=dir;
658     
659     return ac;
660 }
661
662
663
664 int main(int argc, char *argv[])
665 {
666         char **avp=NULL;
667         char *beg=NULL;
668         bool wp=FALSE;
669         int i, j, n;
670         
671         libtu_init(argv[0]);
672         
673         for(i=1; i<argc; i++){
674                 if(strcmp(argv[i], "-h")==0){
675                         printf("Usage: ion-completefile [-help] [-wp] [to_complete...]\n");
676                         return EXIT_SUCCESS;
677                 }
678         }
679         
680         for(i=1; i<argc; i++){
681                 if(strcmp(argv[i], "-wp")==0){
682                         wp=TRUE;
683                 }else{
684                         if(wp){
685                                 n=do_complete_file_with_path(argv[i], &avp, &beg, NULL);
686                         }else{
687                                 n=do_complete_file(argv[i], &avp, &beg, NULL);
688                         }
689                         
690                         if(beg){
691                                 printf("%s\n", beg);
692                                 free(beg);
693                                 beg=NULL;
694                         }else{
695                                 printf("\n");
696                         }
697         
698
699                         if(avp){
700                                 for(j=0; j<n; j++){
701                                         printf("%s\n", avp[j]);
702                                         free(avp[j]);
703                                 }
704                                 free(avp);
705                                 avp=NULL;
706                         }
707                 }
708         }
709         
710         return EXIT_SUCCESS;
711 }