/* $Id: util.c,v 1.23 2002/07/21 13:20:35 richdawe Exp $ */ /* * util.c - Utility functions for pakke * Copyright (C) 1999-2002 by Richard Dawe * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "common.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* libpakke includes */ #include #include /* --- Perl-isms --- */ void die (const char *msg) { fprintf(stderr, "Error: %s\n", msg); exit(EXIT_FAILURE); } void warn (const char *msg) { fprintf(stderr, "Warning: %s\n", msg); } void info (const char *msg) { fprintf(stderr, "Info: %s\n", msg); } /* New-line removal */ void chomp (char *str) { char *p = NULL; if (str == NULL) return; for (p = str + strlen(str) - 1; (*p == '\n') || (*p == '\r'); p--) { *p = '\0'; } } /* Trailing whitespace removal */ void rtrim (char *str) { int i; if (strlen(str) == 0) return; for (i = strlen(str) - 1; i >= 0; i--) { if (!isspace(str[i])) break; str[i] = '\0'; } } /* --- Multiple-argument Perl-isms --- */ void #ifdef __GNUC__ __attribute__((format (printf, 1, 2))) #endif dief (const char *fmt, ...) { va_list arg; va_start(arg, fmt); fprintf(stderr, "Error: "); vfprintf(stderr, fmt, arg); fprintf(stderr, "\n"); va_end(arg); exit(EXIT_FAILURE); } void #ifdef __GNUC__ __attribute__((format (printf, 1, 2))) #endif warnf (const char *fmt, ...) { va_list arg; va_start(arg, fmt); fprintf(stderr, "Warning: "); vfprintf(stderr, fmt, arg); fprintf(stderr, "\n"); va_end(arg); } void #ifdef __GNUC__ __attribute__((format (printf, 1, 2))) #endif infof (const char *fmt, ...) { va_list arg; va_start(arg, fmt); printf("Info: "); vprintf(fmt, arg); printf("\n"); va_end(arg); } /* --- Slash functions --- */ void backslashify (char *p) { if (p == NULL) return; for (; *p != '\0'; p++) { if (*p == '/') *p = '\\'; } } void forwardslashify (char *p) { if (p == NULL) return; for (; *p != '\0'; p++) { if (*p == '\\') *p = '/'; } } /* Add a forward slash to the end safely, i.e. it if has one, don't do it. */ void addforwardslash (char *p) { if (p == NULL) return; if (p[strlen(p) - 1] == '/') return; strcat(p, "/"); } /* --- String functions --- */ /* ------------ * - strdupnx - * ------------ */ /* Copy a string and allocate n extra bytes. */ char * strdupnx (const char *str, const int n) { char *p = NULL; if (str == NULL) return(NULL); p = calloc(strlen(str) + 1 + n, 1); if (p == NULL) return(NULL); strcpy(p, str); return(p); } /* --- File name, path, etc. functions --- */ /* ----------------- * - has_extension - * ----------------- */ /* Returns 1 if the file has the specified extension, 0 otherwise. The * comparison is performed case-insensitively. It'll only look at the last * extension. Beware! */ int has_extension (const char *path, const char *ext) { char *p = strrchr(path, '.'); if (p == NULL) return(0); p++; if (strcasecmp(p, ext) == 0) return(1); else return(0); } int iszip (const char *path) { return(has_extension(path, "zip")); } int isdsm (const char *path) { return(has_extension(path, "dsm")); } /* ----------- * - istargz - * ----------- */ /* Returns 1 if the file is a gzip'd tar file, 0 otherwise. This is based only * on file name, but should suffice. */ int istargz (const char *path) { char *p = strrchr(path, '.'); if (p == NULL) return(0); p++; if (strcasecmp(p, "tgz") == 0) return(1); if (strcasecmp(p, "taz") == 0) return(1); p -= strlen("tar."); if (p < path) return(0); if (strcasecmp(p, "tar.gz") == 0) return(1); return(0); } /* ------------ * - istarbz2 - * ------------ */ /* Returns 1 if the file is a bzip2'd tar file, 0 otherwise. This is based only * on file name, but should suffice. */ int istarbz2 (const char *path) { char *p = strrchr(path, '.'); if (p == NULL) return(0); p++; if (strcasecmp(p, "tbz") == 0) return(1); if (strcasecmp(p, "tbz2") == 0) return(1); p -= strlen("tar."); if (p < path) return(0); if (strcasecmp(p, "tar.bz2") == 0) return(1); return(0); } /* ------------- * - isarchive - * ------------- */ /* This calls iszip(), istargz() to find out, if it's an archive or not. * If other archive types are added, they should be added here too. */ int isarchive (const char *path) { return(iszip(path) || istargz(path)); } /* --------- * - isurl - * --------- */ /* Is this a URL? 1 = yes, 0 = no */ int isurl (const char *path) { char *p = strstr(path, "://"); char *q = NULL; if (p == NULL) return(0); /* Check the scheme part is OK, i.e. only alphanumeric chars */ for (q = p; q != p; q++) { if (!isalpha(*q)) return(0); } /* TODO: Write better checking here */ /* Assume the rest is OK */ return(1); } /* ------------------- * - ends_with_slash - * ------------------- */ /* Does the file name end with a slash? 1 = yes, 0 = no. This is used by * code that deals with archives, since the directories are stored with * trailing slashes in archives - well ZIPs at least. */ int ends_with_slash (const char *d) { if (d == NULL) return(0); if (strlen(d) == 0) return(0); if (d[strlen(d) - 1] != '/') return(0); return(1); } /* --------- * - isdir - * --------- */ /* * Check that the named path is a directory. If it is, 1 is returned, * otherwise 0. On error -1 is returned and errno will contain the error * from stat(). */ int isdir (const char *path) { struct stat s; if (stat(path, &s) == -1) return(-1); if (S_ISDIR(s.st_mode)) return(1); /* Fail by default */ return(0); } /* ------------- * - isabspath - * ------------- */ /* Is the path given absolute? If so, return 1, else 0. */ int isabspath (const char *path) { if (path[0] && (path[0] == '/')) return(1); #ifdef MSDOS /* On MS-DOS, cope with a drive prefix too. */ if ((strlen(path) >= 3) && (path[1] == ':') && (path[2] == '/')) return(1); #endif /* MSDOS */ return(0); } /* ------------- * - isrelpath - * ------------- */ /* Is this a relative path? Simply !isabspath(). */ int isrelpath (const char *path) { return(!isabspath(path)); } /* ----------------- * - isspecialpath - * ----------------- */ /* * Is this a 'special' path? By 'special' it is meant that the path is not * a valid place for reading/writing files. * * The paths in the comments below are in pseudo-Perl-regexp format. */ /* TODO: This code probably isn't particularly efficient. */ int isspecialpath (const char *path) { #ifdef __DJGPP__ { char buf[PATH_MAX]; /* /dev and /dev/env are not valid paths for DJGPP. But allow access via * /dev/env/ and /dev/[a-zA-Z]/? since they will be expanded by * DJGPP's libc. */ const char PREFIX_DEV[] = "/dev"; const char PREFIX_DEV_ENV[] = "/dev/env"; int good_dev = 0; /* Ensure that all slashes are forward slashes. */ strcpy(buf, path); forwardslashify(buf); /* /dev, /dev/ == special */ if (strstr(buf, PREFIX_DEV) == buf) { int len = strlen(PREFIX_DEV); if (buf[len] == '\0') return(1); if ((buf[len] == '/') && (strlen(buf) == (len + 1))) return(1); /* Pass through /dev/ */ } /* /dev/env, /dev/env/ == special */ if (strstr(buf, PREFIX_DEV_ENV) == buf) { int len = strlen(PREFIX_DEV_ENV); if (buf[len] == '\0') return(1); if ((buf[len] == '/') && (strlen(buf) == (len + 1))) return(1); /* Pass through /dev/env/ */ } /* /dev/ */ if ((strstr(buf, PREFIX_DEV) == buf) && (buf[strlen(PREFIX_DEV)] == '/')) { /* /dev/env/ is a good path. */ good_dev = ( (strstr(buf, PREFIX_DEV_ENV) == buf) && (buf[strlen(PREFIX_DEV_ENV)] == '/')); /* /dev/[a-zA-Z]/? are good paths. */ good_dev |= ( isalpha(buf[strlen(PREFIX_DEV + 1)]) && ( (buf[strlen(PREFIX_DEV) + 2] == '/') || (buf[strlen(PREFIX_DEV) + 2] == '\0'))); if (!good_dev) return(1); } } #endif /* __DJGPP__ */ return(0); } /* ------------------- * - recursive_mkdir - * ------------------- */ /* * This works like mkdir, except that all the directory components are created. * This is like 'mkdir -p' from GNU fileutils. * * On success 0 is returned, else -1 is returned and 'errno' will contain * the failure code. */ int recursive_mkdir (const char *pathname, const mode_t mode) { /* Allocate space to add trailing slash, if necessary. */ char *buf = strdupnx(pathname, 1); char *p = buf; int ret; addforwardslash(buf); #ifdef __DJGPP__ /* Skip the drive specifier. */ if (buf[1] == ':') p += 2; #endif while ((p = strchr(p + 1, '/')) != NULL) { /* Quit if it's a trailing slash */ if (*p == '\0') break; /* Chop up temporarily & create a component. */ *p = '\0'; ret = mkdir(buf, mode); *p = '/'; if ((ret != 0) && (errno != EEXIST)) { /* Pass down error */ free(buf); return(-1); } } free(buf); return(0); } /* ----------------- * - find_in_paths _ * ----------------- */ /* * This searches the supplied paths (NULL-terminated list) for the file named * 'name'. If 'case_sensitivity' is true (non-zero), case sensitive * comparisons are performed, else case insensitive. * * On success, the file name is returned; on failure, NULL is returned. The * matching file name is returned in a static buffer, so beware. */ char * find_in_paths (const char *name, const char **paths, const int case_sensitivity) { DIR *d = NULL; struct dirent *de = NULL; static char buf[PATH_MAX]; int ret, i; if (paths == NULL) return(NULL); for (i = 0; paths[i] != NULL; i++) { /* Skip URLs */ if (isurl(paths[i])) continue; d = opendir(paths[i]); if (d == NULL) continue; /* Read all directory entries. */ while((de = readdir(d)) != NULL) { ret = case_sensitivity ? strcmp(de->d_name, name) : strcasecmp(de->d_name, name); if (ret == 0) { /* Found a match */ strcpy(buf, paths[i]); addforwardslash(buf); strcat(buf, name); closedir(d); return(buf); } } closedir(d); } return(NULL); } /* ------------------------------- * - find_in_paths_with_suffixes - * ------------------------------- */ /* * This function searches for 'name' using the paths given in * the NULL-terminated list 'paths'. If that fails, it also searches * the sub-directories given in 'suffixes' for each path in 'paths'. * If 'suffixes' is NULL, then the search terminates after looking in 'paths'. * * If 'case_sensitivity' is true (non-zero), case sensitive comparisons are * performed, else case insensitive. * * 'suffixes' would typically be 'djgpp_archive_prefixes', which contains * e.g. 'v2gnu'. These directories are prefixes to the files on the Simtelnet * archive, but become suffixes to the search paths here. * * On success, the file name is returned; on failure, NULL is returned. The * matching file name is returned in a static buffer, so beware. */ char * find_in_paths_with_suffixes (const char *name, const char **paths, const char **suffixes, const int case_sensitivity) { static char buf[PATH_MAX]; char subdir[PATH_MAX]; char *subdirs[2] = { NULL, NULL }; char *match = NULL; int i = 0; int j = 0; /* Try a normal match */ match = find_in_paths(name, (const char **) paths, case_sensitivity); if (match) { /* Copy match into our static buffer. */ strncpy(buf, match, sizeof(buf)); buf[sizeof(buf) - 1] = '\0'; return(buf); } /* Now try a suffix match, if there are any. */ if (suffixes == NULL) return(NULL); subdirs[0] = subdir; subdirs[1] = NULL; for (i = 0; paths[i] != NULL; i++) { /* Skip URLs */ if (isurl(paths[i])) continue; for (j = 0; suffixes[j] != NULL; j++) { /* Build & search sub-directory. */ strcpy(subdir, paths[i]); addforwardslash(subdir); strcat(subdir, suffixes[j]); match = find_in_paths(name, (const char **) subdirs, case_sensitivity); if (match != NULL) break; } if (match != NULL) break; } if (match) { /* Copy match into our static buffer. */ strncpy(buf, match, sizeof(buf)); buf[sizeof(buf) - 1] = '\0'; return(buf); } return(NULL); } /* ----------------- * - glob_in_paths - * ----------------- */ /* Glob for a file in all the specified paths. For a description * of return values, see glob()'s man/info documentation. */ int glob_in_paths (const char *pattern, const char **paths, int flags, int (*errfunc)(const char *epath, int eerrno), glob_t *pglob) { char buf[PATH_MAX * 2]; /* Allow some space for pattern. */ int ret = 0; /* Succeed by default. */ int i; for (i = 0; paths[i] != NULL; i++) { /* Skip URLs */ if (isurl(paths[i])) continue; /* Skip files or inaccessible directories. */ if (isdir(paths[i]) <= 0) continue; strcpy(buf, paths[i]); addforwardslash(buf); strcat(buf, pattern); ret = glob(buf, flags, errfunc, pglob); /* Abort, if an error occurred. */ if (ret && (ret != GLOB_NOMATCH)) break; /* Append on next loop round. */ flags |= (GLOB_APPEND|GLOB_DOOFFS); } return(ret); } /* ------------------------------- * - glob_in_paths_with_suffixes - * ------------------------------- */ /* * For each path in paths, glob in the path itself and path with each * of the suffixes from suffixes. * * NB: This relies on the fact that we can pass in (GLOB_APPEND|GLOB_DOOFS) * as the flags member. */ int glob_in_paths_with_suffixes (const char *pattern, const char **paths, const char **suffixes, int flags, int (*errfunc)(const char *epath, int eerrno), glob_t *pglob) { char buf[PATH_MAX]; const char *mypaths[2] = { buf, NULL }; /* Fake list for path+suffix. */ int ret = 0; int i, j; /* Glob in paths first. */ ret = glob_in_paths(pattern, paths, flags, errfunc, pglob); /* Abort, if an error occurred. */ if (ret && (ret != GLOB_NOMATCH)) return(ret); /* No suffixes => just call glob_in_paths(). */ if (suffixes == NULL) return(ret); /* Append to list. */ flags |= (GLOB_APPEND|GLOB_DOOFFS); /* Now glob each path+suffix. */ for (i = 0; paths[i] != NULL; i++) { /* Skip URLs */ if (isurl(paths[i])) continue; for (j = 0; suffixes[j] != NULL; j++) { strcpy(buf, paths[i]); addforwardslash(buf); strcat(buf, suffixes[j]); ret = glob_in_paths(pattern, mypaths, flags, errfunc, pglob); /* Abort, if an error occurred. */ if (ret && (ret != GLOB_NOMATCH)) break; } /* Abort, if an error occurred. */ if (ret && (ret != GLOB_NOMATCH)) break; } return(ret); } /* ---------------------------- * - read_text_file_to_memory - * ---------------------------- */ /* Given a file name, this creates a buffer and reads the file's contents * into it. */ char * read_text_file_to_memory (const char *file) { FILE *fp = NULL; char *buf = NULL; struct stat sbuf; int ret; fp = fopen(file, "rb"); if (fp == NULL) return(NULL); /* Get the file's details. */ if (fstat(fileno(fp), &sbuf) != 0) { fclose(fp); return(NULL); } /* Create the buffer */ buf = calloc(1, sbuf.st_size + 1); if (buf == NULL) { fclose(fp); return(NULL); } /* Read the file */ ret = fread(buf, sbuf.st_size, 1, fp); if (ret <= 0) { free(buf); fclose(fp); return(NULL); } fclose(fp); return(buf); } /* ------------- * - copy_file - * ------------- */ /* This copies the specified file from 'src' to 'dest', like the 'cp' * command. It will overwrite the destination file. It returns 1 on success, * 0 otherwise. */ int copy_file (const char *src, const char *dest) { FILE *sfp = NULL, *dfp = NULL; int sfd = 0; struct stat sbuf; char buf[65536]; /* Reasonable size? */ int ret; sfp = fopen(src, "rb"); dfp = fopen(dest, "wb"); if ((sfp == NULL) || (dfp == NULL)) return(0); sfd = fileno(sfp); if (fstat(sfd, &sbuf) != 0) { /* Abort - can't get input file info! */ fclose(dfp); fclose(sfp); unlink(dest); return(0); } while ( (ret = read(sfd, buf, sizeof(buf))) > 0) { if (fwrite(buf, ret, 1, dfp) <= 0) { /* Write failed, abort! */ fclose(dfp); fclose(sfp); unlink(dest); return(0); } } fclose(dfp); fclose(sfp); /* Change the output file's permissions to match the input file. */ chmod(dest, sbuf.st_mode); return(1); } /* ------------ * - count_l0 - * ------------ */ /* This function counts the leading zeros, given a version number string. E.g. * for "02", it will return 1. This is to handle version numbers such as * 2.02 correctly. */ int count_l0 (const char *v) { int i; /* Simple cases */ if (v == NULL) return(0); if (v[0] != '0') return(0); if (!isdigit(v[1])) return(0); /* All others */ for (i = 0; i < strlen(v); i++) { if (v[i] != '0') break; } return(i); } /* ---------------- * - find_archive - * ---------------- */ char * find_archive (const char *name, const char *req_name, const char **archive_paths) { static char buf[PATH_MAX]; char *dirs[] = { NULL, NULL }; char *match = NULL; char *p = NULL; /* * Match case insensitively on archive name. Search the following locations: * * 1. the directory path used in the request, e.g. for the first ZIP file; * * 2. the ZIP directories; * * 3. any of the standard DJGPP archive directories off the * ZIP directories. * * If none of these match, bail out. */ /* Step 1 */ if ((req_name != NULL) && (strchr(req_name, '/') != NULL)) { dirs[0] = strdup(req_name); dirs[1] = NULL; forwardslashify(dirs[0]); p = strrchr(dirs[0], '/'); *p = '\0'; match = find_in_paths(name, (const char **) dirs, 0); free(dirs[0]); dirs[0] = NULL; } /* Step 2, if step 1 failed. */ if (match == NULL) match = find_in_paths(name, (const char **) archive_paths, 0); /* Step 3, if step 1 and/or 2 failed. */ if (match == NULL) match = find_in_paths_with_suffixes(name, archive_paths, djgpp_archive_prefixes, 0); if (match != NULL) { /* Copy match into our static buffer. */ strncpy(buf, match, sizeof(buf)); buf[sizeof(buf) - 1] = '\0'; return(buf); } return(NULL); } /* --------------------- * - get_url_component - * --------------------- */ /* * Parse an HTTP or FTP into its components. See RFC1738: Uniform Resource * Locators (URL). */ int get_url_component (const url_comp_t comp, const char *url, char *buf, const size_t buflen) { const char SCHEME_DELIMITER[] = "://"; char *p = 0; char *q = 0; char *r = 0; ptrdiff_t n = 0; int ok = 1; /* Succeed by default */ /* Duh - give us some buffer! */ if (buflen == 0) return(0); /* We only support Internet URL schemes. */ if (strstr(url, SCHEME_DELIMITER) == NULL) return(0); /* Now get the desired component. */ switch(comp) { case URL_COMP_SCHEME: /* Find the scheme delimiter */ p = strstr(url, SCHEME_DELIMITER); if (p == NULL) { ok = 0; break; } /* Now copy to buf */ n = p - url; if (n < buflen) { strncpy(buf, url, n); buf[n] = '\0'; } else { /* Insufficient space */ ok = 0; } break; case URL_COMP_USER: /* Find the end of the scheme delimiter. */ p = strstr(url, SCHEME_DELIMITER); if (p == NULL) { ok = 0; break; } p += strlen(SCHEME_DELIMITER); /* Find the start of the password or host. */ q = strchr(p, '@'); if (q == NULL) { /* No username */ *buf = '\0'; break; } for (r = q; (r > p) && (*r != ':'); r--) {;} if (*r == ':') q = r; /* Now copy to buf */ n = q - p; if (n < buflen) { strncpy(buf, p, n); buf[n] = '\0'; } else { /* Insufficient space */ ok = 0; } break; case URL_COMP_PASSWORD: /* Find the end of the scheme delimiter. */ p = strstr(url, SCHEME_DELIMITER); if (p == NULL) { ok = 0; break; } p += strlen(SCHEME_DELIMITER); /* Find the start of the host. */ q = strchr(p, '@'); if (q == NULL) { /* No password */ *buf = '\0'; break; } /* Find the start of the password, if any. */ for (r = q - 1; (r > p) && (*r != ':'); r--) {;} if (r == p) { /* No password */ *buf = '\0'; break; } r++; /* Now copy to buf */ n = q - r; if (n < buflen) { strncpy(buf, r, n); buf[n] = '\0'; } else { /* Insufficient space */ ok = 0; } break; case URL_COMP_HOST: /* Find the end of the scheme delimiter. */ p = strstr(url, SCHEME_DELIMITER); if (p == NULL) { ok = 0; break; } p += strlen(SCHEME_DELIMITER); /* Find the end of the host / start of the path. */ q = strchr(p, '/'); if (q == NULL) { /* Point q at end of string, where path would begin. */ q = p + strlen(p); } /* Skip username and password, if present. */ r = strchr(p, '@'); if ((r != NULL) && (r < q)) { /* Username & maybe password present. Skip 'em. */ r++; p = r; } /* Skip port, if present. */ r = strchr(p, ':'); if ((r != NULL) && (r < q)) { /* Skip port */ q = r; } /* Now copy to buf */ n = q - p; if (n < buflen) { strncpy(buf, p, n); buf[n] = '\0'; } else { /* Insufficient space */ ok = 0; } break; case URL_COMP_PORT: /* Find the end of the scheme delimiter. */ p = strstr(url, SCHEME_DELIMITER); if (p == NULL) { ok = 0; break; } p += strlen(SCHEME_DELIMITER); /* Find the end of the host / start of the path. */ q = strchr(p, '/'); if (q == NULL) { /* Point q at end of string, where path would begin. */ q = p + strlen(p); } for (r = q - 1; (r > p) && isdigit(*r); r--) {;} if (*r != ':') { /* No port */ *buf = '\0'; break; } r++; /* Now copy to buf */ n = q - r; if (n < buflen) { strncpy(buf, r, n); buf[n] = '\0'; } else { /* Insufficient space */ ok = 0; } break; case URL_COMP_PATH: /* Find the scheme delimiter */ p = strstr(url, SCHEME_DELIMITER); if (p == NULL) { ok = 0; break; } p += strlen(SCHEME_DELIMITER); /* Find next path delimiter => start of path */ p = strchr(p, '/'); if (p == NULL) { /* No path */ *buf = '\0'; break; } /* Now copy to buf */ n = strlen(p); if (n < buflen) { strncpy(buf, p, n); buf[n] = '\0'; } else { /* Insufficient space */ ok = 0; } break; default: /* Unknown component */ ok = 0; break; } return(ok); }