Search code examples
clinux-kernelsendfile

Why with sendfile() I get [EINVAL Invalid argument], but read() works good (file to socket)?


I try to send a file into the socket using the system call sendfile(), but get the error: ERROR sendfile: [EINVAL Invalid argument]. But the call read() works perfectly with the same arguments. I also tried to give to sendfile() two files description in arguments - got the same result.

Do you have any suggestions why this might be?

Some additional info: OS: Ubuntu 22.04.1 LTS on VirtualBox: 6.1.38 r153438 (Qt5.6.2); Linux kernel: 5.15.0-52-generic; glibc: 2.35.

There are examples of the two SIMPLEST programs that I used:

  1. Program copies data from one file to another using sendfile()
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/sendfile.h>
#include "gen_hdr.h"

int main() {
    /***        Get file size and open files       ***/
    int fd;
    size_t size;
    struct stat statBuf;
    
    if (stat("test_file.txt", &statBuf) == -1) {
        errExit("statBuf");
    }
    
    size = statBuf.st_size;

    fd = open("test_file.txt", O_RDONLY);
    if (-1 == fd) {
        errExit("open");
    }

    int fdNew, openFlags;
    mode_t filePerms;
    openFlags = O_CREAT | O_WRONLY | O_EXCL;
    filePerms = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP |
                S_IROTH | S_IWOTH; /* rw-rw-rw- */
    
    fdNew = open("clone_test_file.txt", openFlags, filePerms);
    /***        END        ***/

    /***        SENDFILE        ***/
    off_t offset = 0;
    ssize_t sent = 0;

    for (; size > 0; ) {
    errno = 0;
    sent = sendfile(fdNew, fd, &offset, size);
    printf("%s\n", strerror(errno));
    printf("%ld\n", sent);
    if (sent <= 0) {                 // Error or end of file
        if (sent != 0) {
            errExit("sendfile");    // Was an error, report it
        }
        break;                      // End of file
    }

    size -= sent;           // Decrease the send length by the amount actually sent
    }
    /***        END        ***/

    if (close(fd) == -1) {
        errExit("close");
    }
    if (close(fdNew) == -1) {
        errExit("close");
    }

    return 0;
}
  1. Program creates a simple TCP server that just send file to a connected client. If I use the system call send()(section SENDFILE) instead of sendfile() - all works well.

Server:

#include <ctype.h>
#include <netinet/in.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/sendfile.h>
#include "gen_hdr.h"

#define PORT_NUM 55550
#define BACKLOG 5

int main() {
    /***        Get file size and open it       ***/
    int fd;
    size_t size_to_send;
    struct stat statBuf;

    if (stat("test_file.txt", &statBuf) == -1) {
        errExit("statBuf");
    }
    
    size_to_send = statBuf.st_size;

    fd = open("test_file.txt", O_RDONLY);
    if (-1 == fd) {
        errExit("open");
    }
    /***        END        ***/

    /***        Server Setup        ***/
    struct sockaddr_in svaddr, claddr;
    //struct sockaddr_storage claddr;
    socklen_t len;
    int sockfd, connfd;
    char claddrStr[INET_ADDRSTRLEN];

    memset(&svaddr, 0, sizeof(struct sockaddr_in));
    svaddr.sin_family = AF_INET;
    svaddr.sin_port = (in_port_t)htons(PORT_NUM);
    svaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == sockfd) {
        errExit("socket");
    }

    if (bind(sockfd, (struct sockaddr *)&svaddr, sizeof(struct sockaddr_in)) == -1) {
         errExit("bind");
    }

    if (listen(sockfd, BACKLOG) == -1) {
         errExit("listen");
    }
    printf("Server is waiting for connection...\n");
    
    len = sizeof(struct sockaddr_in);
    connfd = accept(sockfd, (struct sockaddr*)&claddr, &len);
    if (-1 == connfd) {
        errExit("accept");
    }

    if (inet_ntop(AF_INET, &claddr.sin_addr, claddrStr, INET_ADDRSTRLEN) == NULL) {
        printf("Coudn't convert client address to string\n");
    }
    else {
        printf("Connection from [%s, %u]\n", claddrStr, ntohs(claddr.sin_port));
    }
    /***        END        ***/

    /***        SENDFILE        ***/
    off_t offset = 0;
    ssize_t sent = 0;

    for (; size_to_send > 0; ) {
        errno = 0;
        sent = sendfile(connfd, fd, &offset, size_to_send);
        printf("%s\n", strerror(errno));
        printf("%ld\n", sent);
        if (sent <= 0) {                // Error or end of file
            if (sent != 0) {
                errExit("sendfile");    // Was an error, report it
            }
            break;                      // End of file
        }

        size_to_send -= sent;           // Decrease the send length by the amount actually sent
    }
    /***        END         ***/

    /***        SEND FILE        ***/
    // char* buf = (char*)malloc(size_to_send);
    // if (NULL == buf) {
    //     errExit("malloc");
    // }

    // if (read(fd, buf, size_to_send) == -1) {
    //     errExit("read");
    // }

    // if (send(connfd, buf, size_to_send, 0) == -1) {
    //     errExit("send");
    // }
    // free(buf);
    /***        END         ***/

    printf("File sent...\n");

    if (close(fd) == -1) {
        errExit("close");
    }
    if (close(connfd) == -1) {
        printf("ERROR on closing connection\n");
    }
    if (close(sockfd) == -1) {
        printf("ERROR on closing socket\n");
    }
    return 0;
}

