tags:

views:

670

answers:

7

I've been trying to get this code to work for hours! All I need to do is open a file to see if it is real and readable. I'm new to C so I'm sure there is something stupid I'm missing. Here is the code (shorthand, but copied):

#include <stdio.h>

main() {
    char fpath[200];
    char file = "/test/file.this";
    sprintf(fpath,"~cs4352/projects/proj0%s",file);

    FILE *fp = fopen(fpath,"r");
    if(fp==NULL) {
        printf("There is no file on the server");
        exit(1);
    }
    fclose(fp);
    //do more stuff
}

I have also verified that the path is correctly specifying a real file that I have read permissions to. Any other ideas?

Edit 1: I do know that the fpath ends up as "~cs4352/projects/proj0/test/file.this"

Edit 2: I have also tried the using the absolute file path. In both cases, I can verify that the paths are properly built via ls.

Edit 3: There errno is 2... I'm currently trying to track what that means in google.

Edit 4: Ok, errno of 2 is "There is no such file or directory". I am getting this when the reference path in fopen is "/home/courses1/cs4352/projects/proj0/index.html" which I verified does exist and I have read rights to it. As for the C code listed below, there may be a few semantic/newbie errors in it, but gcc does not give me any compile time warnings, and the code works exactly as it should except that it says that it keeps spitting errno of 2. In other words, I know that all the strings/char array are working properly, but the only thing that could be an issue is the fopen() call.

Solution: Ok, the access() procedure is what helped me the most (and what i am still using as it is less code, not to mention the more elegant way of doing it). The problem actually came from something that I didn't explain to you all (because I didn't see it until I used access()). To derrive the file, I was splitting strings using strtok() and was only splitting on " \n", but because this is a UNIX system, I needed to add "\r" to it as well. Once I fixed that, everything fell into place, and I'm sure that the fopen() function would work as well, but I have not tested it.

Thank you all for your helpful suggestions, and especially to Paul Beckingham for finding this wonderful solution.

Cheers!

A: 

char file = "/test/file.this";

You probably want

char *file = "/test/file.this";

Joe
A: 

Are you sure you do not mean

~/cs4352/projects/proj0%s"

for your home directory?

gbrandt
+1  A: 

You could try examining errno for more information on why you're not getting a valid FILE*.

BTW-- in unix the global value errno is set by some library and system calls when they need to return more information than just "it didn't work". It is only guaranteed to be good immediately after the relevant call.

dmckee
+5  A: 

First, file needs to be declared as char* or const char*, not simply char as you've written. But this might just be a typo, the compiler should at least give a warning there.

Secondly, use an absolute path (or a path relative to the current directory), not shell syntax with ~. The substitution of ~cs4352 by the respective home directory is usually done by the shell, but you are directly opening the file. So you are trying to open a file in a ~cs4352 subdirectory of your current working directory, which I guess is not what you want.

sth
+6  A: 
  1. The "~" is expanded by the shell, and is not expanded by fopen.
  2. To test the existence and readability of a file, consider using the POSIX.1 "access" function:
#include <unistd.h>

if (access ("/path/to/file", F_OK | R_OK) == 0)
{
  // file exists and is readable
}
Paul Beckingham
Thank you so much for giving me a POSIX solution!!! This worked beautifuly.Have a great day!
Mike
A: 

To sum up:

  1. Use char *file=/test/file.this";
  2. Don't expect fopen() to do shell substitution on ~ because it won't. Use the full path or use a relative path and make sure the current directory is approrpriate.
  3. error 2 means the file wasn't found. It wasn't found because of item #2 on this list.

For extra credit, using sprintf() like this to write into a buffer that's allocated on the stack is a dangerous habit. Look up and use snprintf(), at the very least.

As someone else here mentioned, using access() would be a better way to do what you're attempting here.

Ori Pessach
+2  A: 

Other people have probably produced the equivalent (every modern shell, for example), but here's some code that will expand a filename with ~ or ~user notation.

#if __STDC_VERSION__ >= 199901L
#define _XOPEN_SOURCE 600
#else
#define _XOPEN_SOURCE 500
#endif

#include <assert.h>
#include <limits.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

char *relfname(const char *name, char *buffer, size_t bufsiz)
{
    assert(name != 0 && buffer != 0 && bufsiz != 0);
    if (name[0] != '~')
        strncpy(buffer, name, bufsiz);
    else
    {
        const char *copy;
        struct passwd *usr = 0;
        if (name[1] == '/' || name[1] == '\0')
        {
            usr = getpwuid(getuid());
            copy = &name[1];
        }
        else
        {
            char username[PATH_MAX];
            copy = strchr(name, '/');
            if (copy == 0)
                copy = name + strlen(name);
            strncpy(username, &name[1], copy - &name[1]);
            username[copy - &name[1]] = '\0';
            usr = getpwnam(username);
        }
        if (usr == 0)
            return(0);
        snprintf(buffer, bufsiz, "%s%s", usr->pw_dir, copy);
    }
    buffer[bufsiz-1] = '\0';
    return buffer;
}

#ifdef TEST
static struct { const char *name; int result; } files[] =
{
    { "/etc/passwd", 1 },
    { "~/.profile", 1 },
    { "~root/.profile", 1 },
    { "~nonexistent/.profile", 0 },
};

#define DIM(x)  (sizeof(x)/sizeof(*(x)))

int main(void)
{
    int i;
    int fail = 0;
    for (i = 0; i < DIM(files); i++)
    {
        char buffer[PATH_MAX];
        char *name = relfname(files[i].name, buffer, sizeof(buffer));
        if (name == 0 && files[i].result != 0)
        {
            fail++;
            printf("!! FAIL !! %s\n", files[i].name);
        }
        else if (name != 0 && files[i].result == 0)
        {
            fail++;
            printf("!! FAIL !! %s --> %s (unexpectedly)\n", files[i].name, name);
        }
        else if (name == 0)
            printf("** PASS ** %s (no match)\n", files[i].name);
        else
            printf("** PASS ** %s -> %s\n", files[i].name, name);
    }
    return((fail == 0) ? EXIT_SUCCESS : EXIT_FAILURE);
}

#endif
Jonathan Leffler
nice +1 for the effort (haven't tested that code but it looks clear :p). today i have heard about the "wordexp" posix function which does that and more (other expansions). maybe you don't know it yet and may find it useful too :)
Johannes Schaub - litb
I faintly remember that it exists; I've never used it - or the fnmatch() function. They're from what was POSIX.2 and tend to get forgotten.
Jonathan Leffler
And, FWIW, this code uses the definition of home directory from /etc/passwd (or the password database); wordexp() uses the current value of $HOME (which is usually, but not necessarily, the same thing). The change is trivial, of course.
Jonathan Leffler