Determine which binary will run via execlp in advance - c

Edit #1
The "Possible duplicates" so far are not duplicates. They test for the existence of $FILE in $PATH, rather than providing the full path to the first valid result; and the top answer uses bash command line commands, not pure c.
Original Question
Of all the exec family functions, there are a few which do $PATH lookups rather than requiring an absolute path to the binary to execute.
From man exec:
The execlp(), execvp(), and execvpe() functions duplicate the actions
of the shell in searching for an executable file if the specified
filename does not contain a slash
(/) character. The file is sought in the colon-separated list of directory pathnames specified in the PATH environment variable. If
this variable isn't defined, the path
list defaults to the current directory followed by the list of directories returned by confstr(_CS_PATH). (This confstr(3)
call typically returns the value
"/bin:/usr/bin".)
Is there a simple, straightforward way, to test what the first "full path to execute" will evaluate to, without having to manually iterate through all the elements in the $PATH environment variable, and appending the binary name to the end of the path? I would like to use a "de facto standard" approach to estimating the binary to be run, rather than re-writing a task that has likely already been implemented several times over in the past.
I realize that this won't be a guarantee, since someone could potentially invalidate this check via a buggy script, TOCTOU attacks, etc. I just need a decent approximation for testing purposes.
Thank you.

Is there a simple, straightforward way, to test what the first "full path to execute" will evaluate to, without having to manually iterate through all the elements in the $PATH environment variable
No, you need to iterate thru $PATH (i.e. getenv("PATH") in C code). Some (non standard) libraries provide a way to do that, but it is really so simple that you should not bother. You could use strchr(3) to find the "next" occurrence of colon :, so coding that loop is really simple. As Jonathan Leffler commented, they are subtleties (e.g. permissions, hanging symbolic links, some other process adding some new executable to a directory mentionned in your $PATH) but most programs ignore them.
And what is really relevant is the PATH value before running execvp. In practice, it is the value of PATH when starting your program (because outside processes cannot change it). You just need to be sure that your program don't change PATH which is very likely (the corner case, and difficult one, would be some other thread -of the same process- changing the PATH environment variable with putenv(3) or setenv(3)).
In practice the PATH won't change (unless you have some code explicitly changing it). Even if you use proprietary libraries and don't have time to check their source code, you can expect PATH to stay the same in practice during execution of your process.
If you need some more precise thing, and assuming you use execp functions on program names which are compile time constants, or at least constant after your program initialization reading some configuration files, you could do what many shells are doing: "caching" the result of searching the PATH into some hash table, and using execve on that. Still, you cannot avoid the issue of some other process adding or removing files into directories mentioned in your PATH; but most programs don't care (and are written with the implicit hypothesis that this don't happen, or is notified to your program: look at the rehash builtin of zsh as an example).
But you always need to test against failure of exec (including execlp(3) & execve(2)) and fork functions. They could fail for many reasons, even if the PATH has not changed and directories and files mentioned in it have not been changed.

Related

How to deal with bash shell expansions and fopen()

