File Write Thread safe C++ - c++

I am getting error for the below code
I am implementing thread safe write to files as below ::
This is for only learning purpose, i am adding more text because stack overflow wants me to overflow the text.
class OpenFile
{
string fileName;
static map<string, unique_ptr<mutex>> fmap;
bool flag;
public :
OpenFile(string file) : fileName(file) {
try {
if(checkFile(file))
{
flag = false;
fmap.emplace(file, make_unique<mutex>());
}
else
{
flag = true;
}
}
catch(string str)
{
cout << str << endl;
}
}
void writeToFile(const string& str) const
{
if (flag)
{
lock_guard<mutex> lck(*fmap.find(fileName)->second);
ofstream ofile(fileName, ios::app);
ofile << "Writing to the file " << str << endl;
ofile.close();
}
else
{
ofstream ofile(fileName, ios::app);
ofile << "Writing to the file " << str << endl;
ofile.close();
}
}
string ReadFile() const
{
string line;
if (flag)
{
lock_guard<mutex> lck(*fmap.find(fileName)->second);
ifstream ifile(fileName, ios::in);
getline(ifile, line);
ifile.close();
}
else
{
ifstream ifile(fileName, ios::in);
getline(ifile, line);
ifile.close();
}
return line;
}
OpenFile() = delete;
OpenFile& operator=(const OpenFile& o) = delete;
static bool checkFile(string& fname);
};
bool OpenFile::checkFile(string& fname)
{
if (fmap.find(fname)==fmap.end())
{
return true;
}
else
return false;
}
Initial error was because of not passing data by ref to thread..
Error from VS2015 ::
Description Project Line Suppression State
'bool OpenFile::checkFile::<lambda_2bd4a02838a970867505463f4b7b6b9e>::operator ()(const std::pair<std::string,std::unique_ptr<std::mutex,std::default_delete<_Ty>>>) const': cannot convert argument 1 from 'std::pair<const _Kty,_Ty>' to 'const std::pair<std::string,std::unique_ptr<std::mutex,std::default_delete<_Ty>>>' BoQian 43
I think i have fixed it now, thank you

You dont have to use find_if at all in your checkFile function.
This is how you should do:
bool OpenFile::checkFile(string& fname)
{
return fmap.find(fname) != fmap.end();
}

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);
}
}

How to fix still reachable error in valgrind?

-----update-----
I managed to fix the issue by not exiting mid-function but returning to main and exiting there.
Can you explain why it worked?
tnx
I keep getting still reachable valgrind error on my code.
Already tried:
close file before exit, and before function readCSV ends
declare iss once, and using iss.str, iss.clear
(all destructors are default)
I think this function is causing me problems. I's called from the main function, and reads the csv file into a deque:
static std::deque<Fractal *> readCSV(const std::string &path)
{
std::deque<Fractal *> fractals;
std::ifstream ifs(path);
if (ifs.fail())
{
std::cerr << INV_INPUT << std::endl;
exit(1);
}
std::string line;
while (getline(ifs, line))
{
std::istringstream iss(line);
std::string arg;
int type, height;
getline(iss, arg, ',');
type = strToN(arg);//parse int from string, -1 if not a valid integer
getline(iss, arg, ',');
height = strToN(arg);
if (height <= 0 || height > MAX_HEIGHT)
{
std::cerr << INV_INPUT << std::endl;
deleteFractalArr(fractals);
exit(1);
}
if (getline(iss, arg, ','))
{
std::cerr << INV_INPUT << std::endl;
deleteFractalArr(fractals);
exit(1);
}
switch (type)
{
case 1:
fractals.push_front(new SCarpet(height));
break;
case 2:
fractals.push_front(new SSieve(height));
break;
case 3:
fractals.push_front(new CDust(height));
break;
default:
std::cerr << INV_INPUT << std::endl;
deleteFractalArr(fractals);
exit(1);
}
}
return fractals;
}
the deleteFractalArr is as follows:
static void deleteFractalArr(const std::deque<Fractal *> &fractals)
{
for (Fractal *f:fractals)
{
delete f;
f = nullptr;
}
}

is there an example of protobuf with text output?

