inotify inconsistent with devices - c++

On Linux, I am trying to detect a bluetooth controller being connected and start reading from it. I know there's SDL to do that, but I just wanted to learn how to do it specifically on Linux. So I'm using the inotify api to wait for the file /dev/input/js0 to show up. But when I detect the file I cannot open it. I have the following c++ code:
#include <iostream>
#include <fstream>
#include <sys/inotify.h>
#include <unistd.h>
#include <linux/joystick.h>
#include <string.h>
constexpr int NAME_MAX = 16;
int main(int argc, char **argv) {
std::string path = std::string(argv[1]);
std::string directory = path.substr(0, path.find_last_of("/"));
std::string file = path.substr(path.find_last_of("/") + 1);
std::cout << "Directory is " << directory << ", file is " << file << std::endl;
int fd = inotify_init();
if (inotify_add_watch(fd, directory.c_str(), IN_CREATE) < 0) {
std::cout << "Could not watch: " << file << std::endl;
return -1;
}
else
std::cout << "Watching: " << file << std::endl;
char buffer[sizeof(struct inotify_event) + NAME_MAX + 1];
while (true) {
if (read(fd, buffer, sizeof(buffer)) < 0) {
std::cout << "Error reading event" << std::endl;
break;
}
struct inotify_event &event = (struct inotify_event &) buffer;
std::cout << event.name << std::endl;
if ((strcmp(event.name, file.c_str()) == 0) && (event.mask & IN_CREATE)) {
std::cout << "File has been created" << std::endl;
close(fd);
break;
}
}
std::fstream file_stream(file, std::fstream::in);
std::cout << file_stream.is_open() << std::endl;
}
If I run it to detect a regular file, it works, it waits for the file creation event, and when trying to open it with a std::fstream, is_open returns true. But if I run it to detect /dev/input/js0, even when the event comes and the file is detected, opening the fstream does not work, as is_open returns false. Is inotify appropriate to detect device files? If not, what would be the right way to do so?

According to inotify(7)
Inotify reports only events that a user-space program triggers
through the filesystem API. As a result, it does not catch
remote events that occur on network filesystems. (Applications
must fall back to polling the filesystem to catch such events.)
Furthermore, various pseudo-filesystems such as /proc, /sys, and
/dev/pts are not monitorable with inotify.
I would say that /dev/input/ also falls into this bucket.
I wonder if udev could be used: you should get info about the device using udevinfo -a -p /dev/input/js0, but also see what events connecting the peripheral generates using udevadm monitor --environment --udev.
Edit: if you successfuly get an inotify event but can't read the file:
Did you try reading the file with another simpler program when the BT device is already connected?
Is there a difference between fstream::open and open from <cstdio>?
Have you checked the permissions on the device? Also what does cat /dev/input/js0 produces?

Related

writing a c++ linux program that should run as root

