[PATCH 8/9] nfs: switch to nfs3

Jean-Christophe PLAGNIOL-VILLARD plagnioj at jcrosoft.com
Fri Feb 7 01:48:57 EST 2014


On 17:40 Thu 06 Feb     , Uwe Kleine-K??nig wrote:
> This was tested against nfs-kernel-server and unfs3.
> 
> Signed-off-by: Uwe Kleine-König <u.kleine-koenig at pengutronix.de>
> ---
>  fs/nfs.c | 859 ++++++++++++++++++++++++++++++++++++++++++++-------------------
>  1 file changed, 608 insertions(+), 251 deletions(-)
> 
> diff --git a/fs/nfs.c b/fs/nfs.c
> index 373159d6ffb7..8eec63078dd3 100644
> --- a/fs/nfs.c
> +++ b/fs/nfs.c
> @@ -1,10 +1,12 @@
>  /*
>   * nfs.c - barebox NFS driver
>   *
> + * Copyright (c) 2014 Uwe Kleine-König, Pengutronix
>   * Copyright (c) 2012 Sascha Hauer <s.hauer at pengutronix.de>, Pengutronix
>   * Copyright (c) Masami Komiya <mkomiya at sonare.it> 2004
>   *
> - * Based on U-Boot NFS code which is based on NetBSD code
> + * Based on U-Boot NFS code which is based on NetBSD code with
> + * major changes to support nfs3.
>   *
>   * See file CREDITS for list of people who contributed to this
>   * project.
> @@ -17,7 +19,6 @@
>   * but WITHOUT ANY WARRANTY; without even the implied warranty of
>   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>   * GNU General Public License for more details.
> - *
>   */
>  
>  #include <common.h>
> @@ -34,6 +35,9 @@
>  #include <kfifo.h>
>  #include <sizes.h>
>  
> +#define ntohll(val)	__be64_to_cpu(val)
> +#define htonll(val)	__cpu_to_be64(val)

use the cpu_to and to_cpu directly

