/* merge a directory, moving files when possible and overwrite/copy when not.
* Do not overwrite files in $APK_LBUDIRS but install files as .apk-new
*
* Copyright (c) 2008 Natanael Copa <natanael.copa@gmail.com>
*
* Distributed under GPL-2
*/
/*
lbu_filter() {
# Ok... I give up. shell is too slow. lets do it in awk.
awk 'BEGIN {
APK_LBUDIRS="'"$APK_LBUDIRS"'";
numdirs = split(APK_LBUDIRS, lbudir, ":");
#precalc lengths to save a few cpu cycles in loop
for (i = 1; i <= numdirs; i++)
len[i] = length(lbudir[i]);
}
# main loop
{
for (i = 1; i <= numdirs; i++) {
if (index($0, lbudir[i]) == 1 && (len[i] == length() || substr($0, len[i] + 1, 1) == "/")) {
print $0;
}
}
}'
}
# merge a temp directory to $ROOT
pkgf_merge() {
local f src dest destdir
ls -a "$1" | while read f ; do
# skip . and ..
if [ "$f" = '.' ] || [ "$f" = '..' ] ; then
continue
fi
# remove leading ./
src="`beautify_path \"${1#./}/$f\"`"
destdir="$ROOT/$1"
dest="$destdir/$f"
# check if the entry exist on dest
if ! [ -e "$ROOT/$src" ] ; then
mv "$src" "$destdir/"
continue
fi
# parse directories recursively
if [ -d "$src" ] && ! [ -L "$src" ] ; then
pkgf_merge "$src"
else
if is_lbu_file "$src" && ! cmp -s "$src" "$dest" 2>/dev/null ; then
# keep existing file and install as apk-new
mv "$src" "$dest.apk-new"
else
# replace it
# try with busybox mv if we fail to handle
# coreutils installs and busybox upgrades
[ -L "$dest" ] && [ -d "$( readlink $dest )" ] && rm -f "$dest"
mv "$1/$f" "$destdir/" 2>/dev/null \
|| /bin/busybox mv "$1/$f" "$destdir/" \
|| "$1/busybox" mv "$1/$f" "$destdir/"
fi
fi
done
}
*/
#include <sys/stat.h>
#include <sys/types.h>
#include <dirent.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sysexits.h>
#include <unistd.h>
static char *lbudirs;
void usage(const char *prog) {
printf("usage: %s SOURCEDIR DESTDIR\n", prog);
exit(1);
}
void *xmalloc(size_t size)
{
void *buf = malloc(size);
if (buf == NULL)
err(EX_OSERR, "malloc");
return buf;
}
void *xstrdup(const char *s)
{
char *buf = strdup(s);
if (buf == NULL)
err(EX_OSERR, "strdup");
return buf;
}
char *init_lbudirs(void)
{
char *apk_lbudirs = getenv("APK_LBUDIRS");
char *s, *d;
if (apk_lbudirs == NULL) {
lbudirs = xstrdup("etc\0");
return lbudirs;
}
lbudirs = xmalloc(strlen(apk_lbudirs) + 1);
s = apk_lbudirs;
d = lbudirs;
while (*s) {
*d = (*s == ':') ? '\0': *s;
d++;
s++;
}
*d = '\0';
return lbudirs;
}
int is_lbudir(const char *path)
{
char *p = lbudirs;
if (path == NULL || *path == '\0')
return 0;
while (*p) {
int len;
/* strip leading '/' */
while (*p == '/')
p++;
len = strlen(p);
if (strstr(path, p) == path) {
const char *e = path + len;
if (*e == '\0' || *e == '/')
return 1;
}
p += len+1;
}
return 0;
}
int copyfd(int sfd, int dfd, off_t size)
{
long count = 0, n;
while (count < size) {
n = splice(sfd, NULL, dfd, NULL, size - count, 0);
if (n < 1)
return -1;
count += n;
}
return count;
}
int copy_file(const char *src, const char *dest, struct stat *st)
{
int srcfd, destfd;
srcfd = open(src, O_RDONLY);
if (srcfd < 0)
err(EX_OSERR, src);
unlink(dest);
destfd = creat(dest, st->st_mode);
if (destfd < 0)
err(EX_OSERR, dest);
if (copyfd(srcfd, destfd, st->st_size) < 0) {
unlink(dest);
err(EX_OSERR, dest);
}
close(srcfd);
close(destfd);
return 0;
}
int is_equal(const char *a, const char *b, off_t size)
{
#define BUF_SIZE 4096
int fd1, fd2, ret = 0;
char *buf1, *buf2;
off_t count = 0;
if ((fd1 = open(a, O_RDONLY)) < 0)
return 0;
if ((fd2 = open(b, O_RDONLY)) < 0) {
close(fd1);
return 0;
}
buf1 = xmalloc(BUF_SIZE * 2);
buf2 = &buf1[BUF_SIZE];
while (count < size) {
int n1 = read(fd1, buf1, BUF_SIZE);
int n2;
if (n1 < 1)
goto close_and_return;
n2 = read(fd2, buf2, BUF_SIZE);
if (n2 < 1 || n1 != n2)
goto close_and_return;
if (memcmp(buf1, buf2, n1) != 0)
goto close_and_return;
count += n1;
}
ret++;
close_and_return:
free(buf1);
close(fd1);
close(fd2);
return ret;
#undef BUF_SIZE
}
void merge_recursive(const char *destdir, const char *current, int protected)
{
DIR *dir = opendir(".");
struct dirent *de;
char *destpath, *dest;
int len = 0;
int bufsize;
while (current && *current && *current == '/')
current++;
if (!protected)
protected = current && is_lbudir(current);
if (dir == NULL)
err(EX_OSERR, ".");
// printf("DEBUG: merging %s to %s, protected=%i\n", current, destdir, protected);
destpath = xmalloc(PATH_MAX);
destpath[PATH_MAX - 1] = '\0';
snprintf(destpath, PATH_MAX-1, "%s/%s/", destdir, current ? current : "");
len = strlen(destpath);
/* remove multiple trailing '/' */
while (len > 1 && destpath[len-1] == '/' && destpath[len-2] == '/')
destpath[len-- -1] = '\0';
dest = destpath + len;
bufsize = PATH_MAX - 1 - len;
while ((de = readdir(dir)) != NULL) {
struct stat src_st, dest_st;
int exists = 0;
/* skip . and .. */
if (strcmp(de->d_name, ".") == 0
|| strcmp(de->d_name, "..") == 0)
continue;
strncpy(dest, de->d_name, bufsize);
if (lstat(destpath, &dest_st) < 0) {
if (errno == ENOENT) {
/* try move the file/dir */
if (rename(de->d_name, destpath) == 0) {
//printf("DEBUG: moved %s\n", destpath);
continue;
}
} else
err(EX_OSERR, "lstat");
} else {
exists++;
}
/* dest exists or we failed to move */
if (lstat(de->d_name, &src_st) < 0)
err(EX_OSERR, "lstat");
if (S_ISDIR(src_st.st_mode)) {
char *newcurr;
if (mkdir(destpath, src_st.st_mode) < 0
&& errno != EEXIST)
err(EX_OSERR, destpath);
if (asprintf(&newcurr, "%s/%s", current ? current : "",
de->d_name) < 0)
err(EX_OSERR, "asprintf");
chdir(de->d_name);
merge_recursive(destdir, newcurr, protected);
chdir("..");
rmdir(de->d_name);
free(newcurr);
continue;
}
if (!exists) {
copy_file(de->d_name, destpath, &src_st);
continue;
}
if (protected) {
if (src_st.st_size == dest_st.st_size
&& is_equal(de->d_name, destpath,
src_st.st_size)) {
unlink(de->d_name);
continue;
}
strncat(dest, ".apk-new", len);
} else {
if (unlink(destpath) < 0)
err(EX_OSERR, destpath);
}
if (rename(de->d_name, destpath) == 0)
continue;
/* try to copy the file, as last resort */
copy_file(de->d_name, destpath, &src_st);
}
}
int main(int argc, char *argv[])
{
const char *src, *dest;
if (argc != 3)
usage(argv[0]);
src = argv[1];
dest = argv[2];
init_lbudirs();
if (chdir(src) < 0)
err(EX_OSERR, "chdir");
merge_recursive(dest, NULL, 0);
return 0;
}