i am trying to write a linux program that uses the c++ mount function (code below),
however, the mount operation requires permmissions, and running the program throws the errno 'Operation not permitted' (printed using perror)
tried some SO solutions but non was helpful, the alternative is to use the system("sudo mount..") but i prefer the c++ function.
is ther a way to use this function with permmissions?
IDE: Clion 2020.2.4
relevant code below
int returnValue = mount(sourcePath,targetPath,"", MS_SHARED, ""); //mounting the device
if (returnValue==0){
//mount completed
//somecode
}else{
//mount failed
std::cout<<"mount failed\n";
perror("");
}
output
mount failed
Operation not permitted
After you compile the code, change the ownership of the file to the superuser with chown root filename and add "set user or group ID on execution" to the mode of the executable file with chmod u+s filename.
Some options I see:
Just run the binary as root or under sudo;
Use setcap cap_sys_admin+ep on your binary to grant it the CAP_SYS_ADMIN capability;
If the set of possible targetPaths is fixed, edit /etc/fstab to give these paths the userflag.
#include <fstream>
#include <iostream>
#include <string>
int main(int argc, char *argv[]){
std::ifstream tmpfile;
std::string tmpfile_name = ".mytempfile.tmp";
std::string command = "groups>";
std::string searchv[] = {"disk", "sudo", "root"};
int searchc = sizeof(searchv)/sizeof(searchv[0]);
int search_matches = 0;
char data_buffer[128];
if(!system(NULL)) goto ERROR;
command += tmpfile_name;
if(system(command.c_str()) != 0) goto ERROR;
std::cout << "executed external command: \"" << command << "\"" << std::endl;
tmpfile.open(tmpfile_name, std::ios::in);
if(!tmpfile.is_open()) goto ERROR;
std::cout << tmpfile_name << " opened" << std::endl;
do{
tmpfile >> data_buffer;
if(tmpfile.eof()) break;
if(tmpfile.fail()) goto ERROR;
for(int i = 0; i < searchc; i++){
if(!searchv[i].compare(data_buffer)){
search_matches++;
std::cout << "found group " << searchv[i] << std::endl;
}
}
}
while(tmpfile.good());
tmpfile.close();
std::cout << "found " << search_matches << " groups" << std::endl;
return EXIT_SUCCESS;
ERROR:
std::cerr << "something bad happened" << std::endl;
return EXIT_FAILURE;
}
This answer may be off-topic, sorry for that.
This program calls the external Linux program "groups" and searches for keywords "disk", "sudo", "root", which indicating user access rights for mounting a disk.
accessing an os function implies complying with that os's security model.
so short answer, no. you can't override security models in your user-run code

Calling external program using boost::process causes caller to hang (Linux)

