uLib  User mode C/C++ extended API library for Win32 programmers.
ReparsePnt.cpp
Go to the documentation of this file.
1 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2 // Project: uLib - User mode utility library.
3 // Module: NTFS reparse point functions.
4 // Author: Copyright (c) Love Nystrom
5 // License: NNOSL (BSD descendant, see NNOSL.txt in the base directory).
6 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
7 // Ref: http://www.flexhex.com/docs/articles/hard-links.phtml
8 // Ref: Native code by Max Nemirovsky at community.osr.com
9 // https://community.osr.com/discussion/128702/fsctl-set-reparse-point-returns-error-invalid-reparse-data
10 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
11 //
12 // The reparse tags are a DWORD. The 32 bits are laid out as follows:
13 //
14 // 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1
15 // 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
16 // +-+-+-+-+-----------------------+-------------------------------+
17 // |M|R|N|R| Reserved bits | Reparse Tag Value |
18 // +-+-+-+-+-----------------------+-------------------------------+
19 //
20 // M is the Microsoft bit. When set to 1, it denotes a tag owned by Microsoft.
21 // All ISVs must use a tag with a 0 in this position.
22 // Note: If a Microsoft tag is used by non-Microsoft software, the
23 // behavior is not defined.
24 //
25 // R is reserved. Must be zero for non-Microsoft tags.
26 //
27 // N is name surrogate. When set to 1, the file represents another named
28 // entity in the system.
29 //
30 // The M and N bits are OR-able.
31 //
32 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
33 
34 #include <uLib/_Internal.h>
35 #include <uLib/UtilFunc.h>
36 #include <uLib/StrFunc.h>
37 #include <uLib/Debug.h>
38 #include <WinIoCtl.h>
39 
40 //static const DWORD _TEST_RPTAG = 0xCAFE;
41 //static const GUID _TEST_RPGUID = // {27A530A9-0F30-42d1-99ED-C9861C4EC6B2}
42 //{ 0x27a530a9, 0xf30, 0x42d1, { 0x99, 0xed, 0xc9, 0x86, 0x1c, 0x4e, 0xc6, 0xb2 }};
43 
44 //#define OFFS2PTR( base,offs ) PVOID( PBYTE(base) + ULONG_PTR(offs) )
45 //#define PTR2OFFS( base,ptr ) ULONG( PBYTE(ptr) - PBYTE(base) )
46 
47 #ifndef FSCTL_GET_REPARSE_POINT // ntifs.h and winioctl.h
48 
49  #ifndef CTL_CODE
50  #define CTL_CODE( DeviceType, Function, Method, Access ) ( \
51  ((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method) \
52  )
53  #endif
54  #ifndef FILE_DEVICE_FILE_SYSTEM
55  #define FILE_DEVICE_FILE_SYSTEM 0x00000009
56  #endif
57  #ifndef METHOD_BUFFERED
58  #define METHOD_BUFFERED 0
59  #endif
60  #ifndef FILE_ANY_ACCESS
61  #define FILE_ANY_ACCESS 0
62  #endif
63 
64  // Get a REPARSE_DATA_BUFFER or REPARSE_GUID_DATA_BUFFER
65 
66  #define FSCTL_GET_REPARSE_POINT \
67  CTL_CODE( FILE_DEVICE_FILE_SYSTEM, 42, METHOD_BUFFERED, FILE_ANY_ACCESS ) /* REPARSE_DATA_BUFFER */
68 
69  #define FSCTL_SET_REPARSE_POINT \
70  CTL_CODE( FILE_DEVICE_FILE_SYSTEM, 41, METHOD_BUFFERED, FILE_SPECIAL_ACCESS ) /* REPARSE_DATA_BUFFER */
71 
72  #define FSCTL_DELETE_REPARSE_POINT \
73  CTL_CODE( FILE_DEVICE_FILE_SYSTEM, 43, METHOD_BUFFERED, FILE_SPECIAL_ACCESS ) /* REPARSE_DATA_BUFFER */
74 
75 #endif//ndef FSCTL_GET_REPARSE_POINT
76 #ifndef _NTIFS_ // Copy REPARSE_DATA_BUFFER from the DDK..
77 
78  typedef struct _REPARSE_DATA_BUFFER {
79  ULONG ReparseTag;
81  USHORT Reserved;
82  union {
83  struct {
84  USHORT SubstituteNameOffset;
85  USHORT SubstituteNameLength;
86  USHORT PrintNameOffset;
87  USHORT PrintNameLength;
88  ULONG Flags;
89  WCHAR PathBuffer[1];
90  } SymbolicLinkReparseBuffer;
91  struct {
92  USHORT SubstituteNameOffset;
93  USHORT SubstituteNameLength;
94  USHORT PrintNameOffset;
95  USHORT PrintNameLength;
96  WCHAR PathBuffer[1];
97  } MountPointReparseBuffer;
98  struct {
99  UCHAR DataBuffer[1];
100  } GenericReparseBuffer;
101  };
102  } REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER;
103 
104  #define REPARSE_DATA_BUFFER_HEADER_SIZE \
105  FIELD_OFFSET( REPARSE_DATA_BUFFER, GenericReparseBuffer )
106 
107  #ifndef SYMLINK_FLAG_RELATIVE
108  #define SYMLINK_FLAG_RELATIVE 0x00000001
109  #endif
110  #ifndef IO_REPARSE_TAG_IIS_CACHE
111  #define IO_REPARSE_TAG_IIS_CACHE 0xA0000010 // I don't know the data struct of this..
112  #endif
113  #define IO_REPARSE_TAG_VALID_VALUES 0xF000FFFF
114 
115  #define IsReparseTagValid(_tag) ( \
116  !((_tag) & ~IO_REPARSE_TAG_VALID_VALUES) \
117  && ((_tag) > IO_REPARSE_TAG_RESERVED_RANGE) \
118  )
119 
120 #endif//ndef _NTIFS_
121 
122 #define MAX_REPARSE_BUFFER \
123  MAXIMUM_REPARSE_DATA_BUFFER_SIZE // 16 kB
124 
125 // SET_WRPD_PRIVILEGE - WRPD == WriteReParseData
126 // Afaik, FSCTL_SET_REPARSE_POINT requires SE_RESTORE_NAME.
127 // Set to 1 to include privilege setting code..
128 
129 #define SET_WRPD_PRIVILEGE 1
130 
131 #if 0 // Memo - These are declared in WinNT.h
132  typedef struct _REPARSE_GUID_DATA_BUFFER
133  {
134  ULONG ReparseTag;
135  USHORT ReparseDataLength;
136  USHORT Reserved;
137  GUID ReparseGuid;
138  struct {
139  UCHAR DataBuffer[1];
140  } GenericReparseBuffer;
141  }
142  REPARSE_GUID_DATA_BUFFER, *PREPARSE_GUID_DATA_BUFFER;
143 
144  #define REPARSE_GUID_DATA_BUFFER_HEADER_SIZE \
145  FIELD_OFFSET( REPARSE_GUID_DATA_BUFFER, GenericReparseBuffer )
146 #endif
147 
148 //==-------------------------------------------------------------------------------------
149 // LOCAL SUBS
150 //==-------------------------------------------------------------------------------------
151 
152 // PONDER: Some of these a prime candidates for public low level use..!
153 
154 HANDLE _OpenReparsePoint( CSTR LinkName, ACCESS_MASK Access, DWORD Share ) // public
155 // Open an FS object in a manner suitable for reparse data access.
156 {
157  return CheckHandle( CreateFile(
158  LinkName, Access, Share, NULL, OPEN_EXISTING,
159  FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS,
160  NULL
161  ));
162 }
163 
164 static bool _IsNameSurrogateTag( DWORD Tag )
165 // Accept only tags that are known (symlink and mount point).
166 {
167  bool ok = IsReparseTagValid( Tag );
168  if (!ok) SetLastError( ERROR_INVALID_DATA ); // Not a proper tag..
169  else
170  {
171  #if 0 // Flaw: This doesn't exclude future unknown surrogates.
172  ok = IsReparseTagMicrosoft( Tag );
173  if (ok ) ok = IsReparseTagNameSurrogate( Tag );
174  #else // So, be specific: We know symlinks and mount points.
175  ok = (Tag == IO_REPARSE_TAG_SYMLINK);
176  if (!ok) ok = (Tag == IO_REPARSE_TAG_MOUNT_POINT);
177  #endif
178  if (!ok) SetLastError( ERROR_NOT_SUPPORTED ); // Nothing known to resolve..
179  }
180  return ok;
181 }
182 
183 static CSTR _AbsTargetPrefixPath( CSTR TargetPath )
184 // Absolute symlink targets need to be preceeded by the "nonparsed" (\??\) prefix.
185 {
186  static __thread_local TCHAR PfxPath[ MAX_PATH ] = _T("\\??\\");
187 
188  // NOTE: Using a "local" critsec instead of a TLS buffer would preclude
189  // using a local buffer, since we couldn't leave it until /after/
190  // some thread has finished using the buffer.
191  // A hack to avoid using a TLS slot would be to turn this into a macro
192  // (or a __forceinline if the compiler can guarantee it will ALWAYS be expanded).
193 
194  if (_tcsncmp( TargetPath, PfxPath, 4 ) != 0)
195  {
196  _tcsncpyz( &PfxPath[4], TargetPath, MAX_PATH-4 );
197  TargetPath = PfxPath;
198  }
199  return TargetPath;
200 }
201 
202 #if 0 // Absolute symlink targets need to be preceeded by the "nonparsed" (\??\) prefix.
203 #define ABS_TARGET_PREFIX_PATH( TargetPath ) \
204  TCHAR _PfxPath[ MAX_PATH ] = _T("\\??\\"); \
205  if (_tcsncmp( TargetPath, _PfxPath, 4 ) != 0) { \
206  _tcsncpyz( &_PfxPath[4], TargetPath, MAX_PATH-4 ); \
207  TargetPath = _PfxPath; \
208  }
209 #endif
210 
211 bool _CanReparse( CSTR PathName ) // public
212 // Return true if the volume of an FS object supports reparsing.
213 {
214  DWORD volFlg = __GetVolumeFlags( PathName );
215  bool ok = BITS_SET( FILE_SUPPORTS_REPARSE_POINTS, volFlg );
216  if (!ok) SetLastError( ERROR_CALL_NOT_IMPLEMENTED );
217  return ok;
218 }
219 
220 static bool _CanSetReparseData( CSTR PathName )
221 // Return true if the volume supports reparsing and there isn't already reparse data set.
222 {
223  BOOL ok = _CanReparse( PathName ); // Volume supports reparsing?
224  if (ok)
225  {
226  ULARGE_INTEGER attr = GetAttributeAndReparseTag( PathName );
227 
228  // PathName must exist, and *not* be a reparse point already.
229 
230  bool objExist = (attr.LowPart != _BAD_ATTRIBUTE);
231  bool alreadySet = BITS_SET( FILE_ATTRIBUTE_REPARSE_POINT, attr.LowPart );
232  // PS: Don't worry that alreadySet will become true for _BAD_ATTRIBUTE.
233  // The logic below prevents it from affecting the result, and this masking
234  // operation is cheaper than the alternate isolatory logic would be.
235 
236  if (!objExist) SetLastError( ERROR_FILE_NOT_FOUND );
237  else if (alreadySet) SetLastError( ERROR_ALREADY_INITIALIZED );
238  ok = objExist && !alreadySet;
239  }
240  return bool_cast( ok );
241 }
242 
243 static void _ShNfyDirOf( CSTR Path, LONG evId )
244 // Notify the shell that the contents of an existing folder have changed.
245 {
246  TCHAR szDir[ MAX_PATH ];
247  _tcsncpyz( szDir, Path, dimof(szDir) );
248  PathRemoveFileSpec( szDir );
249  SHChangeNotify( evId, SHCNF_PATH, szDir, NULL );
250 }
251 
252 //++ _GetReparseData ++------------------------------------------------------------------
253 
254 // Use FSCTL_GET_REPARSE_POINT to extract the reparse data from an FS object, or return
255 // the required buffer size. On NTFS this reads data from the ::$REPARSE_POINT file stream.
256 
257 // PONDER:
258 // STATUS_BUFFER_OVERFLOW The buffer that the OutputBuffer parameter points to is large enough to hold the fixed portion of the REPARSE_GUID_DATA_BUFFER or REPARSE_DATA_BUFFER structure but not the user-defined data. In this case, only the fixed portion of the reparse point data is returned in the OutputBuffer buffer. The LengthReturned parameter to FltFsControlFile receives the actual length, in bytes, of data returned. This is a warning code.
259 // STATUS_BUFFER_TOO_SMALL The buffer that the OutputBuffer parameter points to is not large enough to hold the reparse point data. The LengthReturned parameter to FltFsControlFile (or the Information member of the IoStatus parameter to ZwFsControlFile) receives the required buffer size. In this case, no reparse point data is returned. This is an error code.
260 // STATUS_IO_REPARSE_DATA_INVALID One of the specified parameter values was invalid. This is an error code.
261 // STATUS_NOT_A_REPARSE_POINT The file or directory is not a reparse point. This is an error code.
262 
263 ULARGE_INTEGER _GetReparseData( HANDLE hObj, PVOID Buffer, DWORD cbBuffer ) // public
264 {
265  DWORD cbRtn, headerSize;
266  REPARSE_GUID_DATA_BUFFER rpd, *pd; // The larger of the two RP structs.
267  ULARGE_INTEGER info = {{0,0}};
268 
269  if (!hObj) SetLastError( ERROR_INVALID_HANDLE );
270  else
271  {
272  if (!Buffer) // then use temp buffer to query data size.
273  {
274  IF_DEBUG( memset( &rpd, 0, sizeof(rpd) ));
275  Buffer = &rpd;
276  cbBuffer = sizeof(rpd);
277  }
278  BOOL ok = DeviceIoControl(
279  hObj, FSCTL_GET_REPARSE_POINT, NULL,0,
280  Buffer, cbBuffer, &cbRtn, NULL
281  );
282  pd = (PREPARSE_GUID_DATA_BUFFER) Buffer;
283  if (ok)
284  {
285  info.HighPart = pd->ReparseTag;
286  info.LowPart = cbRtn;
287  }
288  else if (GetLastError() == ERROR_MORE_DATA) // STATUS_BUFFER_OVERFLOW
289  {
290  // Return the required buffer size, depending on reparse type.
291  // Err on the safe (larger) side if uncertain.
292 
293  if (IsReparseTagNameSurrogate( pd->ReparseTag )
294  && IsReparseTagMicrosoft( pd->ReparseTag ))
295  headerSize = REPARSE_DATA_BUFFER_HEADER_SIZE;
296  else headerSize = REPARSE_GUID_DATA_BUFFER_HEADER_SIZE;
297 
298  info.HighPart = pd->ReparseTag;
299  info.LowPart = pd->ReparseDataLength + headerSize;
300  }
301  }
302  return info;
303 }
304 
305 //++ _SetReparseData ++-----------------------------------------------------------------
306 
307 // TODO: Test _SetReparseData() as a stand-alone call.
308 
309 bool _SetReparseData( HANDLE hObj, PREPARSE_DATA_BUFFER pRpd, OPTIN DWORD cbRpd DEF_(0) ) // public
310 {
311  #if SET_WRPD_PRIVILEGE // AFAIK, SE_RESTORE_NAME is required..
312  HANDLE hToken; TOKEN_PRIVILEGES tp = { 1, { 0,0,0 }};
313  BOOL prvOk = __EnableProcPrivilege( SE_RESTORE_NAME, &tp.Privileges[0], &hToken );
314  #endif
315 
316  DWORD cbRtn, error = 0;
317  if (!cbRpd) cbRpd = pRpd->ReparseDataLength + REPARSE_DATA_BUFFER_HEADER_SIZE;
318  BOOL ok = __ChkOkGetErr( DeviceIoControl(
319  hObj, FSCTL_SET_REPARSE_POINT, pRpd, cbRpd, NULL,0, &cbRtn, NULL
320  ), &error );
321 
322  TRACE_IF( !ok, DP_ERROR, _F("FSCTL_SET_REPARSE_POINT failed: %s\n"), SysErrorMsg( error ));
323  #if SET_WRPD_PRIVILEGE
324  if (prvOk) RestorePrivilege( hToken, &tp );
325  if (hToken) CloseHandle( hToken );
326  #endif
327  if (!ok && error) SetLastError( error );
328  return bool_cast( ok );
329 };
330 
331 //++ _GetNameSurrogates ++---------------------------------------------------------------
332 // Extract the subst and print names from a symlink or mount point and null terminate them.
333 
334 static bool _GetNameSurrogates( PREPARSE_DATA_BUFFER pRpd, WSTR wzSubst, WSTR wzPrint )
335 {
336  bool isMountPoint = (pRpd->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT);
337  bool isSymLink = (pRpd->ReparseTag == IO_REPARSE_TAG_SYMLINK);
338  bool ok = isMountPoint || isSymLink;
339  if (ok)
340  {
341  // NOTE: The following works fine because MountPointReparseBuffer and
342  // SymbolicLinkReparseBuffer are identical as far as lengths and offsets.
343 
344  USHORT ccSubst = pRpd->MountPointReparseBuffer.SubstituteNameLength / WCHAR_SIZE;
345  USHORT ccPrint = pRpd->MountPointReparseBuffer.PrintNameLength / WCHAR_SIZE;
346  USHORT ccSubstOffs = pRpd->MountPointReparseBuffer.SubstituteNameOffset / WCHAR_SIZE;
347  USHORT ccPrintOffs = pRpd->MountPointReparseBuffer.PrintNameOffset / WCHAR_SIZE;
348 
349  WSTR rpdPath = isMountPoint
350  ? pRpd->MountPointReparseBuffer.PathBuffer
351  : pRpd->SymbolicLinkReparseBuffer.PathBuffer;
352 
353  if (wzSubst) wcsncpyz( wzSubst, &rpdPath[ccSubstOffs], ccSubst+1 );
354  if (wzPrint) wcsncpyz( wzPrint, &rpdPath[ccPrintOffs], ccPrint+1 );
355  }
356  return ok;
357 }
358 
359 //++ _CreateNameSurrogate ++-----------------------------------------------------------
360 // Use FSCTL_SET_REPARSE_POINT to write the ::$REPARSE_POINT stream of the link object.
361 
362 // PONDER: Change name to _SetSurrogateName..?
363 // TODO: Find out what happens if one tries to overwrite reparse data..
364 // WIP: Factorize the code that calls FSCTL_SET_REPARSE_POINT,
365 // to facilitate writing a RP data update funtion for exisiting RPs.
366 // See _SetReparseData()
367 
368 namespace { // internal
369  enum eCnsFlags // CreateNameSurrogate flags
370  {
371  CNSF_RELATIVE = WORD( SYMLINK_FLAG_RELATIVE ), // 0x01
372  CNSF_MOUNTPOINT = 0x0002, // If this bit is zero, we create a symlink instead
373  CNSF_SYMLINK = 0x0000, // i.e. Mountpoint bit is unset..
374  CNSF_DIRECTORY = 0x0004, // Directory symlink or junction/mountpoint
375  CNSF_NOPARSE = 0x0008 // Don't modify the target path string.
376  };
377 } // end internal
378 
379 #if 1 // V2 - TODO: Verify it still works..
380 static bool _CreateNameSurrogate( CSTR LinkName, CSTR TargetName, CSTR PrintName, WORD Flags )
381 {
382  BOOL ok = _CanReparse( LinkName );
383  #if 0 // No longer required (since Win6.0 ?)
384  if (ok && (Flags & CNSF_DIRECTORY)) ok = (TargetName[ _tcslen(TargetName) - 1 ] != BSLASH);
385  #endif
386  if (ok)
387  {
388  DWORD error = 0;
389  ACCESS_MASK Access = FILE_WRITE_ATTRIB_EA;
390  HANDLE hLink = _OpenReparsePoint( LinkName, Access, FILE_SHARE_READ );
391  ok = __ChkOkGetErr( hLink != NULL, &error );
392  if (ok)
393  {
394  WORD cbSubstName, cbPrintName, cbPrintOffs, ccPrintOffs, cbPathBufOffs, cbBuf;
395 
396  if (!PrintName) PrintName = TargetName;
397  #ifdef _UNICODE
398  #define wzTargetName TargetName
399  #define wszPrintName PrintName
400  #else
401  WCHAR wzTargetName[ MAX_PATH ], wszPrintName[ MAX_PATH ];
402  MultiByteToWideChar( CP_ACP, 0, TargetName,-1, wzTargetName, MAX_PATH );
403  MultiByteToWideChar( CP_ACP, 0, PrintName,-1, wszPrintName, MAX_PATH );
404  #endif
405  // Don't include any prefix in the print name.
406  WCSTR wzPrintName = SkipPathPrefixW( wszPrintName );
407 
408  cbSubstName = (WORD) wcslen( wzTargetName ) * sizeof(WCHAR);
409  cbPrintName = (WORD) wcslen( wzPrintName ) * sizeof(WCHAR);
410  cbPrintOffs = cbSubstName + sizeof(WCHAR); // Add null terminator (option)
411  ccPrintOffs = cbPrintOffs / sizeof(WCHAR);
412 
413  // SymbolicLinkReparseBuffer has an additional Flags field before the buffer.
414 
415  cbPathBufOffs = (Flags & CNSF_MOUNTPOINT)
416  ? FIELD_OFFSET( REPARSE_DATA_BUFFER, MountPointReparseBuffer.PathBuffer )
417  : FIELD_OFFSET( REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer.PathBuffer );
418 
419  cbBuf = cbPathBufOffs + cbSubstName + cbPrintName + 2*sizeof(WCHAR); // 2 nulls
420 
421  PREPARSE_DATA_BUFFER prdb = (PREPARSE_DATA_BUFFER) mem_Alloc( cbBuf );
422  ok = __ChkOkGetErr( prdb != NULL, &error );
423  if (ok)
424  {
425  if (Flags & CNSF_MOUNTPOINT)
426  prdb->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT;
427  else
428  {
429  prdb->ReparseTag = IO_REPARSE_TAG_SYMLINK;
430  prdb->SymbolicLinkReparseBuffer.Flags = (Flags & CNSF_RELATIVE);
431  }
432  prdb->ReparseDataLength = cbBuf - REPARSE_DATA_BUFFER_HEADER_SIZE;
433 
434  // NOTA BENE: The following works fine because MountPointReparseBuffer and
435  // SymbolicLinkReparseBuffer are identical as far as offsets and lengths.
436  // Also note that SubstituteNameOffset is already zeroed by mem_Alloc.
437 
438  prdb->MountPointReparseBuffer.SubstituteNameLength = cbSubstName;
439  prdb->MountPointReparseBuffer.PrintNameLength = cbPrintName;
440  prdb->MountPointReparseBuffer.PrintNameOffset = cbPrintOffs;
441 
442  WSTR pwzPathBuf = (WSTR)( PBYTE(prdb) + cbPathBufOffs );
443  memcpy( pwzPathBuf, wzTargetName, cbSubstName );
444  memcpy( pwzPathBuf + ccPrintOffs, wzPrintName, cbPrintName );
445 
446  ok = _SetReparseData( hLink, prdb, cbBuf ); // TODO: Test this call
447 
448  mem_Free( prdb );
449  }
450  CloseHandle( hLink );
451  }
452  if (!ok && error) SetLastError( error );
453  }
454  return bool_cast( ok );
455 }
456 #else
457 static bool _CreateNameSurrogate( CSTR LinkName, CSTR TargetName, CSTR PrintName, WORD Flags )
458 {
459  BOOL ok = _CanReparse( LinkName );
460  #if 0 // No longer required (since Win6.0 ?)
461  if (ok && (Flags & CNSF_DIRECTORY)) ok = (TargetName[ _tcslen(TargetName) - 1 ] != BSLASH);
462  #endif
463  if (ok)
464  {
465  DWORD error = 0;
466  #if SET_WRPD_PRIVILEGE
467  HANDLE hToken; TOKEN_PRIVILEGES tp = { 1, { 0,0 }};
468  BOOL prvOk = __EnableProcPrivilege( SE_RESTORE_NAME, &tp.Privileges[0], &hToken );
469  #endif
470 
471  ACCESS_MASK Access = FILE_WRITE_ATTRIB_EA;
472  HANDLE hLink = _OpenReparsePoint( LinkName, Access, FILE_SHARE_READ );
473  ok = __ChkOkGetErr( hLink != NULL, &error );
474  if (ok)
475  {
476  DWORD cbRtn;
477  WORD cbSubstName, cbPrintName, cbPrintOffs, ccPrintOffs, cbPathBufOffs, cbBuf;
478 
479  if (!PrintName) PrintName = TargetName;
480  #ifdef _UNICODE
481  #define wzTargetName TargetName
482  #define wszPrintName PrintName
483  #else
484  WCHAR wzTargetName[ MAX_PATH ], wszPrintName[ MAX_PATH ];
485  MultiByteToWideChar( CP_ACP, 0, TargetName,-1, wzTargetName, MAX_PATH );
486  MultiByteToWideChar( CP_ACP, 0, PrintName,-1, wszPrintName, MAX_PATH );
487  #endif
488  // Don't include any prefix in the print name.
489  WCSTR wzPrintName = SkipPathPrefixW( wszPrintName );
490 
491  cbSubstName = (WORD) wcslen( wzTargetName ) * sizeof(WCHAR);
492  cbPrintName = (WORD) wcslen( wzPrintName ) * sizeof(WCHAR);
493  cbPrintOffs = cbSubstName + sizeof(WCHAR); // Add null terminator (option)
494  ccPrintOffs = cbPrintOffs / sizeof(WCHAR);
495 
496  // SymbolicLinkReparseBuffer has an additional Flags field before the buffer.
497 
498  cbPathBufOffs = (Flags & CNSF_MOUNTPOINT)
499  ? FIELD_OFFSET( REPARSE_DATA_BUFFER, MountPointReparseBuffer.PathBuffer )
500  : FIELD_OFFSET( REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer.PathBuffer );
501 
502  cbBuf = cbPathBufOffs + cbSubstName + cbPrintName + 2*sizeof(WCHAR); // 2 nulls
503 
504  PREPARSE_DATA_BUFFER prdb = (PREPARSE_DATA_BUFFER) mem_Alloc( cbBuf );
505  ok = __ChkOkGetErr( prdb != NULL, &error );
506  if (ok)
507  {
508  if (Flags & CNSF_MOUNTPOINT)
509  prdb->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT;
510  else
511  {
512  prdb->ReparseTag = IO_REPARSE_TAG_SYMLINK;
513  prdb->SymbolicLinkReparseBuffer.Flags = (Flags & CNSF_RELATIVE);
514  }
515  prdb->ReparseDataLength = cbBuf - REPARSE_DATA_BUFFER_HEADER_SIZE;
516 
517  // NOTA BENE: The following works because MountPointReparseBuffer and
518  // SymbolicLinkReparseBuffer are identical as far as offsets and lengths.
519  // Also note that SubstituteNameOffset is already zeroed by allocator.
520 
521  prdb->MountPointReparseBuffer.SubstituteNameLength = cbSubstName;
522  prdb->MountPointReparseBuffer.PrintNameLength = cbPrintName;
523  prdb->MountPointReparseBuffer.PrintNameOffset = cbPrintOffs;
524 
525  WSTR pwzPathBuf = (WSTR)( PBYTE(prdb) + cbPathBufOffs );
526  memcpy( pwzPathBuf, wzTargetName, cbSubstName );
527  memcpy( pwzPathBuf + ccPrintOffs, wzPrintName, cbPrintName );
528 
529  ok = __ChkOkGetErr( DeviceIoControl(
530  hLink, FSCTL_SET_REPARSE_POINT, prdb, cbBuf, NULL,0, &cbRtn, NULL
531  ), &error );
532 
533  mem_Free( prdb );
534  }
535  CloseHandle( hLink );
536  }
537  #if SET_WRPD_PRIVILEGE
538  if (prvOk) RestorePrivilege( hToken, &tp );
539  if (hToken) CloseHandle( hToken );
540  #endif
541  if (!ok && error) SetLastError( error );
542  }
543  return bool_cast( ok );
544 }
545 #endif //_CreateNameSurrogate
546 
547 //==-----------------------------------------------------------------------------------
548 // PUBLIC
549 //==-----------------------------------------------------------------------------------
550 
551 // Return file attributes in LowPart, and reparse tag, if any, in HighPart.
552 #if 1 // Faster execution in the typcal case..
553 
554 ULARGE_INTEGER GetAttributeAndReparseTag( CSTR PathName )
555 {
556  WIN32_FIND_DATA fd;
557  ULARGE_INTEGER info = {{0,0}};
558 
559  // Since the vast majority of files/dirs will _not_ be reparse points,
560  // we don't waste time with an entire FindFirstFile for them..
561 
562  info.LowPart = GetFileAttributes( PathName );
563  if (info.LowPart == INVALID_FILE_ATTRIBUTES)
564  {
565  info.LowPart = 0;
566  TRACE( DP_ERROR, _F("GetFileAttributes: %s\n"), SysErrorMsg() );
567  }
568  else if (BITS_SET( FILE_ATTRIBUTE_REPARSE_POINT, info.LowPart ))
569  {
570  if (BITS_SET( FILE_ATTRIBUTE_DIRECTORY, info.LowPart ))
571  PathName = WithoutBackslash( PathName );
572 
573  HANDLE hf = FindFirstFile( PathName, &fd );
574  if (hf != INVALID_HANDLE_VALUE)
575  {
576  info.HighPart = fd.dwReserved0; // Ref.MSDN: The reparse tag.
577  FindClose( hf );
578  }
579  }
580  return info;
581 }
582 
583 #else // Naive approach, wasteful of CPU cycles..
584 ULARGE_INTEGER GetAttributeAndReparseTag( CSTR PathName )
585 {
586  WIN32_FIND_DATA fd;
587  ULARGE_INTEGER info = {{0,0}};
588  HANDLE hf = FindFirstFile( WithoutBackslash( PathName ), &fd );
589  if (hf != INVALID_HANDLE_VALUE) {
590  info.LowPart = fd.dwFileAttributes;
591  info.HighPart = fd.dwReserved0; // Ref.MSDN: The reparse tag.
592  FindClose( hf );
593  }
594  return info;
595 }
596 #endif
597 
598 //++ IsSymLink/IsSymLinkDir/IsJunctionDir ++-----------------------------------
599 
600 #define FA_REPARSE_DIR \
601  (FILE_ATTRIBUTE_DIRECTORY| FILE_ATTRIBUTE_REPARSE_POINT)
602 
603 // UNDER CONSIDERATTION
604 // Optionally use and return the attrib/tag pair.
605 //
606 //bool IsSymLink( CSTR PathName, OPTIO ULARGE_INTEGER* pAttrTag DEF_(NULL))
607 //
608 //bool _IsSymLinkTag( CSTR PathName, ULARGE_INTEGER* pAttrTag )
609 //{
610 // ULARGE_INTEGER tag;
611 // // Use attrib-tag pair if one was given, else get one.
612 //
613 // if (pAttrTag && pAttrTag->HighPart) tag = *pAttrTag;
614 // else tag = GetAttributeAndReparseTag( PathName );
615 // if (pAttrTag && !pAttrTag->HighPart) *pAttrTag = tag; // Return if given tag was zero.
616 //
617 // // Now check it..
618 //
619 // bool ok = BITS_SET( FILE_ATTRIBUTE_REPARSE_POINT, tag.LowPart );
620 // if (ok) ok = (tag.HighPart == IO_REPARSE_TAG_SYMLINK);
621 // return ok;
622 //}
623 
624 bool IsSymLink( CSTR PathName )
625 {
626  ULARGE_INTEGER tag = GetAttributeAndReparseTag( PathName );
627  bool ok = BITS_SET( FILE_ATTRIBUTE_REPARSE_POINT, tag.LowPart );
628  if (ok) ok = (tag.HighPart == IO_REPARSE_TAG_SYMLINK);
629  return ok;
630 }
631 
632 bool IsSymLinkDir( CSTR PathName )
633 {
634  ULARGE_INTEGER tag = GetAttributeAndReparseTag( PathName );
635  bool ok = BITS_SET( FA_REPARSE_DIR, tag.LowPart );
636  if (ok) ok = (tag.HighPart == IO_REPARSE_TAG_SYMLINK);
637  return ok;
638 }
639 
640 bool IsJunctionDir( CSTR PathName )
641 // Test if pathName is an NTFS junction-mounted directory/volume.
642 {
643  ULARGE_INTEGER tag = GetAttributeAndReparseTag( PathName );
644  bool ok = BITS_SET( FA_REPARSE_DIR, tag.LowPart );
645  // Determine if it's a JUNCTION, not a SYMLINKD.
646  if (ok) ok = (tag.HighPart == IO_REPARSE_TAG_MOUNT_POINT);
647  return ok;
648 }
649 
650 //++ Is directory a mounted volume? ++-----------------------------------------
651 
652 bool IsVolumeMountPoint( CSTR PathName, TSTR VolNameBuf, UINT ccBuf )
653 {
654  bool ok = IsJunctionDir( PathName );
655  if (ok) {
656  // IO_REPARSE_TAG_MOUNT_POINT is inconclusive, since it's true
657  // for directory junctions as well as volume junctions.
658  // Use GetVolumeNameForVolumeMountPoint to determine if it's a volume.
659 
660  TCHAR volname[ 64 ]; // Volume GUID name >>> \\?\Volume{GUID}
661  if (!VolNameBuf || !ccBuf) // If caller doesn't want the volume name..
662  {
663  VolNameBuf = volname;
664  ccBuf = dimof(volname);
665  }
666  // Volume APIs require a trailing backslash.
667  // However, GetAttributeAndReparseTag fails if there *is* one,
668  // so we'll add one on the fly for GetVolumeNameForVolumeMountPoint.
669 
670  PathName = WithBackslash( PathName );
671 
672  // Verify that the junction is a mount point.
673 
674  ok = bool_cast(
675  GetVolumeNameForVolumeMountPoint(
676  PathName, VolNameBuf, ccBuf
677  ));
678  }
679  return ok;
680 }
681 
682 //++ RemoveReparseData ++-------------------------------------------------------------------
683 
684 // STATUS_IO_REPARSE_DATA_INVALID One of the specified parameter values was invalid. This is an error code.
685 // STATUS_NOT_A_REPARSE_POINT The file or directory is not a reparse point. This is an error code.
686 
687 bool RemoveReparseData( CSTR PathName )
688 {
689  DWORD error = 0;
690  ULARGE_INTEGER attr = GetAttributeAndReparseTag( PathName );
691  BOOL ok = (attr.LowPart & FILE_ATTRIBUTE_REPARSE_POINT);
692  if (!ok) error = ERROR_NOT_A_REPARSE_POINT; // i.e. STATUS_NOT_A_REPARSE_POINT
693  else
694  {
695  DWORD cbRtn;
697  ZeroMemory( &rpd, sizeof(rpd) );
698  rpd.ReparseTag = attr.HighPart;
699 
700  ACCESS_MASK Access = DELETE| FILE_WRITE_ATTRIB_EA;
701  HANDLE hObj = _OpenReparsePoint( PathName, Access, FILE_SHARE_NONE );
702  ok = __ChkOkGetErr( hObj != NULL, &error );
703  if ( ok )
704  {
705  ok = __ChkOkGetErr( DeviceIoControl(
707  &rpd, REPARSE_DATA_BUFFER_HEADER_SIZE, NULL,0, &cbRtn, NULL
708  ), &error );
709  CloseHandle( hObj );
710  }
711  }
712  if (!ok && error) SetLastError( error );
713  return bool_cast( ok );
714 }
715 
716 //++ SetCustomReparseData ++-------------------------------------------------------------
717 
719  CSTR PathName, ULONG Tag, REFGUID Guid, PVOID pData, WORD cbData
720  )
721 {
722  DWORD error = 0;
723 
724  // PONDER: Is it legit to simply overwrite previous reparse data..?
725  // After observing FSCTL_SET_REPARSE_POINT I'm inclined to think the
726  // IoCtrl don't really bother with the content, just the protocol.
727 
728  BOOL ok = _CanSetReparseData( PathName );
729  if (ok)
730  {
731  HANDLE hObj = _OpenReparsePoint( PathName, FILE_WRITE_ATTRIB_EA, FILE_SHARE_READ );
732  ok = __ChkOkGetErr( hObj != NULL, &error );
733  if (ok)
734  {
735  DWORD cbBuf, cbRtn;
736 
737  cbBuf = FIELD_OFFSET( REPARSE_GUID_DATA_BUFFER, GenericReparseBuffer.DataBuffer[ cbData ] );
738  PREPARSE_GUID_DATA_BUFFER prdb = (PREPARSE_GUID_DATA_BUFFER) mem_Alloc( cbBuf );
739 
740  prdb->ReparseTag = Tag; // E.g. 0x15;
741  prdb->ReparseGuid = Guid;
742  prdb->ReparseDataLength = cbData;
743  memcpy( prdb->GenericReparseBuffer.DataBuffer, pData, cbData );
744 
745  ok = __ChkOkGetErr( DeviceIoControl(
746  hObj, FSCTL_SET_REPARSE_POINT, prdb, cbBuf, NULL,0, &cbRtn, NULL
747  ), &error );
748 
749  mem_Free( prdb );
750  CloseHandle( hObj );
751  }
752  }
753  if (!ok && error) SetLastError( error );
754  return bool_cast( ok );
755 }
756 
757 //++ CreateJunction ++-------------------------------------------------------------------
758 
759 // PONDER: Implement AllowUpdate parameter..?
760 
761 //bool CreateJunctionDir( CSTR LinkName, CSTR TargetPath, CSTR PrintName, bool AllowUpdate DEF_(false) );
762 
763 // AllowUpdate allows update of the link target of an existing junction dir if true.
764 // If false, the function will fail with ERROR_FILE_EXISTS if LinkName exist.
765 
766 bool CreateJunctionDir( CSTR LinkName, CSTR TargetName, CSTR PrintName )
767 {
768  BOOL ok = __ChkOkSetErr( !DirExist( LinkName ), ERROR_FILE_EXISTS );
769  if (ok) ok = __ChkOkSetErr( !PathIsRelative( TargetName ), ERROR_INVALID_PARAMETER );
770  if (ok)
771  {
772  DWORD error = 0;
773  ok = __ChkOkGetErr( CreateDirectory( LinkName, NULL ), &error );
774  if (ok)
775  {
776  WORD Flags = CNSF_MOUNTPOINT| CNSF_DIRECTORY;
777 
778  if (!PrintName) PrintName = TargetName;
779  TargetName = SkipPathPrefix( TargetName );
780  TargetName = _AbsTargetPrefixPath( TargetName );
781 
782  ok = __ChkOkGetErr( _CreateNameSurrogate(
783  LinkName, TargetName, PrintName, Flags
784  ), &error );
785 
786  if (ok) _ShNfyDirOf( LinkName, SHCNE_MKDIR );
787  else RemoveDirectory( LinkName ); // Defer to OS error checks..
788  }
789  if (!ok && error) SetLastError( error );
790  }
791  return bool_cast( ok );
792 }
793 
794 //++ DeleteJunction ++-------------------------------------------------------------------
795 
796 bool DeleteJunctionDir( CSTR DirName )
797 {
798  DWORD error = 0;
799  BOOL ok = __ChkOkGetErr( IsJunctionDir( DirName ), &error );
800  if (ok)
801  {
802  // This call may be superfluous since we're deleting the directory,
803  // and the FS would have to remove it anyway. Alas, it's unspecified
804  // whether it would do so or return an error, so play it safe.
805 
806  ok = __ChkOkGetErr( RemoveReparseData( DirName ), &error );
807 
808  // Since a junction can only be created on an empty directory
809  // in the first place, there's no reason to verify it's empty.
810  // If the IoCtl succeeded, we can go ahead and delete it.
811 
812  if ( ok ) RemoveDirectory( DirName );
813  // FIXME? Notify parent dir of deletion..
814  if ( ok ) _ShNfyDirOf( DirName, SHCNE_RMDIR );
815  }
816  if (!ok && error) SetLastError( error );
817  return bool_cast( ok );
818 }
819 
820 //++ CreateSymLink ++--------------------------------------------------------------------
821 
822 bool CreateSymLink( CSTR LinkName, CSTR TargetName, CSTR PrintName )
823 {
824  DWORD LnkAttr = GetFileAttributes( LinkName ); // Link must *not* exist.
825  BOOL ok = __ChkOkSetErr( LnkAttr == _BAD_ATTRIBUTE, ERROR_FILE_EXISTS );
826  if (ok)
827  {
828  BOOL TrgRelative = PathIsRelative( TargetName );
829  if (!PrintName) PrintName = TargetName; // Before re-prefixing
830 
831  // If target is relative, change dir to the link path, since the target path
832  // is relative to it, else make sure the target name is prefixed correctly.
833 
834  CSTR saveDir = NULL;
835  if (TrgRelative) saveDir = __ChangeToDirOf( LinkName );
836  else TargetName = _AbsTargetPrefixPath( TargetName );
837 
838  // Determine if target is a directory or file, so we
839  // know if we should create and emtpy file or directory.
840 
841  DWORD TrgAttr = GetFileAttributes( TargetName ); // Target must exist.
842  ok = __ChkOkSetErr( TrgAttr != _BAD_ATTRIBUTE, ERROR_FILE_NOT_FOUND );
843  if (ok)
844  {
845  WORD Flags = CNSF_SYMLINK;
846  if (TrgRelative) Flags |= CNSF_RELATIVE;
847 
848  BOOL ObjCreated, TrgIsDir = (TrgAttr & FILE_ATTRIBUTE_DIRECTORY);
849  LPSECURITY_ATTRIBUTES pSec = NULL; // PONDER: Use &DefSec ..?
850  if (TrgIsDir)
851  {
852  ok = ObjCreated = CreateDirectory( LinkName, pSec );
853  Flags |= CNSF_DIRECTORY;
854  }
855  else // Target ia a file..
856  {
857  HANDLE hFile = CreateFile( LinkName, GENERIC_WRITE, 0, pSec, CREATE_NEW, 0, NULL );
858  ok = ObjCreated = (hFile != _BAD_HANDLE);
859  if (ok) CloseHandle( hFile );
860  }
861 
862  if (ok) ok = _CreateNameSurrogate( LinkName, TargetName, PrintName, Flags );
863 
864  if (!ok && ObjCreated)
865  {
866  if (TrgIsDir) RemoveDirectory( LinkName );
867  else DeleteFile( LinkName );
868  }
869  }
870 
871  if (saveDir) DoneDirectory( saveDir );
872  }
873  return bool_cast( ok );
874 }
875 
876 //++ GetReparseTarget ++-----------------------------------------------------------------
877 
878 #pragma warning( disable: 4996 )
879 #if 1 // version 2 - so far, so good
880 
881 static const WCHAR _BSLASH = L'\\';
882 
883 //
884 // TODO: Factorize GetReparseTarget to facilitate recursive surrogate expansion
885 // to produce correct results for nested reparse paths. In particular, focus on
886 // relative surrogates (only in symlinks), as they are the problematic ones..
887 //
888 
890  CSTR LinkName, TSTR SubstPath, UINT cchSubst, TSTR PrintName, UINT cchPrint,
891  PULARGE_INTEGER AttrTag
892  )
893 {
894  INT ccTarget = 0; // Result
895  ULARGE_INTEGER attr = GetAttributeAndReparseTag( LinkName );
896 
897  if (AttrTag) *AttrTag = attr;
898  if (!SubstPath || !cchSubst) SetLastError( ERROR_INVALID_PARAMETER );
899  else if (_IsNameSurrogateTag( attr.HighPart )) // Mount point or symlink.
900  {
901  HANDLE hLink = _OpenReparsePoint( LinkName, FILE_READ_ATTRIB_EA, FILE_SHARE_READ );
902  if (hLink)
903  {
904  ULARGE_INTEGER info = _GetReparseData( hLink, NULL, 0 ); // Query buf requirement
905  if (info.QuadPart)
906  {
907  WORD ccTemp = 520, cbTemp = ccTemp * WCHAR_SIZE;
908  WORD ccSubst = 260, cbSubst = ccSubst * WCHAR_SIZE;
909  WORD ccPrint = 260, cbPrint = ccPrint * WCHAR_SIZE;
910  PVOID pvBuf = mem_Alloc( info.LowPart + cbSubst + cbPrint + 2*cbTemp );
911  if (pvBuf)
912  {
913  PREPARSE_DATA_BUFFER prpd = (PREPARSE_DATA_BUFFER) pvBuf;
914  WSTR pwSubst = WSTR( PBYTE(pvBuf) + info.LowPart );
915  WSTR pwPrint = pwSubst + ccSubst;
916  WSTR pwTemp = pwPrint + ccPrint;
917  //WSTR pwTemp2 = pwTemp + ccTemp;
918 
919  info = _GetReparseData( hLink, prpd, info.LowPart );
920  if (info.HighPart) // If we got the tag it was successful.
921  {
922  if (_GetNameSurrogates( prpd, pwSubst, pwPrint ))
923  {
924  TRACE( DP_DEBUG, _F("Subst: '%S', Print: '%S'\n"), pwSubst, pwPrint );
925  BOOL ok = true;
926  if ((prpd->ReparseTag == IO_REPARSE_TAG_SYMLINK)
927  && (prpd->SymbolicLinkReparseBuffer.Flags & SYMLINK_FLAG_RELATIVE))
928  {
929  // Nota bene: FSCTL_GET_REPARSE_POINT returns incomplete
930  // relative results for symlinks under other FS links!
931  //
932  // F.ex: Here "Links\" is itself a directory symlink and "SymLink-Ref\"
933  // and "slnk-DumpDlg.cxx" are other symlinks in that directory..
934  // The IOCTL returns an incomplete relative path...
935  //
936  // ERROR: LinkName : F:\Source\CLib\uLib\tests\LinkTest\Links\SymLink-Ref\
937  // ERROR: -> Relative : ..\..\ref (incomplete)
938  // Should be Relative : ..\..\..\uLibLabs\Test\Links\..\..\ref
939  // ERROR: -> Composite : F:\Source\CLib\uLib\tests\LinkTest\Links\..\..\ref
940  // ERROR: -> Result : F:\Source\CLib\uLib\tests\ref
941  // Should be Result : F:\Source\CLib\uLibLabs\ref
942 
943  // PONDER: A somewhat cumbersome workaround here would be to recurse towards
944  // the root dir and concatenate the relative paths until we get a non-relative
945  // base dir, and then canonicalize the totality of it.
946  // This is probably best implementead by factorizing this function
947  // and implementing a static workroutine that resturns a status value
948  // indicating whether the reparse data is absolute or relative.
949 
950  #if 1 // This is beset by the above dilemma..
951  {
952  #ifdef _UNICODE
953  WSTR pwCat = wcsnecpy( pwTemp, LinkName, ccTemp );
954  #else
955  int ccXlate = MultiByteToWideChar( CP_ACP, 0, LinkName,-1, pwTemp, ccTemp );
956  WSTR pwCat = ccXlate ? pwTemp + ccXlate - 1 : pwTemp; // -1 takes pwCat to the NUL terminator..
957  #endif
958  // For directories, we remove any trailing '\' before wcsrchr().
959 
960  if (BITS_SET( FILE_ATTRIBUTE_DIRECTORY, attr.LowPart )
961  && (*--pwCat == _BSLASH)) *pwCat = 0; // Remove ending backslash
962 
963  // Now replace the link name (filename or terminal subpath) with
964  // the relative reparse path, so PathCanonicalize() works out right.
965  // We insert the reparse path after the preceeding '\'.
966 
967  pwCat = 1 + wcsrchr( pwTemp, _BSLASH ); // '\' preceding link name
968  wcscpy( pwCat, pwSubst ); // Add the relative target path
969  }
970  #else
971  {
972  // TODO: Recurse the path and concatenate substitutes
973  // to work around the multihop relative link shortcoming.
974  }
975  #endif
976  // Finally
977 
978  ok = PathCanonicalizeW( pwSubst, pwTemp );
979  if (ok) ccTarget = 1 + (UINT) wcslen( pwSubst ); // Incl NUL
980  else SetLastError( ERROR_INVALID_REPARSE_DATA );
981  }
982  if (ok) // Final sanity check..
983  {
984  DWORD linkAttr = GetFileAttributesW( pwSubst );
985  if (linkAttr == _BAD_ATTRIBUTE)
986  SetLastError( ERROR_INVALID_REPARSE_DATA ); // In lieu of a more precise errcode..
987  else
988  {
989  // Emit the reparsed path to the caller's buffer.
990  // The print name is emitted only if caller wants it.
991  #ifdef _UNICODE
992  WSTR wzEnd = wcsnecpy( SubstPath, pwSubst, cchSubst );
993  ccTarget = INT( wzEnd - SubstPath + 1);
994  if (PrintName) wcsnecpy( PrintName, pwPrint, cchPrint );
995  #else
996  ccTarget = WideCharToMultiByte(
997  CP_ACP, 0, pwSubst,-1, SubstPath, cchSubst, NULL, NULL );
998  if (PrintName) WideCharToMultiByte(
999  CP_ACP, 0, pwPrint,-1, PrintName, cchPrint, NULL, NULL );
1000  #endif
1001  }
1002  }
1003  }
1004  }
1005  mem_Free( pvBuf );
1006  }
1007  }
1008  CloseHandle( hLink );
1009  }
1010  }
1011  return ccTarget;
1012 }
1013 #else // version 1 - R&D
1014 INT GetReparseTarget(
1015  IN CSTR LinkName, OUT TSTR TargetBuf, IN UINT BufLen,
1016  OPTOUT PULARGE_INTEGER AttrTag
1017  )
1018 {
1019  INT ccTarget = 0; // Result
1020  ULARGE_INTEGER attr = GetAttributeAndReparseTag( LinkName );
1021 
1022  if (AttrTag) *AttrTag = attr;
1023  if (!TargetBuf || !BufLen) SetLastError( ERROR_INVALID_PARAMETER );
1024  else
1025  {
1026  if (!IsReparseTagValid( attr.HighPart ))
1027  SetLastError( ERROR_INVALID_DATA ); // Not a proper tag..
1028  else if (!IsReparseTagNameSurrogate( attr.HighPart ))
1029  SetLastError( ERROR_NOT_SUPPORTED ); // Nothing known to resolve..
1030  else
1031  {
1032  //HANDLE hLink = CheckHandle( CreateFile(
1033  // LinkName, FILE_READ_ATTRIBUTES, FILE_SHARE_READ, NULL, OPEN_EXISTING,
1034  // FILE_FLAG_OPEN_REPARSE_POINT| FILE_FLAG_BACKUP_SEMANTICS,
1035  // NULL
1036  // ));
1037  HANDLE hLink = _OpenReparsePoint( LinkName, FILE_READ_ATTRIBUTES, FILE_SHARE_READ );
1038  if (hLink) // (else there's a last error already in place)
1039  {
1040  static const UINT bufSize = MAX_REPARSE_BUFFER;
1041  PVOID rpdBuf = mem_Alloc( bufSize );
1042  DWORD cbRd, ccTrans;
1043  #ifdef _UNICODE
1044  UNUSED( ccTrans );
1045  #endif
1046 
1047  if (rpdBuf && DeviceIoControl(
1048  hLink, FSCTL_GET_REPARSE_POINT, NULL,0, rpdBuf, bufSize, &cbRd, NULL ))
1049  {
1050  if (!IsReparseTagMicrosoft( attr.HighPart )) // ISV tag
1051  {
1052  // FIXME: This will *never* be reached since a GUID repoint is *not* a name surrogate..!
1053  // Put it under !IsReparseTagNameSurrogate or remove it.
1054 
1055  PREPARSE_GUID_DATA_BUFFER pIsv = (PREPARSE_GUID_DATA_BUFFER) rpdBuf;
1056  ccTarget = pIsv->ReparseDataLength;
1057  #if 1 // Nothing known to resolve.. Probably can't do anything with it..
1058  memcpy( TargetBuf, pIsv->GenericReparseBuffer.DataBuffer, min( (int)BufLen, ccTarget ));
1059  //..or perhaps GetGUIDString( pIsv->ReparseGuid, TargetBuf, BufLen );
1060  #endif
1061  ccTarget = -ccTarget; // As documented...
1062  SetLastError( ERROR_NOT_SUPPORTED );
1063  }
1064  else // Microsoft tag
1065  {
1066  PREPARSE_DATA_BUFFER rpd = (PREPARSE_DATA_BUFFER) rpdBuf;
1067  #define _wchLen(_wcb) (_wcb / WCHAR_SIZE)
1068  #define _prnStr(_rps) (_rps.PathBuffer + _wchLen(_rps.PrintNameOffset))
1069  static const WCHAR _BSLASH = L'\\';
1070 
1071  // Use the rear end of the 16kB buffer for temp strings (3-4kB)..
1072 
1073  PWSTR pwTarget = PWSTR( rpdBuf ) + _wchLen( bufSize ) - 512; // tmpbuf for target (512 WCHAR)
1074  PWSTR pwComposite = pwTarget - 1024; // temp buffer for relative composite
1075  IF_DEBUG( PWSTR pwRelative = pwComposite - 512; ) // copy of relative result
1076  PWSTR pwPrint;
1077 
1078  switch( attr.HighPart )
1079  {
1080  case IO_REPARSE_TAG_IIS_CACHE: // Unknown to me..
1081  TRACE( DP_DEBUG, _F("IIS_CACHE: %s\n"), LinkName );
1082  SetLastError( ERROR_NOINTERFACE ); //ERROR_CANNOT_COPY, ERROR_INVALID_REPARSE_DATA, ERROR_REPARSE_OBJECT, ERROR_FOUND_OUT_OF_SCOPE
1083  break;
1084 
1085  case IO_REPARSE_TAG_MOUNT_POINT:
1086  pwPrint = _prnStr( rpd-> MountPointReparseBuffer ); // "X:\somepath"
1087  ccTarget = 1 + _wchLen( rpd-> MountPointReparseBuffer.PrintNameLength ); // Incl NUL
1088  wcsncpyz( pwTarget, pwPrint, ccTarget );
1089  goto __putResult;
1090 
1091  case IO_REPARSE_TAG_SYMLINK:
1092  // FIXME: Do *not* assume that Subst is at offset 0 of PathBuffer..!
1093 
1094  pwPrint = _prnStr( rpd-> SymbolicLinkReparseBuffer ); // NB: Not null terminated
1095  ccTarget = 1 + _wchLen( rpd-> SymbolicLinkReparseBuffer.PrintNameLength ); // Incl NUL
1096  wcsncpyz( pwTarget, pwPrint, ccTarget );
1097  #if _DEBUG
1098  {
1099  WCHAR _wzSubst[ MAX_PATH ], _wzPrint[ MAX_PATH ];
1100  memset( _wzSubst, 0, sizeof(_wzSubst) );
1101  memset( _wzPrint, 0, sizeof(_wzPrint) );
1102  wcsncpyz( _wzSubst, // The following two lines are so Microsofty it makes me nauseous..
1103  &rpd->SymbolicLinkReparseBuffer.PathBuffer[ rpd->SymbolicLinkReparseBuffer.SubstituteNameOffset / WCHAR_SIZE ],
1104  (rpd->SymbolicLinkReparseBuffer.SubstituteNameLength / WCHAR_SIZE ) + 1
1105  );
1106  wcsncpyz( _wzPrint, // dito..
1107  &rpd->SymbolicLinkReparseBuffer.PathBuffer[ rpd->SymbolicLinkReparseBuffer.PrintNameOffset / WCHAR_SIZE ],
1108  (rpd->SymbolicLinkReparseBuffer.PrintNameLength / WCHAR_SIZE) + 1
1109  );
1110  DPrint( DP_DEBUG, _F("Subst: %S, Print: %S\n"), _wzSubst, _wzPrint );
1111  }
1112  #endif
1113  if (rpd->SymbolicLinkReparseBuffer.Flags & SYMLINK_FLAG_RELATIVE)
1114  {
1115  // Nota Bene: FSCTL_GET_REPARSE_POINT returns flawed
1116  // results for relative symlinks under other links.
1117  // See the sanity check further down...
1118  #ifdef _UNICODE
1119  PWSTR pwCat = wcsecpy( pwComposite, LinkName );
1120  #else
1121  PWSTR pwCat = pwComposite - 1 // -1 takes us to the NUL terminator..
1122  + MultiByteToWideChar( CP_ACP, 0, LinkName,-1, pwComposite,1024 );
1123  #endif
1124 
1125  // For directories, we replace the link terminal subpath
1126  // (the link name) with the relative reparse path, so that
1127  // PathCanonicalize will work out right.
1128 
1129  if (BITS_SET( FILE_ATTRIBUTE_DIRECTORY, attr.LowPart )
1130  && (*--pwCat == _BSLASH)) *pwCat = 0; // Remove ending backslash
1131 
1132  pwCat = 1 + wcsrchr( pwComposite, _BSLASH ); // '\' preceding link name
1133  wcscpy( pwCat, pwTarget ); // Add the relative target path
1134  IF_DEBUG( wcscpy( pwRelative, pwTarget ); ) // save a copy
1135 
1136  if (PathCanonicalizeW( pwTarget, pwComposite ))
1137  ccTarget = 1 + (UINT) wcslen( pwTarget ); // Incl NUL
1138  else ccTarget = -ccTarget; // Error
1139  }
1140 
1141  __putResult:
1142  if (ccTarget > 0) // If not already in error
1143  {
1144  if ((int)BufLen < ccTarget)
1145  {
1146  SetLastError( ERROR_INSUFFICIENT_BUFFER );
1147  ccTarget = -ccTarget;
1148  }
1149  else
1150  {
1151  #ifdef _UNICODE
1152  wcsncpyz( TargetBuf, pwTarget, BufLen ); // ccTarget status quo
1153  #else
1154  ccTrans = WideCharToMultiByte( CP_ACP, 0, pwTarget,-1, TargetBuf, BufLen, NULL, NULL );
1155  if (!ccTrans) ccTarget = -ccTarget;
1156  #endif
1157 
1158  // Do a sanity check on the resulting path..
1159 
1160  DWORD trgAttr = GetFileAttributes( TargetBuf );
1161  if (trgAttr == _BAD_ATTRIBUTE) // If target doesn't exist
1162  {
1163  ccTarget = 0;
1164  SetLastError( ERROR_INVALID_REPARSE_DATA );
1165 
1166  TRACE( DP_ERROR, _F("LinkName : %s\n"), LinkName );
1167  TRACE( DP_ERROR, _F(" -> Relative : %S\n"), pwRelative );
1168  TRACE( DP_ERROR, _F(" -> Composite : %S\n"), pwComposite );
1169  TRACE( DP_ERROR, _F(" -> Result : %s\n"), TargetBuf );
1170 
1171  // FSCTL_GET_REPARSE_POINT returns flawed relative results
1172  // for symlinks under other FS links !!!
1173  //
1174  // F.ex: Here "Links\" is itself a directory symlink and "SymLink-Ref\"
1175  // and "slnk-DumpDlg.cxx" are other symlinks in that directory..
1176  // The IOCTL returns an incomplete relative path...
1177  //
1178  // ERROR: LinkName : F:\Source\CLib\uLib\tests\LinkTest\Links\SymLink-Ref\
1179  // ERROR: -> Relative : ..\..\ref (incomplete)
1180  // Should be Relative : ..\..\..\uLibLabs\Test\Links\..\..\ref
1181  // ERROR: -> Composite : F:\Source\CLib\uLib\tests\LinkTest\Links\..\..\ref
1182  // ERROR: -> Result : F:\Source\CLib\uLib\tests\ref
1183  // Should be Result : F:\Source\CLib\uLibLabs\ref
1184 
1185  // PONDER: A somewhat cumbersome workaround here would be to recurse towards
1186  // the root dir and concatenate the relative paths until we get a non-relative
1187  // base dir, and then canonicalize the totality of it.
1188  // This is probably best implementead by factorizing this function
1189  // and implementing a static workroutine that resturns a status value
1190  // indicating whether the reparse data is absolute or relative.
1191  }
1192  }
1193  }
1194  break;
1195 
1196  default:
1197  TRACE( DP_DEBUG, _F("Tag=0x%08X: '%s', Target unknown.\n"), attr.HighPart, LinkName );
1198  break;
1199  }
1200  #undef _wchLen
1201  #undef _prnStr
1202  } // Microsoft tag
1203  }
1204  rpdBuf = mem_Free( rpdBuf );
1205  CloseHandle( hLink );
1206  }
1207  }
1208  }
1209  return ccTarget;
1210 }
1211 #endif
1212 #pragma warning( default: 4996 )
1213 
1214 // EOF
unsigned long DWORD
Definition: Common.h:414
bool IsVolumeMountPoint(CSTR PathName, TSTR VolNameBuf, UINT ccBuf)
Definition: ReparsePnt.cpp:652
bool RemoveReparseData(CSTR PathName)
Definition: ReparsePnt.cpp:687
HANDLE CheckHandle(HANDLE Hnd)
Definition: KernelUtil.cpp:75
#define IsReparseTagValid(_tag)
Definition: ReparsePnt.cpp:115
#define CSTR
Definition: Common.h:329
INT GetReparseTarget(CSTR LinkName, TSTR SubstPath, UINT cchSubst, TSTR PrintName, UINT cchPrint, PULARGE_INTEGER AttrTag)
Definition: ReparsePnt.cpp:889
CSTR WithoutBackslash(CSTR PathName)
Definition: StrFunc.cpp:668
CSTR WithBackslash(CSTR PathName)
Definition: StrFunc.cpp:682
unsigned short WORD
Definition: Common.h:413
bool DeleteJunctionDir(CSTR DirName)
Definition: ReparsePnt.cpp:796
#define IF_DEBUG(code)
Definition: Debug.h:236
#define IO_REPARSE_TAG_IIS_CACHE
Definition: ReparsePnt.cpp:111
#define DP_DEBUG
Definition: Debug.h:85
void * mem_Alloc(size_t Bytes)
Definition: MemFunc.cpp:33
wchar_t * WSTR
Definition: Common.h:366
#define FILE_WRITE_ATTRIB_EA
Definition: _Internal.h:19
#define _BAD_ATTRIBUTE
Definition: Common.h:1010
#define TSTR
Definition: Common.h:328
unsigned char * PBYTE
Definition: Common.h:412
#define FA_REPARSE_DIR
Definition: ReparsePnt.cpp:600
ULARGE_INTEGER GetAttributeAndReparseTag(CSTR PathName)
Definition: ReparsePnt.cpp:554
#define dimof(x)
Definition: Common.h:949
#define MAX_REPARSE_BUFFER
Definition: ReparsePnt.cpp:122
#define OPTOUT
Definition: Common.h:264
void __cdecl DPrint(int Level, CSTR Fmt,...)
Definition: Debug.cpp:134
CSTR DoneDirectory(CSTR prevDir)
Definition: IoUtil.cpp:1040
wchar_t *__fastcall wcsncpyz(register wchar_t *Dst, register const wchar_t *Src, size_t Count)
Definition: StrFunc.cpp:142
#define TRACE(_lvl,...)
Definition: Debug.h:216
bool IsSymLinkDir(CSTR PathName)
Definition: ReparsePnt.cpp:632
bool _SetReparseData(HANDLE hObj, PREPARSE_DATA_BUFFER pRpd, OPTIN DWORD cbRpd=0)
Definition: ReparsePnt.cpp:309
#define FSCTL_SET_REPARSE_POINT
Definition: ReparsePnt.cpp:69
#define FILE_SHARE_NONE
Definition: Common.h:1129
const wchar_t * WCSTR
Definition: Common.h:367
DWORD __GetVolumeFlags(CSTR PathName)
Definition: _Internal.cpp:44
#define TRACE_IF(cond,...)
Definition: Debug.h:227
BOOL(WINAPI *SysImgList::Shell_GetImageLists)(HIMAGELIST *pimlLarge
bool IsSymLink(CSTR PathName)
Definition: ReparsePnt.cpp:624
void * mem_Free(void *pBlk)
Definition: MemFunc.cpp:124
#define _BAD_HANDLE
Definition: Common.h:997
CSTR SysErrorMsg(DWORD Err=0, TSTR Buf=NULL, UINT Length=0)
Definition: Debug.cpp:39
#define FILE_READ_ATTRIB_EA
Definition: _Internal.h:20
bool CreateJunctionDir(CSTR LinkName, CSTR TargetName, CSTR PrintName)
Definition: ReparsePnt.cpp:766
__inline bool __ChkOkSetErr(BOOL Ok, DWORD Err)
Definition: _Internal.h:25
bool RestorePrivilege(HANDLE hToken, PTOKEN_PRIVILEGES pSaved)
See EnablePrivilege(()
Definition: SecUtil.cpp:162
bool __forceinline bool_cast(BOOL B52)
Definition: Common.h:767
ULARGE_INTEGER _GetReparseData(HANDLE hObj, PVOID Buffer, DWORD cbBuffer)
Definition: ReparsePnt.cpp:263
#define DP_ERROR
Definition: Debug.h:82
#define BITS_SET(bits, x)
Definition: Common.h:1016
bool DirExist(CSTR PathName)
Definition: IoUtil.cpp:30
HANDLE _OpenReparsePoint(CSTR LinkName, ACCESS_MASK Access, DWORD Share)
Definition: ReparsePnt.cpp:154
wchar_t *__fastcall wcsnecpy(register wchar_t *Dst, register const wchar_t *Src, size_t N)
Definition: StrFunc.cpp:84
bool _CanReparse(CSTR PathName)
Definition: ReparsePnt.cpp:211
#define UNUSED(x)
Definition: Common.h:970
bool IsJunctionDir(CSTR PathName)
Definition: ReparsePnt.cpp:640
Debug and error handling support.
#define SYMLINK_FLAG_RELATIVE
Definition: ReparsePnt.cpp:108
__inline bool __ChkOkGetErr(BOOL Ok, PDWORD pErr)
Definition: _Internal.h:34
#define REPARSE_DATA_BUFFER_HEADER_SIZE
Definition: ReparsePnt.cpp:104
#define DEF_(x)
Definition: Common.h:240
wchar_t *__fastcall wcsecpy(register wchar_t *Dst, register const wchar_t *Src)
Definition: StrFunc.cpp:68
#define FSCTL_GET_REPARSE_POINT
Definition: ReparsePnt.cpp:66
#define SkipPathPrefix
Definition: UtilFunc.h:2375
#define BSLASH
Definition: Common.h:1217
#define _F(s)
Definition: Debug.h:49
#define min(a, b)
Definition: Common.h:933
#define WCHAR_SIZE
Definition: Common.h:379
CSTR __ChangeToDirOf(CSTR PathName)
Definition: _Internal.cpp:77
bool __EnableProcPrivilege(IN CSTR Privilege, OUT LUID_AND_ATTRIBUTES *pPrv, OUT HANDLE *pToken)
Definition: _Internal.cpp:16
bool SetCustomReparseData(CSTR PathName, ULONG Tag, REFGUID Guid, PVOID pData, WORD cbData)
Definition: ReparsePnt.cpp:718
eCnsFlags
Definition: ReparsePnt.cpp:369
#define FSCTL_DELETE_REPARSE_POINT
Definition: ReparsePnt.cpp:72
bool CreateSymLink(CSTR LinkName, CSTR TargetName, CSTR PrintName)
Definition: ReparsePnt.cpp:822
#define _tcsncpyz
Definition: StrFunc.h:77
#define OPTIN
Definition: Common.h:263
WCSTR SkipPathPrefixW(WCSTR PathName)
Definition: IoUtil.cpp:133