bool zen::recycleOrDelete(const Zstring& itempath) //throw FileError { if (!somethingExists(itempath)) //[!] do not optimize away, OS X needs this for reliable detection of "recycle bin missing" return false; //neither file nor any other object with that name existing: no error situation, manual deletion relies on it! #ifdef ZEN_WIN recycleOrDelete({ itempath }, nullptr); //throw FileError #elif defined ZEN_LINUX GFile* file = ::g_file_new_for_path(itempath.c_str()); //never fails according to docu ZEN_ON_SCOPE_EXIT(g_object_unref(file);)
void zen::recycleOrDelete(const std::vector<Zstring>& itempaths, const std::function<void (const std::wstring& displayPath)>& onRecycleItem) { if (itempaths.empty()) return; //warning: moving long file paths to recycler does not work! //both ::SHFileOperation() and ::IFileOperation cannot delete a folder named "System Volume Information" with normal attributes but shamelessly report success //both ::SHFileOperation() and ::IFileOperation can't handle \\?\-prefix! /* Performance test: delete 1000 files ------------------------------------ SHFileOperation - single file 33s SHFileOperation - multiple files 2,1s IFileOperation - single file 33s IFileOperation - multiple files 2,1s => SHFileOperation and IFileOperation have nearly IDENTICAL performance characteristics! Nevertheless, let's use IFileOperation for better error reporting (including details on locked files)! */ #ifdef ZEN_WIN_VISTA_AND_LATER vista::moveToRecycleBin(itempaths, onRecycleItem); //throw FileError #else //regular recycle bin usage: available since XP: 1. bad error reporting 2. early failure Zstring itempathsDoubleNull; for (const Zstring& itempath : itempaths) { itempathsDoubleNull += itempath; itempathsDoubleNull += L'\0'; } SHFILEOPSTRUCT fileOp = {}; fileOp.hwnd = nullptr; fileOp.wFunc = FO_DELETE; fileOp.pFrom = itempathsDoubleNull.c_str(); fileOp.pTo = nullptr; fileOp.fFlags = FOF_ALLOWUNDO | FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI; fileOp.fAnyOperationsAborted = false; fileOp.hNameMappings = nullptr; fileOp.lpszProgressTitle = nullptr; //"You should use fully-qualified path names with this function. Using it with relative path names is not thread safe." if (::SHFileOperation(&fileOp) != 0 || fileOp.fAnyOperationsAborted) { std::wstring itempathFmt = fmtPath(itempaths[0]); //probably not the correct file name for file lists larger than 1! if (itempaths.size() > 1) itempathFmt += L", ..."; //give at least some hint that there are multiple files, and the error need not be related to the first one throw FileError(replaceCpy(_("Unable to move %x to the recycle bin."), L"%x", itempathFmt)); } #endif }
Zstring makeUpperCopy(const Zstring& str) { const int len = static_cast<int>(str.size()); if (len == 0) //LCMapString does not allow input sizes of 0! return str; Zstring output = str; //LOCALE_INVARIANT is NOT available with Windows 2000 -> ok //use Windows' upper case conversion: faster than ::CharUpper() if (::LCMapString(LOCALE_INVARIANT, //__in LCID Locale, LCMAP_UPPERCASE, //__in DWORD dwMapFlags, str.c_str(), //__in LPCTSTR lpSrcStr, len, //__in int cchSrc, &*output.begin(), //__out LPTSTR lpDestStr, len) == 0) //__in int cchDest throw std::runtime_error("Error comparing strings (LCMapString). " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__)); return output; }
void zen::loginNetworkShare(const Zstring& dirpathOrig, bool allowUserInteraction) //throw() - user interaction: show OS password prompt { /* ATTENTION: it is not safe to retrieve UNC path via ::WNetGetConnection() for every type of network share: network type |::WNetGetConnection rv | lpRemoteName | existing UNC path -----------------------------|-------------------------|---------------------------------|---------------- inactive local network share | ERROR_CONNECTION_UNAVAIL| \\192.168.1.27\new2 | YES WebDrive | NO_ERROR | \\Webdrive-ZenJu\GNU | NO Box.net (WebDav) | NO_ERROR | \\www.box.net\DavWWWRoot\dav | YES NetDrive | ERROR_NOT_CONNECTED | <empty> | NO ____________________________________________________________________________________________________________ Windows Login Prompt Naming Conventions: network share: \\<server>\<share> e.g. \\WIN-XP\folder or \\192.168.1.50\folder user account: <Domain>\<user> e.g. WIN-XP\Zenju or 192.168.1.50\Zenju Windows Command Line: - list *all* active network connections, including deviceless ones which are hidden in Explorer: net use - delete active connection: net use /delete \\server\share ____________________________________________________________________________________________________________ Scenario: XP-shared folder is accessed by Win 7 over LAN with access limited to a certain user Problems: I. WNetAddConnection2() allows (at least certain) invalid credentials (e.g. username: a/password: a) and establishes an *unusable* connection II. WNetAddConnection2() refuses to overwrite an existing (unusable) connection created in I), but shows prompt repeatedly III. WNetAddConnection2() won't bring up the prompt if *wrong* credentials had been entered just recently, even with CONNECT_INTERACTIVE specified! => 2-step proccess */ auto connect = [&](NETRESOURCE& trgRes) //blocks heavily if network is not reachable!!! { //1. first try to connect without user interaction - blocks! DWORD rv = ::WNetAddConnection2(&trgRes, //__in LPNETRESOURCE lpNetResource, nullptr, //__in LPCTSTR lpPassword, nullptr, //__in LPCTSTR lpUsername, 0); //__in DWORD dwFlags //53L ERROR_BAD_NETPATH The network path was not found. //67L ERROR_BAD_NET_NAME //86L ERROR_INVALID_PASSWORD //1219L ERROR_SESSION_CREDENTIAL_CONFLICT Multiple connections to a server or shared resource by the same user, using more than one user name, are not allowed. Disconnect all previous connections to the server or shared resource and try again. //1326L ERROR_LOGON_FAILURE Logon failure: unknown user name or bad password. //1236L ERROR_CONNECTION_ABORTED if (somethingExists(trgRes.lpRemoteName)) //blocks! return; //success: connection usable! -> don't care about "rv" if (rv == ERROR_BAD_NETPATH || //like ERROR_PATH_NOT_FOUND rv == ERROR_BAD_NET_NAME|| //like ERROR_FILE_NOT_FOUND rv == ERROR_CONNECTION_ABORTED) //failed to connect to a network that existed not too long ago; will later return ERROR_BAD_NETPATH return; //no need to show a prompt for an unreachable network device //2. if first attempt failed, we need to *force* prompt by using CONNECT_PROMPT if (allowUserInteraction) { //avoid problem II.) DWORD rv2= ::WNetCancelConnection2(trgRes.lpRemoteName, //_In_ LPCTSTR lpName, 0, //_In_ DWORD dwFlags, true); //_In_ BOOL fForce //2250L ERROR_NOT_CONNECTED //enforce login prompt DWORD rv3 = ::WNetAddConnection2(&trgRes, // __in LPNETRESOURCE lpNetResource, nullptr, // __in LPCTSTR lpPassword, nullptr, // __in LPCTSTR lpUsername, CONNECT_INTERACTIVE | CONNECT_PROMPT); //__in DWORD dwFlags (void)rv2; (void)rv3; } }; Zstring dirpath = removeLongPathPrefix(dirpathOrig); trim(dirpath, true, false); //1. locally mapped network share if (dirpath.size() >= 2 && iswalpha(dirpath[0]) && dirpath[1] == L':') { Zstring driveLetter(dirpath.c_str(), 2); //e.g.: "Q:" { DWORD bufferSize = 10000; std::vector<wchar_t> remoteNameBuffer(bufferSize); //map local -> remote //note: following function call does NOT block! DWORD rv = ::WNetGetConnection(driveLetter.c_str(), //__in LPCTSTR lpLocalName in the form "<driveletter>:" &remoteNameBuffer[0], //__out LPTSTR lpRemoteName, &bufferSize); //__inout LPDWORD lpnLength if (rv == ERROR_CONNECTION_UNAVAIL) //remoteNameBuffer will be filled nevertheless! { //ERROR_CONNECTION_UNAVAIL: network mapping is existing, but not connected Zstring networkShare = &remoteNameBuffer[0]; if (!networkShare.empty()) { NETRESOURCE trgRes = {}; trgRes.dwType = RESOURCETYPE_DISK; trgRes.lpLocalName = const_cast<LPWSTR>(driveLetter.c_str()); //lpNetResource is marked "__in", seems WNetAddConnection2 is not const correct! trgRes.lpRemoteName = const_cast<LPWSTR>(networkShare.c_str()); // connect(trgRes); //blocks! } } } } //2. deviceless network connection else if (startsWith(dirpath, L"\\\\")) //UNC path { const Zstring networkShare = [&]() -> Zstring //extract prefix "\\server\share" { size_t pos = dirpath.find('\\', 2); if (pos == Zstring::npos) return Zstring(); pos = dirpath.find('\\', pos + 1); return pos == Zstring::npos ? dirpath : Zstring(dirpath.c_str(), pos); }(); if (!networkShare.empty()) { //::WNetGetResourceInformation seems to fail with ERROR_BAD_NET_NAME even for existing unconnected network shares! // => unconditionally try to connect to network share, seems we cannot reliably detect connection status otherwise NETRESOURCE trgRes = {}; trgRes.dwType = RESOURCETYPE_DISK; trgRes.lpRemoteName = const_cast<LPWSTR>(networkShare.c_str()); //trgRes is "__in" connect(trgRes); //blocks! } } }
void getDirectoryAliasesRecursive(const Zstring& dirpath, std::set<Zstring, LessFilename>& output) { #ifdef ZEN_WIN //1. replace volume path by volume name: c:\dirpath -> [SYSTEM]\dirpath if (dirpath.size() >= 3 && std::iswalpha(dirpath[0]) && dirpath[1] == L':' && dirpath[2] == L'\\') { Zstring volname = getVolumeName(Zstring(dirpath.c_str(), 3)); //should not block if (!volname.empty()) output.insert(L"[" + volname + L"]" + Zstring(dirpath.c_str() + 2)); } //2. replace volume name by volume path: [SYSTEM]\dirpath -> c:\dirpath { Zstring testVolname = expandVolumeName(dirpath); //should not block if (testVolname != dirpath) if (output.insert(testVolname).second) getDirectoryAliasesRecursive(testVolname, output); //recurse! } #endif //3. environment variables: C:\Users\<user> -> %USERPROFILE% { std::map<Zstring, Zstring> envToDir; //get list of useful variables auto addEnvVar = [&](const Zstring& envName) { if (Opt<Zstring> value = getEnvironmentVar(envName)) envToDir.emplace(envName, *value); }; #ifdef ZEN_WIN addEnvVar(L"AllUsersProfile"); // C:\ProgramData addEnvVar(L"AppData"); // C:\Users\<user>\AppData\Roaming addEnvVar(L"LocalAppData"); // C:\Users\<user>\AppData\Local addEnvVar(L"ProgramData"); // C:\ProgramData addEnvVar(L"ProgramFiles"); // C:\Program Files addEnvVar(L"ProgramFiles(x86)");// C:\Program Files (x86) addEnvVar(L"CommonProgramFiles"); // C:\Program Files\Common Files addEnvVar(L"CommonProgramFiles(x86)"); // C:\Program Files (x86)\Common Files addEnvVar(L"Public"); // C:\Users\Public addEnvVar(L"UserProfile"); // C:\Users\<user> addEnvVar(L"WinDir"); // C:\Windows addEnvVar(L"Temp"); // C:\Windows\Temp //add CSIDL values: http://msdn.microsoft.com/en-us/library/bb762494(v=vs.85).aspx const auto& csidlMap = CsidlConstants::get(); envToDir.insert(csidlMap.begin(), csidlMap.end()); #elif defined ZEN_LINUX || defined ZEN_MAC addEnvVar("HOME"); //Linux: /home/<user> Mac: /Users/<user> #endif //substitute paths by symbolic names auto pathStartsWith = [](const Zstring& path, const Zstring& prefix) -> bool { #if defined ZEN_WIN || defined ZEN_MAC return startsWith(makeUpperCopy(path), makeUpperCopy(prefix)); #elif defined ZEN_LINUX return startsWith(path, prefix); #endif }; for (const auto& entry : envToDir) if (pathStartsWith(dirpath, entry.second)) output.insert(MACRO_SEP + entry.first + MACRO_SEP + (dirpath.c_str() + entry.second.size())); } //4. replace (all) macros: %USERPROFILE% -> C:\Users\<user> { Zstring testMacros = expandMacros(dirpath); if (testMacros != dirpath) if (output.insert(testMacros).second) getDirectoryAliasesRecursive(testMacros, output); //recurse! } }
bool tryLock(const Zstring& lockfilepath) //throw FileError { #ifdef ZEN_WIN #ifdef TODO_MinFFS_ActivatePriviledge try { activatePrivilege(SE_BACKUP_NAME); } catch (const FileError&) {} try { activatePrivilege(SE_RESTORE_NAME); } catch (const FileError&) {} #endif//TODO_MinFFS_ActivatePriviledge const HANDLE fileHandle = ::CreateFile(applyLongPathPrefix(lockfilepath).c_str(), //_In_ LPCTSTR lpFileName, //use both when writing over network, see comment in file_io.cpp GENERIC_READ | GENERIC_WRITE, //_In_ DWORD dwDesiredAccess, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, //_In_ DWORD dwShareMode, nullptr, //_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes, CREATE_NEW, //_In_ DWORD dwCreationDisposition, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS, //_In_ DWORD dwFlagsAndAttributes, nullptr); //_In_opt_ HANDLE hTemplateFile if (fileHandle == INVALID_HANDLE_VALUE) { const DWORD ec = ::GetLastError(); //copy before directly/indirectly making other system calls! if (ec == ERROR_FILE_EXISTS || //confirmed to be used ec == ERROR_ALREADY_EXISTS) //comment on msdn claims, this one is used on Windows Mobile 6 return false; throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(lockfilepath)), formatSystemError(L"CreateFile", ec)); } ScopeGuard guardLockFile = zen::makeGuard([&] { removeFile(lockfilepath); }); FileOutput fileOut(fileHandle, lockfilepath); //pass handle ownership //be careful to avoid CreateFile() + CREATE_ALWAYS on a hidden file -> see file_io.cpp //=> we don't need it that badly //::SetFileAttributes(applyLongPathPrefix(lockfilepath).c_str(), FILE_ATTRIBUTE_HIDDEN); //(try to) hide it #elif defined ZEN_LINUX || defined ZEN_MAC const mode_t oldMask = ::umask(0); //important: we want the lock file to have exactly the permissions specified ZEN_ON_SCOPE_EXIT(::umask(oldMask)); //O_EXCL contains a race condition on NFS file systems: http://linux.die.net/man/2/open const int fileHandle = ::open(lockfilepath.c_str(), O_CREAT | O_EXCL | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); if (fileHandle == -1) { if (errno == EEXIST) return false; else THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(lockfilepath)), L"open"); } ScopeGuard guardLockFile = zen::makeGuard([&] { removeFile(lockfilepath); }); FileOutput fileOut(fileHandle, lockfilepath); //pass handle ownership #endif //write housekeeping info: user, process info, lock GUID ByteArray binStream = [&] { MemStreamOut streamOut; LockInformation(FromCurrentProcess()).toStream(streamOut); return streamOut.ref(); }(); if (!binStream.empty()) fileOut.write(&*binStream.begin(), binStream.size()); //throw FileError guardLockFile.dismiss(); //lockfile created successfully return true; }
void zen::recycleOrDelete(const std::vector<Zstring>& itempaths, const std::function<void (const Zstring& currentItem)>& notifyDeletionStatus) { if (itempaths.empty()) return; //::SetFileAttributes(applyLongPathPrefix(itempath).c_str(), FILE_ATTRIBUTE_NORMAL); //warning: moving long file paths to recycler does not work! //both ::SHFileOperation() and ::IFileOperation() cannot delete a folder named "System Volume Information" with normal attributes but shamelessly report success //both ::SHFileOperation() and ::IFileOperation() can't handle \\?\-prefix! if (vistaOrLater()) //new recycle bin usage: available since Vista { #define DEF_DLL_FUN(name) const DllFun<fileop::FunType_##name> name(fileop::getDllName(), fileop::funName_##name); DEF_DLL_FUN(moveToRecycleBin); DEF_DLL_FUN(getLastErrorMessage); if (!moveToRecycleBin || !getLastErrorMessage) throw FileError(replaceCpy(_("Unable to move %x to the recycle bin."), L"%x", fmtFileName(itempaths[0])), replaceCpy(_("Cannot load file %x."), L"%x", fmtFileName(fileop::getDllName()))); std::vector<const wchar_t*> cNames; for (auto it = itempaths.begin(); it != itempaths.end(); ++it) //CAUTION: do not create temporary strings here!! cNames.push_back(it->c_str()); CallbackData cbd(notifyDeletionStatus); if (!moveToRecycleBin(&cNames[0], cNames.size(), onRecyclerCallback, &cbd)) { if (cbd.exception) std::rethrow_exception(cbd.exception); std::wstring itempathFmt = fmtFileName(itempaths[0]); //probably not the correct file name for file lists larger than 1! if (itempaths.size() > 1) itempathFmt += L", ..."; //give at least some hint that there are multiple files, and the error need not be related to the first one throw FileError(replaceCpy(_("Unable to move %x to the recycle bin."), L"%x", itempathFmt), getLastErrorMessage()); //already includes details about locking errors! } } else //regular recycle bin usage: available since XP { Zstring itempathsDoubleNull; for (const Zstring& itempath : itempaths) { itempathsDoubleNull += itempath; itempathsDoubleNull += L'\0'; } SHFILEOPSTRUCT fileOp = {}; fileOp.hwnd = nullptr; fileOp.wFunc = FO_DELETE; fileOp.pFrom = itempathsDoubleNull.c_str(); fileOp.pTo = nullptr; fileOp.fFlags = FOF_ALLOWUNDO | FOF_NOCONFIRMATION | FOF_SILENT | FOF_NOERRORUI; fileOp.fAnyOperationsAborted = false; fileOp.hNameMappings = nullptr; fileOp.lpszProgressTitle = nullptr; //"You should use fully-qualified path names with this function. Using it with relative path names is not thread safe." if (::SHFileOperation(&fileOp) != 0 || fileOp.fAnyOperationsAborted) { throw FileError(replaceCpy(_("Unable to move %x to the recycle bin."), L"%x", fmtFileName(itempaths[0]))); //probably not the correct file name for file list larger than 1! } } }