NetBSD/share/examples/puffs/pgfs/pgfs_puffs.c

1255 lines
28 KiB
C

/* $NetBSD: pgfs_puffs.c,v 1.4 2012/04/11 14:26:44 yamt Exp $ */
/*-
* Copyright (c)2010,2011 YAMAMOTO Takashi,
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
/*
* puffs node ops and fs ops.
*/
#include <sys/cdefs.h>
#ifndef lint
__RCSID("$NetBSD: pgfs_puffs.c,v 1.4 2012/04/11 14:26:44 yamt Exp $");
#endif /* not lint */
#include <assert.h>
#include <err.h>
#include <errno.h>
#include <puffs.h>
#include <inttypes.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <util.h>
#include <libpq-fe.h>
#include <libpq/libpq-fs.h> /* INV_* */
#include "pgfs.h"
#include "pgfs_db.h"
#include "pgfs_subs.h"
#include "pgfs_debug.h"
static fileid_t
cookie_to_fileid(puffs_cookie_t cookie)
{
return (fileid_t)(uintptr_t)cookie;
}
static puffs_cookie_t
fileid_to_cookie(fileid_t id)
{
puffs_cookie_t cookie = (puffs_cookie_t)(uintptr_t)id;
/* XXX not true for 32-bit ports */
assert(cookie_to_fileid(cookie) == id);
return cookie;
}
puffs_cookie_t
pgfs_root_cookie(void)
{
return fileid_to_cookie(PGFS_ROOT_FILEID);
}
int
pgfs_node_getattr(struct puffs_usermount *pu, puffs_cookie_t opc,
struct vattr *va, const struct puffs_cred *pcr)
{
struct Xconn *xc;
struct fileid_lock_handle *lock;
fileid_t fileid = cookie_to_fileid(opc);
int error;
DPRINTF("%llu\n", fileid);
lock = fileid_lock(fileid, puffs_cc_getcc(pu));
retry:
xc = begin_readonly(pu, "getattr");
error = getattr(xc, fileid, va, GETATTR_ALL);
if (error != 0) {
goto got_error;
}
error = commit(xc);
if (error != 0) {
goto got_error;
}
goto done;
got_error:
rollback(xc);
if (error == EAGAIN) {
goto retry;
}
done:
fileid_unlock(lock);
return error;
}
#define PGFS_DIRCOOKIE_DOT 0 /* . entry */
#define PGFS_DIRCOOKIE_DOTDOT 1 /* .. entry */
#define PGFS_DIRCOOKIE_EOD 2 /* end of directory */
int
pgfs_node_readdir(struct puffs_usermount *pu, puffs_cookie_t opc,
struct dirent *dent, off_t *readoff, size_t *reslen,
const struct puffs_cred *pcr, int *eofflag, off_t *cookies,
size_t *ncookies)
{
fileid_t parent_fileid;
fileid_t child_fileid;
uint64_t cookie;
uint64_t nextcookie;
uint64_t offset;
struct Xconn *xc = NULL;
static const Oid types[] = {
TEXTOID, /* name */
INT8OID, /* cookie */
INT8OID, /* nextcookie */
INT8OID, /* child_fileid */
};
const char *name;
char *nametofree = NULL;
struct fetchstatus s;
int error;
bool fetching;
bool bufferfull;
parent_fileid = cookie_to_fileid(opc);
offset = *readoff;
DPRINTF("%llu %" PRIu64 "\n", parent_fileid, offset);
*ncookies = 0;
fetching = false;
next:
if (offset == PGFS_DIRCOOKIE_DOT) {
name = ".";
child_fileid = parent_fileid;
cookie = offset;
nextcookie = PGFS_DIRCOOKIE_DOTDOT;
goto store_and_next;
}
if (offset == PGFS_DIRCOOKIE_DOTDOT) {
if (parent_fileid != PGFS_ROOT_FILEID) {
if (xc == NULL) {
xc = begin(pu, "readdir1");
}
error = lookupp(xc, parent_fileid, &child_fileid);
if (error != 0) {
rollback(xc);
return error;
}
} else {
child_fileid = parent_fileid;
}
name = "..";
cookie = offset;
nextcookie = PGFS_DIRCOOKIE_EOD + 1;
goto store_and_next;
}
if (offset == PGFS_DIRCOOKIE_EOD) {
*eofflag = 1;
goto done;
}
/* offset > PGFS_DIRCOOKIE_EOD; normal entries */
if (xc == NULL) {
xc = begin(pu, "readdir2");
}
if (!fetching) {
static struct cmd *c;
/*
* a simpler query like "ORDER BY name OFFSET :offset - 3"
* would work well for most of cases. however, it doesn't for
* applications which expect readdir cookies are kept valid
* even after unlink of other entries in the directory.
* eg. cvs, bonnie++
*
* 2::int8 == PGFS_DIRCOOKIE_EOD
*/
CREATECMD(c,
"SELECT name, cookie, "
"lead(cookie, 1, 2::int8) OVER (ORDER BY cookie), "
"child_fileid "
"FROM dirent "
"WHERE parent_fileid = $1 "
"AND cookie >= $2 "
"ORDER BY cookie", INT8OID, INT8OID);
error = sendcmd(xc, c, parent_fileid, offset);
if (error != 0) {
rollback(xc);
return error;
}
fetching = true;
fetchinit(&s, xc);
}
/*
* fetch and process an entry
*/
error = FETCHNEXT(&s, types, &nametofree, &cookie, &nextcookie,
&child_fileid);
if (error == ENOENT) {
DPRINTF("ENOENT\n");
if (offset == PGFS_DIRCOOKIE_EOD + 1) {
DPRINTF("empty directory\n");
*eofflag = 1;
goto done;
}
fetchdone(&s);
rollback(xc);
return EINVAL;
}
if (error != 0) {
DPRINTF("error %d\n", error);
fetchdone(&s);
rollback(xc);
return error;
}
if (offset != cookie && offset != PGFS_DIRCOOKIE_EOD + 1) {
free(nametofree);
fetchdone(&s);
rollback(xc);
return EINVAL;
}
name = nametofree;
store_and_next:
/*
* store an entry and continue processing unless the result buffer
* is full.
*/
bufferfull = !puffs_nextdent(&dent, name, child_fileid, DT_UNKNOWN,
reslen);
free(nametofree);
nametofree = NULL;
if (bufferfull) {
*eofflag = 0;
goto done;
}
PUFFS_STORE_DCOOKIE(cookies, ncookies, cookie);
offset = nextcookie;
*readoff = offset;
goto next;
done:
/*
* cleanup and update atime of the directory.
*/
assert(nametofree == NULL);
if (fetching) {
fetchdone(&s);
fetching = false;
}
if (xc == NULL) {
retry:
xc = begin(pu, "readdir3");
}
error = update_atime(xc, parent_fileid);
if (error != 0) {
goto got_error;
}
error = commit(xc);
if (error != 0) {
goto got_error;
}
return 0;
got_error:
rollback(xc);
if (error == EAGAIN) {
goto retry;
}
return error;
}
int
pgfs_node_lookup(struct puffs_usermount *pu, puffs_cookie_t opc,
struct puffs_newinfo *pni, const struct puffs_cn *pcn)
{
struct vattr dva;
struct vattr cva;
struct puffs_cred * const pcr = pcn->pcn_cred;
fileid_t parent_fileid;
const char *name;
fileid_t child_fileid;
struct Xconn *xc;
mode_t access_mode;
int error;
int saved_error;
parent_fileid = cookie_to_fileid(opc);
name = pcn->pcn_name;
DPRINTF("%llu %s\n", parent_fileid, name);
assert(strcmp(name, ".")); /* . is handled by framework */
retry:
xc = begin_readonly(pu, "lookup");
error = getattr(xc, parent_fileid, &dva,
GETATTR_TYPE|GETATTR_MODE|GETATTR_UID|GETATTR_GID);
if (error != 0) {
goto got_error;
}
access_mode = PUFFS_VEXEC;
if ((pcn->pcn_flags & NAMEI_ISLASTCN) != 0 &&
pcn->pcn_nameiop != NAMEI_LOOKUP) {
access_mode |= PUFFS_VWRITE;
}
error = puffs_access(dva.va_type, dva.va_mode, dva.va_uid, dva.va_gid,
access_mode, pcr);
if (error != 0) {
goto commit_and_return;
}
if (!strcmp(name, "..")) {
error = lookupp(xc, parent_fileid, &child_fileid);
if (error != 0) {
goto got_error;
}
} else {
static struct cmd *c;
static const Oid types[] = { INT8OID, };
struct fetchstatus s;
CREATECMD(c, "SELECT child_fileid "
"FROM dirent "
"WHERE parent_fileid = $1 AND name = $2",
INT8OID, TEXTOID);
error = sendcmd(xc, c, parent_fileid, name);
if (error != 0) {
DPRINTF("sendcmd %d\n", error);
goto got_error;
}
fetchinit(&s, xc);
error = FETCHNEXT(&s, types, &child_fileid);
fetchdone(&s);
if (error == ENOENT) {
goto commit_and_return;
}
if (error != 0) {
goto got_error;
}
}
error = getattr(xc, child_fileid, &cva, GETATTR_TYPE|GETATTR_SIZE);
if (error != 0) {
goto got_error;
}
error = commit(xc);
if (error != 0) {
goto got_error;
}
puffs_newinfo_setcookie(pni, fileid_to_cookie(child_fileid));
puffs_newinfo_setvtype(pni, cva.va_type);
puffs_newinfo_setsize(pni, cva.va_size);
return 0;
got_error:
rollback(xc);
if (error == EAGAIN) {
goto retry;
}
return error;
commit_and_return:
saved_error = error;
error = commit(xc);
if (error != 0) {
goto got_error;
}
return saved_error;
}
int
pgfs_node_mkdir(struct puffs_usermount *pu, puffs_cookie_t opc,
struct puffs_newinfo *pni, const struct puffs_cn *pcn,
const struct vattr *va)
{
struct Xconn *xc;
fileid_t parent_fileid = cookie_to_fileid(opc);
fileid_t new_fileid;
struct puffs_cred * const pcr = pcn->pcn_cred;
uid_t uid;
gid_t gid;
int error;
DPRINTF("%llu %s\n", parent_fileid, pcn->pcn_name);
if (puffs_cred_getuid(pcr, &uid) == -1 ||
puffs_cred_getgid(pcr, &gid) == -1) {
return errno;
}
retry:
xc = begin(pu, "mkdir");
error = mklinkfile(xc, parent_fileid, pcn->pcn_name, VDIR,
va->va_mode, uid, gid, &new_fileid);
if (error == 0) {
error = update_nlink(xc, parent_fileid, 1);
}
if (error != 0) {
goto got_error;
}
error = commit(xc);
if (error != 0) {
goto got_error;
}
puffs_newinfo_setcookie(pni, fileid_to_cookie(new_fileid));
return 0;
got_error:
rollback(xc);
if (error == EAGAIN) {
goto retry;
}
return error;
}
int
pgfs_node_create(struct puffs_usermount *pu, puffs_cookie_t opc,
struct puffs_newinfo *pni, const struct puffs_cn *pcn,
const struct vattr *va)
{
struct Xconn *xc;
fileid_t parent_fileid = cookie_to_fileid(opc);
fileid_t new_fileid;
struct puffs_cred * const pcr = pcn->pcn_cred;
uid_t uid;
gid_t gid;
int error;
DPRINTF("%llu %s\n", parent_fileid, pcn->pcn_name);
if (puffs_cred_getuid(pcr, &uid) == -1 ||
puffs_cred_getgid(pcr, &gid) == -1) {
return errno;
}
retry:
xc = begin(pu, "create");
error = mklinkfile_lo(xc, parent_fileid, pcn->pcn_name, VREG,
va->va_mode,
uid, gid, &new_fileid, NULL);
if (error != 0) {
goto got_error;
}
error = commit(xc);
if (error != 0) {
goto got_error;
}
puffs_newinfo_setcookie(pni, fileid_to_cookie(new_fileid));
return 0;
got_error:
rollback(xc);
if (error == EAGAIN) {
goto retry;
}
return error;
}
int
pgfs_node_write(struct puffs_usermount *pu, puffs_cookie_t opc,
uint8_t *buf, off_t offset, size_t *resid,
const struct puffs_cred *pcr, int ioflags)
{
struct Xconn *xc;
struct fileid_lock_handle *lock;
fileid_t fileid = cookie_to_fileid(opc);
size_t resultlen;
int fd;
int error;
if ((ioflags & PUFFS_IO_APPEND) != 0) {
DPRINTF("%llu append sz %zu\n", fileid, *resid);
} else {
DPRINTF("%llu off %" PRIu64 " sz %zu\n", fileid,
(uint64_t)offset, *resid);
}
lock = fileid_lock(fileid, puffs_cc_getcc(pu));
retry:
xc = begin(pu, "write");
error = update_mctime(xc, fileid);
if (error != 0) {
goto got_error;
}
error = lo_open_by_fileid(xc, fileid, INV_WRITE, &fd);
if (error != 0) {
goto got_error;
}
if ((ioflags & PUFFS_IO_APPEND) != 0) {
int32_t off;
error = my_lo_lseek(xc, fd, 0, SEEK_END, &off);
if (error != 0) {
goto got_error;
}
offset = off;
}
if (offset < 0) { /* negative offset */
error = EINVAL;
goto got_error;
}
if ((uint64_t)(INT64_MAX - offset) < *resid || /* int64 overflow */
INT_MAX < offset + *resid) { /* our max filesize */
error = EFBIG;
goto got_error;
}
if ((ioflags & PUFFS_IO_APPEND) == 0) {
error = my_lo_lseek(xc, fd, offset, SEEK_SET, NULL);
if (error != 0) {
goto got_error;
}
}
error = my_lo_write(xc, fd, (const char *)buf, *resid, &resultlen);
if (error != 0) {
goto got_error;
}
assert(*resid >= resultlen);
error = commit(xc);
if (error != 0) {
goto got_error;
}
*resid -= resultlen;
DPRINTF("resid %zu\n", *resid);
goto done;
got_error:
rollback(xc);
if (error == EAGAIN) {
goto retry;
}
done:
fileid_unlock(lock);
return error;
}
int
pgfs_node_read(struct puffs_usermount *pu, puffs_cookie_t opc,
uint8_t *buf, off_t offset, size_t *resid,
const struct puffs_cred *pcr, int ioflags)
{
struct Xconn *xc;
fileid_t fileid = cookie_to_fileid(opc);
size_t resultlen;
int fd;
int error;
DPRINTF("%llu off %" PRIu64 " sz %zu\n",
fileid, (uint64_t)offset, *resid);
retry:
xc = begin(pu, "read");
/*
* try to update atime first as it's prune to conflict with other
* transactions. eg. read-ahead requests can conflict each other.
* we don't want to retry my_lo_read as it's expensive.
*
* XXX probably worth to implement noatime mount option.
*/
error = update_atime(xc, fileid);
if (error != 0) {
goto got_error;
}
error = lo_open_by_fileid(xc, fileid, INV_READ, &fd);
if (error != 0) {
goto got_error;
}
error = my_lo_lseek(xc, fd, offset, SEEK_SET, NULL);
if (error != 0) {
goto got_error;
}
error = my_lo_read(xc, fd, buf, *resid, &resultlen);
if (error != 0) {
goto got_error;
}
assert(*resid >= resultlen);
error = commit(xc);
if (error != 0) {
goto got_error;
}
*resid -= resultlen;
return 0;
got_error:
rollback(xc);
if (error == EAGAIN) {
goto retry;
}
return error;
}
int
pgfs_node_link(struct puffs_usermount *pu, puffs_cookie_t dir_opc,
puffs_cookie_t targ_opc, const struct puffs_cn *pcn)
{
struct Xconn *xc;
fileid_t dir_fileid = cookie_to_fileid(dir_opc);
fileid_t targ_fileid = cookie_to_fileid(targ_opc);
struct vattr va;
int error;
DPRINTF("%llu %llu %s\n", dir_fileid, targ_fileid, pcn->pcn_name);
retry:
xc = begin(pu, "link");
error = getattr(xc, targ_fileid, &va, GETATTR_TYPE);
if (error != 0) {
goto got_error;
}
if (va.va_type == VDIR) {
error = EPERM;
goto got_error;
}
error = linkfile(xc, dir_fileid, pcn->pcn_name, targ_fileid);
if (error != 0) {
goto got_error;
}
error = update_ctime(xc, targ_fileid);
if (error != 0) {
goto got_error;
}
error = commit(xc);
if (error != 0) {
goto got_error;
}
return 0;
got_error:
rollback(xc);
if (error == EAGAIN) {
goto retry;
}
return error;
}
int
pgfs_node_remove(struct puffs_usermount *pu, puffs_cookie_t opc,
puffs_cookie_t targ, const struct puffs_cn *pcn)
{
struct Xconn *xc;
fileid_t fileid = cookie_to_fileid(opc);
fileid_t targ_fileid = cookie_to_fileid(targ);
struct vattr va;
int error;
retry:
xc = begin(pu, "remove");
error = getattr(xc, targ_fileid, &va, GETATTR_TYPE);
if (error != 0) {
goto got_error;
}
if (va.va_type == VDIR) {
error = EPERM;
goto got_error;
}
error = unlinkfile(xc, fileid, pcn->pcn_name, targ_fileid);
if (error != 0) {
goto got_error;
}
error = commit(xc);
if (error != 0) {
goto got_error;
}
puffs_setback(puffs_cc_getcc(pu), PUFFS_SETBACK_INACT_N2);
return 0;
got_error:
rollback(xc);
if (error == EAGAIN) {
goto retry;
}
return error;
}
int
pgfs_node_rmdir(struct puffs_usermount *pu, puffs_cookie_t opc,
puffs_cookie_t targ, const struct puffs_cn *pcn)
{
struct Xconn *xc;
fileid_t parent_fileid = cookie_to_fileid(opc);
fileid_t targ_fileid = cookie_to_fileid(targ);
struct vattr va;
bool empty;
int error;
retry:
xc = begin(pu, "rmdir");
error = getattr(xc, targ_fileid, &va, GETATTR_TYPE);
if (error != 0) {
goto got_error;
}
if (va.va_type != VDIR) {
error = ENOTDIR;
goto got_error;
}
error = isempty(xc, targ_fileid, &empty);
if (error != 0) {
goto got_error;
}
if (!empty) {
error = ENOTEMPTY;
goto got_error;
}
error = unlinkfile(xc, parent_fileid, pcn->pcn_name, targ_fileid);
if (error == 0) {
error = update_nlink(xc, parent_fileid, -1);
}
if (error != 0) {
goto got_error;
}
error = commit(xc);
if (error != 0) {
goto got_error;
}
puffs_setback(puffs_cc_getcc(pu), PUFFS_SETBACK_INACT_N2);
return 0;
got_error:
rollback(xc);
if (error == EAGAIN) {
goto retry;
}
return error;
}
int
pgfs_node_inactive(struct puffs_usermount *pu, puffs_cookie_t opc)
{
struct Xconn *xc;
fileid_t fileid = cookie_to_fileid(opc);
int error;
/*
* XXX
* probably this should be handed to the separate "reaper" context
* because lo_unlink() can be too expensive to execute synchronously.
* however, the puffs_cc API doesn't provide a way to create a worker
* context.
*/
DPRINTF("%llu\n", fileid);
retry:
xc = begin(pu, "inactive");
error = cleanupfile(xc, fileid);
if (error != 0) {
goto got_error;
}
error = commit(xc);
if (error != 0) {
goto got_error;
}
return 0;
got_error:
rollback(xc);
if (error == EAGAIN) {
goto retry;
}
return error;
}
int
pgfs_node_setattr(struct puffs_usermount *pu, puffs_cookie_t opc,
const struct vattr *va, const struct puffs_cred *pcr)
{
struct Xconn *xc;
struct fileid_lock_handle *lock;
fileid_t fileid = cookie_to_fileid(opc);
struct vattr ova;
unsigned int attrs;
int error;
DPRINTF("%llu\n", fileid);
if (va->va_flags != (u_long)PUFFS_VNOVAL) {
return EOPNOTSUPP;
}
attrs = 0;
if (va->va_uid != (uid_t)PUFFS_VNOVAL ||
va->va_gid != (gid_t)PUFFS_VNOVAL) {
attrs |= GETATTR_UID|GETATTR_GID|GETATTR_MODE;
}
if (va->va_mode != (mode_t)PUFFS_VNOVAL) {
attrs |= GETATTR_TYPE|GETATTR_UID|GETATTR_GID;
}
if (va->va_atime.tv_sec != PUFFS_VNOVAL ||
va->va_mtime.tv_sec != PUFFS_VNOVAL ||
va->va_ctime.tv_sec != PUFFS_VNOVAL) {
attrs |= GETATTR_UID|GETATTR_GID|GETATTR_MODE;
}
lock = fileid_lock(fileid, puffs_cc_getcc(pu));
retry:
xc = begin(pu, "setattr");
error = getattr(xc, fileid, &ova, attrs);
if (error != 0) {
goto got_error;
}
if (va->va_uid != (uid_t)PUFFS_VNOVAL ||
va->va_gid != (gid_t)PUFFS_VNOVAL) {
static struct cmd *c;
uint64_t newuid =
va->va_uid != (uid_t)PUFFS_VNOVAL ? va->va_uid : ova.va_uid;
uint64_t newgid =
va->va_gid != (gid_t)PUFFS_VNOVAL ? va->va_gid : ova.va_gid;
error = puffs_access_chown(ova.va_uid, ova.va_gid,
newuid, newgid, pcr);
if (error != 0) {
goto got_error;
}
CREATECMD(c,
"UPDATE file "
"SET uid = $1, gid = $2 "
"WHERE fileid = $3", INT8OID, INT8OID, INT8OID);
error = simplecmd(xc, c, newuid, newgid, fileid);
if (error != 0) {
goto got_error;
}
ova.va_uid = newuid;
ova.va_gid = newgid;
}
if (va->va_mode != (mode_t)PUFFS_VNOVAL) {
static struct cmd *c;
uint64_t newmode = va->va_mode;
error = puffs_access_chmod(ova.va_uid, ova.va_gid, ova.va_type,
newmode, pcr);
if (error != 0) {
goto got_error;
}
CREATECMD(c,
"UPDATE file "
"SET mode = $1 "
"WHERE fileid = $2", INT8OID, INT8OID);
error = simplecmd(xc, c, newmode, fileid);
if (error != 0) {
goto got_error;
}
ova.va_mode = newmode;
}
if (va->va_atime.tv_sec != PUFFS_VNOVAL ||
va->va_mtime.tv_sec != PUFFS_VNOVAL ||
va->va_ctime.tv_sec != PUFFS_VNOVAL ||
va->va_birthtime.tv_sec != PUFFS_VNOVAL) {
error = puffs_access_times(ova.va_uid, ova.va_gid, ova.va_mode,
(va->va_vaflags & VA_UTIMES_NULL) != 0, pcr);
if (error != 0) {
goto got_error;
}
if (va->va_atime.tv_sec != PUFFS_VNOVAL) {
static struct cmd *c;
char *ts;
error = timespec_to_pgtimestamp(&va->va_atime, &ts);
if (error != 0) {
goto got_error;
}
CREATECMD(c,
"UPDATE file "
"SET atime = $1 "
"WHERE fileid = $2", TIMESTAMPTZOID, INT8OID);
error = simplecmd(xc, c, ts, fileid);
free(ts);
if (error != 0) {
goto got_error;
}
}
if (va->va_mtime.tv_sec != PUFFS_VNOVAL) {
static struct cmd *c;
char *ts;
error = timespec_to_pgtimestamp(&va->va_mtime, &ts);
if (error != 0) {
goto got_error;
}
CREATECMD(c,
"UPDATE file "
"SET mtime = $1 "
"WHERE fileid = $2", TIMESTAMPTZOID, INT8OID);
error = simplecmd(xc, c, ts, fileid);
free(ts);
if (error != 0) {
goto got_error;
}
}
if (va->va_ctime.tv_sec != PUFFS_VNOVAL) {
static struct cmd *c;
char *ts;
error = timespec_to_pgtimestamp(&va->va_ctime, &ts);
if (error != 0) {
goto got_error;
}
CREATECMD(c,
"UPDATE file "
"SET ctime = $1 "
"WHERE fileid = $2", TIMESTAMPTZOID, INT8OID);
error = simplecmd(xc, c, ts, fileid);
free(ts);
if (error != 0) {
goto got_error;
}
}
if (va->va_birthtime.tv_sec != PUFFS_VNOVAL) {
static struct cmd *c;
char *ts;
error = timespec_to_pgtimestamp(&va->va_birthtime, &ts);
if (error != 0) {
goto got_error;
}
CREATECMD(c,
"UPDATE file "
"SET btime = $1 "
"WHERE fileid = $2", TIMESTAMPTZOID, INT8OID);
error = simplecmd(xc, c, ts, fileid);
free(ts);
if (error != 0) {
goto got_error;
}
}
}
if (va->va_size != (uint64_t)PUFFS_VNOVAL) {
int fd;
if (va->va_size > INT_MAX) {
error = EFBIG;
goto got_error;
}
error = lo_open_by_fileid(xc, fileid, INV_READ|INV_WRITE, &fd);
if (error != 0) {
goto got_error;
}
error = my_lo_truncate(xc, fd, va->va_size);
if (error != 0) {
goto got_error;
}
error = my_lo_close(xc, fd);
if (error != 0) {
goto got_error;
}
}
error = commit(xc);
if (error != 0) {
goto got_error;
}
goto done;
got_error:
rollback(xc);
if (error == EAGAIN) {
goto retry;
}
done:
fileid_unlock(lock);
return error;
}
int
pgfs_node_rename(struct puffs_usermount *pu, puffs_cookie_t src_dir,
puffs_cookie_t src, const struct puffs_cn *pcn_src,
puffs_cookie_t targ_dir, puffs_cookie_t targ,
const struct puffs_cn *pcn_targ)
{
struct Xconn *xc;
fileid_t fileid_src_dir = cookie_to_fileid(src_dir);
fileid_t fileid_src = cookie_to_fileid(src);
fileid_t fileid_targ_dir = cookie_to_fileid(targ_dir);
fileid_t fileid_targ = cookie_to_fileid(targ);
struct vattr va_src;
struct vattr va_targ;
int error;
DPRINTF("%llu %llu %llu %llu\n", fileid_src_dir, fileid_src,
fileid_targ_dir, fileid_targ);
retry:
xc = begin(pu, "rename");
error = getattr(xc, fileid_src, &va_src, GETATTR_TYPE);
if (error != 0) {
goto got_error;
}
if (va_src.va_type == VDIR) {
error = check_path(xc, fileid_src, fileid_targ_dir);
if (error != 0) {
goto got_error;
}
}
if (fileid_targ != 0) {
error = getattr(xc, fileid_targ, &va_targ,
GETATTR_TYPE|GETATTR_NLINK);
if (error != 0) {
goto got_error;
}
if (va_src.va_type == VDIR) {
if (va_targ.va_type != VDIR) {
error = ENOTDIR;
goto got_error;
}
if (va_targ.va_nlink != 2) {
error = ENOTEMPTY;
goto got_error;
}
} else if (va_targ.va_type == VDIR) {
error = EISDIR;
goto got_error;
}
error = unlinkfile(xc, fileid_targ_dir, pcn_targ->pcn_name,
fileid_targ);
if (error == 0 && va_targ.va_type == VDIR) {
error = update_nlink(xc, fileid_targ_dir, -1);
}
if (error != 0) {
goto got_error;
}
}
error = linkfile(xc, fileid_targ_dir, pcn_targ->pcn_name, fileid_src);
if (error == 0 && va_src.va_type == VDIR) {
error = update_nlink(xc, fileid_targ_dir, 1);
}
if (error != 0) {
goto got_error;
}
/* XXX ctime? */
error = unlinkfile(xc, fileid_src_dir, pcn_src->pcn_name, fileid_src);
if (error == 0 && va_src.va_type == VDIR) {
error = update_nlink(xc, fileid_src_dir, -1);
}
if (error != 0) {
goto got_error;
}
error = commit(xc);
if (error != 0) {
goto got_error;
}
return 0;
got_error:
rollback(xc);
if (error == EAGAIN) {
goto retry;
}
return error;
}
int
pgfs_node_symlink(struct puffs_usermount *pu, puffs_cookie_t opc,
struct puffs_newinfo *pni, const struct puffs_cn *pcn,
const struct vattr *va, const char *target)
{
struct Xconn *xc;
struct puffs_cred *pcr = pcn->pcn_cred;
fileid_t parent_fileid = cookie_to_fileid(opc);
fileid_t new_fileid;
size_t resultlen;
size_t targetlen;
uid_t uid;
gid_t gid;
int loid;
int fd;
int error;
DPRINTF("%llu %s %s\n", parent_fileid, pcn->pcn_name, target);
if (puffs_cred_getuid(pcr, &uid) == -1 ||
puffs_cred_getgid(pcr, &gid) == -1) {
return errno;
}
retry:
xc = begin(pu, "symlink");
error = mklinkfile_lo(xc, parent_fileid, pcn->pcn_name, VLNK,
va->va_mode, uid, gid, &new_fileid, &loid);
if (error != 0) {
goto got_error;
}
error = my_lo_open(xc, loid, INV_WRITE, &fd);
if (error != 0) {
goto got_error;
}
targetlen = strlen(target);
error = my_lo_write(xc, fd, target, targetlen, &resultlen);
if (error != 0) {
goto got_error;
}
if (resultlen != targetlen) {
error = ENOSPC; /* XXX */
goto got_error;
}
error = commit(xc);
if (error != 0) {
goto got_error;
}
puffs_newinfo_setcookie(pni, fileid_to_cookie(new_fileid));
return 0;
got_error:
rollback(xc);
if (error == EAGAIN) {
goto retry;
}
return error;
}
int
pgfs_node_readlink(struct puffs_usermount *pu, puffs_cookie_t opc,
const struct puffs_cred *pcr, char *buf, size_t *buflenp)
{
fileid_t fileid = cookie_to_fileid(opc);
struct Xconn *xc;
size_t resultlen;
int fd;
int error;
DPRINTF("%llu\n", fileid);
xc = begin_readonly(pu, "readlink");
error = lo_open_by_fileid(xc, fileid, INV_READ, &fd);
if (error != 0) {
rollback(xc);
return error;
}
error = my_lo_read(xc, fd, buf, *buflenp, &resultlen);
if (error != 0) {
rollback(xc);
return error;
}
assert(resultlen <= *buflenp);
error = commit(xc);
if (error != 0) {
return error;
}
*buflenp = resultlen;
return 0;
}
int
pgfs_node_access(struct puffs_usermount *pu, puffs_cookie_t opc,
int mode, const struct puffs_cred *pcr)
{
struct Xconn *xc;
fileid_t fileid = cookie_to_fileid(opc);
struct vattr va;
int error;
DPRINTF("%llu\n", fileid);
retry:
xc = begin_readonly(pu, "access");
error = getattr(xc, fileid, &va,
GETATTR_TYPE|GETATTR_MODE|GETATTR_UID|GETATTR_GID);
if (error != 0) {
goto got_error;
}
error = commit(xc);
if (error != 0) {
goto got_error;
}
return puffs_access(va.va_type, va.va_mode, va.va_uid, va.va_gid, mode,
pcr);
got_error:
rollback(xc);
if (error == EAGAIN) {
goto retry;
}
return error;
}
int
pgfs_node_fsync(struct puffs_usermount *pu, puffs_cookie_t opc,
const struct puffs_cred *pcr, int flags, off_t offlo, off_t offhi)
{
fileid_t fileid = cookie_to_fileid(opc);
DPRINTF("%llu\n", fileid);
return flush_xacts(pu);
}
int
pgfs_fs_statvfs(struct puffs_usermount *pu, struct statvfs *sbp)
{
struct Xconn *xc;
uint64_t nfiles;
uint64_t bytes;
uint64_t lo_bytes;
static struct cmd *c_nfiles;
static struct cmd *c_bytes;
static struct cmd *c_lobytes;
static const Oid types[] = { INT8OID, };
struct fetchstatus s;
int error;
retry:
xc = begin_readonly(pu, "statvfs");
/*
* use an estimate which we can retrieve quickly, instead of
* "SELECT count(*) from file".
*/
CREATECMD_NOPARAM(c_nfiles,
"SELECT reltuples::int8 "
"FROM pg_class c LEFT JOIN pg_namespace n "
"ON (n.oid=c.relnamespace) "
"WHERE n.nspname = 'pgfs' AND c.relname = 'file'");
CREATECMD_NOPARAM(c_bytes,
"SELECT sum(pg_total_relation_size(c.oid))::int8 "
"FROM pg_class c LEFT JOIN pg_namespace n "
"ON (n.oid=c.relnamespace) "
"WHERE n.nspname = 'pgfs'");
/*
* the following is not correct if someone else is using large objects
* in the same database. we don't bother to join with datafork it as
* it's too expensive for the little benefit.
*/
CREATECMD_NOPARAM(c_lobytes,
"SELECT pg_total_relation_size('pg_largeobject')::int8");
error = sendcmd(xc, c_nfiles);
if (error != 0) {
goto got_error;
}
fetchinit(&s, xc);
error = FETCHNEXT(&s, types, &nfiles);
fetchdone(&s);
if (error != 0) {
goto got_error;
}
error = sendcmd(xc, c_bytes);
if (error != 0) {
goto got_error;
}
fetchinit(&s, xc);
error = FETCHNEXT(&s, types, &bytes);
fetchdone(&s);
if (error != 0) {
goto got_error;
}
error = sendcmd(xc, c_lobytes);
if (error != 0) {
goto got_error;
}
fetchinit(&s, xc);
error = FETCHNEXT(&s, types, &lo_bytes);
fetchdone(&s);
if (error != 0) {
goto got_error;
}
error = commit(xc);
if (error != 0) {
goto got_error;
}
/*
* XXX fill f_blocks and f_files with meaningless large values.
* there are no easy way to provide meaningful values for them
* esp. with tablespaces.
*/
sbp->f_bsize = LOBLKSIZE;
sbp->f_frsize = LOBLKSIZE;
sbp->f_blocks = INT64_MAX / 100 / sbp->f_frsize;
sbp->f_bfree = sbp->f_blocks - howmany(bytes + lo_bytes, sbp->f_frsize);
sbp->f_bavail = sbp->f_bfree;
sbp->f_bresvd = 0;
sbp->f_files = INT_MAX;
sbp->f_ffree = sbp->f_files - nfiles;
sbp->f_favail = sbp->f_ffree;
sbp->f_fresvd = 0;
return 0;
got_error:
rollback(xc);
if (error == EAGAIN) {
goto retry;
}
return error;
}