NightWatchMan
NightWatchMan

Reputation: 45

write() to sysfs entry /sys/bus/pci/devices/.../driver/remove_id fails

Seeing the write() function failing on /sys/bus/pci/devices/.../driver/remove_id file returning -1 with errno equal to 19 (ENODEV).

But, same works fine via command line. I have checked the file permission which seems to be fine (--w-------) for user to perform write on this file.

int fp = 0;
int buffer_length = 0;
int bytes_written = 0;

fp = open(cmd_buf, O_WRONLY); // where cmd_buf will hold this string 
                              // "/sys/bus/pci/devices/.../driver/remove_id"
if (fp == -1)
{
    return -1;
}

// where inbuf will be a char * pointing to pci vendor device id like 
// this, "XXXX YYYY"
bytes_written = write(fp, in_buf, sizeof(in_buf));
printf(" bytes_written : %d \n ", bytes_written);

Seeing bytes_written equal to -1 and errno shows 19.

Please let me know if you find something wrong with the code snippet?

Upvotes: 0

Views: 582

Answers (2)

Glärbo
Glärbo

Reputation: 91

You do not provide enough information to pinpoint the problem.

However, here is an example program, example.c, that shows that it is your implementation that has the bug:

#define _POSIX_C_SOURCE  200809L
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>

/* Write string 'data' to existing file or device at 'path'.
   Returns 0 if success, errno error code otherwise.
*/
int write_file(const char *path, const char *data)
{
    const char *const  ends = (data) ? data + strlen(data) : data;
    ssize_t            n;
    int                fd;

    /* NULL or empty path is invalid. */
    if (!path || !*path)
        return errno = EINVAL;

    fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_NOCTTY | O_CLOEXEC, 0666);
    if (fd == -1)
        return errno; /* errno already set by open(). */

    /* Write the contents of data. */
    while (data < ends) {
        n = write(fd, data, (size_t)(ends - data));
        if (n > 0) {
            /* Wrote n bytes. */
            data += n;
        } else
        if (n != -1) {
            /* C Library bug: Should never occur. */
            close(fd);
            return errno = EIO;
        } else {
            /* Error in errno. */
            const int  saved_errno = errno;
            close(fd);
            return errno = saved_errno;
        }
    }

    if (close(fd) == -1) {
        /* It is possible for close() to report a delayed I/O error. */
        return errno;
    }

    /* Success. */
    return 0;
}

static void usage(const char *argv0)
{
    fprintf(stderr, "\n");
    fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv0);
    fprintf(stderr, "       %s FILE CONTENTS\n", argv0);
    fprintf(stderr, "\n");
    fprintf(stderr, "This does the same thing as 'echo -n \"CONTENTS\" > FILE'.\n");
    fprintf(stderr, "\n");
}


int main(int argc, char *argv[])
{
    if (argc < 2) {
        usage((argv && argv[0] && argv[0][0]) ? argv[0] : "(this)");
        return EXIT_SUCCESS;
    } else
    if (argc > 3) {
        usage((argv && argv[0] && argv[0][0]) ? argv[0] : "(this)");
        return EXIT_FAILURE;
    } else
    if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
        usage((argv && argv[0] && argv[0][0]) ? argv[0] : "(this)");
        return EXIT_SUCCESS;
    }

    if (write_file(argv[1], argv[2])) {
        fprintf(stderr, "%s: %s.\n", argv[1], strerror(errno));
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

Compile it using e.g. gcc -Wall -Wextra -O2 example.c -o example, and run using e.g. ./example /sys/bus/pci/devices/.../driver/remove_id "vendor_id device_id". Run it without arguments, or with -h or --help as the only argument, and it will print usage information to standard error.

The program essentially does what echo -n "vendor_id device_id" > /sys/bus/pci/devices/.../drivers/remove_id does.

If it is successful, it will not output anything, just return success (exit status 0). If there is any kind of an error, it will report it to standard error.

If you know the target path is always a device or a pseudo-file (like those in /sys or /proc), use fd = open(path, O_WRONLY | O_NOCTTY | O_CLOEXEC); instead. O_CLOEXEC means that if the process forks at any point, this particular file descriptor is not copied to the child process. O_NOCTTY means that if the path is a tty device, and the current process does not have a controlling terminal, the kernel is NOT to make the opened device the controlling terminal.

echo -n uses O_CREAT | O_TRUNC, so that if the target path exists and is a normal file, it is truncated, but if it does not exist, it is created. It does not affect opening existing character devices and pseudofiles. Whenever O_CREAT is used, there must be a third parameter, which affects the access mode of the file created. This mode is usually 0666, allowing read and write access as moderated by the current umask. One can obtain the current umask using mode_t mask = umask(0); umask(mask);. Access mode bits set in umask are always zero in the final access mode, and access mode bits clear in umask are taken from the third parameter of the open() command when the file is created.

Upvotes: 1

Rachid K.
Rachid K.

Reputation: 5211

Two possible problems:

  1. Using sizeof(in_buf) in the write() system call writes the string "vendorId deviceId" and may be more garbage data behind it if in_buf[] is bigger than 10 chars.
  2. Perhaps in_buf is not a table but a pointer and so, sizeof(in_buf) will return 4 or 8 (the size of the pointer respectively for 32 or 64 bits systems) but not the length of the string it points to.

So, in both cases (in_buf defined as a table or a pointer), strlen(in_buf) instead of sizeof(in_buf) is the most secured solution for the length of the data to write provided that the string is terminated by '\0'.

Upvotes: 1

Related Questions