-2

I am trying to find the file(say marks.txt) in the particular path passed as argument to a function. Is it possible to give the filename and path as arguments to a function which checks if the file exists and prints out the path?

The below function only takes path as argument.

int fileexists(const char *path){                                 
    File *ptr = fopen(path, "r");                                             
    if (fptr == NULL)                                                                                                                                                                                                      
        return 0;
    fclose(fptr);
    return 1;
}        

The required function prototype :

int fileexists(const char *path, const char *filename)
Jabberwocky
  • 48,281
  • 17
  • 65
  • 115
  • 1
    How would the prototype of that function look? Something like `int fileexists(const char *path, const char *filename)`? What are you _actually_ trying to achieve? Read this: [XY Problem](http://xyproblem.info/). Please [edit] your question and clarify. – Jabberwocky Sep 19 '19 at 13:56
  • 2
    Yes, with things like `access()` or `faccessat()` or `stat()`, but to avoid potential race conditions, if you're actually doing something with the file, best to just try to open it and see if that succeeds or not. – Shawn Sep 19 '19 at 13:57
  • @Jabberwocky Yes the function prototype is same as what you mentioned – BhArath sridhar Sep 19 '19 at 14:01
  • Then you should but that information into the question. – Jabberwocky Sep 19 '19 at 14:02
  • What will your program do if `fileexists` returns 1? BTW your question title doesn't match the question. – Jabberwocky Sep 19 '19 at 14:08
  • @Jabberwocky I am still new to the working of stackoverflow. Will try to be more accurate. Thanks – BhArath sridhar Sep 19 '19 at 14:14
  • @BhArathsridhar you can [edit] your question any time in order to improve it. – Jabberwocky Sep 19 '19 at 14:20
  • To the general question of "How can I determine if a file exists?", there are better answers at [What's the best way to check if a file exists in C?](https://stackoverflow.com/questions/230062/whats-the-best-way-to-check-if-a-file-exists-in-c) – Steve Summit Sep 19 '19 at 14:38

3 Answers3

2

The function you have checks if the file can be opened, but it will fail for some files that exist but you have no rights to open. I'd use stat instead. To concatenate the path and filename you can use string functions.

The usual Unix C APIs are dismal. It takes lots of effort to do the simplest of things correctly - and even then I'm not sure that I didn't forget some Unix-ism like signal handling or some obscure error cases. I.e. stuff that's rather trivial to get right in modern C++.

I wish someone designed a modern C system API and implemented it for at least Linux, so that our suffering would end...

Usually, string concatenation requires some higher level API to be done while maintaining a modicum of sanity. Thus, the example below uses a strbuilder class to build the string. This makes things vaguely readable and avoids most common mistakes.

#include <assert.h>
#include <errno.h>
#include <limits.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

struct strbuilder {
    unsigned items, item;
    size_t length, *lengths;
    char *str, *dst;
};

bool strbuilder_pass(struct strbuilder *builder, int *rc);
void strcat_str(struct strbuilder *builder, const char *src);
void strcat_c_ifnone(struct strbuilder *builder, char c);
bool strbuilder_is_freed(const struct strbuilder *builder);

int fileExists(const char *path, const char *filename)
{
    const char pathSep = '/';
    int rc;
    struct strbuilder bld = {0};
    while (strbuilder_pass(&bld, &rc))
    {
        strcat_str(&bld, path);
        strcat_c_ifnone(&bld, pathSep);
        strcat_str(&bld, filename);
        if (!rc)
        {
            struct stat statbuf;
            printf("path = %s\n", bld.str);
            rc = stat(bld.str, &statbuf);
        }
    }
    assert(strbuilder_is_freed(&bld));
    return rc;
}

int main()
{
    int rc = fileExists("/", "dev");
    assert(rc == 0);
    return 0;
}

The string building is controlled by a strbuilder_pass function, which advances the string builder's state through five passes of operation:

  1. Determine the number of items whose width has to be stored (avoids the need to call strlen twice).
  2. Prepare the length storage vector. Determine the length of the buffer needed.
  3. Prepare the output string buffer. Concatenate the elements into the buffer.
  4. Use the output string buffer.
  5. Free the output string buffer.

This API is not particularly special, but fits this use case. Some other ad-hoc approach would work too, but this is IMHO a bit more elegant.

void strbuilder_free(struct strbuilder *builder)
{
    free(builder->lengths);
    free(builder->str);
    memset(builder, 0, sizeof(*builder));
}

bool strbuilder_pass(struct strbuilder *builder, int *rc)
{
    if (!builder->length) {// start of pass 1
        builder->length = 1; /*term*/
        *rc = EAGAIN;
        return true;
    }
    else if (!builder->lengths) // end of pass 1
    {
        builder->lengths = malloc(sizeof(*builder->lengths) * builder->items);
        if (builder->lengths)
            return true;
        *rc = ENOMEM;
    }
    else if (!builder->str) // end of pass 2
    {
        builder->dst = (builder->str = malloc(builder->length));
        builder->item = 0;
        builder->length = 0;
        if (builder->dst) {
            *builder->dst = '\0';
            return true;
        }
        *rc = ENOMEM;
    }
    else if (builder->dst) // end of pass 3
    {
        while (*builder->dst) { // include optional content
            builder->dst++; // skip
            builder->length++;
        }
        builder->dst = NULL;
        *rc = 0;
        return true;
    }
    else if (!builder->dst) // end of pass 4 (if any)
    {}
    else {
        *rc = EINVAL;
    }
    strbuilder_free(builder);
    return false;
}

void strcat_str(struct strbuilder *builder, const char *src)
{
    if (!src)
        return;
    if (!builder->lengths) // pass 1
        builder->items ++;
    else if (!builder->str) // pass 2
    {
        size_t len = strlen(src);
        builder->lengths[builder->item++] = len;
        builder->length += len;
    }
    else if (builder->dst) // pass 3
    {
        size_t len = builder->lengths[builder->item++];
        if (*builder->dst && (!len || *builder->dst != *src))
        {
            builder->dst++;
            builder->length++;
        }
        memcpy(builder->dst, src, len);
        builder->dst += len;
        builder->length += len;
        *builder->dst = '\0';
    }
}

void strcat_c_ifnone(struct strbuilder *builder, char c)
{
    if (!builder->lengths) {} // pass 1
    else if (!builder->str) // pass 2
    {
        if (c) builder->length ++;
    }
    else if (builder->dst) // pass 3
    {
        if (!builder->length || builder->dst[-1] != c)
            *(builder->dst) = c;
    }
}

bool strbuilder_is_freed(const struct strbuilder *builder)
{
    return !builder || (!builder->lengths && !builder->str);
}
Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313
2

There are two parts to this question, and the right answers to them depend on what you're trying to do.

  1. Concatenate a directory name and a file name to form a full path name.
  2. Determine whether a file (referred to by a full path name) exists or not.

Concatenating a directory name and a file name is straightforward. Your friendsstrcpy and strcat will do most of the work. There are a few minor details to be careful of: (a) You'll need a big enough buffer for the full pathname, and you'll need to decide whether to use a fixed-size array (perhaps of size MAX_PATH), or a malloc'ed buffer; (b) you might need to insert an explicit '/' character (and it usually doesn't hurt to stick one in even if the directory string already ends in one); (c) under Windows you might want to use '\\' instead of '/'.

And then determining whether a file named by a full pathname exists is already well answered over at What's the best way to check if a file exists in C?. The big question to ask here is, are you asking whether the file exists in preparation to doing something with the file? If so, you have a serious vulnerability if you check for the file's existence, but then before you do the other thing, something else happens to cause the file to appear or disappear. So rather than checking-and-then-doing, it's usually better to just try doing the other thing, and deal gracefully with any errors.

Steve Summit
  • 45,437
  • 7
  • 70
  • 103
1

You probably want something like this (no error checking for brevity):

...
#include <string.h>   // for str* functions
#include <unistd.h>   // for access
#include <stdlib.h>   // for malloc
...
int fileexists(const char *path, const char *filename)
{
    char *name= malloc(strlen(path) + strlen(filename) + 1);
    strcpy(name, path);
    strcat(name, filename);
    int retval = access(name, F_OK) == 0;
    free(name);
    return retval;
}

Call like this:

if (fileexists("/some/path/", "somefilename.txt")) ...
Jabberwocky
  • 48,281
  • 17
  • 65
  • 115
  • I'm quite sure after 2 decades of using Unix C APIs myself that the "no-error-checking-for-brevity"-cake is a lie. Error checking is where all the complexity is. And it's absurd how hard it's to get the most basic of things done right using existing C APIs. Giving a beginner that can't quite figure out how to check for a file's existence a solution with a disclaimer "no error checking for brevity" is - at least for my comfort - far too likely to propagate into production code somewhere, or give an impression that error checking may be an after-thought :'( This is the pedagogue in me speaking. – Kuba hasn't forgotten Monica Sep 19 '19 at 15:23