Checking if a file exists

Discussion in 'C Programming' started by Army1987, Apr 7, 2007.

  1. Army1987

    Army1987 Guest

    Is this a good way to check wheter a file already exists?

    #include <stdio.h>
    #include <stdlib.h>
    int ask(const char *prompt);
    typedef char filename[FILENAME_MAX];

    int main(int argc, char *argv[])
    {
    FILE *in, *out;
    filename in_name, out_name;
    int overwrite = 0;
    /* get filenames */
    out = fopen(out_name, "r")
    if (out != NULL) {
    fclose(out);
    printf("File %s already exists. ", out_name);
    if (ask("Do you want to overwrite it?") == 1)
    overwrite++;
    else
    exit(EXIT_FAILURE);
    }
    /* etc... */
    }

    --
    #include <stdio.h>
    #include <stdlib.h>
    int main(void) /* Don't try this at home */ {
    const size_t dim = 256; int i;
    for (i=0; malloc(dim); i++) /*nothing*/ ;
    printf("You're done! %zu\n", i*dim);
    puts("\n\n--Army1987"); return 0;
    }
     
    Army1987, Apr 7, 2007
    #1
    1. Advertisements

  2. Army1987 said:
    It's about as good as you're going to get under ISO C, but it's not
    reliable. The open might fail for other reasons than the non-existence
    of the file (e.g. disk error, no read permission, etc). And there is no
    guarantee that the file will continue to exist after the fclose, or
    that it will continue not to exist (if indeed it did not exist) after
    the fopen failure.

    Abstract this functionality into your "non-portable stuff" module, and
    consult your implementation's documentation for details of how to solve
    this problem (if indeed there is a solution) on the platform that it
    targets.
     
    Richard Heathfield, Apr 7, 2007
    #2
    1. Advertisements

  3. No, that's not a good idea. There could be lots of other reasons
    why the fopen() failed, starting from missing permission to open
    it for reading up to e.g. having run out of file descriptors. So
    you shouldn't blindly assume that the file does not already exist
    just because fopen() in read mode failed (and, by the way, it is
    also possible on a multitasking system that the file gets created
    by another process between the time you did the test and the time
    you open the it). If you want to avoid overwriting data in the
    file if it already exists fopen it in append mode and check if
    with ftell() if you're at the very start of the file (but that
    still will not allow to distinguish the cases of the file not
    existing at all and the file existing but being empty).

    Regards, Jens
     
    Jens Thoms Toerring, Apr 7, 2007
    #3
  4. Army1987

    Daniel Rudy Guest

    At about the time of 4/7/2007 6:51 AM, Army1987 stated the following:
    I think that Microsoft implements a file exists call. On Unix systems,
    you can stat(2) the file and check if errno = ENOENT. Granted, this is
    not portable, but it works.


    --
    Daniel Rudy

    Email address has been base64 encoded to reduce spam
    Decode email address using b64decode or uudecode -m

    Why geeks like computers: look chat date touch grep make unzip
    strip view finger mount fcsk more fcsk yes spray umount sleep
     
    Daniel Rudy, Apr 7, 2007
    #4
  5. [snip]

    Testing whether a file exists often (not always, but often) means
    you're asking the wrong question.

    Why do you want to know whether the file exists? If it's so you can
    decide whether to attempt some operation (e.g., reading from the file
    if it does exist, or creating it if it doesn't), it's often better to
    just go ahead and attempt the operation, and handle the error if it
    fails. (This can be difficult in standard C if you're trying to
    create a file; if I recall correctly, it's implementation-defined
    whether attempting to create an existing file will clobber the file or
    fail, but there are often system-specific ways to control this
    behavior.)
     
    Keith Thompson, Apr 8, 2007
    #5
  6. Army1987

    Eric Sosman Guest

    I would prefer to see "File IRREPLACABLE.DAT exists.
    Overwrite?" than to regret it at leisure ...

    This is what you allude to in "system-specific ways," but
    unfortunately Standard C's I/O is too diluted to do the job
    unaided.
     
    Eric Sosman, Apr 8, 2007
    #6
  7. Army1987

    Army1987 Guest

    That's the problem. If the file couldn't be opened for some other reason
    other than not existing, I would expect the fopen(out_name, "w") later in
    the program to fail (unless the user has permission -w- on the file, in
    which case I think he (or its owner) *deserves* to lose it, or some other
    problem which I can't imagine).

    Now if it is possible that out = fopen(out_name, "r"); fclose(out); deletes
    the file, I'd better find another way...
     
    Army1987, Apr 8, 2007
    #7
  8. Army1987

    Army1987 Guest

    7.19.5.3.3 says that fopen(name, "w") "truncate to zero length or
    create text file for writing".
    Simply opening the file for writing loses its contents if it already exists.
     
    Army1987, Apr 8, 2007
    #8
  9. Army1987 said:

    Well, I wouldn't expect that to happen! But I would not be surprised by
    the following sequence of events:

    1) you open the file read-only
    2) you close the file
    3) another process deletes the file
    4) you act on your belief that the file exists

    Multi-user / multi-process systems can be tricky, can't they?
     
    Richard Heathfield, Apr 8, 2007
    #9


  10. In order to avoid just that there's the "a" or "a+" mode you can
    pass to fopen() instead of "w" to open it in append mode. Then an
    already existing file won't get truncated and you're positioned
    at the end of the file while, if the file doesn't exist, it be-
    haves as if you had used "w".
    Regards, Jens
     
    Jens Thoms Toerring, Apr 8, 2007
    #10
  11. Army1987

    Army1987 Guest

    Probably, in this very case it wouldn't harm so much, because I meant to do
    something like:

    #include <stdio.h>
    #include <stdlib.h>
    int ask(const char *prompt);
    typedef char filename[FILENAME_MAX];

    int main(int argc, char *argv[])
    {
    char *name;
    FILE *in, *out;
    filename in_name, out_name;
    int overwrite = 0;
    /* get filenames */
    in = fopen(in_name, "r");
    if (in == NULL) {
    perror("Unable to read from file");
    exit(EXIT_FAILURE);
    out = fopen(out_name, "r");
    if (out != NULL) {
    fclose(out);
    printf("File %s already exists. ", out_name);
    if (ask("Do you want to overwrite it?") == 1)
    overwrite++;
    else
    exit(EXIT_FAILURE);
    }
    name = overwrite ? tmpnam(NULL) : out;
    out = fopen(name, "w");
    if (out == NULL) {
    fclose(in);
    perror(overwrite ? "Unable to create temporary file"
    : "Unable to create destination file");
    exit(EXIT_FAILURE);
    }
    /* do stuff */
    fclose(in);
    if (fclose(out)) {
    perror(overwrite ? "Unable to close temporary file"
    : "Unable to close destination file");
    exit(EXIT_FAILURE);
    }
    if (overwrite) {
    if (remove(in_name)) {
    perror("Unable to overwrite destination file");
    fprintf("The temporary file is available as %s", name);
    exit(EXIT_FAILURE);
    }
    if (rename(name, in_name)) {
    perror("Unable to rename temporary file");
    fprintf("The temporary file is available as %s", name);
    exit(EXIT_FAILURE);
    }
    }
    return EXIT_SUCCESS; /* Finally... */
    }
    (The actual error messages are just examples, I would use fprintf so to
    include filenames.)
    The very worst thing that could happen is that the destination filename
    would have a wrong, temporary name, which the user would know so to rename
    it by hand... or is it?
     
    Army1987, Apr 8, 2007
    #11
  12. Army1987

    Army1987 Guest

    Sorry. I meant:
    fprintf(stderr, ...);
     
    Army1987, Apr 8, 2007
    #12


  13. Thus "often" rather than "always". See also Jens's followup about "a"
    (append) mode.
     
    Keith Thompson, Apr 8, 2007
    #13
  14. Army1987

    Chris Torek Guest

    There is definitely something worse that could happen. Richard
    Heathfield's example covers a case like:

    fp = fopen(name, "r");
    if (fp != NULL) {
    fclose(fp);
    /* allow time to pass */

    fp = fopen(name, "r");
    /* assumption here: fp != NULL */
    }

    Your code (which I snipped), however, has a case more like this:

    fp = fopen(name, "r");
    if (fp != NULL) {
    fclose(fp);
    /* allow time to pass */
    ...
    } else {
    ---> fp = fopen(name, "w");
    ...
    }

    Now, at the line I marked with an arrow "--->", you assume that
    since fopen-for-reading returned NULL, the file does not exist (or,
    as you noted elsewhere, that it has bizarre permissions, in which
    case the user is shooting himself in the foot and there may not be
    much we can do about it anyway).

    The more serious problem -- and one which occurs in real-world
    security holes on these multi-user, multi-process systems that
    Richard Heathfield mentioned -- occurs when enough time passes
    between the first fopen (with "r") and the second (with "w") so
    that the file, which *did not exist* at the time of the first
    fopen(), *does* exist at the time of the second.

    In particular, consider, on a Unix-like system, what happens if
    you "race" your program against another one that does:

    /* evilprogram.c */
    #include <unistd.h> /* yes, non-ANSI */

    #define sensitive_file "/home/user/very_important_data"
    #define name "whatever"

    int main(void) {
    for (;;) {
    (void) unlink(name);
    usleep(1);
    (void) symlink(sensitive_file, name);
    sleep(1);
    }
    /*NOTREACHED*/
    }

    Now, if "evilprogram" runs at the right (wrong?) time while it
    races against your program, your code will do:

    fp = fopen(name, "r");

    and get NULL, because the file named "whatever" does not exist.
    Then evilprogram runs and creates the symlink, so that the file
    named "whatever" refers to your "very_important_data" file (or,
    in the case of the security holes I mentioned earlier, perhaps
    something like "/etc/passwd"). Since, in your code, fp==NULL,
    you assume that the file named "whatever" *still* does not
    exist, and you do:

    fp = fopen(name, "w");

    and are now overwriting your very important data (or /etc/passwd,
    if running as root, e.g.).

    (This problem existed before symlinks: since typical systems at
    the time had /etc and /tmp on the same file system, one could make
    a hard link from /etc/passwd to /tmp/foo and trick a setuid-root
    program into writing on /tmp/foo, which was by then actually
    /etc/passwd. The solution is to avoid the C library routines,
    which are insufficient, and go directly to open() -- and, in the
    case of setuid programs, to use the original 4.3BSD setreuid() or
    the more modern POSIX-style saved-setuid -- not the original,
    broken, System V version, which did not work for the super-user --
    to "give up" setuid permissions temporarily. Fundamentally, what
    is needed is an atomic "test all permissions and, only if OK, then
    do the open" call. This just does not exist in Standard C.)

    There really are times one can, and even should, write non-Standard-C
    code. The trick is knowing when. :)
     
    Chris Torek, Apr 8, 2007
    #14
  15. Army1987

    SM Ryan Guest

    # Now if it is possible that out = fopen(out_name, "r"); fclose(out); deletes
    # the file, I'd better find another way...

    <stdio.h> is sort of greatest common factor programming
    amongst all possible C implementation. It's grossly unsuitable
    for detailed I/O provided on various systems, by design.

    You can beat your head against a wall or decide that you don't
    want to program on all possible systems, just a subset. At that
    point you can depend on other standards like SVID, and you get
    additional operations which can do exactly this.

    For example if you're willing to restrict yourself to Unix
    and perhaps a few other systems, you can do an open(2) with
    O_EXCL followed by an fdopen(3). I'm sure VMS also provides
    this functionality in a completely different manner. Mac
    System 7 also provided this functionality (preserved in Carbon),
    again in completely different manner.
     
    SM Ryan, Apr 8, 2007
    #15
  16. SM Ryan said:
    How are you going to do I/O /without/ it, in a portable manner?
    Which subset?
    Is that available for *all* possible subsets of all possible systems?
    ....you can use comp.unix.programmer. That's what it's for.
     
    Richard Heathfield, Apr 8, 2007
    #16
  17. Army1987

    SM Ryan Guest

    # How are you going to do I/O /without/ it, in a portable manner?

    I'm not part of your inbred clique.
     
    SM Ryan, Apr 8, 2007
    #17
  18. I assume you meant out_name here.

    Isn't the test backwards?
    Didn't you mean out_name throughout this overwrite logic?

    Remove del for email
     
    Barry Schwarz, Apr 8, 2007
    #18
  19. SM Ryan said:
    Neither am I. But I note that you avoid the question instead of
    answering it.
     
    Richard Heathfield, Apr 9, 2007
    #19
  20. Army1987

    Army1987 Guest

    No. The next statement opens out_name if overwrite is 0 (i.e. if a file
    called that way didn't exist before), and creates a temporary file if a file
    called out_name existed and the user answered yes when asked wheter to
    overwrite it.
    Yes... (Luckily enough I didn't test it...)
     
    Army1987, Apr 10, 2007
    #20
    1. Advertisements

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments (here). After that, you can post your question and our members will help you out.