3
\$\begingroup\$

Currently I have a wrapper function for the read/write system calls, the purpose of which is to eliminate partial transfer scenarios, by retrying until either all data is transferred or the system call fails in trying to do so.

Includes:

#include <errno.h>
#include <unistd.h>

Code:

int force(ssize_t(*const func)(int, void *, size_t), const int file, void *ptr, size_t size) {
    for (ssize_t r;; ptr = (char *)ptr+r) {
        if ((r = func(file, ptr, size)) == -1) {
            if (errno == EINTR)
                continue;
            return -1;
        }
        if (size -= r)
            continue;
        return 0;
    }
}

This function would be used as such:

extern struct Conf conf;
int file = open("file", O_RDONLY);
if (force(read, file, &conf, sizeof(struct Conf)))
    handle_error();

If write is passed, ptr may point to const data because write does not modify the data pointed to. But const casts are considered improper. Should I keep this wrapper, or should I write separate and mostly duplicated, but 'proper' wrappers?

int force_read(const int file, void *ptr, size_t size) {
    for (ssize_t r;; ptr = (char *)ptr+r) {
        if ((r = read(file, ptr, size)) == -1) {
            if (errno == EINTR)
                continue;
            return -1;
        }
        if (size -= r)
            continue;
        return 0;
    }
}
int force_write(const int file, const void *ptr, size_t size) {
    for (ssize_t r;; ptr = (const char *)ptr+r) {
        if ((r = write(file, ptr, size)) == -1) {
            if (errno == EINTR)
                continue;
            return -1;
        }
        if (size -= r)
            continue;
        return 0;
    }
}
\$\endgroup\$
7
  • \$\begingroup\$ what about EAGAIN \$\endgroup\$
    – pm100
    Commented Mar 2, 2022 at 23:58
  • 1
    \$\begingroup\$ @pm100 That is for non blocking IO but I am only dealing with regular files \$\endgroup\$
    – user256024
    Commented Mar 3, 2022 at 1:03
  • 1
    \$\begingroup\$ you didnt say that was the case \$\endgroup\$
    – pm100
    Commented Mar 3, 2022 at 1:11
  • 1
    \$\begingroup\$ In addition to read, write, EINTR you identified as non-standard, ssize_t, O_RDONLY, handle_error() are also non-standard C. Recommend that you add the Posix tag (and since the limit is 5, drop one). Further, even with a tag change, posted code does not compile. \$\endgroup\$ Commented Mar 3, 2022 at 16:31
  • 2
    \$\begingroup\$ Please try compiling the posted code (which lacks headers) and then reconsider to post compliable code. \$\endgroup\$ Commented Mar 4, 2022 at 16:58

2 Answers 2

3
\$\begingroup\$

I don't think either of the choices presented are a good option without seeing the code that actually needs to use them.

The read() and write() functions are low level I/O, I rarely use them and when I do it is generally to read a binary file. If I need to use these low level functions for I/O then it requires a lot more control over the input then is demonstrated by these options.

For general input I prefer fgets() or getline(), however, I will sometimes use one of the scanf() functions. For general output I use the printf() family of functions.

\$\endgroup\$
4
  • 1
    \$\begingroup\$ What if I am dealing with file descriptors as opposed to FILE * \$\endgroup\$
    – user256024
    Commented Mar 5, 2022 at 2:47
  • \$\begingroup\$ 'man fdopen' should help. \$\endgroup\$ Commented Mar 5, 2022 at 8:09
  • \$\begingroup\$ My programs almost invariably interact with only binary files as opposed to text files \$\endgroup\$
    – user256024
    Commented Mar 5, 2022 at 15:57
  • 1
    \$\begingroup\$ When I find myself reaching for these low-level functions, then memory mapping is often a better alternative. \$\endgroup\$ Commented Mar 7, 2022 at 13:35
2
\$\begingroup\$

The second choice should be used for a separate reason, an asymmetry between read and write. In the event that read is called via the above function, and EOF is reached before the requested bytes are read, read will return 0, resulting in a forever loop. write does not need to handle a 0 return, unless size is 0, which can be handled by checking size before the first iteration instead after. The read wrapper may return a different value such as 1 signaling an end of file. Local variable names should also be lowercase.

int Read(const int file, void *ptr, size_t size) {
    for (ssize_t r; size; size -= r, ptr = (char *)ptr+r) {
        if ((r = read(file, ptr, size))) {
            if (r == -1)
                return -1;
            continue;
        }
        return memset(ptr, 0, size), 1;
    }
    return 0;
}
int Write(const int file, const void *ptr, size_t size) {
    for (ssize_t r; size; size -= r, ptr = (const char *)ptr+r) {
        if ((r = write(file, ptr, size)) == -1)
            return -1;
    }
    return 0;
}
\$\endgroup\$
0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.