0

This is a simple implementation of the RETR cmd where the server first receives the filename and then it sends the file.

/************************* RECEIVE FILE NAME AND SEND FILE *************************/
if(recv(newsockd, buffer, sizeof(buffer), 0) < 0){
  perror("error receiving file name");
  onexit(newsockd, sockd, 0, 2);
}
other = strtok(buffer, " ");
filename = strtok(NULL, "\n");
if(strcmp(other, "RETR") == 0){
  printf("received RETR request\n");
} else onexit(newsockd, sockd, 0, 2);

fd = open(filename, O_RDONLY);
    if (fd < 0) {
    fprintf(stderr, "cannot open '%s': %s\n", filename, strerror(errno));
    onexit(newsockd, sockd, 0, 2);
}

if(fstat(fd, &fileStat) < 0){
    perror("Error fstat");
    onexit(newsockd, sockd, fd, 3);
}
fsize = fileStat.st_size;
if(send(newsockd, &fsize, sizeof(fsize), 0) < 0){
    perror("Error on sending file size\n");
    onexit(newsockd, sockd, fd, 3);
}

rc = sendfile(newsockd, fd, &offset, fileStat.st_size);
if(rc == -1) {
        fprintf(stderr, "error sending file: '%s'\n", strerror(errno));
        onexit(newsockd, sockd, fd, 3);
}
if((uint32_t)rc != fsize) {
    fprintf(stderr, "transfer incomplete: %d di %d bytes sent\n", rc, (int)fileStat.st_size);
    onexit(newsockd, sockd, fd, 3);
}
memset(buffer, 0, sizeof(buffer));
strcpy(buffer, "226 File trasferito con successo\n\0");
if(send(newsockd, buffer, strlen(buffer)+1, 0) < 0){
  perror("Errore durante l'invio 226");
  onexit(newsockd, sockd, 0, 2);
}
memset(buffer, 0, sizeof(buffer));
strcpy(buffer, "221 Goodbye\n\0");
if(send(newsockd, buffer, strlen(buffer)+1, 0) < 0){
  perror("Errore durante l'invio 221");
  onexit(newsockd, sockd, 0, 2);
}
/************************* END PART *************************/

and this is the snippet of the client program:

/************************* SEND FILE NAME AND RECEIVE FILE *************************/
printf("Inserire il nome del file da scaricare: ");
if(fgets(dirpath, BUFFGETS, stdin) == NULL){
    perror("fgets name file");
    close(sockd);
}
filename = strtok(dirpath, "\n");
sprintf(buffer, "RETR %s", dirpath);
if(send(sockd, buffer, strlen(buffer), 0) < 0){
    perror("error sending file name");
    close(sockd);
    exit(1);
}
if(read(sockd, &fsize, sizeof(fsize)) < 0){
    perror("error on receiving file size\n");
    close(sockd);
    exit(1);
}
fd = open(filename, O_CREAT | O_WRONLY, 0644);
if (fd  < 0) {
    perror("open");
    exit(1);
}

while(((uint32_t)total_bytes_read != fsize) && ((nread = read(sockd, filebuffer, fsize)) > 0)){
    if(write(fd, filebuffer, nread) < 0){
        perror("write");
        close(sockd);
        exit(1);
    }
    total_bytes_read += nread;
}
memset(buffer, 0, sizeof(buffer));
if(recv(sockd, buffer, 34, 0) < 0){
    perror("Error receiving 226");
    close(sockd);
    exit(1);
}
printf("%s", buffer);
memset(buffer, 0, sizeof(buffer));
if(recv(sockd, buffer, 13, 0) < 0){
    perror("Error receiving 221");
    close(sockd);
    exit(1);
}
printf("%s", buffer);
memset(buffer, 0, sizeof(buffer));
close(fd);
/************************* END PART *************************/

What is the problem?
The problem is that the file that has been sent contains also the 2 messages (226 and 221) sent by the server and i don't know why i got this behaviour O.o
Example:
RETR tryfile.txt
File tryfile.txt received
cat tryfile.txt
"This is tryfile.txt, hello client
226 File trasferito con successo
221 Goodbye"

