SHFileOperation FOF_ALLOWUNDO fails on long filenames - windows

I'm using the following function to delete a file to the recycle bin: (C++, MFC, Unicode)
bool DeleteFileToPaperbasket (CString filename)
{
TCHAR Buffer[2048+4];
_tcsncpy_s (Buffer, 2048+4, filename, 2048);
Buffer[_tcslen(Buffer)+1]=0; //Double-Null-Termination
SHFILEOPSTRUCT s;
s.hwnd = NULL;
s.wFunc = FO_DELETE;
s.pFrom = Buffer;
s.pTo = NULL;
s.fFlags = FOF_ALLOWUNDO | FOF_SILENT | FOF_NOERRORUI;
s.fAnyOperationsAborted = false;
s.hNameMappings = NULL;
s.lpszProgressTitle = NULL;
int rc = SHFileOperation(&s);
return (rc==0);
}
This works nicely for most files. But if path+filename exceed 255 characters (and still much shorter that 2048 characters), SHFileOperation returns 124. Which is DE_INVALIDFILES.
But what's wrong? I checked everything a million times. The path is double-null terminated, I'm not using \\?\ and it works for short filenames.
I'm totally out of ideas...

I think backwards comparability is biting you in the --- in several ways, and I'd need to actually see the paths your using and implement some error checking code to help. But here are some hints.
You would not get a DE_INVALIDFILES 0x7C "The path in the source or destination or both was invalid." for a max path violation, you'd get a DE_PATHTOODEEP 0x79 "The source or destination path exceeded or would exceed MAX_PATH."
These error codes(return value) do, can, and have changed over time, to be sure what your specific error code means, you need to check it with GetLastError function(msdn)
Also, taken from the SHFileOperation function documentation: "If you do not check fAnyOperationsAborted as well as the return value, you cannot know that the function accomplished the full task you asked of it and you might proceed under incorrect assumptions."
You should not be using this API for extremely long path names, it has been replaced in vista+ by IFileOperation interface
The explanation for why it may work in explorer and not thru this LEGACY api is - Taken from the msdn page on Naming Files, Paths, and Namespaces
The shell and the file system have different requirements. It is
possible to create a path with the Windows API that the shell user
interface is not able to interpret properly.
Hope this was helpful

The recycle bin doesn't support files whose paths exceed MAX_PATH in length. You can verify this for yourself by trying to recycle such a file in Explorer - you will get an error message about the path being too long.

Related

Windows ShellExecuteW with filename that exceeds MAX_PATH (260 characters)

I am struggling to understand what may be causing the issue in my case.
I am using
ShellExecuteW('open', 'explorer.exe', '/select,[file_name]', None, win32con.SW_SHOW)
What I am trying to do is open the file in the OS, highlight it, and bring the File Explorer to the foreground. This works fine for most cases, but when I try to open a file that exceeds the MAX_PATH limit (260 characters), the file doesn't open, and instead it takes me to the "My Files" page.
I have tried prepending "\\?\" to the beginning of my file name, because that is what other Stack Overflow posts said to do with regards to overriding the MAX_PATH limit, but it has not changed the situation.
Does the ShellExecuteW function not allow for files that exceed MAX_PATH? And, if so, is there any workaround I could use?
I read some cases, about this issue. Find this article:Long Paths in .NET, Part 1 of 3 [Kim Hamilton]
If you prefix the file name with "\?\" and call the Unicode versions of the Windows APIs, then you can use file names up to 32K characters in length. In other words, the \?\ prefix is a way to enable long paths while working with the Windows file APIs.
and:
Long paths with the \?\ prefix can be used in most of the file-related Windows APIs, but not all Windows APIs.
I also test ShellExcuteW with \\?\,it failed.
Working well with SHOpenFolderAndSelectItems
CoInitialize(NULL);
LPCWSTR file_name ;//Change the path according to your needs
PIDLIST_ABSOLUTE pidl;
if (SUCCEEDED(SHParseDisplayName(file_name, 0, &pidl, 0, 0)))
{
ITEMIDLIST idNull = { 0 };
LPCITEMIDLIST pidlNull[1] = { &idNull };
SHOpenFolderAndSelectItems(pidl, 1, pidlNull, 0);
ILFree(pidl);
}
Note:CoInitialize or CoInitializeEx must be called before using SHOpenFolderAndSelectItems. Not doing so causes SHOpenFolderAndSelectItems to fail.

My exists function says the file exists, but winapi functions say it does not

