forked from NetworkBlockDevice/nbd
-
Notifications
You must be signed in to change notification settings - Fork 0
/
nbd-server.c
3097 lines (2805 loc) · 91.8 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"
#include <assert.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/select.h>
#include <sys/wait.h>
#ifdef HAVE_SYS_IOCTL_H
#include <sys/ioctl.h>
#endif
#include <sys/param.h>
#ifdef HAVE_SYS_MOUNT_H
#include <sys/mount.h>
#endif
#include <signal.h>
#include <errno.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>
#include <unistd.h>
#include <getopt.h>
#include <pwd.h>
#include <grp.h>
#include <dirent.h>
#include <glib.h>
/* used in cliserv.h, so must come first */
#define MY_NAME "nbd_server"
#include "cliserv.h"
#include "netdb-compat.h"
#ifdef WITH_SDP
#include <sdp_inet.h>
#endif
/** Default position of the config file */
#ifndef SYSCONFDIR
#define SYSCONFDIR "/etc"
#endif
#define CFILE SYSCONFDIR "/nbd-server/config"
/** Where our config file actually is */
gchar* config_file_pos;
/** global flags */
int glob_flags=0;
/* Whether we should avoid forking */
int dontfork = 0;
/** Logging macros, now nothing goes to syslog unless you say ISSERVER */
#ifdef ISSERVER
#define msg(prio, ...) syslog(prio, __VA_ARGS__)
#else
#define msg(prio, ...) g_log(G_LOG_DOMAIN, G_LOG_LEVEL_MESSAGE, __VA_ARGS__)
#endif
/* Debugging macros */
//#define DODBG
#ifdef DODBG
#define DEBUG(...) printf(__VA_ARGS__)
#else
#define DEBUG(...)
#endif
#ifndef PACKAGE_VERSION
#define PACKAGE_VERSION ""
#endif
/**
* 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 LINELEN 256 /**< Size of static buffer used to read the
authorization file (yuck) */
#define BUFSIZE ((1024*1024)+sizeof(struct nbd_reply)) /**< Size of buffer that can hold requests */
#define DIFFPAGESIZE 4096 /**< diff file uses those chunks */
/** Per-export flags: */
#define F_READONLY 1 /**< flag to tell us a file is readonly */
#define F_MULTIFILE 2 /**< flag to tell us a file is exported using -m */
#define F_COPYONWRITE 4 /**< flag to tell us a file is exported using
copyonwrite */
#define F_AUTOREADONLY 8 /**< flag to tell us a file is set to autoreadonly */
#define F_SPARSE 16 /**< flag to tell us copyronwrite should use a sparse file */
#define F_SDP 32 /**< flag to tell us the export should be done using the Socket Direct Protocol for RDMA */
#define F_SYNC 64 /**< Whether to fsync() after a write */
#define F_FLUSH 128 /**< Whether server wants FLUSH to be sent by the client */
#define F_FUA 256 /**< Whether server wants FUA to be sent by the client */
#define F_ROTATIONAL 512 /**< Whether server wants the client to implement the elevator algorithm */
#define F_TEMPORARY 1024 /**< Whether the backing file is temporary and should be created then unlinked */
#define F_TRIM 2048 /**< Whether server wants TRIM (discard) to be sent by the client */
#define F_FIXED 4096 /**< Client supports fixed new-style protocol (and can thus send us extra options */
/** Global flags: */
#define F_OLDSTYLE 1 /**< Allow oldstyle (port-based) exports */
#define F_LIST 2 /**< Allow clients to list the exports on a server */
GHashTable *children;
char pidfname[256]; /**< name of our PID file */
char pidftemplate[256]; /**< template to be used for the filename of the 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)
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) */
bool logged_oversized=false; /**< whether we logged oversized requests already */
/**
* Types of virtuatlization
**/
typedef enum {
VIRT_NONE=0, /**< No virtualization */
VIRT_IPLIT, /**< Literal IP address as part of the filename */
VIRT_IPHASH, /**< Replacing all dots in an ip address by a / before
doing the same as in IPLIT */
VIRT_CIDR, /**< Every subnet in its own directory */
} VIRT_STYLE;
/**
* Variables associated with a server.
**/
typedef struct {
gchar* exportname; /**< (unprocessed) filename of the file we're exporting */
off_t expected_size; /**< size of the exported file as it was told to
us through configuration */
gchar* listenaddr; /**< The IP address we're listening on */
unsigned int port; /**< port we're exporting this file at */
char* authname; /**< filename of the authorization file */
int flags; /**< flags associated with this exported file */
int socket; /**< The socket of this server. */
int socket_family; /**< family of the socket */
VIRT_STYLE virtstyle;/**< The style of virtualization, if any */
uint8_t cidrlen; /**< The length of the mask when we use
CIDR-style virtualization */
gchar* prerun; /**< command to be ran after connecting a client,
but before starting to serve */
gchar* postrun; /**< command that will be ran after the client
disconnects */
gchar* servename; /**< name of the export as selected by nbd-client */
int max_connections; /**< maximum number of opened connections */
gchar* transactionlog;/**< filename for transaction log */
} SERVER;
/**
* Variables associated with a client socket.
**/
typedef struct {
int fhandle; /**< file descriptor */
off_t startoff; /**< starting offset of this file */
} FILE_INFO;
typedef struct {
off_t exportsize; /**< size of the file we're exporting */
char *clientname; /**< peer */
char *exportname; /**< (processed) filename of the file we're exporting */
GArray *export; /**< array of FILE_INFO of exported files;
array size is always 1 unless we're
doing the multiple file option */
int net; /**< The actual client socket */
SERVER *server; /**< The server this client is getting data from */
char* difffilename; /**< filename of the copy-on-write file, if any */
int difffile; /**< filedescriptor of copyonwrite file. @todo
shouldn't this be an array too? (cfr export) Or
make -m and -c mutually exclusive */
u32 difffilelen; /**< number of pages in difffile */
u32 *difmap; /**< see comment on the global difmap for this one */
gboolean modern; /**< client was negotiated using modern negotiation protocol */
int transactionlogfd;/**< fd for transaction log */
int clientfeats; /**< Features supported by this client */
} CLIENT;
/**
* 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 */
gint flags; /**< global flags */
};
/**
* 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";
default:
return "UNKNOWN";
}
}
/**
* Check whether a client is allowed to connect. Works with an authorization
* file which contains one line per machine, no wildcards.
*
* @param opts The client who's trying to connect.
* @return 0 - authorization refused, 1 - OK
**/
int authorized_client(CLIENT *opts) {
const char *ERRMSG="Invalid entry '%s' in authfile '%s', so, refusing all connections.";
FILE *f ;
char line[LINELEN];
char *tmp;
struct in_addr addr;
struct in_addr client;
struct in_addr cltemp;
int len;
if ((f=fopen(opts->server->authname,"r"))==NULL) {
msg(LOG_INFO, "Can't open authorization file %s (%s).",
opts->server->authname, strerror(errno));
return 1 ;
}
inet_aton(opts->clientname, &client);
while (fgets(line,LINELEN,f)!=NULL) {
if((tmp=strchr(line, '/'))) {
if(strlen(line)<=tmp-line) {
msg(LOG_CRIT, ERRMSG, line, opts->server->authname);
return 0;
}
*(tmp++)=0;
if(!inet_aton(line,&addr)) {
msg(LOG_CRIT, ERRMSG, line, opts->server->authname);
return 0;
}
len=strtol(tmp, NULL, 0);
addr.s_addr>>=32-len;
addr.s_addr<<=32-len;
memcpy(&cltemp,&client,sizeof(client));
cltemp.s_addr>>=32-len;
cltemp.s_addr<<=32-len;
if(addr.s_addr == cltemp.s_addr) {
return 1;
}
}
if (strncmp(line,opts->clientname,strlen(opts->clientname))==0) {
fclose(f);
return 1;
}
}
fclose(f);
return 0;
}
/**
* Read data from a file descriptor into a buffer
*
* @param f a file descriptor
* @param buf a buffer
* @param len the number of bytes to be read
**/
static inline void readit(int f, void *buf, size_t len) {
ssize_t res;
while (len > 0) {
DEBUG("*");
if ((res = read(f, buf, len)) <= 0) {
if(errno != EAGAIN) {
err("Read failed: %m");
}
} else {
len -= res;
buf += res;
}
}
}
/**
* Consume data from an FD that we don't want
*
* @param f a file descriptor
* @param buf a buffer
* @param len the number of bytes to consume
* @param bufsiz the size of the buffer
**/
static inline void consume(int f, void * buf, size_t len, size_t bufsiz) {
size_t curlen;
while (len>0) {
curlen = (len>bufsiz)?bufsiz:len;
readit(f, buf, curlen);
len -= curlen;
}
}
/**
* Write data from a buffer into a filedescriptor
*
* @param f a file descriptor
* @param buf a buffer containing data
* @param len the number of bytes to be written
**/
static inline void writeit(int f, void *buf, size_t len) {
ssize_t res;
while (len > 0) {
DEBUG("+");
if ((res = write(f, buf, len)) <= 0)
err("Send failed: %m");
len -= res;
buf += res;
}
}
/**
* 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]\n"
"\t-r|--read-only\t\tread only\n"
"\t-m|--multi-file\t\tmultiple file\n"
"\t-c|--copy-on-write\tcopy on write\n"
"\t-C|--config-file\tspecify an alternate configuration file\n"
"\t-l|--authorize-file\tfile with list of hosts that are allowed to\n\t\t\t\tconnect.\n"
"\t-p|--pid-file\t\tspecify a filename to write our PID to\n"
"\t-o|--output-config\toutput a config file section for what you\n\t\t\t\tspecified on the command line, with the\n\t\t\t\tspecified section name\n"
"\t-M|--max-connections\tspecify the maximum number of opened connections\n\n"
"\tif port is set to 0, stdin is used (for running from inetd).\n"
"\tif file_to_export contains '%%s', it is substituted with the IP\n"
"\t\taddress of the machine trying to connect\n"
"\tif ip is set, it contains the local IP address on which we're listening.\n\tif not, the server will listen on all local IP addresses\n");
printf("Using configuration file %s\n", CFILE);
}
/* 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);
printf("\tport = %d\n", serve->port);
if(serve->flags & F_READONLY) {
printf("\treadonly = true\n");
}
if(serve->flags & F_MULTIFILE) {
printf("\tmultifile = 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[]) {
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'},
{0,0,0,0}
};
SERVER *serve;
off_t es;
size_t last;
char suffix;
gboolean 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:cdl:mo:rp:M:", 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]) {
serve->port=strtol(addr_port[1], NULL, 0);
serve->listenaddr=g_strdup(addr_port[0]);
} else {
serve->listenaddr=NULL;
serve->port=strtol(addr_port[0], NULL, 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(pidftemplate, optarg, 256);
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;
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 {
glob_flags |= F_OLDSTYLE;
}
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;
}
/**
* Error domain common for all NBD server errors.
**/
#define NBDS_ERR g_quark_from_static_string("server-error-quark")
/**
* NBD server error codes.
**/
typedef enum {
NBDS_ERR_CFILE_NOTFOUND, /**< The configuration file is not found */
NBDS_ERR_CFILE_MISSING_GENERIC, /**< The (required) group "generic" is missing */
NBDS_ERR_CFILE_KEY_MISSING, /**< A (required) key is missing */
NBDS_ERR_CFILE_VALUE_INVALID, /**< A value is syntactically invalid */
NBDS_ERR_CFILE_VALUE_UNSUPPORTED, /**< A value is not supported in this build */
NBDS_ERR_CFILE_NO_EXPORTS, /**< A config file was specified that does not
define any exports */
NBDS_ERR_CFILE_INCORRECT_PORT, /**< The reserved port was specified for an
old-style export. */
NBDS_ERR_CFILE_DIR_UNKNOWN, /**< A directory requested does not exist*/
NBDS_ERR_CFILE_READDIR_ERR, /**< Error occurred during readdir() */
NBDS_ERR_SO_LINGER, /**< Failed to set SO_LINGER to a socket */
NBDS_ERR_SO_REUSEADDR, /**< Failed to set SO_REUSEADDR to a socket */
NBDS_ERR_SO_KEEPALIVE, /**< Failed to set SO_KEEPALIVE to a socket */
NBDS_ERR_GAI, /**< Failed to get address info */
NBDS_ERR_SOCKET, /**< Failed to create a socket */
NBDS_ERR_BIND, /**< Failed to bind an address to socket */
NBDS_ERR_LISTEN, /**< Failed to start listening on a socket */
NBDS_ERR_SYS, /**< Underlying system call or library error */
} NBDS_ERRS;
/**
* duplicate server
* @param s the old server we want to duplicate
* @return new duplicated server
**/
SERVER* dup_serve(const SERVER *const s) {
SERVER *serve = NULL;
serve=g_new0(SERVER, 1);
if(serve == NULL)
return NULL;
if(s->exportname)
serve->exportname = g_strdup(s->exportname);
serve->expected_size = s->expected_size;
if(s->listenaddr)
serve->listenaddr = g_strdup(s->listenaddr);
serve->port = s->port;
if(s->authname)
serve->authname = strdup(s->authname);
serve->flags = s->flags;
serve->socket = s->socket;
serve->socket_family = s->socket_family;
serve->virtstyle = s->virtstyle;
serve->cidrlen = s->cidrlen;
if(s->prerun)
serve->prerun = g_strdup(s->prerun);
if(s->postrun)
serve->postrun = g_strdup(s->postrun);
if(s->transactionlog)
serve->transactionlog = g_strdup(s->transactionlog);
if(s->servename)
serve->servename = g_strdup(s->servename);
serve->max_connections = s->max_connections;
return serve;
}
/**
* append new server to array
* @param s server
* @param a server array
* @return 0 success, -1 error
*/
int append_serve(const SERVER *const s, GArray *const a) {
SERVER *ns = NULL;
struct addrinfo hints;
struct addrinfo *ai = NULL;
struct addrinfo *rp = NULL;
char host[NI_MAXHOST];
gchar *port = NULL;
int e;
int ret;
assert(s != NULL);
port = g_strdup_printf("%d", s->port);
memset(&hints,'\0',sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_ADDRCONFIG | AI_PASSIVE;
hints.ai_protocol = IPPROTO_TCP;
e = getaddrinfo(s->listenaddr, port, &hints, &ai);
if (port)
g_free(port);
if(e == 0) {
for (rp = ai; rp != NULL; rp = rp->ai_next) {
e = getnameinfo(rp->ai_addr, rp->ai_addrlen, host, sizeof(host), NULL, 0, NI_NUMERICHOST);
if (e != 0) { // error
fprintf(stderr, "getnameinfo: %s\n", gai_strerror(e));
continue;
}
// duplicate server and set listenaddr to resolved IP address
ns = dup_serve (s);
if (ns) {
ns->listenaddr = g_strdup(host);
ns->socket_family = rp->ai_family;
g_array_append_val(a, *ns);
free(ns);
ns = NULL;
}
}
ret = 0;
} else {
fprintf(stderr, "getaddrinfo failed on listen host/address: %s (%s)\n", s->listenaddr ? s->listenaddr : "any", gai_strerror(e));
ret = -1;
}
if (ai)
freeaddrinfo(ai);
return ret;
}
/* forward definition of parse_cfile */
GArray* parse_cfile(gchar* f, struct generic_conf *genconf, GError** e);
/**
* 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, GError** e) {
DIR* dirh = opendir(dir);
struct dirent* de;
gchar* fname;
GArray* retval = NULL;
GArray* tmp;
struct stat stbuf;
if(!dir) {
g_set_error(e, NBDS_ERR, NBDS_ERR_CFILE_DIR_UNKNOWN, "Invalid directory specified: %s", strerror(errno));
return NULL;
}
errno=0;
while((de = readdir(dirh))) {
int saved_errno=errno;
fname = g_build_filename(dir, de->d_name, NULL);
switch(de->d_type) {
case DT_UNKNOWN:
/* Filesystem doesn't return type of
* file through readdir. Run stat() on
* the file instead */
if(stat(fname, &stbuf)) {
perror("stat");
goto err_out;
}
if (!S_ISREG(stbuf.st_mode)) {
goto next;
}
case DT_REG:
/* Skip unless the name ends with '.conf' */
if(strcmp((de->d_name + strlen(de->d_name) - 5), ".conf")) {
goto next;
}
tmp = parse_cfile(fname, NULL, e);
errno=saved_errno;
if(*e) {
goto err_out;
}
if(!retval)
retval = g_array_new(FALSE, TRUE, sizeof(SERVER));
retval = g_array_append_vals(retval, tmp->data, tmp->len);
g_array_free(tmp, TRUE);
default:
break;
}
next:
g_free(fname);
}
if(errno) {
g_set_error(e, NBDS_ERR, NBDS_ERR_CFILE_READDIR_ERR, "Error trying to read directory: %s", strerror(errno));
err_out:
if(retval)
g_array_free(retval, TRUE);
return NULL;
}
return retval;
}
/**
* 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.
*
* @return a Array of SERVER* pointers, If the config file is empty or does not
* exist, returns an empty GHashTable; if the config file contains an
* error, returns NULL, and e is set appropriately
**/
GArray* parse_cfile(gchar* f, struct generic_conf *const genconf, GError** e) {
const char* DEFAULT_ERROR = "Could not parse %s in group %s: %s";
const char* MISSING_REQUIRED_ERROR = "Could not find required value %s in group %s: %s";
gchar* cfdir = NULL;
SERVER s;
gchar *virtstyle=NULL;
PARAM lp[] = {
{ "exportname", TRUE, PARAM_STRING, &(s.exportname), 0 },
{ "port", TRUE, PARAM_INT, &(s.port), 0 },
{ "authfile", FALSE, PARAM_STRING, &(s.authname), 0 },
{ "filesize", FALSE, PARAM_OFFT, &(s.expected_size), 0 },
{ "virtstyle", FALSE, PARAM_STRING, &(virtstyle), 0 },
{ "prerun", FALSE, PARAM_STRING, &(s.prerun), 0 },
{ "postrun", FALSE, PARAM_STRING, &(s.postrun), 0 },
{ "transactionlog", FALSE, PARAM_STRING, &(s.transactionlog), 0 },
{ "readonly", FALSE, PARAM_BOOL, &(s.flags), F_READONLY },
{ "multifile", FALSE, PARAM_BOOL, &(s.flags), F_MULTIFILE },
{ "copyonwrite", FALSE, PARAM_BOOL, &(s.flags), F_COPYONWRITE },
{ "sparse_cow", FALSE, PARAM_BOOL, &(s.flags), F_SPARSE },
{ "sdp", FALSE, PARAM_BOOL, &(s.flags), F_SDP },
{ "sync", FALSE, PARAM_BOOL, &(s.flags), F_SYNC },
{ "flush", FALSE, PARAM_BOOL, &(s.flags), F_FLUSH },
{ "fua", FALSE, PARAM_BOOL, &(s.flags), F_FUA },
{ "rotational", FALSE, PARAM_BOOL, &(s.flags), F_ROTATIONAL },
{ "temporary", FALSE, PARAM_BOOL, &(s.flags), F_TEMPORARY },
{ "trim", FALSE, PARAM_BOOL, &(s.flags), F_TRIM },
{ "listenaddr", FALSE, PARAM_STRING, &(s.listenaddr), 0 },
{ "maxconnections", FALSE, PARAM_INT, &(s.max_connections), 0 },
};
const int lp_size=sizeof(lp)/sizeof(PARAM);
struct generic_conf genconftmp;
PARAM gp[] = {
{ "user", FALSE, PARAM_STRING, &(genconftmp.user), 0 },
{ "group", FALSE, PARAM_STRING, &(genconftmp.group), 0 },
{ "oldstyle", FALSE, PARAM_BOOL, &(genconftmp.flags), F_OLDSTYLE },
{ "listenaddr", FALSE, PARAM_STRING, &(genconftmp.modernaddr), 0 },
{ "port", FALSE, PARAM_STRING, &(genconftmp.modernport), 0 },
{ "includedir", FALSE, PARAM_STRING, &cfdir, 0 },
{ "allowlist", FALSE, PARAM_BOOL, &(genconftmp.flags), F_LIST },
};
PARAM* p=gp;
int p_size=sizeof(gp)/sizeof(PARAM);
GKeyFile *cfile;
GError *err = NULL;
const char *err_msg=NULL;
GArray *retval=NULL;
gchar **groups;
gboolean bval;
gint ival;
gint64 i64val;
gchar* sval;
gchar* startgroup;
gint i;
gint j;
memset(&genconftmp, 0, sizeof(struct generic_conf));
if (genconf) {
/* Use the passed configuration values as defaults. The
* parsing algorithm below updates all parameter targets
* found from configuration files. */
memcpy(&genconftmp, genconf, sizeof(struct generic_conf));
}
cfile = g_key_file_new();
retval = g_array_new(FALSE, TRUE, sizeof(SERVER));
if(!g_key_file_load_from_file(cfile, f, G_KEY_FILE_KEEP_COMMENTS |
G_KEY_FILE_KEEP_TRANSLATIONS, &err)) {
g_set_error(e, NBDS_ERR, NBDS_ERR_CFILE_NOTFOUND, "Could not open config file %s: %s",
f, err->message);
g_key_file_free(cfile);
return retval;
}
startgroup = g_key_file_get_start_group(cfile);
if((!startgroup || strcmp(startgroup, "generic")) && genconf) {
g_set_error(e, NBDS_ERR, NBDS_ERR_CFILE_MISSING_GENERIC, "Config file does not contain the [generic] group!");
g_key_file_free(cfile);
return NULL;
}
groups = g_key_file_get_groups(cfile, NULL);
for(i=0;groups[i];i++) {
memset(&s, '\0', sizeof(SERVER));
/* After the [generic] group or when we're parsing an include
* directory, start parsing exports */
if(i==1 || !genconf) {
p=lp;
p_size=lp_size;
if(!(glob_flags & F_OLDSTYLE)) {
lp[1].required = FALSE;
}
}
for(j=0;j<p_size;j++) {
assert(p[j].target != NULL);
assert(p[j].ptype==PARAM_INT||p[j].ptype==PARAM_STRING||p[j].ptype==PARAM_BOOL||p[j].ptype==PARAM_INT64);
switch(p[j].ptype) {
case PARAM_INT:
ival = g_key_file_get_integer(cfile,
groups[i],
p[j].paramname,
&err);
if(!err) {
*((gint*)p[j].target) = ival;
}
break;
case PARAM_INT64:
i64val = g_key_file_get_int64(cfile,
groups[i],
p[j].paramname,
&err);
if(!err) {
*((gint64*)p[j].target) = i64val;
}
break;
case PARAM_STRING:
sval = g_key_file_get_string(cfile,
groups[i],
p[j].paramname,
&err);
if(!err) {
*((gchar**)p[j].target) = sval;
}
break;
case PARAM_BOOL:
bval = g_key_file_get_boolean(cfile,
groups[i],
p[j].paramname, &err);
if(!err) {
if(bval) {
*((gint*)p[j].target) |= p[j].flagval;
} else {
*((gint*)p[j].target) &= ~(p[j].flagval);
}
}
break;
}
if(err) {
if(err->code == G_KEY_FILE_ERROR_KEY_NOT_FOUND) {
if(!p[j].required) {
/* Ignore not-found error for optional values */
g_clear_error(&err);
continue;
} else {
err_msg = MISSING_REQUIRED_ERROR;
}
} else {
err_msg = DEFAULT_ERROR;
}
g_set_error(e, NBDS_ERR, NBDS_ERR_CFILE_VALUE_INVALID, err_msg, p[j].paramname, groups[i], err->message);
g_array_free(retval, TRUE);
g_error_free(err);
g_key_file_free(cfile);
return NULL;