Thu Mar 10 15:09:50 ART 2005  Alberto Bertogli (albertogli@telpin.com.ar)
  tagged 0.22
Thu Mar 10 15:09:32 ART 2005  Alberto Bertogli (albertogli@telpin.com.ar)
  * libjio 0.22
Thu Mar 10 04:34:35 ART 2005  Alberto Bertogli (albertogli@telpin.com.ar)
  * Reintroduce dup() and dup2() in the preloader.
Thu Mar 10 03:42:49 ART 2005  Alberto Bertogli (albertogli@telpin.com.ar)
  * Fix some datatypes to avoid overflows and losses.
Thu Mar 10 03:29:40 ART 2005  Alberto Bertogli (albertogli@telpin.com.ar)
  * Fix transaction lenght checking.
  If we don't cast to a bigger value, there is no way the check can be positive.
  Also, initialize the lenght to 0.
Thu Mar 10 02:28:55 ART 2005  Alberto Bertogli (albertogli@telpin.com.ar)
  * Add a release script.
Thu Mar 10 02:23:21 ART 2005  Alberto Bertogli (albertogli@telpin.com.ar)
  * Remove old utilities.
Thu Mar 10 02:14:58 ART 2005  Alberto Bertogli (albertogli@telpin.com.ar)
  * Fix the exporter script to handle '/' in patch names.
Thu Mar 10 01:51:39 ART 2005  Alberto Bertogli (albertogli@telpin.com.ar)
  * Allow pthread_mutex_lock()/unlock() to fail in the preloader.
Thu Mar  3 03:16:40 ART 2005  Alberto Bertogli (albertogli@telpin.com.ar)
  UNDO: Add dup() and dup2() to the preloader.
  This patch adds dup() and dup2() support to the preloader. It hasn't been
  tested beyond compile yet.
Wed Mar  9 23:37:02 ART 2005  Alberto Bertogli (albertogli@telpin.com.ar)
  * Use -O properly.
  I don't know why the Makefile says -O6 instead of -O3 (it's probably a
  copy/paste from somewhere else), but it's wrong; fix it.
Wed Mar  9 17:52:41 ART 2005  Alberto Bertogli (albertogli@telpin.com.ar)
  * Update the "UPGRADING" document.
Wed Mar  9 15:58:21 ART 2005  Alberto Bertogli (albertogli@telpin.com.ar)
  * Add -fPIC to libjio.so compile.
Thu Mar  3 03:16:40 ART 2005  Alberto Bertogli (albertogli@telpin.com.ar)
  * Add dup() and dup2() to the preloader.
  This patch adds dup() and dup2() support to the preloader. It hasn't been
  tested beyond compile yet.
Sun Nov 28 15:11:02 ART 2004  Alberto Bertogli (albertogli@telpin.com.ar)
  * Add a record exporter script
  
  Add a small script to export each record in order to good old diff -u patches.
  It takes darcs' changes in XML format and runs "darcs diff -u" on each record
  to produce the diffs. It can also list the records.
Sun Nov 28 12:51:12 ART 2004  Alberto Bertogli (albertogli@telpin.com.ar)
  * Limit transaction's size.
  
  Check transaction size, and don't let it grow beyond SSIZE_MAX, since
  otherwise jtrans_commit() return value could overflow and return a failure
  when there is none.
  
Sun Nov 28 12:20:16 ART 2004  Alberto Bertogli (albertogli@telpin.com.ar)
  * Remove failed operations from the list.
  
  If joper_add() fails when malloc()ing buffer memory, it returns 0 but never
  removes the operation from the list, which could break and/or cause
  corruption. The fix is just to allocate under the lock, and remove the
  operation from the list if the allocation fails.
  
  
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.
  
Fri Nov 26 02:18:01 ART 2004  Alberto Bertogli (albertogli@telpin.com.ar)
  * Fix the ANSI jrewind() wrapper definition.
