4

I'm writing a UNIX minishell on ubuntu, and am trying to add built-in commands at this point. When it's not a built-in command I fork and then the child executes it, however for built-in commands I'll just execute it in the current process.

So, I need a way to see if the files exist(if they do it's not a built-in command), however execvp uses the environment PATH variable to automatically look for them, so I have no idea how I would manually check beforehand.

So, do you guys know how I could test an argument to see if it's a built-in command simply by supplying the name?

Thanks guys.

robins35
  • 653
  • 12
  • 37
  • 2
    If you know what your builtin commands are, why don't you check if the program is one by looking in your list of builtin commands? You manage those, right? Or is the real question how to [check for existance of a file in C?](http://stackoverflow.com/questions/230062/whats-the-best-way-to-check-if-a-file-exists-in-c-cross-platform) – nemo Oct 07 '12 at 00:18
  • This is a good point, it would make more sense this way. I'm keeping the built-in functions in another .c file, is there an easy way to check to see if the functions exist in the scope of the main file? – robins35 Oct 07 '12 at 00:23
  • 1
    I'm not sure what you're attempting. There *are* ways to find out which functions are defined in C but before you do that, you should ask yourself if it is right what you're doing there. IMO checking if the supplied command is built-in, shouldn't be more than a search in a list of strings or something similar. – nemo Oct 07 '12 at 00:27
  • 1
    Note that `test` is a shell built-in; it gets executed by the shell even if you write a test program called `test` if you invoke `test` without a pathname (but if you specify the pathname, then your `./test` program is used). This causes confusion when first programming on Unix. Similarly, if I create a command called `cd`, the shell ignores it and invokes its built-in when the name is just `cd` (but as before, if there's a path on the command name, such as `./cd`, then that is invoked by the shell). – Jonathan Leffler Oct 07 '12 at 01:20
  • I took a look at test, but I'm not quite sure what it is. You said in the other post that I shouldn't explicitly search for the existence of a certain function, but rather I should explicitly search for the existence of a file. I'm quite lost, I was going to just try to find a way to see if a function existed in the main scope, and if not then it's a non-built in. – robins35 Oct 07 '12 at 01:28

4 Answers4

3

I have tested the answer by Tom

It contained a number of problems. I have fixed them here and provided a test program.

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>

int is_file(const char* path) {
    struct stat buf;
    stat(path, &buf);
    return S_ISREG(buf.st_mode);
}

/*
 * returns non-zero if the file is a file in the system path, and executable
 */
int is_executable_in_path(char *name)
{
    char *path = getenv("PATH");
    char *item = NULL;
    int found  = 0;

    if (!path) 
        return 0;
    path = strdup(path);

    char real_path[4096]; // or PATH_MAX or something smarter
    for (item = strtok(path, ":"); (!found) && item; item = strtok(NULL, ":"))
    {
        sprintf(real_path, "%s/%s", item, name);
        // printf("Testing %s\n", real_path);
        if ( is_file(real_path) && !(
               access(real_path, F_OK) 
            || access(real_path, X_OK))) // check if the file exists and is executable
        {
            found = 1;
        }
    }

    free(path);
    return found;
}

int main()
{
    if (is_executable_in_path("."))
        puts(". is executable");
    if (is_executable_in_path("echo"))
        puts("echo is executable");
}

Notes

  1. the test for access return value was reversed
  2. the second strtok call had the wrong delimiter
  3. strtok changed the path argument. My sample uses a copy
  4. there was nothing to guarantee a proper path separator char in the concatenated real_path
  5. there was no check whether the matched file was actually a file (directories can be 'executable' too). This leads to strange things like . being recognized as an external binary
sehe
  • 374,641
  • 47
  • 450
  • 633
1

You can iterate yourself through the PATH directories, and for each entry in PATH (You will have to split PATH with :, probably using strtok) concatenate at the end of each path the name of the command called. When you have create this path, check if the file exists and if it is executable using access.

int is_built_in(char *path, char *name)
{
  char *item = strtok(path, ":");

  do {
    char real_path[4096] = strcat(item, name); // you would normally alloc exactly the size needed but lets stick to it for the sake of the example
    if (!access(real_path, F_OK) && !access(real_path, X_OK)) // check if the file exists and is executable
      return 0;
  } while ((item = strtok(NULL, ":")) != NULL);
  return 1;
}
tomahh
  • 13,441
  • 3
  • 49
  • 70
  • Thank you, I'm going to attempt to explicitly search for built-in functions, but if that doesn't work I'll do this. – robins35 Oct 07 '12 at 00:38
  • 3
    You can do that, but it is quicker and more reliable, in practice, to invoke `execve()` on each element — or even just let `execvp()` do the job — than it is to make the system check with `access`. All else apart, the results from `access()` do not necessarily take into account ACLs. Using `strtok()` on PATH is dangerous too; it mangles the path so you won't be able to reuse it on the next invocation. – Jonathan Leffler Oct 07 '12 at 01:23
1

What you can do is you can change the path to the particular directory and then use #include<dirent.h> header file and its readdir and scandir functions to walk through the directory or stat structure to see if the file exists in the directory or not.

Recker
  • 1,915
  • 25
  • 55
1

Why do you want to test before calling execvp? That's the wrong approach. Just call execvp and it will tell you if the program does not exist.

R.. GitHub STOP HELPING ICE
  • 208,859
  • 35
  • 376
  • 711