Jan-Philipp Litza

Switching backups: From fakeroot to -‌-fake-super 12. July 2013

I’m a big fan of incremental backups, and in my opinion one of the best ways to do such backups are hardlink-backups via rsync like snapback2 does them (even though the software has some flaws I currently run into, I’ll have to have a look at that).

However, if you do backups like this, you either need root on the backup machine or some other way to store permissions. When I first set up such backup infrastructures, I came up with the idea to use fakeroot, which is able to store the faked permissions in a file and read them from there again. However, this solution is bad in many aspects:

There are probably numerous other reasons why this was a bad idea in the first place, but it seemed reasonable back then.

Now, I learned about the rsync-option some would have known and used right from the start: --fake-super. This option tells rsync to not save the owner and permissions as the owner and permissions of the file, but instead to create an extended attribute (xattr) for the file (named user.rsync.%stat by the way) which contains all the information I might otherwise need root access to store. This attribute looks like

user.rsync.%stat="104755 0,0 131750:131750"

My goal was to convert our existing backups from fakeroot to --fake-super. The format isn’t that complicated: The first number is octal and contains the file type and permissions, the second tuple of digits is the device number represented as major and minor device number, and the last two are the user and group id. But sadly, stat(1) can only generate the first number as hex (with %f) or without the file type (with %a) but not everything in octal. Everything else isn’t a problem (%t,%T %u:%g). But because of this first digit, I decided to write a small C program that can be run in the fakeroot and sets the correct xattr derived from the current (faked) state of the file. Maybe this would have been possible by using rsync directly, but I wasn’t able to come up with a way that didn’t involve copying everything over again. So, here’s my little program:

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <alloca.h>
#include <errno.h>
#include <string.h>
#include <attr/attributes.h>

#define BUFSIZE 64

int main(int argc, char* argv[]) {
    if (argc < 2) {
            fprintf(stderr, "Error: No arguments\n");
            return 1;
    }
    struct stat a;
    char* xattr = alloca(BUFSIZE);
    int retval = 0, verbose = 0, i = 1;
    if (argc > 2 && !strcmp(argv[1], "-v")) {
            verbose = 1;
            i = 2;
    }
    for(; i < argc; i++) {
            if (lstat(argv[i], &a) == -1) {
                    fprintf(stderr, "Error: lstat() failed for %s: ", argv[i]);
                    perror(NULL);
                    retval |= 2;
                    continue;
            }
            snprintf(xattr, BUFSIZE, "%o %d,%d %d:%d", a.st_mode, major(a.st_rdev), minor(a.st_rdev), a.st_uid, a.st_gid);
            if (attr_set(argv[i], "rsync.%stat", xattr, strlen(xattr), ATTR_CREATE | ATTR_DONTFOLLOW) == -1) {
                    if (errno == EEXIST) {
                            if (verbose)
                                    fprintf(stderr, "%s: Info: xattr already exists\n", argv[i]);
                    } else {
                            fprintf(stderr, "%s: Error: attr_set() failed: ", argv[i]);
                            perror(NULL);
                    }
                    continue;
                    retval |= 4;
            }
    }
    return retval;
}

I don’t normally do C, so there may be a couple of design flaws I’m happy to learn about. The program would be called with find -exec set-rsync-xattr {} +.