diff -rN -u old-libjio/ansi.c new-libjio/ansi.c
--- old-libjio/ansi.c	2005-03-10 15:12:02.239055247 -0300
+++ new-libjio/ansi.c	2005-03-10 03:40:38.000000000 -0300
@@ -175,7 +175,7 @@
 /* fseek wrapper */
 int jfseek(struct jfs *stream, long offset, int whence)
 {
-	long pos;
+	off_t pos;
 
 	pthread_mutex_lock(&(stream->lock));
 	pos = lseek(stream->fd, offset, whence);
@@ -189,9 +189,10 @@
 }
 
 /* ftell wrapper */
-int jftell(struct jfs *stream)
+long jftell(struct jfs *stream)
 {
-	return lseek(stream->fd, 0, SEEK_CUR);
+	/* forced conversion to long to meet the prototype */
+	return (long) lseek(stream->fd, 0, SEEK_CUR);
 }
 
 /* rewind wrapper */
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:12:02.275051378 -0300
+++ new-libjio/bindings/preload/libjio_preload.c	2005-03-10 04:31:00.000000000 -0300
@@ -58,11 +58,14 @@
 static off_t (*c_lseek)(int fd, off_t offset, int whence);
 static off64_t (*c_lseek64)(int fd, off64_t offset, int whence);
 static int (*c_fsync)(int fd);
+static int (*c_dup)(int oldfd);
+static int (*c_dup2)(int oldfd, int newfd);
 
 
 /* file descriptor table, to translate fds to jfs */
 struct fd_entry {
 	int fd;
+	unsigned int *refcount;
 	struct jfs *fs;
 	pthread_mutex_t lock;
 };
@@ -96,6 +99,7 @@
 			if (called)			\
 				fprintf(stderr, "\t");	\
 			called++;			\
+			fprintf(stderr, "%5.5d ", getpid()); \
 			fprintf(stderr, "%s(): ", __FUNCTION__ ); \
 			fprintf(stderr, __VA_ARGS__);	\
 			fflush(stderr);			\
@@ -108,26 +112,30 @@
  * catch out of bounds accesses */
 static inline int fd_lock(int fd)
 {
+	int r;
+
 	if (fd < 0 || fd >= MAXFD) {
 		printd("locking out of bounds fd %d\n", fd);
 		return 0;
 	}
 	//printd("L %d\n", fd);
-	pthread_mutex_lock(&(fd_table[fd].lock));
+	r = pthread_mutex_lock(&(fd_table[fd].lock));
 	//printd("OK %d\n", fd);
-	return 1;
+	return !r;
 }
 
 static inline int fd_unlock(int fd)
 {
+	int r;
+
 	if (fd < 0 || fd >= MAXFD) {
 		printd("unlocking out of bounds fd %d\n", fd);
 		return 0;
 	}
 	//printd("U %d\n", fd);
-	pthread_mutex_unlock(&(fd_table[fd].lock));
+	r = pthread_mutex_unlock(&(fd_table[fd].lock));
 	//printd("OK %d\n", fd);
-	return 1;
+	return !r;
 }
 
 
@@ -178,6 +186,8 @@
 	libc_load(lseek);
 	libc_load(lseek64);
 	libc_load(fsync);
+	libc_load(dup);
+	libc_load(dup2);
 
 	printd("done\n");
 	return 1;
@@ -251,6 +261,8 @@
 
 	fd_lock(fd);
 	fd_table[fd].fd = fd;
+	fd_table[fd].refcount = malloc(sizeof(unsigned int));
+	*fd_table[fd].refcount = 1;
 	fd_table[fd].fs = fs;
 	fd_unlock(fd);
 
@@ -323,6 +335,8 @@
 
 	fd_lock(fd);
 	fd_table[fd].fd = fd;
+	fd_table[fd].refcount = malloc(sizeof(unsigned int));
+	*fd_table[fd].refcount = 1;
 	fd_table[fd].fs = fs;
 	fd_unlock(fd);
 
@@ -330,6 +344,38 @@
 	return fd;
 }
 
