1/*
2 * Copyright (c) 2014 Christoph Hellwig.
3 */
4#include <linux/sunrpc/svc.h>
5#include <linux/exportfs.h>
6#include <linux/nfs4.h>
7
8#include "nfsd.h"
9#include "blocklayoutxdr.h"
10
11#define NFSDDBG_FACILITY	NFSDDBG_PNFS
12
13
14__be32
15nfsd4_block_encode_layoutget(struct xdr_stream *xdr,
16		struct nfsd4_layoutget *lgp)
17{
18	struct pnfs_block_extent *b = lgp->lg_content;
19	int len = sizeof(__be32) + 5 * sizeof(__be64) + sizeof(__be32);
20	__be32 *p;
21
22	p = xdr_reserve_space(xdr, sizeof(__be32) + len);
23	if (!p)
24		return nfserr_toosmall;
25
26	*p++ = cpu_to_be32(len);
27	*p++ = cpu_to_be32(1);		/* we always return a single extent */
28
29	p = xdr_encode_opaque_fixed(p, &b->vol_id,
30			sizeof(struct nfsd4_deviceid));
31	p = xdr_encode_hyper(p, b->foff);
32	p = xdr_encode_hyper(p, b->len);
33	p = xdr_encode_hyper(p, b->soff);
34	*p++ = cpu_to_be32(b->es);
35	return 0;
36}
37
38static int
39nfsd4_block_encode_volume(struct xdr_stream *xdr, struct pnfs_block_volume *b)
40{
41	__be32 *p;
42	int len;
43
44	switch (b->type) {
45	case PNFS_BLOCK_VOLUME_SIMPLE:
46		len = 4 + 4 + 8 + 4 + b->simple.sig_len;
47		p = xdr_reserve_space(xdr, len);
48		if (!p)
49			return -ETOOSMALL;
50
51		*p++ = cpu_to_be32(b->type);
52		*p++ = cpu_to_be32(1);	/* single signature */
53		p = xdr_encode_hyper(p, b->simple.offset);
54		p = xdr_encode_opaque(p, b->simple.sig, b->simple.sig_len);
55		break;
56	default:
57		return -ENOTSUPP;
58	}
59
60	return len;
61}
62
63__be32
64nfsd4_block_encode_getdeviceinfo(struct xdr_stream *xdr,
65		struct nfsd4_getdeviceinfo *gdp)
66{
67	struct pnfs_block_deviceaddr *dev = gdp->gd_device;
68	int len = sizeof(__be32), ret, i;
69	__be32 *p;
70
71	p = xdr_reserve_space(xdr, len + sizeof(__be32));
72	if (!p)
73		return nfserr_resource;
74
75	for (i = 0; i < dev->nr_volumes; i++) {
76		ret = nfsd4_block_encode_volume(xdr, &dev->volumes[i]);
77		if (ret < 0)
78			return nfserrno(ret);
79		len += ret;
80	}
81
82	/*
83	 * Fill in the overall length and number of volumes at the beginning
84	 * of the layout.
85	 */
86	*p++ = cpu_to_be32(len);
87	*p++ = cpu_to_be32(dev->nr_volumes);
88	return 0;
89}
90
91int
92nfsd4_block_decode_layoutupdate(__be32 *p, u32 len, struct iomap **iomapp,
93		u32 block_size)
94{
95	struct iomap *iomaps;
96	u32 nr_iomaps, expected, i;
97
98	if (len < sizeof(u32)) {
99		dprintk("%s: extent array too small: %u\n", __func__, len);
100		return -EINVAL;
101	}
102
103	nr_iomaps = be32_to_cpup(p++);
104	expected = sizeof(__be32) + nr_iomaps * NFS4_BLOCK_EXTENT_SIZE;
105	if (len != expected) {
106		dprintk("%s: extent array size mismatch: %u/%u\n",
107			__func__, len, expected);
108		return -EINVAL;
109	}
110
111	iomaps = kcalloc(nr_iomaps, sizeof(*iomaps), GFP_KERNEL);
112	if (!iomaps) {
113		dprintk("%s: failed to allocate extent array\n", __func__);
114		return -ENOMEM;
115	}
116
117	for (i = 0; i < nr_iomaps; i++) {
118		struct pnfs_block_extent bex;
119
120		memcpy(&bex.vol_id, p, sizeof(struct nfsd4_deviceid));
121		p += XDR_QUADLEN(sizeof(struct nfsd4_deviceid));
122
123		p = xdr_decode_hyper(p, &bex.foff);
124		if (bex.foff & (block_size - 1)) {
125			dprintk("%s: unaligned offset 0x%llx\n",
126				__func__, bex.foff);
127			goto fail;
128		}
129		p = xdr_decode_hyper(p, &bex.len);
130		if (bex.len & (block_size - 1)) {
131			dprintk("%s: unaligned length 0x%llx\n",
132				__func__, bex.foff);
133			goto fail;
134		}
135		p = xdr_decode_hyper(p, &bex.soff);
136		if (bex.soff & (block_size - 1)) {
137			dprintk("%s: unaligned disk offset 0x%llx\n",
138				__func__, bex.soff);
139			goto fail;
140		}
141		bex.es = be32_to_cpup(p++);
142		if (bex.es != PNFS_BLOCK_READWRITE_DATA) {
143			dprintk("%s: incorrect extent state %d\n",
144				__func__, bex.es);
145			goto fail;
146		}
147
148		iomaps[i].offset = bex.foff;
149		iomaps[i].length = bex.len;
150	}
151
152	*iomapp = iomaps;
153	return nr_iomaps;
154fail:
155	kfree(iomaps);
156	return -EINVAL;
157}
158