/*
 * ion/share/ion-completefile/ion-completefile.c
 */

/****************************************************************************/
/*                                                                          */
/* Copyright 1992 Simmule Turner and Rich Salz.  All rights reserved.       */
/*                                                                          */
/* This software is not subject to any license of the American Telephone    */
/* and Telegraph Company or of the Regents of the University of California. */
/*                                                                          */
/* Permission is granted to anyone to use this software for any purpose on  */
/* any computer system, and to alter it and redistribute it freely, subject */
/* to the following restrictions:                                           */
/* 1. The authors are not responsible for the consequences of use of this   */
/*    software, no matter how awful, even if they arise from flaws in it.   */
/* 2. The origin of this software must not be misrepresented, either by     */
/*    explicit claim or by omission.  Since few users ever read sources,    */
/*    credits must appear in the documentation.                             */
/* 3. Altered versions must be plainly marked as such, and must not be      */
/*    misrepresented as being the original software.  Since few users       */
/*    ever read sources, credits must appear in the documentation.          */
/* 4. This notice may not be removed or altered.                            */
/*                                                                          */
/****************************************************************************/
/*                                                                          */
/*  This is a line-editing library, it can be linked into almost any        */
/*  program to provide command-line editing and recall.                     */
/*                                                                          */
/*  Posted to comp.sources.misc Sun, 2 Aug 1992 03:05:27 GMT                */
/*      by rsalz@osf.org (Rich $alz)                                        */
/*                                                                          */
/****************************************************************************/
/*                                                                          */
/*  The version contained here has some modifications by awb@cstr.ed.ac.uk  */
/*  (Alan W Black) in order to integrate it with the Edinburgh Speech Tools */
/*  library and Scheme-in-one-defun in particular.  All modifications to    */
/*  to this work are continued with the same copyright above.  That is      */
/*  This version editline does not have the the "no commercial use"         */
/*  restriction that some of the rest of the EST library may have           */
/*  awb Dec 30 1998                                                         */
/*                                                                          */
/****************************************************************************/
/*  $Revision: 1.2 $
 **
 **  History and file completion functions for editline library.
 */


/*
 * Adapted for use with Ion and tilde-expansion added by
 * Tuomo Valkonen <tuomov@cc.tut.fi>, 2000-08-23.
 */

#include <sys/param.h>
#include <sys/types.h>
#include <stdlib.h>
#include <dirent.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <pwd.h>
/* needed for NGROUPS_MAX on Solaris 10 */
#include <limits.h>

#include <libtu/util.h>
#include <libtu/types.h>
#include <libtu/misc.h>
#include <libtu/output.h>

#define STRDUP(X) scopy(X)
#define DISPOSE(X) free(X)
#define NEW(T, X)  ALLOC_N(T, X)
#define STATIC static
#define SIZE_T int
#define MEM_INC 64
#define COPYFROMTO(new, p, len) \
    (void)memcpy((char *)(new), (char *)(p), (int)(len))

#ifndef NGROUPS
/* Hopefully one of these is defined... */
#define NGROUPS NGROUPS_MAX
#endif

typedef struct dirent DIRENTRY;

static int el_is_directory(char *path)
{
    struct stat sb;

    if ((stat(path, &sb) >= 0) && S_ISDIR(sb.st_mode))
        return 1;
    else
        return 0;
}

static int el_is_executable(char *path)
{
    unsigned int t_uid = geteuid();
    gid_t t_gids[NGROUPS];
    int groupcount;
    struct stat sb;
    int i;

    groupcount = getgroups(NGROUPS, t_gids);

    if((stat(path, &sb) >= 0) && S_ISREG(sb.st_mode)) {
        /* Normal file, see if we can execute it. */

        if (sb.st_mode & S_IXOTH) { /* All can execute */
            return (1);
        }
        if (sb.st_uid == t_uid && (sb.st_mode & S_IXUSR)) {
            return (1);
        }
        if (sb.st_mode & S_IXGRP) {
            for (i = 0; i < groupcount; i++) {
                if (sb.st_gid == t_gids[i]) {
                    return (1);
                }
            }
        }
    }
    return (0);
}

