/*
Copyright (C) Andrew Tridgell 1996
Copyright (C) Paul Mackerras 1996
Copyright (C) 2001, 2002 by Martin Pool <mbp@samba.org>
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
(at your option) any later version.
This program 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 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.
*/
/** @file flist.c
* Generate and receive file lists
*
* @todo Get rid of the string_area optimization. Efficiently
* allocating blocks is the responsibility of the system's malloc
* library, not of rsync.
*
* @sa http://lists.samba.org/pipermail/rsync/2000-June/002351.html
*
**/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <dirent.h>
#include <math.h>
#include <XSUB.h>
#include "rsync.h"
extern struct stats stats;
static char empty_sum[MD4_SUM_LENGTH];
static unsigned int file_struct_len;
void init_flist(void)
{
struct file_struct f;
/* Figure out how big the file_struct is without trailing padding */
file_struct_len = offsetof(struct file_struct, flags) + sizeof f.flags;
}
static int to_wire_mode(mode_t mode)
{
if (S_ISLNK(mode) && (_S_IFLNK != 0120000)) {
return (mode & ~(_S_IFMT)) | 0120000;
}
return (int) mode;
}
static mode_t from_wire_mode(int mode)
{
if ((mode & (_S_IFMT)) == 0120000 && (_S_IFLNK != 0120000)) {
return (mode & ~(_S_IFMT)) | _S_IFLNK;
}
return (mode_t) mode;
}
/* we need this function because of the silly way in which duplicate
entries are handled in the file lists - we can't change this
without breaking existing versions */
static int flist_up(struct file_list *flist, int i)
{
while (!flist->files[i]->basename) i++;
return i;
}
/**
* Make sure @p flist is big enough to hold at least @p flist->count
* entries.
**/
void flist_expand(struct file_list *flist)
{
struct file_struct **new_ptr;
if (flist->count < flist->malloced)
return;
if (flist->malloced < FLIST_START)
flist->malloced = FLIST_START;
else if (flist->malloced >= FLIST_LINEAR)
flist->malloced += FLIST_LINEAR;
else
flist->malloced *= 2;
/*
* In case count jumped or we are starting the list
* with a known size just set it.
*/
if (flist->malloced < flist->count)
flist->malloced = flist->count;
new_ptr = realloc_array(flist->files, struct file_struct *,
flist->malloced);
flist->files = new_ptr;
if (!flist->files)
out_of_memory("flist_expand");
}
static void readfd(struct file_list *f, unsigned char *buffer, size_t N)
{
if ( f->inError || f->inPosn + N > f->inLen ) {
memset(buffer, 0, N);
f->inError = 1;
return;
}
memcpy(buffer, f->inBuf + f->inPosn, N);
f->inPosn += N;
}
int32 read_int(struct file_list *f)
{
unsigned char b[4];
int32 ret;
readfd(f,b,4);
ret = b[0] | b[1] << 8 | b[2] << 16 | b[3] << 24;
if (ret == (int32)0xffffffff) return -1;
return ret;
}
int64 read_longint(struct file_list *f)
{
int32 ret = read_int(f);
double d;
if (ret != (int32)0xffffffff) {
return ret;
}
d = (uint32)read_int(f);
d += ((uint32)read_int(f)) * 65536.0 * 65536.0;
return d;
}
void read_buf(struct file_list *f,char *buf,size_t len)
{
readfd(f, buf, len);
}
void read_sbuf(struct file_list *f,char *buf,size_t len)
{
read_buf(f,buf,len);
buf[len] = 0;
}
unsigned char read_byte(struct file_list *f)
{
unsigned char c;
read_buf (f, (char *)&c, 1);
return c;
}
void receive_file_entry(struct file_list *f, struct file_struct **fptr,
unsigned short flags)
{
/*
* originally static variables to maintain state; now in file_list.
*/
time_t modtime = f->modtime;
mode_t mode = f->mode;
uint64 dev = f->dev;
dev_t rdev = f->rdev;
uint32 rdev_major = f->rdev_major;
uid_t uid = f->uid;
gid_t gid = f->gid;
char lastname[MAXPATHLEN];
char *lastdir = f->lastdir;
int lastdir_depth = f->lastdir_depth;
int lastdir_len = f->lastdir_len;
/*
* original auto variables
*/
char thisname[MAXPATHLEN];
unsigned int l1 = 0, l2 = 0;
int alloc_len, basename_len, dirname_len, linkname_len, sum_len;
OFF_T file_length;
char *basename, *dirname, *bp;
struct file_struct *file;
if (!fptr) {
f->modtime = 0; f->mode = 0;
f->dev = 0; f->rdev = makedev(0, 0);
f->rdev_major = 0;
f->uid = 0; f->gid = 0;
*f->lastname = '\0';
f->lastdir_len = -1;
return;
}
if (flags & XMIT_SAME_NAME)
l1 = read_byte(f);
if (flags & XMIT_LONG_NAME)
l2 = read_int(f);
else
l2 = read_byte(f);
if (l2 >= MAXPATHLEN - l1) {
fprintf(stderr, "overflow: flags=0x%x l1=%d l2=%d, lastname=%s\n",
flags, l1, l2, f->lastname);
f->fatalError = 1;
return;
}
strlcpy(thisname, f->lastname, l1 + 1);
read_sbuf(f, &thisname[l1], l2);
thisname[l1 + l2] = 0;
strlcpy(lastname, thisname, MAXPATHLEN);
clean_fname(thisname, 0);
if (f->sanitize_paths) {
sanitize_path(thisname, thisname, "", 0);
}
if ((basename = strrchr(thisname, '/')) != NULL) {
dirname_len = ++basename - thisname; /* counts future '\0' */
if (lastdir_len == dirname_len - 1
&& strncmp(thisname, lastdir, lastdir_len) == 0) {
dirname = lastdir;
dirname_len = 0; /* indicates no copy is needed */
} else
dirname = thisname;
} else {
basename = thisname;
dirname = NULL;
dirname_len = 0;
}
basename_len = strlen(basename) + 1; /* count the '\0' */
file_length = read_longint(f);
if (!(flags & XMIT_SAME_TIME))
modtime = (time_t)read_int(f);
if (!(flags & XMIT_SAME_MODE))
mode = from_wire_mode(read_int(f));
if (f->preserve_uid && !(flags & XMIT_SAME_UID))
uid = (uid_t)read_int(f);
if (f->preserve_gid && !(flags & XMIT_SAME_GID))
gid = (gid_t)read_int(f);
if (f->preserve_devices) {
if (f->protocol_version < 28) {
if (IS_DEVICE(mode)) {
if (!(flags & XMIT_SAME_RDEV_pre28))
rdev = (dev_t)read_int(f);
} else
rdev = makedev(0, 0);
} else if (IS_DEVICE(mode)) {
uint32 rdev_minor;
if (!(flags & XMIT_SAME_RDEV_MAJOR))
rdev_major = read_int(f);
if (flags & XMIT_RDEV_MINOR_IS_SMALL)
rdev_minor = read_byte(f);
else
rdev_minor = read_int(f);
rdev = makedev(rdev_major, rdev_minor);
}
}
if (f->preserve_links && S_ISLNK(mode)) {
linkname_len = read_int(f) + 1; /* count the '\0' */
if (linkname_len <= 0 || linkname_len > MAXPATHLEN) {
fprintf(stderr, "overflow on symlink: linkname_len=%d\n",
linkname_len - 1);
f->fatalError = 1;
return;
}
} else {
linkname_len = 0;
}
sum_len = f->always_checksum && S_ISREG(mode) ? MD4_SUM_LENGTH : 0;
alloc_len = file_struct_len + dirname_len + basename_len
+ linkname_len + sum_len;
/*
* make sure we have enough input data left to complete the file
* structure.
*
* TODO
*/
bp = pool_alloc(f->file_pool, alloc_len, "receive_file_entry");
file = *fptr = (struct file_struct *)bp;
memset(bp, 0, file_struct_len);
bp += file_struct_len;
file->flags = flags & XMIT_TOP_DIR ? FLAG_TOP_DIR : 0;
file->modtime = modtime;
file->length = file_length;
file->mode = mode;
file->uid = uid;
file->gid = gid;
if (dirname_len) {
file->dirname = lastdir = bp;
lastdir_len = dirname_len - 1;
memcpy(bp, dirname, dirname_len - 1);
bp += dirname_len;
bp[-1] = '\0';
if (f->sanitize_paths)
lastdir_depth = count_dir_elements(lastdir);
} else if (dirname) {
file->dirname = dirname;
}
file->basename = bp;
memcpy(bp, basename, basename_len);
bp += basename_len;
if (f->preserve_devices && IS_DEVICE(mode))
file->u.rdev = rdev;
if (linkname_len) {
file->u.link = bp;
read_sbuf(f, bp, linkname_len - 1);
if (f->sanitize_paths)
sanitize_path(bp, bp, "", lastdir_depth);
bp += linkname_len;
}
if (f->preserve_hard_links && f->protocol_version < 28 && S_ISREG(mode))
flags |= XMIT_HAS_IDEV_DATA;
if (flags & XMIT_HAS_IDEV_DATA) {
uint64 inode;
if (f->protocol_version < 26) {
dev = read_int(f);
inode = read_int(f);
} else {
if (!(flags & XMIT_SAME_DEV))
dev = read_longint(f);
inode = read_longint(f);
}
if (f->idev_pool) {
file->link_u.idev = pool_talloc(f->idev_pool,
struct idev, 1, "inode_table");
file->F_INODE = inode;
file->F_DEV = dev;
}
/*
fprintf(stderr, "Got inode %d, dev %d, idev = %p, idev_pool = %p\n",
(int32)inode, (int32)dev, file->link_u.idev, f->idev_pool);
*/
}
if (f->always_checksum) {
char *sum;
if (sum_len) {
file->u.sum = sum = bp;
/*bp += sum_len;*/
} else if (f->protocol_version < 28) {
/* Prior to 28, we get a useless set of nulls. */
sum = empty_sum;
} else
sum = NULL;
if (sum) {
read_buf(f, sum, f->protocol_version < 21 ? 2 : MD4_SUM_LENGTH);
}
}
/*
* It's important that we don't update anything in f before
* this point. If we ran out of input bytes then we need to
* resume after the caller appends more bytes.
*/
if ( f->inError ) {
pool_free(f->file_pool, alloc_len, bp);
return;
}
f->modtime = modtime;
f->mode = mode;
f->dev = dev;
f->rdev = rdev;
f->rdev_major = rdev_major;
f->uid = uid;
f->gid = gid;
strlcpy(f->lastname, lastname, MAXPATHLEN);
f->lastname[MAXPATHLEN - 1] = 0;
if ( lastdir )
f->lastdir = lastdir;
f->lastdir_depth = lastdir_depth;
f->lastdir_len = lastdir_len;
/* fprintf(stderr, "Got name thisname %s\n", thisname); */
}
static void writefd(struct file_list *f, char *buf, size_t len)
{
if ( !f->outBuf ) {
f->outLen = 32768 + len;
f->outBuf = malloc(f->outLen);
} else if ( f->outPosn + len > f->outLen ) {
f->outLen = 32768 + f->outPosn + len;
f->outBuf = realloc(f->outBuf, f->outLen);
}
memcpy(f->outBuf + f->outPosn, buf, len);
f->outPosn += len;
}
void write_int(struct file_list *f,int32 x)
{
char b[4];
b[0] = x >> 0;
b[1] = x >> 8;
b[2] = x >> 16;
b[3] = x >> 24;
writefd(f,b,4);
}
/*
* Note: int64 may actually be a 32-bit type if ./configure couldn't find any
* 64-bit types on this platform.
*/
void write_longint(struct file_list *f, int64 x)
{
char b[8];
if (x <= 0x7FFFFFFF) {
write_int(f, (int)x);
return;
}
#ifdef INT64_IS_OFF_T
if (sizeof (int64) < 8) {
fprintf(stderr, "write_longint: Integer overflow: attempted 64-bit offset\n");
}
#endif
write_int(f, (int32)0xFFFFFFFF);
SIVAL(b,0,(x&0xFFFFFFFF));
SIVAL(b,4,((x>>32)&0xFFFFFFFF));
writefd(f,b,8);
}
void write_buf(struct file_list *f,char *buf,size_t len)
{
writefd(f,buf,len);
}
/* write a string to the connection */
void write_sbuf(struct file_list *f,char *buf)
{
write_buf(f, buf, strlen(buf));
}
void write_byte(struct file_list *f,unsigned char c)
{
write_buf(f,(char *)&c,1);
}
void send_file_entry(struct file_list *f, struct file_struct *file,
unsigned short base_flags)
{
time_t modtime = f->modtime;
mode_t mode = f->mode;
uint64 dev = f->dev;
dev_t rdev = f->rdev;
uint32 rdev_major = f->rdev_major;
uid_t uid = f->uid;
gid_t gid = f->gid;
char lastname[MAXPATHLEN];
unsigned short flags;
char fname[MAXPATHLEN];
int l1, l2;
strlcpy(lastname, f->lastname, MAXPATHLEN);
if (!file) {
write_byte(f, 0);
f->modtime = 0; f->mode = 0;
f->dev = 0; f->rdev = makedev(0, 0);
f->rdev_major = 0;
f->uid = 0; f->gid = 0;
*f->lastname = '\0';
return;
}
f_name_to(file, fname);
flags = base_flags;
if (file->mode == mode)
flags |= XMIT_SAME_MODE;
else
mode = file->mode;
if (f->preserve_devices) {
if (f->protocol_version < 28) {
if (IS_DEVICE(mode)) {
if (file->u.rdev == rdev)
flags |= XMIT_SAME_RDEV_pre28;
else
rdev = file->u.rdev;
} else
rdev = makedev(0, 0);
} else if (IS_DEVICE(mode)) {
rdev = file->u.rdev;
if ((uint32)major(rdev) == rdev_major)
flags |= XMIT_SAME_RDEV_MAJOR;
else
rdev_major = major(rdev);
if ((uint32)minor(rdev) <= 0xFFu)
flags |= XMIT_RDEV_MINOR_IS_SMALL;
}
}
if (file->uid == uid)
flags |= XMIT_SAME_UID;
else
uid = file->uid;
if (file->gid == gid)
flags |= XMIT_SAME_GID;
else
gid = file->gid;
if (file->modtime == modtime)
flags |= XMIT_SAME_TIME;
else
modtime = file->modtime;
if (file->link_u.idev) {
if (file->F_DEV == dev) {
if (f->protocol_version >= 28)
flags |= XMIT_SAME_DEV;
} else
dev = file->F_DEV;
flags |= XMIT_HAS_IDEV_DATA;
}
for (l1 = 0;
lastname[l1] && (fname[l1] == lastname[l1]) && (l1 < 255);
l1++) {}
l2 = strlen(fname+l1);
if (l1 > 0)
flags |= XMIT_SAME_NAME;
if (l2 > 255)
flags |= XMIT_LONG_NAME;
/* We must make sure we don't send a zero flag byte or the
* other end will terminate the flist transfer. Note that
* the use of XMIT_TOP_DIR on a non-dir has no meaning, so
* it's harmless way to add a bit to the first flag byte. */
if (f->protocol_version >= 28) {
if (!flags && !S_ISDIR(mode))
flags |= XMIT_TOP_DIR;
if ((flags & 0xFF00) || !flags) {
flags |= XMIT_EXTENDED_FLAGS;
write_byte(f, flags);
write_byte(f, flags >> 8);
} else
write_byte(f, flags);
} else {
if (!(flags & 0xFF) && !S_ISDIR(mode))
flags |= XMIT_TOP_DIR;
if (!(flags & 0xFF))
flags |= XMIT_LONG_NAME;
write_byte(f, flags);
}
if (flags & XMIT_SAME_NAME)
write_byte(f, l1);
if (flags & XMIT_LONG_NAME)
write_int(f, l2);
else
write_byte(f, l2);
write_buf(f, fname + l1, l2);
write_longint(f, file->length);
if (!(flags & XMIT_SAME_TIME))
write_int(f, modtime);
if (!(flags & XMIT_SAME_MODE))
write_int(f, to_wire_mode(mode));
if (f->preserve_uid && !(flags & XMIT_SAME_UID)) {
/*
TODO
if (!numeric_ids)
add_uid(uid);
*/
write_int(f, uid);
}
if (f->preserve_gid && !(flags & XMIT_SAME_GID)) {
/*
TODO
if (!numeric_ids)
add_gid(gid);
*/
write_int(f, gid);
}
if (f->preserve_devices && IS_DEVICE(mode)) {
if (f->protocol_version < 28) {
if (!(flags & XMIT_SAME_RDEV_pre28))
write_int(f, (int)rdev);
} else {
if (!(flags & XMIT_SAME_RDEV_MAJOR))
write_int(f, major(rdev));
if (flags & XMIT_RDEV_MINOR_IS_SMALL)
write_byte(f, minor(rdev));
else
write_int(f, minor(rdev));
}
}
if (f->preserve_links && S_ISLNK(mode)) {
int len = strlen(file->u.link);
write_int(f, len);
write_buf(f, file->u.link, len);
}
if (flags & XMIT_HAS_IDEV_DATA) {
if (f->protocol_version < 26) {
/* 32-bit dev_t and ino_t */
write_int(f, dev);
write_int(f, file->F_INODE);
} else {
/* 64-bit dev_t and ino_t */
if (!(flags & XMIT_SAME_DEV))
write_longint(f, dev);
write_longint(f, file->F_INODE);
/*
fprintf(stderr, "file %s sending dev 0x%x%x, inode 0x%x%x\n",
fname,
(unsigned int)(file->F_DEV >> 32),
(unsigned int)(file->F_DEV),
(unsigned int)(file->F_INODE >> 32),
(unsigned int)(file->F_INODE)
);
*/
}
}
if (f->always_checksum) {
char *sum;
if (S_ISREG(mode))
sum = file->u.sum;
else if (f->protocol_version < 28) {
/* Prior to 28, we sent a useless set of nulls. */
sum = empty_sum;
} else
sum = NULL;
if (sum) {
write_buf(f, sum,
f->protocol_version < 21 ? 2 : MD4_SUM_LENGTH);
}
}
f->modtime = modtime;
f->mode = mode;
f->dev = dev;
f->rdev = rdev;
f->rdev_major = rdev_major;
f->uid = uid;
f->gid = gid;
strlcpy(f->lastname, fname, MAXPATHLEN);
}
int flistDecodeBytes(struct file_list *f, unsigned char *bytes, uint32 nBytes)
{
unsigned short flags;
f->inBuf = bytes;
f->inLen = nBytes;
f->inFileStart = f->inPosn = 0;
f->inError = 0;
f->fatalError = 0;
f->decodeDone = 0;
for ( flags = read_byte(f); flags; flags = read_byte(f) ) {
int i = f->count;
flist_expand(f);
if (f->protocol_version >= 28 && (flags & XMIT_EXTENDED_FLAGS))
flags |= read_byte(f) << 8;
receive_file_entry(f, &f->files[i], flags);
if ( f->inError ) {
/*
fprintf(stderr, "Returning on input error, posn = %d\n",
f->inPosn);
*/
break;
}
#if 0
if (S_ISREG(f->files[i]->mode))
stats.total_size += f->files[i]->length;
#endif
f->count++;
f->inFileStart = f->inPosn;
}
if ( f->fatalError ) {
return -1;
} else if ( f->inError ) {
return f->inFileStart;
} else {
f->decodeDone = 1;
return f->inPosn;
}
}
/* Like strncpy but does not 0 fill the buffer and always null
* terminates. bufsize is the size of the destination buffer.
*
* Returns the index of the terminating byte. */
size_t strlcpy(char *d, const char *s, size_t bufsize)
{
size_t len = strlen(s);
size_t ret = len;
if (bufsize <= 0) return 0;
if (len >= bufsize) len = bufsize-1;
memcpy(d, s, len);
d[len] = 0;
return ret;
}
/* 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;
}
/*
* XXX: This is currently the hottest function while building the file
* list, because building f_name()s every time is expensive.
**/
int file_compare(struct file_struct **file1, struct file_struct **file2)
{
struct file_struct *f1 = *file1;
struct file_struct *f2 = *file2;
if (!f1->basename && !f2->basename)
return 0;
if (!f1->basename)
return -1;
if (!f2->basename)
return 1;
if (f1->dirname == f2->dirname)
return u_strcmp(f1->basename, f2->basename);
return f_name_cmp(f1, f2);
}
int flist_find(struct file_list *flist, struct file_struct *f)
{
int low = 0, high = flist->count - 1;
while (high >= 0 && !flist->files[high]->basename) high--;
if (high < 0)
return -1;
while (low != high) {
int mid = (low + high) / 2;
int ret = file_compare(&flist->files[flist_up(flist, mid)],&f);
if (ret == 0)
return flist_up(flist, mid);
if (ret > 0)
high = mid;
else
low = mid + 1;
}
if (file_compare(&flist->files[flist_up(flist, low)], &f) == 0)
return flist_up(flist, low);
return -1;
}
/*
* Free up any resources a file_struct has allocated
* and clear the file.
*/
void clear_file(int i, struct file_list *flist)
{
if (flist->idev_pool && flist->files[i]->link_u.idev)
pool_free(flist->idev_pool, 0, flist->files[i]->link_u.idev);
memset(flist->files[i], 0, file_struct_len);
}
struct file_list *flist_new(int with_hlink, char *msg, int preserve_hard_links)
{
struct file_list *flist;
init_flist();
flist = new(struct file_list);
if (!flist)
out_of_memory(msg);
memset(flist, 0, sizeof (struct file_list));
if (!(flist->file_pool = pool_create(FILE_EXTENT, 0,
out_of_memory, POOL_INTERN)))
out_of_memory(msg);
if (with_hlink && preserve_hard_links) {
if (!(flist->idev_pool = pool_create(HLINK_EXTENT,
sizeof (struct idev), out_of_memory, POOL_INTERN)))
out_of_memory(msg);
}
return flist;
}
/*
* free up all elements in a flist
*/
void flist_free(struct file_list *flist)
{
pool_destroy(flist->file_pool);
pool_destroy(flist->idev_pool);
pool_destroy(flist->hlink_pool);
free(flist->files);
if ( flist->hlink_list )
free(flist->hlink_list);
if ( flist->exclude_list.head )
clear_exclude_list(&flist->exclude_list);
free(flist);
}
/*
* This routine ensures we don't have any duplicate names in our file list.
* duplicate names can cause corruption because of the pipelining
*/
void clean_flist(struct file_list *flist, int strip_root, int no_dups)
{
int i, prev_i = 0;
if (!flist || flist->count == 0)
return;
qsort(flist->files, flist->count,
sizeof flist->files[0], (int (*)())file_compare);
for (i = no_dups? 0 : flist->count; i < flist->count; i++) {
if (flist->files[i]->basename) {
prev_i = i;
break;
}
}
while (++i < flist->count) {
if (!flist->files[i]->basename)
continue;
if (f_name_cmp(flist->files[i], flist->files[prev_i]) == 0) {
/*
fprintf(stderr, "removing duplicate name %s from file list %d\n",
f_name(flist->files[i]), i);
*/
/* Make sure that if we unduplicate '.', that we don't
* lose track of a user-specified starting point (or
* else deletions will mysteriously fail with -R). */
if (flist->files[i]->flags & FLAG_TOP_DIR)
flist->files[prev_i]->flags |= FLAG_TOP_DIR;
clear_file(i, flist);
} else
prev_i = i;
}
if (strip_root) {
/* we need to strip off the root directory in the case
of relative paths, but this must be done _after_
the sorting phase */
for (i = 0; i < flist->count; i++) {
if (flist->files[i]->dirname &&
flist->files[i]->dirname[0] == '/') {
memmove(&flist->files[i]->dirname[0],
&flist->files[i]->dirname[1],
strlen(flist->files[i]->dirname));
}
if (flist->files[i]->dirname &&
!flist->files[i]->dirname[0]) {
flist->files[i]->dirname = NULL;
}
}
}
}
enum fnc_state { fnc_DIR, fnc_SLASH, fnc_BASE };
/* Compare the names of two file_struct entities, just like strcmp()
* would do if it were operating on the joined strings. We assume
* that there are no 0-length strings.
*/
int f_name_cmp(struct file_struct *f1, struct file_struct *f2)
{
int dif;
const uchar *c1, *c2;
enum fnc_state state1, state2;
if (!f1 || !f1->basename) {
if (!f2 || !f2->basename)
return 0;
return -1;
}
if (!f2 || !f2->basename)
return 1;
if (!(c1 = (uchar*)f1->dirname)) {
state1 = fnc_BASE;
c1 = (uchar*)f1->basename;
} else if (!*c1) {
state1 = fnc_SLASH;
c1 = (uchar*)"/";
} else
state1 = fnc_DIR;
if (!(c2 = (uchar*)f2->dirname)) {
state2 = fnc_BASE;
c2 = (uchar*)f2->basename;
} else if (!*c2) {
state2 = fnc_SLASH;
c2 = (uchar*)"/";
} else
state2 = fnc_DIR;
while (1) {
if ((dif = (int)*c1 - (int)*c2) != 0)
break;
if (!*++c1) {
switch (state1) {
case fnc_DIR:
state1 = fnc_SLASH;
c1 = (uchar*)"/";
break;
case fnc_SLASH:
state1 = fnc_BASE;
c1 = (uchar*)f1->basename;
break;
case fnc_BASE:
break;
}
}
if (!*++c2) {
switch (state2) {
case fnc_DIR:
state2 = fnc_SLASH;
c2 = (uchar*)"/";
break;
case fnc_SLASH:
state2 = fnc_BASE;
c2 = (uchar*)f2->basename;
break;
case fnc_BASE:
if (!*c1)
return 0;
break;
}
}
}
return dif;
}
/* Return a copy of the full filename of a flist entry, using the indicated
* buffer. No size-checking is done because we checked the size when creating
* the file_struct entry.
*/
char *f_name_to(struct file_struct *f, char *fbuf)
{
if (!f || !f->basename)
return NULL;
if (f->dirname) {
int len = strlen(f->dirname);
memcpy(fbuf, f->dirname, len);
fbuf[len] = '/';
strcpy(fbuf + len + 1, f->basename);
} else
strcpy(fbuf, f->basename);
return fbuf;
}
/* Like f_name_to(), but we rotate through 5 static buffers of our own.
*/
char *f_name(struct file_struct *f)
{
static char names[5][MAXPATHLEN];
static unsigned int n;
n = (n + 1) % (sizeof names / sizeof names[0]);
return f_name_to(f, names[n]);
}
/* Turns multiple adjacent slashes into a single slash, gets rid of "./"
* elements (but not a trailing dot dir), removes a trailing slash, and
* optionally collapses ".." elements (except for those at the start of the
* string). If the resulting name would be empty, change it into a ".". */
unsigned int clean_fname(char *name, BOOL collapse_dot_dot)
{
char *limit = name - 1, *t = name, *f = name;
int anchored;
if (!name)
return 0;
if ((anchored = *f == '/') != 0)
*t++ = *f++;
while (*f) {
/* discard extra slashes */
if (*f == '/') {
f++;
continue;
}
if (*f == '.') {
/* discard "." dirs (but NOT a trailing '.'!) */
if (f[1] == '/') {
f += 2;
continue;
}
/* collapse ".." dirs */
if (collapse_dot_dot
&& f[1] == '.' && (f[2] == '/' || !f[2])) {
char *s = t - 1;
if (s == name && anchored) {
f += 2;
continue;
}
while (s > limit && *--s != '/') {}
if (s != t - 1 && (s < name || *s == '/')) {
t = s + 1;
f += 2;
continue;
}
limit = t + 2;
}
}
while (*f && (*t++ = *f++) != '/') {}
}
if (t > name+anchored && t[-1] == '/')
t--;
if (t == name)
*t++ = '.';
*t = '\0';
return t - name;
}
/* Make path appear as if a chroot had occurred. This handles a leading
* "/" (either removing it or expanding it) and any leading or embedded
* ".." components that attempt to escape past the module's top dir.
*
* If dest is NULL, a buffer is allocated to hold the result. It is legal
* to call with the dest and the path (p) pointing to the same buffer, but
* 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).
*
* If depth is > 0, it is a count of how many '..'s to allow at the start
* of the path.
*
* We also clean the path in a manner similar to clean_fname() but with a
* 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 *start, *sanp;
int rlen = 0;
if (dest != p) {
int plen = strlen(p);
if (*p == '/') {
if (!rootdir)
rootdir = "";
rlen = strlen(rootdir);
depth = 0;
p++;
}
if (dest) {
if (rlen + plen + 1 >= MAXPATHLEN)
return NULL;
} else if (!(dest = new_array(char, rlen + plen + 1)))
out_of_memory("sanitize_path");
if (rlen) {
memcpy(dest, rootdir, rlen);
if (rlen > 1)
dest[rlen++] = '/';
}
}
start = sanp = dest + rlen;
while (*p != '\0') {
/* discard leading or extra slashes */
if (*p == '/') {
p++;
continue;
}
/* this loop iterates once per filename component in p.
* both p (and sanp if the original had a slash) should
* always be left pointing after a slash
*/
if (*p == '.' && (p[1] == '/' || p[1] == '\0')) {
/* skip "." component */
p++;
continue;
}
if (*p == '.' && p[1] == '.' && (p[2] == '/' || p[2] == '\0')) {
/* ".." component followed by slash or end */
if (depth <= 0 || sanp != start) {
p += 2;
if (sanp != start) {
/* back up sanp one level */
--sanp; /* now pointing at slash */
while (sanp > start && sanp[-1] != '/') {
/* skip back up to slash */
sanp--;
}
}
continue;
}
/* allow depth levels of .. at the beginning */
depth--;
/* move the virtual beginning to leave the .. alone */
start = sanp + 3;
}
/* copy one component through next slash */
while (*p && (*sanp++ = *p++) != '/') {}
}
if (sanp == dest) {
/* ended up with nothing, so put in "." component */
*sanp++ = '.';
}
*sanp = '\0';
return dest;
}
void out_of_memory(char *str)
{
fprintf(stderr, "ERROR: File::RsyncP out of memory in %s\n", str);
exit(1);
}
int count_dir_elements(const char *p)
{
int cnt = 0, new_component = 1;
while (*p) {
if (*p++ == '/')
new_component = 1;
else if (new_component) {
new_component = 0;
cnt++;
}
}
return cnt;
}
#define MALLOC_MAX 0x40000000
void *_new_array(unsigned int size, unsigned long num)
{
if (num >= MALLOC_MAX/size)
return NULL;
return malloc(size * num);
}
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);
}
syntax highlighted by Code2HTML, v. 0.9.1