diff --git a/.hgignore b/.hgignore index 9cec915..1b4b5a4 100644 --- a/.hgignore +++ b/.hgignore @@ -1,3 +1,5 @@ .o$ ~$ ^flexnbd$ +^build/ +^pkg/ diff --git a/Rakefile b/Rakefile index a8d47cc..beebc72 100644 --- a/Rakefile +++ b/Rakefile @@ -1,7 +1,9 @@ DEBUG = true -SOURCES = %w( flexnbd ioutil readwrite serve util parse control remote ) -OBJECTS = SOURCES.map { |s| "#{s}.o" } +ALL_SOURCES =FileList['src/*'] +SOURCES = ALL_SOURCES.select { |c| c =~ /\.c$/ } +OBJECTS = SOURCES.pathmap( "%{^src,build}X.o" ) + LIBS = %w( pthread ) CCFLAGS = %w( -Wall ) LDFLAGS = [] @@ -15,52 +17,66 @@ if DEBUG end desc "Build flexnbd binary" -rule 'default' => 'flexnbd' +task :flexnbd => 'build/flexnbd' namespace "test" do desc "Run all tests" task 'run' => ["unit", "scenarios"] - + desc "Build C tests" - task 'build' => TEST_MODULES.map { |n| "tests/check_#{n}" } - + task 'build' => TEST_MODULES.map { |n| "build/tests/check_#{n}" } + desc "Run C tests" task 'unit' => 'build' do TEST_MODULES.each do |n| ENV['EF_DISABLE_BANNER'] = '1' - sh "./tests/check_#{n}" + sh "build/tests/check_#{n}" end end - + desc "Run NBD test scenarios" task 'scenarios' => 'flexnbd' do sh "cd tests; ruby nbd_scenarios" end end + def gcc_link(target, objects) + FileUtils.mkdir_p File.dirname( target ) + sh "gcc #{LDFLAGS.join(' ')} "+ LIBS.map { |l| "-l#{l}" }.join(" ")+ + " -I src" + " -o #{target} "+ objects.join(" ") end -rule 'flexnbd' => OBJECTS do |t| +rule 'build/flexnbd' => OBJECTS do |t| gcc_link(t.name, t.sources) end -rule(/tests\/check_[a-z]+$/ => [ proc { |target| [target+".o", "util.o"] } ]) do |t| - gcc_link(t.name, t.sources + [LIBCHECK]) +TEST_MODULES.each do |m| + deps = ["tests/check_#{m}.c", "build/util.o"] + maybe_obj_name = "build/#{m}.o" + + deps << maybe_obj_name if OBJECTS.include?( maybe_obj_name ) + + file "build/tests/check_#{m}" => deps do |t| + gcc_link(t.name, deps + [LIBCHECK]) + end end -rule '.o' => '.c' do |t| - sh "gcc -I. -c #{CCFLAGS.join(' ')} -o #{t.name} #{t.source} " + +OBJECTS.zip( SOURCES ).each do |o,c| + file o => c do |t| + FileUtils.mkdir_p File.dirname( o ) + sh "gcc -Isrc -c #{CCFLAGS.join(' ')} -o #{o} #{c} " + end end desc "Remove all build targets, binaries and temporary files" rule 'clean' do - sh "rm -f *~ flexnbd " + ( - OBJECTS + + sh "rm -rf *~ build " + ( TEST_MODULES.map { |n| ["tests/check_#{n}", "tests/check_#{n}.o"] }.flatten ). join(" ") diff --git a/flexnbd.c b/flexnbd.c deleted file mode 100644 index 9029eb3..0000000 --- a/flexnbd.c +++ /dev/null @@ -1,222 +0,0 @@ -/* FlexNBD server (C) Bytemark Hosting 2012 - - 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, either version 3 of the License, or - (at your option) any later version. - - 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, see . -*/ - -/** main() function for parsing and dispatching commands. Each mode has - * a corresponding structure which is filled in and passed to a do_ function - * elsewhere in the program. - */ - -#include "params.h" -#include "util.h" - -#include -#include -#include - -#include -#include -#include -#include -#include - -void syntax() -{ - fprintf(stderr, - "Syntax: flexnbd serve \\\n" - " [full path to control socket] \\\n" - " [allowed connection addresses ...]\n" - " flexnbd read > data\n" - " flexnbd write < data\n" - " flexnbd write \n" - " flexnbd acl [allowed connection addresses ...]\n" - " flexnbd mirror \n" - " [bytes per second] [proxy|nothing|exit]\n" - " flexnbd status \n" - ); - exit(1); -} - -void params_serve( - struct mode_serve_params* out, - char* s_ip_address, - char* s_port, - char* s_file, - int acl_entries, - char** s_acl_entries /* first may actually be path to control socket */ -) -{ - int parsed; - - out->tcp_backlog = 10; /* does this need to be settable? */ - - if (s_ip_address == NULL) - SERVER_ERROR("No IP address supplied"); - if (s_port == NULL) - SERVER_ERROR("No port number supplied"); - if (s_file == NULL) - SERVER_ERROR("No filename supplied"); - - if (parse_ip_to_sockaddr(&out->bind_to.generic, s_ip_address) == 0) - SERVER_ERROR("Couldn't parse server address '%s' (use 0 if " - "you want to bind to all IPs)", s_ip_address); - - out->control_socket_name = NULL; - - if (acl_entries > 0 && s_acl_entries[0][0] == '/') { - out->control_socket_name = s_acl_entries[0]; - s_acl_entries++; - acl_entries--; - } - - out->acl_entries = acl_entries; - parsed = parse_acl(&out->acl, acl_entries, s_acl_entries); - if (parsed != acl_entries) - SERVER_ERROR("Bad ACL entry '%s'", s_acl_entries[parsed]); - - out->bind_to.v4.sin_port = atoi(s_port); - if (out->bind_to.v4.sin_port < 0 || out->bind_to.v4.sin_port > 65535) - SERVER_ERROR("Port number must be >= 0 and <= 65535"); - out->bind_to.v4.sin_port = htobe16(out->bind_to.v4.sin_port); - - out->filename = s_file; - out->filename_incomplete = xmalloc(strlen(s_file)+11); - strcpy(out->filename_incomplete, s_file); - strcpy(out->filename_incomplete + strlen(s_file), ".INCOMPLETE"); -} - -void params_readwrite( - int write_not_read, - struct mode_readwrite_params* out, - char* s_ip_address, - char* s_port, - char* s_from, - char* s_length_or_filename -) -{ - if (s_ip_address == NULL) - SERVER_ERROR("No IP address supplied"); - if (s_port == NULL) - SERVER_ERROR("No port number supplied"); - if (s_from == NULL) - SERVER_ERROR("No from supplied"); - if (s_length_or_filename == NULL) - SERVER_ERROR("No length supplied"); - - if (parse_ip_to_sockaddr(&out->connect_to.generic, s_ip_address) == 0) - SERVER_ERROR("Couldn't parse connection address '%s'", - s_ip_address); - - /* FIXME: duplicated from above */ - out->connect_to.v4.sin_port = atoi(s_port); - if (out->connect_to.v4.sin_port < 0 || out->connect_to.v4.sin_port > 65535) - SERVER_ERROR("Port number must be >= 0 and <= 65535"); - out->connect_to.v4.sin_port = htobe16(out->connect_to.v4.sin_port); - - out->from = atol(s_from); - - if (write_not_read) { - if (s_length_or_filename[0]-48 < 10) { - out->len = atol(s_length_or_filename); - out->data_fd = 0; - } - else { - out->data_fd = open( - s_length_or_filename, O_RDONLY); - SERVER_ERROR_ON_FAILURE(out->data_fd, - "Couldn't open %s", s_length_or_filename); - out->len = lseek64(out->data_fd, 0, SEEK_END); - SERVER_ERROR_ON_FAILURE(out->len, - "Couldn't find length of %s", s_length_or_filename); - SERVER_ERROR_ON_FAILURE( - lseek64(out->data_fd, 0, SEEK_SET), - "Couldn't rewind %s", s_length_or_filename - ); - } - } - else { - out->len = atol(s_length_or_filename); - out->data_fd = 1; - } -} - -void do_serve(struct mode_serve_params* params); -void do_read(struct mode_readwrite_params* params); -void do_write(struct mode_readwrite_params* params); -void do_remote_command(char* command, char* mode, int argc, char** argv); - -union mode_params { - struct mode_serve_params serve; - struct mode_readwrite_params readwrite; -}; - -void mode(char* mode, int argc, char **argv) -{ - union mode_params params; - memset(¶ms, 0, sizeof(params)); - - if (strcmp(mode, "serve") == 0) { - if (argc >= 3) { - params_serve(¶ms.serve, argv[0], argv[1], argv[2], argc-3, argv+3); - do_serve(¶ms.serve); - } - else { - syntax(); - } - } - else if (strcmp(mode, "read") == 0 ) { - if (argc == 4) { - params_readwrite(0, ¶ms.readwrite, argv[0], argv[1], argv[2], argv[3]); - do_read(¶ms.readwrite); - } - else { - syntax(); - } - } - else if (strcmp(mode, "write") == 0 ) { - if (argc == 4) { - params_readwrite(1, ¶ms.readwrite, argv[0], argv[1], argv[2], argv[3]); - do_write(¶ms.readwrite); - } - else { - syntax(); - } - } - else if (strcmp(mode, "acl") == 0 || strcmp(mode, "mirror") == 0 || strcmp(mode, "status") == 0) { - if (argc >= 1) { - do_remote_command(mode, argv[0], argc-1, argv+1); - } - else { - syntax(); - } - } - else { - syntax(); - } - exit(0); -} - -int main(int argc, char** argv) -{ - signal(SIGPIPE, SIG_IGN); /* calls to splice() unhelpfully throw this */ - error_init(); - - if (argc < 2) - syntax(); - mode(argv[1], argc-2, argv+2); /* never returns */ - - return 0; -} - diff --git a/bitset.h b/src/bitset.h similarity index 100% rename from bitset.h rename to src/bitset.h diff --git a/control.c b/src/control.c similarity index 100% rename from control.c rename to src/control.c diff --git a/control.h b/src/control.h similarity index 100% rename from control.h rename to src/control.h diff --git a/src/flexnbd.c b/src/flexnbd.c new file mode 100644 index 0000000..1acb911 --- /dev/null +++ b/src/flexnbd.c @@ -0,0 +1,511 @@ +/* FlexNBD server (C) Bytemark Hosting 2012 + + 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, either version 3 of the License, or + (at your option) any later version. + + 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, see . +*/ + +/** main() function for parsing and dispatching commands. Each mode has + * a corresponding structure which is filled in and passed to a do_ function + * elsewhere in the program. + */ + +#include "params.h" +#include "util.h" + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include "options.h" + + +void exit_err( char *msg ) +{ + fprintf( stderr, msg ); + exit( 1 ); +} + +void params_serve( + struct mode_serve_params* out, + char* s_ip_address, + char* s_port, + char* s_file, + char *s_ctrl_sock, + int acl_entries, + char** s_acl_entries /* first may actually be path to control socket */ +) +{ + int parsed; + + out->tcp_backlog = 10; /* does this need to be settable? */ + + if (s_ip_address == NULL) + SERVER_ERROR("No IP address supplied"); + if (s_port == NULL) + SERVER_ERROR("No port number supplied"); + if (s_file == NULL) + SERVER_ERROR("No filename supplied"); + + if (parse_ip_to_sockaddr(&out->bind_to.generic, s_ip_address) == 0) + SERVER_ERROR("Couldn't parse server address '%s' (use 0 if " + "you want to bind to all IPs)", s_ip_address); + + /* control_socket_name is optional. It just won't get created if + * we pass NULL. */ + out->control_socket_name = s_ctrl_sock; + + out->acl_entries = acl_entries; + parsed = parse_acl(&out->acl, acl_entries, s_acl_entries); + if (parsed != acl_entries) + SERVER_ERROR("Bad ACL entry '%s'", s_acl_entries[parsed]); + + out->bind_to.v4.sin_port = atoi(s_port); + if (out->bind_to.v4.sin_port < 0 || out->bind_to.v4.sin_port > 65535) + SERVER_ERROR("Port number must be >= 0 and <= 65535"); + out->bind_to.v4.sin_port = htobe16(out->bind_to.v4.sin_port); + + out->filename = s_file; + out->filename_incomplete = xmalloc(strlen(s_file)+11); + strcpy(out->filename_incomplete, s_file); + strcpy(out->filename_incomplete + strlen(s_file), ".INCOMPLETE"); +} + +/* TODO: Separate this function. + * It should be: + * params_read( struct mode_readwrite_params* out, + * char *s_ip_address, + * char *s_port, + * char *s_from, + * char *s_length ) + * params_write( struct mode_readwrite_params* out, + * char *s_ip_address, + * char *s_port, + * char *s_from, + * char *s_length, + * char *s_filename ) + */ +void params_readwrite( + int write_not_read, + struct mode_readwrite_params* out, + char* s_ip_address, + char* s_port, + char* s_from, + char* s_length_or_filename +) +{ + if (s_ip_address == NULL) + SERVER_ERROR("No IP address supplied"); + if (s_port == NULL) + SERVER_ERROR("No port number supplied"); + if (s_from == NULL) + SERVER_ERROR("No from supplied"); + if (s_length_or_filename == NULL) + SERVER_ERROR("No length supplied"); + + if (parse_ip_to_sockaddr(&out->connect_to.generic, s_ip_address) == 0) + SERVER_ERROR("Couldn't parse connection address '%s'", + s_ip_address); + + /* FIXME: duplicated from above */ + out->connect_to.v4.sin_port = atoi(s_port); + if (out->connect_to.v4.sin_port < 0 || out->connect_to.v4.sin_port > 65535) + SERVER_ERROR("Port number must be >= 0 and <= 65535"); + out->connect_to.v4.sin_port = htobe16(out->connect_to.v4.sin_port); + + out->from = atol(s_from); + + if (write_not_read) { + if (s_length_or_filename[0]-48 < 10) { + out->len = atol(s_length_or_filename); + out->data_fd = 0; + } + else { + out->data_fd = open( + s_length_or_filename, O_RDONLY); + SERVER_ERROR_ON_FAILURE(out->data_fd, + "Couldn't open %s", s_length_or_filename); + out->len = lseek64(out->data_fd, 0, SEEK_END); + SERVER_ERROR_ON_FAILURE(out->len, + "Couldn't find length of %s", s_length_or_filename); + SERVER_ERROR_ON_FAILURE( + lseek64(out->data_fd, 0, SEEK_SET), + "Couldn't rewind %s", s_length_or_filename + ); + } + } + else { + out->len = atol(s_length_or_filename); + out->data_fd = 1; + } +} + +void do_serve(struct mode_serve_params* params); +void do_read(struct mode_readwrite_params* params); +void do_write(struct mode_readwrite_params* params); +void do_remote_command(char* command, char* mode, int argc, char** argv); + +void read_serve_param( int c, char **ip_addr, char **ip_port, char **file, char **sock ) +{ + switch(c){ + case 'h': + fprintf(stdout, serve_help_text ); + exit( 0 ); + break; + case 'l': + *ip_addr = optarg; + break; + case 'p': + *ip_port = optarg; + break; + case 'f': + *file = optarg; + break; + case 's': + *sock = optarg; + break; + default: + exit_err( serve_help_text ); + break; + } +} + + +void read_readwrite_param( int c, char **ip_addr, char **ip_port, char **from, char **size) +{ + switch(c){ + case 'h': + fprintf(stdout, read_help_text ); + exit( 0 ); + break; + case 'l': + *ip_addr = optarg; + break; + case 'p': + *ip_port = optarg; + break; + case 'F': + *from = optarg; + break; + case 'S': + *size = optarg; + break; + default: + exit_err( read_help_text ); + break; + } +} + +void read_sock_param( int c, char **sock, char *help_text ) +{ + switch(c){ + case 'h': + fprintf( stdout, help_text ); + exit( 0 ); + break; + case 's': + *sock = optarg; + break; + default: + exit_err( help_text ); + break; + } +} + +void read_acl_param( int c, char **sock ) +{ + read_sock_param( c, sock, acl_help_text ); +} + +void read_mirror_param( int c, char **sock, char **ip_addr, char **ip_port ) +{ + switch( c ){ + case 'h': + fprintf( stdout, mirror_help_text ); + exit( 0 ); + break; + case 's': + *sock = optarg; + break; + case 'l': + *ip_addr = optarg; + break; + case 'p': + *ip_port = optarg; + break; + default: + exit_err( mirror_help_text ); + break; + } +} + +void read_status_param( int c, char **sock ) +{ + read_sock_param( c, sock, status_help_text ); +} + +int mode_serve( int argc, char *argv[] ) +{ + int c; + char *ip_addr = NULL; + char *ip_port = NULL; + char *file = NULL; + char *sock = NULL; + int err = 0; + + struct mode_serve_params serve; + + while (1) { + c = getopt_long(argc, argv, serve_short_options, serve_options, NULL); + if ( c == -1 ) break; + read_serve_param( c, &ip_addr, &ip_port, &file, &sock ); + } + + if ( NULL == ip_addr || NULL == ip_port ) { + err = 1; + fprintf( stderr, "both --addr and --port are required.\n" ); + } + if ( NULL == file ) { + err = 1; + fprintf( stderr, "--file is required\n" ); + } + if ( err ) { exit_err( serve_help_text ); } + + memset( &serve, 0, sizeof( serve ) ); + params_serve( &serve, ip_addr, ip_port, file, sock, argc - optind, argv + optind ); + do_serve( &serve ); + + return 0; +} + +int mode_read( int argc, char *argv[] ) +{ + int c; + char *ip_addr = NULL; + char *ip_port = NULL; + char *from = NULL; + char *size = NULL; + int err = 0; + + struct mode_readwrite_params readwrite; + + while (1){ + c = getopt_long(argc, argv, read_short_options, read_options, NULL); + if ( c == -1 ) break; + read_readwrite_param( c, &ip_addr, &ip_port, &from, &size ); + } + + if ( NULL == ip_addr || NULL == ip_port ) { + err = 1; + fprintf( stderr, "both --addr and --port are required.\n" ); + } + if ( NULL == from || NULL == size ) { + err = 1; + fprintf( stderr, "both --from and --size are required.\n" ); + } + if ( err ) { exit_err( read_help_text ); } + + memset( &readwrite, 0, sizeof( readwrite ) ); + params_readwrite( 0, &readwrite, ip_addr, ip_port, from, size ); + do_read( &readwrite ); + return 0; +} + +int mode_write( int argc, char *argv[] ) +{ + int c; + char *ip_addr = NULL; + char *ip_port = NULL; + char *from = NULL; + char *size = NULL; + int err = 0; + + struct mode_readwrite_params readwrite; + + while (1){ + c = getopt_long(argc, argv, write_short_options, write_options, NULL); + if ( c == -1 ) break; + read_readwrite_param( c, &ip_addr, &ip_port, &from, &size ); + } + + if ( NULL == ip_addr || NULL == ip_port ) { + err = 1; + fprintf( stderr, "both --addr and --port are required.\n" ); + } + if ( NULL == from || NULL == size ) { + err = 1; + fprintf( stderr, "both --from and --size are required.\n" ); + } + if ( err ) { exit_err( write_help_text ); } + + memset( &readwrite, 0, sizeof( readwrite ) ); + params_readwrite( 1, &readwrite, ip_addr, ip_port, from, size ); + do_write( &readwrite ); + return 0; +} + +int mode_acl( int argc, char *argv[] ) +{ + int c; + char *sock = NULL; + + while (1) { + c = getopt_long( argc, argv, acl_short_options, acl_options, NULL ); + if ( c == -1 ) break; + read_acl_param( c, &sock ); + } + + if ( NULL == sock ){ + fprintf( stderr, "--sock is required.\n" ); + exit_err( acl_help_text ); + } + + /* Don't use the CMD_ACL macro here, "acl" is the remote command + * name, not the cli option + */ + do_remote_command( "acl", sock, argc - optind, argv + optind ); + + return 0; +} + + +int mode_mirror( int argc, char *argv[] ) +{ + int c; + char *sock = NULL; + char *remote_argv[3] = {0}; + int err = 0; + + while (1) { + c = getopt_long( argc, argv, mirror_short_options, mirror_options, NULL); + if ( -1 == c ) break; + read_mirror_param( c, &sock, &remote_argv[0], &remote_argv[1] ); + } + + if ( NULL == sock ){ + fprintf( stderr, "--sock is required.\n" ); + err = 1; + } + if ( NULL == remote_argv[0] || NULL == remote_argv[1] ) { + fprintf( stderr, "both --addr and --port are required.\n"); + err = 1; + } + if ( err ) { exit_err( mirror_help_text ); } + + do_remote_command( "mirror", sock, 2, remote_argv ); + + return 0; +} + + +int mode_status( int argc, char *argv[] ) +{ + int c; + char *sock = NULL; + + while (1) { + c = getopt_long( argc, argv, status_short_options, status_options, NULL ); + if ( -1 == c ) break; + read_status_param( c, &sock ); + } + + if ( NULL == sock ){ + fprintf( stderr, "--sock is required.\n" ); + exit_err( acl_help_text ); + } + + do_remote_command( "status", sock, argc - optind, argv + optind ); + + return 0; +} + + +int mode_help( int argc, char *argv[] ) +{ + char *cmd; + char *help_text; + + if ( argc < 1 ){ + help_text = help_help_text; + } else { + cmd = argv[0]; + if (IS_CMD( CMD_SERVE, cmd ) ) { + help_text = serve_help_text; + } else if ( IS_CMD( CMD_READ, cmd ) ) { + help_text = read_help_text; + } else if ( IS_CMD( CMD_WRITE, cmd ) ) { + help_text = write_help_text; + } else if ( IS_CMD( CMD_ACL, cmd ) ) { + help_text = acl_help_text; + } else if ( IS_CMD( CMD_MIRROR, cmd ) ) { + help_text = mirror_help_text; + } else if ( IS_CMD( CMD_STATUS, cmd ) ) { + help_text = status_help_text; + } else { exit_err( help_help_text ); } + } + + fprintf( stdout, help_text ); + return 0; +} + + +void mode(char* mode, int argc, char **argv) +{ + if ( IS_CMD( CMD_SERVE, mode ) ) { + mode_serve( argc, argv ); + } + else if ( IS_CMD( CMD_READ, mode ) ) { + mode_read( argc, argv ); + } + else if ( IS_CMD( CMD_WRITE, mode ) ) { + mode_write( argc, argv ); + } + else if ( IS_CMD( CMD_ACL, mode ) ) { + mode_acl( argc, argv ); + } + else if ( IS_CMD( CMD_MIRROR, mode ) ) { + mode_mirror( argc, argv ); + } + else if ( IS_CMD( CMD_STATUS, mode ) ) { + mode_status( argc, argv ); + } + else if ( IS_CMD( CMD_HELP, mode ) ) { + mode_help( argc-1, argv+1 ); + } + else { + mode_help( argc-1, argv+1 ); + exit( 1 ); + } + exit(0); +} + + +int main(int argc, char** argv) +{ + signal(SIGPIPE, SIG_IGN); /* calls to splice() unhelpfully throw this */ + error_init(); + + if (argc < 2) { + exit_err( help_help_text ); + } + mode(argv[1], argc-1, argv+1); /* never returns */ + + return 0; +} + diff --git a/ioutil.c b/src/ioutil.c similarity index 100% rename from ioutil.c rename to src/ioutil.c diff --git a/ioutil.h b/src/ioutil.h similarity index 100% rename from ioutil.h rename to src/ioutil.h diff --git a/nbdtypes.h b/src/nbdtypes.h similarity index 100% rename from nbdtypes.h rename to src/nbdtypes.h diff --git a/src/options.h b/src/options.h new file mode 100644 index 0000000..8c50345 --- /dev/null +++ b/src/options.h @@ -0,0 +1,126 @@ +#define GETOPT_ARG(x,s) {(x), 1, 0, (s)} +#define GETOPT_FLAG(x,v) {(x), 0, 0, (v)} + +#define OPT_HELP "help" +#define OPT_ADDR "addr" +#define OPT_PORT "port" +#define OPT_FILE "file" +#define OPT_SOCK "sock" +#define OPT_FROM "from" +#define OPT_SIZE "size" + +#define CMD_SERVE "serve" +#define CMD_READ "read" +#define CMD_WRITE "write" +#define CMD_ACL "acl" +#define CMD_MIRROR "mirror" +#define CMD_STATUS "status" +#define CMD_HELP "help" +#define LEN_CMD_MAX 6 + +#define PATH_LEN_MAX 1024 +#define ADDR_LEN_MAX 64 + + +#define IS_CMD(x,c) (strncmp((x),(c),(LEN_CMD_MAX)) == 0) + +static struct option serve_options[] = { + GETOPT_FLAG( OPT_HELP, 'h' ), + GETOPT_ARG( OPT_ADDR, 'l' ), + GETOPT_ARG( OPT_PORT, 'p' ), + GETOPT_ARG( OPT_FILE, 'f' ), + GETOPT_ARG( OPT_SOCK, 's' ), + {0} +}; +static char serve_short_options[] = "hl:p:f:s:"; +static char serve_help_text[] = + "Usage: flexnbd " CMD_SERVE " [*]\n\n" + "Serve FILE from ADDR:PORT, with an optional control socket at SOCK.\n\n" + "\t--" OPT_HELP ",-h\tThis text.\n" + "\t--" OPT_ADDR ",-l \tThe address to serve on.\n" + "\t--" OPT_PORT ",-p \tThe port to serve on.\n" + "\t--" OPT_FILE ",-f \tThe file to serve.\n" + "\t--" OPT_SOCK ",-s \tPath to the control socket to open.\n"; + +static struct option read_options[] = { + GETOPT_FLAG( OPT_HELP, 'h' ), + GETOPT_ARG( OPT_ADDR, 'l' ), + GETOPT_ARG( OPT_PORT, 'p' ), + GETOPT_ARG( OPT_FROM, 'F' ), + GETOPT_ARG( OPT_SIZE, 'S' ), + {0} +}; +static char read_short_options[] = "hl:p:F:S:"; +static char read_help_text[] = + "Usage: flexnbd " CMD_READ " \n\n" + "Read SIZE bytes from a server at ADDR:PORT to stdout, starting at OFFSET.\n\n" + "\t--" OPT_HELP ",-h\tThis text.\n" + "\t--" OPT_ADDR ",-l \tThe address to read from.\n" + "\t--" OPT_PORT ",-p \tThe port to read from.\n" + "\t--" OPT_FROM ",-F \tByte offset to read from.\n" + "\t--" OPT_SIZE ",-S \tBytes to read.\n"; + + +static struct option *write_options = read_options; +static char *write_short_options = read_short_options; +static char write_help_text[] = + "Usage: flexnbd " CMD_WRITE" \n\n" + "Write SIZE bytes from stdin to a server at ADDR:PORT, starting at OFFSET.\n\n" + "\t--" OPT_HELP ",-h\tThis text.\n" + "\t--" OPT_ADDR ",-l \tThe address to write to.\n" + "\t--" OPT_PORT ",-p \tThe port to write to.\n" + "\t--" OPT_FROM ",-F \tByte offset to write from.\n" + "\t--" OPT_SIZE ",-S \tBytes to write.\n"; + +struct option acl_options[] = { + GETOPT_FLAG( OPT_HELP, 'h' ), + GETOPT_ARG( OPT_SOCK, 's' ), + {0} +}; +static char acl_short_options[] = "hs:"; +static char acl_help_text[] = + "Usage: flexnbd " CMD_ACL " [+]\n\n" + "Set the access control list for a server with control socket SOCK.\n\n" + "\t--" OPT_HELP ",-h\tThis text.\n" + "\t--" OPT_SOCK ",-s \tPath to the control socket.\n"; + +struct option mirror_options[] = { + GETOPT_FLAG( OPT_HELP, 'h' ), + GETOPT_ARG( OPT_SOCK, 's' ), + GETOPT_ARG( OPT_ADDR, 'l' ), + GETOPT_ARG( OPT_PORT, 'p' ), + {0} +}; +static char mirror_short_options[] = "hs:l:p:"; +static char mirror_help_text[] = + "Usage: flexnbd " CMD_MIRROR " \n\n" + "Start mirroring from the server with control socket SOCK to one at ADDR:PORT.\n\n" + "\t--" OPT_HELP ",-h\tThis text.\n" + "\t--" OPT_SOCK ",-s \tPath to the control socket.\n" + "\t--" OPT_ADDR ",-l \tThe address to mirror to.\n" + "\t--" OPT_PORT ",-p \tThe port to mirror to.\n"; + + +struct option status_options[] = { + GETOPT_FLAG( OPT_HELP, 'h' ), + GETOPT_ARG( OPT_SOCK, 's' ), + {0} +}; +static char status_short_options[] = "hs:"; +static char status_help_text[] = + "Usage: flexnbd " CMD_STATUS " \n\n" + "Get the status for a server with control socket SOCK.\n\n" + "\t--" OPT_HELP ",-h\tThis text.\n" + "\t--" OPT_SOCK ",-s \tPath to the control socket.\n"; + +static char help_help_text[] = + "Usage: flexnbd [cmd options]\n\n" + "Commands:\n" + "\tflexnbd serve\n" + "\tflexnbd read\n" + "\tflexnbd write\n" + "\tflexnbd acl\n" + "\tflexnbd mirror\n" + "\tflexnbd status\n" + "\tflexnbd help\n\n" + "See flexnbd help for further info\n"; diff --git a/params.h b/src/params.h similarity index 100% rename from params.h rename to src/params.h diff --git a/parse.c b/src/parse.c similarity index 100% rename from parse.c rename to src/parse.c diff --git a/parse.h b/src/parse.h similarity index 100% rename from parse.h rename to src/parse.h diff --git a/readwrite.c b/src/readwrite.c similarity index 100% rename from readwrite.c rename to src/readwrite.c diff --git a/readwrite.h b/src/readwrite.h similarity index 100% rename from readwrite.h rename to src/readwrite.h diff --git a/remote.c b/src/remote.c similarity index 100% rename from remote.c rename to src/remote.c diff --git a/serve.c b/src/serve.c similarity index 100% rename from serve.c rename to src/serve.c diff --git a/util.c b/src/util.c similarity index 100% rename from util.c rename to src/util.c diff --git a/util.h b/src/util.h similarity index 100% rename from util.h rename to src/util.h diff --git a/tests/flexnbd.rb b/tests/flexnbd.rb index 2253f9f..49224c9 100644 --- a/tests/flexnbd.rb +++ b/tests/flexnbd.rb @@ -16,7 +16,13 @@ class FlexNBD def serve(ip, port, file, *acl) File.unlink(ctrl) if File.exists?(ctrl) @pid = fork do - exec("#{@bin} serve #{ip} #{port} #{file} #{ctrl} #{acl.join(' ')}") + cmd ="#{@bin} serve "\ + "--addr #{ip} "\ + "--port #{port} "\ + "--file #{file} "\ + "--sock #{ctrl} "\ + "#{acl.join(' ')}" + exec(cmd) end sleep 0.1 until File.socket?(ctrl) end @@ -27,14 +33,22 @@ class FlexNBD end def read(offset, length) - IO.popen("#{@bin} read #{ip} #{port} #{offset} #{length}","r") do |fh| + IO.popen("#{@bin} read "\ + "--addr #{ip} "\ + "--port #{port} "\ + "--from #{offset} "\ + "--size #{length}","r") do |fh| return fh.read end raise "read failed" unless $?.success? end def write(offset, data) - IO.popen("#{@bin} write #{ip} #{port} #{offset} #{data.length}","w") do |fh| + IO.popen("#{@bin} write "\ + "--addr #{ip} "\ + "--port #{port} "\ + "--from #{offset} "\ + "--size #{data.length}","w") do |fh| fh.write(data) end raise "write failed" unless $?.success? diff --git a/tests/nbd_scenarios b/tests/nbd_scenarios index faa4767..7392112 100644 --- a/tests/nbd_scenarios +++ b/tests/nbd_scenarios @@ -13,39 +13,39 @@ class NBDScenarios < Test::Unit::TestCase @available_ports = [*40000..41000] - listening_ports @port1 = @available_ports.shift @port2 = @available_ports.shift - @nbd1 = FlexNBD.new("../flexnbd", @ip, @port1) - end - + @nbd1 = FlexNBD.new("../build/flexnbd", @ip, @port1) + end + def teardown @nbd1.kill rescue nil [@filename1, @filename2].each do |f| File.unlink(f) if File.exists?(f) end end - + def test_read1 writefile1("f"*64) serve1 - + [0, 12, 63].each do |num| - + assert_equal( - @nbd1.read(num*@blocksize, @blocksize), + @nbd1.read(num*@blocksize, @blocksize), @file1.read(num*@blocksize, @blocksize) ) end - + [124, 1200, 10028, 25488].each do |num| assert_equal(@nbd1.read(num, 4), @file1.read(num, 4)) end end - - # Check that we're not + + # Check that we're not # def test_writeread1 writefile1("0"*64) serve1 - + [0, 12, 63].each do |num| data = "X"*@blocksize @nbd1.write(num*@blocksize, data) @@ -53,14 +53,14 @@ class NBDScenarios < Test::Unit::TestCase assert_equal(data, @nbd1.read(num*@blocksize, data.size)) end end - + # Check that we're not overstepping or understepping where our writes end # up. # def test_writeread2 writefile1("0"*1024) serve1 - + d0 = "\0"*@blocksize d1 = "X"*@blocksize (0..63).each do |num| @@ -70,16 +70,16 @@ class NBDScenarios < Test::Unit::TestCase assert_equal(d0, @nbd1.read(((2*num)+1)*@blocksize, d0.size)) end end - + protected def serve1(*acl) @nbd1.serve(@ip, @port1, @filename1, *acl) end - + def writefile1(data) @file1 = TestFileWriter.new(@filename1, @blocksize).write(data) end - + def listening_ports `netstat -ltn`. split("\n").