Module Name: src
Committed By: ozaki-r
Date: Fri May 17 08:48:04 UTC 2019
Modified Files:
src/usr.sbin/puffs/mount_9p: fs.c mount_9p.8 nineproto.c nineproto.h
ninepuffs.c ninepuffs.h node.c
Log Message:
mount_9p: add initial support for 9P2000.u
The implementation enables to work with a server talking 9P2000.u. However, it
doesn't use the extended fields yet; it just ignores those of received messages
and sets "please ignore" values to those of sending messages such as zero-length
strings and maximum unsigned values.
The feature is enabled by the -u option.
To generate a diff of this commit:
cvs rdiff -u -r1.9 -r1.10 src/usr.sbin/puffs/mount_9p/fs.c \
src/usr.sbin/puffs/mount_9p/nineproto.c
cvs rdiff -u -r1.7 -r1.8 src/usr.sbin/puffs/mount_9p/mount_9p.8
cvs rdiff -u -r1.1 -r1.2 src/usr.sbin/puffs/mount_9p/nineproto.h
cvs rdiff -u -r1.24 -r1.25 src/usr.sbin/puffs/mount_9p/ninepuffs.c
cvs rdiff -u -r1.12 -r1.13 src/usr.sbin/puffs/mount_9p/ninepuffs.h
cvs rdiff -u -r1.21 -r1.22 src/usr.sbin/puffs/mount_9p/node.c
Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.
Modified files:
Index: src/usr.sbin/puffs/mount_9p/fs.c
diff -u src/usr.sbin/puffs/mount_9p/fs.c:1.9 src/usr.sbin/puffs/mount_9p/fs.c:1.10
--- src/usr.sbin/puffs/mount_9p/fs.c:1.9 Wed Jun 22 04:03:23 2011
+++ src/usr.sbin/puffs/mount_9p/fs.c Fri May 17 08:48:04 2019
@@ -1,4 +1,4 @@
-/* $NetBSD: fs.c,v 1.9 2011/06/22 04:03:23 mrg Exp $ */
+/* $NetBSD: fs.c,v 1.10 2019/05/17 08:48:04 ozaki-r Exp $ */
/*
* Copyright (c) 2007 Antti Kantee. All Rights Reserved.
@@ -27,7 +27,7 @@
#include <sys/cdefs.h>
#ifndef lint
-__RCSID("$NetBSD: fs.c,v 1.9 2011/06/22 04:03:23 mrg Exp $");
+__RCSID("$NetBSD: fs.c,v 1.10 2019/05/17 08:48:04 ozaki-r Exp $");
#endif /* !lint */
#include <assert.h>
@@ -48,6 +48,17 @@ __RCSID("$NetBSD: fs.c,v 1.9 2011/06/22
rv = fname(a1, a2, a3, a4); \
if (rv) errx(1, "p9p_handshake io failed %d, %d", rv, *a4)
+static const char *
+p9p_ver2str(int version)
+{
+
+ switch (version) {
+ case P9PROTO_VERSION: return P9PROTO_VERSTR;
+ case P9PROTO_VERSION_U: return P9PROTO_VERSTR_U;
+ }
+ return NULL;
+}
+
struct puffs_node *
p9p_handshake(struct puffs_usermount *pu,
const char *username, const char *path)
@@ -63,13 +74,15 @@ p9p_handshake(struct puffs_usermount *pu
const char *p;
uint8_t type;
int rv, done, x = 1, ncomp;
+ uint16_t strsize;
+ char *str;
/* send initial handshake */
pb = p9pbuf_makeout();
p9pbuf_put_1(pb, P9PROTO_T_VERSION);
p9pbuf_put_2(pb, P9PROTO_NOTAG);
p9pbuf_put_4(pb, p9p->maxreq);
- p9pbuf_put_str(pb, P9PROTO_VERSION);
+ p9pbuf_put_str(pb, p9p_ver2str(p9p->protover));
DO_IO(p9pbuf_write, pu, pb, p9p->servsock, &done, rv);
puffs_framebuf_recycle(pb);
@@ -89,6 +102,13 @@ p9p_handshake(struct puffs_usermount *pu
"%d vs. %d", P9P_MINREQLEN, maxreq);
p9p->maxreq = maxreq;
+ if (p9pbuf_get_str(pb, &str, &strsize))
+ errx(1, "server invalid response: no version");
+ if (strncmp(str, p9p_ver2str(p9p->protover), P9PROTO_VERSTR_MAXLEN) != 0) {
+ errx(1, "server doesn't support %s", p9p_ver2str(p9p->protover));
+ /* Should downgrade from 9P2000.u to 9P2000 if the server request? */
+ }
+
/* tell the server we don't support authentication */
p9pbuf_recycleout(pb);
tagid = NEXTTAG(p9p);
@@ -97,6 +117,8 @@ p9p_handshake(struct puffs_usermount *pu
p9pbuf_put_4(pb, P9PROTO_NOFID);
p9pbuf_put_str(pb, username);
p9pbuf_put_str(pb, "");
+ if (p9p->protover == P9PROTO_VERSION_U)
+ p9pbuf_put_4(pb, P9PROTO_NUNAME_UNSPECIFIED); /* n_uname[4] */
DO_IO(p9pbuf_write, pu, pb, p9p->servsock, &done, rv);
puffs_framebuf_recycle(pb);
@@ -117,6 +139,8 @@ p9p_handshake(struct puffs_usermount *pu
p9pbuf_put_4(pb, P9PROTO_NOFID);
p9pbuf_put_str(pb, username);
p9pbuf_put_str(pb, "");
+ if (p9p->protover == P9PROTO_VERSION_U)
+ p9pbuf_put_4(pb, P9PROTO_NUNAME_UNSPECIFIED); /* n_uname[4] */
DO_IO(p9pbuf_write, pu, pb, p9p->servsock, &done, rv);
puffs_framebuf_recycle(pb);
@@ -213,7 +237,7 @@ p9p_handshake(struct puffs_usermount *pu
errx(1, "server invalid tag: %d vs. %d", tagid, rtagid);
if (p9pbuf_get_2(pb, &dummy))
errx(1, "couldn't get stat len parameter");
- if (proto_getstat(pb, &rootva, NULL, NULL))
+ if (proto_getstat(pu, pb, &rootva, NULL, NULL))
errx(1, "could not parse root attributes");
puffs_framebuf_destroy(pb);
Index: src/usr.sbin/puffs/mount_9p/nineproto.c
diff -u src/usr.sbin/puffs/mount_9p/nineproto.c:1.9 src/usr.sbin/puffs/mount_9p/nineproto.c:1.10
--- src/usr.sbin/puffs/mount_9p/nineproto.c:1.9 Fri Nov 30 19:02:38 2007
+++ src/usr.sbin/puffs/mount_9p/nineproto.c Fri May 17 08:48:04 2019
@@ -1,4 +1,4 @@
-/* $NetBSD: nineproto.c,v 1.9 2007/11/30 19:02:38 pooka Exp $ */
+/* $NetBSD: nineproto.c,v 1.10 2019/05/17 08:48:04 ozaki-r Exp $ */
/*
* Copyright (c) 2007 Antti Kantee. All Rights Reserved.
@@ -27,7 +27,7 @@
#include <sys/cdefs.h>
#ifndef lint
-__RCSID("$NetBSD: nineproto.c,v 1.9 2007/11/30 19:02:38 pooka Exp $");
+__RCSID("$NetBSD: nineproto.c,v 1.10 2019/05/17 08:48:04 ozaki-r Exp $");
#endif /* !lint */
#include <sys/types.h>
@@ -117,9 +117,10 @@ do { \
size -= *strsize; \
} while (/*CONSTCOND*/0)
int
-proto_getstat(struct puffs_framebuf *pb, struct vattr *vap,
+proto_getstat(struct puffs_usermount *pu, struct puffs_framebuf *pb, struct vattr *vap,
char **name, uint16_t *rs)
{
+ struct puffs9p *p9p = puffs_getspecific(pu);
char *uid, *gid;
struct qid9p qid;
uint64_t flen;
@@ -176,6 +177,13 @@ proto_getstat(struct puffs_framebuf *pb,
/* muid, not used */
GETSTR(NULL, &v16);
+ if (p9p->protover == P9PROTO_VERSION_U) {
+ uint32_t dummy;
+ GETSTR(NULL, &v16); /* extention[s], not used */
+ GETFIELD(p9pbuf_get_4, &dummy, 4); /* n_uid[4], not used */
+ GETFIELD(p9pbuf_get_4, &dummy, 4); /* n_gid[4], not used */
+ GETFIELD(p9pbuf_get_4, &dummy, 4); /* n_muid[4], not used */
+ }
return 0;
}
@@ -269,9 +277,10 @@ proto_cc_open(struct puffs_usermount *pu
}
void
-proto_make_stat(struct puffs_framebuf *pb, const struct vattr *vap,
- const char *filename, enum vtype vt)
+proto_make_stat(struct puffs_usermount *pu, struct puffs_framebuf *pb,
+ const struct vattr *vap, const char *filename, enum vtype vt)
{
+ struct puffs9p *p9p = puffs_getspecific(pu);
struct vattr fakeva;
uint32_t mode, atime, mtime;
uint64_t flen;
@@ -324,6 +333,12 @@ proto_make_stat(struct puffs_framebuf *p
p9pbuf_put_str(pb, owner);
p9pbuf_put_str(pb, group);
p9pbuf_put_str(pb, ""); /* muid */
+ if (p9p->protover == P9PROTO_VERSION_U) {
+ p9pbuf_put_str(pb, P9PROTO_STAT_NOSTR); /* extentions[s] */
+ p9pbuf_put_4(pb, P9PROTO_STAT_NOVAL4); /* n_uid[4] */
+ p9pbuf_put_4(pb, P9PROTO_STAT_NOVAL4); /* n_gid[4] */
+ p9pbuf_put_4(pb, P9PROTO_STAT_NOVAL4); /* n_muid[4] */
+ }
curoff = puffs_framebuf_telloff(pb);
puffs_framebuf_seekset(pb, startoff);
@@ -352,7 +367,8 @@ proto_expect_qid(struct puffs_framebuf *
}
int
-proto_expect_stat(struct puffs_framebuf *pb, struct vattr *va)
+proto_expect_stat(struct puffs_usermount *pu, struct puffs_framebuf *pb,
+ struct vattr *va)
{
uint16_t dummy;
int rv;
@@ -361,5 +377,5 @@ proto_expect_stat(struct puffs_framebuf
return EPROTO;
if ((rv = p9pbuf_get_2(pb, &dummy)))
return rv;
- return proto_getstat(pb, va, NULL, NULL);
+ return proto_getstat(pu, pb, va, NULL, NULL);
}
Index: src/usr.sbin/puffs/mount_9p/mount_9p.8
diff -u src/usr.sbin/puffs/mount_9p/mount_9p.8:1.7 src/usr.sbin/puffs/mount_9p/mount_9p.8:1.8
--- src/usr.sbin/puffs/mount_9p/mount_9p.8:1.7 Fri May 17 08:26:20 2019
+++ src/usr.sbin/puffs/mount_9p/mount_9p.8 Fri May 17 08:48:04 2019
@@ -1,4 +1,4 @@
-.\" $NetBSD: mount_9p.8,v 1.7 2019/05/17 08:26:20 wiz Exp $
+.\" $NetBSD: mount_9p.8,v 1.8 2019/05/17 08:48:04 ozaki-r Exp $
.\"
.\" Copyright (c) 2007 Antti Kantee. All rights reserved.
.\"
@@ -32,6 +32,7 @@
.Sh SYNOPSIS
.Nm
.Op Fl s
+.Op Fl u
.Op Fl o Ar mntopts
.Op Fl p Ar port
.Ar [user@]host[:path]
@@ -61,6 +62,16 @@ With the
option
.Nm
runs in the forground.
+.Pp
+By default
+.Nm
+follows the 9P2000 protocol.
+With
+.Fl u
+option
+.Nm
+follows the 9P2000.u protocol that includes extensions to better support Unix
+environments.
.Sh SEE ALSO
.Xr puffs 3 ,
.Xr puffs 4 ,
@@ -74,6 +85,9 @@ The
.Nm
utility first appeared in
.Nx 5.0 .
+.Pp
+Experimental 9P2000.u support appeared in
+.Nx 9.0 .
.Sh CAVEATS
Permissions are not handled well.
.Pp
@@ -82,3 +96,5 @@ Authentication support is missing.
Error code handling is missing.
.Pp
Under construction.
+.Pp
+9P2000.u support doesn't use extension fields.
Index: src/usr.sbin/puffs/mount_9p/nineproto.h
diff -u src/usr.sbin/puffs/mount_9p/nineproto.h:1.1 src/usr.sbin/puffs/mount_9p/nineproto.h:1.2
--- src/usr.sbin/puffs/mount_9p/nineproto.h:1.1 Sat Apr 21 14:21:43 2007
+++ src/usr.sbin/puffs/mount_9p/nineproto.h Fri May 17 08:48:04 2019
@@ -1,4 +1,4 @@
-/* $NetBSD: nineproto.h,v 1.1 2007/04/21 14:21:43 pooka Exp $ */
+/* $NetBSD: nineproto.h,v 1.2 2019/05/17 08:48:04 ozaki-r Exp $ */
/*
* Copyright (c) 2007 Antti Kantee. All Rights Reserved.
@@ -30,7 +30,12 @@
#include <stdint.h>
-#define P9PROTO_VERSION "9P2000"
+#define P9PROTO_VERSION 0
+#define P9PROTO_VERSION_U 1
+
+#define P9PROTO_VERSTR "9P2000"
+#define P9PROTO_VERSTR_U "9P2000.u"
+#define P9PROTO_VERSTR_MAXLEN (sizeof(P9PROTO_VERSTR_U))
#define P9PROTO_T_VERSION 100
#define P9PROTO_R_VERSION 101
@@ -66,12 +71,18 @@
#define P9PROTO_NOFID (uint32_t)~0
#define P9PROTO_NOTAG (uint16_t)~0
+#define P9PROTO_NUNAME_UNSPECIFIED (uint16_t)~0
+
/* type field in a qid */
#define P9PROTO_QID_TYPE_DIR 0x80
#define P9PROTO_QID_TYPE_APPEND 0x40
#define P9PROTO_QID_TYPE_EXCL 0x20
#define P9PROTO_QID_TYPE_MOUNT 0x10
#define P9PROTO_QID_TYPE_AUTH 0x08
+/* P92000.u extensions */
+#define P9PROTO_QID_TYPE_TMP 0x04
+#define P9PROTO_QID_TYPE_LINK 0x02
+#define P9PROTO_QID_TYPE_FILE 0x00
/* mode in open */
#define P9PROTO_OMODE_READ 0x00
@@ -83,11 +94,24 @@
/* for creating directories */
#define P9PROTO_CPERM_DIR 0x80000000
+#define P9PROTO_CPERM_APPEND 0x40000000
+#define P9PROTO_CPERM_EXCL 0x20000000
+#define P9PROTO_CPERM_MOUNT 0x10000000
+#define P9PROTO_CPERM_AUTH 0x08000000
+#define P9PROTO_CPERM_TMP 0x04000000
+#define P9PROTO_CPERM_SYMLINK 0x02000000
+/* P92000.u extensions */
+#define P9PROTO_CPERM_DEVICE 0x00800000
+#define P9PROTO_CPERM_NAMEDPIPE 0x00200000
+#define P9PROTO_CPERM_SOCKET 0x00100000
+#define P9PROTO_CPERM_SETUID 0x00080000
+#define P9PROTO_CPERM_SETGID 0x00040000
/* stat non-values */
#define P9PROTO_STAT_NOVAL1 (uint8_t)~0
#define P9PROTO_STAT_NOVAL2 (uint16_t)~0
#define P9PROTO_STAT_NOVAL4 (uint32_t)~0
#define P9PROTO_STAT_NOVAL8 (uint64_t)~0
+#define P9PROTO_STAT_NOSTR ""
#endif /* PUFFS9P_PROTO_H_ */
Index: src/usr.sbin/puffs/mount_9p/ninepuffs.c
diff -u src/usr.sbin/puffs/mount_9p/ninepuffs.c:1.24 src/usr.sbin/puffs/mount_9p/ninepuffs.c:1.25
--- src/usr.sbin/puffs/mount_9p/ninepuffs.c:1.24 Wed Aug 31 13:32:39 2011
+++ src/usr.sbin/puffs/mount_9p/ninepuffs.c Fri May 17 08:48:04 2019
@@ -1,4 +1,4 @@
-/* $NetBSD: ninepuffs.c,v 1.24 2011/08/31 13:32:39 joerg Exp $ */
+/* $NetBSD: ninepuffs.c,v 1.25 2019/05/17 08:48:04 ozaki-r Exp $ */
/*
* Copyright (c) 2007 Antti Kantee. All Rights Reserved.
@@ -31,7 +31,7 @@
#include <sys/cdefs.h>
#ifndef lint
-__RCSID("$NetBSD: ninepuffs.c,v 1.24 2011/08/31 13:32:39 joerg Exp $");
+__RCSID("$NetBSD: ninepuffs.c,v 1.25 2019/05/17 08:48:04 ozaki-r Exp $");
#endif /* !lint */
#include <sys/types.h>
@@ -50,6 +50,7 @@ __RCSID("$NetBSD: ninepuffs.c,v 1.24 201
#include <unistd.h>
#include "ninepuffs.h"
+#include "nineproto.h"
#define DEFPORT_9P 564
@@ -107,6 +108,7 @@ main(int argc, char *argv[])
unsigned short port;
int mntflags, pflags, ch;
int detach;
+ int protover = P9PROTO_VERSION;
setprogname(argv[0]);
@@ -117,7 +119,7 @@ main(int argc, char *argv[])
detach = 1;
port = DEFPORT_9P;
- while ((ch = getopt(argc, argv, "o:p:s")) != -1) {
+ while ((ch = getopt(argc, argv, "o:p:su")) != -1) {
switch (ch) {
case 'o':
mp = getmntopts(optarg, puffsmopts, &mntflags, &pflags);
@@ -131,6 +133,9 @@ main(int argc, char *argv[])
case 's':
detach = 0;
break;
+ case 'u':
+ protover = P9PROTO_VERSION_U;
+ break;
default:
usage();
/*NOTREACHED*/
@@ -177,6 +182,7 @@ main(int argc, char *argv[])
memset(&p9p, 0, sizeof(p9p));
p9p.maxreq = P9P_DEFREQLEN;
p9p.nextfid = 1;
+ p9p.protover = protover;
/* user@ */
if ((p = strchr(argv[0], '@')) != NULL) {
Index: src/usr.sbin/puffs/mount_9p/ninepuffs.h
diff -u src/usr.sbin/puffs/mount_9p/ninepuffs.h:1.12 src/usr.sbin/puffs/mount_9p/ninepuffs.h:1.13
--- src/usr.sbin/puffs/mount_9p/ninepuffs.h:1.12 Fri Nov 30 19:02:38 2007
+++ src/usr.sbin/puffs/mount_9p/ninepuffs.h Fri May 17 08:48:04 2019
@@ -1,4 +1,4 @@
-/* $NetBSD: ninepuffs.h,v 1.12 2007/11/30 19:02:38 pooka Exp $ */
+/* $NetBSD: ninepuffs.h,v 1.13 2019/05/17 08:48:04 ozaki-r Exp $ */
/*
* Copyright (c) 2007 Antti Kantee. All Rights Reserved.
@@ -103,6 +103,8 @@ struct puffs9p {
p9pfid_t nextfid;
size_t maxreq; /* negotiated with server */
+
+ int protover;
};
struct dirfid {
@@ -147,18 +149,19 @@ uint8_t p9pbuf_get_type(struct puffs_fr
uint16_t p9pbuf_get_tag(struct puffs_framebuf *);
int proto_getqid(struct puffs_framebuf *, struct qid9p *);
-int proto_getstat(struct puffs_framebuf *, struct vattr *,
+int proto_getstat(struct puffs_usermount *, struct puffs_framebuf *, struct vattr *,
char **, uint16_t *);
int proto_expect_walk_nqids(struct puffs_framebuf *, uint16_t *);
-int proto_expect_stat(struct puffs_framebuf *, struct vattr *);
+int proto_expect_stat(struct puffs_usermount *, struct puffs_framebuf *,
+ struct vattr *);
int proto_expect_qid(struct puffs_framebuf *, uint8_t, struct qid9p *);
int proto_cc_dupfid(struct puffs_usermount *, p9pfid_t, p9pfid_t);
int proto_cc_clunkfid(struct puffs_usermount *, p9pfid_t, int);
int proto_cc_open(struct puffs_usermount *, p9pfid_t, p9pfid_t, int);
-void proto_make_stat(struct puffs_framebuf *, const struct vattr *,
- const char *, enum vtype);
+void proto_make_stat(struct puffs_usermount *, struct puffs_framebuf *,
+ const struct vattr *, const char *, enum vtype);
struct puffs_node *p9p_handshake(struct puffs_usermount *,
const char *, const char *);
Index: src/usr.sbin/puffs/mount_9p/node.c
diff -u src/usr.sbin/puffs/mount_9p/node.c:1.21 src/usr.sbin/puffs/mount_9p/node.c:1.22
--- src/usr.sbin/puffs/mount_9p/node.c:1.21 Sun Jan 18 10:10:47 2009
+++ src/usr.sbin/puffs/mount_9p/node.c Fri May 17 08:48:04 2019
@@ -1,4 +1,4 @@
-/* $NetBSD: node.c,v 1.21 2009/01/18 10:10:47 lukem Exp $ */
+/* $NetBSD: node.c,v 1.22 2019/05/17 08:48:04 ozaki-r Exp $ */
/*
* Copyright (c) 2007 Antti Kantee. All Rights Reserved.
@@ -27,7 +27,7 @@
#include <sys/cdefs.h>
#ifndef lint
-__RCSID("$NetBSD: node.c,v 1.21 2009/01/18 10:10:47 lukem Exp $");
+__RCSID("$NetBSD: node.c,v 1.22 2019/05/17 08:48:04 ozaki-r Exp $");
#endif /* !lint */
#include <assert.h>
@@ -62,7 +62,7 @@ do_getattr(struct puffs_usermount *pu, s
p9pbuf_put_4(pb, p9n->fid_base);
GETRESPONSE(pb);
- rv = proto_expect_stat(pb, vap);
+ rv = proto_expect_stat(pu, pb, vap);
out:
RETURN(rv);
@@ -120,7 +120,7 @@ puffs9p_node_lookup(struct puffs_usermou
p9pbuf_put_2(pb, tag);
p9pbuf_put_4(pb, tfid);
GETRESPONSE(pb);
- if ((rv = proto_expect_stat(pb, &va)) != 0) {
+ if ((rv = proto_expect_stat(pu, pb, &va)) != 0) {
proto_cc_clunkfid(pu, tfid, 0);
rv = ENOENT;
goto out;
@@ -194,7 +194,7 @@ puffs9p_node_readdir(struct puffs_usermo
}
while (count > 0) {
- if ((rv = proto_getstat(pb, &va, &name, &statsize))) {
+ if ((rv = proto_getstat(pu, pb, &va, &name, &statsize))) {
/*
* If there was an error, it's unlikely we'll be
* coming back, so just nuke the dfp. If we do
@@ -231,7 +231,7 @@ puffs9p_node_setattr(struct puffs_usermo
p9pbuf_put_1(pb, P9PROTO_T_WSTAT);
p9pbuf_put_2(pb, tag);
p9pbuf_put_4(pb, p9n->fid_base);
- proto_make_stat(pb, va, NULL, pn->pn_va.va_type);
+ proto_make_stat(pu, pb, va, NULL, pn->pn_va.va_type);
GETRESPONSE(pb);
if (p9pbuf_get_type(pb) != P9PROTO_R_WSTAT)
@@ -426,6 +426,8 @@ nodecreate(struct puffs_usermount *pu, s
p9pbuf_put_str(pb, name);
p9pbuf_put_4(pb, dirbit | (vap->va_mode & 0777));
p9pbuf_put_1(pb, 0);
+ if (p9p->protover == P9PROTO_VERSION_U)
+ p9pbuf_put_str(pb, ""); /* extension[s] */
GETRESPONSE(pb);
rv = proto_expect_qid(pb, P9PROTO_R_CREATE, &nqid);
@@ -579,7 +581,7 @@ puffs9p_node_rename(struct puffs_usermou
p9pbuf_put_1(pb, P9PROTO_T_WSTAT);
p9pbuf_put_2(pb, tag);
p9pbuf_put_4(pb, p9n_src->fid_base);
- proto_make_stat(pb, NULL, pcn_targ->pcn_name, pn_src->pn_va.va_type);
+ proto_make_stat(pu, pb, NULL, pcn_targ->pcn_name, pn_src->pn_va.va_type);
GETRESPONSE(pb);
if (p9pbuf_get_type(pb) != P9PROTO_R_WSTAT)