Commit message: nbd-server: handle modern-style negotiation in a child process

Previously, the modern style negotiation was carried out in the root
server (listener) process before forking the actual client handler. This
made it possible for a malfunctioning or evil client to terminate the
root process simply by querying a non-existent export or aborting in the
middle of the negotation process (caused SIGPIPE in the server).

This commit moves the negotiation process to the child to keep the root
process up and running no matter what happens during the negotiation.

See http://sourceforge.net/mailarchive/message.php?msg_id=30410146

Signed-off-by: Tuomas Räsänen <tuomasjjrasanen@tjjr.fi>
CVE: CVE-2013-7441
CWE: CWE-399
Vulnerability description: The modern style negotiation in Network Block Device (nbd-server) 2.9.22 through 3.3 allows remote attackers to cause a denial of service (root process termination) by (1) closing the connection during negotiation or (2) specifying a name for a non-existent export.

Project: nbd
File: nbd-server.c

static int append_new_servers(GArray *const servers, GError **const gerror) {
        int i;
        GArray *new_servers;
        const int old_len = servers->len;
        int retval = -1;
        struct generic_conf genconf;

        new_servers = parse_cfile(config_file_pos, &genconf, gerror);
        if (!new_servers)
                goto out;

        for (i = 0; i < new_servers->len; ++i) {
                SERVER new_server = g_array_index(new_servers, SERVER, i);

                if (new_server.servename
                    && -1 == get_index_by_servename(new_server.servename,
                                                    servers)) {
                        if (setup_serve(&new_server, gerror) == -1)
                                goto out;
                        if (append_serve(&new_server, servers) == -1)
                                goto out;
                }
        }

        retval = servers->len - old_len;
out:
        g_array_free(new_servers, TRUE);

        return retval;
}

Project: nbd
File: nbd-server.c

int authorized_client(CLIENT *opts) {
	const char *ERRMSG="Invalid entry '%s' in authfile '%s', so, refusing all connections.";
	FILE *f ;
	char line[LINELEN]; 
	char *tmp;
	struct in_addr addr;
	struct in_addr client;
	struct in_addr cltemp;
	int len;

	if ((f=fopen(opts->server->authname,"r"))==NULL) {
                msg(LOG_INFO, "Can't open authorization file %s (%s).",
                    opts->server->authname, strerror(errno));
		return 1 ; 
	}
  
  	inet_aton(opts->clientname, &client);
	while (fgets(line,LINELEN,f)!=NULL) {
		if((tmp=strchr(line, '/'))) {
			if(strlen(line)<=tmp-line) {
				msg(LOG_CRIT, ERRMSG, line, opts->server->authname);
				return 0;
			}
			*(tmp++)=0;
			if(!inet_aton(line,&addr)) {
				msg(LOG_CRIT, ERRMSG, line, opts->server->authname);
				return 0;
			}
			len=strtol(tmp, NULL, 0);
			addr.s_addr>>=32-len;
			addr.s_addr<<=32-len;
			memcpy(&cltemp,&client,sizeof(client));
			cltemp.s_addr>>=32-len;
			cltemp.s_addr<<=32-len;
			if(addr.s_addr == cltemp.s_addr) {
				return 1;
			}
		}
		if (strncmp(line,opts->clientname,strlen(opts->clientname))==0) {
			fclose(f);
			return 1;
		}
	}
	fclose(f);
	return 0;
}

Project: nbd
File: nbd-server.c

static inline void consume(int f, void * buf, size_t len, size_t bufsiz) {
	size_t curlen;
	while (len>0) {
		curlen = (len>bufsiz)?bufsiz:len;
		readit(f, buf, curlen);
		len -= curlen;
	}
}

Project: nbd
File: nbd-server.c

int copyonwrite_prepare(CLIENT* client) {
	off_t i;
	if ((client->difffilename = malloc(1024))==NULL)
		err("Failed to allocate string for diff file name");
	snprintf(client->difffilename, 1024, "%s-%s-%d.diff",client->exportname,client->clientname,
		(int)getpid()) ;
	client->difffilename[1023]='\0';
	msg(LOG_INFO, "About to create map and diff file %s", client->difffilename) ;
	client->difffile=open(client->difffilename,O_RDWR | O_CREAT | O_TRUNC,0600) ;
	if (client->difffile<0) err("Could not create diff file (%m)") ;
	if ((client->difmap=calloc(client->exportsize/DIFFPAGESIZE,sizeof(u32)))==NULL)
		err("Could not allocate memory") ;
	for (i=0;i<client->exportsize/DIFFPAGESIZE;i++) client->difmap[i]=(u32)-1 ;

	return 0;
}

Project: nbd
File: nbd-server.c

GArray* do_cfile_dir(gchar* dir, GError** e) {
	DIR* dirh = opendir(dir);
	struct dirent* de;
	gchar* fname;
	GArray* retval = NULL;
	GArray* tmp;
	struct stat stbuf;

	if(!dir) {
		g_set_error(e, NBDS_ERR, NBDS_ERR_CFILE_DIR_UNKNOWN, "Invalid directory specified: %s", strerror(errno));
		return NULL;
	}
	errno=0;
	while((de = readdir(dirh))) {
		int saved_errno=errno;
		fname = g_build_filename(dir, de->d_name, NULL);
		switch(de->d_type) {
			case DT_UNKNOWN:
				/* Filesystem doesn't return type of
				 * file through readdir. Run stat() on
				 * the file instead */
				if(stat(fname, &stbuf)) {
					perror("stat");
					goto err_out;
				}
				if (!S_ISREG(stbuf.st_mode)) {
					goto next;
				}
			case DT_REG:
				/* Skip unless the name ends with '.conf' */
				if(strcmp((de->d_name + strlen(de->d_name) - 5), ".conf")) {
					goto next;
				}
				tmp = parse_cfile(fname, NULL, e);
				errno=saved_errno;
				if(*e) {
					goto err_out;
				}
				if(!retval)
					retval = g_array_new(FALSE, TRUE, sizeof(SERVER));
				retval = g_array_append_vals(retval, tmp->data, tmp->len);
				g_array_free(tmp, TRUE);
			default:
				break;
		}
	next:
		g_free(fname);
	}
	if(errno) {
		g_set_error(e, NBDS_ERR, NBDS_ERR_CFILE_READDIR_ERR, "Error trying to read directory: %s", strerror(errno));
	err_out:
		if(retval)
			g_array_free(retval, TRUE);
		return NULL;
	}
	return retval;
}

Project: nbd
File: nbd-server.c

int dosockopts(const int socket, GError **const gerror) {
#ifndef sun
	int yes=1;
#else
	char yes='1';
#endif /* sun */
	struct linger l;

	/* lose the pesky "Address already in use" error message */
	if (setsockopt(socket,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(int)) == -1) {
                g_set_error(gerror, NBDS_ERR, NBDS_ERR_SO_REUSEADDR,
                            "failed to set socket option SO_REUSEADDR: %s",
                            strerror(errno));
                return -1;
	}
	l.l_onoff = 1;
	l.l_linger = 10;
	if (setsockopt(socket,SOL_SOCKET,SO_LINGER,&l,sizeof(l)) == -1) {
                g_set_error(gerror, NBDS_ERR, NBDS_ERR_SO_LINGER,
                            "failed to set socket option SO_LINGER: %s",
                            strerror(errno));
                return -1;
	}
	if (setsockopt(socket,SOL_SOCKET,SO_KEEPALIVE,&yes,sizeof(int)) == -1) {
                g_set_error(gerror, NBDS_ERR, NBDS_ERR_SO_KEEPALIVE,
                            "failed to set socket option SO_KEEPALIVE: %s",
                            strerror(errno));
                return -1;
	}

        return 0;
}

