How does boost::filesystem::remove_all(path) work? - c++

I am trying to remove all directories, subdirectories and the contained files from a specific path using boost::filesystem::remove_all(path). I also want to display an error message in case a file is open in another program. Does boost::filesystem::remove_all(path) throw an exception in this case?
Or is there another way I can achieve this?

this does not fit in a comment so I'm posting as an answer
Just look in the source: http://www.boost.org/doc/libs/1_55_0/libs/filesystem/src/operations.cpp
BOOST_FILESYSTEM_DECL
boost::uintmax_t remove_all(const path& p, error_code* ec)
{
error_code tmp_ec;
file_type type = query_file_type(p, &tmp_ec);
if (error(type == status_error, tmp_ec, p, ec,
"boost::filesystem::remove_all"))
return 0;
return (type != status_error && type != file_not_found) // exists
? remove_all_aux(p, type, ec)
: 0;
}
remove_all_aux is defined few lines above and so is remove_file_or_directory, remove_file, remove_directory and so forth and so on. The primitive operations are:
# if defined(BOOST_POSIX_API)
...
# define BOOST_REMOVE_DIRECTORY(P)(::rmdir(P)== 0)
# define BOOST_DELETE_FILE(P)(::unlink(P)== 0)
...
# else // BOOST_WINDOWS_API
...
# define BOOST_REMOVE_DIRECTORY(P)(::RemoveDirectoryW(P)!= 0)
# define BOOST_DELETE_FILE(P)(::DeleteFileW(P)!= 0)
...
# endif
The behavior of removing a locked file will be whatever your platform will provide.

I am posting some code examples to clarify this issue.
There are 2 scenarios.
In the first scenario I am using the remove_all function to delete the whole directory from a certain path and then I create a new directory at the same path:
try
{
if(exists(directory_path))
{
remove_all(directory_path);
}
create_directory(directory_path);
}
catch(filesystem_error const & e)
{
//display error message
}
This works just as expected, but then I have a second scenario where I am trying to delete certain folders from a path and then create the new directory:
try
{
if(exists(directory_path))
{
for ( boost::filesystem::directory_iterator itr(directory_path); itr != end_itr; itr++)
{
std::string folder = itr->path().filename().string();
if(folder == FOLDER1 || folder == FOLDER2 || folder == FOLDER3)
remove_all(itr->path());
}
}
create_directory(directory_path);
}
catch(filesystem_error const & e)
{
//display error message
}
In this case the exception is not thrown in case a file is open in another program. The files just get deleted. Why does this happen?

