/** * \file lib/misc.c */ #include "system.h" static int _debug = 0; #include #include #include /* XXX for rpmGetPath */ #include "misc.h" #include "debug.h" /*@access Header@*/ /* XXX compared with NULL */ /*@access FD_t@*/ /* XXX compared with NULL */ char * RPMVERSION = VERSION; /* just to put a marker in librpm.a */ char ** splitString(const char * str, int length, char sep) { const char * source; char * s, * dest; char ** list; int i; int fields; s = xmalloc(length + 1); fields = 1; for (source = str, dest = s, i = 0; i < length; i++, source++, dest++) { *dest = *source; if (*dest == sep) fields++; } *dest = '\0'; list = xmalloc(sizeof(char *) * (fields + 1)); dest = s; list[0] = dest; i = 1; while (i < fields) { if (*dest == sep) { list[i++] = dest + 1; *dest = 0; } dest++; } list[i] = NULL; return list; } void freeSplitString(char ** list) { free(list[0]); free(list); } int rpmfileexists(const char * urlfn) { const char *fn; int urltype = urlPath(urlfn, &fn); struct stat buf; if (*fn == '\0') fn = "/"; switch (urltype) { case URL_IS_FTP: /* XXX WRONG WRONG WRONG */ case URL_IS_HTTP: /* XXX WRONG WRONG WRONG */ case URL_IS_PATH: case URL_IS_UNKNOWN: if (Stat(fn, &buf)) { switch(errno) { case ENOENT: case EINVAL: return 0; } } break; case URL_IS_DASH: default: return 0; /*@notreached@*/ break; } return 1; } /* compare alpha and numeric segments of two versions */ /* return 1: a is newer than b */ /* 0: a and b are the same version */ /* -1: b is newer than a */ int rpmvercmp(const char * a, const char * b) { char oldch1, oldch2; char * str1, * str2; char * one, * two; int rc; int isnum; /* easy comparison to see if versions are identical */ if (!strcmp(a, b)) return 0; str1 = alloca(strlen(a) + 1); str2 = alloca(strlen(b) + 1); strcpy(str1, a); strcpy(str2, b); one = str1; two = str2; /* loop through each version segment of str1 and str2 and compare them */ while (*one && *two) { while (*one && !isalnum(*one)) one++; while (*two && !isalnum(*two)) two++; str1 = one; str2 = two; /* grab first completely alpha or completely numeric segment */ /* leave one and two pointing to the start of the alpha or numeric */ /* segment and walk str1 and str2 to end of segment */ if (isdigit(*str1)) { while (*str1 && isdigit(*str1)) str1++; while (*str2 && isdigit(*str2)) str2++; isnum = 1; } else { while (*str1 && isalpha(*str1)) str1++; while (*str2 && isalpha(*str2)) str2++; isnum = 0; } /* save character at the end of the alpha or numeric segment */ /* so that they can be restored after the comparison */ oldch1 = *str1; *str1 = '\0'; oldch2 = *str2; *str2 = '\0'; /* take care of the case where the two version segments are */ /* different types: one numeric, the other alpha (i.e. empty) */ if (one == str1) return -1; /* arbitrary */ if (two == str2) return 1; if (isnum) { /* this used to be done by converting the digit segments */ /* to ints using atoi() - it's changed because long */ /* digit segments can overflow an int - this should fix that. */ /* throw away any leading zeros - it's a number, right? */ while (*one == '0') one++; while (*two == '0') two++; /* whichever number has more digits wins */ if (strlen(one) > strlen(two)) return 1; if (strlen(two) > strlen(one)) return -1; } /* strcmp will return which one is greater - even if the two */ /* segments are alpha or if they are numeric. don't return */ /* if they are equal because there might be more segments to */ /* compare */ rc = strcmp(one, two); if (rc) return rc; /* restore character that was replaced by null above */ *str1 = oldch1; one = str1; *str2 = oldch2; two = str2; } /* this catches the case where all numeric and alpha segments have */ /* compared identically but the segment sepparating characters were */ /* different */ if ((!*one) && (!*two)) return 0; /* whichever version still has characters left over wins */ if (!*one) return -1; else return 1; } int doputenv(const char *str) { char * a; /* FIXME: this leaks memory! */ a = xmalloc(strlen(str) + 1); strcpy(a, str); return putenv(a); } int dosetenv(const char *name, const char *value, int overwrite) { int i; char * a; /* FIXME: this leaks memory! */ if (!overwrite && getenv(name)) return 0; i = strlen(name) + strlen(value) + 2; a = xmalloc(i); if (!a) return 1; strcpy(a, name); strcat(a, "="); strcat(a, value); return putenv(a); } static int rpmMkpath(const char * path, mode_t mode, uid_t uid, gid_t gid) { char * d, * de; int created = 0; int rc; if (path == NULL) return -1; d = alloca(strlen(path)+2); de = stpcpy(d, path); de[1] = '\0'; for (de = d; *de; de++) { struct stat st; char savec; while (*de && *de != '/') de++; savec = de[1]; de[1] = '\0'; rc = stat(d, &st); if (rc) { switch(errno) { default: return errno; /*@notreached@*/ break; case ENOENT: break; } rc = mkdir(d, mode); if (rc) return errno; created = 1; if (!(uid == (uid_t) -1 && gid == (gid_t) -1)) { rc = chown(d, uid, gid); if (rc) return errno; } } else if (!S_ISDIR(st.st_mode)) { return ENOTDIR; } de[1] = savec; } rc = 0; if (created) rpmMessage(RPMMESS_WARNING, "created %%_tmppath directory %s\n", path); return rc; } int makeTempFile(const char * prefix, const char ** fnptr, FD_t * fdptr) { const char * tpmacro = "%{?_tmppath:%{_tmppath}}%{!?_tmppath:/var/tmp}"; const char * tempfn = NULL; const char * tfn = NULL; static int _initialized = 0; int temput; FD_t fd = NULL; int ran; if (!prefix) prefix = ""; /* Create the temp directory if it doesn't already exist. */ if (!_initialized) { _initialized = 1; tempfn = rpmGenPath(prefix, tpmacro, NULL); if (rpmMkpath(tempfn, 0755, (uid_t) -1, (gid_t) -1)) goto errxit; } /* XXX should probably use mkstemp here */ srand(time(NULL)); ran = rand() % 100000; /* maybe this should use link/stat? */ do { char tfnbuf[64]; #ifndef NOTYET sprintf(tfnbuf, "rpm-tmp.%d", ran++); if (tempfn) free((void *)tempfn); tempfn = rpmGenPath(prefix, tpmacro, tfnbuf); #else strcpy(tfnbuf, "rpm-tmp.XXXXXX"); if (tempfn) free((void *)tempfn); tempfn = rpmGenPath(prefix, tpmacro, mktemp(tfnbuf)); #endif temput = urlPath(tempfn, &tfn); if (*tfn == '\0') goto errxit; switch (temput) { case URL_IS_HTTP: case URL_IS_DASH: goto errxit; /*@notreached@*/ break; default: break; } fd = Fopen(tempfn, "w+x.ufdio"); /* XXX FIXME: errno may not be correct for ufdio */ } while ((fd == NULL || Ferror(fd)) && errno == EEXIST); if (fd == NULL || Ferror(fd)) goto errxit; switch(temput) { struct stat sb, sb2; case URL_IS_PATH: case URL_IS_UNKNOWN: if (!stat(tfn, &sb) && S_ISLNK(sb.st_mode)) { rpmError(RPMERR_SCRIPT, _("error creating temporary file %s\n"), tfn); goto errxit; } if (sb.st_nlink != 1) { rpmError(RPMERR_SCRIPT, _("error creating temporary file %s\n"), tfn); goto errxit; } if (fstat(Fileno(fd), &sb2) == 0) { if (sb2.st_ino != sb.st_ino || sb2.st_dev != sb.st_dev) { rpmError(RPMERR_SCRIPT, _("error creating temporary file %s\n"), tfn); goto errxit; } } break; default: break; } if (fnptr) *fnptr = tempfn; else if (tempfn) { free((void *)tempfn); tempfn = NULL; } *fdptr = fd; return 0; errxit: if (tempfn) free((void *)tempfn); if (fd) Fclose(fd); return 1; } char * currentDirectory(void) { int currDirLen; char * currDir; currDirLen = 50; currDir = xmalloc(currDirLen); while (!getcwd(currDir, currDirLen) && errno == ERANGE) { currDirLen += 50; currDir = xrealloc(currDir, currDirLen); } return currDir; } int _noDirTokens = 0; static int dncmp(const void * a, const void * b) { const char *const * first = a; const char *const * second = b; return strcmp(*first, *second); } void compressFilelist(Header h) { char ** fileNames; const char ** dirNames; const char ** baseNames; int_32 * dirIndexes; int count; int i; int dirIndex = -1; /* * This assumes the file list is already sorted, and begins with a * single '/'. That assumption isn't critical, but it makes things go * a bit faster. */ if (headerIsEntry(h, RPMTAG_DIRNAMES)) { headerRemoveEntry(h, RPMTAG_OLDFILENAMES); return; /* Already converted. */ } if (!headerGetEntry(h, RPMTAG_OLDFILENAMES, NULL, (void **) &fileNames, &count)) return; /* no file list */ dirNames = alloca(sizeof(*dirNames) * count); /* worst case */ baseNames = alloca(sizeof(*dirNames) * count); dirIndexes = alloca(sizeof(*dirIndexes) * count); if (fileNames[0][0] != '/') { /* HACK. Source RPM, so just do things differently */ dirIndex = 0; dirNames[dirIndex] = ""; for (i = 0; i < count; i++) { dirIndexes[i] = dirIndex; baseNames[i] = fileNames[i]; } goto exit; } for (i = 0; i < count; i++) { const char ** needle; char *baseName = strrchr(fileNames[i], '/') + 1; char savechar; int len = baseName - fileNames[i]; savechar = *baseName; *baseName = '\0'; if (dirIndex < 0 || (needle = bsearch(&fileNames[i], dirNames, dirIndex + 1, sizeof(dirNames[0]), dncmp)) == NULL) { char *s = alloca(len + 1); memcpy(s, fileNames[i], len + 1); s[len] = '\0'; dirIndexes[i] = ++dirIndex; dirNames[dirIndex] = s; } else dirIndexes[i] = needle - dirNames; *baseName = savechar; baseNames[i] = baseName; } exit: headerAddEntry(h, RPMTAG_DIRINDEXES, RPM_INT32_TYPE, dirIndexes, count); headerAddEntry(h, RPMTAG_BASENAMES, RPM_STRING_ARRAY_TYPE, baseNames, count); headerAddEntry(h, RPMTAG_DIRNAMES, RPM_STRING_ARRAY_TYPE, dirNames, dirIndex + 1); free((void *)fileNames); headerRemoveEntry(h, RPMTAG_OLDFILENAMES); } /* * This is pretty straight-forward. The only thing that even resembles a trick * is getting all of this into a single xmalloc'd block. */ static void doBuildFileList(Header h, /*@out@*/ const char *** fileListPtr, /*@out@*/ int * fileCountPtr, int baseNameTag, int dirNameTag, int dirIndexesTag) { const char ** baseNames; const char ** dirNames; int * dirIndexes; int count; const char ** fileNames; int size; char * data; int i; if (!headerGetEntry(h, baseNameTag, NULL, (void **) &baseNames, &count)) { if (fileListPtr) *fileListPtr = NULL; if (fileCountPtr) *fileCountPtr = 0; return; /* no file list */ } headerGetEntry(h, dirNameTag, NULL, (void **) &dirNames, NULL); headerGetEntry(h, dirIndexesTag, NULL, (void **) &dirIndexes, &count); size = sizeof(*fileNames) * count; for (i = 0; i < count; i++) size += strlen(baseNames[i]) + strlen(dirNames[dirIndexes[i]]) + 1; fileNames = xmalloc(size); data = ((char *) fileNames) + (sizeof(*fileNames) * count); for (i = 0; i < count; i++) { fileNames[i] = data; data = stpcpy( stpcpy(data, dirNames[dirIndexes[i]]), baseNames[i]); *data++ = '\0'; } free((void *)baseNames); free((void *)dirNames); if (fileListPtr) *fileListPtr = fileNames; else free((void *)fileNames); if (fileCountPtr) *fileCountPtr = count; } void expandFilelist(Header h) { const char ** fileNames = NULL; int count = 0; if (!headerIsEntry(h, RPMTAG_OLDFILENAMES)) { doBuildFileList(h, &fileNames, &count, RPMTAG_BASENAMES, RPMTAG_DIRNAMES, RPMTAG_DIRINDEXES); if (fileNames == NULL || count <= 0) return; headerAddEntry(h, RPMTAG_OLDFILENAMES, RPM_STRING_ARRAY_TYPE, fileNames, count); free((void *)fileNames); } headerRemoveEntry(h, RPMTAG_DIRNAMES); headerRemoveEntry(h, RPMTAG_BASENAMES); headerRemoveEntry(h, RPMTAG_DIRINDEXES); } void rpmBuildFileList(Header h, const char *** fileListPtr, int * fileCountPtr) { doBuildFileList(h, fileListPtr, fileCountPtr, RPMTAG_BASENAMES, RPMTAG_DIRNAMES, RPMTAG_DIRINDEXES); } void buildOrigFileList(Header h, const char *** fileListPtr, int * fileCountPtr) { doBuildFileList(h, fileListPtr, fileCountPtr, RPMTAG_ORIGBASENAMES, RPMTAG_ORIGDIRNAMES, RPMTAG_ORIGDIRINDEXES); } /* glob_pattern_p() taken from bash * Copyright (C) 1985, 1988, 1989 Free Software Foundation, Inc. * * Return nonzero if PATTERN has any special globbing chars in it. */ int myGlobPatternP (const char *patternURL) { const char *p; char c; int open = 0; (void) urlPath(patternURL, &p); while ((c = *p++) != '\0') switch (c) { case '?': case '*': return (1); case '[': /* Only accept an open brace if there is a close */ open++; /* brace to match it. Bracket expressions must be */ continue; /* complete, according to Posix.2 */ case ']': if (open) return (1); continue; case '\\': if (*p++ == '\0') return (0); } return (0); } static int glob_error(/*@unused@*/const char *foo, /*@unused@*/int bar) { return 1; } int rpmGlob(const char * patterns, int * argcPtr, const char *** argvPtr) { int ac = 0; const char ** av = NULL; int argc = 0; const char ** argv = NULL; const char * path; const char * globURL; char * globRoot = NULL; size_t maxb, nb; glob_t gl; int ut; int i, j; int rc; rc = poptParseArgvString(patterns, &ac, &av); if (rc) return rc; for (j = 0; j < ac; j++) { if (!myGlobPatternP(av[j])) { if (argc == 0) argv = xmalloc((argc+2) * sizeof(*argv)); else argv = xrealloc(argv, (argc+1) * sizeof(*argv)); if (_debug) fprintf(stderr, "*** rpmGlob argv[%d] \"%s\"\n", argc, av[j]); argv[argc++] = xstrdup(av[j]); continue; } gl.gl_pathc = 0; gl.gl_pathv = NULL; rc = Glob(av[j], 0, glob_error, &gl); if (rc) goto exit; /* XXX Prepend the URL leader for globs that have stripped it off */ maxb = 0; for (i = 0; i < gl.gl_pathc; i++) { if ((nb = strlen(&(gl.gl_pathv[i][0]))) > maxb) maxb = nb; } ut = urlPath(av[j], &path); nb = ((ut > URL_IS_DASH) ? (path - av[j]) : 0); maxb += nb; maxb += 1; globURL = globRoot = xmalloc(maxb); switch (ut) { case URL_IS_HTTP: case URL_IS_FTP: case URL_IS_PATH: case URL_IS_DASH: strncpy(globRoot, av[j], nb); break; case URL_IS_UNKNOWN: break; } globRoot += nb; *globRoot = '\0'; if (_debug) fprintf(stderr, "*** GLOB maxb %d diskURL %d %*s globURL %p %s\n", (int)maxb, (int)nb, (int)nb, av[j], globURL, globURL); if (argc == 0) argv = xmalloc((gl.gl_pathc+1) * sizeof(*argv)); else if (gl.gl_pathc > 0) argv = xrealloc(argv, (argc+gl.gl_pathc+1) * sizeof(*argv)); for (i = 0; i < gl.gl_pathc; i++) { const char * globFile = &(gl.gl_pathv[i][0]); if (globRoot > globURL && globRoot[-1] == '/') while (*globFile == '/') globFile++; strcpy(globRoot, globFile); if (_debug) fprintf(stderr, "*** rpmGlob argv[%d] \"%s\"\n", argc, globURL); argv[argc++] = xstrdup(globURL); } Globfree(&gl); free((void *)globURL); } if (argv != NULL && argc > 0) { argv[argc] = NULL; if (argvPtr) *argvPtr = argv; if (argcPtr) *argcPtr = argc; rc = 0; } else rc = 1; exit: if (av) free((void *)av); if ((rc || argvPtr == NULL) && argv) { for (i = 0; i < argc; i++) free((void *)argv[i]); free((void *)argv); argv = NULL; } return rc; } /* * XXX This is a "dressed" entry to headerGetEntry to do: * 1) DIRNAME/BASENAME/DIRINDICES -> FILENAMES tag conversions. * 2) i18n lookaside (if enabled). */ int rpmHeaderGetEntry(Header h, int_32 tag, int_32 *type, void **p, int_32 *c) { switch (tag) { case RPMTAG_OLDFILENAMES: { const char ** fl = NULL; int count; rpmBuildFileList(h, &fl, &count); if (count > 0) { *p = fl; if (c) *c = count; if (type) *type = RPM_STRING_ARRAY_TYPE; return 1; } if (c) *c = 0; return 0; } /*@notreached@*/ break; case RPMTAG_GROUP: case RPMTAG_DESCRIPTION: case RPMTAG_SUMMARY: { char fmt[128]; const char * msgstr; const char * errstr; fmt[0] = '\0'; (void) stpcpy( stpcpy( stpcpy( fmt, "%{"), tagName(tag)), "}\n"); /* XXX FIXME: memory leak. */ msgstr = headerSprintf(h, fmt, rpmTagTable, rpmHeaderFormats, &errstr); if (msgstr) { *p = (void *) msgstr; if (type) *type = RPM_STRING_TYPE; if (c) *c = 1; return 1; } else { if (c) *c = 0; return 0; } } /*@notreached@*/ break; default: return headerGetEntry(h, tag, type, p, c); /*@notreached@*/ break; } /*@notreached@*/ } /* * XXX Yet Another dressed entry to unify signature/header tag retrieval. */ int rpmPackageGetEntry( /*@unused@*/ void *leadp, Header sigs, Header h, int_32 tag, int_32 *type, void **p, int_32 *c) { int_32 sigtag; switch (tag) { case RPMTAG_SIGSIZE: sigtag = RPMSIGTAG_SIZE; break; case RPMTAG_SIGLEMD5_1: sigtag = RPMSIGTAG_LEMD5_1; break; case RPMTAG_SIGPGP: sigtag = RPMSIGTAG_PGP; break; case RPMTAG_SIGLEMD5_2: sigtag = RPMSIGTAG_LEMD5_2; break; case RPMTAG_SIGMD5: sigtag = RPMSIGTAG_MD5; break; case RPMTAG_SIGGPG: sigtag = RPMSIGTAG_GPG; break; case RPMTAG_SIGPGP5: sigtag = RPMSIGTAG_GPG; break; default: return rpmHeaderGetEntry(h, tag, type, p, c); /*@notreached@*/ break; } if (headerIsEntry(h, tag)) return rpmHeaderGetEntry(h, tag, type, p, c); if (sigs == NULL) { if (c) *c = 0; return 0; } return headerGetEntry(sigs, sigtag, type, p, c); } /* * Up to rpm 3.0.4, packages implicitly provided their own name-version-release. * Retrofit an explicit "Provides: name = epoch:version-release. */ void providePackageNVR(Header h) { const char *name, *version, *release; int_32 * epoch; const char *pEVR; char *p; int_32 pFlags = RPMSENSE_EQUAL; const char ** provides = NULL; const char ** providesEVR = NULL; int_32 * provideFlags = NULL; int providesCount; int i; int bingo = 1; /* Generate provides for this package name-version-release. */ headerNVR(h, &name, &version, &release); pEVR = p = alloca(21 + strlen(version) + 1 + strlen(release) + 1); *p = '\0'; if (headerGetEntry(h, RPMTAG_EPOCH, NULL, (void **) &epoch, NULL)) { sprintf(p, "%d:", *epoch); while (*p) p++; } (void) stpcpy( stpcpy( stpcpy(p, version) , "-") , release); /* * Rpm prior to 3.0.3 does not have versioned provides. * If no provides at all are available, we can just add. */ if (!headerGetEntry(h, RPMTAG_PROVIDENAME, NULL, (void **) &provides, &providesCount)) { goto exit; } /* * Otherwise, fill in entries on legacy packages. */ if (!headerGetEntry(h, RPMTAG_PROVIDEVERSION, NULL, (void **) &providesEVR, NULL)) { for (i = 0; i < providesCount; i++) { char * vdummy = ""; int_32 fdummy = RPMSENSE_ANY; headerAddOrAppendEntry(h, RPMTAG_PROVIDEVERSION, RPM_STRING_ARRAY_TYPE, &vdummy, 1); headerAddOrAppendEntry(h, RPMTAG_PROVIDEFLAGS, RPM_INT32_TYPE, &fdummy, 1); } goto exit; } headerGetEntry(h, RPMTAG_PROVIDEFLAGS, NULL, (void **) &provideFlags, NULL); for (i = 0; i < providesCount; i++) { if (!(provideFlags[i] == RPMSENSE_EQUAL && !strcmp(name, provides[i]) && !strcmp(pEVR, providesEVR[i]))) continue; bingo = 0; break; } exit: if (provides) free((void *)provides); if (providesEVR) free((void *)providesEVR); if (bingo) { headerAddOrAppendEntry(h, RPMTAG_PROVIDENAME, RPM_STRING_ARRAY_TYPE, &name, 1); headerAddOrAppendEntry(h, RPMTAG_PROVIDEFLAGS, RPM_INT32_TYPE, &pFlags, 1); headerAddOrAppendEntry(h, RPMTAG_PROVIDEVERSION, RPM_STRING_ARRAY_TYPE, &pEVR, 1); } }