+/* close() is split in two functions: unlocked_close() that performs the real
+ * actual close and cleanup, and close() which takes care of the locking and
+ * calls unlocked_close(); this is because in dup*() we need to close with
+ * locks already held to avoid races. */
+int unlocked_close(int fd)
+{
+	int r;
+
+	if (*fd_table[fd].refcount > 1) {
+		/* we still have references, don't really close */
+		printd("not closing, refcount: %d\n", *fd_table[fd].refcount);
+		(*fd_table[fd].refcount)--;
+		fd_table[fd].fd = -1;
+		fd_table[fd].refcount = NULL;
+		fd_table[fd].fs = NULL;
+		return 0;
+	}
+
+	rec_inc();
+	r = jclose(fd_table[fd].fs);
+	rec_dec();
+
+	if (fd_table[fd].fs != NULL) {
+		fd_table[fd].fd = -1;
+		free(fd_table[fd].refcount);
+		fd_table[fd].refcount = NULL;
+		free(fd_table[fd].fs);
+		fd_table[fd].fs = NULL;
+	}
+
+	return r;
+}
 
 int close(int fd)
 {
@@ -353,14 +399,7 @@
 	}
 	printd("libjio\n");
 
-	rec_inc();
-	r = jclose(fs);
-	if (fd_table[fd].fs != NULL) {
-		free(fd_table[fd].fs);
-		fd_table[fd].fd = -1;
-		fd_table[fd].fs = NULL;
-	}
-	rec_dec();
+	r = unlocked_close(fd);
 	fd_unlock(fd);
 
 	printd("return %d\n", r);
@@ -380,7 +419,7 @@
 	printd("libjio\n");
 
 	rec_inc();
-	jfsck_cleanup(pathname);
+	jfsck_cleanup(pathname, NULL);
 	rec_dec();
 
 	r = (*c_unlink)(pathname);
@@ -389,6 +428,89 @@
 	return r;
 }
 
+int dup(int oldfd)
+{
+	int r;
+
+	if (called) {
+		printd("orig\n");
+		return (*c_dup)(oldfd);
+	}
+
+	if (fd_table[oldfd].fs == NULL) {
+		printd("NULL fs, fd %d\n", oldfd);
+		fd_unlock(oldfd);
+		return (*c_dup)(oldfd);
+	}
+
+	if (!fd_lock(oldfd)) {
+		printd("out of bounds fd: %d\n", oldfd);
+		return -1;
+	}
+
+	printd("libjio\n");
+
+	rec_inc();
+	r = (*c_dup)(oldfd);
+	rec_dec();
+
+	if (r >= 0) {
+		fd_lock(r);
+		fd_table[r].fd = r;
+		fd_table[r].refcount = fd_table[oldfd].refcount;
+		(*fd_table[r].refcount)++;
+		fd_table[r].fs = fd_table[oldfd].fs;
+		fd_unlock(r);
+	}
+
+	fd_unlock(oldfd);
+	printd("return %d\n", r);
+	return r;
+}
+
+int dup2(int oldfd, int newfd)
+{
+	int r;
+
+	if (called) {
+		printd("orig\n");
+		return (*c_dup2)(oldfd, newfd);
+	}
+
+	if (!fd_lock(oldfd)) {
+		printd("out of bounds fd: %d\n", oldfd);
+		return -1;
+	}
+
+	if (fd_table[oldfd].fs == NULL) {
+		printd("NULL fs, fd %d\n", oldfd);
+		fd_unlock(oldfd);
+		return (*c_dup2)(oldfd, newfd);
+	}
+
+	printd("libjio\n");
+
+	rec_inc();
+	r = (*c_dup2)(oldfd, newfd);
+	rec_dec();
+
+	if (r >= 0) {
+		fd_lock(newfd);
+		if (fd_table[newfd].fs != NULL) {
+			unlocked_close(newfd);
+		}
+		fd_table[newfd].fd = newfd;
+		fd_table[newfd].refcount = fd_table[oldfd].refcount;
+		(*fd_table[newfd].refcount)++;
+		fd_table[newfd].fs = fd_table[oldfd].fs;
+		fd_unlock(newfd);
+	}
+
+	fd_unlock(oldfd);
+	printd("return %d\n", r);
+	return r;
+}
+
 
 /* the rest of the functions are automagically generated from the following
  * macro. The ugliest. I'm so proud. */
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:12:02.276051271 -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:12:02.280050841 -0300
+++ new-libjio/check.c	2005-03-10 03:37:20.000000000 -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;
@@ -106,13 +106,14 @@
 	DIR *dir;
 	struct dirent *dent;
 	unsigned char *map;
