rich
rich

Reputation: 451

How do I detect a file write error in C?

I have an embedded environment where a user might insert or remove a USB flash drive. I would like to know if the drive has been removed, or if there is some other problem when I try to write to the drive. However, Linux just saves the information in its buffers and returns with no indicated error.
The computer I'm using comes with a 2.4.26 kernel and libc 2.3.2.
I'm mounting the drive this way:
i = mount(MEMORY_DEV_PATH, MEMORY_MNT_PATH, "vfat", MS_SYNCHRONOUS, NULL);
That works:
50:/root # mount
/dev/scsi/host0/bus0/target0/lun0/part1 on /mem type vfat (rw,sync)
50:/root #

Later, I try to copy a file to it:

int ifile, ofile;
ifile = open("/tmp/tmpmidi.mid", O_RDONLY);
if (ifile < 0)
{
    perror("open in");
    break;
}
ofile = open(current_file_name.c_str(), O_WRONLY | O_SYNC);
if (ofile < 0)
{
    perror("open out");
    break;
}
#define BUFSZ 256
char buffer[BUFSZ];
while (1)
{
    i = read(ifile, buffer, BUFSZ);
    if (i < 0)
    {
        perror("read");
        break;
    }
    j = write(ofile, buffer, i);
    if (j < 0)
    {
        perror("write");
        break;
    }
    if (i != j)
    {
        perror("Sizes wrong");
        break;
    }
    if (i < BUFSZ)
    {
        printf("Copy is finished, I hope\n");
        close(ifile);
        close(ofile);
        break;
    }
}

If this snippet of code is executed with a write-protected USB memory, the result is

Copy is finished, I hope

amid a flurry of error messages from the kernel on the console.
I believe the same thing would happen if I simply removed the USB drive (without unmounting it).
I have also fiddled with devfs. I figured out how to get it to automatically mount the drive, (with the REGISTER event) but it never seems to trigger the UNREGISTER when I pull out the memory.
How can I determine in my program whether I have successfully created a file?

Update 4 July: It was a silly oversight of me not to check the result from close(). Unfortunately, the file can be closed without error. So that didn't help. What about fsync()? That sounds like a good idea, but that didn't catch the error either.
There might be some interesting information in /sys if I had such a thing. I believe that didn't get added until 2.6.?.
The comment(s) about the quality of my flash drive are probably justified. It's one of the earlier ones. In fact, write protect switches seem to be extremely rare these days.
I think I have to use the overkill option: Create a file, unmount & remount the drive, and check to see if the file is there. If that doesn't solve my problem, then something is really messed up! Note to myself: Make sure the file you try to create isn't already there!
By the way, this does happen to be a C++ program. You can tell by the .c_str() which I had intended to edit out for simplicity.

Upvotes: 4

Views: 9937

Answers (5)

Chris Reuter
Chris Reuter

Reputation: 1670

