r38: configure.in:
[nbd.git] / nbd-server.c
index 26f5499..5163fb3 100644 (file)
  *     _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 <wouter@debian.org>
+ * 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
+ *     <wouter@debian.org>
+ * 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 <wouter@debian.org>
+ * 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
+ *     <wouter@debian.org>
  */
 
-#define VERSION "2.3"
+#define VERSION PACKAGE_VERSION
 #define GIGA (1*1024*1024*1024)
 
 #include <sys/types.h>
 // #define ISSERVER
 #define MY_NAME "nbd_server"
 
-/* Authorization file should contain lines with IP addresses of 
-   clients authorized to use the server. If it does not exist,
-   access is permitted. */
-#define AUTH_FILE "nbd_server.allow"
+/* how much space for child PIDs we have by default. Dynamically
+   allocated, and will be realloc()ed if out of space, so this should
+   probably be fair for most situations. */
+#define DEFAULT_CHILD_ARRAY 256
 
 #include "cliserv.h"
 //#undef _IO
 /* Deep magic: ioctl.h defines _IO macro (at least on linux) */
 
-
 /* Debugging macros, now nothing goes to syslog unless you say ISSERVER */
 #ifdef ISSERVER
 #define msg2(a,b) syslog(a,b)
@@ -73,7 +86,6 @@
 #define msg4(a,b,c,d) do { fprintf(stderr,b,c,d); fputs("\n",stderr) ; } while(0)
 #endif
 
-
 #include <sys/ioctl.h>
 #include <sys/mount.h>         /* For BLKGETSIZE */
 
@@ -95,29 +107,33 @@ void set_peername(int net,char *clientname);
 char difffilename[256];
 unsigned int timeout = 0;
 int autoreadonly = 0;
+char *auth_file="nbd_server.allow";
 
 int authorized_client(char *name)
 /* 0 - authorization refused, 1 - OK 
   authorization file contains one line per machine, no wildcards
 */
-{ FILE *f ;
+{
+       FILE *f ;
    
-  char line[LINELEN] ; 
+       char line[LINELEN] ; 
 
-  if ((f=fopen(AUTH_FILE,"r"))==NULL)
-    { msg4(LOG_INFO,"Can't open authorization file %s (%s).",
-          AUTH_FILE,strerror(errno)) ;
-      return 1 ; 
-    }
+       if ((f=fopen(auth_file,"r"))==NULL) {
+               msg4(LOG_INFO,"Can't open authorization file %s (%s).",
+                    auth_file,strerror(errno)) ;
+               return 1 ; 
+       }
   
-  while (fgets(line,LINELEN,f)!=NULL) {
-    if (strncmp(line,name,strlen(name))==0) { fclose(f)  ; return 1 ; }
-  }
-  fclose(f) ;
-  return 0 ;
+       while (fgets(line,LINELEN,f)!=NULL) {
+               if (strncmp(line,name,strlen(name))==0) {
+                       fclose(f);
+                       return 1;
+               }
+       }
+       fclose(f) ;
+       return 0 ;
 }
 
-
 inline void readit(int f, void *buf, int len)
 {
        int res;
@@ -142,7 +158,11 @@ inline void writeit(int f, void *buf, int len)
        }
 }
 
-#define OFFT_MAX (((off_t)1)<<(sizeof(off_t)*8))-1
+/* This is starting to get ugly. If someone knows a better way to find
+ * the maximum value of a signed type *without* relying on overflow
+ * (doing so breaks on 64bit architectures), that would be nice.
+ */
+#define OFFT_MAX (((((off_t)1)<<((sizeof(off_t)-1)*8))-1)<<7)+127
 int port;                      /* Port I'm listening at */
 char *exportname;              /* File I'm exporting */
 off_t exportsize = OFFT_MAX;   /* ...and its length */
@@ -153,7 +173,9 @@ int difffile=-1 ;
 u32 difffilelen=0 ; /* number of pages in difffile */
 u32 *difmap=NULL ;
 char clientname[256] ;
-
+int child_arraysize=DEFAULT_CHILD_ARRAY;
+pid_t *children;
+char pidfname[256];
 
 #define DIFFPAGESIZE 4096 /* diff file uses those chunks */
 
@@ -171,6 +193,7 @@ void cmdline(int argc, char *argv[])
                       "        -r read only\n"
                       "        -m multiple file\n"
                       "        -c copy on write\n"
