Checking if a file exists

A

Army1987

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;
}
 
R

Richard Heathfield

Army1987 said:
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... */
}

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.
 
J

Jens Thoms Toerring

Army1987 said:
Is this a good way to check wheter a file already exists?
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++;

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
 
D

Daniel Rudy

At about the time of 4/7/2007 6:51 AM, Army1987 stated the following:
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... */
}

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
 
K

Keith Thompson

Army1987 said:
Is this a good way to check wheter a file already exists?
[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.)
 
E

Eric Sosman

Keith said:
Army1987 said:
Is this a good way to check wheter a file already exists?
[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.)

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.
 
A

Army1987

Richard Heathfield said:
Army1987 said:
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... */
}

And there is no
guarantee that the file will continue to exist after the fclose.

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...
 
A

Army1987

Keith Thompson said:
Army1987 said:
Is this a good way to check wheter a file already exists?
[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.

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.
 
R

Richard Heathfield

Army1987 said:

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

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?
 
J

Jens Thoms Toerring

Army1987 said:
Keith Thompson said:
Army1987 said:
Is this a good way to check wheter a file already exists?
[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.
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.


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
 
A

Army1987

Richard Heathfield said:
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?

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?
 
K

Keith Thompson

Army1987 said:
Keith Thompson said:
Army1987 said:
Is this a good way to check wheter a file already exists?
[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.

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.


Thus "often" rather than "always". See also Jens's followup about "a"
(append) mode.
 
C

Chris Torek

Probably, in this very case it wouldn't harm so much ... [code snipped]
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?

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. :)
 
S

SM Ryan

# 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.
 
R

Richard Heathfield

SM Ryan said:
# 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.

How are you going to do I/O /without/ it, in a portable manner?
You can beat your head against a wall or decide that you don't
want to program on all possible systems, just a subset.

Which subset?
At that
point you can depend on other standards like SVID,

Is that available for *all* possible subsets of all possible systems?
and you get
additional operations which can do exactly this.

For example if you're willing to restrict yourself to Unix

....you can use comp.unix.programmer. That's what it's for.
 
S

SM Ryan

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

I'm not part of your inbred clique.
 
B

Barry Schwarz

Richard Heathfield said:
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?

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;

I assume you meant out_name here.

Isn't the test backwards?
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)) {

Didn't you mean out_name throughout this overwrite logic?
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?


Remove del for email
 
R

Richard Heathfield

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

I'm not part of your inbred clique.

Neither am I. But I note that you avoid the question instead of
answering it.
 
A

Army1987

Barry Schwarz said:
I assume you meant out_name here. Yes...

Isn't the test backwards?
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.
Didn't you mean out_name throughout this overwrite logic?
Yes... (Luckily enough I didn't test it...)
 

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. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,579
Members
45,053
Latest member
BrodieSola

Latest Threads

Top