1 /*
2  * COPYRIGHT:       See COPYING in the top level directory
3  * PROJECT:         ReactOS system libraries
4  * FILE:            dll/win32/kernel32/client/file/mntpoint.c
5  * PURPOSE:         File volume mount point functions
6  * PROGRAMMER:      Pierre Schweitzer (pierre@reactos.org)
7  */
8 
9 #include <k32.h>
10 #define NDEBUG
11 #include <debug.h>
12 
13 /*
14  * @implemented
15  */
16 static BOOL
17 GetVolumeNameForRoot(IN LPCWSTR lpszRootPath,
18                      OUT LPWSTR lpszVolumeName,
19                      IN DWORD cchBufferLength)
20 {
21     BOOL Ret;
22     NTSTATUS Status;
23     PWSTR FoundVolume;
24     DWORD BytesReturned;
25     UNICODE_STRING NtPathName;
26     IO_STATUS_BLOCK IoStatusBlock;
27     PMOUNTMGR_MOUNT_POINT MountPoint;
28     ULONG CurrentMntPt, FoundVolumeLen;
29     PMOUNTMGR_MOUNT_POINTS MountPoints;
30     OBJECT_ATTRIBUTES ObjectAttributes;
31     HANDLE VolumeHandle, MountMgrHandle;
32     struct
33     {
34         MOUNTDEV_NAME;
35         WCHAR Buffer[MAX_PATH];
36     } MountDevName;
37 
38     /* It makes no sense on a non-local drive */
39     if (GetDriveTypeW(lpszRootPath) == DRIVE_REMOTE)
40     {
41         SetLastError(ERROR_PATH_NOT_FOUND);
42         return FALSE;
43     }
44 
45     /* Get the NT path */
46     if (!RtlDosPathNameToNtPathName_U(lpszRootPath, &NtPathName, NULL, NULL))
47     {
48         SetLastError(ERROR_PATH_NOT_FOUND);
49         return FALSE;
50     }
51 
52     /* If it's a root path - likely - drop backslash to open volume */
53     if (NtPathName.Buffer[(NtPathName.Length / sizeof(WCHAR)) - 1] == L'\\')
54     {
55         NtPathName.Buffer[(NtPathName.Length / sizeof(WCHAR)) - 1] = UNICODE_NULL;
56         NtPathName.Length -= sizeof(WCHAR);
57     }
58 
59     /* If that's a DOS volume, upper case the letter */
60     if (NtPathName.Length >= 2 * sizeof(WCHAR))
61     {
62         if (NtPathName.Buffer[(NtPathName.Length / sizeof(WCHAR)) - 1] == L':')
63         {
64             NtPathName.Buffer[(NtPathName.Length / sizeof(WCHAR)) - 2] = _toupper(NtPathName.Buffer[(NtPathName.Length / sizeof(WCHAR)) - 2]);
65         }
66     }
67 
68     /* Attempt to open the volume */
69     InitializeObjectAttributes(&ObjectAttributes, &NtPathName,
70                                OBJ_CASE_INSENSITIVE, NULL, NULL);
71     Status = NtOpenFile(&VolumeHandle, SYNCHRONIZE | FILE_READ_ATTRIBUTES,
72                         &ObjectAttributes, &IoStatusBlock,
73                         FILE_SHARE_READ | FILE_SHARE_WRITE,
74                         FILE_SYNCHRONOUS_IO_ALERT);
75     if (!NT_SUCCESS(Status))
76     {
77         RtlFreeHeap(RtlGetProcessHeap(), 0, NtPathName.Buffer);
78         BaseSetLastNTError(Status);
79         return FALSE;
80     }
81 
82     /* Query the device name - that's what we'll translate */
83     if (!DeviceIoControl(VolumeHandle, IOCTL_MOUNTDEV_QUERY_DEVICE_NAME, NULL,
84                          0, &MountDevName, sizeof(MountDevName), &BytesReturned,
85                          NULL))
86     {
87         NtClose(VolumeHandle);
88         RtlFreeHeap(RtlGetProcessHeap(), 0, NtPathName.Buffer);
89         return FALSE;
90     }
91 
92     /* No longer need the volume */
93     NtClose(VolumeHandle);
94     RtlFreeHeap(RtlGetProcessHeap(), 0, NtPathName.Buffer);
95 
96     /* We'll keep the device name for later usage */
97     NtPathName.Length = MountDevName.NameLength;
98     NtPathName.MaximumLength = MountDevName.NameLength + sizeof(UNICODE_NULL);
99     NtPathName.Buffer = RtlAllocateHeap(RtlGetProcessHeap(), 0, NtPathName.MaximumLength);
100     if (NtPathName.Buffer == NULL)
101     {
102         SetLastError(ERROR_NOT_ENOUGH_MEMORY);
103         return FALSE;
104     }
105 
106     RtlCopyMemory(NtPathName.Buffer, MountDevName.Name, NtPathName.Length);
107     NtPathName.Buffer[NtPathName.Length / sizeof(WCHAR)] = UNICODE_NULL;
108 
109     /* Allocate the structure for querying the mount mgr */
110     MountPoint = RtlAllocateHeap(RtlGetProcessHeap(), 0,
111                                  NtPathName.Length + sizeof(MOUNTMGR_MOUNT_POINT));
112     if (MountPoint == NULL)
113     {
114         RtlFreeHeap(RtlGetProcessHeap(), 0, NtPathName.Buffer);
115         SetLastError(ERROR_NOT_ENOUGH_MEMORY);
116         return FALSE;
117     }
118 
119     /* 0 everything, we provide a device name */
120     RtlZeroMemory(MountPoint, sizeof(MOUNTMGR_MOUNT_POINT));
121     MountPoint->DeviceNameOffset = sizeof(MOUNTMGR_MOUNT_POINT);
122     MountPoint->DeviceNameLength = NtPathName.Length;
123     RtlCopyMemory((PVOID)((ULONG_PTR)MountPoint + sizeof(MOUNTMGR_MOUNT_POINT)), NtPathName.Buffer, NtPathName.Length);
124 
125     /* Allocate a dummy output buffer to probe for size */
126     MountPoints = RtlAllocateHeap(RtlGetProcessHeap(), 0, sizeof(MOUNTMGR_MOUNT_POINTS));
127     if (MountPoints == NULL)
128     {
129         RtlFreeHeap(RtlGetProcessHeap(), 0, NtPathName.Buffer);
130         RtlFreeHeap(RtlGetProcessHeap(), 0, MountPoint);
131         SetLastError(ERROR_NOT_ENOUGH_MEMORY);
132         return FALSE;
133     }
134 
135     /* Open a handle to the mount manager */
136     MountMgrHandle = CreateFileW(MOUNTMGR_DOS_DEVICE_NAME, 0,
137                                  FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
138                                  OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,
139                                  INVALID_HANDLE_VALUE);
140     if (MountMgrHandle == INVALID_HANDLE_VALUE)
141     {
142         RtlFreeHeap(RtlGetProcessHeap(), 0, NtPathName.Buffer);
143         RtlFreeHeap(RtlGetProcessHeap(), 0, MountPoints);
144         RtlFreeHeap(RtlGetProcessHeap(), 0, MountPoint);
145         return FALSE;
146     }
147 
148     /* Query the names associated to our device name */
149     Ret = DeviceIoControl(MountMgrHandle, IOCTL_MOUNTMGR_QUERY_POINTS,
150                           MountPoint, NtPathName.Length + sizeof(MOUNTMGR_MOUNT_POINT),
151                           MountPoints, sizeof(MOUNTMGR_MOUNT_POINTS), &BytesReturned,
152                           NULL);
153     /* As long as the buffer is too small, keep looping */
154     while (!Ret && GetLastError() == ERROR_MORE_DATA)
155     {
156         ULONG BufferSize;
157 
158         /* Get the size we've to allocate */
159         BufferSize = MountPoints->Size;
160         /* Reallocate the buffer with enough room */
161         RtlFreeHeap(RtlGetProcessHeap(), 0, MountPoints);
162         MountPoints = RtlAllocateHeap(RtlGetProcessHeap(), 0, BufferSize);
163         if (MountPoints == NULL)
164         {
165             CloseHandle(MountMgrHandle);
166             RtlFreeHeap(RtlGetProcessHeap(), 0, NtPathName.Buffer);
167             RtlFreeHeap(RtlGetProcessHeap(), 0, MountPoint);
168             SetLastError(ERROR_NOT_ENOUGH_MEMORY);
169             return FALSE;
170         }
171 
172         /* Reissue the request, it should work now! */
173         Ret = DeviceIoControl(MountMgrHandle, IOCTL_MOUNTMGR_QUERY_POINTS,
174                               MountPoint, NtPathName.Length + sizeof(MOUNTMGR_MOUNT_POINT),
175                               MountPoints, BufferSize, &BytesReturned, NULL);
176     }
177 
178     /* We're done, no longer need the mount manager */
179     CloseHandle(MountMgrHandle);
180     /* Nor our input buffer */
181     RtlFreeHeap(RtlGetProcessHeap(), 0, MountPoint);
182 
183     /* If the mount manager failed, just quit */
184     if (!Ret)
185     {
186         RtlFreeHeap(RtlGetProcessHeap(), 0, NtPathName.Buffer);
187         RtlFreeHeap(RtlGetProcessHeap(), 0, MountPoints);
188         SetLastError(ERROR_INVALID_PARAMETER);
189         return FALSE;
190     }
191 
192     CurrentMntPt = 0;
193     /* If there were no associated mount points, we'll return the device name */
194     if (MountPoints->NumberOfMountPoints == 0)
195     {
196         FoundVolume = NtPathName.Buffer;
197         FoundVolumeLen = NtPathName.Length;
198     }
199     /* Otherwise, find one which is matching */
200     else
201     {
202         for (; CurrentMntPt < MountPoints->NumberOfMountPoints; ++CurrentMntPt)
203         {
204             UNICODE_STRING SymbolicLink;
205 
206             /* Make a string of it, to easy the checks */
207             SymbolicLink.Length = MountPoints->MountPoints[CurrentMntPt].SymbolicLinkNameLength;
208             SymbolicLink.MaximumLength = SymbolicLink.Length;
209             SymbolicLink.Buffer = (PVOID)((ULONG_PTR)MountPoints + MountPoints->MountPoints[CurrentMntPt].SymbolicLinkNameOffset);
210             /* If that's a NT volume name (GUID form), keep it! */
211             if (MOUNTMGR_IS_NT_VOLUME_NAME(&SymbolicLink))
212             {
213                 FoundVolume = SymbolicLink.Buffer;
214                 FoundVolumeLen = SymbolicLink.Length;
215 
216                 break;
217             }
218         }
219     }
220 
221     /* We couldn't find anything matching, return an error */
222     if (CurrentMntPt == MountPoints->NumberOfMountPoints)
223     {
224         RtlFreeHeap(RtlGetProcessHeap(), 0, NtPathName.Buffer);
225         RtlFreeHeap(RtlGetProcessHeap(), 0, MountPoints);
226         SetLastError(ERROR_INVALID_PARAMETER);
227         return FALSE;
228     }
229 
230     /* We found a matching volume, have we enough memory to return it? */
231     if (cchBufferLength * sizeof(WCHAR) < FoundVolumeLen + 2 * sizeof(WCHAR))
232     {
233         RtlFreeHeap(RtlGetProcessHeap(), 0, NtPathName.Buffer);
234         RtlFreeHeap(RtlGetProcessHeap(), 0, MountPoints);
235         SetLastError(ERROR_FILENAME_EXCED_RANGE);
236         return FALSE;
237     }
238 
239     /* Copy it back! */
240     RtlCopyMemory(lpszVolumeName, FoundVolume, FoundVolumeLen);
241     /* Make it compliant */
242     lpszVolumeName[1] = L'\\';
243     /* And transform it as root path */
244     lpszVolumeName[FoundVolumeLen / sizeof(WCHAR)] = L'\\';
245     lpszVolumeName[FoundVolumeLen / sizeof(WCHAR) + 1] = UNICODE_NULL;
246 
247     /* We're done! */
248     RtlFreeHeap(RtlGetProcessHeap(), 0, NtPathName.Buffer);
249     RtlFreeHeap(RtlGetProcessHeap(), 0, MountPoints);
250     return TRUE;
251 }
252 
253 /*
254  * @implemented
255  */
256 BOOL
257 BasepGetVolumeNameFromReparsePoint(IN LPCWSTR lpszMountPoint,
258                                    OUT LPWSTR lpszVolumeName,
259                                    IN DWORD cchBufferLength,
260                                    OUT LPBOOL IsAMountPoint)
261 {
262     WCHAR Old;
263     DWORD BytesReturned;
264     HANDLE ReparseHandle;
265     UNICODE_STRING SubstituteName;
266     PREPARSE_DATA_BUFFER ReparseBuffer;
267 
268     /* Try to open the reparse point */
269     ReparseHandle = CreateFileW(lpszMountPoint, 0,
270                                 FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
271                                 OPEN_EXISTING,
272                                 FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS | FILE_ATTRIBUTE_NORMAL,
273                                 INVALID_HANDLE_VALUE);
274     /* It failed! */
275     if (ReparseHandle == INVALID_HANDLE_VALUE)
276     {
277         /* Report it's not a mount point (it's not a reparse point) */
278         if (IsAMountPoint != NULL)
279         {
280             *IsAMountPoint = FALSE;
281         }
282 
283         /* And zero output */
284         if (lpszVolumeName != NULL && cchBufferLength >= 1)
285         {
286             lpszVolumeName[0] = UNICODE_NULL;
287         }
288 
289         return FALSE;
290     }
291 
292     /* This is a mount point! */
293     if (IsAMountPoint != NULL)
294     {
295         *IsAMountPoint = TRUE;
296     }
297 
298     /* Prepare a buffer big enough to read its data */
299     ReparseBuffer = RtlAllocateHeap(RtlGetProcessHeap(), 0, MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
300     if (ReparseBuffer == NULL)
301     {
302         CloseHandle(ReparseHandle);
303         SetLastError(ERROR_NOT_ENOUGH_MEMORY);
304 
305         /* Zero output */
306         if (lpszVolumeName != NULL && cchBufferLength >= 1)
307         {
308             lpszVolumeName[0] = UNICODE_NULL;
309         }
310 
311         return FALSE;
312     }
313 
314     /* Dump the reparse point data */
315     if (!DeviceIoControl(ReparseHandle, FSCTL_GET_REPARSE_POINT, NULL, 0,
316                          ReparseBuffer, MAXIMUM_REPARSE_DATA_BUFFER_SIZE, &BytesReturned,
317                          NULL))
318     {
319         RtlFreeHeap(RtlGetProcessHeap(), 0, ReparseBuffer);
320         CloseHandle(ReparseHandle);
321 
322         /* Zero output */
323         if (lpszVolumeName != NULL && cchBufferLength >= 1)
324         {
325             lpszVolumeName[0] = UNICODE_NULL;
326         }
327 
328         return FALSE;
329     }
330 
331     /* We no longer need the reparse point */
332     CloseHandle(ReparseHandle);
333 
334     /* We only handle mount points */
335     if (ReparseBuffer->ReparseTag != IO_REPARSE_TAG_MOUNT_POINT)
336     {
337         RtlFreeHeap(RtlGetProcessHeap(), 0, ReparseBuffer);
338 
339         /* Zero output */
340         if (lpszVolumeName != NULL && cchBufferLength >= 1)
341         {
342             lpszVolumeName[0] = UNICODE_NULL;
343         }
344 
345         return FALSE;
346     }
347 
348     /* Do we have enough room for copying substitue name? */
349     if ((ReparseBuffer->MountPointReparseBuffer.SubstituteNameLength + sizeof(UNICODE_NULL)) > cchBufferLength * sizeof(WCHAR))
350     {
351         RtlFreeHeap(RtlGetProcessHeap(), 0, ReparseBuffer);
352         SetLastError(ERROR_FILENAME_EXCED_RANGE);
353 
354         /* Zero output */
355         if (lpszVolumeName != NULL && cchBufferLength >= 1)
356         {
357             lpszVolumeName[0] = UNICODE_NULL;
358         }
359 
360         return FALSE;
361     }
362 
363     /* Copy the link target */
364     RtlCopyMemory(lpszVolumeName,
365                   &ReparseBuffer->MountPointReparseBuffer.PathBuffer[ReparseBuffer->MountPointReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)],
366                   ReparseBuffer->MountPointReparseBuffer.SubstituteNameLength);
367     /* Make it DOS valid */
368     Old = lpszVolumeName[1];
369     /* We want a root path */
370     lpszVolumeName[1] = L'\\';
371     /* And null terminate obviously */
372     lpszVolumeName[ReparseBuffer->MountPointReparseBuffer.SubstituteNameLength / sizeof(WCHAR)] = UNICODE_NULL;
373 
374     /* Make it a string to easily check it */
375     SubstituteName.Length = ReparseBuffer->MountPointReparseBuffer.SubstituteNameLength;
376     SubstituteName.MaximumLength = SubstituteName.Length;
377     SubstituteName.Buffer = lpszVolumeName;
378 
379     /* No longer need the data? */
380     RtlFreeHeap(RtlGetProcessHeap(), 0, ReparseBuffer);
381 
382     /* Is that a dos volume name with backslash? */
383     if (MOUNTMGR_IS_DOS_VOLUME_NAME_WB(&SubstituteName))
384     {
385         return TRUE;
386     }
387 
388     /* No, so restore previous name and return to the caller */
389     lpszVolumeName[1] = Old;
390     SetLastError(ERROR_INVALID_PARAMETER);
391     return FALSE;
392 }
393 
394 /*
395  * @implemented
396  */
397 BOOL
398 BasepGetVolumeNameForVolumeMountPoint(IN LPCWSTR lpszMountPoint,
399                                       OUT LPWSTR lpszVolumeName,
400                                       IN DWORD cchBufferLength,
401                                       OUT LPBOOL IsAMountPoint)
402 {
403     BOOL Ret;
404     UNICODE_STRING MountPoint;
405 
406     /* Assume it's a mount point (likely for non reparse points) */
407     if (IsAMountPoint != NULL)
408     {
409         *IsAMountPoint = 1;
410     }
411 
412     /* Make a string with the mount point name */
413     RtlInitUnicodeString(&MountPoint, lpszMountPoint);
414     /* Not a root path? */
415     if (MountPoint.Buffer[(MountPoint.Length / sizeof(WCHAR)) - 1] != L'\\')
416     {
417         BaseSetLastNTError(STATUS_OBJECT_NAME_INVALID);
418         /* Zero output */
419         if (lpszVolumeName != NULL && cchBufferLength >= 1)
420         {
421             lpszVolumeName[0] = UNICODE_NULL;
422         }
423 
424         return FALSE;
425     }
426 
427     /* Does it look like <letter>:\? */
428     if (MountPoint.Length == 3 * sizeof(WCHAR))
429     {
430         /* Try to get volume name for root path */
431         Ret = GetVolumeNameForRoot(lpszMountPoint, lpszVolumeName, cchBufferLength);
432         /* It failed? */
433         if (!Ret)
434         {
435             /* If wasn't a drive letter, so maybe a reparse point? */
436             if (MountPoint.Buffer[1] != ':')
437             {
438                 Ret = BasepGetVolumeNameFromReparsePoint(lpszMountPoint, lpszVolumeName, cchBufferLength, IsAMountPoint);
439             }
440             /* It was, so zero output */
441             else if (lpszVolumeName != NULL && cchBufferLength >= 1)
442             {
443                 lpszVolumeName[0] = UNICODE_NULL;
444             }
445         }
446     }
447     else
448     {
449         /* Try to get volume name for root path */
450         Ret = GetVolumeNameForRoot(lpszMountPoint, lpszVolumeName, cchBufferLength);
451         /* It failed? */
452         if (!Ret)
453         {
454             /* It was a DOS volume as UNC name, so fail and zero output */
455             if (MountPoint.Length == 14 && MountPoint.Buffer[0] == '\\' && MountPoint.Buffer[1] == '\\' &&
456                 (MountPoint.Buffer[2] == '.' || MountPoint.Buffer[2] == '?') && MountPoint.Buffer[3] == L'\\' &&
457                 MountPoint.Buffer[5] == ':')
458             {
459                 if (lpszVolumeName != NULL && cchBufferLength >= 1)
460                 {
461                     lpszVolumeName[0] = UNICODE_NULL;
462                 }
463             }
464             /* Maybe it's a reparse point? */
465             else
466             {
467                 Ret = BasepGetVolumeNameFromReparsePoint(lpszMountPoint, lpszVolumeName, cchBufferLength, IsAMountPoint);
468             }
469         }
470     }
471 
472     return Ret;
473 }
474 
475 /**
476  * @name GetVolumeNameForVolumeMountPointW
477  * @implemented
478  *
479  * Return an unique volume name for a drive root or mount point.
480  *
481  * @param VolumeMountPoint
482  *        Pointer to string that contains either root drive name or
483  *        mount point name.
484  * @param VolumeName
485  *        Pointer to buffer that is filled with resulting unique
486  *        volume name on success.
487  * @param VolumeNameLength
488  *        Size of VolumeName buffer in TCHARs.
489  *
490  * @return
491  *     TRUE when the function succeeds and the VolumeName buffer is filled,
492  *     FALSE otherwise.
493  */
494 BOOL
495 WINAPI
496 GetVolumeNameForVolumeMountPointW(IN LPCWSTR VolumeMountPoint,
497                                   OUT LPWSTR VolumeName,
498                                   IN DWORD VolumeNameLength)
499 {
500     BOOL Ret;
501 
502     /* Just query our internal function */
503     Ret = BasepGetVolumeNameForVolumeMountPoint(VolumeMountPoint, VolumeName,
504                                                 VolumeNameLength, NULL);
505     if (!Ret && VolumeName != NULL && VolumeNameLength >= 1)
506     {
507         VolumeName[0] = UNICODE_NULL;
508     }
509 
510     return Ret;
511 }
512 
513 /*
514  * @implemented
515  */
516 BOOL
517 WINAPI
518 GetVolumeNameForVolumeMountPointA(IN LPCSTR lpszVolumeMountPoint,
519                                   IN LPSTR lpszVolumeName,
520                                   IN DWORD cchBufferLength)
521 {
522     BOOL Ret;
523     ANSI_STRING VolumeName;
524     UNICODE_STRING VolumeNameU;
525     PUNICODE_STRING VolumeMountPointU;
526 
527     /* Convert mount point to unicode */
528     VolumeMountPointU = Basep8BitStringToStaticUnicodeString(lpszVolumeMountPoint);
529     if (VolumeMountPointU == NULL)
530     {
531         return FALSE;
532     }
533 
534     /* Initialize the strings we'll use for convention */
535     VolumeName.Buffer = lpszVolumeName;
536     VolumeName.Length = 0;
537     VolumeName.MaximumLength = cchBufferLength - 1;
538 
539     VolumeNameU.Length = 0;
540     VolumeNameU.MaximumLength = (cchBufferLength - 1) * sizeof(WCHAR) + sizeof(UNICODE_NULL);
541     /* Allocate a buffer big enough to contain the returned name */
542     VolumeNameU.Buffer = RtlAllocateHeap(RtlGetProcessHeap(), 0, VolumeNameU.MaximumLength);
543     if (VolumeNameU.Buffer == NULL)
544     {
545         SetLastError(ERROR_NOT_ENOUGH_MEMORY);
546         return FALSE;
547     }
548 
549     /* Query -W */
550     Ret = GetVolumeNameForVolumeMountPointW(VolumeMountPointU->Buffer, VolumeNameU.Buffer, cchBufferLength);
551     /* If it succeed, perform -A conversion */
552     if (Ret)
553     {
554         NTSTATUS Status;
555 
556         /* Reinit our string for length */
557         RtlInitUnicodeString(&VolumeNameU, VolumeNameU.Buffer);
558         /* Convert to ANSI */
559         Status = RtlUnicodeStringToAnsiString(&VolumeName, &VolumeNameU, FALSE);
560         /* If conversion failed, force failure, otherwise, just null terminate */
561         if (!NT_SUCCESS(Status))
562         {
563             Ret = FALSE;
564             BaseSetLastNTError(Status);
565         }
566         else
567         {
568             VolumeName.Buffer[VolumeName.Length] = ANSI_NULL;
569         }
570     }
571 
572     /* Internal buffer no longer needed */
573     RtlFreeHeap(RtlGetProcessHeap(), 0, VolumeNameU.Buffer);
574 
575     return Ret;
576 }
577 
578 /*
579  * @unimplemented
580  */
581 BOOL
582 WINAPI
583 SetVolumeMountPointW(IN LPCWSTR lpszVolumeMountPoint,
584                      IN LPCWSTR lpszVolumeName)
585 {
586     STUB;
587     return 0;
588 }
589 
590 /*
591  * @unimplemented
592  */
593 BOOL
594 WINAPI
595 SetVolumeMountPointA(IN LPCSTR lpszVolumeMountPoint,
596                      IN LPCSTR lpszVolumeName)
597 {
598     STUB;
599     return 0;
600 }
601 
602 /*
603  * @unimplemented
604  */
605 BOOL
606 WINAPI
607 DeleteVolumeMountPointA(IN LPCSTR lpszVolumeMountPoint)
608 {
609     STUB;
610     return 0;
611 }
612 
613 /*
614  * @unimplemented
615  */
616 BOOL
617 WINAPI
618 DeleteVolumeMountPointW(IN LPCWSTR lpszVolumeMountPoint)
619 {
620     STUB;
621     return 0;
622 }
623 
624 /*
625  * @unimplemented
626  */
627 HANDLE
628 WINAPI
629 FindFirstVolumeMountPointW(IN LPCWSTR lpszRootPathName,
630                            IN LPWSTR lpszVolumeMountPoint,
631                            IN DWORD cchBufferLength)
632 {
633     STUB;
634     return 0;
635 }
636 
637 /*
638  * @unimplemented
639  */
640 HANDLE
641 WINAPI
642 FindFirstVolumeMountPointA(IN LPCSTR lpszRootPathName,
643                            IN LPSTR lpszVolumeMountPoint,
644                            IN DWORD cchBufferLength)
645 {
646     STUB;
647     return 0;
648 }
649 
650 /*
651  * @unimplemented
652  */
653 BOOL
654 WINAPI
655 FindNextVolumeMountPointA(IN HANDLE hFindVolumeMountPoint,
656                           IN LPSTR lpszVolumeMountPoint,
657                           DWORD cchBufferLength)
658 {
659     STUB;
660     return 0;
661 }
662 
663 /*
664  * @unimplemented
665  */
666 BOOL
667 WINAPI
668 FindNextVolumeMountPointW(IN HANDLE hFindVolumeMountPoint,
669                           IN LPWSTR lpszVolumeMountPoint,
670                           DWORD cchBufferLength)
671 {
672     STUB;
673     return 0;
674 }
675 
676 /*
677  * @unimplemented
678  */
679 BOOL
680 WINAPI
681 FindVolumeMountPointClose(IN HANDLE hFindVolumeMountPoint)
682 {
683     STUB;
684     return 0;
685 }
686 
687 /* EOF */
688