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