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").