Project: nbd
File: nbd-server.c

void dousers(const gchar *const username, const gchar *const groupname) {
	struct passwd *pw;
	struct group *gr;
	gchar* str;
	if (groupname) {
		gr = getgrnam(groupname);
		if(!gr) {
			str = g_strdup_printf("Invalid group name: %s", groupname);
			err(str);
		}
		if(setgid(gr->gr_gid)<0) {
			err("Could not set GID: %m"); 
		}
	}
	if (username) {
		pw = getpwnam(username);
		if(!pw) {
			str = g_strdup_printf("Invalid user name: %s", username);
			err(str);
		}
		if(setuid(pw->pw_uid)<0) {
			err("Could not set UID: %m");
		}
	}
}

Project: nbd
File: nbd-server.c

int expflush(CLIENT *client) {
	gint i;

        if (client->server->flags & F_COPYONWRITE) {
		return fsync(client->difffile);
	}
	
	for (i = 0; i < client->export->len; i++) {
		FILE_INFO fi = g_array_index(client->export, FILE_INFO, i);
		if (fsync(fi.fhandle) < 0)
			return -1;
	}
	
	return 0;
}

Project: nbd
File: nbd-server.c

int expread(off_t a, char *buf, size_t len, CLIENT *client) {
	off_t rdlen, offset;
	off_t mapcnt, mapl, maph, pagestart;

	if (!(client->server->flags & F_COPYONWRITE))
		return(rawexpread_fully(a, buf, len, client));
	DEBUG("Asked to read %u bytes at %llu.\n", (unsigned int)len, (unsigned long long)a);

	mapl=a/DIFFPAGESIZE; maph=(a+len-1)/DIFFPAGESIZE;

	for (mapcnt=mapl;mapcnt<=maph;mapcnt++) {
		pagestart=mapcnt*DIFFPAGESIZE;
		offset=a-pagestart;
		rdlen=(0<DIFFPAGESIZE-offset && len<(size_t)(DIFFPAGESIZE-offset)) ?
			len : (size_t)DIFFPAGESIZE-offset;
		if (client->difmap[mapcnt]!=(u32)(-1)) { /* the block is already there */
			DEBUG("Page %llu is at %lu\n", (unsigned long long)mapcnt,
			       (unsigned long)(client->difmap[mapcnt]));
			myseek(client->difffile, client->difmap[mapcnt]*DIFFPAGESIZE+offset);
			if (read(client->difffile, buf, rdlen) != rdlen) return -1;
		} else { /* the block is not there */
			DEBUG("Page %llu is not here, we read the original one\n",
			       (unsigned long long)mapcnt);
			if(rawexpread_fully(a, buf, rdlen, client)) return -1;
		}
		len-=rdlen; a+=rdlen; buf+=rdlen;
	}
	return 0;
}

Project: nbd
File: nbd-server.c

int exptrim(struct nbd_request* req, CLIENT* client) {
#if HAVE_FALLOC_PH
	FILE_INFO prev = g_array_index(client->export, FILE_INFO, 0);
	FILE_INFO cur = prev;
	int i = 1;
	/* We're running on a system that supports the
	 * FALLOC_FL_PUNCH_HOLE option to re-sparsify a file */
	do {
		if(i<client->export->len) {
			cur = g_array_index(client->export, FILE_INFO, i);
		}
		if(prev.startoff <= req->from) {
			off_t curoff = req->from - prev.startoff;
			off_t curlen = cur.startoff - prev.startoff - curoff;
			fallocate(prev.fhandle, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, curoff, curlen);
		}
		prev = cur;
	} while(i < client->export->len && cur.startoff < (req->from + req->len));
	DEBUG("Performed TRIM request from %llu to %llu", (unsigned long long) req->from, (unsigned long long) req->len);
#else
	DEBUG("Ignoring TRIM request (not supported on current platform");
#endif
	return 0;
}

Project: nbd
File: nbd-server.c

int expwrite(off_t a, char *buf, size_t len, CLIENT *client, int fua) {
	char pagebuf[DIFFPAGESIZE];
	off_t mapcnt,mapl,maph;
	off_t wrlen,rdlen; 
	off_t pagestart;
	off_t offset;

	if (!(client->server->flags & F_COPYONWRITE))
		return(rawexpwrite_fully(a, buf, len, client, fua)); 
	DEBUG("Asked to write %u bytes at %llu.\n", (unsigned int)len, (unsigned long long)a);

	mapl=a/DIFFPAGESIZE ; maph=(a+len-1)/DIFFPAGESIZE ;

	for (mapcnt=mapl;mapcnt<=maph;mapcnt++) {
		pagestart=mapcnt*DIFFPAGESIZE ;
		offset=a-pagestart ;
		wrlen=(0<DIFFPAGESIZE-offset && len<(size_t)(DIFFPAGESIZE-offset)) ?
			len : (size_t)DIFFPAGESIZE-offset;

		if (client->difmap[mapcnt]!=(u32)(-1)) { /* the block is already there */
			DEBUG("Page %llu is at %lu\n", (unsigned long long)mapcnt,
			       (unsigned long)(client->difmap[mapcnt])) ;
			myseek(client->difffile,
					client->difmap[mapcnt]*DIFFPAGESIZE+offset);
			if (write(client->difffile, buf, wrlen) != wrlen) return -1 ;
		} else { /* the block is not there */
			myseek(client->difffile,client->difffilelen*DIFFPAGESIZE) ;
			client->difmap[mapcnt]=(client->server->flags&F_SPARSE)?mapcnt:client->difffilelen++;
			DEBUG("Page %llu is not here, we put it at %lu\n",
			       (unsigned long long)mapcnt,
			       (unsigned long)(client->difmap[mapcnt]));
			rdlen=DIFFPAGESIZE ;
			if (rawexpread_fully(pagestart, pagebuf, rdlen, client))
				return -1;
			memcpy(pagebuf+offset,buf,wrlen) ;
			if (write(client->difffile, pagebuf, DIFFPAGESIZE) !=
					DIFFPAGESIZE)
				return -1;
		}						    
		len-=wrlen ; a+=wrlen ; buf+=wrlen ;
	}
	if (client->server->flags & F_SYNC) {
		fsync(client->difffile);
	} else if (fua) {
		/* open question: would it be cheaper to do multiple sync_file_ranges?
		   as we iterate through the above?
		 */
		fdatasync(client->difffile);
	}
	return 0;
}

Project: nbd
File: nbd-server.c

int get_filepos(GArray* export, off_t a, int* fhandle, off_t* foffset, size_t* maxbytes ) {
	/* Negative offset not allowed */
	if(a < 0)
		return -1;

	/* Binary search for last file with starting offset <= a */
	FILE_INFO fi;
	int start = 0;
	int end = export->len - 1;
	while( start <= end ) {
		int mid = (start + end) / 2;
		fi = g_array_index(export, FILE_INFO, mid);
		if( fi.startoff < a ) {
			start = mid + 1;
		} else if( fi.startoff > a ) {
			end = mid - 1;
		} else {
			start = end = mid;
			break;
		}
	}

	/* end should never go negative, since first startoff is 0 and a >= 0 */
	assert(end >= 0);

	fi = g_array_index(export, FILE_INFO, end);
	*fhandle = fi.fhandle;
	*foffset = a - fi.startoff;
	*maxbytes = 0;
	if( end+1 < export->len ) {
		FILE_INFO fi_next = g_array_index(export, FILE_INFO, end+1);
		*maxbytes = fi_next.startoff - a;
	}

	return 0;
}

