How to deal with bash shell expansions and fopen() - c

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.

Related

Print text to shell without advancing buffer

I would like to know if there is a way to print text into shell's current buffer/cursor so it can be edited. I am building a program that will store some text values in memory and need a simple way to edit them in the shell without rewriting the whole value. So somehow referencing the current edit buffer in shell and printing to it would be quite nice.
However, I am only using common sense here. Maybe it is more complicated. Looking forward to possible solutions.
Every shell handles user input differently. If there is an "edit buffer", it is most likely to be implemented in the shell itself. (Linux terminals do have a primitive line-editing function but as far as I know, there's no way to inject data into the line. However, only very primitive shells rely on native line-editing.)
So the question must be asked with respect to some specific shell.
Just in case, here's the answer for bash.
Bash relies on the readline library to perform command input from a terminal(-like) standard input. (Command-line history is provided using a related history library.) These libraries have a lot of features and bash does not provide access to all of them; if necessary, you could write a program in C or some scripting language with a readline binding.
But bash does give you the tools necessary to preload input with a line of text. The standard way of collecting input from a shell script is through the read command, which is (almost) always a shell "built-in". Basic ooeration of read is defined by the Posix standard, but the bash version provides a lot of useful extension options, including:
-e: use the readline library (with tab-completion, history and line editing enabked).
-i text: if readline is being used, preload readline's edit buffer with the specified text.
The
bash manual has lots more information about the read command's options.

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.

Determine which binary will run via execlp in advance

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.

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