I am writing a program to make copies of files in Linux. The program takes two arguments:
a char *source which is the path to the source file that needs copying
a char *dest which is the path to the destination where a copy of source will be made
I am using the fopen(3) function to open files.
So far i have noticed fopen() does not recognize common bash shell expansions such as:
~ for current users home directory
To handle this i could use switch/if statements and check if the source path (char *source) has any of the characters and act accordingly. This solution would mean i would have to construct a absolute path (a path from / aka the root directory example: /usr/dir2/books/maths.pdf/). Ideally i would like a solution that works on any path from any current working directory the user is in.
My question is: Is there a better way to handle this? How could i handle the paths in a portable and efficient manner?
So far i have noticed fopen() does not recognize common bash shell expansions
Indeed it does not. shell expansions such as tilde substitution and globbing are performed by the shell. If you want them to be performed by your program, too, then you need to implement that yourself. On the flip side, you do not need to worry about quoting characters that would otherwise be significant to the shell, because you don't get word splitting etc. except if and as you implement it.
Is there a better way to handle this? How could i handle the paths in a portable and efficient manner?
A pretty good way to handle it would be to let somebody else handle it. In particular, if the filenames in question are specified as command-line arguments, then expansions will be handled (or not) by the shell, under control of the user. In that case, users will not expect your program to perform additional expansions. If the file names come from another source (config file, file chooser) then it is reasonable to expect the full path or possibly a correct relative path to be given.
If you nevertheless want to handle bash-style expansion of a bare tilde in particular, then that's not so hard. It applies only to tildes appearing as the first character of a file name, and it expands those to the value of environment variable HOME. You can read environment variables with getenv(), and other than that you just need some relatively simple string manipulation.
More general tilde expansion, where ~username appearing at the beginning of a filename is expanded to the home directory of user username, would require you to consult the user (a.k.a. password) database. On a POSIX system, a natural way to do that would be via the getpwnam() function.
Note well that different shells exhibit differences in the expansions they perform and the conditions under which they perform them. You cannot emulate all shells at the same time, so leaving expansions an external concern is both the most portable and the most efficient option.
Consider using the POSIXwordexp() function (or maybe the POSIX
glob() instead) to convert an input string to list of files, based on bash expansion.
See:
man wordexp — https://man7.org/linux/man-pages/man3/wordexp.3.html
man glob — https://man7.org/linux/man-pages/man3/glob.3.html
Not very efficient, as it forks a shell to perform the expansion, but it does the job.

How can we check if a given input is a valid system command like "ls" or "cd" in c program?

I am writing a c program that takes system commands such as "ls" or "cd" as inputs.However user can give any type of commands out of which some are not commands.How can i find which command is valid and which is not?I am writing the code in Ubuntu.
Off the top of my head there are two ways to check if the input is a valid system command, without actually attempting to run it:
A long list of if()s and else if()s which strcmp() the input string with a hard-coded, predetermined list of valid commands - this may be relatively slow, both to write and to run, but with conditional-compilation with #ifdef, can be nearly perfectly portable (i.e, can be made to work with Windows, Linux, BSD et all from one codebase with enough hard work).
If you don't mind being restricted to a UNIX-like platform only, parse the $PATH variable, and search for executables with the same filename as the input string in the directories found from $PATH, and handle errors if no match is met.
You may wish to implement a hybrid of 2. and 1. by hard-coding some exceptions which may not be found in $PATH.
IMHO, however, I fail to see why you would want to do this; it seems puzzling to me.

fopen() function with a dynamic location in C

I just want to learn that how can I open a file with fopen() function from a dynamic location. I mean, for example it will be a system file and in another computer, this file can be in another location. So if I will set my location in my code not dynamically, my program will not work in another computer. So how Can I set the location dynamically for my program will find this file wherever it is?
You can (and often should) pass program arguments to your main, thru the conventional int argc, char**argv formal arguments of your main. See also this.
(I am focusing on Linux, but you could adapt my answer to other OSes and platforms)
So you would use some convention to pass that file path (not a location, that word usually refers to memory addresses) to your program (often thru the command line starting your program). See also this answer.
You could use (at least on Linux) getopt_long(3) to parse program arguments. But there are other ways, and you can process the arguments of main explicitly.
You could also use some environment variable to pass that information. You'll query it with getenv(3). Read also environ(7).
Many programs have configuration files (whose path is wired into the program but often can be given by program arguments or by environment variables) and are parsing them to find relevant file paths.
And you could even consider some other inter-process communication to pass a file path to your program. After all, a file path is just some string (with restrictions and interpretations explained in path_resolution(7)). There are many ways to pass some data to a program.
Read also about globbing, notably glob(7). On Unix, the shell is expanding the program arguments. You may want to use functions like glob(3) or wordexp(3) on something obtained elsewhere (e.g. in some configuration file) to get similar expansion.
BTW, be sure, when using fopen, to check against its failure. You'll probably use perror like here.
Look also into the source code of several free software projects (perhaps on github) for inspiration.
I would suggest you to use the environment variables, In a PC set your file location as environment variable. then read the environment variable value in your program, then open the file. This idea works both in linux and windows however you have adopt the code based on the OS to read the environment variables.
Besides specifying file location at runtime through command line arguments, environment variables or configuration files, you can implement a PATH-like logic:
Possible locations for your file are set in an environment variable:
export MY_FILE_PATH=/usr/bin:/bin:/opt/bin:$HOME/bin
Your program reads that environment variable, parses its contents and checks existence of file in each specified path, with fopen() return status.