I copied code that's supposed to change desktop wallpaper. I have this constant in my program:
const char * image_name = "button_out.gif";
Later, I write the image on disk using Magick++:
image.write(image_name);
The image appears in program's working directory. If I run the program directly from explorer the working directory equals the program location.
Because the code prints the 0x80070002 - File not found error I added a exist function in the beginning:
#include <sys/stat.h>
bool exists(const char* name) {
struct stat buffer;
return (stat (name, &buffer) == 0);
}
void SetWallpaper(LPCWSTR file){
if(!exists((const char* )file)) {
wcout << "The file "<<file<<" does not exist!" << endl;
return;
... actually try to set a wallpaper ...
}
The error is not printed however and the code proceeds.
Now the question is:
Does my exist function work properly?
Where does windows look for that image?
Full code to set a Magick++ generated image as background in case I have missed something relevant in this question.
Problem 1: String Conversions
Your primary problem is that you are attempting to use LPCWSTR (a const wchar_t *) and const char * interchangeably. I see a number of issues in your source, in particular:
You start with const char * image_name.
You then cast it to a LPCWSTR to pass to SetWallpaper. This basically guarantees that SetWallpaper will fail, as desktop->SetWallpaper is not able to handle non wide-character strings.
You then cast it back to a const char * to pass to stat() via exists(). This should work in your situation (since the original string really is a char *) but isn't correct because your string parameter to SetWallpaper is supposedly a proper LPCWSTR.
You need to pick a string format (wide-character vs. what Windows terms "ANSI") and stick to that format, using consistent APIs throughout.
The easiest option is probably just to leave most of your code untouched, but modify SetWallpaper to take a const char * and convert to a wide-character string when needed (for this you can use mbstowcs). So, for example:
void SetWallpaper(const char * file){ // <- Use a const char* parameter.
...
// Convert to a wide-character string to pass to COM:
wchar_t wcfile[MAX_PATH + 1];
mbstowcs(wcfile, file, sizeof(wcfile) / sizeof(wchar_t));
// Pass the converted wide-character string:
desktop->SetWallpaper(wcfile, 0);
...
}
The other option would be to use wide-character strings throughout, i.e.:
LPCWSTR image_name = L"button_out.gif";
Modify exists() to take a LPCWSTR and use _wstat() instead.
Use wide-character versions of all other API functions.
However, I am unsure how that would interact with the ImageMagick API, which may not have wide-character support. So it's up to you. Choose whatever approach is the easiest to implement but make sure you are consistent. The general rule is do not cast between LPCWSTR and const char *; if you are ever in a situation where you need to change one to the other, you cannot cast, you must convert (via mbstowcs or wcstombs).
Problem 2: SetWallpaper default directory is not current working directory
At this point, your string usage will be consistent. Now that you have that problem ironed out, if SetWallpaper fails while exists() does not, then SetWallpaper is not looking where you think it is. As you discovered in your comment, SetWallpaper looks in the desktop by default. In this case, while I have not tested it, you may be able to work around this by passing an absolute path to SetWallpaper. For this, you can use GetFullPathName to determine the absolute file name given your relative path. Remember to be consistent with your string types, though.
Also, if stat() continues to fail, then that problem is either that your working directory is not what you think it is, or your filename is not what you think it is. To that end you will want to perform the following tests:
Print the current working directory at the point you check for the files existence, verify it is correct.
Print the filename when you check for its existence, verify it is correct.
You should be good to go once you work all the above issues out.

How to get file MFT entry/inode using Java or C++

I've written a duplicate finder in Java, but I need to include hard link support for it. Unfortunately, there seems to be no way to dig out a file's MFT entry in Java.
Although there is a method called fileKey() in the BasicFileAttributeView class, it won't work on the NTFS file system (I haven't tested it on ext yet).
I also found the method isSameFile() (in java.nio.file.Path). Does anyone know how this method works? It seems to be doing the right thing, but it returns a Boolean value, so it is worthless for me (I wish to put the results into a map and group them by their MFT entries).
I can always compare the creation times, modification times, etc. for each file, but this is just giving up.
Is there any way to accomplish what I am trying to do in either C++ or Java? I care more about making it work on NTFS than ext.
You would need to use the FILE_ID_FULL_DIRECTORY_INFORMATION structure along with the NtQueryDirectoryFile function (or the FILE_INTERNAL_INFORMATION structure along with the NtQueryInformationFile, if you already have a handle) inside ntdll.dll (available since Windows XP, if not earlier) to get the 8-byte file IDs and check if they are the same.
This will tell you if they are the same file, but not if they are the same stream of the same file.
I'm not sure how to detect if two files are the same stream from user-mode -- there is a structure named FILE_STREAM_INFORMATION which can return all the streams associated with a file, but it doesn't tell you which stream you have currently opened.
Detecting hard links is usually accomplished by calling FindFirstFileNameW. But there is a lower level way.
To get the NTFS equivalent to inodes, try the FSCTL_GET_OBJECT_ID ioctl code.
There's a unique (until the file is deleted) identifier in the BY_HANDLE_FILE_INFORMATION structure as well.
If the volume has an enabled USN Change Journal, you can issue the FSCTL_READ_FILE_USN_DATA ioctl code. Check the FileReferenceNumber member in the USN_RECORD structure
In Java you can use sun.nio.ch.FileKey which is a non-transparent enclosure for NTFS Inode. All the hard links share the same Inode.
Therefore, if you need to collect hard links, you can create FileKey from each suspect and compare them (e.g. by putting pairs of FileKey -> File into a Multimap)
I find fileKey is always null. Here is some code that can actually read the NTFS inode number. There remain many aspects I'm not happy with, not least, it relies on reflection.
import sun.nio.ch.FileKey;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Path;
class NTFS {
static long inodeFromPath(Path path) throws IOException, NoSuchFieldException, IllegalAccessException {
try (FileInputStream fi = new FileInputStream(path.toFile())) {
FileDescriptor fd = fi.getFD();
FileKey fk = FileKey.create(fd);
Field privateField = FileKey.class.getDeclaredField("nFileIndexHigh");
privateField.setAccessible(true);
long high = (long) privateField.get(fk);
privateField = FileKey.class.getDeclaredField("nFileIndexLow");
privateField.setAccessible(true);
long low = (long) privateField.get(fk);
long power = (long) 1 << 32;
long inode = high * power + low;
return inode;
}
}
}

