"Not a directory" when writing kernel configurations - c++

I am trying to toggle IPv6 on Linux systems using this function in Qt. The problem is that it fails opening the file and simply reports "Not a directory".
bool toggle_ipv6(const bool &enabled) {
const std::vector<std::string> ipv6_kernel_option_files = {
"/proc/sys/net/ipv6/conf/all/disable_ipv6"
"/proc/sys/net/ipv6/conf/default/disable_ipv6"
"/proc/sys/net/ipv6/conf/lo/disable_ipv6"
};
for (const auto &filename: ipv6_kernel_option_files) {
QFile kernel_option_file( filename.c_str() );
if ( kernel_option_file.open(QIODevice::WriteOnly) ) {
QTextStream stream(&kernel_option_file);
stream << (enabled ? "0" : "1");
kernel_option_file.close();
} else {
const std::string error_message = kernel_option_file.errorString().toStdString();
qDebug().nospace().noquote() << '[' << QTime::currentTime().toString() << "]: " << error_message.c_str();
return false;
}
}
return true;
}
I've tried searching the web but I can't find any other issue pertaining to QFile and this particular error message. How can I fix this?

Commas are missing in vector initialization:
const std::vector<std::string> ipv6_kernel_option_files = {
"/proc/sys/net/ipv6/conf/all/disable_ipv6"
"/proc/sys/net/ipv6/conf/default/disable_ipv6"
"/proc/sys/net/ipv6/conf/lo/disable_ipv6"
};
Hence the vector has only one element, which is a string made of the three paths, concatenated:
"/proc/sys/net/ipv6/conf/all/disable_ipv6/proc/sys/net/ipv6/conf/default/disable_ipv6/proc/sys/net/ipv6/conf/lo/disable_ipv6"
Considering that
"/proc/sys/net/ipv6/conf/all/disable_ipv6"
is a file, not a directory, it can't contain the rest of the path.
Use commas to separate the paths in vector initialization:
const std::vector<std::string> ipv6_kernel_option_files = {
"/proc/sys/net/ipv6/conf/all/disable_ipv6",
"/proc/sys/net/ipv6/conf/default/disable_ipv6",
"/proc/sys/net/ipv6/conf/lo/disable_ipv6"
};

Related

open file by std::ifstream occasionaly fails on folder detected by FindFirstChangeNotification (Windows)

