qemu-nbd tool (Anthony Liguori)
git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@4596 c046a42c-6fe2-441c-8c8c-71466251a162
This commit is contained in:
		
							parent
							
								
									e00c1e714e
								
							
						
					
					
						commit
						7a5ca8648b
					
				
							
								
								
									
										14
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								Makefile
									
									
									
									
									
								
							| @ -17,7 +17,7 @@ ifdef CONFIG_STATIC | ||||
| LDFLAGS += -static | ||||
| endif | ||||
| ifdef BUILD_DOCS | ||||
| DOCS=qemu-doc.html qemu-tech.html qemu.1 qemu-img.1 | ||||
| DOCS=qemu-doc.html qemu-tech.html qemu.1 qemu-img.1 qemu-nbd.8 | ||||
| else | ||||
| DOCS= | ||||
| endif | ||||
| @ -159,6 +159,10 @@ qemu-img-%.o: %.c | ||||
| %.o: %.c | ||||
| 	$(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $< | ||||
| 
 | ||||
| qemu-nbd$(EXESUF):  qemu-nbd.o nbd.o qemu-img-block.o \ | ||||
| 		    $(QEMU_IMG_BLOCK_OBJS) | ||||
| 	$(CC) $(LDFLAGS) -o $@ $^ -lz $(LIBS) | ||||
| 
 | ||||
| # dyngen host tool
 | ||||
| dyngen$(EXESUF): dyngen.c | ||||
| 	$(HOST_CC) $(CFLAGS) $(CPPFLAGS) -o $@ $^ | ||||
| @ -191,6 +195,8 @@ install-doc: $(DOCS) | ||||
| ifndef CONFIG_WIN32 | ||||
| 	mkdir -p "$(DESTDIR)$(mandir)/man1" | ||||
| 	$(INSTALL) qemu.1 qemu-img.1 "$(DESTDIR)$(mandir)/man1" | ||||
| 	mkdir -p "$(DESTDIR)$(mandir)/man8" | ||||
| 	$(INSTALL) qemu-nbd.8 "$(DESTDIR)$(mandir)/man8" | ||||
| endif | ||||
| 
 | ||||
| install: all $(if $(BUILD_DOCS),install-doc) | ||||
| @ -244,6 +250,10 @@ qemu-img.1: qemu-img.texi | ||||
| 	$(SRC_PATH)/texi2pod.pl $< qemu-img.pod | ||||
| 	pod2man --section=1 --center=" " --release=" " qemu-img.pod > $@ | ||||
| 
 | ||||
| qemu-nbd.8: qemu-nbd.texi | ||||
| 	$(SRC_PATH)/texi2pod.pl $< qemu-nbd.pod | ||||
| 	pod2man --section=8 --center=" " --release=" " qemu-nbd.pod > $@ | ||||
| 
 | ||||
| info: qemu-doc.info qemu-tech.info | ||||
| 
 | ||||
| dvi: qemu-doc.dvi qemu-tech.dvi | ||||
| @ -296,6 +306,7 @@ tarbin: | ||||
|         $(bindir)/qemu-sh4eb \
 | ||||
|         $(bindir)/qemu-cris \
 | ||||
|         $(bindir)/qemu-img \
 | ||||
|         $(bindir)/qemu-nbd \
 | ||||
| 	$(datadir)/bios.bin \
 | ||||
| 	$(datadir)/vgabios.bin \
 | ||||
| 	$(datadir)/vgabios-cirrus.bin \
 | ||||
| @ -309,6 +320,7 @@ tarbin: | ||||
| 	$(docdir)/qemu-doc.html \
 | ||||
| 	$(docdir)/qemu-tech.html \
 | ||||
| 	$(mandir)/man1/qemu.1 $(mandir)/man1/qemu-img.1 | ||||
| 	$(mandir)/man8/qemu-nbd.8 | ||||
| 
 | ||||
| # Include automatically generated dependency files
 | ||||
| -include $(wildcard *.d audio/*.d slirp/*.d) | ||||
|  | ||||
							
								
								
									
										3
									
								
								configure
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								configure
									
									
									
									
										vendored
									
									
								
							| @ -1105,6 +1105,9 @@ echo "#define CONFIG_UNAME_RELEASE \"$uname_release\"" >> $config_h | ||||
| tools= | ||||
| if test `expr "$target_list" : ".*softmmu.*"` != 0 ; then | ||||
|   tools="qemu-img\$(EXESUF) $tools" | ||||
|   if [ "$linux" = "yes" ] ; then | ||||
|       tools="qemu-nbd\$(EXESUF) $tools" | ||||
|   fi | ||||
| fi | ||||
| echo "TOOLS=$tools" >> $config_mak | ||||
| 
 | ||||
|  | ||||
| @ -126,7 +126,7 @@ static inline int tlb_set_page(CPUState *env1, target_ulong vaddr, | ||||
| #define CODE_GEN_BUFFER_SIZE     (6 * 1024 * 1024) | ||||
| #else | ||||
| /* XXX: make it dynamic on x86 */ | ||||
| #define CODE_GEN_BUFFER_SIZE     (16 * 1024 * 1024) | ||||
| #define CODE_GEN_BUFFER_SIZE     (64 * 1024 * 1024) | ||||
| #endif | ||||
| 
 | ||||
| //#define CODE_GEN_BUFFER_SIZE     (128 * 1024)
 | ||||
|  | ||||
							
								
								
									
										500
									
								
								nbd.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										500
									
								
								nbd.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,500 @@ | ||||
| /*\
 | ||||
|  *  Copyright (C) 2005  Anthony Liguori <anthony@codemonkey.ws> | ||||
|  * | ||||
|  *  Network Block Device | ||||
|  * | ||||
|  *  This program is free software; you can redistribute it and/or modify | ||||
|  *  it under the terms of the GNU General Public License as published by | ||||
|  *  the Free Software Foundation; under version 2 of the License. | ||||
|  * | ||||
|  *  This program is distributed in the hope that it will be useful, | ||||
|  *  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  *  GNU General Public License for more details. | ||||
|  * | ||||
|  *  You should have received a copy of the GNU General Public License | ||||
|  *  along with this program; if not, write to the Free Software | ||||
|  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | ||||
| \*/ | ||||
| 
 | ||||
| #include "nbd.h" | ||||
| 
 | ||||
| #include <errno.h> | ||||
| #include <string.h> | ||||
| #include <sys/ioctl.h> | ||||
| #include <ctype.h> | ||||
| #include <inttypes.h> | ||||
| #include <sys/socket.h> | ||||
| #include <netinet/in.h> | ||||
| #include <netinet/tcp.h> | ||||
| #include <arpa/inet.h> | ||||
| #include <netdb.h> | ||||
| 
 | ||||
| extern int verbose; | ||||
| 
 | ||||
| #define LOG(msg, ...) do { \ | ||||
|     fprintf(stderr, "%s:%s():L%d: " msg "\n", \ | ||||
|             __FILE__, __FUNCTION__, __LINE__, ## __VA_ARGS__); \ | ||||
| } while(0) | ||||
| 
 | ||||
| #define TRACE(msg, ...) do { \ | ||||
|     if (verbose) LOG(msg, ## __VA_ARGS__); \ | ||||
| } while(0) | ||||
| 
 | ||||
| /* This is all part of the "official" NBD API */ | ||||
| 
 | ||||
| #define NBD_REQUEST_MAGIC       0x25609513 | ||||
| #define NBD_REPLY_MAGIC         0x67446698 | ||||
| 
 | ||||
| #define NBD_SET_SOCK            _IO(0xab, 0) | ||||
| #define NBD_SET_BLKSIZE         _IO(0xab, 1) | ||||
| #define NBD_SET_SIZE            _IO(0xab, 2) | ||||
| #define NBD_DO_IT               _IO(0xab, 3) | ||||
| #define NBD_CLEAR_SOCK          _IO(0xab, 4) | ||||
| #define NBD_CLEAR_QUE           _IO(0xab, 5) | ||||
| #define NBD_PRINT_DEBUG	        _IO(0xab, 6) | ||||
| #define NBD_SET_SIZE_BLOCKS	_IO(0xab, 7) | ||||
| #define NBD_DISCONNECT          _IO(0xab, 8) | ||||
| 
 | ||||
| /* That's all folks */ | ||||
| 
 | ||||
| #define read_sync(fd, buffer, size) wr_sync(fd, buffer, size, true) | ||||
| #define write_sync(fd, buffer, size) wr_sync(fd, buffer, size, false) | ||||
| 
 | ||||
| static size_t wr_sync(int fd, void *buffer, size_t size, bool do_read) | ||||
| { | ||||
|     size_t offset = 0; | ||||
| 
 | ||||
|     while (offset < size) { | ||||
|         ssize_t len; | ||||
| 
 | ||||
|         if (do_read) { | ||||
|             len = read(fd, buffer + offset, size - offset); | ||||
|         } else { | ||||
|             len = write(fd, buffer + offset, size - offset); | ||||
|         } | ||||
| 
 | ||||
|         /* recoverable error */ | ||||
|         if (len == -1 && errno == EAGAIN) { | ||||
|             continue; | ||||
|         } | ||||
| 
 | ||||
|         /* eof */ | ||||
|         if (len == 0) { | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         /* unrecoverable error */ | ||||
|         if (len == -1) { | ||||
|             return 0; | ||||
|         } | ||||
| 
 | ||||
|         offset += len; | ||||
|     } | ||||
| 
 | ||||
|     return offset; | ||||
| } | ||||
| 
 | ||||
| static int tcp_socket_outgoing(const char *address, uint16_t port) | ||||
| { | ||||
|     int s; | ||||
|     struct in_addr in; | ||||
|     struct sockaddr_in addr; | ||||
|     int serrno; | ||||
| 
 | ||||
|     s = socket(PF_INET, SOCK_STREAM, 0); | ||||
|     if (s == -1) { | ||||
|         return -1; | ||||
|     } | ||||
| 
 | ||||
|     if (inet_aton(address, &in) == 0) { | ||||
|         struct hostent *ent; | ||||
| 
 | ||||
|         ent = gethostbyname(address); | ||||
|         if (ent == NULL) { | ||||
|             goto error; | ||||
|         } | ||||
| 
 | ||||
|         memcpy(&in, ent->h_addr, sizeof(in)); | ||||
|     } | ||||
| 
 | ||||
|     addr.sin_family = AF_INET; | ||||
|     addr.sin_port = htons(port); | ||||
|     memcpy(&addr.sin_addr.s_addr, &in, sizeof(in)); | ||||
| 
 | ||||
|     if (connect(s, (struct sockaddr *)&addr, sizeof(addr)) == -1) { | ||||
|         goto error; | ||||
|     } | ||||
| 
 | ||||
|     return s; | ||||
| error: | ||||
|     serrno = errno; | ||||
|     close(s); | ||||
|     errno = serrno; | ||||
|     return -1; | ||||
| } | ||||
| 
 | ||||
| int tcp_socket_incoming(const char *address, uint16_t port) | ||||
| { | ||||
|     int s; | ||||
|     struct in_addr in; | ||||
|     struct sockaddr_in addr; | ||||
|     int serrno; | ||||
|     int opt; | ||||
| 
 | ||||
|     s = socket(PF_INET, SOCK_STREAM, 0); | ||||
|     if (s == -1) { | ||||
|         return -1; | ||||
|     } | ||||
| 
 | ||||
|     if (inet_aton(address, &in) == 0) { | ||||
|         struct hostent *ent; | ||||
| 
 | ||||
|         ent = gethostbyname(address); | ||||
|         if (ent == NULL) { | ||||
|             goto error; | ||||
|         } | ||||
| 
 | ||||
|         memcpy(&in, ent->h_addr, sizeof(in)); | ||||
|     } | ||||
| 
 | ||||
|     addr.sin_family = AF_INET; | ||||
|     addr.sin_port = htons(port); | ||||
|     memcpy(&addr.sin_addr.s_addr, &in, sizeof(in)); | ||||
| 
 | ||||
|     opt = 1; | ||||
|     if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1) { | ||||
|         goto error; | ||||
|     } | ||||
| 
 | ||||
|     if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) == -1) { | ||||
|         goto error; | ||||
|     } | ||||
| 
 | ||||
|     if (listen(s, 128) == -1) { | ||||
|         goto error; | ||||
|     } | ||||
| 
 | ||||
|     return s; | ||||
| error: | ||||
|     serrno = errno; | ||||
|     close(s); | ||||
|     errno = serrno; | ||||
|     return -1; | ||||
| } | ||||
| 
 | ||||
