Search code examples
ccygwinstdiogetc

Cygwin: missing stream data due to stdio putc + line buffering


The expected output from the following program is:

received REQUEST
 from client
received REPLY
 from server

this is seen on, for instance, GNU/Linux. However, on Cygwin the actual output is this:

received REQUEST
 from client
received 
 from server

An empty line (the "\n" string) is received from the stream on the client side instead of "REPLY\n".

The problem stems from the combination of putc and line buffering.

  • In the server function, if we replace my_puts with fprintf, the expected output is produced.
  • In server, if we take out the setvbuf call which switches the stream to line buffering, the expected output is also produced.
  • If we change my_puts so that it outputs each character using fprintf(f, "%c", ch) instead of putc(ch, f), the expected output is produced.
  • Replacing putc by fputc makes no difference.
  • In server, adding a file positioning operation, like fseek(cli_stream, 0, SEEK_CUR) between input and output as required by ISO C (thus just before the my_puts call) makes no difference. This is included in the code below.

Is this program doing something wrong to bring about this strange issue?

Code:

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <errno.h>
#include <unistd.h>
#include <signal.h>

int my_puts(FILE *f, const char *str)
{
   int ch;

   while ((ch = *str++))
     if (putc(ch, f) == EOF) {
       perror("putc");
       return EOF;
     }

   return 0;
}

int client(struct sockaddr_in *srv_addr)
{
  int srv_fd = socket(AF_INET, SOCK_STREAM, 0);
  FILE *srv_stream;
  char buf[256];

  if (srv_fd == -1) {
    perror("socket");
    return EXIT_FAILURE;
  }

  if (connect(srv_fd, (struct sockaddr *) srv_addr, sizeof *srv_addr) == -1) {
    perror("connect");
    close(srv_fd);
    return EXIT_FAILURE;
  }

  if ((srv_stream = fdopen(srv_fd, "r+")) == 0) {
    perror("fdopen");
    close(srv_fd);
    return EXIT_FAILURE;
  }

   setvbuf(srv_stream, (char *) NULL, _IOLBF, 0);

  my_puts(srv_stream, "REQUEST\n");
  fflush(srv_stream);
  shutdown(srv_fd, SHUT_WR);

  if (fgets(buf, sizeof buf, srv_stream) == 0) {
    printf("short fread");
    fclose(srv_stream);
    return EXIT_FAILURE;
  }

  printf("received %s from server\n", buf);
  return 0;
}

void server(int acc_fd)
{
  struct sockaddr_in claddr;
  socklen_t claddrlen = sizeof claddr;
  int cli_fd = accept(acc_fd, (struct sockaddr *) &claddr, &claddrlen);
  FILE *cli_stream = fdopen(cli_fd, "r+");;
  char buf[256];

  if (cli_fd == -1) {
    perror("accept");
    if (cli_stream)
      fclose(cli_stream);
    return;
  }

  if (cli_stream == 0) {
    perror("fdopen");
    close(cli_fd);
    return;
  }

  setvbuf(cli_stream, (char *) NULL, _IOLBF, 0);

  if (fgets(buf, sizeof buf, cli_stream) == 0) {
    printf("short fread");
    fclose(cli_stream);
    return;
  }

  printf("received %s from client\n", buf);

  fseek(cli_stream, 0, SEEK_CUR);

  my_puts(cli_stream, "REPLY\n");
  fflush(cli_stream);
  fclose(cli_stream);
}

int main(void)
{
  struct sockaddr_in saddr = { 0 };
  int acc_fd = socket(AF_INET, SOCK_STREAM, 0);
  pid_t child;

  saddr.sin_family = AF_INET;
  saddr.sin_port = htons(12345);
  saddr.sin_addr.s_addr = htons(INADDR_ANY);

  if (acc_fd == -1) {
    perror("socket");
    return EXIT_FAILURE;
  }

  if (bind(acc_fd, (struct sockaddr *) &saddr, sizeof saddr) == -1) {
    perror("bind");
    return EXIT_FAILURE;
  }

  if (listen(acc_fd, 1) == -1) {
    perror("listen");
    return EXIT_FAILURE;
  }

  child = fork();

  if (child == -1) {
    perror("fork");
    return EXIT_FAILURE;
  } else if (child == 0) {
    server(acc_fd);
    _exit(0);
  } else {
    int status;
    int result = client(&saddr);
    kill(child, SIGKILL);
    if (wait(&status) != child) {
      printf("wait problem");
      return EXIT_FAILURE;
    }
    return result;
  }

  return 0;
}

Solution

  • Cygwin git now has the fix, backported from OpenBSD.

    A smaller repro test case was posted to the mailing list.