Client:

#include <netinet/in.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "gen_hdr.h"

#define PORT_NUM 55550
#define BUF_SIZE 10

int main(int argc, char* argv[]) {
    /***        Get file size and allocate memory       ***/
    char* buf;
    size_t size_to_recv;
    struct stat statBuf;

    if (stat("test_file.txt", &statBuf) == -1) {
        errExit("statBuf");
    }
    
    size_to_recv = statBuf.st_size;

    buf = (char*)malloc(size_to_recv);
    if (NULL == buf) {
        errExit("malloc");
    }
    /***        END        ***/

    /***        Server Setup        ***/
    struct sockaddr_in svaddr;
    socklen_t len;
    int sockfd;
    ssize_t numBytes;
    char claddrStr[INET_ADDRSTRLEN];

    memset(&svaddr, 0, sizeof(struct sockaddr_in));
    svaddr.sin_family = AF_INET;
    svaddr.sin_port = (in_port_t)htons(PORT_NUM);
    svaddr.sin_addr.s_addr = inet_addr("127.0.0.1");

    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == sockfd) {
        errExit("socket");
    }

    if (connect(sockfd, (struct sockaddr *)&svaddr, sizeof(struct sockaddr_in)) == -1) {
        errExit("connect");
    }
    /***        END        ***/

    printf("Getting file...\n");

    /***        RECEIVE FILE        ***/
    ssize_t numRead;                /* Bytes that we read from the last call of read() */
    size_t totRead;                 /* Number of bytes that we have read at the moment */
    char* recvBuf = buf;

    for (totRead = 0; totRead < size_to_recv; ) {
        numRead = read(sockfd, recvBuf, size_to_recv - totRead);

        if (numRead > 0) {
            totRead += numRead;
            recvBuf += numRead;
        } else if (numRead == 0) {          /* End of file */
            return totRead;                 /* It could be 0, if it is the first call of read() */
        } else {                            /* Error */
            if (errno == EINTR) {
                continue;                   /* Interrupted --> recall read() */
            }
            else {
                errExit("read");            /* Some other error */
            }
        }
    }
    /***        END         ***/

    printf("Ended\n");

    /***        WRITE IN NEW FILE        ***/
    int fd, openFlags;
    mode_t filePerms;
    openFlags = O_CREAT | O_WRONLY | O_EXCL;
    filePerms = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP |
                S_IROTH | S_IWOTH; /* rw-rw-rw- */
    
    fd = open("clone_test_file.txt", openFlags, filePerms);

    if (write(fd, buf, size_to_recv) == -1) {
        errExit("write");
    }
    /***        END         ***/

    free(buf);
    if (close(fd) == -1) {
        errExit("close");
    }
    if (close(sockfd) == -1) {
        errExit("close");
    }

    return 0;
}

"gen_hdr.h" just includes "standard" libs and declaration of error handling functions:

#ifndef GEN_HDR_H
#define GEN_HDR_H

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <stdint.h>

typedef enum {FALSE, TRUE} boolean;

#include "error_functions.h"

#define max(a,b) (((a) > (b)) ? (a) : (b))
#define min(a,b) (((a) < (b)) ? (a) : (b))

#endif /* GEN_HDR_H */

Error_functions.c:

#include "gen_hdr.h"
#include "ename.c.inc"

static void outputError(int err, boolean flushStdout, const char* msg) {
    #define BUF_SIZE 500
    char buf[BUF_SIZE];
    snprintf(buf, BUF_SIZE, "ERROR %s: [%s %s]\n", msg, ((err > 0 && err < MAX_ENAME) ? ename[err] : "?UNKNOWN?"), strerror(err));
    if (flushStdout)
    {
        fflush(stdout);
    }
    fputs(buf, stderr);
    fflush(stderr);
}

void errExit(char* msg) {
    outputError(errno, TRUE, msg);
    exit(EXIT_FAILURE);
}

void errExitEN(int errNum, char* msg) {
    outputError(errNum, TRUE, msg);
    exit(EXIT_FAILURE);
}

Solution

  • If a file is located in the shared with windows folder, sendfile() doesn't work, although 'mmap()' works.