gboolean accept_fn (GIOChannel *source, GIOCondition condition, gpointer data) { fsp_backend *backend = (fsp_backend *) data; int conn = accept(g_io_channel_unix_get_fd(source), NULL, NULL); if (conn == -1) { if (errno != EINTR) kb_error(LOG_ERR, "accept: %s", strerror(errno)); return TRUE; /* try again */ } pid_t pid = fork(); if (pid == -1) { kb_error(LOG_ERR, "fork: %s", strerror(errno)); close(conn); } else if (pid > 0) { /* parent process */ g_child_watch_add(pid, child_exited, data); close(conn); } else { /* child process */ fs_backend *be = backend->open(global_kb_name, 0); if (be) { fs_backend_set_min_free(be, global_disk_limit); child(conn, backend, be); backend->close(be); } close(conn); exit(0); } return TRUE; }
static void daemonize (void) { /* fork once, we don't want to be process leader */ switch(fork()) { case 0: break; case -1: kb_error(LOG_ERR, "fork() error starting daemon: %s", strerror(errno)); exit(1); default: _exit(0); } /* new session / process group */ if (setsid() == -1) { kb_error(LOG_ERR, "setsid() failed starting daemon: %s", strerror(errno)); exit(1); } /* fork again, separating ourselves from our parent permanently */ switch(fork()) { case 0: break; case -1: kb_error(LOG_ERR, "fork() error starting daemon: %s", strerror(errno)); exit(1); default: _exit(0); } /* close stdin, stdout, stderr */ close(0); close(1); close(2); /* Avahi sucks, we need an open fd or it gets confused -sigh */ if (open("/dev/null", 0) == -1) { kb_error(LOG_ERR, "couldn't open /dev/null: %s", strerror(errno)); } /* use up some more fds as a precaution against printf() getting written to the wire */ open("/dev/null", 0); open("/dev/null", 0); /* move somewhere safe and known */ if (chdir("/")) { kb_error(LOG_ERR, "chdir failed: %s", strerror(errno)); } }
static void do_sigmisc(int sig) { if (fatal_error_in_progress) raise (sig); fatal_error_in_progress = 1; signal (sig, SIG_DFL); kb_error(LOG_INFO, "signal %s received", strsignal(sig)); raise (sig); }
static void child_exited(GPid pid, gint status, gpointer data) { if (WIFEXITED(status)) { if (WEXITSTATUS(status)) { int code = WEXITSTATUS(status); kb_error((code == 0) ? LOG_INFO : LOG_CRIT, "child %d exited with return code %d", pid, code); } } else if (WIFSIGNALED(status)) { int code = WTERMSIG(status); kb_error((code == SIGTERM || code == SIGKILL) ? LOG_INFO : LOG_CRIT, "child %d terminated by signal %d", pid, code); } else if (WIFSTOPPED(status)) { kb_error(LOG_ERR, "child %d stopped by signal %d", pid, WSTOPSIG(status)); } else { kb_error(LOG_CRIT, "child %d was terminated for unknown reasons", pid); } }
static unsigned char * handle_or_fail(const char *name, fsp_backend_fn handle_fn, fs_backend *be, fs_segment segment, unsigned int length, unsigned char *content) { if (handle_fn) { return handle_fn(be, segment, length, content); } else { kb_error(LOG_ERR, "no implementation for %s", name); return fsp_error_new(segment, "not implemented"); } }
static void child (int conn, fsp_backend *backend, fs_backend *be) { int auth = 0; while (1) { fs_segment segment; unsigned int length; unsigned char *msg = message_recv(conn, &segment, &length); unsigned char *reply = NULL; unsigned char *content = msg + FS_HEADER; if (!msg) { /* if the connection is in fact closed, this won't matter, but otherwise this error might help */ reply = fsp_error_new(segment, "protocol mismatch"); unsigned int* const l = (unsigned int *) (reply + 4); unsigned int length = *l; if (write(conn, reply, FS_HEADER + length) != (FS_HEADER+length)) { fs_error(LOG_ERR, "write failed: %s", strerror(errno)); } break; } if (auth) { switch (msg[3]) { case FS_NO_OP: reply = fsp_handle_no_op(segment, length, content); break; case FS_RESOLVE: reply = handle(backend->resolve, be, segment, length, content); break; case FS_BIND: reply = handle(backend->bind, be, segment, length, content); break; case FS_PRICE_BIND: reply = handle(backend->price, be, segment, length, content); break; case FS_DELETE_MODEL: reply = handle(backend->delete_models, be, segment, length, content); break; case FS_INSERT_RESOURCE: reply = handle(backend->insert_resource, be, segment, length, content); break; case FS_SEGMENTS: reply = handle(backend->segments, be, segment, length, content); break; case FS_COMMIT_RESOURCE: reply = handle(backend->commit_resource, be, segment, length, content); break; case FS_START_IMPORT: reply = handle(backend->start_import, be, segment, length, content); break; case FS_STOP_IMPORT: reply = handle(backend->stop_import, be, segment, length, content); break; case FS_GET_SIZE: reply = handle(backend->get_data_size, be, segment, length, content); break; case FS_GET_IMPORT_TIMES: reply = handle(backend->get_import_times, be, segment, length, content); break; case FS_INSERT_QUAD: reply = handle(backend->insert_quad, be, segment, length, content); break; case FS_COMMIT_QUAD: reply = handle(backend->commit_quad, be, segment, length, content); break; case FS_GET_QUERY_TIMES: reply = handle(backend->get_query_times, be, segment, length, content); break; case FS_BIND_LIMIT: reply = handle(backend->bind_limit, be, segment, length, content); break; case FS_BNODE_ALLOC: reply = handle(backend->bnode_alloc, be, segment, length, content); break; case FS_RESOLVE_ATTR: reply = handle(backend->resolve_attr, be, segment, length, content); break; case FS_DELETE_MODELS: reply = handle(backend->delete_models, be, segment, length, content); break; case FS_NEW_MODELS: reply = handle(backend->new_models, be, segment, length, content); break; case FS_BIND_FIRST: reply = handle(backend->bind_first, be, segment, length, content); break; case FS_BIND_NEXT: reply = handle(backend->bind_next, be, segment, length, content); break; case FS_BIND_DONE: reply = handle(backend->bind_done, be, segment, length, content); break; case FS_TRANSACTION: reply = handle(backend->transaction, be, segment, length, content); break; case FS_NODE_SEGMENTS: reply = handle(backend->node_segments, be, segment, length, content); break; case FS_REVERSE_BIND: reply = handle(backend->reverse_bind, be, segment, length, content); break; case FS_LOCK: reply = handle(backend->lock, be, segment, length, content); break; case FS_UNLOCK: reply = handle(backend->unlock, be, segment, length, content); break; case FS_GET_SIZE_REVERSE: reply = handle(backend->get_size_reverse, be, segment, length, content); break; case FS_GET_QUAD_FREQ: reply = handle(backend->get_quad_freq, be, segment, length, content); break; case FS_CHOOSE_SEGMENT: reply = handle(backend->choose_segment, be, segment, length, content); break; case FS_DELETE_QUADS: reply = handle(backend->delete_quads, be, segment, length, content); break; case FS_GET_UUID: reply = handle(backend->get_uuid, be, segment, length, content); break; default: kb_error(LOG_WARNING, "unexpected message type (%d)", msg[3]); reply = fsp_error_new(segment, "unexpected message type"); break; } } else if (msg[3] == FS_AUTH) { if (backend->auth) { reply = backend->auth(be, segment, length, content); } else { reply = message_new(FS_DONE_OK, segment, 0); } if (reply[3] == FS_DONE_OK) auth = 1; } else { reply = fsp_error_new(segment, "authenticate before continuing"); } if (reply) { unsigned int* const l = (unsigned int *) (reply + 4); unsigned int length = *l; if (write(conn, reply, FS_HEADER + length) <= 0) { kb_error(LOG_WARNING, "write reply failed"); } free(reply); } free(msg); } }
void fsp_serve (const char *kb_name, fsp_backend *backend, int daemon, float disk_limit) { struct addrinfo hints, *info0, *info; uint16_t port = FS_DEFAULT_PORT; char cport[6]; int sock[MAXSOCK]; int nsock = 0; int err, on=1, off=0, i; /* we need access to these elsewhere */ global_kb_name = (char *)kb_name; global_disk_limit = disk_limit; if (!backend->open) { /* no open function defined, we will eventually fail anyway, so give up early */ return; } fs_backend *original = NULL; original = backend->open(kb_name, FS_BACKEND_NO_OPEN); if (!original) return; /* don't set any PF_INET or INET6 hints here */ default_hints(&hints); hints.ai_flags = AI_PASSIVE; snprintf(cport, sizeof(cport), "%u", port); if ((err = getaddrinfo(NULL, cport, &hints, &info0))) { kb_error(LOG_ERR, "getaddrinfo failed: %s", gai_strerror(err)); return; } /* keep track of the first addrinfo so we can free the whole chain later */ info = info0; /* iterate through the list of possible addresses. on Linux we only get one: the 'dual stack' or ipv4 address. on some BSDs we will see two or more; often separate INET6 and INET addrinfos. we want to listen on all of them. */ do { sock[nsock] = socket(info->ai_family, info->ai_socktype, info->ai_protocol); if (sock[nsock] < 0) continue; #if defined(IPV6_V6ONLY) if (info->ai_family == AF_INET6) /* don't check the return value -- some platforms (cough, OpenBSD, cough) have IPV6_V6ONLY defined, but refuse to let it be turned off */ setsockopt(sock[nsock], IPPROTO_IPV6, IPV6_V6ONLY, &off, sizeof(off)); #endif if (setsockopt(sock[nsock], SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) kb_error(LOG_ERR, "setsockopt(SO_REUSEADDR) failed, continuing"); if (bind(sock[nsock], info->ai_addr, info->ai_addrlen) < 0) { err = errno; close(sock[nsock]); continue; } if (listen(sock[nsock], 64) < 0) { err = errno; close(sock[nsock]); continue; } ++nsock; } while ((info = info->ai_next) && nsock < MAXSOCK); freeaddrinfo(info0); if (nsock == 0) { kb_error(LOG_ERR, "fsp_serve failed to get a valid listening socket: %s", strerror(err)); return; } /* preload toplevel indexes */ fs_backend *be = backend->open(kb_name, FS_BACKEND_PRELOAD); if (!be) { kb_error(LOG_CRIT, "failed to open backend"); return; } GMainLoop *loop = g_main_loop_new(NULL, FALSE); fsp_mdns_setup_backend(port, kb_name, backend->segment_count(be)); backend->close(be); if (daemon) { daemonize(); } /* set up pid/port lockfile */ err = init_runtime_info(kb_name, cport); if (err < 0) { /* error already logged so just return */ return; } signal_actions(); fs_error(LOG_INFO, "4store backend %s for kb %s on port %s (%d fds)", FS_BACKEND_VER, kb_name, cport, nsock); for (i = 0; i < nsock; ++i) { GIOChannel *listener = g_io_channel_unix_new(sock[i]); g_io_add_watch(listener, G_IO_IN, accept_fn, backend); } g_main_loop_run(loop); return; }
/* Store runtime information (pid+port) in locked file */ static int init_runtime_info(const char *kb_name, const char *cport) { char *path; int len, fd, rv; struct flock ri_lock; /* alloc mem for string path to runtime.info */ len = (strlen(FS_RI_FILE)-2) + strlen(kb_name) + 1; path = (char *)malloc(len * sizeof(char)); if (path == NULL) { kb_error(LOG_CRIT, "failed to malloc %d bytes", len); return -1; } /* generate full path to runtime.info */ rv = sprintf(path, FS_RI_FILE, kb_name); if (rv < 0) { kb_error(LOG_ERR, "sprintf failed to write %d bytes", len); free(path); return -1; } /* Open runtime.info with mode 0644. * Use global so that file is locked for the lifetime of this process. */ umask(022); global_ri_file = fopen(path, "w"); if (global_ri_file == NULL) { kb_error(LOG_ERR, "failed to open '%s' for writing: %s", path, strerror(errno)); free(path); return -1; } /* Get integer file descriptor for use by fcntl */ fd = fileno(global_ri_file); if (fd == -1) { kb_error(LOG_ERR, "failed to get file descriptor: %s", strerror(errno)); free(path); return -1; } /* We want to get a write lock on the entire file */ ri_lock.l_type = F_WRLCK; /* write lock */ ri_lock.l_whence = SEEK_SET; /* l_start begins at start of file */ ri_lock.l_start = 0; /* offset from whence */ ri_lock.l_len = 0; /* until EOF */ /* Check whether file is currently locked. * This *should* be impossible, locks on metadata.nt mean that we should * never get here if a kb backend is running, but can't hurt to check. */ rv = fcntl(fd, F_GETLK, &ri_lock); if (rv == -1) { kb_error(LOG_ERR, "failed to get lock information on '%s'", path); fclose(global_ri_file); free(path); return -1; } if (ri_lock.l_type == F_UNLCK) { /* file is unlocked, should always be the case */ ri_lock.l_type = F_WRLCK; ri_lock.l_whence = SEEK_SET; ri_lock.l_start = 0; ri_lock.l_len = 0; /* get non-blocking write lock */ rv = fcntl(fd, F_SETLK, &ri_lock); if (rv == -1) { kb_error(LOG_ERR, "failed to get lock on '%s'", path); fclose(global_ri_file); free(path); return -1; } } else { /* should never get here */ kb_error(LOG_ERR, "file '%s' already locked with %d by pid %d", path, ri_lock.l_type, ri_lock.l_pid); fclose(global_ri_file); free(path); return -1; } /* write port/pid to file: "%s %s",pid,port */ rv = fprintf(global_ri_file, "%d %s", getpid(), cport); if (rv < 0) { kb_error(LOG_ERR, "failed to write to file '%s'", path); ri_lock.l_type = F_UNLCK; ri_lock.l_whence = SEEK_SET; ri_lock.l_start = 0; ri_lock.l_len = 0; fcntl(fd, F_SETLK, &ri_lock); /* clear lock */ fclose(global_ri_file); free(path); return -1; } rv = fflush(global_ri_file); if (rv == EOF) { kb_error(LOG_ERR, "failed to flush file '%s'", path); ri_lock.l_type = F_UNLCK; ri_lock.l_whence = SEEK_SET; ri_lock.l_start = 0; ri_lock.l_len = 0; fcntl(fd, F_SETLK, &ri_lock); /* clear lock */ fclose(global_ri_file); free(path); return -1; } kb_error(LOG_INFO, "runtime information file created"); free(path); return 0; }
void fsp_serve (const char *kb_name, fsp_backend *backend, int daemon, float disk_limit) { struct addrinfo hints, *info; uint16_t port = FS_DEFAULT_PORT; fs_backend *original = NULL; char cport[6]; int on = 1, off = 0, srv, err; default_hints(&hints); /* what we'll do is set IPv6 here and turn off IPV6-only on hosts where it's the default */ hints.ai_family = AF_INET6; hints.ai_flags = AI_PASSIVE; /* we need access to these elsewhere */ global_kb_name = (char *)kb_name; global_disk_limit = disk_limit; if (backend->open) { original = backend->open(kb_name, FS_BACKEND_NO_OPEN); if (!original) return; } do { sprintf(cport, "%u", port); if ((err = getaddrinfo(NULL, cport, &hints, &info))) { kb_error(LOG_ERR, "getaddrinfo failed: %s", gai_strerror(err)); return; } srv = socket(info->ai_family, info->ai_socktype, info->ai_protocol); if (srv < 0) { if (errno == EAFNOSUPPORT) { kb_error(LOG_INFO, "couldn't get IPv6 dual stack, trying IPv4-only"); hints.ai_family = AF_INET; continue; } kb_error(LOG_ERR, "socket failed: %s", strerror(errno)); freeaddrinfo(info); return; } if (hints.ai_family != AF_INET && setsockopt(srv, IPPROTO_IPV6, IPV6_V6ONLY, &off, sizeof(off)) == -1) { kb_error(LOG_WARNING, "setsockopt IPV6_V6ONLY OFF failed"); } if (setsockopt(srv, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) { kb_error(LOG_WARNING, "setsockopt SO_REUSEADDR failed"); } if (bind(srv, info->ai_addr, info->ai_addrlen) < 0) { if (errno == EADDRINUSE) { close(srv); freeaddrinfo(info); ++port; /* try another port */ continue; } else { kb_error(LOG_ERR, "server socket bind failed: %s", strerror(errno)); freeaddrinfo(info); return; } } break; } while (1); freeaddrinfo(info); if (listen(srv, 64) < 0) { kb_error(LOG_ERR, "listen failed"); return; } /* preload toplevel indexes */ fs_backend *be = backend->open(kb_name, FS_BACKEND_PRELOAD); if (!be) { kb_error(LOG_CRIT, "failed to open backend"); return; } GMainLoop *loop = g_main_loop_new (NULL, FALSE); fsp_mdns_setup_backend (port, kb_name, backend->segment_count(be)); backend->close(be); if (daemon) { daemonize(); } signal_actions(); fs_error(LOG_INFO, "4store backend %s for kb %s on port %s", FS_BACKEND_VER, kb_name, cport); GIOChannel *listener = g_io_channel_unix_new (srv); g_io_add_watch(listener, G_IO_IN, accept_fn, backend); g_main_loop_run(loop); return; }