Update makepath to remove PATH_MAX usage
[pacman-ng.git] / lib / libalpm / util.c
blob65b6372ac66558336c34e833c7da62352e8b646b
1 /*
2 * util.c
4 * Copyright (c) 2002-2007 by Judd Vinet <jvinet@zeroflux.org>
5 * Copyright (c) 2005 by Aurelien Foret <orelien@chez.com>
6 * Copyright (c) 2005 by Christian Hamar <krics@linuxforum.hu>
7 * Copyright (c) 2006 by David Kimpe <dnaku@frugalware.org>
8 * Copyright (c) 2005, 2006 by Miklos Vajna <vmiklos@frugalware.org>
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
20 * You should have received a copy of the GNU General Public License
21 * along with this program. If not, see <http://www.gnu.org/licenses/>.
24 #include "config.h"
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <fcntl.h>
30 #include <unistd.h>
31 #include <ctype.h>
32 #include <dirent.h>
33 #include <time.h>
34 #include <syslog.h>
35 #include <errno.h>
36 #include <sys/types.h>
37 #include <sys/stat.h>
39 /* libarchive */
40 #include <archive.h>
41 #include <archive_entry.h>
43 /* libalpm */
44 #include "util.h"
45 #include "log.h"
46 #include "package.h"
47 #include "alpm.h"
48 #include "alpm_list.h"
49 #include "md5.h"
51 #ifndef HAVE_STRVERSCMP
52 /* GNU's strverscmp() function, taken from glibc 2.3.2 sources
55 /* Compare strings while treating digits characters numerically.
56 Copyright (C) 1997, 2002 Free Software Foundation, Inc.
57 Contributed by Jean-François Bignolles <bignolle@ecoledoc.ibp.fr>, 1997.
59 The GNU C Library is free software; you can redistribute it and/or
60 modify it under the terms of the GNU Lesser General Public
61 License as published by the Free Software Foundation; either
62 version 2.1 of the License, or (at your option) any later version.
65 /* states: S_N: normal, S_I: comparing integral part, S_F: comparing
66 fractionnal parts, S_Z: idem but with leading Zeroes only */
67 #define S_N 0x0
68 #define S_I 0x4
69 #define S_F 0x8
70 #define S_Z 0xC
72 /* result_type: CMP: return diff; LEN: compare using len_diff/diff */
73 #define CMP 2
74 #define LEN 3
76 /* Compare S1 and S2 as strings holding indices/version numbers,
77 returning less than, equal to or greater than zero if S1 is less than,
78 equal to or greater than S2 (for more info, see the texinfo doc).
81 int strverscmp (s1, s2)
82 const char *s1;
83 const char *s2;
85 const unsigned char *p1 = (const unsigned char *) s1;
86 const unsigned char *p2 = (const unsigned char *) s2;
87 unsigned char c1, c2;
88 int state;
89 int diff;
91 /* Symbol(s) 0 [1-9] others (padding)
92 Transition (10) 0 (01) d (00) x (11) - */
93 static const unsigned int next_state[] =
95 /* state x d 0 - */
96 /* S_N */ S_N, S_I, S_Z, S_N,
97 /* S_I */ S_N, S_I, S_I, S_I,
98 /* S_F */ S_N, S_F, S_F, S_F,
99 /* S_Z */ S_N, S_F, S_Z, S_Z
102 static const int result_type[] =
104 /* state x/x x/d x/0 x/- d/x d/d d/0 d/-
105 0/x 0/d 0/0 0/- -/x -/d -/0 -/- */
107 /* S_N */ CMP, CMP, CMP, CMP, CMP, LEN, CMP, CMP,
108 CMP, CMP, CMP, CMP, CMP, CMP, CMP, CMP,
109 /* S_I */ CMP, -1, -1, CMP, +1, LEN, LEN, CMP,
110 +1, LEN, LEN, CMP, CMP, CMP, CMP, CMP,
111 /* S_F */ CMP, CMP, CMP, CMP, CMP, LEN, CMP, CMP,
112 CMP, CMP, CMP, CMP, CMP, CMP, CMP, CMP,
113 /* S_Z */ CMP, +1, +1, CMP, -1, CMP, CMP, CMP,
114 -1, CMP, CMP, CMP
117 if (p1 == p2)
118 return 0;
120 c1 = *p1++;
121 c2 = *p2++;
122 /* Hint: '0' is a digit too. */
123 state = S_N | ((c1 == '0') + (isdigit (c1) != 0));
125 while ((diff = c1 - c2) == 0 && c1 != '\0')
127 state = next_state[state];
128 c1 = *p1++;
129 c2 = *p2++;
130 state |= (c1 == '0') + (isdigit (c1) != 0);
133 state = result_type[state << 2 | (((c2 == '0') + (isdigit (c2) != 0)))];
135 switch (state)
137 case CMP:
138 return diff;
140 case LEN:
141 while (isdigit (*p1++))
142 if (!isdigit (*p2++))
143 return 1;
145 return isdigit (*p2) ? -1 : diff;
147 default:
148 return state;
151 #endif
153 #ifndef HAVE_STRSEP
154 /* This is a replacement for strsep which is not portable (missing on Solaris).
155 * Copyright (c) 2001 by François Gouget <fgouget_at_codeweavers.com> */
156 char* strsep(char** str, const char* delims)
158 char* token;
160 if (*str==NULL) {
161 /* No more tokens */
162 return NULL;
165 token=*str;
166 while (**str!='\0') {
167 if (strchr(delims,**str)!=NULL) {
168 **str='\0';
169 (*str)++;
170 return token;
172 (*str)++;
174 /* There is no other token */
175 *str=NULL;
176 return token;
178 #endif
180 int _alpm_makepath(const char *path)
182 return(_alpm_makepath_mode(path, 0755));
185 /* does the same thing as 'mkdir -p' */
186 int _alpm_makepath_mode(const char *path, mode_t mode)
188 /* A bit of pointer hell here. Descriptions:
189 * orig - a copy of path so we can safely butcher it with strsep
190 * str - the current position in the path string (after the delimiter)
191 * ptr - the original position of str after calling strsep
192 * incr - incrementally generated path for use in stat/mkdir call
194 char *orig, *str, *ptr, *incr;
195 mode_t oldmask = umask(0000);
196 int ret = 0;
198 orig = strdup(path);
199 incr = calloc(strlen(orig) + 1, sizeof(char));
200 str = orig;
201 while((ptr = strsep(&str, "/"))) {
202 if(strlen(ptr)) {
203 struct stat buf;
204 /* we have another path component- append the newest component to
205 * existing string and create one more level of dir structure */
206 strcat(incr, "/");
207 strcat(incr, ptr);
208 if(stat(incr, &buf)) {
209 if(mkdir(incr, mode)) {
210 ret = 1;
211 break;
216 free(orig);
217 free(incr);
218 umask(oldmask);
219 return(ret);
222 #define CPBUFSIZE 8 * 1024
224 int _alpm_copyfile(const char *src, const char *dest)
226 FILE *in, *out;
227 size_t len;
228 char *buf;
229 int ret = 0;
231 in = fopen(src, "rb");
232 if(in == NULL) {
233 return(1);
235 out = fopen(dest, "wb");
236 if(out == NULL) {
237 fclose(in);
238 return(1);
241 CALLOC(buf, 1, CPBUFSIZE, ret = 1; goto cleanup;);
243 /* do the actual file copy */
244 while((len = fread(buf, 1, CPBUFSIZE, in))) {
245 fwrite(buf, 1, len, out);
248 /* chmod dest to permissions of src, as long as it is not a symlink */
249 struct stat statbuf;
250 if(!stat(src, &statbuf)) {
251 if(! S_ISLNK(statbuf.st_mode)) {
252 fchmod(fileno(out), statbuf.st_mode);
254 } else {
255 /* stat was unsuccessful */
256 ret = 1;
259 cleanup:
260 fclose(in);
261 fclose(out);
262 FREE(buf);
263 return(ret);
266 /* Trim whitespace and newlines from a string
268 char *_alpm_strtrim(char *str)
270 char *pch = str;
272 if(*str == '\0') {
273 /* string is empty, so we're done. */
274 return(str);
277 while(isspace((int)*pch)) {
278 pch++;
280 if(pch != str) {
281 memmove(str, pch, (strlen(pch) + 1));
284 /* check if there wasn't anything but whitespace in the string. */
285 if(*str == '\0') {
286 return(str);
289 pch = (str + (strlen(str) - 1));
290 while(isspace((int)*pch)) {
291 pch--;
293 *++pch = '\0';
295 return(str);
298 /* Helper function for _alpm_strreplace */
299 static void _strnadd(char **str, const char *append, unsigned int count)
301 if(*str) {
302 *str = realloc(*str, strlen(*str) + count + 1);
303 } else {
304 *str = calloc(count + 1, sizeof(char));
307 strncat(*str, append, count);
310 /* Replace all occurances of 'needle' with 'replace' in 'str', returning
311 * a new string (must be free'd) */
312 char *_alpm_strreplace(const char *str, const char *needle, const char *replace)
314 const char *p, *q;
315 p = q = str;
317 char *newstr = NULL;
318 unsigned int needlesz = strlen(needle),
319 replacesz = strlen(replace);
321 while (1) {
322 q = strstr(p, needle);
323 if(!q) { /* not found */
324 if(*p) {
325 /* add the rest of 'p' */
326 _strnadd(&newstr, p, strlen(p));
328 break;
329 } else { /* found match */
330 if(q > p){
331 /* add chars between this occurance and last occurance, if any */
332 _strnadd(&newstr, p, q - p);
334 _strnadd(&newstr, replace, replacesz);
335 p = q + needlesz;
339 return newstr;
343 /* Create a lock file */
344 int _alpm_lckmk()
346 int fd, count = 0;
347 char *dir, *ptr;
348 const char *file = alpm_option_get_lockfile();
350 /* create the dir of the lockfile first */
351 dir = strdup(file);
352 ptr = strrchr(dir, '/');
353 if(ptr) {
354 *ptr = '\0';
356 _alpm_makepath(dir);
358 while((fd = open(file, O_WRONLY | O_CREAT | O_EXCL, 0000)) == -1 && errno == EACCES) {
359 if(++count < 1) {
360 sleep(1);
361 } else {
362 return(-1);
366 FREE(dir);
368 return(fd > 0 ? fd : -1);
371 /* Remove a lock file */
372 int _alpm_lckrm()
374 const char *file = alpm_option_get_lockfile();
375 if(unlink(file) == -1 && errno != ENOENT) {
376 return(-1);
378 return(0);
381 /* Compression functions */
384 * @brief Unpack a specific file or all files in an archive.
386 * @param archive the archive to unpack
387 * @param prefix where to extract the files
388 * @param fn a file within the archive to unpack or NULL for all
390 int _alpm_unpack(const char *archive, const char *prefix, const char *fn)
392 int ret = 1;
393 mode_t oldmask;
394 struct archive *_archive;
395 struct archive_entry *entry;
396 char expath[PATH_MAX];
398 ALPM_LOG_FUNC;
400 if((_archive = archive_read_new()) == NULL)
401 RET_ERR(PM_ERR_LIBARCHIVE, -1);
403 archive_read_support_compression_all(_archive);
404 archive_read_support_format_all(_archive);
406 if(archive_read_open_filename(_archive, archive,
407 ARCHIVE_DEFAULT_BYTES_PER_BLOCK) != ARCHIVE_OK) {
408 _alpm_log(PM_LOG_ERROR, _("could not open %s: %s\n"), archive,
409 archive_error_string(_archive));
410 RET_ERR(PM_ERR_PKG_OPEN, -1);
413 oldmask = umask(0022);
414 while(archive_read_next_header(_archive, &entry) == ARCHIVE_OK) {
415 const struct stat *st;
416 const char *entryname; /* the name of the file in the archive */
418 st = archive_entry_stat(entry);
419 entryname = archive_entry_pathname(entry);
421 if(S_ISREG(st->st_mode)) {
422 archive_entry_set_mode(entry, 0644);
423 } else if(S_ISDIR(st->st_mode)) {
424 archive_entry_set_mode(entry, 0755);
427 /* If a specific file was requested skip entries that don't match. */
428 if (fn && strcmp(fn, entryname)) {
429 _alpm_log(PM_LOG_DEBUG, "skipping: %s\n", entryname);
430 if (archive_read_data_skip(_archive) != ARCHIVE_OK) {
431 ret = 1;
432 goto cleanup;
434 continue;
437 /* Extract the archive entry. */
438 ret = 0;
439 snprintf(expath, PATH_MAX, "%s/%s", prefix, entryname);
440 archive_entry_set_pathname(entry, expath);
442 int readret = archive_read_extract(_archive, entry, 0);
443 if(readret == ARCHIVE_WARN) {
444 /* operation succeeded but a non-critical error was encountered */
445 _alpm_log(PM_LOG_DEBUG, "warning extracting %s (%s)\n",
446 entryname, archive_error_string(_archive));
447 } else if(readret != ARCHIVE_OK) {
448 _alpm_log(PM_LOG_ERROR, _("could not extract %s (%s)\n"),
449 entryname, archive_error_string(_archive));
450 ret = 1;
451 goto cleanup;
454 if(fn) {
455 break;
459 cleanup:
460 umask(oldmask);
461 archive_read_finish(_archive);
462 return(ret);
465 /* does the same thing as 'rm -rf' */
466 int _alpm_rmrf(const char *path)
468 int errflag = 0;
469 struct dirent *dp;
470 DIR *dirp;
471 char name[PATH_MAX];
472 struct stat st;
474 if(_alpm_lstat(path, &st) == 0) {
475 if(!S_ISDIR(st.st_mode)) {
476 if(!unlink(path)) {
477 return(0);
478 } else {
479 if(errno == ENOENT) {
480 return(0);
481 } else {
482 return(1);
485 } else {
486 if((dirp = opendir(path)) == (DIR *)-1) {
487 return(1);
489 for(dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) {
490 if(dp->d_ino) {
491 sprintf(name, "%s/%s", path, dp->d_name);
492 if(strcmp(dp->d_name, "..") && strcmp(dp->d_name, ".")) {
493 errflag += _alpm_rmrf(name);
497 closedir(dirp);
498 if(rmdir(path)) {
499 errflag++;
502 return(errflag);
504 return(0);
507 int _alpm_logaction(unsigned short usesyslog, FILE *f, const char *fmt, va_list args)
509 int ret = 0;
511 if(usesyslog) {
512 vsyslog(LOG_WARNING, fmt, args);
515 if(f) {
516 time_t t;
517 struct tm *tm;
519 t = time(NULL);
520 tm = localtime(&t);
522 /* Use ISO-8601 date format */
523 fprintf(f, "[%04d-%02d-%02d %02d:%02d] ",
524 tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday,
525 tm->tm_hour, tm->tm_min);
526 ret = vfprintf(f, fmt, args);
527 fflush(f);
530 return(ret);
533 int _alpm_ldconfig(const char *root)
535 char line[PATH_MAX];
536 struct stat buf;
538 snprintf(line, PATH_MAX, "%setc/ld.so.conf", root);
539 if(stat(line, &buf) == 0) {
540 snprintf(line, PATH_MAX, "%ssbin/ldconfig", root);
541 if(stat(line, &buf) == 0) {
542 char cmd[PATH_MAX];
543 snprintf(cmd, PATH_MAX, "%s -r %s", line, root);
544 system(cmd);
548 return(0);
551 /* Helper function for comparing strings using the
552 * alpm "compare func" signature */
553 int _alpm_str_cmp(const void *s1, const void *s2)
555 return(strcmp(s1, s2));
558 /** Find a filename in a registered alpm cachedir.
559 * @param filename name of file to find
560 * @return malloced path of file, NULL if not found
562 char *_alpm_filecache_find(const char* filename)
564 struct stat buf;
565 char path[PATH_MAX];
566 char *retpath;
567 alpm_list_t *i;
569 /* Loop through the cache dirs until we find a matching file */
570 for(i = alpm_option_get_cachedirs(); i; i = alpm_list_next(i)) {
571 snprintf(path, PATH_MAX, "%s%s", (char*)alpm_list_getdata(i),
572 filename);
573 if(stat(path, &buf) == 0) {
574 /* TODO maybe check to make sure it is readable? */
575 retpath = strdup(path);
576 _alpm_log(PM_LOG_DEBUG, "found cached pkg: %s\n", retpath);
577 return(retpath);
580 /* package wasn't found in any cachedir */
581 return(NULL);
584 /** Check the alpm cachedirs for existance and find a writable one.
585 * If no valid cache directory can be found, use /tmp.
586 * @return pointer to a writable cache directory.
588 const char *_alpm_filecache_setup(void)
590 struct stat buf;
591 alpm_list_t *i, *tmp;
592 char *cachedir;
594 /* Loop through the cache dirs until we find a writeable dir */
595 for(i = alpm_option_get_cachedirs(); i; i = alpm_list_next(i)) {
596 cachedir = alpm_list_getdata(i);
597 if(stat(cachedir, &buf) != 0) {
598 /* cache directory does not exist.... try creating it */
599 _alpm_log(PM_LOG_WARNING, _("no %s cache exists, creating...\n"),
600 cachedir);
601 if(_alpm_makepath(cachedir) == 0) {
602 _alpm_log(PM_LOG_DEBUG, "using cachedir: %s\n", cachedir);
603 return(cachedir);
605 } else if(S_ISDIR(buf.st_mode) && (buf.st_mode & S_IWUSR)) {
606 _alpm_log(PM_LOG_DEBUG, "using cachedir: %s\n", cachedir);
607 return(cachedir);
611 /* we didn't find a valid cache directory. use /tmp. */
612 tmp = alpm_list_add(NULL, strdup("/tmp/"));
613 alpm_option_set_cachedirs(tmp);
614 _alpm_log(PM_LOG_DEBUG, "using cachedir: %s", "/tmp/\n");
615 _alpm_log(PM_LOG_WARNING, _("couldn't create package cache, using /tmp instead\n"));
616 return(alpm_list_getdata(tmp));
619 /** lstat wrapper that treats /path/dirsymlink/ the same as /path/dirsymlink.
620 * Linux lstat follows POSIX semantics and still performs a dereference on
621 * the first, and for uses of lstat in libalpm this is not what we want.
622 * @param path path to file to lstat
623 * @param buf structure to fill with stat information
624 * @return the return code from lstat
626 int _alpm_lstat(const char *path, struct stat *buf)
628 int ret;
629 char *newpath = strdup(path);
630 int len = strlen(newpath);
632 /* strip the trailing slash if one exists */
633 if(len != 0 && newpath[len - 1] == '/') {
634 newpath[len - 1] = '\0';
637 ret = lstat(newpath, buf);
639 FREE(newpath);
640 return(ret);
643 /** Get the md5 sum of file.
644 * @param filename name of the file
645 * @return the checksum on success, NULL on error
646 * @addtogroup alpm_misc
648 char SYMEXPORT *alpm_get_md5sum(const char *filename)
650 unsigned char output[16];
651 char *md5sum;
652 int ret, i;
654 ALPM_LOG_FUNC;
656 ASSERT(filename != NULL, return(NULL));
658 /* allocate 32 chars plus 1 for null */
659 md5sum = calloc(33, sizeof(char));
660 ret = md5_file(filename, output);
662 if (ret > 0) {
663 RET_ERR(PM_ERR_NOT_A_FILE, NULL);
666 /* Convert the result to something readable */
667 for (i = 0; i < 16; i++) {
668 /* sprintf is acceptable here because we know our output */
669 sprintf(md5sum +(i * 2), "%02x", output[i]);
671 md5sum[32] = '\0';
673 _alpm_log(PM_LOG_DEBUG, "md5(%s) = %s\n", filename, md5sum);
674 return(md5sum);
677 int _alpm_test_md5sum(const char *filepath, const char *md5sum)
679 char *md5sum2;
680 int ret;
682 md5sum2 = alpm_get_md5sum(filepath);
684 if(md5sum == NULL || md5sum2 == NULL) {
685 ret = -1;
686 } else if(strcmp(md5sum, md5sum2) != 0) {
687 ret = 1;
688 } else {
689 ret = 0;
692 FREE(md5sum2);
693 return(ret);
696 char *_alpm_archive_fgets(char *line, size_t size, struct archive *a)
698 /* for now, just read one char at a time until we get to a
699 * '\n' char. we can optimize this later with an internal
700 * buffer. */
701 /* leave room for zero terminator */
702 char *last = line + size - 1;
703 char *i;
705 for(i = line; i < last; i++) {
706 int ret = archive_read_data(a, i, 1);
707 /* special check for first read- if null, return null,
708 * this indicates EOF */
709 if(i == line && (ret <= 0 || *i == '\0')) {
710 return(NULL);
712 /* check if read value was null or newline */
713 if(ret <= 0 || *i == '\0' || *i == '\n') {
714 last = i + 1;
715 break;
719 /* always null terminate the buffer */
720 *last = '\0';
722 return(line);
725 /* vim: set ts=2 sw=2 noet: */