[436] in linux-net channel archive

home help back first fref pref prev next nref lref last post

Networking(?) problems with CPIO/RMT

daemon@ATHENA.MIT.EDU (Andreas Haumer)
Sat Jun 10 19:34:26 1995

Date: Sun, 11 Jun 95 00:37 MET DST
From: andreas@vlsivie.tuwien.ac.at (Andreas Haumer)
To: linux-net@vger.rutgers.edu
Cc: andreas@vlsivie.tuwien.ac.at


Hi!

Last weekend I reported a problem to comp.os.linux.networking, regarding
a hangup problem with "cpio" and "rmt".
Thomas Koenig then made a follow-up to this list, and there was also a
short answer from Mark Eichin regarding his solution to the "rdist" problem.

But alas, I still have no solution to the "cpio" hangup. In the past week, 
I wrote a small test-program and did some tests (and transferred several 
giga-bytes through my LAN!) but I was NOT able to reproduce the problem with 
my test-program. Instead, I still get hangups with cpio/rmt! (Have you ever 
encountered the situation where you want to, but can't get your program to 
fail?  ;-)
I also NEVER had the situation where a write(2) to a pipe or socket returned
an indication that only _some_ of the requested data got written (as Mark 
reported it was the reason for the "rdist" problem). I got this situation 
with read(2), though!

Here is what I did:
First I tried to find out, what "cpio" does when it has to read from a
remote tape.
I learned, that "cpio" executes "rsh" in order to start the "rmt" process
on the remote system and does the client/server communication through
a pair of pipes between itself and the "rsh" process.
The "rsh" process uses standard socket I/O (non-blocking!) to communicate 
with the "rmt" process on the remote system (where "rmt" doesn't know 
anything about this, because it just uses stdin/stdout and gets its 
filedescriptors already connected to the corresponding sockets by "inetd").

I then wrote a simple client/server application to check what's going on.
I tried to write it as close as possible to the cpio/rsh/rmt functions in
question (I have included the sources at the end of this mail, in case 
someone wants to try it).
The client just sends a request to the server, indicating it wants to
read some random data, and the server then sends the number of bytes
requested.
Using command-line options, it can be set into several different operation 
modes, see the sources for a short description.
It supports both the "pipe-through-rsh" and the "direct socket" I/O methods.

But as I said, I could not bring the test application to hang, even when
transferring more than 100MB in 5k blocks! On the other hand, "cpio" hangs 
after a few MB!

Conclusion: none, so far! I'm just running out of ideas.
I'm not even sure anymore if it's a network-related problem! But on the other
hand, where do these 6 bytes hanging around in the send-queue come from? And
why does the corresponding "shell" socket on the remote system then just 
remain in state "FIN_WAIT2"?

Next thing I want to check is if it could be some kind of timing problem in
the SCSI tape and/or network software because my test program doesn't address 
this yet.

Any ideas and hints are welcome!

- andreas

Source code of test program following:

---------8<------------8<------------8<----------8<-----------8<----------
/*
 * client.c - Linux network communication testprogram - client
 *
 * written by Andreas Haumer (andreas@vlsivie.tuwien.ac.at), June 1995
 *
 *
 * This program communicates with a server program on a remote host. Its only
 * purpose is to continuously transmit send-requests to the server and then to
 * receive the requested data.
 *
 * compile-command: cc -O2 -m486 -Wall -o client client.c
 *
 * There are several command-line options:
 *      -h host ... name of the server-host
 *      -n num .... Number of bytes the server has to transmit on each request.
 *      -m max .... max. number of requests the client issues.
 *      -B ........ do blocking I/O (wait for each read/write system-call to
 *                  finish).
 *      -S ........ call select(2) before each read/write system-call to
 *                  check if the I/O operation was allowed.
 *      -P ........ do pipe I/O via rsh(1), otherwise do direct client/server
 *                  socket I/O.
 *                  You have to start the server-process on the remote host
 *                  with "server" first when using direct socket I/O!
 *      -v ........ tell number of bytes received.
 */

#include <stdio.h>
#include <stdlib.h>		/* malloc, atoi */
#include <unistd.h>		/* sysconf */
#include <string.h>		/* memset */
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>		/* socket stuff */
#include <sys/ioctl.h>
#include <netinet/in.h>		/* socket stuff */
#include <netdb.h>		/* gethostbyname */

#define PORT 0x1234		/* unprivileged port number */
#define SERVERPROG "/usr/src/nettest/server"
#define RSHPROG "/usr/bin/rsh"

/* program-options */
int verbose;
int do_select;
int pipeio;
int blocking;
int maxreq;

void usage (char *programname)
{
  printf ("usage: %s [-h host] [-n num] [-m max] [-B] [-S] [-P] [-v]\n",
	  programname);
  printf ("\t-h host ... name of server host (default: localhost)\n");
  printf ("\t-n num .... bytes to transmit on each request (default: 5120)\n");
  printf ("\t-m max .... max. number of requests (default: 32768)\n");
  printf ("\t-B ........ do blocking I/O (default: non-blocking I/O)\n");
  printf ("\t-S ........ call select before I/O ops (default: don't)\n");
  printf ("\t-P ........ do pipe I/O (default: do direct socket I/O)\n");
  printf ("\t-v ........ be verbose (default: be quiet)\n");
}

