git » libjio » commit 7776533

Implement journal relocation

author Alberto Bertogli
2004-11-26 05:21:26 UTC
committer Alberto Bertogli
2007-07-15 13:47:10 UTC
parent 99bb7f5c9dd91863ea7241c06efc44fff7de4b8f

Implement journal relocation

This patch implements jmove_journal(), which is used to relocate the journal
directory. It takes a struct jfs and the new path, and does the change. It's
not atomic, so you have to take care of calling this without any other
operation touching the file. Because I expect most people to call this right
after jopen(), it's not much of a problem (you have to be really careful if
you relocate somewhere else).

It also updates the checker, the python bindings and the preloader library.

Finally, it breaks the API and ABI by adding a new field to struct jfs, and
changing jfsck() and jfsck_cleanup(). It sucks, but I rather do it now.

Signed-off-by: Alberto Bertogli <albertito@gmail.com>

bindings/preload/libjio_preload.c +1 -1
bindings/python/libjio.c +41 -12
check.c +40 -17
common.c +3 -19
common.h +2 -1
jiofsck.c +35 -27
libjio.h +4 -2
trans.c +72 -2

diff --git a/bindings/preload/libjio_preload.c b/bindings/preload/libjio_preload.c
index 5ff5f0d..6d201ec 100644
--- a/bindings/preload/libjio_preload.c
+++ b/bindings/preload/libjio_preload.c
@@ -380,7 +380,7 @@ int unlink(const char *pathname)
 	printd("libjio\n");
 
 	rec_inc();
-	jfsck_cleanup(pathname);
+	jfsck_cleanup(pathname, NULL);
 	rec_dec();
 
 	r = (*c_unlink)(pathname);
diff --git a/bindings/python/libjio.c b/bindings/python/libjio.c
index 103f293..51d7745 100644
--- a/bindings/python/libjio.c
+++ b/bindings/python/libjio.c
@@ -283,6 +283,32 @@ static PyObject *jf_jsync(jfileobject *fp, PyObject *args)
 	return PyLong_FromLong(rv);
 }
 
+/* jmove_journal */
+PyDoc_STRVAR(jf_jmove_journal__doc,
+"jmove_journal(newpath)\n\
+\n\
+Moves the journal directory to the new path; note that there MUST NOT BE\n\
+anything else operating on the file.\n\
+It's a wrapper to jmove_journal().\n");
+
+static PyObject *jf_jmove_journal(jfileobject *fp, PyObject *args)
+{
+	long rv;
+	char *newpath;
+
+	if (!PyArg_ParseTuple(args, "s:jmove_journal", &newpath))
+		return NULL;
+
+	Py_BEGIN_ALLOW_THREADS
+	rv = jmove_journal(fp->fs, newpath);
+	Py_END_ALLOW_THREADS
+
+	if (rv != 0)
+		return PyErr_SetFromErrno(PyExc_IOError);
+
+	return PyLong_FromLong(rv);
+}
+
 /* new_trans */
 PyDoc_STRVAR(jf_new_trans__doc,
 "new_trans()\n\
@@ -326,6 +352,7 @@ static PyMethodDef jfile_methods[] = {
 	{ "truncate", (PyCFunction)jf_truncate, METH_VARARGS, jf_truncate__doc },
 	{ "lseek", (PyCFunction)jf_lseek, METH_VARARGS, jf_lseek__doc },
 	{ "jsync", (PyCFunction)jf_jsync, METH_VARARGS, jf_jsync__doc },
+	{ "jmove_journal", (PyCFunction)jf_jmove_journal, METH_VARARGS, jf_jmove_journal__doc },
 	{ "new_trans", (PyCFunction)jf_new_trans, METH_VARARGS, jf_new_trans__doc },
 	{ NULL }
 };
