@@ -3,8 +3,6 @@ | |||
*.8.gz | |||
*.full | |||
*.debug | |||
ggatec/ggatec | |||
ggated/ggated | |||
ggatel/ggatel | |||
ggatehttp/ggatehttp | |||
tests/Kyuafile | |||
tests/ggate_test | |||
tests/ggatehttp_test |
@@ -1,16 +1,13 @@ | |||
# $FreeBSD$ | |||
SUBDIR= ${_ggatec} \ | |||
${_ggated} \ | |||
ggatel \ | |||
SUBDIR= ${_ggatehttp} \ | |||
tests | |||
_ggatec= ggatec | |||
_ggated= ggated | |||
_ggatehttp= ggatehttp | |||
.PHONY: devtest | |||
devtest: | |||
find [gst]* -type f -name '*.c' -o -name 'Makefile*' -o -name '*.sh' -o -name '*.8' | entr sh -c 'make -j 4 && make install -j 4 && (kyua test -k /usr/tests/Kyuafile sys/geom/class/gate || (cd /usr/tests && kyua report --verbose))' | |||
find [gst]* -type f -name '*.c' -o -name 'Makefile*' -o -name '*.sh' -o -name '*.8' | entr sh -c '(set -o pipefail; make -j 4 | head -n 30) && make install -j 4 && (kyua test -k /usr/tests/Kyuafile sys/geom/class/gate || (cd /usr/tests && kyua report --verbose))' | |||
.include <bsd.subdir.mk> |
@@ -1,4 +1,20 @@ | |||
ggate working tree | |||
================== | |||
https ggate working tree | |||
======================== | |||
This is a working tree for ggate work. | |||
This is a variant of ggatec using http(s) GET/PUT instead of talking | |||
to ggated. | |||
Note that when I started on this project, that this would be completely | |||
standards complaint. After running into an issue w/ the wsgidav server | |||
not supporting partial PUTs, I did some research, and came across this | |||
[post](https://blog.sphere.chronosempire.org.uk/2012/11/21/webdav-and-the-http-patch-nightmare) | |||
that talks about how the IETF intentionally broke partial PUTs, despite | |||
having the same problems w/ other parts of their spec. | |||
Servers known to work: | |||
- apache 2.2.x: can truncate file under some conditions | |||
Services known to not work: | |||
- wsgidav (Python): Does not implement partial PUT |
@@ -1,15 +0,0 @@ | |||
# $FreeBSD$ | |||
.PATH: ${.CURDIR:H}/shared | |||
PROG= ggatec | |||
MAN= ggatec.8 | |||
SRCS= ggatec.c ggate.c | |||
CFLAGS+= -DMAX_SEND_SIZE=32768 | |||
CFLAGS+= -DLIBGEOM | |||
CFLAGS+= -I${.CURDIR:H}/shared | |||
LDADD= -lgeom -lutil -lpthread | |||
.include <bsd.prog.mk> |
@@ -1,710 +0,0 @@ | |||
/*- | |||
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD | |||
* | |||
* Copyright (c) 2004 Pawel Jakub Dawidek <pjd@FreeBSD.org> | |||
* All rights reserved. | |||
* | |||
* Redistribution and use in source and binary forms, with or without | |||
* modification, are permitted provided 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 AUTHORS AND CONTRIBUTORS ``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 AUTHORS OR CONTRIBUTORS 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. | |||
* | |||
* $FreeBSD$ | |||
*/ | |||
#include <stdio.h> | |||
#include <stdlib.h> | |||
#include <stdint.h> | |||
#include <fcntl.h> | |||
#include <unistd.h> | |||
#include <string.h> | |||
#include <ctype.h> | |||
#include <libgen.h> | |||
#include <pthread.h> | |||
#include <signal.h> | |||
#include <err.h> | |||
#include <errno.h> | |||
#include <assert.h> | |||
#include <sys/param.h> | |||
#include <sys/ioctl.h> | |||
#include <sys/socket.h> | |||
#include <sys/sysctl.h> | |||
#include <sys/syslog.h> | |||
#include <sys/time.h> | |||
#include <sys/bio.h> | |||
#include <sys/un.h> | |||
#include <netinet/in.h> | |||
#include <netinet/tcp.h> | |||
#include <arpa/inet.h> | |||
#include <geom/gate/g_gate.h> | |||
#include "ggate.h" | |||
static const char *sockprefix = "sock:"; | |||
static enum { UNSET, CREATE, DESTROY, LIST, RESCUE } action = UNSET; | |||
static const char *path = NULL; | |||
static const char *host = NULL; | |||
static int unit = G_GATE_UNIT_AUTO; | |||
static unsigned flags = 0; | |||
static int force = 0; | |||
static unsigned queue_size = G_GATE_QUEUE_SIZE; | |||
static unsigned port = G_GATE_PORT; | |||
static off_t mediasize; | |||
static unsigned sectorsize = 0; | |||
static unsigned timeout = G_GATE_TIMEOUT; | |||
static int sendfd, recvfd; | |||
static uint32_t token; | |||
static pthread_t sendtd, recvtd; | |||
static int reconnect; | |||
static void | |||
usage(void) | |||
{ | |||
fprintf(stderr, "usage: %s create [-nv] [-o <ro|wo|rw>] [-p port] " | |||
"[-q queue_size] [-R rcvbuf] [-S sndbuf] [-s sectorsize] " | |||
"[-t timeout] [-u unit] <host> <path>\n", getprogname()); | |||
fprintf(stderr, " %s rescue [-nv] [-o <ro|wo|rw>] [-p port] " | |||
"[-R rcvbuf] [-S sndbuf] <-u unit> <host> <path>\n", getprogname()); | |||
fprintf(stderr, " %s destroy [-f] <-u unit>\n", getprogname()); | |||
fprintf(stderr, " %s list [-v] [-u unit]\n", getprogname()); | |||
exit(EXIT_FAILURE); | |||
} | |||
static void * | |||
send_thread(void *arg __unused) | |||
{ | |||
struct g_gate_ctl_io ggio; | |||
struct g_gate_hdr hdr; | |||
static char *buf; | |||
ssize_t data; | |||
int buflen = 1024*1024; | |||
int error; | |||
if (buf == NULL) | |||
buf = malloc(buflen); | |||
g_gate_log(LOG_NOTICE, "%s: started!", __func__); | |||
ggio.gctl_version = G_GATE_VERSION; | |||
ggio.gctl_unit = unit; | |||
ggio.gctl_data = buf; | |||
for (;;) { | |||
ggio.gctl_length = buflen; | |||
ggio.gctl_error = 0; | |||
g_gate_ioctl(G_GATE_CMD_START, &ggio); | |||
error = ggio.gctl_error; | |||
switch (error) { | |||
case 0: | |||
break; | |||
case ECANCELED: | |||
if (reconnect) | |||
break; | |||
/* Exit gracefully. */ | |||
g_gate_close_device(); | |||
exit(EXIT_SUCCESS); | |||
#if 0 | |||
case ENOMEM: | |||
/* Buffer too small. */ | |||
ggio.gctl_data = realloc(ggio.gctl_data, | |||
ggio.gctl_length); | |||
if (ggio.gctl_data != NULL) { | |||
bsize = ggio.gctl_length; | |||
goto once_again; | |||
} | |||
/* FALLTHROUGH */ | |||
#endif | |||
case ENXIO: | |||
default: | |||
g_gate_xlog("ioctl(/dev/%s): %s.", G_GATE_CTL_NAME, | |||
strerror(error)); | |||
} | |||
if (reconnect) | |||
break; | |||
g_gate_log(LOG_DEBUG, "ggio, ver: %u, unit: %d, seq: %llu, cmd: %u, offset: %llu, len: %llu", ggio.gctl_version, ggio.gctl_unit, ggio.gctl_seq, ggio.gctl_cmd, ggio.gctl_offset, ggio.gctl_length); | |||
switch (ggio.gctl_cmd) { | |||
case BIO_READ: | |||
hdr.gh_cmd = GGATE_CMD_READ; | |||
break; | |||
case BIO_WRITE: | |||
hdr.gh_cmd = GGATE_CMD_WRITE; | |||
break; | |||
case BIO_DELETE: | |||
hdr.gh_cmd = GGATE_CMD_DELETE; | |||
break; | |||
case BIO_FLUSH: | |||
hdr.gh_cmd = GGATE_CMD_FLUSH; | |||
break; | |||
default: | |||
/* XXX - how to handle this? */ | |||
g_gate_log(LOG_ERR, "Got unhandled cmd: %d", ggio.gctl_cmd); | |||
reconnect = 1; | |||
pthread_kill(recvtd, SIGUSR1); | |||
break; | |||
} | |||
hdr.gh_seq = ggio.gctl_seq; | |||
hdr.gh_offset = ggio.gctl_offset; | |||
hdr.gh_length = ggio.gctl_length; | |||
hdr.gh_error = 0; | |||
g_gate_log(LOG_DEBUG, "hdr packet, cmd: %hhu, off: %llu, len: %u, seq: %u", hdr.gh_cmd, hdr.gh_offset, hdr.gh_length, hdr.gh_seq); | |||
g_gate_swap2n_hdr(&hdr); | |||
data = g_gate_send(sendfd, &hdr, sizeof(hdr), MSG_NOSIGNAL); | |||
g_gate_log(LOG_DEBUG, "Sent hdr packet."); | |||
g_gate_swap2h_hdr(&hdr); | |||
if (reconnect) | |||
break; | |||
if (data != sizeof(hdr)) { | |||
g_gate_log(LOG_ERR, "Lost connection 1."); | |||
reconnect = 1; | |||
pthread_kill(recvtd, SIGUSR1); | |||
break; | |||
} | |||
if (hdr.gh_cmd == GGATE_CMD_WRITE) { | |||
data = g_gate_send(sendfd, ggio.gctl_data, | |||
ggio.gctl_length, MSG_NOSIGNAL); | |||
if (reconnect) | |||
break; | |||
if (data != ggio.gctl_length) { | |||
g_gate_log(LOG_ERR, "Lost connection 2 (%zd != %zd).", data, (ssize_t)ggio.gctl_length); | |||
reconnect = 1; | |||
pthread_kill(recvtd, SIGUSR1); | |||
break; | |||
} | |||
g_gate_log(LOG_DEBUG, "Sent %zd bytes (offset=%llu, " | |||
"size=%u).", data, hdr.gh_offset, hdr.gh_length); | |||
} | |||
} | |||
g_gate_log(LOG_DEBUG, "%s: Died.", __func__); | |||
return (NULL); | |||
} | |||
static void * | |||
recv_thread(void *arg __unused) | |||
{ | |||
struct g_gate_ctl_io ggio; | |||
struct g_gate_hdr hdr; | |||
static char *buf; | |||
int buflen; | |||
ssize_t data; | |||
buflen = 1024*1024; | |||
if (buf == NULL) | |||
buf = malloc(buflen); | |||
g_gate_log(LOG_NOTICE, "%s: started!", __func__); | |||
ggio.gctl_version = G_GATE_VERSION; | |||
ggio.gctl_unit = unit; | |||
ggio.gctl_data = buf; | |||
for (;;) { | |||
data = g_gate_recv(recvfd, &hdr, sizeof(hdr), MSG_WAITALL); | |||
if (reconnect) | |||
break; | |||
g_gate_swap2h_hdr(&hdr); | |||
if (data != sizeof(hdr)) { | |||
if (data == -1 && errno == EAGAIN) | |||
continue; | |||
g_gate_log(LOG_ERR, "Lost connection 3."); | |||
reconnect = 1; | |||
pthread_kill(sendtd, SIGUSR1); | |||
break; | |||
} | |||
g_gate_log(LOG_DEBUG, "Received hdr packet."); | |||
ggio.gctl_seq = hdr.gh_seq; | |||
ggio.gctl_cmd = hdr.gh_cmd; | |||
ggio.gctl_offset = hdr.gh_offset; | |||
ggio.gctl_length = hdr.gh_length; | |||
ggio.gctl_error = hdr.gh_error; | |||
if (ggio.gctl_error == 0 && ggio.gctl_cmd == GGATE_CMD_READ) { | |||
if (ggio.gctl_length > buflen) { | |||
g_gate_log(LOG_ERR, "Received too large data, %llu.", ggio.gctl_length); | |||
reconnect = 1; | |||
pthread_kill(sendtd, SIGUSR1); | |||
break; | |||
} | |||
data = g_gate_recv(recvfd, ggio.gctl_data, | |||
ggio.gctl_length, MSG_WAITALL); | |||
if (reconnect) | |||
break; | |||
g_gate_log(LOG_DEBUG, "Received data packet."); | |||
if (data != ggio.gctl_length) { | |||
g_gate_log(LOG_ERR, "Lost connection 4."); | |||
reconnect = 1; | |||
pthread_kill(sendtd, SIGUSR1); | |||
break; | |||
} | |||
g_gate_log(LOG_DEBUG, "Received %d bytes (offset=%ju, " | |||
"size=%zu).", data, (uintmax_t)hdr.gh_offset, | |||
(size_t)hdr.gh_length); | |||
} | |||
g_gate_ioctl(G_GATE_CMD_DONE, &ggio); | |||
} | |||
g_gate_log(LOG_DEBUG, "%s: Died.", __func__); | |||
pthread_exit(NULL); | |||
} | |||
static int | |||
handshake(int dir) | |||
{ | |||
struct g_gate_version ver; | |||
struct g_gate_cinit cinit; | |||
struct g_gate_sinit sinit; | |||
struct sockaddr *serv; | |||
struct sockaddr_in inaddr; | |||
struct sockaddr_un unixaddr; | |||
socklen_t addrlen; | |||
int sfd; | |||
const char *sockname; | |||
sockname = NULL; | |||
/* | |||
* Do the network stuff. | |||
*/ | |||
if (strncmp(host, sockprefix, strlen(sockprefix)) == 0) { | |||
sockname = host + strlen(sockprefix); | |||
if (strlen(sockname) + 1 > sizeof(unixaddr.sun_path)) { | |||
g_gate_log(LOG_DEBUG, "Socket path is too long."); | |||
return (-1); | |||
} | |||
unixaddr = (struct sockaddr_un) { | |||
.sun_len = sizeof(unixaddr), | |||
.sun_family = AF_UNIX, | |||
}; | |||
strncpy(unixaddr.sun_path, sockname, sizeof(unixaddr.sun_path)); | |||
sfd = socket(AF_UNIX, SOCK_STREAM, 0); | |||
if (sfd == -1) { | |||
g_gate_log(LOG_DEBUG, "Cannot open socket: %s.", | |||
strerror(errno)); | |||
return (-1); | |||
} | |||
serv = (struct sockaddr *)&unixaddr; | |||
addrlen = sizeof unixaddr; | |||
} else { | |||
bzero(&inaddr, sizeof(inaddr)); | |||
inaddr.sin_family = AF_INET; | |||
inaddr.sin_addr.s_addr = g_gate_str2ip(host); | |||
if (inaddr.sin_addr.s_addr == INADDR_NONE) { | |||
g_gate_log(LOG_DEBUG, "Invalid IP/host name: %s.", host); | |||
return (-1); | |||
} | |||
inaddr.sin_port = htons(port); | |||
sfd = socket(AF_INET, SOCK_STREAM, 0); | |||
if (sfd == -1) { | |||
g_gate_log(LOG_DEBUG, "Cannot open socket: %s.", | |||
strerror(errno)); | |||
return (-1); | |||
} | |||
serv = (struct sockaddr *)&inaddr; | |||
addrlen = sizeof inaddr; | |||
g_gate_socket_settings(sfd); | |||
} | |||
if (connect(sfd, serv, addrlen) == -1) { | |||
g_gate_log(LOG_DEBUG, "Cannot connect to server: %s.", | |||
strerror(errno)); | |||
close(sfd); | |||
return (-1); | |||
} | |||
if (sockname != NULL) | |||
g_gate_log(LOG_INFO, "Connected to socket: %s.", sockname); | |||
else | |||
g_gate_log(LOG_INFO, "Connected to the server: %s:%d.", host, port); | |||
/* | |||
* Create and send version packet. | |||
*/ | |||
g_gate_log(LOG_DEBUG, "Sending version packet."); | |||
assert(strlen(GGATE_MAGIC) == sizeof(ver.gv_magic)); | |||
bcopy(GGATE_MAGIC, ver.gv_magic, sizeof(ver.gv_magic)); | |||
ver.gv_version = GGATE_VERSION; | |||
ver.gv_error = 0; | |||
g_gate_swap2n_version(&ver); | |||
if (g_gate_send(sfd, &ver, sizeof(ver), MSG_NOSIGNAL) == -1) { | |||
g_gate_log(LOG_DEBUG, "Error while sending version packet: %s.", | |||
strerror(errno)); | |||
close(sfd); | |||
return (-1); | |||
} | |||
bzero(&ver, sizeof(ver)); | |||
if (g_gate_recv(sfd, &ver, sizeof(ver), MSG_WAITALL) == -1) { | |||
g_gate_log(LOG_DEBUG, "Error while receiving data: %s.", | |||
strerror(errno)); | |||
close(sfd); | |||
return (-1); | |||
} | |||
if (ver.gv_error != 0) { | |||
g_gate_log(LOG_DEBUG, "Version verification problem: %s.", | |||
strerror(errno)); | |||
close(sfd); | |||
return (-1); | |||
} | |||
/* | |||
* Create and send initial packet. | |||
*/ | |||
g_gate_log(LOG_DEBUG, "Sending initial packet."); | |||
if (strlcpy(cinit.gc_path, path, sizeof(cinit.gc_path)) >= | |||
sizeof(cinit.gc_path)) { | |||
g_gate_log(LOG_DEBUG, "Path name too long."); | |||
close(sfd); | |||
return (-1); | |||
} | |||
cinit.gc_flags = flags | dir; | |||
cinit.gc_token = token; | |||
cinit.gc_nconn = 2; | |||
g_gate_swap2n_cinit(&cinit); | |||
if (g_gate_send(sfd, &cinit, sizeof(cinit), MSG_NOSIGNAL) == -1) { | |||
g_gate_log(LOG_DEBUG, "Error while sending initial packet: %s.", | |||
strerror(errno)); | |||
close(sfd); | |||
return (-1); | |||
} | |||
g_gate_swap2h_cinit(&cinit); | |||
/* | |||
* Receiving initial packet from server. | |||
*/ | |||
g_gate_log(LOG_DEBUG, "Receiving initial packet."); | |||
if (g_gate_recv(sfd, &sinit, sizeof(sinit), MSG_WAITALL) == -1) { | |||
g_gate_log(LOG_DEBUG, "Error while receiving data: %s.", | |||
strerror(errno)); | |||
close(sfd); | |||
return (-1); | |||
} | |||
g_gate_swap2h_sinit(&sinit); | |||
if (sinit.gs_error != 0) { | |||
g_gate_log(LOG_DEBUG, "Error from server: %s.", | |||
strerror(sinit.gs_error)); | |||
close(sfd); | |||
return (-1); | |||
} | |||
g_gate_log(LOG_DEBUG, "Received initial packet."); | |||
mediasize = sinit.gs_mediasize; | |||
if (sectorsize == 0) | |||
sectorsize = sinit.gs_sectorsize; | |||
return (sfd); | |||
} | |||
static void | |||
mydaemon(void) | |||
{ | |||
if (g_gate_verbose > 0) | |||
return; | |||
if (daemon(0, 0) == 0) | |||
return; | |||
if (action == CREATE) | |||
g_gate_destroy(unit, 1); | |||
err(EXIT_FAILURE, "Cannot daemonize"); | |||
} | |||
static int | |||
g_gatec_connect(void) | |||
{ | |||
token = arc4random(); | |||
/* | |||
* Our receive descriptor is connected to the send descriptor on the | |||
* server side. | |||
*/ | |||
recvfd = handshake(GGATE_FLAG_SEND); | |||
if (recvfd == -1) | |||
return (0); | |||
/* | |||
* Our send descriptor is connected to the receive descriptor on the | |||
* server side. | |||
*/ | |||
sendfd = handshake(GGATE_FLAG_RECV); | |||
if (sendfd == -1) | |||
return (0); | |||
return (1); | |||
} | |||
static void | |||
g_gatec_start(void) | |||
{ | |||
int error; | |||
reconnect = 0; | |||
error = pthread_create(&recvtd, NULL, recv_thread, NULL); | |||
if (error != 0) { | |||
g_gate_destroy(unit, 1); | |||
g_gate_xlog("pthread_create(recv_thread): %s.", | |||
strerror(error)); | |||
} | |||
sendtd = pthread_self(); | |||
send_thread(NULL); | |||
/* Disconnected. */ | |||
close(sendfd); | |||
close(recvfd); | |||
} | |||
static void | |||
signop(int sig __unused) | |||
{ | |||
/* Do nothing. */ | |||
} | |||
static void | |||
g_gatec_loop(void) | |||
{ | |||
struct g_gate_ctl_cancel ggioc; | |||
signal(SIGUSR1, signop); | |||
for (;;) { | |||
g_gatec_start(); | |||
g_gate_log(LOG_NOTICE, "Disconnected [%s %s]. Connecting...", | |||
host, path); | |||
while (!g_gatec_connect()) { | |||
sleep(2); | |||
g_gate_log(LOG_NOTICE, "Connecting [%s %s]...", host, | |||
path); | |||
} | |||
ggioc.gctl_version = G_GATE_VERSION; | |||
ggioc.gctl_unit = unit; | |||
ggioc.gctl_seq = 0; | |||
g_gate_ioctl(G_GATE_CMD_CANCEL, &ggioc); | |||
} | |||
} | |||
static void | |||
g_gatec_create(void) | |||
{ | |||
struct g_gate_ctl_create ggioc; | |||
if (!g_gatec_connect()) | |||
g_gate_xlog("Cannot connect: %s.", strerror(errno)); | |||
/* | |||
* Ok, got both sockets, time to create provider. | |||
*/ | |||
memset(&ggioc, 0, sizeof(ggioc)); | |||
ggioc.gctl_version = G_GATE_VERSION; | |||
ggioc.gctl_mediasize = mediasize; | |||
ggioc.gctl_sectorsize = sectorsize; | |||
ggioc.gctl_flags = flags; | |||
ggioc.gctl_maxcount = queue_size; | |||
ggioc.gctl_timeout = timeout; | |||
ggioc.gctl_unit = unit; | |||
snprintf(ggioc.gctl_info, sizeof(ggioc.gctl_info), "%s:%u %s", host, | |||
port, path); | |||
g_gate_ioctl(G_GATE_CMD_CREATE, &ggioc); | |||
if (unit == -1) { | |||
printf("%s%u\n", G_GATE_PROVIDER_NAME, ggioc.gctl_unit); | |||
fflush(stdout); | |||
} | |||
unit = ggioc.gctl_unit; | |||
mydaemon(); | |||
g_gatec_loop(); | |||
} | |||
static void | |||
g_gatec_rescue(void) | |||
{ | |||
struct g_gate_ctl_cancel ggioc; | |||
if (!g_gatec_connect()) | |||
g_gate_xlog("Cannot connect: %s.", strerror(errno)); | |||
ggioc.gctl_version = G_GATE_VERSION; | |||
ggioc.gctl_unit = unit; | |||
ggioc.gctl_seq = 0; | |||
g_gate_ioctl(G_GATE_CMD_CANCEL, &ggioc); | |||
mydaemon(); | |||
g_gatec_loop(); | |||
} | |||
int | |||
main(int argc, char *argv[]) | |||
{ | |||
if (argc < 2) | |||
usage(); | |||
if (strcasecmp(argv[1], "create") == 0) | |||
action = CREATE; | |||
else if (strcasecmp(argv[1], "destroy") == 0) | |||
action = DESTROY; | |||
else if (strcasecmp(argv[1], "list") == 0) | |||
action = LIST; | |||
else if (strcasecmp(argv[1], "rescue") == 0) | |||
action = RESCUE; | |||
else | |||
usage(); | |||
argc -= 1; | |||
argv += 1; | |||
for (;;) { | |||
int ch; | |||
ch = getopt(argc, argv, "fno:p:q:R:S:s:t:u:v"); | |||
if (ch == -1) | |||
break; | |||
switch (ch) { | |||
case 'f': | |||
if (action != DESTROY) | |||
usage(); | |||
force = 1; | |||
break; | |||
case 'n': | |||
if (action != CREATE && action != RESCUE) | |||
usage(); | |||
nagle = 0; | |||
break; | |||
case 'o': | |||
if (action != CREATE && action != RESCUE) | |||
usage(); | |||
if (strcasecmp("ro", optarg) == 0) | |||
flags = G_GATE_FLAG_READONLY; | |||
else if (strcasecmp("wo", optarg) == 0) | |||
flags = G_GATE_FLAG_WRITEONLY; | |||
else if (strcasecmp("rw", optarg) == 0) | |||
flags = 0; | |||
else { | |||
errx(EXIT_FAILURE, | |||
"Invalid argument for '-o' option."); | |||
} | |||
break; | |||
case 'p': | |||
if (action != CREATE && action != RESCUE) | |||
usage(); | |||
errno = 0; | |||
port = strtoul(optarg, NULL, 10); | |||
if (port == 0 && errno != 0) | |||
errx(EXIT_FAILURE, "Invalid port."); | |||
break; | |||
case 'q': | |||
if (action != CREATE) | |||
usage(); | |||
errno = 0; | |||
queue_size = strtoul(optarg, NULL, 10); | |||
if (queue_size == 0 && errno != 0) | |||
errx(EXIT_FAILURE, "Invalid queue_size."); | |||
break; | |||
case 'R': | |||
if (action != CREATE && action != RESCUE) | |||
usage(); | |||
errno = 0; | |||
rcvbuf = strtoul(optarg, NULL, 10); | |||
if (rcvbuf == 0 && errno != 0) | |||
errx(EXIT_FAILURE, "Invalid rcvbuf."); | |||
break; | |||
case 'S': | |||
if (action != CREATE && action != RESCUE) | |||
usage(); | |||
errno = 0; | |||
sndbuf = strtoul(optarg, NULL, 10); | |||
if (sndbuf == 0 && errno != 0) | |||
errx(EXIT_FAILURE, "Invalid sndbuf."); | |||
break; | |||
case 's': | |||
if (action != CREATE) | |||
usage(); | |||
errno = 0; | |||
sectorsize = strtoul(optarg, NULL, 10); | |||
if (sectorsize == 0 && errno != 0) | |||
errx(EXIT_FAILURE, "Invalid sectorsize."); | |||
break; | |||
case 't': | |||
if (action != CREATE) | |||
usage(); | |||
errno = 0; | |||
timeout = strtoul(optarg, NULL, 10); | |||
if (timeout == 0 && errno != 0) | |||
errx(EXIT_FAILURE, "Invalid timeout."); | |||
break; | |||
case 'u': | |||
errno = 0; | |||
unit = strtol(optarg, NULL, 10); | |||
if (unit == 0 && errno != 0) | |||
errx(EXIT_FAILURE, "Invalid unit number."); | |||
break; | |||
case 'v': | |||
if (action == DESTROY) | |||
usage(); | |||
g_gate_verbose++; | |||
break; | |||
default: | |||
usage(); | |||
} | |||
} | |||
argc -= optind; | |||
argv += optind; | |||
switch (action) { | |||
case CREATE: | |||
if (argc != 2) | |||
usage(); | |||
g_gate_load_module(); | |||
g_gate_open_device(); | |||
host = argv[0]; | |||
path = argv[1]; | |||
g_gatec_create(); | |||
break; | |||
case DESTROY: | |||
if (unit == -1) { | |||
fprintf(stderr, "Required unit number.\n"); | |||
usage(); | |||
} | |||
g_gate_verbose = 1; | |||
g_gate_open_device(); | |||
g_gate_destroy(unit, force); | |||
break; | |||
case LIST: | |||
g_gate_list(unit, g_gate_verbose); | |||
break; | |||
case RESCUE: | |||
if (argc != 2) | |||
usage(); | |||
if (unit == -1) { | |||
fprintf(stderr, "Required unit number.\n"); | |||
usage(); | |||
} | |||
g_gate_open_device(); | |||
host = argv[0]; | |||
path = argv[1]; | |||
g_gatec_rescue(); | |||
break; | |||
case UNSET: | |||
default: | |||
usage(); | |||
} | |||
g_gate_close_device(); | |||
exit(EXIT_SUCCESS); | |||
} |
@@ -1,13 +0,0 @@ | |||
# $FreeBSD$ | |||
.PATH: ${.CURDIR:H}/shared | |||
PROG= ggated | |||
MAN= ggated.8 | |||
SRCS= ggated.c ggate.c | |||
LDADD= -lpthread -lutil | |||
CFLAGS+= -I${.CURDIR:H}/shared | |||
.include <bsd.prog.mk> |
@@ -1,21 +0,0 @@ | |||
# $FreeBSD$ | |||
# Autogenerated - do NOT edit! | |||
DIRDEPS = \ | |||
gnu/lib/csu \ | |||
include \ | |||
include/arpa \ | |||
include/xlocale \ | |||
lib/${CSU_DIR} \ | |||
lib/libc \ | |||
lib/libcompiler_rt \ | |||
lib/libgeom \ | |||
lib/libthr \ | |||
lib/libutil \ | |||
.include <dirdeps.mk> | |||
.if ${DEP_RELDIR} == ${_DEP_RELDIR} | |||
# local dependencies - needed for -jN in clean tree | |||
.endif |
@@ -1,134 +0,0 @@ | |||
.\" Copyright (c) 2004 Pawel Jakub Dawidek <pjd@FreeBSD.org> | |||
.\" All rights reserved. | |||
.\" | |||
.\" Redistribution and use in source and binary forms, with or without | |||
.\" modification, are permitted provided 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 AUTHORS AND CONTRIBUTORS ``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 AUTHORS OR CONTRIBUTORS 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. | |||
.\" | |||
.\" $FreeBSD$ | |||
.\" | |||
.Dd September 8, 2016 | |||
.Dt GGATED 8 | |||
.Os | |||
.Sh NAME | |||
.Nm ggated | |||
.Nd "GEOM Gate network daemon" | |||
.Sh SYNOPSIS | |||
.Nm | |||
.Op Fl h | |||
.Op Fl n | |||
.Op Fl v | |||
.Op Fl a Ar address | |||
.Op Fl p Ar port | |||
.Op Fl q Ar queue_size | |||
.Op Fl F Ar pidfile | |||
.Op Fl R Ar rcvbuf | |||
.Op Fl S Ar sndbuf | |||
.Op Ar "exports file" | |||
.Sh DESCRIPTION | |||
The | |||
.Nm | |||
utility is a network server for the GEOM Gate class. | |||
It runs on a server machine to service GEOM Gate requests from workers | |||
placed on a client machine. | |||
Keep in mind, that connections between | |||
.Xr ggatec 8 | |||
and | |||
.Nm | |||
are not encrypted. | |||
.Pp | |||
Available options: | |||
.Bl -tag -width ".Ar exports\ file" | |||
.It Fl a Ar address | |||
Specifies an IP address to bind to. | |||
.It Fl h | |||
Print available options. | |||
.It Fl n | |||
Do not use | |||
.Dv TCP_NODELAY | |||
option on TCP sockets. | |||
.It Fl p Ar port | |||
Port on which | |||
.Nm | |||
listens for connections. | |||
Default is 3080. | |||
.It Fl q Ar queue_size | |||
The number of IOs to queue to the disks at once. | |||
The default is 20. | |||
If you have a large number of disks and/or you are not seeing the expected | |||
number of IOPS, increase this parameter. | |||
.It Fl F Ar pidfile | |||
PID file that | |||
.Nm | |||
uses. | |||
.It Fl R Ar rcvbuf | |||
Size of receive buffer to use. | |||
When not specified, the system default is used. | |||
.It Fl S Ar sndbuf | |||
Size of send buffer to use. | |||
When not specified, the system default is used. | |||
.It Fl v | |||
Do not fork, run in foreground and print debug information on standard | |||
output. | |||
.It Ar "exports file" | |||
An alternate location for the exports file. | |||
.El | |||
.Pp | |||
The format of an exports file is as follows: | |||
.Bd -literal -offset indent | |||
1.2.3.4 RO /dev/cd0 | |||
1.2.3.0/24 RW /tmp/test.img | |||
hostname WO /tmp/image | |||
.Ed | |||
.Sh FILES | |||
.Bl -tag -width ".Pa /var/run/ggated.pid" -compact | |||
.It Pa /var/run/ggated.pid | |||
The default location of the | |||
.Nm | |||
PID file. | |||
.El | |||
.Sh EXIT STATUS | |||
Exit status is 0 on success, or 1 if the command fails. | |||
To get details about the failure, | |||
.Nm | |||
should be called with the | |||
.Fl v | |||
option. | |||
.Sh EXAMPLES | |||
Export CD-ROM device and a file: | |||
.Bd -literal -offset indent | |||
# echo "1.2.3.0/24 RO /dev/cd0" > /etc/gg.exports | |||
# echo "client RW /image" >> /etc/gg.exports | |||
# ggated | |||
.Ed | |||
.Sh SEE ALSO | |||
.Xr geom 4 , | |||
.Xr ggatec 8 , | |||
.Xr ggatel 8 | |||
.Sh HISTORY | |||
The | |||
.Nm | |||
utility appeared in | |||
.Fx 5.3 . | |||
.Sh AUTHORS | |||
The | |||
.Nm | |||
utility as well as this manual page was written by | |||
.An Pawel Jakub Dawidek Aq Mt pjd@FreeBSD.org . |
@@ -0,0 +1,19 @@ | |||
# $FreeBSD$ | |||
.PATH: ${.CURDIR:H}/shared | |||
PROG= ggatehttp | |||
MAN= ggatehttp.8 | |||
SRCS= ggatehttp.c ggate.c | |||
CFLAGS+= -DMAX_SEND_SIZE=32768 | |||
CFLAGS+= -DLIBGEOM | |||
CFLAGS+= -I${.CURDIR:H}/shared | |||
CFLAGS+= -I/usr/local/include | |||
#CFLAGS+= -O0 | |||
#CFLAGS+= -fprofile-instr-generate | |||
LDFLAGS+= -L/usr/local/lib | |||
LDADD= -lgeom -lutil -lpthread -lcurl | |||
.include <bsd.prog.mk> |
@@ -0,0 +1,770 @@ | |||
/*- | |||
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD | |||
* | |||
* Copyright (c) 2004 Pawel Jakub Dawidek <pjd@FreeBSD.org> | |||
* All rights reserved. | |||
* Copyright 2020 John-Mark Gurney <jmg@FreeBSD.org> | |||
* | |||
* Redistribution and use in source and binary forms, with or without | |||
* modification, are permitted provided 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 AUTHORS AND CONTRIBUTORS ``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 AUTHORS OR CONTRIBUTORS 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. | |||
* | |||
* $FreeBSD$ | |||
*/ | |||
#include <stdio.h> | |||
#include <stdlib.h> | |||
#include <stdint.h> | |||
#include <fcntl.h> | |||
#include <unistd.h> | |||
#include <string.h> | |||
#include <ctype.h> | |||
#include <libgen.h> | |||
#include <pthread.h> | |||
#include <pthread_np.h> | |||
#include <signal.h> | |||
#include <err.h> | |||
#include <errno.h> | |||
#include <assert.h> | |||
#include <sys/param.h> | |||
#include <sys/ioctl.h> | |||
#include <sys/queue.h> | |||
#include <sys/socket.h> | |||
#include <sys/sysctl.h> | |||
#include <sys/syslog.h> | |||
#include <sys/time.h> | |||
#include <sys/bio.h> | |||
#include <sys/un.h> | |||
#include <netinet/in.h> | |||
#include <netinet/tcp.h> | |||
#include <arpa/inet.h> | |||
#include <semaphore.h> | |||
#include <curl/curl.h> | |||
#include <curl/multi.h> | |||
#include <geom/gate/g_gate.h> | |||
#include "ggate.h" | |||
static enum { UNSET, CREATE, DESTROY, LIST, RESCUE } action = UNSET; | |||
static const char *url = NULL; | |||
static int unit = G_GATE_UNIT_AUTO; | |||
static unsigned flags = 0; | |||
static int force = 0; | |||
static unsigned queue_size = G_GATE_QUEUE_SIZE; | |||
static off_t mediasize; | |||
static unsigned sectorsize = 4096; | |||
static unsigned timeout = G_GATE_TIMEOUT; | |||
static int pushfd, popfd; /* work semaphore */ | |||
static pthread_t reqtd, proctd; | |||
static unsigned maxconnections = 32; | |||
struct ggh_req { | |||
struct g_gate_ctl_io r_ggio; | |||
CURL *r_chandle; | |||
size_t r_bufoff; | |||
TAILQ_ENTRY(ggh_req) r_next; | |||
}; | |||
static TAILQ_HEAD(, ggh_req) procqueue = TAILQ_HEAD_INITIALIZER(procqueue); | |||
static sem_t nconn_sem; | |||
static pthread_mutex_t procqueue_mtx; | |||
static void | |||
usage(void) | |||
{ | |||
fprintf(stderr, "usage: %s create [-v] [-o <ro|wo|rw>] " | |||
"[-q queue_size] [-s sectorsize] [-r nrequests] " | |||
"[-t timeout] [-u unit] <url>\n", getprogname()); | |||
fprintf(stderr, " %s rescue [-v] [-o <ro|wo|rw>] " | |||
"[-r nrequests] <-u unit> <url>\n", getprogname()); | |||
fprintf(stderr, " %s destroy [-f] <-u unit>\n", getprogname()); | |||
fprintf(stderr, " %s list [-v] [-u unit]\n", getprogname()); | |||
exit(EXIT_FAILURE); | |||
} | |||
static void * | |||
req_thread(void *arg __unused) | |||
{ | |||
struct ggh_req *greq; | |||
static char *buf; | |||
int buflen = 1024*1024; | |||
int error; | |||
g_gate_log(LOG_NOTICE, "%s: started!", __func__); | |||
greq = NULL; | |||
for (;;) { | |||
if (greq == NULL) | |||
greq = malloc(sizeof *greq); | |||
if (buf == NULL) | |||
buf = malloc(buflen); | |||
if (greq == NULL || buf == NULL) { | |||
/* XXX */ | |||
g_gate_log(LOG_ERR, "Unable to allocate memory."); | |||
exit(1); | |||
} | |||
greq->r_ggio.gctl_version = G_GATE_VERSION; | |||
greq->r_ggio.gctl_unit = unit; | |||
greq->r_ggio.gctl_data = buf; | |||
greq->r_ggio.gctl_length = buflen; | |||
greq->r_ggio.gctl_error = 0; | |||
//g_gate_log(LOG_DEBUG, "waiting for ioctl"); | |||
g_gate_ioctl(G_GATE_CMD_START, &greq->r_ggio); | |||
//g_gate_log(LOG_DEBUG, "got ioctl"); | |||
error = greq->r_ggio.gctl_error; | |||
switch (error) { | |||
case 0: | |||
break; | |||
case ECANCELED: | |||
/* Exit gracefully. */ | |||
g_gate_close_device(); | |||
exit(EXIT_SUCCESS); | |||
case ENXIO: | |||
default: | |||
g_gate_xlog("ioctl(/dev/%s): %s.", G_GATE_CTL_NAME, | |||
strerror(error)); | |||
} | |||
g_gate_log(LOG_DEBUG, "ggio, ver: %u, unit: %d, seq: %llu, " | |||
"cmd: %u, offset: %llu, len: %llu", | |||
greq->r_ggio.gctl_version, greq->r_ggio.gctl_unit, | |||
greq->r_ggio.gctl_seq, greq->r_ggio.gctl_cmd, | |||
greq->r_ggio.gctl_offset, greq->r_ggio.gctl_length); | |||
switch (greq->r_ggio.gctl_cmd) { | |||
case BIO_READ: | |||
/* use a correctly sized allocation */ | |||
greq->r_ggio.gctl_data = | |||
malloc(greq->r_ggio.gctl_length); | |||
break; | |||
case BIO_WRITE: | |||
/* r_ggio takes ownership of buf now */ | |||
buf = NULL; | |||
break; | |||
case BIO_DELETE: | |||
case BIO_FLUSH: | |||
default: | |||
greq->r_ggio.gctl_error = EOPNOTSUPP; | |||
g_gate_ioctl(G_GATE_CMD_DONE, &greq->r_ggio); | |||
continue; /* return EOPNOTSUPP */ | |||
break; | |||
} | |||
//g_gate_log(LOG_DEBUG, "waiting for slot"); | |||
sem_wait(&nconn_sem); | |||
#if 0 | |||
int semval; | |||
sem_getvalue(&nconn_sem, &semval); | |||
g_gate_log(LOG_DEBUG, "slots: %d", semval); | |||
#endif | |||
error = pthread_mutex_lock(&procqueue_mtx); | |||
assert(error == 0); | |||
TAILQ_INSERT_TAIL(&procqueue, greq, r_next); | |||
error = pthread_mutex_unlock(&procqueue_mtx); | |||
assert(error == 0); | |||
/* notify processing thread a request is waiting */ | |||
error = write(pushfd, "T", 1); | |||
if (error != 1) | |||
g_gate_xlog("write pushfd: %d, error: %s.", error, | |||
strerror(error)); | |||
/* pass ownership */ | |||
greq = NULL; | |||
} | |||
g_gate_log(LOG_DEBUG, "%s: Died.", __func__); | |||
return (NULL); | |||
} | |||
/* | |||
* To support any auth: | |||
* https://curl.haxx.se/libcurl/c/anyauthput.html | |||
*/ | |||
static curlioerr | |||
curl_ioctl(CURL *hndl, curliocmd cmd, void *userdata) | |||
{ | |||
struct ggh_req *greq; | |||
(void)hndl; | |||
greq = (struct ggh_req *)userdata; | |||
switch (cmd) { | |||
case CURLIOCMD_RESTARTREAD: | |||
greq->r_bufoff = 0; | |||
break; | |||
default: | |||
return CURLIOE_UNKNOWNCMD; | |||
} | |||
return CURLIOE_OK; | |||
} | |||
/* | |||
* file the curl buffer with data to send to remote server. | |||
*/ | |||
static size_t | |||
curl_readfun(char *buffer, size_t size, size_t nitems, void *userdata) | |||
{ | |||
struct ggh_req *greq; | |||
size_t cnt; | |||
greq = (struct ggh_req *)userdata; | |||
cnt = MIN(size * nitems, greq->r_ggio.gctl_length - greq->r_bufoff); | |||
//g_gate_log(LOG_DEBUG, "sending %zd bytes on %p", cnt, greq); | |||
memcpy(buffer, (char *)greq->r_ggio.gctl_data + greq->r_bufoff, cnt); | |||
greq->r_bufoff += cnt; | |||
return cnt; | |||
} | |||
static size_t | |||
curl_writefun(char *buffer, size_t size, size_t nitems, void *userdata) | |||
{ | |||
struct ggh_req *greq; | |||
size_t cnt; | |||
greq = (struct ggh_req *)userdata; | |||
cnt = size * nitems; | |||
assert((off_t)(greq->r_bufoff + cnt) <= greq->r_ggio.gctl_length); | |||
memcpy((char *)greq->r_ggio.gctl_data + greq->r_bufoff, buffer, cnt); | |||
greq->r_bufoff += cnt; | |||
return cnt; | |||
} | |||
static void | |||
process_greq(CURLM *cmulti, struct ggh_req *greq) | |||
{ | |||
char range_header[256]; | |||
off_t start, length, end; | |||
/* start processing */ | |||
greq->r_chandle = curl_easy_init(); | |||
curl_easy_setopt(greq->r_chandle, CURLOPT_URL, url); | |||
curl_easy_setopt(greq->r_chandle, CURLOPT_PRIVATE, (char *)greq); | |||
//curl_easy_setopt(greq->r_chandle, CURLOPT_VERBOSE, (long)1); | |||
start = greq->r_ggio.gctl_offset; | |||
length = greq->r_ggio.gctl_length; | |||
end = start + length; | |||
greq->r_bufoff = 0; | |||
switch (greq->r_ggio.gctl_cmd) { | |||
case BIO_READ: | |||
curl_easy_setopt(greq->r_chandle, CURLOPT_WRITEFUNCTION, | |||
curl_writefun); | |||
curl_easy_setopt(greq->r_chandle, CURLOPT_WRITEDATA, greq); | |||
sprintf(range_header, "%zd-%zd", start, end - 1); | |||
g_gate_log(LOG_DEBUG, "read range: %s", range_header); | |||
curl_easy_setopt(greq->r_chandle, CURLOPT_RANGE, range_header); | |||
curl_multi_add_handle(cmulti, greq->r_chandle); | |||
break; | |||
case BIO_WRITE: | |||
curl_easy_setopt(greq->r_chandle, CURLOPT_IOCTLFUNCTION, | |||
curl_ioctl); | |||
curl_easy_setopt(greq->r_chandle, CURLOPT_IOCTLDATA, greq); | |||
curl_easy_setopt(greq->r_chandle, CURLOPT_READFUNCTION, | |||
curl_readfun); | |||
curl_easy_setopt(greq->r_chandle, CURLOPT_READDATA, greq); | |||
curl_easy_setopt(greq->r_chandle, CURLOPT_UPLOAD, (long)1); | |||
/* XXX - support more than basic */ | |||
//curl_easy_setopt(greq->r_chandle, CURLOPT_HTTPAUTH, (long)CURLAUTH_ANY); | |||
curl_easy_setopt(greq->r_chandle, CURLOPT_HTTPAUTH, | |||
(long)CURLAUTH_BASIC); | |||
//curl_easy_setopt(greq->r_chandle, CURLOPT_VERBOSE, (long)1); | |||
/* https://curl.haxx.se/mail/lib-2019-05/0012.html */ | |||
curl_easy_setopt(greq->r_chandle, CURLOPT_INFILESIZE_LARGE, | |||
(curl_off_t)length); | |||
/* we don't need resume from as we don't seek */ | |||
//curl_easy_setopt(greq->r_chandle, CURLOPT_RESUME_FROM_LARGE, (curl_off_t)start); | |||
sprintf(range_header, "Content-Range: bytes %zd-%zd/%zd", | |||
start, end - 1, mediasize); | |||
g_gate_log(LOG_DEBUG, "write range: %s", range_header); | |||
struct curl_slist *header_list; | |||
header_list = curl_slist_append(NULL, range_header); | |||
curl_easy_setopt(greq->r_chandle, CURLOPT_HTTPHEADER, | |||
header_list); | |||
#if 1 | |||
curl_multi_add_handle(cmulti, greq->r_chandle); | |||
#else | |||
CURLcode res; | |||
res = curl_easy_perform(greq->r_chandle); | |||
curl_easy_getinfo(greq->r_chandle, CURLINFO_RESPONSE_CODE, | |||
&code); | |||
if (code != 200) { | |||
g_gate_log(LOG_ERR, | |||
"Got invalid response, HTTP code %03d.", code); | |||
} | |||
#endif | |||
break; | |||
} | |||
/* start processing */ | |||
//curl_multi_add_handle(cmulti, greq->r_chandle); | |||
} | |||
static void * | |||
proc_thread(void *arg __unused) | |||
{ | |||
char scratch[32]; | |||
struct timeval to; | |||
CURLMsg *m; | |||
CURLM *cmulti; | |||
struct ggh_req *greq; | |||
fd_set fdread; | |||
fd_set fdwrite; | |||
fd_set fdexcep; | |||
CURLMcode mc; | |||
long curl_timeo; | |||
long code; | |||
int rc; | |||
int maxfd; | |||
int error; | |||
int still_running; | |||
g_gate_log(LOG_NOTICE, "%s: started!", __func__); | |||
/* make sure we don't block on reading */ | |||
fcntl(popfd, F_SETFL, O_NONBLOCK); | |||
cmulti = curl_multi_init(); | |||
//mc = curl_multi_setopt(cmulti, CURLOPT_VERBOSE, (long)1); | |||
for (;;) { | |||
//g_gate_log(LOG_DEBUG, "looping"); | |||
/* setup polling loop */ | |||
maxfd = -1; | |||
FD_ZERO(&fdread); | |||
FD_ZERO(&fdwrite); | |||
FD_ZERO(&fdexcep); | |||
to = (struct timeval){ .tv_sec = 1 }; | |||
curl_timeo = -1; | |||
curl_multi_timeout(cmulti, &curl_timeo); | |||
if (curl_timeo >= 0) { | |||
to.tv_sec = curl_timeo / 1000; | |||
if (to.tv_sec > 1) | |||
to.tv_sec = 1; | |||
else | |||
to.tv_usec = (curl_timeo % 1000) * 1000; | |||
} | |||
mc = curl_multi_fdset(cmulti, &fdread, &fdwrite, &fdexcep, &maxfd); | |||
if (mc != CURLM_OK) { | |||
g_gate_log(LOG_ERR, "%s: fdset failed.", __func__); | |||
break; | |||
} | |||
/* add in the pop descriptor */ | |||
FD_SET(popfd, &fdread); | |||
maxfd = MAX(popfd, maxfd); | |||
rc = select(maxfd + 1, &fdread, &fdwrite, &fdexcep, &to); | |||
switch (rc) { | |||
case -1: | |||
g_gate_log(LOG_ERR, "%s: select failed: %s", __func__, | |||
strerror(errno)); | |||
break; | |||
case 0: | |||
default: | |||
curl_multi_perform(cmulti, &still_running); | |||
break; | |||
} | |||
/* Check for completed requests */ | |||
do { | |||
int msgq = 0; | |||
m = curl_multi_info_read(cmulti, &msgq); | |||
if (m != NULL && m->msg == CURLMSG_DONE) { | |||
CURL *e = m->easy_handle; | |||
curl_easy_getinfo(e, CURLINFO_PRIVATE, | |||
(char *)&greq); | |||
curl_easy_getinfo(e, CURLINFO_RESPONSE_CODE, | |||
&code); | |||
g_gate_log(LOG_DEBUG, "request code: %d", code); | |||
if (code != 206 && code != 204) { | |||
g_gate_log(LOG_ERR, | |||
"request failed: %d", code); | |||
greq->r_ggio.gctl_error = EIO; | |||
} | |||
g_gate_ioctl(G_GATE_CMD_DONE, &greq->r_ggio); | |||
//g_gate_log(LOG_DEBUG, "releasing slot"); | |||
sem_post(&nconn_sem); | |||
curl_multi_remove_handle(cmulti, e); | |||
curl_easy_cleanup(e); | |||
free(greq->r_ggio.gctl_data); | |||
free(greq); | |||
} else if (m != NULL) { | |||
g_gate_log(LOG_ERR, "unknown curl msg: %d", | |||
m->msg); | |||
} | |||
} while (m != NULL); | |||
if (FD_ISSET(popfd, &fdread)) { | |||
/* read off the tokens */ | |||
read(popfd, scratch, sizeof scratch); | |||
do { | |||
/* get the request */ | |||
error = pthread_mutex_lock(&procqueue_mtx); | |||
assert(error == 0); | |||
greq = TAILQ_FIRST(&procqueue); | |||
if (greq != NULL) | |||
TAILQ_REMOVE(&procqueue, greq, r_next); | |||
error = pthread_mutex_unlock(&procqueue_mtx); | |||
assert(error == 0); | |||
/* no more to process */ | |||
if (greq == NULL) | |||
break; | |||
process_greq(cmulti, greq); | |||
} while (greq != NULL); | |||
} | |||
} | |||
curl_multi_cleanup(cmulti); | |||
g_gate_log(LOG_DEBUG, "%s: Died.", __func__); | |||
pthread_exit(NULL); | |||
} | |||
static void | |||
mydaemon(void) | |||
{ | |||
if (g_gate_verbose > 0) | |||
return; | |||
if (daemon(0, 0) == 0) | |||
return; | |||
if (action == CREATE) | |||
g_gate_destroy(unit, 1); | |||
err(EXIT_FAILURE, "Cannot daemonize"); | |||
} | |||
static int | |||
g_gatehttp_connect(void) | |||
{ | |||
CURL *hndl; | |||
CURLcode cc; | |||
long code; | |||
curl_off_t cl; | |||
/* get the remote's size */ | |||
hndl = curl_easy_init(); | |||
curl_easy_setopt(hndl, CURLOPT_URL, url); | |||
curl_easy_setopt(hndl, CURLOPT_NOBODY, (long)1); | |||
//curl_easy_setopt(hndl, CURLOPT_VERBOSE, (long)1); | |||
cc = curl_easy_perform(hndl); | |||
if (cc != CURLE_OK) { | |||
g_gate_log(LOG_ERR, "curl request failed."); | |||
return 0; | |||
} | |||
curl_easy_getinfo(hndl, CURLINFO_RESPONSE_CODE, &code); | |||
if (code != 200) { | |||
g_gate_log(LOG_ERR, "Got invalid response, HTTP code %03d.", code); | |||
return 0; | |||
} | |||
curl_easy_getinfo(hndl, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &cl); | |||
mediasize = cl; | |||
g_gate_log(LOG_DEBUG, "got mediasize: %zd", mediasize); | |||
curl_easy_cleanup(hndl); | |||
return 1; | |||
} | |||
static void | |||
g_gatehttp_start(void) | |||
{ | |||
int filedes[2]; | |||
int error; | |||
pipe(filedes); | |||
pushfd = filedes[1]; | |||
popfd = filedes[0]; | |||
error = pthread_mutex_init(&procqueue_mtx, NULL); | |||
if (error != 0) { | |||
g_gate_xlog("pthread_mutex_init(inqueue_mtx): %s.", | |||
strerror(error)); | |||
} | |||
sem_init(&nconn_sem, 0, maxconnections); | |||
error = pthread_create(&proctd, NULL, proc_thread, NULL); | |||
if (error != 0) { | |||
g_gate_destroy(unit, 1); | |||
g_gate_xlog("pthread_create(proc_thread): %s.", | |||
strerror(error)); | |||
} | |||
pthread_set_name_np(proctd, "proc"); | |||
reqtd = pthread_self(); | |||
pthread_set_name_np(reqtd, "req"); | |||
req_thread(NULL); | |||
/* Disconnected. */ | |||
close(pushfd); | |||
close(popfd); | |||
} | |||
static void | |||
signop(int sig __unused) | |||
{ | |||
/* Do nothing. */ | |||
} | |||
static void | |||
g_gatehttp_loop(void) | |||
{ | |||
struct g_gate_ctl_cancel ggioc; | |||
signal(SIGUSR1, signop); | |||
for (;;) { | |||
g_gatehttp_start(); | |||
g_gate_log(LOG_NOTICE, "Disconnected [%s]. Connecting...", | |||
url); | |||
ggioc.gctl_version = G_GATE_VERSION; | |||
ggioc.gctl_unit = unit; | |||
ggioc.gctl_seq = 0; | |||
g_gate_ioctl(G_GATE_CMD_CANCEL, &ggioc); | |||
} | |||
} | |||
static void | |||
g_gatehttp_create(void) | |||
{ | |||
struct g_gate_ctl_create ggioc; | |||
if (!g_gatehttp_connect()) | |||
g_gate_xlog("Cannot connect: %s.", strerror(errno)); | |||
/* | |||
* Ok, got both sockets, time to create provider. | |||
*/ | |||
memset(&ggioc, 0, sizeof(ggioc)); | |||
ggioc.gctl_version = G_GATE_VERSION; | |||
ggioc.gctl_mediasize = mediasize; | |||
ggioc.gctl_sectorsize = sectorsize; | |||
ggioc.gctl_flags = flags; | |||
ggioc.gctl_maxcount = queue_size; | |||
ggioc.gctl_timeout = timeout; | |||
ggioc.gctl_unit = unit; | |||
snprintf(ggioc.gctl_info, sizeof(ggioc.gctl_info), "%s", url); | |||
g_gate_ioctl(G_GATE_CMD_CREATE, &ggioc); | |||
if (unit == -1) { | |||
printf("%s%u\n", G_GATE_PROVIDER_NAME, ggioc.gctl_unit); | |||
fflush(stdout); | |||
} | |||
unit = ggioc.gctl_unit; | |||
mydaemon(); | |||
g_gatehttp_loop(); | |||
} | |||
static void | |||
g_gatehttp_rescue(void) | |||
{ | |||
struct g_gate_ctl_cancel ggioc; | |||
if (!g_gatehttp_connect()) | |||
g_gate_xlog("Cannot connect: %s.", strerror(errno)); | |||
ggioc.gctl_version = G_GATE_VERSION; | |||
ggioc.gctl_unit = unit; | |||
ggioc.gctl_seq = 0; | |||
g_gate_ioctl(G_GATE_CMD_CANCEL, &ggioc); | |||
mydaemon(); | |||
g_gatehttp_loop(); | |||
} | |||
int | |||
main(int argc, char *argv[]) | |||
{ | |||
if (argc < 2) | |||
usage(); | |||
if (strcasecmp(argv[1], "create") == 0) | |||
action = CREATE; | |||
else if (strcasecmp(argv[1], "destroy") == 0) | |||
action = DESTROY; | |||
else if (strcasecmp(argv[1], "list") == 0) | |||
action = LIST; | |||
else if (strcasecmp(argv[1], "rescue") == 0) | |||
action = RESCUE; | |||
else | |||
usage(); | |||
argc -= 1; | |||
argv += 1; | |||
for (;;) { | |||
int ch; | |||
ch = getopt(argc, argv, "fo:q:r:s:t:u:v"); | |||
if (ch == -1) | |||
break; | |||
switch (ch) { | |||
case 'f': | |||
if (action != DESTROY) | |||
usage(); | |||
force = 1; | |||
break; | |||
case 'o': | |||
if (action != CREATE && action != RESCUE) | |||
usage(); | |||
if (strcasecmp("ro", optarg) == 0) | |||
flags = G_GATE_FLAG_READONLY; | |||
else if (strcasecmp("wo", optarg) == 0) | |||
flags = G_GATE_FLAG_WRITEONLY; | |||
else if (strcasecmp("rw", optarg) == 0) | |||
flags = 0; | |||
else { | |||
errx(EXIT_FAILURE, | |||
"Invalid argument for '-o' option."); | |||
} | |||
break; | |||
case 'q': | |||
if (action != CREATE) | |||
usage(); | |||
errno = 0; | |||
queue_size = strtoul(optarg, NULL, 10); | |||
if (queue_size == 0 && errno != 0) | |||
errx(EXIT_FAILURE, "Invalid queue_size."); | |||
break; | |||
case 'r': | |||
if (action != CREATE && action != RESCUE) | |||
usage(); | |||
errno = 0; | |||
maxconnections = strtoul(optarg, NULL, 10); | |||
if (maxconnections == 0 && errno != 0) | |||
errx(EXIT_FAILURE, "Invalid queue_size."); | |||
break; | |||
case 's': | |||
if (action != CREATE) | |||
usage(); | |||
errno = 0; | |||
sectorsize = strtoul(optarg, NULL, 10); | |||
if (sectorsize == 0 && errno != 0) | |||
errx(EXIT_FAILURE, "Invalid sectorsize."); | |||
break; | |||
case 't': | |||
if (action != CREATE) | |||
usage(); | |||
errno = 0; | |||
timeout = strtoul(optarg, NULL, 10); | |||
if (timeout == 0 && errno != 0) | |||
errx(EXIT_FAILURE, "Invalid timeout."); | |||
break; | |||
case 'u': | |||
errno = 0; | |||
unit = strtol(optarg, NULL, 10); | |||
if (unit == 0 && errno != 0) | |||
errx(EXIT_FAILURE, "Invalid unit number."); | |||
break; | |||
case 'v': | |||
if (action == DESTROY) | |||
usage(); | |||
g_gate_verbose++; | |||
break; | |||
default: | |||
usage(); | |||
} | |||
} | |||
argc -= optind; | |||
argv += optind; | |||
switch (action) { | |||
case CREATE: | |||
if (argc != 1) | |||
usage(); | |||
g_gate_load_module(); | |||
g_gate_open_device(); | |||
url = argv[0]; | |||
g_gatehttp_create(); | |||
break; | |||
case DESTROY: | |||
if (unit == -1) { | |||
fprintf(stderr, "Required unit number.\n"); | |||
usage(); | |||
} | |||
g_gate_verbose = 1; | |||
g_gate_open_device(); | |||
g_gate_destroy(unit, force); | |||
break; | |||
case LIST: | |||
g_gate_list(unit, g_gate_verbose); | |||
break; | |||
case RESCUE: | |||
if (argc != 1) | |||
usage(); | |||
if (unit == -1) { | |||
fprintf(stderr, "Required unit number.\n"); | |||
usage(); | |||
} | |||
g_gate_open_device(); | |||
url = argv[0]; | |||
g_gatehttp_rescue(); | |||
break; | |||
case UNSET: | |||
default: | |||
usage(); | |||
} | |||
g_gate_close_device(); | |||
exit(EXIT_SUCCESS); | |||
} |
@@ -1,14 +0,0 @@ | |||
# $FreeBSD$ | |||
.PATH: ${.CURDIR:H}/shared | |||
PROG= ggatel | |||
MAN= ggatel.8 | |||
SRCS= ggatel.c ggate.c | |||
CFLAGS+= -DLIBGEOM | |||
CFLAGS+= -I${.CURDIR:H}/shared | |||
LDADD= -lgeom -lutil | |||
.include <bsd.prog.mk> |
@@ -1,22 +0,0 @@ | |||
# $FreeBSD$ | |||
# Autogenerated - do NOT edit! | |||
DIRDEPS = \ | |||
gnu/lib/csu \ | |||
include \ | |||
include/arpa \ | |||
include/xlocale \ | |||
lib/${CSU_DIR} \ | |||
lib/libc \ | |||
lib/libcompiler_rt \ | |||
lib/libexpat \ | |||
lib/libgeom \ | |||
lib/libsbuf \ | |||
lib/libutil \ | |||
.include <dirdeps.mk> | |||
.if ${DEP_RELDIR} == ${_DEP_RELDIR} | |||
# local dependencies - needed for -jN in clean tree | |||
.endif |
@@ -1,164 +0,0 @@ | |||
.\" Copyright (c) 2004 Pawel Jakub Dawidek <pjd@FreeBSD.org> | |||
.\" All rights reserved. | |||
.\" | |||
.\" Redistribution and use in source and binary forms, with or without | |||
.\" modification, are permitted provided 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 AUTHORS AND CONTRIBUTORS ``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 AUTHORS OR CONTRIBUTORS 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. | |||
.\" | |||
.\" $FreeBSD$ | |||
.\" | |||
.Dd September 8, 2016 | |||
.Dt GGATEL 8 | |||
.Os | |||
.Sh NAME | |||
.Nm ggatel | |||
.Nd "GEOM Gate local control utility" | |||
.Sh SYNOPSIS | |||
.Nm | |||
.Cm create | |||
.Op Fl v | |||
.Op Fl o Cm ro | wo | rw | |||
.Op Fl s Ar sectorsize | |||
.Op Fl t Ar timeout | |||
.Op Fl u Ar unit | |||
.Ar path | |||
.Nm | |||
.Cm destroy | |||
.Op Fl f | |||
.Fl u Ar unit | |||
.Nm | |||
.Cm list | |||
.Op Fl v | |||
.Op Fl u Ar unit | |||
.Nm | |||
.Cm rescue | |||
.Op Fl v | |||
.Op Fl o Cm ro | wo | rw | |||
.Fl u Ar unit | |||
.Ar path | |||
.Sh DESCRIPTION | |||
The | |||
.Nm | |||
utility is a local GEOM Gate class consumer. | |||
It can be used as a replacement for | |||
.Xr md 4 | |||
devices or as a | |||
.Dq GEOMificator | |||
for non GEOM-aware devices, but it was mainly created as an example | |||
on how to use and how to communicate with the GEOM Gate kernel subsystem. | |||
.Pp | |||
Available commands: | |||
.Bl -tag -width ".Cm destroy" | |||
.It Cm create | |||
Create a | |||
.Nm ggate | |||
provider related to the given regular file or device. | |||
.It Cm destroy | |||
Destroy the given | |||
.Nm ggate | |||
provider. | |||
.It Cm list | |||
List | |||
.Nm ggate | |||
providers. | |||
.It Cm rescue | |||
Take over a previously created provider and handle pending and future | |||
requests. | |||
This is useful if the initial | |||
.Nm | |||
process died. | |||
To prevent data loss, the given path must lead to the | |||
regular file or device that was used to create the provider. | |||
.El | |||
.Pp | |||
Available options: | |||
.Bl -tag -width ".Fl s Cm ro | wo | rw" | |||
.It Fl f | |||
Forcibly destroy | |||
.Nm ggate | |||
provider (cancels all pending requests). | |||
.It Fl o Cm ro | wo | rw | |||
Specify permissions to use when opening the file or device: read-only | |||
.Pq Cm ro , | |||
write-only | |||
.Pq Cm wo , | |||
or read-write | |||
.Pq Cm rw . | |||
Default is | |||
.Cm rw . | |||
.It Fl s Ar sectorsize | |||
Sector size for | |||
.Nm ggate | |||
provider. | |||
If not specified, it is taken from the device, or set to 512 bytes for files. | |||
.It Fl t Ar timeout | |||
Number of seconds to wait before an I/O request will be canceled. | |||
0 means no timeout. | |||
Default is 30. | |||
.It Fl u Ar unit | |||
Unit number to use. | |||
.It Fl v | |||
Do not fork, run in foreground and print debug information on standard | |||
output. | |||
.It Ar path | |||
Path to a regular file or device. | |||
.El | |||
.Sh EXIT STATUS | |||
Exit status is 0 on success, or 1 if the command fails. | |||
To get details about the failure, | |||
.Nm | |||
should be called with the | |||
.Fl v | |||
option. | |||
.Sh EXAMPLES | |||
.Dq GEOMify | |||
the | |||
.Dq Li fd0 | |||
device and use | |||
.Xr gbde 8 | |||
to encrypt data on a floppy disk. | |||
.Bd -literal -offset indent | |||
ggatel create -u 5 /dev/fd0 | |||
gbde init /dev/ggate5 | |||
gbde attach ggate5 | |||
newfs /dev/ggate5.bde | |||
mount /dev/ggate5.bde /secret | |||
cp /private/foo /secret/ | |||
umount /secret | |||
gbde detach ggate5 | |||
ggatel destroy -u 5 | |||
.Ed | |||
.Sh SEE ALSO | |||
.Xr geom 4 , | |||
.Xr gbde 8 , | |||
.Xr ggatec 8 , | |||
.Xr ggated 8 , | |||
.Xr mount 8 , | |||
.Xr newfs 8 | |||
.Sh HISTORY | |||
The | |||
.Nm | |||
utility appeared in | |||
.Fx 5.3 . | |||
.Sh AUTHORS | |||
The | |||
.Nm | |||
utility as well as this manual page was written by | |||
.An Pawel Jakub Dawidek Aq Mt pjd@FreeBSD.org . |
@@ -1,330 +0,0 @@ | |||
/*- | |||
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD | |||
* | |||
* Copyright (c) 2004 Pawel Jakub Dawidek <pjd@FreeBSD.org> | |||
* All rights reserved. | |||
* | |||
* Redistribution and use in source and binary forms, with or without | |||
* modification, are permitted provided 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 AUTHORS AND CONTRIBUTORS ``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 AUTHORS OR CONTRIBUTORS 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. | |||
* | |||
* $FreeBSD$ | |||
*/ | |||
#include <stdio.h> | |||
#include <stdlib.h> | |||
#include <stdint.h> | |||
#include <fcntl.h> | |||
#include <unistd.h> | |||
#include <string.h> | |||
#include <err.h> | |||
#include <errno.h> | |||
#include <assert.h> | |||
#include <sys/param.h> | |||
#include <sys/time.h> | |||
#include <sys/bio.h> | |||
#include <sys/disk.h> | |||
#include <sys/ioctl.h> | |||
#include <sys/stat.h> | |||
#include <sys/syslog.h> | |||
#include <geom/gate/g_gate.h> | |||
#include "ggate.h" | |||
static enum { UNSET, CREATE, DESTROY, LIST, RESCUE } action = UNSET; | |||
static const char *path = NULL; | |||
static int unit = G_GATE_UNIT_AUTO; | |||
static unsigned flags = 0; | |||
static int force = 0; | |||
static unsigned sectorsize = 0; | |||
static unsigned timeout = G_GATE_TIMEOUT; | |||
static void | |||
usage(void) | |||
{ | |||
fprintf(stderr, "usage: %s create [-v] [-o <ro|wo|rw>] " | |||
"[-s sectorsize] [-t timeout] [-u unit] <path>\n", getprogname()); | |||
fprintf(stderr, " %s rescue [-v] [-o <ro|wo|rw>] <-u unit> " | |||
"<path>\n", getprogname()); | |||
fprintf(stderr, " %s destroy [-f] <-u unit>\n", getprogname()); | |||
fprintf(stderr, " %s list [-v] [-u unit]\n", getprogname()); | |||
exit(EXIT_FAILURE); | |||
} | |||
static int | |||
g_gate_openflags(unsigned ggflags) | |||
{ | |||
if ((ggflags & G_GATE_FLAG_READONLY) != 0) | |||
return (O_RDONLY); | |||
else if ((ggflags & G_GATE_FLAG_WRITEONLY) != 0) | |||
return (O_WRONLY); | |||
return (O_RDWR); | |||
} | |||
static void | |||
g_gatel_serve(int fd) | |||
{ | |||
struct g_gate_ctl_io ggio; | |||
size_t bsize; | |||
if (g_gate_verbose == 0) { | |||
if (daemon(0, 0) == -1) { | |||
g_gate_destroy(unit, 1); | |||
err(EXIT_FAILURE, "Cannot daemonize"); | |||
} | |||
} | |||
g_gate_log(LOG_DEBUG, "Worker created: %u.", getpid()); | |||
ggio.gctl_version = G_GATE_VERSION; | |||
ggio.gctl_unit = unit; | |||
bsize = sectorsize; | |||
ggio.gctl_data = malloc(bsize); | |||
for (;;) { | |||
int error; | |||
once_again: | |||
ggio.gctl_length = bsize; | |||
ggio.gctl_error = 0; | |||
g_gate_ioctl(G_GATE_CMD_START, &ggio); | |||
error = ggio.gctl_error; | |||
switch (error) { | |||
case 0: | |||
break; | |||
case ECANCELED: | |||
/* Exit gracefully. */ | |||
free(ggio.gctl_data); | |||
g_gate_close_device(); | |||
close(fd); | |||
exit(EXIT_SUCCESS); | |||
case ENOMEM: | |||
/* Buffer too small. */ | |||
assert(ggio.gctl_cmd == BIO_DELETE || | |||
ggio.gctl_cmd == BIO_WRITE); | |||
ggio.gctl_data = realloc(ggio.gctl_data, | |||
ggio.gctl_length); | |||
if (ggio.gctl_data != NULL) { | |||
bsize = ggio.gctl_length; | |||
goto once_again; | |||
} | |||
/* FALLTHROUGH */ | |||
case ENXIO: | |||
default: | |||
g_gate_xlog("ioctl(/dev/%s): %s.", G_GATE_CTL_NAME, | |||
strerror(error)); | |||
} | |||
error = 0; | |||
switch (ggio.gctl_cmd) { | |||
case BIO_READ: | |||
if ((size_t)ggio.gctl_length > bsize) { | |||
ggio.gctl_data = realloc(ggio.gctl_data, | |||
ggio.gctl_length); | |||
if (ggio.gctl_data != NULL) | |||
bsize = ggio.gctl_length; | |||
else | |||
error = ENOMEM; | |||
} | |||
if (error == 0) { | |||
if (pread(fd, ggio.gctl_data, ggio.gctl_length, | |||
ggio.gctl_offset) == -1) { | |||
error = errno; | |||
} | |||
} | |||
break; | |||
case BIO_DELETE: | |||
case BIO_WRITE: | |||
if (pwrite(fd, ggio.gctl_data, ggio.gctl_length, | |||
ggio.gctl_offset) == -1) { | |||
error = errno; | |||
} | |||
break; | |||
default: | |||
error = EOPNOTSUPP; | |||
} | |||
ggio.gctl_error = error; | |||
g_gate_ioctl(G_GATE_CMD_DONE, &ggio); | |||
} | |||
} | |||
static void | |||
g_gatel_create(void) | |||
{ | |||
struct g_gate_ctl_create ggioc; | |||
int fd; | |||
fd = open(path, g_gate_openflags(flags) | O_DIRECT | O_FSYNC); | |||
if (fd == -1) | |||
err(EXIT_FAILURE, "Cannot open %s", path); | |||
memset(&ggioc, 0, sizeof(ggioc)); | |||
ggioc.gctl_version = G_GATE_VERSION; | |||
ggioc.gctl_unit = unit; | |||
ggioc.gctl_mediasize = g_gate_mediasize(fd); | |||
if (sectorsize == 0) | |||
sectorsize = g_gate_sectorsize(fd); | |||
ggioc.gctl_sectorsize = sectorsize; | |||
ggioc.gctl_timeout = timeout; | |||
ggioc.gctl_flags = flags; | |||
ggioc.gctl_maxcount = 0; | |||
strlcpy(ggioc.gctl_info, path, sizeof(ggioc.gctl_info)); | |||
g_gate_ioctl(G_GATE_CMD_CREATE, &ggioc); | |||
if (unit == -1) | |||
printf("%s%u\n", G_GATE_PROVIDER_NAME, ggioc.gctl_unit); | |||
unit = ggioc.gctl_unit; | |||
g_gatel_serve(fd); | |||
} | |||
static void | |||
g_gatel_rescue(void) | |||
{ | |||
struct g_gate_ctl_cancel ggioc; | |||
int fd; | |||
fd = open(path, g_gate_openflags(flags)); | |||
if (fd == -1) | |||
err(EXIT_FAILURE, "Cannot open %s", path); | |||
ggioc.gctl_version = G_GATE_VERSION; | |||
ggioc.gctl_unit = unit; | |||
ggioc.gctl_seq = 0; | |||
g_gate_ioctl(G_GATE_CMD_CANCEL, &ggioc); | |||
g_gatel_serve(fd); | |||
} | |||
int | |||
main(int argc, char *argv[]) | |||
{ | |||
if (argc < 2) | |||
usage(); | |||
if (strcasecmp(argv[1], "create") == 0) | |||
action = CREATE; | |||
else if (strcasecmp(argv[1], "rescue") == 0) | |||
action = RESCUE; | |||
else if (strcasecmp(argv[1], "destroy") == 0) | |||
action = DESTROY; | |||
else if (strcasecmp(argv[1], "list") == 0) | |||
action = LIST; | |||
else | |||
usage(); | |||
argc -= 1; | |||
argv += 1; | |||
for (;;) { | |||
int ch; | |||
ch = getopt(argc, argv, "fo:s:t:u:v"); | |||
if (ch == -1) | |||
break; | |||
switch (ch) { | |||
case 'f': | |||
if (action != DESTROY) | |||
usage(); | |||
force = 1; | |||
break; | |||
case 'o': | |||
if (action != CREATE && action != RESCUE) | |||
usage(); | |||
if (strcasecmp("ro", optarg) == 0) | |||
flags = G_GATE_FLAG_READONLY; | |||
else if (strcasecmp("wo", optarg) == 0) | |||
flags = G_GATE_FLAG_WRITEONLY; | |||
else if (strcasecmp("rw", optarg) == 0) | |||
flags = 0; | |||
else { | |||
errx(EXIT_FAILURE, | |||
"Invalid argument for '-o' option."); | |||
} | |||
break; | |||
case 's': | |||
if (action != CREATE) | |||
usage(); | |||
errno = 0; | |||
sectorsize = strtoul(optarg, NULL, 10); | |||
if (sectorsize == 0 && errno != 0) | |||
errx(EXIT_FAILURE, "Invalid sectorsize."); | |||
break; | |||
case 't': | |||
if (action != CREATE) | |||
usage(); | |||
errno = 0; | |||
timeout = strtoul(optarg, NULL, 10); | |||
if (timeout == 0 && errno != 0) | |||
errx(EXIT_FAILURE, "Invalid timeout."); | |||
break; | |||
case 'u': | |||
errno = 0; | |||
unit = strtol(optarg, NULL, 10); | |||
if (unit == 0 && errno != 0) | |||
errx(EXIT_FAILURE, "Invalid unit number."); | |||
break; | |||
case 'v': | |||
if (action == DESTROY) | |||
usage(); | |||
g_gate_verbose++; | |||
break; | |||
default: | |||
usage(); | |||
} | |||
} | |||
argc -= optind; | |||
argv += optind; | |||
switch (action) { | |||
case CREATE: | |||
if (argc != 1) | |||
usage(); | |||
g_gate_load_module(); | |||
g_gate_open_device(); | |||
path = argv[0]; | |||
g_gatel_create(); | |||
break; | |||
case RESCUE: | |||
if (argc != 1) | |||
usage(); | |||
if (unit == -1) { | |||
fprintf(stderr, "Required unit number.\n"); | |||
usage(); | |||
} | |||
g_gate_open_device(); | |||
path = argv[0]; | |||
g_gatel_rescue(); | |||
break; | |||
case DESTROY: | |||
if (unit == -1) { | |||
fprintf(stderr, "Required unit number.\n"); | |||
usage(); | |||
} | |||
g_gate_verbose = 1; | |||
g_gate_open_device(); | |||
g_gate_destroy(unit, force); | |||
break; | |||
case LIST: | |||
g_gate_list(unit, g_gate_verbose); | |||
break; | |||
case UNSET: | |||
default: | |||
usage(); | |||
} | |||
g_gate_close_device(); | |||
exit(EXIT_SUCCESS); | |||
} |
@@ -4,6 +4,6 @@ PACKAGE= tests | |||
TESTSDIR= ${TESTSBASE}/sys/geom/class/gate | |||
ATF_TESTS_SH+= ggate_test | |||
ATF_TESTS_SH+= ggatehttp_test | |||
.include <bsd.test.mk> |
@@ -1,269 +0,0 @@ | |||
# $FreeBSD$ | |||
PIDFILE=ggated.pid | |||
PLAINFILES=plainfiles | |||
PORT=33080 | |||
CONF=gg.exports | |||
atf_test_case ggated cleanup | |||
ggated_head() | |||
{ | |||
atf_set "descr" "ggated can proxy geoms" | |||
atf_set "require.progs" "ggatec ggated" | |||
atf_set "require.user" "root" | |||
atf_set "timeout" 60 | |||
} | |||
ggated_body() | |||
{ | |||
load_ggate | |||
us=$(alloc_ggate_dev) | |||
work=$(alloc_md) | |||
src=$(alloc_md) | |||
atf_check -e ignore -o ignore \ | |||
dd if=/dev/random of=/dev/$work bs=1m count=1 conv=notrunc | |||
atf_check -e ignore -o ignore \ | |||
dd if=/dev/random of=/dev/$src bs=1m count=1 conv=notrunc | |||
echo $CONF >> $PLAINFILES | |||
echo "127.0.0.1 RW /dev/$work" > $CONF | |||
atf_check ggated -p $PORT -F $PIDFILE $CONF | |||
atf_check ggatec create -p $PORT -u $us 127.0.0.1 /dev/$work | |||
ggate_dev=/dev/ggate${us} | |||
wait_for_ggate_device ${ggate_dev} | |||
atf_check -e ignore -o ignore \ | |||
dd if=/dev/${src} of=${ggate_dev} bs=1m count=1 conv=notrunc | |||
checksum /dev/$src /dev/$work | |||
} | |||
ggated_cleanup() | |||
{ | |||
common_cleanup | |||
} | |||
atf_test_case ggatel_file cleanup | |||
ggatel_file_head() | |||
{ | |||
atf_set "descr" "ggatel can proxy files" | |||
atf_set "require.progs" "ggatel" | |||
atf_set "require.user" "root" | |||
atf_set "timeout" 15 | |||
} | |||
ggatel_file_body() | |||
{ | |||
load_ggate | |||
us=$(alloc_ggate_dev) | |||
echo src work >> ${PLAINFILES} | |||
dd if=/dev/random of=work bs=1m count=1 | |||
dd if=/dev/random of=src bs=1m count=1 | |||
atf_check ggatel create -u $us work | |||
ggate_dev=/dev/ggate${us} | |||
wait_for_ggate_device ${ggate_dev} | |||
atf_check -e ignore -o ignore \ | |||
dd if=src of=${ggate_dev} bs=1m count=1 conv=notrunc | |||
checksum src work | |||
} | |||
ggatel_file_cleanup() | |||
{ | |||
common_cleanup | |||
} | |||
atf_test_case ggatel_md cleanup | |||
ggatel_md_head() | |||
{ | |||
atf_set "descr" "ggatel can proxy files" | |||
atf_set "require.progs" "ggatel" | |||
atf_set "require.user" "root" | |||
atf_set "timeout" 15 | |||
} | |||
ggatel_md_body() | |||
{ | |||
load_ggate | |||
us=$(alloc_ggate_dev) | |||
work=$(alloc_md) | |||
src=$(alloc_md) | |||
atf_check -e ignore -o ignore \ | |||
dd if=/dev/random of=$work bs=1m count=1 conv=notrunc | |||
atf_check -e ignore -o ignore \ | |||
dd if=/dev/random of=$src bs=1m count=1 conv=notrunc | |||
atf_check ggatel create -u $us /dev/$work | |||
ggate_dev=/dev/ggate${us} | |||
wait_for_ggate_device ${ggate_dev} | |||
atf_check -e ignore -o ignore \ | |||
dd if=/dev/$src of=${ggate_dev} bs=1m count=1 conv=notrunc | |||
checksum /dev/$src /dev/$work | |||
} | |||
ggatel_md_cleanup() | |||
{ | |||
common_cleanup | |||
} | |||
atf_test_case ggate_sock cleanup | |||
ggate_sock_head() | |||
{ | |||
atf_set "descr" "ggatec can connect to ggated over unix domain socket" | |||
atf_set "require.progs" "ggatec ggated" | |||
atf_set "require.user" "root" | |||
atf_set "timeout" 5 | |||
} | |||
ggate_sock_body() | |||
{ | |||
load_ggate | |||
us=$(alloc_ggate_dev) | |||
work=$(alloc_md) | |||
src=$(alloc_md) | |||
atf_check -e ignore -o ignore \ | |||
dd if=/dev/random of=/dev/$work bs=1m count=1 conv=notrunc | |||
atf_check -e ignore -o ignore \ | |||
dd if=/dev/random of=/dev/$src bs=1m count=1 conv=notrunc | |||
echo $CONF >> $PLAINFILES | |||
echo "0.0.0.0 RW /dev/$work" > $CONF | |||
atf_check ggated -q 10 -s local.sock -F $PIDFILE $CONF | |||
atf_check ggatec create -u $us sock:"$(pwd)/local.sock" /dev/$work | |||
ggate_dev=/dev/ggate${us} | |||
wait_for_ggate_device ${ggate_dev} | |||
atf_check -e ignore -o ignore \ | |||
dd if=/dev/${src} of=${ggate_dev} bs=1m count=1 conv=notrunc | |||
checksum /dev/$src /dev/$work | |||
} | |||
ggate_sock_cleanup() | |||
{ | |||
common_cleanup | |||
} | |||
atf_init_test_cases() | |||
{ | |||
atf_add_test_case ggated | |||
atf_add_test_case ggatel_file | |||
atf_add_test_case ggatel_md | |||
atf_add_test_case ggate_sock | |||
} | |||
alloc_ggate_dev() | |||
{ | |||
local us | |||
us=0 | |||
while [ -c /dev/ggate${us} ]; do | |||
: $(( us += 1 )) | |||
done | |||
echo ${us} > ggate.devs | |||
echo ${us} | |||
} | |||
alloc_md() | |||
{ | |||
local md | |||
md=$(mdconfig -a -t malloc -s 1M) || \ | |||
atf_fail "failed to allocate md device" | |||
echo ${md} >> md.devs | |||
echo ${md} | |||
} | |||
checksum() | |||
{ | |||
local src work | |||
src=$1 | |||
work=$2 | |||
src_checksum=$(md5 -q $src) | |||
work_checksum=$(md5 -q $work) | |||
if [ "$work_checksum" != "$src_checksum" ]; then | |||
atf_fail "work md5 checksum didn't match" | |||
fi | |||
ggate_checksum=$(md5 -q /dev/ggate${us}) | |||
if [ "$ggate_checksum" != "$src_checksum" ]; then | |||
atf_fail "ggate md5 checksum didn't match" | |||
fi | |||
} | |||
common_cleanup() | |||
{ | |||
if [ -f "ggate.devs" ]; then | |||
while read test_ggate; do | |||
ggatec destroy -f -u $test_ggate >/dev/null | |||
done < ggate.devs | |||
rm ggate.devs | |||
fi | |||
if [ -f "$PIDFILE" ]; then | |||
pkill -F "$PIDFILE" | |||
rm $PIDFILE | |||
fi | |||
if [ -f "PLAINFILES" ]; then | |||
while read f; do | |||
rm -f ${f} | |||
done < ${PLAINFILES} | |||
rm ${PLAINFILES} | |||
fi | |||
if [ -f "md.devs" ]; then | |||
while read test_md; do | |||
mdconfig -d -u $test_md 2>/dev/null | |||
done < md.devs | |||
rm md.devs | |||
fi | |||
true | |||
} | |||
load_ggate() | |||
{ | |||
local class=gate | |||
# If the geom class isn't already loaded, try loading it. | |||
if ! kldstat -q -m g_${class}; then | |||
if ! geom ${class} load; then | |||
atf_skip "could not load module for geom class=${class}" | |||
fi | |||
fi | |||
} | |||
# Bug 204616: ggatel(8) creates /dev/ggate* asynchronously if `ggatel create` | |||
# isn't called with `-v`. | |||
wait_for_ggate_device() | |||
{ | |||
ggate_device=$1 | |||
while [ ! -c $ggate_device ]; do | |||
sleep 0.5 | |||
done | |||
} |
@@ -0,0 +1,151 @@ | |||
# $FreeBSD$ | |||
PIDFILE=ggated.pid | |||
TESTURL="$GGATEHTTP_URL" | |||
TEMPFILE="random.data" | |||
atf_test_case ggatehttp cleanup | |||
ggatehttp_head() | |||
{ | |||
atf_set "descr" "ggatehttp can proxy to http" | |||
atf_set "require.progs" "ggatehttp" | |||
atf_set "require.user" "root" | |||
atf_set "timeout" 10 | |||
} | |||
ggatehttp_body() | |||
{ | |||
load_ggate | |||
us=$(alloc_ggate_dev) | |||
src=$(alloc_md) | |||
n1mchunks=10 | |||
atf_check -e ignore -o ignore \ | |||
dd if=/dev/random of="$TEMPFILE" bs=1m count=$n1mchunks conv=notrunc | |||
atf_check ggatehttp create -u $us "$TESTURL" | |||
ggate_dev=/dev/ggate${us} | |||
wait_for_ggate_device ${ggate_dev} | |||
# Test writing | |||
atf_check -e ignore -o ignore \ | |||
dd if="$TEMPFILE" of=${ggate_dev} bs=1m count=$n1mchunks conv=notrunc | |||
# Test reading | |||
atf_check -e ignore -o ignore \ | |||
dd of="$TEMPFILE"2 if=${ggate_dev} bs=1m count=$n1mchunks conv=notrunc | |||
ls -l "$TEMPFILE" "$TEMPFILE"2 | |||
# Verify that we read what we wrote | |||
atf_check cmp "$TEMPFILE" "$TEMPFILE"2 | |||
rm "$TEMPFILE" "$TEMPFILE"2 | |||
} | |||
ggatehttp_cleanup() | |||
{ | |||
common_cleanup | |||
} | |||
atf_init_test_cases() | |||
{ | |||
atf_add_test_case ggatehttp | |||
} | |||
alloc_ggate_dev() | |||
{ | |||
local us | |||
us=0 | |||
while [ -c /dev/ggate${us} ]; do | |||
: $(( us += 1 )) | |||
done | |||
echo ${us} > ggate.devs | |||
echo ${us} | |||
} | |||
alloc_md() | |||
{ | |||
local md | |||
md=$(mdconfig -a -t malloc -s 1M) || \ | |||
atf_fail "failed to allocate md device" | |||
echo ${md} >> md.devs | |||
echo ${md} | |||
} | |||
checksum() | |||
{ | |||
local src work | |||
src=$1 | |||
work=$2 | |||
src_checksum=$(md5 -q $src) | |||
work_checksum=$(md5 -q $work) | |||
if [ "$work_checksum" != "$src_checksum" ]; then | |||
atf_fail "work md5 checksum didn't match" | |||
fi | |||
ggate_checksum=$(md5 -q /dev/ggate${us}) | |||
if [ "$ggate_checksum" != "$src_checksum" ]; then | |||
atf_fail "ggate md5 checksum didn't match" | |||
fi | |||
} | |||
common_cleanup() | |||
{ | |||
if [ -f "ggate.devs" ]; then | |||
while read test_ggate; do | |||
ggatec destroy -f -u $test_ggate >/dev/null | |||
done < ggate.devs | |||
rm ggate.devs | |||
fi | |||
if [ -f "$PIDFILE" ]; then | |||
pkill -F "$PIDFILE" | |||
rm $PIDFILE | |||
fi | |||
if [ -f "PLAINFILES" ]; then | |||
while read f; do | |||
rm -f ${f} | |||
done < ${PLAINFILES} | |||
rm ${PLAINFILES} | |||
fi | |||
if [ -f "md.devs" ]; then | |||
while read test_md; do | |||
mdconfig -d -u $test_md 2>/dev/null | |||
done < md.devs | |||
rm md.devs | |||
fi | |||
true | |||
} | |||
load_ggate() | |||
{ | |||
local class=gate | |||
# If the geom class isn't already loaded, try loading it. | |||
if ! kldstat -q -m g_${class}; then | |||
if ! geom ${class} load; then | |||
atf_skip "could not load module for geom class=${class}" | |||
fi | |||
fi | |||
} | |||
# Bug 204616: ggatel(8) creates /dev/ggate* asynchronously if `ggatel create` | |||
# isn't called with `-v`. | |||
wait_for_ggate_device() | |||
{ | |||
ggate_device=$1 | |||
while [ ! -c $ggate_device ]; do | |||
sleep 0.5 | |||
done | |||
} |