I want to use protobuf and to create the serialization output file in text format for testing and for a replacement of json. I can't figure out how to write it on my own and am looking for examples.
Here is the one on binary output :
#include <iostream>
#include <fstream>
#include <string>
#include "addressbook.pb.h"
using namespace std;
// This function fills in a Person message based on user input.
void PromptForAddress(tutorial::Person* person) {
cout << "Enter person ID number: ";
int id;
cin >> id;
person->set_id(id);
cin.ignore(256, '\n');
cout << "Enter name: ";
getline(cin, *person->mutable_name());
cout << "Enter email address (blank for none): ";
string email;
getline(cin, email);
if (!email.empty()) {
person->set_email(email);
}
while (true) {
cout << "Enter a phone number (or leave blank to finish): ";
string number;
getline(cin, number);
if (number.empty()) {
break;
}
tutorial::Person::PhoneNumber* phone_number = person->add_phones();
phone_number->set_number(number);
cout << "Is this a mobile, home, or work phone? ";
string type;
getline(cin, type);
if (type == "mobile") {
phone_number->set_type(tutorial::Person::MOBILE);
} else if (type == "home") {
phone_number->set_type(tutorial::Person::HOME);
} else if (type == "work") {
phone_number->set_type(tutorial::Person::WORK);
} else {
cout << "Unknown phone type. Using default." << endl;
}
}
}
// Main function: Reads the entire address book from a file,
// adds one person based on user input, then writes it back out to the same
// file.
int main(int argc, char* argv[]) {
// Verify that the version of the library that we linked against is
// compatible with the version of the headers we compiled against.
GOOGLE_PROTOBUF_VERIFY_VERSION;
if (argc != 2) {
cerr << "Usage: " << argv[0] << " ADDRESS_BOOK_FILE" << endl;
return -1;
}
tutorial::AddressBook address_book;
{
// Read the existing address book.
fstream input(argv[1], ios::in | ios::binary);
if (!input) {
cout << argv[1] << ": File not found. Creating a new file." << endl;
} else if (!address_book.ParseFromIstream(&input)) {
cerr << "Failed to parse address book." << endl;
return -1;
}
}
// Add an address.
PromptForAddress(address_book.add_people());
{
// Write the new address book back to disk.
fstream output(argv[1], ios::out | ios::trunc | ios::binary);
if (!address_book.SerializeToOstream(&output)) {
cerr << "Failed to write address book." << endl;
return -1;
}
}
// Optional: Delete all global objects allocated by libprotobuf.
google::protobuf::ShutdownProtobufLibrary();
return 0;
}
Can I just do some minor changes in this one to output in text format or something else needs to be done? Please either suggest the changes required or any link where code exists (in any language).
The debug string output is guaranteed to be valid text-serialized format, but does not care about whether the protocol message is actually valid:
std::string s = msg.DebugString(); // or ShortDebugString
If you want to validate, use TextFormat::PrintToString:
#include <google/protobuf/text_format.h>
if (std::string s; google::protobuf::TextFormat::PrintToString(msg, &s)) {
std::cout << "Your message: " << s;
} else {
std::cerr << "Message not valid (partial content: "
<< msg.ShortDebugString() << ")\n";
}
Tools for JSON interop are available in json_util.h.
This code will serialise protobuf messages to JSON and deserialise JSON to protobuf messages.
This is lifted straight out of production code (which I own and hereby grant you licence to use, but please credit me).
This is linked against protobuf 3.
Header:
struct pretty_json_type {
void operator()(google::protobuf::util::JsonOptions& opts) const {
opts.add_whitespace = true;
}
};
static constexpr pretty_json_type pretty_json{};
struct compact_json_type {
void operator()(google::protobuf::util::JsonOptions& opts) const {
opts.add_whitespace = false;
}
};
static constexpr compact_json_type compact_json{};
struct include_defaults_type {
void operator()(google::protobuf::util::JsonOptions& opts) const {
opts.always_print_primitive_fields = true;
}
};
static constexpr include_defaults_type include_defaults{};
template<class...Options>
auto json_options(Options&&...options)
{
google::protobuf::util::JsonOptions opts;
using expand = int [];
void(expand{
0,
((options(opts)),0)...
});
return opts;
}
std::string as_json(const google::protobuf::Message& msg,
google::protobuf::util::JsonOptions opts = json_options(pretty_json,
include_defaults));
std::string as_json(const google::protobuf::Message* msg,
google::protobuf::util::JsonOptions opts = json_options(pretty_json,
include_defaults));
google::protobuf::Message& from_json(google::protobuf::Message& msg,
const char* first,
std::size_t size);
inline
decltype(auto) from_json(google::protobuf::Message& msg,
const std::string& json)
{
return from_json(msg, json.data(), json.length());
}
Implementation
std::string as_json(const google::protobuf::Message& msg,
google::protobuf::util::JsonOptions opts)
{
namespace pb = google::protobuf;
namespace pbu = google::protobuf::util;
auto buffer = msg.SerializeAsString();
std::string result;
pb::io::ArrayInputStream zistream(buffer.data(), buffer.size());
auto resolver = std::unique_ptr<pbu::TypeResolver> {
pbu::NewTypeResolverForDescriptorPool("",
pb::DescriptorPool::generated_pool())
};
auto status = google::protobuf::util::BinaryToJsonString(resolver.get(),
"/" + msg.GetDescriptor()->full_name(),
buffer,
std::addressof(result),
opts);
if (!status.ok())
{
std::ostringstream ss;
ss << status;
throw std::runtime_error(ss.str());
}
return result;
}
std::string as_json(const google::protobuf::Message* msg,
google::protobuf::util::JsonOptions opts)
{
return as_json(*msg, opts);
}
google::protobuf::Message& from_json(google::protobuf::Message& msg,
const char* first,
std::size_t size)
{
namespace pb = google::protobuf;
namespace pbu = google::protobuf::util;
auto resolver = std::unique_ptr<pbu::TypeResolver> {
pbu::NewTypeResolverForDescriptorPool("", pb::DescriptorPool::generated_pool())
};
auto zistream = std::make_unique<pb::io::ArrayInputStream>(first,
size);
auto binary_buffer = std::string {};
binary_buffer.reserve(size);
auto zostream = std::make_unique<pb::io::StringOutputStream>(std::addressof(binary_buffer));
auto status = pbu::JsonToBinaryStream(resolver.get(),
"/" + msg.GetDescriptor()->full_name(),
zistream.get(), zostream.get());
zistream.reset();
zostream.reset();
if (msg.ParseFromString(binary_buffer))
{
return msg;
}
throw std::runtime_error("invalid message");
}
To convert a message to JSON in three lines of code, do this -
#include <google/protobuf/util/json_util.h>
static std::string ProtoToJson(const google::protobuf::Message& proto)
{
std::string json;
google::protobuf::util::MessageToJsonString(proto, &json);
return json;
}