polslinux
  • 1,739
  • 9
  • 34
  • 73
  • 3
    1) you are assuming message boundaries are preserved by TCP. They are not. 2) you are assuming NUL-terminated strings, but you dont send the NULs. 3) IIRC, line-based protocols should terminate the lines by `\r\n`. – wildplasser Jul 08 '12 at 13:27
  • 1) eh? i've not understand, sorrhy :( 2) corrected `send` with `strlen(buffer)+1` 3) i'm assuming to use normal line :) it is a very simple project – polslinux Jul 08 '12 at 13:37
  • @wildplasser: Although counter intuitive, definitely use `\r\n`. Although that is Windows' line ending, it has been my experience that although Windows has complained and broken when receiving UNIX line endings. However, in standard UNIX fashion, the UNIX systems happily converted it for me from DOS format, didn't complain etc. – Linuxios Jul 08 '12 at 13:38
  • Could you please translate some of the Italian comments? – Linuxios Jul 08 '12 at 13:38
  • I think that all rfc822 (-->> SMTP, HTTP) like protocols want `\r\n` as EOL, indepently of the platform (but implementations should be forgiving in what to expect) @ 1) TCP is a stream protocol. Octets are sent in sequence, but the amount received *per packet* does not need to be equal to the size of the sent "packets". There **are no packets** in TCP. Also the return value for read() and write() does not have to be equal to the 3rd argument. It can be less, or 0 or -1. similar for send/recv. – wildplasser Jul 08 '12 at 13:44
  • That was not me. I can understand enough of the the Italian (and it is not that important, since I can read C ;-) – wildplasser Jul 08 '12 at 13:48
  • Oh sorry i got confused :D @Linuxios done ita to eng! – polslinux Jul 08 '12 at 13:53

2 Answers2

1

The strlen() does not count the \0, so strlen() will return 12:

strcpy(buffer, "221 Goodbye\n\0");
if(send(newsockd, buffer, strlen(buffer), 0) < 0){ ...}

The client uses a hardcoded value 13. (the same for the other status message where 33 is sent and 34 expected.) BTW: you really need some buffering mechanism, at least in the client.

UPDATE: To display the strlen of a string with an "embedded null":

#include <stdio.h>
#include <string.h>

int main(void)
{
fprintf(stderr, "strlen is %u\n", (unsigned) strlen("221 Goodbye\n\0") );

return 0;
}

Explanation: strlen() just counts characters, until it encounters a '\0' character

wildplasser
  • 43,142
  • 8
  • 66
  • 109
  • in the comment above i've written that i have corrected my code :) but i don't have updated the question sorry! however what is wrong with it? Those are predefined string so what is wrong with my code??? Thanks! – polslinux Jul 08 '12 at 18:07
  • You could put an extra debugging `fprintf(stderr, "strlen is %u\n", (unsigned) strlen("221 Goodbye\n\0") );` in the source. – wildplasser Jul 08 '12 at 18:12
  • yes, strlen is 13 :) i don't understand what do you want to say to me :( – polslinux Jul 08 '12 at 18:16
  • Maybe you have a different strlen(); mine prints 12. – wildplasser Jul 08 '12 at 18:21
  • WTF!! You're right xD i'm an idiot, i've put the `strlen+1` because i've thought to '\0' but i have already included it xD – polslinux Jul 08 '12 at 20:02
0

I've found and (finally) solved the problem.
Here is the solution:

uint32_t fsize_tmp = fsize;
while(((uint32_t)total_bytes_read != fsize) && ((nread = read(sockd, filebuffer, fsize_tmp)) > 0)){
     if(write(fd, filebuffer, nread) < 0){
    perror("write");
    close(sockd);
    exit(1);
      }
      total_bytes_read += nread;
      fsize_tmp -= nread;
}

the problem was due to the fact i was not checking the "dynamic" file size (if the file was sent at 75% my while loop expected fsize again and this was impossible) so with this code i will decrease the file size every time it is needed :)

polslinux
  • 1,739
  • 9
  • 34
  • 73
  • 2
    You are still not checking the return value from write. it could be less than nread. Also: the read could return zero, causing the fragment to loop forever. – wildplasser Jul 08 '12 at 15:49
  • but i've put the condition `nread>0` so if 0 is returned there is no problem :) – polslinux Jul 08 '12 at 16:51