author | Alberto Bertogli
<albertito@gmail.com> 2004-05-30 01:29:24 UTC |
committer | Alberto Bertogli
<albertito@gmail.com> 2007-07-15 12:49:16 UTC |
parent | d158773061ab8687204c7bde9a97d5f8997f0fd9 |
Makefile | +6 | -6 |
common.c | +92 | -0 |
common.h | +17 | -0 |
doc/layout | +21 | -0 |
libjio.c => trans.c | +53 | -307 |
unix.c | +191 | -0 |
diff --git a/Makefile b/Makefile index 725b987..fc84cbe 100644 --- a/Makefile +++ b/Makefile @@ -3,18 +3,18 @@ include Make.conf # objects to build -OBJS = libjio.o +OBJS = common.o trans.o unix.o # rules default: all all: shared static jiofsck -shared: libjio.o - $(CC) -shared libjio.o -o libjio.so +shared: $(OBJS) + $(CC) -shared $(OBJS) -o libjio.so -static: libjio.o - $(AR) cr libjio.a libjio.o +static: $(OBJS) + $(AR) cr libjio.a $(OBJS) jiofsck: jiofsck.o static $(CC) jiofsck.o libjio.a -lpthread -o jiofsck @@ -38,7 +38,7 @@ install: all clean: - rm -f libjio.o libjio.a libjio.so jiofsck.o jiofsck + rm -f $(OBJS) libjio.a libjio.so jiofsck.o jiofsck rm -f *.bb *.bbg *.da *.gcov gmon.out diff --git a/common.c b/common.c new file mode 100644 index 0000000..e0abfd3 --- /dev/null +++ b/common.c @@ -0,0 +1,92 @@ + +/* + * libjio - A library for Journaled I/O + * Alberto Bertogli (albertogli@telpin.com.ar) + * + * Common functions + */ + +#include <sys/types.h> +#include <fcntl.h> +#include <unistd.h> + +#include "common.h" + + +/* like lockf, but lock always from the beginning of the file */ +off_t plockf(int fd, int cmd, off_t offset, off_t len) +{ + struct flock fl; + int op; + + if (cmd == F_LOCK) { + fl.l_type = F_WRLCK; + op = F_SETLKW; + } else if (cmd == F_ULOCK) { + fl.l_type = F_UNLCK; + op = F_SETLKW; + } else if (cmd == F_TLOCK) { + fl.l_type = F_WRLCK; + op = F_SETLK; + } else + return 0; + + fl.l_whence = SEEK_SET; + fl.l_start = offset; + fl.l_len = len; + + return fcntl(fd, op, &fl); +} + +/* like pread but either fails, or return a complete read; if we return less + * than count is because EOF was reached */ +ssize_t spread(int fd, void *buf, size_t count, off_t offset) +{ + int rv, c; + + c = 0; + + while (c < count) { + rv = pread(fd, (char *) buf + c, count - c, offset + c); + + if (rv == count) + /* we're done */ + return count; + else if (rv < 0) + /* error */ + return rv; + else if (rv == 0) + /* got EOF */ + return c; + + /* incomplete read, keep on reading */ + c += rv; + } + + return count; +} + +/* like spread() but for pwrite() */ +ssize_t spwrite(int fd, const void *buf, size_t count, off_t offset) +{ + int rv, c; + + c = 0; + + while (c < count) { + rv = pwrite(fd, (char *) buf + c, count - c, offset + c); + + if (rv == count) + /* we're done */ + return count; + else if (rv <= 0) + /* error/nothing was written */ + return rv; + + /* incomplete write, keep on writing */ + c += rv; + } + + return count; +} + diff --git a/common.h b/common.h new file mode 100644 index 0000000..a048f91 --- /dev/null +++ b/common.h @@ -0,0 +1,17 @@ + +/* + * libjio - A library for Journaled I/O + * Alberto Bertogli (albertogli@telpin.com.ar) + * + * Header for internal functions + */ + +#ifndef _COMMON_H +#define _COMMON_H + +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); + +#endif + diff --git a/doc/layout b/doc/layout new file mode 100644 index 0000000..b5cf3f8 --- /dev/null +++ b/doc/layout @@ -0,0 +1,21 @@ + +Source layout +------------- + +The source is structured so code can be read and reviewed in an independant +way, following the way the code really works. + +The main file is called "trans.c" which contains the transaction API; all the +other wrappers depend on it, and they're the core of the library. + +There is also a "common.c" file with some common functions. + +And finally, "unix.c" which implement the wrappers for the UNIX API (read(), +write() and their family), and "ansi.c" where ANSI wrappers live (fopen(), +fread(), etc.). + +If you're trying to read the code for the first time, you can start from +either from the more simple unix.c which relies on trans.c; or from trans.c +which is what everything else relies on, and then the wrappers will be +obvious. + diff --git a/libjio.c b/trans.c similarity index 76% rename from libjio.c rename to trans.c index bcef8e6..b250a1c 100644 --- a/libjio.c +++ b/trans.c @@ -2,6 +2,8 @@ /* * libjio - A library for Journaled I/O * Alberto Bertogli (albertogli@telpin.com.ar) + * + * Core transaction API and recovery functions */ #include <sys/types.h> @@ -14,93 +16,12 @@ #include <libgen.h> #include <stdio.h> #include <dirent.h> -#include <sys/uio.h> #include <errno.h> #include "libjio.h" +#include "common.h" -/* - * small util functions - */ - -/* like lockf, but lock always from the beginning of the file */ -static off_t plockf(int fd, int cmd, off_t offset, off_t len) -{ - struct flock fl; - int op; - - if (cmd == F_LOCK) { - fl.l_type = F_WRLCK; - op = F_SETLKW; - } else if (cmd == F_ULOCK) { - fl.l_type = F_UNLCK; - op = F_SETLKW; - } else if (cmd == F_TLOCK) { - fl.l_type = F_WRLCK; - op = F_SETLK; - } else - return 0; - - fl.l_whence = SEEK_SET; - fl.l_start = offset; - fl.l_len = len; - - return fcntl(fd, op, &fl); -} - -/* like pread but either fails, or return a complete read; if we return less - * than count is because EOF was reached */ -static ssize_t spread(int fd, void *buf, size_t count, off_t offset) -{ - int rv, c; - - c = 0; - - while (c < count) { - rv = pread(fd, (char *) buf + c, count - c, offset + c); - - if (rv == count) - /* we're done */ - return count; - else if (rv < 0) - /* error */ - return rv; - else if (rv == 0) - /* got EOF */ - return c; - - /* incomplete read, keep on reading */ - c += rv; - } - - return count; -} - -/* like spread() but for pwrite() */ -static ssize_t spwrite(int fd, const void *buf, size_t count, off_t offset) -{ - int rv, c; - - c = 0; - - while (c < count) { - rv = pwrite(fd, (char *) buf + c, count - c, offset + c); - - if (rv == count) - /* we're done */ - return count; - else if (rv <= 0) - /* error/nothing was written */ - return rv; - - /* incomplete write, keep on writing */ - c += rv; - } - - return count; -} - /* build the journal directory name out of the filename */ static int get_jdir(const char *filename, char *jdir) { @@ -154,7 +75,7 @@ static unsigned int get_tid(struct jfs *fs) { unsigned int curid; int r, rv; - + /* lock the whole file */ plockf(fs->jfd, F_LOCK, 0, 0); @@ -164,19 +85,19 @@ static unsigned int get_tid(struct jfs *fs) rv = 0; goto exit; } - + /* increment it and handle overflows */ rv = curid + 1; if (rv == 0) rv = 1; - + /* write to the file descriptor */ r = spwrite(fs->jfd, &rv, sizeof(rv), 0); if (r != sizeof(curid)) { rv = 0; goto exit; } - + exit: plockf(fs->jfd, F_ULOCK, 0, 0); return rv; @@ -188,7 +109,7 @@ static void free_tid(struct jfs *fs, unsigned int tid) unsigned int curid, i; int r; char name[PATH_MAX]; - + /* lock the whole file */ plockf(fs->jfd, F_LOCK, 0, 0); @@ -219,7 +140,7 @@ static void free_tid(struct jfs *fs, unsigned int tid) if (r != sizeof(curid)) { goto exit; } - } + } exit: plockf(fs->jfd, F_ULOCK, 0, 0); @@ -269,37 +190,37 @@ int jtrans_commit(struct jtrans *ts) int id, fd, rv, t; char *name; unsigned char *buf_init, *bufp; - + name = (char *) malloc(PATH_MAX); if (name == NULL) return -1; - + id = get_tid(ts->fs); if (id == 0) return -1; - + /* open the transaction file */ if (!get_jtfile(ts->fs->name, id, name)) return -1; fd = open(name, O_RDWR | O_CREAT | O_TRUNC | O_LARGEFILE, 0600); if (fd < 0) return -1; - + /* and lock it */ plockf(fd, F_LOCK, 0, 0); - + ts->id = id; ts->name = name; - + /* lock the file region to work on */ if (!(ts->fs->flags & J_NOLOCK)) plockf(ts->fs->fd, F_LOCK, ts->offset, ts->len); - + /* read the current content and fill in the transaction structure */ ts->pdata = malloc(ts->len); if (ts->pdata == NULL) goto exit; - + ts->plen = ts->len; rv = spread(ts->fs->fd, ts->pdata, ts->len, ts->offset); @@ -312,38 +233,38 @@ int jtrans_commit(struct jtrans *ts) } /* now save the transaction to the file, static data first */ - + buf_init = malloc(J_DISKTFIXSIZE); if (buf_init == NULL) return -1; - + bufp = buf_init; - + memcpy(bufp, (void *) &(ts->id), sizeof(ts->id)); bufp += 4; - + memcpy(bufp, (void *) &(ts->flags), sizeof(ts->flags)); bufp += 4; - + memcpy(bufp, (void *) &(ts->len), sizeof(ts->len)); bufp += 4; - + memcpy(bufp, (void *) &(ts->plen), sizeof(ts->plen)); bufp += 4; - + memcpy(bufp, (void *) &(ts->ulen), sizeof(ts->ulen)); bufp += 4; - + memcpy(bufp, (void *) &(ts->offset), sizeof(ts->offset)); bufp += 8; - + rv = spwrite(fd, buf_init, J_DISKTFIXSIZE, 0); if (rv != J_DISKTFIXSIZE) goto exit; - + free(buf_init); - - + + /* and now the variable data */ if (ts->udata) { @@ -351,24 +272,24 @@ int jtrans_commit(struct jtrans *ts) if (rv != ts->ulen) goto exit; } - + t = J_DISKTFIXSIZE + ts->ulen; rv = spwrite(fd, ts->pdata, ts->plen, t); if (rv != ts->plen) goto exit; - + /* this is a simple but efficient optimization: instead of doing * everything O_SYNC, we sync at this point only, this way we avoid * doing a lot of very small writes; in case of a crash the * transaction file is only useful if it's complete (ie. after this * point) so we only flush here */ fsync(fd); - + /* now that we have a safe transaction file, let's apply it */ rv = spwrite(ts->fs->fd, ts->buf, ts->len, ts->offset); if (rv != ts->len) goto exit; - + /* the transaction has been applied, so we cleanup and remove it from * the disk */ free_tid(ts->fs, ts->id); @@ -377,13 +298,13 @@ int jtrans_commit(struct jtrans *ts) /* mark the transaction as commited, _after_ it was removed */ ts->flags = ts->flags | J_COMMITED; - + exit: close(fd); - + if (!(ts->fs->flags & J_NOLOCK)) plockf(ts->fs->fd, F_ULOCK, ts->offset, ts->len); - + /* return the lenght only if it was properly commited */ if (ts->flags & J_COMMITED) return ts->len; @@ -406,7 +327,7 @@ int jtrans_rollback(struct jtrans *ts) newts.buf = ts->pdata; newts.len = ts->plen; - + if (ts->plen < ts->len) { /* we extended the data in the previous transaction, so we * should truncate it back */ @@ -416,15 +337,15 @@ int jtrans_rollback(struct jtrans *ts) * extended the file further, this will cut it back to what it * was; read the docs for more detail */ ftruncate(ts->fs->fd, ts->offset + ts->plen); - + } - + newts.pdata = ts->pdata; newts.plen = ts->plen; newts.udata = ts->udata; newts.ulen = ts->ulen; - + rv = jtrans_commit(&newts); return rv; } @@ -448,7 +369,7 @@ int jopen(struct jfs *fs, const char *name, int flags, int mode, int jflags) flags = flags & ~O_WRONLY; flags = flags | O_RDWR; } - + fd = open(name, flags, mode); if (fd < 0) return -1; @@ -456,7 +377,7 @@ int jopen(struct jfs *fs, const char *name, int flags, int mode, int jflags) fs->fd = fd; fs->name = strdup(name); fs->flags = jflags; - + /* Note on fs->lock usage: this lock is used only inside the wrappers, * and exclusively to protect the file pointer. This means that it * must only be held while performing operations that depend or alter @@ -469,7 +390,7 @@ int jopen(struct jfs *fs, const char *name, int flags, int mode, int jflags) * make it easier for them by taking care of it here. If performance * is essential, the jpread/jpwrite functions should be used, just as * real life. */ - + pthread_mutex_init( &(fs->lock), NULL); if (!get_jdir(name, jdir)) @@ -478,12 +399,12 @@ int jopen(struct jfs *fs, const char *name, int flags, int mode, int jflags) rv = lstat(jdir, &sinfo); if (rv < 0 || !S_ISDIR(sinfo.st_mode)) return -1; - + snprintf(jlockfile, PATH_MAX, "%s/%s", jdir, "lock"); jfd = open(jlockfile, O_RDWR | O_CREAT, 0600); if (jfd < 0) return -1; - + /* initialize the lock file by writing the first tid to it, but only * if its empty, otherwise there is a race if two processes call * jopen() simultaneously and both initialize the file */ @@ -504,181 +425,6 @@ int jopen(struct jfs *fs, const char *name, int flags, int mode, int jflags) return fd; } - -/* read() family wrappers */ - -/* read wrapper */ -ssize_t jread(struct jfs *fs, void *buf, size_t count) -{ - int rv; - off_t pos; - - pthread_mutex_lock(&(fs->lock)); - - pos = lseek(fs->fd, 0, SEEK_CUR); - - plockf(fs->fd, F_LOCK, pos, count); - rv = spread(fs->fd, buf, count, pos); - plockf(fs->fd, F_ULOCK, pos, count); - - if (rv == count) { - /* if success, advance the file pointer */ - lseek(fs->fd, count, SEEK_CUR); - } - - pthread_mutex_unlock(&(fs->lock)); - - return rv; -} - -/* pread wrapper */ -ssize_t jpread(struct jfs *fs, void *buf, size_t count, off_t offset) -{ - int rv; - - plockf(fs->fd, F_LOCK, offset, count); - rv = spread(fs->fd, buf, count, offset); - plockf(fs->fd, F_ULOCK, offset, count); - - return rv; -} - -/* readv wrapper */ -ssize_t jreadv(struct jfs *fs, struct iovec *vector, int count) -{ - int rv, i; - size_t sum; - off_t pos; - - sum = 0; - for (i = 0; i < count; i++) - sum += vector[i].iov_len; - - pthread_mutex_lock(&(fs->lock)); - pos = lseek(fs->fd, 0, SEEK_CUR); - plockf(fs->fd, F_LOCK, pos, count); - rv = readv(fs->fd, vector, count); - plockf(fs->fd, F_ULOCK, pos, count); - pthread_mutex_unlock(&(fs->lock)); - - return rv; -} - -/* write wrapper */ -ssize_t jwrite(struct jfs *fs, const void *buf, size_t count) -{ - int rv; - off_t pos; - struct jtrans ts; - - pthread_mutex_lock(&(fs->lock)); - - jtrans_init(fs, &ts); - pos = lseek(fs->fd, 0, SEEK_CUR); - ts.offset = pos; - - ts.buf = buf; - ts.len = count; - - rv = jtrans_commit(&ts); - - if (rv >= 0) { - /* if success, advance the file pointer */ - lseek(fs->fd, count, SEEK_CUR); - } - - pthread_mutex_unlock(&(fs->lock)); - - jtrans_free(&ts); - - return rv; -} - -/* write family wrappers */ - -/* pwrite wrapper */ -ssize_t jpwrite(struct jfs *fs, const void *buf, size_t count, off_t offset) -{ - int rv; - struct jtrans ts; - - jtrans_init(fs, &ts); - ts.offset = offset; - - ts.buf = buf; - ts.len = count; - - rv = jtrans_commit(&ts); - - jtrans_free(&ts); - - return rv; -} - -/* writev wrapper */ -ssize_t jwritev(struct jfs *fs, const struct iovec *vector, int count) -{ - int rv, i, bufp; - ssize_t sum; - char *buf; - off_t pos; - struct jtrans ts; - - sum = 0; - for (i = 0; i < count; i++) - sum += vector[i].iov_len; - - /* unify the buffers into one big chunk to commit */ - /* FIXME: can't we do this more efficient? It ruins the whole purpose - * of using writev() :\ - * maybe we should do one transaction per vector */ - buf = malloc(sum); - if (buf == NULL) - return -1; - bufp = 0; - - for (i = 0; i < count; i++) { - memcpy(buf + bufp, vector[i].iov_base, vector[i].iov_len); - bufp += vector[i].iov_len; - } - - pthread_mutex_lock(&(fs->lock)); - - jtrans_init(fs, &ts); - pos = lseek(fs->fd, 0, SEEK_CUR); - ts.offset = pos; - - ts.buf = buf; - ts.len = sum; - - rv = jtrans_commit(&ts); - - if (rv >= 0) { - /* if success, advance the file pointer */ - lseek(fs->fd, count, SEEK_CUR); - } - - pthread_mutex_unlock(&(fs->lock)); - - jtrans_free(&ts); - - return rv; - -} - -/* truncate a file - be careful with this */ -int jtruncate(struct jfs *fs, off_t lenght) -{ - int rv; - - /* lock from lenght to the end of file */ - plockf(fs->fd, F_LOCK, lenght, 0); - rv = ftruncate(fs->fd, lenght); - plockf(fs->fd, F_ULOCK, lenght, 0); - - return rv; -} - /* close a file */ int jclose(struct jfs *fs) { @@ -709,7 +455,7 @@ int jfsck(const char *name, struct jfsck_result *res) DIR *dir; struct dirent *dent; off_t offset; - + fd = open(name, O_RDWR | O_SYNC | O_LARGEFILE); if (fd < 0) return J_ENOENT; @@ -722,7 +468,7 @@ int jfsck(const char *name, struct jfsck_result *res) rv = lstat(jdir, &sinfo); if (rv < 0 || !S_ISDIR(sinfo.st_mode)) return J_ENOJOURNAL; - + /* open the lock file, which is only used to complete the jfs * structure */ snprintf(jlockfile, PATH_MAX, "%s/%s", jdir, "lock"); @@ -730,7 +476,7 @@ int jfsck(const char *name, struct jfsck_result *res) if (rv < 0) return J_ENOJOURNAL; fs.jfd = rv; - + dir = opendir(jdir); if (dir == NULL) return J_ENOJOURNAL; @@ -762,10 +508,10 @@ int jfsck(const char *name, struct jfsck_result *res) curts = malloc(sizeof(struct jtrans)); if (curts == NULL) return J_ENOMEM; - + jtrans_init(&fs, curts); curts->id = i; - + /* open the transaction file, using i as its name, so we are * really looping in order (recovering transaction in a * different order as they were applied means instant @@ -785,14 +531,14 @@ int jfsck(const char *name, struct jfsck_result *res) res->in_progress++; goto loop; } - + /* load from disk, header first */ buf = (unsigned char *) malloc(J_DISKTFIXSIZE); if (buf == NULL) { res->load_error++; goto loop; } - + rv = read(tfd, buf, J_DISKTFIXSIZE); if (rv != J_DISKTFIXSIZE) { res->broken_head++; @@ -830,7 +576,7 @@ int jfsck(const char *name, struct jfsck_result *res) res->load_error++; goto loop; } - + curts->udata = malloc(curts->ulen); if (curts->udata == NULL) { res->load_error++; diff --git a/unix.c b/unix.c new file mode 100644 index 0000000..2d82655 --- /dev/null +++ b/unix.c @@ -0,0 +1,191 @@ + +/* + * libjio - A library for Journaled I/O + * Alberto Bertogli (albertogli@telpin.com.ar) + * + * UNIX API wrappers + */ + +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <unistd.h> + +#include "libjio.h" +#include "common.h" + + +/* read() family wrappers */ + +/* read wrapper */ +ssize_t jread(struct jfs *fs, void *buf, size_t count) +{ + int rv; + off_t pos; + + pthread_mutex_lock(&(fs->lock)); + + pos = lseek(fs->fd, 0, SEEK_CUR); + + plockf(fs->fd, F_LOCK, pos, count); + rv = spread(fs->fd, buf, count, pos); + plockf(fs->fd, F_ULOCK, pos, count); + + if (rv == count) { + /* if success, advance the file pointer */ + lseek(fs->fd, count, SEEK_CUR); + } + + pthread_mutex_unlock(&(fs->lock)); + + return rv; +} + +/* pread wrapper */ +ssize_t jpread(struct jfs *fs, void *buf, size_t count, off_t offset) +{ + int rv; + + plockf(fs->fd, F_LOCK, offset, count); + rv = spread(fs->fd, buf, count, offset); + plockf(fs->fd, F_ULOCK, offset, count); + + return rv; +} + +/* readv wrapper */ +ssize_t jreadv(struct jfs *fs, struct iovec *vector, int count) +{ + int rv, i; + size_t sum; + off_t pos; + + sum = 0; + for (i = 0; i < count; i++) + sum += vector[i].iov_len; + + pthread_mutex_lock(&(fs->lock)); + pos = lseek(fs->fd, 0, SEEK_CUR); + plockf(fs->fd, F_LOCK, pos, count); + rv = readv(fs->fd, vector, count); + plockf(fs->fd, F_ULOCK, pos, count); + pthread_mutex_unlock(&(fs->lock)); + + return rv; +} + + +/* write family wrappers */ + +/* write wrapper */ +ssize_t jwrite(struct jfs *fs, const void *buf, size_t count) +{ + int rv; + off_t pos; + struct jtrans ts; + + pthread_mutex_lock(&(fs->lock)); + + jtrans_init(fs, &ts); + pos = lseek(fs->fd, 0, SEEK_CUR); + ts.offset = pos; + + ts.buf = buf; + ts.len = count; + + rv = jtrans_commit(&ts); + + if (rv >= 0) { + /* if success, advance the file pointer */ + lseek(fs->fd, count, SEEK_CUR); + } + + pthread_mutex_unlock(&(fs->lock)); + + jtrans_free(&ts); + + return rv; +} + +/* pwrite wrapper */ +ssize_t jpwrite(struct jfs *fs, const void *buf, size_t count, off_t offset) +{ + int rv; + struct jtrans ts; + + jtrans_init(fs, &ts); + ts.offset = offset; + + ts.buf = buf; + ts.len = count; + + rv = jtrans_commit(&ts); + + jtrans_free(&ts); + + return rv; +} + +/* writev wrapper */ +ssize_t jwritev(struct jfs *fs, const struct iovec *vector, int count) +{ + int rv, i, bufp; + ssize_t sum; + char *buf; + off_t pos; + struct jtrans ts; + + sum = 0; + for (i = 0; i < count; i++) + sum += vector[i].iov_len; + + /* unify the buffers into one big chunk to commit */ + /* FIXME: can't we do this more efficient? It ruins the whole purpose + * of using writev()! maybe we should do one transaction per vector */ + buf = malloc(sum); + if (buf == NULL) + return -1; + bufp = 0; + + for (i = 0; i < count; i++) { + memcpy(buf + bufp, vector[i].iov_base, vector[i].iov_len); + bufp += vector[i].iov_len; + } + + pthread_mutex_lock(&(fs->lock)); + + jtrans_init(fs, &ts); + pos = lseek(fs->fd, 0, SEEK_CUR); + ts.offset = pos; + + ts.buf = buf; + ts.len = sum; + + rv = jtrans_commit(&ts); + + if (rv >= 0) { + /* if success, advance the file pointer */ + lseek(fs->fd, count, SEEK_CUR); + } + + pthread_mutex_unlock(&(fs->lock)); + + jtrans_free(&ts); + + return rv; + +} + +/* truncate a file - be careful with this */ +int jtruncate(struct jfs *fs, off_t lenght) +{ + int rv; + + /* lock from lenght to the end of file */ + plockf(fs->fd, F_LOCK, lenght, 0); + rv = ftruncate(fs->fd, lenght); + plockf(fs->fd, F_ULOCK, lenght, 0); + + return rv; +} +