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);
}
}
Related
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
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";
I'm having an issue when running the code below. Every time I set the while loop to reach the .eof() it returns a std::bad_alloc
inFile.open(fileName, std::ios::in | std::ios::binary);
if (inFile.is_open())
{
while (!inFile.eof())
{
read(inFile, readIn);
vecMenu.push_back(readIn);
menu.push_back(readIn);
//count++;
}
std::cout << "File was loaded succesfully..." << std::endl;
inFile.close();
}
It runs fine if I set a predetermined number of iterations, but fails when I use the EOF funtion. Here's the code for the read function:
void read(std::fstream& file, std::string& str)
{
if (file.is_open())
{
unsigned len;
char *buf = nullptr;
file.read(reinterpret_cast<char *>(&len), sizeof(unsigned));
buf = new char[len + 1];
file.read(buf, len);
buf[len] = '\0';
str = buf;
std::cout << "Test: " << str << std::endl;
delete[] buf;
}
else
{
std::cout << "File was not accessible" << std::endl;
}
}
Any help you can provide is greatly appreciated.
NOTE: I failed to mention that vecMenu is of type std::vector
and menu is of type std::list
The main problems I see are:
You are using while (!inFile.eof()) to end the loop. See Why is iostream::eof inside a loop condition considered wrong?.
You are not checking whether calls to ifstream::read succeeded before using the variables that were read into.
I suggest:
Changing your version of read to return a reference to ifstream. It should return the ifstream it takes as input. That makes it possible to use the call to read in the conditional of a loop.
Checking whether calls to ifstream::read succeed before using them.
Putting the call to read in the conditional of the while statement.
std::ifstream& read(std::fstream& file, std::string& str)
{
if (file.is_open())
{
unsigned len;
char *buf = nullptr;
if !(file.read(reinterpret_cast<char *>(&len), sizeof(unsigned)))
{
return file;
}
buf = new char[len + 1];
if ( !file.read(buf, len) )
{
delete [] buf;
return file;
}
buf[len] = '\0';
str = buf;
std::cout << "Test: " << str << std::endl;
delete[] buf;
}
else
{
std::cout << "File was not accessible" << std::endl;
}
return file;
}
and
inFile.open(fileName, std::ios::in | std::ios::binary);
if (inFile.is_open())
{
std::cout << "File was loaded succesfully..." << std::endl;
while (read(inFile, readIn))
{
vecMenu.push_back(readIn);
menu.push_back(readIn);
//count++;
}
inFile.close();
}
I am trying to store the name of all txt files in a directory in a string and print them out. I need to count the number of txt files in the directory and then print the names. The part of counting is working, but I can't seem to get the name working. I have found some examples but they don't work in visual studio which is what I'm using.
Here is my code.
int main() {
bool x = true;
int i = 0;
wchar_t* file = L"../Menu/Circuitos/*.txt";
WIN32_FIND_DATA FindFileData;
HANDLE hFind;
hFind = FindFirstFile(file, &FindFileData);
if (hFind != INVALID_HANDLE_VALUE) {
i++;
while ((x = FindNextFile(hFind, &FindFileData)) == TRUE) {
i++;
}
}
cout << "number of files " << i << endl;
return 0;
}
FindFirstFile already has the first valid handle. If you immediately call FindNextFile then the first handle is lost. The file count in your example would be wrong.
Use do-while loop istead.
Also, the handle obtained from FindFirstFile must be closed with FindClose
HANDLE hFind;
hFind = FindFirstFile(file, &FindFileData);
if (hFind != INVALID_HANDLE_VALUE)
{
do {
wcout << FindFileData.cFileName << "\n";
i++;
} while (FindNextFile(hFind, &FindFileData));
FindClose(hFind);
}
cout << "number of files " << i << endl;
Use std::vector and std::wstring to store the items
#include <string>
#include <vector>
...
std::vector<std::wstring> vs;
HANDLE hFind;
hFind = FindFirstFile(file, &FindFileData);
if (hFind != INVALID_HANDLE_VALUE)
{
do {
vs.push_back(FindFileData.cFileName);
} while (FindNextFile(hFind, &FindFileData));
FindClose(hFind);
}
std::cout << "count:" << vs.size() << "\n";
for (auto item : vs)
std::wcout << item << "\n";
For some older compilers auto may not be available, use this instead
for (int i = 0; i < vs.size(); i++)
std::wcout << vs[i] << "\n";
Note, Windows API works with c-strings. In many cases you have to use .c_str() to obtain character array. For example:
if (vs.size())
{
std::wstring str = vs[0];
MessageBox(0, str.c_str(), 0, 0);
}
Here is a portable version using the new ISO Standard Filesystem Library TS (technical specification) for those with compilers that support it:
#include <vector>
#include <iostream>
#include <algorithm>
#include <experimental/filesystem>
// for readability
namespace fs = std::experimental::filesystem;
/**
* Function object to test directory entries
* for a specific file extension.
*/
struct file_extension_is
{
std::string ext;
file_extension_is(std::string const& ext): ext(ext) {}
bool operator()(fs::directory_entry const& entry) const
{
return entry.path().extension() == ext;
}
};
int main(int, char* argv[])
{
try
{
// directory supplied on the command line if present
// else current directory
fs::path dir = argv[1] ? argv[1] : ".";
// place to store the results
std::vector<fs::directory_entry> entries;
// copy directory entries that have file extension ".txt"
// to the results
fs::directory_iterator di(dir);
fs::directory_iterator end;
std::copy_if(di, end, std::back_inserter(entries),
file_extension_is(".txt"));
// print it all out
std::cout << "Number of files: " << entries.size() << '\n';
for(auto const& entry: entries)
std::cout << entry.path().string() << '\n';
}
catch(std::exception const& e)
{
std::cerr << e.what() << '\n';
}
catch(...)
{
std::cerr << "Unknown exception." << '\n';
}
}
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