/* File2net.c * Call me File2net. I establish a socket connection to a designated * Mark-5 machine and send a file or part of a file to it. First (!) * command net2disc or net2out in the Mark-5 machine; then File2net. * My command-line options: * -c Target computer's name, default localhost * -f File name, default save.data * -t Start byte, default 0 * -e End byte, default 0 = size of file * -p Protocol, tcp or udp, default tcp * -d Data port, default m5data * -s Socket sndbufs, bytes, default 0 = OS default * -w Working buffer size, default 131072 bytes * -m Msglev, default 1 * -h Help * Progress can be monitored on the Mark-5 machine. I end when the * prescribed transfer is done, or end me with ^C. Note that * m5data tcp or udp needs to be in /etc/services on this machine. * Copy from the Mark-5 machine if need be. This version of * File2net has been diddled so that it compiles with gcc or g++ * on Linux or with gcc or cc -Ae on HP-UX. This version now * supports file sizes larger than 2 Gbytes. * Revised: 2006 May 14, JAB */ #define _LARGEFILE_SOURCE /* Large File Support (LFS) */ #define _LARGEFILE64_SOURCE #include #include /* For strrchr() */ #include /* For atoi() */ #include /* For ftime() */ #include /* For open(), socket(), connect(), etc. */ #include /* For open(), fchmod(), etc. */ #include /* For fcntl(), fstat(), etc. */ #include /* For open() and fcntl() */ #include /* For socket(), connect(), etc. */ #include /* For socket() with PF_INET */ // #include /* For IP_MTU in getsockopt() */ #define IP_MTU 14 /* From */ #include /* For getservbyname() */ extern void herror(const char * s); /* Needed on HP-UX */ extern int h_errno; /* For herror() */ #define MIN(a,b) ((a) < (b) ? (a) : (b)) #define TRUE 1 #define FALSE 0 char * me; /* My name */ int msglev = 1; /* Debug level, default */ int main(int argc, char * argv[]) { /* File2net */ char * machine = "localhost"; /* Default target computer */ char * filename = "save.data"; /* Default input file */ char * dataport = "m5data"; /* Default port from /etc/services */ char * smod = "tcp"; /* Protocol, default */ unsigned int socbuf = 131072; /* Default size of working buffer */ unsigned int sndbuf, sndbufs = 0; /* Send-buffer size, use Linux default */ /* (Don't confuse those two) */ unsigned int udpbs; /* UDP buffer size */ long long startbyte = 0; long long endbyte = 0; long long ix; int port, sock, i, k, len, type, tof; unsigned int kk; FILE * file; struct servent * set; /* From getservbyname() */ struct sockaddr_in socaddin; /* For connect() socket info */ struct hostent * hostinfo; /* From gethostbyname() */ struct stat64 stats; /* For fstat64() */ unsigned char * uc; /* To debug print IP address */ unsigned long rbuff[1000000]; /* For data */ unsigned int mtu, mss, lmss; /* MTU values */ socklen_t optlen; /* For getsockopt() */ struct timeb time1, time2; /* From ftime() */ double dt; /* Duration of transfer, seconds */ /* *** Initialize *** */ me = (me = strrchr(argv[0], '/')) == NULL ? argv[0] : me+1; /* My name */ /* * Parse command-line options, if any * */ k = 0; while (k >= 0) { switch (k = getopt(argc, argv, "hc:f:t:e:p:d:s:w:m:")) { /* That's help, computer, filename, startbyte, endbyte, protocol, * sndbufs, work-buffer size, and msglev */ case 'h' : /* Help */ (void) fprintf(stderr, "%s USAGE: \n %s -c -f -t -e -p

" "-d -s -w -m -h \n" " where is target computer's name, default localhost, \n" " is file name, default save.data, \n" " is start byte, default 0, \n" " is end byte, default 0 = size of file, \n" "