Project: nbd
File: nbd-server.c

static int get_index_by_servename(const gchar *const servename,
                                  const GArray *const servers) {
        int i;

        for (i = 0; i < servers->len; ++i) {
                const SERVER server = g_array_index(servers, SERVER, i);

                if (strcmp(servename, server.servename) == 0)
                        return i;
        }

        return -1;
}

Project: nbd
File: nbd-server.c

static inline const char * getcommandname(uint64_t command) {
	switch (command) {
	case NBD_CMD_READ:
		return "NBD_CMD_READ";
	case NBD_CMD_WRITE:
		return "NBD_CMD_WRITE";
	case NBD_CMD_DISC:
		return "NBD_CMD_DISC";
	case NBD_CMD_FLUSH:
		return "NBD_CMD_FLUSH";
	case NBD_CMD_TRIM:
		return "NBD_CMD_TRIM";
	default:
		return "UNKNOWN";
	}
}

Project: nbd
File: nbd-server.c

void glib_message_syslog_redirect(const gchar *log_domain,
                                  GLogLevelFlags log_level,
                                  const gchar *message,
                                  gpointer user_data)
{
    int level=LOG_DEBUG;
    
    switch( log_level )
    {
      case G_LOG_FLAG_FATAL:
      case G_LOG_LEVEL_CRITICAL:
      case G_LOG_LEVEL_ERROR:    
        level=LOG_ERR; 
        break;
      case G_LOG_LEVEL_WARNING:
        level=LOG_WARNING;
        break;
      case G_LOG_LEVEL_MESSAGE:
      case G_LOG_LEVEL_INFO:
        level=LOG_INFO;
        break;
      case G_LOG_LEVEL_DEBUG:
        level=LOG_DEBUG;
	break;
      default:
        level=LOG_ERR;
    }
    syslog(level, "%s", message);
}

Project: nbd
File: nbd-server.c

static CLIENT* handle_export_name(uint32_t opt, int net, GArray* servers, uint32_t cflags) {
	uint32_t namelen;
	char* name;
	int i;

	if (read(net, &namelen, sizeof(namelen)) < 0) {
		err("Negotiation failed/7: %m");
		return NULL;
	}
	namelen = ntohl(namelen);
	name = malloc(namelen+1);
	name[namelen]=0;
	if (read(net, name, namelen) < 0) {
		err("Negotiation failed/8: %m");
		free(name);
		return NULL;
	}
	for(i=0; i<servers->len; i++) {
		SERVER* serve = &(g_array_index(servers, SERVER, i));
		if(!strcmp(serve->servename, name)) {
			CLIENT* client = g_new0(CLIENT, 1);
			client->server = serve;
			client->exportsize = OFFT_MAX;
			client->net = net;
			client->modern = TRUE;
			client->transactionlogfd = -1;
			client->clientfeats = cflags;
			free(name);
			return client;
		}
	}
	err("Negotiation failed/8a: Requested export not found");
	free(name);
	return NULL;
}

Project: nbd
File: nbd-server.c

void killchild(gpointer key, gpointer value, gpointer user_data) {
	pid_t *pid=value;

	kill(*pid, SIGTERM);
}

Project: nbd
File: nbd-server.c

int mainloop(CLIENT *client) {
	struct nbd_request request;
	struct nbd_reply reply;
	gboolean go_on=TRUE;
#ifdef DODBG
	int i = 0;
#endif
	negotiate(client->net, client, NULL, client->modern ? NEG_MODERN : (NEG_OLD | NEG_INIT));
	DEBUG("Entering request loop!\n");
	reply.magic = htonl(NBD_REPLY_MAGIC);
	reply.error = 0;
	while (go_on) {
		char buf[BUFSIZE];
		char* p;
		size_t len;
		size_t currlen;
		size_t writelen;
		uint16_t command;
#ifdef DODBG
		i++;
		printf("%d: ", i);
#endif
		readit(client->net, &request, sizeof(request));
		if (client->transactionlogfd != -1)
			writeit(client->transactionlogfd, &request, sizeof(request));

		request.from = ntohll(request.from);
		request.type = ntohl(request.type);
		command = request.type & NBD_CMD_MASK_COMMAND;
		len = ntohl(request.len);

		DEBUG("%s from %llu (%llu) len %u, ", getcommandname(command),
				(unsigned long long)request.from,
				(unsigned long long)request.from / 512, len);

		if (request.magic != htonl(NBD_REQUEST_MAGIC))
			err("Not enough magic.");

		memcpy(reply.handle, request.handle, sizeof(reply.handle));

		if ((command==NBD_CMD_WRITE) || (command==NBD_CMD_READ)) {
			if (request.from + len < request.from) { // 64 bit overflow!!
				DEBUG("[Number too large!]");
				ERROR(client, reply, EINVAL);
				continue;
			}

			if (((off_t)request.from + len) > client->exportsize) {
				DEBUG("[RANGE!]");
				ERROR(client, reply, EINVAL);
				continue;
			}

			currlen = len;
			if (currlen > BUFSIZE - sizeof(struct nbd_reply)) {
				currlen = BUFSIZE - sizeof(struct nbd_reply);
				if(!logged_oversized) {
					msg(LOG_DEBUG, "oversized request (this is not a problem)");
					logged_oversized = true;
				}
			}
		}

		switch (command) {

		case NBD_CMD_DISC:
			msg(LOG_INFO, "Disconnect request received.");
                	if (client->server->flags & F_COPYONWRITE) { 
				if (client->difmap) g_free(client->difmap) ;
                		close(client->difffile);
				unlink(client->difffilename);
				free(client->difffilename);
			}
			go_on=FALSE;
			continue;

		case NBD_CMD_WRITE:
			DEBUG("wr: net->buf, ");
			while(len > 0) {
				readit(client->net, buf, currlen);
				DEBUG("buf->exp, ");
				if ((client->server->flags & F_READONLY) ||
				    (client->server->flags & F_AUTOREADONLY)) {
					DEBUG("[WRITE to READONLY!]");
					ERROR(client, reply, EPERM);
					consume(client->net, buf, len-currlen, BUFSIZE);
					continue;
				}
				if (expwrite(request.from, buf, currlen, client,
					     request.type & NBD_CMD_FLAG_FUA)) {
					DEBUG("Write failed: %m" );
					ERROR(client, reply, errno);
					consume(client->net, buf, len-currlen, BUFSIZE);
					continue;
				}
				len -= currlen;
				request.from += currlen;
				currlen = (len < BUFSIZE) ? len : BUFSIZE;
			}
			SEND(client->net, reply);
			DEBUG("OK!\n");
			continue;

		case NBD_CMD_FLUSH:
			DEBUG("fl: ");
			if (expflush(client)) {
				DEBUG("Flush failed: %m");
				ERROR(client, reply, errno);
				continue;
			}
			SEND(client->net, reply);
			DEBUG("OK!\n");
			continue;

		case NBD_CMD_READ:
			DEBUG("exp->buf, ");
			if (client->transactionlogfd != -1)
				writeit(client->transactionlogfd, &reply, sizeof(reply));
			writeit(client->net, &reply, sizeof(reply));
			p = buf;
			writelen = currlen;
			while(len > 0) {
				if (expread(request.from, p, currlen, client)) {
					DEBUG("Read failed: %m");
					ERROR(client, reply, errno);
					continue;
				}
				
				DEBUG("buf->net, ");
				writeit(client->net, buf, writelen);
				len -= currlen;
				request.from += currlen;
				currlen = (len < BUFSIZE) ? len : BUFSIZE;
				p = buf;
				writelen = currlen;
			}
			DEBUG("OK!\n");
			continue;

		case NBD_CMD_TRIM:
			/* The kernel module sets discard_zeroes_data == 0,
			 * so it is okay to do nothing.  */
			if (exptrim(&request, client)) {
				DEBUG("Trim failed: %m");
				ERROR(client, reply, errno);
				continue;
			}
			SEND(client->net, reply);
			continue;

		default:
			DEBUG ("Ignoring unknown command\n");
			continue;
		}
	}
	return 0;
}

