diff --git a/sbin/ggate/ggatec/ggatec.c b/sbin/ggate/ggatec/ggatec.c index 0695dae0dca2..dfd9506e5e26 100644 --- a/sbin/ggate/ggatec/ggatec.c +++ b/sbin/ggate/ggatec/ggatec.c @@ -1,667 +1,703 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2004 Pawel Jakub Dawidek * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ggate.h" 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 int initialbuffersize = 131072; static void usage(void) { fprintf(stderr, "usage: %s create [-nv] [-o ] [-p port] " "[-q queue_size] [-R rcvbuf] [-S sndbuf] [-s sectorsize] " "[-t timeout] [-u unit] \n", getprogname()); fprintf(stderr, " %s rescue [-nv] [-o ] [-p port] " "[-R rcvbuf] [-S sndbuf] <-u unit> \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; - char buf[MAXPHYS]; - ssize_t data; + size_t buf_capacity; + ssize_t numbytesprocd; int error; + char *newbuf; g_gate_log(LOG_NOTICE, "%s: started!", __func__); + buf_capacity = initialbuffersize; + ggio.gctl_version = G_GATE_VERSION; ggio.gctl_unit = unit; - ggio.gctl_data = buf; + ggio.gctl_data = malloc(buf_capacity); + if (ggio.gctl_data == NULL) { + g_gate_log(LOG_ERR, "%s: Cannot alloc buffer.", __func__); + pthread_exit(NULL); + } for (;;) { - ggio.gctl_length = sizeof(buf); + ggio.gctl_length = buf_capacity; 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, + g_gate_log(LOG_DEBUG, "buffer too small. new size: %u", ggio.gctl_length); - if (ggio.gctl_data != NULL) { - bsize = ggio.gctl_length; - goto once_again; + newbuf = malloc(ggio.gctl_length); + if (newbuf != NULL) { + free(ggio.gctl_data); + ggio.gctl_data = newbuf; + buf_capacity = ggio.gctl_length; + continue; } /* FALLTHROUGH */ -#endif + } + case ENXIO: default: g_gate_xlog("ioctl(/dev/%s): %s.", G_GATE_CTL_NAME, strerror(error)); } if (reconnect) break; 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_FLUSH: + hdr.gh_cmd = GGATE_CMD_FLUSH; + break; default: - g_gate_log(LOG_NOTICE, "Unknown gctl_cmd: %i", ggio.gctl_cmd); - ggio.gctl_error = EOPNOTSUPP; - g_gate_ioctl(G_GATE_CMD_DONE, &ggio); - continue; - } - - /* Don't send requests for more data than we can handle the response for! */ - if (ggio.gctl_length > MAXPHYS) { - g_gate_log(LOG_ERR, "Request too big: %zd", ggio.gctl_length); + g_gate_log(LOG_NOTICE, "Unknown gctl_cmd: %i", + ggio.gctl_cmd); ggio.gctl_error = EOPNOTSUPP; g_gate_ioctl(G_GATE_CMD_DONE, &ggio); continue; } hdr.gh_seq = ggio.gctl_seq; hdr.gh_offset = ggio.gctl_offset; hdr.gh_length = ggio.gctl_length; hdr.gh_error = 0; g_gate_swap2n_hdr(&hdr); - data = g_gate_send(sendfd, &hdr, sizeof(hdr), MSG_NOSIGNAL); + numbytesprocd = 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)) { + if (numbytesprocd != 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, + numbytesprocd = 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); + if (numbytesprocd != ggio.gctl_length) { + g_gate_log(LOG_ERR, "Lost connection 2 (%zd != %zd).", + numbytesprocd, (ssize_t)ggio.gctl_length); reconnect = 1; pthread_kill(recvtd, SIGUSR1); break; } g_gate_log(LOG_DEBUG, "Sent %zd bytes (offset=%" - PRIu64 ", length=%" PRIu32 ").", data, + PRIu64 ", length=%" PRIu32 ").", numbytesprocd, 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; - char buf[MAXPHYS]; - ssize_t data; + ssize_t buf_capacity; + ssize_t numbytesprocd; + char *newbuf; g_gate_log(LOG_NOTICE, "%s: started!", __func__); + buf_capacity = initialbuffersize; + ggio.gctl_version = G_GATE_VERSION; ggio.gctl_unit = unit; - ggio.gctl_data = buf; + ggio.gctl_data = malloc(buf_capacity); + if (ggio.gctl_data == NULL) { + g_gate_log(LOG_ERR, "%s: Cannot alloc buffer.", __func__); + pthread_exit(NULL); + } for (;;) { - data = g_gate_recv(recvfd, &hdr, sizeof(hdr), MSG_WAITALL); + numbytesprocd = 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) + if (numbytesprocd != sizeof(hdr)) { + if (numbytesprocd == -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; - /* Do not overflow our buffer if there is a bogus response. */ - if (ggio.gctl_length > (off_t) sizeof(buf)) { - g_gate_log(LOG_ERR, "Received too big response: %zd", ggio.gctl_length); - break; + if (ggio.gctl_length > buf_capacity) { + newbuf = malloc(ggio.gctl_length); + if (newbuf != NULL) { + free(ggio.gctl_data); + ggio.gctl_data = newbuf; + buf_capacity = ggio.gctl_length; + } else { + g_gate_log(LOG_ERR, "Received too big response: %zd", + ggio.gctl_length); + break; + } } if (ggio.gctl_error == 0 && ggio.gctl_cmd == GGATE_CMD_READ) { - data = g_gate_recv(recvfd, ggio.gctl_data, + numbytesprocd = 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) { + if (numbytesprocd != 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=%" - PRIu64 ", length=%" PRIu32 ").", data, + PRIu64 ", length=%" PRIu32 ").", numbytesprocd, hdr.gh_offset, 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_in serv; int sfd; /* * Do the network stuff. */ bzero(&serv, sizeof(serv)); serv.sin_family = AF_INET; serv.sin_addr.s_addr = g_gate_str2ip(host); if (serv.sin_addr.s_addr == INADDR_NONE) { g_gate_log(LOG_DEBUG, "Invalid IP/host name: %s.", host); return (-1); } serv.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); } g_gate_socket_settings(sfd); if (connect(sfd, (struct sockaddr *)&serv, sizeof(serv)) == -1) { g_gate_log(LOG_DEBUG, "Cannot connect to server: %s.", strerror(errno)); close(sfd); return (-1); } 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(); } +static void +init_initial_buffer_size() +{ + int value; + size_t intsize; + intsize = sizeof(initialbuffersize); + if (sysctlbyname("kern.maxphys", &value, &intsize, NULL, 0) == 0) + initialbuffersize = value; +} + 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; + init_initial_buffer_size(); + 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); } diff --git a/sbin/ggate/ggated/ggated.c b/sbin/ggate/ggated/ggated.c index 226ba1ce72de..7cacbf58037e 100644 --- a/sbin/ggate/ggated/ggated.c +++ b/sbin/ggate/ggated/ggated.c @@ -1,1081 +1,1093 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2004 Pawel Jakub Dawidek * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ggate.h" #define GGATED_EXPORT_FILE "/etc/gg.exports" struct ggd_connection { off_t c_mediasize; unsigned c_sectorsize; unsigned c_flags; /* flags (RO/RW) */ int c_diskfd; int c_sendfd; int c_recvfd; time_t c_birthtime; char *c_path; uint64_t c_token; in_addr_t c_srcip; LIST_ENTRY(ggd_connection) c_next; }; struct ggd_request { struct g_gate_hdr r_hdr; char *r_data; TAILQ_ENTRY(ggd_request) r_next; }; #define r_cmd r_hdr.gh_cmd #define r_offset r_hdr.gh_offset #define r_length r_hdr.gh_length #define r_error r_hdr.gh_error struct ggd_export { char *e_path; /* path to device/file */ in_addr_t e_ip; /* remote IP address */ in_addr_t e_mask; /* IP mask */ unsigned e_flags; /* flags (RO/RW) */ SLIST_ENTRY(ggd_export) e_next; }; static const char *exports_file = GGATED_EXPORT_FILE; static int got_sighup = 0; static in_addr_t bindaddr; static TAILQ_HEAD(, ggd_request) inqueue = TAILQ_HEAD_INITIALIZER(inqueue); static TAILQ_HEAD(, ggd_request) outqueue = TAILQ_HEAD_INITIALIZER(outqueue); static pthread_mutex_t inqueue_mtx, outqueue_mtx; static pthread_cond_t inqueue_cond, outqueue_cond; static SLIST_HEAD(, ggd_export) exports = SLIST_HEAD_INITIALIZER(exports); static LIST_HEAD(, ggd_connection) connections = LIST_HEAD_INITIALIZER(connections); static void *recv_thread(void *arg); static void *disk_thread(void *arg); static void *send_thread(void *arg); static void usage(void) { fprintf(stderr, "usage: %s [-nv] [-a address] [-F pidfile] [-p port] " "[-R rcvbuf] [-S sndbuf] [exports file]\n", getprogname()); exit(EXIT_FAILURE); } static char * ip2str(in_addr_t ip) { static char sip[16]; snprintf(sip, sizeof(sip), "%u.%u.%u.%u", ((ip >> 24) & 0xff), ((ip >> 16) & 0xff), ((ip >> 8) & 0xff), (ip & 0xff)); return (sip); } static in_addr_t countmask(unsigned m) { in_addr_t mask; if (m == 0) { mask = 0x0; } else { mask = 1 << (32 - m); mask--; mask = ~mask; } return (mask); } static void line_parse(char *line, unsigned lineno) { struct ggd_export *ex; char *word, *path, *sflags; unsigned flags, i, vmask; in_addr_t ip, mask; ip = mask = flags = vmask = 0; path = NULL; sflags = NULL; for (i = 0, word = strtok(line, " \t"); word != NULL; i++, word = strtok(NULL, " \t")) { switch (i) { case 0: /* IP address or host name */ ip = g_gate_str2ip(strsep(&word, "/")); if (ip == INADDR_NONE) { g_gate_xlog("Invalid IP/host name at line %u.", lineno); } ip = ntohl(ip); if (word == NULL) vmask = 32; else { errno = 0; vmask = strtoul(word, NULL, 10); if (vmask == 0 && errno != 0) { g_gate_xlog("Invalid IP mask value at " "line %u.", lineno); } if ((unsigned)vmask > 32) { g_gate_xlog("Invalid IP mask value at line %u.", lineno); } } mask = countmask(vmask); break; case 1: /* flags */ if (strcasecmp("rd", word) == 0 || strcasecmp("ro", word) == 0) { flags = O_RDONLY; } else if (strcasecmp("wo", word) == 0) { flags = O_WRONLY; } else if (strcasecmp("rw", word) == 0) { flags = O_RDWR; } else { g_gate_xlog("Invalid value in flags field at " "line %u.", lineno); } sflags = word; break; case 2: /* path */ if (strlen(word) >= MAXPATHLEN) { g_gate_xlog("Path too long at line %u. ", lineno); } path = word; break; default: g_gate_xlog("Too many arguments at line %u. ", lineno); } } if (i != 3) g_gate_xlog("Too few arguments at line %u.", lineno); ex = malloc(sizeof(*ex)); if (ex == NULL) g_gate_xlog("Not enough memory."); ex->e_path = strdup(path); if (ex->e_path == NULL) g_gate_xlog("Not enough memory."); /* Made 'and' here. */ ex->e_ip = (ip & mask); ex->e_mask = mask; ex->e_flags = flags; SLIST_INSERT_HEAD(&exports, ex, e_next); g_gate_log(LOG_DEBUG, "Added %s/%u %s %s to exports list.", ip2str(ex->e_ip), vmask, path, sflags); } static void exports_clear(void) { struct ggd_export *ex; while (!SLIST_EMPTY(&exports)) { ex = SLIST_FIRST(&exports); SLIST_REMOVE_HEAD(&exports, e_next); free(ex); } } #define EXPORTS_LINE_SIZE 2048 static void exports_get(void) { char buf[EXPORTS_LINE_SIZE], *line; unsigned lineno = 0, objs = 0, len; FILE *fd; exports_clear(); fd = fopen(exports_file, "r"); if (fd == NULL) { g_gate_xlog("Cannot open exports file (%s): %s.", exports_file, strerror(errno)); } g_gate_log(LOG_INFO, "Reading exports file (%s).", exports_file); for (;;) { if (fgets(buf, sizeof(buf), fd) == NULL) { if (feof(fd)) break; g_gate_xlog("Error while reading exports file: %s.", strerror(errno)); } /* Increase line count. */ lineno++; /* Skip spaces and tabs. */ for (line = buf; *line == ' ' || *line == '\t'; ++line) ; /* Empty line, comment or empty line at the end of file. */ if (*line == '\n' || *line == '#' || *line == '\0') continue; len = strlen(line); if (line[len - 1] == '\n') { /* Remove new line char. */ line[len - 1] = '\0'; } else { if (!feof(fd)) g_gate_xlog("Line %u too long.", lineno); } line_parse(line, lineno); objs++; } fclose(fd); if (objs == 0) g_gate_xlog("There are no objects to export."); g_gate_log(LOG_INFO, "Exporting %u object(s).", objs); } static int exports_check(struct ggd_export *ex, struct g_gate_cinit *cinit, struct ggd_connection *conn) { char ipmask[32]; /* 32 == strlen("xxx.xxx.xxx.xxx/xxx.xxx.xxx.xxx")+1 */ int error = 0, flags; strlcpy(ipmask, ip2str(ex->e_ip), sizeof(ipmask)); strlcat(ipmask, "/", sizeof(ipmask)); strlcat(ipmask, ip2str(ex->e_mask), sizeof(ipmask)); if ((cinit->gc_flags & GGATE_FLAG_RDONLY) != 0) { if (ex->e_flags == O_WRONLY) { g_gate_log(LOG_WARNING, "Read-only access requested, " "but %s (%s) is exported write-only.", ex->e_path, ipmask); return (EPERM); } else { conn->c_flags |= GGATE_FLAG_RDONLY; } } else if ((cinit->gc_flags & GGATE_FLAG_WRONLY) != 0) { if (ex->e_flags == O_RDONLY) { g_gate_log(LOG_WARNING, "Write-only access requested, " "but %s (%s) is exported read-only.", ex->e_path, ipmask); return (EPERM); } else { conn->c_flags |= GGATE_FLAG_WRONLY; } } else { if (ex->e_flags == O_RDONLY) { g_gate_log(LOG_WARNING, "Read-write access requested, " "but %s (%s) is exported read-only.", ex->e_path, ipmask); return (EPERM); } else if (ex->e_flags == O_WRONLY) { g_gate_log(LOG_WARNING, "Read-write access requested, " "but %s (%s) is exported write-only.", ex->e_path, ipmask); return (EPERM); } } if ((conn->c_flags & GGATE_FLAG_RDONLY) != 0) flags = O_RDONLY; else if ((conn->c_flags & GGATE_FLAG_WRONLY) != 0) flags = O_WRONLY; else flags = O_RDWR; if (conn->c_diskfd != -1) { if (strcmp(conn->c_path, ex->e_path) != 0) { g_gate_log(LOG_ERR, "old %s and new %s: " "Path mismatch during handshakes.", conn->c_path, ex->e_path); return (EPERM); } return (0); } conn->c_diskfd = open(ex->e_path, flags); if (conn->c_diskfd == -1) { error = errno; g_gate_log(LOG_ERR, "Cannot open %s: %s.", ex->e_path, strerror(error)); return (error); } return (0); } static struct ggd_export * exports_find(struct sockaddr *s, struct g_gate_cinit *cinit, struct ggd_connection *conn) { struct ggd_export *ex; in_addr_t ip; int error; ip = htonl(((struct sockaddr_in *)(void *)s)->sin_addr.s_addr); SLIST_FOREACH(ex, &exports, e_next) { if ((ip & ex->e_mask) != ex->e_ip) { g_gate_log(LOG_DEBUG, "exports[%s]: IP mismatch.", ex->e_path); continue; } if (strcmp(cinit->gc_path, ex->e_path) != 0) { g_gate_log(LOG_DEBUG, "exports[%s]: Path mismatch.", ex->e_path); continue; } error = exports_check(ex, cinit, conn); if (error == 0) return (ex); else { errno = error; return (NULL); } } g_gate_log(LOG_WARNING, "Unauthorized connection from: %s.", ip2str(ip)); errno = EPERM; return (NULL); } /* * Remove timed out connections. */ static void connection_cleanups(void) { struct ggd_connection *conn, *tconn; time_t now; time(&now); LIST_FOREACH_SAFE(conn, &connections, c_next, tconn) { if (now - conn->c_birthtime > 10) { LIST_REMOVE(conn, c_next); g_gate_log(LOG_NOTICE, "Connection from %s [%s] removed.", ip2str(conn->c_srcip), conn->c_path); close(conn->c_diskfd); close(conn->c_sendfd); close(conn->c_recvfd); free(conn->c_path); free(conn); } } } static struct ggd_connection * connection_find(struct g_gate_cinit *cinit) { struct ggd_connection *conn; LIST_FOREACH(conn, &connections, c_next) { if (conn->c_token == cinit->gc_token) break; } return (conn); } static struct ggd_connection * connection_new(struct g_gate_cinit *cinit, struct sockaddr *s, int sfd) { struct ggd_connection *conn; in_addr_t ip; /* * First, look for old connections. * We probably should do it every X seconds, but what for? * It is only dangerous if an attacker wants to overload connections * queue, so here is a good place to do the cleanups. */ connection_cleanups(); conn = malloc(sizeof(*conn)); if (conn == NULL) return (NULL); conn->c_path = strdup(cinit->gc_path); if (conn->c_path == NULL) { free(conn); return (NULL); } conn->c_token = cinit->gc_token; ip = htonl(((struct sockaddr_in *)(void *)s)->sin_addr.s_addr); conn->c_srcip = ip; conn->c_diskfd = conn->c_sendfd = conn->c_recvfd = -1; if ((cinit->gc_flags & GGATE_FLAG_SEND) != 0) conn->c_sendfd = sfd; else conn->c_recvfd = sfd; conn->c_mediasize = 0; conn->c_sectorsize = 0; time(&conn->c_birthtime); conn->c_flags = cinit->gc_flags; LIST_INSERT_HEAD(&connections, conn, c_next); g_gate_log(LOG_DEBUG, "Connection created [%s, %s].", ip2str(ip), conn->c_path); return (conn); } static int connection_add(struct ggd_connection *conn, struct g_gate_cinit *cinit, struct sockaddr *s, int sfd) { in_addr_t ip; ip = htonl(((struct sockaddr_in *)(void *)s)->sin_addr.s_addr); if ((cinit->gc_flags & GGATE_FLAG_SEND) != 0) { if (conn->c_sendfd != -1) { g_gate_log(LOG_WARNING, "Send socket already exists [%s, %s].", ip2str(ip), conn->c_path); return (EEXIST); } conn->c_sendfd = sfd; } else { if (conn->c_recvfd != -1) { g_gate_log(LOG_WARNING, "Receive socket already exists [%s, %s].", ip2str(ip), conn->c_path); return (EEXIST); } conn->c_recvfd = sfd; } g_gate_log(LOG_DEBUG, "Connection added [%s, %s].", ip2str(ip), conn->c_path); return (0); } /* * Remove one socket from the given connection or the whole * connection if sfd == -1. */ static void connection_remove(struct ggd_connection *conn) { LIST_REMOVE(conn, c_next); g_gate_log(LOG_DEBUG, "Connection removed [%s %s].", ip2str(conn->c_srcip), conn->c_path); if (conn->c_diskfd != -1) close(conn->c_diskfd); if (conn->c_sendfd != -1) close(conn->c_sendfd); if (conn->c_recvfd != -1) close(conn->c_recvfd); free(conn->c_path); free(conn); } static int connection_ready(struct ggd_connection *conn) { return (conn->c_sendfd != -1 && conn->c_recvfd != -1); } static void connection_launch(struct ggd_connection *conn) { pthread_t td; int error, pid; pid = fork(); if (pid > 0) return; else if (pid == -1) { g_gate_log(LOG_ERR, "Cannot fork: %s.", strerror(errno)); return; } g_gate_log(LOG_DEBUG, "Process created [%s].", conn->c_path); /* * Create condition variables and mutexes for in-queue and out-queue * synchronization. */ error = pthread_mutex_init(&inqueue_mtx, NULL); if (error != 0) { g_gate_xlog("pthread_mutex_init(inqueue_mtx): %s.", strerror(error)); } error = pthread_cond_init(&inqueue_cond, NULL); if (error != 0) { g_gate_xlog("pthread_cond_init(inqueue_cond): %s.", strerror(error)); } error = pthread_mutex_init(&outqueue_mtx, NULL); if (error != 0) { g_gate_xlog("pthread_mutex_init(outqueue_mtx): %s.", strerror(error)); } error = pthread_cond_init(&outqueue_cond, NULL); if (error != 0) { g_gate_xlog("pthread_cond_init(outqueue_cond): %s.", strerror(error)); } /* * Create threads: * recvtd - thread for receiving I/O request * diskio - thread for doing I/O request * sendtd - thread for sending I/O requests back */ error = pthread_create(&td, NULL, send_thread, conn); if (error != 0) { g_gate_xlog("pthread_create(send_thread): %s.", strerror(error)); } error = pthread_create(&td, NULL, recv_thread, conn); if (error != 0) { g_gate_xlog("pthread_create(recv_thread): %s.", strerror(error)); } disk_thread(conn); } static void sendfail(int sfd, int error, const char *fmt, ...) { struct g_gate_sinit sinit; va_list ap; ssize_t data; memset(&sinit, 0, sizeof(sinit)); sinit.gs_error = error; g_gate_swap2n_sinit(&sinit); data = g_gate_send(sfd, &sinit, sizeof(sinit), 0); g_gate_swap2h_sinit(&sinit); if (data != sizeof(sinit)) { g_gate_log(LOG_WARNING, "Cannot send initial packet: %s.", strerror(errno)); return; } if (fmt != NULL) { va_start(ap, fmt); g_gate_vlog(LOG_WARNING, fmt, ap); va_end(ap); } } static void * malloc_waitok(size_t size) { void *p; while ((p = malloc(size)) == NULL) { g_gate_log(LOG_DEBUG, "Cannot allocate %zu bytes.", size); sleep(1); } return (p); } static void * recv_thread(void *arg) { struct ggd_connection *conn; struct ggd_request *req; ssize_t data; int error, fd; conn = arg; g_gate_log(LOG_NOTICE, "%s: started [%s]!", __func__, conn->c_path); fd = conn->c_recvfd; for (;;) { /* * Get header packet. */ req = malloc_waitok(sizeof(*req)); data = g_gate_recv(fd, &req->r_hdr, sizeof(req->r_hdr), MSG_WAITALL); if (data == 0) { g_gate_log(LOG_DEBUG, "Process %u exiting.", getpid()); exit(EXIT_SUCCESS); } else if (data == -1) { g_gate_xlog("Error while receiving hdr packet: %s.", strerror(errno)); } else if (data != sizeof(req->r_hdr)) { g_gate_xlog("Malformed hdr packet received."); } g_gate_log(LOG_DEBUG, "Received hdr packet."); g_gate_swap2h_hdr(&req->r_hdr); g_gate_log(LOG_DEBUG, "%s: offset=%" PRIu64 " length=%" PRIu32, __func__, req->r_offset, req->r_length); /* * Allocate memory for data. */ req->r_data = malloc_waitok(req->r_length); /* * Receive data to write for WRITE request. */ if (req->r_cmd == GGATE_CMD_WRITE) { g_gate_log(LOG_DEBUG, "Waiting for %u bytes of data...", req->r_length); data = g_gate_recv(fd, req->r_data, req->r_length, MSG_WAITALL); if (data == -1) { g_gate_xlog("Error while receiving data: %s.", strerror(errno)); } } /* * Put the request onto the incoming queue. */ error = pthread_mutex_lock(&inqueue_mtx); assert(error == 0); TAILQ_INSERT_TAIL(&inqueue, req, r_next); error = pthread_cond_signal(&inqueue_cond); assert(error == 0); error = pthread_mutex_unlock(&inqueue_mtx); assert(error == 0); } } static void * disk_thread(void *arg) { struct ggd_connection *conn; struct ggd_request *req; ssize_t data; int error, fd; conn = arg; g_gate_log(LOG_NOTICE, "%s: started [%s]!", __func__, conn->c_path); fd = conn->c_diskfd; for (;;) { /* * Get a request from the incoming queue. */ error = pthread_mutex_lock(&inqueue_mtx); assert(error == 0); while ((req = TAILQ_FIRST(&inqueue)) == NULL) { error = pthread_cond_wait(&inqueue_cond, &inqueue_mtx); assert(error == 0); } TAILQ_REMOVE(&inqueue, req, r_next); error = pthread_mutex_unlock(&inqueue_mtx); assert(error == 0); /* * Check the request. */ - assert(req->r_cmd == GGATE_CMD_READ || req->r_cmd == GGATE_CMD_WRITE); assert(req->r_offset + req->r_length <= (uintmax_t)conn->c_mediasize); assert((req->r_offset % conn->c_sectorsize) == 0); assert((req->r_length % conn->c_sectorsize) == 0); g_gate_log(LOG_DEBUG, "%s: offset=%" PRIu64 " length=%" PRIu32, __func__, req->r_offset, req->r_length); /* * Do the request. */ data = 0; switch (req->r_cmd) { case GGATE_CMD_READ: data = pread(fd, req->r_data, req->r_length, req->r_offset); break; case GGATE_CMD_WRITE: data = pwrite(fd, req->r_data, req->r_length, req->r_offset); /* Free data memory here - better sooner. */ free(req->r_data); req->r_data = NULL; break; + case GGATE_CMD_FLUSH: + data = fsync(fd); + if (data != 0) + req->r_error = errno; + break; + default: + g_gate_log(LOG_DEBUG, "Unsupported request: %i", req->r_cmd); + req->r_error = EOPNOTSUPP; + if (req->r_data != NULL) { + free(req->r_data); + req->r_data = NULL; + } + break; } if (data != (ssize_t)req->r_length) { /* Report short reads/writes as I/O errors. */ if (errno == 0) errno = EIO; g_gate_log(LOG_ERR, "Disk error: %s", strerror(errno)); req->r_error = errno; if (req->r_data != NULL) { free(req->r_data); req->r_data = NULL; } } /* * Put the request onto the outgoing queue. */ error = pthread_mutex_lock(&outqueue_mtx); assert(error == 0); TAILQ_INSERT_TAIL(&outqueue, req, r_next); error = pthread_cond_signal(&outqueue_cond); assert(error == 0); error = pthread_mutex_unlock(&outqueue_mtx); assert(error == 0); } /* NOTREACHED */ return (NULL); } static void * send_thread(void *arg) { struct ggd_connection *conn; struct ggd_request *req; ssize_t data; int error, fd; conn = arg; g_gate_log(LOG_NOTICE, "%s: started [%s]!", __func__, conn->c_path); fd = conn->c_sendfd; for (;;) { /* * Get a request from the outgoing queue. */ error = pthread_mutex_lock(&outqueue_mtx); assert(error == 0); while ((req = TAILQ_FIRST(&outqueue)) == NULL) { error = pthread_cond_wait(&outqueue_cond, &outqueue_mtx); assert(error == 0); } TAILQ_REMOVE(&outqueue, req, r_next); error = pthread_mutex_unlock(&outqueue_mtx); assert(error == 0); g_gate_log(LOG_DEBUG, "%s: offset=%" PRIu64 " length=%" PRIu32, __func__, req->r_offset, req->r_length); /* * Send the request. */ g_gate_swap2n_hdr(&req->r_hdr); if (g_gate_send(fd, &req->r_hdr, sizeof(req->r_hdr), 0) == -1) { g_gate_xlog("Error while sending hdr packet: %s.", strerror(errno)); } g_gate_log(LOG_DEBUG, "Sent hdr packet."); g_gate_swap2h_hdr(&req->r_hdr); if (req->r_data != NULL) { data = g_gate_send(fd, req->r_data, req->r_length, 0); if (data != (ssize_t)req->r_length) { g_gate_xlog("Error while sending data: %s.", strerror(errno)); } g_gate_log(LOG_DEBUG, "Sent %zd bytes (offset=%" PRIu64 ", size=%" PRIu32 ").", data, req->r_offset, req->r_length); free(req->r_data); } free(req); } /* NOTREACHED */ return (NULL); } static void log_connection(struct sockaddr *from) { in_addr_t ip; ip = htonl(((struct sockaddr_in *)(void *)from)->sin_addr.s_addr); g_gate_log(LOG_INFO, "Connection from: %s.", ip2str(ip)); } static int handshake(struct sockaddr *from, int sfd) { struct g_gate_version ver; struct g_gate_cinit cinit; struct g_gate_sinit sinit; struct ggd_connection *conn; struct ggd_export *ex; ssize_t data; log_connection(from); /* * Phase 1: Version verification. */ g_gate_log(LOG_DEBUG, "Receiving version packet."); data = g_gate_recv(sfd, &ver, sizeof(ver), MSG_WAITALL); g_gate_swap2h_version(&ver); if (data != sizeof(ver)) { g_gate_log(LOG_WARNING, "Malformed version packet."); return (0); } g_gate_log(LOG_DEBUG, "Version packet received."); if (memcmp(ver.gv_magic, GGATE_MAGIC, strlen(GGATE_MAGIC)) != 0) { g_gate_log(LOG_WARNING, "Invalid magic field."); return (0); } if (ver.gv_version != GGATE_VERSION) { g_gate_log(LOG_WARNING, "Version %u is not supported.", ver.gv_version); return (0); } ver.gv_error = 0; g_gate_swap2n_version(&ver); data = g_gate_send(sfd, &ver, sizeof(ver), 0); g_gate_swap2h_version(&ver); if (data == -1) { sendfail(sfd, errno, "Error while sending version packet: %s.", strerror(errno)); return (0); } /* * Phase 2: Request verification. */ g_gate_log(LOG_DEBUG, "Receiving initial packet."); data = g_gate_recv(sfd, &cinit, sizeof(cinit), MSG_WAITALL); g_gate_swap2h_cinit(&cinit); if (data != sizeof(cinit)) { g_gate_log(LOG_WARNING, "Malformed initial packet."); return (0); } g_gate_log(LOG_DEBUG, "Initial packet received."); conn = connection_find(&cinit); if (conn != NULL) { /* * Connection should already exists. */ g_gate_log(LOG_DEBUG, "Found existing connection (token=%lu).", (unsigned long)conn->c_token); if (connection_add(conn, &cinit, from, sfd) == -1) { connection_remove(conn); return (0); } } else { /* * New connection, allocate space. */ conn = connection_new(&cinit, from, sfd); if (conn == NULL) { sendfail(sfd, ENOMEM, "Cannot allocate new connection."); return (0); } g_gate_log(LOG_DEBUG, "New connection created (token=%lu).", (unsigned long)conn->c_token); } ex = exports_find(from, &cinit, conn); if (ex == NULL) { sendfail(sfd, errno, NULL); connection_remove(conn); return (0); } if (conn->c_mediasize == 0) { conn->c_mediasize = g_gate_mediasize(conn->c_diskfd); conn->c_sectorsize = g_gate_sectorsize(conn->c_diskfd); } sinit.gs_mediasize = conn->c_mediasize; sinit.gs_sectorsize = conn->c_sectorsize; sinit.gs_error = 0; g_gate_log(LOG_DEBUG, "Sending initial packet."); g_gate_swap2n_sinit(&sinit); data = g_gate_send(sfd, &sinit, sizeof(sinit), 0); g_gate_swap2h_sinit(&sinit); if (data == -1) { sendfail(sfd, errno, "Error while sending initial packet: %s.", strerror(errno)); return (0); } if (connection_ready(conn)) { connection_launch(conn); connection_remove(conn); } return (1); } static void huphandler(int sig __unused) { got_sighup = 1; } int main(int argc, char *argv[]) { const char *ggated_pidfile = _PATH_VARRUN "/ggated.pid"; struct pidfh *pfh; struct sockaddr_in serv; struct sockaddr from; socklen_t fromlen; pid_t otherpid; int ch, sfd, tmpsfd; unsigned port; bindaddr = htonl(INADDR_ANY); port = G_GATE_PORT; while ((ch = getopt(argc, argv, "a:hnp:F:R:S:v")) != -1) { switch (ch) { case 'a': bindaddr = g_gate_str2ip(optarg); if (bindaddr == INADDR_NONE) { errx(EXIT_FAILURE, "Invalid IP/host name to bind to."); } break; case 'F': ggated_pidfile = optarg; break; case 'n': nagle = 0; break; case 'p': errno = 0; port = strtoul(optarg, NULL, 10); if (port == 0 && errno != 0) errx(EXIT_FAILURE, "Invalid port."); break; case 'R': errno = 0; rcvbuf = strtoul(optarg, NULL, 10); if (rcvbuf == 0 && errno != 0) errx(EXIT_FAILURE, "Invalid rcvbuf."); break; case 'S': errno = 0; sndbuf = strtoul(optarg, NULL, 10); if (sndbuf == 0 && errno != 0) errx(EXIT_FAILURE, "Invalid sndbuf."); break; case 'v': g_gate_verbose++; break; case 'h': default: usage(); } } argc -= optind; argv += optind; if (argv[0] != NULL) exports_file = argv[0]; exports_get(); pfh = pidfile_open(ggated_pidfile, 0600, &otherpid); if (pfh == NULL) { if (errno == EEXIST) { errx(EXIT_FAILURE, "Daemon already running, pid: %jd.", (intmax_t)otherpid); } err(EXIT_FAILURE, "Cannot open/create pidfile"); } if (!g_gate_verbose) { /* Run in daemon mode. */ if (daemon(0, 0) == -1) g_gate_xlog("Cannot daemonize: %s", strerror(errno)); } pidfile_write(pfh); signal(SIGCHLD, SIG_IGN); sfd = socket(AF_INET, SOCK_STREAM, 0); if (sfd == -1) g_gate_xlog("Cannot open stream socket: %s.", strerror(errno)); bzero(&serv, sizeof(serv)); serv.sin_family = AF_INET; serv.sin_addr.s_addr = bindaddr; serv.sin_port = htons(port); g_gate_socket_settings(sfd); if (bind(sfd, (struct sockaddr *)&serv, sizeof(serv)) == -1) g_gate_xlog("bind(): %s.", strerror(errno)); if (listen(sfd, 5) == -1) g_gate_xlog("listen(): %s.", strerror(errno)); g_gate_log(LOG_INFO, "Listen on port: %d.", port); signal(SIGHUP, huphandler); for (;;) { fromlen = sizeof(from); tmpsfd = accept(sfd, &from, &fromlen); if (tmpsfd == -1) g_gate_xlog("accept(): %s.", strerror(errno)); if (got_sighup) { got_sighup = 0; exports_get(); } if (!handshake(&from, tmpsfd)) close(tmpsfd); } close(sfd); pidfile_remove(pfh); exit(EXIT_SUCCESS); } diff --git a/sbin/ggate/shared/ggate.h b/sbin/ggate/shared/ggate.h index e2e1a57d817c..d399b247cd75 100644 --- a/sbin/ggate/shared/ggate.h +++ b/sbin/ggate/shared/ggate.h @@ -1,198 +1,199 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2004 Pawel Jakub Dawidek * 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$ */ #ifndef _GGATE_H_ #define _GGATE_H_ #include #include #define G_GATE_PORT 3080 #define G_GATE_RCVBUF 131072 #define G_GATE_SNDBUF 131072 #define G_GATE_QUEUE_SIZE 1024 #define G_GATE_TIMEOUT 0 #define GGATE_MAGIC "GEOM_GATE " #define GGATE_VERSION 0 #define GGATE_FLAG_RDONLY 0x0001 #define GGATE_FLAG_WRONLY 0x0002 /* * If neither the GGATE_FLAG_SEND nor the GGATE_FLAG_RECV flag is * set - this is initial connection. * If GGATE_FLAG_SEND flag is set - this is socket to send data. * If GGATE_FLAG_RECV flag is set - this is socket to receive data. */ #define GGATE_FLAG_SEND 0x0004 #define GGATE_FLAG_RECV 0x0008 #define GGATE_CMD_READ 0 #define GGATE_CMD_WRITE 1 +#define GGATE_CMD_FLUSH 3 extern int g_gate_devfd; extern int g_gate_verbose; extern int nagle; extern unsigned rcvbuf, sndbuf; struct g_gate_version { char gv_magic[16]; uint16_t gv_version; uint16_t gv_error; } __packed; /* Client's initial packet. */ struct g_gate_cinit { char gc_path[PATH_MAX + 1]; uint64_t gc_flags; uint16_t gc_nconn; uint32_t gc_token; } __packed; /* Server's initial packet. */ struct g_gate_sinit { uint8_t gs_flags; uint64_t gs_mediasize; uint32_t gs_sectorsize; uint16_t gs_error; } __packed; /* Control struct. */ struct g_gate_hdr { uint8_t gh_cmd; /* command */ uint64_t gh_offset; /* device offset */ uint32_t gh_length; /* size of block */ uint64_t gh_seq; /* request number */ uint16_t gh_error; /* error value (0 if ok) */ } __packed; void g_gate_vlog(int priority, const char *message, va_list ap); void g_gate_log(int priority, const char *message, ...); void g_gate_xvlog(const char *message, va_list ap) __dead2; void g_gate_xlog(const char *message, ...) __dead2; off_t g_gate_mediasize(int fd); unsigned g_gate_sectorsize(int fd); void g_gate_open_device(void); void g_gate_close_device(void); void g_gate_ioctl(unsigned long req, void *data); void g_gate_destroy(int unit, int force); void g_gate_load_module(void); ssize_t g_gate_recv(int s, void *buf, size_t len, int flags); ssize_t g_gate_send(int s, const void *buf, size_t len, int flags); void g_gate_socket_settings(int sfd); #ifdef LIBGEOM void g_gate_list(int unit, int verbose); #endif in_addr_t g_gate_str2ip(const char *str); /* * g_gate_swap2h_* - functions swap bytes to host byte order (from big endian). * g_gate_swap2n_* - functions swap bytes to network byte order (actually * to big endian byte order). */ static __inline void g_gate_swap2h_version(struct g_gate_version *ver) { ver->gv_version = be16toh(ver->gv_version); ver->gv_error = be16toh(ver->gv_error); } static __inline void g_gate_swap2n_version(struct g_gate_version *ver) { ver->gv_version = htobe16(ver->gv_version); ver->gv_error = htobe16(ver->gv_error); } static __inline void g_gate_swap2h_cinit(struct g_gate_cinit *cinit) { cinit->gc_flags = be64toh(cinit->gc_flags); cinit->gc_nconn = be16toh(cinit->gc_nconn); cinit->gc_token = be32toh(cinit->gc_token); } static __inline void g_gate_swap2n_cinit(struct g_gate_cinit *cinit) { cinit->gc_flags = htobe64(cinit->gc_flags); cinit->gc_nconn = htobe16(cinit->gc_nconn); cinit->gc_token = htobe32(cinit->gc_token); } static __inline void g_gate_swap2h_sinit(struct g_gate_sinit *sinit) { /* Swap only used fields. */ sinit->gs_mediasize = be64toh(sinit->gs_mediasize); sinit->gs_sectorsize = be32toh(sinit->gs_sectorsize); sinit->gs_error = be16toh(sinit->gs_error); } static __inline void g_gate_swap2n_sinit(struct g_gate_sinit *sinit) { /* Swap only used fields. */ sinit->gs_mediasize = htobe64(sinit->gs_mediasize); sinit->gs_sectorsize = htobe32(sinit->gs_sectorsize); sinit->gs_error = htobe16(sinit->gs_error); } static __inline void g_gate_swap2h_hdr(struct g_gate_hdr *hdr) { /* Swap only used fields. */ hdr->gh_offset = be64toh(hdr->gh_offset); hdr->gh_length = be32toh(hdr->gh_length); hdr->gh_seq = be64toh(hdr->gh_seq); hdr->gh_error = be16toh(hdr->gh_error); } static __inline void g_gate_swap2n_hdr(struct g_gate_hdr *hdr) { /* Swap only used fields. */ hdr->gh_offset = htobe64(hdr->gh_offset); hdr->gh_length = htobe32(hdr->gh_length); hdr->gh_seq = htobe64(hdr->gh_seq); hdr->gh_error = htobe16(hdr->gh_error); } #endif /* _GGATE_H_ */ diff --git a/sys/geom/gate/g_gate.c b/sys/geom/gate/g_gate.c index c8f6f4a1b3b7..14ec0cc2e9d2 100644 --- a/sys/geom/gate/g_gate.c +++ b/sys/geom/gate/g_gate.c @@ -1,1002 +1,1018 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2004-2006 Pawel Jakub Dawidek * Copyright (c) 2009-2010 The FreeBSD Foundation * All rights reserved. * * Portions of this software were developed by Pawel Jakub Dawidek * under sponsorship from the FreeBSD Foundation. * * 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. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include FEATURE(geom_gate, "GEOM Gate module"); static MALLOC_DEFINE(M_GATE, "gg_data", "GEOM Gate Data"); SYSCTL_DECL(_kern_geom); static SYSCTL_NODE(_kern_geom, OID_AUTO, gate, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, "GEOM_GATE configuration"); static int g_gate_debug = 0; SYSCTL_INT(_kern_geom_gate, OID_AUTO, debug, CTLFLAG_RWTUN, &g_gate_debug, 0, "Debug level"); static u_int g_gate_maxunits = 256; SYSCTL_UINT(_kern_geom_gate, OID_AUTO, maxunits, CTLFLAG_RDTUN, &g_gate_maxunits, 0, "Maximum number of ggate devices"); struct g_class g_gate_class = { .name = G_GATE_CLASS_NAME, .version = G_VERSION, }; static struct cdev *status_dev; static d_ioctl_t g_gate_ioctl; static struct cdevsw g_gate_cdevsw = { .d_version = D_VERSION, .d_ioctl = g_gate_ioctl, .d_name = G_GATE_CTL_NAME }; static struct g_gate_softc **g_gate_units; static u_int g_gate_nunits; static struct mtx g_gate_units_lock; static void g_gate_detach(void *arg, int flags __unused) { struct g_consumer *cp = arg; g_topology_assert(); G_GATE_DEBUG(1, "Destroying read consumer on provider %s orphan.", cp->provider->name); (void)g_access(cp, -1, 0, 0); g_detach(cp); g_destroy_consumer(cp); } static int g_gate_destroy(struct g_gate_softc *sc, boolean_t force) { struct bio_queue_head queue; struct g_provider *pp; struct g_consumer *cp; struct g_geom *gp; struct bio *bp; g_topology_assert(); mtx_assert(&g_gate_units_lock, MA_OWNED); pp = sc->sc_provider; if (!force && (pp->acr != 0 || pp->acw != 0 || pp->ace != 0)) { mtx_unlock(&g_gate_units_lock); return (EBUSY); } mtx_unlock(&g_gate_units_lock); mtx_lock(&sc->sc_queue_mtx); if ((sc->sc_flags & G_GATE_FLAG_DESTROY) == 0) sc->sc_flags |= G_GATE_FLAG_DESTROY; wakeup(sc); mtx_unlock(&sc->sc_queue_mtx); gp = pp->geom; g_wither_provider(pp, ENXIO); callout_drain(&sc->sc_callout); bioq_init(&queue); mtx_lock(&sc->sc_queue_mtx); while ((bp = bioq_takefirst(&sc->sc_inqueue)) != NULL) { sc->sc_queue_count--; bioq_insert_tail(&queue, bp); } while ((bp = bioq_takefirst(&sc->sc_outqueue)) != NULL) { sc->sc_queue_count--; bioq_insert_tail(&queue, bp); } mtx_unlock(&sc->sc_queue_mtx); g_topology_unlock(); while ((bp = bioq_takefirst(&queue)) != NULL) { G_GATE_LOGREQ(1, bp, "Request canceled."); g_io_deliver(bp, ENXIO); } mtx_lock(&g_gate_units_lock); /* One reference is ours. */ sc->sc_ref--; while (sc->sc_ref > 0) msleep(&sc->sc_ref, &g_gate_units_lock, 0, "gg:destroy", 0); g_gate_units[sc->sc_unit] = NULL; KASSERT(g_gate_nunits > 0, ("negative g_gate_nunits?")); g_gate_nunits--; mtx_unlock(&g_gate_units_lock); mtx_destroy(&sc->sc_queue_mtx); mtx_destroy(&sc->sc_read_mtx); g_topology_lock(); if ((cp = sc->sc_readcons) != NULL) { sc->sc_readcons = NULL; (void)g_access(cp, -1, 0, 0); g_detach(cp); g_destroy_consumer(cp); } G_GATE_DEBUG(1, "Device %s destroyed.", gp->name); gp->softc = NULL; g_wither_geom(gp, ENXIO); sc->sc_provider = NULL; free(sc, M_GATE); return (0); } static int g_gate_access(struct g_provider *pp, int dr, int dw, int de) { struct g_gate_softc *sc; if (dr <= 0 && dw <= 0 && de <= 0) return (0); sc = pp->geom->softc; if (sc == NULL || (sc->sc_flags & G_GATE_FLAG_DESTROY) != 0) return (ENXIO); /* XXX: Hack to allow read-only mounts. */ #if 0 if ((sc->sc_flags & G_GATE_FLAG_READONLY) != 0 && dw > 0) return (EPERM); #endif if ((sc->sc_flags & G_GATE_FLAG_WRITEONLY) != 0 && dr > 0) return (EPERM); return (0); } static void g_gate_queue_io(struct bio *bp) { struct g_gate_softc *sc; sc = bp->bio_to->geom->softc; if (sc == NULL || (sc->sc_flags & G_GATE_FLAG_DESTROY) != 0) { g_io_deliver(bp, ENXIO); return; } mtx_lock(&sc->sc_queue_mtx); if (sc->sc_queue_size > 0 && sc->sc_queue_count > sc->sc_queue_size) { mtx_unlock(&sc->sc_queue_mtx); G_GATE_LOGREQ(1, bp, "Queue full, request canceled."); g_io_deliver(bp, ENOMEM); return; } bp->bio_driver1 = (void *)sc->sc_seq; sc->sc_seq++; sc->sc_queue_count++; bioq_insert_tail(&sc->sc_inqueue, bp); wakeup(sc); mtx_unlock(&sc->sc_queue_mtx); } static void g_gate_done(struct bio *cbp) { struct g_gate_softc *sc; struct bio *pbp; struct g_consumer *cp; cp = cbp->bio_from; pbp = cbp->bio_parent; if (cbp->bio_error == 0) { pbp->bio_completed = cbp->bio_completed; g_destroy_bio(cbp); pbp->bio_inbed++; g_io_deliver(pbp, 0); } else { /* If direct read failed, pass it through userland daemon. */ g_destroy_bio(cbp); pbp->bio_children--; g_gate_queue_io(pbp); } sc = cp->geom->softc; mtx_lock(&sc->sc_read_mtx); if (--cp->index == 0 && sc->sc_readcons != cp) g_post_event(g_gate_detach, cp, M_NOWAIT, NULL); mtx_unlock(&sc->sc_read_mtx); } static void g_gate_start(struct bio *pbp) { struct g_gate_softc *sc; struct g_consumer *cp; struct bio *cbp; sc = pbp->bio_to->geom->softc; if (sc == NULL || (sc->sc_flags & G_GATE_FLAG_DESTROY) != 0) { g_io_deliver(pbp, ENXIO); return; } G_GATE_LOGREQ(2, pbp, "Request received."); switch (pbp->bio_cmd) { case BIO_READ: if (sc->sc_readcons == NULL) break; cbp = g_clone_bio(pbp); if (cbp == NULL) { g_io_deliver(pbp, ENOMEM); return; } mtx_lock(&sc->sc_read_mtx); if ((cp = sc->sc_readcons) == NULL) { mtx_unlock(&sc->sc_read_mtx); g_destroy_bio(cbp); pbp->bio_children--; break; } cp->index++; cbp->bio_offset = pbp->bio_offset + sc->sc_readoffset; mtx_unlock(&sc->sc_read_mtx); cbp->bio_done = g_gate_done; g_io_request(cbp, cp); return; case BIO_DELETE: case BIO_WRITE: case BIO_FLUSH: case BIO_SPEEDUP: /* XXX: Hack to allow read-only mounts. */ if ((sc->sc_flags & G_GATE_FLAG_READONLY) != 0) { g_io_deliver(pbp, EPERM); return; } break; case BIO_GETATTR: default: G_GATE_LOGREQ(2, pbp, "Ignoring request."); g_io_deliver(pbp, EOPNOTSUPP); return; } g_gate_queue_io(pbp); } static struct g_gate_softc * g_gate_hold(int unit, const char *name) { struct g_gate_softc *sc = NULL; mtx_lock(&g_gate_units_lock); if (unit >= 0 && unit < g_gate_maxunits) sc = g_gate_units[unit]; else if (unit == G_GATE_NAME_GIVEN) { KASSERT(name != NULL, ("name is NULL")); for (unit = 0; unit < g_gate_maxunits; unit++) { if (g_gate_units[unit] == NULL) continue; if (strcmp(name, g_gate_units[unit]->sc_provider->name) != 0) { continue; } sc = g_gate_units[unit]; break; } } if (sc != NULL) sc->sc_ref++; mtx_unlock(&g_gate_units_lock); return (sc); } static void g_gate_release(struct g_gate_softc *sc) { g_topology_assert_not(); mtx_lock(&g_gate_units_lock); sc->sc_ref--; KASSERT(sc->sc_ref >= 0, ("Negative sc_ref for %s.", sc->sc_name)); if (sc->sc_ref == 0 && (sc->sc_flags & G_GATE_FLAG_DESTROY) != 0) wakeup(&sc->sc_ref); mtx_unlock(&g_gate_units_lock); } static int g_gate_getunit(int unit, int *errorp) { mtx_assert(&g_gate_units_lock, MA_OWNED); if (unit >= 0) { if (unit >= g_gate_maxunits) *errorp = EINVAL; else if (g_gate_units[unit] == NULL) return (unit); else *errorp = EEXIST; } else { for (unit = 0; unit < g_gate_maxunits; unit++) { if (g_gate_units[unit] == NULL) return (unit); } *errorp = ENFILE; } return (-1); } static void g_gate_guard(void *arg) { struct bio_queue_head queue; struct g_gate_softc *sc; struct bintime curtime; struct bio *bp, *bp2; sc = arg; binuptime(&curtime); g_gate_hold(sc->sc_unit, NULL); bioq_init(&queue); mtx_lock(&sc->sc_queue_mtx); TAILQ_FOREACH_SAFE(bp, &sc->sc_inqueue.queue, bio_queue, bp2) { if (curtime.sec - bp->bio_t0.sec < 5) continue; bioq_remove(&sc->sc_inqueue, bp); sc->sc_queue_count--; bioq_insert_tail(&queue, bp); } TAILQ_FOREACH_SAFE(bp, &sc->sc_outqueue.queue, bio_queue, bp2) { if (curtime.sec - bp->bio_t0.sec < 5) continue; bioq_remove(&sc->sc_outqueue, bp); sc->sc_queue_count--; bioq_insert_tail(&queue, bp); } mtx_unlock(&sc->sc_queue_mtx); while ((bp = bioq_takefirst(&queue)) != NULL) { G_GATE_LOGREQ(1, bp, "Request timeout."); g_io_deliver(bp, EIO); } if ((sc->sc_flags & G_GATE_FLAG_DESTROY) == 0) { callout_reset(&sc->sc_callout, sc->sc_timeout * hz, g_gate_guard, sc); } g_gate_release(sc); } static void g_gate_orphan(struct g_consumer *cp) { struct g_gate_softc *sc; struct g_geom *gp; int done; g_topology_assert(); gp = cp->geom; sc = gp->softc; mtx_lock(&sc->sc_read_mtx); if (sc->sc_readcons == cp) sc->sc_readcons = NULL; done = (cp->index == 0); mtx_unlock(&sc->sc_read_mtx); if (done) g_gate_detach(cp, 0); } static void g_gate_dumpconf(struct sbuf *sb, const char *indent, struct g_geom *gp, struct g_consumer *cp, struct g_provider *pp) { struct g_gate_softc *sc; sc = gp->softc; if (sc == NULL || pp != NULL || cp != NULL) return; sc = g_gate_hold(sc->sc_unit, NULL); if (sc == NULL) return; if ((sc->sc_flags & G_GATE_FLAG_READONLY) != 0) { sbuf_printf(sb, "%s%s\n", indent, "read-only"); } else if ((sc->sc_flags & G_GATE_FLAG_WRITEONLY) != 0) { sbuf_printf(sb, "%s%s\n", indent, "write-only"); } else { sbuf_printf(sb, "%s%s\n", indent, "read-write"); } if (sc->sc_readcons != NULL) { sbuf_printf(sb, "%s%jd\n", indent, (intmax_t)sc->sc_readoffset); sbuf_printf(sb, "%s%s\n", indent, sc->sc_readcons->provider->name); } sbuf_printf(sb, "%s%u\n", indent, sc->sc_timeout); sbuf_printf(sb, "%s%s\n", indent, sc->sc_info); sbuf_printf(sb, "%s%u\n", indent, sc->sc_queue_count); sbuf_printf(sb, "%s%u\n", indent, sc->sc_queue_size); sbuf_printf(sb, "%s%u\n", indent, sc->sc_ref); sbuf_printf(sb, "%s%d\n", indent, sc->sc_unit); g_topology_unlock(); g_gate_release(sc); g_topology_lock(); } static int g_gate_create(struct g_gate_ctl_create *ggio) { struct g_gate_softc *sc; struct g_geom *gp; struct g_provider *pp, *ropp; struct g_consumer *cp; - char name[NAME_MAX]; + char name[NAME_MAX + 1]; + char readprov[NAME_MAX + 1]; int error = 0, unit; if (ggio->gctl_mediasize <= 0) { G_GATE_DEBUG(1, "Invalid media size."); return (EINVAL); } if (ggio->gctl_sectorsize <= 0) { G_GATE_DEBUG(1, "Invalid sector size."); return (EINVAL); } if (!powerof2(ggio->gctl_sectorsize)) { G_GATE_DEBUG(1, "Invalid sector size."); return (EINVAL); } if ((ggio->gctl_mediasize % ggio->gctl_sectorsize) != 0) { G_GATE_DEBUG(1, "Invalid media size."); return (EINVAL); } if ((ggio->gctl_flags & G_GATE_FLAG_READONLY) != 0 && (ggio->gctl_flags & G_GATE_FLAG_WRITEONLY) != 0) { G_GATE_DEBUG(1, "Invalid flags."); return (EINVAL); } if (ggio->gctl_unit != G_GATE_UNIT_AUTO && ggio->gctl_unit != G_GATE_NAME_GIVEN && ggio->gctl_unit < 0) { G_GATE_DEBUG(1, "Invalid unit number."); return (EINVAL); } if (ggio->gctl_unit == G_GATE_NAME_GIVEN && ggio->gctl_name[0] == '\0') { G_GATE_DEBUG(1, "No device name."); return (EINVAL); } sc = malloc(sizeof(*sc), M_GATE, M_WAITOK | M_ZERO); sc->sc_flags = (ggio->gctl_flags & G_GATE_USERFLAGS); - strlcpy(sc->sc_info, ggio->gctl_info, sizeof(sc->sc_info)); + memset(sc->sc_info, 0, sizeof(sc->sc_info)); + strncpy(sc->sc_info, ggio->gctl_info, + MIN(sizeof(sc->sc_info) - 1, sizeof(ggio->gctl_info))); sc->sc_seq = 1; bioq_init(&sc->sc_inqueue); bioq_init(&sc->sc_outqueue); mtx_init(&sc->sc_queue_mtx, "gg:queue", NULL, MTX_DEF); mtx_init(&sc->sc_read_mtx, "gg:read", NULL, MTX_DEF); sc->sc_queue_count = 0; sc->sc_queue_size = ggio->gctl_maxcount; if (sc->sc_queue_size > G_GATE_MAX_QUEUE_SIZE) sc->sc_queue_size = G_GATE_MAX_QUEUE_SIZE; sc->sc_timeout = ggio->gctl_timeout; callout_init(&sc->sc_callout, 1); mtx_lock(&g_gate_units_lock); sc->sc_unit = g_gate_getunit(ggio->gctl_unit, &error); if (sc->sc_unit < 0) goto fail1; - if (ggio->gctl_unit == G_GATE_NAME_GIVEN) - snprintf(name, sizeof(name), "%s", ggio->gctl_name); - else { + if (ggio->gctl_unit == G_GATE_NAME_GIVEN) { + memset(name, 0, sizeof(name)); + strncpy(name, ggio->gctl_name, + MIN(sizeof(name) - 1, sizeof(ggio->gctl_name))); + } else { snprintf(name, sizeof(name), "%s%d", G_GATE_PROVIDER_NAME, sc->sc_unit); } /* Check for name collision. */ for (unit = 0; unit < g_gate_maxunits; unit++) { if (g_gate_units[unit] == NULL) continue; if (strcmp(name, g_gate_units[unit]->sc_name) != 0) continue; error = EEXIST; goto fail1; } + // local stack buffer 'name' assigned here temporarily only. + // the real provider name is assigned below. sc->sc_name = name; g_gate_units[sc->sc_unit] = sc; g_gate_nunits++; mtx_unlock(&g_gate_units_lock); g_topology_lock(); if (ggio->gctl_readprov[0] == '\0') { ropp = NULL; } else { - ropp = g_provider_by_name(ggio->gctl_readprov); + memset(readprov, 0, sizeof(readprov)); + strncpy(readprov, ggio->gctl_readprov, + MIN(sizeof(readprov) - 1, sizeof(ggio->gctl_readprov))); + ropp = g_provider_by_name(readprov); if (ropp == NULL) { - G_GATE_DEBUG(1, "Provider %s doesn't exist.", - ggio->gctl_readprov); + G_GATE_DEBUG(1, "Provider %s doesn't exist.", readprov); error = EINVAL; goto fail2; } if ((ggio->gctl_readoffset % ggio->gctl_sectorsize) != 0) { G_GATE_DEBUG(1, "Invalid read offset."); error = EINVAL; goto fail2; } if (ggio->gctl_mediasize + ggio->gctl_readoffset > ropp->mediasize) { G_GATE_DEBUG(1, "Invalid read offset or media size."); error = EINVAL; goto fail2; } } gp = g_new_geomf(&g_gate_class, "%s", name); gp->start = g_gate_start; gp->access = g_gate_access; gp->orphan = g_gate_orphan; gp->dumpconf = g_gate_dumpconf; gp->softc = sc; if (ropp != NULL) { cp = g_new_consumer(gp); cp->flags |= G_CF_DIRECT_SEND | G_CF_DIRECT_RECEIVE; error = g_attach(cp, ropp); if (error != 0) { G_GATE_DEBUG(1, "Unable to attach to %s.", ropp->name); goto fail3; } error = g_access(cp, 1, 0, 0); if (error != 0) { G_GATE_DEBUG(1, "Unable to access %s.", ropp->name); g_detach(cp); goto fail3; } sc->sc_readcons = cp; sc->sc_readoffset = ggio->gctl_readoffset; } ggio->gctl_unit = sc->sc_unit; pp = g_new_providerf(gp, "%s", name); pp->flags |= G_PF_DIRECT_SEND | G_PF_DIRECT_RECEIVE; pp->mediasize = ggio->gctl_mediasize; pp->sectorsize = ggio->gctl_sectorsize; sc->sc_provider = pp; g_error_provider(pp, 0); g_topology_unlock(); mtx_lock(&g_gate_units_lock); sc->sc_name = sc->sc_provider->name; mtx_unlock(&g_gate_units_lock); G_GATE_DEBUG(1, "Device %s created.", gp->name); if (sc->sc_timeout > 0) { callout_reset(&sc->sc_callout, sc->sc_timeout * hz, g_gate_guard, sc); } return (0); fail3: g_destroy_consumer(cp); g_destroy_geom(gp); fail2: g_topology_unlock(); mtx_lock(&g_gate_units_lock); g_gate_units[sc->sc_unit] = NULL; KASSERT(g_gate_nunits > 0, ("negative g_gate_nunits?")); g_gate_nunits--; fail1: mtx_unlock(&g_gate_units_lock); mtx_destroy(&sc->sc_queue_mtx); mtx_destroy(&sc->sc_read_mtx); free(sc, M_GATE); return (error); } static int g_gate_modify(struct g_gate_softc *sc, struct g_gate_ctl_modify *ggio) { + char readprov[NAME_MAX + 1]; struct g_provider *pp; struct g_consumer *cp; int done, error; if ((ggio->gctl_modify & GG_MODIFY_MEDIASIZE) != 0) { if (ggio->gctl_mediasize <= 0) { G_GATE_DEBUG(1, "Invalid media size."); return (EINVAL); } pp = sc->sc_provider; if ((ggio->gctl_mediasize % pp->sectorsize) != 0) { G_GATE_DEBUG(1, "Invalid media size."); return (EINVAL); } g_resize_provider(pp, ggio->gctl_mediasize); return (0); } - if ((ggio->gctl_modify & GG_MODIFY_INFO) != 0) - (void)strlcpy(sc->sc_info, ggio->gctl_info, sizeof(sc->sc_info)); - + if ((ggio->gctl_modify & GG_MODIFY_INFO) != 0) { + memset(sc->sc_info, 0, sizeof(sc->sc_info)); + strncpy(sc->sc_info, ggio->gctl_info, + MIN(sizeof(sc->sc_info) - 1, sizeof(ggio->gctl_info))); + } cp = NULL; if ((ggio->gctl_modify & GG_MODIFY_READPROV) != 0) { g_topology_lock(); mtx_lock(&sc->sc_read_mtx); if ((cp = sc->sc_readcons) != NULL) { sc->sc_readcons = NULL; done = (cp->index == 0); mtx_unlock(&sc->sc_read_mtx); if (done) g_gate_detach(cp, 0); } else mtx_unlock(&sc->sc_read_mtx); if (ggio->gctl_readprov[0] != '\0') { - pp = g_provider_by_name(ggio->gctl_readprov); + memset(readprov, 0, sizeof(readprov)); + strncpy(readprov, ggio->gctl_readprov, + MIN(sizeof(readprov) - 1, + sizeof(ggio->gctl_readprov))); + pp = g_provider_by_name(readprov); if (pp == NULL) { g_topology_unlock(); G_GATE_DEBUG(1, "Provider %s doesn't exist.", - ggio->gctl_readprov); + readprov); return (EINVAL); } cp = g_new_consumer(sc->sc_provider->geom); cp->flags |= G_CF_DIRECT_SEND | G_CF_DIRECT_RECEIVE; error = g_attach(cp, pp); if (error != 0) { G_GATE_DEBUG(1, "Unable to attach to %s.", pp->name); } else { error = g_access(cp, 1, 0, 0); if (error != 0) { G_GATE_DEBUG(1, "Unable to access %s.", pp->name); g_detach(cp); } } if (error != 0) { g_destroy_consumer(cp); g_topology_unlock(); return (error); } } } else { cp = sc->sc_readcons; } if ((ggio->gctl_modify & GG_MODIFY_READOFFSET) != 0) { if (cp == NULL) { G_GATE_DEBUG(1, "No read provider."); return (EINVAL); } pp = sc->sc_provider; if ((ggio->gctl_readoffset % pp->sectorsize) != 0) { G_GATE_DEBUG(1, "Invalid read offset."); return (EINVAL); } if (pp->mediasize + ggio->gctl_readoffset > cp->provider->mediasize) { G_GATE_DEBUG(1, "Invalid read offset or media size."); return (EINVAL); } sc->sc_readoffset = ggio->gctl_readoffset; } if ((ggio->gctl_modify & GG_MODIFY_READPROV) != 0) { sc->sc_readcons = cp; g_topology_unlock(); } return (0); } #define G_GATE_CHECK_VERSION(ggio) do { \ if ((ggio)->gctl_version != G_GATE_VERSION) { \ printf("Version mismatch %d != %d.\n", \ ggio->gctl_version, G_GATE_VERSION); \ return (EINVAL); \ } \ } while (0) static int g_gate_ioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flags, struct thread *td) { struct g_gate_softc *sc; struct bio *bp; int error = 0; G_GATE_DEBUG(4, "ioctl(%s, %lx, %p, %x, %p)", devtoname(dev), cmd, addr, flags, td); switch (cmd) { case G_GATE_CMD_CREATE: { struct g_gate_ctl_create *ggio = (void *)addr; G_GATE_CHECK_VERSION(ggio); error = g_gate_create(ggio); /* * Reset TDP_GEOM flag. * There are pending events for sure, because we just created * new provider and other classes want to taste it, but we * cannot answer on I/O requests until we're here. */ td->td_pflags &= ~TDP_GEOM; return (error); } case G_GATE_CMD_MODIFY: { struct g_gate_ctl_modify *ggio = (void *)addr; G_GATE_CHECK_VERSION(ggio); sc = g_gate_hold(ggio->gctl_unit, NULL); if (sc == NULL) return (ENXIO); error = g_gate_modify(sc, ggio); g_gate_release(sc); return (error); } case G_GATE_CMD_DESTROY: { struct g_gate_ctl_destroy *ggio = (void *)addr; G_GATE_CHECK_VERSION(ggio); sc = g_gate_hold(ggio->gctl_unit, ggio->gctl_name); if (sc == NULL) return (ENXIO); g_topology_lock(); mtx_lock(&g_gate_units_lock); error = g_gate_destroy(sc, ggio->gctl_force); g_topology_unlock(); if (error != 0) g_gate_release(sc); return (error); } case G_GATE_CMD_CANCEL: { struct g_gate_ctl_cancel *ggio = (void *)addr; struct bio *tbp, *lbp; G_GATE_CHECK_VERSION(ggio); sc = g_gate_hold(ggio->gctl_unit, ggio->gctl_name); if (sc == NULL) return (ENXIO); lbp = NULL; mtx_lock(&sc->sc_queue_mtx); TAILQ_FOREACH_SAFE(bp, &sc->sc_outqueue.queue, bio_queue, tbp) { if (ggio->gctl_seq == 0 || ggio->gctl_seq == (uintptr_t)bp->bio_driver1) { G_GATE_LOGREQ(1, bp, "Request canceled."); bioq_remove(&sc->sc_outqueue, bp); /* * Be sure to put requests back onto incoming * queue in the proper order. */ if (lbp == NULL) bioq_insert_head(&sc->sc_inqueue, bp); else { TAILQ_INSERT_AFTER(&sc->sc_inqueue.queue, lbp, bp, bio_queue); } lbp = bp; /* * If only one request was canceled, leave now. */ if (ggio->gctl_seq != 0) break; } } if (ggio->gctl_unit == G_GATE_NAME_GIVEN) ggio->gctl_unit = sc->sc_unit; mtx_unlock(&sc->sc_queue_mtx); g_gate_release(sc); return (error); } case G_GATE_CMD_START: { struct g_gate_ctl_io *ggio = (void *)addr; G_GATE_CHECK_VERSION(ggio); sc = g_gate_hold(ggio->gctl_unit, NULL); if (sc == NULL) return (ENXIO); error = 0; for (;;) { mtx_lock(&sc->sc_queue_mtx); bp = bioq_first(&sc->sc_inqueue); if (bp != NULL) break; if ((sc->sc_flags & G_GATE_FLAG_DESTROY) != 0) { ggio->gctl_error = ECANCELED; mtx_unlock(&sc->sc_queue_mtx); goto start_end; } if (msleep(sc, &sc->sc_queue_mtx, PPAUSE | PDROP | PCATCH, "ggwait", 0) != 0) { ggio->gctl_error = ECANCELED; goto start_end; } } ggio->gctl_cmd = bp->bio_cmd; if (bp->bio_cmd == BIO_WRITE && bp->bio_length > ggio->gctl_length) { mtx_unlock(&sc->sc_queue_mtx); ggio->gctl_length = bp->bio_length; ggio->gctl_error = ENOMEM; goto start_end; } bioq_remove(&sc->sc_inqueue, bp); bioq_insert_tail(&sc->sc_outqueue, bp); mtx_unlock(&sc->sc_queue_mtx); ggio->gctl_seq = (uintptr_t)bp->bio_driver1; ggio->gctl_offset = bp->bio_offset; ggio->gctl_length = bp->bio_length; switch (bp->bio_cmd) { case BIO_READ: case BIO_DELETE: case BIO_FLUSH: case BIO_SPEEDUP: break; case BIO_WRITE: error = copyout(bp->bio_data, ggio->gctl_data, bp->bio_length); if (error != 0) { mtx_lock(&sc->sc_queue_mtx); bioq_remove(&sc->sc_outqueue, bp); bioq_insert_head(&sc->sc_inqueue, bp); mtx_unlock(&sc->sc_queue_mtx); goto start_end; } break; } start_end: g_gate_release(sc); return (error); } case G_GATE_CMD_DONE: { struct g_gate_ctl_io *ggio = (void *)addr; G_GATE_CHECK_VERSION(ggio); sc = g_gate_hold(ggio->gctl_unit, NULL); if (sc == NULL) return (ENOENT); error = 0; mtx_lock(&sc->sc_queue_mtx); TAILQ_FOREACH(bp, &sc->sc_outqueue.queue, bio_queue) { if (ggio->gctl_seq == (uintptr_t)bp->bio_driver1) break; } if (bp != NULL) { bioq_remove(&sc->sc_outqueue, bp); sc->sc_queue_count--; } mtx_unlock(&sc->sc_queue_mtx); if (bp == NULL) { /* * Request was probably canceled. */ goto done_end; } if (ggio->gctl_error == EAGAIN) { bp->bio_error = 0; G_GATE_LOGREQ(1, bp, "Request desisted."); mtx_lock(&sc->sc_queue_mtx); sc->sc_queue_count++; bioq_insert_head(&sc->sc_inqueue, bp); wakeup(sc); mtx_unlock(&sc->sc_queue_mtx); } else { bp->bio_error = ggio->gctl_error; if (bp->bio_error == 0) { bp->bio_completed = bp->bio_length; switch (bp->bio_cmd) { case BIO_READ: error = copyin(ggio->gctl_data, bp->bio_data, bp->bio_length); if (error != 0) bp->bio_error = error; break; case BIO_DELETE: case BIO_WRITE: case BIO_FLUSH: case BIO_SPEEDUP: break; } } G_GATE_LOGREQ(2, bp, "Request done."); g_io_deliver(bp, bp->bio_error); } done_end: g_gate_release(sc); return (error); } } return (ENOIOCTL); } static void g_gate_device(void) { status_dev = make_dev(&g_gate_cdevsw, 0x0, UID_ROOT, GID_WHEEL, 0600, G_GATE_CTL_NAME); } static int g_gate_modevent(module_t mod, int type, void *data) { int error = 0; switch (type) { case MOD_LOAD: mtx_init(&g_gate_units_lock, "gg_units_lock", NULL, MTX_DEF); g_gate_units = malloc(g_gate_maxunits * sizeof(g_gate_units[0]), M_GATE, M_WAITOK | M_ZERO); g_gate_nunits = 0; g_gate_device(); break; case MOD_UNLOAD: mtx_lock(&g_gate_units_lock); if (g_gate_nunits > 0) { mtx_unlock(&g_gate_units_lock); error = EBUSY; break; } mtx_unlock(&g_gate_units_lock); mtx_destroy(&g_gate_units_lock); if (status_dev != NULL) destroy_dev(status_dev); free(g_gate_units, M_GATE); break; default: return (EOPNOTSUPP); break; } return (error); } static moduledata_t g_gate_module = { G_GATE_MOD_NAME, g_gate_modevent, NULL }; DECLARE_MODULE(geom_gate, g_gate_module, SI_SUB_DRIVERS, SI_ORDER_MIDDLE); DECLARE_GEOM_CLASS(g_gate_class, g_gate); MODULE_VERSION(geom_gate, 0);