I have an application that must monitor some folders (in Windows) to detect if a file was created in that folder (real use is to detect incoming FTP files).
If a file is detected , it is read, then deleted .
Occasionally, I get a file reading error on a file that was detected.
Question is: Why?
To simulate the error, I created a simple program to reproduce it:
std::vector<std::filesystem::path> watch;
void main()
{
watch.push_back("D:\\test1"); //must exist
watch.push_back("D:\\test2");
watch_dir();
}
this example monitors 2 folders.
To simulate incoming files on the folder, another program copies files to that folder
continuously at configurable intervals (say 100 milliseconds).
To detect folder changes , WIN32 API functions FindFirstChangeNotification and WaitForMultipleObjects are used, based on this Microsoft example
https://learn.microsoft.com/en-us/windows/win32/fileio/obtaining-directory-change-notifications
detection function adapted from the example (Note: WaitForMultipleObjects blocks until a change is detected)
void watch_dir()
{
HANDLE handle[2];
memset(handle, 0, 2 * sizeof(HANDLE));
for (size_t idx = 0; idx < watch.size(); idx++)
{
std::string str = watch.at(idx).string();
LPTSTR path = (LPTSTR)str.c_str();
std::cout << "watch path " << path << std::endl;
handle[idx] = FindFirstChangeNotification(
path, // directory to watch
FALSE, // do not watch subtree
FILE_NOTIFY_CHANGE_FILE_NAME); // watch file name changes
if (handle[idx] == INVALID_HANDLE_VALUE)
{
assert(0);
ExitProcess(GetLastError());
}
}
while (TRUE)
{
std::cout << "Waiting for notification..." << std::endl;
DWORD wait_status = WaitForMultipleObjects(watch.size(), handle, FALSE, INFINITE);
std::cout << "Directory " << watch.at(wait_status) << " changed" << std::endl;
if (FindNextChangeNotification(handle[wait_status]) == FALSE)
{
assert(0);
ExitProcess(GetLastError());
}
std::filesystem::path path = watch.at(wait_status);
send_files_in_path(path);
}
}
Once a change is detected by the function above, then all files in the folder are listed
and read, by these functions
void send_files_in_path(const std::filesystem::path& ftp_path)
{
std::vector<std::filesystem::path> list = get_files(ftp_path);
for (size_t idx = 0; idx < list.size(); idx++)
{
std::string buf;
read_file(list.at(idx).string(), buf);
std::this_thread::sleep_for(std::chrono::milliseconds(10));
std::filesystem::remove(list.at(idx));
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
//get_files
//get all ".txt" files inside a FTP folder
/////////////////////////////////////////////////////////////////////////////////////////////////////
std::vector<std::filesystem::path> get_files(const std::filesystem::path& base_archive_path)
{
std::vector<std::filesystem::path> list;
try
{
for (const auto& entry : std::filesystem::recursive_directory_iterator(base_archive_path))
{
std::filesystem::path path = entry.path();
if (!entry.is_regular_file())
{
continue;
}
std::string fname = entry.path().filename().string();
size_t len = fname.size();
size_t pos = len - 4;
//check if last 4 characters are ".txt"
if (fname.find(".txt", pos) == std::string::npos && fname.find(".TXT", pos) == std::string::npos)
{
continue;
}
SPDLOG_INFO("loading: " + entry.path().string());
list.push_back(path);
}//this path
} //try
catch (const std::exception& e)
{
SPDLOG_ERROR(e.what());
}
return list;
}
The function where the error happens is
int read_file(const std::string& fname, std::string& buf)
{
std::ifstream ifs;
std::ios_base::iostate mask = ifs.exceptions() | std::ios::failbit;
ifs.exceptions(mask);
std::this_thread::sleep_for(std::chrono::milliseconds(0));
std::cout << "opening : " << fname << std::endl;
try
{
ifs.open(fname);
if (!ifs.is_open())
{
std::cout << "open fail: " << fname << std::endl;
return -1;
}
}
catch (const std::exception& e)
{
std::cout << e.what() << std::endl;
return -1;
}
std::stringstream ss;
ss << ifs.rdbuf();
ifs.close();
buf = ss.str();
return 0;
}
the try/catch block, again, occasionally , is triggered with the error
ios_base::failbit set: iostream stream error
removing the try/catch block, and the open mask (just to try), then
ifs.is_open
fails.
A temporary solution was to detect the cases where the open() failed and repeat it.. which succeeds, because the file does exist.
Calling this with a small delay before the open call has the effect of reducing the open fails
std::this_thread::sleep_for(std::chrono::milliseconds(10));
ifs.open(fname);
But still would like to find out the reason for the occasional failure

Windows path problems using libssh C++ wrapper

I am having problems with Windows file path separators using the libssh c++ wrapper libsshpp.
Suppose I have following code:
#define SSH_NO_CPP_EXCEPTIONS
#include "libssh/libsshpp.hpp"
#include <iostream>
#pragma comment(lib, "ssh")
int main()
{
ssh::Session session;
int sessionMsg = -1;
std::string host = "myhost.com";
std::string user = "username";
std::string idfile = "%s\\.ssh\\id_ed25519";
std::string hostkeys = "ssh-ed25519";
std::string keyExchange = "curve25519-sha256";
session.setOption(SSH_OPTIONS_HOST, host.c_str());
session.setOption(SSH_OPTIONS_USER, user.c_str());
session.setOption(SSH_OPTIONS_STRICTHOSTKEYCHECK, (long)0);
session.setOption(SSH_OPTIONS_HOSTKEYS, hostkeys.c_str());
session.setOption(SSH_OPTIONS_KEY_EXCHANGE, keyExchange.c_str());
session.setOption(SSH_OPTIONS_ADD_IDENTITY, idfile.c_str());
std::cout << "Trying to connect to " << host << " with user " << user << "...\n";
session.connect();
if (session.isServerKnown() != SSH_SERVER_KNOWN_OK) {
std::cout << "Server unknown.\n";
if (session.writeKnownhost() != SSH_OK) {
std::cout << "Unable to write to known_hosts file.\n";
}
else {
session.connect();
}
}
sessionMsg = session.userauthPublickeyAuto();
std::string err = session.getError();
if (sessionMsg != SSH_AUTH_SUCCESS) {
if (!err.empty()) {
std::cout << err;
}
std::cout << "Auth failed.";
}
else {
std::cout << err.empty() ? session.getIssueBanner() : err;
}
}
In the beginning I had set the idfile value to just id_ed25519 but then libssh complained: Failed to read private key: C:\Users\MyUser/.ssh/id_ed25519 (notice the switching slashes). After changing it to %s\\.ssh\\id_ed25519 it seemed to have had a positive impact on the connection routine, however now I keep falling into the (session.writeKnownhost() != SSH_OK) code part.
Now, I am wondering if this might be due to the same "switching slashes" problem which came up for the private key file path because apparently libssh wants to access C:\Users\MyUser\.ssh\known_hosts but quite possibly the path is set as something like C:\Users\MyUser/.ssh/known_hosts.
My question is: is there a possibility to change the path seperators to windows-style somehow in the session or is there something else I am overseeing or doing wrong here?
I was able to solve the problem adding the SSH_OPTIONS_SSH_DIR option and changing the private key and known_hosts paths (now relative to the ssh directory path):
// note here: %s will be replaced by libssh with the home directory path
std::string sshDir = "%s//.ssh";
std::string idFile = "id_ed25519";
std::string knownHosts = "known_hosts";
// ...
session.setOption(SSH_OPTIONS_USER, user.c_str());
session.setOption(SSH_OPTIONS_SSH_DIR, sshDir.c_str()); // <-- added
// ...

How can I check if an option is a flag with CLI11?

I am using the CLI11 library (link) for parsing of command line arguments of my programm.
Now I want to print information about the options of my program to stdout.
It seems that flags added via App::add_flag(...) are stored as Options internally as well, but I need to distinguish them in my output.
How can I determine which option is a flag?
Here is a simplified example:
std::string file, bool myflag;
CLI::App *command = app.add_subcommand("start", "Start the program");
command->add_option("file", file, "This is a file option")->required();
command->add_flag("--myflag", myflag);
print_description(command);
...
std::string print_description(CLI::App* command) {
for (const auto &option : command->get_options()) {
result << R"(<option name=")" << option->get_name() << R"(" description=")" << option->get_description()
<< R"(" type=")";
if (/*option is a flag*/) {
result << "flag";
} else {
result << "option";
}
result << R"("/>)";
}
return result.str();
}
According to this issue: https://github.com/CLIUtils/CLI11/issues/439, the function Option::get_expected_min will always return 0 for flags.
So it's possible to check it like this:
if (option->get_expected_min() == 0) {
result << "flag";
} else {
result << "option";
}

