Skip to content

Commit

Permalink
Merge pull request #6101 from rstudio/bugfix/replace-respect-permissions
Browse files Browse the repository at this point in the history
Check for write permissions before performing a global replace
  • Loading branch information
MariaSemple committed Feb 4, 2020
2 parents 66fad93 + 566a218 commit 242ac8d
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 17 deletions.
40 changes: 25 additions & 15 deletions src/cpp/session/modules/SessionFind.cpp
Expand Up @@ -525,6 +525,9 @@ class GrepOperation : public boost::enable_shared_from_this<GrepOperation>
!tempReplaceFile_.getAbsolutePath().empty() &&
outputStream_->good())
{
Error error = FilePath(currentFile_).testWritePermissions();
if (error)
return error;
std::string line;
while (std::getline(*inputStream_, line))
{
Expand All @@ -534,7 +537,7 @@ class GrepOperation : public boost::enable_shared_from_this<GrepOperation>
outputStream_->flush();
inputStream_.reset();
outputStream_.reset();
Error error = tempReplaceFile_.move(FilePath(currentFile_));
error = tempReplaceFile_.move(FilePath(currentFile_));
currentFile_.clear();
return error;
}
Expand Down Expand Up @@ -593,15 +596,18 @@ class GrepOperation : public boost::enable_shared_from_this<GrepOperation>

Error initializeFileForReplace(FilePath file)
{
Error error;
fileSuccess_ = false;
Error error = file.testWritePermissions();
if (error)
return error;
if (!findResults().preview())
{
tempReplaceFile_ = module_context::tempFile("replace", "txt");
error = tempReplaceFile_.openForWrite(outputStream_);
if (error)
return error;
}
if (!error)
error = file.openForRead(inputStream_);
error = file.openForRead(inputStream_);
if (!error)
{
fileSuccess_ = true;
Expand Down Expand Up @@ -872,6 +878,7 @@ class GrepOperation : public boost::enable_shared_from_this<GrepOperation>
findResults().replacePattern().empty()))
{
FilePath fullPath(module_context::resolveAliasedPath(file));
// check if we are looking at a new file
if (currentFile_.empty() || currentFile_ != fullPath.getAbsolutePath())
{
if (!currentFile_.empty())
Expand All @@ -881,18 +888,19 @@ class GrepOperation : public boost::enable_shared_from_this<GrepOperation>
addReplaceErrorMessage(error.asString(), &errorMessage,
&replaceMatchOn, &replaceMatchOff, &fileSuccess_);
}
else if (!fileSuccess_)
{
// the first time a file is processed it gets a more detailed initialization error
addReplaceErrorMessage("Cannot perform replace", &errorMessage,
&replaceMatchOn, &replaceMatchOff, &fileSuccess_);
}
if (!fileSuccess_ || lineInfo.decodedPreview.length() > MAX_LINE_LENGTH)
{
// if we failed for any reason, update the progress
if (!findResults().preview())
findResults().replaceProgress()->
addUnits(gsl::narrow_cast<int>(matchOn.getSize()));
if (!fileSuccess_ && inputLineNum_ != 0)
{
// the first time a file is processed it gets a more detailed initialization error
addReplaceErrorMessage("Cannot perform replace", &errorMessage,
&replaceMatchOn, &replaceMatchOff, &fileSuccess_);
}
else if (fileSuccess_)
if (fileSuccess_)
{
bool lineSuccess;
addReplaceErrorMessage("Line exceeds maximum character length for replace",
Expand Down Expand Up @@ -1098,9 +1106,10 @@ class GrepOptions : public boost::noncopyable
LOG_DEBUG_MESSAGE("Exclude files contain non-string value");
else
{
if (filePattern.getString().compare("gitExclusions") == 0)
std::string excludeText = boost::algorithm::trim_copy(filePattern.getString());
if (excludeText.compare("gitExclusions") == 0)
gitFlag_ = true;
else
else if (!excludeText.empty())
excludeArgs_.push_back("--exclude=" + filePattern.getString());
}
}
Expand All @@ -1115,9 +1124,10 @@ class GrepOptions : public boost::noncopyable
LOG_DEBUG_MESSAGE("Include files contain non-string value");
else
{
if (filePattern.getString().compare("package") == 0)
std::string includeText = boost::algorithm::trim_copy(filePattern.getString());
if (includeText.compare("package") == 0)
packageFlag_ = true;
else
else if (!includeText.empty())
includeArgs_.push_back("--include=" + filePattern.getString());
}
}
Expand Down
60 changes: 58 additions & 2 deletions src/cpp/shared_core/FilePath.cpp
Expand Up @@ -1117,9 +1117,9 @@ Error FilePath::moveIndirect(const FilePath& in_targetPath) const

Error FilePath::openForRead(std::shared_ptr<std::istream>& out_stream) const
{
std::istream* pResult = nullptr;
try
{
std::istream* pResult = nullptr;
#ifdef _WIN32
using namespace boost::iostreams;
HANDLE hFile = ::CreateFileW(m_impl->Path.wstring().c_str(),
Expand Down Expand Up @@ -1155,6 +1155,8 @@ Error FilePath::openForRead(std::shared_ptr<std::istream>& out_stream) const
}
catch(const std::exception& e)
{
delete pResult;

Error error = systemError(boost::system::errc::io_error,
ERROR_LOCATION);
error.addProperty("what", e.what());
Expand All @@ -1168,9 +1170,9 @@ Error FilePath::openForRead(std::shared_ptr<std::istream>& out_stream) const

Error FilePath::openForWrite(std::shared_ptr<std::ostream>& out_stream, bool in_truncate) const
{
std::ostream* pResult = nullptr;
try
{
std::ostream* pResult = nullptr;
#ifdef _WIN32
using namespace boost::iostreams;
HANDLE hFile = ::CreateFileW(m_impl->Path.wstring().c_str(),
Expand Down Expand Up @@ -1212,6 +1214,7 @@ Error FilePath::openForWrite(std::shared_ptr<std::ostream>& out_stream, bool in_
}
catch(const std::exception& e)
{
delete pResult;
Error error = systemError(boost::system::errc::io_error,
ERROR_LOCATION);
error.addProperty("what", e.what());
Expand Down Expand Up @@ -1289,6 +1292,59 @@ void FilePath::setLastWriteTime(std::time_t in_time) const
}
}

Error FilePath::testWritePermissions() const
{
std::ostream* pStream = nullptr;
try
{
#ifdef _WIN32
using namespace boost::iostreams;
HANDLE hFile = ::CreateFileW(m_impl->Path.wstring().c_str(),
FILE_APPEND_DATA,
0, // exclusive access
nullptr,
OPEN_EXISTING,
0,
nullptr);
if (hFile == INVALID_HANDLE_VALUE)
{
Error error = LAST_SYSTEM_ERROR();
error.addProperty("path", getAbsolutePath());
return error;
}
file_descriptor_sink fd;
fd.open(hFile, close_handle);
pStream = new boost::iostreams::stream<file_descriptor_sink>(fd);
#else
using std::ios_base;
ios_base::openmode flags = ios_base::in | ios_base::out | ios_base::binary;
pStream = new std::ofstream(getAbsolutePath().c_str(), flags);
#endif

if (!(*pStream))
{
delete pStream;

Error error = systemError(boost::system::errc::no_such_file_or_directory, ERROR_LOCATION);
error.addProperty("path", getAbsolutePath());
return error;
}
}
catch(const std::exception& e)
{
delete pStream;

Error error = systemError(boost::system::errc::io_error,
ERROR_LOCATION);
error.addProperty("what", e.what());
error.addProperty("path", getAbsolutePath());
return error;
}

delete pStream;
return Success();
}

// PathScope Classes ===================================================================================================
struct PathScopeImpl
{
Expand Down
10 changes: 10 additions & 0 deletions src/cpp/shared_core/include/shared_core/FilePath.hpp
Expand Up @@ -609,6 +609,16 @@ class FilePath
*/
void setLastWriteTime(std::time_t in_time = ::time(nullptr)) const;

/**
* @brief Checks if a file can be written to by opening the file.
*
* To be successful, the file must already exist on the system.
* If write access is not absolutely necessary, use isFileWriteable from FileMode.hpp.
*
* @return Success if file can be written to; system error otherwise (e.g. EPERM, ENOENT, etc.)
*/
Error testWritePermissions() const;

private:
// The private implementation of FilePath.
PRIVATE_IMPL_SHARED(m_impl);
Expand Down

0 comments on commit 242ac8d

Please sign in to comment.