Why are LoadResource/LockResource sometimes returning concatenated resource data?

I'm working with a Visual Studio C++ project that contains a number of HTML resources. They are loaded by a method that looks like this:
LPCTSTR loadHTML(HMODULE hModule, LPCTSTR sResourceName)
{
HRSRC hResource = FindResource(hModule, sResourceName, RT_HTML);
if(!hResource)
return 0;
HGLOBAL hResourceData = LoadResource(hModule, hResource);
if(!hResourceData)
return 0;
return reinterpret_cast<LPCTSTR>(LockResource(hResourceData));
}
Most of the time, this works fine. Some times, though, it returns a resource concatenated with another resource. When this happens, it is a persistent problem in that build. I can "fix" it by adding a few blank lines to the resource in question and then rebuilding the project. It happens periodically, even when the resources haven't changed.
I am keen to get to the bottom of why it is happening. Has anyone else come across it? Could there be something peculiar about my resources that is causing the problem? Is my code wrong?
Sadly, I'm reluctant to post an example resource here; they're pretty long and this is proprietary software.
Whats peculiar about your resources is you are expecting them to be zero terminated. iirc resource sections are aligned on 16 byte boundries, which means that whenever a "blob" is a multiple of 16 bytes long there won't be any separating byte's between the resource and the next.
Either ensure that the resources are saved with a terminating zero character, or use SizeofResource to determine where the resource ends.
How do you determine the end of a resource? Do your resource files end in a (double for unicode) NULL? I don't think there is any guarantee that a resource is NULL terminated in the PE file and you seem to be treating it as a string.

Way to get the SearchPath API to not look in c:\windows?

Is there a way to get the SearchPath API to not search in c:\windows when using the default search path (passing NULL as the first param)? I can't modify the caller to send in a specific path.
I have a system with an application ini file in c:\windows (which I don't want it to use, but for legacy reasons has to remain there). I put my copy of the same ini file in c:\users\public, and put c:\users\public at the front of my system path environment variable, but a call to SearchPath still finds the c:\windows version. If I delete that version, it then finds the c:\users\public version, so I know the path was set correctly.
I know this is very late, but having just run into this problem myself, I would propose a better solution.
The first argument to SearchPath, as you have found, can be used to specify the directories you want it to search, instead of the default order. You can retrieve and use the current user's PATH with GetEnvironmentVariable, and then search within that:
DWORD err = GetEnvironmentVariable("PATH", NULL, 0);
char* path = new char[err+1]; path[err] = 0;
GetEnvironmentVariable("PATH", path, err);
err = SearchPath(path, "application", ".ini", 0, NULL, NULL);
char* searchResult = new char[err+1]; searchResult[err] = 0;
err = SearchPath(path, "application", ".ini", err, searchResult, NULL);
According to MSDN, there's nothing you can do about this bar changing a system level (HKLM) registry entry (Which is a "bad thing"). The registry change would cause the search order to start with the current working directory, which you could set to the desired folder in a shortcut. (Again, I'm going to say; changing a Machine Level registry entry to do this - is potentially dangerous!)
Have you looked into application shims? This may be something that could work for you.
Try SetCurrentDirectory("c:\users\public") and then SearchPath(...).

Resources