file reading in debian;

I need to read config file, containing key value pairs.
File contains several strings like this:
key1=value1
key2=value2
keyN=valueN
***//terminate
class CFG
{
public:
static void Init(char* path_);
static string Param(string name_);
static string PrintAll();
private:
static void GetPathEXE(string path_);
static void ParseConfigFile();
static map<string, string> mapData;
};
void CFG::ParseConfigFile()
{
ifstream file;
file.open(Param("HomePath") + Param("Slash") + "ConfigMS.cfg");
if (file.is_open())
{
string file_line;
cmatch results;
regex reg3("(.*)=(.*)", std::regex_constants::ECMAScript);
std::cout<<"Parsing cfg file"<<endl;
while (getline(file, file_line) && (file_line.substr(0, 3) != "***"))
{
std::cout<<file_line<<endl;
std::regex_match(file_line.c_str(), results, reg3);
mapData.insert(std::pair<string,string>(results.str(1),results.str(2) ));
mapData[string(results.str(1))] =string( results.str(2));
std::cout<<"key: "<<results.str(1)<<" val: "<<results.str(2)<<endl;
}
//mapData["OuterIP"] = "10.77.1.68";
std::cout<<"Config loaded\n";
for (auto it : mapData)
{
std::cout<<it.first<<"="<<it.second<<endl;
}
if (Param("OuterIP") == "") mapData["Error"] = "IP for receiving messages not set in *.cfg file.\n";
//else if (data["sipName"] == "") error = "sipName for receiving messages not set in *.cfg file.\n";
}
else { mapData["Error"] = "Could not open *.cfg file. Check its existance or name. Name Must \"be ConfigMS.cfg\".\n"; }
if (Param("RTPPort") == Param("MGCPPort"))
{
mapData["Error"] = "RTP port is same as MGCP port. Please change one of them.\n";
}
if (Param("Error") != "")
{
cout << "\n" + Param("Error");
//system("pause");
exit(-1);
}
}
string CFG::Param(string name_)
{
return mapData[name_];
}
If I rely on file input i have [OuterIP,10.77.1.68] key value pair in my map, but function CFG::Param("OuterIP") returns empty string; And I have no idea how to work around this problem;
I've compiled you code with small modifications and it works.
rafix07 told us the program exits if there is no MGCPort and RTPPort values, and he is right, but you told explicitly that the function returns an empty string. Are you sure there isn't another empty OuterIP parameter in the file?
BTW my modifications are removing the path, reading just the file and commenting out the exit(-1) thing. (As well as adding a main function.)
#include <regex>
#include <map>
#include <string>
#include <iostream>
#include <fstream>
using namespace std;
class CFG
{
public:
static void Init(char* path_);
static string Param(string name_);
static void ParseConfigFile();
private:
static void GetPathEXE(string path_);
static map<string, string> mapData;
};
void CFG::ParseConfigFile()
{
ifstream file;
file.open("ConfigMS.cfg");
if (file.is_open())
{
string file_line;
cmatch results;
regex reg3("(.*)=(.*)", std::regex_constants::ECMAScript);
std::cout<<"Parsing cfg file"<<endl;
while (getline(file, file_line) && (file_line.substr(0, 3) != "***"))
{
std::cout<<file_line<<endl;
std::regex_match(file_line.c_str(), results, reg3);
mapData.insert(std::pair<string,string>(results.str(1),results.str(2) ));
mapData[string(results.str(1))] =string( results.str(2));
std::cout<<"key: "<<results.str(1)<<" val: "<<results.str(2)<<endl;
}
//mapData["OuterIP"] = "10.77.1.68";
std::cout<<"Config loaded\n";
for (auto it : mapData)
{
std::cout<<it.first<<"="<<it.second<<endl;
}
if (Param("OuterIP") == "") mapData["Error"] = "IP for receiving messages not set in *.cfg file.\n";
//else if (data["sipName"] == "") error = "sipName for receiving messages not set in *.cfg file.\n";
}
else { mapData["Error"] = "Could not open *.cfg file. Check its existance or name. Name Must \"be ConfigMS.cfg\".\n"; }
if (Param("RTPPort") == Param("MGCPPort"))
{
mapData["Error"] = "RTP port is same as MGCP port. Please change one of them.\n";
}
if (Param("Error") != "")
{
cout << "\n" + Param("Error");
//system("pause");
//exit(-1);
}
}
string CFG::Param(string name_)
{
return mapData[name_];
}
map<string, string> CFG::mapData;
int main ()
{
CFG::ParseConfigFile ();
cout << "Param: " << CFG::Param("OuterIP") << std::endl;
return 0;
}
This is the program as I've compiled with:
g++ main.cc -o regex -std=c++11
This is the configuration file:
OuterIP=10.0.0.1
And these are the results:
Parsing cfg file
key: val:
key: val:
OuterIP=10.0.0.1
key: OuterIP val: 10.0.0.1
Config loaded
=
OuterIP=10.0.0.1
RTP port is same as MGCP port. Please change one of them.
Param: 10.0.0.1
As you can see, the last line is the cout in main.
So your code works. You have to remove the exit(-1) from the class. It doesn't have sense anyway.