/*
 **  Fill in *avp with an array of names that match file, up to its length.
 **  Ignore . and .. .
 */
static int FindMatches(char *dir,char *file,char ***avp)
{
    char	**av;
    char	**new;
    char	*p;
    DIR		*dp;
    DIRENTRY	*ep;
    SIZE_T	ac;
    SIZE_T	len;

    if(*dir=='\0')
        dir=".";

    if ((dp = opendir(dir)) == NULL)
        return 0;

    av = NULL;
    ac = 0;
    len = strlen(file);
    while ((ep = readdir(dp)) != NULL) {
        p = ep->d_name;
        if (p[0] == '.' && (p[1] == '\0' || (p[1] == '.' && p[2] == '\0')))
            continue;
        if (len && strncmp(p, file, len) != 0)
            continue;

        if ((ac % MEM_INC) == 0) {
            if ((new = NEW(char*, ac + MEM_INC)) == NULL)
                break;
            if (ac) {
                COPYFROMTO(new, av, ac * sizeof (char **));
                DISPOSE(av);
            }
            *avp = av = new;
        }

        if ((av[ac] = STRDUP(p)) == NULL) {
            if (ac == 0)
                DISPOSE(av);
            break;
        }
        ac++;
    }

    /* Clean up and return. */
    (void)closedir(dp);
    /*    if (ac)
     qsort(av, ac, sizeof (char **), compare);*/
    return ac;
}


/*
 ** Slight modification on FindMatches, to search through current PATH
 **
 */
static int FindFullPathMatches(char *file,char ***avp)
{
    char	**av;
    char	**new;
    char	*p;
    char    *path = getenv("PATH");
    char    t_tmp[1024];
    char    *t_path;
    char    *t_t_path;
    DIR		*dp;
    DIRENTRY*ep;
    SIZE_T	ac;
    SIZE_T	len;

    t_path = strdup(path);
    t_t_path = t_path;

    av = NULL;
    ac = 0;

    t_t_path = strtok(t_path, ":\n\0");
    while (t_t_path) {
        if ((dp = opendir(t_t_path)) == NULL) {
            t_t_path = strtok(NULL, ":\n\0");
            continue;
        }

        len = strlen(file);
        while ((ep = readdir(dp)) != NULL) {
            p = ep->d_name;
            if (p[0] == '.' && (p[1] == '\0' || (p[0] == '.' &&
                                                 p[1] == '.' &&
                                                 p[2] == '\0'))) {
                continue;
            }
            if (len && strncmp(p, file, len) != 0) {
                continue;
            }

            snprintf(t_tmp, 1024, "%s/%s", t_t_path, p);
            if(!el_is_executable(t_tmp)) {
                continue;
            }

            if ((ac % MEM_INC) == 0) {
                if ((new = NEW(char*, ac + MEM_INC)) == NULL) {
                    break;
                }
                if (ac) {
                    COPYFROMTO(new, av, ac * sizeof (char **));
                    DISPOSE(av);
                }
                *avp = av = new;
            }
            if ((av[ac] = STRDUP(p)) == NULL) {
                if (ac == 0)
                    DISPOSE(av);
                break;
            }
            ac++;
        }
        (void)closedir(dp);
        t_t_path = strtok(NULL, ":\n\0");


    } /* t_path-while */

    /* Clean up and return. */

    /*if (ac)
     qsort(av, ac, sizeof (char **), compare); */
    free(t_path);

    return ac;
}


/*
 **  Split a pathname into allocated directory and trailing filename parts.
 */