as if we have the standard define in any header later this will cause issues
> +
>  #define SUNRPC_PORT     111
>  
>  #define PROG_PORTMAP    100000
> @@ -48,34 +52,54 @@
>  #define MOUNT_ADDENTRY	1
>  #define MOUNT_UMOUNT	3
>  
> -#define NFS_GETATTR     1
> -#define NFS_LOOKUP      4
> -#define NFS_READLINK    5
> -#define NFS_READ        6
> -#define NFS_READDIR	16
> -
> -#define NFS_FHSIZE      32
> -
> -enum nfs_stat {
> -	NFS_OK		= 0,
> -	NFSERR_PERM	= 1,
> -	NFSERR_NOENT	= 2,
> -	NFSERR_IO	= 5,
> -	NFSERR_NXIO	= 6,
> -	NFSERR_ACCES	= 13,
> -	NFSERR_EXIST	= 17,
> -	NFSERR_NODEV	= 19,
> -	NFSERR_NOTDIR	= 20,
> -	NFSERR_ISDIR	= 21,
> -	NFSERR_FBIG	= 27,
> -	NFSERR_NOSPC	= 28,
> -	NFSERR_ROFS	= 30,
> -	NFSERR_NAMETOOLONG=63,
> -	NFSERR_NOTEMPTY	= 66,
> -	NFSERR_DQUOT	= 69,
> -	NFSERR_STALE	= 70,
> -	NFSERR_WFLUSH	= 99,
> -};
> +#define NFSPROC3_GETATTR	1
> +#define NFSPROC3_LOOKUP		3
> +#define NFSPROC3_READLINK	5
> +#define NFSPROC3_READ		6
> +#define NFSPROC3_READDIR	16
> +
> +#define NFS3_FHSIZE      64
> +#define NFS3_COOKIEVERFSIZE	8
> +
> +/* values of enum ftype3 */
> +#define NF3REG	1
> +#define NF3DIR	2
> +#define NF3BLK	3
> +#define NF3CHR	4
> +#define NF3LNK	5
> +#define NF3SOCK	6
> +#define NF3FIFO	7
> +
> +/* values for enum nfsstat3 */
> +#define NFS3_OK			    0
> +#define NFS3ERR_PERM		    1
> +#define NFS3ERR_NOENT		    2
> +#define NFS3ERR_IO   		    5
> +#define NFS3ERR_NXIO 		    6
> +#define NFS3ERR_ACCES		   13
> +#define NFS3ERR_EXIST		   17
> +#define NFS3ERR_XDEV		   18
> +#define NFS3ERR_NODEV		   19
> +#define NFS3ERR_NOTDIR		   20
> +#define NFS3ERR_ISDIR		   21
> +#define NFS3ERR_INVAL		   22
> +#define NFS3ERR_FBIG		   27
> +#define NFS3ERR_NOSPC		   28
> +#define NFS3ERR_ROFS		   30
> +#define NFS3ERR_MLINK		   31
> +#define NFS3ERR_NAMETOOLONG	   63
> +#define NFS3ERR_NOTEMPTY	   66
> +#define NFS3ERR_DQUOT		   69
> +#define NFS3ERR_STALE		   70
> +#define NFS3ERR_REMOTE		   71
> +#define NFS3ERR_BADHANDLE 	10001
> +#define NFS3ERR_NOT_SYNC	10002
> +#define NFS3ERR_BAD_COOKIE	10003
> +#define NFS3ERR_NOTSUPP		10004
> +#define NFS3ERR_TOOSMALL	10005
> +#define NFS3ERR_SERVERFAULT	10006
> +#define NFS3ERR_BADTYPE		10007
> +#define NFS3ERR_JUKEBOX		10008
>  
>  static void *nfs_packet;
>  static int nfs_len;
> @@ -107,51 +131,96 @@ struct nfs_priv {
>  	struct net_connection *con;
>  	IPaddr_t server;
>  	char *path;
> -	int mount_port;
> -	int nfs_port;
> -	unsigned long rpc_id;
> -	char rootfh[NFS_FHSIZE];
> +	unsigned short mount_port;
> +	unsigned short nfs_port;
> +	uint32_t rpc_id;
> +	uint32_t rootfh_len;
> +	char rootfh[NFS3_FHSIZE];
>  };
>  
>  struct file_priv {
>  	struct kfifo *fifo;
>  	void *buf;
> -	char filefh[NFS_FHSIZE];
> +	uint32_t filefh_len;
> +	char filefh[NFS3_FHSIZE];
>  	struct nfs_priv *npriv;
>  };
>  
>  static uint64_t nfs_timer_start;
>  
> -static int	nfs_state;
> +static int nfs_state;
>  #define STATE_DONE			1
>  #define STATE_START			2
>  
> -enum ftype {
> -	NFNON = 0,
> -	NFREG = 1,
> -	NFDIR = 2,
> -	NFBLK = 3,
> -	NFCHR = 4,
> -	NFLNK = 5
> -};
> -
> -struct fattr {
> -	uint32_t type;
> -	uint32_t mode;
> -	uint32_t nlink;
> -	uint32_t uid;
> -	uint32_t gid;
> -	uint32_t size;
> -	uint32_t blocksize;
> -	uint32_t rdev;
> -	uint32_t blocks;
> -};
> -
> -struct readdirargs {
> -	char filefh[NFS_FHSIZE];
> -	uint32_t cookie;
> -	uint32_t count;
> -};
> +/*
> + * common types used in more than one request:
> + *
> + * typedef uint32 count3;
> + * typedef uint32 gid3;
> + * typedef uint32 mode3;
> + * typedef uint32 uid3;
> + *
> + * typedef uint64 cookie3;
> + * typedef uint64 fileid3;
> + * typedef uint64 size3;
> + *
> + * typedef string filename3<>;
> + * typedef string nfspath3<>;
> + *
> + * typedef opaque cookieverf3[NFS3_COOKIEVERFSIZE];
> + *
> + * enum ftype3 {
> + * 	NF3REG = 1,
> + * 	NF3DIR = 2,
> + * 	NF3BLK = 3,
> + * 	NF3CHR = 4,
> + * 	NF3LNK = 5,
> + * 	NF3SOCK = 6,
> + * 	NF3FIFO = 7
> + * };
> + *
> + * struct specdata3 {
> + * 	uint32 specdata1;
> + * 	uint32 specdata2;
> + * };
> + *
> + * struct nfs_fh3 {
> + * 	opaque data<NFS3_FHSIZE>;
> + * }
> + *
> + * struct nfstime3 {
> + * 	uint32 seconds;
> + * 	uint32 nseconds;
> + * };
> + *
> + * struct fattr3 {
> + * 	ftype3 type;
> + * 	mode3 mode;
> + * 	uint32_t nlink;
> + * 	uid3 uid;
> + * 	gid3 gid;
> + * 	size3 size;
> + * 	size3 used;
> + * 	specdata3 rdev;
> + * 	uint64_t fsid;
> + * 	fileid3 fileid;
> + * 	nfstime3 atime;
> + * 	nfstime3 mtime;
> + * 	nfstime3 ctime;
> + * };
> + *
> + * struct diropargs3 {
> + * 	nfs_fh3 dir;
> + * 	filename3 name;
> + * }
> + *
> + * union post_op_attr switch (bool attributes_follow) {
> + * case TRUE:
> + * 	fattr3 attributes;
> + * case FALSE:
> + * 	void;
> + * };
> + */
>  
>  struct xdr_stream {
>  	__be32 *p;
> @@ -162,6 +231,20 @@ struct xdr_stream {
>  #define xdr_zero 0
>  #define XDR_QUADLEN(l)		(((l) + 3) >> 2)
>  
> +struct nfs_dir {
> +	DIR dir;
> +
> +	/*
> +	 * stream points to the next entry3 in the reply member of READDIR3res
> +	 * (if any, to the end indicator otherwise).
> +	 */
> +	struct xdr_stream stream;
> +	struct dirent ent;
> +	struct file_priv *priv;
> +	uint64_t cookie;
> +	char cookieverf[NFS3_COOKIEVERFSIZE];
> +};
> +
>  static void xdr_init(struct xdr_stream *stream, void *buf, int len)
>  {
>  	stream->p = stream->buf = buf;
> @@ -192,8 +275,10 @@ static __be32 *xdr_inline_decode(struct xdr_stream *xdr, size_t nbytes)
>  	return p;
>  }
>  
> -static int decode_filename(struct xdr_stream *xdr,
> -		char *name, u32 *length)
> +/*
> + * name is expected to point to a buffer with a size of at least 256 bytes.
> + */
> +static int decode_filename(struct xdr_stream *xdr, char *name, u32 *length)
>  {
>  	__be32 *p;
>  	u32 count;
> @@ -201,7 +286,7 @@ static int decode_filename(struct xdr_stream *xdr,
>  	p = xdr_inline_decode(xdr, 4);
>  	if (!p)
>  		goto out_overflow;
> -	count = be32_to_cpup(p);
> +	count = ntohl(net_read_uint32(p));
>  	if (count > 255)
>  		goto out_nametoolong;
>  	p = xdr_inline_decode(xdr, count);
> @@ -211,11 +296,13 @@ static int decode_filename(struct xdr_stream *xdr,
>  	name[count] = 0;
>  	*length = count;
>  	return 0;
> +
>  out_nametoolong:
> -	printk("NFS: returned filename too long: %u\n", count);
> +	printf("%s: returned a too long filename: %u\n", __func__, count);
can we use dev_xx for message
>  	return -ENAMETOOLONG;
> +
>  out_overflow:
> -	printf("%s overflow\n",__func__);
> +	printf("%s: premature end of packet\n", __func__);
>  	return -EIO;
>  }
>  
> @@ -247,7 +334,8 @@ static uint32_t *rpc_add_credentials(uint32_t *p)
>  	return p;
>  }
>  
> -static int rpc_check_reply(unsigned char *pkt, int rpc_prog, unsigned long rpc_id, int *nfserr)
> +static int rpc_check_reply(unsigned char *pkt,
> +		int rpc_prog, uint32_t rpc_id, int *nfserr)
>  {
>  	uint32_t *data;
>  	struct rpc_reply rpc;
> @@ -292,37 +380,41 @@ static int rpc_req(struct nfs_priv *npriv, int rpc_prog, int rpc_proc,
>  		uint32_t *data, int datalen)
>  {
>  	struct rpc_call pkt;
> -	unsigned long id;
> -	int dport;
> +	unsigned short dport;
>  	int ret;
>  	unsigned char *payload = net_udp_get_payload(npriv->con);
>  	int nfserr;
>  	int tries = 0;
>  
>  	npriv->rpc_id++;
> -	id = npriv->rpc_id;
>  
> -	pkt.id = htonl(id);
> +	pkt.id = htonl(npriv->rpc_id);
>  	pkt.type = htonl(MSG_CALL);
>  	pkt.rpcvers = htonl(2);	/* use RPC version 2 */
>  	pkt.prog = htonl(rpc_prog);
> -	pkt.vers = htonl(2);	/* portmapper is version 2 */
>  	pkt.proc = htonl(rpc_proc);
>  
> -	memcpy(payload, &pkt, sizeof(pkt));
> -	memcpy(payload + sizeof(pkt), data, datalen * sizeof(uint32_t));
> +	debug("%s: prog: %d, proc: %d\n", __func__, rpc_prog, rpc_proc);
>  
> -	if (rpc_prog == PROG_PORTMAP)
> +	if (rpc_prog == PROG_PORTMAP) {
>  		dport = SUNRPC_PORT;
> -	else if (rpc_prog == PROG_MOUNT)
> +		pkt.vers = htonl(2);
> +	} else if (rpc_prog == PROG_MOUNT) {
>  		dport = npriv->mount_port;
> -	else
> +		pkt.vers = htonl(3);
> +	} else {
>  		dport = npriv->nfs_port;
> +		pkt.vers = htonl(3);
> +	}
> +
> +	memcpy(payload, &pkt, sizeof(pkt));
> +	memcpy(payload + sizeof(pkt), data, datalen * sizeof(uint32_t));
>  
>  	npriv->con->udp->uh_dport = htons(dport);
>  
>  again:
> -	ret = net_udp_send(npriv->con, sizeof(pkt) + datalen * sizeof(uint32_t));
> +	ret = net_udp_send(npriv->con,
> +			sizeof(pkt) + datalen * sizeof(uint32_t));
>  
>  	nfs_timer_start = get_time_ns();
>  
> @@ -357,7 +449,7 @@ again:
>  /*
>   * rpc_lookup_req - Lookup RPC Port numbers
>   */
> -static int rpc_lookup_req(struct nfs_priv *npriv, int prog, int ver)
> +static int rpc_lookup_req(struct nfs_priv *npriv, uint32_t prog, uint32_t ver)
>  {
>  	uint32_t data[16];
>  	int ret;
> @@ -378,6 +470,135 @@ static int rpc_lookup_req(struct nfs_priv *npriv, int prog, int ver)
>  	return port;
>  }
>  
> +static uint32_t *nfs_add_uint32(uint32_t *p, uint32_t val)
> +{
> +	*p++ = htonl(val);
> +	return p;
> +}
> +
> +static uint32_t *nfs_add_uint64(uint32_t *p, uint64_t val)
> +{
> +	uint64_t nval = htonll(val);
missing blank line

> +	memcpy(p, &nval, 8);
> +	return p + 2;
> +}
> +
> +static uint32_t *nfs_add_fh3(uint32_t *p, unsigned fh_len, const char *fh)
> +{
> +	*p++ = htonl(fh_len);
> +
> +	/* zero padding */
> +	if (fh_len & 3)
> +		p[fh_len / 4] = 0;
> +
> +	memcpy(p, fh, fh_len);
> +	p += DIV_ROUND_UP(fh_len, 4);
> +	return p;
> +}
> +
> +static uint32_t *nfs_add_filename(uint32_t *p,
> +		uint32_t filename_len, const char *filename)
> +{
> +	*p++ = htonl(filename_len);
> +
> +	/* zero padding */
> +	if (filename_len & 3)
> +		p[filename_len / 4] = 0;
> +
> +	memcpy(p, filename, filename_len);
> +	p += DIV_ROUND_UP(filename_len, 4);
> +	return p;
> +}
> +
what is the difference with the function upper?

> +/* This is a 1:1 mapping for Linux, the compiler optimizes it out */
> +static const struct {
> +	uint32_t nfsmode;
> +	unsigned short statmode;
> +} nfs3_mode_bits[] = {
> +	{ 0x00001, S_IXOTH },
> +	{ 0x00002, S_IWOTH },
> +	{ 0x00004, S_IROTH },
> +	{ 0x00008, S_IXGRP },
> +	{ 0x00010, S_IWGRP },
> +	{ 0x00020, S_IRGRP },
> +	{ 0x00040, S_IXUSR },
> +	{ 0x00080, S_IWUSR },
> +	{ 0x00100, S_IRUSR },
> +	{ 0x00200, S_ISVTX },
> +	{ 0x00400, S_ISGID },
> +	{ 0x00800, S_ISUID },
> +};
> +
> +static int nfs_fattr3_to_stat(uint32_t *p, struct stat *s)
> +{
> +	uint32_t mode;
> +	size_t i;
> +
> +	/* offsetof(struct fattr3, type) = 0 */
> +	switch (ntohl(net_read_uint32(p + 0))) {
> +	case NF3REG:
> +		s->st_mode = S_IFREG;
> +		break;
> +	case NF3DIR:
> +		s->st_mode = S_IFDIR;
> +		break;
> +	case NF3BLK:
> +		s->st_mode = S_IFBLK;
> +		break;
> +	case NF3CHR:
> +		s->st_mode = S_IFCHR;
> +		break;
> +	case NF3LNK:
> +		s->st_mode = S_IFLNK;
> +		break;
> +	case NF3SOCK:
> +		s->st_mode = S_IFSOCK;
> +		break;
> +	case NF3FIFO:
> +		s->st_mode = S_IFIFO;
> +		break;
> +	default:
> +		printf("%s: invalid mode %x\n",
> +				__func__, ntohl(net_read_uint32(p + 0)));
> +		return -EIO;
> +	}
> +
> +	/* offsetof(struct fattr3, mode) = 4 */
> +	mode = ntohl(net_read_uint32(p + 1));
> +	for (i = 0; i < ARRAY_SIZE(nfs3_mode_bits); ++i) {
> +		if (mode & nfs3_mode_bits[i].nfsmode)
> +			s->st_mode |= nfs3_mode_bits[i].statmode;
> +	}
> +
> +	/* offsetof(struct fattr3, size) = 20 */
> +	s->st_size = ntohll(net_read_uint64(p + 5));
> +
> +	return 0;
> +}
> +
> +static uint32_t *nfs_read_post_op_attr(uint32_t *p, struct stat **s)
> +{
> +	struct stat dummy;
> +	/*
> +	 * union post_op_attr switch (bool attributes_follow) {
> +	 * case TRUE:
> +	 * 	fattr3 attributes;
> +	 * case FALSE:
> +	 * 	void;
> +	 * };
> +	 */
> +
> +	if (ntohl(net_read_uint32(p++))) {
> +		nfs_fattr3_to_stat(p, s ? *s : &dummy);
> +		p += 21;
> +	} else if (s) {
> +		/* no attributes available */
> +		*s = NULL;
> +	}
> +
> +	return p;
> +}
> +
>  /*
>   * nfs_mount_req - Mount an NFS Filesystem
>   */
> @@ -409,7 +630,16 @@ static int nfs_mount_req(struct nfs_priv *npriv)
>  	if (ret)
>  		return ret;
>  
> -	memcpy(npriv->rootfh, nfs_packet + sizeof(struct rpc_reply) + 4, NFS_FHSIZE);
> +	p = nfs_packet + sizeof(struct rpc_reply) + 4;
> +
> +	npriv->rootfh_len = ntohl(net_read_uint32(p++));
> +	if (npriv->rootfh_len > NFS3_FHSIZE) {
> +		printf("%s: file handle too big: %lu\n", __func__,
> +				(unsigned long)npriv->rootfh_len);
> +		return -EIO;
really EIO?
> +	}
> +	memcpy(npriv->rootfh, p, npriv->rootfh_len);
> +	p += DIV_ROUND_UP(npriv->rootfh_len, 4);
>  
>  	return 0;
>  }
> @@ -429,12 +659,7 @@ static void nfs_umount_req(struct nfs_priv *npriv)
>  	p = &(data[0]);
>  	p = rpc_add_credentials(p);
>  
> -	*p++ = htonl(pathlen);
> -	if (pathlen & 3)
> -		*(p + pathlen / 4) = 0;
> -
> -	memcpy (p, npriv->path, pathlen);
> -	p += (pathlen + 3) / 4;
> +	p = nfs_add_filename(p, pathlen, npriv->path);
>  
>  	len = p - &(data[0]);
>  
> @@ -443,36 +668,68 @@ static void nfs_umount_req(struct nfs_priv *npriv)
>  
>  /*
>   * nfs_lookup_req - Lookup Pathname
> + *
> + * *s is set to NULL if LOOKUP3resok doesn't contain obj_attributes.
>   */
> -static int nfs_lookup_req(struct file_priv *priv, const char *filename,
> -		int fnamelen)
> +static int nfs_lookup_req(struct file_priv *priv,
> +		uint32_t filename_len, const char *filename, struct stat **s)
>  {
....
>  static struct dirent *nfs_readdir(struct device_d *dev, DIR *dir)
>  {
> -	struct nfs_dir *ndir = (void *)dir;
> -	__be32 *p;
> +	struct nfs_dir *ndir = container_of(dir, struct nfs_dir, dir);
> +	uint32_t *p;
>  	int ret;
>  	int len;
>  	struct xdr_stream *xdr = &ndir->stream;
>  
>  again:
>  	p = xdr_inline_decode(xdr, 4);
> -	if (!p)
> -		goto out_overflow;
> +	if (!p) {
> +		printf("%s: premature end of packet\n", __func__);
> +		return NULL;
> +	}
>  
> -	if (*p++ == xdr_zero) {
> +	if (!net_read_uint32(p)) {
> +		/* eof? */
>  		p = xdr_inline_decode(xdr, 4);
> -		if (!p)
> -			goto out_overflow;
> -		if (*p++ == xdr_zero) {
> -			void *buf;
> -			int len;
> -
> -			/*
> -			 * End of current entries, read next chunk.
> -			 */
> +		if (!p) {
> +			printf("%s: premature end of packet\n", __func__);
> +			return NULL;
> +		}
> +		if (net_read_uint32(p))
> +			return NULL;
>  
> -			free(ndir->stream.buf);
> +		if (!nfs_readdirattr_req(ndir->priv, ndir)) {
> +			printf("%s: nfs_readdirattr_req failed\n", __func__);
> +			return NULL;
> +		}
>  
> -			buf = nfs_readdirattr_req(ndir->priv, &len, ndir->cookie);
> -			if (!buf)
> -				return NULL;
> +		goto again;
> +	}
>  
> -			xdr_init(&ndir->stream, buf, len);
> +	/* there is another entry available in the last reply */
>  
> -			goto again;
> -		}
> -		return NULL; /* -EINVAL */
> +	/* skip over fileid */
> +	p = xdr_inline_decode(xdr, 8);
> +	if (!p) {
> +		printf("%s: premature end of packet\n", __func__);
> +		return NULL;
>  	}
>  
> -	p = xdr_inline_decode(xdr, 4);
> -	if (!p)
> -		goto out_overflow;
> -
>  	ret = decode_filename(xdr, ndir->ent.d_name, &len);
>  	if (ret)
>  		return NULL;
>  
> -	/*
> -	 * The type (size and byte order) of nfscookie isn't defined in
> -	 * RFC 1094.  This implementation assumes that it's an XDR uint32.
> -	 */
> -	p = xdr_inline_decode(xdr, 4);
> -	if (!p)
> -		goto out_overflow;
> -
> -	ndir->cookie = be32_to_cpup(p);
> +	p = xdr_inline_decode(xdr, 8);
> +	if (!p) {
> +		printf("%s: premature end of packet\n", __func__);
> +		return NULL;
> +	}
> +	ndir->cookie = ntohll(net_read_uint64(p));
>  
>  	return &ndir->ent;
> -
> -out_overflow:
> -
> -	printf("nfs: overflow error\n");
> -
> -	return NULL;
> -
>  }
>  
>  static int nfs_closedir(struct device_d *dev, DIR *dir)
> @@ -990,7 +1347,7 @@ static int nfs_probe(struct device_d *dev)
>  	}
>  	npriv->mount_port = ret;
>  
> -	ret = rpc_lookup_req(npriv, PROG_NFS, 2);
> +	ret = rpc_lookup_req(npriv, PROG_NFS, 3);

so we loose nfs2?
>  	if (ret < 0) {
>  		printf("lookup nfs port failed with %d\n", ret);
>  		goto err2;
> -- 
> 1.8.5.2
> 
> 
> _______________________________________________
> barebox mailing list
> barebox at lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/barebox



More information about the barebox mailing list