blob: f54134b417beefcc77c9c73e4eb31ba549320cb6 [file] [log] [blame]
/*
* vim:noexpandtab:shiftwidth=8:tabstop=8:
*
* Copyright (C) Max Matveev, 2012
* Copyright CEA/DAM/DIF (2008)
*
* contributeur : Philippe DENIEL philippe.deniel@cea.fr
* Thomas LEIBOVICI thomas.leibovici@cea.fr
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
/* Proxy handle methods */
#include "config.h"
#include "fsal.h"
#include <assert.h>
#include <pthread.h>
#include <arpa/inet.h>
#include <sys/poll.h>
#include <netdb.h>
#include "gsh_list.h"
#include "abstract_atomic.h"
#include "fsal_types.h"
#include "FSAL/fsal_commonlib.h"
#include "pxy_fsal_methods.h"
#include "fsal_nfsv4_macros.h"
#include "nfs_core.h"
#include "nfs_proto_functions.h"
#include "nfs_proto_tools.h"
#include "export_mgr.h"
#define FSAL_PROXY_NFS_V4 4
static clientid4 pxy_clientid;
static pthread_mutex_t pxy_clientid_mutex = PTHREAD_MUTEX_INITIALIZER;
static char pxy_hostname[MAXNAMLEN + 1];
static pthread_t pxy_recv_thread;
static pthread_t pxy_renewer_thread;
static struct glist_head rpc_calls;
static struct glist_head free_contexts;
static int rpc_sock = -1;
static uint32_t rpc_xid;
static pthread_mutex_t listlock = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t sockless = PTHREAD_COND_INITIALIZER;
static pthread_cond_t need_context = PTHREAD_COND_INITIALIZER;
/*
* Protects the "free_contexts" list and the "need_context" condition.
*/
static pthread_mutex_t context_lock = PTHREAD_MUTEX_INITIALIZER;
/* NB! nfs_prog is just an easy way to get this info into the call
* It should really be fetched via export pointer */
struct pxy_rpc_io_context {
pthread_mutex_t iolock;
pthread_cond_t iowait;
struct glist_head calls;
uint32_t rpc_xid;
int iodone;
int ioresult;
unsigned int nfs_prog;
unsigned int sendbuf_sz;
unsigned int recvbuf_sz;
char *sendbuf;
char *recvbuf;
};
/* Use this to estimate storage requirements for fattr4 blob */
struct pxy_fattr_storage {
fattr4_type type;
fattr4_change change_time;
fattr4_size size;
fattr4_fsid fsid;
fattr4_filehandle filehandle;
fattr4_fileid fileid;
fattr4_mode mode;
fattr4_numlinks numlinks;
fattr4_owner owner;
fattr4_owner_group owner_group;
fattr4_space_used space_used;
fattr4_time_access time_access;
fattr4_time_metadata time_metadata;
fattr4_time_modify time_modify;
fattr4_rawdev rawdev;
char padowner[MAXNAMLEN + 1];
char padgroup[MAXNAMLEN + 1];
char padfh[NFS4_FHSIZE];
};
#define FATTR_BLOB_SZ sizeof(struct pxy_fattr_storage)
/*
* This is what becomes an opaque FSAL handle for the upper layers.
*
* The type is a placeholder for future expansion.
*/
struct pxy_handle_blob {
uint8_t len;
uint8_t type;
uint8_t bytes[0];
};
struct pxy_obj_handle {
struct fsal_obj_handle obj;
nfs_fh4 fh4;
#ifdef PROXY_HANDLE_MAPPING
nfs23_map_handle_t h23;
#endif
fsal_openflags_t openflags;
struct pxy_handle_blob blob;
};
static struct pxy_obj_handle *pxy_alloc_handle(struct fsal_export *exp,
const nfs_fh4 *fh,
fattr4 *obj_attributes,
struct attrlist *attrs_out);
static fsal_status_t nfsstat4_to_fsal(nfsstat4 nfsstatus)
{
switch (nfsstatus) {
case NFS4ERR_SAME:
case NFS4ERR_NOT_SAME:
case NFS4_OK:
return fsalstat(ERR_FSAL_NO_ERROR, (int)nfsstatus);
case NFS4ERR_PERM:
return fsalstat(ERR_FSAL_PERM, (int)nfsstatus);
case NFS4ERR_NOENT:
return fsalstat(ERR_FSAL_NOENT, (int)nfsstatus);
case NFS4ERR_IO:
return fsalstat(ERR_FSAL_IO, (int)nfsstatus);
case NFS4ERR_NXIO:
return fsalstat(ERR_FSAL_NXIO, (int)nfsstatus);
case NFS4ERR_EXPIRED:
case NFS4ERR_LOCKED:
case NFS4ERR_SHARE_DENIED:
case NFS4ERR_LOCK_RANGE:
case NFS4ERR_OPENMODE:
case NFS4ERR_FILE_OPEN:
case NFS4ERR_ACCESS:
case NFS4ERR_DENIED:
return fsalstat(ERR_FSAL_ACCESS, (int)nfsstatus);
case NFS4ERR_EXIST:
return fsalstat(ERR_FSAL_EXIST, (int)nfsstatus);
case NFS4ERR_XDEV:
return fsalstat(ERR_FSAL_XDEV, (int)nfsstatus);
case NFS4ERR_NOTDIR:
return fsalstat(ERR_FSAL_NOTDIR, (int)nfsstatus);
case NFS4ERR_ISDIR:
return fsalstat(ERR_FSAL_ISDIR, (int)nfsstatus);
case NFS4ERR_FBIG:
return fsalstat(ERR_FSAL_FBIG, 0);
case NFS4ERR_NOSPC:
return fsalstat(ERR_FSAL_NOSPC, (int)nfsstatus);
case NFS4ERR_ROFS:
return fsalstat(ERR_FSAL_ROFS, (int)nfsstatus);
case NFS4ERR_MLINK:
return fsalstat(ERR_FSAL_MLINK, (int)nfsstatus);
case NFS4ERR_NAMETOOLONG:
return fsalstat(ERR_FSAL_NAMETOOLONG, (int)nfsstatus);
case NFS4ERR_NOTEMPTY:
return fsalstat(ERR_FSAL_NOTEMPTY, (int)nfsstatus);
case NFS4ERR_DQUOT:
return fsalstat(ERR_FSAL_DQUOT, (int)nfsstatus);
case NFS4ERR_STALE:
return fsalstat(ERR_FSAL_STALE, (int)nfsstatus);
case NFS4ERR_NOFILEHANDLE:
case NFS4ERR_BADHANDLE:
return fsalstat(ERR_FSAL_BADHANDLE, (int)nfsstatus);
case NFS4ERR_BAD_COOKIE:
return fsalstat(ERR_FSAL_BADCOOKIE, (int)nfsstatus);
case NFS4ERR_NOTSUPP:
return fsalstat(ERR_FSAL_NOTSUPP, (int)nfsstatus);
case NFS4ERR_TOOSMALL:
return fsalstat(ERR_FSAL_TOOSMALL, (int)nfsstatus);
case NFS4ERR_SERVERFAULT:
return fsalstat(ERR_FSAL_SERVERFAULT, (int)nfsstatus);
case NFS4ERR_BADTYPE:
return fsalstat(ERR_FSAL_BADTYPE, (int)nfsstatus);
case NFS4ERR_GRACE:
case NFS4ERR_DELAY:
return fsalstat(ERR_FSAL_DELAY, (int)nfsstatus);
case NFS4ERR_FHEXPIRED:
return fsalstat(ERR_FSAL_FHEXPIRED, (int)nfsstatus);
case NFS4ERR_WRONGSEC:
return fsalstat(ERR_FSAL_SEC, (int)nfsstatus);
case NFS4ERR_SYMLINK:
return fsalstat(ERR_FSAL_SYMLINK, (int)nfsstatus);
case NFS4ERR_ATTRNOTSUPP:
return fsalstat(ERR_FSAL_ATTRNOTSUPP, (int)nfsstatus);
case NFS4ERR_BADNAME:
return fsalstat(ERR_FSAL_BADNAME, (int)nfsstatus);
case NFS4ERR_INVAL:
case NFS4ERR_CLID_INUSE:
case NFS4ERR_MOVED:
case NFS4ERR_RESOURCE:
case NFS4ERR_MINOR_VERS_MISMATCH:
case NFS4ERR_STALE_CLIENTID:
case NFS4ERR_STALE_STATEID:
case NFS4ERR_OLD_STATEID:
case NFS4ERR_BAD_STATEID:
case NFS4ERR_BAD_SEQID:
case NFS4ERR_RESTOREFH:
case NFS4ERR_LEASE_MOVED:
case NFS4ERR_NO_GRACE:
case NFS4ERR_RECLAIM_BAD:
case NFS4ERR_RECLAIM_CONFLICT:
case NFS4ERR_BADXDR:
case NFS4ERR_BADCHAR:
case NFS4ERR_BAD_RANGE:
case NFS4ERR_BADOWNER:
case NFS4ERR_OP_ILLEGAL:
case NFS4ERR_LOCKS_HELD:
case NFS4ERR_LOCK_NOTSUPP:
case NFS4ERR_DEADLOCK:
case NFS4ERR_ADMIN_REVOKED:
case NFS4ERR_CB_PATH_DOWN:
default:
return fsalstat(ERR_FSAL_INVAL, (int)nfsstatus);
}
}
#define PXY_ATTR_BIT(b) (1U << b)
#define PXY_ATTR_BIT2(b) (1U << (b - 32))
static struct bitmap4 pxy_bitmap_getattr = {
.map[0] =
(PXY_ATTR_BIT(FATTR4_TYPE) | PXY_ATTR_BIT(FATTR4_CHANGE) |
PXY_ATTR_BIT(FATTR4_SIZE) | PXY_ATTR_BIT(FATTR4_FSID) |
PXY_ATTR_BIT(FATTR4_FILEID)),
.map[1] =
(PXY_ATTR_BIT2(FATTR4_MODE) | PXY_ATTR_BIT2(FATTR4_NUMLINKS) |
PXY_ATTR_BIT2(FATTR4_OWNER) | PXY_ATTR_BIT2(FATTR4_OWNER_GROUP) |
PXY_ATTR_BIT2(FATTR4_SPACE_USED) |
PXY_ATTR_BIT2(FATTR4_TIME_ACCESS) |
PXY_ATTR_BIT2(FATTR4_TIME_METADATA) |
PXY_ATTR_BIT2(FATTR4_TIME_MODIFY) | PXY_ATTR_BIT2(FATTR4_RAWDEV)),
.bitmap4_len = 2
};
static struct bitmap4 pxy_bitmap_fsinfo = {
.map[0] =
(PXY_ATTR_BIT(FATTR4_FILES_AVAIL) | PXY_ATTR_BIT(FATTR4_FILES_FREE)
| PXY_ATTR_BIT(FATTR4_FILES_TOTAL)),
.map[1] =
(PXY_ATTR_BIT2(FATTR4_SPACE_AVAIL) |
PXY_ATTR_BIT2(FATTR4_SPACE_FREE) |
PXY_ATTR_BIT2(FATTR4_SPACE_TOTAL)),
.bitmap4_len = 2
};
static struct bitmap4 lease_bits = {
.map[0] = PXY_ATTR_BIT(FATTR4_LEASE_TIME),
.bitmap4_len = 1
};
#undef PXY_ATTR_BIT
#undef PXY_ATTR_BIT2
static struct {
attrmask_t mask;
int fattr_bit;
} fsal_mask2bit[] = {
{
ATTR_SIZE, FATTR4_SIZE}, {
ATTR_MODE, FATTR4_MODE}, {
ATTR_OWNER, FATTR4_OWNER}, {
ATTR_GROUP, FATTR4_OWNER_GROUP}, {
ATTR_ATIME, FATTR4_TIME_ACCESS_SET}, {
ATTR_ATIME_SERVER, FATTR4_TIME_ACCESS_SET}, {
ATTR_MTIME, FATTR4_TIME_MODIFY_SET}, {
ATTR_MTIME_SERVER, FATTR4_TIME_MODIFY_SET}, {
ATTR_CTIME, FATTR4_TIME_METADATA}
};
static struct bitmap4 empty_bitmap = {
.map[0] = 0,
.map[1] = 0,
.map[2] = 0,
.bitmap4_len = 2
};
static int pxy_fsalattr_to_fattr4(const struct attrlist *attrs, fattr4 *data)
{
int i;
struct bitmap4 bmap = empty_bitmap;
struct xdr_attrs_args args;
for (i = 0; i < ARRAY_SIZE(fsal_mask2bit); i++) {
if (FSAL_TEST_MASK(attrs->valid_mask, fsal_mask2bit[i].mask)) {
if (fsal_mask2bit[i].fattr_bit > 31) {
bmap.map[1] |=
1U << (fsal_mask2bit[i].fattr_bit - 32);
bmap.bitmap4_len = 2;
} else {
bmap.map[0] |=
1U << fsal_mask2bit[i].fattr_bit;
}
}
}
memset(&args, 0, sizeof(args));
args.attrs = (struct attrlist *)attrs;
args.data = NULL;
return nfs4_FSALattr_To_Fattr(&args, &bmap, data);
}
static GETATTR4resok *pxy_fill_getattr_reply(nfs_resop4 *resop, char *blob,
size_t blob_sz)
{
GETATTR4resok *a = &resop->nfs_resop4_u.opgetattr.GETATTR4res_u.resok4;
a->obj_attributes.attrmask = empty_bitmap;
a->obj_attributes.attr_vals.attrlist4_val = blob;
a->obj_attributes.attr_vals.attrlist4_len = blob_sz;
return a;
}
static int pxy_got_rpc_reply(struct pxy_rpc_io_context *ctx, int sock, int sz,
u_int xid)
{
char *repbuf = ctx->recvbuf;
int size;
if (sz > ctx->recvbuf_sz)
return -E2BIG;
PTHREAD_MUTEX_lock(&ctx->iolock);
memcpy(repbuf, &xid, sizeof(xid));
/*
* sz includes 4 bytes of xid which have been processed
* together with record mark - reduce the read to avoid
* gobbing up next record mark.
*/
repbuf += 4;
ctx->ioresult = 4;
sz -= 4;
while (sz > 0) {
/* TODO: handle timeouts - use poll(2) */
int bc = read(sock, repbuf, sz);
if (bc <= 0) {
ctx->ioresult = -((bc < 0) ? errno : ETIMEDOUT);
break;
}
repbuf += bc;
ctx->ioresult += bc;
sz -= bc;
}
ctx->iodone = 1;
size = ctx->ioresult;
pthread_cond_signal(&ctx->iowait);
PTHREAD_MUTEX_unlock(&ctx->iolock);
return size;
}
static int pxy_rpc_read_reply(int sock)
{
struct {
uint recmark;
uint xid;
} h;
char *buf = (char *)&h;
struct glist_head *c;
char sink[256];
int cnt = 0;
while (cnt < 8) {
int bc = read(sock, buf + cnt, 8 - cnt);
if (bc < 0)
return -errno;
cnt += bc;
}
h.recmark = ntohl(h.recmark);
/* TODO: check for final fragment */
h.xid = ntohl(h.xid);
LogDebug(COMPONENT_FSAL, "Recmark %x, xid %u\n", h.recmark, h.xid);
h.recmark &= ~(1U << 31);
PTHREAD_MUTEX_lock(&listlock);
glist_for_each(c, &rpc_calls) {
struct pxy_rpc_io_context *ctx =
container_of(c, struct pxy_rpc_io_context, calls);
if (ctx->rpc_xid == h.xid) {
glist_del(c);
PTHREAD_MUTEX_unlock(&listlock);
return pxy_got_rpc_reply(ctx, sock, h.recmark, h.xid);
}
}
PTHREAD_MUTEX_unlock(&listlock);
cnt = h.recmark - 4;
LogDebug(COMPONENT_FSAL, "xid %u is not on the list, skip %d bytes\n",
h.xid, cnt);
while (cnt > 0) {
int rb = (cnt > sizeof(sink)) ? sizeof(sink) : cnt;
rb = read(sock, sink, rb);
if (rb <= 0)
return -errno;
cnt -= rb;
}
return 0;
}
static void pxy_new_socket_ready(void)
{
struct glist_head *nxt;
struct glist_head *c;
/* If there is anyone waiting for the socket then tell them
* it's ready */
pthread_cond_broadcast(&sockless);
/* If there are any outstanding calls then tell them to resend */
glist_for_each_safe(c, nxt, &rpc_calls) {
struct pxy_rpc_io_context *ctx =
container_of(c, struct pxy_rpc_io_context, calls);
glist_del(c);
PTHREAD_MUTEX_lock(&ctx->iolock);
ctx->iodone = 1;
ctx->ioresult = -EAGAIN;
pthread_cond_signal(&ctx->iowait);
PTHREAD_MUTEX_unlock(&ctx->iolock);
}
}
static int pxy_connect(struct pxy_client_params *info,
struct sockaddr_in *dest)
{
int sock;
if (info->use_privileged_client_port) {
int priv_port = 0;
sock = rresvport(&priv_port);
if (sock < 0)
LogCrit(COMPONENT_FSAL,
"Cannot create TCP socket on privileged port");
} else {
sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sock < 0)
LogCrit(COMPONENT_FSAL, "Cannot create TCP socket - %d",
errno);
}
if (sock >= 0) {
if (connect(sock, (struct sockaddr *)dest, sizeof(*dest)) < 0) {
close(sock);
sock = -1;
} else {
pxy_new_socket_ready();
}
}
return sock;
}
/*
* NB! rpc_sock can be closed by the sending thread but it will not be
* changing its value. Only this function will change rpc_sock which
* means that it can look at the value without holding the lock.
*/
static void *pxy_rpc_recv(void *arg)
{
struct pxy_client_params *info = arg;
struct sockaddr_in addr_rpc;
struct sockaddr_in *info_sock = (struct sockaddr_in *)&info->srv_addr;
char addr[INET_ADDRSTRLEN];
struct pollfd pfd;
int millisec = info->srv_timeout * 1000;
memset(&addr_rpc, 0, sizeof(addr_rpc));
addr_rpc.sin_family = AF_INET;
addr_rpc.sin_port = info->srv_port;
memcpy(&addr_rpc.sin_addr, &info_sock->sin_addr,
sizeof(struct in_addr));
for (;;) {
int nsleeps = 0;
PTHREAD_MUTEX_lock(&listlock);
do {
rpc_sock = pxy_connect(info, &addr_rpc);
if (rpc_sock < 0) {
if (nsleeps == 0)
LogCrit(COMPONENT_FSAL,
"Cannot connect to server %s:%u",
inet_ntop(AF_INET,
&addr_rpc.sin_addr,
addr,
sizeof(addr)),
ntohs(info->srv_port));
PTHREAD_MUTEX_unlock(&listlock);
sleep(info->retry_sleeptime);
nsleeps++;
PTHREAD_MUTEX_lock(&listlock);
} else {
LogDebug(COMPONENT_FSAL,
"Connected after %d sleeps, resending outstanding calls",
nsleeps);
}
} while (rpc_sock < 0);
PTHREAD_MUTEX_unlock(&listlock);
pfd.fd = rpc_sock;
pfd.events = POLLIN | POLLRDHUP;
while (rpc_sock >= 0) {
switch (poll(&pfd, 1, millisec)) {
case 0:
LogDebug(COMPONENT_FSAL,
"Timeout, wait again...");
continue;
case -1:
break;
default:
if (pfd.revents & POLLRDHUP) {
LogEvent(COMPONENT_FSAL,
"Other end has closed connection, reconnecting...");
} else if (pfd.revents & POLLNVAL) {
LogEvent(COMPONENT_FSAL,
"Socket is closed");
} else {
if (pxy_rpc_read_reply(rpc_sock) >= 0)
continue;
}
break;
}
PTHREAD_MUTEX_lock(&listlock);
close(rpc_sock);
rpc_sock = -1;
PTHREAD_MUTEX_unlock(&listlock);
}
}
return NULL;
}
static enum clnt_stat pxy_process_reply(struct pxy_rpc_io_context *ctx,
COMPOUND4res *res)
{
enum clnt_stat rc = RPC_CANTRECV;
struct timespec ts;
PTHREAD_MUTEX_lock(&ctx->iolock);
ts.tv_sec = time(NULL) + 60;
ts.tv_nsec = 0;
while (!ctx->iodone) {
int w = pthread_cond_timedwait(&ctx->iowait, &ctx->iolock, &ts);
if (w == ETIMEDOUT) {
PTHREAD_MUTEX_unlock(&ctx->iolock);
return RPC_TIMEDOUT;
}
}
ctx->iodone = 0;
PTHREAD_MUTEX_unlock(&ctx->iolock);
if (ctx->ioresult > 0) {
struct rpc_msg reply;
XDR x;
memset(&reply, 0, sizeof(reply));
reply.acpted_rply.ar_results.proc =
(xdrproc_t) xdr_COMPOUND4res;
reply.acpted_rply.ar_results.where = (caddr_t) res;
memset(&x, 0, sizeof(x));
xdrmem_create(&x, ctx->recvbuf, ctx->ioresult, XDR_DECODE);
/* macro is defined, GCC 4.7.2 ignoring */
if (xdr_replymsg(&x, &reply)) {
if (reply.rm_reply.rp_stat == MSG_ACCEPTED) {
switch (reply.rm_reply.rp_acpt.ar_stat) {
case SUCCESS:
rc = RPC_SUCCESS;
break;
case PROG_UNAVAIL:
rc = RPC_PROGUNAVAIL;
break;
case PROG_MISMATCH:
rc = RPC_PROGVERSMISMATCH;
break;
case PROC_UNAVAIL:
rc = RPC_PROCUNAVAIL;
break;
case GARBAGE_ARGS:
rc = RPC_CANTDECODEARGS;
break;
case SYSTEM_ERR:
rc = RPC_SYSTEMERROR;
break;
default:
rc = RPC_FAILED;
break;
}
} else {
switch (reply.rm_reply.rp_rjct.rj_stat) {
case RPC_MISMATCH:
rc = RPC_VERSMISMATCH;
break;
case AUTH_ERROR:
rc = RPC_AUTHERROR;
break;
default:
rc = RPC_FAILED;
break;
}
}
} else {
rc = RPC_CANTDECODERES;
}
reply.acpted_rply.ar_results.proc = (xdrproc_t) xdr_void;
reply.acpted_rply.ar_results.where = NULL;
xdr_free((xdrproc_t) xdr_replymsg, &reply);
}
return rc;
}
static void pxy_rpc_need_sock(void)
{
PTHREAD_MUTEX_lock(&listlock);
while (rpc_sock < 0)
pthread_cond_wait(&sockless, &listlock);
PTHREAD_MUTEX_unlock(&listlock);
}
static int pxy_rpc_renewer_wait(int timeout)
{
struct timespec ts;
int rc;
PTHREAD_MUTEX_lock(&listlock);
ts.tv_sec = time(NULL) + timeout;
ts.tv_nsec = 0;
rc = pthread_cond_timedwait(&sockless, &listlock, &ts);
PTHREAD_MUTEX_unlock(&listlock);
return (rc == ETIMEDOUT);
}
static int pxy_compoundv4_call(struct pxy_rpc_io_context *pcontext,
const struct user_cred *cred,
COMPOUND4args *args, COMPOUND4res *res)
{
XDR x;
struct rpc_msg rmsg;
AUTH *au;
enum clnt_stat rc;
PTHREAD_MUTEX_lock(&listlock);
rmsg.rm_xid = rpc_xid++;
PTHREAD_MUTEX_unlock(&listlock);
rmsg.rm_direction = CALL;
rmsg.rm_call.cb_rpcvers = RPC_MSG_VERSION;
rmsg.rm_call.cb_prog = pcontext->nfs_prog;
rmsg.rm_call.cb_vers = FSAL_PROXY_NFS_V4;
rmsg.rm_call.cb_proc = NFSPROC4_COMPOUND;
if (cred) {
au = authunix_create(pxy_hostname, cred->caller_uid,
cred->caller_gid, cred->caller_glen,
cred->caller_garray);
} else {
au = authunix_create_default();
}
if (au == NULL)
return RPC_AUTHERROR;
rmsg.rm_call.cb_cred = au->ah_cred;
rmsg.rm_call.cb_verf = au->ah_verf;
memset(&x, 0, sizeof(x));
xdrmem_create(&x, pcontext->sendbuf + 4, pcontext->sendbuf_sz,
XDR_ENCODE);
if (xdr_callmsg(&x, &rmsg) && xdr_COMPOUND4args(&x, args)) {
u_int pos = xdr_getpos(&x);
u_int recmark = ntohl(pos | (1U << 31));
int first_try = 1;
pcontext->rpc_xid = rmsg.rm_xid;
memcpy(pcontext->sendbuf, &recmark, sizeof(recmark));
pos += 4;
do {
int bc = 0;
char *buf = pcontext->sendbuf;
LogDebug(COMPONENT_FSAL, "%ssend XID %u with %d bytes",
(first_try ? "First attempt to " : "Re"),
rmsg.rm_xid, pos);
PTHREAD_MUTEX_lock(&listlock);
while (bc < pos) {
int wc = write(rpc_sock, buf, pos - bc);
if (wc <= 0) {
close(rpc_sock);
break;
}
bc += wc;
buf += wc;
}
if (bc == pos) {
if (first_try) {
glist_add_tail(&rpc_calls,
&pcontext->calls);
first_try = 0;
}
} else {
if (!first_try)
glist_del(&pcontext->calls);
}
PTHREAD_MUTEX_unlock(&listlock);
if (bc == pos)
rc = pxy_process_reply(pcontext, res);
else
rc = RPC_CANTSEND;
} while (rc == RPC_TIMEDOUT);
} else {
rc = RPC_CANTENCODEARGS;
}
if (au)
auth_destroy(au);
return rc;
}
int pxy_compoundv4_execute(const char *caller, const struct user_cred *creds,
uint32_t cnt, nfs_argop4 *argoparray,
nfs_resop4 *resoparray)
{
enum clnt_stat rc;
struct pxy_rpc_io_context *ctx;
COMPOUND4args arg = {
.argarray.argarray_val = argoparray,
.argarray.argarray_len = cnt
};
COMPOUND4res res = {
.resarray.resarray_val = resoparray,
.resarray.resarray_len = cnt
};
PTHREAD_MUTEX_lock(&context_lock);
while (glist_empty(&free_contexts))
pthread_cond_wait(&need_context, &context_lock);
ctx =
glist_first_entry(&free_contexts, struct pxy_rpc_io_context, calls);
glist_del(&ctx->calls);
PTHREAD_MUTEX_unlock(&context_lock);
do {
rc = pxy_compoundv4_call(ctx, creds, &arg, &res);
if (rc != RPC_SUCCESS)
LogDebug(COMPONENT_FSAL, "%s failed with %d", caller,
rc);
if (rc == RPC_CANTSEND)
pxy_rpc_need_sock();
} while ((rc == RPC_CANTRECV && (ctx->ioresult == -EAGAIN))
|| (rc == RPC_CANTSEND));
PTHREAD_MUTEX_lock(&context_lock);
pthread_cond_signal(&need_context);
glist_add(&free_contexts, &ctx->calls);
PTHREAD_MUTEX_unlock(&context_lock);
if (rc == RPC_SUCCESS)
return res.status;
return rc;
}
#define pxy_nfsv4_call(exp, creds, cnt, args, resp) \
pxy_compoundv4_execute(__func__, creds, cnt, args, resp)
void pxy_get_clientid(clientid4 *ret)
{
PTHREAD_MUTEX_lock(&pxy_clientid_mutex);
*ret = pxy_clientid;
PTHREAD_MUTEX_unlock(&pxy_clientid_mutex);
}
static int pxy_setclientid(clientid4 *resultclientid, uint32_t *lease_time)
{
int rc;
int opcnt = 0;
#define FSAL_CLIENTID_NB_OP_ALLOC 2
nfs_argop4 arg[FSAL_CLIENTID_NB_OP_ALLOC];
nfs_resop4 res[FSAL_CLIENTID_NB_OP_ALLOC];
nfs_client_id4 nfsclientid;
uint64_t temp_verifier;
cb_client4 cbproxy;
char clientid_name[MAXNAMLEN + 1];
SETCLIENTID4resok *sok;
struct sockaddr_in sin;
socklen_t slen = sizeof(sin);
char addrbuf[sizeof("255.255.255.255")];
LogEvent(COMPONENT_FSAL,
"Negotiating a new ClientId with the remote server");
if (getsockname(rpc_sock, &sin, &slen))
return -errno;
snprintf(clientid_name, MAXNAMLEN, "%s(%d) - GANESHA NFSv4 Proxy",
inet_ntop(AF_INET, &sin.sin_addr, addrbuf, sizeof(addrbuf)),
getpid());
nfsclientid.id.id_len = strlen(clientid_name);
nfsclientid.id.id_val = clientid_name;
/* copy to intermediate uint64_t to 0-fill or truncate as needed */
temp_verifier = (uint64_t)ServerBootTime.tv_sec;
BUILD_BUG_ON(sizeof(nfsclientid.verifier) != sizeof(uint64_t));
memcpy(&nfsclientid.verifier, &temp_verifier, sizeof(uint64_t));
cbproxy.cb_program = 0;
cbproxy.cb_location.r_netid = "tcp";
cbproxy.cb_location.r_addr = "127.0.0.1";
sok = &res[0].nfs_resop4_u.opsetclientid.SETCLIENTID4res_u.resok4;
arg[0].argop = NFS4_OP_SETCLIENTID;
arg[0].nfs_argop4_u.opsetclientid.client = nfsclientid;
arg[0].nfs_argop4_u.opsetclientid.callback = cbproxy;
arg[0].nfs_argop4_u.opsetclientid.callback_ident = 0;
rc = pxy_compoundv4_execute(__func__, NULL, 1, arg, res);
if (rc != NFS4_OK)
return -1;
arg[0].argop = NFS4_OP_SETCLIENTID_CONFIRM;
arg[0].nfs_argop4_u.opsetclientid_confirm.clientid = sok->clientid;
memcpy(arg[0].nfs_argop4_u.opsetclientid_confirm.setclientid_confirm,
sok->setclientid_confirm, NFS4_VERIFIER_SIZE);
rc = pxy_compoundv4_execute(__func__, NULL, 1, arg, res);
if (rc != NFS4_OK)
return -1;
/* Keep the confirmed client id */
*resultclientid = arg[0].nfs_argop4_u.opsetclientid_confirm.clientid;
/* Get the lease time */
opcnt = 0;
COMPOUNDV4_ARG_ADD_OP_PUTROOTFH(opcnt, arg);
pxy_fill_getattr_reply(res + opcnt, (char *)lease_time,
sizeof(*lease_time));
COMPOUNDV4_ARG_ADD_OP_GETATTR(opcnt, arg, lease_bits);
rc = pxy_compoundv4_execute(__func__, NULL, opcnt, arg, res);
if (rc != NFS4_OK)
*lease_time = 60;
else
*lease_time = ntohl(*lease_time);
return 0;
}
static void *pxy_clientid_renewer(void *Arg)
{
int rc;
int needed = 1;
nfs_argop4 arg;
nfs_resop4 res;
uint32_t lease_time = 60;
while (1) {
clientid4 newcid = 0;
if (!needed && pxy_rpc_renewer_wait(lease_time - 5)) {
/* Simply renew the client id you've got */
LogDebug(COMPONENT_FSAL, "Renewing client id %" PRIx64,
pxy_clientid);
arg.argop = NFS4_OP_RENEW;
arg.nfs_argop4_u.oprenew.clientid = pxy_clientid;
rc = pxy_compoundv4_execute(__func__, NULL, 1, &arg,
&res);
if (rc == NFS4_OK) {
LogDebug(COMPONENT_FSAL,
"Renewed client id %" PRIx64,
pxy_clientid);
continue;
}
}
/* We've either failed to renew or rpc socket has been
* reconnected and we need new client id */
LogDebug(COMPONENT_FSAL, "Need %d new client id", needed);
pxy_rpc_need_sock();
needed = pxy_setclientid(&newcid, &lease_time);
if (!needed) {
PTHREAD_MUTEX_lock(&pxy_clientid_mutex);
pxy_clientid = newcid;
PTHREAD_MUTEX_unlock(&pxy_clientid_mutex);
}
}
return NULL;
}
static void free_io_contexts(void)
{
struct glist_head *cur, *n;
glist_for_each_safe(cur, n, &free_contexts) {
struct pxy_rpc_io_context *c =
container_of(cur, struct pxy_rpc_io_context, calls);
glist_del(cur);
gsh_free(c);
}
}
int pxy_init_rpc(const struct pxy_fsal_module *pm)
{
int rc;
int i = 16;
glist_init(&rpc_calls);
glist_init(&free_contexts);
/**
* @todo this lock is not really necessary so long as we can
* only do one export at a time. This is a reminder that
* there is work to do to get this fnctn to truely be
* per export.
*/
PTHREAD_MUTEX_lock(&listlock);
if (rpc_xid == 0)
rpc_xid = getpid() ^ time(NULL);
PTHREAD_MUTEX_unlock(&listlock);
if (gethostname(pxy_hostname, sizeof(pxy_hostname)))
strncpy(pxy_hostname, "NFS-GANESHA/Proxy",
sizeof(pxy_hostname));
for (i = 16; i > 0; i--) {
struct pxy_rpc_io_context *c =
gsh_malloc(sizeof(*c) + pm->special.srv_sendsize +
pm->special.srv_recvsize);
if (!c) {
free_io_contexts();
return ENOMEM;
}
PTHREAD_MUTEX_init(&c->iolock, NULL);
PTHREAD_COND_init(&c->iowait, NULL);
c->nfs_prog = pm->special.srv_prognum;
c->sendbuf_sz = pm->special.srv_sendsize;
c->recvbuf_sz = pm->special.srv_recvsize;
c->sendbuf = (char *)(c + 1);
c->recvbuf = c->sendbuf + c->sendbuf_sz;
glist_add(&free_contexts, &c->calls);
}
rc = pthread_create(&pxy_recv_thread, NULL, pxy_rpc_recv,
(void *)&pm->special);
if (rc) {
LogCrit(COMPONENT_FSAL,
"Cannot create proxy rpc receiver thread - %s",
strerror(rc));
free_io_contexts();
return rc;
}
rc = pthread_create(&pxy_renewer_thread, NULL, pxy_clientid_renewer,
NULL);
if (rc) {
LogCrit(COMPONENT_FSAL,
"Cannot create proxy clientid renewer thread - %s",
strerror(rc));
free_io_contexts();
}
return rc;
}
static fsal_status_t pxy_make_object(struct fsal_export *export,
fattr4 *obj_attributes,
const nfs_fh4 *fh,
struct fsal_obj_handle **handle,
struct attrlist *attrs_out)
{
struct pxy_obj_handle *pxy_hdl;
pxy_hdl = pxy_alloc_handle(export, fh, obj_attributes, attrs_out);
if (pxy_hdl == NULL)
return fsalstat(ERR_FSAL_FAULT, 0);
*handle = &pxy_hdl->obj;
return fsalstat(ERR_FSAL_NO_ERROR, 0);
}
/*
* NULL parent pointer is only used by lookup_path when it starts
* from the root handle and has its own export pointer, everybody
* else is supposed to provide a real parent pointer and matching
* export
*/
static fsal_status_t pxy_lookup_impl(struct fsal_obj_handle *parent,
struct fsal_export *export,
const struct user_cred *cred,
const char *path,
struct fsal_obj_handle **handle,
struct attrlist *attrs_out)
{
int rc;
uint32_t opcnt = 0;
GETATTR4resok *atok;
GETFH4resok *fhok;
#define FSAL_LOOKUP_NB_OP_ALLOC 4
nfs_argop4 argoparray[FSAL_LOOKUP_NB_OP_ALLOC];
nfs_resop4 resoparray[FSAL_LOOKUP_NB_OP_ALLOC];
char fattr_blob[FATTR_BLOB_SZ];
char padfilehandle[NFS4_FHSIZE];
if (!handle)
return fsalstat(ERR_FSAL_INVAL, 0);
if (!parent) {
COMPOUNDV4_ARG_ADD_OP_PUTROOTFH(opcnt, argoparray);
} else {
struct pxy_obj_handle *pxy_obj =
container_of(parent, struct pxy_obj_handle, obj);
switch (parent->type) {
case DIRECTORY:
break;
default:
return fsalstat(ERR_FSAL_NOTDIR, 0);
}
COMPOUNDV4_ARG_ADD_OP_PUTFH(opcnt, argoparray, pxy_obj->fh4);
}
if (path) {
if (!strcmp(path, ".")) {
if (!parent)
return fsalstat(ERR_FSAL_FAULT, 0);
} else if (!strcmp(path, "..")) {
if (!parent)
return fsalstat(ERR_FSAL_FAULT, 0);
COMPOUNDV4_ARG_ADD_OP_LOOKUPP(opcnt, argoparray);
} else {
COMPOUNDV4_ARG_ADD_OP_LOOKUP(opcnt, argoparray, path);
}
}
fhok = &resoparray[opcnt].nfs_resop4_u.opgetfh.GETFH4res_u.resok4;
COMPOUNDV4_ARG_ADD_OP_GETFH(opcnt, argoparray);
atok =
pxy_fill_getattr_reply(resoparray + opcnt, fattr_blob,
sizeof(fattr_blob));
COMPOUNDV4_ARG_ADD_OP_GETATTR(opcnt, argoparray, pxy_bitmap_getattr);
fhok->object.nfs_fh4_val = (char *)padfilehandle;
fhok->object.nfs_fh4_len = sizeof(padfilehandle);
rc = pxy_nfsv4_call(export, cred, opcnt, argoparray, resoparray);
if (rc != NFS4_OK)
return nfsstat4_to_fsal(rc);
return pxy_make_object(export, &atok->obj_attributes, &fhok->object,
handle, attrs_out);
}
static fsal_status_t pxy_lookup(struct fsal_obj_handle *parent,
const char *path,
struct fsal_obj_handle **handle,
struct attrlist *attrs_out)
{
return pxy_lookup_impl(parent, op_ctx->fsal_export,
op_ctx->creds, path, handle, attrs_out);
}
static fsal_status_t pxy_do_close(const struct user_cred *creds,
const nfs_fh4 *fh4,
seqid4 open_owner_seqid,
stateid4 *sid,
struct fsal_export *exp)
{
int rc;
int opcnt = 0;
#define FSAL_CLOSE_NB_OP_ALLOC 2
nfs_argop4 argoparray[FSAL_CLOSE_NB_OP_ALLOC];
nfs_resop4 resoparray[FSAL_CLOSE_NB_OP_ALLOC];
char All_Zero[] = "\0\0\0\0\0\0\0\0\0\0\0\0"; /* 12 times \0 */
/* Check if this was a "stateless" open,
* then nothing is to be done at close */
if (!memcmp(sid->other, All_Zero, 12))
return fsalstat(ERR_FSAL_NO_ERROR, 0);
COMPOUNDV4_ARG_ADD_OP_PUTFH(opcnt, argoparray, *fh4);
COMPOUNDV4_ARG_ADD_OP_CLOSE(opcnt, argoparray, sid, open_owner_seqid);
rc = pxy_nfsv4_call(exp, creds, opcnt, argoparray, resoparray);
if (rc != NFS4_OK)
return nfsstat4_to_fsal(rc);
return fsalstat(ERR_FSAL_NO_ERROR, 0);
}
static fsal_status_t pxy_open_confirm(const struct user_cred *cred,
const nfs_fh4 *fh4,
seqid4 open_owner_seqid,
stateid4 *stateid,
struct fsal_export *export)
{
int rc;
int opcnt = 0;
#define FSAL_PROXY_OPEN_CONFIRM_NB_OP_ALLOC 2
nfs_argop4 argoparray[FSAL_PROXY_OPEN_CONFIRM_NB_OP_ALLOC];
nfs_resop4 resoparray[FSAL_PROXY_OPEN_CONFIRM_NB_OP_ALLOC];
nfs_argop4 *op;
OPEN_CONFIRM4resok *conok;
COMPOUNDV4_ARG_ADD_OP_PUTFH(opcnt, argoparray, *fh4);
conok =
&resoparray[opcnt].nfs_resop4_u.opopen_confirm.OPEN_CONFIRM4res_u.
resok4;
op = argoparray + opcnt++;
op->argop = NFS4_OP_OPEN_CONFIRM;
op->nfs_argop4_u.opopen_confirm.open_stateid.seqid = stateid->seqid;
memcpy(op->nfs_argop4_u.opopen_confirm.open_stateid.other,
stateid->other, 12);
op->nfs_argop4_u.opopen_confirm.seqid = open_owner_seqid;
rc = pxy_nfsv4_call(export, cred, opcnt, argoparray, resoparray);
if (rc != NFS4_OK)
return nfsstat4_to_fsal(rc);
stateid->seqid = conok->open_stateid.seqid;
memcpy(stateid->other, conok->open_stateid.other, 12);
return fsalstat(ERR_FSAL_NO_ERROR, 0);
}
/* TODO: make this per-export */
static uint64_t fcnt;
static fsal_status_t pxy_create(struct fsal_obj_handle *dir_hdl,
const char *name, struct attrlist *attrib,
struct fsal_obj_handle **handle,
struct attrlist *attrs_out)
{
int rc;
int opcnt = 0;
fattr4 input_attr;
char padfilehandle[NFS4_FHSIZE];
char fattr_blob[FATTR_BLOB_SZ];
#define FSAL_CREATE_NB_OP_ALLOC 4
nfs_argop4 argoparray[FSAL_CREATE_NB_OP_ALLOC];
nfs_resop4 resoparray[FSAL_CREATE_NB_OP_ALLOC];
char owner_val[128];
unsigned int owner_len = 0;
seqid4 open_owner_seqid = 0;
GETFH4resok *fhok;
GETATTR4resok *atok;
OPEN4resok *opok;
struct pxy_obj_handle *ph;
fsal_status_t st;
clientid4 cid;
/* Create the owner */
snprintf(owner_val, sizeof(owner_val), "GANESHA/PROXY: pid=%u %" PRIu64,
getpid(), atomic_inc_uint64_t(&fcnt));
owner_len = strnlen(owner_val, sizeof(owner_val));
attrib->valid_mask &= ATTR_MODE | ATTR_OWNER | ATTR_GROUP;
if (pxy_fsalattr_to_fattr4(attrib, &input_attr) == -1)
return fsalstat(ERR_FSAL_INVAL, -1);
ph = container_of(dir_hdl, struct pxy_obj_handle, obj);
COMPOUNDV4_ARG_ADD_OP_PUTFH(opcnt, argoparray, ph->fh4);
opok = &resoparray[opcnt].nfs_resop4_u.opopen.OPEN4res_u.resok4;
opok->attrset = empty_bitmap;
pxy_get_clientid(&cid);
COMPOUNDV4_ARG_ADD_OP_OPEN_CREATE(opcnt, argoparray, (char *)name,
input_attr, cid, owner_val,
owner_len, open_owner_seqid);
fhok = &resoparray[opcnt].nfs_resop4_u.opgetfh.GETFH4res_u.resok4;
fhok->object.nfs_fh4_val = padfilehandle;
fhok->object.nfs_fh4_len = sizeof(padfilehandle);
COMPOUNDV4_ARG_ADD_OP_GETFH(opcnt, argoparray);
atok =
pxy_fill_getattr_reply(resoparray + opcnt, fattr_blob,
sizeof(fattr_blob));
COMPOUNDV4_ARG_ADD_OP_GETATTR(opcnt, argoparray, pxy_bitmap_getattr);
rc = pxy_nfsv4_call(op_ctx->fsal_export, op_ctx->creds,
opcnt, argoparray, resoparray);
nfs4_Fattr_Free(&input_attr);
if (rc != NFS4_OK)
return nfsstat4_to_fsal(rc);
/* See if a OPEN_CONFIRM is required */
if (opok->rflags & OPEN4_RESULT_CONFIRM) {
st = pxy_open_confirm(op_ctx->creds, &fhok->object,
++open_owner_seqid, &opok->stateid,
op_ctx->fsal_export);
if (FSAL_IS_ERROR(st))
return st;
}
/* The created file is still opened, to preserve the correct
* seqid for later use, we close it */
st = pxy_do_close(op_ctx->creds, &fhok->object, ++open_owner_seqid,
&opok->stateid, op_ctx->fsal_export);
if (FSAL_IS_ERROR(st))
return st;
st = pxy_make_object(op_ctx->fsal_export, &atok->obj_attributes,
&fhok->object, handle, attrs_out);
if (FSAL_IS_ERROR(st))
return st;
return (*handle)->obj_ops.getattrs(*handle, attrib);
}
static fsal_status_t pxy_mkdir(struct fsal_obj_handle *dir_hdl,
const char *name, struct attrlist *attrib,
struct fsal_obj_handle **handle,
struct attrlist *attrs_out)
{
int rc;
int opcnt = 0;
fattr4 input_attr;
char padfilehandle[NFS4_FHSIZE];
struct pxy_obj_handle *ph;
char fattr_blob[FATTR_BLOB_SZ];
GETATTR4resok *atok;
GETFH4resok *fhok;
fsal_status_t st;
#define FSAL_MKDIR_NB_OP_ALLOC 4
nfs_argop4 argoparray[FSAL_MKDIR_NB_OP_ALLOC];
nfs_resop4 resoparray[FSAL_MKDIR_NB_OP_ALLOC];
/*
* The caller gives us partial attributes which include mode and owner
* and expects the full attributes back at the end of the call.
*/
attrib->valid_mask &= ATTR_MODE | ATTR_OWNER | ATTR_GROUP;
if (pxy_fsalattr_to_fattr4(attrib, &input_attr) == -1)
return fsalstat(ERR_FSAL_INVAL, -1);
ph = container_of(dir_hdl, struct pxy_obj_handle, obj);
COMPOUNDV4_ARG_ADD_OP_PUTFH(opcnt, argoparray, ph->fh4);
resoparray[opcnt].nfs_resop4_u.opcreate.CREATE4res_u.resok4.attrset =
empty_bitmap;
COMPOUNDV4_ARG_ADD_OP_MKDIR(opcnt, argoparray, (char *)name,
input_attr);
fhok = &resoparray[opcnt].nfs_resop4_u.opgetfh.GETFH4res_u.resok4;
fhok->object.nfs_fh4_val = padfilehandle;
fhok->object.nfs_fh4_len = sizeof(padfilehandle);
COMPOUNDV4_ARG_ADD_OP_GETFH(opcnt, argoparray);
atok =
pxy_fill_getattr_reply(resoparray + opcnt, fattr_blob,
sizeof(fattr_blob));
COMPOUNDV4_ARG_ADD_OP_GETATTR(opcnt, argoparray, pxy_bitmap_getattr);
rc = pxy_nfsv4_call(op_ctx->fsal_export, op_ctx->creds,
opcnt, argoparray, resoparray);
nfs4_Fattr_Free(&input_attr);
if (rc != NFS4_OK)
return nfsstat4_to_fsal(rc);
st = pxy_make_object(op_ctx->fsal_export, &atok->obj_attributes,
&fhok->object, handle, attrs_out);
if (FSAL_IS_ERROR(st))
return st;
return (*handle)->obj_ops.getattrs(*handle, attrib);
}
static fsal_status_t pxy_mknod(struct fsal_obj_handle *dir_hdl,
const char *name, object_file_type_t nodetype,
fsal_dev_t *dev, struct attrlist *attrib,
struct fsal_obj_handle **handle,
struct attrlist *attrs_out)
{
int rc;
int opcnt = 0;
fattr4 input_attr;
char padfilehandle[NFS4_FHSIZE];
struct pxy_obj_handle *ph;
char fattr_blob[FATTR_BLOB_SZ];
GETATTR4resok *atok;
GETFH4resok *fhok;
fsal_status_t st;
enum nfs_ftype4 nf4type;
specdata4 specdata = { 0, 0 };
nfs_argop4 argoparray[4];
nfs_resop4 resoparray[4];
switch (nodetype) {
case CHARACTER_FILE:
if (!dev)
return fsalstat(ERR_FSAL_FAULT, EINVAL);
specdata.specdata1 = dev->major;
specdata.specdata2 = dev->minor;
nf4type = NF4CHR;
break;
case BLOCK_FILE:
if (!dev)
return fsalstat(ERR_FSAL_FAULT, EINVAL);
specdata.specdata1 = dev->major;
specdata.specdata2 = dev->minor;
nf4type = NF4BLK;
break;
case SOCKET_FILE:
nf4type = NF4SOCK;
break;
case FIFO_FILE:
nf4type = NF4FIFO;
break;
default:
return fsalstat(ERR_FSAL_FAULT, EINVAL);
}
/*
* The caller gives us partial attributes which include mode and owner
* and expects the full attributes back at the end of the call.
*/
attrib->valid_mask &= ATTR_MODE | ATTR_OWNER | ATTR_GROUP;
if (pxy_fsalattr_to_fattr4(attrib, &input_attr) == -1)
return fsalstat(ERR_FSAL_INVAL, -1);
ph = container_of(dir_hdl, struct pxy_obj_handle, obj);
COMPOUNDV4_ARG_ADD_OP_PUTFH(opcnt, argoparray, ph->fh4);
resoparray[opcnt].nfs_resop4_u.opcreate.CREATE4res_u.resok4.attrset =
empty_bitmap;
COMPOUNDV4_ARG_ADD_OP_CREATE(opcnt, argoparray, (char *)name, nf4type,
input_attr, specdata);
fhok = &resoparray[opcnt].nfs_resop4_u.opgetfh.GETFH4res_u.resok4;
fhok->object.nfs_fh4_val = padfilehandle;
fhok->object.nfs_fh4_len = sizeof(padfilehandle);
COMPOUNDV4_ARG_ADD_OP_GETFH(opcnt, argoparray);
atok =
pxy_fill_getattr_reply(resoparray + opcnt, fattr_blob,
sizeof(fattr_blob));
COMPOUNDV4_ARG_ADD_OP_GETATTR(opcnt, argoparray, pxy_bitmap_getattr);
rc = pxy_nfsv4_call(op_ctx->fsal_export, op_ctx->creds,
opcnt, argoparray, resoparray);
nfs4_Fattr_Free(&input_attr);
if (rc != NFS4_OK)
return nfsstat4_to_fsal(rc);
st = pxy_make_object(op_ctx->fsal_export, &atok->obj_attributes,
&fhok->object, handle, attrs_out);
if (FSAL_IS_ERROR(st))
return st;
return (*handle)->obj_ops.getattrs(*handle, attrib);
}
static fsal_status_t pxy_symlink(struct fsal_obj_handle *dir_hdl,
const char *name, const char *link_path,
struct attrlist *attrib,
struct fsal_obj_handle **handle,
struct attrlist *attrs_out)
{
int rc;
int opcnt = 0;
fattr4 input_attr;
char padfilehandle[NFS4_FHSIZE];
char fattr_blob[FATTR_BLOB_SZ];
#define FSAL_SYMLINK_NB_OP_ALLOC 4
nfs_argop4 argoparray[FSAL_SYMLINK_NB_OP_ALLOC];
nfs_resop4 resoparray[FSAL_SYMLINK_NB_OP_ALLOC];
GETATTR4resok *atok;
GETFH4resok *fhok;
fsal_status_t st;
struct pxy_obj_handle *ph;
/* Tests if symlinking is allowed by configuration. */
if (!op_ctx->fsal_export->exp_ops.fs_supports(op_ctx->fsal_export,
fso_symlink_support))
return fsalstat(ERR_FSAL_NOTSUPP, ENOTSUP);
attrib->valid_mask = ATTR_MODE;
if (pxy_fsalattr_to_fattr4(attrib, &input_attr) == -1)
return fsalstat(ERR_FSAL_INVAL, -1);
ph = container_of(dir_hdl, struct pxy_obj_handle, obj);
COMPOUNDV4_ARG_ADD_OP_PUTFH(opcnt, argoparray, ph->fh4);
resoparray[opcnt].nfs_resop4_u.opcreate.CREATE4res_u.resok4.attrset =
empty_bitmap;
COMPOUNDV4_ARG_ADD_OP_SYMLINK(opcnt, argoparray, (char *)name,
(char *)link_path, input_attr);
fhok = &resoparray[opcnt].nfs_resop4_u.opgetfh.GETFH4res_u.resok4;
fhok->object.nfs_fh4_val = padfilehandle;
fhok->object.nfs_fh4_len = sizeof(padfilehandle);
COMPOUNDV4_ARG_ADD_OP_GETFH(opcnt, argoparray);
atok =
pxy_fill_getattr_reply(resoparray + opcnt, fattr_blob,
sizeof(fattr_blob));
COMPOUNDV4_ARG_ADD_OP_GETATTR(opcnt, argoparray, pxy_bitmap_getattr);
rc = pxy_nfsv4_call(op_ctx->fsal_export, op_ctx->creds,
opcnt, argoparray, resoparray);
nfs4_Fattr_Free(&input_attr);
if (rc != NFS4_OK)
return nfsstat4_to_fsal(rc);
st = pxy_make_object(op_ctx->fsal_export, &atok->obj_attributes,
&fhok->object, handle, attrs_out);
if (FSAL_IS_ERROR(st))
return st;
return (*handle)->obj_ops.getattrs(*handle, attrib);
}
static fsal_status_t pxy_readlink(struct fsal_obj_handle *obj_hdl,
struct gsh_buffdesc *link_content,
bool refresh)
{
int rc;
int opcnt = 0;
struct pxy_obj_handle *ph;
#define FSAL_READLINK_NB_OP_ALLOC 2
nfs_argop4 argoparray[FSAL_READLINK_NB_OP_ALLOC];
nfs_resop4 resoparray[FSAL_READLINK_NB_OP_ALLOC];
READLINK4resok *rlok;
ph = container_of(obj_hdl, struct pxy_obj_handle, obj);
COMPOUNDV4_ARG_ADD_OP_PUTFH(opcnt, argoparray, ph->fh4);
/* This saves us from having to do one allocation for the XDR,
another allocation for the return, and a copy just to get
the \NUL terminator. The link length should be cached in
the file handle. */
link_content->len = fsal_default_linksize;
link_content->addr = gsh_malloc(link_content->len);
rlok = &resoparray[opcnt].nfs_resop4_u.opreadlink.READLINK4res_u.resok4;
rlok->link.utf8string_val = link_content->addr;
rlok->link.utf8string_len = link_content->len;
COMPOUNDV4_ARG_ADD_OP_READLINK(opcnt, argoparray);
rc = pxy_nfsv4_call(op_ctx->fsal_export, op_ctx->creds,
opcnt, argoparray, resoparray);
if (rc != NFS4_OK) {
gsh_free(link_content->addr);
link_content->addr = NULL;
link_content->len = 0;
return nfsstat4_to_fsal(rc);
}
rlok->link.utf8string_val[rlok->link.utf8string_len] = '\0';
link_content->len = rlok->link.utf8string_len + 1;
return fsalstat(ERR_FSAL_NO_ERROR, 0);
}
static fsal_status_t pxy_link(struct fsal_obj_handle *obj_hdl,
struct fsal_obj_handle *destdir_hdl,
const char *name)
{
int rc;
struct pxy_obj_handle *tgt;
struct pxy_obj_handle *dst;
#define FSAL_LINK_NB_OP_ALLOC 4
nfs_argop4 argoparray[FSAL_LINK_NB_OP_ALLOC];
nfs_resop4 resoparray[FSAL_LINK_NB_OP_ALLOC];
int opcnt = 0;
/* Tests if hardlinking is allowed by configuration. */
if (!op_ctx->fsal_export->exp_ops.fs_supports(op_ctx->fsal_export,
fso_link_support))
return fsalstat(ERR_FSAL_NOTSUPP, ENOTSUP);
tgt = container_of(obj_hdl, struct pxy_obj_handle, obj);
dst = container_of(destdir_hdl, struct pxy_obj_handle, obj);
COMPOUNDV4_ARG_ADD_OP_PUTFH(opcnt, argoparray, tgt->fh4);
COMPOUNDV4_ARG_ADD_OP_SAVEFH(opcnt, argoparray);
COMPOUNDV4_ARG_ADD_OP_PUTFH(opcnt, argoparray, dst->fh4);
COMPOUNDV4_ARG_ADD_OP_LINK(opcnt, argoparray, (char *)name);
rc = pxy_nfsv4_call(op_ctx->fsal_export, op_ctx->creds,
opcnt, argoparray, resoparray);
return nfsstat4_to_fsal(rc);
}
static bool xdr_readdirres(XDR *x, nfs_resop4 *rdres)
{
return xdr_nfs_resop4(x, rdres) && xdr_nfs_resop4(x, rdres + 1);
}
/*
* Trying to guess how many entries can fit into a readdir buffer
* is complicated and usually results in either gross over-allocation
* of the memory for results or under-allocation (on large directories)
* and buffer overruns - just pay the price of allocating the memory
* inside XDR decoding and free it when done
*/
static fsal_status_t pxy_do_readdir(struct pxy_obj_handle *ph,
nfs_cookie4 *cookie, fsal_readdir_cb cb,
void *cbarg, attrmask_t attrmask,
bool *eof)
{
uint32_t opcnt = 0;
int rc;
entry4 *e4;
#define FSAL_READDIR_NB_OP_ALLOC 2
nfs_argop4 argoparray[FSAL_READDIR_NB_OP_ALLOC];
nfs_resop4 resoparray[FSAL_READDIR_NB_OP_ALLOC];
READDIR4resok *rdok;
fsal_status_t st = { ERR_FSAL_NO_ERROR, 0 };
COMPOUNDV4_ARG_ADD_OP_PUTFH(opcnt, argoparray, ph->fh4);
rdok = &resoparray[opcnt].nfs_resop4_u.opreaddir.READDIR4res_u.resok4;
rdok->reply.entries = NULL;
COMPOUNDV4_ARG_ADD_OP_READDIR(opcnt, argoparray, *cookie,
pxy_bitmap_getattr);
rc = pxy_nfsv4_call(ph->obj.export, op_ctx->creds, opcnt, argoparray,
resoparray);
if (rc != NFS4_OK)
return nfsstat4_to_fsal(rc);
*eof = rdok->reply.eof;
for (e4 = rdok->reply.entries; e4; e4 = e4->nextentry) {
struct attrlist attrs;
char name[MAXNAMLEN + 1];
struct fsal_obj_handle *handle;
bool cb_rc;
/* UTF8 name does not include trailing 0 */
if (e4->name.utf8string_len > sizeof(name) - 1)
return fsalstat(ERR_FSAL_SERVERFAULT, E2BIG);
memcpy(name, e4->name.utf8string_val, e4->name.utf8string_len);
name[e4->name.utf8string_len] = '\0';
if (nfs4_Fattr_To_FSAL_attr(&attrs, &e4->attrs, NULL))
return fsalstat(ERR_FSAL_FAULT, 0);
*cookie = e4->cookie;
/** @todo FSF: this could be handled by getting handle as part
* of readdir attributes. However, if acl is
* requested, we might get it separately to
* avoid over large READDIR response.
*/
st = pxy_lookup_impl(&ph->obj, op_ctx->fsal_export,
op_ctx->creds, name, &handle, NULL);
if (FSAL_IS_ERROR(st))
break;
cb_rc = cb(name, handle, &attrs, cbarg, e4->cookie);
fsal_release_attrs(&attrs);
if (!cb_rc)
break;
}
xdr_free((xdrproc_t) xdr_readdirres, resoparray);
return st;
}
/* What to do about verifier if server needs one? */
static fsal_status_t pxy_readdir(struct fsal_obj_handle *dir_hdl,
fsal_cookie_t *whence, void *cbarg,
fsal_readdir_cb cb, attrmask_t attrmask,
bool *eof)
{
nfs_cookie4 cookie = 0;
struct pxy_obj_handle *ph;
if (whence)
cookie = (nfs_cookie4) *whence;
ph = container_of(dir_hdl, struct pxy_obj_handle, obj);
do {
fsal_status_t st;
st = pxy_do_readdir(ph, &cookie, cb, cbarg, attrmask, eof);
if (FSAL_IS_ERROR(st))
return st;
} while (*eof == false);
return fsalstat(ERR_FSAL_NO_ERROR, 0);
}
static fsal_status_t pxy_rename(struct fsal_obj_handle *obj_hdl,
struct fsal_obj_handle *olddir_hdl,
const char *old_name,
struct fsal_obj_handle *newdir_hdl,
const char *new_name)
{
int rc;
int opcnt = 0;
#define FSAL_RENAME_NB_OP_ALLOC 4
nfs_argop4 argoparray[FSAL_RENAME_NB_OP_ALLOC];
nfs_resop4 resoparray[FSAL_RENAME_NB_OP_ALLOC];
struct pxy_obj_handle *src;
struct pxy_obj_handle *tgt;
src = container_of(olddir_hdl, struct pxy_obj_handle, obj);
tgt = container_of(newdir_hdl, struct pxy_obj_handle, obj);
COMPOUNDV4_ARG_ADD_OP_PUTFH(opcnt, argoparray, src->fh4);
COMPOUNDV4_ARG_ADD_OP_SAVEFH(opcnt, argoparray);
COMPOUNDV4_ARG_ADD_OP_PUTFH(opcnt, argoparray, tgt->fh4);
COMPOUNDV4_ARG_ADD_OP_RENAME(opcnt, argoparray, (char *)old_name,
(char *)new_name);
rc = pxy_nfsv4_call(op_ctx->fsal_export, op_ctx->creds,
opcnt, argoparray, resoparray);
return nfsstat4_to_fsal(rc);
}
static fsal_status_t pxy_getattrs(struct fsal_obj_handle *obj_hdl,
struct attrlist *attrs)
{
struct pxy_obj_handle *ph;
int rc;
uint32_t opcnt = 0;
#define FSAL_GETATTR_NB_OP_ALLOC 2
nfs_argop4 argoparray[FSAL_GETATTR_NB_OP_ALLOC];
nfs_resop4 resoparray[FSAL_GETATTR_NB_OP_ALLOC];
GETATTR4resok *atok;
char fattr_blob[FATTR_BLOB_SZ];
ph = container_of(obj_hdl, struct pxy_obj_handle, obj);
COMPOUNDV4_ARG_ADD_OP_PUTFH(opcnt, argoparray, ph->fh4);
atok = pxy_fill_getattr_reply(resoparray + opcnt, fattr_blob,
sizeof(fattr_blob));
COMPOUNDV4_ARG_ADD_OP_GETATTR(opcnt, argoparray, pxy_bitmap_getattr);
rc = pxy_nfsv4_call(op_ctx->fsal_export, op_ctx->creds, opcnt,
argoparray, resoparray);
if (rc != NFS4_OK) {
if (attrs->request_mask & ATTR_RDATTR_ERR) {
/* Caller asked for error to be visible. */
attrs->valid_mask = ATTR_RDATTR_ERR;
}
return nfsstat4_to_fsal(rc);
}
if (nfs4_Fattr_To_FSAL_attr(attrs, &atok->obj_attributes, NULL) !=
NFS4_OK)
return fsalstat(ERR_FSAL_INVAL, 0);
return fsalstat(ERR_FSAL_NO_ERROR, 0);
}
/*
* Couple of things to note:
* 1. We assume that checks for things like cansettime are done
* by the caller.
* 2. attrs can be modified in this function but caller cannot
* assume that the attributes are up-to-date
*/
static fsal_status_t pxy_setattrs(struct fsal_obj_handle *obj_hdl,
struct attrlist *attrs)
{
int rc;
fattr4 input_attr;
uint32_t opcnt = 0;
struct pxy_obj_handle *ph;
#if GETATTR_AFTER
char fattr_blob[FATTR_BLOB_SZ];
GETATTR4resok *atok;
struct attrlist attrs_after;
#endif
#define FSAL_SETATTR_NB_OP_ALLOC 3
nfs_argop4 argoparray[FSAL_SETATTR_NB_OP_ALLOC];
nfs_resop4 resoparray[FSAL_SETATTR_NB_OP_ALLOC];
/*
* No way to update CTIME using a NFSv4 SETATTR.
* Server will return NFS4ERR_INVAL (22).
* time_metadata is a readonly attribute in NFSv4 and NFSv4.1.
* (section 5.7 in RFC7530 or RFC5651)
* Nevermind : this update is useless, we prevent it.
*/
FSAL_UNSET_MASK(attrs->valid_mask, ATTR_CTIME);
if (FSAL_TEST_MASK(attrs->valid_mask, ATTR_MODE))
attrs->mode &= ~op_ctx->fsal_export->exp_ops.
fs_umask(op_ctx->fsal_export);
ph = container_of(obj_hdl, struct pxy_obj_handle, obj);
if (pxy_fsalattr_to_fattr4(attrs, &input_attr) == -1)
return fsalstat(ERR_FSAL_INVAL, EINVAL);
COMPOUNDV4_ARG_ADD_OP_PUTFH(opcnt, argoparray, ph->fh4);
resoparray[opcnt].nfs_resop4_u.opsetattr.attrsset = empty_bitmap;
COMPOUNDV4_ARG_ADD_OP_SETATTR(opcnt, argoparray, input_attr);
#if GETATTR_AFTER
atok =
pxy_fill_getattr_reply(resoparray + opcnt, fattr_blob,
sizeof(fattr_blob));
COMPOUNDV4_ARG_ADD_OP_GETATTR(opcnt, argoparray, pxy_bitmap_getattr);
#endif
rc = pxy_nfsv4_call(op_ctx->fsal_export, op_ctx->creds,
opcnt, argoparray, resoparray);
nfs4_Fattr_Free(&input_attr);
if (rc != NFS4_OK)
return nfsstat4_to_fsal(rc);
#if GETATTR_AFTER
rc = nfs4_Fattr_To_FSAL_attr(&attrs_after, &atok->obj_attributes, NULL);
if (rc != NFS4_OK) {
LogWarn(COMPONENT_FSAL,
"Attribute conversion fails with %d, ignoring attibutes after making changes",
rc);
} else {
ph->attributes = attrs_after;
}
#endif
return fsalstat(ERR_FSAL_NO_ERROR, 0);
}
static bool pxy_handle_is(struct fsal_obj_handle *obj_hdl,
object_file_type_t type)
{
return obj_hdl->type == type;
}
static fsal_status_t pxy_unlink(struct fsal_obj_handle *dir_hdl,
struct fsal_obj_handle *obj_hdl,
const char *name)
{
int opcnt = 0;
int rc;
struct pxy_obj_handle *ph;
#define FSAL_UNLINK_NB_OP_ALLOC 3
nfs_argop4 argoparray[FSAL_UNLINK_NB_OP_ALLOC];
nfs_resop4 resoparray[FSAL_UNLINK_NB_OP_ALLOC];
#if GETATTR_AFTER
GETATTR4resok *atok;
char fattr_blob[FATTR_BLOB_SZ];
struct attrlist dirattr;
#endif
ph = container_of(dir_hdl, struct pxy_obj_handle, obj);
COMPOUNDV4_ARG_ADD_OP_PUTFH(opcnt, argoparray, ph->fh4);
COMPOUNDV4_ARG_ADD_OP_REMOVE(opcnt, argoparray, (char *)name);
#if GETATTR_AFTER
atok =
pxy_fill_getattr_reply(resoparray + opcnt, fattr_blob,
sizeof(fattr_blob));
COMPOUNDV4_ARG_ADD_OP_GETATTR(opcnt, argoparray, pxy_bitmap_getattr);
#endif
rc = pxy_nfsv4_call(op_ctx->fsal_export, op_ctx->creds,
opcnt, argoparray, resoparray);
if (rc != NFS4_OK)
return nfsstat4_to_fsal(rc);
#if GETATTR_AFTER
if (nfs4_Fattr_To_FSAL_attr(&dirattr, &atok->obj_attributes, NULL) ==
NFS4_OK)
ph->attributes = dirattr;
#endif
return fsalstat(ERR_FSAL_NO_ERROR, 0);
}
static fsal_status_t pxy_handle_digest(const struct fsal_obj_handle *obj_hdl,
fsal_digesttype_t output_type,
struct gsh_buffdesc *fh_desc)
{
struct pxy_obj_handle *ph =
container_of(obj_hdl, struct pxy_obj_handle, obj);
size_t fhs;
void *data;
/* sanity checks */
if (!fh_desc || !fh_desc->addr)
return fsalstat(ERR_FSAL_FAULT, 0);
switch (output_type) {
case FSAL_DIGEST_NFSV3:
#ifdef PROXY_HANDLE_MAPPING
fhs = sizeof(ph->h23);
data = &ph->h23;
break;
#endif
case FSAL_DIGEST_NFSV4:
fhs = ph->blob.len;
data = &ph->blob;
break;
default:
return fsalstat(ERR_FSAL_SERVERFAULT, 0);
}
if (fh_desc->len < fhs)
return fsalstat(ERR_FSAL_TOOSMALL, 0);
memcpy(fh_desc->addr, data, fhs);
fh_desc->len = fhs;
return fsalstat(ERR_FSAL_NO_ERROR, 0);
}
static void pxy_handle_to_key(struct fsal_obj_handle *obj_hdl,
struct gsh_buffdesc *fh_desc)
{
struct pxy_obj_handle *ph =
container_of(obj_hdl, struct pxy_obj_handle, obj);
fh_desc->addr = &ph->blob;
fh_desc->len = ph->blob.len;
}
static void pxy_hdl_release(struct fsal_obj_handle *obj_hdl)
{
struct pxy_obj_handle *ph =
container_of(obj_hdl, struct pxy_obj_handle, obj);
fsal_obj_handle_fini(obj_hdl);
gsh_free(ph);
}
/*
* Without name the 'open' for NFSv4 makes no sense - we could
* send a getattr to the backend server but it's not going to
* do anything useful anyway, so just save the openflags to record
* the fact that file has been 'opened' and be done.
*/
static fsal_status_t pxy_open(struct fsal_obj_handle *obj_hdl,
fsal_openflags_t openflags)
{
struct pxy_obj_handle *ph;
if (!obj_hdl)
return fsalstat(ERR_FSAL_FAULT, EINVAL);
ph = container_of(obj_hdl, struct pxy_obj_handle, obj);
if ((ph->openflags != FSAL_O_CLOSED) && (ph->openflags != openflags))
return fsalstat(ERR_FSAL_FILE_OPEN, EBADF);
ph->openflags = openflags;
return fsalstat(ERR_FSAL_NO_ERROR, 0);
}
static fsal_openflags_t pxy_status(struct fsal_obj_handle *obj_hdl)
{
struct pxy_obj_handle *ph;
ph = container_of(obj_hdl, struct pxy_obj_handle, obj);
return ph->openflags;
}
static fsal_status_t pxy_read(struct fsal_obj_handle *obj_hdl,
uint64_t offset, size_t buffer_size, void *buffer,
size_t *read_amount, bool *end_of_file)
{
int mr;
int rc;
int opcnt = 0;
struct pxy_obj_handle *ph;
#define FSAL_READ_NB_OP_ALLOC 2
nfs_argop4 argoparray[FSAL_READ_NB_OP_ALLOC];
nfs_resop4 resoparray[FSAL_READ_NB_OP_ALLOC];
READ4resok *rok;
if (!buffer_size) {
*read_amount = 0;
*end_of_file = false;
return fsalstat(ERR_FSAL_NO_ERROR, 0);
}
ph = container_of(obj_hdl, struct pxy_obj_handle, obj);
#if 0
if ((ph->openflags & (FSAL_O_RDONLY | FSAL_O_RDWR)) == 0)
return fsalstat(ERR_FSAL_FILE_OPEN, EBADF);
#endif
mr = op_ctx->fsal_export->exp_ops.fs_maxread(op_ctx->fsal_export);
if (buffer_size > mr)
buffer_size = mr;
COMPOUNDV4_ARG_ADD_OP_PUTFH(opcnt, argoparray, ph->fh4);
rok = &resoparray[opcnt].nfs_resop4_u.opread.READ4res_u.resok4;
rok->data.data_val = buffer;
rok->data.data_len = buffer_size;
COMPOUNDV4_ARG_ADD_OP_READ(opcnt, argoparray, offset, buffer_size);
rc = pxy_nfsv4_call(op_ctx->fsal_export, op_ctx->creds,
opcnt, argoparray, resoparray);
if (rc != NFS4_OK)
return nfsstat4_to_fsal(rc);
*end_of_file = rok->eof;
*read_amount = rok->data.data_len;
return fsalstat(ERR_FSAL_NO_ERROR, 0);
}
static fsal_status_t pxy_write(struct fsal_obj_handle *obj_hdl,
uint64_t offset, size_t size, void *buffer,
size_t *write_amount, bool *fsal_stable)
{
int mw;
int rc;
int opcnt = 0;
#define FSAL_WRITE_NB_OP_ALLOC 2
nfs_argop4 argoparray[FSAL_WRITE_NB_OP_ALLOC];
nfs_resop4 resoparray[FSAL_WRITE_NB_OP_ALLOC];
WRITE4resok *wok;
struct pxy_obj_handle *ph;
if (!size) {
*write_amount = 0;
return fsalstat(ERR_FSAL_NO_ERROR, 0);
}
ph = container_of(obj_hdl, struct pxy_obj_handle, obj);
#if 0
if ((ph->openflags & (FSAL_O_WRONLY | FSAL_O_RDWR | FSAL_O_APPEND)) ==
0) {
return fsalstat(ERR_FSAL_FILE_OPEN, EBADF);
}
#endif
mw = op_ctx->fsal_export->exp_ops.fs_maxwrite(op_ctx->fsal_export);
if (size > mw)
size = mw;
COMPOUNDV4_ARG_ADD_OP_PUTFH(opcnt, argoparray, ph->fh4);
wok = &resoparray[opcnt].nfs_resop4_u.opwrite.WRITE4res_u.resok4;
COMPOUNDV4_ARG_ADD_OP_WRITE(opcnt, argoparray, offset, buffer, size);
rc = pxy_nfsv4_call(op_ctx->fsal_export, op_ctx->creds,
opcnt, argoparray, resoparray);
if (rc != NFS4_OK)
return nfsstat4_to_fsal(rc);
*write_amount = wok->count;
*fsal_stable = false;
return fsalstat(ERR_FSAL_NO_ERROR, 0);
}
/* We send all out writes as DATA_SYNC, commit becomes a NO-OP */
static fsal_status_t pxy_commit(struct fsal_obj_handle *obj_hdl,
off_t offset,
size_t len)
{
return fsalstat(ERR_FSAL_NO_ERROR, 0);
}
static fsal_status_t pxy_close(struct fsal_obj_handle *obj_hdl)
{
struct pxy_obj_handle *ph;
if (!obj_hdl)
return fsalstat(ERR_FSAL_FAULT, EINVAL);
ph = container_of(obj_hdl, struct pxy_obj_handle, obj);
if (ph->openflags == FSAL_O_CLOSED)
return fsalstat(ERR_FSAL_NOT_OPENED, EBADF);
ph->openflags = FSAL_O_CLOSED;
return fsalstat(ERR_FSAL_NO_ERROR, 0);
}
void pxy_handle_ops_init(struct fsal_obj_ops *ops)
{
ops->release = pxy_hdl_release;
ops->lookup = pxy_lookup;
ops->readdir = pxy_readdir;
ops->create = pxy_create;
ops->mkdir = pxy_mkdir;
ops->mknode = pxy_mknod;
ops->symlink = pxy_symlink;
ops->readlink = pxy_readlink;
ops->getattrs = pxy_getattrs;
ops->setattrs = pxy_setattrs;
ops->link = pxy_link;
ops->rename = pxy_rename;
ops->unlink = pxy_unlink;
ops->open = pxy_open;
ops->status = pxy_status;
ops->read = pxy_read;
ops->write = pxy_write;
ops->commit = pxy_commit;
ops->close = pxy_close;
ops->handle_is = pxy_handle_is;
ops->handle_digest = pxy_handle_digest;
ops->handle_to_key = pxy_handle_to_key;
}
#ifdef PROXY_HANDLE_MAPPING
static unsigned int hash_nfs_fh4(const nfs_fh4 *fh, unsigned int cookie)
{
const char *cpt;
unsigned int sum = cookie;
unsigned int extract;
unsigned int mod = fh->nfs_fh4_len % sizeof(unsigned int);
for (cpt = fh->nfs_fh4_val;
cpt - fh->nfs_fh4_val < fh->nfs_fh4_len - mod;
cpt += sizeof(unsigned int)) {
memcpy(&extract, cpt, sizeof(unsigned int));
sum = (3 * sum + 5 * extract + 1999);
}
/*
* If the handle is not 32 bits-aligned, the last loop will
* get uninitialized chars after the end of the handle. We
* must avoid this by skipping the last loop and doing a
* special processing for the last bytes
*/
if (mod) {
extract = 0;
while (cpt - fh->nfs_fh4_val < fh->nfs_fh4_len) {
extract <<= 8;
extract |= (uint8_t) (*cpt++);
}
sum = (3 * sum + 5 * extract + 1999);
}
return sum;
}
#endif
static struct pxy_obj_handle *pxy_alloc_handle(struct fsal_export *exp,
const nfs_fh4 *fh,
fattr4 *obj_attributes,
struct attrlist *attrs_out)
{
struct pxy_obj_handle *n = gsh_malloc(sizeof(*n) + fh->nfs_fh4_len);
compound_data_t data;
struct attrlist attributes;
memset(&attributes, 0, sizeof(attributes));
memset(&data, 0, sizeof(data));
if (n) {
data.current_obj = &n->obj;
if (nfs4_Fattr_To_FSAL_attr(&attributes,
obj_attributes,
&data) != NFS4_OK) {
gsh_free(n);
return NULL;
}
n->fh4 = *fh;
n->fh4.nfs_fh4_val = n->blob.bytes;
memcpy(n->blob.bytes, fh->nfs_fh4_val, fh->nfs_fh4_len);
n->blob.len = fh->nfs_fh4_len + sizeof(n->blob);
n->blob.type = attributes.type;
#ifdef PROXY_HANDLE_MAPPING
int rc;
memset(&n->h23, 0, sizeof(n->h23));
n->h23.len = sizeof(n->h23);
n->h23.type = PXY_HANDLE_MAPPED;
n->h23.object_id = n->obj.fileid;
n->h23.handle_hash = hash_nfs_fh4(fh, n->obj.fileid);
rc = HandleMap_SetFH(&n->h23, &n->blob, n->blob.len);
if ((rc != HANDLEMAP_SUCCESS) && (rc != HANDLEMAP_EXISTS)) {
gsh_free(n);
return NULL;
}
#endif
fsal_obj_handle_init(&n->obj, exp, attributes.type);
n->obj.fsid = attributes.fsid;
n->obj.fileid = attributes.fileid;
pxy_handle_ops_init(&n->obj.obj_ops);
if (attrs_out != NULL) {
/* We aren't keeping ACL ref ourself, so pass it
* to the caller.
*/
fsal_copy_attrs(attrs_out, &attributes, true);
} else {
/* Make sure we release the attributes. */
fsal_release_attrs(&attributes);
}
}
return n;
}
/* export methods that create object handles
*/
fsal_status_t pxy_lookup_path(struct fsal_export *exp_hdl,
const char *path,
struct fsal_obj_handle **handle,
struct attrlist *attrs_out)
{
struct fsal_obj_handle *next;
struct fsal_obj_handle *parent = NULL;
char *saved;
char *pcopy;
char *p, *pnext;
struct user_cred *creds = op_ctx->creds;
pcopy = gsh_strdup(path);
p = strtok_r(pcopy, "/", &saved);
while (p) {
if (strcmp(p, "..") == 0) {
/* Don't allow lookup of ".." */
LogInfo(COMPONENT_FSAL,
"Attempt to use \"..\" element in path %s",
path);
gsh_free(pcopy);
return fsalstat(ERR_FSAL_ACCESS, EACCES);
}
/* Get the next token now, so we know if we are at the
* terminal token or not.
*/
pnext = strtok_r(NULL, "/", &saved);
/* Note that if any element is a symlink, the following will
* fail, thus no security exposure. Only pass back the
* attributes of the terminal lookup.
*/
fsal_status_t st = pxy_lookup_impl(parent, exp_hdl, creds, p,
&next, pnext == NULL ?
attrs_out : NULL);
if (FSAL_IS_ERROR(st)) {
gsh_free(pcopy);
return st;
}
p = pnext;
parent = next;
}
/* The final element could be a symlink, but either way we are called
* will not work with a symlink, so no security exposure there.
*/
gsh_free(pcopy);
*handle = next;
return fsalstat(ERR_FSAL_NO_ERROR, 0);
}
/*
* Create an FSAL 'object' from the handle - used
* to construct objects from a handle which has been
* 'extracted' by .extract_handle.
*/
fsal_status_t pxy_create_handle(struct fsal_export *exp_hdl,
struct gsh_buffdesc *hdl_desc,
struct fsal_obj_handle **handle,
struct attrlist *attrs_out)
{
nfs_fh4 fh4;
struct pxy_obj_handle *ph;
struct pxy_handle_blob *blob;
int rc;
uint32_t opcnt = 0;
nfs_argop4 argoparray[FSAL_GETATTR_NB_OP_ALLOC];
nfs_resop4 resoparray[FSAL_GETATTR_NB_OP_ALLOC];
GETATTR4resok *atok;
char fattr_blob[FATTR_BLOB_SZ];
blob = (struct pxy_handle_blob *)hdl_desc->addr;
if (blob->len != hdl_desc->len)
return fsalstat(ERR_FSAL_INVAL, 0);
fh4.nfs_fh4_val = blob->bytes;
fh4.nfs_fh4_len = blob->len - sizeof(*blob);
COMPOUNDV4_ARG_ADD_OP_PUTFH(opcnt, argoparray, fh4);
atok = pxy_fill_getattr_reply(resoparray + opcnt, fattr_blob,
sizeof(fattr_blob));
COMPOUNDV4_ARG_ADD_OP_GETATTR(opcnt, argoparray, pxy_bitmap_getattr);
rc = pxy_nfsv4_call(exp_hdl, op_ctx->creds, opcnt,
argoparray, resoparray);
if (rc != NFS4_OK)
return nfsstat4_to_fsal(rc);
ph = pxy_alloc_handle(exp_hdl, &fh4, &atok->obj_attributes, attrs_out);
if (!ph)
return fsalstat(ERR_FSAL_FAULT, 0);
*handle = &ph->obj;
return fsalstat(ERR_FSAL_NO_ERROR, 0);
}
fsal_status_t pxy_get_dynamic_info(struct fsal_export *exp_hdl,
struct fsal_obj_handle *obj_hdl,
fsal_dynamicfsinfo_t *infop)
{
int rc;
int opcnt = 0;
#define FSAL_FSINFO_NB_OP_ALLOC 2
nfs_argop4 argoparray[FSAL_FSINFO_NB_OP_ALLOC];
nfs_resop4 resoparray[FSAL_FSINFO_NB_OP_ALLOC];
GETATTR4resok *atok;
char fattr_blob[48]; /* 6 values, 8 bytes each */
struct pxy_obj_handle *ph;
ph = container_of(obj_hdl, struct pxy_obj_handle, obj);
COMPOUNDV4_ARG_ADD_OP_PUTFH(opcnt, argoparray, ph->fh4);
atok =
pxy_fill_getattr_reply(resoparray + opcnt, fattr_blob,
sizeof(fattr_blob));
COMPOUNDV4_ARG_ADD_OP_GETATTR(opcnt, argoparray, pxy_bitmap_fsinfo);
rc = pxy_nfsv4_call(exp_hdl, op_ctx->creds, opcnt, argoparray,
resoparray);
if (rc != NFS4_OK)
return nfsstat4_to_fsal(rc);
if (nfs4_Fattr_To_fsinfo(infop, &atok->obj_attributes) != NFS4_OK)
return fsalstat(ERR_FSAL_INVAL, 0);
return fsalstat(ERR_FSAL_NO_ERROR, 0);
}
/* Convert of-the-wire digest into unique 'handle' which
* can be used to identify the object */
fsal_status_t pxy_extract_handle(struct fsal_export *exp_hdl,
fsal_digesttype_t in_type,
struct gsh_buffdesc *fh_desc,
int flags)
{
struct pxy_handle_blob *pxyblob;
size_t fh_size;
if (!fh_desc || !fh_desc->addr)
return fsalstat(ERR_FSAL_FAULT, EINVAL);
pxyblob = (struct pxy_handle_blob *)fh_desc->addr;
fh_size = pxyblob->len;
#ifdef PROXY_HANDLE_MAPPING
if (in_type == FSAL_DIGEST_NFSV3)
fh_size = sizeof(nfs23_map_handle_t);
#endif
if (fh_desc->len != fh_size) {
LogMajor(COMPONENT_FSAL,
"Size mismatch for handle. should be %zu, got %zu",
fh_size, fh_desc->len);
return fsalstat(ERR_FSAL_SERVERFAULT, 0);
}
#ifdef PROXY_HANDLE_MAPPING
if (in_type == FSAL_DIGEST_NFSV3) {
nfs23_map_handle_t *h23 = (nfs23_map_handle_t *) fh_desc->addr;
if (h23->type != PXY_HANDLE_MAPPED)
return fsalstat(ERR_FSAL_STALE, ESTALE);
/* As long as HandleMap_GetFH copies nfs23 handle into
* the key before lookup I can get away with using
* the same buffer for input and output */
if (HandleMap_GetFH(h23, fh_desc) != HANDLEMAP_SUCCESS)
return fsalstat(ERR_FSAL_STALE, 0);
fh_size = fh_desc->len;
}
#endif
fh_desc->len = fh_size;
return fsalstat(ERR_FSAL_NO_ERROR, 0);
}