STATIC int SplitPath(const char *path,char **dirpart,char **filepart)
{
    static char	DOT[] = "./";
    char	*dpart;
    char	*fpart;

    if ((fpart = strrchr(path, '/')) == NULL) {
        /* No slashes in path */
        if ((dpart = STRDUP(DOT)) == NULL)
            return -1;
        if ((fpart = STRDUP(path)) == NULL) {
            DISPOSE(dpart);
            return -1;
        }
    }else{
        if ((dpart = STRDUP(path)) == NULL)
            return -1;
        /* Include the slash -- Tuomo */
        dpart[fpart - path + 1] = '\0';
        if ((fpart = STRDUP(++fpart)) == NULL) {
            DISPOSE(dpart);
            return -1;
        }
        /* Root no longer a special case due above -- Tuomo
         if (dpart[0] == '\0')
         {
         dpart[0] = '/';
         dpart[1] = '\0';
         }
         */
    }
    *dirpart = dpart;
    *filepart = fpart;
    return 0;
}

/*
 **  Split a pathname into allocated directory and trailing filename parts.
 */
STATIC int SplitRelativePath(const char *path,char **dirpart,char **filepart)
{
    static char EOL[] = "\0";
    char *dpart;
    char *fpart;

    if ((fpart = strrchr(path, '/')) == NULL) {
        /* No slashes in path */
        if ((dpart = STRDUP(EOL)) == NULL)
            return -1;
        if ((fpart = STRDUP(path)) == NULL) {
            DISPOSE(dpart);
            return -1;
        }
    }
    else {
        if ((dpart = STRDUP(path)) == NULL)
            return -1;
        /* Include the slash -- Tuomo */
        dpart[fpart - path + 1] = '\0';
        if ((fpart = STRDUP(++fpart)) == NULL) {
            DISPOSE(dpart);
            return -1;
        }
        /* Root no longer a special case due above -- Tuomo
         if (dpart[0] == '\0')
         {
         dpart[0] = '/';
         dpart[1] = '\0';
         }
         */
    }
    *dirpart = dpart;
    *filepart = fpart;
    return 0;
}

static int complete_homedir(const char *username, char ***cp_ret, char **UNUSED(beg))
{
    struct passwd *pw;
    char *name;
    char **cp;
    int n=0, l=strlen(username);

    *cp_ret=NULL;

    for(pw=getpwent(); pw!=NULL; pw=getpwent()){
        name=pw->pw_name;

        if(l && strncmp(name, username, l))
            continue;

        name=scat3("~", name, "/");

        if(name==NULL){
            warn_err();
            continue;
        }

        cp=REALLOC_N(*cp_ret, char*, n, n+1);

        if(cp==NULL){
            warn_err();
            free(name);
            if(*cp_ret!=NULL)
                free(*cp_ret);
        }else{
            cp[n]=name;
            n++;
            *cp_ret=cp;
        }
    }

    endpwent();

    if(n==1){
        name=**cp_ret;
        name[strlen(name)-1]='\0';
        pw=getpwnam(name+1);
        if(pw!=NULL && pw->pw_dir!=NULL){
            name=scat(pw->pw_dir, "/");
            if(name!=NULL){
                free(**cp_ret);
                **cp_ret=name;
            }
        }
    }

    return n;
}

/*
 * ret: 0 not a home directory,
 * 		1 home directory (repath set)
 *		2 someone's home directory
 */
static int tilde_complete(char *path, char **retpath)
{
    char *home;
    char *p;
    struct passwd *pw;

    if(*path!='~')
        return 0;

    if(*(path+1)!='/' && *(path+1)!='\0'){
        p=strchr(path, '/');

        if(p==NULL)
            return 2;

        *p='\0';
        pw=getpwnam(path+1);
        *p='/';

        if(pw==NULL)
            return 0;

        home=pw->pw_dir;
    }else{
        p=path+1;
        home=getenv("HOME");
    }

    if(home!=NULL){
        if(*p=='\0')
            *retpath=scat3(home, p, "/");
        else
            *retpath=scat(home, p);
    }

    return (*retpath!=NULL);
}


/*
 **  Return all possible completions.
 */
