4

I am working on implementing a Unix shell in C and I'm currently dealing with the problem of relative paths. Notably while inputting commands. For now I have to enter the full path of the executable every time, when I would much rather simply put "ls" or "cat".

I have managed to get the $PATH env variable. My idea is to split the variable at the ":" character, then append each new string to the command name and check if the file exists and is executable.

For example if my PATH is: "/bin:/usr/bin" and I input "ls", I would like the program to check first if "/bin/ls" exists and is executable, if not move on to "/usr/bin/".

Two questions:

1) Is it a good way to do it? (Doesn't it have to be necessarily the best. I just want to make sure that it would work.

2) More importantly, How can I check in C, if a file exists and is executable?

I hope I'm clear enough, and ... well thanks :)

6 Answers 6

10

Don't. Performing this check is wrong; it's inherently subject to a race condition. Instead, try executing it with the appropriate exec-family call. If it's not executable, you'll get an error.

Also note that you don't need to search the PATH yourself; execvp can do this for you.

Sign up to request clarification or add additional context in comments.

2 Comments

That's exactly right. Perhaps it might be useful to make this check as a form of pre-validation, but the reality is that nothing short of attempting to run it will tell you if it can be run.
the problem is if you are trying to detect one of two programs each of which might take 30 seconds to start up.
9

stat? Psh. Way more than you need.

Check out the access() syscall.

if (access(filename, F_OK|X_OK) == 0)
{
    /* You can execute this file. */
}

Note that any check for file access or existence of a file has an inherent race condition in it. You can't guarantee at your call to execve that someone didn't remove the executable bit, change the ownership of the file, delete the file, etc., in the time since your check happened. Keep this in mind when you write your code and decide how to handle error conditions.

5 Comments

+1 for reminding about the race condition. Solutions like this can't assume that nothing has changed between checking and acting on external (not within the program) resources.
No, X_OK isn't sufficient. You'd still have to call stat(2) to see if it is a file and then try to execute it to see if it was really okay. From the fine manual: "X_OK for execute/search permission" and even "Access() is a potential security hole and should never be used".
@mu is too short - It looks like I missed F_OK for file existence. But I'm confused about what X_OK does if not check the bit. Interesting. As for the "potential security hole" part, this is very vague. They could be referring to the race condition part.
F_OK still doesn't check if it is a directory, file, or what have you; F_OK is just a simple "is it there" check and X_OK is just a simple "does it have the execute bit set" check. F_OK|X_OK will return the same results for /bin and /bin/ls. The vague security warning probably is talking about the race condition.
Note that this is also true for directories.
4

Use the stat function: http://linux.die.net/man/2/stat

e.g.:

struct stat sb;
if(!stat(path, &sb))
{
  if(IS_REG(sb.st_mode) && sb.st_mode & 0111)
    printf("%s is executable\n", path);
}

Comments

4

Call stat on the full pathname, and if it returns 0 (success), the target exists. In that case you can use the st_mode field in the returned structure to test whether the target is a directory, device, named pipe, or ordinary file. If it's a file, st_mode will also contain the permission bits. (If it's a directory, some "executable" bits might be set, but that would imply "searchable" rather than "executable".)

Edit: As others have noted, this is subject to a race condition, and there are better ways of accomplishing what you want to do in this particular situation.

Comments

0

How are you creating the process??

Use execlp or execvp.

2 Comments

execvp did that for me. What is the difference with execlp?
The difference is revealed in the documentation! (And if you have google, you have documentation.) If you are doing this sort of thing, you really want to look at the documentation as a normal thing. You might even learn about something you didn't expect to learn.
0

The access() answer is the only sensible one. If you use stat, you'll have to parse /etc/group to find out all the groups you are in BESIDE getgid() and test them together with the group-executable bit.

1 Comment

Please check the comments on the "access" answer from @asveikau, it is not a correct check.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.