+                      "        -l file with list of hosts that are allowed to connect.\n"
                       "        -a maximum idle seconds, terminates when idle time exceeded\n"
                       "        if port is set to 0, stdin is used (for running from inetd)\n"
                       "        if file_to_export contains '%%s', it is substituted with IP\n"
@@ -190,6 +213,14 @@ void cmdline(int argc, char *argv[])
                                break;
                        case 'c': flags |=F_COPYONWRITE;
                                break;
+                       case 'l':
+                               free(auth_file);
+                               if (i+1<argc) {
+                                       auth_file=argv[++i];
+                               } else {
+                                       fprintf(stderr, "host list file requires an argument");
+                               }
+                               break;
                        case 'a': 
                                if (i+1<argc) {
                                        timeout = atoi(argv[i+1]);
@@ -223,7 +254,41 @@ void cmdline(int argc, char *argv[])
 
 void sigchld_handler(int s)
 {
-       while(wait(NULL) > 0);
+        int* status=NULL;
+       int i;
+       pid_t pid;
+
+       while((pid=wait(status)) > 0) {
+               if(WIFEXITED(status)) {
+                       msg3(LOG_INFO, "Child exited with %d", WEXITSTATUS(status));
+               }
+               for(i=0;children[i]!=pid&&i<child_arraysize;i++);
+               if(i>=child_arraysize) {
+                       msg3(LOG_INFO, "SIGCHLD received for an unknown child with PID %ld",(long) pid);
+               } else {
+                       children[i]=(pid_t)0;
+                       DEBUG2("Removing %d from the list of children", pid);
+               }
+       }
+}
+
+/* If we are terminated, make sure our children are, too. */
+void sigterm_handler(int s) {
+       int i;
+       int parent=0;
+
+       for(i=0;i<child_arraysize;i++) {
+               if(children[i]) {
+                       kill(children[i], s);
+                       parent=1;
+               }
+       }
+
+       if(parent) {
+               unlink(pidfname);
+       }
+               
+       exit(0);
 }
 
 void connectme(int port)
@@ -231,19 +296,42 @@ void connectme(int port)
        struct sockaddr_in addrin;
        struct sigaction sa;
        int addrinlen = sizeof(addrin);
-       int net, sock, newpid;
+       int net, sock, newpid, i;
 #ifndef sun
        int yes=1;
 #else
        char yes='1';
-#endif
+#endif /* sun */
+#ifndef NODAEMON
+#ifndef NOFORK
+       FILE*pidf;
+
+       if(port) {
+               if(daemon(0,0)<0) {
+                       err("daemon");
+               }
+               snprintf(pidfname, sizeof(char)*255, "/var/run/nbd-server.%d.pid", port);
+               pidf=fopen(pidfname, "w");
+               if(pidf) {
+                       fprintf(pidf,"%d", (int)getpid());
+                       fclose(pidf);
+               } else {
+                       perror("fopen");
+                       fprintf(stderr, "Not fatal; continuing");
+               }
+       }
+#endif /* NOFORK */
+#endif /* NODAEMON */
 
        if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
                err("socket: %m");
 
        /* lose the pesky "Address already in use" error message */
        if (setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(int)) == -1) {
-               err("setsockopt");
+               err("setsockopt SO_REUSEADDR");
+       }
+       if (setsockopt(sock,SOL_SOCKET,SO_KEEPALIVE,&yes,sizeof(int)) == -1) {
+               err("setsockopt SO_KEEPALIVE");
        }
 
        DEBUG("Waiting for connections... bind, ");
@@ -261,28 +349,46 @@ void connectme(int port)
        sa.sa_flags = SA_RESTART;
        if(sigaction(SIGCHLD, &sa, NULL) == -1)
                err("sigaction: %m");
