Ask Learn
Preview
Please sign in to use this experience.
Sign inThis browser is no longer supported.
Upgrade to Microsoft Edge to take advantage of the latest features, security updates, and technical support.
Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
To map a volume with drive letter to disk partition, one may use some combination of WMI classes like
Win32_LogicalDisk,Win32_LogicalDiskToPartition,Win32_DiskPartition, Win32_DiskDriveToDiskPartition and Win32_DiskDrive.
Unfortunately WMI does not provide a way to map a disk partition that does not have a drive letter associated with it. There is no WMI class to associate a disk volume to disk partition directly. However, one can use the low level DeviceIoControl API to request disk partition information directly from the disk device driver.
Below is a sample program to list all the volumeIDs with corresponding partitions and steps to build the sample using WDK.
1) Download and install the WDK from www.microsoft.com/downloads/details.aspx?displaylang=en&FamilyID=36a2630f-5d56-43b5-b996-7633f2ec14ff
2) Start a command prompt and go to the bin directory in WinDDK installtion folder and run setenv.bat for whatever system you are building for, for example:
setenv C:\WinDDK\7268.0.0 chk WNET
3) At this point you have a build environment pointing to the WDK install folders created within the CMD window.
4) Create a folder named 'mounts' in the build environment folder created in step 3 (in this example C:\WinDDK\7268.0.0) and create following files in it
* A Header file named 'enumvol.h' using the code snippet given below under enumvol.h header
* A 'C' source file named 'mounts.c' using code snippet given below under 'mounts.c'header
* A 'MakeFile' file named 'MakeFile'using the text given under header 'MakeFile' header
* A 'Source' file named 'Source'using the text given under 'Source'header
5) Navigate to the folder created in step 4 and run “bcz”, this will create an executable file that will display the disk partition information.
Source Files
================
'===================================================================
' DISCLAIMER:
'-------------------------------------------------------------------
'
' This sample is provided as is and is not meant for use on a
' production environment. It is provided only for illustrative
' purposes. The end user must test and modify the sample to suit
' their target environment.
'
' Microsoft can make no representation concerning the content of
' this sample. Microsoft is providing this information only as a
' convenience to you. This is to inform you that Microsoft has not
' tested the sample and therefore cannot make any representations
' regarding the quality, safety, or suitability of any code or
' information found here.
'
'===================================================================
/**************************************************enumvol.h************************************************/
#ifndef _ENUMVOL_H_
#define _ENUMVOL_H_
//
// Command Descriptor Block constants.
//
#define CDB6GENERIC_LENGTH 6
#define CDB10GENERIC_LENGTH 10
#define SCSIOP_INQUIRY 0x12
#define BUF_LEN (8*1024)
#define DIRECT_ACCESS_DEVICE 0
// Define constants for \DosDevices\X:
#define DOSDEVICES_LENGTH 12
#define DRIVE_LETTER_LENGTH 14
#define DRIVE_LETTER_POSITION 12
#define DRIVE_COLON_POSITION 13
#define MOUNTMGR_NAME "\\\\.\\MountPointManager"
//
// Bus Type
//
static char* BusType[] = {
"UNKNOWN", // 0x00
"SCSI",
"ATAPI",
"ATA",
"IEEE 1394",
"SSA",
"FIBRE",
"USB",
"RAID"
};
//
// SCSI Device Type
//
static char* DeviceType[] = {
"Direct Access Device", // 0x00
"Tape Device", // 0x01
"Printer Device", // 0x02
"Processor Device", // 0x03
"WORM Device", // 0x04
"CDROM Device", // 0x05
"Scanner Device", // 0x06
"Optical Disk", // 0x07
"Media Changer", // 0x08
"Comm. Device", // 0x09
"ASCIT8", // 0x0A
"ASCIT8", // 0x0B
"Array Device", // 0x0C
"Enclosure Device", // 0x0D
"RBC Device", // 0x0E
"Unknown Device" // 0x0F
};
typedef struct _SCSI_PASS_THROUGH_WITH_BUFFERS {
SCSI_PASS_THROUGH Spt;
ULONG Filler; // realign buffers to double word boundary
UCHAR SenseBuf[32];
UCHAR DataBuf[512];
} SCSI_PASS_THROUGH_WITH_BUFFERS, *PSCSI_PASS_THROUGH_WITH_BUFFERS;
VOID PrintError( ULONG );
BOOL GetDeviceProperty( HDEVINFO, DWORD );
VOID DebugPrint( USHORT, PCHAR, ... );
void PrintMountName(PMOUNTDEV_NAME mountName);
void PrintName(PWCHAR Name, USHORT Len);
void GetDriveLetter(PMOUNTDEV_NAME mountName);
void GetVolumeExtents(PWCHAR volumeGuid);
BOOL IsDriveLetter(PWCHAR Name, USHORT Len);
BOOL IsVolumeGuid(PWCHAR Name, USHORT Len);
#endif // _ENUMVOL_H_
/**************************************************enumvol.h************************************************/
/**************************************************mounts.c*************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <windows.h>
#include <initguid.h> // Guid definition
#include <devguid.h> // Device guids
#include <setupapi.h> // for SetupDiXxx functions.
#include <cfgmgr32.h> // for SetupDiXxx functions.
#include <devioctl.h>
#include <ntdddisk.h>
#include <ntddvol.h>
#include <ntddscsi.h>
#include <mountdev.h>
#include <mountmgr.h>
#include <enumvol.h>
BOOL GetStorageDeviceNumber(HANDLE device, PSTORAGE_DEVICE_NUMBER number)
{
BOOL status;
unsigned long returned_bytes;
//unsigned long returned_bytes, errno;
status = DeviceIoControl(device,
IOCTL_STORAGE_GET_DEVICE_NUMBER,
NULL,
0,
number,
sizeof(STORAGE_DEVICE_NUMBER),
&returned_bytes,
FALSE);
if (!status) {
errno = GetLastError();
//printf("Error getting storage device number: status %d, returned %d\n", errno, returned_bytes);
return FALSE;
} else {
//printf("got storage device number: disk%d, part%d\n",
// number->DeviceNumber, number->PartitionNumber);
}
return TRUE;
}
BOOL GetPartitionInfoEx(HANDLE device, PPARTITION_INFORMATION_EX PartInfo)
{
BOOL status;
unsigned long returned_bytes;
//unsigned long returned_bytes, errno;
status = DeviceIoControl(device,
IOCTL_DISK_GET_PARTITION_INFO_EX,
NULL,
0,
PartInfo,
sizeof(PARTITION_INFORMATION_EX),
&returned_bytes,
FALSE);
if (!status) {
errno = GetLastError();
//printf("Error getting partition info: status %d, returned %d\n", errno, returned_bytes);
return FALSE;
} else {
//printf("got partition info: offset %I64x, length %I64x\n", PartInfo->StartingOffset, PartInfo->PartitionLength);
}
return TRUE;
}
BOOL GetPartitionInfo(HANDLE device, PPARTITION_INFORMATION PartInfo)
{
BOOL status;
unsigned long returned_bytes;
//unsigned long returned_bytes, errno;
status = DeviceIoControl(device,
IOCTL_DISK_GET_PARTITION_INFO,
NULL,
0,
PartInfo,
sizeof(PARTITION_INFORMATION),
&returned_bytes,
FALSE);
if (!status) {
errno = GetLastError();
//printf("Error getting partition info: status %d, returned %d\n", errno, returned_bytes);
return FALSE;
} else {
//printf("got partition info: offset %I64x, length %I64x\n", PartInfo->StartingOffset, PartInfo->PartitionLength);
}
return TRUE;
}
ULONG DebugLevel = 1;
// 0 = Suppress All Messages
// 1 = Display & Fatal Error Message
// 2 = Warning & Debug Messages
// 3 = Informational Messages
BOOL IsFixedDisk;
WCHAR FoundDevice[256];
BOOL found = FALSE;
VOID DebugPrint( USHORT DebugPrintLevel, PCHAR DebugMessage, ... )
/*++
Routine Description:
This routine print the given string, if given debug level is <= to the
current debug level.
Arguments:
DebugPrintLevel - Debug level of the given message
DebugMessage - Message to be printed
--*/
{
va_list args;
va_start(args, DebugMessage);
if (DebugPrintLevel <= DebugLevel) {
char buffer[128];
(VOID) vsprintf(buffer, DebugMessage, args);
printf( "%s", buffer );
}
va_end(args);
}
/*++
Routine Description:
This function prints the given Unicode string.
Arguments:
WCHAR string and length
--*/
void PrintName(PWCHAR Name, USHORT Len)
{
SHORT i;
for ( i = 0; i < (Len) ; i++ ) {
printf("%C", Name[i]);
}
}
void GetMounts()
{
DWORD bytesReturned;
UCHAR Bytes[10000];
PMOUNTMGR_MOUNT_POINTS pMntPoints = (PMOUNTMGR_MOUNT_POINTS) Bytes;
MOUNTMGR_MOUNT_POINT mntPoint, *pmnt;
DWORD err = 0;
HANDLE h;
ULONG index;
WCHAR symbolicName[MAX_PATH], deviceName[MAX_PATH], uniqueId[MAX_PATH];
h = CreateFile("\\\\.\\MountPointManager",
GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
0,
NULL);
if (h == INVALID_HANDLE_VALUE) {
err = GetLastError();
printf("can't open: %d\n", err);
return;
}
ZeroMemory(pMntPoints, 10000);
ZeroMemory(&mntPoint, sizeof(MOUNTMGR_MOUNT_POINT));
pMntPoints->Size = 10000;
if(DeviceIoControl(h,
IOCTL_MOUNTMGR_QUERY_POINTS,
&mntPoint,
sizeof(MOUNTMGR_MOUNT_POINT),
pMntPoints,
10000,
&bytesReturned,
NULL)) {
if(bytesReturned && pMntPoints->Size && pMntPoints->NumberOfMountPoints) {
for(index = 0; index < pMntPoints->NumberOfMountPoints; index++) {
pmnt = &pMntPoints->MountPoints[index];
ZeroMemory(symbolicName, MAX_PATH);
ZeroMemory(deviceName, MAX_PATH);
ZeroMemory(uniqueId, MAX_PATH);
wcsncpy(symbolicName, (PWCHAR) &Bytes[pmnt->SymbolicLinkNameOffset], pmnt->SymbolicLinkNameLength/sizeof(WCHAR));
wcsncpy(uniqueId, (PWCHAR) &Bytes[pmnt->UniqueIdOffset], pmnt->UniqueIdLength/sizeof(WCHAR));
wcsncpy(deviceName, (PWCHAR) &Bytes[pmnt->DeviceNameOffset], pmnt->DeviceNameLength/sizeof(WCHAR));
//if (IsVolumeGuid(symbolicName, pmnt->SymbolicLinkNameLength/sizeof(WCHAR))) {
printf("%d) %ws => %ws\n", index, symbolicName, deviceName);
//}
//printf("%d) %ws => %ws\n", index, symbolicName, deviceName);
}
}
} else {
err = GetLastError();
}
CloseHandle(h);
printf("err=%d ret=%d\n", err, bytesReturned);
}
int __cdecl main()
/*++
Routine Description:
This is the main function. It takes no arguments from the user.
Arguments:
None
Return Value:
Status
--*/
{
HDEVINFO hIntDevInfo;
DWORD index;
BOOL status;
//
// Open the device using device interface registered by the driver
//
found = FALSE;
//
// Get the interface device information set that contains all devices of event class.
//
hIntDevInfo = SetupDiGetClassDevs (
(LPGUID) &VolumeClassGuid, //&GUID_DEVCLASS_VOLUMESNAPSHOT, //&MOUNTDEV_MOUNTED_DEVICE_GUID, //
NULL, // Enumerator
NULL, // Parent Window
(DIGCF_PRESENT | DIGCF_INTERFACEDEVICE // Only Devices present & Interface class
));
if( hIntDevInfo == INVALID_HANDLE_VALUE ) {
DebugPrint( 1, "SetupDiGetClassDevs failed with error: %d\n", GetLastError() );
exit(1);
}
//
// Enumerate all the disk devices
//
index = 0;
while (TRUE) {
status = GetDeviceProperty( hIntDevInfo, index );
if ( status == FALSE ) {
break;
}
index++;
}
SetupDiDestroyDeviceInfoList(hIntDevInfo);
return 0;
}
#define MAX_COMP_INSTID 2096
#define MAX_COMP_DESC 2096
#define MAX_FRIENDLY 2096
BOOL GetDeviceProperty(HDEVINFO IntDevInfo, DWORD Index )
/*++
Routine Description:
This routine enumerates the volumes using the Device interface
GUID VolumeClassGuid. Gets the Adapter & Device property from the port
driver. Get the mount name, SCSI address and locks the volume for exclusive
access, if possible.
Arguments:
IntDevInfo - Handles to the interface device information list
Index - Device member
Return Value:
TRUE / FALSE. This decides whether to continue or not
--*/
{
SP_DEVICE_INTERFACE_DATA interfaceData;
PSP_DEVICE_INTERFACE_DETAIL_DATA interfaceDetailData = NULL;
STORAGE_PROPERTY_QUERY query;
PSTORAGE_ADAPTER_DESCRIPTOR adpDesc;
PSTORAGE_DEVICE_DESCRIPTOR devDesc;
SCSI_PASS_THROUGH_WITH_BUFFERS sptwb;
PMOUNTDEV_NAME mountName;
PSCSI_ADDRESS scsiAddress;
HANDLE hDevice;
BOOL status;
PUCHAR p;
UCHAR outBuf[BUF_LEN];
ULONG returnedLength;
ULONG length = 0,
returned = 0;
DWORD interfaceDetailDataSize,
reqSize,
errorCode,
i;
PMOUNTDEV_UNIQUE_ID mountId;
STORAGE_DEVICE_NUMBER number;
SP_DEVINFO_DATA deid;
DWORD dwRegType;
char szPdoName[MAX_FRIENDLY];
char buffer[2048];
//PDRIVE_LAYOUT_INFORMATION_EX DriveLayout = (PDRIVE_LAYOUT_INFORMATION_EX) buffer;
PDRIVE_LAYOUT_INFORMATION DriveLayout = (PDRIVE_LAYOUT_INFORMATION) buffer;
PARTITION_INFORMATION PartInfo;
PARTITION_INFORMATION_EX PartInfoEx;
interfaceData.cbSize = sizeof (SP_INTERFACE_DEVICE_DATA);
status = SetupDiEnumDeviceInterfaces (
IntDevInfo, // Interface Device Info handle
0, // Device Info data
(LPGUID)&VolumeClassGuid,
Index, // Member
&interfaceData // Device Interface Data
);
if ( status == FALSE ) {
errorCode = GetLastError();
if ( errorCode == ERROR_NO_MORE_ITEMS ) {
DebugPrint( 2, "No more interfaces\n" );
}
else {
DebugPrint( 1, "SetupDiEnumDeviceInterfaces failed with error: %d\n", errorCode );
}
return FALSE;
}
//
// Find out required buffer size, so pass NULL
//
status = SetupDiGetDeviceInterfaceDetail (
IntDevInfo, // Interface Device info handle
&interfaceData, // Interface data for the event class
NULL, // Checking for buffer size
0, // Checking for buffer size
&reqSize, // Buffer size required to get the detail data
NULL // Checking for buffer size
);
//
// This call returns ERROR_INSUFFICIENT_BUFFER with reqSize
// set to the required buffer size. Ignore the above error and
// pass a bigger buffer to get the detail data
//
if ( status == FALSE ) {
errorCode = GetLastError();
if ( errorCode != ERROR_INSUFFICIENT_BUFFER ) {
DebugPrint( 1, "SetupDiGetDeviceInterfaceDetail failed with error: %d\n", errorCode );
return FALSE;
}
}
//
// Allocate memory to get the interface detail data
// This contains the devicepath we need to open the device
//
interfaceDetailDataSize = reqSize;
interfaceDetailData = malloc (interfaceDetailDataSize);
if ( interfaceDetailData == NULL ) {
DebugPrint( 1, "Unable to allocate memory to get the interface detail data.\n" );
return FALSE;
}
interfaceDetailData->cbSize = sizeof (SP_INTERFACE_DEVICE_DETAIL_DATA);
status = SetupDiGetDeviceInterfaceDetail (
IntDevInfo, // Interface Device info handle
&interfaceData, // Interface data for the event class
interfaceDetailData, // Interface detail data
interfaceDetailDataSize, // Interface detail data size
&reqSize, // Buffer size required to get the detail data
NULL); // Interface device info
if ( status == FALSE ) {
DebugPrint( 1, "Error in SetupDiGetDeviceInterfaceDetail failed with error: %d\n", GetLastError() );
return FALSE;
}
//printf("Interface: %s\n", interfaceDetailData->DevicePath);
memset(szPdoName, 0, MAX_FRIENDLY);
deid.cbSize = sizeof(SP_DEVINFO_DATA);
SetupDiEnumDeviceInfo(IntDevInfo, Index, &deid);
SetupDiGetDeviceRegistryProperty(IntDevInfo, &deid,
SPDRP_PHYSICAL_DEVICE_OBJECT_NAME,
&dwRegType,
(BYTE*) szPdoName,
MAX_FRIENDLY,
NULL);
//printf(" PdoName : %s\n", szPdoName);
//
// Now we have the device path. Open the device interface
// to send Pass Through command
hDevice = CreateFile(
interfaceDetailData->DevicePath, // device interface name
GENERIC_READ | GENERIC_WRITE, // dwDesiredAccess
FILE_SHARE_READ | FILE_SHARE_WRITE, // dwShareMode
NULL, // lpSecurityAttributes
OPEN_EXISTING, // dwCreationDistribution
0, // dwFlagsAndAttributes
NULL // hTemplateFile
);
if (hDevice == INVALID_HANDLE_VALUE) {
DebugPrint( 1, "CreateFile failed with error: %d\n", GetLastError() );
return TRUE;
}
free (interfaceDetailData);
if (hDevice == INVALID_HANDLE_VALUE) {
DebugPrint( 1, "CreateFile failed with error: %d\n", GetLastError() );
return TRUE;
}
IsFixedDisk = FALSE;
status = DeviceIoControl(
hDevice,
IOCTL_MOUNTDEV_QUERY_DEVICE_NAME,
&outBuf,
BUF_LEN,
&outBuf,
BUF_LEN,
&returnedLength,
NULL
);
if ( !status ) {
DebugPrint( 1, "\nIOCTL_MOUNTDEV_QUERY_DEVICE_NAME failed with error code: %d.\n\n", GetLastError() );
}
else {
mountName = (PMOUNTDEV_NAME)&outBuf;
#if 0
if (!found) {
if (wcsstr(mountName->Name, L"Volume\0")) {
wcsncpy(FoundDevice, mountName->Name, mountName->NameLength);
//swprintf(FoundDevice, L"%s", mountName->Name);
found = TRUE;
printf("\noh yeah we found the device = %ws\n", FoundDevice);
}
}
#endif
}
// Get Drive letter from mount name
if (GetStorageDeviceNumber(hDevice, &number)) {
if (number.PartitionNumber != -1) {
GetDriveLetter(mountName);
printf("Disk #%d, Partition #%d", number.DeviceNumber, number.PartitionNumber);
if (GetPartitionInfoEx(hDevice, &PartInfoEx)) {
printf("\tOffset 0x%I64x\n", PartInfoEx.StartingOffset);
} else {
printf("[not available for this device]\n");
}
}
}
// Close handle the driver
if ( !CloseHandle(hDevice) ) {
DebugPrint( 2, "Failed to close device.\n");
}
return TRUE;
}
void GetDriveLetter(PMOUNTDEV_NAME mountName)
{
HANDLE hDevice;
BOOL status;
ULONG returnedLength,
nameLength,
mountPointsSize;
UCHAR outBuf[BUF_LEN];
PMOUNTMGR_MOUNT_POINTS mountMgrQueryPoints;
PMOUNTMGR_MOUNT_POINT pMountPoint;
USHORT i;
WCHAR volumeGuid[255];
BOOL showedDevice;
PWCHAR pName;
hDevice = CreateFile(
MOUNTMGR_NAME, // device interface name
GENERIC_READ, // dwDesiredAccess
FILE_SHARE_READ, // dwShareMode
NULL, // lpSecurityAttributes
OPEN_EXISTING, // dwCreationDistribution
0, // dwFlagsAndAttributes
NULL // hTemplateFile
);
if (hDevice == INVALID_HANDLE_VALUE) {
DebugPrint( 1, "CreateFile failed with error: %d\n", GetLastError() );
return;
}
nameLength = mountName->NameLength;
mountPointsSize = sizeof(MOUNTMGR_MOUNT_POINT) + nameLength;
pMountPoint = malloc(mountPointsSize);
ZeroMemory(pMountPoint, mountPointsSize );
pMountPoint->DeviceNameOffset = (USHORT) sizeof(MOUNTMGR_MOUNT_POINT);
pMountPoint->DeviceNameLength = (USHORT) nameLength;
memcpy( pMountPoint + 1, mountName->Name, nameLength );
ZeroMemory(outBuf, BUF_LEN );
// Get the triples (symbolic links, mount name & unique ID) from Mount Manager
status = DeviceIoControl(
hDevice,
IOCTL_MOUNTMGR_QUERY_POINTS,
pMountPoint,
mountPointsSize,
&outBuf,
BUF_LEN,
&returnedLength,
NULL
);
if ( !status ) {
DebugPrint( 1, "\nIOCTL_MOUNTMGR_QUERY_POINTS failed with error code: %d.\n\n", GetLastError() );
}
mountMgrQueryPoints = (PMOUNTMGR_MOUNT_POINTS) outBuf;
showedDevice = TRUE;
for (i = 0; i < mountMgrQueryPoints->NumberOfMountPoints - 1; i++ ) {
pMountPoint = &mountMgrQueryPoints->MountPoints[i];
if (!showedDevice) {
if (pMountPoint->DeviceNameLength) {
DebugPrint( 1, " Device Name: ");
PrintName( (PWCHAR)((PCHAR)mountMgrQueryPoints + pMountPoint->DeviceNameOffset), pMountPoint->DeviceNameLength/2);
printf("\n");
}
if (pMountPoint->UniqueIdLength) {
DebugPrint( 1, " Unique Id: ");
PrintName( (PWCHAR)((PCHAR)mountMgrQueryPoints + pMountPoint->UniqueIdOffset), pMountPoint->UniqueIdLength/2);
printf("\n");
}
showedDevice = TRUE;
}
if (pMountPoint->SymbolicLinkNameLength) {
//pName = (PWCHAR)((PCHAR)mountMgrQueryPoints + pMountPoint->SymbolicLinkNameOffset);
//if (wcsstr(pName, L"DosDevices")) {
// printf("got DosDevices...\n");
// //PrintName( pName, (pMountPoint->SymbolicLinkNameLength/2));
// PrintName( (PWCHAR)((PCHAR)mountMgrQueryPoints + pMountPoint->SymbolicLinkNameOffset), (pMountPoint->SymbolicLinkNameLength/2));
// printf("\n");
//}
pName = (PWCHAR)((PCHAR)mountMgrQueryPoints + pMountPoint->SymbolicLinkNameOffset);
if (wcsstr(pName, L"Volume")) {
pName = (PWCHAR)((PCHAR)mountMgrQueryPoints + pMountPoint->SymbolicLinkNameOffset);
pName += 11;
PrintName( pName, (pMountPoint->SymbolicLinkNameLength/2)-12);
printf("\t");
}
//PrintName( (PWCHAR)((PCHAR)mountMgrQueryPoints + pMountPoint->SymbolicLinkNameOffset), (pMountPoint->SymbolicLinkNameLength/2));
}
}
//
// Close handle the driver
if ( !CloseHandle(hDevice) ) {
DebugPrint( 1, "Failed to close device.\n");
}
free(pMountPoint);
return;
}
/**************************************************mounts.c*************************************************/
/*************************************************MakeFile**************************************************/
#
# DO NOT EDIT THIS FILE!!! Edit .\sources. if you want to add a new source
# file to this component. This file merely indirects to the real make file
# that is shared by all the driver components of the Windows NT DDK
#
!INCLUDE $(NTMAKEENV)\makefile.def
/*************************************************MakeFile**************************************************/
/*************************************************Sources***************************************************/
TARGETNAME=enumvol
TARGETTYPE=PROGRAM
UMTYPE=console
UMBASE=0x1000000
USE_LIBCMT=1
TARGETPATH=obj
INCLUDES=$(DDK_INC_PATH); \
$(BASEDIR)\inc; \
$(BASEDIR)\inc\ddk;
TARGETLIBS= $(DDK_LIB_PATH)\setupapi.lib
SOURCES=mounts.c
/*************************************************Sources**********************************************
Please sign in to use this experience.
Sign in