Project: nbd

CLIENT* negotiate(int net, CLIENT *client, GArray* servers, int phase) {
	char zeros[128];
	uint64_t size_host;
	uint32_t flags = NBD_FLAG_HAS_FLAGS;
	uint16_t smallflags = 0;
	uint64_t magic;

	memset(zeros, '\0', sizeof(zeros));
	assert(((phase & NEG_INIT) && (phase & NEG_MODERN)) || client);
	if(phase & NEG_MODERN) {
		smallflags |= NBD_FLAG_FIXED_NEWSTYLE;
	}
	if(phase & NEG_INIT) {
		/* common */
		if (write(net, INIT_PASSWD, 8) < 0) {
			err_nonfatal("Negotiation failed/1: %m");
			if(client)
				exit(EXIT_FAILURE);
		}
		if(phase & NEG_MODERN) {
			/* modern */
			magic = htonll(opts_magic);
		} else {
			/* oldstyle */
			magic = htonll(cliserv_magic);
		}
		if (write(net, &magic, sizeof(magic)) < 0) {
			err_nonfatal("Negotiation failed/2: %m");
			if(phase & NEG_OLD)
				exit(EXIT_FAILURE);
		}
	}
	if ((phase & NEG_MODERN) && (phase & NEG_INIT)) {
		/* modern */
		uint32_t cflags;
		uint32_t opt;

		if(!servers)
			err("programmer error");
		smallflags = htons(smallflags);
		if (write(net, &smallflags, sizeof(uint16_t)) < 0)
			err_nonfatal("Negotiation failed/3: %m");
		if (read(net, &cflags, sizeof(cflags)) < 0)
			err_nonfatal("Negotiation failed/4: %m");
		cflags = htonl(cflags);
		do {
			if (read(net, &magic, sizeof(magic)) < 0)
				err_nonfatal("Negotiation failed/5: %m");
			magic = ntohll(magic);
			if(magic != opts_magic) {
				err_nonfatal("Negotiation failed/5a: magic mismatch");
				return NULL;
			}
			if (read(net, &opt, sizeof(opt)) < 0)
				err_nonfatal("Negotiation failed/6: %m");
			opt = ntohl(opt);
			switch(opt) {
			case NBD_OPT_EXPORT_NAME:
				return handle_export_name(opt, net, servers, cflags);
				break;
			case NBD_OPT_LIST:
				handle_list(opt, net, servers, cflags);
				break;
			case NBD_OPT_ABORT:
				break;
			default:
				send_reply(opt, net, NBD_REP_ERR_UNSUP, 0, NULL);
				break;
			}
		} while((opt != NBD_OPT_EXPORT_NAME) && (opt != NBD_OPT_ABORT));
		if(opt == NBD_OPT_ABORT) {
			err_nonfatal("Session terminated by client");
			return NULL;
		}
	}
	/* common */
	size_host = htonll((u64)(client->exportsize));
	if (write(net, &size_host, 8) < 0)
		err("Negotiation failed/9: %m");
	if (client->server->flags & F_READONLY)
		flags |= NBD_FLAG_READ_ONLY;
	if (client->server->flags & F_FLUSH)
		flags |= NBD_FLAG_SEND_FLUSH;
	if (client->server->flags & F_FUA)
		flags |= NBD_FLAG_SEND_FUA;
	if (client->server->flags & F_ROTATIONAL)
		flags |= NBD_FLAG_ROTATIONAL;
	if (client->server->flags & F_TRIM)
		flags |= NBD_FLAG_SEND_TRIM;
	if (phase & NEG_OLD) {
		/* oldstyle */
		flags = htonl(flags);
		if (write(client->net, &flags, 4) < 0)
			err("Negotiation failed/10: %m");
	} else {
		/* modern */
		smallflags = (uint16_t)(flags & ~((uint16_t)0));
		smallflags = htons(smallflags);
		if (write(client->net, &smallflags, sizeof(smallflags)) < 0) {
			err("Negotiation failed/11: %m");
		}
	}
	/* common */
	if (write(client->net, zeros, 124) < 0)
		err("Negotiation failed/12: %m");
	return NULL;
}

Project: nbd
File: nbd-server.c

int open_modern(const gchar *const addr, const gchar *const port,
                GError **const gerror) {
	struct addrinfo hints;
	struct addrinfo* ai = NULL;
	struct addrinfo* ai_bak;
	struct sock_flags;
	int e;
        int retval = -1;
	int i=0;
	int sock = -1;

	memset(&hints, '\0', sizeof(hints));
	hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG;
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_family = AF_UNSPEC;
	hints.ai_protocol = IPPROTO_TCP;
	e = getaddrinfo(addr, port ? port : NBD_DEFAULT_PORT, &hints, &ai);
	ai_bak = ai;
	if(e != 0) {
                g_set_error(gerror, NBDS_ERR, NBDS_ERR_GAI,
                            "failed to open a modern socket: "
                            "failed to get address info: %s",
                            gai_strerror(e));
                goto out;
	}

	while(ai != NULL) {
		sock = -1;

		if((sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol))<0) {
			g_set_error(gerror, NBDS_ERR, NBDS_ERR_SOCKET,
				    "failed to open a modern socket: "
				    "failed to create a socket: %s",
				    strerror(errno));
			goto out;
		}

		if (dosockopts(sock, gerror) == -1) {
			g_prefix_error(gerror, "failed to open a modern socket: ");
			goto out;
		}

		if(bind(sock, ai->ai_addr, ai->ai_addrlen)) {
			/* This is so wrong. 
			 * 
			 * Linux will return multiple entries for the
			 * same system when we ask it for something
			 * AF_UNSPEC, even though the first entry will
			 * listen to both protocols. Other systems will
			 * return multiple entries too, but we actually
			 * do need to open both. Sigh.
			 *
			 * Handle it by ignoring EADDRINUSE if we've
			 * already got at least one socket open
			 */
			if(errno == EADDRINUSE && modernsocks->len > 0) {
				goto next;
			}
			g_set_error(gerror, NBDS_ERR, NBDS_ERR_BIND,
				    "failed to open a modern socket: "
				    "failed to bind an address to a socket: %s",
				    strerror(errno));
			goto out;
		}

		if(listen(sock, 10) <0) {
			g_set_error(gerror, NBDS_ERR, NBDS_ERR_BIND,
				    "failed to open a modern socket: "
				    "failed to start listening on a socket: %s",
				    strerror(errno));
			goto out;
		}
		g_array_append_val(modernsocks, sock);
	next:
		ai = ai->ai_next;
	}

        retval = 0;
out:

        if (retval == -1 && sock >= 0) {
                close(sock);
        }
	if(ai_bak)
		freeaddrinfo(ai_bak);

        return retval;
}

Project: nbd
File: nbd-server.c