int doit (int in_fd, int out_fd, int n)
{
  char wbuf[64], *rbuf;
  int req, i, wc, cc;
  unsigned long rcnt = 0L;
  int tsize = (int)sysconf (_SC_OPEN_MAX);
  fd_set r_set, w_set;
  
  sprintf (wbuf, "S%d\n", n);
  wc = strlen (wbuf);
  
  if ((rbuf = (char *)malloc (n)) == NULL) {
    perror ("malloc");
    return (-1);
  }
  
  for (req=0; req < maxreq; req++) {
    if (do_select) {
/*
 * Check and wait until process is able to write to file <fd>
 */
      FD_ZERO (&w_set);
      FD_SET (out_fd, &w_set);
      if (select (tsize, NULL, &w_set, NULL, NULL) == -1) {
	perror ("select");
	return (-1);
      }
    
      if (!FD_ISSET (out_fd, &w_set))
	continue;		/* not the file we want to read to */
    }
    
/*
 * OK, we're allowed to write our command, let's do it.
 * Report an error if we can't write all data we want to.
 */
    if (write (out_fd, wbuf, wc) != wc) {
      perror ("write");		/* write problem? */
      return (-1);
    }

/*	
 * We exactly expect <n> bytes, but data could be splitted into several
 * blocks, so read until we got all <n> bytes.
 */
    for (cc=i=0; i<n; i += cc) {
      if (do_select) {
/*
 * Now check and wait until there is something to read on <fd>
 * (the answer from our server process!)
 */
	FD_ZERO (&r_set);
	FD_SET (in_fd, &r_set);
	if (select (tsize, &r_set, NULL, NULL, NULL) == -1) {
	  perror ("select");
	  return (-1);
	}
	
	if (!FD_ISSET (in_fd, &r_set))
	  continue;		/* not the file we want to read from */
      }

/*
 * OK, there is some data to read from <fd>. Get it!
 * In case of non-blocking I/O, read(2) can return on a "there is no
 * data available yet" condition (<errno> is set to EAGAIN). In that case
 * we just repeat the operation.
 */
      if ((cc = read (in_fd, &rbuf[i], n-i)) < 0) {
	if (errno == EAGAIN)
	  cc = 0;		/* no error, just try it again */
	else {
	  perror ("read");
	  return (-1);
	}
      }
    }	/* for (i) */

    rcnt += i;			/* count number of bytes read */
    
    if (verbose) {
      printf ("got %ld chars\r", rcnt);
      fflush (stdout);
    }
  } /* for (req) */

  if (verbose)
    printf ("\nok.\n");
  
  return (0);
} /* doit */

     
int main (int argc, char *argv[])
{
  extern int getopt();
  extern char *optarg;
  char c, *hostname;
  int in_fd, out_fd, n, sd, one = 1;
  struct sockaddr_in p_in;
  struct hostent *hp;
  
  hostname  = "localhost";
  n         = 5120;
  blocking  = 0;
  verbose   = 0;
  pipeio    = 0;
  do_select = 0;
  maxreq    = 32768;
  
  while ((c = getopt (argc, argv, "h:m:n:vBSP")) != EOF)
    switch (c) {
    case 'h':
      hostname = optarg;	/* name of server host */
      break;
    case 'm':
      maxreq = atoi (optarg);	/* max. number of requests */
      break;
    case 'n':
      n = atoi (optarg);	/* number of bytes to transmit */
      break;
    case 'v':
      verbose = 1;		/* be verbose */
      break;
    case 'B':
      blocking = 1;		/* do blocking I/O */
      break;
    case 'P':
      pipeio = 1;		/* do pipe I/O (use rsh(1)) */
      break;
    case 'S':
      do_select = 1;		/* call select(2) before read/write */
      break;
    default:
      usage (argv[0]);
      exit (-1);
    } /* switch */

  if (!pipeio) {		/* use direct socket communication */
    if ((hp = gethostbyname (hostname)) == NULL) {
      perror ("gethostbyname");
      exit (-1);
    }

    memset (&p_in, 0, sizeof (p_in));
    p_in.sin_family = AF_INET;
    p_in.sin_addr.s_addr = ((struct in_addr *)(hp->h_addr))->s_addr;
    p_in.sin_port = htons (PORT);

    if ((sd = socket (AF_INET, SOCK_STREAM, 0)) == -1) {
      perror ("socket");
      exit (-1);
    }

    if (connect (sd, (struct sockaddr *)&p_in, sizeof (p_in)) == -1) {
      perror ("connect");
      exit (-1);
    }

    in_fd = sd;
    out_fd = sd;
    
  }
  else {			/* do it the cpio(1) way */
    int in_pfd[2], out_pfd[2];

/*
 * create pipes to child process ("rsh")
 */
    if ((pipe (in_pfd) == -1) || (pipe (out_pfd) == -1)) {
      perror ("pipe");
      exit (-1);
    }

    switch (fork ()) {
    case -1:
      perror ("fork");
      exit (-1);
    case 0:			/* child */
      close (0);
      dup (in_pfd[0]);		/* stdin */
      close (in_pfd[0]);
      close (in_pfd[1]);
      close (1);
      dup (out_pfd[1]);		/* stdout */
      close (out_pfd[0]);
      close (out_pfd[1]);

      execl (RSHPROG, "rsh", hostname,
	     SERVERPROG, "-P", (char *)0);
      perror ("execl");
      exit (-1);

    default:			/* parent */
      close (in_pfd[0]);
      close (out_pfd[1]);

      in_fd = out_pfd[0];
      out_fd = in_pfd[1];
      
    } /* switch */
  } /* else */

  if (!blocking) {
    if (ioctl (in_fd, FIONBIO, &one) == -1) {
      perror ("ioctl");
      exit (-1);
    }
    if (ioctl (out_fd, FIONBIO, &one) == -1) {
      perror ("ioctl");
      exit (-1);
    }
  }
  
  exit (doit (in_fd, out_fd, n));
  
} /* main */

