r25: * Added Large File Support to nbd-server.
[nbd.git] / nbd-server.c
index 5803811..046ff05 100644 (file)
@@ -3,6 +3,7 @@
  *
  * Copyright 1996-1998 Pavel Machek, distribute under GPL
  *  <pavel@atrey.karlin.mff.cuni.cz>
+ * Copyright 2002 Anton Altaparmakov <aia21@cam.ac.uk>
  *
  * Version 1.0 - hopefully 64-bit-clean
  * Version 1.1 - merging enhancements from Josh Parsons, <josh@coombs.anu.edu.au>
  *     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 <ken@nlc.net.au>.
+ * Version 1.6 - fix autodetection of block device size and really make 64 bit
+ *     clean on 32 bit machines. Anton Altaparmakov <aia21@cam.ac.uk>
+ * 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 <wouter@debian.org>
+ * 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 <wouter@debian.org>
  */
 
-#define VERSION "1.5"
+#define VERSION "2.3"
 #define GIGA (1*1024*1024*1024)
 
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <sys/stat.h>
+#include <sys/wait.h>          /* wait */
+#include <signal.h>            /* sigaction */
 #include <netinet/tcp.h>
 #include <netinet/in.h>                /* sockaddr_in, htons, in_addr */
 #include <netdb.h>             /* hostent, gethostby*, getservby* */
@@ -33,7 +48,7 @@
 #include <arpa/inet.h>
 #include <strings.h>
 
-#define _IO(a,b)
+//#define _IO(a,b)
 // #define ISSERVER
 #define MY_NAME "nbd_server"
 
@@ -43,7 +58,7 @@
 #define AUTH_FILE "nbd_server.allow"
 
 #include "cliserv.h"
-#undef _IO
+//#undef _IO
 /* Deep magic: ioctl.h defines _IO macro (at least on linux) */
 
 
 #include <sys/ioctl.h>
 #include <sys/mount.h>         /* For BLKGETSIZE */
 
-#ifdef FS_32BIT
-typedef u32            fsoffset_t;
-#define htonll         htonl
-#define ntohll         ntohl
-#else
-typedef u64            fsoffset_t;
-#endif
-
-
 //#define DODBG
 #ifdef DODBG
 #define DEBUG( a ) printf( a )
@@ -82,17 +88,13 @@ typedef u64         fsoffset_t;
 #define DEBUG3( a,b,c ) 
 #endif
 
-#if    defined(HAVE_LLSEEK) && !defined(sun)
-/* Solaris already has llseek defined in unistd.h */
-extern long long llseek(unsigned int, long long, unsigned int);
-#endif
-
 void serveconnection(int net);
 void set_peername(int net,char *clientname);
 
 #define LINELEN 256 
 char difffilename[256];
 unsigned int timeout = 0;
+int autoreadonly = 0;
 
 int authorized_client(char *name)
 /* 0 - authorization refused, 1 - OK 
@@ -140,9 +142,11 @@ inline void writeit(int f, void *buf, int len)
        }
 }
 
+#define OFFT_MAX (((off_t)1)<<(sizeof(off_t)*8))-1
 int port;                      /* Port I'm listening at */
 char *exportname;              /* File I'm exporting */
-fsoffset_t exportsize = ~0, hunksize = ~0;     /* ...and its length */
+off_t exportsize = OFFT_MAX;   /* ...and its length */
+off_t hunksize = OFFT_MAX;
 int flags = 0;
 int export[1024];
 int difffile=-1 ;