-	off_t filelen;
+	off_t filelen, lr;
 
 	tfd = -1;
 	filelen = 0;
 	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;
 		}
@@ -222,13 +239,15 @@
 
 		/* try to lock the transaction file, if it's locked then it is
 		 * currently being used so we skip it */
-		rv = plockf(tfd, F_TLOCKW, 0, 0);
-		if (rv == -1) {
+		lr = plockf(tfd, F_TLOCKW, 0, 0);
+		if (lr == -1) {
 			res->in_progress++;
 			goto loop;
 		}
 
 		filelen = lseek(tfd, 0, SEEK_END);
+		/* no overflow problems because we know the transaction size
+		 * is limited to SSIZE_MAX */
 		map = mmap(0, filelen, PROT_READ, MAP_SHARED, tfd, 0);
 		if (map == MAP_FAILED) {
 			res->broken++;
@@ -290,6 +309,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 +321,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 +349,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 +364,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:12:02.279050948 -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:12:02.279050948 -0300
+++ new-libjio/common.h	2004-11-28 12:41:28.000000000 -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
@@ -25,12 +26,14 @@
 #define F_TLOCKW	(_F_TLOCK | _F_WRITE)
 #define F_UNLOCK	(_F_ULOCK)
 
+#define MAX_TSIZE	(SSIZE_MAX)
+
 
 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 -rN -u old-libjio/jiofsck.c new-libjio/jiofsck.c
--- old-libjio/jiofsck.c	2005-03-10 15:12:02.279050948 -0300
+++ new-libjio/jiofsck.c	2005-03-10 03:40:59.000000000 -0300
@@ -9,44 +9,54 @@
 #include "libjio.h"
 
 
-void usage()
+static 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:12:02.281050733 -0300
+++ new-libjio/libjio.h	2005-03-10 03:40:09.000000000 -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 */
@@ -62,6 +63,7 @@
 	int id;			/* transaction id */
 	uint32_t flags;		/* transaction flags */
 	unsigned int numops;	/* quantity of operations in the list */
+	ssize_t len;		/* transaction's length */
 	pthread_mutex_t lock;	/* used to modify the operation list */
 	struct joper *op;	/* list of operations */
 };
@@ -110,12 +112,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);
@@ -138,8 +141,8 @@
 void jclearerr(struct jfs *stream);
 int jferror(struct jfs *stream);
 int jfseek(struct jfs *stream, long offset, int whence);
-int jftell(struct jfs *stream);
-void frewind(struct jfs *stream);
+long jftell(struct jfs *stream);
+void jrewind(struct jfs *stream);
 FILE *jfsopen(struct jfs *stream, const char *mode);
 
 
diff -rN -u old-libjio/Make.conf new-libjio/Make.conf
--- old-libjio/Make.conf	2005-03-10 15:12:02.253053742 -0300
+++ new-libjio/Make.conf	2005-03-10 15:09:20.000000000 -0300
@@ -1,8 +1,8 @@
 
-VERSION="0.21"
+VERSION="0.22"
 
 CC = gcc
-CFLAGS += -Wall -O6 \
+CFLAGS += -Wall -O3 \
 	-D_LARGEFILE_SOURCE=1 -D_LARGEFILE64_SOURCE=1 \
 	-D_LFS_LARGEFILE=1 -D_LFS64_LARGEFILE=1 \
 	-D_FILE_OFFSET_BITS=64 `getconf LFS_CFLAGS 2>/dev/null` \
diff -rN -u old-libjio/Makefile new-libjio/Makefile
--- old-libjio/Makefile	2005-03-10 15:12:02.255053527 -0300
+++ new-libjio/Makefile	2005-03-09 23:36:59.000000000 -0300
@@ -11,7 +11,7 @@
 all: libjio.so libjio.a jiofsck
 
 libjio.so: $(OBJS)
