Initial version of ext4-crypto-cp
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c2773d8
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+*~
+ext4-crypto-cp
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..a43a57b
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,15 @@
+CFLAGS=-g -O
+
+
+all: ext4-crypto-cp
+
+ext4-crypto-cp: ext4-crypto-cp.c
+ cc -static -m32 $(CFLAGS) -o $@ $<
+
+install-test-progs: ext4-crypto-cp
+ mount -t ext4 /dev/closure/test-4k /mnt
+ cp ext4-crypto-cp test-crypto-cp /mnt
+ umount /mnt
+
+clean:
+ rm ext4-crypto-cp
diff --git a/ext4-crypto-cp.c b/ext4-crypto-cp.c
new file mode 100644
index 0000000..7f0303b
--- /dev/null
+++ b/ext4-crypto-cp.c
@@ -0,0 +1,450 @@
+/*
+ * ext4-crypto-cp-md.c
+ *
+ * Test program to test the new crypto metadata backup/restore ioctl's
+ *
+ */
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <limits.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <linux/fs.h>
+
+typedef unsigned long u32;
+typedef signed long s32;
+
+struct ext4_encrypted_metadata {
+ s32 fd; /* Only used by EXT4_IOC_SET_ENCRYPTED_FILENAME */
+ u32 len;
+ unsigned char *data;
+};
+
+#ifndef EXT4_IOC_GET_ENCRYPTION_METADATA
+#define EXT4_IOC_GET_ENCRYPTION_METADATA _IOWR('f', 22, struct ext4_encrypted_metadata)
+#endif
+#ifndef EXT4_IOC_SET_ENCRYPTION_METADATA
+#define EXT4_IOC_SET_ENCRYPTION_METADATA _IOR('f', 23, struct ext4_encrypted_metadata)
+#endif
+#ifndef EXT4_IOC_GET_ENCRYPTED_FILENAME
+#define EXT4_IOC_GET_ENCRYPTED_FILENAME _IOWR('f', 24, struct ext4_encrypted_metadata)
+#endif
+#ifndef EXT4_IOC_SET_ENCRYPTED_FILENAME
+#define EXT4_IOC_SET_ENCRYPTED_FILENAME _IOR('f', 25, struct ext4_encrypted_metadata)
+#endif
+#ifndef EXT4_ENCRYPT_FL
+#define EXT4_ENCRYPT_FL 0x00000800 /* encrypted file */
+#endif
+
+#define DEFAULT_BLOCK_SIZE 4096
+#define BUFFER_SIZE 32768
+#define MDATA_BUFSIZE 512
+
+/* Assumes b is a power of two */
+#define ROUND_UP(a, b) (((a) + (b) - 1) & ~((b) - 1))
+
+const char *progname;
+
+void print_mdata(const char *s, struct ext4_encrypted_metadata *mdata)
+{
+ int i;
+
+ printf("%s len %lu: \n", s, mdata->len);
+ for (i = 0; i < mdata->len; i++)
+ printf("%02x ", mdata->data[i] & 0xFF);
+ printf("\n");
+}
+
+static void copy_data(int src, int dest)
+{
+ off_t start_data, start_hole, cur = 0;
+ off_t len, full_len, actual;
+ void *ptr, *buf;
+
+ printf("Copying data...\n");
+ if (posix_memalign(&buf, BUFFER_SIZE, DEFAULT_BLOCK_SIZE) != 0) {
+ printf("posix_memalign failed!\n");
+ exit(1);
+ }
+
+ while (1) {
+ start_data = lseek(src, cur, SEEK_DATA);
+ if (start_data == (off_t) -1) {
+ if (errno == ENXIO)
+ break;
+ perror("SEEK_DATA");
+ break;
+ }
+ start_hole = lseek(src, start_data, SEEK_HOLE);
+ if (start_hole == (off_t) -1) {
+ perror("SEEK_DATA");
+ break;
+ }
+ len = start_hole - start_data;
+ full_len = ROUND_UP(len, DEFAULT_BLOCK_SIZE);
+ printf("start_data %d start_hole %d len %d full len %d\n",
+ (int) start_data, (int) start_hole, (int) len,
+ (int) full_len);
+ if (len > BUFFER_SIZE)
+ len = BUFFER_SIZE;
+ if (lseek(src, start_data, SEEK_SET) == (off_t) -1) {
+ perror("SEEK_SET src");
+ break;
+ }
+ for (actual = 0, ptr = buf; actual < len;) {
+ int n;
+
+ printf("doing read(len %lu)\n", full_len - actual);
+ n = read(src, ptr, full_len - actual);
+ if (n < 0) {
+ if ((errno == EAGAIN) || (errno == EINTR))
+ continue;
+ perror("read");
+ break;
+ }
+ actual += n;
+ }
+ if (lseek(dest, start_data, SEEK_SET) == (off_t) -1) {
+ perror("SEEK_SET dest");
+ break;
+ }
+ for (actual = 0, ptr = buf; actual < full_len;) {
+ int n;
+
+ printf("doing write(len %lu)\n", full_len - actual);
+ n = write(dest, ptr, full_len - actual);
+ if (n < 0) {
+ if ((errno == EAGAIN) || (errno == EINTR))
+ continue;
+ perror("write");
+ break;
+ }
+ actual += n;
+ }
+ cur = start_hole;
+ }
+}
+
+int is_directory(const char *file)
+{
+ struct stat st;
+
+ if (stat(file, &st) < 0)
+ return (errno == ENOENT) ? 0 : -1;
+ return S_ISDIR(st.st_mode) ? 1 : 0;
+}
+
+/*
+ * Return the absolute pathname of the parent directory. The last
+ * component in the pathname does not need to exist. This function
+ * requires the POSIX.1-2008 behavior of realpath().
+ */
+char *get_parent(const char *file)
+{
+ char *cp, *tmp, *ret;
+
+ ret = realpath(file, NULL);
+ if (ret) {
+ cp = strrchr(ret, '/');
+ if (cp && cp != ret)
+ *cp = '\0';
+ return ret;
+ }
+ if (!ret && errno != ENOENT)
+ return NULL;
+
+ cp = strrchr(file, '/');
+ if (!cp)
+ return realpath(".", NULL);
+
+ tmp = strdup(file);
+ if (!tmp)
+ return NULL;
+
+ cp = strrchr(tmp, '/');
+ if (!cp)
+ abort(); /* Should never happen */
+ if (cp == tmp)
+ cp++;
+ *cp = 0;
+
+ ret = realpath(tmp, NULL);
+ free(tmp);
+ return ret;
+}
+
+#if 0
+int is_parent_encrypted(const char *file)
+{
+ int ret = -1;
+ char *parent;
+
+ parent = get_parent(file);
+ if (parent)
+ ret = is_encrypted(parent);
+ else
+ errno = ENOMEM;
+ free(parent);
+ return ret;
+}
+#endif
+
+int is_encrypted(const char *file)
+{
+ int fd, ret, f;
+
+ fd = open(file, O_RDONLY);
+ if (fd < 0)
+ return fd;
+ ret = ioctl(fd, FS_IOC_GETFLAGS, &f);
+ if (ret >= 0)
+ ret = (f & EXT4_ENCRYPT_FL) ? 1 : 0;
+ close(fd);
+ return ret;
+}
+
+char *get_cwd(void)
+{
+ char *ret;
+ int len = PATH_MAX;
+
+retry:
+ ret = malloc(len);
+ errno = ENOMEM;
+ if (!ret)
+ return NULL;
+ getcwd(ret, len);
+ if (!ret && (errno = ERANGE)) {
+ len = len << 1;
+ goto retry;
+ }
+ return ret;
+}
+
+/*
+ * Return the first parent directory that is unencrypted. With any
+ * luck this should be in the same file system as the given file,
+ * since we don't allow for encrypted root directories (yet).
+ */
+char *get_unencrypted_topdir(const char *file)
+{
+ char *parent = NULL, *cp;
+
+ if (strchr(file, '/')) {
+ parent = malloc(strlen(file) + 1);
+ if (!parent) {
+ errno = ENOMEM;
+ return NULL;
+ }
+ strcpy(parent, file);
+ }
+ while (1) {
+ if (!parent || ((cp = strrchr(parent, '/')) == NULL)) {
+ free(parent);
+ parent = get_cwd();
+ cp = strrchr(parent, '/');
+ if (cp == NULL) {
+ fprintf(stderr,
+ "get_cwd returned %s w/o slash?!?\n",
+ parent);
+ abort();
+ }
+ }
+ if (cp == parent)
+ return parent;
+ if (!is_encrypted(parent))
+ break;
+ *cp = '\0';
+ }
+ return parent;
+}
+
+#define GET_FD_INFO_DIRECTORY 0x0001
+#define GET_FD_INFO_ENCRYPTED 0x0002
+#define GET_FD_INFO_PARENT_ENCRYPTED 0x0004
+
+/*
+ * Open the requested file, doing the appropriate error checking
+ */
+static int get_fd(const char *filename, int open_flags, int perms,
+ int *ret_info)
+{
+ int ret, isdir, fd, f = 0, info = 0;
+ char *parent;
+
+ isdir = is_directory(filename);
+ if (isdir) {
+ info = GET_FD_INFO_DIRECTORY;
+ open_flags = O_DIRECTORY;
+ }
+ fd = open(filename, open_flags, perms);
+ if (fd < 0) {
+ perror("filename");
+ exit(1);
+ }
+ ret = ioctl(fd, FS_IOC_GETFLAGS, &f);
+ if (ret >= 0 && (f & EXT4_ENCRYPT_FL))
+ info |= GET_FD_INFO_ENCRYPTED;
+ parent = get_parent(filename);
+ if (is_encrypted(parent))
+ info |= GET_FD_INFO_PARENT_ENCRYPTED;
+ free(parent);
+ if (ret_info)
+ *ret_info = info;
+ return fd;
+}
+
+static void usage(void)
+{
+ fprintf(stderr, "Usage: %s source [destination] [destdir]\n",
+ progname);
+ exit(1);
+}
+
+int main(int argc, char **argv)
+{
+ struct ext4_encrypted_metadata f_mdata, fn_mdata;
+ unsigned char f_mdata_buf[MDATA_BUFSIZE];
+ unsigned char fn_mdata_buf[MDATA_BUFSIZE];
+ char *topdir, *tmpfn;
+ int s_fd, d_fd = -1, tmp_fd = -1;
+ int s_info, d_info, f;
+ progname = argv[0];
+
+#if 0
+ char *parent, *topdir;
+ parent = get_parent(argv[1]);
+ if (parent)
+ printf("parent: %s\n", parent);
+ else
+ perror(argv[1]);
+ topdir = get_unencrypted_topdir(argv[1]);
+ if (topdir)
+ printf("topdir: %s\n", topdir);
+ else
+ perror(argv[1]);
+ s_fd = get_fd(argv[1], O_RDONLY, 0, &s_info);
+ printf("get_fd %d %d\n", s_fd, s_info);
+ exit(0);
+#endif
+
+ if (argc < 2 || argc > 4) {
+ usage();
+ }
+ s_fd = get_fd(argv[1], O_RDONLY | O_DIRECT, 0, &s_info);
+ if (!(s_info & GET_FD_INFO_ENCRYPTED)) {
+ fprintf(stderr, "source file/directory %s not encrypted!\n",
+ argv[1]);
+ exit(1);
+ }
+ f_mdata.len = MDATA_BUFSIZE;
+ f_mdata.fd = -1;
+ f_mdata.data = f_mdata_buf;
+ if (ioctl(s_fd, EXT4_IOC_GET_ENCRYPTION_METADATA, &f_mdata)) {
+ perror("EXT4_IOC_GET_ENCRYPTION_METADATA");
+ exit(1);
+ }
+ print_mdata("source file", &f_mdata);
+
+ if (s_info & GET_FD_INFO_PARENT_ENCRYPTED) {
+ fn_mdata.len = MDATA_BUFSIZE;
+ fn_mdata.fd = -1;
+ fn_mdata.data = fn_mdata_buf;
+ if (ioctl(s_fd, EXT4_IOC_GET_ENCRYPTED_FILENAME, &fn_mdata)) {
+ perror("EXT4_IOC_GET_ENCRYPTED_FILENAME");
+ exit(1);
+ }
+ print_mdata("source filename", &fn_mdata);
+ } else
+ fn_mdata.len = 0;
+ if (argc == 2)
+ exit(0);
+
+ d_fd = get_fd(argv[2],
+ O_CREAT | O_TRUNC | O_DIRECT | O_WRONLY | O_DIRECT,
+ 0644, &d_info);
+ if ((d_info & GET_FD_INFO_DIRECTORY) == 0) {
+ if (s_info & GET_FD_INFO_DIRECTORY) {
+ fprintf(stderr, "Source is a directory, destination "
+ "must also be a directory\n");
+ exit(1);
+ }
+ copy_data(s_fd, d_fd);
+ copy_encryption_metadata:
+ if (ioctl(d_fd, EXT4_IOC_SET_ENCRYPTION_METADATA, &f_mdata)) {
+ perror("EXT4_IOC_SET_ENCRYPTION_METADATA");
+ exit(1);
+ }
+ exit(0);
+ }
+ /* destination is a directory */
+ if (s_info & GET_FD_INFO_DIRECTORY) {
+ /* source is a directory */
+ if (fn_mdata.len == 0)
+ /*
+ * source's parent is not encrypted, so copy
+ * encryption metadata to destination directory
+ */
+ goto copy_encryption_metadata;
+ /*
+ * source's parent is encrypted -- this means source's
+ * filename is encrypted
+ */
+ if (d_info & GET_FD_INFO_PARENT_ENCRYPTED) {
+ fprintf(stderr, "Destination directory is not "
+ "encrypted but source's filename is "
+ "encrypted\n");
+ exit(1);
+ }
+ if (ioctl(d_fd, EXT4_IOC_SET_ENCRYPTED_FILENAME, &fn_mdata)) {
+ perror("EXT4_IOC_SET_ENCRYPTED_FILENAME");
+ exit(1);
+ }
+ exit(0);
+ }
+ /* source is a regular file */
+ topdir = get_unencrypted_topdir(argv[1]);
+ tmpfn = malloc(strlen(topdir)+12);
+ if (!tmpfn) {
+ fprintf(stderr, "malloc failed for tmpfn!\n");
+ exit(1);
+ }
+ sprintf(tmpfn, "%s/tmp.XXXXXX", topdir);
+ tmp_fd = mkstemp(tmpfn);
+ if (tmp_fd < 0) {
+ perror("mkstemp");
+ exit(1);
+ }
+ f = fcntl(tmp_fd, F_GETFL);
+ if (f == -1) {
+ perror("fcntl F_GETFL");
+ exit(1);
+ }
+ f = fcntl(tmp_fd, F_SETFL, f | O_DIRECT);
+ if (f == -1) {
+ perror("fcntl F_SETFL");
+ exit(1);
+ }
+ copy_data(s_fd, tmp_fd);
+ if (ioctl(tmp_fd, EXT4_IOC_SET_ENCRYPTION_METADATA, &f_mdata)) {
+ perror("EXT4_IOC_SET_ENCRYPTION_METADATA");
+ exit(1);
+ }
+ fn_mdata.fd = tmp_fd;
+ if (ioctl(d_fd, EXT4_IOC_SET_ENCRYPTED_FILENAME, &fn_mdata)) {
+ perror("EXT4_IOC_SET_ENCRYPTED_FILENAME");
+ }
+ if (unlink(tmpfn) < 0) {
+ perror("unlink");
+ exit(1);
+ }
+ close(tmp_fd);
+ return 0;
+}
diff --git a/test-crypto-cp b/test-crypto-cp
new file mode 100755
index 0000000..4eb7035
--- /dev/null
+++ b/test-crypto-cp
@@ -0,0 +1,56 @@
+#!/bin/bash -vx
+#
+# Sample shell script which demonstrates how to use ext4-crypto-cp
+# program.
+#
+# This shows the example of copying an top-level encrypted directory
+# whose filename is not encryped (because it is located in an
+# unencrypted directory); copying an encrypted regular file in an
+# encrypted directory to an encrypted directory; and creating a copy
+# of an encrypted directory with an encrypted filename.
+#
+# To do a recursive copy, one must first copy all of the encrypted
+# directories before populating them, since we can only create empty
+# encrypted directories.
+
+umount /vdc
+dmesg -n 7
+mke2fs -Fq -t ext4 -O encrypt /dev/vdc
+debugfs -w -R "ssv encrypt_pw_salt deadbeef-dead-beef-1234-5678deadbeef" /dev/vdc
+mount -t ext4 /dev/vdc /vdc
+mkdir /vdc/a
+echo foobar | e4crypt add_key /vdc/a
+cat << EOF > /vdc/a/test_file
+Lorem ipsum dolor sit amet, consectetur adipiscing elit. In accumsan
+mi ac magna vestibulum commodo. Cras facilisis posuere tellus in
+efficitur. Sed mollis mi eget elit vulputate pellentesque. Ut vitae
+laoreet diam. Aliquam sem leo, luctus eget leo eu, hendrerit egestas
+risus. Nulla non nisi ut nisl suscipit dictum. Donec eleifend dapibus
+mi eu porttitor. Nulla lacinia tellus nec porttitor tincidunt. Nam
+lectus nibh, fringilla sit amet enim id, consequat tincidunt
+mauris. Ut blandit orci vitae elit suscipit varius. Donec vel sem
+tristique, efficitur felis sit amet, sagittis metus. In laoreet
+ultricies interdum. Aliquam felis est, pharetra eget nisl vel,
+fringilla aliquet velit. Etiam ut augue ut ante fringilla gravida quis
+a arcu.
+EOF
+file_inum=$(stat --format=%i /vdc/a/test_file)
+mkdir /vdc/a/test_dir
+dir_inum=$(stat --format=%i /vdc/a/test_dir)
+umount /vdc
+keyctl purge logon
+mount -t ext4 -o ciphertext_access /dev/vdc /vdc
+F=$(find /vdc/a -inum $file_inum -print)
+D=$(find /vdc/a -inum $dir_inum -print)
+mkdir /vdc/b
+/vdb/ext4-crypto-cp /vdc/a /vdc/b
+/vdb/ext4-crypto-cp $F /vdc/b
+/vdb/ext4-crypto-cp $D /vdc/b
+
+echo foobar | e4crypt add_key
+md5sum /vdc/b/test_file /vdc/a/test_file
+umount /vdc
+e2fsck -fn /dev/vdc
+keyctl purge logon
+
+exit 0