Short answer: the problem is with the OS and/or the USB drive. Linux will (sometimes?) happily mount a USB drive as writable even if the write-protect switch is set. When it actually tries to write to it however, the drive refuses and this is handled more or less as a defective drive. (I'm not sure why this happens. My guess is that the flash drive isn't reporting its read-only-ness to the OS. This may depend on the make and model of flash drive you're using.)

I can get the same behaviour with a (very old) USB key on a 3.2.0-26 kernel (Ubuntu 12, specifically). I have no trouble mounting the write-protected USB key read-write and 'cp' won't complain if I copy a file to it. The file may even show up in the directory (because of buffering) but nothing ever actually gets written to it. I do get a lot of error messages in the syslog.

If I were you, I'd try to actually write to the flash drive and ensure it succeeded before assuming the drive is actually writable. Specifically, I'd:

  1. Mount the drive as writeable.

  2. Create a new file on the drive with a unique name.

  3. Unmount the drive to empty the buffers.

  4. Mount the drive again.

  5. Check if the new file is still there and contains the data you wrote to it.

  6. Delete the test file.

I'm not sufficiently up on the hardware and driver states to tell you if there's a way to detect unwritable drives from the API--there might be, but I don't know. But even if there is, this case will detect USB drives that don't behave properly.

Update:

I did a bit more research and it turns out that an fsync() done on the file handle will fail if the write-protect switch is set. As such, I retract the above advice. Instead, here's my test program:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

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

int
main(int argc, char *argv[]) {
    char *path;
    int f;
    size_t stat;
    const char *message = "Hello, world.\n";

    if (argc != 2) {
        printf("Need filename\n");
        exit(1);
    }

    path = argv[1];

    f = open(path, O_CREAT | O_WRONLY | O_SYNC, S_IRWXU);
    if (f < 0) {
        perror("open out");
        exit(1);
    }/* if */

    stat = write(f, message, strlen(message));
    if (stat < 0) {
        perror("write");
        exit(1);
    }/* if */

    stat = fsync(f);
    if (stat) {
        perror("fsync");
        exit(1);
    }

    stat = close(f);
    if (stat) {
        perror("close");
        exit(1);
    }/* if */

    printf("(Apparently) successfully wrote '%s'\n", path);
    return 0;
}

This should fail on the fsync() call if the device isn't writable.

Upvotes: 1

Nominal Animal
Nominal Animal

Reputation: 39426

If your application wants to save one or more files to an USB stick, and let the user pull out the stick just about immediately after the little LED turns off, you need to

  1. mount() the USB stick just before writing the files

    You don't need to mount SYNC for this, which helps with poor-quality USB sticks

  2. Save the files using correct code

    Your attempt at C low-level I/O is quite incorrect. In particular, any read() or write() is allowed to return a short count, any positive value between 1 and the requested size, inclusive, without it indicating any sort of an error.

  3. fsync() the file on the USB stick

    Verify that it returns success. If it does, then you know the data has hit the USB stick.

  4. fsync() the directory containing the file on the USB stick

    Verify that it returns success. If it does, then you know the file metadata has hit the USB stick, and the file should now be accessible even if the user yanks the USB stick out.

  5. umount() the USB stick

    This will block until the USB stick is ready to be disconnected, so your application should look like it is saving to the file until the umount() returns.

    If you did the fsync()s above, the umount() should be pretty much immediate. Depending on the filesystem, there may be some bookkeeping the kernel wishes to do, but it should not take long in any case.

Anything else is simply not reliable. You might be able to make certain assumptions by mounting a VFAT partition with synchronous access, but that's basically just handwaving.

If you don't want to run your application with root privileges, you can always write a simple privileged service that manages the mounting and unmounting. I'd recommend that very warmly if you suspect you might need more than one application at some point, since only a centralized mounter/unmounter can tell when the media is ready. (It can delay returning the "unmounted" message if another application is writing to the same USB media at the same time. That works quite well when you don't need to mount the USB media synchronously, by the way.) I would personally use a unix domain datagram socket in /var/run/, maybe /var/run/mounter.socket, for the interprocess communication.

Finally, if your Linux kernel is properly configured and you have the /sys/ partition mounted, you can scan all /sys/block/sd?/ directories for removable media:

  • /sys/block/sd?/removable will contain a nonzero decimal number string
  • /sys/block/sd?/size contains the device size in units of 512 bytes, as a decimal number string
  • /sys/block/sd?/device/vendor contains the vendor name as string
  • /sys/block/sd?/device/model contains the model name as string

These entries are hardware-level, and are available whenever the USB stick is connected and powered; it does not matter whether it is mounted or not. The entire device directory tree will vanish immediately if/when the user yanks out the USB stick.

Upvotes: 3

caf
caf

Reputation: 239321

If you want to detect all write errors, you need to check more than just the return code of write() - you must also call fsync() (and check the return value), and also check for errors reported by close().

Upvotes: 2

Emile Cormier
Emile Cormier

Reputation: 29229

If you can't find a solution, you could try this ugly hack. Right after writing and closing the output file, you could simply try opening it in read mode and check that it has the right size. If you really want to make sure it has the right contents, you could verify that it has the same checksum as the file you just wrote. This assumes that the OS will read the file back directly from the USB drive and not some kind of cache.

Upvotes: 0

user457586
user457586

Reputation:

Here's the POSIX way of doing things. If the return value of write is -1 you know for sure that something went horribly wrong. However, if write returns 0, something also may have gone wrong. Check the errno variable to see if it matches one of the predefined write errors shown here: http://www.kernel.org/doc/man-pages/online/pages/man2/write.2.html

Upvotes: 0

Related Questions