SORRROW
SORRROW

Reputation: 55

Reading from stdin in a thread to write in a file in c

I am creating a FTP server using TCP in c. I am having a problem with my files transfer, I want to be able to "put" or "get" a file either to/from the server from the client. I am handling the multi-connections with select() and using threads to handle the file transfer. I have a created a simple .c example that sums up my problem:

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

void    *read_stdin(void * null)
{
  int   fd;
  int   len;
  char  ret;
  char  buff[1024];

  fd = open("dest", O_RDWR | O_TRUNC | O_CREAT, 0600);
  len = 1;
  while (len)
   {
     ret = read(0, &len, 1);
     len = atoi(ret);
     if (len)
      {
       read(0, buff, len);
       write(fd, buff, len);
      }
   }
 return (null);
}

int             main()
{
 pthread_t     t;
 int           fd;
 char           len;
 char          buff[1024];

  pthread_create(&t, NULL, &read_stdin, NULL);
  fd = open("source", O_RDONLY);
  while ((len = read(fd, buff, 1024)))
    {
         write(0, &len, 1);
         write(0, buff, len);
    }
   write(0, "0", 1);
  pthread_join(t, NULL);
  return (0);
}

Now my problem is that the read() in the thread is blocking, it is not reading what I'm writing on STDIN from the main process and I don't understand why, is it doing the same thing in my program but instead of reading from the standard input I am reading from a socket but to keep this simple I used the stdin in this example.

Thanks!

EDIT: Changed my len in the main to char from int and reading in my thread_func with a char and then using atoi() to convert.

Upvotes: 2

Views: 3954

Answers (3)

SORRROW
SORRROW

Reputation: 55

EDIT:

Thanks for the answers. I solved my problem: My file transfer was working by blocks, I was first sending the size to read to the side that was going to receive the file and right after the block itself. But the problem was that on the side that was going to receive the data, both writes were going too fast on one end and could not be read on the other end. So I had to sync the serv with the client with a read() between both writes, and a write between both read to sync both of them. Maybe this will help someone.

The code :

The client side when it wants to get a file :

  #include <sys/types.h>
  #include <sys/socket.h>
  #include <inttypes.h>
  #include <fcntl.h>
  #include <sys/stat.h>
  #include <unistd.h>
  #include <stdio.h>
  #include "client.h"

 static int      write_to_file(int fd, int cs)
{
   int           len;
   char          buff[2048];
   char          size[24];

   memset(size, 0, 24);
   read(cs, size, 24);
   len = atoi(size);
   memset(buff, 0, 2048);
   if (len)
    {
      write(cs, "\n", 1);
      len = read(cs, buff, len);
      write(fd, buff, len);
    }
  return (len);
}

int     get_file_content(t_client *c, char *filename)
{
  int   len;
  int   fd;

  if ((fd = open(filename, O_RDWR | O_TRUNC | O_CREAT, 0600)) == -1)
    return (my_perror(strerror(errno)));
  write(c->cs, c->cmd, strlen(c->cmd));
  len = 1;
  while (len)
    len = write_to_file(fd, c->cs);
  close(fd);
  printf("ok\n");
  return (0);
}

The server side on the "get" that is sending the file:

#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <inttypes.h>
#include <stdio.h>
#include <string.h>
#include "server.h"

static void     write_to_sck(int len, char *buff, t_fd *now)
 {
  char          size[24];
  char          tmp[3];

  memset(size, 0, 24);
  memset(tmp, 0, 2);
  sprintf(size, "%d\n", len);
  write(now->sck, size, strlen(size));
  read(now->sck, tmp, 1);
  write(now->sck, buff, len);
}

 void            *write_file_content(void *ptr)
 {
  t_fd          *now;
  t_files       *file;
  int           len;
  char          buff[2048];

  now = (t_fd *)ptr;
  find_file(now->head, &file, now->curr_file);
  pthread_mutex_lock(&file->m);
  if ((file->fd = open(file->name, O_RDONLY)) == -1)
   {
     write(now->sck, strerror(errno), strlen(strerror(errno)));
      return (ptr);
   }
   memset(buff, 0, 2048);
   while ((len = read(file->fd, buff, 2048)))
   {
     write_to_sck(len, buff, now);
      memset(buff, 0, 2048);
    }
  write(now->sck, "0\n", 1);
  close(file->fd);
  pthread_mutex_unlock(&file->m);
  now->threaded = 0;
  return (0);
 }
