Import lib9p 7ddb1164407da19b9b1afb83df83ae65a71a9a66.
Approved by: trasz (mentor) MFC after: 1 month Sponsored by: Conclusive Engineering
This commit is contained in:
commit
6d9daf7da2
Notes:
svn2git
2020-12-20 02:59:44 +00:00
svn path=/vendor/lib9p/dist/; revision=360470 svn path=/vendor/lib9p/7ddb1164407da19b9b1afb83df83ae65a71a9a66/; revision=360471; tag=vendor/lib9p/7ddb1164407da19b9b1afb83df83ae65a71a9a66
37
.gitignore
vendored
Normal file
37
.gitignore
vendored
Normal file
@ -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
|
47
COPYRIGHT
Normal file
47
COPYRIGHT
Normal file
@ -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.
|
76
GNUmakefile
Normal file
76
GNUmakefile
Normal file
@ -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 $@
|
27
Makefile
Normal file
27
Makefile
Normal file
@ -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>
|
20
README.md
Normal file
20
README.md
Normal file
@ -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)
|
27
apple_endian.h
Normal file
27
apple_endian.h
Normal file
@ -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 */
|
69
backend/backend.h
Normal file
69
backend/backend.h
Normal file
@ -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 */
|
3061
backend/fs.c
Normal file
3061
backend/fs.c
Normal file
File diff suppressed because it is too large
Load Diff
37
backend/fs.h
Normal file
37
backend/fs.h
Normal file
@ -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 */
|
215
connection.c
Normal file
215
connection.c
Normal file
@ -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);
|
||||
}
|
10
example/Makefile
Normal file
10
example/Makefile
Normal file
@ -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>
|
89
example/server.c
Normal file
89
example/server.c
Normal file
@ -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);
|
||||
}
|
624
fcall.h
Normal file
624
fcall.h
Normal file
@ -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 */
|
160
fid.h
Normal file
160
fid.h
Normal file
@ -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 */
|
720
genacl.c
Normal file
720
genacl.c
Normal file
@ -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
|
307
genacl.h
Normal file
307
genacl.h
Normal file
@ -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
|
267
hashtable.c
Normal file
267
hashtable.c
Normal file
@ -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);
|
||||
}
|
107
hashtable.h
Normal file
107
hashtable.h
Normal file
@ -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 */
|
249
lib9p.h
Normal file
249
lib9p.h
Normal file
@ -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 */
|
78
lib9p_impl.h
Normal file
78
lib9p_impl.h
Normal file
@ -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 */
|
247
linux_errno.h
Normal file
247
linux_errno.h
Normal file
@ -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 */
|
67
log.c
Normal file
67
log.c
Normal file
@ -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);
|
||||
}
|
46
log.h
Normal file
46
log.h
Normal file
@ -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 */
|
993
pack.c
Normal file
993
pack.c
Normal file
@ -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);
|
||||
}
|
3
pytest/.gitignore
vendored
Normal file
3
pytest/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
*.pyc
|
||||
__pycache__
|
||||
testconf.ini
|
9
pytest/Makefile
Normal file
9
pytest/Makefile
Normal file
@ -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
|
32
pytest/README
Normal file
32
pytest/README
Normal file
@ -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".
|
643
pytest/client.py
Executable file
643
pytest/client.py
Executable file
@ -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')
|
291
pytest/lerrno.py
Normal file
291
pytest/lerrno.py
Normal file
@ -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()
|
379
pytest/numalloc.py
Normal file
379
pytest/numalloc.py
Normal file
@ -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')
|
1788
pytest/p9conn.py
Normal file
1788
pytest/p9conn.py
Normal file
File diff suppressed because it is too large
Load Diff
146
pytest/p9err.py
Normal file
146
pytest/p9err.py
Normal file
@ -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()
|
204
pytest/pfod.py
Normal file
204
pytest/pfod.py
Normal file
@ -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()
|
1998
pytest/protocol.py
Normal file
1998
pytest/protocol.py
Normal file
File diff suppressed because it is too large
Load Diff
653
pytest/sequencer.py
Normal file
653
pytest/sequencer.py
Normal file
@ -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()
|
16
pytest/testconf.ini.sample
Normal file
16
pytest/testconf.ini.sample
Normal file
@ -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
|
320
rfuncs.c
Normal file
320
rfuncs.c
Normal file
@ -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
|
79
rfuncs.h
Normal file
79
rfuncs.h
Normal file
@ -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 */
|
127
sbuf/sbuf.c
Normal file
127
sbuf/sbuf.c
Normal file
@ -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);
|
||||
}
|
55
sbuf/sbuf.h
Normal file
55
sbuf/sbuf.h
Normal file
@ -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 */
|
||||
|
422
threadpool.c
Normal file
422
threadpool.c
Normal file
@ -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);
|
||||
}
|
118
threadpool.h
Normal file
118
threadpool.h
Normal file
@ -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 */
|
363
transport/socket.c
Normal file
363
transport/socket.c
Normal file
@ -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);
|
||||
}
|
40
transport/socket.h
Normal file
40
transport/socket.h
Normal file
@ -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 */
|
Loading…
Reference in New Issue
Block a user