-	$(CC) -shared $(OBJS) -o libjio.so
+	$(CC) -shared -fPIC $(OBJS) -o libjio.so
 
 libjio.a: $(OBJS)
 	$(AR) cr libjio.a $(OBJS)
@@ -46,7 +46,7 @@
 
 preload: all
 	install -d bindings/preload/build/
-	$(CC) $(INCLUDES) -Wall -O6 -shared -fPIC \
+	$(CC) $(INCLUDES) -Wall -O3 -shared -fPIC \
 		-D_XOPEN_SOURCE=500 \
 		-ldl -lpthread -L. -ljio -I. \
 		bindings/preload/libjio_preload.c \
diff -rN -u old-libjio/trans.c new-libjio/trans.c
--- old-libjio/trans.c	2005-03-10 15:12:02.278051056 -0300
+++ new-libjio/trans.c	2005-03-10 03:34:11.000000000 -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;
@@ -102,6 +102,7 @@
 	ts->flags = fs->flags;
 	ts->op = NULL;
 	ts->numops = 0;
+	ts->len = 0;
 	pthread_mutexattr_init(&attr);
 	pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);
 	pthread_mutex_init( &(ts->lock), &attr);
@@ -146,6 +147,11 @@
 		return 0;
 	}
 
+	if ((long long) ts->len + count > MAX_TSIZE) {
+		pthread_mutex_unlock(&(ts->lock));
+		return 0;
+	}
+
 	/* find the last operation in the transaction and create a new one at
 	 * the end */
 	if (ts->op == NULL) {
@@ -167,14 +173,24 @@
 		tmpop->next->prev = tmpop;
 		jop = tmpop->next;
 	}
-	pthread_mutex_unlock(&(ts->lock));
 
 	jop->buf = malloc(count);
 	if (jop->buf == NULL) {
+		/* remove from the list and fail */
+		if (jop->prev == NULL) {
+			ts->op = NULL;
+		} else {
+			jop->prev->next = jop->next;
+		}
 		free(jop);
+		pthread_mutex_unlock(&(ts->lock));
 		return 0;
 	}
 
+	ts->numops++;
+	ts->len += count;
+	pthread_mutex_unlock(&(ts->lock));
+
 	/* we copy the buffer because then the caller can reuse it */
 	memcpy(jop->buf, buf, count);
 	jop->len = count;
@@ -184,8 +200,6 @@
 	jop->pdata = NULL;
 	jop->locked = 0;
 
-	ts->numops++;
-
 	return 1;
 }
 
