Copying extended attributes

Extended attributes allow you to provide extra metadata for a file.

It's effectively a key-value store using a string for the key, but values can be arbitrary blobs.

flistxattr(2) to know which xattrs exist. fgetxattr(2) to read any xattrs. fsetxattr(2) to write an xattr to a file.

Regular users can set xattrs beginning user., and as far as Linux is concerned that's arbitrary data that it doesn't need to care what they are.

However there are attributes outside the user. namespace which have special meaning to Linux, so we shouldn't try to copy everything as it is.

static int realloc_double(void **buf, size_t *size) {
    size_t new_size = *size * 2;
    void *new_buf = realloc(*buf, new_size);
    if (new_buf == NULL && new_size != 0)
        return -1;
    *buf = new_buf;
    *size = new_size;
    return 0;
}

static int xattr_list(int fd, char **names, size_t *size) {
    ssize_t ret;

    if (*names == NULL && *size == 0) {
        ret = TEMP_FAILURE_RETRY(flistxattr(fd, NULL, 0));
        if (ret < 0)
            goto error;
        *size = ret;

        *names = malloc(*size);
        if (*names == NULL) {
            ret = -1;
            goto error;
        }
    }

    for (;;) {
        ret = TEMP_FAILURE_RETRY(flistxattr(fd, *names, *size));
        if (ret >= 0) {
            *size = ret;
            break;
        }

        if (errno != ERANGE)
            goto error;

        /* New xattr added since first flistxattr */
        ret = realloc_double((void**)names, size);
        if (ret < 0)
            goto error;
    }

    ret = 0;
error:
    return ret;
}

static int xattr_get(int fd, const char *name, void **value, size_t *size) {
    ssize_t ret;

    if (*value == NULL && *size == 0) {
        ret = TEMP_FAILURE_RETRY(fgetxattr(fd, name, NULL, 0));
        if (ret < 0)
            goto error;
        *size = ret;

        *value = malloc(*size);
        if (*value == NULL) {
            ret = -1;
            goto error;
        }
    }

    for (;;) {
        ret = TEMP_FAILURE_RETRY(flistxattr(fd, *value, *size));
        if (ret >= 0) {
            *size = ret;
            break;
        }

        if (errno != ERANGE)
            goto error;

        /* xattr grew since first getxattr */
        ret = realloc_double(value, size);
        if (ret < 0)
            goto error;
    }

    ret = 0;
error:
    return ret;
}

static int str_starts_with(const char *s1, const char *s2) {
    return strncmp(s1, s2, strlen(s2)) == 0;
}

static int copy_xattrs(int srcfd, int tgtfd) {
    ssize_t ret;
    char *names = NULL;
    void *value = NULL;
    size_t names_size = 0, value_size = 0;

    ret = xattr_list(srcfd, &names, &names_size);
    if (ret < 0)
        goto cleanup;

    for (char *name = names; name < names + names_size;
         name = strchrnul(name, '\0') + 1) {
        /* Skip xattrs that need special handling */
        if (!str_starts_with(name, "user.")) {
            continue;
        }

        ret = xattr_get(srcfd, name, &value, &value_size);
        if (ret < 0)
            goto cleanup;

        ret = TEMP_FAILURE_RETRY(fsetxattr(tgtfd, name, value, value_size, 0));
        if (ret < 0)
            goto cleanup;
    }

cleanup:
    free(names);
    free(value);
    return ret;
}

POSIX ACLs

Feature from a POSIX design specification that wasn't widely adopted, but Linux supports the draft specification.

Not used much outside of NFS or SAMBA. You would be forgiven for not knowing they exist.

How they work is beyond the scope of this article, but man7.org covers some details about what it does and lwn.net covers some limitations.

For this article we're not concerned with how to use POSIX ACLs, it's relevant to us because they work by storing data in a special attribute, so if we want to preserve this we need to copy system.posix_acl_access.

Since this attribute doesn't start with "user." we need root privileges to copy it faithfully.

int copy_posix_acls(int srcfd, int tgtfd) {
    static const char name[] = "system.posix_acl_access";
    int ret = 0;
    void *value = NULL;
    size_t size = 0;

    ret = xattr_get(srcfd, name, &value, &size);
    if (ret < 0) {
        if (errno == ENODATA)
            ret = 0;
        goto cleanup;
    }

    ret = TEMP_FAILURE_RETRY(fsetxattr(tgtfd, name, value, size, 0));
    if (ret < 0) {
        goto cleanup;
    }

cleanup:
    free(value);
    return ret;
}

As with previous articles, the full version of the my-mv.c source file and the Makefile may be downloaded.

Surely that's the last of the metadata!

I mentioned some extended attributes have special meaning to Linux. POSIX ACLs can be sanely copied, but some attributes have tricky semantics that require special handling.