Similar to junction points, directory symbolic links, mount points - symlink

I wrote a program to detect if a directory is a ReparsePoint and it precisely detects junction points, directory symbolic links and mount drives. I saw a directory which looks and works similar to junction point(I didn't create it, created by someone else) but my code does not detect that specific thing alone! Is there anything else similar to these ReparsePoints? If yes how to detect them via CPP?
EDIT:
I'll insert code so that everyone understands better,
BOOL FileReports::IsReparsePoint(jstring jsrvName,jobject ErrHdlr)
{
const char *path = env->GetStringUTFChars(jsrvName, 0);
BOOL flag = FALSE;
if(GetFileAttributesA(path) & FILE_ATTRIBUTE_REPARSE_POINT){
flag = TRUE;
}
log(NORMAL,"cleaning up");
Cleanup:
env->ReleaseStringUTFChars(jsrvName, path);
return flag;
}

DFS links
DFS links are ReparsePoints in Windows server 2003 but in Windows server 2000 they are not ReparsePoints (i.e.,) FILE_ATTRIBUTE_REPARSE_POINT is not set. instead they are handled by the DFS filter directly by monitoring every open and matching the correct path during pre-create. reference link

Related

How to I determine whether two file paths (or file URLs) identify the same file or directory on macOS?

Imagine this simple example of two paths on macOS:
/etc/hosts
/private/etc/hosts
Both point to the same file. But how do you determine that?
Another example:
~/Desktop
/Users/yourname/Desktop
Or what about upper / lower case mixes on a case-insensitive file system:
/Volumes/external/my file
/Volumes/External/My File
And even this:
/Applications/Über.app
Here: The "Ü" can be specified in two unicode composition formats (NFD, NFC). For an example where this can happen when you use the (NS)URL API see this gist of mine.
Since macOS 10.15 (Catalina) there are additionally firmlinks that link from one volume to another in a volume group. Paths for the same FS object could be written as:
/Applications/Find Any File.app
/System/Volumes/Data/Applications/Find Any File.app
I like to document ways that reliably deal with all these intricacies, with the goal of being efficient (i.e. fast).
There are two ways to check if two paths (or their file URLs) point to the same file system item:
Compare their paths. This requires that the paths get prepared first.
Compare their IDs (inodes). This is overall safer as it avoids all the complications with unicode intricacies and wrong case.
Comparing file IDs
In ObjC this is fairly easy (note: Accordingly to a knowledgeable Apple developer one should not rely on [NSURL fileReferenceURL], so this code uses a cleaner way):
NSString *p1 = #"/etc/hosts";
NSString *p2 = #"/private/etc/hosts";
NSURL *url1 = [NSURL fileURLWithPath:p1];
NSURL *url2 = [NSURL fileURLWithPath:p2];
id ref1 = nil, ref2 = nil;
[url1 getResourceValue:&ref1 forKey:NSURLFileResourceIdentifierKey error:nil];
[url2 getResourceValue:&ref2 forKey:NSURLFileResourceIdentifierKey error:nil];
BOOL equal = [ref1 isEqual:ref2];
The equivalent in Swift (note: do not use fileReferenceURL, see this bug report):
let p1 = "/etc/hosts"
let p2 = "/private/etc/hosts"
let url1 = URL(fileURLWithPath: p1)
let url2 = URL(fileURLWithPath: p2)
let ref1 = try url1.resourceValues(forKeys[.fileResourceIdentifierKey])
.fileResourceIdentifier
let ref2 = try url2.resourceValues(forKeys[.fileResourceIdentifierKey])
.fileResourceIdentifier
let equal = ref1?.isEqual(ref2) ?? false
Both solution use the BSD function lstat under the hood, so you could also write this in plain C:
static bool paths_are_equal (const char *p1, const char *p2) {
struct stat stat1, stat2;
int res1 = lstat (p1, &stat1);
int res2 = lstat (p2, &stat2);
return (res1 == 0 && res2 == 0) &&
(stat1.st_dev == stat2.st_dev) && (stat1.st_ino == stat2.st_ino);
}
However, heed the warning about using these kind of file references:
The value of this identifier is not persistent across system restarts.
This is mainly meant for the volume ID, but may also affect the file ID on file systems that do not support persistent file IDs.
Comparing paths
To compare the paths you must get their canonical path first.
If you do not do this, you can not be sure that the case is correct, which in turn will lead to very complex comparison code. (See using NSURLCanonicalPathKey for details.)
There are different ways how the case can be messed up:
The user may have entered the name manually, with the wrong case.
You have previously stored the path but the user has renamed the file's case in the meantime. You path will still identify the same file, but now the case is wrong and a comparison for equal paths could fail depending on how you got the other path you compare with.
Only if you got the path from a file system operation where you could not specify any part of the path incorrectly (i.e. with the wrong case), you do not need to get the canonical path but can just call standardizingPath and then compare their paths for equality (no case-insensitive option necessary).
Otherwise, and to be on the safe side, get the canonical path from a URL like this:
import Foundation
let uncleanPath = "/applications"
let url = URL(fileURLWithPath: uncleanPath)
if let resourceValues = try? url.resourceValues(forKeys: [.canonicalPathKey]),
let resolvedPath = resourceValues.canonicalPath {
print(resolvedPath) // gives "/Applications"
}
If your path is stored in an String instead of a URL object, you could call stringByStandardizingPath (Apple Docs). But that would neither resolve incorrect case nor would it decompose the characters, which may cause problems as shown in the aforementioned gist.
Therefore, it's safer to create a file URL from the String and then use the above method to get the canonical path or, even better, use the lstat() solution to compare the file IDs as shown above.
There's also a BSD function to get the canonical path from a C string: realpath(). However, this is not safe because it does not resolve the case of different paths in a volume group (as shown in the question) to the same string. Therefore, this function should be avoided for this purpose.

Print all the files in a given folder and sub-folders without using recursion/stack

I recently had an interview with a reputable company for the position of Software Developer and this was one of the questions asked:
"Given the following methods:
List subDirectories(String directoryName){ ... };
List filesInDirectory(String directoryName) { ... };
As the names suggest, the first method returns a list of names of immediate sub-directories in the input directory ('directoryName') and the second method returns a list of names of all files in this folder.
Print all the files in the file system."
I thought about it and gave the interview a pretty obvious recursive solution. She then told me to do it without recursion. Since recursion makes use of the call stack, I told her I will use an auxillary stack instead, at which point point she told me not to use a stack either. Unfortunately, I wasn't able to come up with a solution. I did ask how it can be done without recursion/stack, but she wouldn't say.
How can this be done?
You want to use a queue and a BFS algorithm.
I guess some pseudo-code would be nice:
files = filesInDirectory("/")
foreach (file in files) {
fileQ.append(file)
}
dirQ = subDirectories("/")
while (dirQ != empty) {
dir = dirQ.pop
files = filesInDirectory(dir)
foreach (file in files) {
fileQ.append(file)
}
dirQ.append(subDirectories(dir))
}
while (fileQ != empty) {
print fileQ.pop
}
If I understood correctly, immediate sub-directories are only the directories in that folder. I mean if I=we have these three paths /home/user, /home/config and /home/user/u001, we can say that both user and config are immediate subdirectories of /home/, but u001 isn't. The same applies if user, and u001 are files (user is immediate while u001 isn't).
So you don't really need recursion or stack to return a list of immediate subdirectories or files.
EDIT: I thought that the OP wanted to implement the subDirectories() and filesInDirectories() functions.
So, you can do something like to print all files (kind of pseudocode):
List subd = subDirectories(current_dir);
List files = filesInDirectories(current_dir);
foreach (file in files) {
print file.name();
}
while (!subd.empty()) {
dir = subd.pop();
files = filesInDirectory(dir.name());
foreach (file in files) {
print file.name();
}
subd.append(subDirectories(dir.path()));
}
I think that what #lqs suggests is indeed an acceptable answer that she might have been looking for: store the full path in a variable, and append the directory name to it if you enter a subdirectory, and clip off the last directory name when you leave it. This way, your full path acts as the pointer to where you currently are in the file system.
Because the full path is always modified at the end, the full path behaves (not surprisingly) as your stack.
Interview questions aside, I think I would still pick a real stack over string manipulation though...

