/***************************************************************************/ /***************************************************************************/ /* */ /* (c) 1995-1999. The Regents of the University of California. All */ /* rights reserved. */ /* */ /* This work was produced at the University of California, Lawrence */ /* Livermore National Laboratory (UC LLNL) under contract no. */ /* W-7405-ENG-48 (Contract 48) between the U.S. Department of Energy */ /* (DOE) and The Regents of the University of California (University) */ /* for the operation of UC LLNL. Copyright is reserved to the */ /* University for purposes of controlled dissemination, */ /* commercialization through formal licensing, or other disposition */ /* under terms of Contract 48; DOE policies, regulations and orders; */ /* and U.S. statutes. The rights of the Federal Government are */ /* reserved under Contract 48 subject to the restrictions agreed upon */ /* by the DOE and University. */ /* */ /* */ /* DISCLAIMER */ /* */ /* This software was prepared as an account of work sponsored by an */ /* agency of the United States Government. Neither the United States */ /* Government nor the University of California nor any of their */ /* employees, makes any warranty, express or implied, or assumes any */ /* liability or responsibility for the accuracy, completeness, or */ /* usefulness of any information, apparatus, product, or process */ /* disclosed, or represents that its specific commercial products, */ /* process, or service by trade name, trademark, manufacturer, or */ /* otherwise, does not necessarily constitute or imply its */ /* endorsement, recommendation, or favoring by the United States */ /* Government or the University of California. The views and opinions */ /* of the authors expressed herein do not necessarily state or reflect */ /* those of the United States Government or the University of */ /* California, and shall not be used for advertising or product */ /* endorsement purposes. */ /* */ /* Permission to use, copy, modify and distribute this software and its */ /* documentation for any non-commercial purpose, without fee, is */ /* hereby granted, provided that the above copyright notice and this */ /* permission notice appear in all copies of the software and */ /* supporting documentation, and that all UC LLNL identification in */ /* the user interface remain unchanged. The title to copyright LLNL */ /* XDIR shall at all times remain with The Regents of the University */ /* of California and users agree to preserve same. Users seeking the */ /* right to make derivative works with LLNL XDIR for commercial */ /* purposes may obtain a license from the Lawrence Livermore National */ /* Laboratory's Technology Transfer Office, P.O. Box 808, L-795, */ /* Livermore, CA 94550. */ /* */ /***************************************************************************/ /***************************************************************************/ #include #include #include #ifdef apollo #include #else #ifdef NEXT #include #else #include #endif #endif #include #include #include #include "xdir.h" #include "list.h" #include "str.h" #ifdef apollo #define dirent direct #endif #ifdef __EMX__ #define lstat stat #endif #ifndef S_ISLNK #define S_ISLNK(mode) (((mode)&0170000) == 0120000) /* Kludge for nonPOSIX */ #endif #ifndef S_ISSOCK #define S_ISSOCK(mode) (((mode)&0170000) == 0140000) /* Kludge for nonPOSIX */ #endif #define MAXDIRLINE 200 extern int max_ftp_retries; extern int diagnostics; extern struct st_host_info hinfo[]; extern int maxt; extern int passive_mode; struct sl_struct *create_array_list(); char *ifgets(); struct sl_struct *create_null_array_list(); int cmp_long_vms_entries(); /* * local_ls - Sets "dlist" to a pointer to a structure that contains a * sorted list of directory entries in "directory" on the * local host. Set "sym_mode" to True if type symbol is to * be appended to directory entries. Set "dotfiles" to True * if dot files are to be included. Returns 0 if successful, * else returns -1. If unable to read the directory because * of lack of permission, returns 0 and a single entry named * ".unreadable". */ local_ls(directory, dlist, sym_mode, dotfiles) char *directory; struct sl_struct **dlist; int sym_mode; int dotfiles; { struct entry_link *head = NULL; DIR *dirp; struct dirent *dp; char entry[MAXPATHLEN]; struct stat status; int retval; if ((dirp = opendir(directory)) == NULL) { if (errno == EACCES) { add_to_linked_list(&head, ".unreadable"); goto done; } else return -1; } if (sym_mode) retval = chdir(directory); for (dp=readdir(dirp); dp!=NULL; dp=readdir(dirp)) if (strcmp(dp->d_name, ".") && strcmp(dp->d_name, "..") && (dotfiles || dp->d_name[0] != '.')) { if (sym_mode && !retval && !lstat(dp->d_name, &status)) { strcpy(entry, dp->d_name); if (S_ISREG(status.st_mode)) { if ((S_IXUSR | S_IXGRP | S_IXOTH) & status.st_mode) strcat(entry, "*"); } else if (S_ISDIR(status.st_mode)) strcat(entry, "/"); else if (S_ISLNK(status.st_mode)) strcat(entry, "@"); else if (S_ISSOCK(status.st_mode)) strcat(entry, "="); else if ((S_IXUSR | S_IXGRP | S_IXOTH) & status.st_mode) strcat(entry, "*"); add_to_linked_list(&head, entry); } else add_to_linked_list(&head, dp->d_name); } closedir(dirp); done: /* Convert linked list into array */ *dlist = create_array_list(&head); /* Sort entries */ if (sym_mode) sort_symbol_entries((*dlist)->entries, (*dlist)->nentries); else quicksort((*dlist)->entries, (*dlist)->nentries, strcmp); return 0; } /* * local_dir - Sets "dlist" to a pointer to a structure that contains a * sorted list of directory entries in "directory" on the * local host. Set "dotfiles" to True if dot files are to * be included. Returns 0 if successful, else returns -1. */ local_dir(directory, dlist, dotfiles) char *directory; struct sl_struct **dlist; int dotfiles; { struct entry_link *head = NULL; char dirline[MAXDIRLINE+1]; FILE *fp; int len; int dummy; char cmd[MAXPATHLEN+15]; char *name; char *info; char type; save_sigcld_handler(); #ifdef __EMX__ sprintf(cmd, "ls -gal %s", directory); #else sprintf(cmd, "/bin/ls -gal %s", directory); #endif if ((fp = popen(cmd, "r")) == NULL) { restore_sigcld_handler(); return -1; } while (ifgets(dirline, MAXDIRLINE, fp) != NULL) { len = strlen(dirline); if (dirline[len-1] == '\n') dirline[--len] = '\0'; if (sscanf(dirline, "total %d", &dummy) == 1) continue; unix_parse_long_entry(dirline, &name, &info, &type); if (strcmp(name, ".") && strcmp(name, "..") && (dotfiles || name[0] != '.')) add_to_linked_list(&head, dirline); XtFree(name); XtFree(info); } restore_sigcld_handler(); if (ferror(fp)) { report_perror("Getting long list"); release_linked_list(&head); return -1; } pclose(fp); /* Convert linked list into array */ *dlist = create_array_list(&head); return 0; } /* * remote_dirlist - Set "dlist" to a pointer to a structure that * contains a list of entries in "directory" on the * remote host. Set "cmd" to either "NLST" or * "LIST", optionally followed by an option * (e.g., "NLST -lagt"). "host" is the host id. * Returns 0 if successful, -3 for broken connection, * -6 for stop button pressed, and -1 for other errors. */ remote_dirlist(host, directory, cmd, dlist) int host; char *directory; char *cmd; struct sl_struct **dlist; { char ftpline[MAXFTPLINE+1]; struct entry_link *head; int data_fd; char ch; int nbytes; int code; char reply[MAXFTPREPLY]; int sock; int linelen; char tcpbuf[MAXTCPBUF]; int len_tcpbuf; int index_tcpbuf; int retval; int retries = 0; int scode; char temp[20]; char *request; int stop_was_pressed = False; int errval; /* Verify that request is valid */ strcpy(temp, cmd); if ((request = strtok(temp, " ")) == NULL) return -1; if (strcmp(request, "NLST") && strcmp(request, "LIST")) return -1; /* Always use ASCII mode for lists */ if ((retval = ftp_type(host, ASCII)) < 0) return retval; retry: /* Initialize linked list of directory entries */ head = NULL; /* "cd" to directory */ if ((retval = remote_cd(host, directory, True)) < 0) return retval; /* Initialize data connection */ if (passive_mode) { if ((data_fd = passive_data_conn(host)) < 0) return data_fd; } else if ((sock = init_data_conn(host)) < 0) return sock; /* Send command to FTP server */ if ((retval = send_ftp_cmd(host, cmd)) == -2) { report_client_timeout(host); errval = -1; goto error; } else if (retval < 0) { errval = retval; goto error; } /* Get initial response from FTP server */ scode = get_ftp_reply(host, reply, MAXFTPREPLY, &code, maxt); switch (scode) { case 1: break; case 4: if (passive_mode) close(data_fd); else close(sock); if (code >= 450 && code < 460 && retries++ < max_ftp_retries) { if (diagnostics >= VERBOSE) report_retry("Directory list"); goto retry; } if (diagnostics < VERBOSE) report_ftp_reply(host, reply); return -1; case 5: if (passive_mode) close(data_fd); else close(sock); if (code == 550) /* Kludge to handle empty UNICOS directories */ goto convert; else if (code == 501) goto convert; /* Kludge to handle empty MacOS directories */ if (diagnostics < VERBOSE) report_ftp_reply(host, reply); return -1; case -6: abort_ftp_cmd(host); case -3: errval = scode; goto error; case -2: report_client_timeout(host); default: if (scode > 0 && diagnostics < VERBOSE) report_ftp_reply(host, reply); errval = -1; goto error; } /* Establish data connection for nonpassive mode*/ if (!passive_mode) { data_fd = accept_data_conn(sock); close(sock); if (data_fd < 0) return -1; } /* Get directory list from FTP server */ len_tcpbuf = 0; index_tcpbuf = 0; linelen = 0; while (1) { if (index_tcpbuf >= len_tcpbuf) { nbytes = read_tcp(host, data_fd, tcpbuf, MAXTCPBUF, maxt); switch (nbytes) { case -6: stop_was_pressed = True; case -2: release_linked_list(&head); goto abort; case -1: case -3: release_linked_list(&head); close(data_fd); return nbytes; case 0: goto got_list; } index_tcpbuf = 0; len_tcpbuf = nbytes; } ch = tcpbuf[index_tcpbuf++]; if (ch == '\n') { ftpline[linelen] = '\0'; if (diagnostics == DEBUG) { write_log(ftpline); write_log("\n"); } add_to_linked_list(&head, ftpline); linelen = 0; } else if (ch != '\r') { if (linelen >= MAXFTPLINE) { release_linked_list(&head); close(data_fd); return -1; } ftpline[linelen++] = ch; } } got_list: /* Get final response from FTP server */ close(data_fd); scode = get_ftp_reply(host, reply, MAXFTPREPLY, &code, maxt); switch (scode) { case 2: break; case -6: abort_ftp_cmd(host); case -3: release_linked_list(&head); return scode; case -2: report_client_timeout(host); default: if (scode == 4 && code/10 == 45 && retries++ < max_ftp_retries) { if (diagnostics >= VERBOSE) report_retry("Directory list"); release_linked_list(&head); goto retry; } if (code == 550) { /* Kludge to handle empty VMS directories */ break; } if (scode > 0 && diagnostics < VERBOSE) report_ftp_reply(host, reply); release_linked_list(&head); return -1; } convert: /* Convert linked list into array */ *dlist = create_array_list(&head); return 0; abort: /* Send urgent abort to server */ if (urgent_abort(host) == -6) stop_was_pressed = True; /* Clear out all incoming data */ while ((retval = read_tcp(host, data_fd, tcpbuf, MAXTCPBUF, 10)) > 0); if (retval == -6) stop_was_pressed = True; close(data_fd); /* Get responses from server */ retval = get_ftp_reply(host, reply, MAXFTPREPLY, &code, 15); if (retval == -6) stop_was_pressed = True; if (retval > 0) { retval = get_ftp_reply(host, reply, MAXFTPREPLY, &code, 5); if (retval == -6) stop_was_pressed = True; } /* Report any timeout */ if (retval == -2) report_client_timeout(host); /* Want to check for lost host before issuing next command */ hinfo[host].last_reply_time = 0; /* Determine return value */ if (stop_was_pressed) return -6; else if ((retval == -3) || (retval == -2)) return -3; else return -1; error: if (passive_mode) close(data_fd); else close(sock); return errval; } /* * remote_ls - Sets "dlist" to a pointer to a structure that contains * a sorted list of directory entries in "directory" on the * remote host. "host" is the host id. Set sym_mode to * True if type symbol is to be appended to directory * entries. Set "dotfiles" to True if dot files are to * be included. Returns 0 if successful, -3 for broken * connection, -6 for stop button pressed, and -1 for * other errors. */ remote_ls(host, directory, dlist, sym_mode, dotfiles) int host; char *directory; struct sl_struct **dlist; int sym_mode; int dotfiles; { int retval; int i; int j; int len; char *dirline; /* Get short list */ if (sym_mode && ((hinfo[host].system == SYS_UNIX) || (hinfo[host].server == SERVER_MAC_FTPD) || (hinfo[host].system == SYS_NT))) retval = remote_dirlist(host, directory, "NLST -aF", dlist); else if (dotfiles && (hinfo[host].system == SYS_UNIX)) retval = remote_dirlist(host, directory, "NLST -a", dlist); else retval = remote_dirlist(host, directory, "NLST", dlist); if (retval < 0) return retval; /* Kludge for certain VMS FTP servers */ if (hinfo[host].server == SERVER_VMS_3_0) for (i=0; i<(*dlist)->nentries; i++) { len = strlen((*dlist)->entries[i]); for (j=0; jentries[i][j] = tolower((*dlist)->entries[i][j]); } /* Remove trailing slashes from certain FTP servers */ if (!sym_mode && (hinfo[host].server == SERVER_MAC_VERSATERM || hinfo[host].server == SERVER_MAC_NCSATELNET || hinfo[host].server == SERVER_DOS_WINQVT)) for (i=0; i<(*dlist)->nentries; i++) { len = strlen((*dlist)->entries[i]); if (len > 0 && (*dlist)->entries[i][len-1] == '/') (*dlist)->entries[i][len-1] = '\0'; } /* Delete unwanted entries */ if (hinfo[host].system != SYS_VMS) for (i=(*dlist)->nentries-1; i>=0; i--) { dirline = (*dlist)->entries[i]; if (sym_mode) { if (!strcmp(dirline, "./") || !strcmp(dirline, "../")) delete_from_array_list(dlist, i); } else { if (!strcmp(dirline, ".") || !strcmp(dirline, "..")) delete_from_array_list(dlist, i); } } /* Process dot files */ if (!dotfiles) for (i=(*dlist)->nentries-1; i>=0; i--) { if ((*dlist)->entries[i][0] == '.') delete_from_array_list(dlist, i); } /* Sort entries */ if (sym_mode && ((hinfo[host].system == SYS_UNIX) || (hinfo[host].system == SYS_NT) || (hinfo[host].server == SERVER_MAC_FTPD) || (hinfo[host].server == SERVER_MAC_VERSATERM) || (hinfo[host].server == SERVER_MAC_NCSATELNET) || (hinfo[host].server == SERVER_DOS_WINQVT))) sort_symbol_entries((*dlist)->entries, (*dlist)->nentries); else quicksort((*dlist)->entries, (*dlist)->nentries, strcmp); return 0; } /* * remote_dir - Sets "dlist" to a pointer to a structure that contains * a long list of directory entries in "directory" on the * remote host. "host" is the host id. Set "dotfiles" * to True if dot files are to be included. Returns 0 if * successful, -3 for broken connection, -6 for stop button * pressed, and -1 for other errors. */ remote_dir(host, directory, dlist, dotfiles) int host; char *directory; struct sl_struct **dlist; int dotfiles; { int i; int j; int retval; int dummy; int len; char *dirline; char *name; char *info; char type; char ch; int count; char *prev_dirline; char *temp; /* Get extended list */ if (dotfiles && (hinfo[host].system == SYS_UNIX) && (hinfo[host].server != SERVER_UNIX_UNITREE) && (hinfo[host].server != SERVER_UNIX_NSL_UNITREE)) retval = remote_dirlist(host, directory, "NLST -gal", dlist); else retval = remote_dirlist(host, directory, "LIST", dlist); if (retval) return retval; /* Done if no entries */ if ((*dlist)->nentries == 0) return 0; /* Delete unwanted lines */ switch (hinfo[host].system) { case SYS_UNIX: case SYS_WINDOWS: for (i=(*dlist)->nentries-1; i>=0; i--) { dirline = (*dlist)->entries[i]; len = strlen(dirline); if (sscanf(dirline, "total %d", &dummy) == 1) delete_from_array_list(dlist, i); } break; case SYS_VMS: if ((*dlist)->nentries < 4) goto error; dirline = (*dlist)->entries[(*dlist)->nentries-1]; if ((count = extracted_vms_file_count(dirline)) < 0) goto error; delete_from_array_list(dlist, (*dlist)->nentries-1); for (i=2; i>=0; i--) delete_from_array_list(dlist, i); for (i=(*dlist)->nentries-1; i>=0; i--) if (strlen((*dlist)->entries[i])) break; else delete_from_array_list(dlist, i); if ((*dlist)->nentries < count) goto error; for (i=(*dlist)->nentries-1; i>=0; i--) { dirline = (*dlist)->entries[i]; if (dirline[0] == ' ') { if (i == 0) goto error; len = strlen(dirline); for (j=0; jentries[i-1]; temp = XtMalloc(strlen(prev_dirline)+strlen(&(dirline[j]))+3); strcpy(temp, prev_dirline); strcat(temp, " "); strcat(temp, &(dirline[j])); delete_from_array_list(dlist, i); i--; XtFree((*dlist)->entries[i]); (*dlist)->entries[i] = temp; } } if (count != (*dlist)->nentries) goto error; } /* Get rid of unwanted entries */ for (i=(*dlist)->nentries-1; i>=0; i--) { parse_long_entry(hinfo[host].system, hinfo[host].server, (*dlist)->entries[i], &name, &info, &type); if (((hinfo[host].system == SYS_UNIX || hinfo[host].system == SYS_WINDOWS) && (!strcmp(name, ".") || !strcmp(name, ".."))) || (!dotfiles && name[0] == '.')) delete_from_array_list(dlist, i); XtFree(name); XtFree(info); } /* Kludge for certain VMS servers that sort funny */ if (hinfo[host].server == SERVER_VMS_3_0) quicksort((*dlist)->entries, (*dlist)->nentries, cmp_long_vms_entries); return 0; error: release_array_list(*dlist); return -1; } /* * extracted_vms_file_count - Extracts the number of files specified in the * last line "line" of a long list of a nonempty * VMS directory. This routine looks for a * pattern of the form " xxx files." at the end * of "line". If this pattern is found, the * file length is returned, otherwise -1 is * returned. */ extracted_vms_file_count(line) char *line; { int count; int i; char *ptr; /* Number appears before string " file" */ ptr = strstr(line, " file"); for (i=ptr-line-1; i>=0; i--) if (!isdigit(line[i])) break; if ((i < 0) || (i == ptr-line-1) || (line[i] != ' ')) return -1; ptr = &(line[i+1]); count = 0; while (isdigit(*ptr)) count = (count*10)+(*ptr++-'0'); return count; } /* * get_dirlist - Sets "dlist" to a pointer to a structure that contains * a list of directory entries in directory "path" for host * "host". "layout" determines the format of the directory * list. Set "dotfiles" to True if dot files are to be * included in the directory list. Set "cfetch_ok" to * True if it is ok to fetch the directory list from the * cache. Set "cstore_ok" to True if it is ok to use the * directory list to update the cache. Returns 0 if * successful, 1 if list came from cache, -3 for broken * connection, -6 for stop button pressed, and -1 for other * errors. */ get_dirlist(host, path, layout, dotfiles, cstore_ok, cfetch_ok, dlist) int host; char *path; int layout; int dotfiles; int cstore_ok; int cfetch_ok; struct sl_struct **dlist; { int retval; int i; char *name; char *info; char type; /* Can the directory list cache be used? */ if (cfetch_ok && (host != LOCAL)) { if (fetch_dirlist_from_cache(host, path, layout, dotfiles, dlist)) return 1; } /* Get directory list */ if (host == LOCAL) switch (layout) { case TABULAR: retval = local_ls(path, dlist, False, dotfiles); break; case ICONIC: case TREE: retval = local_ls(path, dlist, True, dotfiles); break; case FULL_INFO: retval = local_dir(path, dlist, dotfiles); } else { switch (layout) { case TABULAR: retval = remote_ls(host, path, dlist, False, dotfiles); break; case ICONIC: case TREE: if (hinfo[host].server == SERVER_UNIX_NSL_UNITREE || hinfo[host].server == SERVER_WINDOWS_WFTPD) { if (!(retval = remote_dir(host, path, dlist, dotfiles))) for (i=0; i<(*dlist)->nentries; i++) { unix_parse_long_entry((*dlist)->entries[i], &name, &info, &type); XtFree((*dlist)->entries[i]); XtFree(info); (*dlist)->entries[i] = XtMalloc(strlen(name)+2); strcpy((*dlist)->entries[i], name); XtFree(name); switch (type) { case DIRECTORY_TYPE: strcat((*dlist)->entries[i], "/"); break; case LINK_TYPE: strcat((*dlist)->entries[i], "@"); break; case SOCKET_TYPE: strcat((*dlist)->entries[i], "="); break; case EXECUTABLE_TYPE: strcat((*dlist)->entries[i], "*"); } } } else retval = remote_ls(host, path, dlist, True, dotfiles); break; case FULL_INFO: retval = remote_dir(host, path, dlist, dotfiles); } if (!retval && cstore_ok) add_dirlist_to_cache(host, path, layout, dotfiles, *dlist); } return retval; }