GArray* parse_cfile(gchar* f, struct generic_conf *const genconf, GError** e) {
	const char* DEFAULT_ERROR = "Could not parse %s in group %s: %s";
	const char* MISSING_REQUIRED_ERROR = "Could not find required value %s in group %s: %s";
	gchar* cfdir = NULL;
	SERVER s;
	gchar *virtstyle=NULL;
	PARAM lp[] = {
		{ "exportname", TRUE,	PARAM_STRING, 	&(s.exportname),	0 },
		{ "port", 	TRUE,	PARAM_INT, 	&(s.port),		0 },
		{ "authfile",	FALSE,	PARAM_STRING,	&(s.authname),		0 },
		{ "filesize",	FALSE,	PARAM_OFFT,	&(s.expected_size),	0 },
		{ "virtstyle",	FALSE,	PARAM_STRING,	&(virtstyle),		0 },
		{ "prerun",	FALSE,	PARAM_STRING,	&(s.prerun),		0 },
		{ "postrun",	FALSE,	PARAM_STRING,	&(s.postrun),		0 },
		{ "transactionlog", FALSE, PARAM_STRING, &(s.transactionlog),	0 },
		{ "readonly",	FALSE,	PARAM_BOOL,	&(s.flags),		F_READONLY },
		{ "multifile",	FALSE,	PARAM_BOOL,	&(s.flags),		F_MULTIFILE },
		{ "copyonwrite", FALSE,	PARAM_BOOL,	&(s.flags),		F_COPYONWRITE },
		{ "sparse_cow",	FALSE,	PARAM_BOOL,	&(s.flags),		F_SPARSE },
		{ "sdp",	FALSE,	PARAM_BOOL,	&(s.flags),		F_SDP },
		{ "sync",	FALSE,  PARAM_BOOL,	&(s.flags),		F_SYNC },
		{ "flush",	FALSE,  PARAM_BOOL,	&(s.flags),		F_FLUSH },
		{ "fua",	FALSE,  PARAM_BOOL,	&(s.flags),		F_FUA },
		{ "rotational",	FALSE,  PARAM_BOOL,	&(s.flags),		F_ROTATIONAL },
		{ "temporary",	FALSE,  PARAM_BOOL,	&(s.flags),		F_TEMPORARY },
		{ "trim",	FALSE,  PARAM_BOOL,	&(s.flags),		F_TRIM },
		{ "listenaddr", FALSE,  PARAM_STRING,   &(s.listenaddr),	0 },
		{ "maxconnections", FALSE, PARAM_INT,	&(s.max_connections),	0 },
	};
	const int lp_size=sizeof(lp)/sizeof(PARAM);
        struct generic_conf genconftmp;
	PARAM gp[] = {
		{ "user",	FALSE, PARAM_STRING,	&(genconftmp.user),       0 },
		{ "group",	FALSE, PARAM_STRING,	&(genconftmp.group),      0 },
		{ "oldstyle",	FALSE, PARAM_BOOL,	&(genconftmp.flags),      F_OLDSTYLE },
		{ "listenaddr", FALSE, PARAM_STRING,	&(genconftmp.modernaddr), 0 },
		{ "port", 	FALSE, PARAM_STRING,	&(genconftmp.modernport), 0 },
		{ "includedir", FALSE, PARAM_STRING,	&cfdir,                   0 },
		{ "allowlist",  FALSE, PARAM_BOOL,	&(genconftmp.flags),      F_LIST },
	};
	PARAM* p=gp;
	int p_size=sizeof(gp)/sizeof(PARAM);
	GKeyFile *cfile;
	GError *err = NULL;
	const char *err_msg=NULL;
	GArray *retval=NULL;
	gchar **groups;
	gboolean bval;
	gint ival;
	gint64 i64val;
	gchar* sval;
	gchar* startgroup;
	gint i;
	gint j;

        memset(&genconftmp, 0, sizeof(struct generic_conf));

        if (genconf) {
                /* Use the passed configuration values as defaults. The
                 * parsing algorithm below updates all parameter targets
                 * found from configuration files. */
                memcpy(&genconftmp, genconf, sizeof(struct generic_conf));
        }

	cfile = g_key_file_new();
	retval = g_array_new(FALSE, TRUE, sizeof(SERVER));
	if(!g_key_file_load_from_file(cfile, f, G_KEY_FILE_KEEP_COMMENTS |
			G_KEY_FILE_KEEP_TRANSLATIONS, &err)) {
		g_set_error(e, NBDS_ERR, NBDS_ERR_CFILE_NOTFOUND, "Could not open config file %s: %s",
				f, err->message);
		g_key_file_free(cfile);
		return retval;
	}
	startgroup = g_key_file_get_start_group(cfile);
	if((!startgroup || strcmp(startgroup, "generic")) && genconf) {
		g_set_error(e, NBDS_ERR, NBDS_ERR_CFILE_MISSING_GENERIC, "Config file does not contain the [generic] group!");
		g_key_file_free(cfile);
		return NULL;
	}
	groups = g_key_file_get_groups(cfile, NULL);
	for(i=0;groups[i];i++) {
		memset(&s, '\0', sizeof(SERVER));

		/* After the [generic] group or when we're parsing an include
		 * directory, start parsing exports */
		if(i==1 || !genconf) {
			p=lp;
			p_size=lp_size;
			if(!(glob_flags & F_OLDSTYLE)) {
				lp[1].required = FALSE;
			}
		} 
		for(j=0;j<p_size;j++) {
			assert(p[j].target != NULL);
			assert(p[j].ptype==PARAM_INT||p[j].ptype==PARAM_STRING||p[j].ptype==PARAM_BOOL||p[j].ptype==PARAM_INT64);
			switch(p[j].ptype) {
				case PARAM_INT:
					ival = g_key_file_get_integer(cfile,
								groups[i],
								p[j].paramname,
								&err);
					if(!err) {
						*((gint*)p[j].target) = ival;
					}
					break;
				case PARAM_INT64:
					i64val = g_key_file_get_int64(cfile,
								groups[i],
								p[j].paramname,
								&err);
					if(!err) {
						*((gint64*)p[j].target) = i64val;
					}
					break;
				case PARAM_STRING:
					sval = g_key_file_get_string(cfile,
								groups[i],
								p[j].paramname,
								&err);
					if(!err) {
						*((gchar**)p[j].target) = sval;
					}
					break;
				case PARAM_BOOL:
					bval = g_key_file_get_boolean(cfile,
							groups[i],
							p[j].paramname, &err);
					if(!err) {
						if(bval) {
							*((gint*)p[j].target) |= p[j].flagval;
						} else {
							*((gint*)p[j].target) &= ~(p[j].flagval);
						}
					}
					break;
			}
			if(err) {
				if(err->code == G_KEY_FILE_ERROR_KEY_NOT_FOUND) {
					if(!p[j].required) {
						/* Ignore not-found error for optional values */
						g_clear_error(&err);
						continue;
					} else {
						err_msg = MISSING_REQUIRED_ERROR;
					}
				} else {
					err_msg = DEFAULT_ERROR;
				}
				g_set_error(e, NBDS_ERR, NBDS_ERR_CFILE_VALUE_INVALID, err_msg, p[j].paramname, groups[i], err->message);
				g_array_free(retval, TRUE);
				g_error_free(err);
				g_key_file_free(cfile);
				return NULL;
			}
		}
		if(virtstyle) {
			if(!strncmp(virtstyle, "none", 4)) {
				s.virtstyle=VIRT_NONE;
			} else if(!strncmp(virtstyle, "ipliteral", 9)) {
				s.virtstyle=VIRT_IPLIT;
			} else if(!strncmp(virtstyle, "iphash", 6)) {
				s.virtstyle=VIRT_IPHASH;
			} else if(!strncmp(virtstyle, "cidrhash", 8)) {
				s.virtstyle=VIRT_CIDR;
				if(strlen(virtstyle)<10) {
					g_set_error(e, NBDS_ERR, NBDS_ERR_CFILE_VALUE_INVALID, "Invalid value %s for parameter virtstyle in group %s: missing length", virtstyle, groups[i]);
					g_array_free(retval, TRUE);
					g_key_file_free(cfile);
					return NULL;
				}
				s.cidrlen=strtol(virtstyle+8, NULL, 0);
			} else {
				g_set_error(e, NBDS_ERR, NBDS_ERR_CFILE_VALUE_INVALID, "Invalid value %s for parameter virtstyle in group %s", virtstyle, groups[i]);
				g_array_free(retval, TRUE);
				g_key_file_free(cfile);
				return NULL;
			}
		} else {
			s.virtstyle=VIRT_IPLIT;
		}
		if(s.port && !(glob_flags & F_OLDSTYLE)) {
			g_warning("A port was specified, but oldstyle exports were not requested. This may not do what you expect.");
			g_warning("Please read 'man 5 nbd-server' and search for oldstyle for more info");
		}
		/* Don't need to free this, it's not our string */
		virtstyle=NULL;
		/* Don't append values for the [generic] group */
		if(i>0 || !genconf) {
			s.socket_family = AF_UNSPEC;
			s.servename = groups[i];

			append_serve(&s, retval);
		}
#ifndef WITH_SDP
		if(s.flags & F_SDP) {
			g_set_error(e, NBDS_ERR, NBDS_ERR_CFILE_VALUE_UNSUPPORTED, "This nbd-server was built without support for SDP, yet group %s uses it", groups[i]);
			g_array_free(retval, TRUE);
			g_key_file_free(cfile);
			return NULL;
		}
#endif
	}
	g_key_file_free(cfile);
	if(cfdir) {
		GArray* extra = do_cfile_dir(cfdir, e);
		if(extra) {
			retval = g_array_append_vals(retval, extra->data, extra->len);
			i+=extra->len;
			g_array_free(extra, TRUE);
		} else {
			if(*e) {
				g_array_free(retval, TRUE);
				return NULL;
			}
		}
	}
	if(i==1 && genconf) {
		g_set_error(e, NBDS_ERR, NBDS_ERR_CFILE_NO_EXPORTS, "The config file does not specify any exports");
	}

        if (genconf) {
                /* Return the updated generic configuration through the
                 * pointer parameter. */
                memcpy(genconf, &genconftmp, sizeof(struct generic_conf));
        }

	return retval;
}