I am using boost::process to call an external program - the external program reads input via stdin, and writes to stdout and stderr. The external program is as follows (expects a single argument - the path to a file for debugging)
#include <fstream>
#include <iostream>
#include <stdexcept>
#include <string>
#include <vector>
int main(int argc, char** argv)
{
try
{
if (argc != 2)
{
throw std::logic_error("Expected two arguments");
}
std::ofstream ofs(argv[1]);
std::vector<std::string> someTestInput;
ofs << "Starting program..." << std::endl;
// Read from cin
{
ofs << "Reading from cin..." << std::endl;
std::string input;
while (std::getline(std::cin, input))
{
ofs << "Received from cin: " << input << std::endl;
someTestInput.emplace_back(input);
}
ofs << "Finished receiving from cin..." << std::endl;
}
// Error if nothing has been input
if (someTestInput.empty())
{
throw std::logic_error("Expected some input and received nothing...");
}
ofs << "Writing to cout..." << std::endl;
// Write to cout
for (const auto& output : someTestInput)
{
std::cout << output << '\n';
}
ofs << "Finished!\n";
}
catch (std::exception& e)
{
std::cerr << "Error caught: " << e.what() << '\n';
return 1;
}
return 0;
}
The caller expects 2+ arguments, one of which is the path to the external program, and the rest are passed on as arguments to the external program.
It hangs while waiting for the process to exit, and it seems like the external program is waiting for an EOF from stdin.
#include <memory>
#include <vector>
#include <boost/iostreams/device/file_descriptor.hpp>
#include <boost/iostreams/stream.hpp>
#include <boost/process.hpp>
int main(int argc, char** argv)
{
try
{
if (argc < 2)
{
throw std::logic_error("Expecting at least 2 arguments...");
}
std::vector<std::string> args;
for (int i = 1; i < argc; ++i)
{
args.emplace_back(argv[i]);
}
std::cout << "Creating stdout, stderr pipes...\n";
// Create pipes for stdout, stderr
boost::process::pipe pstdout = boost::process::create_pipe();
boost::process::pipe pstderr = boost::process::create_pipe();
std::cout << "Mapping pipes to sources...\n";
// Map pipe source from stdout and stderr to sources
boost::iostreams::file_descriptor_source sourcestdout(pstdout.source, boost::iostreams::close_handle);
boost::iostreams::file_descriptor_source sourcestderr(pstderr.source, boost::iostreams::close_handle);
std::cout << "Setting up streams for the sources...\n";
// And set up streams for the sources
boost::iostreams::stream<boost::iostreams::file_descriptor_source> istdout(sourcestdout);
boost::iostreams::stream<boost::iostreams::file_descriptor_source> istderr(sourcestderr);
std::unique_ptr<boost::process::child> p;
// Want to check for process result, but also need to ensure stdin handle is closed properly,
// so place everything in separate scope
{
std::cout << "Mapping pipes to sinks...\n";
// Map pipe sink from stdout and stderr to sinks
boost::iostreams::file_descriptor_sink sinkstdout(pstdout.sink, boost::iostreams::close_handle);
boost::iostreams::file_descriptor_sink sinkstderr(pstderr.sink, boost::iostreams::close_handle);
std::cout << "Creating stdin pipe, mapping to source and sink...\n";
boost::process::pipe pstdin = boost::process::create_pipe();
// For stdin, map pipe to source and sink as before - want it to close on exiting this scope
boost::iostreams::file_descriptor_sink sinkstdin(pstdin.sink, boost::iostreams::close_handle);
boost::iostreams::file_descriptor_source sourcestdin(pstdin.source, boost::iostreams::close_handle);
boost::iostreams::stream<boost::iostreams::file_descriptor_sink> ostdin(sinkstdin);
std::cout << "Calling process... \n";
// Call process
p = std::unique_ptr<boost::process::child>(new boost::process::child(boost::process::execute(
boost::process::initializers::set_args(args),
boost::process::initializers::throw_on_error(),
boost::process::initializers::bind_stdout(sinkstdout),
boost::process::initializers::bind_stderr(sinkstderr),
boost::process::initializers::bind_stdin(sourcestdin)
)));
std::cout << "Sending test data...\n";
// Send some test data to cin - comment out the below to test for error case
ostdin << "Test Input 1\n";
ostdin << "Some\n";
ostdin << "Useful\n";
ostdin << "Data\n";
std::cout << "Test data sent, exiting scope...\n";
}
std::cout << "Check if process has exited...\n";
// Check if process has exited OK - if not, report errors
if (boost::process::wait_for_exit(*p))
{
std::cout << "Has not exited OK, reporting problems...\n";
// Gather output from stderr
std::string error;
while (std::getline(istderr, error))
{
std::cout << "Error: " << error << '\n';
}
throw std::logic_error("Problem executing TestProgram...");
}
std::cout << "Exited OK, here is output from the callee...\n";
// Gather the output
std::string output;
while (std::getline(istdout, output))
{
std::cout << output << '\n';
}
}
catch (std::exception& e)
{
std::cerr << "Error: " << e.what() << '\n';
return 1;
}
}
I was under the impression that placing my stdin pipe and related sources/sinks within a scope will guarantee they're closed, and therefore send the EOF.
The same code works perfectly under Windows (VS2013, boost_1_53).
I am using boost_1_53, boost-process 0.5, gcc 4.8.2.
That does not happen, because there's still a pipe handle open in the child process; that is only closed on posix if you set it explicitly (on windows it is done automatically). So you'd need to add something like that:
#if defined (BOOST_POSIX_API)
fcntl(pstdout.sink, F_SETFD, FD_CLOEXEC);
fcntl(pstderr.sink, F_SETFD, FD_CLOEXEC);
#endif
I would however recommend to use boost.asio and wait asynchronously for the exit of the subprocess and close the pipes there.
Just FYI: I've worked on boost-process 0.6 which has a different interface but makes the asio stuff much easier. This will hopefully be in review in October/November, so it might become an official boost library soon. It's currently in beta so you might want to check that one out.

How to convert from popen() to fork() and not duplicate process memory?