@@ -221,7 +235,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)
@@ -264,9 +278,10 @@
 	 * same spots and we could end up with interleaved writes, that could
 	 * break atomicity warantees if we need to rollback */
 	if (!(ts->flags & J_NOLOCK)) {
+		off_t lr;
 		for (op = ts->op; op != NULL; op = op->next) {
-			rv = plockf(ts->fs->fd, F_LOCKW, op->offset, op->len);
-			if (rv == -1)
+			lr = plockf(ts->fs->fd, F_LOCKW, op->offset, op->len);
+			if (lr == -1)
 				/* note it can fail with EDEADLK */
 				goto unlink_exit;
 			op->locked = 1;
@@ -331,7 +346,8 @@
 		curpos += op->len;
 	}
 
-	/* compute and save the checksum */
+	/* compute and save the checksum (curpos is always small, so there's
+	 * no overflow possibility when we convert to size_t) */
 	if (!checksum(fd, curpos, &csum))
 		goto unlink_exit;
 
@@ -533,6 +549,7 @@
 
 	fs->fd = -1;
 	fs->jfd = -1;
+	fs->jdir = NULL;
 	fs->jdirfd = -1;
 	fs->jmap = MAP_FAILED;
 
@@ -588,6 +605,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 +682,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 +767,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;
diff -rN -u old-libjio/UPGRADING new-libjio/UPGRADING
--- old-libjio/UPGRADING	2005-03-10 15:12:02.254053635 -0300
+++ new-libjio/UPGRADING	2005-03-09 17:52:24.000000000 -0300
@@ -11,6 +11,9 @@
 
 If you want to see what motivated the changes, see the changelog or just ask.
 
+0.21 -> 0.22
+* Applications need to be recompiled due to a change in the jfs structure.
+
 0.19 -> 0.20
 * Applications need to be recompiled due to a change in the jfs structure.
 * When you link your applications with libjio, you need to make sure you
diff -rN -u old-libjio/utils/exporter new-libjio/utils/exporter
--- old-libjio/utils/exporter	1969-12-31 21:00:00.000000000 -0300
+++ new-libjio/utils/exporter	2005-03-10 02:13:21.000000000 -0300
@@ -0,0 +1,127 @@
+#!/usr/bin/python
+
+import sys
+import os
+from xml.sax import saxutils
+from xml.sax import make_parser
+from xml.sax.handler import feature_namespaces
+
+
+class Patch:
+	"Represents a single patch/record"
+	def __init__(self):
+		self.hash = ''
+		self.author = ''
+		self.date = ''
+		self.local_date = ''
+		self.name = ''
+		self.comment = ''
+
+	def tostr(self):
+		s = "%s\n\tAuthor: %s\n\tDate: %s\n\tHash: %s\n" % \
+			(self.name, self.author, self.date, self.hash)
+		return s
+
+	def export(self, order, path):
+		# '/'s are not allowed in filenames
+		name = self.name.replace('/', '-')
+
+		# avoid 'name..patch'
+		if name[-1] == '.':
+			name = name[:-1]
+			
+		file = "%s/%.2d - %s.patch" % (path, order, name)
+		cmd = 'darcs diff -u --match "hash %s" > "%s"' % \
+				(self.hash, file)
+		if os.system(cmd):
+			print "Command failed: '%s'" % cmd
+
+
+class BuildPatchList(saxutils.DefaultHandler):
+	def __init__(self):
+		self.db = {}
+		self.list = []
+		self.cur_hash = ''
+		self.cur_elem = None
+		self.cur_val = ''
+
+	def startElement(self, name, attrs):
+		if name == 'patch':
+			p = Patch()
+			p.author = attrs.get('author', None)
+			p.date = attrs.get('date', None)
+			p.local_date = attrs.get('local_date', None)
+			p.hash = attrs.get('hash')
+			self.db[p.hash] = p
+			self.current = p.hash
+			self.list.append(p.hash)
+		elif name == 'name':
+			self.db[self.current].name = ''
+			self.cur_elem = 'name'
+		elif name == 'comment':
+			self.db[self.current].comment = ''
+			self.cur_elem = 'name'
+		else:
+			self.cur_elem = None
+
+	def characters(self, s):
+		if not self.cur_elem:
+			return
+		self.cur_val += s
+
+	def endElement(self, name):
+		if name == 'name':
+			self.db[self.current].name = self.cur_val
+		elif name == 'comment':
+			self.db[self.current].current = self.cur_val
+
+		self.cur_elem = None
+		self.cur_val = ''
+
+
+# main
+
+if len(sys.argv) < 3:
+	print "Use: exporter [xmlfile|-] [list|export destdir]"
+	print
+	print "Examples:"
+	print " # darcs changes --xml-output | exporter - export /tmp"
+	print " # darcs changes --xml-output | exporter - list"
+	sys.exit(1)
+
+if sys.argv[1] == '-':
+	file = sys.stdin
+else:
+	file = sys.argv[1]
+
+parser = make_parser()
+parser.setFeature(feature_namespaces, 0)
+
+handler = BuildPatchList()
+parser.setContentHandler(handler)
+parser.parse(file)
+
+# reverse the list so the oldest is the first, and the newest is the last
+handler.list.reverse()
+
+# we now have two main structures: handler.db is the hash table of Patches,
+# indexed by their hash, and handler.list is the ordered list of hashes.
+
+if sys.argv[2] == 'list':
+	c = 1
+	for h in handler.list:
+		print "%.2d:" % c, handler.db[h].tostr()
+		c += 1
+elif sys.argv[2] == 'export':
+	if len(sys.argv) < 4:
+		print "Destination directory missing"
+		sys.exit(1)
+	c = 1
+	for h in handler.list:
+		p = handler.db[h]
+		print "%.2d: %s" % (c, p.name)
+		p.export(c, sys.argv[3])
+		c += 1
+else:
+	print "Unknown parameter"
+
diff -rN -u old-libjio/utils/prerelease new-libjio/utils/prerelease
--- old-libjio/utils/prerelease	2005-03-10 15:12:02.244054709 -0300
+++ new-libjio/utils/prerelease	1969-12-31 21:00:00.000000000 -0300
@@ -1,22 +0,0 @@
-#!/bin/bash
-
-if [ $# -ne 1 ]; then
-	echo "Usage: prerelease RELNUM"
-	exit
-fi
-
-echo "Creating the full patch"
-patchdesc $(cat ps/series) > ../libjio-$1.patch
-patchdesc $(cat ps/series) > ../Changelog-$1
-echo >> ../libjio-$1.patch
-echo >> ../libjio-$1.patch
-combine-series ../libjio-$1-t1.patch
-diffstat ../libjio-$1-t1.patch >> ../libjio-$1.patch
-echo >> ../libjio-$1.patch
-echo >> ../libjio-$1.patch
-cat ../libjio-$1-t1.patch >> ../libjio-$1.patch
-rm ../libjio-$1-t1.patch
-
-echo "Cleaning up"
-make clean
-
diff -rN -u old-libjio/utils/psenv new-libjio/utils/psenv
--- old-libjio/utils/psenv	2005-03-10 15:12:02.245054602 -0300
+++ new-libjio/utils/psenv	1969-12-31 21:00:00.000000000 -0300
@@ -1,22 +0,0 @@
-#!/bin/bash
-
-# sets the environment to work using Andrew Morton great patch scripts
-
-# run it in the top directory, like:
-#  . utils/psenv
-
-
-# create the directories
-for i in ps/pc ps/patches ps/txt; do
-	if [ ! -d $i ]; then
-		mkdir -p -v $i
-	fi
-done
-
-
-PATH=/root/bin/patch-scripts:$PATH
-PATCHSCRIPTS=./ps
-
-export PATH
-export PATCHSCRIPTS
-
diff -rN -u old-libjio/utils/release new-libjio/utils/release
--- old-libjio/utils/release	1969-12-31 21:00:00.000000000 -0300
+++ new-libjio/utils/release	2005-03-10 02:28:12.000000000 -0300
@@ -0,0 +1,47 @@
+#!/bin/bash
+
+PKG=libjio
+
+if [ "$1" == "" -o "$2" == "" ]; then
+	echo "Use: release OLDREL NEWREL"
+	echo "Run from the repo root"
+	exit
+fi
+
+OLDREL=$1
+NEWREL=$2
+
+TARGZBALL="$PKG-$NEWREL.tar.gz"
+TARBZBALL="$PKG-$NEWREL.tar.bz2"
+RELDIR="../$NEWREL"
+
+if [ -d $RELDIR ]; then
+	echo "$RELDIR already exists!"
+	exit
+fi
+
+echo "* making $RELDIR"
+mkdir $RELDIR > /dev/null 2> /dev/null
+
+echo "* darcs dist"
+darcs dist -d $PKG-$NEWREL
+mv $TARGZBALL $RELDIR
+
+echo "* darcs changes"
+darcs changes --from-tag $OLDREL > $RELDIR/Changelog-$NEWREL
+
+echo "* darcs diff"
+darcs diff -u --from-tag $OLDREL > $RELDIR/$PKG-$NEWREL.patch
+
+echo "* export patches"
+mkdir $RELDIR/$PKG-$NEWREL-broken-out
+darcs changes --xml-output --from-tag $OLDREL | \
+	./utils/exporter - export $RELDIR/$PKG-$NEWREL-broken-out/
+
+echo "* unpack"
+cd $RELDIR
+tar -zxf $TARGZBALL
+
+echo "* tar.bz2"
+tar -cjf $TARBZBALL $PKG-$NEWREL
+