is protocol, tcp or udp, default tcp, \n" " is data port, default m5data, \n" " is socket sndbufs, bytes, OS default, \n" " is working-buffer size, default %u bytes, \n" " is message level, default 1, \n" " and -h shows this help message. \n", me, me, socbuf); return(0); /* End here */ break; /* (Not used) */ case 'm' : /* msglev */ /* msglev = -1 Vast quantities of debuggery prints * msglev = 0 Some debuggery prints * msglev = 1 Default and normal operation; warnings and errors print * msglev = 2 Only errors and operational messages print * msglev = 3 Nothing prints except fatal errors when program dies * (default 1 above) */ msglev = atoi(optarg); /* (No check) */ if (msglev < 1) /* Debuggery? */ (void) fprintf(stderr, /* Yes */ "%s DEBUG: msglev set to %d \n", me, msglev); break; case 'c' : /* Computer name */ machine = optarg; break; case 'f' : /* File name */ filename = optarg; break; case 't' : /* Start byte */ (void) sscanf(optarg, "%lld", &startbyte); /* startbyte = atoll(optarg); */ /* (Because HP-UX has no atoll()) */ break; case 'e' : /* End byte */ (void) sscanf(optarg, "%lld", &endbyte); /* endbyte = atoll(optarg); */ break; case 'p' : /* Protocol */ smod = optarg; break; case 'd' : /* Data port */ dataport = optarg; break; case 's' : /* Send-buffer size */ sndbufs = atoi(optarg); break; case 'w' : /* Working-buffer size */ if ((kk = atoi(optarg)) > 0 && kk <= sizeof(rbuff)) /* OK? */ socbuf = kk; /* Yes, bytes */ else { /* Not OK, keep previous value */ if (msglev < 2) /* Debuggery? */ (void) fprintf(stderr, "%s ERROR: socbuf %d > %d \n", me, kk, sizeof(rbuff)); /* Yes */ } break; case '?' : /* Error: character not in list */ if (msglev < 2) /* Debuggery? */ (void) fprintf(stderr, /* Yes */ "%s WARNING: Unknown option on command line \n", me); break; case ':' : /* Error: missing numerical parameter */ if (msglev < 2) /* Debuggery? */ (void) fprintf(stderr, /* Yes */ "%s WARNING: Missing number on command line \n", me); break; default : /* Probably -1, end of parameters */ break; } /* End of switch on k */ } /* End of while k */ /* ** Open input file ** */ if ((port = open64(filename, O_RDONLY)) < 0) { /* OK? */ (void) fprintf(stderr, "%s ERROR: \007 Can't open64() file ", me); /* No */ perror(filename); return(-2); /* Error */ } /* * Position file to start byte * */ if (lseek64(port, startbyte, SEEK_SET) < 0) { /* To startbyte, OK? */ (void) fprintf(stderr, "%s ERROR: \007 Can't lseek64() file ", me); /* No */ perror(filename); return(-3); /* Error */ } /* * Open this input file also as a stream * */ if ((file = fdopen(port, "r")) == NULL) { /* OK? */ (void) fprintf(stderr, "%s ERROR: \007 Can't fdopen() file ", me); /* No */ perror(filename); return(-4); /* Error */ } /* ** Create and configure a socket to send data through ** */ if (strcmp(smod, "tcp") == 0) /* tcp? */ type = SOCK_STREAM; /* Yes */ else if (strcmp(smod, "udp") == 0) /* udp? */ type = SOCK_DGRAM; /* Yes */ else { /* Neither tcp nor udp is an error */ (void) fprintf(stderr, "%s ERROR: Can't do type %s \n", me, smod); return(-5); /* Error */ } if ((sock = socket(PF_INET, type, 0)) < 0) { /* OK? */ (void) fprintf(stderr, /* Nope */ "%s ERROR: \007 socket() returned %d ", me, sock); perror("error"); return(-6); /* Error */ } /* If tcp, then we set keep-alive in case a firewall needs it */ if (type == SOCK_STREAM) { /* tcp? */ tof = TRUE; /* Yes */ if(setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &tof, sizeof(int)) < 0) { /* Set keep-alive, OK? */ (void) fprintf(stderr, /* Nope */ "%s ERROR: \007 setsockopt() SO_KEEPALIVE returned ", me); perror("error"); return(-7); /* Error */ } } /* SO_SNDBUF sets or gets the maximum socket send buffer in bytes. * The default value is set by the wmem_default sysctl, and the * maximum allowed value is set by the wmem_max sysctl. */ if (sndbufs > 0) { /* Change SO_SNDBUF? */ if(setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (void *) &sndbufs, sizeof(sndbufs)) < 0) { /* Yes, socket send-buffer size, OK? */ if (msglev < 2) { /* Nope. Debuggery? */ (void) fprintf(stderr, /* Yes */ "%s WARNING: setsockopt() SO_SNDBUF %d returned ", me, sndbufs); perror("error"); } /* Error, but we try to go on */ } } /* End of if change SO_SNDBUF */ if (msglev < 1) { /* Debuggery? */ optlen = sizeof(unsigned int); /* Yes */ (void) getsockopt(sock, SOL_SOCKET, SO_SNDBUF, (void *) &sndbuf, &optlen); /* Socket send-buffer size */ (void) fprintf(stderr, "%s DEBUG: sndbufs is %u bytes \n", me, sndbuf); } /* * Get service numbers for socket dataport * */ if ((set = getservbyname(dataport, smod)) == NULL) { /* OK? */ (void) fprintf(stderr, /* Nope */ "%s ERROR: \007 %s %s not found in /etc/services \n", me, dataport, smod); return(-8); /* Error */ } /* Else OK so far */ socaddin.sin_port = set->s_port; /* Port service's number */ if (msglev < 1) /* Debuggery? */ /* Yes. For Intelendian, we need to swap bytes in set->s_port */ (void) fprintf(stderr, "%s DEBUG: %s %s port is %d \n", me, dataport, smod, ntohs(socaddin.sin_port)); socaddin.sin_family = PF_INET; /* To agree with socket() above */ /* * Find IP address of machine to connect to * */ if ((hostinfo = gethostbyname(machine)) == NULL) { /* Get IP, OK? */ (void) fprintf(stderr, /* Nope */ "%s ERROR: \007 gethostbyname() on %s returned NULL ", me, machine); herror("error"); /* Error */ switch (h_errno) { /* Which error? */ case HOST_NOT_FOUND : (void) fprintf(stderr, "%s ERROR: host %s not found \n", me, machine); break; case TRY_AGAIN : (void) fprintf(stderr, "%s ERROR: no response, try again later \n", me); break; case NO_RECOVERY : (void) fprintf(stderr, "%s ERROR: unknown error, not recoverable \n", me); break; case NO_ADDRESS : /* = NO_DATA */ (void) fprintf(stderr, "%s ERROR: No Internet address available \n", me); } /* End of switch */ return(-9); /* Error */ } /* End of if hostinfo NULL */ if (hostinfo->h_addr_list[0] == NULL) { /* First IP address OK? */ (void) fprintf(stderr, /* Nope */ "%s ERROR: \007 gethostbyname() on %s " "returned NULL IP address \n", me, machine); return(-10); /* Error */ } if (msglev < 1) { /* Debuggery? */ uc = (unsigned char *) hostinfo->h_addr_list[0]; /* Yes */ (void) fprintf(stderr, "%s DEBUG: IP address of %s is [", me, machine); for (i = 0; i < hostinfo->h_length; i++) { if (i > 0) (void) printf("."); (void) printf("%u", uc[i]); } (void) printf("] \n"); } socaddin.sin_addr.s_addr = *((unsigned long *) hostinfo->h_addr_list[0]); /* Use first address */ /* * Connect this socket to service on machine * */ if (msglev < 1) /* Debuggery? */ (void) fprintf(stderr, /* Yes */ "%s DEBUG: Trying to connect() \n", me); if (connect(sock, (const struct sockaddr *) &socaddin, sizeof(struct sockaddr_in)) < 0) { /* Connect, errors? */ (void) fprintf(stderr, /* Yes */ "%s ERROR: \007 connect() returned ", me); perror("error"); return(-11); /* Error */ } if (msglev < 1) /* Debuggery? */ (void) fprintf(stderr, /* Yes */ "%s DEBUG: Got a connect() on sock %d \n", me, sock); if(type == SOCK_DGRAM) { /* udp? */ /* Yes, so get MTU (see man ip) */ optlen = sizeof(unsigned int); if (getsockopt(sock, SOL_IP, IP_MTU, (void *) &mtu, &optlen) < 0) { /* Get local MTU OK? */ (void) fprintf(stderr, /* No */ "%s ERROR: \007 getsockopt() returned ", me); perror("error"); (void) close(sock); return(-12); /* Error */ } /* Else OK udp * Assume ip header is 20 bytes and upd header is 8 bytes. * If fragmentation, then the first fragment has MTU-28 data bytes, * and all subsequent fragments each contain MTU-20 data bytes. */ mss = mtu - 28; /* MSS */ lmss = mtu - 20; /* Local MSS for fragments */ /* mtu is typically 1500, mss 1472, and lmss 1480 bytes. * We assume mss and lmss are each an integer times 8 bytes. * The following is empirical, but see "big datagram size" in man ip. * We adjust udpbs so that each fragment except the last is full. */ if (socbuf > 65528) /* Use socbuf? */ udpbs = 55000 / lmss * lmss - 8; /* No, presumably too big */ /* (Here typically udpbs = 54752 bytes in 37 fragments) */ else /* Use socbuf */ udpbs = (socbuf + 8) / lmss * lmss - 8; /* For full fragments */ /* We cannot assume mss and lmss are each an integer times 8 bytes, so: */ udpbs -= udpbs % 8; if (msglev < 1) /* Debuggery? */ (void) fprintf(stderr, /* Yes */ "%s DEBUG: MTU %d, mss %d, lmss %d, udpbs %d \n", me, mtu, mss, lmss, udpbs); } /* End of if udp */ /* *** Read data from the file and write to the socket (net) *** */ ix = startbyte; /* Initialize */ if (endbyte <= startbyte) { /* To end of file? */ if ((k = fstat64(port, &stats)) != 0) { /* Yes, get stats, OK? */ (void) fprintf(stderr, /* Nope */ "%s ERROR: \007 fstat() returned %d on ", me, k); perror(filename); return(-13); /* Error */ } endbyte = stats.st_size; /* Revised to end of file */ } (void) ftime(&time1); /* Time at start of transfer */ while (1) { /* Loop until break */ /* * Read data from file * */ if(type == SOCK_DGRAM) /* udp? */ len = MIN(udpbs, endbyte - ix); /* Yes. Size of this chunk */ else /* tcp */ len = MIN(socbuf, endbyte - ix); /* Size of this chunk */ k = fread((void *) rbuff, len, 1, file); /* Read a chunk */ /* Check for errors */ if (k != 1) { /* OK? */ (void) fprintf(stderr, /* Nope */ "%s ERROR: \007 fread() returned %d on file ", me, k); perror(filename); return(-14); /* Error */ } /* * OK, send data to the socket * */ k = send(sock, (void *) rbuff, len, 0); if (k < 0) { /* Errors? */ (void) fprintf(stderr, /* Yes */ "%s ERROR: \007 Send() on socket %d returned %d ", me, sock, k); perror("error"); return(-15); /* Error */ } /* Else OK */ ix += len; /* Bump counter */ if (ix + 8 > endbyte) { /* Done? */ if (msglev < 0) /* Yes. Debuggery? */ (void) fprintf(stderr, " \n"); /* Yes */ if (msglev < 1) /* More debuggery? */ (void) fprintf(stderr, /* Yes */ "%s DEBUG: ix %Ld, endbyte %Ld; done \n", me, ix, endbyte); break; /* Out of while loop until break */ } if (msglev < 0) /* Debuggery? */ (void) fprintf(stderr, "#"); /* Yes */ } /* End of while loop until break */ (void) ftime(&time2); /* Time at end of transfer */ dt = time2.time + time2.millitm/1000.0 - time1.time - time1.millitm/1000.0; if (msglev < 1) /* Debuggery? */ (void) fprintf(stderr, /* Yes */ "%s DEBUG: Sent %Ld bytes in %.1f seconds at %.3e baud \n", me, endbyte - startbyte, dt, 8.0*(endbyte - startbyte)/dt); (void) fprintf(stderr, "%s: The End \n", me); return(0); /* Normal end */ } /* End of main = File2net */