Project: nbd
File: nbd-server.c

ssize_t rawexpread(off_t a, char *buf, size_t len, CLIENT *client) {
	int fhandle;
	off_t foffset;
	size_t maxbytes;

	if(get_filepos(client->export, a, &fhandle, &foffset, &maxbytes))
		return -1;
	if(maxbytes && len > maxbytes)
		len = maxbytes;

	DEBUG("(READ from fd %d offset %llu len %u), ", fhandle, (long long unsigned int)foffset, (unsigned int)len);

	myseek(fhandle, foffset);
	return read(fhandle, buf, len);
}

Project: nbd
File: nbd-server.c

ssize_t rawexpwrite(off_t a, char *buf, size_t len, CLIENT *client, int fua) {
	int fhandle;
	off_t foffset;
	size_t maxbytes;
	ssize_t retval;

	if(get_filepos(client->export, a, &fhandle, &foffset, &maxbytes))
		return -1;
	if(maxbytes && len > maxbytes)
		len = maxbytes;

	DEBUG("(WRITE to fd %d offset %llu len %u fua %d), ", fhandle, (long long unsigned)foffset, (unsigned int)len, fua);

	myseek(fhandle, foffset);
	retval = write(fhandle, buf, len);
	if(client->server->flags & F_SYNC) {
		fsync(fhandle);
	} else if (fua) {

	  /* This is where we would do the following
	   *   #ifdef USE_SYNC_FILE_RANGE
	   * However, we don't, for the reasons set out below
	   * by Christoph Hellwig <hch@infradead.org>
	   *
	   * [BEGINS] 
	   * fdatasync is equivalent to fsync except that it does not flush
	   * non-essential metadata (basically just timestamps in practice), but it
	   * does flush metadata requried to find the data again, e.g. allocation
	   * information and extent maps.  sync_file_range does nothing but flush
	   * out pagecache content - it means you basically won't get your data
	   * back in case of a crash if you either:
	   * 
	   *  a) have a volatile write cache in your disk (e.g. any normal SATA disk)
	   *  b) are using a sparse file on a filesystem
	   *  c) are using a fallocate-preallocated file on a filesystem
	   *  d) use any file on a COW filesystem like btrfs
	   * 
	   * e.g. it only does anything useful for you if you do not have a volatile
	   * write cache, and either use a raw block device node, or just overwrite
	   * an already fully allocated (and not preallocated) file on a non-COW
	   * filesystem.
	   * [ENDS]
	   *
	   * What we should do is open a second FD with O_DSYNC set, then write to
	   * that when appropriate. However, with a Linux client, every REQ_FUA
	   * immediately follows a REQ_FLUSH, so fdatasync does not cause performance
	   * problems.
	   *
	   */
#if 0
		sync_file_range(fhandle, foffset, len,
				SYNC_FILE_RANGE_WAIT_BEFORE | SYNC_FILE_RANGE_WRITE |
				SYNC_FILE_RANGE_WAIT_AFTER);
#else
		fdatasync(fhandle);
#endif
	}
	return retval;
}

Project: nbd
File: nbd-server.c

static inline void readit(int f, void *buf, size_t len) {
	ssize_t res;
	while (len > 0) {
		DEBUG("*");
		if ((res = read(f, buf, len)) <= 0) {
			if(errno != EAGAIN) {
				err("Read failed: %m");
			}
		} else {
			len -= res;
			buf += res;
		}
	}
}

Project: nbd
File: nbd-server.c

static void send_reply(uint32_t opt, int net, uint32_t reply_type, size_t datasize, void* data) {
	uint64_t magic = htonll(0x3e889045565a9LL);
	reply_type = htonl(reply_type);
	uint32_t datsize = htonl(datasize);
	struct iovec v_data[] = {
		{ &magic, sizeof(magic) },
		{ &opt, sizeof(opt) },
		{ &reply_type, sizeof(reply_type) },
		{ &datsize, sizeof(datsize) },
		{ data, datasize },
	};
	writev(net, v_data, 5);
}

Project: nbd
File: nbd-server.c

void serveconnection(CLIENT *client) {
	if (client->server->transactionlog && (client->transactionlogfd == -1))
	{
		if (-1 == (client->transactionlogfd = open(client->server->transactionlog,
							   O_WRONLY | O_CREAT,
							   S_IRUSR | S_IWUSR)))
			g_warning("Could not open transaction log %s",
				  client->server->transactionlog);
	}

	if(do_run(client->server->prerun, client->exportname)) {
		exit(EXIT_FAILURE);
	}
	setupexport(client);

	if (client->server->flags & F_COPYONWRITE) {
		copyonwrite_prepare(client);
	}

	setmysockopt(client->net);

	mainloop(client);
	do_run(client->server->postrun, client->exportname);

	if (-1 != client->transactionlogfd)
	{
		close(client->transactionlogfd);
		client->transactionlogfd = -1;
	}
}

Project: nbd
File: nbd-server.c

