Import lib9p 7ddb1164407da19b9b1afb83df83ae65a71a9a66.
Approved by: trasz MFC after: 1 month Sponsored by: Conclusive Engineering (development), vStack.com (funding)
This commit is contained in:
@@ -0,0 +1,37 @@
|
|||||||
|
# Object files
|
||||||
|
*.o
|
||||||
|
*.ko
|
||||||
|
*.obj
|
||||||
|
*.elf
|
||||||
|
|
||||||
|
# Precompiled Headers
|
||||||
|
*.gch
|
||||||
|
*.pch
|
||||||
|
|
||||||
|
# Libraries
|
||||||
|
*.lib
|
||||||
|
*.a
|
||||||
|
*.la
|
||||||
|
*.lo
|
||||||
|
|
||||||
|
# Shared objects (inc. Windows DLLs)
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.so.*
|
||||||
|
*.dylib
|
||||||
|
|
||||||
|
# Executables
|
||||||
|
*.exe
|
||||||
|
*.out
|
||||||
|
*.app
|
||||||
|
*.i*86
|
||||||
|
*.x86_64
|
||||||
|
*.hex
|
||||||
|
|
||||||
|
# Debug files
|
||||||
|
*.dSYM/
|
||||||
|
/build/
|
||||||
|
|
||||||
|
*.po
|
||||||
|
*.pico
|
||||||
|
*.depend
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
Copyright 2016 Jakub Klama <jceel@FreeBSD.org>
|
||||||
|
All rights reserved
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted providing that the following conditions
|
||||||
|
are met:
|
||||||
|
1. Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||||
|
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||||||
|
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
||||||
|
IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
Some parts of the code are based on libixp (http://libs.suckless.org/libixp)
|
||||||
|
library code released under following license:
|
||||||
|
|
||||||
|
© 2005-2006 Anselm R. Garbe <garbeam@gmail.com>
|
||||||
|
© 2006-2010 Kris Maglione <maglione.k at Gmail>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
copy of this software and associated documentation files (the "Software"),
|
||||||
|
to deal in the Software without restriction, including without limitation
|
||||||
|
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||||
|
and/or sell copies of the Software, and to permit persons to whom the
|
||||||
|
Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||||
|
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
DEALINGS IN THE SOFTWARE.
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
CC_VERSION := $(shell $(CC) --version | \
|
||||||
|
sed -n -e '/clang-/s/.*clang-\([0-9][0-9]*\).*/\1/p')
|
||||||
|
ifeq ($(CC_VERSION),)
|
||||||
|
# probably not clang
|
||||||
|
CC_VERSION := 0
|
||||||
|
endif
|
||||||
|
|
||||||
|
WFLAGS :=
|
||||||
|
|
||||||
|
# Warnings are version-dependent, unfortunately,
|
||||||
|
# so test for version before adding a -W flag.
|
||||||
|
# Note: gnu make requires $(shell test ...) for "a > b" type tests.
|
||||||
|
ifeq ($(shell test $(CC_VERSION) -gt 0; echo $$?),0)
|
||||||
|
WFLAGS += -Weverything
|
||||||
|
WFLAGS += -Wno-padded
|
||||||
|
WFLAGS += -Wno-gnu-zero-variadic-macro-arguments
|
||||||
|
WFLAGS += -Wno-format-nonliteral
|
||||||
|
WFLAGS += -Wno-unused-macros
|
||||||
|
WFLAGS += -Wno-disabled-macro-expansion
|
||||||
|
WFLAGS += -Werror
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifeq ($(shell test $(CC_VERSION) -gt 600; echo $$?),0)
|
||||||
|
WFLAGS += -Wno-reserved-id-macro
|
||||||
|
endif
|
||||||
|
|
||||||
|
CFLAGS := $(WFLAGS) \
|
||||||
|
-g \
|
||||||
|
-O0 \
|
||||||
|
-DL9P_DEBUG=L9P_DEBUG
|
||||||
|
# Note: to turn on debug, use -DL9P_DEBUG=L9P_DEBUG,
|
||||||
|
# and set env variable LIB9P_LOGGING to stderr or to
|
||||||
|
# the (preferably full path name of) the debug log file.
|
||||||
|
|
||||||
|
LIB_SRCS := \
|
||||||
|
pack.c \
|
||||||
|
connection.c \
|
||||||
|
request.c \
|
||||||
|
genacl.c \
|
||||||
|
log.c \
|
||||||
|
hashtable.c \
|
||||||
|
utils.c \
|
||||||
|
rfuncs.c \
|
||||||
|
threadpool.c \
|
||||||
|
sbuf/sbuf.c \
|
||||||
|
transport/socket.c \
|
||||||
|
backend/fs.c
|
||||||
|
|
||||||
|
SERVER_SRCS := \
|
||||||
|
example/server.c
|
||||||
|
|
||||||
|
BUILD_DIR := build
|
||||||
|
LIB_OBJS := $(addprefix build/,$(LIB_SRCS:.c=.o))
|
||||||
|
SERVER_OBJS := $(SERVER_SRCS:.c=.o)
|
||||||
|
LIB := lib9p.dylib
|
||||||
|
SERVER := server
|
||||||
|
|
||||||
|
all: build $(LIB) $(SERVER)
|
||||||
|
|
||||||
|
$(LIB): $(LIB_OBJS)
|
||||||
|
cc -dynamiclib $^ -o build/$@
|
||||||
|
|
||||||
|
$(SERVER): $(SERVER_OBJS) $(LIB)
|
||||||
|
cc $< -o build/$(SERVER) -Lbuild/ -l9p
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf build
|
||||||
|
rm -f $(SERVER_OBJS)
|
||||||
|
build:
|
||||||
|
mkdir build
|
||||||
|
mkdir build/sbuf
|
||||||
|
mkdir build/transport
|
||||||
|
mkdir build/backend
|
||||||
|
|
||||||
|
build/%.o: %.c
|
||||||
|
$(CC) $(CFLAGS) -c $< -o $@
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# Note: to turn on debug, use -DL9P_DEBUG=L9P_DEBUG,
|
||||||
|
# and set env variable LIB9P_LOGGING to stderr or to
|
||||||
|
# the (preferably full path name of) the debug log file.
|
||||||
|
|
||||||
|
LIB= 9p
|
||||||
|
SHLIB_MAJOR= 1
|
||||||
|
SRCS= pack.c \
|
||||||
|
connection.c \
|
||||||
|
request.c log.c \
|
||||||
|
hashtable.c \
|
||||||
|
genacl.c \
|
||||||
|
utils.c \
|
||||||
|
rfuncs.c \
|
||||||
|
threadpool.c \
|
||||||
|
transport/socket.c \
|
||||||
|
backend/fs.c
|
||||||
|
|
||||||
|
INCS= lib9p.h
|
||||||
|
CC= clang
|
||||||
|
CFLAGS= -g -O0 -DL9P_DEBUG=L9P_DEBUG -DWITH_CASPER
|
||||||
|
LIBADD= sbuf libcasper libcap_pwd libcap_grp
|
||||||
|
SUBDIR= example
|
||||||
|
|
||||||
|
cscope: .PHONY
|
||||||
|
cd ${.CURDIR}; cscope -buq $$(find . -name '*.[ch]' -print)
|
||||||
|
|
||||||
|
.include <bsd.lib.mk>
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# lib9p
|
||||||
|
|
||||||
|
lib9p is a server library implementing 9p2000, 9p2000.u and 9p2000.L revisions
|
||||||
|
of 9P protocol. It is being developed primarily as a backend for virtio-9p in
|
||||||
|
BHyVe, the FreeBSD hypervisor.
|
||||||
|
|
||||||
|
# Features
|
||||||
|
|
||||||
|
* 9p2000, 9p2000.u and 9p2000.L protocol support
|
||||||
|
* Built-in TCP transport
|
||||||
|
|
||||||
|
# Supported operating systems
|
||||||
|
|
||||||
|
* FreeBSD (>=10)
|
||||||
|
* macOS (>=10.9)
|
||||||
|
|
||||||
|
# Authors
|
||||||
|
|
||||||
|
* Jakub Klama [jceel](https://github.com/jceel)
|
||||||
|
* Chris Torek [chris3torek](https://github.com/chris3torek)
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
#ifndef _APPLE_ENDIAN_H
|
||||||
|
#define _APPLE_ENDIAN_H
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Shims to make Apple's endian headers and macros compatible
|
||||||
|
* with <sys/endian.h> (which is awful).
|
||||||
|
*/
|
||||||
|
|
||||||
|
# include <libkern/OSByteOrder.h>
|
||||||
|
|
||||||
|
# define _LITTLE_ENDIAN 0x12345678
|
||||||
|
# define _BIG_ENDIAN 0x87654321
|
||||||
|
|
||||||
|
# ifdef __LITTLE_ENDIAN__
|
||||||
|
# define _BYTE_ORDER _LITTLE_ENDIAN
|
||||||
|
# endif
|
||||||
|
# ifdef __BIG_ENDIAN__
|
||||||
|
# define _BYTE_ORDER _BIG_ENDIAN
|
||||||
|
# endif
|
||||||
|
|
||||||
|
# define htole32(x) OSSwapHostToLittleInt32(x)
|
||||||
|
# define le32toh(x) OSSwapLittleToHostInt32(x)
|
||||||
|
|
||||||
|
# define htobe32(x) OSSwapHostToBigInt32(x)
|
||||||
|
# define be32toh(x) OSSwapBigToHostInt32(x)
|
||||||
|
|
||||||
|
#endif /* _APPLE_ENDIAN_H */
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 Jakub Klama <jceel@FreeBSD.org>
|
||||||
|
* All rights reserved
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted providing that the following conditions
|
||||||
|
* are met:
|
||||||
|
* 1. Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||||
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||||||
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
||||||
|
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef LIB9P_BACKEND_H
|
||||||
|
#define LIB9P_BACKEND_H
|
||||||
|
|
||||||
|
struct l9p_backend {
|
||||||
|
void *softc;
|
||||||
|
void (*freefid)(void *, struct l9p_fid *);
|
||||||
|
int (*attach)(void *, struct l9p_request *);
|
||||||
|
int (*clunk)(void *, struct l9p_fid *);
|
||||||
|
int (*create)(void *, struct l9p_request *);
|
||||||
|
int (*open)(void *, struct l9p_request *);
|
||||||
|
int (*read)(void *, struct l9p_request *);
|
||||||
|
int (*remove)(void *, struct l9p_fid *);
|
||||||
|
int (*stat)(void *, struct l9p_request *);
|
||||||
|
int (*walk)(void *, struct l9p_request *);
|
||||||
|
int (*write)(void *, struct l9p_request *);
|
||||||
|
int (*wstat)(void *, struct l9p_request *);
|
||||||
|
int (*statfs)(void *, struct l9p_request *);
|
||||||
|
int (*lopen)(void *, struct l9p_request *);
|
||||||
|
int (*lcreate)(void *, struct l9p_request *);
|
||||||
|
int (*symlink)(void *, struct l9p_request *);
|
||||||
|
int (*mknod)(void *, struct l9p_request *);
|
||||||
|
int (*rename)(void *, struct l9p_request *);
|
||||||
|
int (*readlink)(void *, struct l9p_request *);
|
||||||
|
int (*getattr)(void *, struct l9p_request *);
|
||||||
|
int (*setattr)(void *, struct l9p_request *);
|
||||||
|
int (*xattrwalk)(void *, struct l9p_request *);
|
||||||
|
int (*xattrcreate)(void *, struct l9p_request *);
|
||||||
|
int (*xattrread)(void *, struct l9p_request *);
|
||||||
|
int (*xattrwrite)(void *, struct l9p_request *);
|
||||||
|
int (*xattrclunk)(void *, struct l9p_fid *);
|
||||||
|
int (*readdir)(void *, struct l9p_request *);
|
||||||
|
int (*fsync)(void *, struct l9p_request *);
|
||||||
|
int (*lock)(void *, struct l9p_request *);
|
||||||
|
int (*getlock)(void *, struct l9p_request *);
|
||||||
|
int (*link)(void *, struct l9p_request *);
|
||||||
|
int (*mkdir)(void *, struct l9p_request *);
|
||||||
|
int (*renameat)(void *, struct l9p_request *);
|
||||||
|
int (*unlinkat)(void *, struct l9p_request *);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* LIB9P_BACKEND_H */
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,37 @@
|
|||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Chris Torek <torek@ixsystems.com>
|
||||||
|
* All rights reserved
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted providing that the following conditions
|
||||||
|
* are met:
|
||||||
|
* 1. Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||||
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||||||
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
||||||
|
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef LIB9P_BACKEND_FS_H
|
||||||
|
#define LIB9P_BACKEND_FS_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include "backend.h"
|
||||||
|
|
||||||
|
int l9p_backend_fs_init(struct l9p_backend **backendp, int rootfd, bool ro);
|
||||||
|
|
||||||
|
#endif /* LIB9P_BACKEND_FS_H */
|
||||||
@@ -0,0 +1,215 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 Jakub Klama <jceel@FreeBSD.org>
|
||||||
|
* All rights reserved
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted providing that the following conditions
|
||||||
|
* are met:
|
||||||
|
* 1. Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||||
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||||||
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
||||||
|
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <sys/queue.h>
|
||||||
|
#include "lib9p.h"
|
||||||
|
#include "lib9p_impl.h"
|
||||||
|
#include "fid.h"
|
||||||
|
#include "hashtable.h"
|
||||||
|
#include "log.h"
|
||||||
|
#include "threadpool.h"
|
||||||
|
#include "backend/backend.h"
|
||||||
|
|
||||||
|
int
|
||||||
|
l9p_server_init(struct l9p_server **serverp, struct l9p_backend *backend)
|
||||||
|
{
|
||||||
|
struct l9p_server *server;
|
||||||
|
|
||||||
|
server = l9p_calloc(1, sizeof (*server));
|
||||||
|
server->ls_max_version = L9P_2000L;
|
||||||
|
server->ls_backend = backend;
|
||||||
|
LIST_INIT(&server->ls_conns);
|
||||||
|
|
||||||
|
*serverp = server;
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
l9p_connection_init(struct l9p_server *server, struct l9p_connection **conn)
|
||||||
|
{
|
||||||
|
struct l9p_connection *newconn;
|
||||||
|
|
||||||
|
assert(server != NULL);
|
||||||
|
assert(conn != NULL);
|
||||||
|
|
||||||
|
newconn = calloc(1, sizeof (*newconn));
|
||||||
|
if (newconn == NULL)
|
||||||
|
return (-1);
|
||||||
|
newconn->lc_server = server;
|
||||||
|
newconn->lc_msize = L9P_DEFAULT_MSIZE;
|
||||||
|
if (l9p_threadpool_init(&newconn->lc_tp, L9P_NUMTHREADS)) {
|
||||||
|
free(newconn);
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
ht_init(&newconn->lc_files, 100);
|
||||||
|
ht_init(&newconn->lc_requests, 100);
|
||||||
|
LIST_INSERT_HEAD(&server->ls_conns, newconn, lc_link);
|
||||||
|
*conn = newconn;
|
||||||
|
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
l9p_connection_free(struct l9p_connection *conn)
|
||||||
|
{
|
||||||
|
|
||||||
|
LIST_REMOVE(conn, lc_link);
|
||||||
|
free(conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
l9p_connection_recv(struct l9p_connection *conn, const struct iovec *iov,
|
||||||
|
const size_t niov, void *aux)
|
||||||
|
{
|
||||||
|
struct l9p_request *req;
|
||||||
|
int error;
|
||||||
|
|
||||||
|
req = l9p_calloc(1, sizeof (struct l9p_request));
|
||||||
|
req->lr_aux = aux;
|
||||||
|
req->lr_conn = conn;
|
||||||
|
|
||||||
|
req->lr_req_msg.lm_mode = L9P_UNPACK;
|
||||||
|
req->lr_req_msg.lm_niov = niov;
|
||||||
|
memcpy(req->lr_req_msg.lm_iov, iov, sizeof (struct iovec) * niov);
|
||||||
|
|
||||||
|
req->lr_resp_msg.lm_mode = L9P_PACK;
|
||||||
|
|
||||||
|
if (l9p_pufcall(&req->lr_req_msg, &req->lr_req, conn->lc_version) != 0) {
|
||||||
|
L9P_LOG(L9P_WARNING, "cannot unpack received message");
|
||||||
|
l9p_freefcall(&req->lr_req);
|
||||||
|
free(req);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ht_add(&conn->lc_requests, req->lr_req.hdr.tag, req)) {
|
||||||
|
L9P_LOG(L9P_WARNING, "client reusing outstanding tag %d",
|
||||||
|
req->lr_req.hdr.tag);
|
||||||
|
l9p_freefcall(&req->lr_req);
|
||||||
|
free(req);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
error = conn->lc_lt.lt_get_response_buffer(req,
|
||||||
|
req->lr_resp_msg.lm_iov,
|
||||||
|
&req->lr_resp_msg.lm_niov,
|
||||||
|
conn->lc_lt.lt_aux);
|
||||||
|
if (error) {
|
||||||
|
L9P_LOG(L9P_WARNING, "cannot obtain buffers for response");
|
||||||
|
ht_remove(&conn->lc_requests, req->lr_req.hdr.tag);
|
||||||
|
l9p_freefcall(&req->lr_req);
|
||||||
|
free(req);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* NB: it's up to l9p_threadpool_run to decide whether
|
||||||
|
* to queue the work or to run it immediately and wait
|
||||||
|
* (it must do the latter for Tflush requests).
|
||||||
|
*/
|
||||||
|
l9p_threadpool_run(&conn->lc_tp, req);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
l9p_connection_close(struct l9p_connection *conn)
|
||||||
|
{
|
||||||
|
struct ht_iter iter;
|
||||||
|
struct l9p_fid *fid;
|
||||||
|
struct l9p_request *req;
|
||||||
|
|
||||||
|
L9P_LOG(L9P_DEBUG, "waiting for thread pool to shut down");
|
||||||
|
l9p_threadpool_shutdown(&conn->lc_tp);
|
||||||
|
|
||||||
|
/* Drain pending requests (if any) */
|
||||||
|
L9P_LOG(L9P_DEBUG, "draining pending requests");
|
||||||
|
ht_iter(&conn->lc_requests, &iter);
|
||||||
|
while ((req = ht_next(&iter)) != NULL) {
|
||||||
|
#ifdef notyet
|
||||||
|
/* XXX would be good to know if there is anyone listening */
|
||||||
|
if (anyone listening) {
|
||||||
|
/* XXX crude - ops like Tclunk should succeed */
|
||||||
|
req->lr_error = EINTR;
|
||||||
|
l9p_respond(req, false, false);
|
||||||
|
} else
|
||||||
|
#endif
|
||||||
|
l9p_respond(req, true, false); /* use no-answer path */
|
||||||
|
ht_remove_at_iter(&iter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Close opened files (if any) */
|
||||||
|
L9P_LOG(L9P_DEBUG, "closing opened files");
|
||||||
|
ht_iter(&conn->lc_files, &iter);
|
||||||
|
while ((fid = ht_next(&iter)) != NULL) {
|
||||||
|
conn->lc_server->ls_backend->freefid(
|
||||||
|
conn->lc_server->ls_backend->softc, fid);
|
||||||
|
free(fid);
|
||||||
|
ht_remove_at_iter(&iter);
|
||||||
|
}
|
||||||
|
|
||||||
|
ht_destroy(&conn->lc_requests);
|
||||||
|
ht_destroy(&conn->lc_files);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct l9p_fid *
|
||||||
|
l9p_connection_alloc_fid(struct l9p_connection *conn, uint32_t fid)
|
||||||
|
{
|
||||||
|
struct l9p_fid *file;
|
||||||
|
|
||||||
|
file = l9p_calloc(1, sizeof (struct l9p_fid));
|
||||||
|
file->lo_fid = fid;
|
||||||
|
/*
|
||||||
|
* Note that the new fid is not marked valid yet.
|
||||||
|
* The insert here will fail if the fid number is
|
||||||
|
* in use, otherwise we have an invalid fid in the
|
||||||
|
* table (as desired).
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (ht_add(&conn->lc_files, fid, file) != 0) {
|
||||||
|
free(file);
|
||||||
|
return (NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (file);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
l9p_connection_remove_fid(struct l9p_connection *conn, struct l9p_fid *fid)
|
||||||
|
{
|
||||||
|
struct l9p_backend *be;
|
||||||
|
|
||||||
|
/* fid should be marked invalid by this point */
|
||||||
|
assert(!l9p_fid_isvalid(fid));
|
||||||
|
|
||||||
|
be = conn->lc_server->ls_backend;
|
||||||
|
be->freefid(be->softc, fid);
|
||||||
|
|
||||||
|
ht_remove(&conn->lc_files, fid->lo_fid);
|
||||||
|
free(fid);
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
PROG= server
|
||||||
|
SRCS= server.c
|
||||||
|
MAN=
|
||||||
|
|
||||||
|
CFLAGS= -pthread -g -O0
|
||||||
|
|
||||||
|
LDFLAGS=-L..
|
||||||
|
LDADD= -lsbuf -l9p -lcasper -lcap_pwd -lcap_grp
|
||||||
|
|
||||||
|
.include <bsd.prog.mk>
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 Jakub Klama <jceel@FreeBSD.org>
|
||||||
|
* All rights reserved
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted providing that the following conditions
|
||||||
|
* are met:
|
||||||
|
* 1. Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||||
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||||||
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
||||||
|
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <err.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include "../lib9p.h"
|
||||||
|
#include "../backend/fs.h"
|
||||||
|
#include "../transport/socket.h"
|
||||||
|
|
||||||
|
int
|
||||||
|
main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
struct l9p_backend *fs_backend;
|
||||||
|
struct l9p_server *server;
|
||||||
|
char *host = "0.0.0.0";
|
||||||
|
char *port = "564";
|
||||||
|
char *path;
|
||||||
|
bool ro = false;
|
||||||
|
int rootfd;
|
||||||
|
int opt;
|
||||||
|
|
||||||
|
while ((opt = getopt(argc, argv, "h:p:r")) != -1) {
|
||||||
|
switch (opt) {
|
||||||
|
case 'h':
|
||||||
|
host = optarg;
|
||||||
|
break;
|
||||||
|
case 'p':
|
||||||
|
port = optarg;
|
||||||
|
break;
|
||||||
|
case 'r':
|
||||||
|
ro = true;
|
||||||
|
break;
|
||||||
|
case '?':
|
||||||
|
default:
|
||||||
|
goto usage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (optind >= argc) {
|
||||||
|
usage:
|
||||||
|
errx(1, "Usage: server [-h <host>] [-p <port>] [-r] <path>");
|
||||||
|
}
|
||||||
|
|
||||||
|
path = argv[optind];
|
||||||
|
rootfd = open(path, O_DIRECTORY);
|
||||||
|
|
||||||
|
if (rootfd < 0)
|
||||||
|
err(1, "cannot open root directory");
|
||||||
|
|
||||||
|
if (l9p_backend_fs_init(&fs_backend, rootfd, ro) != 0)
|
||||||
|
err(1, "cannot init backend");
|
||||||
|
|
||||||
|
if (l9p_server_init(&server, fs_backend) != 0)
|
||||||
|
err(1, "cannot create server");
|
||||||
|
|
||||||
|
server->ls_max_version = L9P_2000L;
|
||||||
|
if (l9p_start_server(server, host, port))
|
||||||
|
err(1, "l9p_start_server() failed");
|
||||||
|
|
||||||
|
/* XXX - we never get here, l9p_start_server does not return */
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
@@ -0,0 +1,624 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 Jakub Klama <jceel@FreeBSD.org>
|
||||||
|
* All rights reserved
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted providing that the following conditions
|
||||||
|
* are met:
|
||||||
|
* 1. Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||||
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||||||
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
||||||
|
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Based on libixp code: ©2007-2010 Kris Maglione <maglione.k at Gmail>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef LIB9P_FCALL_H
|
||||||
|
#define LIB9P_FCALL_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#define L9P_MAX_WELEM 256
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Function call/reply (Tfoo/Rfoo) numbers.
|
||||||
|
*
|
||||||
|
* These are protocol code numbers, so the exact values
|
||||||
|
* matter. However, __FIRST and __LAST_PLUS_ONE are for
|
||||||
|
* debug code, and just need to encompass the entire range.
|
||||||
|
*
|
||||||
|
* Note that we rely (in the debug code) on Rfoo == Tfoo+1.
|
||||||
|
*/
|
||||||
|
enum l9p_ftype {
|
||||||
|
L9P__FIRST = 6, /* NB: must be <= all legal values */
|
||||||
|
L9P_TLERROR = 6, /* illegal; exists for parity with Rlerror */
|
||||||
|
L9P_RLERROR,
|
||||||
|
L9P_TSTATFS = 8,
|
||||||
|
L9P_RSTATFS,
|
||||||
|
L9P_TLOPEN = 12,
|
||||||
|
L9P_RLOPEN,
|
||||||
|
L9P_TLCREATE = 14,
|
||||||
|
L9P_RLCREATE,
|
||||||
|
L9P_TSYMLINK = 16,
|
||||||
|
L9P_RSYMLINK,
|
||||||
|
L9P_TMKNOD = 18,
|
||||||
|
L9P_RMKNOD,
|
||||||
|
L9P_TRENAME = 20,
|
||||||
|
L9P_RRENAME,
|
||||||
|
L9P_TREADLINK = 22,
|
||||||
|
L9P_RREADLINK,
|
||||||
|
L9P_TGETATTR = 24,
|
||||||
|
L9P_RGETATTR,
|
||||||
|
L9P_TSETATTR = 26,
|
||||||
|
L9P_RSETATTR,
|
||||||
|
L9P_TXATTRWALK = 30,
|
||||||
|
L9P_RXATTRWALK,
|
||||||
|
L9P_TXATTRCREATE = 32,
|
||||||
|
L9P_RXATTRCREATE,
|
||||||
|
L9P_TREADDIR = 40,
|
||||||
|
L9P_RREADDIR,
|
||||||
|
L9P_TFSYNC = 50,
|
||||||
|
L9P_RFSYNC,
|
||||||
|
L9P_TLOCK = 52,
|
||||||
|
L9P_RLOCK,
|
||||||
|
L9P_TGETLOCK = 54,
|
||||||
|
L9P_RGETLOCK,
|
||||||
|
L9P_TLINK = 70,
|
||||||
|
L9P_RLINK,
|
||||||
|
L9P_TMKDIR = 72,
|
||||||
|
L9P_RMKDIR,
|
||||||
|
L9P_TRENAMEAT = 74,
|
||||||
|
L9P_RRENAMEAT,
|
||||||
|
L9P_TUNLINKAT = 76,
|
||||||
|
L9P_RUNLINKAT,
|
||||||
|
L9P_TVERSION = 100,
|
||||||
|
L9P_RVERSION,
|
||||||
|
L9P_TAUTH = 102,
|
||||||
|
L9P_RAUTH,
|
||||||
|
L9P_TATTACH = 104,
|
||||||
|
L9P_RATTACH,
|
||||||
|
L9P_TERROR = 106, /* illegal */
|
||||||
|
L9P_RERROR,
|
||||||
|
L9P_TFLUSH = 108,
|
||||||
|
L9P_RFLUSH,
|
||||||
|
L9P_TWALK = 110,
|
||||||
|
L9P_RWALK,
|
||||||
|
L9P_TOPEN = 112,
|
||||||
|
L9P_ROPEN,
|
||||||
|
L9P_TCREATE = 114,
|
||||||
|
L9P_RCREATE,
|
||||||
|
L9P_TREAD = 116,
|
||||||
|
L9P_RREAD,
|
||||||
|
L9P_TWRITE = 118,
|
||||||
|
L9P_RWRITE,
|
||||||
|
L9P_TCLUNK = 120,
|
||||||
|
L9P_RCLUNK,
|
||||||
|
L9P_TREMOVE = 122,
|
||||||
|
L9P_RREMOVE,
|
||||||
|
L9P_TSTAT = 124,
|
||||||
|
L9P_RSTAT,
|
||||||
|
L9P_TWSTAT = 126,
|
||||||
|
L9P_RWSTAT,
|
||||||
|
L9P__LAST_PLUS_1, /* NB: must be last */
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* When a Tfoo request comes over the wire, we decode it
|
||||||
|
* (pack.c) from wire format into a request laid out in
|
||||||
|
* a "union l9p_fcall" object. This object is not in wire
|
||||||
|
* format, but rather in something more convenient for us
|
||||||
|
* to operate on.
|
||||||
|
*
|
||||||
|
* We then dispatch the request (request.c, backend/fs.c) and
|
||||||
|
* use another "union l9p_fcall" object to build a reply.
|
||||||
|
* The reply is converted to wire format on the way back out
|
||||||
|
* (pack.c again).
|
||||||
|
*
|
||||||
|
* All sub-objects start with a header containing the request
|
||||||
|
* or reply type code and two-byte tag, and whether or not it
|
||||||
|
* is needed, a four-byte fid.
|
||||||
|
*
|
||||||
|
* What this means here is that the data structures within
|
||||||
|
* the union can be shared across various requests and replies.
|
||||||
|
* For instance, replies to OPEN, CREATE, LCREATE, LOPEN, MKDIR, and
|
||||||
|
* SYMLINK are all fairly similar (providing a qid and sometimes
|
||||||
|
* an iounit) and hence can all use the l9p_f_ropen structure.
|
||||||
|
* Which structures are used for which operations is somewhat
|
||||||
|
* arbitrary; for programming ease, if an operation shares a
|
||||||
|
* data structure, it still has its own name: there are union
|
||||||
|
* members named ropen, rcreate, rlcreate, rlopen, rmkdir, and
|
||||||
|
* rsymlink, even though all use struct l9p_f_ropen.
|
||||||
|
*
|
||||||
|
* The big exception to the above rule is struct l9p_f_io, which
|
||||||
|
* is used as both request and reply for all of READ, WRITE, and
|
||||||
|
* READDIR. Moreover, the READDIR reply must be pre-packed into
|
||||||
|
* wire format (it is handled like raw data a la READ).
|
||||||
|
*
|
||||||
|
* Some request messages (e.g., TREADLINK) fit in a header, having
|
||||||
|
* just type code, tag, and fid. These have no separate data
|
||||||
|
* structure, nor union member name. Similarly, some reply
|
||||||
|
* messages (e.g., RCLUNK, RREMOVE, RRENAME) have just the type
|
||||||
|
* code and tag.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Type code bits in (the first byte of) a qid.
|
||||||
|
*/
|
||||||
|
enum l9p_qid_type {
|
||||||
|
L9P_QTDIR = 0x80, /* type bit for directories */
|
||||||
|
L9P_QTAPPEND = 0x40, /* type bit for append only files */
|
||||||
|
L9P_QTEXCL = 0x20, /* type bit for exclusive use files */
|
||||||
|
L9P_QTMOUNT = 0x10, /* type bit for mounted channel */
|
||||||
|
L9P_QTAUTH = 0x08, /* type bit for authentication file */
|
||||||
|
L9P_QTTMP = 0x04, /* type bit for non-backed-up file */
|
||||||
|
L9P_QTSYMLINK = 0x02, /* type bit for symbolic link */
|
||||||
|
L9P_QTFILE = 0x00 /* type bits for plain file */
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Extra permission bits in create and file modes (stat).
|
||||||
|
*/
|
||||||
|
#define L9P_DMDIR 0x80000000
|
||||||
|
enum {
|
||||||
|
L9P_DMAPPEND = 0x40000000,
|
||||||
|
L9P_DMEXCL = 0x20000000,
|
||||||
|
L9P_DMMOUNT = 0x10000000,
|
||||||
|
L9P_DMAUTH = 0x08000000,
|
||||||
|
L9P_DMTMP = 0x04000000,
|
||||||
|
L9P_DMSYMLINK = 0x02000000,
|
||||||
|
/* 9P2000.u extensions */
|
||||||
|
L9P_DMDEVICE = 0x00800000,
|
||||||
|
L9P_DMNAMEDPIPE = 0x00200000,
|
||||||
|
L9P_DMSOCKET = 0x00100000,
|
||||||
|
L9P_DMSETUID = 0x00080000,
|
||||||
|
L9P_DMSETGID = 0x00040000,
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Open/create mode bits in 9P2000 and 9P2000.u operations
|
||||||
|
* (not Linux lopen and lcreate flags, which are different).
|
||||||
|
* Note that the mode field is only one byte wide.
|
||||||
|
*/
|
||||||
|
enum l9p_omode {
|
||||||
|
L9P_OREAD = 0, /* open for read */
|
||||||
|
L9P_OWRITE = 1, /* write */
|
||||||
|
L9P_ORDWR = 2, /* read and write */
|
||||||
|
L9P_OEXEC = 3, /* execute, == read but check execute permission */
|
||||||
|
L9P_OACCMODE = 3, /* mask for the above access-mode bits */
|
||||||
|
L9P_OTRUNC = 16, /* or'ed in (except for exec), truncate file first */
|
||||||
|
L9P_OCEXEC = 32, /* or'ed in, close on exec */
|
||||||
|
L9P_ORCLOSE = 64, /* or'ed in, remove on close */
|
||||||
|
L9P_ODIRECT = 128, /* or'ed in, direct access */
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Flag bits in 9P2000.L operations (Tlopen, Tlcreate). These are
|
||||||
|
* basically just the Linux L_* flags. The bottom 3 bits are the
|
||||||
|
* same as for l9p_omode, although open-for-exec is not used:
|
||||||
|
* instead, the client does a Tgetattr and checks the mode for
|
||||||
|
* execute bits, then just opens for reading.
|
||||||
|
*
|
||||||
|
* Each L_O_xxx is just value O_xxx has on Linux in <fcntl.h>;
|
||||||
|
* not all are necessarily used. From observation, we do get
|
||||||
|
* L_O_CREAT and L_O_EXCL when creating with exclusive, and always
|
||||||
|
* get L_O_LARGEFILE. We do get L_O_APPEND when opening for
|
||||||
|
* append. We also get both L_O_DIRECT and L_O_DIRECTORY set
|
||||||
|
* when opening directories.
|
||||||
|
*
|
||||||
|
* We probably never get L_O_NOCTTY which makes no sense, and
|
||||||
|
* some of the other options may need to be handled on the client.
|
||||||
|
*/
|
||||||
|
enum l9p_l_o_flags {
|
||||||
|
L9P_L_O_CREAT = 000000100U,
|
||||||
|
L9P_L_O_EXCL = 000000200U,
|
||||||
|
L9P_L_O_NOCTTY = 000000400U,
|
||||||
|
L9P_L_O_TRUNC = 000001000U,
|
||||||
|
L9P_L_O_APPEND = 000002000U,
|
||||||
|
L9P_L_O_NONBLOCK = 000004000U,
|
||||||
|
L9P_L_O_DSYNC = 000010000U,
|
||||||
|
L9P_L_O_FASYNC = 000020000U,
|
||||||
|
L9P_L_O_DIRECT = 000040000U,
|
||||||
|
L9P_L_O_LARGEFILE = 000100000U,
|
||||||
|
L9P_L_O_DIRECTORY = 000200000U,
|
||||||
|
L9P_L_O_NOFOLLOW = 000400000U,
|
||||||
|
L9P_L_O_NOATIME = 001000000U,
|
||||||
|
L9P_L_O_CLOEXEC = 002000000U,
|
||||||
|
L9P_L_O_SYNC = 004000000U,
|
||||||
|
L9P_L_O_PATH = 010000000U,
|
||||||
|
L9P_L_O_TMPFILE = 020000000U,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct l9p_hdr {
|
||||||
|
uint8_t type;
|
||||||
|
uint16_t tag;
|
||||||
|
uint32_t fid;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct l9p_qid {
|
||||||
|
uint8_t type;
|
||||||
|
uint32_t version;
|
||||||
|
uint64_t path;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct l9p_stat {
|
||||||
|
uint16_t type;
|
||||||
|
uint32_t dev;
|
||||||
|
struct l9p_qid qid;
|
||||||
|
uint32_t mode;
|
||||||
|
uint32_t atime;
|
||||||
|
uint32_t mtime;
|
||||||
|
uint64_t length;
|
||||||
|
char *name;
|
||||||
|
char *uid;
|
||||||
|
char *gid;
|
||||||
|
char *muid;
|
||||||
|
char *extension;
|
||||||
|
uint32_t n_uid;
|
||||||
|
uint32_t n_gid;
|
||||||
|
uint32_t n_muid;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define L9P_FSTYPE 0x01021997
|
||||||
|
|
||||||
|
struct l9p_statfs {
|
||||||
|
uint32_t type; /* file system type */
|
||||||
|
uint32_t bsize; /* block size for I/O */
|
||||||
|
uint64_t blocks; /* file system size (bsize-byte blocks) */
|
||||||
|
uint64_t bfree; /* free blocks in fs */
|
||||||
|
uint64_t bavail; /* free blocks avail to non-superuser*/
|
||||||
|
uint64_t files; /* file nodes in file system (# inodes) */
|
||||||
|
uint64_t ffree; /* free file nodes in fs */
|
||||||
|
uint64_t fsid; /* file system identifier */
|
||||||
|
uint32_t namelen; /* maximum length of filenames */
|
||||||
|
};
|
||||||
|
|
||||||
|
struct l9p_f_version {
|
||||||
|
struct l9p_hdr hdr;
|
||||||
|
uint32_t msize;
|
||||||
|
char *version;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct l9p_f_tflush {
|
||||||
|
struct l9p_hdr hdr;
|
||||||
|
uint16_t oldtag;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct l9p_f_error {
|
||||||
|
struct l9p_hdr hdr;
|
||||||
|
char *ename;
|
||||||
|
uint32_t errnum;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct l9p_f_ropen {
|
||||||
|
struct l9p_hdr hdr;
|
||||||
|
struct l9p_qid qid;
|
||||||
|
uint32_t iounit;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct l9p_f_rauth {
|
||||||
|
struct l9p_hdr hdr;
|
||||||
|
struct l9p_qid aqid;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct l9p_f_attach {
|
||||||
|
struct l9p_hdr hdr;
|
||||||
|
uint32_t afid;
|
||||||
|
char *uname;
|
||||||
|
char *aname;
|
||||||
|
uint32_t n_uname;
|
||||||
|
};
|
||||||
|
#define L9P_NOFID ((uint32_t)-1) /* in Tattach, no auth fid */
|
||||||
|
#define L9P_NONUNAME ((uint32_t)-1) /* in Tattach, no n_uname */
|
||||||
|
|
||||||
|
struct l9p_f_tcreate {
|
||||||
|
struct l9p_hdr hdr;
|
||||||
|
uint32_t perm;
|
||||||
|
char *name;
|
||||||
|
uint8_t mode; /* +Topen */
|
||||||
|
char *extension;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct l9p_f_twalk {
|
||||||
|
struct l9p_hdr hdr;
|
||||||
|
uint32_t newfid;
|
||||||
|
uint16_t nwname;
|
||||||
|
char *wname[L9P_MAX_WELEM];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct l9p_f_rwalk {
|
||||||
|
struct l9p_hdr hdr;
|
||||||
|
uint16_t nwqid;
|
||||||
|
struct l9p_qid wqid[L9P_MAX_WELEM];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct l9p_f_io {
|
||||||
|
struct l9p_hdr hdr;
|
||||||
|
uint64_t offset; /* Tread, Twrite, Treaddir */
|
||||||
|
uint32_t count; /* Tread, Twrite, Rread, Treaddir, Rreaddir */
|
||||||
|
};
|
||||||
|
|
||||||
|
struct l9p_f_rstat {
|
||||||
|
struct l9p_hdr hdr;
|
||||||
|
struct l9p_stat stat;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct l9p_f_twstat {
|
||||||
|
struct l9p_hdr hdr;
|
||||||
|
struct l9p_stat stat;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct l9p_f_rstatfs {
|
||||||
|
struct l9p_hdr hdr;
|
||||||
|
struct l9p_statfs statfs;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Used for Tlcreate, Tlopen, Tmkdir, Tunlinkat. */
|
||||||
|
struct l9p_f_tlcreate {
|
||||||
|
struct l9p_hdr hdr;
|
||||||
|
char *name; /* Tlcreate, Tmkdir, Tunlinkat */
|
||||||
|
uint32_t flags; /* Tlcreate, Tlopen, Tmkdir, Tunlinkat */
|
||||||
|
uint32_t mode; /* Tlcreate, Tmkdir */
|
||||||
|
uint32_t gid; /* Tlcreate, Tmkdir */
|
||||||
|
};
|
||||||
|
|
||||||
|
struct l9p_f_tsymlink {
|
||||||
|
struct l9p_hdr hdr;
|
||||||
|
char *name;
|
||||||
|
char *symtgt;
|
||||||
|
uint32_t gid;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct l9p_f_tmknod {
|
||||||
|
struct l9p_hdr hdr;
|
||||||
|
char *name;
|
||||||
|
uint32_t mode;
|
||||||
|
uint32_t major;
|
||||||
|
uint32_t minor;
|
||||||
|
uint32_t gid;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct l9p_f_trename {
|
||||||
|
struct l9p_hdr hdr;
|
||||||
|
uint32_t dfid;
|
||||||
|
char *name;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct l9p_f_rreadlink {
|
||||||
|
struct l9p_hdr hdr;
|
||||||
|
char *target;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct l9p_f_tgetattr {
|
||||||
|
struct l9p_hdr hdr;
|
||||||
|
uint64_t request_mask;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct l9p_f_rgetattr {
|
||||||
|
struct l9p_hdr hdr;
|
||||||
|
uint64_t valid;
|
||||||
|
struct l9p_qid qid;
|
||||||
|
uint32_t mode;
|
||||||
|
uint32_t uid;
|
||||||
|
uint32_t gid;
|
||||||
|
uint64_t nlink;
|
||||||
|
uint64_t rdev;
|
||||||
|
uint64_t size;
|
||||||
|
uint64_t blksize;
|
||||||
|
uint64_t blocks;
|
||||||
|
uint64_t atime_sec;
|
||||||
|
uint64_t atime_nsec;
|
||||||
|
uint64_t mtime_sec;
|
||||||
|
uint64_t mtime_nsec;
|
||||||
|
uint64_t ctime_sec;
|
||||||
|
uint64_t ctime_nsec;
|
||||||
|
uint64_t btime_sec;
|
||||||
|
uint64_t btime_nsec;
|
||||||
|
uint64_t gen;
|
||||||
|
uint64_t data_version;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Fields in req->request_mask and reply->valid for Tgetattr, Rgetattr. */
|
||||||
|
enum l9pl_getattr_flags {
|
||||||
|
L9PL_GETATTR_MODE = 0x00000001,
|
||||||
|
L9PL_GETATTR_NLINK = 0x00000002,
|
||||||
|
L9PL_GETATTR_UID = 0x00000004,
|
||||||
|
L9PL_GETATTR_GID = 0x00000008,
|
||||||
|
L9PL_GETATTR_RDEV = 0x00000010,
|
||||||
|
L9PL_GETATTR_ATIME = 0x00000020,
|
||||||
|
L9PL_GETATTR_MTIME = 0x00000040,
|
||||||
|
L9PL_GETATTR_CTIME = 0x00000080,
|
||||||
|
L9PL_GETATTR_INO = 0x00000100,
|
||||||
|
L9PL_GETATTR_SIZE = 0x00000200,
|
||||||
|
L9PL_GETATTR_BLOCKS = 0x00000400,
|
||||||
|
/* everything up to and including BLOCKS is BASIC */
|
||||||
|
L9PL_GETATTR_BASIC = L9PL_GETATTR_MODE |
|
||||||
|
L9PL_GETATTR_NLINK |
|
||||||
|
L9PL_GETATTR_UID |
|
||||||
|
L9PL_GETATTR_GID |
|
||||||
|
L9PL_GETATTR_RDEV |
|
||||||
|
L9PL_GETATTR_ATIME |
|
||||||
|
L9PL_GETATTR_MTIME |
|
||||||
|
L9PL_GETATTR_CTIME |
|
||||||
|
L9PL_GETATTR_INO |
|
||||||
|
L9PL_GETATTR_SIZE |
|
||||||
|
L9PL_GETATTR_BLOCKS,
|
||||||
|
L9PL_GETATTR_BTIME = 0x00000800,
|
||||||
|
L9PL_GETATTR_GEN = 0x00001000,
|
||||||
|
L9PL_GETATTR_DATA_VERSION = 0x00002000,
|
||||||
|
/* BASIC + birthtime + gen + data-version = ALL */
|
||||||
|
L9PL_GETATTR_ALL = L9PL_GETATTR_BASIC |
|
||||||
|
L9PL_GETATTR_BTIME |
|
||||||
|
L9PL_GETATTR_GEN |
|
||||||
|
L9PL_GETATTR_DATA_VERSION,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct l9p_f_tsetattr {
|
||||||
|
struct l9p_hdr hdr;
|
||||||
|
uint32_t valid;
|
||||||
|
uint32_t mode;
|
||||||
|
uint32_t uid;
|
||||||
|
uint32_t gid;
|
||||||
|
uint64_t size;
|
||||||
|
uint64_t atime_sec; /* if valid & L9PL_SETATTR_ATIME_SET */
|
||||||
|
uint64_t atime_nsec; /* (else use on-server time) */
|
||||||
|
uint64_t mtime_sec; /* if valid & L9PL_SETATTR_MTIME_SET */
|
||||||
|
uint64_t mtime_nsec; /* (else use on-server time) */
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Fields in req->valid for Tsetattr. */
|
||||||
|
enum l9pl_setattr_flags {
|
||||||
|
L9PL_SETATTR_MODE = 0x00000001,
|
||||||
|
L9PL_SETATTR_UID = 0x00000002,
|
||||||
|
L9PL_SETATTR_GID = 0x00000004,
|
||||||
|
L9PL_SETATTR_SIZE = 0x00000008,
|
||||||
|
L9PL_SETATTR_ATIME = 0x00000010,
|
||||||
|
L9PL_SETATTR_MTIME = 0x00000020,
|
||||||
|
L9PL_SETATTR_CTIME = 0x00000040,
|
||||||
|
L9PL_SETATTR_ATIME_SET = 0x00000080,
|
||||||
|
L9PL_SETATTR_MTIME_SET = 0x00000100,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct l9p_f_txattrwalk {
|
||||||
|
struct l9p_hdr hdr;
|
||||||
|
uint32_t newfid;
|
||||||
|
char *name;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct l9p_f_rxattrwalk {
|
||||||
|
struct l9p_hdr hdr;
|
||||||
|
uint64_t size;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct l9p_f_txattrcreate {
|
||||||
|
struct l9p_hdr hdr;
|
||||||
|
char *name;
|
||||||
|
uint64_t attr_size;
|
||||||
|
uint32_t flags;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct l9p_f_tlock {
|
||||||
|
struct l9p_hdr hdr;
|
||||||
|
uint8_t type; /* from l9pl_lock_type */
|
||||||
|
uint32_t flags; /* from l9pl_lock_flags */
|
||||||
|
uint64_t start;
|
||||||
|
uint64_t length;
|
||||||
|
uint32_t proc_id;
|
||||||
|
char *client_id;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum l9pl_lock_type {
|
||||||
|
L9PL_LOCK_TYPE_RDLOCK = 0,
|
||||||
|
L9PL_LOCK_TYPE_WRLOCK = 1,
|
||||||
|
L9PL_LOCK_TYPE_UNLOCK = 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum l9pl_lock_flags {
|
||||||
|
L9PL_LOCK_TYPE_BLOCK = 1,
|
||||||
|
L9PL_LOCK_TYPE_RECLAIM = 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct l9p_f_rlock {
|
||||||
|
struct l9p_hdr hdr;
|
||||||
|
uint8_t status; /* from l9pl_lock_status */
|
||||||
|
};
|
||||||
|
|
||||||
|
enum l9pl_lock_status {
|
||||||
|
L9PL_LOCK_SUCCESS = 0,
|
||||||
|
L9PL_LOCK_BLOCKED = 1,
|
||||||
|
L9PL_LOCK_ERROR = 2,
|
||||||
|
L9PL_LOCK_GRACE = 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct l9p_f_getlock {
|
||||||
|
struct l9p_hdr hdr;
|
||||||
|
uint8_t type; /* from l9pl_lock_type */
|
||||||
|
uint64_t start;
|
||||||
|
uint64_t length;
|
||||||
|
uint32_t proc_id;
|
||||||
|
char *client_id;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct l9p_f_tlink {
|
||||||
|
struct l9p_hdr hdr;
|
||||||
|
uint32_t dfid;
|
||||||
|
char *name;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct l9p_f_trenameat {
|
||||||
|
struct l9p_hdr hdr;
|
||||||
|
char *oldname;
|
||||||
|
uint32_t newdirfid;
|
||||||
|
char *newname;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Flags in Tunlinkat (which re-uses f_tlcreate data structure but
|
||||||
|
* with different meaning).
|
||||||
|
*/
|
||||||
|
enum l9p_l_unlinkat_flags {
|
||||||
|
/* not sure if any other AT_* flags are passed through */
|
||||||
|
L9PL_AT_REMOVEDIR = 0x0200,
|
||||||
|
};
|
||||||
|
|
||||||
|
union l9p_fcall {
|
||||||
|
struct l9p_hdr hdr;
|
||||||
|
struct l9p_f_version version;
|
||||||
|
struct l9p_f_tflush tflush;
|
||||||
|
struct l9p_f_ropen ropen;
|
||||||
|
struct l9p_f_ropen rcreate;
|
||||||
|
struct l9p_f_ropen rattach;
|
||||||
|
struct l9p_f_error error;
|
||||||
|
struct l9p_f_rauth rauth;
|
||||||
|
struct l9p_f_attach tattach;
|
||||||
|
struct l9p_f_attach tauth;
|
||||||
|
struct l9p_f_tcreate tcreate;
|
||||||
|
struct l9p_f_tcreate topen;
|
||||||
|
struct l9p_f_twalk twalk;
|
||||||
|
struct l9p_f_rwalk rwalk;
|
||||||
|
struct l9p_f_twstat twstat;
|
||||||
|
struct l9p_f_rstat rstat;
|
||||||
|
struct l9p_f_rstatfs rstatfs;
|
||||||
|
struct l9p_f_tlcreate tlopen;
|
||||||
|
struct l9p_f_ropen rlopen;
|
||||||
|
struct l9p_f_tlcreate tlcreate;
|
||||||
|
struct l9p_f_ropen rlcreate;
|
||||||
|
struct l9p_f_tsymlink tsymlink;
|
||||||
|
struct l9p_f_ropen rsymlink;
|
||||||
|
struct l9p_f_tmknod tmknod;
|
||||||
|
struct l9p_f_ropen rmknod;
|
||||||
|
struct l9p_f_trename trename;
|
||||||
|
struct l9p_f_rreadlink rreadlink;
|
||||||
|
struct l9p_f_tgetattr tgetattr;
|
||||||
|
struct l9p_f_rgetattr rgetattr;
|
||||||
|
struct l9p_f_tsetattr tsetattr;
|
||||||
|
struct l9p_f_txattrwalk txattrwalk;
|
||||||
|
struct l9p_f_rxattrwalk rxattrwalk;
|
||||||
|
struct l9p_f_txattrcreate txattrcreate;
|
||||||
|
struct l9p_f_tlock tlock;
|
||||||
|
struct l9p_f_rlock rlock;
|
||||||
|
struct l9p_f_getlock getlock;
|
||||||
|
struct l9p_f_tlink tlink;
|
||||||
|
struct l9p_f_tlcreate tmkdir;
|
||||||
|
struct l9p_f_ropen rmkdir;
|
||||||
|
struct l9p_f_trenameat trenameat;
|
||||||
|
struct l9p_f_tlcreate tunlinkat;
|
||||||
|
struct l9p_f_io io;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* LIB9P_FCALL_H */
|
||||||
@@ -0,0 +1,160 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 Jakub Klama <jceel@FreeBSD.org>
|
||||||
|
* All rights reserved
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted providing that the following conditions
|
||||||
|
* are met:
|
||||||
|
* 1. Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||||
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||||||
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
||||||
|
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef LIB9P_FID_H
|
||||||
|
#define LIB9P_FID_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Data structure for a fid. All active fids in one session
|
||||||
|
* are stored in a hash table; the hash table provides the
|
||||||
|
* iterator to process them. (See also l9p_connection in lib9p.h.)
|
||||||
|
*
|
||||||
|
* The back-end code has additional data per fid, found via
|
||||||
|
* lo_aux. Currently this is allocated with a separate calloc().
|
||||||
|
*
|
||||||
|
* Most fids represent a file or directory, but a few are special
|
||||||
|
* purpose, including the auth fid from Tauth+Tattach, and the
|
||||||
|
* fids used for extended attributes. We have our own set of
|
||||||
|
* flags here in lo_flags.
|
||||||
|
*
|
||||||
|
* Note that all new fids start as potentially-valid (reserving
|
||||||
|
* their 32-bit fid value), but not actually-valid. If another
|
||||||
|
* (threaded) op is invoked on a not-yet-valid fid, the fid cannot
|
||||||
|
* be used. A fid can also be locked against other threads, in
|
||||||
|
* which case they must wait for it: this happens during create
|
||||||
|
* and open, which on success result in the fid changing from a
|
||||||
|
* directory to a file. (At least, all this applies in principle
|
||||||
|
* -- we're currently single-threaded per connection so the locks
|
||||||
|
* are nop-ed out and the valid bit is mainly just for debug.)
|
||||||
|
*
|
||||||
|
* Fids that are "open" (the underlying file or directory is open)
|
||||||
|
* are marked as well.
|
||||||
|
*
|
||||||
|
* Locking is managed by the front end (request.c); validation
|
||||||
|
* and type-marking can be done by either side as needed.
|
||||||
|
*
|
||||||
|
* Fid types and validity are manipulated by set* and unset*
|
||||||
|
* functions, and tested by is* ops. Note that we only
|
||||||
|
* distinguish between "directory" and "not directory" at this
|
||||||
|
* level, i.e., symlinks and devices are just "not a directory
|
||||||
|
* fid". Also, fids cannot be unset as auth or xattr fids,
|
||||||
|
* nor can an open fid become closed, except by being clunked.
|
||||||
|
* While files should not normally become directories, it IS normal
|
||||||
|
* for directory fids to become file fids due to Twalk operations.
|
||||||
|
*
|
||||||
|
* (These accessor functions are just to leave wiggle room for
|
||||||
|
* different future implementations.)
|
||||||
|
*/
|
||||||
|
struct l9p_fid {
|
||||||
|
void *lo_aux;
|
||||||
|
uint32_t lo_fid;
|
||||||
|
uint32_t lo_flags; /* volatile atomic_t when threaded? */
|
||||||
|
};
|
||||||
|
|
||||||
|
enum l9p_lo_flags {
|
||||||
|
L9P_LO_ISAUTH = 0x01,
|
||||||
|
L9P_LO_ISDIR = 0x02,
|
||||||
|
L9P_LO_ISOPEN = 0x04,
|
||||||
|
L9P_LO_ISVALID = 0x08,
|
||||||
|
L9P_LO_ISXATTR = 0x10,
|
||||||
|
};
|
||||||
|
|
||||||
|
static inline bool
|
||||||
|
l9p_fid_isauth(struct l9p_fid *fid)
|
||||||
|
{
|
||||||
|
return ((fid->lo_flags & L9P_LO_ISAUTH) != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
l9p_fid_setauth(struct l9p_fid *fid)
|
||||||
|
{
|
||||||
|
fid->lo_flags |= L9P_LO_ISAUTH;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool
|
||||||
|
l9p_fid_isdir(struct l9p_fid *fid)
|
||||||
|
{
|
||||||
|
return ((fid->lo_flags & L9P_LO_ISDIR) != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
l9p_fid_setdir(struct l9p_fid *fid)
|
||||||
|
{
|
||||||
|
fid->lo_flags |= L9P_LO_ISDIR;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
l9p_fid_unsetdir(struct l9p_fid *fid)
|
||||||
|
{
|
||||||
|
fid->lo_flags &= ~(uint32_t)L9P_LO_ISDIR;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool
|
||||||
|
l9p_fid_isopen(struct l9p_fid *fid)
|
||||||
|
{
|
||||||
|
return ((fid->lo_flags & L9P_LO_ISOPEN) != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
l9p_fid_setopen(struct l9p_fid *fid)
|
||||||
|
{
|
||||||
|
fid->lo_flags |= L9P_LO_ISOPEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool
|
||||||
|
l9p_fid_isvalid(struct l9p_fid *fid)
|
||||||
|
{
|
||||||
|
return ((fid->lo_flags & L9P_LO_ISVALID) != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
l9p_fid_setvalid(struct l9p_fid *fid)
|
||||||
|
{
|
||||||
|
fid->lo_flags |= L9P_LO_ISVALID;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
l9p_fid_unsetvalid(struct l9p_fid *fid)
|
||||||
|
{
|
||||||
|
fid->lo_flags &= ~(uint32_t)L9P_LO_ISVALID;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool
|
||||||
|
l9p_fid_isxattr(struct l9p_fid *fid)
|
||||||
|
{
|
||||||
|
return ((fid->lo_flags & L9P_LO_ISXATTR) != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
l9p_fid_setxattr(struct l9p_fid *fid)
|
||||||
|
{
|
||||||
|
fid->lo_flags |= L9P_LO_ISXATTR;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* LIB9P_FID_H */
|
||||||
@@ -0,0 +1,720 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 Chris Torek <torek@ixsystems.com>
|
||||||
|
* All rights reserved
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted providing that the following conditions
|
||||||
|
* are met:
|
||||||
|
* 1. Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||||
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||||||
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
||||||
|
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/acl.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
#include "lib9p.h"
|
||||||
|
#include "lib9p_impl.h"
|
||||||
|
#include "genacl.h"
|
||||||
|
#include "fid.h"
|
||||||
|
#include "log.h"
|
||||||
|
|
||||||
|
typedef int econvertfn(acl_entry_t, struct l9p_ace *);
|
||||||
|
|
||||||
|
#ifndef __APPLE__
|
||||||
|
static struct l9p_acl *l9p_new_acl(uint32_t acetype, uint32_t aceasize);
|
||||||
|
static struct l9p_acl *l9p_growacl(struct l9p_acl *acl, uint32_t aceasize);
|
||||||
|
static int l9p_count_aces(acl_t sysacl);
|
||||||
|
static struct l9p_acl *l9p_sysacl_to_acl(int, acl_t, econvertfn *);
|
||||||
|
#endif
|
||||||
|
static bool l9p_ingroup(gid_t tid, gid_t gid, gid_t *gids, size_t ngids);
|
||||||
|
static int l9p_check_aces(int32_t mask, struct l9p_acl *acl, struct stat *st,
|
||||||
|
uid_t uid, gid_t gid, gid_t *gids, size_t ngids);
|
||||||
|
|
||||||
|
void
|
||||||
|
l9p_acl_free(struct l9p_acl *acl)
|
||||||
|
{
|
||||||
|
|
||||||
|
free(acl);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Is the given group ID tid (test-id) any of the gid's in agids?
|
||||||
|
*/
|
||||||
|
static bool
|
||||||
|
l9p_ingroup(gid_t tid, gid_t gid, gid_t *gids, size_t ngids)
|
||||||
|
{
|
||||||
|
size_t i;
|
||||||
|
|
||||||
|
if (tid == gid)
|
||||||
|
return (true);
|
||||||
|
for (i = 0; i < ngids; i++)
|
||||||
|
if (tid == gids[i])
|
||||||
|
return (true);
|
||||||
|
return (false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* #define ACE_DEBUG */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Note that NFSv4 tests are done on a "first match" basis.
|
||||||
|
* That is, we check each ACE sequentially until we run out
|
||||||
|
* of ACEs, or find something explicitly denied (DENIED!),
|
||||||
|
* or have cleared out all our attempt-something bits. Once
|
||||||
|
* we come across an ALLOW entry for the bits we're trying,
|
||||||
|
* we clear those from the bits we're still looking for, in
|
||||||
|
* the order they appear.
|
||||||
|
*
|
||||||
|
* The result is either "definitely allowed" (we cleared
|
||||||
|
* all the bits), "definitely denied" (we hit a deny with
|
||||||
|
* some or all of the bits), or "unspecified". We
|
||||||
|
* represent these three states as +1 (positive = yes = allow),
|
||||||
|
* -1 (negative = no = denied), or 0 (no strong answer).
|
||||||
|
*
|
||||||
|
* For our caller's convenience, if we are called with a
|
||||||
|
* mask of 0, we return 0 (no answer).
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
l9p_check_aces(int32_t mask, struct l9p_acl *acl, struct stat *st,
|
||||||
|
uid_t uid, gid_t gid, gid_t *gids, size_t ngids)
|
||||||
|
{
|
||||||
|
uint32_t i;
|
||||||
|
struct l9p_ace *ace;
|
||||||
|
#ifdef ACE_DEBUG
|
||||||
|
const char *acetype, *allowdeny;
|
||||||
|
bool show_tid;
|
||||||
|
#endif
|
||||||
|
bool match;
|
||||||
|
uid_t tid;
|
||||||
|
|
||||||
|
if (mask == 0)
|
||||||
|
return (0);
|
||||||
|
|
||||||
|
for (i = 0; mask != 0 && i < acl->acl_nace; i++) {
|
||||||
|
ace = &acl->acl_aces[i];
|
||||||
|
switch (ace->ace_type) {
|
||||||
|
case L9P_ACET_ACCESS_ALLOWED:
|
||||||
|
case L9P_ACET_ACCESS_DENIED:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
/* audit, alarm - ignore */
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
#ifdef ACE_DEBUG
|
||||||
|
show_tid = false;
|
||||||
|
#endif
|
||||||
|
if (ace->ace_flags & L9P_ACEF_OWNER) {
|
||||||
|
#ifdef ACE_DEBUG
|
||||||
|
acetype = "OWNER@";
|
||||||
|
#endif
|
||||||
|
match = st->st_uid == uid;
|
||||||
|
} else if (ace->ace_flags & L9P_ACEF_GROUP) {
|
||||||
|
#ifdef ACE_DEBUG
|
||||||
|
acetype = "GROUP@";
|
||||||
|
#endif
|
||||||
|
match = l9p_ingroup(st->st_gid, gid, gids, ngids);
|
||||||
|
} else if (ace->ace_flags & L9P_ACEF_EVERYONE) {
|
||||||
|
#ifdef ACE_DEBUG
|
||||||
|
acetype = "EVERYONE@";
|
||||||
|
#endif
|
||||||
|
match = true;
|
||||||
|
} else {
|
||||||
|
if (ace->ace_idsize != sizeof(tid))
|
||||||
|
continue;
|
||||||
|
#ifdef ACE_DEBUG
|
||||||
|
show_tid = true;
|
||||||
|
#endif
|
||||||
|
memcpy(&tid, &ace->ace_idbytes, sizeof(tid));
|
||||||
|
if (ace->ace_flags & L9P_ACEF_IDENTIFIER_GROUP) {
|
||||||
|
#ifdef ACE_DEBUG
|
||||||
|
acetype = "group";
|
||||||
|
#endif
|
||||||
|
match = l9p_ingroup(tid, gid, gids, ngids);
|
||||||
|
} else {
|
||||||
|
#ifdef ACE_DEBUG
|
||||||
|
acetype = "user";
|
||||||
|
#endif
|
||||||
|
match = tid == uid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* If this ACE applies to us, check remaining bits.
|
||||||
|
* If any of those bits also apply, check the type:
|
||||||
|
* DENY means "stop now", ALLOW means allow these bits
|
||||||
|
* and keep checking.
|
||||||
|
*/
|
||||||
|
#ifdef ACE_DEBUG
|
||||||
|
allowdeny = ace->ace_type == L9P_ACET_ACCESS_DENIED ?
|
||||||
|
"deny" : "allow";
|
||||||
|
#endif
|
||||||
|
if (match && (ace->ace_mask & (uint32_t)mask) != 0) {
|
||||||
|
#ifdef ACE_DEBUG
|
||||||
|
if (show_tid)
|
||||||
|
L9P_LOG(L9P_DEBUG,
|
||||||
|
"ACE: %s %s %d: mask 0x%x ace_mask 0x%x",
|
||||||
|
allowdeny, acetype, (int)tid,
|
||||||
|
(u_int)mask, (u_int)ace->ace_mask);
|
||||||
|
else
|
||||||
|
L9P_LOG(L9P_DEBUG,
|
||||||
|
"ACE: %s %s: mask 0x%x ace_mask 0x%x",
|
||||||
|
allowdeny, acetype,
|
||||||
|
(u_int)mask, (u_int)ace->ace_mask);
|
||||||
|
#endif
|
||||||
|
if (ace->ace_type == L9P_ACET_ACCESS_DENIED)
|
||||||
|
return (-1);
|
||||||
|
mask &= ~ace->ace_mask;
|
||||||
|
#ifdef ACE_DEBUG
|
||||||
|
L9P_LOG(L9P_DEBUG, "clear 0x%x: now mask=0x%x",
|
||||||
|
(u_int)ace->ace_mask, (u_int)mask);
|
||||||
|
#endif
|
||||||
|
} else {
|
||||||
|
#ifdef ACE_DEBUG
|
||||||
|
if (show_tid)
|
||||||
|
L9P_LOG(L9P_DEBUG,
|
||||||
|
"ACE: SKIP %s %s %d: "
|
||||||
|
"match %d mask 0x%x ace_mask 0x%x",
|
||||||
|
allowdeny, acetype, (int)tid,
|
||||||
|
(int)match, (u_int)mask,
|
||||||
|
(u_int)ace->ace_mask);
|
||||||
|
else
|
||||||
|
L9P_LOG(L9P_DEBUG,
|
||||||
|
"ACE: SKIP %s %s: "
|
||||||
|
"match %d mask 0x%x ace_mask 0x%x",
|
||||||
|
allowdeny, acetype,
|
||||||
|
(int)match, (u_int)mask,
|
||||||
|
(u_int)ace->ace_mask);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Return 1 if access definitely granted. */
|
||||||
|
#ifdef ACE_DEBUG
|
||||||
|
L9P_LOG(L9P_DEBUG, "ACE: end of ACEs, mask now 0x%x: %s",
|
||||||
|
mask, mask ? "no-definitive-answer" : "ALLOW");
|
||||||
|
#endif
|
||||||
|
return (mask == 0 ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Test against ACLs.
|
||||||
|
*
|
||||||
|
* The return value is normally 0 (access allowed) or EPERM
|
||||||
|
* (access denied), so it could just be a boolean....
|
||||||
|
*
|
||||||
|
* For "make new dir in dir" and "remove dir in dir", you must
|
||||||
|
* set the mask to test the directory permissions (not ADD_FILE but
|
||||||
|
* ADD_SUBDIRECTORY, and DELETE_CHILD). For "make new file in dir"
|
||||||
|
* you must set the opmask to test file ADD_FILE.
|
||||||
|
*
|
||||||
|
* The L9P_ACE_DELETE flag means "can delete this thing"; it's not
|
||||||
|
* clear whether it should override the parent directory's ACL if
|
||||||
|
* any. In our case it does not, but a caller may try
|
||||||
|
* L9P_ACE_DELETE_CHILD (separately, on its own) and then a
|
||||||
|
* (second, separate) L9P_ACE_DELETE, to make the permissions work
|
||||||
|
* as "or" instead of "and".
|
||||||
|
*
|
||||||
|
* Pass a NULL parent/pstat if they are not applicable, e.g.,
|
||||||
|
* for doing operations on an existing file, such as reading or
|
||||||
|
* writing data or attributes. Pass in a null child/cstat if
|
||||||
|
* that's not applicable, such as creating a new file/dir.
|
||||||
|
*
|
||||||
|
* NB: it's probably wise to allow the owner of any file to update
|
||||||
|
* the ACLs of that file, but we leave that test to the caller.
|
||||||
|
*/
|
||||||
|
int l9p_acl_check_access(int32_t opmask, struct l9p_acl_check_args *args)
|
||||||
|
{
|
||||||
|
struct l9p_acl *parent, *child;
|
||||||
|
struct stat *pstat, *cstat;
|
||||||
|
int32_t pop, cop;
|
||||||
|
size_t ngids;
|
||||||
|
uid_t uid;
|
||||||
|
gid_t gid, *gids;
|
||||||
|
int panswer, canswer;
|
||||||
|
|
||||||
|
assert(opmask != 0);
|
||||||
|
parent = args->aca_parent;
|
||||||
|
pstat = args->aca_pstat;
|
||||||
|
child = args->aca_child;
|
||||||
|
cstat = args->aca_cstat;
|
||||||
|
uid = args->aca_uid;
|
||||||
|
gid = args->aca_gid;
|
||||||
|
gids = args->aca_groups;
|
||||||
|
ngids = args->aca_ngroups;
|
||||||
|
|
||||||
|
#ifdef ACE_DEBUG
|
||||||
|
L9P_LOG(L9P_DEBUG,
|
||||||
|
"l9p_acl_check_access: opmask=0x%x uid=%ld gid=%ld ngids=%zd",
|
||||||
|
(u_int)opmask, (long)uid, (long)gid, ngids);
|
||||||
|
#endif
|
||||||
|
/*
|
||||||
|
* If caller said "superuser semantics", check that first.
|
||||||
|
* Note that we apply them regardless of ACLs.
|
||||||
|
*/
|
||||||
|
if (uid == 0 && args->aca_superuser)
|
||||||
|
return (0);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If told to ignore ACLs and use only stat-based permissions,
|
||||||
|
* discard any non-NULL ACL pointers.
|
||||||
|
*
|
||||||
|
* This will need some fancying up when we support POSIX ACLs.
|
||||||
|
*/
|
||||||
|
if ((args->aca_aclmode & L9P_ACM_NFS_ACL) == 0)
|
||||||
|
parent = child = NULL;
|
||||||
|
|
||||||
|
assert(parent == NULL || parent->acl_acetype == L9P_ACLTYPE_NFSv4);
|
||||||
|
assert(parent == NULL || pstat != NULL);
|
||||||
|
assert(child == NULL || child->acl_acetype == L9P_ACLTYPE_NFSv4);
|
||||||
|
assert(child == NULL || cstat != NULL);
|
||||||
|
assert(pstat != NULL || cstat != NULL);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If the operation is UNLINK we should have either both ACLs
|
||||||
|
* or no ACLs, but we won't require that here.
|
||||||
|
*
|
||||||
|
* If a parent ACL is supplied, it's a directory by definition.
|
||||||
|
* Make sure we're allowed to do this there, whatever this is.
|
||||||
|
* If a child ACL is supplied, check it too. Note that the
|
||||||
|
* DELETE permission only applies in the child though, not
|
||||||
|
* in the parent, and the DELETE_CHILD only applies in the
|
||||||
|
* parent.
|
||||||
|
*/
|
||||||
|
pop = cop = opmask;
|
||||||
|
if (parent != NULL || pstat != NULL) {
|
||||||
|
/*
|
||||||
|
* Remove child-only bits from parent op and
|
||||||
|
* parent-only bits from child op.
|
||||||
|
*
|
||||||
|
* L9P_ACE_DELETE is child-only.
|
||||||
|
*
|
||||||
|
* L9P_ACE_DELETE_CHILD is parent-only, and three data
|
||||||
|
* access bits overlap with three directory access bits.
|
||||||
|
* We should have child==NULL && cstat==NULL, so the
|
||||||
|
* three data bits should be redundant, but it's
|
||||||
|
* both trivial and safest to remove them anyway.
|
||||||
|
*/
|
||||||
|
pop &= ~L9P_ACE_DELETE;
|
||||||
|
cop &= ~(L9P_ACE_DELETE_CHILD | L9P_ACE_LIST_DIRECTORY |
|
||||||
|
L9P_ACE_ADD_FILE | L9P_ACE_ADD_SUBDIRECTORY);
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
* Remove child-only bits from parent op. We need
|
||||||
|
* not bother since we just found we have no parent
|
||||||
|
* and no pstat, and hence won't actually *use* pop.
|
||||||
|
*
|
||||||
|
* pop &= ~(L9P_ACE_READ_DATA | L9P_ACE_WRITE_DATA |
|
||||||
|
* L9P_ACE_APPEND_DATA);
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
panswer = 0;
|
||||||
|
canswer = 0;
|
||||||
|
if (parent != NULL)
|
||||||
|
panswer = l9p_check_aces(pop, parent, pstat,
|
||||||
|
uid, gid, gids, ngids);
|
||||||
|
if (child != NULL)
|
||||||
|
canswer = l9p_check_aces(cop, child, cstat,
|
||||||
|
uid, gid, gids, ngids);
|
||||||
|
|
||||||
|
if (panswer || canswer) {
|
||||||
|
/*
|
||||||
|
* Got a definitive answer from parent and/or
|
||||||
|
* child ACLs. We're not quite done yet though.
|
||||||
|
*/
|
||||||
|
if (opmask == L9P_ACOP_UNLINK) {
|
||||||
|
/*
|
||||||
|
* For UNLINK, we can get an allow from child
|
||||||
|
* and deny from parent, or vice versa. It's
|
||||||
|
* not 100% clear how to handle the two-answer
|
||||||
|
* case. ZFS says that if either says "allow",
|
||||||
|
* we allow, and if both definitely say "deny",
|
||||||
|
* we deny. This makes sense, so we do that
|
||||||
|
* here for all cases, even "strict".
|
||||||
|
*/
|
||||||
|
if (panswer > 0 || canswer > 0)
|
||||||
|
return (0);
|
||||||
|
if (panswer < 0 && canswer < 0)
|
||||||
|
return (EPERM);
|
||||||
|
/* non-definitive answer from one! move on */
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
* Have at least one definitive answer, and
|
||||||
|
* should have only one; obey whichever
|
||||||
|
* one it is.
|
||||||
|
*/
|
||||||
|
if (panswer)
|
||||||
|
return (panswer < 0 ? EPERM : 0);
|
||||||
|
return (canswer < 0 ? EPERM : 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* No definitive answer from ACLs alone. Check for ZFS style
|
||||||
|
* permissions checking and an "UNLINK" operation under ACLs.
|
||||||
|
* If so, find write-and-execute permission on parent.
|
||||||
|
* Note that WRITE overlaps with ADD_FILE -- that's ZFS's
|
||||||
|
* way of saying "allow write to dir" -- but EXECUTE is
|
||||||
|
* separate from LIST_DIRECTORY, so that's at least a little
|
||||||
|
* bit cleaner.
|
||||||
|
*
|
||||||
|
* Note also that only a definitive yes (both bits are
|
||||||
|
* explicitly allowed) results in granting unlink, and
|
||||||
|
* a definitive no (at least one bit explicitly denied)
|
||||||
|
* results in EPERM. Only "no answer" moves on.
|
||||||
|
*/
|
||||||
|
if ((args->aca_aclmode & L9P_ACM_ZFS_ACL) &&
|
||||||
|
opmask == L9P_ACOP_UNLINK && parent != NULL) {
|
||||||
|
panswer = l9p_check_aces(L9P_ACE_ADD_FILE | L9P_ACE_EXECUTE,
|
||||||
|
parent, pstat, uid, gid, gids, ngids);
|
||||||
|
if (panswer)
|
||||||
|
return (panswer < 0 ? EPERM : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* No definitive answer from ACLs.
|
||||||
|
*
|
||||||
|
* Try POSIX style rwx permissions if allowed. This should
|
||||||
|
* be rare, occurring mainly when caller supplied no ACLs
|
||||||
|
* or set the mode to suppress them.
|
||||||
|
*
|
||||||
|
* The stat to check is the parent's if we don't have a child
|
||||||
|
* (i.e., this is a dir op), or if the DELETE_CHILD bit is set
|
||||||
|
* (i.e., this is an unlink or similar). Otherwise it's the
|
||||||
|
* child's.
|
||||||
|
*/
|
||||||
|
if (args->aca_aclmode & L9P_ACM_STAT_MODE) {
|
||||||
|
struct stat *st;
|
||||||
|
int rwx, bits;
|
||||||
|
|
||||||
|
rwx = l9p_ace_mask_to_rwx(opmask);
|
||||||
|
if ((st = cstat) == NULL || (opmask & L9P_ACE_DELETE_CHILD))
|
||||||
|
st = pstat;
|
||||||
|
if (uid == st->st_uid)
|
||||||
|
bits = (st->st_mode >> 6) & 7;
|
||||||
|
else if (l9p_ingroup(st->st_gid, gid, gids, ngids))
|
||||||
|
bits = (st->st_mode >> 3) & 7;
|
||||||
|
else
|
||||||
|
bits = st->st_mode & 7;
|
||||||
|
/*
|
||||||
|
* If all the desired bits are set, we're OK.
|
||||||
|
*/
|
||||||
|
if ((rwx & bits) == rwx)
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* all methods have failed, return EPERM */
|
||||||
|
return (EPERM);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Collapse fancy ACL operation mask down to simple Unix bits.
|
||||||
|
*
|
||||||
|
* Directory operations don't map that well. However, listing
|
||||||
|
* a directory really does require read permission, and adding
|
||||||
|
* or deleting files really does require write permission, so
|
||||||
|
* this is probably sufficient.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
l9p_ace_mask_to_rwx(int32_t opmask)
|
||||||
|
{
|
||||||
|
int rwx = 0;
|
||||||
|
|
||||||
|
if (opmask &
|
||||||
|
(L9P_ACE_READ_DATA | L9P_ACE_READ_NAMED_ATTRS |
|
||||||
|
L9P_ACE_READ_ATTRIBUTES | L9P_ACE_READ_ACL))
|
||||||
|
rwx |= 4;
|
||||||
|
if (opmask &
|
||||||
|
(L9P_ACE_WRITE_DATA | L9P_ACE_APPEND_DATA |
|
||||||
|
L9P_ACE_ADD_FILE | L9P_ACE_ADD_SUBDIRECTORY |
|
||||||
|
L9P_ACE_DELETE | L9P_ACE_DELETE_CHILD |
|
||||||
|
L9P_ACE_WRITE_NAMED_ATTRS | L9P_ACE_WRITE_ATTRIBUTES |
|
||||||
|
L9P_ACE_WRITE_ACL))
|
||||||
|
rwx |= 2;
|
||||||
|
if (opmask & L9P_ACE_EXECUTE)
|
||||||
|
rwx |= 1;
|
||||||
|
return (rwx);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef __APPLE__
|
||||||
|
/*
|
||||||
|
* Allocate new ACL holder and ACEs.
|
||||||
|
*/
|
||||||
|
static struct l9p_acl *
|
||||||
|
l9p_new_acl(uint32_t acetype, uint32_t aceasize)
|
||||||
|
{
|
||||||
|
struct l9p_acl *ret;
|
||||||
|
size_t asize, size;
|
||||||
|
|
||||||
|
asize = aceasize * sizeof(struct l9p_ace);
|
||||||
|
size = sizeof(struct l9p_acl) + asize;
|
||||||
|
ret = malloc(size);
|
||||||
|
if (ret != NULL) {
|
||||||
|
ret->acl_acetype = acetype;
|
||||||
|
ret->acl_nace = 0;
|
||||||
|
ret->acl_aceasize = aceasize;
|
||||||
|
}
|
||||||
|
return (ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Expand ACL to accomodate more entries.
|
||||||
|
*
|
||||||
|
* Currently won't shrink, only grow, so it's a fast no-op until
|
||||||
|
* we hit the allocated size. After that, it's best to grow in
|
||||||
|
* big chunks, or this will be O(n**2).
|
||||||
|
*/
|
||||||
|
static struct l9p_acl *
|
||||||
|
l9p_growacl(struct l9p_acl *acl, uint32_t aceasize)
|
||||||
|
{
|
||||||
|
struct l9p_acl *tmp;
|
||||||
|
size_t asize, size;
|
||||||
|
|
||||||
|
if (acl->acl_aceasize < aceasize) {
|
||||||
|
asize = aceasize * sizeof(struct l9p_ace);
|
||||||
|
size = sizeof(struct l9p_acl) + asize;
|
||||||
|
tmp = realloc(acl, size);
|
||||||
|
if (tmp == NULL)
|
||||||
|
free(acl);
|
||||||
|
acl = tmp;
|
||||||
|
}
|
||||||
|
return (acl);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Annoyingly, there's no POSIX-standard way to count the number
|
||||||
|
* of ACEs in a system ACL other than to walk through them all.
|
||||||
|
* This is silly, but at least 2n is still O(n), and the walk is
|
||||||
|
* short. (If the system ACL mysteriously grows, we'll handle
|
||||||
|
* that OK via growacl(), too.)
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
l9p_count_aces(acl_t sysacl)
|
||||||
|
{
|
||||||
|
acl_entry_t entry;
|
||||||
|
uint32_t n;
|
||||||
|
int id;
|
||||||
|
|
||||||
|
id = ACL_FIRST_ENTRY;
|
||||||
|
for (n = 0; acl_get_entry(sysacl, id, &entry) == 1; n++)
|
||||||
|
id = ACL_NEXT_ENTRY;
|
||||||
|
|
||||||
|
return ((int)n);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create ACL with ACEs from the given acl_t. We use the given
|
||||||
|
* convert function on each ACE.
|
||||||
|
*/
|
||||||
|
static struct l9p_acl *
|
||||||
|
l9p_sysacl_to_acl(int acetype, acl_t sysacl, econvertfn *convert)
|
||||||
|
{
|
||||||
|
struct l9p_acl *acl;
|
||||||
|
acl_entry_t entry;
|
||||||
|
uint32_t n;
|
||||||
|
int error, id;
|
||||||
|
|
||||||
|
acl = l9p_new_acl((uint32_t)acetype, (uint32_t)l9p_count_aces(sysacl));
|
||||||
|
if (acl == NULL)
|
||||||
|
return (NULL);
|
||||||
|
id = ACL_FIRST_ENTRY;
|
||||||
|
for (n = 0;;) {
|
||||||
|
if (acl_get_entry(sysacl, id, &entry) != 1)
|
||||||
|
break;
|
||||||
|
acl = l9p_growacl(acl, n + 1);
|
||||||
|
if (acl == NULL)
|
||||||
|
return (NULL);
|
||||||
|
error = (*convert)(entry, &acl->acl_aces[n]);
|
||||||
|
id = ACL_NEXT_ENTRY;
|
||||||
|
if (error == 0)
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
acl->acl_nace = n;
|
||||||
|
return (acl);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(HAVE_POSIX_ACLS) && 0 /* not yet */
|
||||||
|
struct l9p_acl *
|
||||||
|
l9p_posix_acl_to_acl(acl_t sysacl)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(HAVE_FREEBSD_ACLS)
|
||||||
|
static int
|
||||||
|
l9p_frombsdnfs4(acl_entry_t sysace, struct l9p_ace *ace)
|
||||||
|
{
|
||||||
|
acl_tag_t tag; /* e.g., USER_OBJ, GROUP, etc */
|
||||||
|
acl_entry_type_t entry_type; /* e.g., allow/deny */
|
||||||
|
acl_permset_t absdperm;
|
||||||
|
acl_flagset_t absdflag;
|
||||||
|
acl_perm_t bsdperm; /* e.g., READ_DATA */
|
||||||
|
acl_flag_t bsdflag; /* e.g., FILE_INHERIT_ACE */
|
||||||
|
uint32_t flags, mask;
|
||||||
|
int error;
|
||||||
|
uid_t uid, *aid;
|
||||||
|
|
||||||
|
error = acl_get_tag_type(sysace, &tag);
|
||||||
|
if (error == 0)
|
||||||
|
error = acl_get_entry_type_np(sysace, &entry_type);
|
||||||
|
if (error == 0)
|
||||||
|
error = acl_get_flagset_np(sysace, &absdflag);
|
||||||
|
if (error == 0)
|
||||||
|
error = acl_get_permset(sysace, &absdperm);
|
||||||
|
if (error)
|
||||||
|
return (error);
|
||||||
|
|
||||||
|
flags = 0;
|
||||||
|
uid = 0;
|
||||||
|
aid = NULL;
|
||||||
|
|
||||||
|
/* move user/group/everyone + id-is-group-id into flags */
|
||||||
|
switch (tag) {
|
||||||
|
case ACL_USER_OBJ:
|
||||||
|
flags |= L9P_ACEF_OWNER;
|
||||||
|
break;
|
||||||
|
case ACL_GROUP_OBJ:
|
||||||
|
flags |= L9P_ACEF_GROUP;
|
||||||
|
break;
|
||||||
|
case ACL_EVERYONE:
|
||||||
|
flags |= L9P_ACEF_EVERYONE;
|
||||||
|
break;
|
||||||
|
case ACL_GROUP:
|
||||||
|
flags |= L9P_ACEF_IDENTIFIER_GROUP;
|
||||||
|
/* FALLTHROUGH */
|
||||||
|
case ACL_USER:
|
||||||
|
aid = acl_get_qualifier(sysace); /* ugh, this malloc()s */
|
||||||
|
if (aid == NULL)
|
||||||
|
return (ENOMEM);
|
||||||
|
uid = *(uid_t *)aid;
|
||||||
|
free(aid);
|
||||||
|
aid = &uid;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return (EINVAL); /* can't happen */
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (entry_type) {
|
||||||
|
|
||||||
|
case ACL_ENTRY_TYPE_ALLOW:
|
||||||
|
ace->ace_type = L9P_ACET_ACCESS_ALLOWED;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ACL_ENTRY_TYPE_DENY:
|
||||||
|
ace->ace_type = L9P_ACET_ACCESS_DENIED;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ACL_ENTRY_TYPE_AUDIT:
|
||||||
|
ace->ace_type = L9P_ACET_SYSTEM_AUDIT;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ACL_ENTRY_TYPE_ALARM:
|
||||||
|
ace->ace_type = L9P_ACET_SYSTEM_ALARM;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return (EINVAL); /* can't happen */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* transform remaining BSD flags to internal NFS-y form */
|
||||||
|
bsdflag = *absdflag;
|
||||||
|
if (bsdflag & ACL_ENTRY_FILE_INHERIT)
|
||||||
|
flags |= L9P_ACEF_FILE_INHERIT_ACE;
|
||||||
|
if (bsdflag & ACL_ENTRY_DIRECTORY_INHERIT)
|
||||||
|
flags |= L9P_ACEF_DIRECTORY_INHERIT_ACE;
|
||||||
|
if (bsdflag & ACL_ENTRY_NO_PROPAGATE_INHERIT)
|
||||||
|
flags |= L9P_ACEF_NO_PROPAGATE_INHERIT_ACE;
|
||||||
|
if (bsdflag & ACL_ENTRY_INHERIT_ONLY)
|
||||||
|
flags |= L9P_ACEF_INHERIT_ONLY_ACE;
|
||||||
|
if (bsdflag & ACL_ENTRY_SUCCESSFUL_ACCESS)
|
||||||
|
flags |= L9P_ACEF_SUCCESSFUL_ACCESS_ACE_FLAG;
|
||||||
|
if (bsdflag & ACL_ENTRY_FAILED_ACCESS)
|
||||||
|
flags |= L9P_ACEF_FAILED_ACCESS_ACE_FLAG;
|
||||||
|
ace->ace_flags = flags;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Transform BSD permissions to ace_mask. Note that directory
|
||||||
|
* vs file bits are the same in both sets, so we don't need
|
||||||
|
* to worry about that, at least.
|
||||||
|
*
|
||||||
|
* There seem to be no BSD equivalents for WRITE_RETENTION
|
||||||
|
* and WRITE_RETENTION_HOLD.
|
||||||
|
*/
|
||||||
|
mask = 0;
|
||||||
|
bsdperm = *absdperm;
|
||||||
|
if (bsdperm & ACL_READ_DATA)
|
||||||
|
mask |= L9P_ACE_READ_DATA;
|
||||||
|
if (bsdperm & ACL_WRITE_DATA)
|
||||||
|
mask |= L9P_ACE_WRITE_DATA;
|
||||||
|
if (bsdperm & ACL_APPEND_DATA)
|
||||||
|
mask |= L9P_ACE_APPEND_DATA;
|
||||||
|
if (bsdperm & ACL_READ_NAMED_ATTRS)
|
||||||
|
mask |= L9P_ACE_READ_NAMED_ATTRS;
|
||||||
|
if (bsdperm & ACL_WRITE_NAMED_ATTRS)
|
||||||
|
mask |= L9P_ACE_WRITE_NAMED_ATTRS;
|
||||||
|
if (bsdperm & ACL_EXECUTE)
|
||||||
|
mask |= L9P_ACE_EXECUTE;
|
||||||
|
if (bsdperm & ACL_DELETE_CHILD)
|
||||||
|
mask |= L9P_ACE_DELETE_CHILD;
|
||||||
|
if (bsdperm & ACL_READ_ATTRIBUTES)
|
||||||
|
mask |= L9P_ACE_READ_ATTRIBUTES;
|
||||||
|
if (bsdperm & ACL_WRITE_ATTRIBUTES)
|
||||||
|
mask |= L9P_ACE_WRITE_ATTRIBUTES;
|
||||||
|
/* L9P_ACE_WRITE_RETENTION */
|
||||||
|
/* L9P_ACE_WRITE_RETENTION_HOLD */
|
||||||
|
/* 0x00800 */
|
||||||
|
if (bsdperm & ACL_DELETE)
|
||||||
|
mask |= L9P_ACE_DELETE;
|
||||||
|
if (bsdperm & ACL_READ_ACL)
|
||||||
|
mask |= L9P_ACE_READ_ACL;
|
||||||
|
if (bsdperm & ACL_WRITE_ACL)
|
||||||
|
mask |= L9P_ACE_WRITE_ACL;
|
||||||
|
if (bsdperm & ACL_WRITE_OWNER)
|
||||||
|
mask |= L9P_ACE_WRITE_OWNER;
|
||||||
|
if (bsdperm & ACL_SYNCHRONIZE)
|
||||||
|
mask |= L9P_ACE_SYNCHRONIZE;
|
||||||
|
ace->ace_mask = mask;
|
||||||
|
|
||||||
|
/* fill in variable-size user or group ID bytes */
|
||||||
|
if (aid == NULL)
|
||||||
|
ace->ace_idsize = 0;
|
||||||
|
else {
|
||||||
|
ace->ace_idsize = sizeof(uid);
|
||||||
|
memcpy(&ace->ace_idbytes[0], aid, sizeof(uid));
|
||||||
|
}
|
||||||
|
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct l9p_acl *
|
||||||
|
l9p_freebsd_nfsv4acl_to_acl(acl_t sysacl)
|
||||||
|
{
|
||||||
|
|
||||||
|
return (l9p_sysacl_to_acl(L9P_ACLTYPE_NFSv4, sysacl, l9p_frombsdnfs4));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(HAVE_DARWIN_ACLS) && 0 /* not yet */
|
||||||
|
struct l9p_acl *
|
||||||
|
l9p_darwin_nfsv4acl_to_acl(acl_t sysacl)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,307 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 Chris Torek <torek@ixsystems.com>
|
||||||
|
* All rights reserved
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted providing that the following conditions
|
||||||
|
* are met:
|
||||||
|
* 1. Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||||
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||||||
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
||||||
|
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* General ACL support for 9P2000.L.
|
||||||
|
*
|
||||||
|
* We mostly use Linux's xattr name space and nfs4 ACL bits, as
|
||||||
|
* these are the most general forms available.
|
||||||
|
*
|
||||||
|
* Linux requests attributes named
|
||||||
|
*
|
||||||
|
* "system.posix_acl_default"
|
||||||
|
* "system.posix_acl_access"
|
||||||
|
*
|
||||||
|
* to get POSIX style ACLs, and:
|
||||||
|
*
|
||||||
|
* "system.nfs4_acl"
|
||||||
|
*
|
||||||
|
* to get NFSv4 style ACLs. The v9fs client does not explicitly
|
||||||
|
* ask for the latter, but if you use the Ubuntu nfs4-acl-tools
|
||||||
|
* package, it should be able to read and write these.
|
||||||
|
*
|
||||||
|
* For the record, the Linux kernel source code also shows:
|
||||||
|
*
|
||||||
|
* - Lustre uses "trusted.*", with "*" matching "lov", "lma",
|
||||||
|
* "lmv", "dmv", "link", "fid", "version", "som", "hsm", and
|
||||||
|
* "lfsck_namespace".
|
||||||
|
*
|
||||||
|
* - ceph has a name tree of the form "ceph.<type>.<name>" with
|
||||||
|
* <type,name> pairs like <"dir","entries">, <"dir","files>,
|
||||||
|
* <"file","layout">, and so on.
|
||||||
|
*
|
||||||
|
* - ext4 uses the POSIX names, plus some special ext4-specific
|
||||||
|
* goop that might not get externalized.
|
||||||
|
*
|
||||||
|
* - NFS uses both the POSIX names and the NFSv4 ACLs. However,
|
||||||
|
* what it mainly does is have nfsd generate fake NFSv4 ACLs
|
||||||
|
* from POSIX ACLs. If you run an NFS client, the client
|
||||||
|
* relies on the server actually implementing the ACLs, and
|
||||||
|
* lets nfs4-acl-tools read and write the system.nfs4_acl xattr
|
||||||
|
* data. If you run an NFS server off, e.g., an ext4 file system,
|
||||||
|
* the server looks for the system.nfs4_acl xattr, serves that
|
||||||
|
* out if found, and otherwise just generates the fakes.
|
||||||
|
*
|
||||||
|
* - "security.*" and "selinux.*" are reserved.
|
||||||
|
*
|
||||||
|
* - "security.capability" is the name for capabilities.
|
||||||
|
*
|
||||||
|
* - sockets use "system.sockprotoname".
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if defined(__APPLE__)
|
||||||
|
#define HAVE_POSIX_ACLS
|
||||||
|
#define HAVE_DARWIN_ACLS
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(__FreeBSD__)
|
||||||
|
#define HAVE_POSIX_ACLS
|
||||||
|
#define HAVE_FREEBSD_ACLS
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/acl.h> /* XXX assumes existence of sys/acl.h */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* An ACL consists of a number of ACEs that grant some kind of
|
||||||
|
* "allow" or "deny" to some specific entity.
|
||||||
|
*
|
||||||
|
* The number of ACEs is potentially unlimited, although in practice
|
||||||
|
* they tend not to be that long.
|
||||||
|
*
|
||||||
|
* It's the responsibility of the back-end to supply the ACL
|
||||||
|
* for each test. However, the ACL may be in some sort of
|
||||||
|
* system-specific form. It's the responsibility of some
|
||||||
|
* (system-specific) code to translate it to *this* form, after
|
||||||
|
* which the backend may use l9p_acl_check_access() to get
|
||||||
|
* access granted or denied (and, eventually, audits and alarms
|
||||||
|
* recorded and raises, although that's yet to be designed).
|
||||||
|
*
|
||||||
|
* The reason for all this faffing-about with formats is so that
|
||||||
|
* we can *report* the ACLs using Linux 9p style xattrs.
|
||||||
|
*/
|
||||||
|
|
||||||
|
struct l9p_acl;
|
||||||
|
struct l9p_fid;
|
||||||
|
|
||||||
|
void l9p_acl_free(struct l9p_acl *);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* An ACL is made up of ACEs.
|
||||||
|
*
|
||||||
|
* Each ACE has:
|
||||||
|
*
|
||||||
|
* - a type: allow, deny, audit, alarm
|
||||||
|
* - a set of flags
|
||||||
|
* - permissions bits: a "mask"
|
||||||
|
* - an optional, nominally-variable-length identity
|
||||||
|
*
|
||||||
|
* The last part is especially tricky and currently has limited
|
||||||
|
* support here: it's always a 16 byte field on Darwin, and just
|
||||||
|
* a uint32_t on BSD (should be larger, really). Linux supports
|
||||||
|
* very large, actually-variable-size values; we'll deal with
|
||||||
|
* this later, maybe.
|
||||||
|
*
|
||||||
|
* We will define the mask first, below, since these are also the bits
|
||||||
|
* passed in for the accmask argument to l9p_acl_check_access().
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ACL entry mask, and accmask argument flags.
|
||||||
|
*
|
||||||
|
* NB: not every bit is implemented, but they are all here because
|
||||||
|
* they are all defined as part of an NFSv4 ACL entry, which is
|
||||||
|
* more or less a superset of a POSIX ACL entry. This means you
|
||||||
|
* can put a complete NFSv4 ACL in and we can reproduce it.
|
||||||
|
*
|
||||||
|
* Note that the LIST_DIRECTORY, ADD_FILE, and ADD_SUBDIRECTORY bits
|
||||||
|
* apply only to a directory, while the READ_DATA, WRITE_DATA, and
|
||||||
|
* APPEND_DATA bits apply only to a file. See aca_parent/aca_child
|
||||||
|
* below.
|
||||||
|
*/
|
||||||
|
#define L9P_ACE_READ_DATA 0x00001
|
||||||
|
#define L9P_ACE_LIST_DIRECTORY 0x00001 /* same as READ_DATA */
|
||||||
|
#define L9P_ACE_WRITE_DATA 0x00002
|
||||||
|
#define L9P_ACE_ADD_FILE 0x00002 /* same as WRITE_DATA */
|
||||||
|
#define L9P_ACE_APPEND_DATA 0x00004
|
||||||
|
#define L9P_ACE_ADD_SUBDIRECTORY 0x00004 /* same as APPEND_DATA */
|
||||||
|
#define L9P_ACE_READ_NAMED_ATTRS 0x00008
|
||||||
|
#define L9P_ACE_WRITE_NAMED_ATTRS 0x00010
|
||||||
|
#define L9P_ACE_EXECUTE 0x00020
|
||||||
|
#define L9P_ACE_DELETE_CHILD 0x00040
|
||||||
|
#define L9P_ACE_READ_ATTRIBUTES 0x00080
|
||||||
|
#define L9P_ACE_WRITE_ATTRIBUTES 0x00100
|
||||||
|
#define L9P_ACE_WRITE_RETENTION 0x00200 /* not used here */
|
||||||
|
#define L9P_ACE_WRITE_RETENTION_HOLD 0x00400 /* not used here */
|
||||||
|
/* 0x00800 unused? */
|
||||||
|
#define L9P_ACE_DELETE 0x01000
|
||||||
|
#define L9P_ACE_READ_ACL 0x02000
|
||||||
|
#define L9P_ACE_WRITE_ACL 0x04000
|
||||||
|
#define L9P_ACE_WRITE_OWNER 0x08000
|
||||||
|
#define L9P_ACE_SYNCHRONIZE 0x10000 /* not used here */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This is not an ACE bit, but is used with the access checking
|
||||||
|
* below. It represents a request to unlink (delete child /
|
||||||
|
* delete) an entity, and is equivalent to asking for *either*
|
||||||
|
* (not both) permission.
|
||||||
|
*/
|
||||||
|
#define L9P_ACOP_UNLINK (L9P_ACE_DELETE_CHILD | L9P_ACE_DELETE)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Access checking takes a lot of arguments, so they are
|
||||||
|
* collected into a "struct" here.
|
||||||
|
*
|
||||||
|
* The aca_parent and aca_pstat fields may/must be NULL if the
|
||||||
|
* operation itself does not involve "directory" permissions.
|
||||||
|
* The aca_child and aca_cstat fields may/must be NULL if the
|
||||||
|
* operation does not involve anything *but* a directory. This
|
||||||
|
* is how we decide whether you're interested in L9P_ACE_READ_DATA
|
||||||
|
* vs L9P_ACE_LIST_DIRECTORY, for instance.
|
||||||
|
*
|
||||||
|
* Note that it's OK for both parent and child to be directories
|
||||||
|
* (as is the case when we're adding or deleting a subdirectory).
|
||||||
|
*/
|
||||||
|
struct l9p_acl_check_args {
|
||||||
|
uid_t aca_uid; /* the uid that is requesting access */
|
||||||
|
gid_t aca_gid; /* the gid that is requesting access */
|
||||||
|
gid_t *aca_groups; /* the additional group-set, if any */
|
||||||
|
size_t aca_ngroups; /* number of groups in group-set */
|
||||||
|
struct l9p_acl *aca_parent; /* ACLs associated with parent/dir */
|
||||||
|
struct stat *aca_pstat; /* stat data for parent/dir */
|
||||||
|
struct l9p_acl *aca_child; /* ACLs associated with file */
|
||||||
|
struct stat *aca_cstat; /* stat data for file */
|
||||||
|
int aca_aclmode; /* mode checking bits, see below */
|
||||||
|
bool aca_superuser; /* alway allow uid==0 in STAT_MODE */
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Access checking mode bits in aca_checkmode. If you enable
|
||||||
|
* ACLs, they are used first, optionally with ZFS style ACLs.
|
||||||
|
* This means that even if aca_superuser is set, if an ACL denies
|
||||||
|
* permission to uid 0, permission is really denied.
|
||||||
|
*
|
||||||
|
* NFS style ACLs run before POSIX style ACLs (though POSIX
|
||||||
|
* ACLs aren't done yet anyway).
|
||||||
|
*
|
||||||
|
* N.B.: you probably want L9P_ACL_ZFS, especially when operating
|
||||||
|
* with a ZFS file system on FreeBSD.
|
||||||
|
*/
|
||||||
|
#define L9P_ACM_NFS_ACL 0x0001 /* enable NFS ACL checking */
|
||||||
|
#define L9P_ACM_ZFS_ACL 0x0002 /* use ZFS ACL unlink semantics */
|
||||||
|
#define L9P_ACM_POSIX_ACL 0x0004 /* enable POSIX ACL checking (notyet) */
|
||||||
|
#define L9P_ACM_STAT_MODE 0x0008 /* enable st_mode bits */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Requests to access some file or directory must provide:
|
||||||
|
*
|
||||||
|
* - An operation. This should usually be just one bit from the
|
||||||
|
* L9P_ACE_* bit-sets above, or our special L9P_ACOP_UNLINK.
|
||||||
|
* For a few file-open operations it may be multiple bits,
|
||||||
|
* e.g., both read and write data.
|
||||||
|
* - The identity of the accessor: uid + gid + gid-set.
|
||||||
|
* - The type of access desired: this may be multiple bits.
|
||||||
|
* - The parent directory, if applicable.
|
||||||
|
* - The child file/dir being accessed, if applicable.
|
||||||
|
* - stat data for parent and/or child, if applicable.
|
||||||
|
*
|
||||||
|
* The ACLs and/or stat data of the parent and/or child get used
|
||||||
|
* here, so the caller must provide them. We should have a way to
|
||||||
|
* cache these on fids, but not yet. The parent and child
|
||||||
|
* arguments are a bit tricky; see the code in genacl.c.
|
||||||
|
*/
|
||||||
|
int l9p_acl_check_access(int32_t op, struct l9p_acl_check_args *args);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* When falling back to POSIX ACL or Unix-style permissions
|
||||||
|
* testing, it's nice to collapse the above detailed permissions
|
||||||
|
* into simple read/write/execute bits (value 0..7). We provide
|
||||||
|
* a small utility function that does this.
|
||||||
|
*/
|
||||||
|
int l9p_ace_mask_to_rwx(int32_t);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The rest of the data in an ACE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* type in ace_type */
|
||||||
|
#define L9P_ACET_ACCESS_ALLOWED 0
|
||||||
|
#define L9P_ACET_ACCESS_DENIED 1
|
||||||
|
#define L9P_ACET_SYSTEM_AUDIT 2
|
||||||
|
#define L9P_ACET_SYSTEM_ALARM 3
|
||||||
|
|
||||||
|
/* flags in ace_flags */
|
||||||
|
#define L9P_ACEF_FILE_INHERIT_ACE 0x001
|
||||||
|
#define L9P_ACEF_DIRECTORY_INHERIT_ACE 0x002
|
||||||
|
#define L9P_ACEF_NO_PROPAGATE_INHERIT_ACE 0x004
|
||||||
|
#define L9P_ACEF_INHERIT_ONLY_ACE 0x008
|
||||||
|
#define L9P_ACEF_SUCCESSFUL_ACCESS_ACE_FLAG 0x010
|
||||||
|
#define L9P_ACEF_FAILED_ACCESS_ACE_FLAG 0x020
|
||||||
|
#define L9P_ACEF_IDENTIFIER_GROUP 0x040
|
||||||
|
#define L9P_ACEF_OWNER 0x080
|
||||||
|
#define L9P_ACEF_GROUP 0x100
|
||||||
|
#define L9P_ACEF_EVERYONE 0x200
|
||||||
|
|
||||||
|
#if defined(__APPLE__)
|
||||||
|
# define L9P_ACE_IDSIZE 16 /* but, how do we map Darwin uuid? */
|
||||||
|
#else
|
||||||
|
# define L9P_ACE_IDSIZE 4
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct l9p_ace {
|
||||||
|
uint16_t ace_type; /* ACL entry type */
|
||||||
|
uint16_t ace_flags; /* ACL entry flags */
|
||||||
|
uint32_t ace_mask; /* ACL entry mask */
|
||||||
|
uint32_t ace_idsize; /* length of ace_idbytes */
|
||||||
|
unsigned char ace_idbytes[L9P_ACE_IDSIZE];
|
||||||
|
};
|
||||||
|
|
||||||
|
#define L9P_ACLTYPE_NFSv4 1 /* currently the only valid type */
|
||||||
|
struct l9p_acl {
|
||||||
|
uint32_t acl_acetype; /* reserved for future expansion */
|
||||||
|
uint32_t acl_nace; /* number of occupied ACEs */
|
||||||
|
uint32_t acl_aceasize; /* actual size of ACE array */
|
||||||
|
struct l9p_ace acl_aces[]; /* variable length ACE array */
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* These are the system-specific converters.
|
||||||
|
*
|
||||||
|
* Right now the backend needs to just find BSD NFSv4 ACLs
|
||||||
|
* and convert them before each operation that needs to be
|
||||||
|
* tested.
|
||||||
|
*/
|
||||||
|
#if defined(HAVE_DARWIN_ACLS)
|
||||||
|
struct l9p_acl *l9p_darwin_nfsv4acl_to_acl(acl_t acl);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(HAVE_FREEBSD_ACLS)
|
||||||
|
struct l9p_acl *l9p_freebsd_nfsv4acl_to_acl(acl_t acl);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(HAVE_POSIX_ACLS) && 0 /* not yet */
|
||||||
|
struct l9p_acl *l9p_posix_acl_to_acl(acl_t acl);
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,267 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 Jakub Klama <jceel@FreeBSD.org>
|
||||||
|
* All rights reserved
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted providing that the following conditions
|
||||||
|
* are met:
|
||||||
|
* 1. Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||||
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||||||
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
||||||
|
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/queue.h>
|
||||||
|
#include "lib9p_impl.h"
|
||||||
|
#include "hashtable.h"
|
||||||
|
|
||||||
|
static struct ht_item *ht_iter_advance(struct ht_iter *, struct ht_item *);
|
||||||
|
|
||||||
|
void
|
||||||
|
ht_init(struct ht *h, ssize_t size)
|
||||||
|
{
|
||||||
|
ssize_t i;
|
||||||
|
|
||||||
|
memset(h, 0, sizeof(struct ht));
|
||||||
|
h->ht_nentries = size;
|
||||||
|
h->ht_entries = l9p_calloc((size_t)size, sizeof(struct ht_entry));
|
||||||
|
pthread_rwlock_init(&h->ht_rwlock, NULL);
|
||||||
|
|
||||||
|
for (i = 0; i < size; i++)
|
||||||
|
TAILQ_INIT(&h->ht_entries[i].hte_items);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ht_destroy(struct ht *h)
|
||||||
|
{
|
||||||
|
struct ht_entry *he;
|
||||||
|
struct ht_item *item, *tmp;
|
||||||
|
ssize_t i;
|
||||||
|
|
||||||
|
for (i = 0; i < h->ht_nentries; i++) {
|
||||||
|
he = &h->ht_entries[i];
|
||||||
|
TAILQ_FOREACH_SAFE(item, &he->hte_items, hti_link, tmp) {
|
||||||
|
free(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_rwlock_destroy(&h->ht_rwlock);
|
||||||
|
free(h->ht_entries);
|
||||||
|
h->ht_entries = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *
|
||||||
|
ht_find(struct ht *h, uint32_t hash)
|
||||||
|
{
|
||||||
|
void *result;
|
||||||
|
|
||||||
|
ht_rdlock(h);
|
||||||
|
result = ht_find_locked(h, hash);
|
||||||
|
ht_unlock(h);
|
||||||
|
return (result);
|
||||||
|
}
|
||||||
|
|
||||||
|
void *
|
||||||
|
ht_find_locked(struct ht *h, uint32_t hash)
|
||||||
|
{
|
||||||
|
struct ht_entry *entry;
|
||||||
|
struct ht_item *item;
|
||||||
|
|
||||||
|
entry = &h->ht_entries[hash % h->ht_nentries];
|
||||||
|
|
||||||
|
TAILQ_FOREACH(item, &entry->hte_items, hti_link) {
|
||||||
|
if (item->hti_hash == hash)
|
||||||
|
return (item->hti_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
ht_add(struct ht *h, uint32_t hash, void *value)
|
||||||
|
{
|
||||||
|
struct ht_entry *entry;
|
||||||
|
struct ht_item *item;
|
||||||
|
|
||||||
|
ht_wrlock(h);
|
||||||
|
entry = &h->ht_entries[hash % h->ht_nentries];
|
||||||
|
|
||||||
|
TAILQ_FOREACH(item, &entry->hte_items, hti_link) {
|
||||||
|
if (item->hti_hash == hash) {
|
||||||
|
errno = EEXIST;
|
||||||
|
ht_unlock(h);
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
item = l9p_calloc(1, sizeof(struct ht_item));
|
||||||
|
item->hti_hash = hash;
|
||||||
|
item->hti_data = value;
|
||||||
|
TAILQ_INSERT_TAIL(&entry->hte_items, item, hti_link);
|
||||||
|
ht_unlock(h);
|
||||||
|
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
ht_remove(struct ht *h, uint32_t hash)
|
||||||
|
{
|
||||||
|
int result;
|
||||||
|
|
||||||
|
ht_wrlock(h);
|
||||||
|
result = ht_remove_locked(h, hash);
|
||||||
|
ht_unlock(h);
|
||||||
|
return (result);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
ht_remove_locked(struct ht *h, uint32_t hash)
|
||||||
|
{
|
||||||
|
struct ht_entry *entry;
|
||||||
|
struct ht_item *item, *tmp;
|
||||||
|
ssize_t slot = hash % h->ht_nentries;
|
||||||
|
|
||||||
|
entry = &h->ht_entries[slot];
|
||||||
|
|
||||||
|
TAILQ_FOREACH_SAFE(item, &entry->hte_items, hti_link, tmp) {
|
||||||
|
if (item->hti_hash == hash) {
|
||||||
|
TAILQ_REMOVE(&entry->hte_items, item, hti_link);
|
||||||
|
free(item);
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
errno = ENOENT;
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Inner workings for advancing the iterator.
|
||||||
|
*
|
||||||
|
* If we have a current item, that tells us how to find the
|
||||||
|
* next item. If not, we get the first item from the next
|
||||||
|
* slot (well, the next slot with an item); in any case, we
|
||||||
|
* record the new slot and return the next item.
|
||||||
|
*
|
||||||
|
* For bootstrapping, iter->htit_slot can be -1 to start
|
||||||
|
* searching at slot 0.
|
||||||
|
*
|
||||||
|
* Caller must hold a lock on the table.
|
||||||
|
*/
|
||||||
|
static struct ht_item *
|
||||||
|
ht_iter_advance(struct ht_iter *iter, struct ht_item *cur)
|
||||||
|
{
|
||||||
|
struct ht_item *next;
|
||||||
|
struct ht *h;
|
||||||
|
ssize_t slot;
|
||||||
|
|
||||||
|
h = iter->htit_parent;
|
||||||
|
|
||||||
|
if (cur == NULL)
|
||||||
|
next = NULL;
|
||||||
|
else
|
||||||
|
next = TAILQ_NEXT(cur, hti_link);
|
||||||
|
|
||||||
|
if (next == NULL) {
|
||||||
|
slot = iter->htit_slot;
|
||||||
|
while (++slot < h->ht_nentries) {
|
||||||
|
next = TAILQ_FIRST(&h->ht_entries[slot].hte_items);
|
||||||
|
if (next != NULL)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
iter->htit_slot = slot;
|
||||||
|
}
|
||||||
|
return (next);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Remove the current item - there must be one, or this is an
|
||||||
|
* error. This (necessarily) pre-locates the next item, so callers
|
||||||
|
* must not use it on an actively-changing table.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
ht_remove_at_iter(struct ht_iter *iter)
|
||||||
|
{
|
||||||
|
struct ht_item *item;
|
||||||
|
struct ht *h;
|
||||||
|
ssize_t slot;
|
||||||
|
|
||||||
|
assert(iter != NULL);
|
||||||
|
|
||||||
|
if ((item = iter->htit_curr) == NULL) {
|
||||||
|
errno = EINVAL;
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* remove the item from the table, saving the NEXT one */
|
||||||
|
h = iter->htit_parent;
|
||||||
|
ht_wrlock(h);
|
||||||
|
slot = iter->htit_slot;
|
||||||
|
iter->htit_next = ht_iter_advance(iter, item);
|
||||||
|
TAILQ_REMOVE(&h->ht_entries[slot].hte_items, item, hti_link);
|
||||||
|
ht_unlock(h);
|
||||||
|
|
||||||
|
/* mark us as no longer on an item, then free it */
|
||||||
|
iter->htit_curr = NULL;
|
||||||
|
free(item);
|
||||||
|
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Initialize iterator. Subsequent ht_next calls will find the
|
||||||
|
* first item, then the next, and so on. Callers should in general
|
||||||
|
* not use this on actively-changing tables, though we do our best
|
||||||
|
* to make it semi-sensible.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
ht_iter(struct ht *h, struct ht_iter *iter)
|
||||||
|
{
|
||||||
|
|
||||||
|
iter->htit_parent = h;
|
||||||
|
iter->htit_curr = NULL;
|
||||||
|
iter->htit_next = NULL;
|
||||||
|
iter->htit_slot = -1; /* which will increment to 0 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Return the next item, which is the first item if we have not
|
||||||
|
* yet been called on this iterator, or the next item if we have.
|
||||||
|
*/
|
||||||
|
void *
|
||||||
|
ht_next(struct ht_iter *iter)
|
||||||
|
{
|
||||||
|
struct ht_item *item;
|
||||||
|
struct ht *h;
|
||||||
|
|
||||||
|
if ((item = iter->htit_next) == NULL) {
|
||||||
|
/* no pre-loaded next; find next from current */
|
||||||
|
h = iter->htit_parent;
|
||||||
|
ht_rdlock(h);
|
||||||
|
item = ht_iter_advance(iter, iter->htit_curr);
|
||||||
|
ht_unlock(h);
|
||||||
|
} else
|
||||||
|
iter->htit_next = NULL;
|
||||||
|
iter->htit_curr = item;
|
||||||
|
return (item == NULL ? NULL : item->hti_data);
|
||||||
|
}
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 Jakub Klama <jceel@FreeBSD.org>
|
||||||
|
* All rights reserved
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted providing that the following conditions
|
||||||
|
* are met:
|
||||||
|
* 1. Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||||
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||||||
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
||||||
|
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef LIB9P_HASHTABLE_H
|
||||||
|
#define LIB9P_HASHTABLE_H
|
||||||
|
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <sys/queue.h>
|
||||||
|
|
||||||
|
struct ht {
|
||||||
|
struct ht_entry * ht_entries;
|
||||||
|
ssize_t ht_nentries;
|
||||||
|
pthread_rwlock_t ht_rwlock;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ht_entry {
|
||||||
|
TAILQ_HEAD(, ht_item) hte_items;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ht_item {
|
||||||
|
uint32_t hti_hash;
|
||||||
|
void * hti_data;
|
||||||
|
TAILQ_ENTRY(ht_item) hti_link;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ht_iter {
|
||||||
|
struct ht * htit_parent;
|
||||||
|
struct ht_item * htit_curr;
|
||||||
|
struct ht_item * htit_next;
|
||||||
|
ssize_t htit_slot;
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef __clang__
|
||||||
|
#pragma clang diagnostic push
|
||||||
|
#pragma clang diagnostic ignored "-Wthread-safety-analysis"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Obtain read-lock on hash table.
|
||||||
|
*/
|
||||||
|
static inline int
|
||||||
|
ht_rdlock(struct ht *h)
|
||||||
|
{
|
||||||
|
|
||||||
|
return (pthread_rwlock_rdlock(&h->ht_rwlock));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Obtain write-lock on hash table.
|
||||||
|
*/
|
||||||
|
static inline int
|
||||||
|
ht_wrlock(struct ht *h)
|
||||||
|
{
|
||||||
|
|
||||||
|
return (pthread_rwlock_wrlock(&h->ht_rwlock));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Release lock on hash table.
|
||||||
|
*/
|
||||||
|
static inline int
|
||||||
|
ht_unlock(struct ht *h)
|
||||||
|
{
|
||||||
|
|
||||||
|
return (pthread_rwlock_unlock(&h->ht_rwlock));
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef __clang__
|
||||||
|
#pragma clang diagnostic pop
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void ht_init(struct ht *h, ssize_t size);
|
||||||
|
void ht_destroy(struct ht *h);
|
||||||
|
void *ht_find(struct ht *h, uint32_t hash);
|
||||||
|
void *ht_find_locked(struct ht *h, uint32_t hash);
|
||||||
|
int ht_add(struct ht *h, uint32_t hash, void *value);
|
||||||
|
int ht_remove(struct ht *h, uint32_t hash);
|
||||||
|
int ht_remove_locked(struct ht *h, uint32_t hash);
|
||||||
|
int ht_remove_at_iter(struct ht_iter *iter);
|
||||||
|
void ht_iter(struct ht *h, struct ht_iter *iter);
|
||||||
|
void *ht_next(struct ht_iter *iter);
|
||||||
|
|
||||||
|
#endif /* LIB9P_HASHTABLE_H */
|
||||||
@@ -0,0 +1,249 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 Jakub Klama <jceel@FreeBSD.org>
|
||||||
|
* All rights reserved
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted providing that the following conditions
|
||||||
|
* are met:
|
||||||
|
* 1. Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||||
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||||||
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
||||||
|
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef LIB9P_LIB9P_H
|
||||||
|
#define LIB9P_LIB9P_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/queue.h>
|
||||||
|
#include <sys/uio.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
|
||||||
|
#if defined(__FreeBSD__)
|
||||||
|
#include <sys/sbuf.h>
|
||||||
|
#else
|
||||||
|
#include "sbuf/sbuf.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "fcall.h"
|
||||||
|
#include "threadpool.h"
|
||||||
|
#include "hashtable.h"
|
||||||
|
|
||||||
|
#define L9P_DEFAULT_MSIZE 8192
|
||||||
|
#define L9P_MAX_IOV 128
|
||||||
|
#define L9P_NUMTHREADS 8
|
||||||
|
|
||||||
|
struct l9p_request;
|
||||||
|
struct l9p_backend;
|
||||||
|
struct l9p_fid;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Functions to implement underlying transport for lib9p.
|
||||||
|
*
|
||||||
|
* The transport is responsible for:
|
||||||
|
*
|
||||||
|
* - allocating a response buffer (filling in the iovec and niov)
|
||||||
|
* (gets req, pointer to base of iov array of size L9P_MAX_IOV,
|
||||||
|
* pointer to niov, lt_aux)
|
||||||
|
*
|
||||||
|
* - sending a response, when a request has a reply ready
|
||||||
|
* (gets req, pointer to iov, niov, actual response length, lt_aux)
|
||||||
|
*
|
||||||
|
* - dropping the response buffer, when a request has been
|
||||||
|
* flushed or otherwise dropped without a response
|
||||||
|
* (gets req, pointer to iov, niov, lt_aux)
|
||||||
|
*
|
||||||
|
* The transport is of course also responsible for feeding in
|
||||||
|
* request-buffers, but that happens by the transport calling
|
||||||
|
* l9p_connection_recv().
|
||||||
|
*/
|
||||||
|
struct l9p_transport {
|
||||||
|
void *lt_aux;
|
||||||
|
int (*lt_get_response_buffer)(struct l9p_request *, struct iovec *,
|
||||||
|
size_t *, void *);
|
||||||
|
int (*lt_send_response)(struct l9p_request *, const struct iovec *,
|
||||||
|
size_t, size_t, void *);
|
||||||
|
void (*lt_drop_response)(struct l9p_request *, const struct iovec *,
|
||||||
|
size_t, void *);
|
||||||
|
};
|
||||||
|
|
||||||
|
enum l9p_pack_mode {
|
||||||
|
L9P_PACK,
|
||||||
|
L9P_UNPACK
|
||||||
|
};
|
||||||
|
|
||||||
|
enum l9p_integer_type {
|
||||||
|
L9P_BYTE = 1,
|
||||||
|
L9P_WORD = 2,
|
||||||
|
L9P_DWORD = 4,
|
||||||
|
L9P_QWORD = 8
|
||||||
|
};
|
||||||
|
|
||||||
|
enum l9p_version {
|
||||||
|
L9P_INVALID_VERSION = 0,
|
||||||
|
L9P_2000 = 1,
|
||||||
|
L9P_2000U = 2,
|
||||||
|
L9P_2000L = 3
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This structure is used for unpacking (decoding) incoming
|
||||||
|
* requests and packing (encoding) outgoing results. It has its
|
||||||
|
* own copy of the iov array, with its own counters for working
|
||||||
|
* through that array, but it borrows the actual DATA from the
|
||||||
|
* original iov array associated with the original request (see
|
||||||
|
* below).
|
||||||
|
*/
|
||||||
|
struct l9p_message {
|
||||||
|
enum l9p_pack_mode lm_mode;
|
||||||
|
struct iovec lm_iov[L9P_MAX_IOV];
|
||||||
|
size_t lm_niov;
|
||||||
|
size_t lm_cursor_iov;
|
||||||
|
size_t lm_cursor_offset;
|
||||||
|
size_t lm_size;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Data structure for a request/response pair (Tfoo/Rfoo).
|
||||||
|
*
|
||||||
|
* Note that the response is not formatted out into raw data
|
||||||
|
* (overwriting the request raw data) until we are really
|
||||||
|
* responding, with the exception of read operations Tread
|
||||||
|
* and Treaddir, which overlay their result-data into the
|
||||||
|
* iov array in the process of reading.
|
||||||
|
*
|
||||||
|
* We have room for two incoming fids, in case we are
|
||||||
|
* using 9P2000.L protocol. Note that nothing that uses two
|
||||||
|
* fids also has an output fid (newfid), so we could have a
|
||||||
|
* union of lr_fid2 and lr_newfid, but keeping them separate
|
||||||
|
* is probably a bit less error-prone. (If we want to shave
|
||||||
|
* memory requirements there are more places to look.)
|
||||||
|
*
|
||||||
|
* (The fid, fid2, and newfid fields should be removed via
|
||||||
|
* reorganization, as they are only used for smuggling data
|
||||||
|
* between request.c and the backend and should just be
|
||||||
|
* parameters to backend ops.)
|
||||||
|
*/
|
||||||
|
struct l9p_request {
|
||||||
|
struct l9p_message lr_req_msg; /* for unpacking the request */
|
||||||
|
struct l9p_message lr_resp_msg; /* for packing the response */
|
||||||
|
union l9p_fcall lr_req; /* the request, decoded/unpacked */
|
||||||
|
union l9p_fcall lr_resp; /* the response, not yet packed */
|
||||||
|
|
||||||
|
struct l9p_fid *lr_fid;
|
||||||
|
struct l9p_fid *lr_fid2;
|
||||||
|
struct l9p_fid *lr_newfid;
|
||||||
|
|
||||||
|
struct l9p_connection *lr_conn; /* containing connection */
|
||||||
|
void *lr_aux; /* reserved for transport layer */
|
||||||
|
|
||||||
|
struct iovec lr_data_iov[L9P_MAX_IOV]; /* iovecs for req + resp */
|
||||||
|
size_t lr_data_niov; /* actual size of data_iov */
|
||||||
|
|
||||||
|
int lr_error; /* result from l9p_dispatch_request */
|
||||||
|
|
||||||
|
/* proteced by threadpool mutex */
|
||||||
|
enum l9p_workstate lr_workstate; /* threadpool: work state */
|
||||||
|
enum l9p_flushstate lr_flushstate; /* flush state if flushee */
|
||||||
|
struct l9p_worker *lr_worker; /* threadpool: worker */
|
||||||
|
STAILQ_ENTRY(l9p_request) lr_worklink; /* reserved to threadpool */
|
||||||
|
|
||||||
|
/* protected by tag hash table lock */
|
||||||
|
struct l9p_request_queue lr_flushq; /* q of flushers */
|
||||||
|
STAILQ_ENTRY(l9p_request) lr_flushlink; /* link w/in flush queue */
|
||||||
|
};
|
||||||
|
|
||||||
|
/* N.B.: these dirents are variable length and for .L only */
|
||||||
|
struct l9p_dirent {
|
||||||
|
struct l9p_qid qid;
|
||||||
|
uint64_t offset;
|
||||||
|
uint8_t type;
|
||||||
|
char *name;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The 9pfs protocol has the notion of a "session", which is
|
||||||
|
* traffic between any two "Tversion" requests. All fids
|
||||||
|
* (lc_files, below) are specific to one particular session.
|
||||||
|
*
|
||||||
|
* We need a data structure per connection (client/server
|
||||||
|
* pair). This data structure lasts longer than these 9pfs
|
||||||
|
* sessions, but contains the request/response pairs and fids.
|
||||||
|
* Logically, the per-session data should be separate, but
|
||||||
|
* most of the time that would just require an extra
|
||||||
|
* indirection. Instead, a new session simply clunks all
|
||||||
|
* fids, and otherwise keeps using this same connection.
|
||||||
|
*/
|
||||||
|
struct l9p_connection {
|
||||||
|
struct l9p_server *lc_server;
|
||||||
|
struct l9p_transport lc_lt;
|
||||||
|
struct l9p_threadpool lc_tp;
|
||||||
|
enum l9p_version lc_version;
|
||||||
|
uint32_t lc_msize;
|
||||||
|
uint32_t lc_max_io_size;
|
||||||
|
struct ht lc_files;
|
||||||
|
struct ht lc_requests;
|
||||||
|
LIST_ENTRY(l9p_connection) lc_link;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct l9p_server {
|
||||||
|
struct l9p_backend *ls_backend;
|
||||||
|
enum l9p_version ls_max_version;
|
||||||
|
LIST_HEAD(, l9p_connection) ls_conns;
|
||||||
|
};
|
||||||
|
|
||||||
|
int l9p_pufcall(struct l9p_message *msg, union l9p_fcall *fcall,
|
||||||
|
enum l9p_version version);
|
||||||
|
ssize_t l9p_pustat(struct l9p_message *msg, struct l9p_stat *s,
|
||||||
|
enum l9p_version version);
|
||||||
|
uint16_t l9p_sizeof_stat(struct l9p_stat *stat, enum l9p_version version);
|
||||||
|
int l9p_pack_stat(struct l9p_message *msg, struct l9p_request *req,
|
||||||
|
struct l9p_stat *s);
|
||||||
|
ssize_t l9p_pudirent(struct l9p_message *msg, struct l9p_dirent *de);
|
||||||
|
|
||||||
|
int l9p_server_init(struct l9p_server **serverp, struct l9p_backend *backend);
|
||||||
|
|
||||||
|
int l9p_connection_init(struct l9p_server *server,
|
||||||
|
struct l9p_connection **connp);
|
||||||
|
void l9p_connection_free(struct l9p_connection *conn);
|
||||||
|
void l9p_connection_recv(struct l9p_connection *conn, const struct iovec *iov,
|
||||||
|
size_t niov, void *aux);
|
||||||
|
void l9p_connection_close(struct l9p_connection *conn);
|
||||||
|
struct l9p_fid *l9p_connection_alloc_fid(struct l9p_connection *conn,
|
||||||
|
uint32_t fid);
|
||||||
|
void l9p_connection_remove_fid(struct l9p_connection *conn,
|
||||||
|
struct l9p_fid *fid);
|
||||||
|
|
||||||
|
int l9p_dispatch_request(struct l9p_request *req);
|
||||||
|
void l9p_respond(struct l9p_request *req, bool drop, bool rmtag);
|
||||||
|
|
||||||
|
void l9p_init_msg(struct l9p_message *msg, struct l9p_request *req,
|
||||||
|
enum l9p_pack_mode mode);
|
||||||
|
void l9p_seek_iov(struct iovec *iov1, size_t niov1, struct iovec *iov2,
|
||||||
|
size_t *niov2, size_t seek);
|
||||||
|
size_t l9p_truncate_iov(struct iovec *iov, size_t niov, size_t length);
|
||||||
|
void l9p_describe_fcall(union l9p_fcall *fcall, enum l9p_version version,
|
||||||
|
struct sbuf *sb);
|
||||||
|
void l9p_freefcall(union l9p_fcall *fcall);
|
||||||
|
void l9p_freestat(struct l9p_stat *stat);
|
||||||
|
|
||||||
|
gid_t *l9p_getgrlist(const char *, gid_t, int *);
|
||||||
|
|
||||||
|
#endif /* LIB9P_LIB9P_H */
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 Jakub Klama <jceel@FreeBSD.org>
|
||||||
|
* All rights reserved
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted providing that the following conditions
|
||||||
|
* are met:
|
||||||
|
* 1. Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||||
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||||||
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
||||||
|
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef LIB9P_LIB9P_IMPL_H
|
||||||
|
#define LIB9P_LIB9P_IMPL_H
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#ifndef _KERNEL
|
||||||
|
static inline void *
|
||||||
|
l9p_malloc(size_t size)
|
||||||
|
{
|
||||||
|
void *r = malloc(size);
|
||||||
|
|
||||||
|
if (r == NULL) {
|
||||||
|
fprintf(stderr, "cannot allocate %zd bytes: out of memory\n",
|
||||||
|
size);
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (r);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void *
|
||||||
|
l9p_calloc(size_t n, size_t size)
|
||||||
|
{
|
||||||
|
void *r = calloc(n, size);
|
||||||
|
|
||||||
|
if (r == NULL) {
|
||||||
|
fprintf(stderr, "cannot allocate %zd bytes: out of memory\n",
|
||||||
|
n * size);
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (r);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void *
|
||||||
|
l9p_realloc(void *ptr, size_t newsize)
|
||||||
|
{
|
||||||
|
void *r = realloc(ptr, newsize);
|
||||||
|
|
||||||
|
if (r == NULL) {
|
||||||
|
fprintf(stderr, "cannot allocate %zd bytes: out of memory\n",
|
||||||
|
newsize);
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (r);
|
||||||
|
}
|
||||||
|
#endif /* _KERNEL */
|
||||||
|
|
||||||
|
#endif /* LIB9P_LIB9P_IMPL_H */
|
||||||
@@ -0,0 +1,247 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 Chris Torek <torek@ixsystems.com>
|
||||||
|
* All rights reserved
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted providing that the following conditions
|
||||||
|
* are met:
|
||||||
|
* 1. Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||||
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||||||
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
||||||
|
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef LIB9P_LINUX_ERRNO_H
|
||||||
|
#define LIB9P_LINUX_ERRNO_H
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Linux error numbers that are outside of the original base range
|
||||||
|
* (which ends with ERANGE).
|
||||||
|
*
|
||||||
|
* This is pretty much the same as Linux's errno.h except that the
|
||||||
|
* names are prefixed with "LINUX_", and we add _STR with the
|
||||||
|
* string name.
|
||||||
|
*
|
||||||
|
* The string expansions were obtained with a little program to
|
||||||
|
* print every strerror().
|
||||||
|
*
|
||||||
|
* Note that BSD EDEADLK is 11 and BSD EAGAIN is 35, vs
|
||||||
|
* Linux / Plan9 EAGAIN at 11. So one value in the ERANGE
|
||||||
|
* range still needs translation too.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define LINUX_EAGAIN 11
|
||||||
|
#define LINUX_EAGAIN_STR "Resource temporarily unavailable"
|
||||||
|
|
||||||
|
#define LINUX_EDEADLK 35
|
||||||
|
#define LINUX_EDEADLK_STR "Resource deadlock avoided"
|
||||||
|
#define LINUX_ENAMETOOLONG 36
|
||||||
|
#define LINUX_ENAMETOOLONG_STR "File name too long"
|
||||||
|
#define LINUX_ENOLCK 37
|
||||||
|
#define LINUX_ENOLCK_STR "No locks available"
|
||||||
|
#define LINUX_ENOSYS 38
|
||||||
|
#define LINUX_ENOSYS_STR "Function not implemented"
|
||||||
|
#define LINUX_ENOTEMPTY 39
|
||||||
|
#define LINUX_ENOTEMPTY_STR "Directory not empty"
|
||||||
|
#define LINUX_ELOOP 40
|
||||||
|
#define LINUX_ELOOP_STR "Too many levels of symbolic links"
|
||||||
|
/* 41 unused */
|
||||||
|
#define LINUX_ENOMSG 42
|
||||||
|
#define LINUX_ENOMSG_STR "No message of desired type"
|
||||||
|
#define LINUX_EIDRM 43
|
||||||
|
#define LINUX_EIDRM_STR "Identifier removed"
|
||||||
|
#define LINUX_ECHRNG 44
|
||||||
|
#define LINUX_ECHRNG_STR "Channel number out of range"
|
||||||
|
#define LINUX_EL2NSYNC 45
|
||||||
|
#define LINUX_EL2NSYNC_STR "Level 2 not synchronized"
|
||||||
|
#define LINUX_EL3HLT 46
|
||||||
|
#define LINUX_EL3HLT_STR "Level 3 halted"
|
||||||
|
#define LINUX_EL3RST 47
|
||||||
|
#define LINUX_EL3RST_STR "Level 3 reset"
|
||||||
|
#define LINUX_ELNRNG 48
|
||||||
|
#define LINUX_ELNRNG_STR "Link number out of range"
|
||||||
|
#define LINUX_EUNATCH 49
|
||||||
|
#define LINUX_EUNATCH_STR "Protocol driver not attached"
|
||||||
|
#define LINUX_ENOCSI 50
|
||||||
|
#define LINUX_ENOCSI_STR "No CSI structure available"
|
||||||
|
#define LINUX_EL2HLT 51
|
||||||
|
#define LINUX_EL2HLT_STR "Level 2 halted"
|
||||||
|
#define LINUX_EBADE 52
|
||||||
|
#define LINUX_EBADE_STR "Invalid exchange"
|
||||||
|
#define LINUX_EBADR 53
|
||||||
|
#define LINUX_EBADR_STR "Invalid request descriptor"
|
||||||
|
#define LINUX_EXFULL 54
|
||||||
|
#define LINUX_EXFULL_STR "Exchange full"
|
||||||
|
#define LINUX_ENOANO 55
|
||||||
|
#define LINUX_ENOANO_STR "No anode"
|
||||||
|
#define LINUX_EBADRQC 56
|
||||||
|
#define LINUX_EBADRQC_STR "Invalid request code"
|
||||||
|
#define LINUX_EBADSLT 57
|
||||||
|
#define LINUX_EBADSLT_STR "Invalid slot"
|
||||||
|
/* 58 unused */
|
||||||
|
#define LINUX_EBFONT 59
|
||||||
|
#define LINUX_EBFONT_STR "Bad font file format"
|
||||||
|
#define LINUX_ENOSTR 60
|
||||||
|
#define LINUX_ENOSTR_STR "Device not a stream"
|
||||||
|
#define LINUX_ENODATA 61
|
||||||
|
#define LINUX_ENODATA_STR "No data available"
|
||||||
|
#define LINUX_ETIME 62
|
||||||
|
#define LINUX_ETIME_STR "Timer expired"
|
||||||
|
#define LINUX_ENOSR 63
|
||||||
|
#define LINUX_ENOSR_STR "Out of streams resources"
|
||||||
|
#define LINUX_ENONET 64
|
||||||
|
#define LINUX_ENONET_STR "Machine is not on the network"
|
||||||
|
#define LINUX_ENOPKG 65
|
||||||
|
#define LINUX_ENOPKG_STR "Package not installed"
|
||||||
|
#define LINUX_EREMOTE 66
|
||||||
|
#define LINUX_EREMOTE_STR "Object is remote"
|
||||||
|
#define LINUX_ENOLINK 67
|
||||||
|
#define LINUX_ENOLINK_STR "Link has been severed"
|
||||||
|
#define LINUX_EADV 68
|
||||||
|
#define LINUX_EADV_STR "Advertise error"
|
||||||
|
#define LINUX_ESRMNT 69
|
||||||
|
#define LINUX_ESRMNT_STR "Srmount error"
|
||||||
|
#define LINUX_ECOMM 70
|
||||||
|
#define LINUX_ECOMM_STR "Communication error on send"
|
||||||
|
#define LINUX_EPROTO 71
|
||||||
|
#define LINUX_EPROTO_STR "Protocol error"
|
||||||
|
#define LINUX_EMULTIHOP 72
|
||||||
|
#define LINUX_EMULTIHOP_STR "Multihop attempted"
|
||||||
|
#define LINUX_EDOTDOT 73
|
||||||
|
#define LINUX_EDOTDOT_STR "RFS specific error"
|
||||||
|
#define LINUX_EBADMSG 74
|
||||||
|
#define LINUX_EBADMSG_STR "Bad message"
|
||||||
|
#define LINUX_EOVERFLOW 75
|
||||||
|
#define LINUX_EOVERFLOW_STR "Value too large for defined data type"
|
||||||
|
#define LINUX_ENOTUNIQ 76
|
||||||
|
#define LINUX_ENOTUNIQ_STR "Name not unique on network"
|
||||||
|
#define LINUX_EBADFD 77
|
||||||
|
#define LINUX_EBADFD_STR "File descriptor in bad state"
|
||||||
|
#define LINUX_EREMCHG 78
|
||||||
|
#define LINUX_EREMCHG_STR "Remote address changed"
|
||||||
|
#define LINUX_ELIBACC 79
|
||||||
|
#define LINUX_ELIBACC_STR "Can not access a needed shared library"
|
||||||
|
#define LINUX_ELIBBAD 80
|
||||||
|
#define LINUX_ELIBBAD_STR "Accessing a corrupted shared library"
|
||||||
|
#define LINUX_ELIBSCN 81
|
||||||
|
#define LINUX_ELIBSCN_STR ".lib section in a.out corrupted"
|
||||||
|
#define LINUX_ELIBMAX 82
|
||||||
|
#define LINUX_ELIBMAX_STR "Attempting to link in too many shared libraries"
|
||||||
|
#define LINUX_ELIBEXEC 83
|
||||||
|
#define LINUX_ELIBEXEC_STR "Cannot exec a shared library directly"
|
||||||
|
#define LINUX_EILSEQ 84
|
||||||
|
#define LINUX_EILSEQ_STR "Invalid or incomplete multibyte or wide character"
|
||||||
|
#define LINUX_ERESTART 85
|
||||||
|
#define LINUX_ERESTART_STR "Interrupted system call should be restarted"
|
||||||
|
#define LINUX_ESTRPIPE 86
|
||||||
|
#define LINUX_ESTRPIPE_STR "Streams pipe error"
|
||||||
|
#define LINUX_EUSERS 87
|
||||||
|
#define LINUX_EUSERS_STR "Too many users"
|
||||||
|
#define LINUX_ENOTSOCK 88
|
||||||
|
#define LINUX_ENOTSOCK_STR "Socket operation on non-socket"
|
||||||
|
#define LINUX_EDESTADDRREQ 89
|
||||||
|
#define LINUX_EDESTADDRREQ_STR "Destination address required"
|
||||||
|
#define LINUX_EMSGSIZE 90
|
||||||
|
#define LINUX_EMSGSIZE_STR "Message too long"
|
||||||
|
#define LINUX_EPROTOTYPE 91
|
||||||
|
#define LINUX_EPROTOTYPE_STR "Protocol wrong type for socket"
|
||||||
|
#define LINUX_ENOPROTOOPT 92
|
||||||
|
#define LINUX_ENOPROTOOPT_STR "Protocol not available"
|
||||||
|
#define LINUX_EPROTONOSUPPORT 93
|
||||||
|
#define LINUX_EPROTONOSUPPORT_STR "Protocol not supported"
|
||||||
|
#define LINUX_ESOCKTNOSUPPORT 94
|
||||||
|
#define LINUX_ESOCKTNOSUPPORT_STR "Socket type not supported"
|
||||||
|
#define LINUX_EOPNOTSUPP 95
|
||||||
|
#define LINUX_EOPNOTSUPP_STR "Operation not supported"
|
||||||
|
#define LINUX_EPFNOSUPPORT 96
|
||||||
|
#define LINUX_EPFNOSUPPORT_STR "Protocol family not supported"
|
||||||
|
#define LINUX_EAFNOSUPPORT 97
|
||||||
|
#define LINUX_EAFNOSUPPORT_STR "Address family not supported by protocol"
|
||||||
|
#define LINUX_EADDRINUSE 98
|
||||||
|
#define LINUX_EADDRINUSE_STR "Address already in use"
|
||||||
|
#define LINUX_EADDRNOTAVAIL 99
|
||||||
|
#define LINUX_EADDRNOTAVAIL_STR "Cannot assign requested address"
|
||||||
|
#define LINUX_ENETDOWN 100
|
||||||
|
#define LINUX_ENETDOWN_STR "Network is down"
|
||||||
|
#define LINUX_ENETUNREACH 101
|
||||||
|
#define LINUX_ENETUNREACH_STR "Network is unreachable"
|
||||||
|
#define LINUX_ENETRESET 102
|
||||||
|
#define LINUX_ENETRESET_STR "Network dropped connection on reset"
|
||||||
|
#define LINUX_ECONNABORTED 103
|
||||||
|
#define LINUX_ECONNABORTED_STR "Software caused connection abort"
|
||||||
|
#define LINUX_ECONNRESET 104
|
||||||
|
#define LINUX_ECONNRESET_STR "Connection reset by peer"
|
||||||
|
#define LINUX_ENOBUFS 105
|
||||||
|
#define LINUX_ENOBUFS_STR "No buffer space available"
|
||||||
|
#define LINUX_EISCONN 106
|
||||||
|
#define LINUX_EISCONN_STR "Transport endpoint is already connected"
|
||||||
|
#define LINUX_ENOTCONN 107
|
||||||
|
#define LINUX_ENOTCONN_STR "Transport endpoint is not connected"
|
||||||
|
#define LINUX_ESHUTDOWN 108
|
||||||
|
#define LINUX_ESHUTDOWN_STR "Cannot send after transport endpoint shutdown"
|
||||||
|
#define LINUX_ETOOMANYREFS 109
|
||||||
|
#define LINUX_ETOOMANYREFS_STR "Too many references: cannot splice"
|
||||||
|
#define LINUX_ETIMEDOUT 110
|
||||||
|
#define LINUX_ETIMEDOUT_STR "Connection timed out"
|
||||||
|
#define LINUX_ECONNREFUSED 111
|
||||||
|
#define LINUX_ECONNREFUSED_STR "Connection refused"
|
||||||
|
#define LINUX_EHOSTDOWN 112
|
||||||
|
#define LINUX_EHOSTDOWN_STR "Host is down"
|
||||||
|
#define LINUX_EHOSTUNREACH 113
|
||||||
|
#define LINUX_EHOSTUNREACH_STR "No route to host"
|
||||||
|
#define LINUX_EALREADY 114
|
||||||
|
#define LINUX_EALREADY_STR "Operation already in progress"
|
||||||
|
#define LINUX_EINPROGRESS 115
|
||||||
|
#define LINUX_EINPROGRESS_STR "Operation now in progress"
|
||||||
|
#define LINUX_ESTALE 116
|
||||||
|
#define LINUX_ESTALE_STR "Stale file handle"
|
||||||
|
#define LINUX_EUCLEAN 117
|
||||||
|
#define LINUX_EUCLEAN_STR "Structure needs cleaning"
|
||||||
|
#define LINUX_ENOTNAM 118
|
||||||
|
#define LINUX_ENOTNAM_STR "Not a XENIX named type file"
|
||||||
|
#define LINUX_ENAVAIL 119
|
||||||
|
#define LINUX_ENAVAIL_STR "No XENIX semaphores available"
|
||||||
|
#define LINUX_EISNAM 120
|
||||||
|
#define LINUX_EISNAM_STR "Is a named type file"
|
||||||
|
#define LINUX_EREMOTEIO 121
|
||||||
|
#define LINUX_EREMOTEIO_STR "Remote I/O error"
|
||||||
|
#define LINUX_EDQUOT 122
|
||||||
|
#define LINUX_EDQUOT_STR "Quota exceeded"
|
||||||
|
#define LINUX_ENOMEDIUM 123
|
||||||
|
#define LINUX_ENOMEDIUM_STR "No medium found"
|
||||||
|
#define LINUX_EMEDIUMTYPE 124
|
||||||
|
#define LINUX_EMEDIUMTYPE_STR "Wrong medium type"
|
||||||
|
#define LINUX_ECANCELED 125
|
||||||
|
#define LINUX_ECANCELED_STR "Operation canceled"
|
||||||
|
#define LINUX_ENOKEY 126
|
||||||
|
#define LINUX_ENOKEY_STR "Required key not available"
|
||||||
|
#define LINUX_EKEYEXPIRED 127
|
||||||
|
#define LINUX_EKEYEXPIRED_STR "Key has expired"
|
||||||
|
#define LINUX_EKEYREVOKED 128
|
||||||
|
#define LINUX_EKEYREVOKED_STR "Key has been revoked"
|
||||||
|
#define LINUX_EKEYREJECTED 129
|
||||||
|
#define LINUX_EKEYREJECTED_STR "Key was rejected by service"
|
||||||
|
#define LINUX_EOWNERDEAD 130
|
||||||
|
#define LINUX_EOWNERDEAD_STR "Owner died"
|
||||||
|
#define LINUX_ENOTRECOVERABLE 131
|
||||||
|
#define LINUX_ENOTRECOVERABLE_STR "State not recoverable"
|
||||||
|
#define LINUX_ERFKILL 132
|
||||||
|
#define LINUX_ERFKILL_STR "Operation not possible due to RF-kill"
|
||||||
|
#define LINUX_EHWPOISON 133
|
||||||
|
#define LINUX_EHWPOISON_STR "Memory page has hardware error"
|
||||||
|
|
||||||
|
#endif /* LIB9P_LINUX_ERRNO_H */
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 Jakub Klama <jceel@FreeBSD.org>
|
||||||
|
* All rights reserved
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted providing that the following conditions
|
||||||
|
* are met:
|
||||||
|
* 1. Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||||
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||||||
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
||||||
|
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "log.h"
|
||||||
|
|
||||||
|
static const char *l9p_log_level_names[] = {
|
||||||
|
"DEBUG",
|
||||||
|
"INFO",
|
||||||
|
"WARN",
|
||||||
|
"ERROR"
|
||||||
|
};
|
||||||
|
|
||||||
|
void
|
||||||
|
l9p_logf(enum l9p_log_level level, const char *func, const char *fmt, ...)
|
||||||
|
{
|
||||||
|
const char *dest = NULL;
|
||||||
|
static FILE *stream = NULL;
|
||||||
|
va_list ap;
|
||||||
|
|
||||||
|
if (stream == NULL) {
|
||||||
|
dest = getenv("LIB9P_LOGGING");
|
||||||
|
if (dest == NULL)
|
||||||
|
return;
|
||||||
|
else if (!strcmp(dest, "stderr"))
|
||||||
|
stream = stderr;
|
||||||
|
else {
|
||||||
|
stream = fopen(dest, "a");
|
||||||
|
if (stream == NULL)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
va_start(ap, fmt);
|
||||||
|
fprintf(stream, "[%s]\t %s: ", l9p_log_level_names[level], func);
|
||||||
|
vfprintf(stream, fmt, ap);
|
||||||
|
fprintf(stream, "\n");
|
||||||
|
fflush(stream);
|
||||||
|
va_end(ap);
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 Jakub Klama <jceel@FreeBSD.org>
|
||||||
|
* All rights reserved
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted providing that the following conditions
|
||||||
|
* are met:
|
||||||
|
* 1. Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||||
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||||||
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
||||||
|
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef LIB9P_LOG_H
|
||||||
|
#define LIB9P_LOG_H
|
||||||
|
|
||||||
|
enum l9p_log_level {
|
||||||
|
L9P_DEBUG,
|
||||||
|
L9P_INFO,
|
||||||
|
L9P_WARNING,
|
||||||
|
L9P_ERROR
|
||||||
|
};
|
||||||
|
|
||||||
|
void l9p_logf(enum l9p_log_level level, const char *func, const char *fmt, ...);
|
||||||
|
|
||||||
|
#if defined(L9P_DEBUG)
|
||||||
|
#define L9P_LOG(level, fmt, ...) l9p_logf(level, __func__, fmt, ##__VA_ARGS__)
|
||||||
|
#else
|
||||||
|
#define L9P_LOG(level, fmt, ...)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* LIB9P_LOG_H */
|
||||||
@@ -0,0 +1,993 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 Jakub Klama <jceel@FreeBSD.org>
|
||||||
|
* All rights reserved
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted providing that the following conditions
|
||||||
|
* are met:
|
||||||
|
* 1. Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||||
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||||||
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
||||||
|
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Based on libixp code: ©2007-2010 Kris Maglione <maglione.k at Gmail>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/param.h>
|
||||||
|
#ifdef __APPLE__
|
||||||
|
# include "apple_endian.h"
|
||||||
|
#else
|
||||||
|
# include <sys/endian.h>
|
||||||
|
#endif
|
||||||
|
#include <sys/uio.h>
|
||||||
|
#include "lib9p.h"
|
||||||
|
#include "lib9p_impl.h"
|
||||||
|
#include "log.h"
|
||||||
|
|
||||||
|
#define N(ary) (sizeof(ary) / sizeof(*ary))
|
||||||
|
#define STRING_SIZE(s) (L9P_WORD + (s != NULL ? (uint16_t)strlen(s) : 0))
|
||||||
|
#define QID_SIZE (L9P_BYTE + L9P_DWORD + L9P_QWORD)
|
||||||
|
|
||||||
|
static ssize_t l9p_iov_io(struct l9p_message *, void *, size_t);
|
||||||
|
static inline ssize_t l9p_pu8(struct l9p_message *, uint8_t *);
|
||||||
|
static inline ssize_t l9p_pu16(struct l9p_message *, uint16_t *);
|
||||||
|
static inline ssize_t l9p_pu32(struct l9p_message *, uint32_t *);
|
||||||
|
static inline ssize_t l9p_pu64(struct l9p_message *, uint64_t *);
|
||||||
|
static ssize_t l9p_pustring(struct l9p_message *, char **s);
|
||||||
|
static ssize_t l9p_pustrings(struct l9p_message *, uint16_t *, char **, size_t);
|
||||||
|
static ssize_t l9p_puqid(struct l9p_message *, struct l9p_qid *);
|
||||||
|
static ssize_t l9p_puqids(struct l9p_message *, uint16_t *, struct l9p_qid *q);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Transfer data from incoming request, or to outgoing response,
|
||||||
|
* using msg to track position and direction within request/response.
|
||||||
|
*
|
||||||
|
* Returns the number of bytes actually transferred (which is always
|
||||||
|
* just len itself, converted to signed), or -1 if we ran out of space.
|
||||||
|
*
|
||||||
|
* Note that if we return -1, subsequent l9p_iov_io() calls with
|
||||||
|
* the same (and not-reset) msg and len > 0 will also return -1.
|
||||||
|
* This means most users can just check the *last* call for failure.
|
||||||
|
*/
|
||||||
|
static ssize_t
|
||||||
|
l9p_iov_io(struct l9p_message *msg, void *buffer, size_t len)
|
||||||
|
{
|
||||||
|
size_t done = 0;
|
||||||
|
size_t left = len;
|
||||||
|
|
||||||
|
assert(msg != NULL);
|
||||||
|
|
||||||
|
if (len == 0)
|
||||||
|
return (0);
|
||||||
|
|
||||||
|
if (msg->lm_cursor_iov >= msg->lm_niov)
|
||||||
|
return (-1);
|
||||||
|
|
||||||
|
assert(buffer != NULL);
|
||||||
|
|
||||||
|
while (left > 0) {
|
||||||
|
size_t idx = msg->lm_cursor_iov;
|
||||||
|
size_t space = msg->lm_iov[idx].iov_len - msg->lm_cursor_offset;
|
||||||
|
size_t towrite = MIN(space, left);
|
||||||
|
|
||||||
|
if (msg->lm_mode == L9P_PACK) {
|
||||||
|
memcpy((char *)msg->lm_iov[idx].iov_base +
|
||||||
|
msg->lm_cursor_offset, (char *)buffer + done,
|
||||||
|
towrite);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg->lm_mode == L9P_UNPACK) {
|
||||||
|
memcpy((char *)buffer + done,
|
||||||
|
(char *)msg->lm_iov[idx].iov_base +
|
||||||
|
msg->lm_cursor_offset, towrite);
|
||||||
|
}
|
||||||
|
|
||||||
|
msg->lm_cursor_offset += towrite;
|
||||||
|
|
||||||
|
done += towrite;
|
||||||
|
left -= towrite;
|
||||||
|
|
||||||
|
if (space - towrite == 0) {
|
||||||
|
/* Advance to next iov */
|
||||||
|
msg->lm_cursor_iov++;
|
||||||
|
msg->lm_cursor_offset = 0;
|
||||||
|
|
||||||
|
if (msg->lm_cursor_iov >= msg->lm_niov && left > 0)
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
msg->lm_size += done;
|
||||||
|
return ((ssize_t)done);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Pack or unpack a byte (8 bits).
|
||||||
|
*
|
||||||
|
* Returns 1 (success, 1 byte) or -1 (error).
|
||||||
|
*/
|
||||||
|
static inline ssize_t
|
||||||
|
l9p_pu8(struct l9p_message *msg, uint8_t *val)
|
||||||
|
{
|
||||||
|
|
||||||
|
return (l9p_iov_io(msg, val, sizeof (uint8_t)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Pack or unpack 16-bit value.
|
||||||
|
*
|
||||||
|
* Returns 2 or -1.
|
||||||
|
*/
|
||||||
|
static inline ssize_t
|
||||||
|
l9p_pu16(struct l9p_message *msg, uint16_t *val)
|
||||||
|
{
|
||||||
|
#if _BYTE_ORDER != _LITTLE_ENDIAN
|
||||||
|
/*
|
||||||
|
* The ifdefs are annoying, but there is no need
|
||||||
|
* for all of this foolery on little-endian hosts,
|
||||||
|
* and I don't expect the compiler to optimize it
|
||||||
|
* all away.
|
||||||
|
*/
|
||||||
|
uint16_t copy;
|
||||||
|
ssize_t ret;
|
||||||
|
|
||||||
|
if (msg->lm_mode == L9P_PACK) {
|
||||||
|
copy = htole16(*val);
|
||||||
|
return (l9p_iov_io(msg, ©, sizeof (uint16_t)));
|
||||||
|
}
|
||||||
|
ret = l9p_iov_io(msg, val, sizeof (uint16_t));
|
||||||
|
*val = le16toh(*val);
|
||||||
|
return (ret);
|
||||||
|
#else
|
||||||
|
return (l9p_iov_io(msg, val, sizeof (uint16_t)));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Pack or unpack 32-bit value.
|
||||||
|
*
|
||||||
|
* Returns 4 or -1.
|
||||||
|
*/
|
||||||
|
static inline ssize_t
|
||||||
|
l9p_pu32(struct l9p_message *msg, uint32_t *val)
|
||||||
|
{
|
||||||
|
#if _BYTE_ORDER != _LITTLE_ENDIAN
|
||||||
|
uint32_t copy;
|
||||||
|
ssize_t ret;
|
||||||
|
|
||||||
|
if (msg->lm_mode == L9P_PACK) {
|
||||||
|
copy = htole32(*val);
|
||||||
|
return (l9p_iov_io(msg, ©, sizeof (uint32_t)));
|
||||||
|
}
|
||||||
|
ret = l9p_iov_io(msg, val, sizeof (uint32_t));
|
||||||
|
*val = le32toh(*val);
|
||||||
|
return (ret);
|
||||||
|
#else
|
||||||
|
return (l9p_iov_io(msg, val, sizeof (uint32_t)));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Pack or unpack 64-bit value.
|
||||||
|
*
|
||||||
|
* Returns 8 or -1.
|
||||||
|
*/
|
||||||
|
static inline ssize_t
|
||||||
|
l9p_pu64(struct l9p_message *msg, uint64_t *val)
|
||||||
|
{
|
||||||
|
#if _BYTE_ORDER != _LITTLE_ENDIAN
|
||||||
|
uint64_t copy;
|
||||||
|
ssize_t ret;
|
||||||
|
|
||||||
|
if (msg->lm_mode == L9P_PACK) {
|
||||||
|
copy = htole64(*val);
|
||||||
|
return (l9p_iov_io(msg, ©, sizeof (uint64_t)));
|
||||||
|
}
|
||||||
|
ret = l9p_iov_io(msg, val, sizeof (uint32_t));
|
||||||
|
*val = le64toh(*val);
|
||||||
|
return (ret);
|
||||||
|
#else
|
||||||
|
return (l9p_iov_io(msg, val, sizeof (uint64_t)));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Pack or unpack a string, encoded as 2-byte length followed by
|
||||||
|
* string bytes. The returned length is 2 greater than the
|
||||||
|
* length of the string itself.
|
||||||
|
*
|
||||||
|
* When unpacking, this allocates a new string (NUL-terminated).
|
||||||
|
*
|
||||||
|
* Return -1 on error (not space, or failed to allocate string,
|
||||||
|
* or illegal string).
|
||||||
|
*
|
||||||
|
* Note that pustring (and hence pustrings) can return an error
|
||||||
|
* even when l9p_iov_io succeeds.
|
||||||
|
*/
|
||||||
|
static ssize_t
|
||||||
|
l9p_pustring(struct l9p_message *msg, char **s)
|
||||||
|
{
|
||||||
|
uint16_t len;
|
||||||
|
|
||||||
|
if (msg->lm_mode == L9P_PACK)
|
||||||
|
len = *s != NULL ? (uint16_t)strlen(*s) : 0;
|
||||||
|
|
||||||
|
if (l9p_pu16(msg, &len) < 0)
|
||||||
|
return (-1);
|
||||||
|
|
||||||
|
if (msg->lm_mode == L9P_UNPACK) {
|
||||||
|
*s = l9p_calloc(1, len + 1);
|
||||||
|
if (*s == NULL)
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (l9p_iov_io(msg, *s, len) < 0)
|
||||||
|
return (-1);
|
||||||
|
|
||||||
|
if (msg->lm_mode == L9P_UNPACK) {
|
||||||
|
/*
|
||||||
|
* An embedded NUL byte in a string is illegal.
|
||||||
|
* We don't necessarily have to check (we'll just
|
||||||
|
* treat it as a shorter string), but checking
|
||||||
|
* seems like a good idea.
|
||||||
|
*/
|
||||||
|
if (memchr(*s, '\0', len) != NULL)
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ((ssize_t)len + 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Pack or unpack a number (*num) of strings (but at most max of
|
||||||
|
* them).
|
||||||
|
*
|
||||||
|
* Returns the number of bytes transferred, including the packed
|
||||||
|
* number of strings. If packing and the packed number of strings
|
||||||
|
* was reduced, the original *num value is unchanged; only the
|
||||||
|
* wire-format number is reduced. If unpacking and the input
|
||||||
|
* number of strings exceeds the max, the incoming *num is reduced
|
||||||
|
* to lim, if needed. (NOTE ASYMMETRY HERE!)
|
||||||
|
*
|
||||||
|
* Returns -1 on error.
|
||||||
|
*/
|
||||||
|
static ssize_t
|
||||||
|
l9p_pustrings(struct l9p_message *msg, uint16_t *num, char **strings,
|
||||||
|
size_t max)
|
||||||
|
{
|
||||||
|
size_t i, lim;
|
||||||
|
ssize_t r, ret;
|
||||||
|
uint16_t adjusted;
|
||||||
|
|
||||||
|
if (msg->lm_mode == L9P_PACK) {
|
||||||
|
lim = *num;
|
||||||
|
if (lim > max)
|
||||||
|
lim = max;
|
||||||
|
adjusted = (uint16_t)lim;
|
||||||
|
r = l9p_pu16(msg, &adjusted);
|
||||||
|
} else {
|
||||||
|
r = l9p_pu16(msg, num);
|
||||||
|
lim = *num;
|
||||||
|
if (lim > max)
|
||||||
|
*num = (uint16_t)(lim = max);
|
||||||
|
}
|
||||||
|
if (r < 0)
|
||||||
|
return (-1);
|
||||||
|
|
||||||
|
for (i = 0; i < lim; i++) {
|
||||||
|
ret = l9p_pustring(msg, &strings[i]);
|
||||||
|
if (ret < 1)
|
||||||
|
return (-1);
|
||||||
|
|
||||||
|
r += ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (r);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Pack or unpack a qid.
|
||||||
|
*
|
||||||
|
* Returns 13 (success) or -1 (error).
|
||||||
|
*/
|
||||||
|
static ssize_t
|
||||||
|
l9p_puqid(struct l9p_message *msg, struct l9p_qid *qid)
|
||||||
|
{
|
||||||
|
ssize_t r;
|
||||||
|
uint8_t type;
|
||||||
|
|
||||||
|
if (msg->lm_mode == L9P_PACK) {
|
||||||
|
type = qid->type;
|
||||||
|
r = l9p_pu8(msg, &type);
|
||||||
|
} else {
|
||||||
|
r = l9p_pu8(msg, &type);
|
||||||
|
qid->type = type;
|
||||||
|
}
|
||||||
|
if (r > 0)
|
||||||
|
r = l9p_pu32(msg, &qid->version);
|
||||||
|
if (r > 0)
|
||||||
|
r = l9p_pu64(msg, &qid->path);
|
||||||
|
|
||||||
|
return (r > 0 ? QID_SIZE : r);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Pack or unpack *num qids.
|
||||||
|
*
|
||||||
|
* Returns 2 + 13 * *num (after possibly setting *num), or -1 on error.
|
||||||
|
*/
|
||||||
|
static ssize_t
|
||||||
|
l9p_puqids(struct l9p_message *msg, uint16_t *num, struct l9p_qid *qids)
|
||||||
|
{
|
||||||
|
size_t i, lim;
|
||||||
|
ssize_t ret, r;
|
||||||
|
|
||||||
|
r = l9p_pu16(msg, num);
|
||||||
|
if (r > 0) {
|
||||||
|
for (i = 0, lim = *num; i < lim; i++) {
|
||||||
|
ret = l9p_puqid(msg, &qids[i]);
|
||||||
|
if (ret < 0)
|
||||||
|
return (-1);
|
||||||
|
r += ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (r);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Pack or unpack a l9p_stat.
|
||||||
|
*
|
||||||
|
* These have variable size, and the size further depends on
|
||||||
|
* the protocol version.
|
||||||
|
*
|
||||||
|
* Returns the number of bytes packed/unpacked, or -1 on error.
|
||||||
|
*/
|
||||||
|
ssize_t
|
||||||
|
l9p_pustat(struct l9p_message *msg, struct l9p_stat *stat,
|
||||||
|
enum l9p_version version)
|
||||||
|
{
|
||||||
|
ssize_t r = 0;
|
||||||
|
uint16_t size;
|
||||||
|
|
||||||
|
/* The on-wire size field excludes the size of the size field. */
|
||||||
|
if (msg->lm_mode == L9P_PACK)
|
||||||
|
size = l9p_sizeof_stat(stat, version) - 2;
|
||||||
|
|
||||||
|
r += l9p_pu16(msg, &size);
|
||||||
|
r += l9p_pu16(msg, &stat->type);
|
||||||
|
r += l9p_pu32(msg, &stat->dev);
|
||||||
|
r += l9p_puqid(msg, &stat->qid);
|
||||||
|
r += l9p_pu32(msg, &stat->mode);
|
||||||
|
r += l9p_pu32(msg, &stat->atime);
|
||||||
|
r += l9p_pu32(msg, &stat->mtime);
|
||||||
|
r += l9p_pu64(msg, &stat->length);
|
||||||
|
r += l9p_pustring(msg, &stat->name);
|
||||||
|
r += l9p_pustring(msg, &stat->uid);
|
||||||
|
r += l9p_pustring(msg, &stat->gid);
|
||||||
|
r += l9p_pustring(msg, &stat->muid);
|
||||||
|
|
||||||
|
if (version >= L9P_2000U) {
|
||||||
|
r += l9p_pustring(msg, &stat->extension);
|
||||||
|
r += l9p_pu32(msg, &stat->n_uid);
|
||||||
|
r += l9p_pu32(msg, &stat->n_gid);
|
||||||
|
r += l9p_pu32(msg, &stat->n_muid);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (r < size + 2)
|
||||||
|
return (-1);
|
||||||
|
|
||||||
|
return (r);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Pack or unpack a variable-length dirent.
|
||||||
|
*
|
||||||
|
* If unpacking, the name field is malloc()ed and the caller must
|
||||||
|
* free it.
|
||||||
|
*
|
||||||
|
* Returns the wire-format length, or -1 if we ran out of room.
|
||||||
|
*/
|
||||||
|
ssize_t
|
||||||
|
l9p_pudirent(struct l9p_message *msg, struct l9p_dirent *de)
|
||||||
|
{
|
||||||
|
ssize_t r, s;
|
||||||
|
|
||||||
|
r = l9p_puqid(msg, &de->qid);
|
||||||
|
r += l9p_pu64(msg, &de->offset);
|
||||||
|
r += l9p_pu8(msg, &de->type);
|
||||||
|
s = l9p_pustring(msg, &de->name);
|
||||||
|
if (r < QID_SIZE + 8 + 1 || s < 0)
|
||||||
|
return (-1);
|
||||||
|
return (r + s);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Pack or unpack a request or response (fcall).
|
||||||
|
*
|
||||||
|
* Returns 0 on success, -1 on error. (It's up to the caller
|
||||||
|
* to call l9p_freefcall on our failure.)
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
l9p_pufcall(struct l9p_message *msg, union l9p_fcall *fcall,
|
||||||
|
enum l9p_version version)
|
||||||
|
{
|
||||||
|
uint32_t length = 0;
|
||||||
|
ssize_t r;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get overall length, type, and tag, which should appear
|
||||||
|
* in all messages. If not even that works, abort immediately.
|
||||||
|
*/
|
||||||
|
l9p_pu32(msg, &length);
|
||||||
|
l9p_pu8(msg, &fcall->hdr.type);
|
||||||
|
r = l9p_pu16(msg, &fcall->hdr.tag);
|
||||||
|
if (r < 0)
|
||||||
|
return (-1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Decode remainder of message. When unpacking, this may
|
||||||
|
* allocate memory, even if we fail during the decode.
|
||||||
|
* Note that the initial fcall is zeroed out, though, so
|
||||||
|
* we can just freefcall() to release whatever might have
|
||||||
|
* gotten allocated, if the unpack fails due to a short
|
||||||
|
* packet.
|
||||||
|
*/
|
||||||
|
switch (fcall->hdr.type) {
|
||||||
|
case L9P_TVERSION:
|
||||||
|
case L9P_RVERSION:
|
||||||
|
l9p_pu32(msg, &fcall->version.msize);
|
||||||
|
r = l9p_pustring(msg, &fcall->version.version);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case L9P_TAUTH:
|
||||||
|
l9p_pu32(msg, &fcall->tauth.afid);
|
||||||
|
r = l9p_pustring(msg, &fcall->tauth.uname);
|
||||||
|
if (r < 0)
|
||||||
|
break;
|
||||||
|
r = l9p_pustring(msg, &fcall->tauth.aname);
|
||||||
|
if (r < 0)
|
||||||
|
break;
|
||||||
|
if (version >= L9P_2000U)
|
||||||
|
r = l9p_pu32(msg, &fcall->tauth.n_uname);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case L9P_RAUTH:
|
||||||
|
r = l9p_puqid(msg, &fcall->rauth.aqid);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case L9P_TATTACH:
|
||||||
|
l9p_pu32(msg, &fcall->hdr.fid);
|
||||||
|
l9p_pu32(msg, &fcall->tattach.afid);
|
||||||
|
r = l9p_pustring(msg, &fcall->tattach.uname);
|
||||||
|
if (r < 0)
|
||||||
|
break;
|
||||||
|
r = l9p_pustring(msg, &fcall->tattach.aname);
|
||||||
|
if (r < 0)
|
||||||
|
break;
|
||||||
|
if (version >= L9P_2000U)
|
||||||
|
r = l9p_pu32(msg, &fcall->tattach.n_uname);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case L9P_RATTACH:
|
||||||
|
r = l9p_puqid(msg, &fcall->rattach.qid);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case L9P_RERROR:
|
||||||
|
r = l9p_pustring(msg, &fcall->error.ename);
|
||||||
|
if (r < 0)
|
||||||
|
break;
|
||||||
|
if (version >= L9P_2000U)
|
||||||
|
r = l9p_pu32(msg, &fcall->error.errnum);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case L9P_RLERROR:
|
||||||
|
r = l9p_pu32(msg, &fcall->error.errnum);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case L9P_TFLUSH:
|
||||||
|
r = l9p_pu16(msg, &fcall->tflush.oldtag);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case L9P_RFLUSH:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case L9P_TWALK:
|
||||||
|
l9p_pu32(msg, &fcall->hdr.fid);
|
||||||
|
l9p_pu32(msg, &fcall->twalk.newfid);
|
||||||
|
r = l9p_pustrings(msg, &fcall->twalk.nwname,
|
||||||
|
fcall->twalk.wname, N(fcall->twalk.wname));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case L9P_RWALK:
|
||||||
|
r = l9p_puqids(msg, &fcall->rwalk.nwqid, fcall->rwalk.wqid);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case L9P_TOPEN:
|
||||||
|
l9p_pu32(msg, &fcall->hdr.fid);
|
||||||
|
r = l9p_pu8(msg, &fcall->topen.mode);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case L9P_ROPEN:
|
||||||
|
l9p_puqid(msg, &fcall->ropen.qid);
|
||||||
|
r = l9p_pu32(msg, &fcall->ropen.iounit);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case L9P_TCREATE:
|
||||||
|
l9p_pu32(msg, &fcall->hdr.fid);
|
||||||
|
r = l9p_pustring(msg, &fcall->tcreate.name);
|
||||||
|
if (r < 0)
|
||||||
|
break;
|
||||||
|
l9p_pu32(msg, &fcall->tcreate.perm);
|
||||||
|
r = l9p_pu8(msg, &fcall->tcreate.mode);
|
||||||
|
if (version >= L9P_2000U)
|
||||||
|
r = l9p_pustring(msg, &fcall->tcreate.extension);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case L9P_RCREATE:
|
||||||
|
l9p_puqid(msg, &fcall->rcreate.qid);
|
||||||
|
r = l9p_pu32(msg, &fcall->rcreate.iounit);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case L9P_TREAD:
|
||||||
|
case L9P_TREADDIR:
|
||||||
|
l9p_pu32(msg, &fcall->hdr.fid);
|
||||||
|
l9p_pu64(msg, &fcall->io.offset);
|
||||||
|
r = l9p_pu32(msg, &fcall->io.count);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case L9P_RREAD:
|
||||||
|
case L9P_RREADDIR:
|
||||||
|
r = l9p_pu32(msg, &fcall->io.count);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case L9P_TWRITE:
|
||||||
|
l9p_pu32(msg, &fcall->hdr.fid);
|
||||||
|
l9p_pu64(msg, &fcall->io.offset);
|
||||||
|
r = l9p_pu32(msg, &fcall->io.count);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case L9P_RWRITE:
|
||||||
|
r = l9p_pu32(msg, &fcall->io.count);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case L9P_TCLUNK:
|
||||||
|
case L9P_TSTAT:
|
||||||
|
case L9P_TREMOVE:
|
||||||
|
case L9P_TSTATFS:
|
||||||
|
r = l9p_pu32(msg, &fcall->hdr.fid);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case L9P_RCLUNK:
|
||||||
|
case L9P_RREMOVE:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case L9P_RSTAT:
|
||||||
|
{
|
||||||
|
uint16_t size = l9p_sizeof_stat(&fcall->rstat.stat,
|
||||||
|
version);
|
||||||
|
l9p_pu16(msg, &size);
|
||||||
|
r = l9p_pustat(msg, &fcall->rstat.stat, version);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case L9P_TWSTAT:
|
||||||
|
{
|
||||||
|
uint16_t size;
|
||||||
|
l9p_pu32(msg, &fcall->hdr.fid);
|
||||||
|
l9p_pu16(msg, &size);
|
||||||
|
r = l9p_pustat(msg, &fcall->twstat.stat, version);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case L9P_RWSTAT:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case L9P_RSTATFS:
|
||||||
|
l9p_pu32(msg, &fcall->rstatfs.statfs.type);
|
||||||
|
l9p_pu32(msg, &fcall->rstatfs.statfs.bsize);
|
||||||
|
l9p_pu64(msg, &fcall->rstatfs.statfs.blocks);
|
||||||
|
l9p_pu64(msg, &fcall->rstatfs.statfs.bfree);
|
||||||
|
l9p_pu64(msg, &fcall->rstatfs.statfs.bavail);
|
||||||
|
l9p_pu64(msg, &fcall->rstatfs.statfs.files);
|
||||||
|
l9p_pu64(msg, &fcall->rstatfs.statfs.ffree);
|
||||||
|
l9p_pu64(msg, &fcall->rstatfs.statfs.fsid);
|
||||||
|
r = l9p_pu32(msg, &fcall->rstatfs.statfs.namelen);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case L9P_TLOPEN:
|
||||||
|
l9p_pu32(msg, &fcall->hdr.fid);
|
||||||
|
r = l9p_pu32(msg, &fcall->tlopen.flags);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case L9P_RLOPEN:
|
||||||
|
l9p_puqid(msg, &fcall->rlopen.qid);
|
||||||
|
r = l9p_pu32(msg, &fcall->rlopen.iounit);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case L9P_TLCREATE:
|
||||||
|
l9p_pu32(msg, &fcall->hdr.fid);
|
||||||
|
r = l9p_pustring(msg, &fcall->tlcreate.name);
|
||||||
|
if (r < 0)
|
||||||
|
break;
|
||||||
|
l9p_pu32(msg, &fcall->tlcreate.flags);
|
||||||
|
l9p_pu32(msg, &fcall->tlcreate.mode);
|
||||||
|
r = l9p_pu32(msg, &fcall->tlcreate.gid);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case L9P_RLCREATE:
|
||||||
|
l9p_puqid(msg, &fcall->rlcreate.qid);
|
||||||
|
r = l9p_pu32(msg, &fcall->rlcreate.iounit);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case L9P_TSYMLINK:
|
||||||
|
l9p_pu32(msg, &fcall->hdr.fid);
|
||||||
|
r = l9p_pustring(msg, &fcall->tsymlink.name);
|
||||||
|
if (r < 0)
|
||||||
|
break;
|
||||||
|
r = l9p_pustring(msg, &fcall->tsymlink.symtgt);
|
||||||
|
if (r < 0)
|
||||||
|
break;
|
||||||
|
r = l9p_pu32(msg, &fcall->tlcreate.gid);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case L9P_RSYMLINK:
|
||||||
|
r = l9p_puqid(msg, &fcall->rsymlink.qid);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case L9P_TMKNOD:
|
||||||
|
l9p_pu32(msg, &fcall->hdr.fid);
|
||||||
|
r = l9p_pustring(msg, &fcall->tmknod.name);
|
||||||
|
if (r < 0)
|
||||||
|
break;
|
||||||
|
l9p_pu32(msg, &fcall->tmknod.mode);
|
||||||
|
l9p_pu32(msg, &fcall->tmknod.major);
|
||||||
|
l9p_pu32(msg, &fcall->tmknod.minor);
|
||||||
|
r = l9p_pu32(msg, &fcall->tmknod.gid);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case L9P_RMKNOD:
|
||||||
|
r = l9p_puqid(msg, &fcall->rmknod.qid);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case L9P_TRENAME:
|
||||||
|
l9p_pu32(msg, &fcall->hdr.fid);
|
||||||
|
l9p_pu32(msg, &fcall->trename.dfid);
|
||||||
|
r = l9p_pustring(msg, &fcall->trename.name);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case L9P_RRENAME:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case L9P_TREADLINK:
|
||||||
|
r = l9p_pu32(msg, &fcall->hdr.fid);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case L9P_RREADLINK:
|
||||||
|
r = l9p_pustring(msg, &fcall->rreadlink.target);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case L9P_TGETATTR:
|
||||||
|
l9p_pu32(msg, &fcall->hdr.fid);
|
||||||
|
r = l9p_pu64(msg, &fcall->tgetattr.request_mask);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case L9P_RGETATTR:
|
||||||
|
l9p_pu64(msg, &fcall->rgetattr.valid);
|
||||||
|
l9p_puqid(msg, &fcall->rgetattr.qid);
|
||||||
|
l9p_pu32(msg, &fcall->rgetattr.mode);
|
||||||
|
l9p_pu32(msg, &fcall->rgetattr.uid);
|
||||||
|
l9p_pu32(msg, &fcall->rgetattr.gid);
|
||||||
|
l9p_pu64(msg, &fcall->rgetattr.nlink);
|
||||||
|
l9p_pu64(msg, &fcall->rgetattr.rdev);
|
||||||
|
l9p_pu64(msg, &fcall->rgetattr.size);
|
||||||
|
l9p_pu64(msg, &fcall->rgetattr.blksize);
|
||||||
|
l9p_pu64(msg, &fcall->rgetattr.blocks);
|
||||||
|
l9p_pu64(msg, &fcall->rgetattr.atime_sec);
|
||||||
|
l9p_pu64(msg, &fcall->rgetattr.atime_nsec);
|
||||||
|
l9p_pu64(msg, &fcall->rgetattr.mtime_sec);
|
||||||
|
l9p_pu64(msg, &fcall->rgetattr.mtime_nsec);
|
||||||
|
l9p_pu64(msg, &fcall->rgetattr.ctime_sec);
|
||||||
|
l9p_pu64(msg, &fcall->rgetattr.ctime_nsec);
|
||||||
|
l9p_pu64(msg, &fcall->rgetattr.btime_sec);
|
||||||
|
l9p_pu64(msg, &fcall->rgetattr.btime_nsec);
|
||||||
|
l9p_pu64(msg, &fcall->rgetattr.gen);
|
||||||
|
r = l9p_pu64(msg, &fcall->rgetattr.data_version);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case L9P_TSETATTR:
|
||||||
|
l9p_pu32(msg, &fcall->hdr.fid);
|
||||||
|
l9p_pu32(msg, &fcall->tsetattr.valid);
|
||||||
|
l9p_pu32(msg, &fcall->tsetattr.mode);
|
||||||
|
l9p_pu32(msg, &fcall->tsetattr.uid);
|
||||||
|
l9p_pu32(msg, &fcall->tsetattr.gid);
|
||||||
|
l9p_pu64(msg, &fcall->tsetattr.size);
|
||||||
|
l9p_pu64(msg, &fcall->tsetattr.atime_sec);
|
||||||
|
l9p_pu64(msg, &fcall->tsetattr.atime_nsec);
|
||||||
|
l9p_pu64(msg, &fcall->tsetattr.mtime_sec);
|
||||||
|
r = l9p_pu64(msg, &fcall->tsetattr.mtime_nsec);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case L9P_RSETATTR:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case L9P_TXATTRWALK:
|
||||||
|
l9p_pu32(msg, &fcall->hdr.fid);
|
||||||
|
l9p_pu32(msg, &fcall->txattrwalk.newfid);
|
||||||
|
r = l9p_pustring(msg, &fcall->txattrwalk.name);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case L9P_RXATTRWALK:
|
||||||
|
r = l9p_pu64(msg, &fcall->rxattrwalk.size);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case L9P_TXATTRCREATE:
|
||||||
|
l9p_pu32(msg, &fcall->hdr.fid);
|
||||||
|
r = l9p_pustring(msg, &fcall->txattrcreate.name);
|
||||||
|
if (r < 0)
|
||||||
|
break;
|
||||||
|
l9p_pu64(msg, &fcall->txattrcreate.attr_size);
|
||||||
|
r = l9p_pu32(msg, &fcall->txattrcreate.flags);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case L9P_RXATTRCREATE:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case L9P_TFSYNC:
|
||||||
|
r = l9p_pu32(msg, &fcall->hdr.fid);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case L9P_RFSYNC:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case L9P_TLOCK:
|
||||||
|
l9p_pu32(msg, &fcall->hdr.fid);
|
||||||
|
l9p_pu8(msg, &fcall->tlock.type);
|
||||||
|
l9p_pu32(msg, &fcall->tlock.flags);
|
||||||
|
l9p_pu64(msg, &fcall->tlock.start);
|
||||||
|
l9p_pu64(msg, &fcall->tlock.length);
|
||||||
|
l9p_pu32(msg, &fcall->tlock.proc_id);
|
||||||
|
r = l9p_pustring(msg, &fcall->tlock.client_id);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case L9P_RLOCK:
|
||||||
|
r = l9p_pu8(msg, &fcall->rlock.status);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case L9P_TGETLOCK:
|
||||||
|
l9p_pu32(msg, &fcall->hdr.fid);
|
||||||
|
/* FALLTHROUGH */
|
||||||
|
|
||||||
|
case L9P_RGETLOCK:
|
||||||
|
l9p_pu8(msg, &fcall->getlock.type);
|
||||||
|
l9p_pu64(msg, &fcall->getlock.start);
|
||||||
|
l9p_pu64(msg, &fcall->getlock.length);
|
||||||
|
l9p_pu32(msg, &fcall->getlock.proc_id);
|
||||||
|
r = l9p_pustring(msg, &fcall->getlock.client_id);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case L9P_TLINK:
|
||||||
|
l9p_pu32(msg, &fcall->tlink.dfid);
|
||||||
|
l9p_pu32(msg, &fcall->hdr.fid);
|
||||||
|
r = l9p_pustring(msg, &fcall->tlink.name);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case L9P_RLINK:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case L9P_TMKDIR:
|
||||||
|
l9p_pu32(msg, &fcall->hdr.fid);
|
||||||
|
r = l9p_pustring(msg, &fcall->tmkdir.name);
|
||||||
|
if (r < 0)
|
||||||
|
break;
|
||||||
|
l9p_pu32(msg, &fcall->tmkdir.mode);
|
||||||
|
r = l9p_pu32(msg, &fcall->tmkdir.gid);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case L9P_RMKDIR:
|
||||||
|
r = l9p_puqid(msg, &fcall->rmkdir.qid);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case L9P_TRENAMEAT:
|
||||||
|
l9p_pu32(msg, &fcall->hdr.fid);
|
||||||
|
r = l9p_pustring(msg, &fcall->trenameat.oldname);
|
||||||
|
if (r < 0)
|
||||||
|
break;
|
||||||
|
l9p_pu32(msg, &fcall->trenameat.newdirfid);
|
||||||
|
r = l9p_pustring(msg, &fcall->trenameat.newname);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case L9P_RRENAMEAT:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case L9P_TUNLINKAT:
|
||||||
|
l9p_pu32(msg, &fcall->hdr.fid);
|
||||||
|
r = l9p_pustring(msg, &fcall->tunlinkat.name);
|
||||||
|
if (r < 0)
|
||||||
|
break;
|
||||||
|
r = l9p_pu32(msg, &fcall->tunlinkat.flags);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case L9P_RUNLINKAT:
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
L9P_LOG(L9P_ERROR, "%s(): missing case for type %d",
|
||||||
|
__func__, fcall->hdr.type);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check for over- or under-run, or pustring error. */
|
||||||
|
if (r < 0)
|
||||||
|
return (-1);
|
||||||
|
|
||||||
|
if (msg->lm_mode == L9P_PACK) {
|
||||||
|
/* Rewind to the beginning and install size at front. */
|
||||||
|
uint32_t len = (uint32_t)msg->lm_size;
|
||||||
|
msg->lm_cursor_offset = 0;
|
||||||
|
msg->lm_cursor_iov = 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Subtract 4 bytes from current size, becase we're
|
||||||
|
* overwriting size (rewinding message to the beginning)
|
||||||
|
* and writing again, which will increase it 4 more.
|
||||||
|
*/
|
||||||
|
msg->lm_size -= sizeof(uint32_t);
|
||||||
|
|
||||||
|
if (fcall->hdr.type == L9P_RREAD ||
|
||||||
|
fcall->hdr.type == L9P_RREADDIR)
|
||||||
|
len += fcall->io.count;
|
||||||
|
|
||||||
|
l9p_pu32(msg, &len);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Free any strings or other data malloc'ed in the process of
|
||||||
|
* packing or unpacking an fcall.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
l9p_freefcall(union l9p_fcall *fcall)
|
||||||
|
{
|
||||||
|
uint16_t i;
|
||||||
|
|
||||||
|
switch (fcall->hdr.type) {
|
||||||
|
|
||||||
|
case L9P_TVERSION:
|
||||||
|
case L9P_RVERSION:
|
||||||
|
free(fcall->version.version);
|
||||||
|
return;
|
||||||
|
|
||||||
|
case L9P_TATTACH:
|
||||||
|
free(fcall->tattach.aname);
|
||||||
|
free(fcall->tattach.uname);
|
||||||
|
return;
|
||||||
|
|
||||||
|
case L9P_TWALK:
|
||||||
|
for (i = 0; i < fcall->twalk.nwname; i++)
|
||||||
|
free(fcall->twalk.wname[i]);
|
||||||
|
return;
|
||||||
|
|
||||||
|
case L9P_TCREATE:
|
||||||
|
case L9P_TOPEN:
|
||||||
|
free(fcall->tcreate.name);
|
||||||
|
free(fcall->tcreate.extension);
|
||||||
|
return;
|
||||||
|
|
||||||
|
case L9P_RSTAT:
|
||||||
|
l9p_freestat(&fcall->rstat.stat);
|
||||||
|
return;
|
||||||
|
|
||||||
|
case L9P_TWSTAT:
|
||||||
|
l9p_freestat(&fcall->twstat.stat);
|
||||||
|
return;
|
||||||
|
|
||||||
|
case L9P_TLCREATE:
|
||||||
|
free(fcall->tlcreate.name);
|
||||||
|
return;
|
||||||
|
|
||||||
|
case L9P_TSYMLINK:
|
||||||
|
free(fcall->tsymlink.name);
|
||||||
|
free(fcall->tsymlink.symtgt);
|
||||||
|
return;
|
||||||
|
|
||||||
|
case L9P_TMKNOD:
|
||||||
|
free(fcall->tmknod.name);
|
||||||
|
return;
|
||||||
|
|
||||||
|
case L9P_TRENAME:
|
||||||
|
free(fcall->trename.name);
|
||||||
|
return;
|
||||||
|
|
||||||
|
case L9P_RREADLINK:
|
||||||
|
free(fcall->rreadlink.target);
|
||||||
|
return;
|
||||||
|
|
||||||
|
case L9P_TXATTRWALK:
|
||||||
|
free(fcall->txattrwalk.name);
|
||||||
|
return;
|
||||||
|
|
||||||
|
case L9P_TXATTRCREATE:
|
||||||
|
free(fcall->txattrcreate.name);
|
||||||
|
return;
|
||||||
|
|
||||||
|
case L9P_TLOCK:
|
||||||
|
free(fcall->tlock.client_id);
|
||||||
|
return;
|
||||||
|
|
||||||
|
case L9P_TGETLOCK:
|
||||||
|
case L9P_RGETLOCK:
|
||||||
|
free(fcall->getlock.client_id);
|
||||||
|
return;
|
||||||
|
|
||||||
|
case L9P_TLINK:
|
||||||
|
free(fcall->tlink.name);
|
||||||
|
return;
|
||||||
|
|
||||||
|
case L9P_TMKDIR:
|
||||||
|
free(fcall->tmkdir.name);
|
||||||
|
return;
|
||||||
|
|
||||||
|
case L9P_TRENAMEAT:
|
||||||
|
free(fcall->trenameat.oldname);
|
||||||
|
free(fcall->trenameat.newname);
|
||||||
|
return;
|
||||||
|
|
||||||
|
case L9P_TUNLINKAT:
|
||||||
|
free(fcall->tunlinkat.name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
l9p_freestat(struct l9p_stat *stat)
|
||||||
|
{
|
||||||
|
free(stat->name);
|
||||||
|
free(stat->extension);
|
||||||
|
free(stat->uid);
|
||||||
|
free(stat->gid);
|
||||||
|
free(stat->muid);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t
|
||||||
|
l9p_sizeof_stat(struct l9p_stat *stat, enum l9p_version version)
|
||||||
|
{
|
||||||
|
uint16_t size = L9P_WORD /* size */
|
||||||
|
+ L9P_WORD /* type */
|
||||||
|
+ L9P_DWORD /* dev */
|
||||||
|
+ QID_SIZE /* qid */
|
||||||
|
+ 3 * L9P_DWORD /* mode, atime, mtime */
|
||||||
|
+ L9P_QWORD /* length */
|
||||||
|
+ STRING_SIZE(stat->name)
|
||||||
|
+ STRING_SIZE(stat->uid)
|
||||||
|
+ STRING_SIZE(stat->gid)
|
||||||
|
+ STRING_SIZE(stat->muid);
|
||||||
|
|
||||||
|
if (version >= L9P_2000U) {
|
||||||
|
size += STRING_SIZE(stat->extension)
|
||||||
|
+ 3 * L9P_DWORD;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (size);
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
*.pyc
|
||||||
|
__pycache__
|
||||||
|
testconf.ini
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
PYTHON?=python
|
||||||
|
|
||||||
|
selftest:
|
||||||
|
for f in lerrno p9err pfod protocol sequencer; do \
|
||||||
|
${PYTHON} $$f.py; \
|
||||||
|
done
|
||||||
|
|
||||||
|
clean cleandir:
|
||||||
|
rm -rf *.pyc __pycache__ *.log
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
Here are some very skeletal instructions for using
|
||||||
|
the client test code.
|
||||||
|
|
||||||
|
on server (assumes BSD style LD_LIBRARY_PATH):
|
||||||
|
|
||||||
|
mkdir /tmp/foo
|
||||||
|
cd lib9p
|
||||||
|
env LD_LIBRARY_PATH=. LIB9P_LOGGING=stderr example/server -h localhost -p 12345 /tmp/foo
|
||||||
|
|
||||||
|
(this can be run as a non-root user for now, but some things
|
||||||
|
only work when run as root)
|
||||||
|
|
||||||
|
on client (same machine as server, but can always be run as
|
||||||
|
non-root user):
|
||||||
|
|
||||||
|
cd lib9p/pytest
|
||||||
|
ONE TIME ONLY: copy testconf.ini.sample to testconf.ini, adjust to taste
|
||||||
|
./client.py
|
||||||
|
|
||||||
|
TODO: rework ./client so it can locate the .ini file better
|
||||||
|
|
||||||
|
########
|
||||||
|
|
||||||
|
IF USING diod (http://github.com/chaos/diod) AS THE SERVER ON
|
||||||
|
A LINUX MACHINE:
|
||||||
|
|
||||||
|
- The instructions for running the server are (or were):
|
||||||
|
sudo ./diod -f -d 1 -n -e /tmp/9
|
||||||
|
- You must mkdir the exported 9pfs file system (e.g., mkdir /tmp/9).
|
||||||
|
- While uname is not really used, aname (the attach name) IS used
|
||||||
|
and must match the exported file system, e.g., testconf.ini
|
||||||
|
must have "aname = /tmp/9".
|
||||||
Executable
+643
@@ -0,0 +1,643 @@
|
|||||||
|
#! /usr/bin/env python
|
||||||
|
|
||||||
|
"""
|
||||||
|
Run various tests, as a client.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
try:
|
||||||
|
import ConfigParser as configparser
|
||||||
|
except ImportError:
|
||||||
|
import configparser
|
||||||
|
import functools
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import socket
|
||||||
|
import struct
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
import p9conn
|
||||||
|
import protocol
|
||||||
|
|
||||||
|
LocalError = p9conn.LocalError
|
||||||
|
RemoteError = p9conn.RemoteError
|
||||||
|
TEError = p9conn.TEError
|
||||||
|
|
||||||
|
class TestState(object):
|
||||||
|
def __init__(self):
|
||||||
|
self.config = None
|
||||||
|
self.logger = None
|
||||||
|
self.successes = 0
|
||||||
|
self.skips = 0
|
||||||
|
self.failures = 0
|
||||||
|
self.exceptions = 0
|
||||||
|
self.clnt_tab = {}
|
||||||
|
self.mkclient = None
|
||||||
|
self.stop = False
|
||||||
|
self.gid = 0
|
||||||
|
|
||||||
|
def ccc(self, cid=None):
|
||||||
|
"""
|
||||||
|
Connect or reconnect as client (ccc = check and connect client).
|
||||||
|
|
||||||
|
If caller provides a cid (client ID) we check that specific
|
||||||
|
client. Otherwise the default ID ('base') is used.
|
||||||
|
In any case we return the now-connected client, plus the
|
||||||
|
attachment (session info) if any.
|
||||||
|
"""
|
||||||
|
if cid is None:
|
||||||
|
cid = 'base'
|
||||||
|
pair = self.clnt_tab.get(cid)
|
||||||
|
if pair is None:
|
||||||
|
clnt = self.mkclient()
|
||||||
|
pair = [clnt, None]
|
||||||
|
self.clnt_tab[cid] = pair
|
||||||
|
else:
|
||||||
|
clnt = pair[0]
|
||||||
|
if not clnt.is_connected():
|
||||||
|
clnt.connect()
|
||||||
|
return pair
|
||||||
|
|
||||||
|
def dcc(self, cid=None):
|
||||||
|
"""
|
||||||
|
Disconnect client (disconnect checked client). If no specific
|
||||||
|
client ID is provided, this disconnects ALL checked clients!
|
||||||
|
"""
|
||||||
|
if cid is None:
|
||||||
|
for cid in list(self.clnt_tab.keys()):
|
||||||
|
self.dcc(cid)
|
||||||
|
pair = self.clnt_tab.get(cid)
|
||||||
|
if pair is not None:
|
||||||
|
clnt = pair[0]
|
||||||
|
if clnt.is_connected():
|
||||||
|
clnt.shutdown()
|
||||||
|
del self.clnt_tab[cid]
|
||||||
|
|
||||||
|
def ccs(self, cid=None):
|
||||||
|
"""
|
||||||
|
Like ccc, but establish a session as well, by setting up
|
||||||
|
the uname/n_uname.
|
||||||
|
|
||||||
|
Return the client instance (only).
|
||||||
|
"""
|
||||||
|
pair = self.ccc(cid)
|
||||||
|
clnt = pair[0]
|
||||||
|
if pair[1] is None:
|
||||||
|
# No session yet - establish one. Note, this may fail.
|
||||||
|
section = None if cid is None else ('client-' + cid)
|
||||||
|
aname = getconf(self.config, section, 'aname', '')
|
||||||
|
uname = getconf(self.config, section, 'uname', '')
|
||||||
|
if clnt.proto > protocol.plain:
|
||||||
|
n_uname = getint(self.config, section, 'n_uname', 1001)
|
||||||
|
else:
|
||||||
|
n_uname = None
|
||||||
|
clnt.attach(afid=None, aname=aname, uname=uname, n_uname=n_uname)
|
||||||
|
pair[1] = (aname, uname, n_uname)
|
||||||
|
return clnt
|
||||||
|
|
||||||
|
def getconf(conf, section, name, default=None, rtype=str):
|
||||||
|
"""
|
||||||
|
Get configuration item for given section, or for "client" if
|
||||||
|
there is no entry for that particular section (or if section
|
||||||
|
is None).
|
||||||
|
|
||||||
|
This lets us get specific values for specific tests or
|
||||||
|
groups ([foo] name=value), falling back to general values
|
||||||
|
([client] name=value).
|
||||||
|
|
||||||
|
The type of the returned value <rtype> can be str, int, bool,
|
||||||
|
or float. The default is str (and see getconfint, getconfbool,
|
||||||
|
getconffloat below).
|
||||||
|
|
||||||
|
A default value may be supplied; if it is, that's the default
|
||||||
|
return value (this default should have the right type). If
|
||||||
|
no default is supplied, a missing value is an error.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# note: conf.get(None, 'foo') raises NoSectionError
|
||||||
|
where = section
|
||||||
|
result = conf.get(where, name)
|
||||||
|
except (configparser.NoSectionError, configparser.NoOptionError):
|
||||||
|
try:
|
||||||
|
where = 'client'
|
||||||
|
result = conf.get(where, name)
|
||||||
|
except configparser.NoSectionError:
|
||||||
|
sys.exit('no [{0}] section in configuration!'.format(where))
|
||||||
|
except configparser.NoOptionError:
|
||||||
|
if default is not None:
|
||||||
|
return default
|
||||||
|
if section is not None:
|
||||||
|
where = '[{0}] or [{1}]'.format(section, where)
|
||||||
|
else:
|
||||||
|
where = '[{0}]'.format(where)
|
||||||
|
raise LocalError('need {0}=value in {1}'.format(name, where))
|
||||||
|
where = '[{0}]'.format(where)
|
||||||
|
if rtype is str:
|
||||||
|
return result
|
||||||
|
if rtype is int:
|
||||||
|
return int(result)
|
||||||
|
if rtype is float:
|
||||||
|
return float(result)
|
||||||
|
if rtype is bool:
|
||||||
|
if result.lower() in ('1', 't', 'true', 'y', 'yes'):
|
||||||
|
return True
|
||||||
|
if result.lower() in ('0', 'f', 'false', 'n', 'no'):
|
||||||
|
return False
|
||||||
|
raise ValueError('{0} {1}={2}: invalid boolean'.format(where, name,
|
||||||
|
result))
|
||||||
|
raise ValueError('{0} {1}={2}: internal error: bad result type '
|
||||||
|
'{3!r}'.format(where, name, result, rtype))
|
||||||
|
|
||||||
|
def getint(conf, section, name, default=None):
|
||||||
|
"get integer config item"
|
||||||
|
return getconf(conf, section, name, default, int)
|
||||||
|
|
||||||
|
def getfloat(conf, section, name, default=None):
|
||||||
|
"get float config item"
|
||||||
|
return getconf(conf, section, name, default, float)
|
||||||
|
|
||||||
|
def getbool(conf, section, name, default=None):
|
||||||
|
"get boolean config item"
|
||||||
|
return getconf(conf, section, name, default, bool)
|
||||||
|
|
||||||
|
def pluralize(n, singular, plural):
|
||||||
|
"return singular or plural based on value of n"
|
||||||
|
return plural if n != 1 else singular
|
||||||
|
|
||||||
|
class TCDone(Exception):
|
||||||
|
"used in succ/fail/skip - skips rest of testcase with"
|
||||||
|
pass
|
||||||
|
|
||||||
|
class TestCase(object):
|
||||||
|
"""
|
||||||
|
Start a test case. Most callers must then do a ccs() to connect.
|
||||||
|
|
||||||
|
A failed test will generally disconnect from the server; a
|
||||||
|
new ccs() will reconnect, if the server is still alive.
|
||||||
|
"""
|
||||||
|
def __init__(self, name, tstate):
|
||||||
|
self.name = name
|
||||||
|
self.status = None
|
||||||
|
self.detail = None
|
||||||
|
self.tstate = tstate
|
||||||
|
self._shutdown = None
|
||||||
|
self._autoclunk = None
|
||||||
|
self._acconn = None
|
||||||
|
|
||||||
|
def auto_disconnect(self, conn):
|
||||||
|
self._shutdown = conn
|
||||||
|
|
||||||
|
def succ(self, detail=None):
|
||||||
|
"set success status"
|
||||||
|
self.status = 'SUCC'
|
||||||
|
self.detail = detail
|
||||||
|
raise TCDone()
|
||||||
|
|
||||||
|
def fail(self, detail):
|
||||||
|
"set failure status"
|
||||||
|
self.status = 'FAIL'
|
||||||
|
self.detail = detail
|
||||||
|
raise TCDone()
|
||||||
|
|
||||||
|
def skip(self, detail=None):
|
||||||
|
"set skip status"
|
||||||
|
self.status = 'SKIP'
|
||||||
|
self.detail = detail
|
||||||
|
raise TCDone()
|
||||||
|
|
||||||
|
def autoclunk(self, fid):
|
||||||
|
"mark fid to be closed/clunked on test exit"
|
||||||
|
if self._acconn is None:
|
||||||
|
raise ValueError('autoclunk: no _acconn')
|
||||||
|
self._autoclunk.append(fid)
|
||||||
|
|
||||||
|
def trace(self, msg, *args, **kwargs):
|
||||||
|
"add tracing info to log-file output"
|
||||||
|
level = kwargs.pop('level', logging.INFO)
|
||||||
|
self.tstate.logger.log(level, ' ' + msg, *args, **kwargs)
|
||||||
|
|
||||||
|
def ccs(self):
|
||||||
|
"call tstate ccs, turn socket.error connect failure into test fail"
|
||||||
|
try:
|
||||||
|
self.detail = 'connecting'
|
||||||
|
ret = self.tstate.ccs()
|
||||||
|
self.detail = None
|
||||||
|
self._acconn = ret
|
||||||
|
return ret
|
||||||
|
except socket.error as err:
|
||||||
|
self.fail(str(err))
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
self.tstate.logger.log(logging.DEBUG, 'ENTER: %s', self.name)
|
||||||
|
self._autoclunk = []
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||||
|
tstate = self.tstate
|
||||||
|
eat_exc = False
|
||||||
|
tb_detail = None
|
||||||
|
if exc_type is TCDone:
|
||||||
|
# we exited with succ, fail, or skip
|
||||||
|
eat_exc = True
|
||||||
|
exc_type = None
|
||||||
|
if exc_type is not None:
|
||||||
|
if self.status is None:
|
||||||
|
self.status = 'EXCP'
|
||||||
|
else:
|
||||||
|
self.status += ' EXC'
|
||||||
|
if exc_type == TEError:
|
||||||
|
# timeout/eof - best guess is that we crashed the server!
|
||||||
|
eat_exc = True
|
||||||
|
tb_detail = ['timeout or EOF']
|
||||||
|
elif exc_type in (socket.error, RemoteError, LocalError):
|
||||||
|
eat_exc = True
|
||||||
|
tb_detail = traceback.format_exception(exc_type, exc_val,
|
||||||
|
exc_tb)
|
||||||
|
level = logging.ERROR
|
||||||
|
tstate.failures += 1
|
||||||
|
tstate.exceptions += 1
|
||||||
|
else:
|
||||||
|
if self.status is None:
|
||||||
|
self.status = 'SUCC'
|
||||||
|
if self.status == 'SUCC':
|
||||||
|
level = logging.INFO
|
||||||
|
tstate.successes += 1
|
||||||
|
elif self.status == 'SKIP':
|
||||||
|
level = logging.INFO
|
||||||
|
tstate.skips += 1
|
||||||
|
else:
|
||||||
|
level = logging.ERROR
|
||||||
|
tstate.failures += 1
|
||||||
|
tstate.logger.log(level, '%s: %s', self.status, self.name)
|
||||||
|
if self.detail:
|
||||||
|
tstate.logger.log(level, ' detail: %s', self.detail)
|
||||||
|
if tb_detail:
|
||||||
|
for line in tb_detail:
|
||||||
|
tstate.logger.log(level, ' %s', line.rstrip())
|
||||||
|
for fid in self._autoclunk:
|
||||||
|
self._acconn.clunk(fid, ignore_error=True)
|
||||||
|
if self._shutdown:
|
||||||
|
self._shutdown.shutdown()
|
||||||
|
return eat_exc
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"the usual main"
|
||||||
|
parser = argparse.ArgumentParser(description='run tests against a server')
|
||||||
|
|
||||||
|
parser.add_argument('-c', '--config',
|
||||||
|
action='append',
|
||||||
|
help='specify additional file(s) to read (beyond testconf.ini)')
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
config = configparser.SafeConfigParser()
|
||||||
|
# use case sensitive keys
|
||||||
|
config.optionxform = str
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open('testconf.ini', 'r') as stream:
|
||||||
|
config.readfp(stream)
|
||||||
|
except (OSError, IOError) as err:
|
||||||
|
sys.exit(str(err))
|
||||||
|
if args.config:
|
||||||
|
ok = config.read(args.config)
|
||||||
|
failed = set(ok) - set(args.config)
|
||||||
|
if len(failed):
|
||||||
|
nfailed = len(failed)
|
||||||
|
word = 'files' if nfailed > 1 else 'file'
|
||||||
|
failed = ', '.join(failed)
|
||||||
|
print('failed to read {0} {1}: {2}'.format(nfailed, word, failed))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
logging.basicConfig(level=config.get('client', 'loglevel').upper())
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
tstate = TestState()
|
||||||
|
tstate.logger = logger
|
||||||
|
tstate.config = config
|
||||||
|
|
||||||
|
server = config.get('client', 'server')
|
||||||
|
port = config.getint('client', 'port')
|
||||||
|
proto = config.get('client', 'protocol')
|
||||||
|
may_downgrade = config.getboolean('client', 'may_downgrade')
|
||||||
|
timeout = config.getfloat('client', 'timeout')
|
||||||
|
|
||||||
|
tstate.stop = True # unless overwritten below
|
||||||
|
with TestCase('send bad packet', tstate) as tc:
|
||||||
|
tc.detail = 'connecting to {0}:{1}'.format(server, port)
|
||||||
|
try:
|
||||||
|
conn = p9conn.P9SockIO(logger, server=server, port=port)
|
||||||
|
except socket.error as err:
|
||||||
|
tc.fail('cannot connect at all (server down?)')
|
||||||
|
tc.auto_disconnect(conn)
|
||||||
|
tc.detail = None
|
||||||
|
pkt = struct.pack('<I', 256);
|
||||||
|
conn.write(pkt)
|
||||||
|
# ignore reply if any, we're just trying to trip the server
|
||||||
|
tstate.stop = False
|
||||||
|
tc.succ()
|
||||||
|
|
||||||
|
if not tstate.stop:
|
||||||
|
tstate.mkclient = functools.partial(p9conn.P9Client, logger,
|
||||||
|
timeout, proto, may_downgrade,
|
||||||
|
server=server, port=port)
|
||||||
|
tstate.stop = True
|
||||||
|
with TestCase('send bad Tversion', tstate) as tc:
|
||||||
|
try:
|
||||||
|
clnt = tstate.mkclient()
|
||||||
|
except socket.error as err:
|
||||||
|
tc.fail('can no longer connect, did bad pkt crash server?')
|
||||||
|
tc.auto_disconnect(clnt)
|
||||||
|
clnt.set_monkey('version', b'wrongo, fishbreath!')
|
||||||
|
tc.detail = 'connecting'
|
||||||
|
try:
|
||||||
|
clnt.connect()
|
||||||
|
except RemoteError as err:
|
||||||
|
tstate.stop = False
|
||||||
|
tc.succ(err.args[0])
|
||||||
|
tc.fail('server accepted a bad Tversion')
|
||||||
|
|
||||||
|
if not tstate.stop:
|
||||||
|
# All NUL characters in strings are invalid.
|
||||||
|
with TestCase('send illegal NUL in Tversion', tstate) as tc:
|
||||||
|
clnt = tstate.mkclient()
|
||||||
|
tc.auto_disconnect(clnt)
|
||||||
|
clnt.set_monkey('version', b'9P2000\0')
|
||||||
|
# Forcibly allow downgrade so that Tversion
|
||||||
|
# succeeds if they ignore the \0.
|
||||||
|
clnt.may_downgrade = True
|
||||||
|
tc.detail = 'connecting'
|
||||||
|
try:
|
||||||
|
clnt.connect()
|
||||||
|
except (TEError, RemoteError) as err:
|
||||||
|
tc.succ(err.args[0])
|
||||||
|
tc.fail('server accepted NUL in Tversion')
|
||||||
|
|
||||||
|
if not tstate.stop:
|
||||||
|
with TestCase('connect normally', tstate) as tc:
|
||||||
|
tc.detail = 'connecting'
|
||||||
|
try:
|
||||||
|
tstate.ccc()
|
||||||
|
except RemoteError as err:
|
||||||
|
# can't test any further, but this might be success
|
||||||
|
tstate.stop = True
|
||||||
|
if 'they only support version' in err.args[0]:
|
||||||
|
tc.succ(err.args[0])
|
||||||
|
tc.fail(err.args[0])
|
||||||
|
tc.succ()
|
||||||
|
|
||||||
|
if not tstate.stop:
|
||||||
|
with TestCase('attach with bad afid', tstate) as tc:
|
||||||
|
clnt = tstate.ccc()[0]
|
||||||
|
section = 'attach-with-bad-afid'
|
||||||
|
aname = getconf(tstate.config, section, 'aname', '')
|
||||||
|
uname = getconf(tstate.config, section, 'uname', '')
|
||||||
|
if clnt.proto > protocol.plain:
|
||||||
|
n_uname = getint(tstate.config, section, 'n_uname', 1001)
|
||||||
|
else:
|
||||||
|
n_uname = None
|
||||||
|
try:
|
||||||
|
clnt.attach(afid=42, aname=aname, uname=uname, n_uname=n_uname)
|
||||||
|
except RemoteError as err:
|
||||||
|
tc.succ(err.args[0])
|
||||||
|
tc.dcc()
|
||||||
|
tc.fail('bad attach afid not rejected')
|
||||||
|
|
||||||
|
try:
|
||||||
|
if not tstate.stop:
|
||||||
|
# Various Linux tests need gids. Just get them for everyone.
|
||||||
|
tstate.gid = getint(tstate.config, 'client', 'gid', 0)
|
||||||
|
more_test_cases(tstate)
|
||||||
|
finally:
|
||||||
|
tstate.dcc()
|
||||||
|
|
||||||
|
n_tests = tstate.successes + tstate.failures
|
||||||
|
print('summary:')
|
||||||
|
if tstate.successes:
|
||||||
|
print('{0}/{1} tests succeeded'.format(tstate.successes, n_tests))
|
||||||
|
if tstate.failures:
|
||||||
|
print('{0}/{1} tests failed'.format(tstate.failures, n_tests))
|
||||||
|
if tstate.skips:
|
||||||
|
print('{0} {1} skipped'.format(tstate.skips,
|
||||||
|
pluralize(tstate.skips,
|
||||||
|
'test', 'tests')))
|
||||||
|
if tstate.exceptions:
|
||||||
|
print('{0} {1} occurred'.format(tstate.exceptions,
|
||||||
|
pluralize(tstate.exceptions,
|
||||||
|
'exception', 'exceptions')))
|
||||||
|
if tstate.stop:
|
||||||
|
print('tests stopped early')
|
||||||
|
return 1 if tstate.stop or tstate.exceptions or tstate.failures else 0
|
||||||
|
|
||||||
|
def more_test_cases(tstate):
|
||||||
|
"run cases that can only proceed if connecting works at all"
|
||||||
|
with TestCase('attach normally', tstate) as tc:
|
||||||
|
tc.ccs()
|
||||||
|
tc.succ()
|
||||||
|
if tstate.stop:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Empty string is not technically illegal. It's not clear
|
||||||
|
# whether it should be accepted or rejected. However, it
|
||||||
|
# used to crash the server entirely, so it's a desirable
|
||||||
|
# test case.
|
||||||
|
with TestCase('empty string in Twalk request', tstate) as tc:
|
||||||
|
clnt = tc.ccs()
|
||||||
|
try:
|
||||||
|
fid, qid = clnt.lookup(clnt.rootfid, [b''])
|
||||||
|
except RemoteError as err:
|
||||||
|
tc.succ(err.args[0])
|
||||||
|
clnt.clunk(fid)
|
||||||
|
tc.succ('note: empty Twalk component name not rejected')
|
||||||
|
|
||||||
|
# Name components may not contain /
|
||||||
|
with TestCase('embedded / in lookup component name', tstate) as tc:
|
||||||
|
clnt = tc.ccs()
|
||||||
|
try:
|
||||||
|
fid, qid = clnt.lookup(clnt.rootfid, [b'/'])
|
||||||
|
tc.autoclunk(fid)
|
||||||
|
except RemoteError as err:
|
||||||
|
tc.succ(err.args[0])
|
||||||
|
tc.fail('/ in lookup component name not rejected')
|
||||||
|
|
||||||
|
# Proceed from a clean tree. As a side effect, this also tests
|
||||||
|
# either the old style readdir (read() on a directory fid) or
|
||||||
|
# the dot-L readdir().
|
||||||
|
#
|
||||||
|
# The test case will fail if we don't have permission to remove
|
||||||
|
# some file(s).
|
||||||
|
with TestCase('clean up tree (readdir+remove)', tstate) as tc:
|
||||||
|
clnt = tc.ccs()
|
||||||
|
fset = clnt.uxreaddir(b'/')
|
||||||
|
fset = [i for i in fset if i != '.' and i != '..']
|
||||||
|
tc.trace("what's there initially: {0!r}".format(fset))
|
||||||
|
try:
|
||||||
|
clnt.uxremove(b'/', force=False, recurse=True)
|
||||||
|
except RemoteError as err:
|
||||||
|
tc.trace('failed to read or clean up tree', level=logging.ERROR)
|
||||||
|
tc.trace('this might be a permissions error', level=logging.ERROR)
|
||||||
|
tstate.stop = True
|
||||||
|
tc.fail(str(err))
|
||||||
|
fset = clnt.uxreaddir(b'/')
|
||||||
|
fset = [i for i in fset if i != '.' and i != '..']
|
||||||
|
tc.trace("what's left after removing everything: {0!r}".format(fset))
|
||||||
|
if fset:
|
||||||
|
tstate.stop = True
|
||||||
|
tc.trace('note: could be a permissions error', level=logging.ERROR)
|
||||||
|
tc.fail('/ not empty after removing all: {0!r}'.format(fset))
|
||||||
|
tc.succ()
|
||||||
|
if tstate.stop:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Name supplied to create, mkdir, etc, may not contain /.
|
||||||
|
# Note that this test may fail for the wrong reason if /dir
|
||||||
|
# itself does not already exist, so first let's make /dir.
|
||||||
|
only_dotl = getbool(tstate.config, 'client', 'only_dotl', False)
|
||||||
|
with TestCase('mkdir', tstate) as tc:
|
||||||
|
clnt = tc.ccs()
|
||||||
|
if only_dotl and not clnt.supports(protocol.td.Tmkdir):
|
||||||
|
tc.skip('cannot test dot-L mkdir on {0}'.format(clnt.proto))
|
||||||
|
try:
|
||||||
|
fid, qid = clnt.uxlookup(b'/dir', None)
|
||||||
|
tc.autoclunk(fid)
|
||||||
|
tstate.stop = True
|
||||||
|
tc.fail('found existing /dir after cleaning tree')
|
||||||
|
except RemoteError as err:
|
||||||
|
# we'll just assume it's "no such file or directory"
|
||||||
|
pass
|
||||||
|
if only_dotl:
|
||||||
|
qid = clnt.mkdir(clnt.rootfid, b'dir', 0o777, tstate.gid)
|
||||||
|
else:
|
||||||
|
qid, _ = clnt.create(clnt.rootfid, b'dir',
|
||||||
|
protocol.td.DMDIR | 0o777,
|
||||||
|
protocol.td.OREAD)
|
||||||
|
if qid.type != protocol.td.QTDIR:
|
||||||
|
tstate.stop = True
|
||||||
|
tc.fail('creating /dir: result is not a directory')
|
||||||
|
tc.trace('now attempting to create /dir/sub the wrong way')
|
||||||
|
try:
|
||||||
|
if only_dotl:
|
||||||
|
qid = clnt.mkdir(clnt.rootfid, b'dir/sub', 0o777, tstate.gid)
|
||||||
|
else:
|
||||||
|
qid, _ = clnt.create(clnt.rootfid, b'dir/sub',
|
||||||
|
protocol.td.DMDIR | 0o777,
|
||||||
|
protocol.td.OREAD)
|
||||||
|
# it's not clear what happened on the server at this point!
|
||||||
|
tc.trace("creating dir/sub (with embedded '/') should have "
|
||||||
|
'failed but did not')
|
||||||
|
tstate.stop = True
|
||||||
|
fset = clnt.uxreaddir(b'/dir')
|
||||||
|
if 'sub' in fset:
|
||||||
|
tc.trace('(found our dir/sub detritus)')
|
||||||
|
clnt.uxremove(b'dir/sub', force=True)
|
||||||
|
fset = clnt.uxreaddir(b'/dir')
|
||||||
|
if 'sub' not in fset:
|
||||||
|
tc.trace('(successfully removed our dir/sub detritus)')
|
||||||
|
tstate.stop = False
|
||||||
|
tc.fail('created dir/sub as single directory with embedded slash')
|
||||||
|
except RemoteError as err:
|
||||||
|
# we'll just assume it's the right kind of error
|
||||||
|
tc.trace('invalid path dir/sub failed with: %s', str(err))
|
||||||
|
tc.succ('embedded slash in mkdir correctly refused')
|
||||||
|
if tstate.stop:
|
||||||
|
return
|
||||||
|
|
||||||
|
with TestCase('getattr/setattr', tstate) as tc:
|
||||||
|
# This test is not really thorough enough, need to test
|
||||||
|
# all combinations of settings. Should also test that
|
||||||
|
# old values are restored on failure, although it is not
|
||||||
|
# clear how to trigger failures.
|
||||||
|
clnt = tc.ccs()
|
||||||
|
if not clnt.supports(protocol.td.Tgetattr):
|
||||||
|
tc.skip('%s does not support Tgetattr', clnt)
|
||||||
|
fid, _, _, _ = clnt.uxopen(b'/dir/file', os.O_CREAT | os.O_RDWR, 0o666,
|
||||||
|
gid=tstate.gid)
|
||||||
|
tc.autoclunk(fid)
|
||||||
|
written = clnt.write(fid, 0, 'bytes\n')
|
||||||
|
if written != 6:
|
||||||
|
tc.trace('expected to write 6 bytes, actually wrote %d', written,
|
||||||
|
level=logging.WARN)
|
||||||
|
attrs = clnt.Tgetattr(fid)
|
||||||
|
#tc.trace('getattr: after write, before setattr: got %s', attrs)
|
||||||
|
if attrs.size != written:
|
||||||
|
tc.fail('getattr: expected size=%d, got size=%d',
|
||||||
|
written, attrs.size)
|
||||||
|
# now truncate, set mtime to (3,14), and check result
|
||||||
|
set_time_to = p9conn.Timespec(sec=0, nsec=140000000)
|
||||||
|
clnt.Tsetattr(fid, size=0, mtime=set_time_to)
|
||||||
|
attrs = clnt.Tgetattr(fid)
|
||||||
|
#tc.trace('getattr: after setattr: got %s', attrs)
|
||||||
|
if attrs.mtime.sec != set_time_to.sec or attrs.size != 0:
|
||||||
|
tc.fail('setattr: expected to get back mtime.sec={0}, size=0; '
|
||||||
|
'got mtime.sec={1}, size='
|
||||||
|
'{1}'.format(set_time_to.sec, attrs.mtime.sec, attrs.size))
|
||||||
|
# nsec is not as stable but let's check
|
||||||
|
if attrs.mtime.nsec != set_time_to.nsec:
|
||||||
|
tc.trace('setattr: expected to get back mtime_nsec=%d; '
|
||||||
|
'got %d', set_time_to.nsec, mtime_nsec)
|
||||||
|
tc.succ('able to set and see size and mtime')
|
||||||
|
|
||||||
|
# this test should be much later, but we know the current
|
||||||
|
# server is broken...
|
||||||
|
with TestCase('rename adjusts other fids', tstate) as tc:
|
||||||
|
clnt = tc.ccs()
|
||||||
|
dirfid, _ = clnt.uxlookup(b'/dir')
|
||||||
|
tc.autoclunk(dirfid)
|
||||||
|
clnt.uxmkdir(b'd1', 0o777, tstate.gid, startdir=dirfid)
|
||||||
|
clnt.uxmkdir(b'd1/sub', 0o777, tstate.gid, startdir=dirfid)
|
||||||
|
d1fid, _ = clnt.uxlookup(b'd1', dirfid)
|
||||||
|
tc.autoclunk(d1fid)
|
||||||
|
subfid, _ = clnt.uxlookup(b'sub', d1fid)
|
||||||
|
tc.autoclunk(subfid)
|
||||||
|
fid, _, _, _ = clnt.uxopen(b'file', os.O_CREAT | os.O_RDWR,
|
||||||
|
0o666, startdir=subfid, gid=tstate.gid)
|
||||||
|
tc.autoclunk(fid)
|
||||||
|
written = clnt.write(fid, 0, 'filedata\n')
|
||||||
|
if written != 9:
|
||||||
|
tc.trace('expected to write 9 bytes, actually wrote %d', written,
|
||||||
|
level=logging.WARN)
|
||||||
|
# Now if we rename /dir/d1 to /dir/d2, the fids for both
|
||||||
|
# sub/file and sub itself should still be usable. This
|
||||||
|
# holds for both Trename (Linux only) and Twstat based
|
||||||
|
# rename ops.
|
||||||
|
#
|
||||||
|
# Note that some servers may cache some number of files and/or
|
||||||
|
# diretories held open, so we should open many fids to wipe
|
||||||
|
# out the cache (XXX notyet).
|
||||||
|
if clnt.supports(protocol.td.Trename):
|
||||||
|
clnt.rename(d1fid, dirfid, name=b'd2')
|
||||||
|
else:
|
||||||
|
clnt.wstat(d1fid, name=b'd2')
|
||||||
|
try:
|
||||||
|
rofid, _, _, _ = clnt.uxopen(b'file', os.O_RDONLY, startdir=subfid)
|
||||||
|
clnt.clunk(rofid)
|
||||||
|
except RemoteError as err:
|
||||||
|
tc.fail('open file in renamed dir/d2/sub: {0}'.format(err))
|
||||||
|
tc.succ()
|
||||||
|
|
||||||
|
# Even if xattrwalk is supported by the protocol, it's optional
|
||||||
|
# on the server.
|
||||||
|
with TestCase('xattrwalk', tstate) as tc:
|
||||||
|
clnt = tc.ccs()
|
||||||
|
if not clnt.supports(protocol.td.Txattrwalk):
|
||||||
|
tc.skip('{0} does not support Txattrwalk'.format(clnt))
|
||||||
|
dirfid, _ = clnt.uxlookup(b'/dir')
|
||||||
|
tc.autoclunk(dirfid)
|
||||||
|
try:
|
||||||
|
# need better tests...
|
||||||
|
attrfid, size = clnt.xattrwalk(dirfid)
|
||||||
|
tc.autoclunk(attrfid)
|
||||||
|
data = clnt.read(attrfid, 0, size)
|
||||||
|
tc.trace('xattrwalk with no name: data=%r', data)
|
||||||
|
tc.succ('xattrwalk size={0} datalen={1}'.format(size, len(data)))
|
||||||
|
except RemoteError as err:
|
||||||
|
tc.trace('xattrwalk on /dir: {0}'.format(err))
|
||||||
|
tc.succ('xattrwalk apparently not implemented')
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
try:
|
||||||
|
sys.exit(main())
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
sys.exit('\nInterrupted')
|
||||||
@@ -0,0 +1,291 @@
|
|||||||
|
#! /usr/bin/env python
|
||||||
|
|
||||||
|
"""
|
||||||
|
Error number definitions for Linux.
|
||||||
|
"""
|
||||||
|
|
||||||
|
EPERM = 1
|
||||||
|
ENOENT = 2
|
||||||
|
ESRCH = 3
|
||||||
|
EINTR = 4
|
||||||
|
EIO = 5
|
||||||
|
ENXIO = 6
|
||||||
|
E2BIG = 7
|
||||||
|
ENOEXEC = 8
|
||||||
|
EBADF = 9
|
||||||
|
ECHILD = 10
|
||||||
|
EAGAIN = 11
|
||||||
|
ENOMEM = 12
|
||||||
|
EACCES = 13
|
||||||
|
EFAULT = 14
|
||||||
|
ENOTBLK = 15
|
||||||
|
EBUSY = 16
|
||||||
|
EEXIST = 17
|
||||||
|
EXDEV = 18
|
||||||
|
ENODEV = 19
|
||||||
|
ENOTDIR = 20
|
||||||
|
EISDIR = 21
|
||||||
|
EINVAL = 22
|
||||||
|
ENFILE = 23
|
||||||
|
EMFILE = 24
|
||||||
|
ENOTTY = 25
|
||||||
|
ETXTBSY = 26
|
||||||
|
EFBIG = 27
|
||||||
|
ENOSPC = 28
|
||||||
|
ESPIPE = 29
|
||||||
|
EROFS = 30
|
||||||
|
EMLINK = 31
|
||||||
|
EPIPE = 32
|
||||||
|
EDOM = 33
|
||||||
|
ERANGE = 34
|
||||||
|
EDEADLK = 35
|
||||||
|
ENAMETOOLONG = 36
|
||||||
|
ENOLCK = 37
|
||||||
|
ENOSYS = 38
|
||||||
|
ENOTEMPTY = 39
|
||||||
|
ELOOP = 40
|
||||||
|
# 41 unused
|
||||||
|
ENOMSG = 42
|
||||||
|
EIDRM = 43
|
||||||
|
ECHRNG = 44
|
||||||
|
EL2NSYNC = 45
|
||||||
|
EL3HLT = 46
|
||||||
|
EL3RST = 47
|
||||||
|
ELNRNG = 48
|
||||||
|
EUNATCH = 49
|
||||||
|
ENOCSI = 50
|
||||||
|
EL2HLT = 51
|
||||||
|
EBADE = 52
|
||||||
|
EBADR = 53
|
||||||
|
EXFULL = 54
|
||||||
|
ENOANO = 55
|
||||||
|
EBADRQC = 56
|
||||||
|
EBADSLT = 57
|
||||||
|
# 58 unused
|
||||||
|
EBFONT = 59
|
||||||
|
ENOSTR = 60
|
||||||
|
ENODATA = 61
|
||||||
|
ETIME = 62
|
||||||
|
ENOSR = 63
|
||||||
|
ENONET = 64
|
||||||
|
ENOPKG = 65
|
||||||
|
EREMOTE = 66
|
||||||
|
ENOLINK = 67
|
||||||
|
EADV = 68
|
||||||
|
ESRMNT = 69
|
||||||
|
ECOMM = 70
|
||||||
|
EPROTO = 71
|
||||||
|
EMULTIHOP = 72
|
||||||
|
EDOTDOT = 73
|
||||||
|
EBADMSG = 74
|
||||||
|
EOVERFLOW = 75
|
||||||
|
ENOTUNIQ = 76
|
||||||
|
EBADFD = 77
|
||||||
|
EREMCHG = 78
|
||||||
|
ELIBACC = 79
|
||||||
|
ELIBBAD = 80
|
||||||
|
ELIBSCN = 81
|
||||||
|
ELIBMAX = 82
|
||||||
|
ELIBEXEC = 83
|
||||||
|
EILSEQ = 84
|
||||||
|
ERESTART = 85
|
||||||
|
ESTRPIPE = 86
|
||||||
|
EUSERS = 87
|
||||||
|
ENOTSOCK = 88
|
||||||
|
EDESTADDRREQ = 89
|
||||||
|
EMSGSIZE = 90
|
||||||
|
EPROTOTYPE = 91
|
||||||
|
ENOPROTOOPT = 92
|
||||||
|
EPROTONOSUPPORT = 93
|
||||||
|
ESOCKTNOSUPPORT = 94
|
||||||
|
EOPNOTSUPP = 95
|
||||||
|
EPFNOSUPPORT = 96
|
||||||
|
EAFNOSUPPORT = 97
|
||||||
|
EADDRINUSE = 98
|
||||||
|
EADDRNOTAVAIL = 99
|
||||||
|
ENETDOWN = 100
|
||||||
|
ENETUNREACH = 101
|
||||||
|
ENETRESET = 102
|
||||||
|
ECONNABORTED = 103
|
||||||
|
ECONNRESET = 104
|
||||||
|
ENOBUFS = 105
|
||||||
|
EISCONN = 106
|
||||||
|
ENOTCONN = 107
|
||||||
|
ESHUTDOWN = 108
|
||||||
|
ETOOMANYREFS = 109
|
||||||
|
ETIMEDOUT = 110
|
||||||
|
ECONNREFUSED = 111
|
||||||
|
EHOSTDOWN = 112
|
||||||
|
EHOSTUNREACH = 113
|
||||||
|
EALREADY = 114
|
||||||
|
EINPROGRESS = 115
|
||||||
|
ESTALE = 116
|
||||||
|
EUCLEAN = 117
|
||||||
|
ENOTNAM = 118
|
||||||
|
ENAVAIL = 119
|
||||||
|
EISNAM = 120
|
||||||
|
EREMOTEIO = 121
|
||||||
|
EDQUOT = 122
|
||||||
|
ENOMEDIUM = 123
|
||||||
|
EMEDIUMTYPE = 124
|
||||||
|
ECANCELED = 125
|
||||||
|
ENOKEY = 126
|
||||||
|
EKEYEXPIRED = 127
|
||||||
|
EKEYREVOKED = 128
|
||||||
|
EKEYREJECTED = 129
|
||||||
|
EOWNERDEAD = 130
|
||||||
|
ENOTRECOVERABLE = 131
|
||||||
|
ERFKILL = 132
|
||||||
|
EHWPOISON = 133
|
||||||
|
|
||||||
|
_strerror = {
|
||||||
|
EPERM: 'Permission denied',
|
||||||
|
ENOENT: 'No such file or directory',
|
||||||
|
ESRCH: 'No such process',
|
||||||
|
EINTR: 'Interrupted system call',
|
||||||
|
EIO: 'Input/output error',
|
||||||
|
ENXIO: 'Device not configured',
|
||||||
|
E2BIG: 'Argument list too long',
|
||||||
|
ENOEXEC: 'Exec format error',
|
||||||
|
EBADF: 'Bad file descriptor',
|
||||||
|
ECHILD: 'No child processes',
|
||||||
|
EAGAIN: 'Resource temporarily unavailable',
|
||||||
|
ENOMEM: 'Cannot allocate memory',
|
||||||
|
EACCES: 'Permission denied',
|
||||||
|
EFAULT: 'Bad address',
|
||||||
|
ENOTBLK: 'Block device required',
|
||||||
|
EBUSY: 'Device busy',
|
||||||
|
EEXIST: 'File exists',
|
||||||
|
EXDEV: 'Cross-device link',
|
||||||
|
ENODEV: 'Operation not supported by device',
|
||||||
|
ENOTDIR: 'Not a directory',
|
||||||
|
EISDIR: 'Is a directory',
|
||||||
|
EINVAL: 'Invalid argument',
|
||||||
|
ENFILE: 'Too many open files in system',
|
||||||
|
EMFILE: 'Too many open files',
|
||||||
|
ENOTTY: 'Inappropriate ioctl for device',
|
||||||
|
ETXTBSY: 'Text file busy',
|
||||||
|
EFBIG: 'File too large',
|
||||||
|
ENOSPC: 'No space left on device',
|
||||||
|
ESPIPE: 'Illegal seek',
|
||||||
|
EROFS: 'Read-only filesystem',
|
||||||
|
EMLINK: 'Too many links',
|
||||||
|
EPIPE: 'Broken pipe',
|
||||||
|
EDOM: 'Numerical argument out of domain',
|
||||||
|
ERANGE: 'Result too large',
|
||||||
|
EDEADLK: 'Resource deadlock avoided',
|
||||||
|
ENAMETOOLONG: 'File name too long',
|
||||||
|
ENOLCK: 'No locks available',
|
||||||
|
ENOSYS: 'Function not implemented',
|
||||||
|
ENOTEMPTY: 'Directory not empty',
|
||||||
|
ELOOP: 'Too many levels of symbolic links',
|
||||||
|
ENOMSG: 'No message of desired type',
|
||||||
|
EIDRM: 'Identifier removed',
|
||||||
|
ECHRNG: 'Channel number out of range',
|
||||||
|
EL2NSYNC: 'Level 2 not synchronized',
|
||||||
|
EL3HLT: 'Level 3 halted',
|
||||||
|
EL3RST: 'Level 3 reset',
|
||||||
|
ELNRNG: 'Link number out of range',
|
||||||
|
EUNATCH: 'Protocol driver not attached',
|
||||||
|
ENOCSI: 'No CSI structure available',
|
||||||
|
EL2HLT: 'Level 2 halted',
|
||||||
|
EBADE: 'Invalid exchange',
|
||||||
|
EBADR: 'Invalid request descriptor',
|
||||||
|
EXFULL: 'Exchange full',
|
||||||
|
ENOANO: 'No anode',
|
||||||
|
EBADRQC: 'Invalid request code',
|
||||||
|
EBADSLT: 'Invalid slot',
|
||||||
|
EBFONT: 'Bad font file format',
|
||||||
|
ENOSTR: 'Device not a stream',
|
||||||
|
ENODATA: 'No data available',
|
||||||
|
ETIME: 'Timer expired',
|
||||||
|
ENOSR: 'Out of streams resources',
|
||||||
|
ENONET: 'Machine is not on the network',
|
||||||
|
ENOPKG: 'Package not installed',
|
||||||
|
EREMOTE: 'Object is remote',
|
||||||
|
ENOLINK: 'Link has been severed',
|
||||||
|
EADV: 'Advertise error',
|
||||||
|
ESRMNT: 'Srmount error',
|
||||||
|
ECOMM: 'Communication error on send',
|
||||||
|
EPROTO: 'Protocol error',
|
||||||
|
EMULTIHOP: 'Multihop attempted',
|
||||||
|
EDOTDOT: 'RFS specific error',
|
||||||
|
EBADMSG: 'Bad message',
|
||||||
|
EOVERFLOW: 'Value too large for defined data type',
|
||||||
|
ENOTUNIQ: 'Name not unique on network',
|
||||||
|
EBADFD: 'File descriptor in bad state',
|
||||||
|
EREMCHG: 'Remote address changed',
|
||||||
|
ELIBACC: 'Can not access a needed shared library',
|
||||||
|
ELIBBAD: 'Accessing a corrupted shared library',
|
||||||
|
ELIBSCN: '.lib section in a.out corrupted',
|
||||||
|
ELIBMAX: 'Attempting to link in too many shared libraries',
|
||||||
|
ELIBEXEC: 'Cannot exec a shared library directly',
|
||||||
|
EILSEQ: 'Invalid or incomplete multibyte or wide character',
|
||||||
|
ERESTART: 'Interrupted system call should be restarted',
|
||||||
|
ESTRPIPE: 'Streams pipe error',
|
||||||
|
EUSERS: 'Too many users',
|
||||||
|
ENOTSOCK: 'Socket operation on non-socket',
|
||||||
|
EDESTADDRREQ: 'Destination address required',
|
||||||
|
EMSGSIZE: 'Message too long',
|
||||||
|
EPROTOTYPE: 'Protocol wrong type for socket',
|
||||||
|
ENOPROTOOPT: 'Protocol not available',
|
||||||
|
EPROTONOSUPPORT: 'Protocol not supported',
|
||||||
|
ESOCKTNOSUPPORT: 'Socket type not supported',
|
||||||
|
EOPNOTSUPP: 'Operation not supported',
|
||||||
|
EPFNOSUPPORT: 'Protocol family not supported',
|
||||||
|
EAFNOSUPPORT: 'Address family not supported by protocol',
|
||||||
|
EADDRINUSE: 'Address already in use',
|
||||||
|
EADDRNOTAVAIL: 'Cannot assign requested address',
|
||||||
|
ENETDOWN: 'Network is down',
|
||||||
|
ENETUNREACH: 'Network is unreachable',
|
||||||
|
ENETRESET: 'Network dropped connection on reset',
|
||||||
|
ECONNABORTED: 'Software caused connection abort',
|
||||||
|
ECONNRESET: 'Connection reset by peer',
|
||||||
|
ENOBUFS: 'No buffer space available',
|
||||||
|
EISCONN: 'Transport endpoint is already connected',
|
||||||
|
ENOTCONN: 'Transport endpoint is not connected',
|
||||||
|
ESHUTDOWN: 'Cannot send after transport endpoint shutdown',
|
||||||
|
ETOOMANYREFS: 'Too many references: cannot splice',
|
||||||
|
ETIMEDOUT: 'Connection timed out',
|
||||||
|
ECONNREFUSED: 'Connection refused',
|
||||||
|
EHOSTDOWN: 'Host is down',
|
||||||
|
EHOSTUNREACH: 'No route to host',
|
||||||
|
EALREADY: 'Operation already in progress',
|
||||||
|
EINPROGRESS: 'Operation now in progress',
|
||||||
|
ESTALE: 'Stale file handle',
|
||||||
|
EUCLEAN: 'Structure needs cleaning',
|
||||||
|
ENOTNAM: 'Not a XENIX named type file',
|
||||||
|
ENAVAIL: 'No XENIX semaphores available',
|
||||||
|
EISNAM: 'Is a named type file',
|
||||||
|
EREMOTEIO: 'Remote I/O error',
|
||||||
|
EDQUOT: 'Quota exceeded',
|
||||||
|
ENOMEDIUM: 'No medium found',
|
||||||
|
EMEDIUMTYPE: 'Wrong medium type',
|
||||||
|
ECANCELED: 'Operation canceled',
|
||||||
|
ENOKEY: 'Required key not available',
|
||||||
|
EKEYEXPIRED: 'Key has expired',
|
||||||
|
EKEYREVOKED: 'Key has been revoked',
|
||||||
|
EKEYREJECTED: 'Key was rejected by service',
|
||||||
|
EOWNERDEAD: 'Owner died',
|
||||||
|
ENOTRECOVERABLE: 'State not recoverable',
|
||||||
|
ERFKILL: 'Operation not possible due to RF-kill',
|
||||||
|
EHWPOISON: 'Memory page has hardware error',
|
||||||
|
}
|
||||||
|
|
||||||
|
def strerror(errnum):
|
||||||
|
"""
|
||||||
|
Translate Linux errno to string.
|
||||||
|
|
||||||
|
>>> strerror(ENOKEY)
|
||||||
|
'Required key not available'
|
||||||
|
>>> strerror(41)
|
||||||
|
'Unknown error 41'
|
||||||
|
"""
|
||||||
|
ret = _strerror.get(errnum)
|
||||||
|
if ret:
|
||||||
|
return ret
|
||||||
|
return 'Unknown error {0}'.format(errnum)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
import doctest
|
||||||
|
doctest.testmod()
|
||||||
@@ -0,0 +1,379 @@
|
|||||||
|
#! /usr/bin/env python
|
||||||
|
|
||||||
|
"""
|
||||||
|
Integer number allocator.
|
||||||
|
|
||||||
|
Basically, these keep track of a set of allocatable values in
|
||||||
|
some range (you provide min and max) and let you allocate out of
|
||||||
|
the range and return values into the range.
|
||||||
|
|
||||||
|
You may pick a value using "next since last time", or "next
|
||||||
|
available after provided value". Note that next-after will
|
||||||
|
wrap around as needed (modular arithmetic style).
|
||||||
|
|
||||||
|
The free lists are thread-locked so that this code can be used
|
||||||
|
with threads.
|
||||||
|
|
||||||
|
>>> a = NumAlloc(5, 10) # note closed interval: 5..10 inclusive
|
||||||
|
>>> a
|
||||||
|
NumAlloc(5, 10)
|
||||||
|
>>> a.avail
|
||||||
|
[[5, 10]]
|
||||||
|
>>> a.alloc()
|
||||||
|
5
|
||||||
|
>>> a.avail
|
||||||
|
[[6, 10]]
|
||||||
|
>>> a.alloc(8)
|
||||||
|
8
|
||||||
|
>>> a.avail
|
||||||
|
[[6, 7], [9, 10]]
|
||||||
|
>>> a.free(5)
|
||||||
|
>>> a.avail
|
||||||
|
[[5, 7], [9, 10]]
|
||||||
|
>>> a.free(8)
|
||||||
|
>>> a.avail
|
||||||
|
[[5, 10]]
|
||||||
|
|
||||||
|
Attempting to free a value that is already free is an error:
|
||||||
|
|
||||||
|
>>> a.free(5)
|
||||||
|
Traceback (most recent call last):
|
||||||
|
...
|
||||||
|
ValueError: free: 5 already available
|
||||||
|
|
||||||
|
You can, however, free a value that is outside the min/max
|
||||||
|
range. You can also free multiple values at once:
|
||||||
|
|
||||||
|
>>> a.free_multi([0, 1, 2, 4])
|
||||||
|
>>> a.avail
|
||||||
|
[[0, 2], [4, 10]]
|
||||||
|
>>> a.free_multi([3, 12])
|
||||||
|
>>> a.avail
|
||||||
|
[[0, 10], [12, 12]]
|
||||||
|
|
||||||
|
Note that this changes the min/max values:
|
||||||
|
|
||||||
|
>>> a
|
||||||
|
NumAlloc(0, 12)
|
||||||
|
|
||||||
|
To prevent adding values outside the min/max range, create the
|
||||||
|
NumArray with autoextend=False, or set .autoextend=False at any
|
||||||
|
time:
|
||||||
|
|
||||||
|
>>> a.autoextend = False
|
||||||
|
>>> a
|
||||||
|
NumAlloc(0, 12, autoextend=False)
|
||||||
|
>>> a.free(13)
|
||||||
|
Traceback (most recent call last):
|
||||||
|
...
|
||||||
|
ValueError: free: 13 is outside range limit
|
||||||
|
|
||||||
|
You can create an empty range, which is really only useful once
|
||||||
|
you free values into it:
|
||||||
|
|
||||||
|
>>> r = NumAlloc(0, -1)
|
||||||
|
>>> r
|
||||||
|
NumAlloc(0, -1)
|
||||||
|
>>> r.alloc() is None
|
||||||
|
True
|
||||||
|
>>> r.free_multi(range(50))
|
||||||
|
>>> r
|
||||||
|
NumAlloc(0, 49)
|
||||||
|
|
||||||
|
Note that r.alloc() starts from where you last left off, even if
|
||||||
|
you've freed a value:
|
||||||
|
|
||||||
|
>>> r.alloc()
|
||||||
|
0
|
||||||
|
>>> r.free(0)
|
||||||
|
>>> r.alloc()
|
||||||
|
1
|
||||||
|
|
||||||
|
Of course, in multithreaded code you can't really depend on this
|
||||||
|
since it will race other threads. Still, it generally makes for
|
||||||
|
efficient allocation. To force allocation to start from the
|
||||||
|
range's minimum, provide the minimum (e.g., r.min_val) as an
|
||||||
|
argument to r.alloc():
|
||||||
|
|
||||||
|
>>> r.alloc()
|
||||||
|
2
|
||||||
|
>>> r.alloc(r.min_val)
|
||||||
|
0
|
||||||
|
|
||||||
|
Providing a number to alloc() tries to allocate that number,
|
||||||
|
but wraps around to the next one if needed:
|
||||||
|
|
||||||
|
>>> r.alloc(49)
|
||||||
|
49
|
||||||
|
>>> r.alloc(49)
|
||||||
|
3
|
||||||
|
>>> r.alloc(99999)
|
||||||
|
4
|
||||||
|
>>> r.avail
|
||||||
|
[[5, 48]]
|
||||||
|
|
||||||
|
There is currently no way to find all allocated values, although
|
||||||
|
the obvious method (going through r.avail) will work. Any iterator
|
||||||
|
would not be thread-safe.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import threading
|
||||||
|
|
||||||
|
class NumAlloc(object):
|
||||||
|
"""
|
||||||
|
Number allocator object.
|
||||||
|
"""
|
||||||
|
def __init__(self, min_val, max_val, autoextend=True):
|
||||||
|
self.min_val = min_val
|
||||||
|
self.max_val = max_val
|
||||||
|
if min_val <= max_val:
|
||||||
|
self.avail = [[min_val, max_val]]
|
||||||
|
else:
|
||||||
|
self.avail = []
|
||||||
|
self.autoextend = autoextend
|
||||||
|
self.last = None
|
||||||
|
self.lock = threading.Lock()
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
myname = self.__class__.__name__
|
||||||
|
if self.autoextend:
|
||||||
|
ae = ''
|
||||||
|
else:
|
||||||
|
ae = ', autoextend=False'
|
||||||
|
return '{0}({1}, {2}{3})'.format(myname, self.min_val, self.max_val, ae)
|
||||||
|
|
||||||
|
def _find_block(self, val):
|
||||||
|
"""
|
||||||
|
Find the block that contains val, or that should contain val.
|
||||||
|
Remember that self.avail is a list of avaliable ranges of
|
||||||
|
the form [[min1, max1], [min2, max2], ..., [minN, maxN]]
|
||||||
|
where max1 < min2, max2 < min3, ..., < minN.
|
||||||
|
|
||||||
|
The input value either falls into one of the available
|
||||||
|
blocks, or falls into a gap between two available blocks.
|
||||||
|
We want to know which block it goes in, or if it goes
|
||||||
|
between two, which block it comes before.
|
||||||
|
|
||||||
|
We can do a binary search to find this block. When we
|
||||||
|
find it, return its index and its values.
|
||||||
|
|
||||||
|
If we find that val is not in a block, return the position
|
||||||
|
where the value should go, were it to be put into a new
|
||||||
|
block by itself. E.g., suppose val is 17, and there is a
|
||||||
|
block [14,16] and a block [18,20]. We would make this
|
||||||
|
[14,16],[17,17],[18,20] by inserting [17,17] between them.
|
||||||
|
(Afterward, we will want to fuse all three blocks to make
|
||||||
|
[14,18]. However, if we insert as block 0, e.g., if the
|
||||||
|
list starts with [18,20] and we insert to get
|
||||||
|
[17,17][18,20], we really end up just modifying block 0 to
|
||||||
|
[17,20]. Or, if we insert as the new final block, we
|
||||||
|
might end up modifying the last block.)
|
||||||
|
"""
|
||||||
|
low = 0
|
||||||
|
high = len(self.avail) - 1
|
||||||
|
while low <= high:
|
||||||
|
mid = low + ((high - low) // 2)
|
||||||
|
pair = self.avail[mid]
|
||||||
|
if val < pair[0]:
|
||||||
|
# must go before block mid
|
||||||
|
high = mid - 1
|
||||||
|
elif val > pair[1]:
|
||||||
|
# must go after block mid
|
||||||
|
low = mid + 1
|
||||||
|
else:
|
||||||
|
# val >= first and val <= last, so we found it
|
||||||
|
return mid, pair
|
||||||
|
# Low > high: no block actually contains val, or
|
||||||
|
# there are no blocks at all. If there are no blocks,
|
||||||
|
# return block #0 and None. Otherwise return the
|
||||||
|
return low, None
|
||||||
|
|
||||||
|
def alloc(self, val=None):
|
||||||
|
"""
|
||||||
|
Get new available value.
|
||||||
|
|
||||||
|
If val is None, we start from the most recently
|
||||||
|
allocated value, plus 1.
|
||||||
|
|
||||||
|
If val is a numeric value, we start from that value.
|
||||||
|
Hence, since the range is min_val..max_val, you can
|
||||||
|
provide min_val to take the first available value.
|
||||||
|
|
||||||
|
This may return None, if no values are still available.
|
||||||
|
"""
|
||||||
|
with self.lock:
|
||||||
|
if val is None:
|
||||||
|
val = self.last + 1 if self.last is not None else self.min_val
|
||||||
|
if val is None or val > self.max_val or val < self.min_val:
|
||||||
|
val = self.min_val
|
||||||
|
i, pair = self._find_block(val)
|
||||||
|
if pair is None:
|
||||||
|
# Value is is not available. The next
|
||||||
|
# available value that is greater than val
|
||||||
|
# is in the block right after block i.
|
||||||
|
# If there is no block after i, the next
|
||||||
|
# available value is in block 0. If there
|
||||||
|
# is no block 0, there are no available
|
||||||
|
# values.
|
||||||
|
nblocks = len(self.avail)
|
||||||
|
i += 1
|
||||||
|
if i >= nblocks:
|
||||||
|
if nblocks == 0:
|
||||||
|
return None
|
||||||
|
i = 0
|
||||||
|
pair = self.avail[i]
|
||||||
|
val = pair[0]
|
||||||
|
# Value val is available - take it.
|
||||||
|
#
|
||||||
|
# There are four special cases to handle.
|
||||||
|
#
|
||||||
|
# 1. pair[0] < val < pair[1]: split the pair.
|
||||||
|
# 2. pair[0] == val < pair[1]: increase pair[0].
|
||||||
|
# 3. pair[0] == val == pair[1]: delete the pair
|
||||||
|
# 4. pair[0] < val == pair[1]: decrease pair[1].
|
||||||
|
assert pair[0] <= val <= pair[1]
|
||||||
|
if pair[0] == val:
|
||||||
|
# case 2 or 3: Take the left edge or delete the pair.
|
||||||
|
if val == pair[1]:
|
||||||
|
del self.avail[i]
|
||||||
|
else:
|
||||||
|
pair[0] = val + 1
|
||||||
|
else:
|
||||||
|
# case 1 or 4: split the pair or take the right edge.
|
||||||
|
if val == pair[1]:
|
||||||
|
pair[1] = val - 1
|
||||||
|
else:
|
||||||
|
newpair = [val + 1, pair[1]]
|
||||||
|
pair[1] = val - 1
|
||||||
|
self.avail.insert(i + 1, newpair)
|
||||||
|
self.last = val
|
||||||
|
return val
|
||||||
|
|
||||||
|
def free(self, val):
|
||||||
|
"Free one value"
|
||||||
|
self._free_multi('free', [val])
|
||||||
|
|
||||||
|
def free_multi(self, values):
|
||||||
|
"Free many values (provide any iterable)"
|
||||||
|
values = list(values)
|
||||||
|
values.sort()
|
||||||
|
self._free_multi('free_multi', values)
|
||||||
|
|
||||||
|
def _free_multi(self, how, values):
|
||||||
|
"""
|
||||||
|
Free a (sorted) list of values.
|
||||||
|
"""
|
||||||
|
if len(values) == 0:
|
||||||
|
return
|
||||||
|
with self.lock:
|
||||||
|
while values:
|
||||||
|
# Take highest value, and any contiguous lower values.
|
||||||
|
# Note that it can be significantly faster this way
|
||||||
|
# since coalesced ranges make for shorter copies.
|
||||||
|
highval = values.pop()
|
||||||
|
val = highval
|
||||||
|
while len(values) and values[-1] == val - 1:
|
||||||
|
val = values.pop()
|
||||||
|
self._free_range(how, val, highval)
|
||||||
|
|
||||||
|
def _maybe_increase_max(self, how, val):
|
||||||
|
"""
|
||||||
|
If needed, widen our range to include new high val -- i.e.,
|
||||||
|
possibly increase self.max_val. Do nothing if this is not a
|
||||||
|
new all time high; fail if we have autoextend disabled.
|
||||||
|
"""
|
||||||
|
if val <= self.max_val:
|
||||||
|
return
|
||||||
|
if self.autoextend:
|
||||||
|
self.max_val = val
|
||||||
|
return
|
||||||
|
raise ValueError('{0}: {1} is outside range limit'.format(how, val))
|
||||||
|
|
||||||
|
def _maybe_decrease_min(self, how, val):
|
||||||
|
"""
|
||||||
|
If needed, widen our range to include new low val -- i.e.,
|
||||||
|
possibly decrease self.min_val. Do nothing if this is not a
|
||||||
|
new all time low; fail if we have autoextend disabled.
|
||||||
|
"""
|
||||||
|
if val >= self.min_val:
|
||||||
|
return
|
||||||
|
if self.autoextend:
|
||||||
|
self.min_val = val
|
||||||
|
return
|
||||||
|
raise ValueError('{0}: {1} is outside range limit'.format(how, val))
|
||||||
|
|
||||||
|
def _free_range(self, how, val, highval):
|
||||||
|
"""
|
||||||
|
Free the range [val..highval]. Note, val==highval it's just
|
||||||
|
a one-element range.
|
||||||
|
|
||||||
|
The lock is already held.
|
||||||
|
"""
|
||||||
|
# Find the place to store the lower value.
|
||||||
|
# We should never find an actual pair here.
|
||||||
|
i, pair = self._find_block(val)
|
||||||
|
if pair:
|
||||||
|
raise ValueError('{0}: {1} already available'.format(how, val))
|
||||||
|
# If we're freeing a range, check that the high val
|
||||||
|
# does not span into the *next* range, either.
|
||||||
|
if highval > val and i < len(self.avail):
|
||||||
|
if self.avail[i][0] <= highval:
|
||||||
|
raise ValueError('{0}: {2} (from {{1}..{2}) already '
|
||||||
|
'available'.format(how, val, highval))
|
||||||
|
|
||||||
|
# We'll need to insert a block and perhaps fuse it
|
||||||
|
# with blocks before and/or after. First, check
|
||||||
|
# whether there *is* a before and/or after, and find
|
||||||
|
# their corresponding edges and whether we abut them.
|
||||||
|
if i > 0:
|
||||||
|
abuts_below = self.avail[i - 1][1] + 1 == val
|
||||||
|
else:
|
||||||
|
abuts_below = False
|
||||||
|
if i < len(self.avail):
|
||||||
|
abuts_above = self.avail[i][0] - 1 == highval
|
||||||
|
else:
|
||||||
|
abuts_above = False
|
||||||
|
# Now there are these four cases:
|
||||||
|
# 1. abuts below and above: fuse the two blocks.
|
||||||
|
# 2. abuts below only: adjust previous (i-1'th) block
|
||||||
|
# 3. abuts above only: adjust next (i'th) block
|
||||||
|
# 4. doesn't abut: insert new block
|
||||||
|
if abuts_below:
|
||||||
|
if abuts_above:
|
||||||
|
# case 1
|
||||||
|
self.avail[i - 1][1] = self.avail[i][1]
|
||||||
|
del self.avail[i]
|
||||||
|
else:
|
||||||
|
# case 2
|
||||||
|
self._maybe_increase_max(how, highval)
|
||||||
|
self.avail[i - 1][1] = highval
|
||||||
|
else:
|
||||||
|
if abuts_above:
|
||||||
|
# case 3
|
||||||
|
self._maybe_decrease_min(how, val)
|
||||||
|
self.avail[i][0] = val
|
||||||
|
else:
|
||||||
|
# case 4
|
||||||
|
self._maybe_decrease_min(how, val)
|
||||||
|
self._maybe_increase_max(how, highval)
|
||||||
|
newblock = [val, highval]
|
||||||
|
self.avail.insert(i, newblock)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
import doctest
|
||||||
|
import sys
|
||||||
|
|
||||||
|
doctest.testmod()
|
||||||
|
if sys.version_info[0] >= 3:
|
||||||
|
xrange = range
|
||||||
|
# run some worst case tests
|
||||||
|
# NB: coalesce is terribly slow when done bottom up
|
||||||
|
r = NumAlloc(0, 2**16 - 1)
|
||||||
|
for i in xrange(r.min_val, r.max_val, 2):
|
||||||
|
r.alloc(i)
|
||||||
|
print('worst case alloc: len(r.avail) = {0}'.format(len(r.avail)))
|
||||||
|
for i in xrange(r.max_val - 1, r.min_val, -2):
|
||||||
|
r.free(i)
|
||||||
|
print('free again; len(r.avail) should be 1; is {0}'.format(len(r.avail)))
|
||||||
|
if len(r.avail) != 1:
|
||||||
|
sys.exit('failure')
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,146 @@
|
|||||||
|
#! /usr/bin/env python
|
||||||
|
|
||||||
|
"""
|
||||||
|
Error number definitions for 9P2000, .u, and .L.
|
||||||
|
|
||||||
|
Note that there is no native-to-9P2000 (plain) translation
|
||||||
|
table since 9P2000 takes error *strings* rather than error
|
||||||
|
*numbers*.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import errno as _errno
|
||||||
|
import lerrno as _lerrno
|
||||||
|
import os as _os
|
||||||
|
|
||||||
|
_native_to_dotu = {
|
||||||
|
# These are in the "standard" range(1, errno.ERANGE)
|
||||||
|
# but do not map to themselves, so map them here first.
|
||||||
|
_errno.ENOTEMPTY: _errno.EPERM,
|
||||||
|
_errno.EDQUOT: _errno.EPERM,
|
||||||
|
_errno.ENOSYS: _errno.EPERM,
|
||||||
|
}
|
||||||
|
|
||||||
|
_native_to_dotl = {}
|
||||||
|
|
||||||
|
# Add standard errno's.
|
||||||
|
for _i in range(1, _errno.ERANGE):
|
||||||
|
_native_to_dotu.setdefault(_i, _i)
|
||||||
|
_native_to_dotl[_i] = _i
|
||||||
|
|
||||||
|
# Add linux errno's. Note that Linux EAGAIN at #11 overrides BSD EDEADLK,
|
||||||
|
# but Linux has EDEADLK at #35 which overrides BSD EAGAIN, so it all
|
||||||
|
# works out.
|
||||||
|
#
|
||||||
|
# We just list every BSD error name here, since the hasattr()s do
|
||||||
|
# the real work.
|
||||||
|
for _i in (
|
||||||
|
'EDEADLK',
|
||||||
|
'EAGAIN',
|
||||||
|
'EINPROGRESS',
|
||||||
|
'EALREADY',
|
||||||
|
'ENOTSOCK',
|
||||||
|
'EDESTADDRREQ',
|
||||||
|
'EMSGSIZE',
|
||||||
|
'EPROTOTYPE',
|
||||||
|
'ENOPROTOOPT',
|
||||||
|
'EPROTONOSUPPORT',
|
||||||
|
'ESOCKTNOSUPPORT',
|
||||||
|
'EOPNOTSUPP',
|
||||||
|
'EPFNOSUPPORT',
|
||||||
|
'EAFNOSUPPORT',
|
||||||
|
'EADDRINUSE',
|
||||||
|
'EADDRNOTAVAIL',
|
||||||
|
'ENETDOWN',
|
||||||
|
'ENETUNREACH',
|
||||||
|
'ENETRESET',
|
||||||
|
'ECONNABORTED',
|
||||||
|
'ECONNRESET',
|
||||||
|
'ENOBUFS',
|
||||||
|
'EISCONN',
|
||||||
|
'ENOTCONN',
|
||||||
|
'ESHUTDOWN',
|
||||||
|
'ETOOMANYREFS',
|
||||||
|
'ETIMEDOUT',
|
||||||
|
'ECONNREFUSED',
|
||||||
|
'ELOOP',
|
||||||
|
'ENAMETOOLONG',
|
||||||
|
'EHOSTDOWN',
|
||||||
|
'EHOSTUNREACH',
|
||||||
|
'ENOTEMPTY',
|
||||||
|
'EPROCLIM',
|
||||||
|
'EUSERS',
|
||||||
|
'EDQUOT',
|
||||||
|
'ESTALE',
|
||||||
|
'EREMOTE',
|
||||||
|
'EBADRPC',
|
||||||
|
'ERPCMISMATCH',
|
||||||
|
'EPROGUNAVAIL',
|
||||||
|
'EPROGMISMATCH',
|
||||||
|
'EPROCUNAVAIL',
|
||||||
|
'ENOLCK',
|
||||||
|
'ENOSYS',
|
||||||
|
'EFTYPE',
|
||||||
|
'EAUTH',
|
||||||
|
'ENEEDAUTH',
|
||||||
|
'EIDRM',
|
||||||
|
'ENOMSG',
|
||||||
|
'EOVERFLOW',
|
||||||
|
'ECANCELED',
|
||||||
|
'EILSEQ',
|
||||||
|
'EDOOFUS',
|
||||||
|
'EBADMSG',
|
||||||
|
'EMULTIHOP',
|
||||||
|
'ENOLINK',
|
||||||
|
'EPROTO',
|
||||||
|
'ENOTCAPABLE',
|
||||||
|
'ECAPMODE',
|
||||||
|
'ENOTRECOVERABLE',
|
||||||
|
'EOWNERDEAD',
|
||||||
|
):
|
||||||
|
if hasattr(_errno, _i) and hasattr(_lerrno, _i):
|
||||||
|
_native_to_dotl[getattr(_errno, _i)] = getattr(_lerrno, _i)
|
||||||
|
del _i
|
||||||
|
|
||||||
|
def to_dotu(errnum):
|
||||||
|
"""
|
||||||
|
Translate native errno to 9P2000.u errno.
|
||||||
|
|
||||||
|
>>> import errno
|
||||||
|
>>> to_dotu(errno.EIO)
|
||||||
|
5
|
||||||
|
>>> to_dotu(errno.EDQUOT)
|
||||||
|
1
|
||||||
|
>>> to_dotu(errno.ELOOP)
|
||||||
|
5
|
||||||
|
|
||||||
|
There is a corresponding dotu_strerror() (which is really
|
||||||
|
just os.strerror):
|
||||||
|
|
||||||
|
>>> dotu_strerror(5)
|
||||||
|
'Input/output error'
|
||||||
|
|
||||||
|
"""
|
||||||
|
return _native_to_dotu.get(errnum, _errno.EIO) # default to EIO
|
||||||
|
|
||||||
|
def to_dotl(errnum):
|
||||||
|
"""
|
||||||
|
Translate native errno to 9P2000.L errno.
|
||||||
|
|
||||||
|
>>> import errno
|
||||||
|
>>> to_dotl(errno.ELOOP)
|
||||||
|
40
|
||||||
|
|
||||||
|
There is a corresponding dotl_strerror():
|
||||||
|
|
||||||
|
>>> dotl_strerror(40)
|
||||||
|
'Too many levels of symbolic links'
|
||||||
|
"""
|
||||||
|
return _native_to_dotl.get(errnum, _lerrno.ENOTRECOVERABLE)
|
||||||
|
|
||||||
|
dotu_strerror = _os.strerror
|
||||||
|
|
||||||
|
dotl_strerror = _lerrno.strerror
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
import doctest
|
||||||
|
doctest.testmod()
|
||||||
@@ -0,0 +1,204 @@
|
|||||||
|
#! /usr/bin/env python
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
|
__all__ = ['pfod', 'OrderedDict']
|
||||||
|
|
||||||
|
### shameless stealing from namedtuple here
|
||||||
|
|
||||||
|
"""
|
||||||
|
pfod - prefilled OrderedDict
|
||||||
|
|
||||||
|
This is basically a hybrid of a class and an OrderedDict,
|
||||||
|
or, sort of a data-only class. When an instance of the
|
||||||
|
class is created, all its fields are set to None if not
|
||||||
|
initialized.
|
||||||
|
|
||||||
|
Because it is an OrderedDict you can add extra fields to an
|
||||||
|
instance, and they will be in inst.keys(). Because it
|
||||||
|
behaves in a class-like way, if the keys are 'foo' and 'bar'
|
||||||
|
you can write print(inst.foo) or inst.bar = 3. Setting an
|
||||||
|
attribute that does not currently exist causes a new key
|
||||||
|
to be added to the instance.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys as _sys
|
||||||
|
from keyword import iskeyword as _iskeyword
|
||||||
|
from collections import OrderedDict
|
||||||
|
from collections import deque as _deque
|
||||||
|
|
||||||
|
_class_template = '''\
|
||||||
|
class {typename}(OrderedDict):
|
||||||
|
'{typename}({arg_list})'
|
||||||
|
__slots__ = ()
|
||||||
|
|
||||||
|
_fields = {field_names!r}
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
'Create new instance of {typename}()'
|
||||||
|
super({typename}, self).__init__()
|
||||||
|
args = _deque(args)
|
||||||
|
for field in self._fields:
|
||||||
|
if field in kwargs:
|
||||||
|
self[field] = kwargs.pop(field)
|
||||||
|
elif len(args) > 0:
|
||||||
|
self[field] = args.popleft()
|
||||||
|
else:
|
||||||
|
self[field] = None
|
||||||
|
if len(kwargs):
|
||||||
|
raise TypeError('unexpected kwargs %s' % kwargs.keys())
|
||||||
|
if len(args):
|
||||||
|
raise TypeError('unconsumed args %r' % tuple(args))
|
||||||
|
|
||||||
|
def _copy(self):
|
||||||
|
'copy to new instance'
|
||||||
|
new = {typename}()
|
||||||
|
new.update(self)
|
||||||
|
return new
|
||||||
|
|
||||||
|
def __getattr__(self, attr):
|
||||||
|
if attr in self:
|
||||||
|
return self[attr]
|
||||||
|
raise AttributeError('%r object has no attribute %r' %
|
||||||
|
(self.__class__.__name__, attr))
|
||||||
|
|
||||||
|
def __setattr__(self, attr, val):
|
||||||
|
if attr.startswith('_OrderedDict_'):
|
||||||
|
super({typename}, self).__setattr__(attr, val)
|
||||||
|
else:
|
||||||
|
self[attr] = val
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
'Return a nicely formatted representation string'
|
||||||
|
return '{typename}({repr_fmt})'.format(**self)
|
||||||
|
'''
|
||||||
|
|
||||||
|
_repr_template = '{name}={{{name}!r}}'
|
||||||
|
|
||||||
|
# Workaround for py2k exec-as-statement, vs py3k exec-as-function.
|
||||||
|
# Since the syntax differs, we have to exec the definition of _exec!
|
||||||
|
if _sys.version_info[0] < 3:
|
||||||
|
# py2k: need a real function. (There is a way to deal with
|
||||||
|
# this without a function if the py2k is new enough, but this
|
||||||
|
# works in more cases.)
|
||||||
|
exec("""def _exec(string, gdict, ldict):
|
||||||
|
"Python 2: exec string in gdict, ldict"
|
||||||
|
exec string in gdict, ldict""")
|
||||||
|
else:
|
||||||
|
# py3k: just make an alias for builtin function exec
|
||||||
|
exec("_exec = exec")
|
||||||
|
|
||||||
|
def pfod(typename, field_names, verbose=False, rename=False):
|
||||||
|
"""
|
||||||
|
Return a new subclass of OrderedDict with named fields.
|
||||||
|
|
||||||
|
Fields are accessible by name. Note that this means
|
||||||
|
that to copy a PFOD you must use _copy() - field names
|
||||||
|
may not start with '_' unless they are all numeric.
|
||||||
|
|
||||||
|
When creating an instance of the new class, fields
|
||||||
|
that are not initialized are set to None.
|
||||||
|
|
||||||
|
>>> Point = pfod('Point', ['x', 'y'])
|
||||||
|
>>> Point.__doc__ # docstring for the new class
|
||||||
|
'Point(x, y)'
|
||||||
|
>>> p = Point(11, y=22) # instantiate with positional args or keywords
|
||||||
|
>>> p
|
||||||
|
Point(x=11, y=22)
|
||||||
|
>>> p['x'] + p['y'] # indexable
|
||||||
|
33
|
||||||
|
>>> p.x + p.y # fields also accessable by name
|
||||||
|
33
|
||||||
|
>>> p._copy()
|
||||||
|
Point(x=11, y=22)
|
||||||
|
>>> p2 = Point()
|
||||||
|
>>> p2.extra = 2
|
||||||
|
>>> p2
|
||||||
|
Point(x=None, y=None)
|
||||||
|
>>> p2.extra
|
||||||
|
2
|
||||||
|
>>> p2['extra']
|
||||||
|
2
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Validate the field names. At the user's option, either generate an error
|
||||||
|
if _sys.version_info[0] >= 3:
|
||||||
|
string_type = str
|
||||||
|
else:
|
||||||
|
string_type = basestring
|
||||||
|
# message or automatically replace the field name with a valid name.
|
||||||
|
if isinstance(field_names, string_type):
|
||||||
|
field_names = field_names.replace(',', ' ').split()
|
||||||
|
field_names = list(map(str, field_names))
|
||||||
|
typename = str(typename)
|
||||||
|
if rename:
|
||||||
|
seen = set()
|
||||||
|
for index, name in enumerate(field_names):
|
||||||
|
if (not all(c.isalnum() or c=='_' for c in name)
|
||||||
|
or _iskeyword(name)
|
||||||
|
or not name
|
||||||
|
or name[0].isdigit()
|
||||||
|
or name.startswith('_')
|
||||||
|
or name in seen):
|
||||||
|
field_names[index] = '_%d' % index
|
||||||
|
seen.add(name)
|
||||||
|
for name in [typename] + field_names:
|
||||||
|
if type(name) != str:
|
||||||
|
raise TypeError('Type names and field names must be strings')
|
||||||
|
if not all(c.isalnum() or c=='_' for c in name):
|
||||||
|
raise ValueError('Type names and field names can only contain '
|
||||||
|
'alphanumeric characters and underscores: %r' % name)
|
||||||
|
if _iskeyword(name):
|
||||||
|
raise ValueError('Type names and field names cannot be a '
|
||||||
|
'keyword: %r' % name)
|
||||||
|
if name[0].isdigit():
|
||||||
|
raise ValueError('Type names and field names cannot start with '
|
||||||
|
'a number: %r' % name)
|
||||||
|
seen = set()
|
||||||
|
for name in field_names:
|
||||||
|
if name.startswith('_OrderedDict_'):
|
||||||
|
raise ValueError('Field names cannot start with _OrderedDict_: '
|
||||||
|
'%r' % name)
|
||||||
|
if name.startswith('_') and not rename:
|
||||||
|
raise ValueError('Field names cannot start with an underscore: '
|
||||||
|
'%r' % name)
|
||||||
|
if name in seen:
|
||||||
|
raise ValueError('Encountered duplicate field name: %r' % name)
|
||||||
|
seen.add(name)
|
||||||
|
|
||||||
|
# Fill-in the class template
|
||||||
|
class_definition = _class_template.format(
|
||||||
|
typename = typename,
|
||||||
|
field_names = tuple(field_names),
|
||||||
|
arg_list = repr(tuple(field_names)).replace("'", "")[1:-1],
|
||||||
|
repr_fmt = ', '.join(_repr_template.format(name=name)
|
||||||
|
for name in field_names),
|
||||||
|
)
|
||||||
|
if verbose:
|
||||||
|
print(class_definition,
|
||||||
|
file=verbose if isinstance(verbose, file) else _sys.stdout)
|
||||||
|
|
||||||
|
# Execute the template string in a temporary namespace and support
|
||||||
|
# tracing utilities by setting a value for frame.f_globals['__name__']
|
||||||
|
namespace = dict(__name__='PFOD%s' % typename,
|
||||||
|
OrderedDict=OrderedDict, _deque=_deque)
|
||||||
|
try:
|
||||||
|
_exec(class_definition, namespace, namespace)
|
||||||
|
except SyntaxError as e:
|
||||||
|
raise SyntaxError(e.message + ':\n' + class_definition)
|
||||||
|
result = namespace[typename]
|
||||||
|
|
||||||
|
# For pickling to work, the __module__ variable needs to be set to the frame
|
||||||
|
# where the named tuple is created. Bypass this step in environments where
|
||||||
|
# sys._getframe is not defined (Jython for example) or sys._getframe is not
|
||||||
|
# defined for arguments greater than 0 (IronPython).
|
||||||
|
try:
|
||||||
|
result.__module__ = _sys._getframe(1).f_globals.get('__name__', '__main__')
|
||||||
|
except (AttributeError, ValueError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
import doctest
|
||||||
|
doctest.testmod()
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,653 @@
|
|||||||
|
#! /usr/bin/env python
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
|
#__all__ = ['EncDec', 'EncDecSimple', 'EncDecTyped', 'EncDecA',
|
||||||
|
# 'SequenceError', 'Sequencer']
|
||||||
|
|
||||||
|
import abc
|
||||||
|
import struct
|
||||||
|
import sys
|
||||||
|
|
||||||
|
_ProtoStruct = {
|
||||||
|
'1': struct.Struct('<B'),
|
||||||
|
'2': struct.Struct('<H'),
|
||||||
|
'4': struct.Struct('<I'),
|
||||||
|
'8': struct.Struct('<Q'),
|
||||||
|
'_string_': None, # handled specially
|
||||||
|
}
|
||||||
|
for _i in (1, 2, 4, 8):
|
||||||
|
_ProtoStruct[_i] = _ProtoStruct[str(_i)]
|
||||||
|
del _i
|
||||||
|
|
||||||
|
class EncDec(object):
|
||||||
|
__metaclass__ = abc.ABCMeta
|
||||||
|
"""
|
||||||
|
Base class for en/de-coders, which are put into sequencers.
|
||||||
|
|
||||||
|
All have a name and arbitrary user-supplied auxiliary data
|
||||||
|
(default=None).
|
||||||
|
|
||||||
|
All provide a pack() and unpack(). The pack() function
|
||||||
|
returns a "bytes" value. This is internally implemented as a
|
||||||
|
function apack() that returns a list of struct.pack() bytes,
|
||||||
|
and pack() just joins them up as needed.
|
||||||
|
|
||||||
|
The pack/unpack functions take a dictionary of variable names
|
||||||
|
and values, and a second dictionary for conditionals, but at
|
||||||
|
this level conditionals don't apply: they are just being
|
||||||
|
passed through. Variable names do apply to array encoders
|
||||||
|
|
||||||
|
EncDec also provide b2s() and s2b() static methods, which
|
||||||
|
convert strings to bytes and vice versa, as reversibly as
|
||||||
|
possible (using surrogateescape encoding). In Python2 this is
|
||||||
|
a no-op since the string type *is* the bytes type (<type
|
||||||
|
'unicode'>) is the unicode-ized string type).
|
||||||
|
|
||||||
|
EncDec also provides b2u() and u2b() to do conversion to/from
|
||||||
|
Unicode.
|
||||||
|
|
||||||
|
These are partly for internal use (all strings get converted
|
||||||
|
to UTF-8 byte sequences when coding a _string_ type) and partly
|
||||||
|
for doctests, where we just want some py2k/py3k compat hacks.
|
||||||
|
"""
|
||||||
|
def __init__(self, name, aux):
|
||||||
|
self.name = name
|
||||||
|
self.aux = aux
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def b2u(byte_sequence):
|
||||||
|
"transform bytes to unicode"
|
||||||
|
return byte_sequence.decode('utf-8', 'surrogateescape')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def u2b(unicode_sequence):
|
||||||
|
"transform unicode to bytes"
|
||||||
|
return unicode_sequence.encode('utf-8', 'surrogateescape')
|
||||||
|
|
||||||
|
if sys.version_info[0] >= 3:
|
||||||
|
b2s = b2u
|
||||||
|
@staticmethod
|
||||||
|
def s2b(string):
|
||||||
|
"transform string to bytes (leaves raw byte sequence unchanged)"
|
||||||
|
if isinstance(string, bytes):
|
||||||
|
return string
|
||||||
|
return string.encode('utf-8', 'surrogateescape')
|
||||||
|
else:
|
||||||
|
@staticmethod
|
||||||
|
def b2s(byte_sequence):
|
||||||
|
"transform bytes to string - no-op in python2.7"
|
||||||
|
return byte_sequence
|
||||||
|
@staticmethod
|
||||||
|
def s2b(string):
|
||||||
|
"transform string or unicode to bytes"
|
||||||
|
if isinstance(string, unicode):
|
||||||
|
return string.encode('utf-8', 'surrogateescape')
|
||||||
|
return string
|
||||||
|
|
||||||
|
def pack(self, vdict, cdict, val):
|
||||||
|
"encode value <val> into a byte-string"
|
||||||
|
return b''.join(self.apack(vdict, cdict, val))
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def apack(self, vdict, cdict, val):
|
||||||
|
"encode value <val> into [bytes1, b2, ..., bN]"
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def unpack(self, vdict, cdict, bstring, offset, noerror=False):
|
||||||
|
"unpack bytes from <bstring> at <offset>"
|
||||||
|
|
||||||
|
|
||||||
|
class EncDecSimple(EncDec):
|
||||||
|
r"""
|
||||||
|
Encode/decode a simple (but named) field. The field is not an
|
||||||
|
array, which requires using EncDecA, nor a typed object
|
||||||
|
like a qid or stat instance -- those require a Sequence and
|
||||||
|
EncDecTyped.
|
||||||
|
|
||||||
|
The format is one of '1'/1, '2'/2, '4'/4, '8'/8, or '_string_'.
|
||||||
|
|
||||||
|
Note: using b2s here is purely a doctest/tetsmod python2/python3
|
||||||
|
compat hack. The output of e.pack is <type 'bytes'>; b2s
|
||||||
|
converts it to a string, purely for display purposes. (It might
|
||||||
|
be better to map py2 output to bytes but they just print as a
|
||||||
|
string anyway.) In normal use, you should not call b2s here.
|
||||||
|
|
||||||
|
>>> e = EncDecSimple('eggs', 2)
|
||||||
|
>>> e.b2s(e.pack({}, {}, 0))
|
||||||
|
'\x00\x00'
|
||||||
|
>>> e.b2s(e.pack({}, {}, 256))
|
||||||
|
'\x00\x01'
|
||||||
|
|
||||||
|
Values that cannot be packed produce a SequenceError:
|
||||||
|
|
||||||
|
>>> e.pack({}, {}, None)
|
||||||
|
Traceback (most recent call last):
|
||||||
|
...
|
||||||
|
SequenceError: failed while packing 'eggs'=None
|
||||||
|
>>> e.pack({}, {}, -1)
|
||||||
|
Traceback (most recent call last):
|
||||||
|
...
|
||||||
|
SequenceError: failed while packing 'eggs'=-1
|
||||||
|
|
||||||
|
Unpacking both returns a value, and tells how many bytes it
|
||||||
|
used out of the bytestring or byte-array argument. If there
|
||||||
|
are not enough bytes remaining at the starting offset, it
|
||||||
|
raises a SequenceError, unless noerror=True (then unset
|
||||||
|
values are None)
|
||||||
|
|
||||||
|
>>> e.unpack({}, {}, b'\x00\x01', 0)
|
||||||
|
(256, 2)
|
||||||
|
>>> e.unpack({}, {}, b'', 0)
|
||||||
|
Traceback (most recent call last):
|
||||||
|
...
|
||||||
|
SequenceError: out of data while unpacking 'eggs'
|
||||||
|
>>> e.unpack({}, {}, b'', 0, noerror=True)
|
||||||
|
(None, 2)
|
||||||
|
|
||||||
|
Note that strings can be provided as regular strings, byte
|
||||||
|
strings (same as regular strings in py2k), or Unicode strings
|
||||||
|
(same as regular strings in py3k). Unicode strings will be
|
||||||
|
converted to UTF-8 before being packed. Since this leaves
|
||||||
|
7-bit characters alone, these examples work in both py2k and
|
||||||
|
py3k. (Note: the UTF-8 encoding of u'\u1234' is
|
||||||
|
'\0xe1\0x88\0xb4' or 225, 136, 180. The b2i trick below is
|
||||||
|
another py2k vs py3k special case just for doctests: py2k
|
||||||
|
tries to display the utf-8 encoded data as a string.)
|
||||||
|
|
||||||
|
>>> e = EncDecSimple('spam', '_string_')
|
||||||
|
>>> e.b2s(e.pack({}, {}, 'p3=unicode,p2=bytes'))
|
||||||
|
'\x13\x00p3=unicode,p2=bytes'
|
||||||
|
|
||||||
|
>>> e.b2s(e.pack({}, {}, b'bytes'))
|
||||||
|
'\x05\x00bytes'
|
||||||
|
|
||||||
|
>>> import sys
|
||||||
|
>>> ispy3k = sys.version_info[0] >= 3
|
||||||
|
|
||||||
|
>>> b2i = lambda x: x if ispy3k else ord(x)
|
||||||
|
>>> [b2i(x) for x in e.pack({}, {}, u'\u1234')]
|
||||||
|
[3, 0, 225, 136, 180]
|
||||||
|
|
||||||
|
The byte length of the utf-8 data cannot exceed 65535 since
|
||||||
|
the encoding has the length as a 2-byte field (a la the
|
||||||
|
encoding for 'eggs' here). A too-long string produces
|
||||||
|
a SequenceError as well.
|
||||||
|
|
||||||
|
>>> e.pack({}, {}, 16384 * 'spam')
|
||||||
|
Traceback (most recent call last):
|
||||||
|
...
|
||||||
|
SequenceError: string too long (len=65536) while packing 'spam'
|
||||||
|
|
||||||
|
Unpacking strings produces byte arrays. (Of course,
|
||||||
|
in py2k these are also known as <type 'str'>.)
|
||||||
|
|
||||||
|
>>> unpacked = e.unpack({}, {}, b'\x04\x00data', 0)
|
||||||
|
>>> etype = bytes if ispy3k else str
|
||||||
|
>>> print(isinstance(unpacked[0], etype))
|
||||||
|
True
|
||||||
|
>>> e.b2s(unpacked[0])
|
||||||
|
'data'
|
||||||
|
>>> unpacked[1]
|
||||||
|
6
|
||||||
|
|
||||||
|
You may use e.b2s() to conver them to unicode strings in py3k,
|
||||||
|
or you may set e.autob2s. This still only really does
|
||||||
|
anything in py3k, since py2k strings *are* bytes, so it's
|
||||||
|
really just intended for doctest purposes (see EncDecA):
|
||||||
|
|
||||||
|
>>> e.autob2s = True
|
||||||
|
>>> e.unpack({}, {}, b'\x07\x00stringy', 0)
|
||||||
|
('stringy', 9)
|
||||||
|
"""
|
||||||
|
def __init__(self, name, fmt, aux=None):
|
||||||
|
super(EncDecSimple, self).__init__(name, aux)
|
||||||
|
self.fmt = fmt
|
||||||
|
self.struct = _ProtoStruct[fmt]
|
||||||
|
self.autob2s = False
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
if self.aux is None:
|
||||||
|
return '{0}({1!r}, {2!r})'.format(self.__class__.__name__,
|
||||||
|
self.name, self.fmt)
|
||||||
|
return '{0}({1!r}, {2!r}, {3!r})'.format(self.__class__.__name__,
|
||||||
|
self.name, self.fmt, self.aux)
|
||||||
|
|
||||||
|
__str__ = __repr__
|
||||||
|
|
||||||
|
def apack(self, vdict, cdict, val):
|
||||||
|
"encode a value"
|
||||||
|
try:
|
||||||
|
if self.struct:
|
||||||
|
return [self.struct.pack(val)]
|
||||||
|
sval = self.s2b(val)
|
||||||
|
if len(sval) > 65535:
|
||||||
|
raise SequenceError('string too long (len={0:d}) '
|
||||||
|
'while packing {1!r}'.format(len(sval), self.name))
|
||||||
|
return [EncDecSimple.string_len.pack(len(sval)), sval]
|
||||||
|
# Include AttributeError in case someone tries to, e.g.,
|
||||||
|
# pack name=None and self.s2b() tries to use .encode on it.
|
||||||
|
except (struct.error, AttributeError):
|
||||||
|
raise SequenceError('failed '
|
||||||
|
'while packing {0!r}={1!r}'.format(self.name, val))
|
||||||
|
|
||||||
|
def _unpack1(self, via, bstring, offset, noerror):
|
||||||
|
"internal function to unpack single item"
|
||||||
|
try:
|
||||||
|
tup = via.unpack_from(bstring, offset)
|
||||||
|
except struct.error as err:
|
||||||
|
if 'unpack_from requires a buffer of at least' in str(err):
|
||||||
|
if noerror:
|
||||||
|
return None, offset + via.size
|
||||||
|
raise SequenceError('out of data '
|
||||||
|
'while unpacking {0!r}'.format(self.name))
|
||||||
|
# not clear what to do here if noerror
|
||||||
|
raise SequenceError('failed '
|
||||||
|
'while unpacking {0!r}'.format(self.name))
|
||||||
|
assert len(tup) == 1
|
||||||
|
return tup[0], offset + via.size
|
||||||
|
|
||||||
|
def unpack(self, vdict, cdict, bstring, offset, noerror=False):
|
||||||
|
"decode a value; return the value and the new offset"
|
||||||
|
if self.struct:
|
||||||
|
return self._unpack1(self.struct, bstring, offset, noerror)
|
||||||
|
slen, offset = self._unpack1(EncDecSimple.string_len, bstring, offset,
|
||||||
|
noerror)
|
||||||
|
if slen is None:
|
||||||
|
return None, offset
|
||||||
|
nexto = offset + slen
|
||||||
|
if len(bstring) < nexto:
|
||||||
|
if noerror:
|
||||||
|
val = None
|
||||||
|
else:
|
||||||
|
raise SequenceError('out of data '
|
||||||
|
'while unpacking {0!r}'.format(self.name))
|
||||||
|
else:
|
||||||
|
val = bstring[offset:nexto]
|
||||||
|
if self.autob2s:
|
||||||
|
val = self.b2s(val)
|
||||||
|
return val, nexto
|
||||||
|
|
||||||
|
# string length: 2 byte unsigned field
|
||||||
|
EncDecSimple.string_len = _ProtoStruct[2]
|
||||||
|
|
||||||
|
class EncDecTyped(EncDec):
|
||||||
|
r"""
|
||||||
|
EncDec for typed objects (which are build from PFODs, which are
|
||||||
|
a sneaky class variant of OrderedDict similar to namedtuple).
|
||||||
|
|
||||||
|
Calling the klass() function with no arguments must create an
|
||||||
|
instance with all-None members.
|
||||||
|
|
||||||
|
We also require a Sequencer to pack and unpack the members of
|
||||||
|
the underlying pfod.
|
||||||
|
|
||||||
|
>>> qid_s = Sequencer('qid')
|
||||||
|
>>> qid_s.append_encdec(None, EncDecSimple('type', 1))
|
||||||
|
>>> qid_s.append_encdec(None, EncDecSimple('version', 4))
|
||||||
|
>>> qid_s.append_encdec(None, EncDecSimple('path', 8))
|
||||||
|
>>> len(qid_s)
|
||||||
|
3
|
||||||
|
|
||||||
|
>>> from pfod import pfod
|
||||||
|
>>> qid = pfod('qid', ['type', 'version', 'path'])
|
||||||
|
>>> len(qid._fields)
|
||||||
|
3
|
||||||
|
>>> qid_inst = qid(1, 2, 3)
|
||||||
|
>>> qid_inst
|
||||||
|
qid(type=1, version=2, path=3)
|
||||||
|
|
||||||
|
>>> e = EncDecTyped(qid, 'aqid', qid_s)
|
||||||
|
>>> e.b2s(e.pack({}, {}, qid_inst))
|
||||||
|
'\x01\x02\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00'
|
||||||
|
>>> e.unpack({}, {},
|
||||||
|
... b'\x01\x02\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00', 0)
|
||||||
|
(qid(type=1, version=2, path=3), 13)
|
||||||
|
|
||||||
|
If an EncDecTyped instance has a conditional sequencer, note
|
||||||
|
that unpacking will leave un-selected items set to None (see
|
||||||
|
the Sequencer example below):
|
||||||
|
|
||||||
|
>>> breakfast = pfod('breakfast', 'eggs spam ham')
|
||||||
|
>>> breakfast()
|
||||||
|
breakfast(eggs=None, spam=None, ham=None)
|
||||||
|
>>> bfseq = Sequencer('breakfast')
|
||||||
|
>>> bfseq.append_encdec(None, EncDecSimple('eggs', 1))
|
||||||
|
>>> bfseq.append_encdec('yuck', EncDecSimple('spam', 1))
|
||||||
|
>>> bfseq.append_encdec(None, EncDecSimple('ham', 1))
|
||||||
|
>>> e = EncDecTyped(breakfast, 'bfname', bfseq)
|
||||||
|
>>> e.unpack({}, {'yuck': False}, b'\x02\x01\x04', 0)
|
||||||
|
(breakfast(eggs=2, spam=None, ham=1), 2)
|
||||||
|
|
||||||
|
This used just two of the three bytes: eggs=2, ham=1.
|
||||||
|
|
||||||
|
>>> e.unpack({}, {'yuck': True}, b'\x02\x01\x04', 0)
|
||||||
|
(breakfast(eggs=2, spam=1, ham=4), 3)
|
||||||
|
|
||||||
|
This used the third byte, so ham=4.
|
||||||
|
"""
|
||||||
|
def __init__(self, klass, name, sequence, aux=None):
|
||||||
|
assert len(sequence) == len(klass()._fields) # temporary
|
||||||
|
super(EncDecTyped, self).__init__(name, aux)
|
||||||
|
self.klass = klass
|
||||||
|
self.name = name
|
||||||
|
self.sequence = sequence
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
if self.aux is None:
|
||||||
|
return '{0}({1!r}, {2!r}, {3!r})'.format(self.__class__.__name__,
|
||||||
|
self.klass, self.name, self.sequence)
|
||||||
|
return '{0}({1!r}, {2!r}, {3!r}, {4!r})'.format(self.__class__.__name__,
|
||||||
|
self.klass, self.name, self.sequence, self.aux)
|
||||||
|
|
||||||
|
__str__ = __repr__
|
||||||
|
|
||||||
|
def apack(self, vdict, cdict, val):
|
||||||
|
"""
|
||||||
|
Pack each of our instance variables.
|
||||||
|
|
||||||
|
Note that some packing may be conditional.
|
||||||
|
"""
|
||||||
|
return self.sequence.apack(val, cdict)
|
||||||
|
|
||||||
|
def unpack(self, vdict, cdict, bstring, offset, noerror=False):
|
||||||
|
"""
|
||||||
|
Unpack each instance variable, into a new object of
|
||||||
|
self.klass. Return the new instance and new offset.
|
||||||
|
|
||||||
|
Note that some unpacking may be conditional.
|
||||||
|
"""
|
||||||
|
obj = self.klass()
|
||||||
|
offset = self.sequence.unpack_from(obj, cdict, bstring, offset, noerror)
|
||||||
|
return obj, offset
|
||||||
|
|
||||||
|
class EncDecA(EncDec):
|
||||||
|
r"""
|
||||||
|
EncDec for arrays (repeated objects).
|
||||||
|
|
||||||
|
We take the name of repeat count variable, and a sub-coder
|
||||||
|
(Sequencer instance). For instance, we can en/de-code
|
||||||
|
repeat='nwname' copies of name='wname', or nwname of
|
||||||
|
name='wqid', in a Twalk en/de-code.
|
||||||
|
|
||||||
|
Note that we don't pack or unpack the repeat count itself --
|
||||||
|
that must be done by higher level code. We just get its value
|
||||||
|
from vdict.
|
||||||
|
|
||||||
|
>>> subcode = EncDecSimple('wname', '_string_')
|
||||||
|
>>> e = EncDecA('nwname', 'wname', subcode)
|
||||||
|
>>> e.b2s(e.pack({'nwname': 2}, {}, ['A', 'BC']))
|
||||||
|
'\x01\x00A\x02\x00BC'
|
||||||
|
|
||||||
|
>>> subcode.autob2s = True # so that A and BC decode to py3k str
|
||||||
|
>>> e.unpack({'nwname': 2}, {}, b'\x01\x00A\x02\x00BC', 0)
|
||||||
|
(['A', 'BC'], 7)
|
||||||
|
|
||||||
|
When using noerror, the first sub-item that fails to decode
|
||||||
|
completely starts the None-s. Strings whose length fails to
|
||||||
|
decode are assumed to be zero bytes long as well, for the
|
||||||
|
purpose of showing the expected packet length:
|
||||||
|
|
||||||
|
>>> e.unpack({'nwname': 2}, {}, b'\x01\x00A\x02\x00', 0, noerror=True)
|
||||||
|
(['A', None], 7)
|
||||||
|
>>> e.unpack({'nwname': 2}, {}, b'\x01\x00A\x02', 0, noerror=True)
|
||||||
|
(['A', None], 5)
|
||||||
|
>>> e.unpack({'nwname': 3}, {}, b'\x01\x00A\x02', 0, noerror=True)
|
||||||
|
(['A', None, None], 7)
|
||||||
|
|
||||||
|
As a special case, supplying None for the sub-coder
|
||||||
|
makes the repeated item pack or unpack a simple byte
|
||||||
|
string. (Note that autob2s is not supported here.)
|
||||||
|
A too-short byte string is simply truncated!
|
||||||
|
|
||||||
|
>>> e = EncDecA('count', 'data', None)
|
||||||
|
>>> e.b2s(e.pack({'count': 5}, {}, b'12345'))
|
||||||
|
'12345'
|
||||||
|
>>> x = list(e.unpack({'count': 3}, {}, b'123', 0))
|
||||||
|
>>> x[0] = e.b2s(x[0])
|
||||||
|
>>> x
|
||||||
|
['123', 3]
|
||||||
|
>>> x = list(e.unpack({'count': 3}, {}, b'12', 0, noerror=True))
|
||||||
|
>>> x[0] = e.b2s(x[0])
|
||||||
|
>>> x
|
||||||
|
['12', 3]
|
||||||
|
"""
|
||||||
|
def __init__(self, repeat, name, sub, aux=None):
|
||||||
|
super(EncDecA, self).__init__(name, aux)
|
||||||
|
self.repeat = repeat
|
||||||
|
self.name = name
|
||||||
|
self.sub = sub
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
if self.aux is None:
|
||||||
|
return '{0}({1!r}, {2!r}, {3!r})'.format(self.__class__.__name__,
|
||||||
|
self.repeat, self.name, self.sub)
|
||||||
|
return '{0}({1!r}, {2!r}, {3!r}, {4!r})'.format(self.__class__.__name__,
|
||||||
|
self.repeat, self.name, self.sub, self.aux)
|
||||||
|
|
||||||
|
__str__ = __repr__
|
||||||
|
|
||||||
|
def apack(self, vdict, cdict, val):
|
||||||
|
"pack each val[i], for i in range(vdict[self.repeat])"
|
||||||
|
num = vdict[self.repeat]
|
||||||
|
assert num == len(val)
|
||||||
|
if self.sub is None:
|
||||||
|
assert isinstance(val, bytes)
|
||||||
|
return [val]
|
||||||
|
parts = []
|
||||||
|
for i in val:
|
||||||
|
parts.extend(self.sub.apack(vdict, cdict, i))
|
||||||
|
return parts
|
||||||
|
|
||||||
|
def unpack(self, vdict, cdict, bstring, offset, noerror=False):
|
||||||
|
"unpack repeatedly, per self.repeat, into new array."
|
||||||
|
num = vdict[self.repeat]
|
||||||
|
if num is None and noerror:
|
||||||
|
num = 0
|
||||||
|
else:
|
||||||
|
assert num >= 0
|
||||||
|
if self.sub is None:
|
||||||
|
nexto = offset + num
|
||||||
|
if len(bstring) < nexto and not noerror:
|
||||||
|
raise SequenceError('out of data '
|
||||||
|
'while unpacking {0!r}'.format(self.name))
|
||||||
|
return bstring[offset:nexto], nexto
|
||||||
|
array = []
|
||||||
|
for i in range(num):
|
||||||
|
obj, offset = self.sub.unpack(vdict, cdict, bstring, offset,
|
||||||
|
noerror)
|
||||||
|
array.append(obj)
|
||||||
|
return array, offset
|
||||||
|
|
||||||
|
class SequenceError(Exception):
|
||||||
|
"sequence error: item too big, or ran out of data"
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Sequencer(object):
|
||||||
|
r"""
|
||||||
|
A sequencer is an object that packs (marshals) or unpacks
|
||||||
|
(unmarshals) a series of objects, according to their EncDec
|
||||||
|
instances.
|
||||||
|
|
||||||
|
The objects themselves (and their values) come from, or
|
||||||
|
go into, a dictionary: <vdict>, the first argument to
|
||||||
|
pack/unpack.
|
||||||
|
|
||||||
|
Some fields may be conditional. The conditions are in a
|
||||||
|
separate dictionary (the second or <cdict> argument).
|
||||||
|
|
||||||
|
Some objects may be dictionaries or PFODs, e.g., they may
|
||||||
|
be a Plan9 qid or stat structure. These have their own
|
||||||
|
sub-encoding.
|
||||||
|
|
||||||
|
As with each encoder, we have both an apack() function
|
||||||
|
(returns a list of parts) and a plain pack(). Users should
|
||||||
|
mostly stick with plain pack().
|
||||||
|
|
||||||
|
>>> s = Sequencer('monty')
|
||||||
|
>>> s
|
||||||
|
Sequencer('monty')
|
||||||
|
>>> e = EncDecSimple('eggs', 2)
|
||||||
|
>>> s.append_encdec(None, e)
|
||||||
|
>>> s.append_encdec(None, EncDecSimple('spam', 1))
|
||||||
|
>>> s[0]
|
||||||
|
(None, EncDecSimple('eggs', 2))
|
||||||
|
>>> e.b2s(s.pack({'eggs': 513, 'spam': 65}, {}))
|
||||||
|
'\x01\x02A'
|
||||||
|
|
||||||
|
When particular fields are conditional, they appear in
|
||||||
|
packed output, or are taken from the byte-string during
|
||||||
|
unpacking, only if their condition is true.
|
||||||
|
|
||||||
|
As with struct, use unpack_from to start at an arbitrary
|
||||||
|
offset and/or omit verification that the entire byte-string
|
||||||
|
is consumed.
|
||||||
|
|
||||||
|
>>> s = Sequencer('python')
|
||||||
|
>>> s.append_encdec(None, e)
|
||||||
|
>>> s.append_encdec('.u', EncDecSimple('spam', 1))
|
||||||
|
>>> s[1]
|
||||||
|
('.u', EncDecSimple('spam', 1))
|
||||||
|
>>> e.b2s(s.pack({'eggs': 513, 'spam': 65}, {'.u': True}))
|
||||||
|
'\x01\x02A'
|
||||||
|
>>> e.b2s(s.pack({'eggs': 513, 'spam': 65}, {'.u': False}))
|
||||||
|
'\x01\x02'
|
||||||
|
|
||||||
|
>>> d = {}
|
||||||
|
>>> s.unpack(d, {'.u': True}, b'\x01\x02A')
|
||||||
|
>>> print(d['eggs'], d['spam'])
|
||||||
|
513 65
|
||||||
|
>>> d = {}
|
||||||
|
>>> s.unpack(d, {'.u': False}, b'\x01\x02A', 0)
|
||||||
|
Traceback (most recent call last):
|
||||||
|
...
|
||||||
|
SequenceError: 1 byte(s) unconsumed
|
||||||
|
>>> s.unpack_from(d, {'.u': False}, b'\x01\x02A', 0)
|
||||||
|
2
|
||||||
|
>>> print(d)
|
||||||
|
{'eggs': 513}
|
||||||
|
|
||||||
|
The incoming dictionary-like object may be pre-initialized
|
||||||
|
if you like; only sequences that decode are filled-in:
|
||||||
|
|
||||||
|
>>> d = {'eggs': None, 'spam': None}
|
||||||
|
>>> s.unpack_from(d, {'.u': False}, b'\x01\x02A', 0)
|
||||||
|
2
|
||||||
|
>>> print(d['eggs'], d['spam'])
|
||||||
|
513 None
|
||||||
|
|
||||||
|
Some objects may be arrays; if so their EncDec is actually
|
||||||
|
an EncDecA, the repeat count must be in the dictionary, and
|
||||||
|
the object itself must have a len() and be index-able:
|
||||||
|
|
||||||
|
>>> s = Sequencer('arr')
|
||||||
|
>>> s.append_encdec(None, EncDecSimple('n', 1))
|
||||||
|
>>> ae = EncDecSimple('array', 2)
|
||||||
|
>>> s.append_encdec(None, EncDecA('n', 'array', ae))
|
||||||
|
>>> ae.b2s(s.pack({'n': 2, 'array': [257, 514]}, {}))
|
||||||
|
'\x02\x01\x01\x02\x02'
|
||||||
|
|
||||||
|
Unpacking an array creates a list of the number of items.
|
||||||
|
The EncDec encoder that decodes the number of items needs to
|
||||||
|
occur first in the sequencer, so that the dictionary will have
|
||||||
|
acquired the repeat-count variable's value by the time we hit
|
||||||
|
the array's encdec:
|
||||||
|
|
||||||
|
>>> d = {}
|
||||||
|
>>> s.unpack(d, {}, b'\x01\x04\x00')
|
||||||
|
>>> d['n'], d['array']
|
||||||
|
(1, [4])
|
||||||
|
"""
|
||||||
|
def __init__(self, name):
|
||||||
|
self.name = name
|
||||||
|
self._codes = []
|
||||||
|
self.debug = False # or sys.stderr
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '{0}({1!r})'.format(self.__class__.__name__, self.name)
|
||||||
|
|
||||||
|
__str__ = __repr__
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self._codes)
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return iter(self._codes)
|
||||||
|
|
||||||
|
def __getitem__(self, index):
|
||||||
|
return self._codes[index]
|
||||||
|
|
||||||
|
def dprint(self, *args, **kwargs):
|
||||||
|
if not self.debug:
|
||||||
|
return
|
||||||
|
if isinstance(self.debug, bool):
|
||||||
|
dest = sys.stdout
|
||||||
|
else:
|
||||||
|
dest = self.debug
|
||||||
|
print(*args, file=dest, **kwargs)
|
||||||
|
|
||||||
|
def append_encdec(self, cond, code):
|
||||||
|
"add EncDec en/de-coder, conditional on cond"
|
||||||
|
self._codes.append((cond, code))
|
||||||
|
|
||||||
|
def apack(self, vdict, cdict):
|
||||||
|
"""
|
||||||
|
Produce packed representation of each field.
|
||||||
|
"""
|
||||||
|
packed_data = []
|
||||||
|
for cond, code in self._codes:
|
||||||
|
# Skip this item if it's conditional on a false thing.
|
||||||
|
if cond is not None and not cdict[cond]:
|
||||||
|
self.dprint('skip %r - %r is False' % (code, cond))
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Pack the item.
|
||||||
|
self.dprint('pack %r - no cond or %r is True' % (code, cond))
|
||||||
|
packed_data.extend(code.apack(vdict, cdict, vdict[code.name]))
|
||||||
|
|
||||||
|
return packed_data
|
||||||
|
|
||||||
|
def pack(self, vdict, cdict):
|
||||||
|
"""
|
||||||
|
Flatten packed data.
|
||||||
|
"""
|
||||||
|
return b''.join(self.apack(vdict, cdict))
|
||||||
|
|
||||||
|
def unpack_from(self, vdict, cdict, bstring, offset=0, noerror=False):
|
||||||
|
"""
|
||||||
|
Unpack from byte string.
|
||||||
|
|
||||||
|
The values are unpacked into a dictionary vdict;
|
||||||
|
some of its entries may themselves be ordered
|
||||||
|
dictionaries created by typedefed codes.
|
||||||
|
|
||||||
|
Raises SequenceError if the string is too short,
|
||||||
|
unless you set noerror, in which case we assume
|
||||||
|
you want see what you can get out of the data.
|
||||||
|
"""
|
||||||
|
for cond, code in self._codes:
|
||||||
|
# Skip this item if it's conditional on a false thing.
|
||||||
|
if cond is not None and not cdict[cond]:
|
||||||
|
self.dprint('skip %r - %r is False' % (code, cond))
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Unpack the item.
|
||||||
|
self.dprint('unpack %r - no cond or %r is True' % (code, cond))
|
||||||
|
obj, offset = code.unpack(vdict, cdict, bstring, offset, noerror)
|
||||||
|
vdict[code.name] = obj
|
||||||
|
|
||||||
|
return offset
|
||||||
|
|
||||||
|
def unpack(self, vdict, cdict, bstring, noerror=False):
|
||||||
|
"""
|
||||||
|
Like unpack_from but unless noerror=True, requires that
|
||||||
|
we completely use up the given byte string.
|
||||||
|
"""
|
||||||
|
offset = self.unpack_from(vdict, cdict, bstring, 0, noerror)
|
||||||
|
if not noerror and offset != len(bstring):
|
||||||
|
raise SequenceError('{0} byte(s) unconsumed'.format(
|
||||||
|
len(bstring) - offset))
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
import doctest
|
||||||
|
doctest.testmod()
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
# test configuration
|
||||||
|
|
||||||
|
[client]
|
||||||
|
server = localhost
|
||||||
|
port = 12345
|
||||||
|
# timeout is in seconds
|
||||||
|
timeout = 0.1
|
||||||
|
loglevel = INFO
|
||||||
|
logfile = ./ctest.log
|
||||||
|
# logfmt = ...
|
||||||
|
# protocol = 9p2000, 9p2000.u, or 9p2000.L
|
||||||
|
protocol = 9p2000.L
|
||||||
|
only_dotl = true
|
||||||
|
may_downgrade = False
|
||||||
|
uname = anonymous
|
||||||
|
n_uname = 1001
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,320 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 Chris Torek <chris.torek@gmail.com>
|
||||||
|
* All rights reserved
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted providing that the following conditions
|
||||||
|
* are met:
|
||||||
|
* 1. Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||||
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||||||
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
||||||
|
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#if defined(WITH_CASPER)
|
||||||
|
#include <libcasper.h>
|
||||||
|
#include <casper/cap_pwd.h>
|
||||||
|
#include <casper/cap_grp.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "rfuncs.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This is essentially a clone of the BSD basename_r function,
|
||||||
|
* which is like POSIX basename() but puts the result in a user
|
||||||
|
* supplied buffer.
|
||||||
|
*
|
||||||
|
* In BSD basename_r, the buffer must be least MAXPATHLEN bytes
|
||||||
|
* long. In our case we take the size of the buffer as an argument.
|
||||||
|
*
|
||||||
|
* Note that it's impossible in general to do this without
|
||||||
|
* a temporary buffer since basename("foo/bar") is "bar",
|
||||||
|
* but basename("foo/bar/") is still "bar" -- no trailing
|
||||||
|
* slash is allowed.
|
||||||
|
*
|
||||||
|
* The return value is your supplied buffer <buf>, or NULL if
|
||||||
|
* the length of the basename of the supplied <path> equals or
|
||||||
|
* exceeds your indicated <bufsize>.
|
||||||
|
*
|
||||||
|
* As a special but useful case, if you supply NULL for the <buf>
|
||||||
|
* argument, we allocate the buffer dynamically to match the
|
||||||
|
* basename, i.e., the result is basically strdup()ed for you.
|
||||||
|
* In this case <bufsize> is ignored (recommended: pass 0 here).
|
||||||
|
*/
|
||||||
|
char *
|
||||||
|
r_basename(const char *path, char *buf, size_t bufsize)
|
||||||
|
{
|
||||||
|
const char *endp, *comp;
|
||||||
|
size_t len;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* NULL or empty path means ".". This is perhaps overly
|
||||||
|
* forgiving but matches libc basename_r(), and avoids
|
||||||
|
* breaking the code below.
|
||||||
|
*/
|
||||||
|
if (path == NULL || *path == '\0') {
|
||||||
|
comp = ".";
|
||||||
|
len = 1;
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
* Back up over any trailing slashes. If we reach
|
||||||
|
* the top of the path and it's still a trailing
|
||||||
|
* slash, it's also a leading slash and the entire
|
||||||
|
* path is just "/" (or "//", or "///", etc).
|
||||||
|
*/
|
||||||
|
endp = path + strlen(path) - 1;
|
||||||
|
while (*endp == '/' && endp > path)
|
||||||
|
endp--;
|
||||||
|
/* Invariant: *endp != '/' || endp == path */
|
||||||
|
if (*endp == '/') {
|
||||||
|
/* then endp==path and hence entire path is "/" */
|
||||||
|
comp = "/";
|
||||||
|
len = 1;
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
* We handled empty strings earlier, and
|
||||||
|
* we just proved *endp != '/'. Hence
|
||||||
|
* we have a non-empty basename, ending
|
||||||
|
* at endp.
|
||||||
|
*
|
||||||
|
* Back up one path name component. The
|
||||||
|
* part between these two is the basename.
|
||||||
|
*
|
||||||
|
* Note that we only stop backing up when
|
||||||
|
* either comp==path, or comp[-1] is '/'.
|
||||||
|
*
|
||||||
|
* Suppose path[0] is '/'. Then, since *endp
|
||||||
|
* is *not* '/', we had comp>path initially, and
|
||||||
|
* stopped backing up because we found a '/'
|
||||||
|
* (perhaps path[0], perhaps a later '/').
|
||||||
|
*
|
||||||
|
* Or, suppose path[0] is NOT '/'. Then,
|
||||||
|
* either there are no '/'s at all and
|
||||||
|
* comp==path, or comp[-1] is '/'.
|
||||||
|
*
|
||||||
|
* In all cases, we want all bytes from *comp
|
||||||
|
* to *endp, inclusive.
|
||||||
|
*/
|
||||||
|
comp = endp;
|
||||||
|
while (comp > path && comp[-1] != '/')
|
||||||
|
comp--;
|
||||||
|
len = (size_t)(endp - comp + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (buf == NULL) {
|
||||||
|
buf = malloc(len + 1);
|
||||||
|
if (buf == NULL)
|
||||||
|
return (NULL);
|
||||||
|
} else {
|
||||||
|
if (len >= bufsize) {
|
||||||
|
errno = ENAMETOOLONG;
|
||||||
|
return (NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
memcpy(buf, comp, len);
|
||||||
|
buf[len] = '\0';
|
||||||
|
return (buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This is much like POSIX dirname(), but is reentrant.
|
||||||
|
*
|
||||||
|
* We examine a path, find the directory portion, and copy that
|
||||||
|
* to a user supplied buffer <buf> of the given size <bufsize>.
|
||||||
|
*
|
||||||
|
* Note that dirname("/foo/bar/") is "/foo", dirname("/foo") is "/",
|
||||||
|
* and dirname("////") is "/". However, dirname("////foo/bar") is
|
||||||
|
* "////foo" (we do not resolve these leading slashes away -- this
|
||||||
|
* matches the BSD libc behavior).
|
||||||
|
*
|
||||||
|
* The return value is your supplied buffer <buf>, or NULL if
|
||||||
|
* the length of the dirname of the supplied <path> equals or
|
||||||
|
* exceeds your indicated <bufsize>.
|
||||||
|
*
|
||||||
|
* As a special but useful case, if you supply NULL for the <buf>
|
||||||
|
* argument, we allocate the buffer dynamically to match the
|
||||||
|
* dirname, i.e., the result is basically strdup()ed for you.
|
||||||
|
* In this case <bufsize> is ignored (recommended: pass 0 here).
|
||||||
|
*/
|
||||||
|
char *
|
||||||
|
r_dirname(const char *path, char *buf, size_t bufsize)
|
||||||
|
{
|
||||||
|
const char *endp, *dirpart;
|
||||||
|
size_t len;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* NULL or empty path means ".". This is perhaps overly
|
||||||
|
* forgiving but matches libc dirname(), and avoids breaking
|
||||||
|
* the code below.
|
||||||
|
*/
|
||||||
|
if (path == NULL || *path == '\0') {
|
||||||
|
dirpart = ".";
|
||||||
|
len = 1;
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
* Back up over any trailing slashes, then back up
|
||||||
|
* one path name, then back up over more slashes.
|
||||||
|
* In all cases, stop as soon as endp==path so
|
||||||
|
* that we do not back out of the buffer entirely.
|
||||||
|
*
|
||||||
|
* The first loop takes care of trailing slashes
|
||||||
|
* in names like "/foo/bar//" (where the dirname
|
||||||
|
* part is to be "/foo"), the second strips out
|
||||||
|
* the non-dir-name part, and the third leaves us
|
||||||
|
* pointing to the end of the directory component.
|
||||||
|
*
|
||||||
|
* If the entire name is of the form "/foo" or
|
||||||
|
* "//foo" (or "/foo/", etc, but we already
|
||||||
|
* handled trailing slashes), we end up pointing
|
||||||
|
* to the leading "/", which is what we want; but
|
||||||
|
* if it is of the form "foo" (or "foo/", etc) we
|
||||||
|
* point to a non-slash. So, if (and only if)
|
||||||
|
* endp==path AND *endp is not '/', the dirname is
|
||||||
|
* ".", but in all cases, the LENGTH of the
|
||||||
|
* dirname is (endp-path+1).
|
||||||
|
*/
|
||||||
|
endp = path + strlen(path) - 1;
|
||||||
|
while (endp > path && *endp == '/')
|
||||||
|
endp--;
|
||||||
|
while (endp > path && *endp != '/')
|
||||||
|
endp--;
|
||||||
|
while (endp > path && *endp == '/')
|
||||||
|
endp--;
|
||||||
|
|
||||||
|
len = (size_t)(endp - path + 1);
|
||||||
|
if (endp == path && *endp != '/')
|
||||||
|
dirpart = ".";
|
||||||
|
else
|
||||||
|
dirpart = path;
|
||||||
|
}
|
||||||
|
if (buf == NULL) {
|
||||||
|
buf = malloc(len + 1);
|
||||||
|
if (buf == NULL)
|
||||||
|
return (NULL);
|
||||||
|
} else {
|
||||||
|
if (len >= bufsize) {
|
||||||
|
errno = ENAMETOOLONG;
|
||||||
|
return (NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
memcpy(buf, dirpart, len);
|
||||||
|
buf[len] = '\0';
|
||||||
|
return (buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
r_pginit(struct r_pgdata *pg)
|
||||||
|
{
|
||||||
|
|
||||||
|
/* Note: init to half size since the first thing we do is double it */
|
||||||
|
pg->r_pgbufsize = 1 << 9;
|
||||||
|
pg->r_pgbuf = NULL; /* note that realloc(NULL) == malloc */
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
r_pgexpand(struct r_pgdata *pg)
|
||||||
|
{
|
||||||
|
size_t nsize;
|
||||||
|
|
||||||
|
nsize = pg->r_pgbufsize << 1;
|
||||||
|
if (nsize >= (1 << 20) ||
|
||||||
|
(pg->r_pgbuf = realloc(pg->r_pgbuf, nsize)) == NULL)
|
||||||
|
return (ENOMEM);
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
r_pgfree(struct r_pgdata *pg)
|
||||||
|
{
|
||||||
|
|
||||||
|
free(pg->r_pgbuf);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct passwd *
|
||||||
|
r_getpwuid(uid_t uid, struct r_pgdata *pg)
|
||||||
|
{
|
||||||
|
struct passwd *result = NULL;
|
||||||
|
int error;
|
||||||
|
|
||||||
|
r_pginit(pg);
|
||||||
|
do {
|
||||||
|
error = r_pgexpand(pg);
|
||||||
|
if (error == 0)
|
||||||
|
error = getpwuid_r(uid, &pg->r_pgun.un_pw,
|
||||||
|
pg->r_pgbuf, pg->r_pgbufsize, &result);
|
||||||
|
} while (error == ERANGE);
|
||||||
|
|
||||||
|
return (error ? NULL : result);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct group *
|
||||||
|
r_getgrgid(gid_t gid, struct r_pgdata *pg)
|
||||||
|
{
|
||||||
|
struct group *result = NULL;
|
||||||
|
int error;
|
||||||
|
|
||||||
|
r_pginit(pg);
|
||||||
|
do {
|
||||||
|
error = r_pgexpand(pg);
|
||||||
|
if (error == 0)
|
||||||
|
error = getgrgid_r(gid, &pg->r_pgun.un_gr,
|
||||||
|
pg->r_pgbuf, pg->r_pgbufsize, &result);
|
||||||
|
} while (error == ERANGE);
|
||||||
|
|
||||||
|
return (error ? NULL : result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(WITH_CASPER)
|
||||||
|
struct passwd *
|
||||||
|
r_cap_getpwuid(cap_channel_t *cap, uid_t uid, struct r_pgdata *pg)
|
||||||
|
{
|
||||||
|
struct passwd *result = NULL;
|
||||||
|
int error;
|
||||||
|
|
||||||
|
r_pginit(pg);
|
||||||
|
do {
|
||||||
|
error = r_pgexpand(pg);
|
||||||
|
if (error == 0)
|
||||||
|
error = cap_getpwuid_r(cap, uid, &pg->r_pgun.un_pw,
|
||||||
|
pg->r_pgbuf, pg->r_pgbufsize, &result);
|
||||||
|
} while (error == ERANGE);
|
||||||
|
|
||||||
|
return (error ? NULL : result);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct group *
|
||||||
|
r_cap_getgrgid(cap_channel_t *cap, gid_t gid, struct r_pgdata *pg)
|
||||||
|
{
|
||||||
|
struct group *result = NULL;
|
||||||
|
int error;
|
||||||
|
|
||||||
|
r_pginit(pg);
|
||||||
|
do {
|
||||||
|
error = r_pgexpand(pg);
|
||||||
|
if (error == 0)
|
||||||
|
error = cap_getgrgid_r(cap, gid, &pg->r_pgun.un_gr,
|
||||||
|
pg->r_pgbuf, pg->r_pgbufsize, &result);
|
||||||
|
} while (error == ERANGE);
|
||||||
|
|
||||||
|
return (error ? NULL : result);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 Chris Torek <chris.torek@gmail.com>
|
||||||
|
* All rights reserved
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted providing that the following conditions
|
||||||
|
* are met:
|
||||||
|
* 1. Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||||
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||||||
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
||||||
|
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef LIB9P_RFUNCS_H
|
||||||
|
#define LIB9P_RFUNCS_H
|
||||||
|
|
||||||
|
#include <grp.h>
|
||||||
|
#include <pwd.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#if defined(WITH_CASPER)
|
||||||
|
#include <libcasper.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Reentrant, optionally-malloc-ing versions of
|
||||||
|
* basename() and dirname().
|
||||||
|
*/
|
||||||
|
char *r_basename(const char *, char *, size_t);
|
||||||
|
char *r_dirname(const char *, char *, size_t);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Yuck: getpwuid, getgrgid are not thread-safe, and the
|
||||||
|
* POSIX replacements (getpwuid_r, getgrgid_r) are horrible.
|
||||||
|
* This is to allow us to loop over the get.*_r calls with ever
|
||||||
|
* increasing buffers until they succeed or get unreasonable
|
||||||
|
* (same idea as the libc code for the non-reentrant versions,
|
||||||
|
* although prettier).
|
||||||
|
*
|
||||||
|
* The getpwuid/getgrgid functions auto-init one of these,
|
||||||
|
* but the caller must call r_pgfree() when done with the
|
||||||
|
* return values.
|
||||||
|
*
|
||||||
|
* If we need more later, we may have to expose the init function.
|
||||||
|
*/
|
||||||
|
struct r_pgdata {
|
||||||
|
char *r_pgbuf;
|
||||||
|
size_t r_pgbufsize;
|
||||||
|
union {
|
||||||
|
struct passwd un_pw;
|
||||||
|
struct group un_gr;
|
||||||
|
} r_pgun;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* void r_pginit(struct r_pgdata *); */
|
||||||
|
void r_pgfree(struct r_pgdata *);
|
||||||
|
struct passwd *r_getpwuid(uid_t, struct r_pgdata *);
|
||||||
|
struct group *r_getgrgid(gid_t, struct r_pgdata *);
|
||||||
|
|
||||||
|
#if defined(WITH_CASPER)
|
||||||
|
struct passwd *r_cap_getpwuid(cap_channel_t *, uid_t, struct r_pgdata *);
|
||||||
|
struct group *r_cap_getgrgid(cap_channel_t *, gid_t, struct r_pgdata *);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* LIB9P_RFUNCS_H */
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 Jakub Klama <jceel@FreeBSD.org>
|
||||||
|
* All rights reserved
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted providing that the following conditions
|
||||||
|
* are met:
|
||||||
|
* 1. Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||||
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||||||
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
||||||
|
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Minimal libsbuf reimplementation for Mac OS X.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include "sbuf.h"
|
||||||
|
|
||||||
|
#define SBUF_INITIAL_SIZE 128
|
||||||
|
|
||||||
|
struct sbuf *
|
||||||
|
sbuf_new_auto()
|
||||||
|
{
|
||||||
|
struct sbuf *s;
|
||||||
|
|
||||||
|
s = malloc(sizeof(struct sbuf));
|
||||||
|
s->s_buf = calloc(1, SBUF_INITIAL_SIZE + 1);
|
||||||
|
s->s_capacity = s->s_buf != NULL ? SBUF_INITIAL_SIZE : 0;
|
||||||
|
s->s_size = 0;
|
||||||
|
|
||||||
|
return (s);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
sbuf_cat(struct sbuf *s, const char *str)
|
||||||
|
{
|
||||||
|
int req = (int)strlen(str);
|
||||||
|
|
||||||
|
if (s->s_size + req >= s->s_capacity) {
|
||||||
|
s->s_capacity = s->s_size + req + 1;
|
||||||
|
s->s_buf = realloc(s->s_buf, (size_t)s->s_capacity);
|
||||||
|
}
|
||||||
|
if (s->s_buf == NULL)
|
||||||
|
return (-1);
|
||||||
|
|
||||||
|
strcpy(s->s_buf + s->s_size, str);
|
||||||
|
s->s_size += req;
|
||||||
|
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
sbuf_printf(struct sbuf *s, const char *fmt, ...)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
va_list ap;
|
||||||
|
|
||||||
|
va_start(ap, fmt);
|
||||||
|
ret = sbuf_vprintf(s, fmt, ap);
|
||||||
|
va_end(ap);
|
||||||
|
|
||||||
|
return (ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
sbuf_vprintf(struct sbuf *s, const char *fmt, va_list args)
|
||||||
|
{
|
||||||
|
va_list copy;
|
||||||
|
int req;
|
||||||
|
|
||||||
|
va_copy(copy, args);
|
||||||
|
req = vsnprintf(NULL, 0, fmt, copy);
|
||||||
|
va_end(copy);
|
||||||
|
|
||||||
|
if (s->s_size + req >= s->s_capacity) {
|
||||||
|
s->s_capacity = s->s_size + req + 1;
|
||||||
|
s->s_buf = realloc(s->s_buf, (size_t)s->s_capacity);
|
||||||
|
}
|
||||||
|
if (s->s_buf == NULL)
|
||||||
|
return (-1);
|
||||||
|
|
||||||
|
req = vsnprintf(s->s_buf + s->s_size, req + 1, fmt, args);
|
||||||
|
s->s_size += req;
|
||||||
|
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
char *
|
||||||
|
sbuf_data(struct sbuf *s)
|
||||||
|
{
|
||||||
|
return (s->s_buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
sbuf_finish(struct sbuf *s)
|
||||||
|
{
|
||||||
|
if (s->s_buf != NULL)
|
||||||
|
s->s_buf[s->s_size] = '\0';
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
sbuf_delete(struct sbuf *s)
|
||||||
|
{
|
||||||
|
free(s->s_buf);
|
||||||
|
free(s);
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 Jakub Klama <jceel@FreeBSD.org>
|
||||||
|
* All rights reserved
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted providing that the following conditions
|
||||||
|
* are met:
|
||||||
|
* 1. Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||||
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||||||
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
||||||
|
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Minimal libsbuf reimplementation for Mac OS X.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef LIB9P_SBUF_H
|
||||||
|
#define LIB9P_SBUF_H
|
||||||
|
|
||||||
|
#include <stdarg.h>
|
||||||
|
|
||||||
|
struct sbuf
|
||||||
|
{
|
||||||
|
char *s_buf;
|
||||||
|
int s_size;
|
||||||
|
int s_capacity;
|
||||||
|
int s_position;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct sbuf *sbuf_new_auto(void);
|
||||||
|
int sbuf_cat(struct sbuf *s, const char *str);
|
||||||
|
int sbuf_printf(struct sbuf *s, const char *fmt, ...);
|
||||||
|
int sbuf_vprintf(struct sbuf *s, const char *fmt, va_list args);
|
||||||
|
int sbuf_done(struct sbuf *s);
|
||||||
|
void sbuf_delete(struct sbuf *s);
|
||||||
|
int sbuf_finish(struct sbuf *s);
|
||||||
|
char *sbuf_data(struct sbuf *s);
|
||||||
|
|
||||||
|
#endif /* LIB9P_SBUF_H */
|
||||||
|
|
||||||
@@ -0,0 +1,422 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 Jakub Klama <jceel@FreeBSD.org>
|
||||||
|
* All rights reserved
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted providing that the following conditions
|
||||||
|
* are met:
|
||||||
|
* 1. Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||||
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||||||
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
||||||
|
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#if defined(__FreeBSD__)
|
||||||
|
#include <pthread_np.h>
|
||||||
|
#endif
|
||||||
|
#include <sys/queue.h>
|
||||||
|
#include "lib9p.h"
|
||||||
|
#include "threadpool.h"
|
||||||
|
|
||||||
|
static void l9p_threadpool_rflush(struct l9p_threadpool *tp,
|
||||||
|
struct l9p_request *req);
|
||||||
|
|
||||||
|
static void *
|
||||||
|
l9p_responder(void *arg)
|
||||||
|
{
|
||||||
|
struct l9p_threadpool *tp;
|
||||||
|
struct l9p_worker *worker = arg;
|
||||||
|
struct l9p_request *req;
|
||||||
|
|
||||||
|
tp = worker->ltw_tp;
|
||||||
|
for (;;) {
|
||||||
|
/* get next reply to send */
|
||||||
|
pthread_mutex_lock(&tp->ltp_mtx);
|
||||||
|
while (STAILQ_EMPTY(&tp->ltp_replyq) && !worker->ltw_exiting)
|
||||||
|
pthread_cond_wait(&tp->ltp_reply_cv, &tp->ltp_mtx);
|
||||||
|
if (worker->ltw_exiting) {
|
||||||
|
pthread_mutex_unlock(&tp->ltp_mtx);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* off reply queue */
|
||||||
|
req = STAILQ_FIRST(&tp->ltp_replyq);
|
||||||
|
STAILQ_REMOVE_HEAD(&tp->ltp_replyq, lr_worklink);
|
||||||
|
|
||||||
|
/* request is now in final glide path, can't be Tflush-ed */
|
||||||
|
req->lr_workstate = L9P_WS_REPLYING;
|
||||||
|
|
||||||
|
/* any flushers waiting for this request can go now */
|
||||||
|
if (req->lr_flushstate != L9P_FLUSH_NONE)
|
||||||
|
l9p_threadpool_rflush(tp, req);
|
||||||
|
|
||||||
|
pthread_mutex_unlock(&tp->ltp_mtx);
|
||||||
|
|
||||||
|
/* send response */
|
||||||
|
l9p_respond(req, false, true);
|
||||||
|
}
|
||||||
|
return (NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *
|
||||||
|
l9p_worker(void *arg)
|
||||||
|
{
|
||||||
|
struct l9p_threadpool *tp;
|
||||||
|
struct l9p_worker *worker = arg;
|
||||||
|
struct l9p_request *req;
|
||||||
|
|
||||||
|
tp = worker->ltw_tp;
|
||||||
|
pthread_mutex_lock(&tp->ltp_mtx);
|
||||||
|
for (;;) {
|
||||||
|
while (STAILQ_EMPTY(&tp->ltp_workq) && !worker->ltw_exiting)
|
||||||
|
pthread_cond_wait(&tp->ltp_work_cv, &tp->ltp_mtx);
|
||||||
|
if (worker->ltw_exiting)
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* off work queue; now work-in-progress, by us */
|
||||||
|
req = STAILQ_FIRST(&tp->ltp_workq);
|
||||||
|
STAILQ_REMOVE_HEAD(&tp->ltp_workq, lr_worklink);
|
||||||
|
req->lr_workstate = L9P_WS_INPROGRESS;
|
||||||
|
req->lr_worker = worker;
|
||||||
|
pthread_mutex_unlock(&tp->ltp_mtx);
|
||||||
|
|
||||||
|
/* actually try the request */
|
||||||
|
req->lr_error = l9p_dispatch_request(req);
|
||||||
|
|
||||||
|
/* move to responder queue, updating work-state */
|
||||||
|
pthread_mutex_lock(&tp->ltp_mtx);
|
||||||
|
req->lr_workstate = L9P_WS_RESPQUEUED;
|
||||||
|
req->lr_worker = NULL;
|
||||||
|
STAILQ_INSERT_TAIL(&tp->ltp_replyq, req, lr_worklink);
|
||||||
|
|
||||||
|
/* signal the responder */
|
||||||
|
pthread_cond_signal(&tp->ltp_reply_cv);
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&tp->ltp_mtx);
|
||||||
|
return (NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Just before finally replying to a request that got touched by
|
||||||
|
* a Tflush request, we enqueue its flushers (requests of type
|
||||||
|
* Tflush, which are now on the flushee's lr_flushq) onto the
|
||||||
|
* response queue.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
l9p_threadpool_rflush(struct l9p_threadpool *tp, struct l9p_request *req)
|
||||||
|
{
|
||||||
|
struct l9p_request *flusher;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* https://swtch.com/plan9port/man/man9/flush.html says:
|
||||||
|
*
|
||||||
|
* "Should multiple Tflushes be received for a pending
|
||||||
|
* request, they must be answered in order. A Rflush for
|
||||||
|
* any of the multiple Tflushes implies an answer for all
|
||||||
|
* previous ones. Therefore, should a server receive a
|
||||||
|
* request and then multiple flushes for that request, it
|
||||||
|
* need respond only to the last flush." This means
|
||||||
|
* we could march through the queue of flushers here,
|
||||||
|
* marking all but the last one as "to be dropped" rather
|
||||||
|
* than "to be replied-to".
|
||||||
|
*
|
||||||
|
* However, we'll leave that for later, if ever -- it
|
||||||
|
* should be harmless to respond to each, in order.
|
||||||
|
*/
|
||||||
|
STAILQ_FOREACH(flusher, &req->lr_flushq, lr_flushlink) {
|
||||||
|
flusher->lr_workstate = L9P_WS_RESPQUEUED;
|
||||||
|
#ifdef notdef
|
||||||
|
if (not the last) {
|
||||||
|
flusher->lr_flushstate = L9P_FLUSH_NOT_RUN;
|
||||||
|
/* or, flusher->lr_drop = true ? */
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
STAILQ_INSERT_TAIL(&tp->ltp_replyq, flusher, lr_worklink);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
l9p_threadpool_init(struct l9p_threadpool *tp, int size)
|
||||||
|
{
|
||||||
|
struct l9p_worker *worker;
|
||||||
|
#if defined(__FreeBSD__)
|
||||||
|
char threadname[16];
|
||||||
|
#endif
|
||||||
|
int error;
|
||||||
|
int i, nworkers, nresponders;
|
||||||
|
|
||||||
|
if (size <= 0)
|
||||||
|
return (EINVAL);
|
||||||
|
error = pthread_mutex_init(&tp->ltp_mtx, NULL);
|
||||||
|
if (error)
|
||||||
|
return (error);
|
||||||
|
error = pthread_cond_init(&tp->ltp_work_cv, NULL);
|
||||||
|
if (error)
|
||||||
|
goto fail_work_cv;
|
||||||
|
error = pthread_cond_init(&tp->ltp_reply_cv, NULL);
|
||||||
|
if (error)
|
||||||
|
goto fail_reply_cv;
|
||||||
|
|
||||||
|
STAILQ_INIT(&tp->ltp_workq);
|
||||||
|
STAILQ_INIT(&tp->ltp_replyq);
|
||||||
|
LIST_INIT(&tp->ltp_workers);
|
||||||
|
|
||||||
|
nresponders = 0;
|
||||||
|
nworkers = 0;
|
||||||
|
for (i = 0; i <= size; i++) {
|
||||||
|
worker = calloc(1, sizeof(struct l9p_worker));
|
||||||
|
worker->ltw_tp = tp;
|
||||||
|
worker->ltw_responder = i == 0;
|
||||||
|
error = pthread_create(&worker->ltw_thread, NULL,
|
||||||
|
worker->ltw_responder ? l9p_responder : l9p_worker,
|
||||||
|
(void *)worker);
|
||||||
|
if (error) {
|
||||||
|
free(worker);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (worker->ltw_responder)
|
||||||
|
nresponders++;
|
||||||
|
else
|
||||||
|
nworkers++;
|
||||||
|
|
||||||
|
#if defined(__FreeBSD__)
|
||||||
|
if (worker->ltw_responder) {
|
||||||
|
pthread_set_name_np(worker->ltw_thread, "9p-responder");
|
||||||
|
} else {
|
||||||
|
sprintf(threadname, "9p-worker:%d", i - 1);
|
||||||
|
pthread_set_name_np(worker->ltw_thread, threadname);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
LIST_INSERT_HEAD(&tp->ltp_workers, worker, ltw_link);
|
||||||
|
}
|
||||||
|
if (nresponders == 0 || nworkers == 0) {
|
||||||
|
/* need the one responder, and at least one worker */
|
||||||
|
l9p_threadpool_shutdown(tp);
|
||||||
|
return (error);
|
||||||
|
}
|
||||||
|
return (0);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We could avoid these labels by having multiple destroy
|
||||||
|
* paths (one for each error case), or by having booleans
|
||||||
|
* for which variables were initialized. Neither is very
|
||||||
|
* appealing...
|
||||||
|
*/
|
||||||
|
fail_reply_cv:
|
||||||
|
pthread_cond_destroy(&tp->ltp_work_cv);
|
||||||
|
fail_work_cv:
|
||||||
|
pthread_mutex_destroy(&tp->ltp_mtx);
|
||||||
|
|
||||||
|
return (error);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Run a request, usually by queueing it.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
l9p_threadpool_run(struct l9p_threadpool *tp, struct l9p_request *req)
|
||||||
|
{
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Flush requests must be handled specially, since they
|
||||||
|
* can cancel / kill off regular requests. (But we can
|
||||||
|
* run them through the regular dispatch mechanism.)
|
||||||
|
*/
|
||||||
|
if (req->lr_req.hdr.type == L9P_TFLUSH) {
|
||||||
|
/* not on a work queue yet so we can touch state */
|
||||||
|
req->lr_workstate = L9P_WS_IMMEDIATE;
|
||||||
|
(void) l9p_dispatch_request(req);
|
||||||
|
} else {
|
||||||
|
pthread_mutex_lock(&tp->ltp_mtx);
|
||||||
|
req->lr_workstate = L9P_WS_NOTSTARTED;
|
||||||
|
STAILQ_INSERT_TAIL(&tp->ltp_workq, req, lr_worklink);
|
||||||
|
pthread_cond_signal(&tp->ltp_work_cv);
|
||||||
|
pthread_mutex_unlock(&tp->ltp_mtx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Run a Tflush request. Called via l9p_dispatch_request() since
|
||||||
|
* it has some debug code in it, but not called from worker thread.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
l9p_threadpool_tflush(struct l9p_request *req)
|
||||||
|
{
|
||||||
|
struct l9p_connection *conn;
|
||||||
|
struct l9p_threadpool *tp;
|
||||||
|
struct l9p_request *flushee;
|
||||||
|
uint16_t oldtag;
|
||||||
|
enum l9p_flushstate nstate;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Find what we're supposed to flush (the flushee, as it were).
|
||||||
|
*/
|
||||||
|
req->lr_error = 0; /* Tflush always succeeds */
|
||||||
|
conn = req->lr_conn;
|
||||||
|
tp = &conn->lc_tp;
|
||||||
|
oldtag = req->lr_req.tflush.oldtag;
|
||||||
|
ht_wrlock(&conn->lc_requests);
|
||||||
|
flushee = ht_find_locked(&conn->lc_requests, oldtag);
|
||||||
|
if (flushee == NULL) {
|
||||||
|
/*
|
||||||
|
* Nothing to flush! The old request must have
|
||||||
|
* been done and gone already. Just queue this
|
||||||
|
* Tflush for a success reply.
|
||||||
|
*/
|
||||||
|
ht_unlock(&conn->lc_requests);
|
||||||
|
pthread_mutex_lock(&tp->ltp_mtx);
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Found the original request. We'll need to inspect its
|
||||||
|
* work-state to figure out what to do.
|
||||||
|
*/
|
||||||
|
pthread_mutex_lock(&tp->ltp_mtx);
|
||||||
|
ht_unlock(&conn->lc_requests);
|
||||||
|
|
||||||
|
switch (flushee->lr_workstate) {
|
||||||
|
|
||||||
|
case L9P_WS_NOTSTARTED:
|
||||||
|
/*
|
||||||
|
* Flushee is on work queue, but not yet being
|
||||||
|
* handled by a worker.
|
||||||
|
*
|
||||||
|
* The documentation -- see
|
||||||
|
* http://ericvh.github.io/9p-rfc/rfc9p2000.html
|
||||||
|
* https://swtch.com/plan9port/man/man9/flush.html
|
||||||
|
* -- says that "the server should answer the
|
||||||
|
* flush message immediately". However, Linux
|
||||||
|
* sends flush requests for operations that
|
||||||
|
* must finish, such as Tclunk, and it's not
|
||||||
|
* possible to *answer* the flush request until
|
||||||
|
* it has been handled (if necessary) or aborted
|
||||||
|
* (if allowed).
|
||||||
|
*
|
||||||
|
* We therefore now just the original request
|
||||||
|
* and let the request-handler do whatever is
|
||||||
|
* appropriate. NOTE: we could have a table of
|
||||||
|
* "requests that can be aborted without being
|
||||||
|
* run" vs "requests that must be run to be
|
||||||
|
* aborted", but for now that seems like an
|
||||||
|
* unnecessary complication.
|
||||||
|
*/
|
||||||
|
nstate = L9P_FLUSH_REQUESTED_PRE_START;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case L9P_WS_IMMEDIATE:
|
||||||
|
/*
|
||||||
|
* This state only applies to Tflush requests, and
|
||||||
|
* flushing a Tflush is illegal. But we'll do nothing
|
||||||
|
* special here, which will make us act like a flush
|
||||||
|
* request for the flushee that arrived too late to
|
||||||
|
* do anything about the flushee.
|
||||||
|
*/
|
||||||
|
nstate = L9P_FLUSH_REQUESTED_POST_START;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case L9P_WS_INPROGRESS:
|
||||||
|
/*
|
||||||
|
* Worker thread flushee->lr_worker is working on it.
|
||||||
|
* Kick it to get it out of blocking system calls.
|
||||||
|
* (This requires that it carefully set up some
|
||||||
|
* signal handlers, and may be FreeBSD-dependent,
|
||||||
|
* it probably cannot be handled this way on MacOS.)
|
||||||
|
*/
|
||||||
|
#ifdef notyet
|
||||||
|
pthread_kill(...);
|
||||||
|
#endif
|
||||||
|
nstate = L9P_FLUSH_REQUESTED_POST_START;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case L9P_WS_RESPQUEUED:
|
||||||
|
/*
|
||||||
|
* The flushee is already in the response queue.
|
||||||
|
* We'll just mark it as having had some flush
|
||||||
|
* action applied.
|
||||||
|
*/
|
||||||
|
nstate = L9P_FLUSH_TOOLATE;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case L9P_WS_REPLYING:
|
||||||
|
/*
|
||||||
|
* Although we found the flushee, it's too late to
|
||||||
|
* make us depend on it: it's already heading out
|
||||||
|
* the door as a reply.
|
||||||
|
*
|
||||||
|
* We don't want to do anything to the flushee.
|
||||||
|
* Instead, we want to work the same way as if
|
||||||
|
* we had never found the tag.
|
||||||
|
*/
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Now add us to the list of Tflush-es that are waiting
|
||||||
|
* for the flushee (creating the list if needed, i.e., if
|
||||||
|
* this is the first Tflush for the flushee). We (req)
|
||||||
|
* will get queued for reply later, when the responder
|
||||||
|
* processes the flushee and calls l9p_threadpool_rflush().
|
||||||
|
*/
|
||||||
|
if (flushee->lr_flushstate == L9P_FLUSH_NONE)
|
||||||
|
STAILQ_INIT(&flushee->lr_flushq);
|
||||||
|
flushee->lr_flushstate = nstate;
|
||||||
|
STAILQ_INSERT_TAIL(&flushee->lr_flushq, req, lr_flushlink);
|
||||||
|
|
||||||
|
pthread_mutex_unlock(&tp->ltp_mtx);
|
||||||
|
|
||||||
|
return (0);
|
||||||
|
|
||||||
|
done:
|
||||||
|
/*
|
||||||
|
* This immediate op is ready to be replied-to now, so just
|
||||||
|
* stick it onto the reply queue.
|
||||||
|
*/
|
||||||
|
req->lr_workstate = L9P_WS_RESPQUEUED;
|
||||||
|
STAILQ_INSERT_TAIL(&tp->ltp_replyq, req, lr_worklink);
|
||||||
|
pthread_mutex_unlock(&tp->ltp_mtx);
|
||||||
|
pthread_cond_signal(&tp->ltp_reply_cv);
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
l9p_threadpool_shutdown(struct l9p_threadpool *tp)
|
||||||
|
{
|
||||||
|
struct l9p_worker *worker, *tmp;
|
||||||
|
|
||||||
|
LIST_FOREACH_SAFE(worker, &tp->ltp_workers, ltw_link, tmp) {
|
||||||
|
pthread_mutex_lock(&tp->ltp_mtx);
|
||||||
|
worker->ltw_exiting = true;
|
||||||
|
if (worker->ltw_responder)
|
||||||
|
pthread_cond_signal(&tp->ltp_reply_cv);
|
||||||
|
else
|
||||||
|
pthread_cond_broadcast(&tp->ltp_work_cv);
|
||||||
|
pthread_mutex_unlock(&tp->ltp_mtx);
|
||||||
|
pthread_join(worker->ltw_thread, NULL);
|
||||||
|
LIST_REMOVE(worker, ltw_link);
|
||||||
|
free(worker);
|
||||||
|
}
|
||||||
|
pthread_cond_destroy(&tp->ltp_reply_cv);
|
||||||
|
pthread_cond_destroy(&tp->ltp_work_cv);
|
||||||
|
pthread_mutex_destroy(&tp->ltp_mtx);
|
||||||
|
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 Jakub Klama <jceel@FreeBSD.org>
|
||||||
|
* All rights reserved
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted providing that the following conditions
|
||||||
|
* are met:
|
||||||
|
* 1. Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||||
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||||||
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
||||||
|
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef LIB9P_THREADPOOL_H
|
||||||
|
#define LIB9P_THREADPOOL_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <sys/queue.h>
|
||||||
|
#include "lib9p.h"
|
||||||
|
|
||||||
|
STAILQ_HEAD(l9p_request_queue, l9p_request);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Most of the workers in the threadpool run requests.
|
||||||
|
*
|
||||||
|
* One distinguished worker delivers responses from the
|
||||||
|
* response queue. The reason this worker exists is to
|
||||||
|
* guarantee response order, so that flush responses go
|
||||||
|
* after their flushed requests.
|
||||||
|
*/
|
||||||
|
struct l9p_threadpool {
|
||||||
|
struct l9p_connection * ltp_conn; /* the connection */
|
||||||
|
struct l9p_request_queue ltp_workq; /* requests awaiting a worker */
|
||||||
|
struct l9p_request_queue ltp_replyq; /* requests that are done */
|
||||||
|
pthread_mutex_t ltp_mtx; /* locks queues and cond vars */
|
||||||
|
pthread_cond_t ltp_work_cv; /* to signal regular workers */
|
||||||
|
pthread_cond_t ltp_reply_cv; /* to signal reply-worker */
|
||||||
|
LIST_HEAD(, l9p_worker) ltp_workers; /* list of all workers */
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* All workers, including the responder, use this as their
|
||||||
|
* control structure. (The only thing that distinguishes the
|
||||||
|
* responder is that it runs different code and waits on the
|
||||||
|
* reply_cv.)
|
||||||
|
*/
|
||||||
|
struct l9p_worker {
|
||||||
|
struct l9p_threadpool * ltw_tp;
|
||||||
|
pthread_t ltw_thread;
|
||||||
|
bool ltw_exiting;
|
||||||
|
bool ltw_responder;
|
||||||
|
LIST_ENTRY(l9p_worker) ltw_link;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Each request has a "work state" telling where the request is,
|
||||||
|
* in terms of workers working on it. That is, this tells us
|
||||||
|
* which threadpool queue, if any, the request is in now or would
|
||||||
|
* go in, or what's happening with it.
|
||||||
|
*/
|
||||||
|
enum l9p_workstate {
|
||||||
|
L9P_WS_NOTSTARTED, /* not yet started */
|
||||||
|
L9P_WS_IMMEDIATE, /* Tflush being done sans worker */
|
||||||
|
L9P_WS_INPROGRESS, /* worker is working on it */
|
||||||
|
L9P_WS_RESPQUEUED, /* worker is done, response queued */
|
||||||
|
L9P_WS_REPLYING, /* responder is in final reply path */
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Each request has a "flush state", initally NONE meaning no
|
||||||
|
* Tflush affected the request.
|
||||||
|
*
|
||||||
|
* If a Tflush comes in before we ever assign a work thread,
|
||||||
|
* the flush state goes to FLUSH_REQUESTED_PRE_START.
|
||||||
|
*
|
||||||
|
* If a Tflush comes in after we assign a work thread, the
|
||||||
|
* flush state goes to FLUSH_REQUESTED_POST_START. The flush
|
||||||
|
* request may be too late: the request might finish anyway.
|
||||||
|
* Or it might be soon enough to abort. In all cases, though, the
|
||||||
|
* operation requesting the flush (the "flusher") must wait for
|
||||||
|
* the other request (the "flushee") to go through the respond
|
||||||
|
* path. The respond routine gets to decide whether to send a
|
||||||
|
* normal response, send an error, or drop the request
|
||||||
|
* entirely.
|
||||||
|
*
|
||||||
|
* There's one especially annoying case: what if a Tflush comes in
|
||||||
|
* *while* we're sending a response? In this case it's too late:
|
||||||
|
* the flush just waits for the fully-composed response.
|
||||||
|
*/
|
||||||
|
enum l9p_flushstate {
|
||||||
|
L9P_FLUSH_NONE = 0, /* must be zero */
|
||||||
|
L9P_FLUSH_REQUESTED_PRE_START, /* not even started before flush */
|
||||||
|
L9P_FLUSH_REQUESTED_POST_START, /* started, then someone said flush */
|
||||||
|
L9P_FLUSH_TOOLATE /* too late, already responding */
|
||||||
|
};
|
||||||
|
|
||||||
|
void l9p_threadpool_flushee_done(struct l9p_request *);
|
||||||
|
int l9p_threadpool_init(struct l9p_threadpool *, int);
|
||||||
|
void l9p_threadpool_run(struct l9p_threadpool *, struct l9p_request *);
|
||||||
|
int l9p_threadpool_shutdown(struct l9p_threadpool *);
|
||||||
|
int l9p_threadpool_tflush(struct l9p_request *);
|
||||||
|
|
||||||
|
#endif /* LIB9P_THREADPOOL_H */
|
||||||
@@ -0,0 +1,363 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 Jakub Klama <jceel@FreeBSD.org>
|
||||||
|
* All rights reserved
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted providing that the following conditions
|
||||||
|
* are met:
|
||||||
|
* 1. Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||||
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||||||
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
||||||
|
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#ifdef __APPLE__
|
||||||
|
# include "../apple_endian.h"
|
||||||
|
#else
|
||||||
|
# include <sys/endian.h>
|
||||||
|
#endif
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/event.h>
|
||||||
|
#include <sys/uio.h>
|
||||||
|
#include <netdb.h>
|
||||||
|
#include "../lib9p.h"
|
||||||
|
#include "../lib9p_impl.h"
|
||||||
|
#include "../log.h"
|
||||||
|
#include "socket.h"
|
||||||
|
|
||||||
|
struct l9p_socket_softc
|
||||||
|
{
|
||||||
|
struct l9p_connection *ls_conn;
|
||||||
|
struct sockaddr ls_sockaddr;
|
||||||
|
socklen_t ls_socklen;
|
||||||
|
pthread_t ls_thread;
|
||||||
|
int ls_fd;
|
||||||
|
};
|
||||||
|
|
||||||
|
static int l9p_socket_readmsg(struct l9p_socket_softc *, void **, size_t *);
|
||||||
|
static int l9p_socket_get_response_buffer(struct l9p_request *,
|
||||||
|
struct iovec *, size_t *, void *);
|
||||||
|
static int l9p_socket_send_response(struct l9p_request *, const struct iovec *,
|
||||||
|
const size_t, const size_t, void *);
|
||||||
|
static void l9p_socket_drop_response(struct l9p_request *, const struct iovec *,
|
||||||
|
size_t, void *);
|
||||||
|
static void *l9p_socket_thread(void *);
|
||||||
|
static ssize_t xread(int, void *, size_t);
|
||||||
|
static ssize_t xwrite(int, void *, size_t);
|
||||||
|
|
||||||
|
int
|
||||||
|
l9p_start_server(struct l9p_server *server, const char *host, const char *port)
|
||||||
|
{
|
||||||
|
struct addrinfo *res, *res0, hints;
|
||||||
|
struct kevent kev[2];
|
||||||
|
struct kevent event[2];
|
||||||
|
int err, kq, i, val, evs, nsockets = 0;
|
||||||
|
int sockets[2];
|
||||||
|
|
||||||
|
memset(&hints, 0, sizeof(hints));
|
||||||
|
hints.ai_family = PF_UNSPEC;
|
||||||
|
hints.ai_socktype = SOCK_STREAM;
|
||||||
|
err = getaddrinfo(host, port, &hints, &res0);
|
||||||
|
|
||||||
|
if (err)
|
||||||
|
return (-1);
|
||||||
|
|
||||||
|
for (res = res0; res; res = res->ai_next) {
|
||||||
|
int s = socket(res->ai_family, res->ai_socktype,
|
||||||
|
res->ai_protocol);
|
||||||
|
|
||||||
|
val = 1;
|
||||||
|
setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val));
|
||||||
|
|
||||||
|
if (s < 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (bind(s, res->ai_addr, res->ai_addrlen) < 0) {
|
||||||
|
close(s);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
sockets[nsockets] = s;
|
||||||
|
EV_SET(&kev[nsockets++], s, EVFILT_READ, EV_ADD | EV_ENABLE, 0,
|
||||||
|
0, 0);
|
||||||
|
listen(s, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nsockets < 1) {
|
||||||
|
L9P_LOG(L9P_ERROR, "bind(): %s", strerror(errno));
|
||||||
|
return(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
kq = kqueue();
|
||||||
|
|
||||||
|
if (kevent(kq, kev, nsockets, NULL, 0, NULL) < 0) {
|
||||||
|
L9P_LOG(L9P_ERROR, "kevent(): %s", strerror(errno));
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
evs = kevent(kq, NULL, 0, event, nsockets, NULL);
|
||||||
|
if (evs < 0) {
|
||||||
|
if (errno == EINTR)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
L9P_LOG(L9P_ERROR, "kevent(): %s", strerror(errno));
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < evs; i++) {
|
||||||
|
struct sockaddr client_addr;
|
||||||
|
socklen_t client_addr_len = sizeof(client_addr);
|
||||||
|
int news = accept((int)event[i].ident, &client_addr,
|
||||||
|
&client_addr_len);
|
||||||
|
|
||||||
|
if (news < 0) {
|
||||||
|
L9P_LOG(L9P_WARNING, "accept(): %s",
|
||||||
|
strerror(errno));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
l9p_socket_accept(server, news, &client_addr,
|
||||||
|
client_addr_len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
l9p_socket_accept(struct l9p_server *server, int conn_fd,
|
||||||
|
struct sockaddr *client_addr, socklen_t client_addr_len)
|
||||||
|
{
|
||||||
|
struct l9p_socket_softc *sc;
|
||||||
|
struct l9p_connection *conn;
|
||||||
|
char host[NI_MAXHOST + 1];
|
||||||
|
char serv[NI_MAXSERV + 1];
|
||||||
|
int err;
|
||||||
|
|
||||||
|
err = getnameinfo(client_addr, client_addr_len, host, NI_MAXHOST, serv,
|
||||||
|
NI_MAXSERV, NI_NUMERICHOST | NI_NUMERICSERV);
|
||||||
|
|
||||||
|
if (err != 0) {
|
||||||
|
L9P_LOG(L9P_WARNING, "cannot look up client name: %s",
|
||||||
|
gai_strerror(err));
|
||||||
|
} else {
|
||||||
|
L9P_LOG(L9P_INFO, "new connection from %s:%s", host, serv);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (l9p_connection_init(server, &conn) != 0) {
|
||||||
|
L9P_LOG(L9P_ERROR, "cannot create new connection");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sc = l9p_calloc(1, sizeof(*sc));
|
||||||
|
sc->ls_conn = conn;
|
||||||
|
sc->ls_fd = conn_fd;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Fill in transport handler functions and aux argument.
|
||||||
|
*/
|
||||||
|
conn->lc_lt.lt_aux = sc;
|
||||||
|
conn->lc_lt.lt_get_response_buffer = l9p_socket_get_response_buffer;
|
||||||
|
conn->lc_lt.lt_send_response = l9p_socket_send_response;
|
||||||
|
conn->lc_lt.lt_drop_response = l9p_socket_drop_response;
|
||||||
|
|
||||||
|
err = pthread_create(&sc->ls_thread, NULL, l9p_socket_thread, sc);
|
||||||
|
if (err) {
|
||||||
|
L9P_LOG(L9P_ERROR,
|
||||||
|
"pthread_create (for connection from %s:%s): error %s",
|
||||||
|
host, serv, strerror(err));
|
||||||
|
l9p_connection_close(sc->ls_conn);
|
||||||
|
free(sc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *
|
||||||
|
l9p_socket_thread(void *arg)
|
||||||
|
{
|
||||||
|
struct l9p_socket_softc *sc = (struct l9p_socket_softc *)arg;
|
||||||
|
struct iovec iov;
|
||||||
|
void *buf;
|
||||||
|
size_t length;
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
if (l9p_socket_readmsg(sc, &buf, &length) != 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
iov.iov_base = buf;
|
||||||
|
iov.iov_len = length;
|
||||||
|
l9p_connection_recv(sc->ls_conn, &iov, 1, NULL);
|
||||||
|
free(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
L9P_LOG(L9P_INFO, "connection closed");
|
||||||
|
l9p_connection_close(sc->ls_conn);
|
||||||
|
free(sc);
|
||||||
|
return (NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
l9p_socket_readmsg(struct l9p_socket_softc *sc, void **buf, size_t *size)
|
||||||
|
{
|
||||||
|
uint32_t msize;
|
||||||
|
size_t toread;
|
||||||
|
ssize_t ret;
|
||||||
|
void *buffer;
|
||||||
|
int fd = sc->ls_fd;
|
||||||
|
|
||||||
|
assert(fd > 0);
|
||||||
|
|
||||||
|
buffer = l9p_malloc(sizeof(uint32_t));
|
||||||
|
|
||||||
|
ret = xread(fd, buffer, sizeof(uint32_t));
|
||||||
|
if (ret < 0) {
|
||||||
|
L9P_LOG(L9P_ERROR, "read(): %s", strerror(errno));
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret != sizeof(uint32_t)) {
|
||||||
|
if (ret == 0)
|
||||||
|
L9P_LOG(L9P_DEBUG, "%p: EOF", (void *)sc->ls_conn);
|
||||||
|
else
|
||||||
|
L9P_LOG(L9P_ERROR,
|
||||||
|
"short read: %zd bytes of %zd expected",
|
||||||
|
ret, sizeof(uint32_t));
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
msize = le32toh(*(uint32_t *)buffer);
|
||||||
|
toread = msize - sizeof(uint32_t);
|
||||||
|
buffer = l9p_realloc(buffer, msize);
|
||||||
|
|
||||||
|
ret = xread(fd, (char *)buffer + sizeof(uint32_t), toread);
|
||||||
|
if (ret < 0) {
|
||||||
|
L9P_LOG(L9P_ERROR, "read(): %s", strerror(errno));
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret != (ssize_t)toread) {
|
||||||
|
L9P_LOG(L9P_ERROR, "short read: %zd bytes of %zd expected",
|
||||||
|
ret, toread);
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
*size = msize;
|
||||||
|
*buf = buffer;
|
||||||
|
L9P_LOG(L9P_INFO, "%p: read complete message, buf=%p size=%d",
|
||||||
|
(void *)sc->ls_conn, buffer, msize);
|
||||||
|
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
l9p_socket_get_response_buffer(struct l9p_request *req, struct iovec *iov,
|
||||||
|
size_t *niovp, void *arg __unused)
|
||||||
|
{
|
||||||
|
size_t size = req->lr_conn->lc_msize;
|
||||||
|
void *buf;
|
||||||
|
|
||||||
|
buf = l9p_malloc(size);
|
||||||
|
iov[0].iov_base = buf;
|
||||||
|
iov[0].iov_len = size;
|
||||||
|
|
||||||
|
*niovp = 1;
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
l9p_socket_send_response(struct l9p_request *req __unused,
|
||||||
|
const struct iovec *iov, const size_t niov __unused, const size_t iolen,
|
||||||
|
void *arg)
|
||||||
|
{
|
||||||
|
struct l9p_socket_softc *sc = (struct l9p_socket_softc *)arg;
|
||||||
|
|
||||||
|
assert(sc->ls_fd >= 0);
|
||||||
|
|
||||||
|
L9P_LOG(L9P_DEBUG, "%p: sending reply, buf=%p, size=%d", arg,
|
||||||
|
iov[0].iov_base, iolen);
|
||||||
|
|
||||||
|
if (xwrite(sc->ls_fd, iov[0].iov_base, iolen) != (int)iolen) {
|
||||||
|
L9P_LOG(L9P_ERROR, "short write: %s", strerror(errno));
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(iov[0].iov_base);
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
l9p_socket_drop_response(struct l9p_request *req __unused,
|
||||||
|
const struct iovec *iov, size_t niov __unused, void *arg)
|
||||||
|
{
|
||||||
|
|
||||||
|
L9P_LOG(L9P_DEBUG, "%p: drop buf=%p", arg, iov[0].iov_base);
|
||||||
|
free(iov[0].iov_base);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t
|
||||||
|
xread(int fd, void *buf, size_t count)
|
||||||
|
{
|
||||||
|
size_t done = 0;
|
||||||
|
ssize_t ret;
|
||||||
|
|
||||||
|
while (done < count) {
|
||||||
|
ret = read(fd, (char *)buf + done, count - done);
|
||||||
|
if (ret < 0) {
|
||||||
|
if (errno == EINTR)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret == 0)
|
||||||
|
return ((ssize_t)done);
|
||||||
|
|
||||||
|
done += (size_t)ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ((ssize_t)done);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t
|
||||||
|
xwrite(int fd, void *buf, size_t count)
|
||||||
|
{
|
||||||
|
size_t done = 0;
|
||||||
|
ssize_t ret;
|
||||||
|
|
||||||
|
while (done < count) {
|
||||||
|
ret = write(fd, (char *)buf + done, count - done);
|
||||||
|
if (ret < 0) {
|
||||||
|
if (errno == EINTR)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret == 0)
|
||||||
|
return ((ssize_t)done);
|
||||||
|
|
||||||
|
done += (size_t)ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ((ssize_t)done);
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 Jakub Klama <jceel@FreeBSD.org>
|
||||||
|
* All rights reserved
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted providing that the following conditions
|
||||||
|
* are met:
|
||||||
|
* 1. Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||||
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||||||
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
||||||
|
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef LIB9P_SOCKET_H
|
||||||
|
#define LIB9P_SOCKET_H
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include "../lib9p.h"
|
||||||
|
|
||||||
|
int l9p_start_server(struct l9p_server *server, const char *host,
|
||||||
|
const char *port);
|
||||||
|
void l9p_socket_accept(struct l9p_server *server, int conn_fd,
|
||||||
|
struct sockaddr *client_addr, socklen_t client_addr_len);
|
||||||
|
|
||||||
|
#endif /* LIB9P_SOCKET_H */
|
||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user