int do_complete_file(char *pathname, char ***avp, char **beg)
{
    char	*dir;
    char	*file, *path=NULL, *tt;
    int		ac=0, i;

    switch(tilde_complete(pathname, &path)){
    case 0:
        i=SplitPath(pathname, &dir, &file);
        break;
    case 2:
        return complete_homedir(pathname+1, avp, beg);
    default:
        i=SplitPath(path, &dir, &file);
    }

    if(i<0)
        return 0;

    ac=FindMatches(dir, file, avp);

    DISPOSE(file);

    if(ac==0 && path!=NULL){
        *avp=ALLOC(char*);
        if(*avp==NULL)
            return 0;
        **avp=path;
        return 1;
    }else if(path!=NULL){
        free(path);
    }

    /* Identify directories with trailing / */
    for(i=0; i<ac; i++){
        path = NEW(char,strlen(dir)+strlen((*avp)[i])+3);
        sprintf(path,"%s/%s",dir,(*avp)[i]);
        if(el_is_directory(path)){
            tt = NEW(char,strlen((*avp)[i])+2);
            sprintf(tt,"%s/",(*avp)[i]);
            DISPOSE((*avp)[i]);
            (*avp)[i] = tt;
        }
        DISPOSE(path);
    }

    *beg=dir;

    return ac;
}


/*
 **  Return all possible completions.
 */
int do_complete_file_with_path(char *pathname, char ***avp, char **beg)
{
    char	*dir;
    char	*file, *path=NULL, *tt;
    int		ac=0, i, dcomp=FALSE;

    switch(tilde_complete(pathname, &path)){
    case 0:
        i=SplitRelativePath(pathname, &dir, &file);
        break;
    case 2:
        return complete_homedir(pathname+1, avp, beg);
    default:
        i=SplitPath(path, &dir, &file);
    }

    if(i<0)
        return 0;

    if(*dir=='\0')
        ac=FindFullPathMatches(file, avp); /* No slashes in path so far. */

    if(ac==0){
        if(*dir=='\0'){
            dir=scopy("./");
            if(dir==NULL){
                warn_err();
                return 0;
            }
        }
        ac=FindMatches(dir, file, avp);
        dcomp=TRUE;
    }

    DISPOSE(file);

    if(ac==0 && path!=NULL){
        *avp=ALLOC(char*);
        if(*avp==NULL)
            return 0;
        **avp=path;
        return 1;
    }else if(path!=NULL){
        free(path);
    }

    /* Identify directories with trailing / */
    if(dcomp){
        for (i = 0; i < ac; i++) {
            path = NEW(char,strlen(dir)+strlen((*avp)[i])+3);
            sprintf(path,"%s/%s",dir,(*avp)[i]);
            if (el_is_directory(path)) {
                tt = NEW(char,strlen((*avp)[i])+2);
                sprintf(tt,"%s/",(*avp)[i]);
                DISPOSE((*avp)[i]);
                (*avp)[i] = tt;
            }
            DISPOSE(path);
        }
    }

    *beg=dir;

    return ac;
}



int main(int argc, char *argv[])
{
    char **avp=NULL;
    char *beg=NULL;
    bool wp=FALSE;
    int i, j, n;

    libtu_init(argv[0]);

    for(i=1; i<argc; i++){
        if(strcmp(argv[i], "-h")==0){
            printf("Usage: ion-completefile [-help] [-wp] [to_complete...]\n");
            return EXIT_SUCCESS;
        }
    }

    for(i=1; i<argc; i++){
        if(strcmp(argv[i], "-wp")==0){
            wp=TRUE;
        }else{
            if(wp){
                n=do_complete_file_with_path(argv[i], &avp, &beg);
            }else{
                n=do_complete_file(argv[i], &avp, &beg);
            }

            if(beg){
                printf("%s\n", beg);
                free(beg);
                beg=NULL;
            }else{
                printf("\n");
            }


            if(avp){
                for(j=0; j<n; j++){
                    printf("%s\n", avp[j]);
                    free(avp[j]);
                }
                free(avp);
                avp=NULL;
            }
        }
    }

    return EXIT_SUCCESS;
}
