C/C++ - executable path - c++

I want to get the current executable's file path without the executable name at the end.
I'm using:
char path[1024];
uint32_t size = sizeof(path);
if (_NSGetExecutablePath(path, &size) == 0)
printf("executable path is %s\n", path);
else
printf("buffer too small; need size %u\n", size);
It works, but this adds the executable name at the end.

dirname(path);
should return path without executable after you acquire path with, that is on Unix systems, for windows you can do some strcpy/strcat magic.
For dirname you need to include #include <libgen.h>...

Best solution for Ubuntu!!
std::string getpath() {
char buf[PATH_MAX + 1];
if (readlink("/proc/self/exe", buf, sizeof(buf) - 1) == -1)
throw std::string("readlink() failed");
std::string str(buf);
return str.substr(0, str.rfind('/'));
}
int main() {
std::cout << "This program resides in " << getpath() << std::endl;
return 0;
}

Use dirname.

char* program_name = dirname(path);

For Linux:
Function to execute system command
int syscommand(string aCommand, string & result) {
FILE * f;
if ( !(f = popen( aCommand.c_str(), "r" )) ) {
cout << "Can not open file" << endl;
return NEGATIVE_ANSWER;
}
const int BUFSIZE = 4096;
char buf[ BUFSIZE ];
if (fgets(buf,BUFSIZE,f)!=NULL) {
result = buf;
}
pclose( f );
return POSITIVE_ANSWER;
}
Then we get app name
string getBundleName () {
pid_t procpid = getpid();
stringstream toCom;
toCom << "cat /proc/" << procpid << "/comm";
string fRes="";
syscommand(toCom.str(),fRes);
size_t last_pos = fRes.find_last_not_of(" \n\r\t") + 1;
if (last_pos != string::npos) {
fRes.erase(last_pos);
}
return fRes;
}
Then we extract application path
string getBundlePath () {
pid_t procpid = getpid();
string appName = getBundleName();
stringstream command;
command << "readlink /proc/" << procpid << "/exe | sed \"s/\\(\\/" << appName << "\\)$//\"";
string fRes;
syscommand(command.str(),fRes);
return fRes;
}
Do not forget to trim the line after

Related

Windows service cannot read file with owner other than LOCAL_SERVICE