Why am I getting an std::bad_alloc error

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();
}

finding a string in a file using fstream in c++

This is my code
/*
Asks the user for their ID, depending on the ID depends on the results. It either goes to maintanance
or it asks the user to return DVD's or check DVD's out and changes the stock of the DVD's.
Cody Close
*/
#include <iostream>
#include <fstream>
#include <conio.h>
#include <sstream>
#include <string>
using namespace std;
void custID();
void sales();
void returns();
void discounts();
void maint();
void createAcc(string* filename, string* newID);
bool checkID(string* filename, string* search);
int main()
{
//Declares all the variables for the program
int mainID= 99959, menuChoice;
bool close = false;
bool done = false;
string vidId;
//Declares and input file and opens a file
fstream inFile;
inFile.open("dayin00.dat");
do{
do{
cout << "accountID: " << endl;
cin >> mainID;
stringstream out;
out << mainID;
mainid = out.str();
checkID("IDlist.txt", mainid);
}while(mainid.length() < 5 || mainid.length() > 9);
if(mainID!= 99959)
{
do
{
cout << "MENU:" << endl;
cout << "(1)Purchase\n(2)Return\n(3)Exit" << endl;
cin >> menuChoice;
switch(menuChoice)
{
case 1:
case 2:
case 3:
done = true;
}
}while(done == false);
}else{
maint();
}
close = true;
}while(close == false);
return 0;
}
void maint()
{
int maintChoice;
cout << "\n(1)Summary\n(2)Withdrawl\n(3)Close Down\n(4)Back to >main\n(0)Help" << endl;
cin >> maintChoice;
switch (maintChoice)
{
case 1:
case 2:
case 3:
case 4:
default:
cout << "1 for summary, 2 for withdrawl, 3 to close down, 4 to >go back to main" << endl;
}
}
void createAcc(string* filename, string* newID)
{
fstream newFile;
newFile.open(filename);
newFile << newID;
}
void checkID(string* filename, string* ID)
{
fstream infile;
infile.open("IDlist.txt");
string word;
infile >> word;
while (!infile.eof()){
if(word == ID)
{
cout << "ID FOUND!" << endl;
}else{
createAcc(infile, ID);
}
}
}
The text file only contains the ID 99959. How do I check if the ID the user types in already exists in the text file and if it doesn't, then it goes to createAcc(),setting up a new account using the ID that the user has entered.
The code opens file with users ID in read mode, reads it line by line and tries to finde ID. If ID not found in file, it opens file in write mode and add user ID in file.
#include <iostream>
#include <fstream>
#include <stdexcept>
void createAcc(const std::string& filename, const std::string& id)
{
std::ofstream os(filename);
if (os)
os << id;
else
throw std::runtime_error("Open file error: " + filename);
}
bool isStringContainsID(const std::string& line, const std::string& id)
{
if (line.find(id) == std::string::npos)
return false;
else
return true;
}
bool isFileContainsID(const std::string& filename, const std::string& id)
{
std::ifstream is(filename);
if (!is)
throw std::runtime_error("Open file error: " + filename);
std::string line;
while (is)
{
std::getline(is, line);
if (isStringContainsID(line, id))
return true;
}
return false;
}
int main() {
std::string id("99959");
std::string file_name("IDlist.txt");
if (isFileContainsID(file_name, id))
std::cout << "ID FOUND!" << std::endl;
else
createAcc(file_name, id);
return 0;
}
Note that all users ID should have the same length in string representation, otherwise the code can find shorter ID in file that contains larger ID with shorter ID as sub-string.