How can I copy a directory using Boost Filesystem

How can I copy a directory using Boost Filesystem?
I have tried boost::filesystem::copy_directory() but that only creates the target directory and does not copy the contents.
bool copyDir(
boost::filesystem::path const & source,
boost::filesystem::path const & destination
)
{
namespace fs = boost::filesystem;
try
{
// Check whether the function call is valid
if(
!fs::exists(source) ||
!fs::is_directory(source)
)
{
std::cerr << "Source directory " << source.string()
<< " does not exist or is not a directory." << '\n'
;
return false;
}
if(fs::exists(destination))
{
std::cerr << "Destination directory " << destination.string()
<< " already exists." << '\n'
;
return false;
}
// Create the destination directory
if(!fs::create_directory(destination))
{
std::cerr << "Unable to create destination directory"
<< destination.string() << '\n'
;
return false;
}
}
catch(fs::filesystem_error const & e)
{
std::cerr << e.what() << '\n';
return false;
}
// Iterate through the source directory
for(
fs::directory_iterator file(source);
file != fs::directory_iterator(); ++file
)
{
try
{
fs::path current(file->path());
if(fs::is_directory(current))
{
// Found directory: Recursion
if(
!copyDir(
current,
destination / current.filename()
)
)
{
return false;
}
}
else
{
// Found file: Copy
fs::copy_file(
current,
destination / current.filename()
);
}
}
catch(fs::filesystem_error const & e)
{
std:: cerr << e.what() << '\n';
}
}
return true;
}
Usage:
copyDir(boost::filesystem::path("/home/nijansen/test"), boost::filesystem::path("/home/nijansen/test_copy")); (Unix)
copyDir(boost::filesystem::path("C:\\Users\\nijansen\\test"), boost::filesystem::path("C:\\Users\\nijansen\\test2")); (Windows)
As far as I see, the worst that can happen is that nothing happens, but I won't promise anything! Use at your own risk.
Please note that the directory you're copying to must not exist. If directories within the directory you are trying to copy can't be read (think rights management), they will be skipped, but the other ones should still be copied.
Update
Refactored the function respective to the comments. Furthermore the function now returns a success result. It will return false if the requirements for the given directories or any directory within the source directory are not met, but not if a single file could not be copied.
Since C++17 you don't need boost for this operation anymore as filesystem has been added to the standard.
Use std::filesystem::copy
#include <exception>
#include <filesystem>
namespace fs = std::filesystem;
int main()
{
fs::path source = "path/to/source/folder";
fs::path target = "path/to/target/folder";
try {
fs::copy(source, target, fs::copy_options::recursive);
}
catch (std::exception& e) { // Not using fs::filesystem_error since std::bad_alloc can throw too.
// Handle exception or use error code overload of fs::copy.
}
}
See also std::filesystem::copy_options.
I see this version as an improved upon version of #nijansen's answer. It also supports the source and/or destination directories to be relative.
namespace fs = boost::filesystem;
void copyDirectoryRecursively(const fs::path& sourceDir, const fs::path& destinationDir)
{
if (!fs::exists(sourceDir) || !fs::is_directory(sourceDir))
{
throw std::runtime_error("Source directory " + sourceDir.string() + " does not exist or is not a directory");
}
if (fs::exists(destinationDir))
{
throw std::runtime_error("Destination directory " + destinationDir.string() + " already exists");
}
if (!fs::create_directory(destinationDir))
{
throw std::runtime_error("Cannot create destination directory " + destinationDir.string());
}
for (const auto& dirEnt : fs::recursive_directory_iterator{sourceDir})
{
const auto& path = dirEnt.path();
auto relativePathStr = path.string();
boost::replace_first(relativePathStr, sourceDir.string(), "");
fs::copy(path, destinationDir / relativePathStr);
}
}
The main differences are exceptions instead of return values, the use of recursive_directory_iterator and boost::replace_first to strip the common part of the iterator path, and relying on boost::filesystem::copy() to do the right thing with different file types (preserving symlinks, for instance).
This is a non-Boost version I'm using based on Doineann's code. I'm using std::filesystem but couldn't use a simple fs::copy(src, dst, fs::copy_options::recursive); because I wanted to filter which files are copied by file extension inside the loop.
void CopyRecursive(fs::path src, fs::path dst)
{
//Loop through all the dirs
for (auto dir : fs::recursive_directory_iterator(src))
{
//copy the path's string to store relative path string
std::wstring relstr = dir.path().wstring();
//remove the substring matching the src path
//this leaves only the relative path
relstr.erase(0, std::wstring(src).size());
//combine the destination root path with relative path
fs::path newFullPath = dst / relstr;
//Create dir if it's a dir
if (fs::is_directory(newFullPath))
{
fs::create_directory(newFullPath);
}
//copy the files
fs::copy(dir.path(), newFullPath, fs::copy_options::recursive | fs::copy_options::overwrite_existing);
}
}
relstr.erase(0, std::wstring(src).size()); is a working Boost-less replacement for the boost::replace_first() call used in the other answer