Including (linux) bash commands - c++

I'm currently working on creating a simple Linux shell. I have a Shell class, with a vector I am treating as a sort of queue. The user could input something like
$ ls -l /
and the program would put this into my vector through a private method, with "ls" being at the 0th position, "/" in the 2nd position, you get the idea. Then I go into my "interpretation" stage:
First I check to make sure the user has put something into the prompt.
Then it checks to see if the user typed in the word "exit", and if so it exits.
If neither of these checks go off, the program forks. It then checks to make sure the fork didn't fail.
This is where I'm stumbling. If the second to last character string in the queue is ">" we know that the last string is going to be a file that we need to create and/or open (and/or truncate?) and write the results of the command given by the user to said file.
I need to do this using the bash command open and using flags, I cannot use c++ openers like ofstream. I then need to execute the input using the bash command exec.
Below is how my code has this written as of the submission of this post.
if (commandQueue.size() >= 3 && commandQueue.at(commandQueue.size() - 2) == ">") {
//commandQueue.at(commandQueue.size() - 1) is fileName open
//commandQueue.at(0) is name of program to run exec
//remaining substrings are args
}; //commandQueue.at(0) is name of program to run exec
// remaining substrings are args
How should I format these commands? fork was easy, it was just
pid_t pid = fork();
and a check. But I'm unsure as to how I'm supposed to open a file using flags, or how to format the exec command. I appreciate any help I receive, thank you.
Edit: I should probably put the second part of the if statement I provided in an else statement, or else return from the if statement.
Edit 2: I should probably mention I'm pretty new to bash commands, I've used a few of them before, but this is the first program I've written using them inside.
Edit 3 (reply to Galik):
Like, if you were using execve you'd have something like
const char *path = "/bin/ls";
char *const argv[] = { "/bin/ls", "/", NULL };
execve(path, argv, environ);
(this is c code though, not c++) you see the "execve(path, argv, environ)"? That's what I'm curious about.
The man page for open has
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
but since I'm not using c, and I have my user's input stored as strings, I'm not sure how to deal with the const char*, or the flags or mode.

Related

Check/list all bash commands in C++?

