forked from NetworkBlockDevice/nbd
-
Notifications
You must be signed in to change notification settings - Fork 0
/
nbd-server.c
3601 lines (3276 loc) · 104 KB
/
nbd-server.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* Network Block Device - server
*
* Copyright 1996-1998 Pavel Machek, distribute under GPL
* Copyright 2001-2004 Wouter Verhelst <[email protected]>
* Copyright 2002 Anton Altaparmakov <[email protected]>
*
* Version 1.0 - hopefully 64-bit-clean
* Version 1.1 - merging enhancements from Josh Parsons, <[email protected]>
* Version 1.2 - autodetect size of block devices, thanx to Peter T. Breuer" <[email protected]>
* Version 1.5 - can compile on Unix systems that don't have 64 bit integer
* type, or don't have 64 bit file offsets by defining FS_32BIT
* in compile options for nbd-server *only*. This can be done
* with make FSCHOICE=-DFS_32BIT nbd-server. (I don't have the
* original autoconf input file, or I would make it a configure
* option.) Ken Yap <[email protected]>.
* Version 1.6 - fix autodetection of block device size and really make 64 bit
* clean on 32 bit machines. Anton Altaparmakov <[email protected]>
* Version 2.0 - Version synchronised with client
* Version 2.1 - Reap zombie client processes when they exit. Removed
* (uncommented) the _IO magic, it's no longer necessary. Wouter
* Verhelst <[email protected]>
* Version 2.2 - Auto switch to read-only mode (usefull for floppies).
* Version 2.3 - Fixed code so that Large File Support works. This
* removes the FS_32BIT compile-time directive; define
* _FILE_OFFSET_BITS=64 and _LARGEFILE_SOURCE if you used to be
* using FS_32BIT. This will allow you to use files >2GB instead of
* having to use the -m option. Wouter Verhelst <[email protected]>
* Version 2.4 - Added code to keep track of children, so that we can
* properly kill them from initscripts. Add a call to daemon(),
* so that processes don't think they have to wait for us, which is
* interesting for initscripts as well. Wouter Verhelst
* Version 2.5 - Bugfix release: forgot to reset child_arraysize to
* zero after fork()ing, resulting in nbd-server going berserk
* when it receives a signal with at least one child open. Wouter
* Verhelst <[email protected]>
* 10/10/2003 - Added socket option SO_KEEPALIVE (sf.net bug 819235);
* rectified type of mainloop::size_host (sf.net bugs 814435 and
* 817385); close the PID file after writing to it, so that the
* daemon can actually be found. Wouter Verhelst
* 10/10/2003 - Size of the data "size_host" was wrong and so was not
* correctly put in network endianness. Many types were corrected
* (size_t and off_t instead of int). <[email protected]>
* Version 2.6 - Some code cleanup.
* Version 2.7 - Better build system.
* 11/02/2004 - Doxygenified the source, modularized it a bit. Needs a
* lot more work, but this is a start. Wouter Verhelst
* 16/03/2010 - Add IPv6 support.
* Kitt Tientanopajai <[email protected]>
* Neutron Soutmun <[email protected]>
* Suriya Soutmun <[email protected]>
*/
/* Includes LFS defines, which defines behaviours of some of the following
* headers, so must come before those */
#include "lfs.h"
#define _DEFAULT_SOURCE
#define _XOPEN_SOURCE 500 /* to get pread/pwrite */
#if NEED_BSD_SOURCE
#define _BSD_SOURCE /* to get DT_* macros on some platforms */
#endif
#define _DARWIN_C_SOURCE /* to get DT_* macros on OS X */
#include <assert.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/select.h>
#include <sys/wait.h>
#include <sys/un.h>
#ifdef HAVE_SYS_IOCTL_H
#include <sys/ioctl.h>
#endif
#ifdef HAVE_SYS_UIO_H
#include <sys/uio.h>
#endif
#include <sys/param.h>
#include <signal.h>
#include <errno.h>
#include <libgen.h>
#include <netinet/tcp.h>
#include <netinet/in.h>
#include <netdb.h>
#include <syslog.h>
#include <unistd.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#if HAVE_FALLOC_PH
#include <linux/falloc.h>
#endif
#include <arpa/inet.h>
#include <strings.h>
#include <dirent.h>
#ifdef HAVE_SYS_DIR_H
#include <sys/dir.h>
#endif
#ifdef HAVE_SYS_DIRENT_H
#include <sys/dirent.h>
#endif
#include <getopt.h>
#include <pwd.h>
#include <grp.h>
#include <dirent.h>
#include <ctype.h>
#include <inttypes.h>
#include <glib.h>
#if HAVE_OLD_GLIB
#include <pthread.h>
#endif
#include <semaphore.h>
/* used in cliserv.h, so must come first */
#define MY_NAME "nbd_server"
#include "cliserv.h"
#include "nbd-debug.h"
#include "netdb-compat.h"
#include "backend.h"
#include "treefiles.h"
#ifdef WITH_SDP
#include <sdp_inet.h>
#endif
#if HAVE_FSCTL_SET_ZERO_DATA
#include <io.h>
/* don't include <windows.h> to avoid redefining eg the ERROR macro */
#define NOMINMAX 1
#include <windef.h>
#include <winbase.h>
#include <winioctl.h>
#endif
/** Default position of the config file */
#ifndef SYSCONFDIR
#define SYSCONFDIR "/etc"
#endif
#define CFILE SYSCONFDIR "/nbd-server/config"
#if HAVE_GNUTLS
#include <gnutls/gnutls.h>
#include <gnutls/x509.h>
#endif
/** Where our config file actually is */
gchar* config_file_pos;
/** global flags */
int glob_flags=0;
/* Whether we should avoid forking */
int dontfork = 0;
/**
* The highest value a variable of type off_t can reach. This is a signed
* integer, so set all bits except for the leftmost one.
**/
#define OFFT_MAX ~((off_t)1<<(sizeof(off_t)*8-1))
#define BUFSIZE ((1024*1024)+sizeof(struct nbd_reply)) /**< Size of buffer that can hold requests */
#define DIFFPAGESIZE 4096 /**< diff file uses those chunks */
/** Global flags: */
#define F_OLDSTYLE 1 /**< Allow oldstyle (port-based) exports */
#define F_LIST 2 /**< Allow clients to list the exports on a server */
#define F_NO_ZEROES 4 /**< Do not send zeros to client */
// also accepts F_FORCEDTLS (which is 16384)
GHashTable *children;
char pidfname[256]; /**< name of our PID file */
char default_authname[] = SYSCONFDIR "/nbd-server/allow"; /**< default name of allow file */
#define NEG_INIT (1 << 0)
#define NEG_OLD (1 << 1)
#define NEG_MODERN (1 << 2)
/*
* If we want what the system really has set we'd have to read
* /proc/sys/fs/pipe-max-size, but for now 1mb should be enough.
*/
#define MAX_PIPE_SIZE (1 * 1024 * 1024)
#define SPLICE_IN 0
#define SPLICE_OUT 1
#include <nbdsrv.h>
/* Our thread pool */
GThreadPool *tpool;
/* A work package for the thread pool functions */
struct work_package {
CLIENT* client;
struct nbd_request* req;
int pipefd[2];
void* data; /**< for read requests */
};
static volatile sig_atomic_t is_sigchld_caught; /**< Flag set by
SIGCHLD handler
to mark a child
exit */
static volatile sig_atomic_t is_sigterm_caught; /**< Flag set by
SIGTERM handler
to mark a exit
request */
static volatile sig_atomic_t is_sighup_caught; /**< Flag set by SIGHUP
handler to mark a
reconfiguration
request */
GArray* modernsocks; /**< Sockets for the modern handler. Not used
if a client was only specified on the
command line; only port used if
oldstyle is set to false (and then the
command-line client isn't used, gna gna).
This may be more than one socket on
systems that don't support serving IPv4
and IPv6 from the same socket (like,
e.g., FreeBSD) */
GArray* childsocks; /**< parent-side sockets for communication with children */
int commsocket; /**< child-side socket for communication with parent */
static sem_t file_wait_sem;
bool logged_oversized=false; /**< whether we logged oversized requests already */
/**
* Type of configuration file values
**/
typedef enum {
PARAM_INT, /**< This parameter is an integer */
PARAM_INT64, /**< This parameter is an integer */
PARAM_STRING, /**< This parameter is a string */
PARAM_BOOL, /**< This parameter is a boolean */
} PARAM_TYPE;
/**
* Configuration file values
**/
typedef struct {
gchar *paramname; /**< Name of the parameter, as it appears in
the config file */
gboolean required; /**< Whether this is a required (as opposed to
optional) parameter */
PARAM_TYPE ptype; /**< Type of the parameter. */
gpointer target; /**< Pointer to where the data of this
parameter should be written. If ptype is
PARAM_BOOL, the data is or'ed rather than
overwritten. */
gint flagval; /**< Flag mask for this parameter in case ptype
is PARAM_BOOL. */
} PARAM;
/**
* Configuration file values of the "generic" section
**/
struct generic_conf {
gchar *user; /**< user we run the server as */
gchar *group; /**< group we run running as */
gchar *modernaddr; /**< address of the modern socket */
gchar *modernport; /**< port of the modern socket */
gchar *unixsock; /**< file name of the unix domain socket */
gchar *certfile; /**< certificate file */
gchar *keyfile; /**< key file */
gchar *cacertfile; /**< CA certificate file */
gchar *tlsprio; /**< TLS priority string */
gint flags; /**< global flags */
gint threads; /**< maximum number of parallel threads we want to run */
};
/**
* Translate a command name into human readable form
*
* @param command The command number (after applying NBD_CMD_MASK_COMMAND)
* @return pointer to the command name
**/
static inline const char * getcommandname(uint64_t command) {
switch (command) {
case NBD_CMD_READ:
return "NBD_CMD_READ";
case NBD_CMD_WRITE:
return "NBD_CMD_WRITE";
case NBD_CMD_DISC:
return "NBD_CMD_DISC";
case NBD_CMD_FLUSH:
return "NBD_CMD_FLUSH";
case NBD_CMD_TRIM:
return "NBD_CMD_TRIM";
case NBD_CMD_WRITE_ZEROES:
return "NBD_CMD_WRITE_ZEROES";
default:
return "UNKNOWN";
}
}
#if HAVE_GNUTLS
static int writeit_tls(gnutls_session_t s, void *buf, size_t len) {
ssize_t res;
char *m;
while(len > 0) {
DEBUG("+");
if ((res = gnutls_record_send(s, buf, len)) < 0 && !gnutls_error_is_fatal(res)) {
m = g_strdup_printf("issue while sending data: %s", gnutls_strerror(res));
err_nonfatal(m);
g_free(m);
} else if(res < 0) {
m = g_strdup_printf("could not send data: %s", gnutls_strerror(res));
err_nonfatal(m);
g_free(m);
return -1;
} else {
len -= res;
buf += res;
}
}
return 0;
}
static int readit_tls(gnutls_session_t s, void *buf, size_t len) {
ssize_t res;
char *m;
while(len > 0) {
DEBUG("*");
if((res = gnutls_record_recv(s, buf, len)) < 0 && !gnutls_error_is_fatal(res)) {
m = g_strdup_printf("issue while receiving data: %s", gnutls_strerror(res));
err_nonfatal(m);
g_free(m);
} else if(res < 0) {
m = g_strdup_printf("could not receive data: %s", gnutls_strerror(res));
err_nonfatal(m);
g_free(m);
return -1;
} else {
len -= res;
buf += res;
}
}
return 0;
}
static int socket_read_tls(CLIENT* client, void *buf, size_t len) {
return readit_tls(*((gnutls_session_t*)client->tls_session), buf, len);
}
static int socket_write_tls(CLIENT* client, void *buf, size_t len) {
return writeit_tls(*((gnutls_session_t*)client->tls_session), buf, len);
}
#endif // HAVE_GNUTLS
static int socket_read_notls(CLIENT* client, void *buf, size_t len) {
return readit(client->net, buf, len);
}
static int socket_write_notls(CLIENT* client, void *buf, size_t len) {
return writeit(client->net, buf, len);
}
static void socket_read(CLIENT* client, void *buf, size_t len) {
g_assert(client->socket_read != NULL);
if(client->socket_read(client, buf, len)<0) {
g_assert(client->socket_closed != NULL);
client->socket_closed(client);
}
}
/**
* Consume data from a socket that we don't want
*
* @param c the client to read from
* @param len the number of bytes to consume
* @param buf a buffer
* @param bufsiz the size of the buffer
**/
static inline void consume(CLIENT* c, size_t len, void * buf, size_t bufsiz) {
size_t curlen;
while (len>0) {
curlen = (len>bufsiz)?bufsiz:len;
socket_read(c, buf, curlen);
len -= curlen;
}
}
/**
* Consume a length field and corresponding payload that we don't want
*
* @param c the client to read from
**/
static inline void consume_len(CLIENT* c) {
uint32_t len;
char buf[1024];
socket_read(c, &len, sizeof(len));
len = ntohl(len);
consume(c, len, buf, sizeof(buf));
}
static void socket_write(CLIENT* client, void *buf, size_t len) {
g_assert(client->socket_write != NULL);
if(client->socket_write(client, buf, len)<0) {
g_assert(client->socket_closed != NULL);
client->socket_closed(client);
}
}
static inline void socket_closed_negotiate(CLIENT* client) {
err("Negotiation failed: %m");
}
/**
* Run a command. This is used for the ``prerun'' and ``postrun'' config file
* options
*
* @param command the command to be ran. Read from the config file
* @param file the file name we're about to export
**/
int do_run(gchar* command, gchar* file) {
gchar* cmd;
int retval=0;
if(command && *command) {
cmd = g_strdup_printf(command, file);
retval=system(cmd);
g_free(cmd);
}
return retval;
}
static inline void finalize_client(CLIENT* client) {
g_thread_pool_free(tpool, FALSE, TRUE);
do_run(client->server->postrun, client->exportname);
if(client->transactionlogfd != -1) {
close(client->transactionlogfd);
client->transactionlogfd = -1;
}
if(client->server->flags & F_COPYONWRITE) {
unlink(client->difffilename);
}
}
static inline void socket_closed_transmission(CLIENT* client) {
int saved_errno = errno;
finalize_client(client);
errno = saved_errno;
err("Connection dropped: %m");
}
#ifdef HAVE_SPLICE
/**
* Splice data between a pipe and a file descriptor
*
* @param fd_in The fd to splice from.
* @param off_in The fd_in offset to splice from.
* @param fd_out The fd to splice to.
* @param off_out The fd_out offset to splice to.
* @param len The length to splice.
*/
static inline void spliceit(int fd_in, loff_t *off_in, int fd_out,
loff_t *off_out, size_t len)
{
ssize_t ret;
while (len > 0) {
if ((ret = splice(fd_in, off_in, fd_out, off_out, len,
SPLICE_F_MOVE)) <= 0)
err("Splice failed: %m");
len -= ret;
}
}
#endif
/**
* Print out a message about how to use nbd-server. Split out to a separate
* function so that we can call it from multiple places
*/
void usage() {
printf("This is nbd-server version " VERSION "\n");
printf("Usage: [ip:|ip6@]port file_to_export [size][kKmM] [-l authorize_file] [-r] [-m] [-c] [-C configuration file] [-p PID file name] [-o section name] [-M max connections] [-V]\n"
"\t-r|--read-only\t\tread only\n"
"\t-m|--multi-file\t\tmultiple file\n"
"\t-c|--copy-on-write\tcopy on write\n"
"\t-C|--config-file\tspecify an alternate configuration file\n"
"\t-l|--authorize-file\tfile with list of hosts that are allowed to\n\t\t\t\tconnect.\n"
"\t-p|--pid-file\t\tspecify a filename to write our PID to\n"
"\t-o|--output-config\toutput a config file section for what you\n\t\t\t\tspecified on the command line, with the\n\t\t\t\tspecified section name\n"
"\t-M|--max-connections\tspecify the maximum number of opened connections\n"
"\t-V|--version\toutput the version and exit\n\n"
"\tif port is set to 0, stdin is used (for running from inetd).\n"
"\tif file_to_export contains '%%s', it is substituted with the IP\n"
"\t\taddress of the machine trying to connect\n"
"\tif ip is set, it contains the local IP address on which we're listening.\n\tif not, the server will listen on all local IP addresses\n");
printf("Using configuration file %s\n", CFILE);
printf("For help, or when encountering bugs, please contact %s\n", PACKAGE_BUGREPORT);
}
/* Dumps a config file section of the given SERVER*, and exits. */
void dump_section(SERVER* serve, gchar* section_header) {
printf("[%s]\n", section_header);
printf("\texportname = %s\n", serve->exportname);
printf("\tlistenaddr = %s\n", serve->listenaddr);
if(serve->flags & F_READONLY) {
printf("\treadonly = true\n");
}
if(serve->flags & F_MULTIFILE) {
printf("\tmultifile = true\n");
}
if(serve->flags & F_TREEFILES) {
printf("\ttreefiles = true\n");
}
if(serve->flags & F_COPYONWRITE) {
printf("\tcopyonwrite = true\n");
}
if(serve->expected_size) {
printf("\tfilesize = %lld\n", (long long int)serve->expected_size);
}
if(serve->authname) {
printf("\tauthfile = %s\n", serve->authname);
}
exit(EXIT_SUCCESS);
}
/**
* Parse the command line.
*
* @param argc the argc argument to main()
* @param argv the argv argument to main()
**/
SERVER* cmdline(int argc, char *argv[], struct generic_conf *genconf) {
int i=0;
int nonspecial=0;
int c;
struct option long_options[] = {
{"read-only", no_argument, NULL, 'r'},
{"multi-file", no_argument, NULL, 'm'},
{"copy-on-write", no_argument, NULL, 'c'},
{"dont-fork", no_argument, NULL, 'd'},
{"authorize-file", required_argument, NULL, 'l'},
{"config-file", required_argument, NULL, 'C'},
{"pid-file", required_argument, NULL, 'p'},
{"output-config", required_argument, NULL, 'o'},
{"max-connection", required_argument, NULL, 'M'},
{"version", no_argument, NULL, 'V'},
{0,0,0,0}
};
SERVER *serve;
off_t es;
size_t last;
char suffix;
bool do_output=false;
gchar* section_header="";
gchar** addr_port;
if(argc==1) {
return NULL;
}
serve=g_new0(SERVER, 1);
serve->authname = g_strdup(default_authname);
serve->virtstyle=VIRT_IPLIT;
while((c=getopt_long(argc, argv, "-C:cwdl:mo:rp:M:V", long_options, &i))>=0) {
switch (c) {
case 1:
/* non-option argument */
switch(nonspecial++) {
case 0:
if(strchr(optarg, ':') == strrchr(optarg, ':')) {
addr_port=g_strsplit(optarg, ":", 2);
/* Check for "@" - maybe user using this separator
for IPv4 address */
if(!addr_port[1]) {
g_strfreev(addr_port);
addr_port=g_strsplit(optarg, "@", 2);
}
} else {
addr_port=g_strsplit(optarg, "@", 2);
}
if(addr_port[1]) {
genconf->modernport=g_strdup(addr_port[1]);
genconf->modernaddr=g_strdup(addr_port[0]);
} else {
g_free(genconf->modernaddr);
genconf->modernaddr=NULL;
genconf->modernport=g_strdup(addr_port[0]);
}
g_strfreev(addr_port);
break;
case 1:
serve->exportname = g_strdup(optarg);
if(serve->exportname[0] != '/') {
fprintf(stderr, "E: The to be exported file needs to be an absolute filename!\n");
exit(EXIT_FAILURE);
}
break;
case 2:
last=strlen(optarg)-1;
suffix=optarg[last];
if (suffix == 'k' || suffix == 'K' ||
suffix == 'm' || suffix == 'M')
optarg[last] = '\0';
es = (off_t)atoll(optarg);
switch (suffix) {
case 'm':
case 'M': es <<= 10;
case 'k':
case 'K': es <<= 10;
default : break;
}
serve->expected_size = es;
break;
}
break;
case 'r':
serve->flags |= F_READONLY;
break;
case 'm':
serve->flags |= F_MULTIFILE;
break;
case 'o':
do_output = true;
section_header = g_strdup(optarg);
break;
case 'p':
strncpy(pidfname, optarg, 256);
pidfname[255]='\0';
break;
case 'c':
serve->flags |=F_COPYONWRITE;
break;
case 'd':
dontfork = 1;
break;
case 'C':
g_free(config_file_pos);
config_file_pos=g_strdup(optarg);
break;
case 'l':
g_free(serve->authname);
serve->authname=g_strdup(optarg);
break;
case 'M':
serve->max_connections = strtol(optarg, NULL, 0);
break;
case 'V':
printf("This is nbd-server version " VERSION "\n");
exit(EXIT_SUCCESS);
break;
default:
usage();
exit(EXIT_FAILURE);
break;
}
}
/* What's left: the port to export, the name of the to be exported
* file, and, optionally, the size of the file, in that order. */
if(nonspecial<2) {
g_free(serve);
serve=NULL;
} else {
serve->servename = "";
}
if(do_output) {
if(!serve) {
g_critical("Need a complete configuration on the command line to output a config file section!");
exit(EXIT_FAILURE);
}
dump_section(serve, section_header);
}
return serve;
}
/* forward definition of parse_cfile */
GArray* parse_cfile(gchar* f, struct generic_conf *genconf, bool expect_generic, GError** e);
#ifdef HAVE_STRUCT_DIRENT_D_TYPE
#define NBD_D_TYPE de->d_type
#else
#define NBD_D_TYPE 0
#define DT_UNKNOWN 0
#define DT_REG 1
#endif
/**
* Parse config file snippets in a directory. Uses readdir() and friends
* to find files and open them, then passes them on to parse_cfile
* with have_global set false
**/
GArray* do_cfile_dir(gchar* dir, struct generic_conf *const genconf, GError** e) {
DIR* dirh = opendir(dir);
struct dirent* de;
gchar* fname;
GArray* retval = NULL;
GArray* tmp;
struct stat stbuf;
if(!dirh) {
g_set_error(e, NBDS_ERR, NBDS_ERR_CFILE_DIR_UNKNOWN, "Invalid directory specified: %s", strerror(errno));
return NULL;
}
errno=0;
while((de = readdir(dirh))) {
int saved_errno=errno;
fname = g_build_filename(dir, de->d_name, NULL);
switch(NBD_D_TYPE) {
case DT_UNKNOWN:
/* Filesystem doesn't return type of
* file through readdir. Run stat() on
* the file instead */
if(stat(fname, &stbuf)) {
perror("stat");
goto err_out;
}
if (!S_ISREG(stbuf.st_mode)) {
goto next;
}
case DT_REG:
/* Skip unless the name ends with '.conf' */
if(strcmp((de->d_name + strlen(de->d_name) - 5), ".conf")) {
goto next;
}
tmp = parse_cfile(fname, genconf, false, e);
errno=saved_errno;
if(*e) {
goto err_out;
}
if(!retval)
retval = g_array_new(FALSE, TRUE, sizeof(SERVER));
retval = g_array_append_vals(retval, tmp->data, tmp->len);
g_array_free(tmp, TRUE);
default:
break;
}
next:
g_free(fname);
}
if(errno) {
g_set_error(e, NBDS_ERR, NBDS_ERR_CFILE_READDIR_ERR, "Error trying to read directory: %s", strerror(errno));
err_out:
if(retval)
g_array_free(retval, TRUE);
retval = NULL;
}
if(dirh)
closedir(dirh);
return retval;
}
/**
* Parse the config file.
*
* @param f the name of the config file
*
* @param genconf a pointer to generic configuration which will get
* updated with parsed values. If NULL, then parsed generic
* configuration values are safely and silently discarded.
*
* @param e a GError. Error code can be any of the following:
* NBDS_ERR_CFILE_NOTFOUND, NBDS_ERR_CFILE_MISSING_GENERIC,
* NBDS_ERR_CFILE_VALUE_INVALID, NBDS_ERR_CFILE_VALUE_UNSUPPORTED
* or NBDS_ERR_CFILE_NO_EXPORTS. @see NBDS_ERRS.
*
* @param expect_generic if true, we expect a configuration file that
* contains a [generic] section. If false, we don't.
*
* @return a GArray of SERVER* pointers. If the config file is empty or does not
* exist, returns an empty GArray; if the config file contains an
* error, returns NULL, and e is set appropriately
**/
GArray* parse_cfile(gchar* f, struct generic_conf *const genconf, bool expect_generic, GError** e) {
const char* DEFAULT_ERROR = "Could not parse %s in group %s: %s";
const char* MISSING_REQUIRED_ERROR = "Could not find required value %s in group %s: %s";
gchar* cfdir = NULL;
SERVER s;
gchar *virtstyle=NULL;
PARAM lp[] = {
{ "exportname", TRUE, PARAM_STRING, &(s.exportname), 0 },
{ "authfile", FALSE, PARAM_STRING, &(s.authname), 0 },
{ "filesize", FALSE, PARAM_OFFT, &(s.expected_size), 0 },
{ "virtstyle", FALSE, PARAM_STRING, &(virtstyle), 0 },
{ "prerun", FALSE, PARAM_STRING, &(s.prerun), 0 },
{ "postrun", FALSE, PARAM_STRING, &(s.postrun), 0 },
{ "transactionlog", FALSE, PARAM_STRING, &(s.transactionlog), 0 },
{ "cowdir", FALSE, PARAM_STRING, &(s.cowdir), 0 },
{ "readonly", FALSE, PARAM_BOOL, &(s.flags), F_READONLY },
{ "multifile", FALSE, PARAM_BOOL, &(s.flags), F_MULTIFILE },
{ "treefiles", FALSE, PARAM_BOOL, &(s.flags), F_TREEFILES },
{ "copyonwrite", FALSE, PARAM_BOOL, &(s.flags), F_COPYONWRITE },
{ "waitfile", FALSE, PARAM_BOOL, &(s.flags), F_WAIT },
{ "sparse_cow", FALSE, PARAM_BOOL, &(s.flags), F_SPARSE },
{ "sdp", FALSE, PARAM_BOOL, &(s.flags), F_SDP },
{ "sync", FALSE, PARAM_BOOL, &(s.flags), F_SYNC },
{ "flush", FALSE, PARAM_BOOL, &(s.flags), F_FLUSH },
{ "fua", FALSE, PARAM_BOOL, &(s.flags), F_FUA },
{ "rotational", FALSE, PARAM_BOOL, &(s.flags), F_ROTATIONAL },
{ "temporary", FALSE, PARAM_BOOL, &(s.flags), F_TEMPORARY },
{ "trim", FALSE, PARAM_BOOL, &(s.flags), F_TRIM },
{ "listenaddr", FALSE, PARAM_STRING, &(s.listenaddr), 0 },
{ "maxconnections", FALSE, PARAM_INT, &(s.max_connections), 0 },
{ "force_tls", FALSE, PARAM_BOOL, &(s.flags), F_FORCEDTLS },
{ "splice", FALSE, PARAM_BOOL, &(s.flags), F_SPLICE},
};
const int lp_size=sizeof(lp)/sizeof(PARAM);
struct generic_conf genconftmp;
PARAM gp[] = {
{ "user", FALSE, PARAM_STRING, &(genconftmp.user), 0 },
{ "group", FALSE, PARAM_STRING, &(genconftmp.group), 0 },
{ "oldstyle", FALSE, PARAM_BOOL, &(genconftmp.flags), F_OLDSTYLE }, // only left here so we can issue an appropriate error message when the option is used
{ "listenaddr", FALSE, PARAM_STRING, &(genconftmp.modernaddr), 0 },
{ "port", FALSE, PARAM_STRING, &(genconftmp.modernport), 0 },
{ "includedir", FALSE, PARAM_STRING, &cfdir, 0 },
{ "allowlist", FALSE, PARAM_BOOL, &(genconftmp.flags), F_LIST },
{ "unixsock", FALSE, PARAM_STRING, &(genconftmp.unixsock), 0 },
{ "max_threads", FALSE, PARAM_INT, &(genconftmp.threads), 0 },
{ "force_tls", FALSE, PARAM_BOOL, &(genconftmp.flags), F_FORCEDTLS },
{ "certfile", FALSE, PARAM_STRING, &(genconftmp.certfile), 0 },
{ "keyfile", FALSE, PARAM_STRING, &(genconftmp.keyfile), 0 },
{ "cacertfile", FALSE, PARAM_STRING, &(genconftmp.cacertfile), 0 },
{ "tlsprio", FALSE, PARAM_STRING, &(genconftmp.tlsprio), 0 },
};
PARAM* p=gp;
int p_size=sizeof(gp)/sizeof(PARAM);
GKeyFile *cfile;
GError *err = NULL;
const char *err_msg=NULL;
GArray *retval=NULL;
gchar **groups;
gboolean bval;
gint ival;
gint64 i64val;
gchar* sval;
gchar* startgroup;
gint i;
gint j;
memset(&genconftmp, 0, sizeof(struct generic_conf));
genconftmp.tlsprio = "NORMAL:-VERS-TLS-ALL:+VERS-TLS1.2:%SERVER_PRECEDENCE";
if (genconf) {
/* Use the passed configuration values as defaults. The
* parsing algorithm below updates all parameter targets
* found from configuration files. */
memcpy(&genconftmp, genconf, sizeof(struct generic_conf));
}
cfile = g_key_file_new();
retval = g_array_new(FALSE, TRUE, sizeof(SERVER));
if(!g_key_file_load_from_file(cfile, f, G_KEY_FILE_KEEP_COMMENTS |
G_KEY_FILE_KEEP_TRANSLATIONS, &err)) {
g_set_error(e, NBDS_ERR, NBDS_ERR_CFILE_NOTFOUND, "Could not open config file %s: %s",
f, err->message);
g_key_file_free(cfile);
return retval;
}
startgroup = g_key_file_get_start_group(cfile);
if((!startgroup || strcmp(startgroup, "generic")) && expect_generic) {
g_set_error(e, NBDS_ERR, NBDS_ERR_CFILE_MISSING_GENERIC, "Config file does not contain the [generic] group!");
g_key_file_free(cfile);
return NULL;
}
groups = g_key_file_get_groups(cfile, NULL);
for(i=0;groups[i];i++) {
memset(&s, '\0', sizeof(SERVER));
/* After the [generic] group or when we're parsing an include
* directory, start parsing exports */
if(i==1 || !expect_generic) {
p=lp;
p_size=lp_size;
}
for(j=0;j<p_size;j++) {
assert(p[j].target != NULL);
assert(p[j].ptype==PARAM_INT||p[j].ptype==PARAM_STRING||p[j].ptype==PARAM_BOOL||p[j].ptype==PARAM_INT64);
switch(p[j].ptype) {
case PARAM_INT:
ival = g_key_file_get_integer(cfile,
groups[i],
p[j].paramname,
&err);
if(!err) {
*((gint*)p[j].target) = ival;
}
break;
case PARAM_INT64:
i64val = g_key_file_get_int64(cfile,
groups[i],
p[j].paramname,
&err);
if(!err) {
*((gint64*)p[j].target) = i64val;
}
break;
case PARAM_STRING:
sval = g_key_file_get_string(cfile,
groups[i],
p[j].paramname,
&err);
if(!err) {
*((gchar**)p[j].target) = sval;
}
break;
case PARAM_BOOL:
bval = g_key_file_get_boolean(cfile,
groups[i],
p[j].paramname, &err);
if(!err) {
if(bval) {
*((gint*)p[j].target) |= p[j].flagval;
} else {
*((gint*)p[j].target) &= ~(p[j].flagval);
}
}
break;
}
if(err) {
if(err->code == G_KEY_FILE_ERROR_KEY_NOT_FOUND) {
if(!p[j].required) {
/* Ignore not-found error for optional values */
g_clear_error(&err);
continue;
} else {
err_msg = MISSING_REQUIRED_ERROR;
}
} else {
err_msg = DEFAULT_ERROR;
}
g_set_error(e, NBDS_ERR, NBDS_ERR_CFILE_VALUE_INVALID, err_msg, p[j].paramname, groups[i], err->message);
g_array_free(retval, TRUE);
g_error_free(err);
g_key_file_free(cfile);
return NULL;
}
}
if(virtstyle) {
if(!strncmp(virtstyle, "none", 4)) {
s.virtstyle=VIRT_NONE;
} else if(!strncmp(virtstyle, "ipliteral", 9)) {
s.virtstyle=VIRT_IPLIT;
} else if(!strncmp(virtstyle, "iphash", 6)) {
s.virtstyle=VIRT_IPHASH;
} else if(!strncmp(virtstyle, "cidrhash", 8)) {
s.virtstyle=VIRT_CIDR;
if(strlen(virtstyle)<10) {
g_set_error(e, NBDS_ERR, NBDS_ERR_CFILE_VALUE_INVALID, "Invalid value %s for parameter virtstyle in group %s: missing length", virtstyle, groups[i]);
g_array_free(retval, TRUE);
g_key_file_free(cfile);
return NULL;
}
s.cidrlen=strtol(virtstyle+8, NULL, 0);
} else {
g_set_error(e, NBDS_ERR, NBDS_ERR_CFILE_VALUE_INVALID, "Invalid value %s for parameter virtstyle in group %s", virtstyle, groups[i]);
g_array_free(retval, TRUE);
g_key_file_free(cfile);
return NULL;
}
} else {
s.virtstyle=VIRT_IPLIT;
}
if(genconftmp.flags & F_OLDSTYLE) {
g_message("Since 3.10, the oldstyle protocol is no longer supported. Please migrate to the newstyle protocol.");
g_message("Exiting.");
return NULL;
}
#ifndef HAVE_SPLICE
if (s.flags & F_SPLICE) {
g_set_error(e, NBDS_ERR, NBDS_ERR_CFILE_VALUE_UNSUPPORTED, "This nbd-server was built without splice support, yet group %s uses it", groups[i]);
g_array_free(retval, TRUE);
g_key_file_free(cfile);
return NULL;
}
#endif
/* We can't mix copyonwrite and splice. */
if ((s.flags & F_COPYONWRITE) && (s.flags & F_SPLICE)) {
g_set_error(e, NBDS_ERR, NBDS_ERR_CFILE_INVALID_SPLICE,
"Cannot mix copyonwrite with splice for an export in group %s",
groups[i]);
g_array_free(retval, TRUE);
g_key_file_free(cfile);
return NULL;
}
if ((s.flags & F_COPYONWRITE) && (s.flags & F_WAIT)) {
g_set_error(e, NBDS_ERR, NBDS_ERR_CFILE_INVALID_WAIT,
"Cannot mix copyonwrite with waitfile for an export in group %s",
groups[i]);
g_array_free(retval, TRUE);
g_key_file_free(cfile);
return NULL;
}
/* Don't need to free this, it's not our string */
virtstyle=NULL;
/* Don't append values for the [generic] group */
if(i>0 || !expect_generic) {
s.servename = groups[i];