@@ -514,21 +541,22 @@ static PyObject *jf_open(PyObject *self, PyObject *args)
 
 /* jfsck */
 PyDoc_STRVAR(jf_jfsck__doc,
-"jfsck(name)\n\
+"jfsck(name[, jdir])\n\
 \n\
-Checks the integrity of the file with the given name; returns a dictionary\n\
-with all the different values of the check (equivalent to the 'struct\n\
-jfsck_result'), or None if there was nothing to check.\n\
+Checks the integrity of the file with the given name, using (optionally) jdir\n\
+as the journal directory; returns a dictionary with all the different values\n\
+of the check (equivalent to the 'struct jfsck_result'), or None if there was\n\
+nothing to check.\n\
 It's a wrapper to jfsck().\n");
 
 static PyObject *jf_jfsck(PyObject *self, PyObject *args)
 {
 	int rv;
-	char *name;
+	char *name, *jdir;
 	struct jfsck_result res;
 	PyObject *dict;
 
-	if (!PyArg_ParseTuple(args, "s:jfsck", &name))
+	if (!PyArg_ParseTuple(args, "s|s:jfsck", &name, &jdir))
 		return NULL;
 
 	dict = PyDict_New();
@@ -536,7 +564,7 @@ static PyObject *jf_jfsck(PyObject *self, PyObject *args)
 		return PyErr_NoMemory();
 
 	Py_BEGIN_ALLOW_THREADS
-	rv = jfsck(name, &res);
+	rv = jfsck(name, jdir, &res);
 	Py_END_ALLOW_THREADS
 
 	if (rv == J_ENOMEM) {
@@ -558,21 +586,22 @@ static PyObject *jf_jfsck(PyObject *self, PyObject *args)
 
 /* jfsck_cleanup */
 PyDoc_STRVAR(jf_jfsck_cleanup__doc,
-"jfsck_cleanup()\n\
+"jfsck_cleanup(name[, jdir])\n\
 \n\
-Clean the journal directory and leave it ready to use.\n\
+Clean the journal directory for the given file using (optionally) jdir as the\n\
+journal directory, and leave it ready to use.\n\
 It's a wrapper to jfsck_cleanup().\n");
 
 static PyObject *jf_jfsck_cleanup(PyObject *self, PyObject *args)
 {
 	long rv;
-	char *name;
+	char *name, *jdir;
 
-	if (!PyArg_ParseTuple(args, "s:jfsck_cleanup", &name))
+	if (!PyArg_ParseTuple(args, "s|s:jfsck_cleanup", &name, &jdir))
 		return NULL;
 
 	Py_BEGIN_ALLOW_THREADS
-	rv = jfsck_cleanup(name);
+	rv = jfsck_cleanup(name, jdir);
 	Py_END_ALLOW_THREADS
 
 	return PyInt_FromLong(rv);
diff --git a/check.c b/check.c
index a7933e6..95dca6a 100644
--- a/check.c
+++ b/check.c
@@ -93,12 +93,12 @@ error:
 }
 
 /* check the journal and rollback incomplete transactions */
-int jfsck(const char *name, struct jfsck_result *res)
+int jfsck(const char *name, const char *jdir, struct jfsck_result *res)
 {
 	int tfd, rv, i, ret;
 	unsigned int maxtid;
 	uint32_t csum1, csum2;
-	char jdir[PATH_MAX], jlockfile[PATH_MAX], tname[PATH_MAX];
+	char jlockfile[PATH_MAX], tname[PATH_MAX];
 	struct stat sinfo;
 	struct jfs fs;
 	struct jtrans *curts;
@@ -113,6 +113,7 @@ int jfsck(const char *name, struct jfsck_result *res)
 	dir = NULL;
 	fs.fd = -1;
 	fs.jfd = -1;
+	fs.jdir = NULL;
 	fs.jdirfd = -1;
 	fs.jmap = MAP_FAILED;
 	map = NULL;
@@ -134,17 +135,33 @@ int jfsck(const char *name, struct jfsck_result *res)
 
 	fs.name = (char *) name;
 
-	if (!get_jdir(name, jdir)) {
-		ret = J_ENOMEM;
-		goto exit;
+	if (jdir == NULL) {
+		fs.jdir = (char *) malloc(PATH_MAX);
+		if (fs.jdir == NULL) {
+			ret = J_ENOMEM;
+			goto exit;
+		}
+
+		if (!get_jdir(name, fs.jdir)) {
+			ret = J_ENOMEM;
+			goto exit;
+		}
+	} else {
+		fs.jdir = (char *) malloc(strlen(jdir) + 1);
+		if (fs.jdir == NULL) {
+			ret = J_ENOMEM;
+			goto exit;
+		}
+		strcpy(fs.jdir, jdir);
 	}
-	rv = lstat(jdir, &sinfo);
+
+	rv = lstat(fs.jdir, &sinfo);
 	if (rv < 0 || !S_ISDIR(sinfo.st_mode)) {
 		ret = J_ENOJOURNAL;
 		goto exit;
 	}
 
-	fs.jdirfd = open(jdir, O_RDONLY);
+	fs.jdirfd = open(fs.jdir, O_RDONLY);
 	if (fs.jdirfd < 0) {
 		ret = J_ENOJOURNAL;
 		goto exit;
@@ -152,7 +169,7 @@ int jfsck(const char *name, struct jfsck_result *res)
 
 	/* open the lock file, which is only used to complete the jfs
 	 * structure */
-	snprintf(jlockfile, PATH_MAX, "%s/%s", jdir, "lock");
+	snprintf(jlockfile, PATH_MAX, "%s/%s", fs.jdir, "lock");
 	rv = open(jlockfile, O_RDWR | O_CREAT, 0600);
 	if (rv < 0) {
 		ret = J_ENOJOURNAL;
@@ -167,7 +184,7 @@ int jfsck(const char *name, struct jfsck_result *res)
 		goto exit;
 	}
 
-	dir = opendir(jdir);
+	dir = opendir(fs.jdir);
 	if (dir == NULL) {
 		ret = J_ENOJOURNAL;
 		goto exit;
@@ -210,7 +227,7 @@ int jfsck(const char *name, struct jfsck_result *res)
 		 * really looping in order (recovering transaction in a
 		 * different order as they were applied means instant
 		 * corruption) */
-		if (!get_jtfile(name, i, tname)) {
+		if (!get_jtfile(&fs, i, tname)) {
 			ret = J_ENOMEM;
 			goto exit;
 		}
@@ -290,6 +307,8 @@ exit:
 		close(fs.jfd);
 	if (fs.jdirfd >= 0)
 		close(fs.jdirfd);
+	if (fs.jdir)
+		free(fs.jdir);
 	if (dir != NULL)
 		closedir(dir);
 	if (fs.jmap != MAP_FAILED)
@@ -300,16 +319,20 @@ exit:
 }
 
 /* remove all the files in the journal directory (if any) */
-int jfsck_cleanup(const char *name)
+int jfsck_cleanup(const char *name, const char *jdir)
 {
-	char jdir[PATH_MAX], tfile[PATH_MAX*3];
+	char path[PATH_MAX], tfile[PATH_MAX*3];
 	DIR *dir;
 	struct dirent *dent;
 
-	if (!get_jdir(name, jdir))
-		return 0;
+	if (jdir == NULL) {
+		if (!get_jdir(name, path))
+			return 0;
+	} else {
+		strcpy(path, jdir);
+	}
 
-	dir = opendir(jdir);
+	dir = opendir(path);
 	if (dir == NULL && errno == ENOENT)
 		/* it doesn't exist, so it's clean */
 		return 1;
@@ -324,7 +347,7 @@ int jfsck_cleanup(const char *name)
 
 		/* build the full path to the transaction file */
 		memset(tfile, 0, PATH_MAX * 3);
-		strcat(tfile, jdir);
+		strcat(tfile, path);
 		strcat(tfile, "/");
 		strcat(tfile, dent->d_name);
 
@@ -339,7 +362,7 @@ int jfsck_cleanup(const char *name)
 	}
 	closedir(dir);
 
-	rmdir(jdir);
+	rmdir(path);
 
 	return 1;
 }
diff --git a/common.c b/common.c
index a2e9bf0..739b5e4 100644
--- a/common.c
+++ b/common.c
@@ -15,6 +15,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 
+#include "libjio.h"
 #include "common.h"
 
 
@@ -126,26 +127,9 @@ int get_jdir(const char *filename, char *jdir)
 }
 
 /* build the filename of a given transaction */
-int get_jtfile(const char *filename, unsigned int tid, char *jtfile)
+int get_jtfile(struct jfs *fs, unsigned int tid, char *jtfile)
 {
-	char *base, *baset;
-	char *dir, *dirt;
-
-	baset = strdup(filename);
-	if (baset == NULL)
-		return 0;
-	base = basename(baset);
-
-	dirt = strdup(filename);
-	if (dirt == NULL)
-		return 0;
-	dir = dirname(dirt);
-
-	snprintf(jtfile, PATH_MAX, "%s/.%s.jio/%u", dir, base, tid);
-
-	free(baset);
-	free(dirt);
-
+	snprintf(jtfile, PATH_MAX, "%s/%u", fs->jdir, tid);
 	return 1;
 }
 
diff --git a/common.h b/common.h
index 586ceb5..76a3e3a 100644
--- a/common.h
+++ b/common.h
@@ -12,6 +12,7 @@
 #include <sys/types.h>	/* for ssize_t and off_t */
 #include <stdint.h>	/* for uint*_t */
 
+#include "libjio.h"	/* for struct jfs */
 
 #define _F_READ		0x00001
 #define _F_WRITE	0x00010
@@ -30,7 +31,7 @@ off_t plockf(int fd, int cmd, off_t offset, off_t len);
 ssize_t spread(int fd, void *buf, size_t count, off_t offset);
 ssize_t spwrite(int fd, const void *buf, size_t count, off_t offset);
 int get_jdir(const char *filename, char *jdir);
-int get_jtfile(const char *filename, unsigned int tid, char *jtfile);
+int get_jtfile(struct jfs *fs, unsigned int tid, char *jtfile);
 
 int checksum(int fd, size_t len, uint32_t *csum);
 uint32_t checksum_map(uint8_t *map, size_t count);
diff --git a/jiofsck.c b/jiofsck.c
index db3f6ba..d2cf7e4 100644
--- a/jiofsck.c
+++ b/jiofsck.c
@@ -1,5 +1,5 @@
 
-/* 
+/*
  * jiofsck - A journal checker and recovery tool for libjio
  * Alberto Bertogli (albertogli@telpin.com.ar)
  */
@@ -11,42 +11,52 @@
 
 void usage()
 {
-	printf("Use: jiofsck [clean] FILE\n\n");
-	printf("Where \"FILE\" is the name of the file "
-			"which you want to check the journal from,\n"
-			"and the optional parameter \"clean\" makes "
-			"jiofsck to clean up the journal after\n"
-			"recovery.\n");
+	printf("\
+Use: jiofsck [clean=1] [dir=DIR] FILE\n\
+\n\
+Where \"FILE\" is the name of the file you want to check the journal from,\n\
+and the optional parameter \"clean\" makes jiofsck to clean up the journal\n\
+after recovery.\n\
+The parameter \"dir=DIR\", also optional, is used to indicate the position\n\
+of the journal directory.\n\
+\n\
+Examples:\n\
+# jiofsck file\n\
+# jiofsck clean=1 file\n\
+# jiofsck dir=/tmp/journal file\n\
+# jiofsck clean=1 dir=/tmp/journal file\n\
+\n");
 }
 
 int main(int argc, char **argv)
 {
-	int rv, do_cleanup;
-	char *file;
+	int i, rv, do_cleanup;
+	char *file, *jdir;
 	struct jfsck_result res;
-	
-	if (argc != 2 && argc != 3) {
+
+	file = jdir = NULL;
+	do_cleanup = 0;
+
+	if (argc < 2) {
 		usage();
 		return 1;
 	}
 
-	if (argc == 3) {
-		if (strcmp("clean", argv[1]) != 0 ) {
-			usage();
-			return 1;
+	for (i = 1; i < argc; i++) {
+		if (strcmp("clean=1", argv[i]) == 0) {
+			do_cleanup = 1;
+		} else if (strncmp("dir=", argv[i], 4) == 0) {
+			jdir = argv[i] + 4;
+		} else {
+			file = argv[i];
 		}
-		file = argv[2];
-		do_cleanup = 1;
-	} else {
-		file = argv[1];
-		do_cleanup = 0;
 	}
 
 	memset(&res, 0, sizeof(res));
-	
+
 	printf("Checking journal: ");
 	fflush(stdout);
-	rv = jfsck(file, &res);
+	rv = jfsck(file, jdir, &res);
 
 	if (rv == J_ENOENT) {
 		printf("No such file or directory\n");
@@ -62,7 +72,7 @@ int main(int argc, char **argv)
 	if (do_cleanup) {
 		printf("Cleaning journal: ");
 		fflush(stdout);
-		if (!jfsck_cleanup(file)) {
+		if (!jfsck_cleanup(file, jdir)) {
 			printf("Error cleaning journal\n");
 			return 1;
 		}
@@ -76,14 +86,12 @@ int main(int argc, char **argv)
 	printf("Total:\t\t %d\n", res.total);
 	printf("Invalid:\t %d\n", res.invalid);
 	printf("In progress:\t %d\n", res.in_progress);
-	printf("Broken head:\t %d\n", res.broken_head);
-	printf("Broken body:\t %d\n", res.broken_body);
-	printf("Load error:\t %d\n", res.load_error);
+	printf("Broken:\t\t %d\n", res.broken);
 	printf("Corrupt:\t %d\n", res.corrupt);
 	printf("Apply error:\t %d\n", res.apply_error);
 	printf("Reapplied:\t %d\n", res.reapplied);
 	printf("\n");
-	
+
 	if (!do_cleanup) {
 		printf("You can now safely remove the journal directory "
 				"completely\nto start a new journal.\n");
diff --git a/libjio.h b/libjio.h
index 7c24796..32519d5 100644
--- a/libjio.h
+++ b/libjio.h
@@ -34,6 +34,7 @@ extern "C" {
 struct jfs {
 	int fd;			/* main file descriptor */
 	char *name;		/* and its name */
+	char *jdir;		/* journal directory */
 	int jdirfd;		/* journal directory file descriptor */
 	int jfd;		/* journal's lock file descriptor */
 	unsigned int *jmap;	/* journal's lock file mmap area */
@@ -110,12 +111,13 @@ ssize_t jtrans_commit(struct jtrans *ts);
 ssize_t jtrans_rollback(struct jtrans *ts);
 void jtrans_free(struct jtrans *ts);
 int jsync(struct jfs *fs);
+int jmove_journal(struct jfs *fs, const char *newpath);
 int jclose(struct jfs *fs);
 
 
 /* journal checker */
-int jfsck(const char *name, struct jfsck_result *res);
-int jfsck_cleanup(const char *name);
+int jfsck(const char *name, const char *jdir, struct jfsck_result *res);
+int jfsck_cleanup(const char *name, const char *jdir);
 
 /* UNIX API wrappers */
 ssize_t jread(struct jfs *fs, void *buf, size_t count);
diff --git a/trans.c b/trans.c
index bb1daff..7ee2978 100644
--- a/trans.c
+++ b/trans.c
@@ -71,7 +71,7 @@ static void free_tid(struct jfs *fs, unsigned int tid)
 			/* this can fail if we're low on mem, but we don't
 			 * care checking here because the problem will come
 			 * out later and we can fail more properly */
-			get_jtfile(fs->name, i, name);
+			get_jtfile(fs, i, name);
 			if (access(name, R_OK | W_OK) == 0) {
 				curid = i;
 				break;
@@ -221,7 +221,7 @@ ssize_t jtrans_commit(struct jtrans *ts)
 		goto exit;
 
 	/* open the transaction file */
-	if (!get_jtfile(ts->fs->name, id, name))
+	if (!get_jtfile(ts->fs, id, name))
 		goto exit;
 	fd = open(name, O_RDWR | O_CREAT | O_TRUNC, 0600);
 	if (fd < 0)
@@ -533,6 +533,7 @@ int jopen(struct jfs *fs, const char *name, int flags, int mode, int jflags)
 
 	fs->fd = -1;
 	fs->jfd = -1;
+	fs->jdir = NULL;
 	fs->jdirfd = -1;
 	fs->jmap = MAP_FAILED;
 
@@ -588,6 +589,11 @@ int jopen(struct jfs *fs, const char *name, int flags, int mode, int jflags)
 	if (rv < 0 || !S_ISDIR(sinfo.st_mode))
 		goto error_exit;
 
+	fs->jdir = (char *) malloc(strlen(jdir) + 1);
+	if (fs->jdir == NULL)
+		goto error_exit;
+	strcpy(fs->jdir, jdir);
+
 	/* open the directory, we will use it to flush transaction files'
 	 * metadata in jtrans_commit() */
 	fs->jdirfd = open(jdir, O_RDONLY);
@@ -660,6 +666,68 @@ int jsync(struct jfs *fs)
 	return 0;
 }
 
+/* change the location of the journal directory */
+int jmove_journal(struct jfs *fs, const char *newpath)
+{
+	int ret;
+	char *oldpath, jlockfile[PATH_MAX];
+
+	/* we try to be sure that all lingering transactions have been
+	 * applied, so when we try to remove the journal directory, only the
+	 * lockfile is there; however, we do this just to be nice, but the
+	 * caller must be sure there are no in-flight transactions or any
+	 * other kind of operation around when he calls this function */
+	jsync(fs);
+
+	oldpath = fs->jdir;
+
+	fs->jdir = (char *) malloc(strlen(newpath + 1));
+	if (fs->jdir == NULL)
+		return -1;
+	strcpy(fs->jdir, newpath);
+
+	ret = rename(oldpath, newpath);
+	if (ret == -1 && (errno == ENOTEMPTY || errno == EEXIST) ) {
+		/* rename() failed, the dest. directory is not empty, so we
+		 * have to reload everything */
+
+		close(fs->jdirfd);
+		fs->jdirfd = open(newpath, O_RDONLY);
+		if (fs->jdirfd < 0) {
+			ret = -1;
+			goto exit;
+		}
+
+		close(fs->jfd);
+		snprintf(jlockfile, PATH_MAX, "%s/%s", newpath, "lock");
+		fs->jfd = open(jlockfile, O_RDWR | O_CREAT, 0600);
+		if (fs->jfd < 0)
+			goto exit;
+
+		munmap(fs->jmap, sizeof(unsigned int));
+		fs->jmap = (unsigned int *) mmap(NULL, sizeof(unsigned int),
+			PROT_READ | PROT_WRITE, MAP_SHARED, fs->jfd, 0);
+		if (fs->jmap == MAP_FAILED)
+			goto exit;
+
+		/* remove the journal directory, if possible */
+		snprintf(jlockfile, PATH_MAX, "%s/%s", oldpath, "lock");
+		unlink(jlockfile);
+		ret = rmdir(oldpath);
+		if (ret == -1) {
+			/* we couldn't remove it, something went wrong
+			 * (possible it had some files left) */
+			goto exit;
+		}
+
+		ret = 0;
+	}
+
+exit:
+	free(oldpath);
+	return ret;
+}
+
 /* close a file */
 int jclose(struct jfs *fs)
 {
@@ -683,6 +751,8 @@ int jclose(struct jfs *fs)
 	if (fs->name)
 		/* allocated by strdup() in jopen() */
 		free(fs->name);
+	if (fs->jdir)
+		free(fs->jdir);
 	pthread_mutex_destroy(&(fs->lock));
 
 	return ret;