Basically, is there a simple way to get a list of all bash commands in the PATH environment variable in C++? My current solution is to run a command beforehand that lists all the commands into a .txt, which is then read into the C++ program. I want to be able to cut out this step, if possible.
ls ${PATH//:/ } > commands.txt
If you do NOT need to use stdin in your C++ program
This is the easy solution. Just pipe the output of the ls command to your C++ program. Then, in your C++ program, read the contents of the file from stdin like you would read from a normal file. Literally use stdin wherever you need to provide a file descriptor. So, your command would look something like
ls ${PATH//:/ } | ./a.out
The | denotes a pipe in bash. It takes stdout from the first program (here ls) and redirects it to stdin of the second program (here your C++ program).
If you do need to use stdin in your C++ program
This is going to be tricky. You essentially need to make your C++ program do everything itself. The first way to this that comes to mind is
Read $PATH using getenv().
Parse $PATH by replacing all occurrences of : with (a blank space). This is easy enough to do in a loop, but you could also use std::replace.
Now that you have the directory paths from $PATH, you simply need the contents of each directory. This post will help you get the contents of a directory.
UPDATE: Another Approach
I've thought of another way to approach your problem that allows you to use IO redirection (ie. use the pipe), and also use stdin at the same time. The problem is that it is probably not portable.
The basic idea is that you read the output of ls from stdin (using the pipe operator in bash). Next, you essentially reset stdin using freopen. Something along the lines of
#include <stdio.h>
int main(void)
{
char buf[BUFSIZ];
puts("Reading from stdin...");
while(fgets(buf, sizeof(buf), stdin))
fputs(buf, stdout);
freopen("/dev/tty", "rw", stdin);
puts("Reading from stdin again...");
while(fgets(buf, sizeof(buf), stdin))
fputs(buf, stdout);
return 0;
}
The above code is from here. It reads stdin, resets stdin, and reads from stdin again. I would suggest not using this approach for anything important, or for something that needs to work on several platforms. While it is more convenient since it allows you to use IO redirection while retaining the ability to use stdin, it is not portable.

execve(...) does not execute program despite passing in PATH variable

I'm executing a simple shell program from the directory:
/home/user/shell.exe
Using the code below, I'm able to run files that are in the same folder as my shell executable, but am unable to run programs such as ls.exe.
The tokens container includes the file name as the first element and any subsequent tokens (such as "-l" in the input "ls.exe -l") in the following elements.
if (fork())
{
int status;
wait(&status);
}
else
{
std::vector<const char*> exeArgs;
std::vector<const char*> envArgs;
std::for_each(tokens.begin(), tokens.end(),
[&exeArgs](const string& elem){ exeArgs.push_back(elem.c_str()); }
);
exeArgs.push_back(nullptr);
string path = "PATH=";
path.append(getenv("PATH"));
envArgs.push_back(path.c_str());
envArgs.push_back(nullptr);
if (execve(exeArgs[0], const_cast<char *const *>(&exeArgs[0]),
const_cast<char *const *>(&envArgs[0])))
{
std::cout << word << ": command not found" << std::endl;
exit(0);
}
}
I've spent countless hours just googling and reading the man pages over and over but can't seem to get a clue why this code doesn't work.
The idea is that my shell program should allow users to set the PATH variable and then execute programs with that PATH variable, which is why I have to make execve() work properly instead of just using execvp().
I have a map of shell variables in a separate part of the file but since I can't even get this to work, I thought it would be pointless to include that.
You do know that the exec family of functions replaces the current process with the image of the new program? That's why it's so common to use fork before exec.
Armed with that knowledge, it's easy to find a solution for you, and how you can use execvp (which you need to use, execve doesn't really use the environment you pass, it just passes it along to the new program): You fork and use setenv to set the PATH of the new process, before calling execvp.

How to use standard input '<' in execl?

I want to achieve the equivalent of "./myfile < input.txt" using execl():
execl("path/myfile", ",myfile", "< input.txt");
execl("/home/user/Desktop/Fuzzer/clear/easy_fuzzer/buf", "/home/user/Desktop/Fuzzer/clear/easy_fuzzer/buf", "buf < input", NULL);
execlp("/home/user/Desktop/Fuzzer/clear/easy_fuzzer/buf", "/home/user/Desktop/Fuzzer/clear/easy_fuzzer/buf", "input");
but the command fails...
I want 'input.txt' and '<' command through myfile using execl — how do I do it?
Your code has to do the I/O redirection before you run the execl() code.
If you want to achieve the effect of a shell running:
/user/Desktop/Fuzzer/clear/easy_fuzzer/buf < input
then you will need to write something like this in the child code:
const char *filename = "input"; // or "input.txt" — the question uses both
int fd = open(file, O_RDONLY);
if (fd < 0)
err_syserr("failed to open file %s for reading\n", filename);
if (dup2(fd, STDIN_FILENO) < 0)
err_syserr("failed to redirect %s to standard input\n", filename);
close(fd); // In theory, it could fail, but there isn't much you can do about it
const char *cmdpath = "/home/user/Desktop/Fuzzer/clear/easy_fuzzer/buf";
execl(cmdpath, "buf", (char *)NULL);
err_syserr("failed to execute program %s\n", cmdpath);
This should normally all be in the code executed by the child.
You can find the code for the err_syserr() function in stderr.c and stderr.h from https://github.com/jleffler/soq/tree/master/src/libsoq. One line error handling makes it less onerous than writing out multiple lines. Note that there's no reason to check the return value from any of the exec*() functions. If the function returns, it failed. If it succeeds, there's a different process running in place of the current process.
If you like doing things the long-winded way, you can investigate whether your system supports posix_spawn() and its colleagues. You can do all sorts of things by setting up appropriate sequences of attributes. For my money, it is far simpler and clearer to write the code as shown above.
I would probably not use execl() — I'd probably use execv() (or perhaps execvp()) because it allows the argument list to be fixed at run-time instead of mandating that it is fixed at compile time. The code passes buf as the value for argv[0] to the executed program. If you want the full path name as argv[0] you can do that.
Note that if the file name part of the first argument to execlp() (or execvp(), or any other path-searching exec*() function) contains any / at all, then there is no path-based search performed, so it is not appropriate to use them if the command name is an absolute path name as in the example.

How do I add file paths as nodes to a tree or stack in C++

I have a project to search, rename or delete files and folders on a selected drive on the computer using a data structure(A tree, a stack, or a queue). My question is, how do I add file paths and directories as nodes in C++?
Comment in other answer suggests using one of the exec() functions. Then parsing and studying the output.
I approve of that idea, but I find it easier to use popen(). Each of the following examples are part of the Linux API, so the calls are c compatible and can be used directly by C++. I expect popen() will be available on other OS's.
To clarify,
1) popen() is a function call for your C++ code to invoke.
2) You will also need to create strings for your OS to generate the lists you want, and submit them to your invocation of popen(). The 1st parameter is the command string
3) in read mode, the output of your "ls -lsa " or "dir" command will be written into the output pipe of the spawned process, and your code will need to 'suck it in', I recommend capturing it to a std::stringstream.
4) after capture of the "dir -r" output, then parse and extract dir's and file names from the stringstream.
Examples of C++ access to popen:
FILE* m_pipe = nullptr; // popen return a FILE*
// use m_pipe to read from sub-process std::out
m_pipe = ::popen (m_cmd.c_str(), "r"); // read mode
// ^^ because popen is not in a namespace
m_pipe = ::popen(m_cmd.c_str(), "w"); // write to sub-process std::in
int32_t pcloseStat = ::pclose(m_pipe);
{
(void)memset(buff, 0, BUFF_SIZE);
// Reads characters from stream and stores them as a C string
// into buff until
// a) (BUFF_SIZE-1) characters have been read or
// b) a newline or
// c) the end-of-file is reached,
// whichever happens first.
char* stat = fgets (buff, BUFF_SIZE, m_pipe); // returns buff or null
int myErrno = errno; //^^^^^^ -- created by popen
}
Example of building a linux command for popen 1st parameter ...
std::string md5sumCmd ("head --bytes=1M " + mPFN +" | md5sum");
This command grabs the 1st 1Megabyte of file name in mPFN (a std::string), and pipes that output into md5sum ... essentially generating an md5sum of the 1s Meg of the file. The md5sum output is what will be received by the calling process.
You will need to create appropriate commands (to pass to popen) to show dir's and folder's and file names, etc.
What ever works from the command line should be fine, but some options might make parsing the output easier.
For your node based structures, add a string property that would serve as your file path. You might need to replace "\" with "/" in it however as the forward slash is often an escape character in most languages. For example in a queue:
class Node {
Node next;
char[50] path;
}
And you can create accessors and mutators the same way you would anything else in a class. This will allow you to assign it values and to read the values.
Folders could be used as a parent and the files are children. A tree structure would likely be the easiest way to do this.

Opening excel files with system( ) on mac?

I am trying to get my C++ program to open up an existing Excel spreadsheet (along with a bunch of applications), however it keeps returning an error that a file does not exist. I am using the following code:
int main(){
system("open ~/path/file");
//--open applications using same command--//
}
The file is definitely there and this command works to open all the applications, so I'm not sure what I am doing wrong.
Thanks in advance!!
Very probably, the system /bin/sh -which by definition is used by system(3)- does not expand ~.
You might try something like
char cmd[256];
snprintf(cmd, sizeof(cmd), "open %s/path/file", getenv("HOME"));
if (0 != system(cmd))
{ fprintf(stderr, "%s failed\n", cmd); exit(EXIT_FAILURE); };
since interactive shells usually expand ~ as $HOME and HOME is generally an environment variable.
(With C++, you could use std::string operations instead of snprintf)
My snprintf + system trick is not at all failproof. If $HOME contains spaces or bizarre characters like ; or ', it wont work. And snprintf itself might fail (e.g. because $HOME is huge).
Of course, you'll better test before that getenv("HOME") is not NULL. You might use getpwuid(3) with getuid(2) if getenv("HOME") fails by returning NULL.
On Linux you probably want xdg-open instead of open.