Windows: File rename and directory iteration clash - c++

I'm using boost::filesystem to rename a file like this:
boost::filesystem::rename(tmpFileName, targetFile);
tmpFileName / targetFile are of type boost::filsystem::path.
While doing this, I iterate over the directory using this code in another thread:
directory_iterator end_itr;
for (directory_iterator itr(dirInfoPath); itr != end_itr; ++itr)
{
path currentPath = itr->path();
if (is_directory(itr->status()))
{
// skip directories
}
else
{
std::string file_name = currentPath.leaf();
if (!boost::algorithm::starts_with(file_name, "new")
&& !boost::algorithm::starts_with(file_name, "finished")
&& boost::algorithm::ends_with(file_name, ".info"))
{
// save found filename in some variable
return true;
}
}
}
When this code is executed, I get an exception while renaming:
boost::filesystem::rename: The process cannot access the file because it is being used by another process
Is it possible that the iteration and the rename operation clash, because they both access the directory inode, or do I have some other problem?

code you provided doesn't contain any file open operations, so it cannot lock the file. you iterate over directory and renaming file, right? so it's possible this file is really used by another application like file viewer or something else, it's quite typical error. or you have it opened in your app somewhere else

Related

Check if a file is in the current directory or its children using C++

I am writing a small HTTP web server in C++ as part of a hobby project, and I need to serve static files. However, one problem I want to avoid is a user typing in, for example, http://example.com/../passwd. To ensure that users don't enter in a malicious path, I want to check if a path entered is in the current parent directory.
My current approach is to use std::filesystem::directory_iterator, checking if the provided one is a file and if its the same as the one provided. However, this is very slow and clunky, and I believe that there is a better solution.
A better solution would be to simply append the user's specified path to your desired root path, canonicalize the result, and then check if the result is still within the root path.
For example, when the user requests http://example.com/../passwd, your server will see a request like:
GET /../passwd HTTP/1.1
So, append just "../passwd" to your root folder and compare the result, for example:
#include <string>
#include <filesystem>
namespace fs = std::filesystem;
bool isSubDir(fs::path p, fs::path root)
{
static const fs::path emptyPath;
while (p != emptyPath) {
if (fs::equivalent(p, root)) {
return true;
}
p = p.parent_path();
}
return false;
}
...
fs::path requestedPath = fs::path("../passwd").make_preferred();
fs::path parentPath = fs::path("C:\\webroot\\");
fs::path actualPath = fs::canonical(parentPath / requestedPath);
if (isSubDir(actualPath, parentPath))
{
// serve file at actualPath as needed...
}
else
{
// send error reply...
}

std::filesystem::copy() only copies files in folder