int set_peername(int net, CLIENT *client) {
	struct sockaddr_storage addrin;
	struct sockaddr_storage netaddr;
	struct sockaddr_in  *netaddr4 = NULL;
	struct sockaddr_in6 *netaddr6 = NULL;
	socklen_t addrinlen = sizeof( addrin );
	struct addrinfo hints;
	struct addrinfo *ai = NULL;
	char peername[NI_MAXHOST];
	char netname[NI_MAXHOST];
	char *tmp = NULL;
	int i;
	int e;
	int shift;

	if (getpeername(net, (struct sockaddr *) &addrin, &addrinlen) < 0) {
		msg(LOG_INFO, "getpeername failed: %m");
		return -1;
	}

	if((e = getnameinfo((struct sockaddr *)&addrin, addrinlen,
			peername, sizeof (peername), NULL, 0, NI_NUMERICHOST))) {
		msg(LOG_INFO, "getnameinfo failed: %s", gai_strerror(e));
		return -1;
	}

	memset(&hints, '\0', sizeof (hints));
	hints.ai_flags = AI_ADDRCONFIG;
	e = getaddrinfo(peername, NULL, &hints, &ai);

	if(e != 0) {
		msg(LOG_INFO, "getaddrinfo failed: %s", gai_strerror(e));
		freeaddrinfo(ai);
		return -1;
	}

	switch(client->server->virtstyle) {
		case VIRT_NONE:
			msg(LOG_DEBUG, "virtualization is off");
			client->exportname=g_strdup(client->server->exportname);
			break;
		case VIRT_IPHASH:
			msg(LOG_DEBUG, "virtstyle iphash");
			for(i=0;i<strlen(peername);i++) {
				if(peername[i]=='.') {
					peername[i]='/';
				}
			}
		case VIRT_IPLIT:
			msg(LOG_DEBUG, "virststyle ipliteral");
			client->exportname=g_strdup_printf(client->server->exportname, peername);
			break;
		case VIRT_CIDR:
			msg(LOG_DEBUG, "virtstyle cidr %d", client->server->cidrlen);
			memcpy(&netaddr, &addrin, addrinlen);
			if(ai->ai_family == AF_INET) {
				netaddr4 = (struct sockaddr_in *)&netaddr;
				(netaddr4->sin_addr).s_addr>>=32-(client->server->cidrlen);
				(netaddr4->sin_addr).s_addr<<=32-(client->server->cidrlen);

				getnameinfo((struct sockaddr *) netaddr4, addrinlen,
							netname, sizeof (netname), NULL, 0, NI_NUMERICHOST);
				tmp=g_strdup_printf("%s/%s", netname, peername);
			}else if(ai->ai_family == AF_INET6) {
				netaddr6 = (struct sockaddr_in6 *)&netaddr;

				shift = 128-(client->server->cidrlen);
				i = 3;
				while(shift >= 8) {
					((netaddr6->sin6_addr).s6_addr[i])=0;
					shift-=8;
					i--;
				}
				(netaddr6->sin6_addr).s6_addr[i]>>=shift;
				(netaddr6->sin6_addr).s6_addr[i]<<=shift;

				getnameinfo((struct sockaddr *)netaddr6, addrinlen,
					    netname, sizeof(netname), NULL, 0, NI_NUMERICHOST);
				tmp=g_strdup_printf("%s/%s", netname, peername);
			}

			if(tmp != NULL)
			  client->exportname=g_strdup_printf(client->server->exportname, tmp);

			break;
	}

	freeaddrinfo(ai);
        msg(LOG_INFO, "connect from %s, assigned file is %s",
            peername, client->exportname);
	client->clientname=g_strdup(peername);
	return 0;
}

Project: nbd
File: nbd-server.c

int setup_serve(SERVER *const serve, GError **const gerror) {
	struct addrinfo hints;
	struct addrinfo *ai = NULL;
	gchar *port = NULL;
	int e;
        int retval = -1;

        /* Without this, it's possible that socket == 0, even if it's
         * not initialized at all. And that would be wrong because 0 is
         * totally legal value for properly initialized descriptor. This
         * line is required to ensure that unused/uninitialized
         * descriptors are marked as such (new style configuration
         * case). Currently, servers are being initialized in multiple
         * places, and some of the them do the socket initialization
         * incorrectly. This is the only point common to all code paths,
         * and therefore setting -1 is put here. However, the whole
         * server initialization procedure should be extracted to its
         * own function and all code paths wanting to mess with servers
         * should initialize servers with that function.
         * 
         * TODO: fix server initialization */
        serve->socket = -1;

	if(!(glob_flags & F_OLDSTYLE)) {
		return serve->servename ? 1 : 0;
	}
	memset(&hints,'\0',sizeof(hints));
	hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG | AI_NUMERICSERV;
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_family = serve->socket_family;

	port = g_strdup_printf("%d", serve->port);
	if (!port) {
                g_set_error(gerror, NBDS_ERR, NBDS_ERR_SYS,
                            "failed to open an export socket: "
                            "failed to convert a port number to a string: %s",
                            strerror(errno));
                goto out;
        }

	e = getaddrinfo(serve->listenaddr,port,&hints,&ai);

	g_free(port);

	if(e != 0) {
                g_set_error(gerror, NBDS_ERR, NBDS_ERR_GAI,
                            "failed to open an export socket: "
                            "failed to get address info: %s",
                            gai_strerror(e));
                goto out;
	}

	if(serve->socket_family == AF_UNSPEC)
		serve->socket_family = ai->ai_family;

#ifdef WITH_SDP
	if ((serve->flags) && F_SDP) {
		if (ai->ai_family == AF_INET)
			ai->ai_family = AF_INET_SDP;
		else (ai->ai_family == AF_INET6)
			ai->ai_family = AF_INET6_SDP;
	}
#endif
	if ((serve->socket = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol)) < 0) {
                g_set_error(gerror, NBDS_ERR, NBDS_ERR_SOCKET,
                            "failed to open an export socket: "
                            "failed to create a socket: %s",
                            strerror(errno));
                goto out;
        }

	if (dosockopts(serve->socket, gerror) == -1) {
                g_prefix_error(gerror, "failed to open an export socket: ");
                goto out;
        }

	DEBUG("Waiting for connections... bind, ");
	e = bind(serve->socket, ai->ai_addr, ai->ai_addrlen);
	if (e != 0 && errno != EADDRINUSE) {
                g_set_error(gerror, NBDS_ERR, NBDS_ERR_BIND,
                            "failed to open an export socket: "
                            "failed to bind an address to a socket: %s",
                            strerror(errno));
                goto out;
        }
	DEBUG("listen, ");
	if (listen(serve->socket, 1) < 0) {
                g_set_error(gerror, NBDS_ERR, NBDS_ERR_BIND,
                            "failed to open an export socket: "
                            "failed to start listening on a socket: %s",
                            strerror(errno));
                goto out;
        }

        retval = serve->servename ? 1 : 0;
out:

        if (retval == -1 && serve->socket >= 0) {
                close(serve->socket);
                serve->socket = -1;
        }
	freeaddrinfo (ai);

        return retval;
}

Project: nbd
File: nbd-server.c