@@ -196,13 +200,13 @@ void cmdline(int argc, char *argv[])
                                }
                        }
                } else {
-                       fsoffset_t es;
+                       off_t es;
                        int last = strlen(argv[i])-1;
                        char suffix = argv[i][last];
                        if (suffix == 'k' || suffix == 'K' ||
                            suffix == 'm' || suffix == 'M')
                                argv[i][last] = '\0';
-                       es = (fsoffset_t)atol(argv[i]);
+                       es = (off_t)atol(argv[i]);
                        switch (suffix) {
                                case 'm':
                                case 'M':  es <<= 10;
@@ -217,9 +221,15 @@ void cmdline(int argc, char *argv[])
        exportname = argv[2];
 }
 
+void sigchld_handler(int s)
+{
+       while(wait(NULL) > 0);
+}
+
 void connectme(int port)
 {
        struct sockaddr_in addrin;
+       struct sigaction sa;
        int addrinlen = sizeof(addrin);
        int net, sock, newpid;
 #ifndef sun
@@ -246,6 +256,11 @@ void connectme(int port)
        if (listen(sock, 1) < 0)
                err("listen: %m");
        DEBUG("accept, ");
+       sa.sa_handler = sigchld_handler;
+       sigemptyset(&sa.sa_mask);
+       sa.sa_flags = SA_RESTART;
+       if(sigaction(SIGCHLD, &sa, NULL) == -1)
+               err("sigaction: %m");
        for(;;) { /* infinite loop */
          if ((net = accept(sock, (struct sockaddr *) &addrin, &addrinlen)) < 0)
            err("accept: %m");
@@ -274,114 +289,109 @@ void connectme(int port)
 #define SEND writeit( net, &reply, sizeof( reply ));
 #define ERROR { reply.error = htonl(-1); SEND; reply.error = 0; lastpoint = -1; }
 
-fsoffset_t lastpoint = -1;
+off_t lastpoint = (off_t)-1;
 
-void maybeseek(int handle, fsoffset_t a)
+void maybeseek(int handle, off_t a)
 {
-       if (a > exportsize)
-               err("Can not happen\n");
-       if (lastpoint != a) {
-#if    defined(HAVE_LLSEEK) && !defined(FS_32BIT)
-               if (llseek(handle, a, SEEK_SET) < 0)
-#else
-               if (lseek(handle, (long)a, SEEK_SET) < 0)
-#endif
-                       err("Can not seek locally!\n");
-               lastpoint = a;
-       } else {
-               DEBUG("@");
-       }
+if (a > exportsize)
+       err("Can not happen\n");
+if (lastpoint != a) {
+       if (lseek(handle, a, SEEK_SET) < 0)
+               err("Can not seek locally!\n");
+       lastpoint = a;
+} else {
+       DEBUG("@");
+}
 }
 
-void myseek(int handle,fsoffset_t a)
+void myseek(int handle,off_t a)
 {
-#if HAVE_LLSEEK && !defined(FS_32BIT)
-  if (llseek(handle, a, SEEK_SET) < 0)
-#else
-  if (lseek(handle, (long)a, SEEK_SET) < 0)
-#endif 
-    err("Can not seek locally!\n");
+       if (lseek(handle, a, SEEK_SET) < 0)
+               err("Can not seek locally!\n");
 }
 
-char pagebuf[DIFFPAGESIZE] ;
+char pagebuf[DIFFPAGESIZE];
 
-
-int rawexpread(fsoffset_t a, char *buf, int len)
+int rawexpread(off_t a, char *buf, int len)
 {
   maybeseek(export[a/hunksize], a%hunksize);
   return (read(export[a/hunksize], buf, len) != len);
 }
 
-int expread(fsoffset_t a, char *buf, int len)
-{ int rdlen ; fsoffset_t mapcnt,mapl,maph ;
-  fsoffset_t pagestart; int offset ;
+int expread(off_t a, char *buf, int len)
+{
+       int rdlen, offset;
+       off_t mapcnt, mapl, maph, pagestart;
  
-  if (flags & F_COPYONWRITE) {
-    DEBUG3("Asked to read %d bytes at %lu.\n",len,(unsigned long)a) ;
-
-    mapl=a/DIFFPAGESIZE ; maph=(a+len-1)/DIFFPAGESIZE ;
-
-    for (mapcnt=mapl;mapcnt<=maph;mapcnt++) {
-      pagestart=mapcnt*DIFFPAGESIZE ;
-      offset=a-pagestart ;
-      rdlen=(len<DIFFPAGESIZE-offset) ? len : DIFFPAGESIZE-offset ;
-      if (difmap[mapcnt]!=(u32)(-1)) { /* the block is already there */
-       DEBUG3("Page %d is at %ld\n",(int)mapcnt,(long int)difmap[mapcnt]) ;
-       myseek(difffile,difmap[mapcnt]*DIFFPAGESIZE+offset) ;
-       if (read(difffile, buf, rdlen) != rdlen) return -1 ;
-      } else { /* the block is not there */
-       DEBUG2("Page %d is not here, we read the original one\n",
-             (int)mapcnt) ;
-       if (rawexpread(a,buf,rdlen)) return -1 ;
-      }
-      len-=rdlen ; a+=rdlen ; buf+=rdlen ;
-    }
-  } else return rawexpread(a,buf,len) ;
-  return 0 ;
+       if (!(flags & F_COPYONWRITE))
+               return rawexpread(a, buf, len);
+       DEBUG3("Asked to read %d bytes at %Lu.\n", len, (unsigned long long)a);
+
+       mapl=a/DIFFPAGESIZE; maph=(a+len-1)/DIFFPAGESIZE;
+
+       for (mapcnt=mapl;mapcnt<=maph;mapcnt++) {
+               pagestart=mapcnt*DIFFPAGESIZE;
+               offset=a-pagestart;
+               rdlen=(len<DIFFPAGESIZE-offset) ? len : DIFFPAGESIZE-offset;
+               if (difmap[mapcnt]!=(u32)(-1)) { /* the block is already there */
+                       DEBUG3("Page %Lu is at %lu\n", (unsigned long long)mapcnt,
+                              (unsigned long)difmap[mapcnt]);
+                       myseek(difffile, difmap[mapcnt]*DIFFPAGESIZE+offset);
+                       if (read(difffile, buf, rdlen) != rdlen) return -1;
+               } else { /* the block is not there */
+                       DEBUG2("Page %Lu is not here, we read the original one\n",
+                              (unsigned long long)mapcnt);
+                       return rawexpread(a, buf, rdlen);
+               }
+               len-=rdlen; a+=rdlen; buf+=rdlen;
+       }
+       return 0;
 }
 
-int rawexpwrite(fsoffset_t a, char *buf, int len)
+int rawexpwrite(off_t a, char *buf, int len)
 {
        maybeseek(export[a/hunksize], a%hunksize);
        return (write(export[a/hunksize], buf, len) != len);
 }
 
 
-int expwrite(fsoffset_t a, char *buf, int len)
-{  u32 mapcnt,mapl,maph ; int wrlen,rdlen ; 
-   fsoffset_t pagestart ; int offset ;
-
-  if (flags & F_COPYONWRITE) {
-    DEBUG3("Asked to write %d bytes at %lu.\n",len,(unsigned long)a) ;
-
-    mapl=a/DIFFPAGESIZE ; maph=(a+len-1)/DIFFPAGESIZE ;
-
-    for (mapcnt=mapl;mapcnt<=maph;mapcnt++) {
-      pagestart=mapcnt*DIFFPAGESIZE ;
-      offset=a-pagestart ;
-      wrlen=(len<DIFFPAGESIZE-offset) ? len : DIFFPAGESIZE-offset ;
-
-      if (difmap[mapcnt]!=(u32)(-1)) { /* the block is already there */
-       DEBUG3("Page %d is at %ld\n",mapcnt,(long)difmap[mapcnt]) ;
-       myseek(difffile,difmap[mapcnt]*DIFFPAGESIZE+offset) ;
-       if (write(difffile, buf, wrlen) != wrlen) return -1 ;
-      } else { /* the block is not there */
-       myseek(difffile,difffilelen*DIFFPAGESIZE) ;
-       difmap[mapcnt]=difffilelen++ ;
-       DEBUG3("Page %d is not here, we put it at %ld\n",
-             mapcnt,(long)difmap[mapcnt]) ;
-
-       rdlen=DIFFPAGESIZE ;
-       if (rdlen+pagestart%hunksize>hunksize) 
-         rdlen=hunksize-(pagestart%hunksize) ;
-       if (rawexpread(pagestart,pagebuf,rdlen)) return -1 ;
-       memcpy(pagebuf+offset,buf,wrlen) ;
-       if (write(difffile,pagebuf,DIFFPAGESIZE)!=DIFFPAGESIZE) return -1 ;
-      }                                                    
-      len-=wrlen ; a+=wrlen ; buf+=wrlen ;
-    }
-  } else return(rawexpwrite(a,buf,len)); 
-  return 0 ;
+int expwrite(off_t a, char *buf, int len)
+{
+       u32 mapcnt,mapl,maph ; int wrlen,rdlen ; 
+       off_t pagestart ; int offset ;
+
+       if (!(flags & F_COPYONWRITE))
+               return(rawexpwrite(a,buf,len)); 
+       DEBUG3("Asked to write %d bytes at %Lu.\n", len, (unsigned long long)a);
+
+       mapl=a/DIFFPAGESIZE ; maph=(a+len-1)/DIFFPAGESIZE ;
+
+       for (mapcnt=mapl;mapcnt<=maph;mapcnt++) {
+               pagestart=mapcnt*DIFFPAGESIZE ;
+               offset=a-pagestart ;
+               wrlen=(len<DIFFPAGESIZE-offset) ? len : DIFFPAGESIZE-offset ;
+
+               if (difmap[mapcnt]!=(u32)(-1)) { /* the block is already there */
+                       DEBUG3("Page %Lu is at %lu\n", (unsigned long long)mapcnt,
+                              (unsigned long)difmap[mapcnt]) ;
+                       myseek(difffile,difmap[mapcnt]*DIFFPAGESIZE+offset) ;
+                       if (write(difffile, buf, wrlen) != wrlen) return -1 ;
+               } else { /* the block is not there */
+                       myseek(difffile,difffilelen*DIFFPAGESIZE) ;
+                       difmap[mapcnt]=difffilelen++ ;
+                       DEBUG3("Page %Lu is not here, we put it at %lu\n",
+                              (unsigned long long)mapcnt,
+                              (unsigned long)difmap[mapcnt]);
+                       rdlen=DIFFPAGESIZE ;
+                       if (rdlen+pagestart%hunksize>hunksize) 
+                               rdlen=hunksize-(pagestart%hunksize) ;
+                       if (rawexpread(pagestart,pagebuf,rdlen)) return -1 ;
+                       memcpy(pagebuf+offset,buf,wrlen) ;
+                       if (write(difffile,pagebuf,DIFFPAGESIZE)!=DIFFPAGESIZE) return -1 ;
+               }                                                   
+               len-=wrlen ; a+=wrlen ; buf+=wrlen ;
+       }
+       return 0;
 }
 
 int mainloop(int net)
@@ -390,22 +400,16 @@ int mainloop(int net)
        struct nbd_reply reply;
        char zeros[300];
        int i = 0;
-       fsoffset_t size_host;
+       off_t size_host;
 
        memset(zeros, 0, 290);
        if (write(net, INIT_PASSWD, 8) < 0)
                err("Negotiation failed: %m");
-#ifndef        FS_32BIT
        cliserv_magic = htonll(cliserv_magic);
-#endif
        if (write(net, &cliserv_magic, sizeof(cliserv_magic)) < 0)
                err("Negotiation failed: %m");
        size_host = htonll(exportsize);
-#ifdef FS_32BIT
-        if (write(net, zeros, 4) < 0 || write(net, &size_host, 4) < 0)
-#else
        if (write(net, &size_host, 8) < 0)
-#endif
                err("Negotiation failed: %m");
        if (write(net, zeros, 128) < 0)
                err("Negotiation failed: %m");
@@ -443,11 +447,17 @@ int mainloop(int net)
                if (len > BUFSIZE)
                        err("Request too big!");
 #ifdef DODBG
-               printf("%s from %d (%d) len %d, ", (request.type ? "WRITE" : "READ"),
-                      (int) request.from, (int) request.from / 512, len);
+               printf("%s from %Lu (%Lu) len %d, ", request.type ? "WRITE" :
+                               "READ", (unsigned long long)request.from,
+                               (unsigned long long)request.from / 512, len);
 #endif
                memcpy(reply.handle, request.handle, sizeof(reply.handle));
-               if (((request.from + len) > exportsize) ||
+               if ((request.from + len) > (OFFT_MAX)) {
+                 DEBUG("[Number too large!]");
+                 ERROR;
+                 continue;
+               }
+               if ((((off_t)request.from + len) > exportsize) ||
                    ((flags & F_READONLY) && request.type)) {
                        DEBUG("[RANGE!]");
                        ERROR;
@@ -457,7 +467,7 @@ int mainloop(int net)
                        DEBUG("wr: net->buf, ");
                        readit(net, buf, len);
                        DEBUG("buf->exp, ");
-                       if (expwrite(request.from, buf, len)) {
+                       if ((autoreadonly == 1) || expwrite(request.from, buf, len)) {
                                DEBUG("Write failed: %m" );
                                ERROR;
                                continue;
@@ -501,37 +511,39 @@ void set_peername(int net,char *clientname)
        strncpy(clientname,peername,255) ;
 }
 
-fsoffset_t size_autodetect(int export)
+off_t size_autodetect(int export)
 {
-       fsoffset_t es;
+       off_t es;
+       u32 es32;
+       struct stat stat_buf;
+       int error;
+
        DEBUG("looking for export size with lseek SEEK_END\n");
-       if ((int)(es = lseek(export, 0, SEEK_END)) == -1 || es == 0) {
-               struct stat stat_buf;
-               int error;
-               DEBUG("looking for export size with fstat\n");
-               stat_buf.st_size = 0;
-               if ((error = fstat(export, &stat_buf)) == -1 || stat_buf.st_size == 0 ) {
-                       DEBUG("looking for export size with ioctl BLKGETSIZE\n");
+       es = lseek(export, (off_t)0, SEEK_END);
+       if (es > ((off_t)0))
+               return es;
+
+       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)
+               return (off_t)stat_buf.st_size;
+
 #ifdef BLKGETSIZE
-                       if(ioctl(export, BLKGETSIZE, &es) || es == 0) {
-#else
-                       if(1){
-#endif
-                               err("Could not find size of exported block device: %m");
-                       } else {
-                               es *= 512; /* assume blocksize 512 */
-                       }
-               } else {
-                       es = stat_buf.st_size;
-               }
+       DEBUG("looking for export size with ioctl BLKGETSIZE\n");
+       if (!ioctl(export, BLKGETSIZE, &es32) && es32) {
+               es = (off_t)es32 * (off_t)512;
+               return es;
        }
-       return es;
+#endif
+       err("Could not find size of exported block device: %m");
+       return (off_t)-1;
 }
 
 int main(int argc, char *argv[])
 {
        int net;
-       fsoffset_t i;
+       off_t i;
 
        if (sizeof( struct nbd_request )!=28) {
                fprintf(stderr,"Bad size of structure. Alignment problems?\n");
@@ -548,34 +560,31 @@ int main(int argc, char *argv[])
 
 void serveconnection(int net) 
 {   
-  u64 i ;
+  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)
-      err("Could not open exported file: %m");
+    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 == (u64)~0) {
-      exportsize = size_autodetect(export[0]);
+
+    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");
     }
-    if (exportsize > (~0UL >> 1))
-#ifdef HAVE_LLSEEK
-    if ((exportsize >> 10) > (~0UL >> 1))
-      msg3(LOG_INFO, "size of exported file/device is %luMB",
-              (unsigned long)(exportsize >> 20));
-    else
-      msg3(LOG_INFO, "size of exported file/device is %luKB",
-          (unsigned long)(exportsize >> 10));
-#else
-    err("Size of exported file is too big\n");
-#endif
     else
-      msg3(LOG_INFO, "size of exported file/device is %lu",
-                      (unsigned long)exportsize);
+       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,