---------8<------------8<------------8<----------8<-----------8<----------
/*
 * server.c - Linux network communication testprogram - server
 *
 * written by Andreas Haumer (andreas@vlsivie.tuwien.ac.at), June 1995
 *
 * compile-command: cc -O2 -m486 -Wall -o server server.c
 *
 * There are several command-line options:
 *      -P ........ do pipe I/O via rsh(1), otherwise do direct client/server
 *                  socket I/O.
 *      -v ........ tell number of bytes sent.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>

#define PORT 0x1234
#define	SSIZE	64

int verbose;
int pipeio;

void getstring (int fd, char *bp)
{
  int i;
  char *cp = bp;

  for (i = 0; i < SSIZE; i++) {
    if (read (fd, cp + i, 1) != 1)
      exit (0);
    if (cp[i] == '\n')
      break;
  }
  
  cp[i] = '\0';
}

int doit (int in_fd, int out_fd)
{
  char count[SSIZE];
  char cmd;
  char *buf;
  int i, n, m;
  unsigned long wcnt = 0L;
  
  while (read (in_fd, &cmd, 1) == 1) {
    switch (cmd) {
    case 'X':
      return (0);
    case 'S':			/* send */
      getstring (in_fd, count);
      n = atoi (count);
      if ((buf = (char *)calloc (n, sizeof (char))) == NULL) {
	perror ("calloc");
	return (-1);
      }

/*
 * create buffer of random data
 */
      for (i=0; i<n; i++)
	buf[i] = (char)(rand() % 256);

      if ((m = write (out_fd, buf, n)) != n) {
	perror ("write");
	return (-1);
      }
      
      wcnt += m;		/* count number of bytes written */
      
      if (verbose) {
	printf ("wrote %ld chars\r", wcnt);
	fflush (stdout);
      }
      
      free (buf);
      break;
    default:
      fprintf (stderr, "Unknown command %c\n", cmd);
      return (-1);
    }
  } /* while */

  return (0);
}

int main (int argc, char *argv[])
{
  extern int getopt();
  extern char *optarg;
  char c;
  int sd, act_sd, in_fd, out_fd;
  int addrlen;
  struct sockaddr_in s_in, p_in;

  srand (time (NULL));

  pipeio   = 0;
  verbose  = 0;
  
  while ((c = getopt (argc, argv, "Pv")) != EOF)
    switch (c) {
    case 'P':
      pipeio = 1;
      break;
    case 'v':
      verbose = 1;
      break;
    }

  if (pipeio) {
    in_fd  = 0;
    out_fd = 1;
  }
  else {
    if ((sd = socket (AF_INET, SOCK_STREAM, 0)) == -1) {
      perror ("socket");
      exit (-1);
    }

    memset (&s_in, 0, sizeof (s_in));
    s_in.sin_family = AF_INET;
    s_in.sin_addr.s_addr = INADDR_ANY;
    s_in.sin_port = htons (PORT);

    if (bind (sd, (struct sockaddr *)&s_in, sizeof (s_in)) == -1) {
      perror ("bind");
      exit (-1);
    }

    if (listen (sd, 5) == -1) {
      perror ("listen");
      exit (-1);
    }

    if ((act_sd = accept (sd, (struct sockaddr *)&p_in, &addrlen)) == -1) {
      perror ("accept");
      exit (-1);
    }

    in_fd  = act_sd;
    out_fd = act_sd;
  }

  exit (doit (in_fd, out_fd));
}
---------8<------------8<------------8<----------8<-----------8<----------

--
In your head, in your head
Zombie
What's in your head, in your head?
Zombie!
         "Zombie", The Cranberries

Gewidmet allen Faschisten und Bombenlegern - aus gegebenen Anlass.

----------------------+------------------------------+-------------------------
 andreas haumer       | andreas@vlsivie.tuwien.ac.at |
 buchengasse 67/8     | tel:  +43.1.6001508  (ISDN)  |
 a-1100 vienna        |       +43.664.3004449 (GSM)  | god is real - 
 austria              | fax:  +43.1.6001084          | unless declared integer


home help back first fref pref prev next nref lref last post