| /* Basic flow
 | ||||
| 
 | ||||
|    Server         Client | ||||
| 
 | ||||
|    Negotiate | ||||
|                   Request | ||||
|    Response | ||||
|                   Request | ||||
|    Response | ||||
|                   ... | ||||
|    ... | ||||
|                   Request (type == 2) | ||||
| */ | ||||
| 
 | ||||
| int nbd_negotiate(BlockDriverState *bs, int csock, off_t size) | ||||
| { | ||||
| 	char buf[8 + 8 + 8 + 128]; | ||||
| 
 | ||||
| 	/* Negotiate
 | ||||
| 	   [ 0 ..   7]   passwd   ("NBDMAGIC") | ||||
| 	   [ 8 ..  15]   magic    (0x00420281861253) | ||||
| 	   [16 ..  23]   size | ||||
| 	   [24 .. 151]   reserved (0) | ||||
| 	 */ | ||||
| 
 | ||||
| 	TRACE("Beginning negotiation."); | ||||
| 	memcpy(buf, "NBDMAGIC", 8); | ||||
| 	cpu_to_be64w((uint64_t*)(buf + 8), 0x00420281861253LL); | ||||
| 	cpu_to_be64w((uint64_t*)(buf + 16), size); | ||||
| 	memset(buf + 24, 0, 128); | ||||
| 
 | ||||
| 	if (write_sync(csock, buf, sizeof(buf)) != sizeof(buf)) { | ||||
| 		LOG("write failed"); | ||||
| 		errno = EINVAL; | ||||
| 		return -1; | ||||
| 	} | ||||
| 
 | ||||
| 	TRACE("Negotation succeeded."); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| int nbd_receive_negotiate(int fd, int csock) | ||||
| { | ||||
| 	char buf[8 + 8 + 8 + 128]; | ||||
| 	uint64_t magic; | ||||
| 	off_t size; | ||||
| 	size_t blocksize; | ||||
| 
 | ||||
| 	TRACE("Receiving negotation."); | ||||
| 
 | ||||
| 	if (read_sync(csock, buf, sizeof(buf)) != sizeof(buf)) { | ||||
| 		LOG("read failed"); | ||||
| 		errno = EINVAL; | ||||
| 		return -1; | ||||
| 	} | ||||
| 
 | ||||
| 	magic = be64_to_cpup((uint64_t*)(buf + 8)); | ||||
| 	size = be64_to_cpup((uint64_t*)(buf + 16)); | ||||
| 	blocksize = 1024; | ||||
| 
 | ||||
| 	TRACE("Magic is %c%c%c%c%c%c%c%c", | ||||
| 	      isprint(buf[0]) ? buf[0] : '.', | ||||
| 	      isprint(buf[1]) ? buf[1] : '.', | ||||
| 	      isprint(buf[2]) ? buf[2] : '.', | ||||
| 	      isprint(buf[3]) ? buf[3] : '.', | ||||
| 	      isprint(buf[4]) ? buf[4] : '.', | ||||
| 	      isprint(buf[5]) ? buf[5] : '.', | ||||
| 	      isprint(buf[6]) ? buf[6] : '.', | ||||
| 	      isprint(buf[7]) ? buf[7] : '.'); | ||||
| 	TRACE("Magic is 0x%" PRIx64, magic); | ||||
| 	TRACE("Size is %" PRIu64, size); | ||||
| 
 | ||||
| 	if (memcmp(buf, "NBDMAGIC", 8) != 0) { | ||||
| 		LOG("Invalid magic received"); | ||||
| 		errno = EINVAL; | ||||
| 		return -1; | ||||
| 	} | ||||
| 
 | ||||
| 	TRACE("Checking magic"); | ||||
| 
 | ||||
| 	if (magic != 0x00420281861253LL) { | ||||
| 		LOG("Bad magic received"); | ||||
| 		errno = EINVAL; | ||||
| 		return -1; | ||||
| 	} | ||||
| 
 | ||||
| 	TRACE("Setting block size to %lu", (unsigned long)blocksize); | ||||
| 
 | ||||
| 	if (ioctl(fd, NBD_SET_BLKSIZE, blocksize) == -1) { | ||||
| 		int serrno = errno; | ||||
| 		LOG("Failed setting NBD block size"); | ||||
| 		errno = serrno; | ||||
| 		return -1; | ||||
| 	} | ||||
| 
 | ||||
| 	TRACE("Setting size to %llu block(s)", | ||||
| 	      (unsigned long long)(size / blocksize)); | ||||
| 
 | ||||
| 	if (ioctl(fd, NBD_SET_SIZE_BLOCKS, size / blocksize) == -1) { | ||||
| 		int serrno = errno; | ||||
| 		LOG("Failed setting size (in blocks)"); | ||||
| 		errno = serrno; | ||||
| 		return -1; | ||||
| 	} | ||||
| 
 | ||||
| 	TRACE("Clearing NBD socket"); | ||||
| 
 | ||||
| 	if (ioctl(fd, NBD_CLEAR_SOCK) == -1) { | ||||
| 		int serrno = errno; | ||||
| 		LOG("Failed clearing NBD socket"); | ||||
| 		errno = serrno; | ||||
| 		return -1; | ||||
| 	} | ||||
| 
 | ||||
| 	TRACE("Setting NBD socket"); | ||||
| 
 | ||||
| 	if (ioctl(fd, NBD_SET_SOCK, csock) == -1) { | ||||
| 		int serrno = errno; | ||||
| 		LOG("Failed to set NBD socket"); | ||||
| 		errno = serrno; | ||||
| 		return -1; | ||||
| 	} | ||||
| 
 | ||||
| 	TRACE("Negotiation ended"); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| int nbd_disconnect(int fd) | ||||
| { | ||||
| 	ioctl(fd, NBD_CLEAR_QUE); | ||||
| 	ioctl(fd, NBD_DISCONNECT); | ||||
| 	ioctl(fd, NBD_CLEAR_SOCK); | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| int nbd_client(int fd, int csock) | ||||
| { | ||||
| 	int ret; | ||||
| 	int serrno; | ||||
| 
 | ||||
| 	TRACE("Doing NBD loop"); | ||||
| 
 | ||||
| 	ret = ioctl(fd, NBD_DO_IT); | ||||
| 	serrno = errno; | ||||
| 
 | ||||
| 	TRACE("NBD loop returned %d: %s", ret, strerror(serrno)); | ||||
| 
 | ||||
| 	TRACE("Clearing NBD queue"); | ||||
| 	ioctl(fd, NBD_CLEAR_QUE); | ||||
| 
 | ||||
| 	TRACE("Clearing NBD socket"); | ||||
| 	ioctl(fd, NBD_CLEAR_SOCK); | ||||
| 
 | ||||
| 	errno = serrno; | ||||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
| int nbd_trip(BlockDriverState *bs, int csock, off_t size, uint64_t dev_offset, off_t *offset, bool readonly) | ||||
| { | ||||
| #ifndef _REENTRANT | ||||
| 	static uint8_t data[1024 * 1024]; // keep this off of the stack
 | ||||
| #else | ||||
| 	uint8_t data[1024 * 1024]; | ||||
| #endif | ||||
| 	uint8_t buf[4 + 4 + 8 + 8 + 4]; | ||||
| 	uint32_t magic; | ||||
| 	uint32_t type; | ||||
| 	uint64_t from; | ||||
| 	uint32_t len; | ||||
| 
 | ||||
| 	TRACE("Reading request."); | ||||
| 
 | ||||
| 	if (read_sync(csock, buf, sizeof(buf)) != sizeof(buf)) { | ||||
| 		LOG("read failed"); | ||||
| 		errno = EINVAL; | ||||
| 		return -1; | ||||
| 	} | ||||
| 
 | ||||
| 	/* Request
 | ||||
| 	  [ 0 ..  3]   magic   (NBD_REQUEST_MAGIC) | ||||
| 	  [ 4 ..  7]   type    (0 == READ, 1 == WRITE) | ||||
| 	  [ 8 .. 15]   handle | ||||
| 	  [16 .. 23]   from | ||||
| 	  [24 .. 27]   len | ||||
| 	 */ | ||||
| 
 | ||||
| 	magic = be32_to_cpup((uint32_t*)buf); | ||||
| 	type  = be32_to_cpup((uint32_t*)(buf + 4)); | ||||
| 	from  = be64_to_cpup((uint64_t*)(buf + 16)); | ||||
| 	len   = be32_to_cpup((uint32_t*)(buf + 24)); | ||||
| 
 | ||||
| 	TRACE("Got request: " | ||||
| 	      "{ magic = 0x%x, .type = %d, from = %" PRIu64" , len = %u }", | ||||
| 	      magic, type, from, len); | ||||
| 
 | ||||
| 
 | ||||
| 	if (magic != NBD_REQUEST_MAGIC) { | ||||
| 		LOG("invalid magic (got 0x%x)", magic); | ||||
| 		errno = EINVAL; | ||||
| 		return -1; | ||||
| 	} | ||||
| 
 | ||||
| 	if (len > sizeof(data)) { | ||||
| 		LOG("len (%u) is larger than max len (%lu)", | ||||
| 		    len, sizeof(data)); | ||||
| 		errno = EINVAL; | ||||
| 		return -1; | ||||
| 	} | ||||
| 
 | ||||
| 	if ((from + len) < from) { | ||||
| 		LOG("integer overflow detected! " | ||||
| 		    "you're probably being attacked"); | ||||
| 		errno = EINVAL; | ||||
| 		return -1; | ||||
| 	} | ||||
| 
 | ||||
| 	if ((from + len) > size) { | ||||
| 	        LOG("From: %" PRIu64 ", Len: %u, Size: %" PRIu64 | ||||
| 		    ", Offset: %" PRIu64 "\n", | ||||
| 		     from, len, size, dev_offset); | ||||
| 		LOG("requested operation past EOF--bad client?"); | ||||
| 		errno = EINVAL; | ||||
| 		return -1; | ||||
| 	} | ||||
| 
 | ||||
| 	/* Reply
 | ||||
| 	 [ 0 ..  3]    magic   (NBD_REPLY_MAGIC) | ||||
| 	 [ 4 ..  7]    error   (0 == no error) | ||||
|          [ 7 .. 15]    handle | ||||
| 	 */ | ||||
| 	cpu_to_be32w((uint32_t*)buf, NBD_REPLY_MAGIC); | ||||
| 	cpu_to_be32w((uint32_t*)(buf + 4), 0); | ||||
| 
 | ||||
| 	TRACE("Decoding type"); | ||||
| 
 | ||||
| 	switch (type) { | ||||
| 	case 0: | ||||
| 		TRACE("Request type is READ"); | ||||
| 
 | ||||
| 		if (bdrv_read(bs, (from + dev_offset) / 512, data, len / 512) == -1) { | ||||
| 			LOG("reading from file failed"); | ||||
| 			errno = EINVAL; | ||||
| 			return -1; | ||||
| 		} | ||||
| 		*offset += len; | ||||
| 
 | ||||
| 		TRACE("Read %u byte(s)", len); | ||||
| 
 | ||||
| 		TRACE("Sending OK response"); | ||||
| 
 | ||||
| 		if (write_sync(csock, buf, 16) != 16) { | ||||
| 			LOG("writing to socket failed"); | ||||
| 			errno = EINVAL; | ||||
| 			return -1; | ||||
| 		} | ||||
| 
 | ||||
| 		TRACE("Sending data to client"); | ||||
| 
 | ||||
| 		if (write_sync(csock, data, len) != len) { | ||||
| 			LOG("writing to socket failed"); | ||||
| 			errno = EINVAL; | ||||
| 			return -1; | ||||
| 		} | ||||
| 		break; | ||||
| 	case 1: | ||||
| 		TRACE("Request type is WRITE"); | ||||
| 
 | ||||
| 		TRACE("Reading %u byte(s)", len); | ||||
| 
 | ||||
| 		if (read_sync(csock, data, len) != len) { | ||||
| 			LOG("reading from socket failed"); | ||||
| 			errno = EINVAL; | ||||
| 			return -1; | ||||
| 		} | ||||
| 
 | ||||
| 		if (readonly) { | ||||
| 			TRACE("Server is read-only, return error"); | ||||
| 
 | ||||
| 			cpu_to_be32w((uint32_t*)(buf + 4), 1); | ||||
| 		} else { | ||||
| 			TRACE("Writing to device"); | ||||
| 
 | ||||
| 			if (bdrv_write(bs, (from + dev_offset) / 512, data, len / 512) == -1) { | ||||
| 				LOG("writing to file failed"); | ||||
| 				errno = EINVAL; | ||||
| 				return -1; | ||||
| 			} | ||||
| 
 | ||||
| 			*offset += len; | ||||
| 		} | ||||
| 
 | ||||
| 		TRACE("Sending response to client"); | ||||
| 
 | ||||
| 		if (write_sync(csock, buf, 16) != 16) { | ||||
| 			LOG("writing to socket failed"); | ||||
| 			errno = EINVAL; | ||||
| 			return -1; | ||||
| 		} | ||||
| 		break; | ||||
| 	case 2: | ||||
| 		TRACE("Request type is DISCONNECT"); | ||||
| 		errno = 0; | ||||
| 		return 1; | ||||
| 	default: | ||||
| 		LOG("invalid request type (%u) received", type); | ||||
| 		errno = EINVAL; | ||||
| 		return -1; | ||||
| 	} | ||||
| 
 | ||||
| 	TRACE("Request/Reply complete"); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
							
								
								
									
										37
									
								
								nbd.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								nbd.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,37 @@ | ||||
| /*\
 | ||||
|  *  Copyright (C) 2005  Anthony Liguori <anthony@codemonkey.ws> | ||||
|  * | ||||
|  *  Network Block Device | ||||
|  * | ||||
|  *  This program is free software; you can redistribute it and/or modify | ||||
|  *  it under the terms of the GNU General Public License as published by | ||||
|  *  the Free Software Foundation; under version 2 of the License. | ||||
|  * | ||||
|  *  This program is distributed in the hope that it will be useful, | ||||
|  *  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  *  GNU General Public License for more details. | ||||
|  * | ||||
|  *  You should have received a copy of the GNU General Public License | ||||
|  *  along with this program; if not, write to the Free Software | ||||
|  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | ||||
| \*/ | ||||
| 
 | ||||
| #ifndef NBD_H | ||||
| #define NBD_H | ||||
| 
 | ||||
| #include <sys/types.h> | ||||
| #include <stdbool.h> | ||||
| 
 | ||||
| #include <qemu-common.h> | ||||
| #include "block_int.h" | ||||
| 
 | ||||
| int tcp_socket_incoming(const char *address, uint16_t port); | ||||
| 
 | ||||
| int nbd_negotiate(BlockDriverState *bs, int csock, off_t size); | ||||
| int nbd_receive_negotiate(int fd, int csock); | ||||
| int nbd_trip(BlockDriverState *bs, int csock, off_t size, uint64_t dev_offset, off_t *offset, bool readonly); | ||||
| int nbd_client(int fd, int csock); | ||||
| int nbd_disconnect(int fd); | ||||
| 
 | ||||
| #endif | ||||
							
								
								
									
										275
									
								
								qemu-nbd.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										275
									
								
								qemu-nbd.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,275 @@ | ||||
| /*\
 | ||||
|  *  Copyright (C) 2005  Anthony Liguori <anthony@codemonkey.ws> | ||||
|  * | ||||
|  *  Network Block Device | ||||
|  * | ||||
|  *  This program is free software; you can redistribute it and/or modify | ||||
|  *  it under the terms of the GNU General Public License as published by | ||||
|  *  the Free Software Foundation; under version 2 of the License. | ||||
|  * | ||||
|  *  This program is distributed in the hope that it will be useful, | ||||
|  *  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  *  GNU General Public License for more details. | ||||
|  * | ||||
|  *  You should have received a copy of the GNU General Public License | ||||
|  *  along with this program; if not, write to the Free Software | ||||
|  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | ||||
|  */ | ||||
| 
 | ||||
| #include <qemu-common.h> | ||||
| #include "block_int.h" | ||||
| #include "nbd.h" | ||||
| 
 | ||||
| #include <malloc.h> | ||||
| #include <stdarg.h> | ||||
| #include <stdio.h> | ||||
| #include <getopt.h> | ||||
| #include <err.h> | ||||
| #include <sys/socket.h> | ||||
| #include <netinet/in.h> | ||||
| #include <netinet/tcp.h> | ||||
| #include <arpa/inet.h> | ||||
| 
 | ||||
| int verbose; | ||||
| 
 | ||||
| static void usage(const char *name) | ||||
| { | ||||
|     printf( | ||||
| "Usage: %s [OPTIONS] FILE\n" | ||||
| "QEMU Disk Network Block Device Server\n" | ||||
| "\n" | ||||
| "  -p, --port=PORT      port to listen on (default `1024')\n" | ||||
| "  -o, --offset=OFFSET  offset into the image\n" | ||||
| "  -b, --bind=IFACE     interface to bind to (default `0.0.0.0')\n" | ||||
| "  -r, --read-only      export read-only\n" | ||||
| "  -P, --partition=NUM  only expose partition NUM\n" | ||||
| "  -v, --verbose        display extra debugging information\n" | ||||
| "  -h, --help           display this help and exit\n" | ||||
| "  -V, --version        output version information and exit\n" | ||||
| "\n" | ||||
| "Report bugs to <anthony@codemonkey.ws>\n" | ||||
|     , name); | ||||
| } | ||||
| 
 | ||||
| static void version(const char *name) | ||||
| { | ||||
|     printf( | ||||
| "qemu-nbd version 0.0.1\n" | ||||
| "Written by Anthony Liguori.\n" | ||||
| "\n" | ||||
| "Copyright (C) 2006 Anthony Liguori <anthony@codemonkey.ws>.\n" | ||||
| "This is free software; see the source for copying conditions.  There is NO\n" | ||||
| "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n" | ||||
|     ); | ||||
| } | ||||
| 
 | ||||
| struct partition_record | ||||
| { | ||||
|     uint8_t bootable; | ||||
|     uint8_t start_head; | ||||
|     uint32_t start_cylinder; | ||||
|     uint8_t start_sector; | ||||
|     uint8_t system; | ||||
|     uint8_t end_head; | ||||
|     uint8_t end_cylinder; | ||||
|     uint8_t end_sector; | ||||
|     uint32_t start_sector_abs; | ||||
|     uint32_t nb_sectors_abs; | ||||
| }; | ||||
| 
 | ||||
| static void read_partition(uint8_t *p, struct partition_record *r) | ||||
| { | ||||
|     r->bootable = p[0]; | ||||
|     r->start_head = p[1]; | ||||
|     r->start_cylinder = p[3] | ((p[2] << 2) & 0x0300); | ||||
|     r->start_sector = p[2] & 0x3f; | ||||
|     r->system = p[4]; | ||||
|     r->end_head = p[5]; | ||||
|     r->end_cylinder = p[7] | ((p[6] << 2) & 0x300); | ||||
|     r->end_sector = p[6] & 0x3f; | ||||
|     r->start_sector_abs = p[8] | p[9] << 8 | p[10] << 16 | p[11] << 24; | ||||
|     r->nb_sectors_abs = p[12] | p[13] << 8 | p[14] << 16 | p[15] << 24; | ||||
| } | ||||
| 
 | ||||
| static int find_partition(BlockDriverState *bs, int partition, | ||||
|                           off_t *offset, off_t *size) | ||||
| { | ||||
|     struct partition_record mbr[4]; | ||||
|     uint8_t data[512]; | ||||
|     int i; | ||||
|     int ext_partnum = 4; | ||||
| 
 | ||||
|     if (bdrv_read(bs, 0, data, 1)) | ||||
|         errx(EINVAL, "error while reading"); | ||||
| 
 | ||||
|     if (data[510] != 0x55 || data[511] != 0xaa) { | ||||
|         errno = -EINVAL; | ||||
|         return -1; | ||||
|     } | ||||
| 
 | ||||
|     for (i = 0; i < 4; i++) { | ||||
|         read_partition(&data[446 + 16 * i], &mbr[i]); | ||||
| 
 | ||||
|         if (!mbr[i].nb_sectors_abs) | ||||
|             continue; | ||||
| 
 | ||||
|         if (mbr[i].system == 0xF || mbr[i].system == 0x5) { | ||||
|             struct partition_record ext[4]; | ||||
|             uint8_t data1[512]; | ||||
|             int j; | ||||
| 
 | ||||
|             if (bdrv_read(bs, mbr[i].start_sector_abs, data1, 1)) | ||||
|                 errx(EINVAL, "error while reading"); | ||||
| 
 | ||||
|             for (j = 0; j < 4; j++) { | ||||
|                 read_partition(&data1[446 + 16 * j], &ext[j]); | ||||
|                 if (!ext[j].nb_sectors_abs) | ||||
|                     continue; | ||||
| 
 | ||||
|                 if ((ext_partnum + j + 1) == partition) { | ||||
|                     *offset = (uint64_t)ext[j].start_sector_abs << 9; | ||||
|                     *size = (uint64_t)ext[j].nb_sectors_abs << 9; | ||||
|                     return 0; | ||||
|                 } | ||||
|             } | ||||
|             ext_partnum += 4; | ||||
|         } else if ((i + 1) == partition) { | ||||
|             *offset = (uint64_t)mbr[i].start_sector_abs << 9; | ||||
|             *size = (uint64_t)mbr[i].nb_sectors_abs << 9; | ||||
|             return 0; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     errno = -ENOENT; | ||||
|     return -1; | ||||
| } | ||||
| 
 | ||||
| int main(int argc, char **argv) | ||||
| { | ||||
|     BlockDriverState *bs; | ||||
|     off_t dev_offset = 0; | ||||
|     off_t offset = 0; | ||||
|     bool readonly = false; | ||||
|     const char *bindto = "0.0.0.0"; | ||||
|     int port = 1024; | ||||
|     int sock, csock; | ||||
|     struct sockaddr_in addr; | ||||
|     socklen_t addr_len = sizeof(addr); | ||||
|     off_t fd_size; | ||||
|     const char *sopt = "hVbo:p:rsP:v"; | ||||
|     struct option lopt[] = { | ||||
|         { "help", 0, 0, 'h' }, | ||||
|         { "version", 0, 0, 'V' }, | ||||
|         { "bind", 1, 0, 'b' }, | ||||
|         { "port", 1, 0, 'p' }, | ||||
|         { "offset", 1, 0, 'o' }, | ||||
|         { "read-only", 0, 0, 'r' }, | ||||
|         { "partition", 1, 0, 'P' }, | ||||
|         { "snapshot", 0, 0, 's' }, | ||||
|         { "verbose", 0, 0, 'v' }, | ||||
|     }; | ||||
|     int ch; | ||||
|     int opt_ind = 0; | ||||
|     int li; | ||||
|     char *end; | ||||
|     bool snapshot = false; | ||||
|     int partition = -1; | ||||
| 
 | ||||
|     while ((ch = getopt_long(argc, argv, sopt, lopt, &opt_ind)) != -1) { | ||||
|         switch (ch) { | ||||
|         case 's': | ||||
|             snapshot = true; | ||||
|             break; | ||||
|         case 'b': | ||||
|             bindto = optarg; | ||||
|             break; | ||||
|         case 'p': | ||||
|             li = strtol(optarg, &end, 0); | ||||
|             if (*end) { | ||||
|                 errx(EINVAL, "Invalid port `%s'", optarg); | ||||
|             } | ||||
|             if (li < 1 || li > 65535) { | ||||
|                 errx(EINVAL, "Port out of range `%s'", optarg); | ||||
|             } | ||||
|             port = (uint16_t)li; | ||||
|             break; | ||||
|         case 'o': | ||||
|                 dev_offset = strtoll (optarg, &end, 0); | ||||
|             if (*end) { | ||||
|                 errx(EINVAL, "Invalid offset `%s'", optarg); | ||||
|             } | ||||
|             if (dev_offset < 0) { | ||||
|                 errx(EINVAL, "Offset must be positive `%s'", optarg); | ||||
|             } | ||||
|             break; | ||||
|         case 'r': | ||||
|             readonly = true; | ||||
|             break; | ||||
|         case 'P': | ||||
|             partition = strtol(optarg, &end, 0); | ||||
|             if (*end) | ||||
|                 errx(EINVAL, "Invalid partition `%s'", optarg); | ||||
|             if (partition < 1 || partition > 8) | ||||
|                 errx(EINVAL, "Invalid partition %d", partition); | ||||
|             break; | ||||
|         case 'v': | ||||
|             verbose = 1; | ||||
|             break; | ||||
|         case 'V': | ||||
|             version(argv[0]); | ||||
|             exit(0); | ||||
|             break; | ||||
|         case 'h': | ||||
|             usage(argv[0]); | ||||
|             exit(0); | ||||
|             break; | ||||
|         case '?': | ||||
|             errx(EINVAL, "Try `%s --help' for more information.", | ||||
|                  argv[0]); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if ((argc - optind) != 1) { | ||||
|         errx(EINVAL, "Invalid number of argument.\n" | ||||
|              "Try `%s --help' for more information.", | ||||
|              argv[0]); | ||||
|     } | ||||
| 
 | ||||
|     bdrv_init(); | ||||
| 
 | ||||
|     bs = bdrv_new("hda"); | ||||
|     if (bs == NULL) | ||||
|         return 1; | ||||
| 
 | ||||
|     if (bdrv_open(bs, argv[optind], snapshot) == -1) | ||||
|         return 1; | ||||
| 
 | ||||
|     fd_size = bs->total_sectors * 512; | ||||
| 
 | ||||
|     if (partition != -1 && | ||||
|         find_partition(bs, partition, &dev_offset, &fd_size)) | ||||
|         errx(errno, "Could not find partition %d", partition); | ||||
| 
 | ||||
|     sock = tcp_socket_incoming(bindto, port); | ||||
|     if (sock == -1) | ||||
|         return 1; | ||||
| 
 | ||||
|     csock = accept(sock, | ||||
|                (struct sockaddr *)&addr, | ||||
|                &addr_len); | ||||
|     if (csock == -1) | ||||
|         return 1; | ||||
| 
 | ||||
|     /* new fd_size is calculated by find_partition */ | ||||
|     if (nbd_negotiate(bs, csock, fd_size) == -1) | ||||
|         return 1; | ||||
| 
 | ||||
|     while (nbd_trip(bs, csock, fd_size, dev_offset, &offset, readonly) == 0); | ||||
| 
 | ||||
|     close(csock); | ||||
|     close(sock); | ||||
|     bdrv_close(bs); | ||||
| 
 | ||||
|     return 0; | ||||
| } | ||||
							
								
								
									
										52
									
								
								qemu-nbd.texi
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								qemu-nbd.texi
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,52 @@ | ||||
| @example | ||||
| @c man begin SYNOPSIS | ||||
| usage: qemu-nbd [OPTION]...  @var{filename} | ||||
| @c man end | ||||
| @end example | ||||
| 
 | ||||
| @c man begin DESCRIPTION | ||||
| 
 | ||||
| Export Qemu disk image using NBD protocol. | ||||
| 
 | ||||
| @c man end | ||||
| 
 | ||||
| @c man begin OPTIONS | ||||
| @table @var | ||||
| @item filename | ||||
|  is a disk image filename | ||||
| @item -p, --port=PORT | ||||
|   port to listen on (default `1024') | ||||
| @item -o, --offset=OFFSET | ||||
|   offset into the image | ||||
| @item -b, --bind=IFACE | ||||
|   interface to bind to (default `0.0.0.0') | ||||
| @item -r, --read-only | ||||
|   export read-only | ||||
| @item -P, --partition=NUM | ||||
|   only expose partition NUM | ||||
| @item -v, --verbose | ||||
|   display extra debugging information | ||||
| @item -h, --help | ||||
|   display this help and exit | ||||
| @item -V, --version | ||||
|   output version information and exit | ||||
| @end table | ||||
| 
 | ||||
| @c man end | ||||
| 
 | ||||
| @ignore | ||||
| 
 | ||||
| @setfilename qemu-nbd | ||||
| @settitle QEMU Disk Network Block Device Server | ||||
| 
 | ||||
| @c man begin AUTHOR | ||||
| Copyright (C) 2006 Anthony Liguori <anthony@codemonkey.ws>. | ||||
| This is free software; see the source for copying conditions.  There is NO | ||||
| warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | ||||
| @c man end | ||||
| 
 | ||||
| @c man begin SEEALSO | ||||
| qemu-img(1) | ||||
| @c man end | ||||
| 
 | ||||
| @end ignore | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 bellard
						bellard