Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / Win32

Inside Mountvol.exe

4.71/5 (8 votes)
29 Jun 2008CPOL4 min read 1   1.1K  
Programaticaly mounting a volume as a common user
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.

/* Open mounting folder
*/
  .
CreateFile (pszMountPointDir,
            GENERIC_WRITE,
            0, NULL, OPEN_EXISTING,
            FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS , NULL);
  .                              

- Controling the open directory .

/* Prepare Reparse data structure and send the device control
   to get the reparse point datas
*/
  .
  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)
                                   // REPARSE_DATA_BUFFER,
#define FSCTL_GET_REPARSE_POINT    CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 42,
                                   METHOD_BUFFERED, FILE_ANY_ACCESS)
                                   // REPARSE_DATA_BUFFER
#define FSCTL_DELETE_REPARSE_POINT CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 43,
                                   METHOD_BUFFERED, FILE_SPECIAL_ACCESS)
                                   // REPARSE_DATA_BUFFER,

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

/**********************************************************************/
/*                        Function SlxIsMountVol                      */
/**********************************************************************/
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)];

  /* Open the mount point directory
  */
  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;
  }

  /* Prepare the junction point structure
  */
  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;

  /* Translate the volume name
  */
  dwSize = WideCharToMultiByte (CP_ACP, 0,
                        prgdbReparseDataStruct->MountPointReparseBuffer.PathBuffer,
                        -1, pszTargetDir, sizeof(pszTargetDir),
                        NULL, NULL);
  if (dwSize == 0)
  {
    ... error ...
    return -3;
  }

  /* Get the volume part
  */
  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;

  /* Initialising sructure for registering mountpoint
  */
  memset (pszTargetDir, 0, sizeof(pszTargetDir));
  if (pszTarget != NULL)
  {
    strcpy (pszTargetDir, "$$??$$"); // replace $ by \
    strcat (pszTargetDir, pszTarget);
  }

  memset (pszLocalMountPointDir, 0, sizeof(pszTargetDir));
  strcpy (pszLocalMountPointDir, "$$DosDevices$$"); // replace $ by \
  strcat (pszLocalMountPointDir, pszMountPointDir);

  /* Unicode conversion
  */
  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;
  }

  /* Building structure
  */
  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);

  /* Opening an Handle on the MountMgr
  */
  hMountManager = CreateFile ((LPCSTR)MOUNTMGR_DOS_DEVICE_NAME, //MOUNTMGR_DEVICE_NAME,
                              GENERIC_WRITE | GENERIC_READ,
                              0,
                              NULL, OPEN_EXISTING,
                              /*FILE_FLAG_BACKUP_SEMANTICS*/0, NULL);
  if (hMountManager == INVALID_HANDLE_VALUE)
  {
    ... error ...
    return -3;
  }
  
  /* Registering information against the MountMgr
  */
  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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)