slxMountVol Mounting_Folder_Name Folder_To_Mount_Name
The mounting folder MUST be empty
The folder to mount can take following forms :
C:\TEMP\DOSSIER\CIBLE
or
Volume{GUIG}
or
/D to suppress the mount
EX:
slxMountVol c:\mnt\temp c:\temp
slxMountVol c:\mnt\cdrom Volume{346bfa41-38fb-11db-bc21-806d6172696f}
slxMountVol c:\mnt\temp /D
Introduction
Since Windows 2000 we can mount a directory or a volume into an NTFS directory. It exists some commandline tools like "LINKD.EXE" or "MONTVOL.EXE" that allow mounting.
On the programmation point of view, it appears APIs in the "File management functions" and the "volume Management functions" that allow to manage links, to list, mount and unmount volumes. I list the déclarations in the paragraph called "SDK APIs for directories" and "SDK APIs for mount points".
At this point I wrote a program that mount a volume. This program had to run as a normal user in W2K and Vista. I used the SDK function "SetVolumeMountPoint". It works on W2K, but not on XP or Vista. At the begening I pay no attention to this trouble, just thinking that it was a privilege trouble. When I dug, trying to give SE_MANAGE_VOLUME_NAME, I realise that I was not able to mount a volume as a normal user.
Looking in the MSDN I foud an article explaining that mount points and links were layered on a "Reparse point" and "Directory junction" thechnologie. The paragraph "My Understanding of reparse point" give some way to mount and link using reparse point.
Then I was able to mount a volume or to create a symbolic link as a simple user. For the volume mount point, it was technicaly working, I can browse the volume across the mount point. But when I ran MOUNTVOL.EXE I couldn't see my moint point. the last paragraph called "Registering MountMgr" explain how to register the volume mount point to the MountMGR. This part need to be administrator and I still don't know why !
SDK APIs for mount points (begining W2K NT 5.0)
I just give here some entry point to list mount points.
HANDLE FindFirstVolumeMountPoint (LPTSTR lpszRootPathName,
LPTSTR lpszVolumeMountPoint,
DWORD cchBufferLength);
BOOL FindNextVolumeMountPoint (HANDLE hFindVolumeMountPoint,
LPTSTR lpszVolumeMountPoint,
DWORD cchBufferLength);
BOOL FindVolumeMountPointClose (HANDLE hFindVolumeMountPoint);
Mount a volume.
BOOL SetVolumeMountPoint (LPCTSTR lpszVolumeMountPoint,
LPCTSTR lpszVolumeName);
Unount a volume.
BOOL DeleteVolumeMountPoint (LPCTSTR lpszVolumeMountPoint);
Get a volume unique name from a volume mount point.
BOOL GetVolumeNameForVolumeMountPoint (LPCTSTR lpszVolumeMountPoint,
LPTSTR lpszVolumeName,
DWORD cchBufferLength);
SDK APIs for directories
Here is the entry point to create hard links (begining W2K NT 5.0).
BOOL CreateHardLink (LPCTSTR lpFileName,
LPCTSTR lpExistingFileName,
LPSECURITY_ATTRIBUTES lpSecurityAttributes);
Here is the entry point to create symbolic links (begining Vista NT 6.0).
BOOL CreateSymbolicLink (LPCWSTR lpSymlinkFileName,
LPCWSTR lpTargetFileName,
DWORD dwFlags);
My Understanding of reparse point
A file or directory can contain a reparse point, which is a collection of user-defined data. For example, reparse points are used to implement NTFS file system links and the Microsoft Remote Storage Server.
- Reparse points can be established for a directory, but the directory must be empty.
- Reparse points and extended attributes are mutually exclusive.
- Reparse point data, including the tag and optional GUID, cannot exceed 16 kilobytes.
At this point I understand that the way of creating a reparse point was :
- Opening a empty directory.
.
CreateFile (pszMountPointDir,
GENERIC_WRITE,
0, NULL, OPEN_EXISTING,
FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS , NULL);
.
- Controling the open directory .
.
dwSize = 0;
if (!DeviceIoControl (hMountPointDir,
FSCTL_GET_REPARSE_POINT,
NULL,
0,
prgdbReparseDataStruct, sizeof(ptrReparseDataStruct),
&dwSize, NULL))
.
The data structure documented is.
typedef struct _REPARSE_GUID_DATA_BUFFER {
DWORD ReparseTag;
WORD ReparseDataLength;
WORD Reserved;
GUID ReparseGuid;
struct
{
BYTE DataBuffer[1];
} GenericReparseBuffer;
} REPARSE_GUID_DATA_BUFFER;
The control codes are.
#define FSCTL_SET_REPARSE_POINT CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 41,
METHOD_BUFFERED, FILE_SPECIAL_ACCESS)
#define FSCTL_GET_REPARSE_POINT CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 42,
METHOD_BUFFERED, FILE_ANY_ACCESS)
#define FSCTL_DELETE_REPARSE_POINT CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 43,
METHOD_BUFFERED, FILE_SPECIAL_ACCESS)
This seems to be fine, but when I try to put a FSCTL_GET_REPARSE_POINT on a directory where I mount a CDROM using "MONTVOL.EXE" giving the REPARSE_GUID_DATA_BUFFER it doesn't work. So I underdstood that the REPARSE_GUID_DATA_BUFFER is defined in the "WINNT.H" file for users reparse point. It isn't the good structure for Microsoft mount points. the good structure looks like the following. It can be found in the old WINNT.H coming with Visual Studio 6.0 (see the comment at the end of FSCTL_* definitions).
typedef struct _REPARSE_DATA_BUFFER
{
DWORD ReparseTag;
WORD ReparseDataLength;
WORD Reserved;
union
{
struct _SymbolicLinkReparseBuffer
{
WORD SubstituteNameOffset;
WORD SubstituteNameLength;
WORD PrintNameOffset;
WORD PrintNameLength;
WCHAR PathBuffer[1];
} SymbolicLinkReparseBuffer;
struct _MountPointReparseBuffer
{
WORD SubstituteNameOffset;
WORD SubstituteNameLength;
WORD PrintNameOffset;
WORD PrintNameLength;
WCHAR PathBuffer[1];
} MountPointReparseBuffer;
struct
{
BYTE DataBuffer[1];
} GenericReparseBuffer;
};
} REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER;
So trying again the following code, I got the volume name
DWORD SlxIsMountVol (LPCTSTR pszMountPointDir, LPTSTR pszTarget)
{
DWORD dwRc=0, dwErrLen, dwSize;
CHAR lpzErr[MAX_PATH];
HANDLE hPrvToken = NULL, hMountPointDir=NULL;
CHAR pszTargetDir[MAX_PATH], *ptrVolume;
PREPARSE_DATA_BUFFER prgdbReparseDataStruct;
BYTE ptrReparseDataStruct[sizeof(REPARSE_DATA_BUFFER) +
2 * MAX_PATH * sizeof(WCHAR)];
hMountPointDir = CreateFile (pszMountPointDir,
GENERIC_WRITE,
0, NULL, OPEN_EXISTING,
FILE_FLAG_OPEN_REPARSE_POINT |
FILE_FLAG_BACKUP_SEMANTICS,
NULL);
if (hMountPointDir == INVALID_HANDLE_VALUE)
{
... error ...
return -1;
}
memset (pszTargetDir, 0, sizeof(pszTargetDir));
prgdbReparseDataStruct = (PREPARSE_DATA_BUFFER)ptrReparseDataStruct;
memset(ptrReparseDataStruct, 0, sizeof(ptrReparseDataStruct));
prgdbReparseDataStruct->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT;
dwSize = 0;
if (!DeviceIoControl (hMountPointDir,
FSCTL_GET_REPARSE_POINT, NULL, 0,
prgdbReparseDataStruct, sizeof(ptrReparseDataStruct),
&dwSize, NULL))
{
... error ...
return -2;
}
CloseHandle (hMountPointDir);
dwRc = 1;
dwSize = WideCharToMultiByte (CP_ACP, 0,
prgdbReparseDataStruct->MountPointReparseBuffer.PathBuffer,
-1, pszTargetDir, sizeof(pszTargetDir),
NULL, NULL);
if (dwSize == 0)
{
... error ...
return -3;
}
ptrVolume = strstr (pszTargetDir, "Volume");
if (ptrVolume != NULL)
{
strcpy ((PCHAR)pszTarget, ptrVolume);
*((PCHAR)pszTarget+strlen (pszTarget) - 1)= 0;
}
else
return -4;
return dwRc;
}
Debuging this code allow easily to find how to fill the REPARSE_DATA_BUFFER to set a reparse point in order to build a mount point or a symbolic link. The last detail is around the syntax of the names of the mount directory and mount volume.
EX :
\??\c:\mnt\
\\?\Volume{afddc510-485b-11db-8167-806d6172696f}\
Registering MountMgr
Creating a reparse point is enought to build a mount point. But this mount point doesn't appear using MOUNTVOL.EXE. It semms to mean that the mount is not registered with the mount manager. I found the information in the DDK. The mount manager is responsible for managing volume names. For each volume, it stores a name that is unique and is permanently identified with the volume. Following data structure and I/O controls are coming from DDK.
typedef struct _MOUNTMGR_VOLUME_MOUNT_POINT {
USHORT SourceVolumeNameOffset;
USHORT SourceVolumeNameLength;
USHORT TargetVolumeNameOffset;
USHORT TargetVolumeNameLength;
} MOUNTMGR_VOLUME_MOUNT_POINT, *PMOUNTMGR_VOLUME_MOUNT_POINT, *PMVMP;
#define MOUNTMGRCONTROLTYPE ((ULONG) 'm')
#define IOCTL_MOUNTMGR_VOLUME_MOUNT_POINT_CREATED CTL_CODE(MOUNTMGRCONTROLTYPE,6,
METHOD_BUFFERED, FILE_READ_ACCESS |
FILE_WRITE_ACCESS)
#define IOCTL_MOUNTMGR_VOLUME_MOUNT_POINT_DELETED CTL_CODE(MOUNTMGRCONTROLTYPE, 7,
METHOD_BUFFERED, FILE_READ_ACCESS |
FILE_WRITE_ACCESS)
The following code allow to register a mounted volume. If somebody can explain WHY it's necessary to be administrator to do that ? I'am quite sure that it's around ACLs on driver but I can't find where.
#define MOUNTMGR_DEVICE_NAME "\\Device\\MountPointManager"
#define MOUNTMGR_DOS_DEVICE_NAME <a>\\\\.\\MountPointManager
</a><a>
EX</a> :
pszMountPointDir = "c:\mnt\cdrom"
pszTarget = Volume{afddc510-485b-11db-8167-806d6172696f}
DWORD SlxRegisterMountVol (LPCTSTR pszMountPointDir, LPCTSTR pszTarget, BOOL blDelete)
{
DWORD dwRc=0, dwErrLen, dwSize, dwIOControl;
CHAR lpzErr[MAX_PATH];
HANDLE hMountManager=NULL;
CHAR pszTargetDir[MAX_PATH];
WCHAR wszTargetDir[MAX_PATH];
CHAR pszLocalMountPointDir[MAX_PATH];
WCHAR wszMountPointDir[MAX_PATH];
CHAR ptrVolumeBuffer[1024];
CHAR ptrVolumeMountPoint[sizeof(MOUNTMGR_VOLUME_MOUNT_POINT)+MAX_PATH+MAX_PATH];
PMOUNTMGR_VOLUME_MOUNT_POINT strVolumeMountPoint=(PMVMP)ptrVolumeMountPoint;
memset (pszTargetDir, 0, sizeof(pszTargetDir));
if (pszTarget != NULL)
{
strcpy (pszTargetDir, "$$??$$");
strcat (pszTargetDir, pszTarget);
}
memset (pszLocalMountPointDir, 0, sizeof(pszTargetDir));
strcpy (pszLocalMountPointDir, "$$DosDevices$$");
strcat (pszLocalMountPointDir, pszMountPointDir);
memset (wszTargetDir, 0, sizeof(wszTargetDir));
memset (wszMountPointDir, 0, sizeof(wszMountPointDir));
dwSize = MultiByteToWideChar (CP_ACP,
0,
pszTargetDir,
-1,
wszTargetDir,
sizeof(wszTargetDir));
if (dwSize == 0)
{
... error ...
return -1;
}
dwSize = MultiByteToWideChar (CP_ACP,
0,
pszLocalMountPointDir,
-1,
wszMountPointDir,
sizeof(wszMountPointDir));
if (dwSize == 0)
{
... error ...
return -2;
}
memset (ptrVolumeMountPoint, 0, sizeof(ptrVolumeMountPoint));
strVolumeMountPoint->SourceVolumeNameLength = wcslen (wszMountPointDir)*sizeof(WCHAR);
strVolumeMountPoint->TargetVolumeNameLength = wcslen (wszTargetDir)*sizeof(WCHAR);
strVolumeMountPoint->SourceVolumeNameOffset = sizeof(MOUNTMGR_VOLUME_MOUNT_POINT);
memcpy (ptrVolumeMountPoint + strVolumeMountPoint->SourceVolumeNameOffset,
wszMountPointDir,
strVolumeMountPoint->SourceVolumeNameLength);
strVolumeMountPoint->TargetVolumeNameOffset = sizeof(MOUNTMGR_VOLUME_MOUNT_POINT)+
strVolumeMountPoint->SourceVolumeNameLength;
memcpy (ptrVolumeMountPoint + strVolumeMountPoint->TargetVolumeNameOffset,
wszTargetDir,
strVolumeMountPoint->TargetVolumeNameLength);
hMountManager = CreateFile ((LPCSTR)MOUNTMGR_DOS_DEVICE_NAME,
GENERIC_WRITE | GENERIC_READ,
0,
NULL, OPEN_EXISTING,
0, NULL);
if (hMountManager == INVALID_HANDLE_VALUE)
{
... error ...
return -3;
}
dwSize = 0;
dwIOControl = (blDelete)?
IOCTL_MOUNTMGR_VOLUME_MOUNT_POINT_DELETED:IOCTL_MOUNTMGR_VOLUME_MOUNT_POINT_CREATED;
if (!DeviceIoControl (hMountManager,
dwIOControl,
ptrVolumeMountPoint,
sizeof(MOUNTMGR_VOLUME_MOUNT_POINT)+
strVolumeMountPoint->SourceVolumeNameLength+
strVolumeMountPoint->TargetVolumeNameLength,
ptrVolumeBuffer, sizeof(ptrVolumeBuffer), &dwSize, NULL))
{
... error ...
return -4;
}
CloseHandle (hMountManager);
return dwRc;
}
Using the Code
slxMountVol.exe is written with visual studio 2005. you'll find everything commented in french inside.
Points of Interest
Mounting operations without administrative rights.
Understanding how MONTVOL.EXE works.
History
I will feel if i've got some more informations