X-Git-Url: https://mattmccutchen.net/rsync/rsync.git/blobdiff_plain/af9f56a09df90e07bd3ba33e2583b30fd77f3e0f..8e41b68e8f975c02a5d9281be780ba5d1a385107:/util.c diff --git a/util.c b/util.c index b20cdb64..656c3bf5 100644 --- a/util.c +++ b/util.c @@ -1,12 +1,14 @@ -/* -*- c-file-style: "linux" -*- +/* + * Utility routines used in rsync. * - * Copyright (C) 1996-2000 by Andrew Tridgell - * Copyright (C) Paul Mackerras 1996 - * Copyright (C) 2001, 2002 by Martin Pool + * Copyright (C) 1996-2000 Andrew Tridgell + * Copyright (C) 1996 Paul Mackerras + * Copyright (C) 2001, 2002 Martin Pool + * Copyright (C) 2003-2007 Wayne Davison * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or + * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, @@ -14,38 +16,36 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * You should have received a copy of the GNU General Public License along + * with this program; if not, visit the http://fsf.org website. */ -/** - * @file - * - * Utilities used in rsync - **/ - #include "rsync.h" extern int verbose; extern int dry_run; extern int module_id; extern int modify_window; +extern int relative_paths; +extern int human_readable; +extern char *module_dir; +extern unsigned int module_dirlen; +extern mode_t orig_umask; extern char *partial_dir; extern struct filter_list_struct server_filter_list; int sanitize_paths = 0; +char curr_dir[MAXPATHLEN]; +unsigned int curr_dir_len; +int curr_dir_depth; /* This is only set for a sanitizing daemon. */ - -/** - * Set a fd into nonblocking mode - **/ +/* Set a fd into nonblocking mode. */ void set_nonblocking(int fd) { int val; - if ((val = fcntl(fd, F_GETFL, 0)) == -1) + if ((val = fcntl(fd, F_GETFL)) == -1) return; if (!(val & NONBLOCK_FLAG)) { val |= NONBLOCK_FLAG; @@ -53,14 +53,12 @@ void set_nonblocking(int fd) } } -/** - * Set a fd into blocking mode - **/ +/* Set a fd into blocking mode. */ void set_blocking(int fd) { int val; - if ((val = fcntl(fd, F_GETFL, 0)) == -1) + if ((val = fcntl(fd, F_GETFL)) == -1) return; if (val & NONBLOCK_FLAG) { val &= ~NONBLOCK_FLAG; @@ -68,7 +66,6 @@ void set_blocking(int fd) } } - /** * Create a file descriptor pair - like pipe() but use socketpair if * possible (because of blocking issues on pipes). @@ -93,10 +90,9 @@ int fd_pair(int fd[2]) return ret; } - void print_child_argv(char **cmd) { - rprintf(FINFO, "opening connection using "); + rprintf(FCLIENT, "opening connection using "); for (; *cmd; cmd++) { /* Look for characters that ought to be quoted. This * is not a great quoting algorithm, but it's @@ -105,34 +101,36 @@ void print_child_argv(char **cmd) "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "0123456789" ",.-_=+@/") != strlen(*cmd)) { - rprintf(FINFO, "\"%s\" ", safe_fname(*cmd)); + rprintf(FCLIENT, "\"%s\" ", *cmd); } else { - rprintf(FINFO, "%s ", safe_fname(*cmd)); + rprintf(FCLIENT, "%s ", *cmd); } } - rprintf(FINFO, "\n"); + rprintf(FCLIENT, "\n"); } - -void out_of_memory(char *str) +NORETURN void out_of_memory(const char *str) { - rprintf(FERROR, "ERROR: out of memory in %s\n", str); + rprintf(FERROR, "ERROR: out of memory in %s [%s]\n", str, who_am_i()); exit_cleanup(RERR_MALLOC); } -void overflow(char *str) +NORETURN void overflow_exit(const char *str) { - rprintf(FERROR, "ERROR: buffer overflow in %s\n", str); + rprintf(FERROR, "ERROR: buffer overflow in %s [%s]\n", str, who_am_i()); exit_cleanup(RERR_MALLOC); } - - -int set_modtime(char *fname, time_t modtime) +int set_modtime(const char *fname, time_t modtime, mode_t mode) { +#if !defined HAVE_LUTIMES || !defined HAVE_UTIMES + if (S_ISLNK(mode)) + return 1; +#endif + if (verbose > 2) { rprintf(FINFO, "set modtime of %s to (%ld) %s", - safe_fname(fname), (long)modtime, + fname, (long)modtime, asctime(localtime(&modtime))); } @@ -140,7 +138,20 @@ int set_modtime(char *fname, time_t modtime) return 0; { -#ifdef HAVE_UTIMBUF +#ifdef HAVE_UTIMES + struct timeval t[2]; + t[0].tv_sec = time(NULL); + t[0].tv_usec = 0; + t[1].tv_sec = modtime; + t[1].tv_usec = 0; +# ifdef HAVE_LUTIMES + if (S_ISLNK(mode)) { + lutimes(fname, t); + return 0; /* ignore errors */ + } +# endif + return utimes(fname, t); +#elif defined HAVE_UTIMBUF struct utimbuf tbuf; tbuf.actime = time(NULL); tbuf.modtime = modtime; @@ -151,41 +162,49 @@ int set_modtime(char *fname, time_t modtime) t[1] = modtime; return utime(fname,t); #else - struct timeval t[2]; - t[0].tv_sec = time(NULL); - t[0].tv_usec = 0; - t[1].tv_sec = modtime; - t[1].tv_usec = 0; - return utimes(fname,t); +#error No file-time-modification routine found! #endif } } +/* This creates a new directory with default permissions. Since there + * might be some directory-default permissions affecting this, we can't + * force the permissions directly using the original umask and mkdir(). */ +int mkdir_defmode(char *fname) +{ + int ret; -/** - Create any necessary directories in fname. Unfortunately we don't know - what perms to give the directory when this is called so we need to rely - on the umask -**/ -int create_directory_path(char *fname, int base_umask) + umask(orig_umask); + ret = do_mkdir(fname, ACCESSPERMS); + umask(0); + + return ret; +} + +/* Create any necessary directories in fname. Any missing directories are + * created with default permissions. */ +int create_directory_path(char *fname) { char *p; + int ret = 0; while (*fname == '/') fname++; while (strncmp(fname, "./", 2) == 0) fname += 2; + umask(orig_umask); p = fname; while ((p = strchr(p,'/')) != NULL) { - *p = 0; - do_mkdir(fname, 0777 & ~base_umask); - *p = '/'; - p++; + *p = '\0'; + if (do_mkdir(fname, ACCESSPERMS) < 0 && errno != EEXIST) + ret = -1; + *p++ = '/'; } - return 0; -} + umask(0); + return ret; +} /** * Write @p len bytes at @p ptr to descriptor @p desc, retrying if @@ -197,7 +216,7 @@ int create_directory_path(char *fname, int base_umask) * * Derived from GNU C's cccp.c. */ -int full_write(int desc, char *ptr, size_t len) +int full_write(int desc, const char *ptr, size_t len) { int total_written; @@ -216,7 +235,6 @@ int full_write(int desc, char *ptr, size_t len) return total_written; } - /** * Read @p len bytes at @p ptr from descriptor @p desc, retrying if * interrupted. @@ -242,12 +260,11 @@ static int safe_read(int desc, char *ptr, size_t len) return n_chars; } - /** Copy a file. * * This is used in conjunction with the --temp-dir, --backup, and * --copy-dest options. */ -int copy_file(char *source, char *dest, mode_t mode) +int copy_file(const char *source, const char *dest, mode_t mode) { int ifd; int ofd; @@ -316,7 +333,7 @@ int copy_file(char *source, char *dest, mode_t mode) * --delete trying to remove old .rsyncNNN files, hence it renames it * each time. **/ -int robust_unlink(char *fname) +int robust_unlink(const char *fname) { #ifndef ETXTBSY return do_unlink(fname); @@ -344,14 +361,14 @@ int robust_unlink(char *fname) /* start where the last one left off to reduce chance of clashes */ start = counter; do { - sprintf(&path[pos], "%03d", counter); + snprintf(&path[pos], MAX_RENAMES_DIGITS+1, "%03d", counter); if (++counter >= MAX_RENAMES) counter = 1; } while ((rc = access(path, 0)) == 0 && counter != start); if (verbose > 0) { rprintf(FINFO,"renaming %s to %s because of text busy\n", - safe_fname(fname), safe_fname(path)); + fname, path); } /* maybe we should return rename()'s exit status? Nah. */ @@ -364,8 +381,11 @@ int robust_unlink(char *fname) } /* Returns 0 on successful rename, 1 if we successfully copied the file - * across filesystems, -2 if copy_file() failed, and -1 on other errors. */ -int robust_rename(char *from, char *to, int mode) + * across filesystems, -2 if copy_file() failed, and -1 on other errors. + * If partialptr is not NULL and we need to do a copy, copy the file into + * the active partial-dir instead of over the destination file. */ +int robust_rename(const char *from, const char *to, const char *partialptr, + int mode) { int tries = 4; @@ -381,6 +401,11 @@ int robust_rename(char *from, char *to, int mode) break; #endif case EXDEV: + if (partialptr) { + if (!handle_partial_dir(partialptr,PDIR_CREATE)) + return -1; + to = partialptr; + } if (copy_file(from, to, mode) != 0) return -2; do_unlink(from); @@ -392,7 +417,6 @@ int robust_rename(char *from, char *to, int mode) return -1; } - static pid_t all_pids[10]; static int num_pids; @@ -436,9 +460,8 @@ void kill_all(int sig) } } - /** Turn a user name into a uid */ -int name_to_uid(char *name, uid_t *uid) +int name_to_uid(const char *name, uid_t *uid) { struct passwd *pass; if (!name || !*name) @@ -452,7 +475,7 @@ int name_to_uid(char *name, uid_t *uid) } /** Turn a group name into a gid */ -int name_to_gid(char *name, gid_t *gid) +int name_to_gid(const char *name, gid_t *gid) { struct group *grp; if (!name || !*name) @@ -465,7 +488,6 @@ int name_to_gid(char *name, gid_t *gid) return 0; } - /** Lock a byte range in a open file */ int lock_range(int fd, int offset, int len) { @@ -517,7 +539,6 @@ static void glob_expand_one(char *s, char ***argv_ptr, int *argc_ptr, filter_server_path(s); #else glob_t globbuf; - int i; if (maxargs <= argc) return; @@ -525,7 +546,7 @@ static void glob_expand_one(char *s, char ***argv_ptr, int *argc_ptr, s = "."; if (sanitize_paths) - s = sanitize_path(NULL, s, "", 0); + s = sanitize_path(NULL, s, "", 0, NULL); else s = strdup(s); @@ -542,9 +563,9 @@ static void glob_expand_one(char *s, char ***argv_ptr, int *argc_ptr, if (globbuf.gl_pathc == 0) argv[argc++] = s; else { - int j = globbuf.gl_pathc; + int i; free(s); - for (i = 0; i < j; i++) { + for (i = 0; i < (int)globbuf.gl_pathc; i++) { if (!(argv[argc++] = strdup(globbuf.gl_pathv[i]))) out_of_memory("glob_expand_one"); } @@ -593,8 +614,8 @@ void glob_expand(char *base1, char ***argv_ptr, int *argc_ptr, int *maxargs_ptr) void strlower(char *s) { while (*s) { - if (isupper(*(unsigned char *)s)) - *s = tolower(*(unsigned char *)s); + if (isUpper(s)) + *s = toLower(s); s++; } } @@ -656,7 +677,7 @@ int count_dir_elements(const char *p) int cnt = 0, new_component = 1; while (*p) { if (*p++ == '/') - new_component = 1; + new_component = (*p != '.' || (p[1] != '/' && p[1] != '\0')); else if (new_component) { new_component = 0; cnt++; @@ -729,29 +750,36 @@ unsigned int clean_fname(char *name, BOOL collapse_dot_dot) * rootdir will be ignored to avoid expansion of the string. * * The rootdir string contains a value to use in place of a leading slash. - * Specify NULL to get the default of lp_path(module_id). + * Specify NULL to get the default of "module_dir". * - * If depth is >= 0, it is a count of how many '..'s to allow at the start - * of the path. Use -1 to allow unlimited depth. + * The depth var is a count of how many '..'s to allow at the start of the + * path. If symlink is set, combine its value with the "p" value to get + * the target path, and **return NULL if any '..'s try to escape**. * * We also clean the path in a manner similar to clean_fname() but with a - * few differences: + * few differences: * * Turns multiple adjacent slashes into a single slash, gets rid of "." dir * elements (INCLUDING a trailing dot dir), PRESERVES a trailing slash, and * ALWAYS collapses ".." elements (except for those at the start of the * string up to "depth" deep). If the resulting name would be empty, * change it into a ".". */ -char *sanitize_path(char *dest, const char *p, const char *rootdir, int depth) +char *sanitize_path(char *dest, const char *p, const char *rootdir, int depth, + const char *symlink) { - char *start, *sanp; - int rlen = 0; + char *start, *sanp, *save_dest = dest; + int rlen = 0, leave_one_dotdir = relative_paths; + + if (symlink && *symlink == '/') { + p = symlink; + symlink = ""; + } if (dest != p) { int plen = strlen(p); if (*p == '/') { if (!rootdir) - rootdir = lp_path(module_id); + rootdir = module_dir; rlen = strlen(rootdir); depth = 0; p++; @@ -769,7 +797,18 @@ char *sanitize_path(char *dest, const char *p, const char *rootdir, int depth) } start = sanp = dest + rlen; - while (*p != '\0') { + while (1) { + if (*p == '\0') { + if (!symlink || !*symlink) + break; + while (sanp != start && sanp[-1] != '/') { + /* strip last element */ + sanp--; + } + /* Append a relative symlink */ + p = symlink; + symlink = ""; + } /* discard leading or extra slashes */ if (*p == '/') { p++; @@ -780,13 +819,22 @@ char *sanitize_path(char *dest, const char *p, const char *rootdir, int depth) * always be left pointing after a slash */ if (*p == '.' && (p[1] == '/' || p[1] == '\0')) { - /* skip "." component */ - p++; - continue; + if (leave_one_dotdir && p[1]) + leave_one_dotdir = 0; + else { + /* skip "." component */ + p++; + continue; + } } if (*p == '.' && p[1] == '.' && (p[2] == '/' || p[2] == '\0')) { /* ".." component followed by slash or end */ if (depth <= 0 || sanp != start) { + if (symlink && sanp == start) { + if (!save_dest) + free(dest); + return NULL; + } p += 2; if (sanp != start) { /* back up sanp one level */ @@ -815,15 +863,10 @@ char *sanitize_path(char *dest, const char *p, const char *rootdir, int depth) return dest; } -char curr_dir[MAXPATHLEN]; -unsigned int curr_dir_len; - -/** - * Like chdir(), but it keeps track of the current directory (in the +/* Like chdir(), but it keeps track of the current directory (in the * global "curr_dir"), and ensures that the path size doesn't overflow. - * Also cleans the path using the clean_fname() function. - **/ -int push_dir(char *dir) + * Also cleans the path using the clean_fname() function. */ +int push_dir(const char *dir, int set_path_only) { static int initialised; unsigned int len; @@ -841,10 +884,12 @@ int push_dir(char *dir) if (len == 1 && *dir == '.') return 1; - if ((*dir == '/' ? len : curr_dir_len + 1 + len) >= sizeof curr_dir) + if ((*dir == '/' ? len : curr_dir_len + 1 + len) >= sizeof curr_dir) { + errno = ENAMETOOLONG; return 0; + } - if (chdir(dir)) + if (!set_path_only && chdir(dir)) return 0; if (*dir == '/') { @@ -857,6 +902,11 @@ int push_dir(char *dir) } curr_dir_len = clean_fname(curr_dir, 1); + if (sanitize_paths) { + if (module_dirlen > curr_dir_len) + module_dirlen = curr_dir_len; + curr_dir_depth = count_dir_elements(curr_dir + module_dirlen); + } return 1; } @@ -865,7 +915,7 @@ int push_dir(char *dir) * Reverse a push_dir() call. You must pass in an absolute path * that was copied from a prior value of "curr_dir". **/ -int pop_dir(char *dir) +int pop_dir(const char *dir) { if (chdir(dir)) return 0; @@ -873,37 +923,12 @@ int pop_dir(char *dir) curr_dir_len = strlcpy(curr_dir, dir, sizeof curr_dir); if (curr_dir_len >= sizeof curr_dir) curr_dir_len = sizeof curr_dir - 1; + if (sanitize_paths) + curr_dir_depth = count_dir_elements(curr_dir + module_dirlen); return 1; } -/* Return the filename, turning any non-printable characters into '?'s. - * This ensures that outputting it on a line of its own cannot generate an - * empty line. This function can return only MAX_SAFE_NAMES values at a - * time! The returned value can be longer than MAXPATHLEN (because we - * may be trying to output an error about a too-long filename)! */ -char *safe_fname(const char *fname) -{ -#define MAX_SAFE_NAMES 4 - static char fbuf[MAX_SAFE_NAMES][MAXPATHLEN*2]; - static int ndx = 0; - int limit = sizeof fbuf / MAX_SAFE_NAMES - 1; - char *t; - - ndx = (ndx + 1) % MAX_SAFE_NAMES; - for (t = fbuf[ndx]; *fname; fname++) { - if (!isprint(*(uchar*)fname)) - *t++ = '?'; - else - *t++ = *fname; - if (--limit == 0) - break; - } - *t = '\0'; - - return fbuf[ndx]; -} - /** * Return a quoted string with the full pathname of the indicated filename. * The string " (in MODNAME)" may also be appended. The returned pointer @@ -918,11 +943,10 @@ char *full_fname(const char *fn) if (result) free(result); - fn = safe_fname(fn); if (*fn == '/') p1 = p2 = ""; else { - p1 = curr_dir; + p1 = curr_dir + module_dirlen; for (p2 = p1; *p2 == '/'; p2++) {} if (*p2) p2 = "/"; @@ -931,17 +955,11 @@ char *full_fname(const char *fn) m1 = " (in "; m2 = lp_name(module_id); m3 = ")"; - if (p1 == curr_dir) { - if (!lp_use_chroot(module_id)) { - char *p = lp_path(module_id); - if (*p != '/' || p[1]) - p1 += strlen(p); - } - } } else m1 = m2 = m3 = ""; - asprintf(&result, "\"%s%s%s\"%s%s%s", p1, p2, fn, m1, m2, m3); + if (asprintf(&result, "\"%s%s%s\"%s%s%s", p1, p2, fn, m1, m2, m3) <= 0) + out_of_memory("full_fname"); return result; } @@ -967,13 +985,11 @@ char *partial_dir_fname(const char *fname) if ((int)pathjoin(t, sz, partial_dir, fn) >= sz) return NULL; if (server_filter_list.head) { - static int len; - if (!len) - len = strlen(partial_dir); - t[len] = '\0'; + t = strrchr(partial_fname, '/'); + *t = '\0'; if (check_filter(&server_filter_list, partial_fname, 1) < 0) return NULL; - t[len] = '/'; + *t = '/'; if (check_filter(&server_filter_list, partial_fname, 0) < 0) return NULL; } @@ -1013,22 +1029,6 @@ int handle_partial_dir(const char *fname, int create) return 1; } -/** We need to supply our own strcmp function for file list comparisons - to ensure that signed/unsigned usage is consistent between machines. */ -int u_strcmp(const char *cs1, const char *cs2) -{ - const uchar *s1 = (const uchar *)cs1; - const uchar *s2 = (const uchar *)cs2; - - while (*s1 && *s2 && (*s1 == *s2)) { - s1++; s2++; - } - - return (int)*s1 - (int)*s2; -} - - - /** * Determine if a symlink points outside the current directory tree. * This is considered "unsafe" because e.g. when mirroring somebody @@ -1093,14 +1093,70 @@ int unsafe_symlink(const char *dest, const char *src) return (depth < 0); } +/* Return the int64 number as a string. If the --human-readable option was + * specified, we may output the number in K, M, or G units. We can return + * up to 4 buffers at a time. */ +char *human_num(int64 num) +{ + static char bufs[4][128]; /* more than enough room */ + static unsigned int n; + char *s; -/** - * Return the date and time as a string - **/ + n = (n + 1) % (sizeof bufs / sizeof bufs[0]); + + if (human_readable) { + char units = '\0'; + int mult = human_readable == 1 ? 1000 : 1024; + double dnum = 0; + if (num > mult*mult*mult) { + dnum = (double)num / (mult*mult*mult); + units = 'G'; + } else if (num > mult*mult) { + dnum = (double)num / (mult*mult); + units = 'M'; + } else if (num > mult) { + dnum = (double)num / mult; + units = 'K'; + } + if (units) { + snprintf(bufs[n], sizeof bufs[0], "%.2f%c", dnum, units); + return bufs[n]; + } + } + + s = bufs[n] + sizeof bufs[0] - 1; + *s = '\0'; + + if (!num) + *--s = '0'; + while (num) { + *--s = (char)(num % 10) + '0'; + num /= 10; + } + return s; +} + +/* Return the double number as a string. If the --human-readable option was + * specified, we may output the number in K, M, or G units. We use a buffer + * from human_num() to return our result. */ +char *human_dnum(double dnum, int decimal_digits) +{ + char *buf = human_num(dnum); + int len = strlen(buf); + if (isDigit(buf + len - 1)) { + /* There's extra room in buf prior to the start of the num. */ + buf -= decimal_digits + 1; + snprintf(buf, len + decimal_digits + 2, "%.*f", decimal_digits, dnum); + } + return buf; +} + +/* Return the date and time as a string. Some callers tweak returned buf. */ char *timestring(time_t t) { static char TimeBuf[200]; struct tm *tm = localtime(&t); + char *p; #ifdef HAVE_STRFTIME strftime(TimeBuf, sizeof TimeBuf - 1, "%Y/%m/%d %H:%M:%S", tm); @@ -1108,14 +1164,12 @@ char *timestring(time_t t) strlcpy(TimeBuf, asctime(tm), sizeof TimeBuf); #endif - if (TimeBuf[strlen(TimeBuf)-1] == '\n') { - TimeBuf[strlen(TimeBuf)-1] = 0; - } + if ((p = strchr(TimeBuf, '\n')) != NULL) + *p = '\0'; - return(TimeBuf); + return TimeBuf; } - /** * Sleep for a specified number of milliseconds. * @@ -1144,11 +1198,8 @@ int msleep(int t) return True; } - -/** - * Determine if two file modification times are equivalent (either - * exact or in the modification timestamp window established by - * --modify-window). +/* Determine if two time_t values are equivalent (either exact, or in + * the modification timestamp window established by --modify-window). * * @retval 0 if the times should be treated as the same * @@ -1156,7 +1207,7 @@ int msleep(int t) * * @retval -1 if the 2nd is later **/ -int cmp_modtime(time_t file1, time_t file2) +int cmp_time(time_t file1, time_t file2) { if (file2 > file1) { if (file2 - file1 <= modify_window) @@ -1202,7 +1253,6 @@ int _Insure_trap_error(int a1, int a2, int a3, int a4, int a5, int a6) } #endif - #define MALLOC_MAX 0x40000000 void *_new_array(unsigned int size, unsigned long num) @@ -1216,7 +1266,6 @@ void *_realloc_array(void *ptr, unsigned int size, unsigned long num) { if (num >= MALLOC_MAX/size) return NULL; - /* No realloc should need this, but just in case... */ if (!ptr) return malloc(size * num); return realloc(ptr, size * num); @@ -1259,7 +1308,7 @@ const char *find_filename_suffix(const char *fn, int fn_len, int *len_ptr) if (strcmp(s+1, "orig") == 0) continue; } else if (s_len > 2 && had_tilde - && s[1] == '~' && isdigit(*(uchar*)(s+2))) + && s[1] == '~' && isDigit(s + 2)) continue; *len_ptr = s_len; suf = s; @@ -1267,7 +1316,7 @@ const char *find_filename_suffix(const char *fn, int fn_len, int *len_ptr) break; /* Determine if the suffix is all digits. */ for (s++, s_len--; s_len > 0; s++, s_len--) { - if (!isdigit(*(uchar*)s)) + if (!isDigit(s)) return suf; } /* An all-digit suffix may not be that signficant. */ @@ -1328,3 +1377,123 @@ uint32 fuzzy_distance(const char *s1, int len1, const char *s2, int len2) return a[len2-1]; } + +#define BB_SLOT_SIZE (16*1024) /* Desired size in bytes */ +#define BB_PER_SLOT_BITS (BB_SLOT_SIZE * 8) /* Number of bits per slot */ +#define BB_PER_SLOT_INTS (BB_SLOT_SIZE / 4) /* Number of int32s per slot */ + +struct bitbag { + uint32 **bits; + int slot_cnt; +}; + +struct bitbag *bitbag_create(int max_ndx) +{ + struct bitbag *bb = new(struct bitbag); + bb->slot_cnt = (max_ndx + BB_PER_SLOT_BITS - 1) / BB_PER_SLOT_BITS; + + if (!(bb->bits = (uint32**)calloc(bb->slot_cnt, sizeof (uint32*)))) + out_of_memory("bitbag_create"); + + return bb; +} + +void bitbag_set_bit(struct bitbag *bb, int ndx) +{ + int slot = ndx / BB_PER_SLOT_BITS; + ndx %= BB_PER_SLOT_BITS; + + if (!bb->bits[slot]) { + if (!(bb->bits[slot] = (uint32*)calloc(BB_PER_SLOT_INTS, 4))) + out_of_memory("bitbag_set_bit"); + } + + bb->bits[slot][ndx/32] |= 1u << (ndx % 32); +} + +#if 0 /* not needed yet */ +void bitbag_clear_bit(struct bitbag *bb, int ndx) +{ + int slot = ndx / BB_PER_SLOT_BITS; + ndx %= BB_PER_SLOT_BITS; + + if (!bb->bits[slot]) + return; + + bb->bits[slot][ndx/32] &= ~(1u << (ndx % 32)); +} + +int bitbag_check_bit(struct bitbag *bb, int ndx) +{ + int slot = ndx / BB_PER_SLOT_BITS; + ndx %= BB_PER_SLOT_BITS; + + if (!bb->bits[slot]) + return 0; + + return bb->bits[slot][ndx/32] & (1u << (ndx % 32)) ? 1 : 0; +} +#endif + +/* Call this with -1 to start checking from 0. Returns -1 at the end. */ +int bitbag_next_bit(struct bitbag *bb, int after) +{ + uint32 bits, mask; + int i, ndx = after + 1; + int slot = ndx / BB_PER_SLOT_BITS; + ndx %= BB_PER_SLOT_BITS; + + mask = (1u << (ndx % 32)) - 1; + for (i = ndx / 32; slot < bb->slot_cnt; slot++, i = mask = 0) { + if (!bb->bits[slot]) + continue; + for ( ; i < BB_PER_SLOT_INTS; i++, mask = 0) { + if (!(bits = bb->bits[slot][i] & ~mask)) + continue; + /* The xor magic figures out the lowest enabled bit in + * bits, and the switch quickly computes log2(bit). */ + switch (bits ^ (bits & (bits-1))) { +#define LOG2(n) case 1u << n: return slot*BB_PER_SLOT_BITS + i*32 + n + LOG2(0); LOG2(1); LOG2(2); LOG2(3); + LOG2(4); LOG2(5); LOG2(6); LOG2(7); + LOG2(8); LOG2(9); LOG2(10); LOG2(11); + LOG2(12); LOG2(13); LOG2(14); LOG2(15); + LOG2(16); LOG2(17); LOG2(18); LOG2(19); + LOG2(20); LOG2(21); LOG2(22); LOG2(23); + LOG2(24); LOG2(25); LOG2(26); LOG2(27); + LOG2(28); LOG2(29); LOG2(30); LOG2(31); + } + return -1; /* impossible... */ + } + } + + return -1; +} + +void *expand_item_list(item_list *lp, size_t item_size, + const char *desc, int incr) +{ + /* First time through, 0 <= 0, so list is expanded. */ + if (lp->malloced <= lp->count) { + void *new_ptr; + size_t new_size = lp->malloced; + if (incr < 0) + new_size -= incr; /* increase slowly */ + else if (new_size < (size_t)incr) + new_size += incr; + else + new_size *= 2; + new_ptr = realloc_array(lp->items, char, new_size * item_size); + if (verbose >= 4) { + rprintf(FINFO, "[%s] expand %s to %.0f bytes, did%s move\n", + who_am_i(), desc, (double)new_size * item_size, + new_ptr == lp->items ? " not" : ""); + } + if (!new_ptr) + out_of_memory("expand_item_list"); + + lp->items = new_ptr; + lp->malloced = new_size; + } + return (char*)lp->items + (lp->count++ * item_size); +}