2 * Copyright (c) 2014 by David I. Bell
3 * Permission is granted to use, distribute, or modify this source,
4 * provided that this copyright notice remains intact.
11 #include <sys/types.h>
19 * Chunks contain data which is allocated as needed, but which is
20 * not freed until all of the data needs freeing, such as at
21 * the beginning of the next command.
23 typedef struct chunk CHUNK;
24 #define CHUNK_INIT_SIZE 4
29 char data[CHUNK_INIT_SIZE]; /* actually of varying length */
33 static CHUNK * chunkList;
38 * Return the standard ls-like mode string from a file mode.
39 * This is static and so is overwritten on each call.
46 strcpy(buf, "----------");
49 * Fill in the file type.
69 * Now fill in the normal file permissions.
91 * Finally fill in magic stuff like suid and sticky text.
94 buf[3] = ((mode & S_IXUSR) ? 's' : 'S');
96 buf[6] = ((mode & S_IXGRP) ? 's' : 'S');
98 buf[9] = ((mode & S_IXOTH) ? 't' : 'T');
105 * Get the time string to be used for a file.
106 * This is down to the minute for new files, but only the date for old files.
107 * The string is returned from a static buffer, and so is overwritten for
111 timeString(time_t timeVal)
119 str = ctime(&timeVal);
121 strcpy(buf, &str[4]);
124 if ((timeVal > now) || (timeVal < now - 365*24*60*60L))
126 strcpy(&buf[7], &str[20]);
135 * Return TRUE if a fileName is a directory.
136 * Nonexistant files return FALSE.
139 isDirectory(const char * name)
143 if (stat(name, &statBuf) < 0)
146 return S_ISDIR(statBuf.st_mode);
151 * Return TRUE if a filename is a block or character device.
152 * Nonexistant files return FALSE.
155 isDevice(const char * name)
159 if (stat(name, &statBuf) < 0)
162 return S_ISBLK(statBuf.st_mode) || S_ISCHR(statBuf.st_mode);
167 * Copy one file to another, while possibly preserving its modes, times,
168 * and modes. Returns TRUE if successful, or FALSE on a failure with an
169 * error message output. (Failure is not indicted if the attributes cannot
174 const char * srcName,
175 const char * destName,
183 struct stat statBuf1;
184 struct stat statBuf2;
185 struct utimbuf times;
187 if (stat(srcName, &statBuf1) < 0)
194 if (stat(destName, &statBuf2) < 0)
196 statBuf2.st_ino = -1;
197 statBuf2.st_dev = -1;
200 if ((statBuf1.st_dev == statBuf2.st_dev) &&
201 (statBuf1.st_ino == statBuf2.st_ino))
203 fprintf(stderr, "Copying file \"%s\" to itself\n", srcName);
208 rfd = open(srcName, O_RDONLY);
217 wfd = creat(destName, statBuf1.st_mode);
227 while ((rcc = read(rfd, buf, sizeof(buf))) > 0)
237 if (fullWrite(wfd, buf, rcc) < 0)
247 checkStatus("close", close(rfd));
258 checkStatus("chmod", chmod(destName, statBuf1.st_mode));
260 checkStatus("chown", chown(destName, statBuf1.st_uid, statBuf1.st_gid));
262 times.actime = statBuf1.st_atime;
263 times.modtime = statBuf1.st_mtime;
265 checkStatus("utime", utime(destName, ×));
280 * Build a path name from the specified directory name and file name.
281 * If the directory name is NULL, then the original fileName is returned.
282 * The built path is in a static area, and is overwritten for each call.
285 buildName(const char * dirName, const char * fileName)
288 static char buf[PATH_LEN];
290 if ((dirName == NULL) || (*dirName == '\0'))
293 cp = strrchr(fileName, '/');
298 strcpy(buf, dirName);
300 strcat(buf, fileName);
307 * Expand the wildcards in a fileName wildcard pattern, if any.
308 * Returns an argument list with matching fileNames in sorted order.
309 * The expanded names are stored in memory chunks which can later all
310 * be freed at once. The returned list is only valid until the next
311 * call or until the next command. Returns zero if the name is not a
312 * wildcard, or returns the count of matched files if the name is a
313 * wildcard and there was at least one match, or returns -1 if either
314 * no fileNames matched or there was an allocation error.
317 expandWildCards(const char * fileNamePattern, const char *** retFileTable)
327 int newFileTableSize;
328 char ** newFileTable;
329 char dirName[PATH_LEN];
331 static int fileCount;
332 static int fileTableSize;
333 static char ** fileTable;
336 * Clear the return values until we know their final values.
339 *retFileTable = NULL;
342 * Scan the file name pattern for any wildcard characters.
344 cp1 = strchr(fileNamePattern, '*');
345 cp2 = strchr(fileNamePattern, '?');
346 cp3 = strchr(fileNamePattern, '[');
349 * If there are no wildcard characters then return zero to
350 * indicate that there was actually no wildcard pattern.
352 if ((cp1 == NULL) && (cp2 == NULL) && (cp3 == NULL))
356 * There are wildcards in the specified filename.
357 * Get the last component of the file name.
359 last = strrchr(fileNamePattern, '/');
364 last = fileNamePattern;
367 * If any wildcards were found before the last filename component
368 * then return an error.
370 if ((cp1 && (cp1 < last)) || (cp2 && (cp2 < last)) ||
371 (cp3 && (cp3 < last)))
374 "Wildcards only implemented for last file name component\n");
380 * Assume at first that we are scanning the current directory.
386 * If there was a directory given as part of the file name then
387 * copy it and null terminate it.
389 if (last != fileNamePattern)
391 memcpy(dirName, fileNamePattern, last - fileNamePattern);
392 dirName[last - fileNamePattern - 1] = '\0';
394 if (dirName[0] == '\0')
402 * Open the directory containing the files to be checked.
404 dirp = opendir(dirName);
414 * Prepare the directory name for use in making full path names.
416 dirLen = strlen(dirName);
418 if (last == fileNamePattern)
423 else if (dirName[dirLen - 1] != '/')
425 dirName[dirLen++] = '/';
426 dirName[dirLen] = '\0';
430 * Find all of the files in the directory and check them against
431 * the wildcard pattern.
433 while ((dp = readdir(dirp)) != NULL)
436 * Skip the current and parent directories.
438 if ((strcmp(dp->d_name, ".") == 0) ||
439 (strcmp(dp->d_name, "..") == 0))
445 * If the file name doesn't match the pattern then skip it.
447 if (!match(dp->d_name, last))
451 * This file name is selected.
452 * See if we need to reallocate the file name table.
454 if (fileCount >= fileTableSize)
457 * Increment the file table size and reallocate it.
459 newFileTableSize = fileTableSize + EXPAND_ALLOC;
461 newFileTable = (char **) realloc((char *) fileTable,
462 (newFileTableSize * sizeof(char *)));
464 if (newFileTable == NULL)
466 fprintf(stderr, "Cannot allocate file list\n");
472 fileTable = newFileTable;
473 fileTableSize = newFileTableSize;
477 * Allocate space for storing the file name in a chunk.
479 str = getChunk(dirLen + strlen(dp->d_name) + 1);
483 fprintf(stderr, "No memory for file name\n");
490 * Save the file name in the chunk.
493 memcpy(str, dirName, dirLen);
495 strcpy(str + dirLen, dp->d_name);
498 * Save the allocated file name into the file table.
500 fileTable[fileCount++] = str;
504 * Close the directory and check for any matches.
510 fprintf(stderr, "No matches\n");
516 * Sort the list of file names.
518 qsort((void *) fileTable, fileCount, sizeof(char *), nameSort);
521 * Return the file list and count.
523 *retFileTable = (const char **) fileTable;
530 * Sort routine for list of fileNames.
533 nameSort(const void * p1, const void * p2)
538 s1 = (const char **) p1;
539 s2 = (const char **) p2;
541 return strcmp(*s1, *s2);
546 * Routine to see if a text string is matched by a wildcard pattern.
547 * Returns TRUE if the text is matched, or FALSE if it is not matched
548 * or if the pattern is invalid.
549 * * matches zero or more characters
550 * ? matches a single character
551 * [abc] matches 'a', 'b' or 'c'
552 * \c quotes character c
553 * Adapted from code written by Ingo Wilken.
556 match(const char * text, const char * pattern)
558 const char * retryPat;
559 const char * retryText;
566 while (*text || *pattern)
580 while ((ch = *pattern++) != ']')
598 /* fall into next case */
612 /* fall into next case */
641 * Take a command string and break it up into an argc, argv list while
642 * handling quoting and wildcards. The returned argument list and
643 * strings are in static memory, and so are overwritten on each call.
644 * The argument list is ended with a NULL pointer for convenience.
645 * Returns TRUE if successful, or FALSE on an error with a message
649 makeArgs(const char * cmd, int * retArgc, const char *** retArgv)
651 const char * argument;
655 const char ** fileTable;
656 const char ** newArgTable;
662 BOOL quotedWildCards;
663 BOOL unquotedWildCards;
665 static int stringsLength;
666 static char * strings;
668 static int argTableSize;
669 static const char ** argTable;
672 * Clear the returned values until we know them.
679 * Copy the command string into a buffer that we can modify,
680 * reallocating it if necessary.
682 len = strlen(cmd) + 1;
684 if (len > stringsLength)
686 newStrings = realloc(strings, len);
688 if (newStrings == NULL)
690 fprintf(stderr, "Cannot allocate string\n");
695 strings = newStrings;
699 memcpy(strings, cmd, len);
703 * Keep parsing the command string as long as there are any
709 * Save the beginning of this argument.
715 * Reset quoting and wildcarding for this argument.
718 quotedWildCards = FALSE;
719 unquotedWildCards = FALSE;
722 * Loop over the string collecting the next argument while
723 * looking for quoted strings or quoted characters, and
724 * remembering whether there are any wildcard characters
732 * If we are not in a quote and we see a blank then
733 * this argument is done.
735 if (isBlank(ch) && (quote == '\0'))
739 * If we see a backslash then accept the next
740 * character no matter what it is.
747 * Make sure there is a next character.
752 "Bad quoted character\n");
758 * Remember whether the quoted character
762 quotedWildCards = TRUE;
770 * If we see one of the wildcard characters then
771 * remember whether it was seen inside or outside
777 quotedWildCards = TRUE;
779 unquotedWildCards = TRUE;
783 * If we were in a quote and we saw the same quote
784 * character again then the quote is done.
794 * If we weren't in a quote and we see either type
795 * of quote character, then remember that we are
796 * now inside of a quote.
798 if ((quote == '\0') && ((ch == '\'') || (ch == '"')))
806 * Store the character.
812 * Make sure that quoting is terminated properly.
816 fprintf(stderr, "Unmatched quote character\n");
822 * Null terminate the argument if it had shrunk, and then
823 * skip over all blanks to the next argument, nulling them
833 * If both quoted and unquoted wildcards were used then
834 * complain since we don't handle them properly.
836 if (quotedWildCards && unquotedWildCards)
839 "Cannot use quoted and unquoted wildcards\n");
845 * Expand the argument into the matching filenames or accept
846 * it as is depending on whether there were any unquoted
847 * wildcard characters in it.
849 if (unquotedWildCards)
852 * Expand the argument into the matching filenames.
854 fileCount = expandWildCards(argument, &fileTable);
857 * Return an error if the wildcards failed to match.
864 fprintf(stderr, "Wildcard expansion error\n");
872 * Set up to only store the argument itself.
874 fileTable = &argument;
879 * Now reallocate the argument table to hold the file name.
881 if (argCount + fileCount >= argTableSize)
883 newArgTableSize = argCount + fileCount + 1;
885 newArgTable = (const char **) realloc(argTable,
886 (sizeof(const char *) * newArgTableSize));
888 if (newArgTable == NULL)
890 fprintf(stderr, "No memory for arg list\n");
895 argTable = newArgTable;
896 argTableSize = newArgTableSize;
900 * Copy the new arguments to the end of the old ones.
902 memcpy((void *) &argTable[argCount], (const void *) fileTable,
903 (sizeof(const char **) * fileCount));
906 * Add to the argument count.
908 argCount += fileCount;
912 * Null terminate the argument list and return it.
914 argTable[argCount] = NULL;
924 * Make a NULL-terminated string out of an argc, argv pair.
925 * Returns TRUE if successful, or FALSE if the string is too long,
926 * with an error message given. This does not handle spaces within
927 * arguments correctly.
945 fprintf(stderr, "Argument string too long\n");
950 strcpy(buf, *argv++);
968 * Allocate a chunk of memory (like malloc).
969 * The difference, though, is that the memory allocated is put on a
970 * list of chunks which can be freed all at one time. You CAN NOT free
971 * an individual chunk.
978 if (size < CHUNK_INIT_SIZE)
979 size = CHUNK_INIT_SIZE;
981 chunk = (CHUNK *) malloc(size + sizeof(CHUNK) - CHUNK_INIT_SIZE);
986 chunk->next = chunkList;
994 * Duplicate a string value using the chunk allocator.
995 * The returned string cannot be individually freed, but can only be freed
996 * with other strings when freeChunks is called. Returns NULL on failure.
999 chunkstrdup(const char * str)
1004 len = strlen(str) + 1;
1005 newStr = getChunk(len);
1008 memcpy(newStr, str, len);
1015 * Free all chunks of memory that had been allocated since the last
1016 * call to this routine.
1026 chunkList = chunk->next;
1027 free((char *) chunk);
1033 * Try writing data to the specified file descriptor.
1034 * Only the first write error if any is printed.
1035 * This is used when writing to STDOUT.
1038 tryWrite(int fd, const char * cp, int len)
1040 static int failed = FALSE;
1042 int status = fullWrite(fd, cp, len);
1044 if ((status < 0) && !failed)
1053 * Write all of the supplied buffer out to a file.
1054 * This does multiple writes as necessary.
1055 * Returns the amount written, or -1 on an error.
1058 fullWrite(int fd, const char * buf, int len)
1067 cc = write(fd, buf, len);
1082 * Read all of the supplied buffer from a file.
1083 * This does multiple reads as necessary.
1084 * Returns the amount read, or -1 on an error.
1085 * A short read is returned on an end of file.
1088 fullRead(int fd, char * buf, int len)
1097 cc = read(fd, buf, len);
1115 * Call system for the specified command and print an error
1116 * message if the execution fails. The exit status of the
1117 * command is returned.
1120 trySystem(const char * cmd)
1124 status = system(cmd);
1127 fprintf(stderr, "Error starting command: %s\n", cmd);
1134 * Check the status for the most recent system call and complain
1135 * if its value is -1 which indicates it failed.
1138 checkStatus(const char * name, int status)