I have a multi-threaded C++03 application that presently uses popen() to invoke itself (same binary) and ssh (different binary) again in a new process and reads the output, however, when porting to Android NDK this is posing some issues such as not not having permissions to access ssh, so I'm linking in Dropbear ssh to my application to try and avoid that issue. Further, my current popen solution requires that stdout and stderr be merged together into a single FD which is a bit messy and I'd like to stop doing that.
I would think the pipe code could be simplified by using fork() instead but wonder how to drop all of the parent's stack/memory which is not needed in the child of the fork? Here is a snippet of the old working code:
#include <iostream>
#include <stdio.h>
#include <string>
#include <errno.h>
using std::endl;
using std::cerr;
using std::cout;
using std::string;
void
doPipe()
{
// Redirect stderr to stdout with '2>&1' so that we see any error messages
// in the pipe output.
const string selfCmd = "/path/to/self/binary arg1 arg2 arg3 2>&1";
FILE *fPtr = ::popen(selfCmd.c_str(), "r");
const int bufSize = 4096;
char buf[bufSize + 1];
if (fPtr == NULL) {
cerr << "Failed attempt to popen '" << selfCmd << "'." << endl;
} else {
cout << "Result of: '" << selfCmd << "':\n";
while (true) {
if (::fgets(buf, bufSize, fPtr) == NULL) {
if (!::feof(fPtr)) {
cerr << "Failed attempt to fgets '" << selfCmd << "'." << endl;
}
break;
} else {
cout << buf;
}
}
if (pclose(fPtr) == -1) {
if (errno != 10) {
cerr << "Failed attempt to pclose '" << selfCmd << "'." << endl;
}
}
cout << "\n";
}
}
So far, this is loosely what I have done to convert to fork(), but fork needlessly duplicates the entire parent process memory space. Further, it does not quite work, because the parent never sees EOF on the outFD it is reading from the pipe(). Where else do I need to close the FDs for this to work? How can I do something like execlp() without supplying a binary path (not easily available on Android) but instead start over with the same binary and a blank image with new args?
#include <iostream>
#include <stdio.h>
#include <string>
#include <errno.h>
using std::endl;
using std::cerr;
using std::cout;
using std::string;
int
selfAction(int argc, char *argv[], int &outFD, int &errFD)
{
pid_t childPid; // Process id used for current process.
// fd[0] is the read end of the pipe and fd[1] is the write end of the pipe.
int fd[2]; // Pipe for normal communication between parent/child.
int fdErr[2]; // Pipe for error communication between parent/child.
// Create a pipe for IPC between child and parent.
const int pipeResult = pipe(fd);
if (pipeResult) {
cerr << "selfAction normal pipe failed: " << errno << ".\n";
return -1;
}
const int errorPipeResult = pipe(fdErr);
if (errorPipeResult) {
cerr << "selfAction error pipe failed: " << errno << ".\n";
return -1;
}
// Fork - error.
if ((childPid = fork()) < 0) {
cerr << "selfAction fork failed: " << errno << ".\n";
return -1;
} else if (childPid == 0) { // Fork -> child.
// Close read end of pipe.
::close(fd[0]);
::close(fdErr[0]);
// Close stdout and set fd[1] to it, this way any stdout of the child is
// piped to the parent.
::dup2(fd[1], STDOUT_FILENO);
::dup2(fdErr[1], STDERR_FILENO);
// Close write end of pipe.
::close(fd[1]);
::close(fdErr[1]);
// Exit child process.
exit(main(argc, argv));
} else { // Fork -> parent.
// Close write end of pipe.
::close(fd[1]);
::close(fdErr[1]);
// Provide fd's to our caller for stdout and stderr:
outFD = fd[0];
errFD = fdErr[0];
return 0;
}
}
void
doFork()
{
int argc = 4;
char *argv[4] = { "/path/to/self/binary", "arg1", "arg2", "arg3" };
int outFD = -1;
int errFD = -1;
int result = selfAction(argc, argv, outFD, errFD);
if (result) {
cerr << "Failed to execute selfAction." << endl;
return;
}
FILE *outFile = fdopen(outFD, "r");
FILE *errFile = fdopen(errFD, "r");
const int bufSize = 4096;
char buf[bufSize + 1];
if (outFile == NULL) {
cerr << "Failed attempt to open fork file." << endl;
return;
} else {
cout << "Result:\n";
while (true) {
if (::fgets(buf, bufSize, outFile) == NULL) {
if (!::feof(outFile)) {
cerr << "Failed attempt to fgets." << endl;
}
break;
} else {
cout << buf;
}
}
if (::close(outFD) == -1) {
if (errno != 10) {
cerr << "Failed attempt to close." << endl;
}
}
cout << "\n";
}
if (errFile == NULL) {
cerr << "Failed attempt to open fork file err." << endl;
return;
} else {
cerr << "Error result:\n";
while (true) {
if (::fgets(buf, bufSize, errFile) == NULL) {
if (!::feof(errFile)) {
cerr << "Failed attempt to fgets err." << endl;
}
break;
} else {
cerr << buf;
}
}
if (::close(errFD) == -1) {
if (errno != 10) {
cerr << "Failed attempt to close err." << endl;
}
}
cerr << "\n";
}
}
There are two kinds of child processes created in this fashion with different tasks in my application:
SSH to another machine and invoke a server that will communicate back to the parent that is acting as a client.
Compute a signature, delta, or merge file using rsync.
First of all, popen is a very thin wrapper on top of fork() followed by exec() [and some call to pipe and dup and so on to manage the ends of a pipe] .
Second, the memory is only duplicated in form of "copy-on-write" memory - meaning that unless one of the processes writes to some page, the actual physical memory is shared between the two processes.
It does mean, of course, the OS has to create a memory map with 4-8 bytes per 4KB [in typical cases] (probably plus some internal OS data to track how many copies there are of that page and stuff - but as long as the page remains the same one as the parent process, the child page uses the parent processes internal data). Compared to everything else involved in creating a new process and loading an executable file into the new process, it's a pretty small part of the time. Since you are almost immediately doing exec, not much of the parent process' memory will be touched, so very little will happen there.
My advice would be that if popen works, keep using popen. If popen doesn't quite do what you want for some reason, then use fork + exec - but make sure you know what the reason for doing so is.