I am trying to copy a folder to another folder using std::filesystem::copy(), so far it only copies the files and folders within the folder I'm trying to move over, instead of the folder itself. Any ideas why?
I know this could be done manually by creating a new directory using std::filesystem::create_directory(), but it won't carry over the security info and permissions from the original folder.
EDIT:
path = C:\Users\Test\Folder
nPath = C:\Users\Test\Movehere
boolean CopyToPath(std::string path, std::string nPath) {
if (fs::exists(path) && fs::exists(nPath)) {
try {
//path = Folder To Copy, nPath = Destination
fs::copy(path, nPath, fs::copy_options::overwrite_existing | fs::copy_options::recursive);
return true;
}
catch (fs::filesystem_error e) {
std::cout << e.what() << "\n";
return false;
}
}
else
return false;
}
This is expected behavior, as documented at https://en.cppreference.com/w/cpp/filesystem/copy:
Otherwise, if from is a directory and either options has copy_options::recursive or is copy_options::none,
If to does not exist, first executes create_directory(to, from) (creates the new directory with a copy of the old directory's attributes)
Then, whether to already existed or was just created, iterates over the files contained in from as if by for (const std::filesystem::directory_entry& x : std::filesystem::directory_iterator(from)) and for each directory entry, recursively calls copy(x.path(), to/x.path().filename(), options | in-recursive-copy), where in-recursive-copy is a special bit that has no other effect when set in options. (The sole purpose of setting this bit is to prevent recursive copying subdirectories if options is copy_options::none.)
In other words, copy() does not copy the source directory itself, only the contents of the directory.
If your goal is to make a copy of Folder itself inside of Movehere, ie C:\Users\Test\Movehere\Folder, then you will have to extract Folder from the source directory and append it to the target path, eg:
fs::path src = "C:\\Users\\Test\\Folder";
fs::path dest = "C:\\Users\\Test\\Movehere";
dest /= src.filename();
fs::create_directory(dest, src);
// only because CopyToPath() requires this due
// to its use of fs::exists(), instead of letting
// fs::copy() create it...
CopyToPath(src, dest);

Append files to an existing zip file with Poco::Zip

After successfully compress the folder, here is my situation :
If append = true and overWrite = false I have to check whether if the target zip file exists or not if existed I will check the existed zip file which files it doesn't contain and append new file from the source folder to it.
My question is:
How can I open the zip file and put it to the compress object? or which others library in Poco should I use to open zip stream? I'm trying to use std::ifstream but Poco::zip::Compress doesn't seem to receive an std::ifstream
I surely have to modify the Poco source code itself to match with my requirement. Thanks in advance.
void ZipFile(string source, string target, List extensions, bool append, bool overWrite)
{
Poco::File tempFile(source);
if (tempFile.exists())
{
if (Poco::File(target).exists() && append && !overWrite) {
fs::path targetPath = fs::path(target);
std::ifstream targetFileStream(targetPath.string(), std::ios::binary);
std::ofstream outStream(target, ios::binary);
CompressEx compress(outStream, false, false);
if (tempFile.isDirectory())
{
Poco::Path sourceDir(source);
sourceDir.makeDirectory();
compress.addRecursive(sourceDir, Poco::Zip::ZipCommon::CompressionMethod::CM_AUTO,
Poco::Zip::ZipCommon::CL_NORMAL, false);
}
else if (tempFile.isFile())
{
Poco::Path path(tempFile.path());
compress.addFile(path, path.getFileName(), Poco::Zip::ZipCommon::CompressionMethod::CM_AUTO,
Poco::Zip::ZipCommon::CL_NORMAL);
}
compress.close(); // MUST be done to finalize the Zip file
outStream.close();
}
}
No need to modify the Poco source code. Poco allows you to get the contents of an archive and add files to it.
First, open the target archive to check which files are already in there:
Poco::ZipArchive archive(targetFileStream);
Then collect all files you want to add, that are not in the archive, yet:
std::vector<fs::path> files;
if (fs::is_directory(source)) {
for(auto &entry : fs::recursive_directory_iterator())
// if entry is file and not in zip
if (fs::is_regular_file(entry)
&& archive.findHeader(fs::relative(entry.path, source)) == archive.headerEnd()) {
files.push_back(entry.path);
}
} else if (fs::is_regular_file(entry)
&& archive.findHeader(source) == archive.headerEnd()) {
files.push_back(source);
}
Finally, add the files to your zip
Poco::Zip::ZipManipulator manipulator(target, false);
for(auto &file : files)
manipulator.addFile(fs::relative(file, source), file,
Poco::Zip::ZipCommon::CompressionMethod::CM_AUTO,
Poco::Zip::ZipCommon::CL_NORMAL);
I had no opportunity to test this. So try it out and see what needs to be done to make it work.

Rename a non-empty directory in an archive using libarchive

I'm trying to rename the entries of an archive using the libarchive library.
In particular I'm using the function archive_entry_set_pathname.
Files and empty directories are correctly renamed, but unfortunately this is not working if a directory is not empty: instead of being renamed, a new empty directory with the new name is created as sibling of the target directory, which has the old name.
Relevant code snippet:
...
while (archive_read_next_header(inputArchive, &entry) == ARCHIVE_OK) {
if (file == QFile::decodeName(archive_entry_pathname(entry))) {
// FIXME: not working with non-empty directories
archive_entry_set_pathname(entry, QFile::encodeName(newPath));
}
int header_response;
if ((header_response = archive_write_header(outputArchive, entry)) == ARCHIVE_OK) {
... // write the (new) outputArchive on disk
}
}
What's wrong with non-empty directories?
In an archive, the files are stored with their full path names relative to the root of the archive. Your code only matches the directory entry, you also need to match all entries below that directory and rename them. I'm no Qt expert and I haven't tried this code, but you will get the idea.
QStringLiteral oldPath("foo/");
QStringLiteral newPath("bar/");
while (archive_read_next_header(inputArchive, &entry) == ARCHIVE_OK) {
QString arEntryPath = QFile::decodeName(archive_entry_pathname(entry));
if(arEntryPath.startsWith(oldPath) {
arEntryPath.replace(0, oldPath.length(), newPath);
archive_entry_set_pathname(entry, QFile::encodeName(arEntryPath));
}
int header_response;
if ((header_response = archive_write_header(outputArchive, entry)) == ARCHIVE_OK) {
... // write the (new) outputArchive on disk
}
}

filesystem directory_iterator not returning full file path

EDIT: I'm using std::tr2::sys not boost's filesytem
I'm working on a simple app that scans directories on the computer and returns statistics about those files. There are two ways that scan can be called: recursively and non-recursively. A path is passed into the scan method along with a flag for whether or not the scan should be done recursively. Given the path folder the two implementations look something like this:
directory_iterator start( folder ), end;
for( ; start != end; ++start ) {
//Some code in here
// get the path like this:
auto path = start->path();
}
or
recursive_directory_iterator start( folder ), end;
for( ; start != end; ++start ) {
//Some code in here
// get the path like this:
auto path = start->path();
}
The problem I'm encountering is that in the block that uses the regular directory_iterator when I try and grab the path it will return only the file name ie. "myTextFile.txt" where as getting it from the recursive_directory_iterator returns the full path to the file on the system. In each case the value of folder is the same (a full file path as well). Is there a reason the front part of the path is getting chopped off or is there some way I can get the full file path from just the abbreviated one that I'm getting back from directory_iterator?
For full path you need to use start->path().directory_string()
directory_iterator end;
for (directory_iterator start(p);
start != end;
++start)
{
if (is_directory(start->status()))
{
//A directory
}
if (is_regular_file(start->status()))
{
std::cout << start->path().directory_string()<< "\n";
}
}
Play around with following based on your need, refer boost docs for more details, however names their suggest all.
start->path().branch_path();
start->path().directory_string();
start->path().file_string();
start->path().root_path();
start->path().parent_path();
start->path().string();
start->path().basename();
start->path().filename();
start->path().root_name();
start->path().relative_path();
Use the complete function:
auto full_path = std::tr2::sys::complete(path, folder);
Another simple way to do it would be be to simply append the filename to the path (tested on MSVC2012 with the <filesystem> in the std::tr2::sys namespace):
for (; start != end; ++start) {
auto path = start->path();
auto full_path = folder / path;
}