Fri Nov 26 02:21:26 ART 2004  Alberto Bertogli (albertogli@telpin.com.ar)
  * 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.
  
diff -rN -u old-libjio/bindings/preload/libjio_preload.c new-libjio/bindings/preload/libjio_preload.c
--- old-libjio/bindings/preload/libjio_preload.c	2005-03-10 15:15:10.053870642 -0300
+++ new-libjio/bindings/preload/libjio_preload.c	2005-03-10 15:15:10.099865699 -0300
@@ -380,7 +380,7 @@
 	printd("libjio\n");
 
 	rec_inc();
-	jfsck_cleanup(pathname);
+	jfsck_cleanup(pathname, NULL);
 	rec_dec();
 
 	r = (*c_unlink)(pathname);
diff -rN -u old-libjio/bindings/python/libjio.c new-libjio/bindings/python/libjio.c
--- old-libjio/bindings/python/libjio.c	2005-03-10 15:15:10.054870535 -0300
+++ new-libjio/bindings/python/libjio.c	2004-11-26 02:21:21.000000000 -0300
@@ -283,6 +283,32 @@
 	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 @@
 	{ "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 @@
 
 /* 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 @@
 		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 @@
 
 /* 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 -rN -u old-libjio/check.c new-libjio/check.c
--- old-libjio/check.c	2005-03-10 15:15:10.058870105 -0300
+++ new-libjio/check.c	2005-03-10 15:15:10.076868171 -0300
@@ -93,12 +93,12 @@
 }
 
 /* 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 @@
 	dir = NULL;
 	fs.fd = -1;
 	fs.jfd = -1;
+	fs.jdir = NULL;
 	fs.jdirfd = -1;
 	fs.jmap = MAP_FAILED;
 	map = NULL;
@@ -134,17 +135,33 @@
 
 	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 @@
 
 	/* 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 @@
 		goto exit;
 	}
 
-	dir = opendir(jdir);
+	dir = opendir(fs.jdir);
 	if (dir == NULL) {
 		ret = J_ENOJOURNAL;
 		goto exit;
@@ -210,7 +227,7 @@
 		 * 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 @@
 		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 @@
 }
 
 /* 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 @@
 
 		/* 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 @@
 	}
 	closedir(dir);
 
-	rmdir(jdir);
+	rmdir(path);
 
 	return 1;
 }
diff -rN -u old-libjio/common.c new-libjio/common.c
--- old-libjio/common.c	2005-03-10 15:15:10.057870213 -0300
+++ new-libjio/common.c	2004-11-26 02:21:21.000000000 -0300
@@ -15,6 +15,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 
+#include "libjio.h"
 #include "common.h"
 
 
@@ -126,26 +127,9 @@
 }
 
 /* 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 -rN -u old-libjio/common.h new-libjio/common.h
--- old-libjio/common.h	2005-03-10 15:15:10.056870320 -0300
+++ new-libjio/common.h	2005-03-10 15:15:10.105865055 -0300
@@ -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 @@
 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 -rN -u old-libjio/jiofsck.c new-libjio/jiofsck.c
--- old-libjio/jiofsck.c	2005-03-10 15:15:10.056870320 -0300
+++ new-libjio/jiofsck.c	2005-03-10 15:15:10.076868171 -0300
@@ -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 @@
 	if (do_cleanup) {
 		printf("Cleaning journal: ");
 		fflush(stdout);
-		if (!jfsck_cleanup(file)) {
+		if (!jfsck_cleanup(file, jdir)) {
 			printf("Error cleaning journal\n");
 			return 1;
 		}
diff -rN -u old-libjio/libjio.h new-libjio/libjio.h
--- old-libjio/libjio.h	2005-03-10 15:15:10.056870320 -0300
+++ new-libjio/libjio.h	2005-03-10 15:15:10.105865055 -0300
@@ -34,6 +34,7 @@
 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_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 -rN -u old-libjio/trans.c new-libjio/trans.c
--- old-libjio/trans.c	2005-03-10 15:15:10.055870427 -0300
+++ new-libjio/trans.c	2005-03-10 15:15:10.107864840 -0300
@@ -71,7 +71,7 @@
 			/* 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 @@
 		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 @@
 
 	fs->fd = -1;
 	fs->jfd = -1;
+	fs->jdir = NULL;
 	fs->jdirfd = -1;
 	fs->jmap = MAP_FAILED;
 
@@ -588,6 +589,11 @@
 	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 @@
 	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 @@
 	if (fs->name)
 		/* allocated by strdup() in jopen() */
 		free(fs->name);
+	if (fs->jdir)
+		free(fs->jdir);
 	pthread_mutex_destroy(&(fs->lock));
 
 	return ret;