+       sa.sa_handler = sigterm_handler;
+       sigemptyset(&sa.sa_mask);
+       sa.sa_flags = SA_RESTART;
+       if(sigaction(SIGTERM, &sa, NULL) == -1)
+               err("sigaction: %m");
+       children=malloc(sizeof(pid_t)*child_arraysize);
+       memset(children, 0, sizeof(pid_t)*DEFAULT_CHILD_ARRAY);
        for(;;) { /* infinite loop */
-         if ((net = accept(sock, (struct sockaddr *) &addrin, &addrinlen)) < 0)
-           err("accept: %m");
-
-         set_peername(net,clientname) ;
-         if (!authorized_client(clientname)) {
-           msg2(LOG_INFO,"Unauthorized client") ;
-           close(net) ;
-           continue ;
-         }
-         msg2(LOG_INFO,"Authorized client") ;
-         if ((newpid=fork())<0) {
-           msg3(LOG_INFO,"Could not fork (%s)",strerror(errno)) ;
-           close(net) ;
-           continue ;
-         }
-         if (newpid>0) { /* parent */
-           close(net) ; continue ; }
-         /* child */
-         close(sock) ;
-         msg2(LOG_INFO,"Starting to serve") ;
-         serveconnection(net) ;        
+               if ((net = accept(sock, (struct sockaddr *) &addrin, &addrinlen)) < 0)
+                       err("accept: %m");
+               
+               set_peername(net,clientname) ;
+               if (!authorized_client(clientname)) {
+                       msg2(LOG_INFO,"Unauthorized client") ;
+                       close(net) ;
+                       continue ;
+               }
+               msg2(LOG_INFO,"Authorized client") ;
+               for(i=0;children[i]&&i<child_arraysize;i++);
+               if(i>=child_arraysize) {
+                       realloc(children, sizeof(pid_t)*child_arraysize);
+                       memset(children+child_arraysize, 0, sizeof(pid_t)*DEFAULT_CHILD_ARRAY);
+                       i=child_arraysize+1;
+                       child_arraysize+=DEFAULT_CHILD_ARRAY;
+               }
+#ifndef NOFORK
+               if ((children[i]=fork())<0) {
+                       msg3(LOG_INFO,"Could not fork (%s)",strerror(errno)) ;
+                       close(net) ;
+                       continue ;
+               }
+               if (children[i]>0) { /* parent */
+                       close(net) ; continue ; }
+               /* child */
+               realloc(children,0);
+               child_arraysize=0;
+               close(sock) ;
+#endif // NOFORK
+               msg2(LOG_INFO,"Starting to serve") ;
+               serveconnection(net) ;        
        }
 }
 
@@ -314,8 +420,8 @@ char pagebuf[DIFFPAGESIZE];
 
 int rawexpread(off_t a, char *buf, int len)
 {
-  maybeseek(export[a/hunksize], a%hunksize);
-  return (read(export[a/hunksize], buf, len) != len);
+       maybeseek(export[a/hunksize], a%hunksize);
+       return (read(export[a/hunksize], buf, len) != len);
 }
 
 int expread(off_t a, char *buf, int len)
@@ -400,7 +506,7 @@ int mainloop(int net)
        struct nbd_reply reply;
        char zeros[300];
        int i = 0;
-       off_t size_host;
+       u64 size_host;
 
        memset(zeros, 0, 290);
        if (write(net, INIT_PASSWD, 8) < 0)
@@ -408,7 +514,7 @@ int mainloop(int net)
        cliserv_magic = htonll(cliserv_magic);
        if (write(net, &cliserv_magic, sizeof(cliserv_magic)) < 0)
                err("Negotiation failed: %m");
-       size_host = htonll(exportsize);
+       size_host = htonll((u64)exportsize);
        if (write(net, &size_host, 8) < 0)
                err("Negotiation failed: %m");
        if (write(net, zeros, 128) < 0)
@@ -421,7 +527,6 @@ int mainloop(int net)
 #define BUFSIZE (1024*1024)
                char buf[BUFSIZE];
                int len;
-
 #ifdef DODBG
                i++;
                printf("%d: ", i);
@@ -518,18 +623,24 @@ off_t size_autodetect(int export)
        u32 es32;
        struct stat stat_buf;
        int error;
-
+       
        DEBUG("looking for export size with lseek SEEK_END\n");
        es = lseek(export, (off_t)0, SEEK_END);
-       if (es > ((off_t)0))
+       if (es > ((off_t)0)) {
                return es;
-
+        } else {
+                DEBUG2("lseek failed: %d", errno==EBADF?1:(errno==ESPIPE?2:(errno==EINVAL?3:4)));
+        }
+       
        DEBUG("looking for export size with fstat\n");
        stat_buf.st_size = 0;
        error = fstat(export, &stat_buf);