void setup_servers(GArray *const servers, const gchar *const modernaddr,
                   const gchar *const modernport) {
	int i;
	struct sigaction sa;
	int want_modern=0;

	for(i=0;i<servers->len;i++) {
                GError *gerror = NULL;
                SERVER *server = &g_array_index(servers, SERVER, i);
                int ret;

		ret = setup_serve(server, &gerror);
                if (ret == -1) {
                        msg(LOG_ERR, "failed to setup servers: %s",
                            gerror->message);
                        g_clear_error(&gerror);
                        exit(EXIT_FAILURE);
                }
                want_modern |= ret;
	}
	if(want_modern) {
                GError *gerror = NULL;
                if (open_modern(modernaddr, modernport, &gerror) == -1) {
                        msg(LOG_ERR, "failed to setup servers: %s",
                            gerror->message);
                        g_clear_error(&gerror);
                        exit(EXIT_FAILURE);
                }
	}
	children=g_hash_table_new_full(g_int_hash, g_int_equal, NULL, destroy_pid_t);

	sa.sa_handler = sigchld_handler;
	sigemptyset(&sa.sa_mask);
	sigaddset(&sa.sa_mask, SIGTERM);
	sa.sa_flags = SA_RESTART;
	if(sigaction(SIGCHLD, &sa, NULL) == -1)
		err("sigaction: %m");

	sa.sa_handler = sigterm_handler;
	sigemptyset(&sa.sa_mask);
	sigaddset(&sa.sa_mask, SIGCHLD);
	sa.sa_flags = SA_RESTART;
	if(sigaction(SIGTERM, &sa, NULL) == -1)
		err("sigaction: %m");

	sa.sa_handler = sighup_handler;
	sigemptyset(&sa.sa_mask);
	sa.sa_flags = SA_RESTART;
	if(sigaction(SIGHUP, &sa, NULL) == -1)
		err("sigaction: %m");
}

Project: nbd
File: nbd-server.c

void setupexport(CLIENT* client) {
	int i;
	off_t laststartoff = 0, lastsize = 0;
	int multifile = (client->server->flags & F_MULTIFILE);
	int temporary = (client->server->flags & F_TEMPORARY) && !multifile;
	int cancreate = (client->server->expected_size) && !multifile;

	client->export = g_array_new(TRUE, TRUE, sizeof(FILE_INFO));

	/* If multi-file, open as many files as we can.
	 * If not, open exactly one file.
	 * Calculate file sizes as we go to get total size. */
	for(i=0; ; i++) {
		FILE_INFO fi;
		gchar *tmpname;
		gchar* error_string;

		if (i)
		  cancreate = 0;
		/* if expected_size is specified, and this is the first file, we can create the file */
		mode_t mode = (client->server->flags & F_READONLY) ?
		  O_RDONLY : (O_RDWR | (cancreate?O_CREAT:0));

		if (temporary) {
			tmpname=g_strdup_printf("%s.%d-XXXXXX", client->exportname, i);
			DEBUG( "Opening %s\n", tmpname );
			fi.fhandle = mkstemp(tmpname);
		} else {
			if(multifile) {
				tmpname=g_strdup_printf("%s.%d", client->exportname, i);
			} else {
				tmpname=g_strdup(client->exportname);
			}
			DEBUG( "Opening %s\n", tmpname );
			fi.fhandle = open(tmpname, mode, 0x600);
			if(fi.fhandle == -1 && mode == O_RDWR) {
				/* Try again because maybe media was read-only */
				fi.fhandle = open(tmpname, O_RDONLY);
				if(fi.fhandle != -1) {
					/* Opening the base file in copyonwrite mode is
					 * okay */
					if(!(client->server->flags & F_COPYONWRITE)) {
						client->server->flags |= F_AUTOREADONLY;
						client->server->flags |= F_READONLY;
					}
				}
			}
		}
		if(fi.fhandle == -1) {
			if(multifile && i>0)
				break;
			error_string=g_strdup_printf(
				"Could not open exported file %s: %%m",
				tmpname);
			err(error_string);
		}

		if (temporary)
			unlink(tmpname); /* File will stick around whilst FD open */

		fi.startoff = laststartoff + lastsize;
		g_array_append_val(client->export, fi);
		g_free(tmpname);

		/* Starting offset and size of this file will be used to
		 * calculate starting offset of next file */
		laststartoff = fi.startoff;
		lastsize = size_autodetect(fi.fhandle);

		/* If we created the file, it will be length zero */
		if (!lastsize && cancreate) {
			assert(!multifile);
			if(ftruncate (fi.fhandle, client->server->expected_size)<0) {
				err("Could not expand file: %m");
			}
			lastsize = client->server->expected_size;
			break; /* don't look for any more files */
		}

		if(!multifile || temporary)
			break;
	}

	/* Set export size to total calculated size */
	client->exportsize = laststartoff + lastsize;

	/* Export size may be overridden */
	if(client->server->expected_size) {
		/* desired size must be <= total calculated size */
		if(client->server->expected_size > client->exportsize) {
			err("Size of exported file is too big\n");
		}

		client->exportsize = client->server->expected_size;
	}

	msg(LOG_INFO, "Size of exported file/device is %llu", (unsigned long long)client->exportsize);
	if(multifile) {
		msg(LOG_INFO, "Total number of files: %d", i);
	}
}

Project: nbd
File: nbd-server.c

void sigchld_handler(int s) {
        int status;
	int* i;
	pid_t pid;

	while((pid=waitpid(-1, &status, WNOHANG)) > 0) {
		if(WIFEXITED(status)) {
			msg(LOG_INFO, "Child exited with %d", WEXITSTATUS(status));
		}
		i=g_hash_table_lookup(children, &pid);
		if(!i) {
			msg(LOG_INFO, "SIGCHLD received for an unknown child with PID %ld", (long)pid);
		} else {
			DEBUG("Removing %d from the list of children", pid);
			g_hash_table_remove(children, &pid);
		}
	}
}

Project: nbd
File: nbd-server.c

static void sighup_handler(const int s G_GNUC_UNUSED) {
        is_sighup_caught = 1;
}

Project: nbd
File: nbd-server.c

void sigterm_handler(int s) {
	g_hash_table_foreach(children, killchild, NULL);
	unlink(pidfname);

	exit(EXIT_SUCCESS);
}

Project: nbd
File: nbd-server.c

void usage() {
	printf("This is nbd-server version " VERSION "\n");
	printf("Usage: [ip:|ip6@]port file_to_export [size][kKmM] [-l authorize_file] [-r] [-m] [-c] [-C configuration file] [-p PID file name] [-o section name] [-M max connections]\n"
	       "\t-r|--read-only\t\tread only\n"
	       "\t-m|--multi-file\t\tmultiple file\n"
	       "\t-c|--copy-on-write\tcopy on write\n"
	       "\t-C|--config-file\tspecify an alternate configuration file\n"
	       "\t-l|--authorize-file\tfile with list of hosts that are allowed to\n\t\t\t\tconnect.\n"
	       "\t-p|--pid-file\t\tspecify a filename to write our PID to\n"
	       "\t-o|--output-config\toutput a config file section for what you\n\t\t\t\tspecified on the command line, with the\n\t\t\t\tspecified section name\n"
	       "\t-M|--max-connections\tspecify the maximum number of opened connections\n\n"
	       "\tif port is set to 0, stdin is used (for running from inetd).\n"
	       "\tif file_to_export contains '%%s', it is substituted with the IP\n"
	       "\t\taddress of the machine trying to connect\n" 
	       "\tif ip is set, it contains the local IP address on which we're listening.\n\tif not, the server will listen on all local IP addresses\n");
	printf("Using configuration file %s\n", CFILE);
}

Project: nbd
File: nbd-server.c

static inline void writeit(int f, void *buf, size_t len) {
	ssize_t res;
	while (len > 0) {
		DEBUG("+");
		if ((res = write(f, buf, len)) <= 0)
			err("Send failed: %m");
		len -= res;
		buf += res;
	}
}