SHFileOperation FOF_ALLOWUNDO fails on long filenames

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.

How to get default volume on Mac OS X 64-bit?

What's the way to get default volume on Mac 64-bit?
I have a code like that:
GetVolParmsInfoBuffer buf_64 = { 0 };
status = FSGetVolumeParms(vol_ref, // use default volume
&buf_64, // write
req_count);
The problem is that I can't pass 0 in vol_ref. On Mac 32-bit I could write:
GetVolParmsInfoBuffer buf_32 = { 0 };
HParamBlockRec pb;
pb.ioParam.ioCompletion = NULL; // not doing async I/O
pb.ioParam.ioNamePtr = NULL; // we don't use path name
pb.ioParam.ioVRefNum = 0; // use default volume
pb.ioParam.ioBuffer = reinterpret_cast(&buf_32); // write data here
pb.ioParam.ioReqCount = req_count;
OSErr err = PBHGetVolParmsSync(&pb);
ASSERT_EQ(err, noErr);
Thanks in advance,
- Oleksii
In the File Manager docs, you'll notice a function group titled “Manipulating the Default Volume”. All of those functions are deprecated.
If you search Google for the functions therein, particularly HSetVol, you'll find this mailing list post by Eric Schlegel, which says HSetVol had the effect of setting the current working directory (expressed as a volume/directory pair) on Mac OS. He also says that it doesn't work on Mac OS X: It should work on File Manager functions, but does not set the working directory used for resolving relative paths in other APIs (e.g., open and fopen) like it did on Mac OS.
Moreover, those functions are not available in 64-bit Mac OS X. So the answer is: You don't, because there is no default volume.
The old meaning of it was analogous to the current working directory, so you can do the same thing by getting the CWD and resolving that path to an FSRef. However, for a Mac OS X application (particularly one that doesn't set the CWD by any means, as most don't), this is not terribly useful: The default CWD for an application is /, the root directory of the boot volume. On the other hand, if you run your executable directly or under Xcode's Debugger, its CWD will not be /, which means it could be some other volume—most probably, the one with your Home folder on it.
You should refer to the boot volume (or whatever volume you're interested in) specifically, not attempt to get or simulate getting the default (current working) directory.
For the boot volume, you might try kOnSystemDisk, which is one of the constants in the Folder Manager. If that doesn't work, use Folder Manager's FSFindFolder function to retrieve the System folder, then use File Manager's FSGetVolumeInfo function to get what volume it's on.
Well. I don't really know what "default volume" is. All I know is that Carbon manual (File Manager) says:
ioVRefNum
A volume reference number, 0 for the default volume, or a drive number.
Well, I seem to find the answer for my question.
FSVolumeInfoParam vol_info = { 0 };
vol_info.ioVRefNum = kFSInvalidVolumeRefNum; // will obtain it
vol_info.volumeIndex = 1; // XXX: is it the default volume as well?
vol_info.whichInfo = kFSVolInfoNone; // don't pass volume info
err = PBGetVolumeInfoSync(&vol_info);
The only thing I'm not sure of is if the 1st volume is the default one...
P.S. I guess the problem is that I don't quite understand what "default volume" really is ;-)

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