Process Start Limitation

I have create a simple application which using C++ and produce a executable file.
I would like to ensure the process cannot be start twice. How to enforce a process/service is start once only ?
Thanks.
What operating system? On Windows, the typical way to do this is to create a named Mutex, because the OS will give you an ERROR_ALREADY_EXISTS if some other process already created a mutex with that name, and the OS will ensure that the mutex is released when the process ends (even if it crashes or is killed).
Write a temporary file and use it as a lock.
Edit: To answer the comment: If you are on a Unix system, write a file /tmp/my_application_lock_file. If it already exists, stop your program with an appropriate message. On exit of the creator of the file, delete it.
#include <sys/stat.h>
#include <errno.h>
#include <unistd.h>
#include <iostream>
#include <fstream>
int main (void)
{
struct stat file_info;
// test for lock file
if (stat("/tmp/my_application_lock", &file_info) == 0) {
std::cout << "My application is already running, will abort now..." << std::endl;
return -1;
} else {
// create lock file
std::ofstream out;
out.open("/tmp/my_application_lock");
if (!out) {
std::cout << "Could not create lock file!" << std::endl;
return -1;
}
out << "locked" << std::endl;
out.close();
// do some work
std::string s;
std::cin >> s;
// remove lock file
errno = 0;
if (unlink("/tmp/my_application_lock"))
std::cout << "Error: " << strerror(errno) << std::endl;
}
return 0;
}

How to (legitimately) access files after putting self into chrooted sandbox?

