summaryrefslogtreecommitdiff
path: root/zen/file_access.cpp
diff options
context:
space:
mode:
authorDaniel Wilhelm <daniel@wili.li>2016-03-16 21:31:24 +0100
committerDaniel Wilhelm <daniel@wili.li>2016-03-16 21:31:24 +0100
commit89621addb4a7c87d2e3f3e7462e3c690cf71de71 (patch)
tree008b5dea7624ee1eeb57ff82c45fdf1afcab3b08 /zen/file_access.cpp
parent7.5 (diff)
downloadFreeFileSync-89621addb4a7c87d2e3f3e7462e3c690cf71de71.tar.gz
FreeFileSync-89621addb4a7c87d2e3f3e7462e3c690cf71de71.tar.bz2
FreeFileSync-89621addb4a7c87d2e3f3e7462e3c690cf71de71.zip
7.6
Diffstat (limited to 'zen/file_access.cpp')
-rw-r--r--zen/file_access.cpp170
1 files changed, 86 insertions, 84 deletions
diff --git a/zen/file_access.cpp b/zen/file_access.cpp
index e04673d3..2ca373aa 100644
--- a/zen/file_access.cpp
+++ b/zen/file_access.cpp
@@ -8,7 +8,6 @@
#include <map>
#include <algorithm>
#include <stdexcept>
-#include "int64.h"
#include "file_traverser.h"
#include "scope_guard.h"
#include "symlink_target.h"
@@ -17,6 +16,7 @@
#ifdef ZEN_WIN
#include <Aclapi.h>
+ #include "int64.h"
#include "privilege.h"
#include "long_path_prefix.h"
#include "win_ver.h"
@@ -24,7 +24,6 @@
#include <zen/vista_file_op.h> //requires COM initialization!
#endif
-
#elif defined ZEN_LINUX
#include <sys/vfs.h> //statfs
#include <sys/time.h> //lutimes
@@ -135,7 +134,7 @@ bool zen::somethingExists(const Zstring& itemPath)
namespace
{
#ifdef ZEN_WIN
-bool isFatDrive(const Zstring& filePath) //throw()
+bool isFatDrive(const Zstring& filePath) //noexcept
{
const DWORD bufferSize = MAX_PATH + 1;
std::vector<wchar_t> buffer(bufferSize);
@@ -202,12 +201,17 @@ std::uint64_t zen::getFilesize(const Zstring& filePath) //throw FileError
THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(filePath)), L"CreateFile");
ZEN_ON_SCOPE_EXIT(::CloseHandle(hFile));
- //why not use ::GetFileSizeEx() instead???
- BY_HANDLE_FILE_INFORMATION fileInfoHnd = {};
- if (!::GetFileInformationByHandle(hFile, &fileInfoHnd))
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(filePath)), L"GetFileInformationByHandle");
+ LARGE_INTEGER fileSize = {};
+ if (!::GetFileSizeEx(hFile, //_In_ HANDLE hFile,
+ &fileSize)) //_Out_ PLARGE_INTEGER lpFileSize
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(filePath)), L"GetFileSizeEx");
+ return fileSize.QuadPart;
- return get64BitUInt(fileInfoHnd.nFileSizeLow, fileInfoHnd.nFileSizeHigh);
+ //alternative:
+ //BY_HANDLE_FILE_INFORMATION fileInfoHnd = {};
+ //if (!::GetFileInformationByHandle(hFile, &fileInfoHnd))
+ // THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(filePath)), L"GetFileInformationByHandle");
+ //return get64BitUInt(fileInfoHnd.nFileSizeLow, fileInfoHnd.nFileSizeHigh);
#elif defined ZEN_LINUX || defined ZEN_MAC
struct ::stat fileInfo = {};
@@ -312,53 +316,56 @@ void zen::removeDirectorySimple(const Zstring& dirPath) //throw FileError
return;
}
#endif
-
throw FileError(replaceCpy(_("Cannot delete directory %x."), L"%x", fmtPath(dirPath)), formatSystemError(functionName, ec));
}
- //may spuriously fail with ERROR_DIR_NOT_EMPTY(145) even though all child items have
- //successfully been *marked* for deletion, but some application still has a handle open!
- //e.g. Open "C:\Test\Dir1\Dir2" (filled with lots of files) in Explorer, then delete "C:\Test\Dir1" via ::RemoveDirectory() => Error 145
- //Sample code: http://us.generation-nt.com/answer/createfile-directory-handles-removing-parent-help-29126332.html
+ /*
+ Windows: may spuriously fail with ERROR_DIR_NOT_EMPTY(145) even though all child items have
+ successfully been *marked* for deletion, but some application still has a handle open!
+ e.g. Open "C:\Test\Dir1\Dir2" (filled with lots of files) in Explorer, then delete "C:\Test\Dir1" via ::RemoveDirectory() => Error 145
+ Sample code: http://us.generation-nt.com/answer/createfile-directory-handles-removing-parent-help-29126332.html
+ Alternatives: 1. move file/empty folder to some other location, then DeleteFile()/RemoveDirectory()
+ 2. use CreateFile/FILE_FLAG_DELETE_ON_CLOSE *without* FILE_SHARE_DELETE instead of DeleteFile() => early failure
+ */
}
namespace
{
-void removeDirectoryImpl(const Zstring& dirPath) //throw FileError
+void removeDirectoryImpl(const Zstring& folderPath) //throw FileError
{
- assert(dirExists(dirPath)); //[!] no symlinks in this context!!!
- //attention: check if dirPath is a symlink! Do NOT traverse into it deleting contained files!!!
+ assert(dirExists(folderPath)); //[!] no symlinks in this context!!!
+ //attention: check if folderPath is a symlink! Do NOT traverse into it deleting contained files!!!
- std::vector<Zstring> fileList;
- std::vector<Zstring> dirLinkList;
- std::vector<Zstring> dirList;
+ std::vector<Zstring> filePaths;
+ std::vector<Zstring> folderSymlinkPaths;
+ std::vector<Zstring> folderPaths;
//get all files and directories from current directory (WITHOUT subdirectories!)
- traverseFolder(dirPath,
- [&](const FileInfo& fi) { fileList.push_back(fi.fullPath); },
- [&](const DirInfo& di) { dirList .push_back(di.fullPath); }, //defer recursion => save stack space and allow deletion of extremely deep hierarchies!
+ traverseFolder(folderPath,
+ [&](const FileInfo& fi) { filePaths.push_back(fi.fullPath); },
+ [&](const DirInfo& di) { folderPaths .push_back(di.fullPath); }, //defer recursion => save stack space and allow deletion of extremely deep hierarchies!
[&](const SymlinkInfo& si)
{
#ifdef ZEN_WIN
if (dirExists(si.fullPath)) //dir symlink
- dirLinkList.push_back(si.fullPath);
+ folderSymlinkPaths.push_back(si.fullPath);
else //file symlink, broken symlink
#endif
- fileList.push_back(si.fullPath);
+ filePaths.push_back(si.fullPath);
},
[](const std::wstring& errorMsg) { throw FileError(errorMsg); });
- for (const Zstring& filePath : fileList)
+ for (const Zstring& filePath : filePaths)
removeFile(filePath); //throw FileError
- for (const Zstring& dirLinkPath : dirLinkList)
- removeDirectorySimple(dirLinkPath); //throw FileError
+ for (const Zstring& symlinkPath : folderSymlinkPaths)
+ removeDirectorySimple(symlinkPath); //throw FileError
//delete directories recursively
- for (const Zstring& subDirPath : dirList)
- removeDirectoryImpl(subDirPath); //throw FileError; call recursively to correctly handle symbolic links
+ for (const Zstring& subFolderPath : folderPaths)
+ removeDirectoryImpl(subFolderPath); //throw FileError; call recursively to correctly handle symbolic links
- removeDirectorySimple(dirPath); //throw FileError
+ removeDirectorySimple(folderPath); //throw FileError
}
}
@@ -442,7 +449,7 @@ void renameFile_sub(const Zstring& pathSource, const Zstring& pathTarget) //thro
}
#elif defined ZEN_LINUX || defined ZEN_MAC
- //rename() will never fail with EEXIST, but always overwrite!
+ //rename() will never fail with EEXIST, but always (atomically) overwrite! => equivalent to SetFileInformationByHandle() + FILE_RENAME_INFO::ReplaceIfExists
//=> Linux: renameat2() with RENAME_NOREPLACE -> still new, probably buggy
//=> OS X: no solution
@@ -458,7 +465,7 @@ void renameFile_sub(const Zstring& pathSource, const Zstring& pathTarget) //thro
throw FileError(errorMsg, errorDescr);
};
- if (!EqualFilePath()(pathSource, pathTarget)) //OS X: changing file name case is not an "already exists" error!
+ if (!equalFilePath(pathSource, pathTarget)) //OS X: changing file name case is not an "already exists" error!
if (somethingExists(pathTarget))
throwException(EEXIST);
@@ -511,7 +518,7 @@ Zstring findUnused8Dot3Name(const Zstring& filePath) //find a unique 8.3 short n
if (!somethingExists(output)) //ensure uniqueness
return output;
}
- throw std::runtime_error(std::string("100000000 files, one for each number, exist in this directory? You're kidding...") + utfCvrtTo<std::string>(pathPrefix) +
+ throw std::runtime_error(std::string("100,000,000 files, one for each number, exist in this directory? You're kidding...") + utfCvrtTo<std::string>(pathPrefix) +
"\n" + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__));
}
@@ -529,8 +536,8 @@ bool have8dot3NameClash(const Zstring& filePath)
if (!shortName.empty() &&
!longName .empty() &&
- EqualFilePath()(origName, shortName) &&
- !EqualFilePath()(shortName, longName))
+ equalFilePath(origName, shortName) &&
+ !equalFilePath(shortName, longName))
{
//for filePath short and long file name are equal and another unrelated file happens to have the same short name
//e.g. filePath == "TESTWE~1", but another file is existing named "TestWeb" with short name ""TESTWE~1"
@@ -896,9 +903,9 @@ void setFileTimeRaw(const Zstring& filePath, const struct ::timespec& modTime, P
#elif defined ZEN_MAC
struct AttrBufFileTimes
{
- std::uint32_t length;
- struct ::timespec createTime; //keep order; see docs!
- struct ::timespec writeTime; //
+ std::uint32_t length = 0;
+ struct ::timespec createTime = {}; //keep order; see docs!
+ struct ::timespec writeTime = {}; //
} __attribute__((aligned(4), packed));
@@ -915,7 +922,7 @@ void setFileTimeRaw(const Zstring& filePath,
attribs.bitmapcount = ATTR_BIT_MAP_COUNT;
attribs.commonattr = (createTime ? ATTR_CMN_CRTIME : 0) | ATTR_CMN_MODTIME;
- AttrBufFileTimes newTimes = {};
+ AttrBufFileTimes newTimes;
if (createTime)
{
newTimes.createTime.tv_sec = createTime->tv_sec;
@@ -944,7 +951,7 @@ void getFileTimeRaw(int fd, //throw FileError
attribs.bitmapcount = ATTR_BIT_MAP_COUNT;
attribs.commonattr = ATTR_CMN_CRTIME | ATTR_CMN_MODTIME;
- AttrBufFileTimes fileTimes = {};
+ AttrBufFileTimes fileTimes;
const int rv = ::fgetattrlist(fd, //int fd,
&attribs, //struct ::attrlist* attrList,
@@ -1080,7 +1087,7 @@ void copySecurityContext(const Zstring& source, const Zstring& target, ProcSymli
if (rv3 < 0)
THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write security context of %x."), L"%x", fmtPath(target)), L"setfilecon");
}
-#endif //HAVE_SELINUX
+#endif
//copy permissions for files, directories or symbolic links: requires admin rights
@@ -1340,7 +1347,9 @@ void zen::copyNewDirectory(const Zstring& sourcePath, const Zstring& targetPath,
bool copyFilePermissions)
{
#ifdef ZEN_WIN
- //special handling for volume root: trying to create existing root directory results in ERROR_ACCESS_DENIED rather than ERROR_ALREADY_EXISTS!
+auto getErrorMsg = [](const Zstring& path){ return replaceCpy(_("Cannot create directory %x."), L"%x", fmtPath(path)); };
+
+//special handling for volume root: trying to create existing root directory results in ERROR_ACCESS_DENIED rather than ERROR_ALREADY_EXISTS!
Zstring dirTmp = removeLongPathPrefix(endsWith(targetPath, FILE_NAME_SEPARATOR) ?
beforeLast(targetPath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE) :
targetPath);
@@ -1349,16 +1358,19 @@ void zen::copyNewDirectory(const Zstring& sourcePath, const Zstring& targetPath,
{
dirTmp += FILE_NAME_SEPARATOR; //we do not support "C:" to represent a relative path!
- const ErrorCode lastError = somethingExists(dirTmp) ? ERROR_ALREADY_EXISTS : ERROR_PATH_NOT_FOUND; //don't use dirExists() => harmonize with ErrorTargetExisting!
-
- const std::wstring errorMsg = replaceCpy(_("Cannot create directory %x."), L"%x", fmtPath(dirTmp));
- const std::wstring errorDescr = formatSystemError(L"CreateDirectory", lastError);
+ const DWORD ec = somethingExists(dirTmp) ? ERROR_ALREADY_EXISTS : ERROR_PATH_NOT_FOUND; //don't use dirExists() => harmonize with ErrorTargetExisting!
+ const std::wstring errorDescr = formatSystemError(L"CreateDirectory", ec);
- if (lastError == ERROR_ALREADY_EXISTS)
- throw ErrorTargetExisting(errorMsg, errorDescr);
- throw FileError(errorMsg, errorDescr); //[!] this is NOT a ErrorTargetPathMissing case!
+ if (ec == ERROR_ALREADY_EXISTS)
+ throw ErrorTargetExisting(getErrorMsg(dirTmp), errorDescr);
+ throw FileError(getErrorMsg(dirTmp), errorDescr); //[!] this is NOT a ErrorTargetPathMissing case!
}
+ //deliberately don't support creating irregular folders like "...." https://social.technet.microsoft.com/Forums/windows/en-US/ffee2322-bb6b-4fdf-86f9-8f93cf1fa6cb/
+ if (endsWith(targetPath, L' ') ||
+ endsWith(targetPath, L'.'))
+ throw FileError(getErrorMsg(targetPath), replaceCpy(_("%x is not a regular directory name."), L"%x", fmtPath(afterLast(targetPath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL))));
+
//don't use ::CreateDirectoryEx:
//- it may fail with "wrong parameter (error code 87)" when source is on mapped online storage
//- automatically copies symbolic links if encountered: unfortunately it doesn't copy symlinks over network shares but silently creates empty folders instead (on XP)!
@@ -1383,14 +1395,13 @@ void zen::copyNewDirectory(const Zstring& sourcePath, const Zstring& targetPath,
if (ec != ERROR_SUCCESS)
{
- const std::wstring errorMsg = replaceCpy(_("Cannot create directory %x."), L"%x", fmtPath(targetPath));
const std::wstring errorDescr = formatSystemError(L"CreateDirectory", ec);
if (ec == ERROR_ALREADY_EXISTS)
- throw ErrorTargetExisting(errorMsg, errorDescr);
+ throw ErrorTargetExisting(getErrorMsg(targetPath), errorDescr);
else if (ec == ERROR_PATH_NOT_FOUND)
- throw ErrorTargetPathMissing(errorMsg, errorDescr);
- throw FileError(errorMsg, errorDescr);
+ throw ErrorTargetPathMissing(getErrorMsg(targetPath), errorDescr);
+ throw FileError(getErrorMsg(targetPath), errorDescr);
}
}
@@ -1489,13 +1500,12 @@ void zen::copyNewDirectory(const Zstring& sourcePath, const Zstring& targetPath,
/*int rv =*/ ::copyfile(sourcePath.c_str(), targetPath.c_str(), 0, COPYFILE_XATTR);
#endif
- zen::ScopeGuard guardNewDir = zen::makeGuard([&] { try { removeDirectorySimple(targetPath); } catch (FileError&) {} }); //ensure cleanup:
+ ZEN_ON_SCOPE_FAIL(try { removeDirectorySimple(targetPath); }
+ catch (FileError&) {}); //ensure cleanup:
//enforce copying file permissions: it's advertized on GUI...
if (copyFilePermissions)
copyItemPermissions(sourcePath, targetPath, ProcSymlink::FOLLOW); //throw FileError
-
- guardNewDir.dismiss(); //target has been created successfully!
}
}
@@ -1529,7 +1539,8 @@ void zen::copySymlink(const Zstring& sourceLink, const Zstring& targetLink, bool
THROW_LAST_FILE_ERROR(replaceCpy(replaceCpy(_("Cannot copy symbolic link %x to %y."), L"%x", L"\n" + fmtPath(sourceLink)), L"%y", L"\n" + fmtPath(targetLink)), functionName);
//allow only consistent objects to be created -> don't place before ::symlink, targetLink may already exist!
- zen::ScopeGuard guardNewLink = zen::makeGuard([&]
+
+ auto cleanUp = [&]
{
try
{
@@ -1541,7 +1552,8 @@ void zen::copySymlink(const Zstring& sourceLink, const Zstring& targetLink, bool
removeFile(targetLink); //throw FileError
}
catch (FileError&) {}
- });
+ };
+ ZEN_ON_SCOPE_FAIL(cleanUp());
//file times: essential for sync'ing a symlink: enforce this! (don't just try!)
#ifdef ZEN_WIN
@@ -1573,8 +1585,6 @@ void zen::copySymlink(const Zstring& sourceLink, const Zstring& targetLink, bool
if (copyFilePermissions)
copyItemPermissions(sourceLink, targetLink, ProcSymlink::DIRECT); //throw FileError
-
- guardNewLink.dismiss(); //target has been created successfully!
}
@@ -1798,7 +1808,8 @@ InSyncAttributes copyFileWindowsBackupStream(const Zstring& sourceFile, //throw
throw FileError(errorMsg, errorDescr);
}
- ScopeGuard guardTarget = makeGuard([&] { try { removeFile(targetFile); } catch (FileError&) {} }); //transactional behavior: guard just after opening target and before managing hFileTarget
+ ZEN_ON_SCOPE_FAIL(try { removeFile(targetFile); }
+ catch (FileError&) {} ); //transactional behavior: guard just after opening target and before managing hFileTarget
ZEN_ON_SCOPE_EXIT(::CloseHandle(hFileTarget));
//----------------------------------------------------------------------
@@ -1807,7 +1818,7 @@ InSyncAttributes copyFileWindowsBackupStream(const Zstring& sourceFile, //throw
THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(targetFile)), L"GetFileInformationByHandle");
//return up-to-date file attributes
- InSyncAttributes newAttrib = {};
+ InSyncAttributes newAttrib;
newAttrib.fileSize = get64BitUInt(fileInfoSource.nFileSizeLow, fileInfoSource.nFileSizeHigh);
newAttrib.modificationTime = filetimeToTimeT(fileInfoSource.ftLastWriteTime); //no DST hack (yet)
newAttrib.sourceFileId = extractFileId(fileInfoSource);
@@ -1923,7 +1934,6 @@ InSyncAttributes copyFileWindowsBackupStream(const Zstring& sourceFile, //throw
&fileInfoSource.ftLastWriteTime))
THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtPath(targetFile)), L"SetFileTime");
- guardTarget.dismiss();
return newAttrib;
}
@@ -1938,20 +1948,17 @@ struct CallbackData
const Zstring& targetFile) :
sourceFile_(sourceFile),
targetFile_(targetFile),
- onUpdateCopyStatus_(onUpdateCopyStatus),
- fileInfoSrc(),
- fileInfoTrg(),
- bytesReported() {}
+ onUpdateCopyStatus_(onUpdateCopyStatus) {}
const Zstring& sourceFile_;
const Zstring& targetFile_;
const std::function<void(std::int64_t bytesDelta)>& onUpdateCopyStatus_; //optional
std::exception_ptr exception; //out
- BY_HANDLE_FILE_INFORMATION fileInfoSrc; //out: modified by CopyFileEx() at beginning
- BY_HANDLE_FILE_INFORMATION fileInfoTrg; //
+ BY_HANDLE_FILE_INFORMATION fileInfoSrc{}; //out: modified by CopyFileEx() at beginning
+ BY_HANDLE_FILE_INFORMATION fileInfoTrg{}; //
- std::int64_t bytesReported; //used internally to calculate bytes transferred delta
+ std::int64_t bytesReported = 0; //used internally to calculate bytes transferred delta
};
@@ -2060,7 +2067,7 @@ InSyncAttributes copyFileWindowsDefault(const Zstring& sourceFile, //throw FileE
try { activatePrivilege(SE_RESTORE_NAME); }
catch (const FileError&) { backupPrivilegesActive = false; }
- zen::ScopeGuard guardTarget = zen::makeGuard([&] { try { removeFile(targetFile); } catch (FileError&) {} });
+ auto guardTarget = zen::makeGuard<ScopeGuardRunMode::ON_FAIL>([&] { try { removeFile(targetFile); } catch (FileError&) {} });
//transactional behavior: guard just before starting copy, we don't trust ::CopyFileEx(), do we? ;)
DWORD copyFlags = COPY_FILE_FAIL_IF_EXISTS;
@@ -2152,9 +2159,7 @@ InSyncAttributes copyFileWindowsDefault(const Zstring& sourceFile, //throw FileE
// - perf: recent measurements show no slow down at all for buffered USB sticks!
setFileTimeRaw(targetFile, &cbd.fileInfoSrc.ftCreationTime, cbd.fileInfoSrc.ftLastWriteTime, ProcSymlink::FOLLOW); //throw FileError
- guardTarget.dismiss(); //target has been created successfully!
-
- InSyncAttributes newAttrib = {};
+ InSyncAttributes newAttrib;
newAttrib.fileSize = get64BitUInt(cbd.fileInfoSrc.nFileSizeLow, cbd.fileInfoSrc.nFileSizeHigh);
newAttrib.modificationTime = filetimeToTimeT(cbd.fileInfoSrc.ftLastWriteTime);
newAttrib.sourceFileId = extractFileId(cbd.fileInfoSrc);
@@ -2228,8 +2233,9 @@ InSyncAttributes copyFileOsSpecific(const Zstring& sourceFile, //throw FileError
}
if (onUpdateCopyStatus) onUpdateCopyStatus(0); //throw X!
- InSyncAttributes newAttrib = {};
- zen::ScopeGuard guardTarget = zen::makeGuard([&] { try { removeFile(targetFile); } catch (FileError&) {} });
+ InSyncAttributes newAttrib;
+ ZEN_ON_SCOPE_FAIL( try { removeFile(targetFile); }
+ catch (FileError&) {} );
//transactional behavior: place guard after ::open() and before lifetime of FileOutput:
//=> don't delete file that existed previously!!!
{
@@ -2278,7 +2284,6 @@ InSyncAttributes copyFileOsSpecific(const Zstring& sourceFile, //throw FileError
setFileTime(targetFile, sourceInfo.st_mtime, ProcSymlink::FOLLOW); //throw FileError
#endif
- guardTarget.dismiss(); //target has been created successfully!
return newAttrib;
}
#endif
@@ -2303,15 +2308,12 @@ InSyncAttributes zen::copyNewFile(const Zstring& sourceFile, const Zstring& targ
{
const InSyncAttributes attr = copyFileOsSpecific(sourceFile, targetFile, onUpdateCopyStatus); //throw FileError, ErrorTargetExisting, ErrorFileLocked
- if (copyFilePermissions)
- {
- //at this point we know we created a new file, so it's fine to delete it for cleanup!
- zen::ScopeGuard guardTargetFile = zen::makeGuard([&] { try { removeFile(targetFile); } catch (FileError&) {}});
+ //at this point we know we created a new file, so it's fine to delete it for cleanup!
+ ZEN_ON_SCOPE_FAIL(try { removeFile(targetFile); }
+ catch (FileError&) {});
+ if (copyFilePermissions)
copyItemPermissions(sourceFile, targetFile, ProcSymlink::FOLLOW); //throw FileError
- guardTargetFile.dismiss(); //target has been created successfully!
- }
-
return attr;
}
bgstack15