int     get_func(t_fd *now)
 {
 char  **tab;

 if (!(tab = str_to_wordtab(now->cmd)))
  {
    write(now->sck, "ko\n", 3);
    return (my_perror("error: str_to_wordtab() in get_func()"));
  }
 if (access(tab[1], R_OK) == -1)
  {
    free_tab(tab);
     return (write(now->sck, strerror(errno), strlen(strerror(errno))));
  }
 if (add_new_file(now, tab[1]))
  {
    free_tab(tab);
    return (write(now->sck, "ko\n", 3));
  }
 now->threaded = 1;
 pthread_create(&now->t, NULL, write_file_content, now);
 free_tab(tab);
 return (0);
}

My server has a linked list of sockets including his and his clients in t_fd * and the client has a structure in t_client containing his fd.

Upvotes: 0

foobar
foobar

Reputation: 606

write(0, &len, 1); writes one byte of len to stdin, but len has actually sizeof(int) bytes. Also when you read that value back with read(0, &len, 1); you only read one byte instead of sizeof(int).

Additionally you read into len for every execution of the loop and receive a random value in the stream. Unless you accidentally read a null byte into len, the condition will always be true and you end up in an endless loop. Once you've read the whole stream, the next call to read will block.

So you should properly write the length of the message to the socket and read it back properly. Also you have to check if you've already read all bytes to prevent the call to read from blocking.

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

void* read_stdin(void * null) {
        int fd;
        char len;
        char ret;
        char buff[255];

        fd = open("dest", O_RDWR | O_TRUNC | O_CREAT, 0600);
        len = 1;
        while (len) {
                len = read(0, &len, sizeof(len));
                if (len) {
                        read(0, buff, len);
                        write(fd, buff, len);
                }
        }
        close(fd);
        return NULL;
}

int main() {
        pthread_t t;
        int fd;
        char len;
        char buff[128];

        pthread_create(&t, NULL, &read_stdin, NULL);
        fd = open("source", O_RDONLY);
        while (len = read(fd, buff, 128)) { // you can only read 128 characters as len is a signed char (assuming an eight bit byte)
                write(0, &len, sizeof(len));
                write(0, buff, len);
        }
        //write(0, "0", 1);
        close(0);
        close(fd);
        pthread_join(t, NULL);

        return 0;
}

Note that this code only works if stdin isn't bound to a tty. If it is, it will be read by the terminal before you can read it yourself. In case that stdin is bound to a tty, you'll need a pipe or socket pair as loreb mentions in his answer.

I tried to adjust your code as little as possible, just to make it work. It's still not nice and it's error prone. Unfortunately I have no reference to anything that shows how to do it right. But maybe someone else here can provide a reference.

Upvotes: 1

loreb
loreb

Reputation: 1357

You cannot write to fd 0 and read back the same data with a read! You'll need some special kind of fd (pipe, socketpair) to write on one end and read from the other. Notice that if you try you may get that the data is written but never read if your stdin is a tty, see the following program (try it reading from the terminal, then with redirected input):

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#define TRACE(wtf) \
        fprintf(stderr, "%u: %s...\n", __LINE__, wtf)
#define BUF 123
int main()
{
        char buf[BUF] = "abc\n";
        char got[BUF];
        int n;
        /* writes to tty, appears on terminal, but it's NOT READABLE FROM STDIN */
        if(write(0, buf, sizeof buf) < 0) {
                perror("write");
                return 111;
        }
        TRACE("wok");
        /* read should block (until you type something) */
        if((n = read(0, got, sizeof got)) < 0) {
                perror("read");
                return 111;
        }
        TRACE("rok");
        if(memcmp(buf, got, n) != 0)
                return printf("wtf?\n");
        return 0;
}

Edit: to make it extra clear, this is in addition too foobar's answer; I just couldn't fit the code sample in a comment :)

Upvotes: 1

Related Questions