Changing a Linux C++ program which gives the user limited file access. Thus the program chroots itself to a sandbox with the files the user can get at. All worked well.
Now, however, the program needs to access some files for its own needs (not the user's) but they are outside the sandbox. I know chroot allows access to files opened before the chroot but in this case the needed files could a few among many hundreds so it is obviously impractical to open them all just for the couple that might be required.
Is there any way to get at the files?
Copy them into the sandbox or open them all before chrooting. Seriously. If there was a way to do this, there would be a way to suborn it to allow other access and make your protection useless.
The whole point of the sandbox is to prevent exactly what you're trying to achieve.
If the files are all in 1 directory, you could use mount to bind them to a directory inside the sandbox.
mount --bind /path/to/files /sandbox/files
The you can access the files through /sandbox/files/. If you don't want the user to see them, do mount --bind /path/to/files /sandbox/.files so the .files directory is hidden
I guess that you ought to be able to split your program into two parts, one which is chroot'ed and one which isn't, and have the chroot'ed portion request files' contents from the non-chroot'ed portion via the IPC mechanism of your choice.
This is a hack, and it may be easy to get wrong, negating any benefit of a chroot. Like paxdiablo says, you're trying to get around the whole purpose of a chroot sandbox, so your options are very, very limited.
Maybe if you explained a bit more what you're trying to accomplish, we might be able to offer some other ideas. For example, SELinux and AppArmor are more flexible than chroot and may be able to give you the security you seek.
If the files you need to access are within a few directories you could open those directories before you chroot and save the file descriptors. You can then use the so-called *at functions (e.g. openat(), renameat(), etc.) to get at the individual files. Basically you are opening the files relative to the already open directory file descriptors rather than the chrooted directory.
Whether this is a safe thing to do is open to question but it should work in Linux.
EDIT: This is on the ugly side but it seems to work. You should poke around a lot more for vulnerabilities than I have. I haven't tested how dropping privileges and so forth will effect things.
#include <iostream>
#include <string>
using namespace std;
#include <cstdio>
#include <cstdlib>
#include <cerrno>
#include <cstring>
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/stat.h>
int main(int argc, char *argv[])
{
if (argc < 4)
{
cerr << "USAGE: " << argv[0] << " <jail directory> <freeworld directory> <filename>\n";
exit(EXIT_FAILURE);
}
const string JAILDIR(argv[1]);
const string FREEDIR(argv[2]);
string freefilename(argv[3]);
while (freefilename[0] == '/')
freefilename.erase(0, 1);
DIR *pDir;
if ((pDir = opendir(FREEDIR.c_str())) == NULL)
{
perror("Could not open outside dir");
exit(EXIT_FAILURE);
}
int freeFD = dirfd(pDir);
//cd to jail dir
if (chdir(JAILDIR.c_str()) == -1)
{
perror("cd before chroot");
exit(EXIT_FAILURE);
}
//lock in jail
if (chroot(JAILDIR.c_str()) < 0)
{
cerr << "Failed to chroot to " << JAILDIR << " - " << strerror(errno) << endl;
exit(EXIT_FAILURE);
}
//
//in jail, won't work
//
string JailFile(FREEDIR);
JailFile += "/";
JailFile += freefilename;
int jailFD;
if ((jailFD = open(JailFile.c_str(), O_RDONLY)) == -1)
{
cout << "as expected, could not open " << JailFile << endl;
perror("exected open fail");
}
else
{
cout << "defying all logic, opened " << JailFile << endl;
exit(EXIT_FAILURE);
}
//
//using this works
//
if ((jailFD = openat(freeFD, freefilename.c_str(), O_RDONLY)) == -1)
{
cout << "example did not work. Could not open " << freefilename << " Sorry!" << endl;
exit(EXIT_FAILURE);
}
else
cout << "opened " << freefilename << " from inside jail" << endl;
char buff[255];
ssize_t numread;
while (1)
{
if ((numread = read(jailFD, buff, sizeof(buff) - 1)) == -1)
{
perror("read");
exit(EXIT_FAILURE);
}
if (numread == 0)
break;
buff[numread] = '\0';
cout << buff << endl;
}
return 0;
}
To test:
echo "Hello World" >/tmp/mystuff.dat
mkdir /tmp/jail
sudo ./myprog /tmp/jail /tmp mystuff.dat