setenv() to update PATH environment variable

i want to write a C program to append a string to PATH Environment variable.
something like "export PATH=$PATH:$HOME/mylib"
i have C code like this
setenv("PATH", "$PATH:$HOME/mylib",1); //which does not work.
other work arround i thought was get PATH and HOME using getenv() and create a memory in heap then append them using strcat().
I might have to update PATH many times in my code: so this is a tiresome process.
is there any alternative?
Thanks
The $FOO syntax, which expands to the value of the environment variable with the name FOO, is a feature of the shell; it's not directly available in C.
Your system may provide the wordexp() function, which gives you similar functionality in C.
But since you're just expanding two environment variables with fixed names ("HOME" and "PATH"), it makes more sense to use the portable getenv() function and a little string processing. (You might consider using sprintf or snprintf rather than strcat.)
NOTE: If you're only using the updated $PATH internally in your program, you can stop reading here.
Hopefully you're not expecting any changes to $PATH to be available on the command line after your program finishes running. Your running C program is most likely a child process of your interactive shell. Environment variables are inherited by child processes; they don't propagate back to parent proceses.
If that's what you're trying to do, you can have your program print the new PATH value to stdout, and then have the shell evaluate it:
PATH=`your-program`
Or it can print the command(s) to set one or more environment variables:
eval `your-program`
(In bash, you can use $(your-program) as well as `your-program`.)
No there is no alternative. You have to build the literal string and pass it to setenv.
There is no other option immediately available. You can write a separate function to handle this, if you need to do it several times.

POSIX function to search PATH for an executable?

Is there a POSIX function that searches PATH for an executable according to the POSIX spec's description of the PATH environment variable and returns the absolute path to the executable?
If not, is there a simple, safe, standard, and reliable way to search PATH?
Edit:
glibc's execvpe() function does its own PATH search, so I'm guessing there isn't a specific PATH search function defined by the standard.
Edit 2: I don't want to copy someone else's code or implement the PATH search myself for a few reasons:
DRY
More code I have to test and maintain
Possible licensing issues
POSIX says, "If PATH is unset or is set to null, the path search is implementation-defined." I would like the behavior in these cases to be consistent with whatever the system does, but I can't do this if there's not a standard function I can call.
Is there a POSIX function that searches PATH for an executable according to the POSIX spec's description of the PATH environment variable and returns the absolute path to the executable?
No.
If not, is there a simple, safe, standard, and reliable way to search PATH?
Yes and no. Yes, there is a standard for the format of PATH, from which the correctness/reliability of implementations follow.
No, there is no standard function that does this. Copying code is your best bet.
If PATH is unset or is set to null, the path search is implementation-defined.
That means you can't always portably replicate what execvp does, but searching /bin:/usr/bin is a pretty safe bet. Alternatively, just raise an error in this case.
(I admit that it would have been nice if POSIX had had this function, but it just isn't there.)
The command line tool which will do that. here's the man page
and the source
What about doing something like:
FILE *f = popen("command -v somecommand", "r")
and then read its output? This would result in behavior that matches the system's handling of empty/unset PATH, and it may be simpler than manually searching PATH.
There are some drawbacks to this approach:
If somecommand comes from the user, it may have to be sanitized to prevent code injection attacks. That would add to the complexity.
Reliably reading the stream while handling all possible error cases is not trivial. More complexity.
If somecommand is a shell special built-in (e.g., set), it'll return bogus results. This case should be detectable, but then what? More complexity.

Resources