-       if (!error && stat_buf.st_size > 0)
+       if (!error && stat_buf.st_size > 0) {
                return (off_t)stat_buf.st_size;
-
+        } else {
+                err("fstat failed: %m");
+        }
+       
 #ifdef BLKGETSIZE
        DEBUG("looking for export size with ioctl BLKGETSIZE\n");
        if (!ioctl(export, BLKGETSIZE, &es32) && es32) {
@@ -538,7 +649,7 @@ off_t size_autodetect(int export)
        }
 #endif
        err("Could not find size of exported block device: %m");
-       return (off_t)-1;
+       return OFFT_MAX;
 }
 
 int main(int argc, char *argv[])
@@ -561,44 +672,44 @@ int main(int argc, char *argv[])
 
 void serveconnection(int net) 
 {   
-  off_t i ;
-
-  for (i=0; i<exportsize; i+=hunksize) {
-    char exportname3[1024];
-    
-    sprintf(exportname3, exportname2, i/hunksize);
-    printf( "Opening %s\n", exportname3 );
-    if ((export[i/hunksize] = open(exportname3, (flags & F_READONLY) ? O_RDONLY : O_RDWR)) == -1) {
-               /* Read WRITE ACCESS was requested by media is only read only */
-               autoreadonly = 1;
-               flags |= F_READONLY;
-               if ((export[i/hunksize] = open(exportname3, O_RDONLY)) == -1) 
-                       err("Could not open exported file: %m");
+       off_t i ;
+       
+       for (i=0; i<exportsize; i+=hunksize) {
+               char exportname3[1024];
+               
+               sprintf(exportname3, exportname2, i/hunksize);
+               printf( "Opening %s\n", exportname3 );
+               if ((export[i/hunksize] = open(exportname3, (flags & F_READONLY) ? O_RDONLY : O_RDWR)) == -1) {
+                       /* Read WRITE ACCESS was requested by media is only read only */
+                       autoreadonly = 1;
+                       flags |= F_READONLY;
+                       if ((export[i/hunksize] = open(exportname3, O_RDONLY)) == -1) 
+                               err("Could not open exported file: %m");
+               }
        }
-    }
-
-    if (exportsize == (off_t)OFFT_MAX) {
-       exportsize = size_autodetect(export[0]);
-    }
-    if (exportsize > (off_t)OFFT_MAX) {
-       err("Size of exported file is too big\n");
-    }
-    else
-       msg3(LOG_INFO, "size of exported file/device is %Lu",
-                       (unsigned long long)exportsize);
-
-    if (flags & F_COPYONWRITE) {
-      sprintf(difffilename,"%s-%s-%d.diff",exportname2,clientname,
-             (int)getpid()) ;
-      msg3(LOG_INFO,"About to create map and diff file %s",difffilename) ;
-      difffile=open(difffilename,O_RDWR | O_CREAT | O_TRUNC,0600) ;
-      if (difffile<0) err("Could not create diff file (%m)") ;
-      if ((difmap=calloc(exportsize/DIFFPAGESIZE,sizeof(u32)))==NULL)
-         err("Could not allocate memory") ;
-      for (i=0;i<exportsize/DIFFPAGESIZE;i++) difmap[i]=(u32)-1 ;        
-    }
-    
-    setmysockopt(net);
-      
-    mainloop(net);
+       
+       if (exportsize == (off_t)OFFT_MAX) {
+               exportsize = size_autodetect(export[0]);
+       }
+       if (exportsize > (off_t)OFFT_MAX) {
+               err("Size of exported file is too big\n");
+       }
+       else
+               msg3(LOG_INFO, "size of exported file/device is %Lu",
+                    (unsigned long long)exportsize);
+       
+       if (flags & F_COPYONWRITE) {
+               sprintf(difffilename,"%s-%s-%d.diff",exportname2,clientname,
+                       (int)getpid()) ;
+               msg3(LOG_INFO,"About to create map and diff file %s",difffilename) ;
+               difffile=open(difffilename,O_RDWR | O_CREAT | O_TRUNC,0600) ;
+               if (difffile<0) err("Could not create diff file (%m)") ;
+               if ((difmap=calloc(exportsize/DIFFPAGESIZE,sizeof(u32)))==NULL)
+                       err("Could not allocate memory") ;
+               for (i=0;i<exportsize/DIFFPAGESIZE;i++) difmap[i]=(u32)-1 ;       
+       }
+       
+       setmysockopt(net);
+       
+       mainloop(net);
 }