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/>.
36 #include <sys/types.h>
41 #include <archive_entry.h>
48 #include "alpm_list.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 */
72 /* result_type: CMP: return diff; LEN: compare using len_diff/diff */
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
)
85 const unsigned char *p1
= (const unsigned char *) s1
;
86 const unsigned char *p2
= (const unsigned char *) s2
;
91 /* Symbol(s) 0 [1-9] others (padding)
92 Transition (10) 0 (01) d (00) x (11) - */
93 static const unsigned int next_state
[] =
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
,
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
];
130 state
|= (c1
== '0') + (isdigit (c1
) != 0);
133 state
= result_type
[state
<< 2 | (((c2
== '0') + (isdigit (c2
) != 0)))];
141 while (isdigit (*p1
++))
142 if (!isdigit (*p2
++))
145 return isdigit (*p2
) ? -1 : diff
;
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
)
166 while (**str
!='\0') {
167 if (strchr(delims
,**str
)!=NULL
) {
174 /* There is no other token */
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);
199 incr
= calloc(strlen(orig
) + 1, sizeof(char));
201 while((ptr
= strsep(&str
, "/"))) {
204 /* we have another path component- append the newest component to
205 * existing string and create one more level of dir structure */
208 if(stat(incr
, &buf
)) {
209 if(mkdir(incr
, mode
)) {
222 #define CPBUFSIZE 8 * 1024
224 int _alpm_copyfile(const char *src
, const char *dest
)
231 in
= fopen(src
, "rb");
235 out
= fopen(dest
, "wb");
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 */
250 if(!stat(src
, &statbuf
)) {
251 if(! S_ISLNK(statbuf
.st_mode
)) {
252 fchmod(fileno(out
), statbuf
.st_mode
);
255 /* stat was unsuccessful */
266 /* Trim whitespace and newlines from a string
268 char *_alpm_strtrim(char *str
)
273 /* string is empty, so we're done. */
277 while(isspace((int)*pch
)) {
281 memmove(str
, pch
, (strlen(pch
) + 1));
284 /* check if there wasn't anything but whitespace in the string. */
289 pch
= (str
+ (strlen(str
) - 1));
290 while(isspace((int)*pch
)) {
298 /* Helper function for _alpm_strreplace */
299 static void _strnadd(char **str
, const char *append
, unsigned int count
)
302 *str
= realloc(*str
, strlen(*str
) + count
+ 1);
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
)
318 unsigned int needlesz
= strlen(needle
),
319 replacesz
= strlen(replace
);
322 q
= strstr(p
, needle
);
323 if(!q
) { /* not found */
325 /* add the rest of 'p' */
326 _strnadd(&newstr
, p
, strlen(p
));
329 } else { /* found match */
331 /* add chars between this occurance and last occurance, if any */
332 _strnadd(&newstr
, p
, q
- p
);
334 _strnadd(&newstr
, replace
, replacesz
);
343 /* Create a lock file */
348 const char *file
= alpm_option_get_lockfile();
350 /* create the dir of the lockfile first */
352 ptr
= strrchr(dir
, '/');
358 while((fd
= open(file
, O_WRONLY
| O_CREAT
| O_EXCL
, 0000)) == -1 && errno
== EACCES
) {
368 return(fd
> 0 ? fd
: -1);
371 /* Remove a lock file */
374 const char *file
= alpm_option_get_lockfile();
375 if(unlink(file
) == -1 && errno
!= ENOENT
) {
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
)
394 struct archive
*_archive
;
395 struct archive_entry
*entry
;
396 char expath
[PATH_MAX
];
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
) {
437 /* Extract the archive entry. */
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
));
461 archive_read_finish(_archive
);
465 /* does the same thing as 'rm -rf' */
466 int _alpm_rmrf(const char *path
)
474 if(_alpm_lstat(path
, &st
) == 0) {
475 if(!S_ISDIR(st
.st_mode
)) {
479 if(errno
== ENOENT
) {
486 if((dirp
= opendir(path
)) == (DIR *)-1) {
489 for(dp
= readdir(dirp
); dp
!= NULL
; dp
= readdir(dirp
)) {
491 sprintf(name
, "%s/%s", path
, dp
->d_name
);
492 if(strcmp(dp
->d_name
, "..") && strcmp(dp
->d_name
, ".")) {
493 errflag
+= _alpm_rmrf(name
);
507 int _alpm_logaction(unsigned short usesyslog
, FILE *f
, const char *fmt
, va_list args
)
512 vsyslog(LOG_WARNING
, fmt
, args
);
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
);
533 int _alpm_ldconfig(const char *root
)
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) {
543 snprintf(cmd
, PATH_MAX
, "%s -r %s", line
, root
);
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
)
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
),
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
);
580 /* package wasn't found in any cachedir */
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)
591 alpm_list_t
*i
, *tmp
;
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"),
601 if(_alpm_makepath(cachedir
) == 0) {
602 _alpm_log(PM_LOG_DEBUG
, "using cachedir: %s\n", 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
);
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
)
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
);
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];
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
);
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
]);
673 _alpm_log(PM_LOG_DEBUG
, "md5(%s) = %s\n", filename
, md5sum
);
677 int _alpm_test_md5sum(const char *filepath
, const char *md5sum
)
682 md5sum2
= alpm_get_md5sum(filepath
);
684 if(md5sum
== NULL
|| md5sum2
== NULL
) {
686 } else if(strcmp(md5sum
, md5sum2
) != 0) {
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
701 /* leave room for zero terminator */
702 char *last
= line
+ size
- 1;
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')) {
712 /* check if read value was null or newline */
713 if(ret
<= 0 || *i
== '\0' || *i
== '\n') {
719 /* always null terminate the buffer */
725 /* vim: set ts=2 sw=2 noet: */