I have a Windows service written in C++ with the Win32 API. Before entering in "service" mode, the C++ program tries to read a configuration file specified as an absolute path. But the program cannot read it and exits. Some debugging leaves me to suspect that this is because of file ownership.
Question is, how can I modify the file ownership (preferably with a power-shell script) , so that the file can be read?
Here are the relevant parts
main program (exits, file cannot be read)
int main()
{
std::string cfg_file_name = config::get_config(config::comm_config_file);
if (cfg.read(cfg_file_name) < 0)
{
events::start_log(cfg.log_path, cfg.log_spdlog_level);
SPDLOG_CRITICAL("Cannot read: " + cfg_file_name);
return 1;
}
function get_config() uses some Win32 API calls to get the executable path (where the file is located) and concatenates it with the file name, to get an absolute path
std::string config::get_config(const std::string& config_name)
{
#ifdef _MSC_VER
std::string s = config::get_executable_path();
//this is done before log starts; it will be written to C:\Windows\System32 as a first log/debugging tool
std::ofstream ofs("comm.txt");
ofs << "GetModuleFileName: " << s << std::endl;
TCHAR buf[MAX_PATH];
GetCurrentDirectory(MAX_PATH, buf);
ofs << "GetCurrentDirectory: " << buf << std::endl;
//change the current directory of the process to be the executable path
if (SetCurrentDirectory(s.c_str()) == 0)
{
ofs << "SetCurrentDirectory" << std::endl;
}
GetCurrentDirectory(MAX_PATH, buf);
ofs << "GetCurrentDirectory: " << buf << std::endl;
s += config_name;
ofs.close();
return s;
#else
return config_name;
#endif
}
service is created by a power-shell script
sc.exe create _comm_ftp_server binPath= "$install_dir\ftp_server.exe" start= auto obj= "NT AUTHORITY\LocalService" password= " "
to debug it, I wrote a simple test service that writes a file and reads that same file, with no problem (so, a file can be read)
int main(int argc, char* argv[])
{
std::string path = config::get_executable_path();
cfg.log_path = path;
events::start_log(cfg.log_path, "trace", true);
//A service process has a SERVICE_TABLE_ENTRY structure for each service that it can start.
//The structure specifies the service name and a pointer to the service main function for that service.
//The main function of a service program calls the StartServiceCtrlDispatcher
//function to connect to the service control manager (SCM)
SERVICE_TABLE_ENTRY service_table[] =
{
{ (LPSTR)service_name, (LPSERVICE_MAIN_FUNCTION)service_main },
{ NULL, NULL }
};
if (StartServiceCtrlDispatcher(service_table))
{
return 0;
}
else
{
return 1;
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
//service_main
/////////////////////////////////////////////////////////////////////////////////////////////////////
void WINAPI service_main(DWORD argc, LPTSTR* argv)
{
service_handle = RegisterServiceCtrlHandler(service_name, service_handler);
if (service_handle == NULL)
{
return;
}
service_stop_event = CreateEvent(NULL, TRUE, FALSE, NULL);
if (service_stop_event == NULL)
{
return;
}
report_status(SERVICE_START_PENDING);
report_status(SERVICE_RUNNING);
SPDLOG_INFO("service running..." + std::to_string(current_state));
HANDLE thread_service = 0;
thread_service = CreateThread(NULL, 0, service_thread, NULL, 0, NULL);
WaitForSingleObject(thread_service, INFINITE);
/////////////////////////////////////////////////////////////////////////////////////////////////////
//service shutdown requested
/////////////////////////////////////////////////////////////////////////////////////////////////////
CloseHandle(thread_service);
report_status(SERVICE_STOP_PENDING);
SPDLOG_INFO("service stop pending..." + std::to_string(current_state));
CloseHandle(service_stop_event);
report_status(SERVICE_STOPPED);
SPDLOG_INFO("service stopped..." + std::to_string(current_state));
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
//service_thread
/////////////////////////////////////////////////////////////////////////////////////////////////////
DWORD WINAPI service_thread(LPVOID lpParam)
{
std::string path = cfg.log_path;
SPDLOG_INFO("service started in..." + cfg.log_path);
path += "\\test.txt";
size_t i = 0;
while (WaitForSingleObject(service_stop_event, 0) != WAIT_OBJECT_0)
{
write_txt_file(path, "writing...#" + std::to_string(i));
i++;
Sleep(10000);
read_txt_file(path);
}
return ERROR_SUCCESS;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
//write_txt_file
/////////////////////////////////////////////////////////////////////////////////////////////////////
void write_txt_file(const std::string& file_name, const std::string& input)
{
FILE* f = fopen(file_name.c_str(), "a+");
fprintf(f, "%s\n", input.c_str());
fclose(f);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
//read_txt_file
/////////////////////////////////////////////////////////////////////////////////////////////////////
void read_txt_file(const std::string& file_name)
{
std::ifstream ifs;
ifs.open(file_name);
if (!ifs.is_open())
{
SPDLOG_ERROR("Cannot open: " + file_name);
return;
}
std::string line;
while (std::getline(ifs, line))
{
SPDLOG_INFO("Line: " + line);
}
ifs.close();
}
Examining the file written by the test service in Windows explorer (Properties->Details) reveals a file owner as LOCAL_SERVICE
The file that must be read has owner "Administrators"
This leaves me to suspect that this is the problem. How can the file ownership be changed, or is there a way to create the service with privileges that can read any file ?
reference for SC.EXE Create
https://learn.microsoft.com/en-US/windows-server/administration/windows-commands/sc-create
To read the file, std::ifstream is used (default read only)
int config::config_t::read(const std::string& fname)
{
try
{
std::ifstream ifs(fname);
ifs >> configuration_json;
ifs.close();
from_json(configuration_json, *this);
}
catch (const std::exception& e)
{
SPDLOG_ERROR(e.what());
return -1;
}
return 0;
}
The read error was because the library JSON for modern C++
https://github.com/nlohmann/json
detects an error reading the last entry of this file because of the comma ","
{
"archive_path":"D:\\archive",
"test_comm_input_path":"D:\\test_comm_input_path",
}
in the reading function
int config::config_t::read(const std::string& fname)
{
//this is done before log starts; it will be written to the executable path as a first log/debugging tool in service mode
std::ofstream ofs("comm.txt");
try
{
std::ifstream ifs;
std::ios_base::iostate mask = ifs.exceptions() | std::ios::failbit;
ifs.exceptions(mask);
ifs.open(fname);
if (!ifs.is_open())
{
ofs << "open fail: " << fname << std::endl;
ofs.close();
return -1;
}
else
{
ofs << "open: " << fname << std::endl;
}
ifs >> configuration_json;
ifs.close();
from_json(configuration_json, *this);
ofs << "read: " << fname << std::endl;
ofs.close();
}
catch (const std::exception& e)
{
ofs << "json read: " << e.what() << std::endl;
ofs.close();
return -1;
}
return 0;
}
where
void from_json(const nlohmann::json& j, config::config_t& c)
{
if (j.contains("archive_path"))
{
j.at("archive_path").get_to(c.archive_path);
}
}

c++ How to put path from command line argument to fprintf

I am having trouble figuring out how to take a path from the command line argument and having my program write to that folder using fprinf. For example: /here would write the file to the "here" folder with /here being the path from where the program is to where it needs to be written. Same for if the path were /there/here. This is just this section of my code:
void write_file(int sockfd, char* path)
{
int n;
FILE *fp;
char filename[] = "file1.txt";
char buffer[SIZE];
fp = fopen(filename, "w");
if (fp == NULL)
{
std::cerr << "ERROR: Could not write file.\n";
exit(EXIT_FAILURE);
}
while (1)
{
n = recv(sockfd, buffer, SIZE, 0);
if (n <= 0)
{
break;
return;
}
fprintf(fp, "%s", buffer);
bzero(buffer, SIZE);
}
fclose(fp);
return;
}
This code works but saves the file in my current folder. The variable "path" is the command line argument.
Edit: This is code that does not print lines to a file. Instead it copies lines from an existing file and puts those lines into the "file1.txt" file.
Here's an example using std::filesystem (C++17):
void write_file(int sockfd, const char* path)
{
auto filename = std::filesystem::path(path) / "file1.txt";
std::ofstream out{ filename, ios::trunc | ios::out };
if (!out) {
std::cerr << "ERROR: " << std::strerror(errno) << "\n";
exit(EXIT_FAILURE);
}
char buffer[SIZE];
while (1)
{
int n = recv(sockfd, buffer, sizeof(buffer), 0);
if (n == 0)
{
break;
}
if (n < 0) {
std::cerr << "ERROR: " << std::strerror(errno) << "\n";
break;
}
if (!out.write(buffer, n)) {
std::cerr << "ERROR: " << std::strerror(errno) << "\n";
break;
}
}
}
If you don't have std::filesystem support, you can replace the first 2 statements with some manual path concatenation like:
std::string filename = path;
if (filename != "" && filename.back() != '/')
filename += '/';
filename += "file1.txt";

how to use mount function from c?

I want to use mount function to implement NFS.
int mount(const char *source, const char *target,
const char *filesystemtype, unsigned long mountflags,
const void *data);
I can implement it by using mount command e.g mount 172.16.0.144:/tmp/test /tmp/test. But when I use the mount() function , it doesn't work. This is my code here .
#include<sys/mount.h>
#include<iostream>
#include<errno.h>
#include<fstream>
#include<string.h>
using namespace std;
int main(int argc, char**argv) {
const char* srcPath = "/tmp/watchman";
const char* targetPath = "172.16.0.144:/tmp/watchman";
if (argc == 3) {
srcPath = argv[1];
targetPath = argv[2];
cerr << "reset the src && target path\n";
} else {
if (argc != 1) {
cerr << "wrong input argument!\n";
return 0;
}
}
cerr << "srcPath = " << srcPath << endl;
cerr << "target = " << targetPath << endl;
int ret_val = mount(srcPath, targetPath, "", MS_SHARED, "");
if (ret_val == 0) {
cerr << "mount succeed\n";
string filename = string(srcPath) + "/" + "tmp.txt";
fstream fin(filename.c_str(), ios::out);
fin << "there is a write test from client\n";
fin.close();
ret_val = umount(srcPath);
if (ret_val == 0) {
cerr << "umount succeed \n";
} else {
cerr << "umount failed \n";
printf("%s/n", strerror(errno));
}
} else {
cout<<"ret_val = "<<ret_val<<endl;
cerr << "mount failed \n";
cerr << strerror(errno) << endl;
}
return 0;
}
It printf mount failed,No such file or directory. anyone can help me? please !!!
If you read the mount manual page you will see that
mount() attaches the filesystem specified by source (which is often a pathname referring to a device, but can also be the pathname of a directory or file, or a dummy string) to the location (a directory or file) specified by the pathname in target.
You have switched the source and target in your application.

Traversing directory and iterators in c++

I am an absolute newbie to C++ and have only started to program with it 3 days ago.
I am trying to do the folliwng:
traverse a directory for X.X files (typically .), and for each file, do the following:
Search within the file for a string (findFirst) and then search until another string (findLast) - The files will be HTML format.
In this selection, I want to perform several tasks (yet to write) - but they will be the following:
One of the strings will be the Filename I want to write to. - so extract this field and create an outputfile with this name
Some of the lines will be manufacturer part numbers - extract these and format the output file accordingly
most of it will be description of product. Again - this will be in an HTML construct - so extract this and format the output file.
So far, I have managed to get working the traverse directory, and selecting the start and finish keywords - using some help from the internet.
My problem is here
processFiles(inputFileName, "testing", "finish");
I need the inputFileName to be the name of the traversed filename.
All the examples I have found simply print the filename using cout
I need to pass this into the processFiles function.
Can someone tell me what i need to use? i have tried it->c_Str() and other variations of (*it) and .at, .begin etc
my non printing example is below:
// Chomp.cpp : Defines the entry point for the console application.
//
#include <stdafx.h>
#include <windows.h>
#include <string>
#include <iostream>
#include <fstream>
#include <stdlib.h>
#include <cctype>
#include <algorithm>
#include <vector>
#include <stack>
//std::ifstream inFile ( "c:/temp/input.txt" ) ;
std::ofstream outFile( "c:/temp/output.txt") ;
using namespace std;
/////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////
void openFiles()
{
if (!(outFile.is_open()))
{
printf ("Could not Create Output file\n");
exit(0);
}
}
/////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////
bool ListFiles(wstring path, wstring mask, vector<wstring>& files)
{
HANDLE hFind = INVALID_HANDLE_VALUE;
WIN32_FIND_DATA ffd;
wstring spec;
stack<wstring> directories;
directories.push(path);
files.clear();
while (!directories.empty())
{
path = directories.top();
spec = path + L"\\" + mask;
directories.pop();
hFind = FindFirstFile(spec.c_str(), &ffd);
if (hFind == INVALID_HANDLE_VALUE)
return false;
do
{
if (wcscmp(ffd.cFileName, L".") != 0 && wcscmp(ffd.cFileName, L"..") != 0)
{
if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
directories.push(path + L"\\" + ffd.cFileName);
else
files.push_back(path + L"\\" + ffd.cFileName);
}
} while (FindNextFile(hFind, &ffd) != 0);
if (GetLastError() != ERROR_NO_MORE_FILES)
{
FindClose(hFind);
return false;
}
FindClose(hFind);
hFind = INVALID_HANDLE_VALUE;
}
return true;
}
/////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////
void processFiles(const wchar_t *inFileName, std::string findFirst,std::string findLast )
{
/*
std::string findFirst = "testing" ;
std::string findLast = "finish" ;
*/
std::string inputLine ;
int lineNum = 0 ;
char buffer[2048];
size_t found = 0;
std::ifstream inFile;
inFile.open (inFileName); // Open The file
if (inFile.is_open())
{
while( std::getline( inFile, inputLine ))
{
++lineNum ;
// printf ("Line len = %d\n ", inputLine.length());
if( (found = inputLine.find(findFirst)) != std::string::npos )
{
std::cout << "###Line " << lineNum << " At Position [ " << found << " ]\n" ;
sprintf_s(buffer, 2048, "[%-5.5d] %s\n", lineNum, inputLine.c_str());
outFile << buffer ;
bool foundLast = 0;
while( std::getline( inFile, inputLine ))
{
++lineNum ;
sprintf_s(buffer, 2048, "[%-5.5d] %s\n", lineNum, inputLine.c_str());
if( (found = inputLine.find(findLast)) != std::string::npos )
{
outFile << buffer ;
break; // Found last string - so stop after printing last line
}
else
outFile << buffer ;
}
}
else
{
// std::cout << "=>" << inputLine << '\n' ;
}
}
}
else
{
printf ("Cant open file \n");
exit(0);
}
inFile.close() ; // Close The file
}
/////////////////////////////////////////////////////////////////////////////////////////////
/// M A I N
/////////////////////////////////////////////////////////////////////////////////////////////
int main()
{
std::ifstream inFile ;
int startLine = 0;
int endLine = 0;
int lineSize = 0;
char buffer[512];
vector<wstring> files; // For Parsing Directory structure
openFiles();
// Start The Recursive parsing of Directory Structure
if (ListFiles(L"C:\\temp", L"*.*", files))
{
for (vector<wstring>::iterator it = files.begin(); it != files.end(); ++it)
{
printf ("Filename1 is %s\n", it->c_str());
printf ("Filename2 is %s\n", files.begin());
outFile << "\n------------------------------\n";
//outFile << it << endl;
wcout << it->c_str() << endl;
outFile << "\n------------------------------\n";
const wchar_t *inputFileName = it->c_str();
// processFiles(inputFileName, "testing", "finish");
// getchar();
}
}
outFile.close();
getchar();
}
Make your processFile accept a wstring, viz:
void processFiles(wstring inFileName, std::string findFirst,std::string findLast )
{
// Make the necessary changes so that you use a wstring for inFileName
}
Call it from main() using:
processFiles(*it, "testing", "finish");
You need to change processFile to use a wifstream instead of a ifstream and you should change all of your narrow strings to use wide strings (or vice versa). Narrow strings and wide strings are not compatible with each other and in order to use one with the other a conversion function must be used such as mbstowcs.
Edit:
You can find an example that should compile here.

Recursive Directories and File streaming and Searching Strings

I have an issue with the recursive call in the walkThroughFunction.
The code is supposed to go through directories and count the sub directories and if it finds a file it should open it and search a certain string.
The code only goes through one directory. Can someone help me with this. You will find the braces misplaced a little. Kindly ignore those.
int directories=0;
void walkThroughDirectory(char *directory_name,char *searchString){
DIR * directory;
struct dirent * walker;
char d_name[PATH_MAX];
int path_length;
char path[PATH_MAX];
directory=opendir(directory_name);
if(directory==NULL){
cout<<"Error"<<endl;
cout<<directory_name<<" Cannot be Opened"<<endl;
exit(10000);
}
while((walker=readdir(directory)) !=NULL){
strcpy(d_name,walker->d_name);
cout<<directory_name<<"/"<<endl;
if (strcmp (d_name, "..") == 0 &&
strcmp (d_name, ".") == 0){
continue;
}
else{
path_length = snprintf(path,PATH_MAX,"%s/%s\n",directory_name,d_name);
cout<<"HELLO"<<endl;
cout<<path<<endl;
if (path_length >= PATH_MAX){
cout<<"Path is too long"<<endl;
exit (1000);
}
if(walker->d_type==DT_DIR){
cout<<"Hello"<<endl;
directories++;
walkThroughDirectory (path,searchString);
}
else if(walker->d_type==DT_REG){
ifstream openFile;
openFile.open(path);
char line[1500];
int currentLine = 0;
if (openFile.is_open()){
while (openFile.good()){
currentLine++;
openFile.getline(line, 1500);
if (strstr(line, searchString) != NULL)
cout<<path<<": "<<currentLine<<": "<<line<<endl;
}
}
openFile.close();
}
/*
struct stat directory_stat;
if (stat(path, &directory_stat) == -1){
return;
}
if (S_ISDIR(directory_stat.st_mode)){
cout<<"HELLO"<<endl;
directories++;
walkThroughDirectory(path, searchString);
}
else if (S_ISREG(directory_stat.st_mode)){
ifstream openFile;
openFile.open(path);
char line[1500];
int currentLine = 0;
if (openFile.is_open()){
while (openFile.good()){
currentLine++;
openFile.getline(line, 1500);
if (strstr(line, searchString) != NULL)
cout<<path<<": "<<currentLine<<": "<<line<<endl;
}
}
// it's a file so search for text in file
}
*/
}
}
if (closedir (directory))
{
cout<<"Unable to close "<<directory_name<<endl;
exit (1000);
}
}
int main(){
char * name;
name=new char;
cout<<"Total Directories "<< directories<<endl;
name=get_current_dir_name();
cout<<"Current Directory is: "<<name<<endl;
/*
cout<<"Now Enter The Desired Directory from the root or the current path"<<endl;
char *desiredDirectory;
desiredDirectory=new char;
cin>>desiredDirectory;
cout<<"Enter The String You want to search"<<endl;
char *searchString;
searchString=new char;
cin>>searchString;
*/
char ourpath[400];
strcpy(ourpath,name);
walkThroughDirectory(ourpath,"diminutive");
cout<<"Total Directories "<< directories<<endl;
return 0;
}
This code has several problems. First, when you perform the strcmp to check if d_name is "." or "..", you need to use an OR, not an AND. Second, when you call sprintf to create your c-string path, you should not have a newline at the end of the string. This is what was causing your code to only go one level deep. Third, when you call get_current_dir_name, it does all the malloc work for you. What you're doing is allocating space for a single char, which won't work in itself and is not a correct use of the API. See the man page for get_current_dir_name.
The below code addresses these issues (and also has proper indentation).
#include <iostream>
#include <fstream>
#include <stdlib.h>
#include <unistd.h>
#include <dirent.h>
#include <limits.h>
#include <string.h>
int directories=0;
void walkThroughDirectory(char *directory_name,char *searchString)
{
DIR *directory;
struct dirent *walker;
char d_name[PATH_MAX];
int path_length;
char path[PATH_MAX];
directory = opendir(directory_name);
if(directory == NULL)
{
std::cout << directory_name << " Cannot be Opened" << std::endl;
exit(1);
}
while((walker=readdir(directory)) != NULL)
{
strcpy(d_name, walker->d_name);
// Needs to be || not &&
if (strcmp(d_name, "..") == 0 || strcmp(d_name, ".") == 0)
{
continue;
}
else
{
// No newline on the path name.
path_length = snprintf(path, PATH_MAX, "%s/%s", directory_name, d_name);
if (path_length >= PATH_MAX)
{
std::cout << "Path is too long" << std::endl;
exit(2);
}
if(walker->d_type == DT_DIR)
{
directories++;
walkThroughDirectory(path, searchString);
}
else if(walker->d_type==DT_REG)
{
std::ifstream openFile;
openFile.open(path);
char line[1500];
int currentLine = 0;
if (openFile.is_open())
{
while (openFile.good())
{
currentLine++;
openFile.getline(line, 1500);
if (strstr(line, searchString) != NULL)
std::cout << path << ": " << currentLine << ": " << line << std::endl;
}
}
openFile.close();
}
}
}
if (closedir(directory))
{
std::cout << "Unable to close " << directory_name << std::endl;
exit(3);
}
}
int main()
{
// get_current_dir_name() mallocs a string for you.
char *name;
name = get_current_dir_name();
walkThroughDirectory(name, "matthew");
free(name);
std::cout << "Total Directories: " << directories << std::endl;
return 0;
}