It depends on which overload of remove_all you call; this is clearly documented in the documentation for the function. (What isn't clear is, if you use the function which reports errors by means of an error code, does the function continue, or does it return after the first error?)

Related

rapidjson's assert IsObject() fails randomly while it shouldn't

we are facing an issue where the RAPIDJSON_ASSERT(IsObject()) called by MemberEnd() which is called by HasMember() fails. However, that rapidjson::Value is guaranteed to be an object by other logic.
Here is the code snippet:
const std::string str = "{\"outer_key\":{\"inner_key\":\"value\", \"foo\":\"bar\"}}";
rapidjson::Document doc;
if (doc.Parse(str.c_str()).HasParseError()) {
return -1;
}
rapidjson::Value::MemberIterator it = doc.FindMember("outer_key");
// make sure the member is of Object type
if (it == doc.MemberEnd() || !it->value.IsObject()) {
return -1;
}
rapidjson::Value* p_json;
p_json = &(it->value);
rapidjson::Document new_doc;
rapidjson::Document::AllocatorType& new_doc_allocator = new_doc.GetAllocator();
// if SOME_FLAG is set and "outer_key" exists in the input JSON string,
// use "outer_key"'s value so that `new_doc` will have other properties such as `foo` for free.
if (SOME_FLAG && p_json != NULL) {
new_doc.CopyFrom(*p_json, new_doc_allocator);
// The value of "inner_key" will be re-added later, so remove it for now.
// assert(IsObject()) fails here and core is dumped.
if (new_doc.HasMember("inner_key")) {
new_doc.RemoveMember("inner_key");
}
} else {
// Otherwise, start from an empty object.
new_doc.SetObject();
}
// Add "inner_key" for both cases above.
const std::string new_value_str = "new_value";
new_doc.AddMember("inner_key", rapidjson::StringRef(new_value_str.c_str()), new_doc_allocator);
We suspect that the new_doc.CopyFrom(*p_json, new_doc_allocator); line is the culprit, but are kind of new to rapidjson and not sure whether it is the correct way to use new_doc_allocator to copy *p_json to new_doc.
The weird part is that this issue only happened online for a small number of requests. We extracted the input JSON string, checked that it is valid, but couldn't reproduce the failure offline.
It would be good if someone could point out where we did wrong. Thanks in advance.

Why does this exception appears when reading a file, but not when storing in it?

I'm currently working on a project with MFC and I noticed something weird that apparently has been there for a couple of years. When I launch the .exe of the program, it will do n number of things including reading a .DAT file and storing it as well. If the file doesn't exists, the program will try to find it with no luck throwing this CFile exception: The file could not be located. Which is correct because it doesn't exists. I have to do some operations first to generate that file, the storing process works fine. When the file exists and I run the program again, it's supposed read the file but this CArchive exception shows up: Invalid file format. And I don't understand why.
This is the Serialize():
//Fruits.cpp
void CFruits::Serialize(CArchive &ar)
{
int nVersion = 0;
CObject::Serialize(ar);
ar.SerializeClass(GetRuntimeClass());
if(ar.IsStoring())
{
ar.Write(&m_bInit,sizeof(bool));
ar.Write(&m_bYummy,sizeof(bool));
ar.Write(&m_bAcid, sizeof(bool));
ar.Write(&m_bFresh,sizeof(bool));
...
...
...
ar<<m_cType;
ar<<m_cColour;
ar<<m_cFlavor;
ar<<m_cPrice;
ar<<m_cQuantity;
}
else
{
nVersion = ar.GetObjectSchema();
ar.Read(&m_bInit,sizeof(bool));
ar.Read(&m_bYummy,sizeof(bool));
ar.Read(&m_bAcid, sizeof(bool));
ar.Read(&m_bFresh,sizeof(bool));
...
...
...
if( nVersion >= 2 || nVersion < 0)
ar<<m_cType;
else
m_cType=0;
if (nVersion >= 3 || nVersion < 0)
ar<<m_cColour;
else
m_cColour=0;
if (nVersion >= 4 || nVersion < 0)
ar<<m_cFlavor;
else
ar<<m_cFlavor=0;
if( nVersion >= 5 || nVersion < 0)
{
ar<<m_cPrice;
ar<<m_cQuantity;
}
else
{
m_cPrice=0;
m_cQuantity=0;
}
}
m_oSales.Serialize(ar);
m_oAdmin.Serialize(ar);
...
...
}
IMPLEMENT_SERIAL(CVehiculo,CObject,VERSIONABLE_SCHEMA | 6)
This is the SerializeElements:
//Fruits.cpp
void AFXAPI SerializeElements(CArchive &ar,CFruits * fruits,int ncount)
{
try
{
for(cont=0;cont<ncount;cont++)
fruits[cont].Serialize(ar);
}
catch(CArchiveException *AE)
{
//Here it stores the exception in a Log. Exception 5
}
}
The serializeElements is used to store and read the file n times, as declared here in the header file of fruits:
//Fruits.h
class CFruits : public CObject
{
public:
CFruits();
CFruits(const CFruits &O);
virtual ~CFruits();
void operator = (const CFruits &O);
void Serialize(CArchive &ar);
protected:
DECLARE_SERIAL(CFruits)
};
void AFXAPI SerializeElements(CArchive &ar,CFruits * fruits,int ncount);
typedef CArray<CFruits, CFruitso&> TArrayFruits;
The values of this Array, and the methods used to call the serialize are defined here in my main function:
//main.h
#include "CFruits.h"
class CMain : public CDialog
{
// Construction
public:
enum T_Fruits { eFruitsOnLine, eFruitsIng, eFruitsTra, eFruitsAnt, eFruitsP3, eFruitsP2, eFruitsP1, eFruitsC1, eFruitsC0, eFruitsEscape, eFruitsVideo};
private:
void StoreFruits();
void ReadFruits();
The SerializeElements for-loop is supposed to run 11 times, but I noticed that it only does it 1 time, then the Schema version changes to -1, (originally 6 cause I managed to trace the value). This happens only when reading the file.
I've tried the following:
I can't use debug so I have to use Logs, I placed a Log after every sentence in the Serialize() function, I found what seems to be the issue, this line:
ar.SerializeClass(GetRuntimeClass());
I used a try-catch and found that when that sentence happens, it throws the exception so, it doesn't continue reading. That is the moment where the version changes to -1. I tried to change that to:
ar.SerializeClass(RUNTIME_CLASS(CFruits));
Is the same result, I've read many forums trying to find the answer but I can't seem to do so. I've read the documentation and I found this here:
https://learn.microsoft.com/en-us/cpp/mfc/reference/carchive-class?view=vs-2019#serializeclass
Like ReadClass, SerializeClass verifies that the archived class
information is compatible with your runtime class. If it is not
compatible, SerializeClass will throw a CArchiveException.
But it doesn't make sense to me, because it doesn't fail storing. Should I look into something else?
Thank you
EDIT:
I'm posting the Store and Read methods
void CMain::ReadFruits()
{
CString CSerror, sFileName;
CString sDebug;
try
{
sFileName.Format("FRUITS%03d.DAT",GetNumT());
CFile fFruitsTag(sFileName,CFile::modeRead);
CArchive ar(&fFruitsTag,CArchive::load);
m_vFruits.Serialize(ar);
ar.Close();
fFruitsTag.Close();
}
catch(CFileException *FE)
{
...
}
catch(CArchiveException *AE)
{
...
}
}
void CMain::StoreFruits()
{
CString CSerror, sFileName;
try
{
if(!m_bStoringFruits)
{
sFileName.Format("FRUITS%03d.DAT",GetNumT());
m_bStoringFruits=true;
CFile fFruitsTag(sFileName,CFile::modeCreate|CFile::modeWrite);
CArchive ar(&fFruitsTag,CArchive::store);
m_vFruits.Serialize(ar);
ar.Close();
fFruitsTag.Close();
m_bStoringFruits=false;
}
}
catch(CFileException *FE)
{
...
}
catch(CArchiveException *AE)
{
...
}
catch(CException *e)
{
...
}
}

Is there an elegant way to cascade-merge two JSON trees using jsoncpp?

I am using jsoncpp to read settings from a JSON file.
I would like to have two cascading settings file, say MasterSettings.json and LocalSettings.json where LocalSettings is a subset of MasterSettings. I would like to load MasterSettings first and then LocalSettings. Where LocalSettings has a value that differs from MasterSettings, that value would overwrite the one from MasterSettings. Much like the cascade in CSS.
Is there any elegant way to do this with jsoncpp?
I'm going to assume your settings files are JSON objects.
As seen here, when JSONCpp parses a file, it clears the contents of the root node. This mean that trying to parse a new file on top of the old one won't preserve the old data. However, if you parse both files into separate Json::Value nodes, it's straight forward to recursively copy the values yourself by iterating over the keys in the second object using getMemberNames.
// Recursively copy the values of b into a. Both a and b must be objects.
void update(Json::Value& a, Json::Value& b) {
if (!a.isObject() || !b.isObject()) return;
for (const auto& key : b.getMemberNames()) {
if (a[key].isObject()) {
update(a[key], b[key]);
} else {
a[key] = b[key];
}
}
}
I know it has been a while. but...
In addition to the correct answer and the commentary, here is a code version for those who use a older g++ version:
void jsonMerge(Json::Value &a, Json::Value &b) {
if (!a.isObject() || !b.isObject()) return;
vector<string> member_name = b.getMemberNames();
string key = "";
for (unsigned i = 0, len = member_name.size(); i < len; i++) {
key = member_name[i];
if (!a[key].isNull() && a[key].type() == Json::objectValue && b[key].type() == Json::objectValue) {
jsonMerge(a[key], b[key]);
} else {
a[key] = b[key];
}
}
member_name.clear();
}

File search APIs on Linux

In my project, I need to show all files on user's drive filtered by the filename with a text line. Are there any APIs to do such thing?
On Windows, I know, there're FindFirstFile and FindNextFile functions in WinAPI.
I use C++/Qt.
There's ftw() and linux has fts()
Besides those, you can iterate directories, using e.g. opendir8/readdir()
Qt provides the QDirIterator class:
QDirIterator iter("/", QDirIterator::Subdirectories);
while (iter.hasNext()) {
QString current = iter.next();
// Do something with 'current'...
}
If you are looking for a Unix command, you could do this :
find source_dir -name 'regex'
If you want to do it C++ style, I'd suggest to use boost::filesystem. It's a very powerfull cross platform library.
Of course, you will have to add an additional library.
Here is an example :
std::vector<std::string> list_files(const std::string& root, const bool& recursive, const std::string& filter, const bool& regularFilesOnly)
{
namespace fs = boost::filesystem;
fs::path rootPath(root);
// Throw exception if path doesn't exist or isn't a directory.
if (!fs::exists(rootPath)) {
throw std::exception("rootPath does not exist");
}
if (!fs::is_directory(rootPath)) {
throw std::exception("rootPath is not a directory.");
}
// List all the files in the directory
const std::regex regexFilter(filter);
auto fileList = std::vector<std::string>();
fs::directory_iterator end_itr;
for( fs::directory_iterator it(rootPath); it != end_itr; ++it) {
std::string filepath(it->path().string());
// For a directory
if (fs::is_directory(it->status())) {
if (recursive && it->path().string() != "..") {
// List the files in the directory
auto currentDirFiles = list_files(filepath, recursive, filter, regularFilesOnly);
// Add to the end of the current vector
fileList.insert(fileList.end(), currentDirFiles.begin(), currentDirFiles.end());
}
} else if (fs::is_regular_file(it->status())) { // For a regular file
if (filter != "" && !regex_match(filepath, regexFilter)) {
continue;
}
} else {
// something else
}
if (regularFilesOnly && !fs::is_regular_file(it->status())) {
continue;
}
// Add the file or directory to the list
fileList.push_back(filepath);
}
return fileList;
}
you can also use glob
http://man7.org/linux/man-pages/man3/glob.3.html
has the advantage of existing on a lot of Unices (Solaris for sure) as it is part of POSIX.
Ok, it's not C++ but pure C.
Look man find. find supports filtering by a mask ( -name option for examole)

directory structures C++

C:\Projects\Logs\RTC\MNH\Debug
C:\Projects\Logs\FF
Is there an expression/string that would say go back until you find "Logs" and open it? (assuming you were always below it)
The same executable is run out of "Debug", "MNH" or "FF" at different times, the executable always should save it's log files into "Logs".
What expression would get there WITHOUT referring to the entire path C:\Projects\Logs?
Thanks.
You might have luck using the boost::filesystem library.
Without a compiler (and ninja-copies from boost documentation), something like:
#include <boost/filesystem.hpp>
namespace boost::filesystem = fs;
bool contains_folder(const fs::path& path, const std::string& folder)
{
// replace with recursive iterator to check within
// sub-folders. in your case you just want to continue
// down parents paths, though
typedef fs::directory_iterator dir_iter;
dir_iter end_iter; // default construction yields past-the-end
for (dir_iter iter(path); iter != end_iter; ++iter)
{
if (fs::is_directory(iter->status()))
{
if (iter->path().filename() == folder)
{
return true;
}
}
}
return false;
}
fs::path find_folder(const fs::path& path, const std::string& folder)
{
if (contains_folder(path, folder))
{
return path.string() + folder;
}
fs::path searchPath = path.parent_path();
while (!searchPath.empty())
{
if (contains_folder(searchPath, folder))
{
return searchPath.string() + folder;
}
searchPath = searchPath.parent_path();
}
return "":
}
int main(void)
{
fs::path logPath = find_folder(fs::initial_path(), "Log");
if (logPath.empty())
{
// not found
}
}
For now this is completely untested :)
It sounds like you're asking about a relative path.
If the working directory is C:\Projects\Logs\RTC\MNH\Debug\, the path ..\..\..\file represents a file in the Logs directory.
If you might be in either C:\Projects\Logs\RTC\MNH\ or C:\Projects\Logs\RTC\MNH\Debug\, then no single expression will get you back to Logs from either place. You could try checking for the existence of ..\..\..\..\Logs and if that doesn't exist, try ..\..\..\Logs, ..\..\Logs and ..\Logs, which one exists would tell you how "deep" you are and how many ..s are required to get you back to Logs.