/*===========================================================================* * * * sfldir.c - * * * * Copyright (c) 1991-2003 iMatix Corporation * * * * ------------------ GPL Licensed Source Code ------------------ * * iMatix makes this software available under the GNU General * * Public License (GPL) license for open source projects. For * * details of the GPL license please see www.gnu.org or read the * * file license.gpl provided in this package. * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program in the file 'license.gpl'; if * * not, write to the Free Software Foundation, Inc., 59 Temple * * Place - Suite 330, Boston, MA 02111-1307, USA. * * * * You can also license this software under iMatix's General Terms * * of Business (GTB) for commercial projects. If you have not * * explicitly licensed this software under the iMatix GTB you may * * only use it under the terms of the GNU General Public License. * * * * For more information, send an email to info@imatix.com. * * -------------------------------------------------------------- * *===========================================================================*/ #include "prelude.h" /* Universal header file */ #include "sfldate.h" /* Date handling functions */ #include "sfluid.h" /* Uid/gid functions */ #include "sflstr.h" /* String functions */ #include "sfllist.h" /* Linked-list functions */ #include "sflmem.h" /* Memory allocation functions */ #include "sflnode.h" /* Linked-list functions */ #include "sflfile.h" /* File-access functions */ #include "sflcons.h" /* Console display functions */ #include "sflprint.h" /* snprintf functions */ #include "sfldir.h" /* Prototypes for functions */ /* Static variables used globally in this source file only */ static char *sort_key; /* Function prototypes */ static Bool populate_entry (DIRST *dir); static time_t get_six_months_ago (void); static char *format_mode (DIRST *dir); static char *format_name (DIRST *dir, Bool full); static char *format_time (DIRST *dir); static Bool compare_dir (LIST *p1, LIST *p2); static Bool path_delimiter (char delim); /* ---------------------------------------------------------------------[<]- Function: open_dir Synopsis: Creates a directory stream and returns the first entry in the directory. The order of entries is arbitrary, and it is undefined whether you will get entries like '.' and '..' or not. Returns TRUE if something was found, else FALSE. If TRUE, fills-in the values in the directory stream block. Use the same directory stream block for the read_dir and close_dir() functions. You must supply a DIRST block when calling open_dir(). If you do not supply a dir_name (i.e. it is NULL or ""), open_dir() assumes you want to read from the current working directory. You must call close_dir() after an open_dir(), even if it fails. The strings in DIRST all point to static areas that may change after a further call to read_dir. If you need persistent data (i.e. because you want to collect a set of DIRSTs and then sort them, call fix_dir() after each call to open_dir and read_dir. You should then call free_dir() to release each DIRST when you are finished. ---------------------------------------------------------------------[>]-*/ Bool open_dir ( DIRST *dir, const char *dir_name) { char *dir_spec, /* Directory to search through */ *dir_spec_end; /* Points to NULL in dir_spec */ ASSERT (dir != NULL); memset (dir, 0, sizeof (DIRST)); /* Copy and prepare the directory specification */ dir_spec = mem_alloc (NAME_MAX); if (dir_name == NULL || *dir_name == 0) strcpy (dir_spec, DEFAULT_DIR); else strcpy (dir_spec, dir_name); #if (defined (GATES_FILESYSTEM)) strconvch (dir_spec, '/', '\\'); #endif /* Remove a trailing slash from the directory name */ dir_spec_end = dir_spec + strlen (dir_spec); if (dir_spec_end [-1] == PATHEND) { dir_spec_end [-1] = '\0'; dir_spec_end--; } /* Open directory stream or find first directory entry */ #if (defined (__UNIX__) || defined (__VMS_XOPEN) || defined (__OS2__)) if (strnull (dir_spec)) strcpy (dir_spec, "/"); # if (defined (__OS2__)) if (dir_spec_end [-1] == ':') /* Special case d: to d:\ */ strcat (dir_spec, "\\"); # endif if ((dir-> _dir_handle = opendir (dir_spec)) == NULL) #elif (defined (WIN32)) strcat (dir_spec, "\\*"); if ((dir-> _dir_handle = FindFirstFile (dir_spec, &dir-> _dir_entry)) == INVALID_HANDLE_VALUE) #elif (defined (_MSC_VER)) strcat (dir_spec, "\\*.*"); if ((dir-> _dir_handle = _dos_findfirst (dir_spec, _A_NORMAL | _A_SUBDIR, &dir-> _dir_entry)) != 0) #elif (defined (__TURBOC__) || defined (__DJGPP__)) strcat (dir_spec, "\\*.*"); if (findfirst (dir_spec, &dir-> _dir_entry, 255 - FA_LABEL) == -1) #endif { mem_free (dir_spec); return (FALSE); /* Could not open directory */ } /* Save the directory name in directory stream structure */ #if (defined (__MSDOS__) || defined (__OS2__)) *dir_spec_end = '\0'; /* Kill the \*.* again */ #endif dir-> dir_name = dir_spec; /* Now owned by DIRST structure */ #if (defined (__UNIX__) || defined (__VMS_XOPEN) || defined (__OS2__)) /* Under UNIX & VMS we still need to fetch the first file entry */ return (read_dir (dir)); #elif (defined (WIN32)) /* Under Win32 we have read an entry, so return those values */ return (populate_entry (dir)); #elif (defined (_MSC_VER)) /* Under MSC we have read an entry, so return those values */ return (populate_entry (dir)); #elif (defined (__TURBOC__) || defined (__DJGPP__)) /* Under Borland C we have read an entry, so return those values */ return (populate_entry (dir)); #else return (FALSE); /* Directory access not supported */ #endif } /* ------------------------------------------------------------------------- * populate_entry -- internal * * Sets the various public fields in the directory stream from the system- * specific values in the private fields. Returns TRUE if okay, FALSE if * there was a problem getting the file status information. */ static Bool populate_entry (DIRST *dir) { #if (defined (WIN32)) static char *default_user = "user"; #else char *full_path; /* Full path name of the file */ struct stat stat_buf; /* File status structure */ int rc; #endif ASSERT (dir != NULL); if (dir-> _fixed) free_dir (dir); /* De-allocate old strings if reqd */ /* Get name of file from directory structure */ #if (defined (__UNIX__) || defined (__VMS_XOPEN) || defined (__OS2__)) dir-> file_name = dir-> _dir_entry-> d_name; #elif (defined (WIN32)) dir-> file_name = dir-> _dir_entry.cFileName; #elif (defined (_MSC_VER)) dir-> file_name = dir-> _dir_entry.name; #elif (defined (__TURBOC__) || defined (__DJGPP__)) dir-> file_name = dir-> _dir_entry.ff_name; #endif #if (defined (__UNIX__) || defined (GATES_FILESYSTEM)) /* If the filename is . or .., skip this entry and do the next. We */ /* use a little bit of a recursive call to make this code simple. */ if (dir-> file_name [0] == '.' && (dir-> file_name [1] == '\0' || (dir-> file_name [1] == '.' && dir-> file_name [2] == '\0'))) return (read_dir (dir)); #endif /* Prepare full path and get file status information. Most systems have * some kind of stat() function; we call this, even if similar info is * already available in the _dir_entry structures. Except for Win32 * where we use the native Win32 API to support compilers which may not * have all the C wrapper functions (e.g. RSXNT). */ #if (defined (WIN32)) /* Get file info using Win32 calls */ { unsigned long thi, tlo; double dthi, dtlo; double secs_since_1601; double delta = 11644473600.; double two_to_32 = 4294967296.; thi = dir-> _dir_entry.ftLastWriteTime.dwHighDateTime; tlo = dir-> _dir_entry.ftLastWriteTime.dwLowDateTime; dthi = (double) thi; dtlo = (double) tlo; secs_since_1601 = (dthi * two_to_32 + dtlo) / 1.0e7; dir-> file_time = (unsigned long) (secs_since_1601 - delta); dir-> file_size = dir->_dir_entry.nFileSizeLow; dir-> file_mode = 0; if (dir-> _dir_entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) dir-> file_mode |= S_IFDIR; else dir-> file_mode |= S_IFREG; if (!(dir-> _dir_entry.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN)) dir-> file_mode |= S_IREAD; if (!(dir-> _dir_entry.dwFileAttributes & FILE_ATTRIBUTE_READONLY)) dir-> file_mode |= S_IWRITE; dir-> file_nlink = 1; dir-> owner = NULL; dir-> group = NULL; } #else full_path = xstrcpy (NULL, dir-> dir_name, "/", dir-> file_name, NULL); rc = stat (full_path , &stat_buf); mem_free (full_path); if (rc == -1) return (FALSE); dir-> file_time = stat_buf.st_mtime; /* Modification time */ dir-> file_size = stat_buf.st_size; /* Size in bytes */ dir-> file_mode = stat_buf.st_mode; /* UNIX-ish permissions */ dir-> file_nlink = stat_buf.st_nlink; /* Number of links to file */ /* Get owner and group names */ dir-> owner = get_uid_name (stat_buf.st_uid); dir-> group = get_gid_name (stat_buf.st_gid); #endif /* Prepare DOS-ish permission bits */ #if (defined (__UNIX__) || defined (__VMS_XOPEN) || defined (__OS2__)) dir-> file_attrs = 0; if ((stat_buf.st_mode & S_IREAD) == 0) dir-> file_attrs |= ATTR_HIDDEN; if ((stat_buf.st_mode & S_IWRITE) == 0) dir-> file_attrs |= ATTR_RDONLY; if ((stat_buf.st_mode & S_IFDIR) != 0) dir-> file_attrs |= ATTR_SUBDIR; #elif (defined (WIN32)) dir-> file_attrs = 0; if (dir-> _dir_entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) dir-> file_attrs |= ATTR_SUBDIR; if (dir-> _dir_entry.dwFileAttributes & FILE_ATTRIBUTE_READONLY) dir-> file_attrs |= ATTR_RDONLY; if (dir-> _dir_entry.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) dir-> file_attrs |= ATTR_HIDDEN; if (dir-> _dir_entry.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) dir-> file_attrs |= ATTR_SYSTEM; dir-> owner = dir-> group = default_user; #elif (defined (_MSC_VER)) dir-> file_attrs = (byte) dir-> _dir_entry.attrib & ATTR_MASK; #elif (defined (__TURBOC__) || defined (__DJGPP__)) dir-> file_attrs = (byte) dir-> _dir_entry.ff_attrib & ATTR_MASK; #endif return (TRUE); /* No errors */ } /* ---------------------------------------------------------------------[<]- Function: read_dir Synopsis: Reads the next entry from the directory stream. Returns TRUE if there was more data to read; returns FALSE if there was nothing more to read. Updates the fields in the directory structure when it returns TRUE. The strings in DIRST all point to static areas that may change after a further call to read_dir. If you need persistent data (i.e. because you want to collect a set of DIRSTs and then sort them, call fix_dir() after each call to open_dir and read_dir. You should then call free_dir() to release each DIRST when you are finished. ---------------------------------------------------------------------[>]-*/ Bool read_dir ( DIRST *dir) { ASSERT (dir != NULL); #if (defined (__UNIX__) || defined (__VMS_XOPEN) || defined (__OS2__)) if ((dir-> _dir_entry = (struct Dirent *) readdir (dir-> _dir_handle)) != NULL) return (populate_entry (dir)); else #elif (defined (WIN32)) if (FindNextFile (dir-> _dir_handle, &dir-> _dir_entry)) return (populate_entry (dir)); else #elif (defined (_MSC_VER)) if (_dos_findnext (&dir-> _dir_entry) == 0) return (populate_entry (dir)); else #elif (defined (__TURBOC__) || defined (__DJGPP__)) if (findnext (&dir-> _dir_entry) == 0) return (populate_entry (dir)); else #endif return (FALSE); } /* ---------------------------------------------------------------------[<]- Function: close_dir Synopsis: Close the directory stream, and free any allocated memory. You should call this function when you are done reading a directory, or you will get memory leaks. Returns TRUE if okay, FALSE if there was an error. ---------------------------------------------------------------------[>]-*/ Bool close_dir ( DIRST *dir) { Bool rc; ASSERT (dir != NULL); mem_free (dir-> dir_name); /* Free dynamically-allocated name */ #if (defined (__UNIX__) || defined (__VMS_XOPEN) || defined (__OS2__)) ASSERT (dir-> _dir_handle); rc = (closedir (dir-> _dir_handle) == 0); #elif (defined (WIN32)) rc = FindClose (dir-> _dir_handle); #elif (defined (_MSC_VER)) rc = TRUE; /* No function to close a dir */ #elif (defined (__TURBOC__) || defined (__DJGPP__)) rc = TRUE; /* No function to close a dir */ #else rc = FALSE; /* Directory access not supported */ #endif return (rc); } /* ---------------------------------------------------------------------[<]- Function: format_dir Synopsis: Formats the directory entry information using the same conventions as the UNIX 'ls -l' command. Returns a static buffer that contains the the formatted string. If the full argument is TRUE, adds cosmetic hints to indicate the file type; for instance '/' if the file is a directory, '*' if it is executable. ---------------------------------------------------------------------[>]-*/ char * format_dir ( DIRST *dir, Bool full) { static char buffer [LINE_MAX]; /* Formatted directory entry */ ASSERT (dir != NULL); snprintf (buffer, sizeof (buffer), "%s %3d %-8.8s %-8.8s %8ld %s %s", format_mode (dir), dir-> file_nlink, dir-> owner, dir-> group, (long) dir-> file_size, format_time (dir), format_name (dir, full) ); return (buffer); } /* ------------------------------------------------------------------------- * format_mode -- internal * * Returns a static string holding the ASCII representation of the UNIX-ish * permission bits for the directory entry specified. */ static char * format_mode ( DIRST *dir) { static char buffer [11], /* duuugggooo + null */ *bufptr; /* Pointer into buffer */ dbyte mode = dir-> file_mode; /* File mode bits */ int field; /* User, group, other */ bufptr = buffer; *bufptr++ = ((mode & S_IFDIR) ? 'd' : '-'); for (field = 0; field < 3; field++) { *bufptr++ = ((mode & (S_IREAD >> (field * 3))) ? 'r' : '-'); *bufptr++ = ((mode & (S_IWRITE >> (field * 3))) ? 'w' : '-'); *bufptr = ((mode & (S_IEXEC >> (field * 3))) ? 'x' : '-'); #if (defined (S_ISUID)) if (field == 0 && (mode & S_ISUID)) *bufptr = (*bufptr == 'x' ? 's' : 'S'); else if (field == 1 && (mode & S_ISGID)) *bufptr = (*bufptr == 'x' ? 's' : 'S'); # if (defined (S_ISVTX)) else if (field == 2 && (mode & S_ISVTX)) *bufptr = (*bufptr == 'x' ? 't' : 'T'); # endif #endif ++bufptr; } *bufptr = '\0'; return (buffer); } /* ------------------------------------------------------------------------- * format_name -- internal * * Returns a static string holding the cleaned-up filename for the directory * entry specified. Replaces any control characters by an octal string * and appends '/' if the entry is a directory, or '*' if it is executable, * if the full argument is TRUE. */ static char * format_name (DIRST *dir, Bool full) { static char buffer [NAME_MAX + 1], *fromptr, *bufptr; bufptr = buffer; for (fromptr = dir-> file_name; *fromptr; bufptr++, fromptr++) { *bufptr = *fromptr; if ((byte) *bufptr < ' ') { sprintf (bufptr, "\\%03o", (byte) *fromptr); bufptr += strlen (bufptr) - 1; } } if (full) { if (dir-> file_mode & S_IFDIR) *bufptr++ = '/'; else if (dir-> file_mode & S_IEXEC) *bufptr++ = '*'; } *bufptr = '\0'; return (buffer); } /* ------------------------------------------------------------------------- * format_time -- internal * * Returns a static string holding the ASCII representation of the date and * time for the specified directory entry. The format of the date and time * depends on whether the file is older than 6 months, roughly: * * Oct 9 1995 if older than 6 months * Jan 12 09:55 if not older than 6 months */ static char * format_time (DIRST *dir) { static char buffer [13], /* Returned string */ *months = "Jan\0Feb\0Mar\0Apr\0May\0Jun\0Jul\0Aug\0Sep\0Oct\0Nov\0Dec"; static time_t six_months_ago = 0; struct tm *file_time; if (six_months_ago == 0) six_months_ago = get_six_months_ago (); file_time = safe_localtime (&dir-> file_time); strncpy (buffer, months + file_time-> tm_mon * 4, 4); if (dir-> file_time < six_months_ago || dir-> file_time > time (NULL)) snprintf (buffer + 3, sizeof (buffer) - 3, " %2d %4d", file_time-> tm_mday, file_time-> tm_year + 1900); else snprintf (buffer + 3, sizeof (buffer) - 3, " %2d %02d:%02d", file_time-> tm_mday, file_time-> tm_hour, file_time-> tm_min); return (buffer); } /* ------------------------------------------------------------------------- * get_six_months_ago -- internal * * Returns system clock 6 months ago. */ static time_t get_six_months_ago () { int count, year, month; long days; time_t time_val; struct tm *time_now; time_val = time (NULL); time_now = safe_localtime (&time_val); month = time_now-> tm_mon + 1; year = time_now-> tm_year; days = 0; for (count = 0; count < 6; count++) { if (--month < 1) /* Start with last month */ { month = 12; year--; } switch (month) { case 2: /* Feb */ days += leap_year (year)? 29: 28; break; case 1: /* Jan */ case 3: /* Mar */ case 5: /* May */ case 7: /* Jul */ case 8: /* Aug */ case 10: /* Oct */ case 12: /* Dec */ days++; case 4: /* Apr */ case 6: /* Jun */ case 9: /* Sep */ case 11: /* Nov */ days += 30; } } /* 60 * 60 * 24 = 86400 sec / day */ return (time (NULL) - (long) (86400L * days)); } /* ---------------------------------------------------------------------[<]- Function: fix_dir Synopsis: Converts all strings in the DIRST into permenant values, by allocating heap memory for each string. You must call this function if you intend to keep a set of DIRSTs, for searching or sorting. You do not need to call fix_dir() if you handle each call to read_dir() independently. If you use fix_dir(), you must call free_dir() for each DIRST when you terminate. Returns 0 if okay, -1 if there was insufficient memory or another fatal error. ---------------------------------------------------------------------[>]-*/ int fix_dir (DIRST *dir) { char *owner, *group, *file_name; /* Allocate each string */ owner = mem_strdup (dir-> owner); group = mem_strdup (dir-> group); file_name = mem_strdup (dir-> file_name); /* If all okay, assign new strings and indicate everything okay */ if (owner && group && file_name) { dir-> owner = owner; dir-> group = group; dir-> file_name = file_name; dir-> _fixed = TRUE; return (0); } else { /* Otherwise patch things back the way they were */ if (owner) mem_free (owner); if (group) mem_free (group); if (file_name) mem_free (file_name); return (-1); } } /* ---------------------------------------------------------------------[<]- Function: free_dir Synopsis: Frees all strings used in the DIRST. You should call this function to free space allocated by fix_dir(). If you try to call free_dir() for a DIRST that was not fixed, you will get an error feedback, and (if you compiled with DEBUG defined) an assertion fault. Returns 0 if okay, -1 if there was an error. After a call to free_dir(), do not try to access the strings in DIRST; they are all set to point to an empty string. ---------------------------------------------------------------------[>]-*/ int free_dir (DIRST *dir) { static char *empty = ""; ASSERT (dir-> _fixed); if (dir-> _fixed) { /* Free allocated strings */ mem_free (dir-> owner); mem_free (dir-> group); mem_free (dir-> file_name); /* Now point the strings to an empty string */ dir-> owner = empty; dir-> group = empty; dir-> file_name = empty; /* And mark the DIRST as no longer 'fixed' */ dir-> _fixed = FALSE; return (0); } else return (-1); } /* ---------------------------------------------------------------------[<]- Function: load_dir_list Synopsis: Loads and sorts the contents of a directory. Returns a NODE pointer to a linked list containing the directory entries. Each node is a FILEINFO structure (mapped onto a NODE structure for purposes of manipulating the linked list). You can ask for the directory list to be sorted in various ways; in this case subdirectory entries are always sorted first. To specify the sort order you pass a string consisting of one or more of these characters, which are then used in order: