uLib  User mode C/C++ extended API library for Win32 programmers.
Hardlink.cpp
Go to the documentation of this file.
1 #include <uLib/UtilFunc.h>
2 #include <uLib/StrFunc.h>
3 #include <uLib/_Internal.h>
4 
5 //++ GetHardLinkCount ++-------------------------------------------------------
6 
7 UINT GetHardLinkCount( CSTR FileName )
8 {
9  UINT nLinks = 0;
10  BY_HANDLE_FILE_INFORMATION bhi;
11  if (GetFileInformationByName( FileName, &bhi )) nLinks = bhi.nNumberOfLinks;
12  return nLinks;
13 }
14 
15 //++ _CreateHardLink ++------------------------------------------------------------------
16 /*
17 _CreateHardLink() is based on the following KB article.
18 The OS has had this function since Win2k, so this is mostly for edification and memo.
19 The linker will exclude it if you don't use it, so no harm done :)
20 I dug this out from my old 1999 source base and solidified it with some more
21 error checks. (I was much more lax about such things in those days).
22 The function basically "restores" a link to the file.
23 
24 HOWTO: Create Hard Links in Windows NT and Windows 2000 KB.ID: Q234727
25 -------------------------------------------------------------------------------
26 The information in this article applies to:
27 Microsoft Win32 Application Programming Interface (API), included with:
28 Microsoft Windows NT, versions 3.51, 4.0
29 Microsoft Windows 2000
30 -------------------------------------------------------------------------------
31 SUMMARY
32 
33 Windows NT and Windows 2000 support hard links on NTFS disk volumes.
34 Following are two programmatic approaches that exist for creating hard links:
35 
36 1) Use CreateHardLink() in Windows 2000.
37 2) Use the BackupWrite() to create a hard link in Windows NT and Windows 2000.
38 
39 If your application requires Windows 2000, use method one because it is simpler;
40 if your application must run on Windows NT and Windows 2000, you should use
41 method two. The remainder of this article provides more detailed information
42 about hard links and how to create them with both methods.
43 
44 MORE INFORMATION
45 
46 A hard link is a directory entry for a file. Every file can be considered to
47 have at least one hard link. On NTFS volumes, each file can have multiple hard
48 links, and thus a single file can appear in many directories (or even in the same
49 directory with different names). Since all of the links reference the same file,
50 applications can open any of the links and modify the file. A file is deleted
51 from the file system only after all links to it have been deleted. Once a hard
52 link is created, applications can use it like any other file name. Therefore,
53 you can use the full Win32 file I/O API to access a file through a link. The
54 changes made to a file through one link will be reflected by all other links
55 (because they point to the same file). To remove a link use DeleteFile(). Hard
56 links are supported on NTFS only; they may not be created on FAT or FAT32 volumes.
57 In addition, a hard link must reside on the same volume as the file it references.
58 You cannot create a hard link on one volume that points to a file on another
59 volume. This is because a hard link is a directory entry that points to a file.
60 
61 The security descriptor for a file belongs to the file, not the links that
62 reference it. Thus, all links to a file grant the same access to the file.
63 If the security descriptor for the file is changed by using one link, all other
64 links will reflect that change.
65 
66 The hard links created with BackupWrite() and CreateHardLink() are identical;
67 the only difference is in the functions the applications call to create them.
68 Creating hard links with BackupWrite() is supported on Windows 2000 and should
69 be used when your application must also run on Windows NT version 4.0 and earlier.
70 If your application is designed to run on Windows 2000, use CreateHardLink()
71 because it is easier to use.
72 
73 CreateHardLink() Example
74 
75 The following code snippet demonstrates how to call CreateHardLink() so that it
76 doesn't modify the file's security descriptor.
77 
78 // Set security descriptor argument to NULL to leave file's security
79 // descriptor alone.
80 
81 fCreatedLink = CreateHardLink( pszNewLinkName, pszExistingFileName, NULL );
82 if (!fCreatedLink)
83  ; // error occurred, handle it.
84 
85 NOTE: PszExistingFileName can be the original filename, or any already-existing
86 link to the file. After this code is executed, pszNewLinkName will refer to the
87 file.
88 
89 BackupWrite() Example
90 
91 The sample code demonstrates how to use BackupWrite() to create a
92 hard link on an NTFS volume. Basically, it "restores" a link to the file.
93 
94 PseudoCode Algorithm for Restoring POSIX File Links
95 
96  1. While there are more files to restore
97  2. If the file is a LINK
98  3. Use the full path which was saved as data to open the file.
99  4. Init a WIN32_STREAM_ID structure with dwStreamId equal to BACKUP_LINK.
100  5. Init the dwStreamAttributes to 0.
101  6. Init the dwStreamNameSize to 0.
102  7. Init a UNICODE buffer containing the full path of the file you are restoring.
103  8. Init the dwStreamSizeHigh to 0.
104  9. Init the dwStreamSizeLow to the byte size of the buffer containing the full path.
105  10. Call BackupWrite() with the WIN32_STREAM_ID
106  11. Call BackupWrite() with the buffer containing the full path.
107  12. Else
108  13. Call BackupWrite() with the data stored on your backup media.
109  14. Endif
110  15. EndWhile
111 
112  NOTE: The new link name must be supplied in Unicode to BackupWrite().
113 
114 Last MS Review: June 16, 1999
115 Last LN Review: April, 2021
116 */
117 
118 #ifndef FILE_SUPPORTS_HARD_LINKS
119 #define FILE_SUPPORTS_HARD_LINKS 0x00400000
120 #endif
121 
122 bool _CreateHardLink( CSTR LinkName, CSTR TargetName )
123 {
124  HANDLE hToken, hTarget;
125  TOKEN_PRIVILEGES tp = { 1, { 0,0,0 }};
126  DWORD error = 0, volFlg;
127  BOOL ok = false, prvOk;
128 
129  if (FileExist( LinkName ))
130  {
131  error = ERROR_FILE_EXISTS;
132  goto __chl_Exit;
133  }
134  #if 1 // N.B: Flag declared in XP (but not implemented before Win7 ?).
135  volFlg = __GetVolumeFlags( LinkName );
136  ok = BITS_SET( FILE_SUPPORTS_HARD_LINKS, volFlg );
137  if (!ok)
138  {
139  error = ERROR_CALL_NOT_IMPLEMENTED;
140  goto __chl_Exit;
141  }
142  #else
143  UNUSED( volFlg );
144  #endif
145 
146  // BackupWrite requires the SE_RESTORE_NAME privilege.
147  prvOk = __EnableProcPrivilege( SE_RESTORE_NAME, &tp.Privileges[0], &hToken );
148 
149  // Open existing file that we link to
150 
151  hTarget = CheckHandle( CreateFile(
152  TargetName, FILE_WRITE_ATTRIB_EA, FILE_SHARE_READ, NULL,
153  OPEN_EXISTING, 0, NULL
154  ));
155  ok = __ChkOkGetErr( hTarget != NULL, &error );
156  if (ok)
157  {
158  // Validate and sanitize supplied link path.
159 
160  TCHAR szLinkName[ MAX_PATH ]; // Full pathname of link
161  DWORD ccLinkName = GetFullPathName( LinkName, MAX_PATH, szLinkName, NULL );
162  ok = __ChkOkGetErr( ccLinkName > 0, &error );
163  if (ok)
164  {
165  // Hard links must be on the same volume, so rather than facing
166  // ramifications of a BackupWrite failure, let's pre-check it.
167 
168  DWORD TargetVol = __GetHandleVolumeId( hTarget );
169  DWORD LinkVol = __GetVolumeId( szLinkName );
170  if (LinkVol != TargetVol)
171  {
172  error = ERROR_NOT_SAME_DEVICE;
173  goto __chl_CloseTrg;
174  }
175 
176  #ifdef _UNICODE
177  #define wzLinkName szLinkName
178  #else
179  WCHAR wzLinkName[ MAX_PATH ];
180  // The full path MUST be Unicode for BackupWrite.
181  MultiByteToWideChar( CP_ACP, 0, szLinkName,-1, wzLinkName, MAX_PATH );
182  #endif
183  DWORD cbLinkName = (ccLinkName+1) * sizeof(WCHAR);
184  PVOID pBkupCtx = NULL; // Internal BackupWrite context.
185 
186  // Prepare and write the WIN32_STREAM_ID out
187 
188  WIN32_STREAM_ID sid;
189  ZeroMemory( &sid, sizeof(WIN32_STREAM_ID) );
190  sid.dwStreamId = BACKUP_LINK;
191  sid.Size.u.LowPart = cbLinkName;
192 
193  DWORD cbWr, cbStreamHdr = FIELD_OFFSET( WIN32_STREAM_ID, cStreamName );
194 
195  ok = BackupWrite( hTarget,
196  (PBYTE)&sid, cbStreamHdr, &cbWr, false, false,
197  &pBkupCtx
198  );
199  if (__ChkOkGetErr( ok, &error ))
200  {
201  // Write out the buffer containing the link pathname.
202  // FYI: Sadly, the OS is not as clever as I in determining if the link
203  // and target are on the same volume, so this fails with ERROR_NOT_SAME_DEVICE
204  // if you specify a share name for one and a dos path for the other, even
205  // if they are the same physical volume. (I don't flunk that, see above).
206 
207  ok = BackupWrite( hTarget,
208  (PBYTE)wzLinkName, cbLinkName, &cbWr, false, false,
209  &pBkupCtx
210  );
211  if (!ok) error = GetLastError();
212 
213  // Free the context
214 
215  BOOL bkOk = BackupWrite( hTarget, NULL, 0, &cbWr, true, false, &pBkupCtx );
216  // Only pick up a "backup done" error if there was no previous error.
217  if (ok && !bkOk) { ok = bkOk; error = GetLastError(); }
218  }
219  }
220  __chl_CloseTrg:
221  CloseHandle( hTarget );
222  }
223  if (prvOk) RestorePrivilege( hToken, &tp );
224  if (hToken) CloseHandle( hToken );
225 
226  __chl_Exit:
227  if (!ok && error) SetLastError( error );
228  return bool_cast( ok );
229 }
230 
231 //++ EnumerateHardLinks +------------------------------------------------------
232 #if (WINVER >= 0x0600) // FindFirstFileNameW requires Vista.
233 
234 // Alphatest passed: Gives full path from relative input path on current drive.
235 
236 UINT EnumerateHardLinks( CSTR FileName, PFnEnumHardLinks Action, PVOID usrData )
237 {
238  UINT nLinks = 0;
239  // In Unicode, the wzLink buffer is passed to Action verbatim.
240  WSTR wzLink = mem_AllocWStr( MAX_PATH ); // Not on stack, for safety's sake.
241 
242  // FindFirstFileName/FindNextFileName doesn't give us a drive spec,
243  // so do some value added processing to give the caller absolute paths.
244 
245  WSTR pwzLink = wzLink; // Buffer pointer for FindFirstFileName/FindNextFileName
246  DWORD ccDrv = 0; // Drive spec length to subtract from buffer size
247 
248  #ifdef _UNICODE
249  #define wzFile FileName
250  #else // We need two char conversion buffers, since the APIs are W only.
251  WSTR wzFile = mem_WDupAStr( FileName, CP_ACP ); // Convert FileName to unicode
252  TSTR pzLink = mem_AllocAStr( MAX_PATH ); // Buffer for the Action call-out
253  #endif
254 
255  WCSTR pwzSep = wcschr( wzFile, COLON ); // Do we have a drive spec ?
256  if (pwzSep) // Yes.. Copy it to wzLink
257  {
258  ccDrv = DWORD( 1 + CharIndex( wzFile, pwzSep ));
259  wcsncpyz( wzLink, wzFile, ccDrv+1 );
260  }
261  else if (GetFullPathNameW( wzFile, MAX_PATH, wzLink, NULL )) // No, get one into wzLink
262  {
263  wzLink[0] = towupper( wzLink[0] ); // Upper case drive char looks better
264  pwzSep = wcschr( wzLink, COLON ); // Skip past drive spec
265  ccDrv = DWORD( 1 + CharIndex( wzLink, pwzSep ));
266  }
267  pwzLink += ccDrv; // Fwd the buffer ptr past the drive spec
268 
269  // Now we can do the hardlink enumeration and produce absolute paths...
270 
271  DWORD ccLink = MAX_PATH - ccDrv;
272  HANDLE hFind = CheckHandle( FindFirstFileNameW( wzFile, 0, &ccLink, pwzLink ));
273  while( hFind )
274  {
275  nLinks++;
276  #ifdef _UNICODE
277  bool proceed = Action( wzLink, usrData );
278  #else
279  WideCharToMultiByte( CP_ACP, 0, wzLink, -1, pzLink, MAX_PATH, NULL, NULL );
280  bool proceed = Action( pzLink, usrData );
281  #endif
282  ccLink = MAX_PATH - ccDrv;
283  if (!proceed || !FindNextFileNameW( hFind, &ccLink, pwzLink ))
284  hFind = FindCloseEx( hFind );
285  }
286 
287  mem_Free( wzLink );
288  #ifndef _UNICODE
289  mem_Free( wzFile );
290  mem_Free( pzLink );
291  #endif
292  return nLinks;
293 }
294 
295 #endif//(WINVER >= 0x0600)
296 // EOF
unsigned long DWORD
Definition: Common.h:414
bool(__stdcall * PFnEnumHardLinks)(CSTR PathName, PVOID usrData)
Definition: UtilFunc.h:2652
HANDLE CheckHandle(HANDLE Hnd)
Definition: KernelUtil.cpp:75
#define CSTR
Definition: Common.h:329
bool FileExist(CSTR PathName)
Definition: IoUtil.cpp:24
wchar_t * WSTR
Definition: Common.h:366
#define FILE_WRITE_ATTRIB_EA
Definition: _Internal.h:19
#define TSTR
Definition: Common.h:328
unsigned char * PBYTE
Definition: Common.h:412
bool _CreateHardLink(CSTR LinkName, CSTR TargetName)
Definition: Hardlink.cpp:122
wchar_t *__fastcall wcsncpyz(register wchar_t *Dst, register const wchar_t *Src, size_t Count)
Definition: StrFunc.cpp:142
WSTR mem_WDupAStr(ACSTR AStr, UINT CodePg=CP_ACP, UINT ccExtra=0)
Definition: StrFunc.cpp:298
HANDLE FindCloseEx(HANDLE hFind)
Definition: IoUtil.cpp:1072
bool GetFileInformationByName(CSTR FileName, LPBY_HANDLE_FILE_INFORMATION pInfo)
Definition: IoUtil.cpp:163
DWORD __GetHandleVolumeId(HANDLE hObj)
Definition: _Internal.cpp:68
const wchar_t * WCSTR
Definition: Common.h:367
DWORD __GetVolumeFlags(CSTR PathName)
Definition: _Internal.cpp:44
ASTR mem_AllocAStr(WORD nChar)
Definition: StrFunc.cpp:258
BOOL(WINAPI *SysImgList::Shell_GetImageLists)(HIMAGELIST *pimlLarge
#define FILE_SUPPORTS_HARD_LINKS
Definition: Hardlink.cpp:119
void * mem_Free(void *pBlk)
Definition: MemFunc.cpp:124
WSTR mem_AllocWStr(WORD nChar)
Definition: StrFunc.cpp:264
bool RestorePrivilege(HANDLE hToken, PTOKEN_PRIVILEGES pSaved)
See EnablePrivilege(()
Definition: SecUtil.cpp:162
bool __forceinline bool_cast(BOOL B52)
Definition: Common.h:767
#define BITS_SET(bits, x)
Definition: Common.h:1016
#define UNUSED(x)
Definition: Common.h:970
__inline bool __ChkOkGetErr(BOOL Ok, PDWORD pErr)
Definition: _Internal.h:34
#define COLON
Definition: Common.h:1222
INT_PTR CharIndex(const chrType *Buffer, const chrType *Inside)
Definition: StrFunc.h:93
UINT GetHardLinkCount(CSTR FileName)
Definition: Hardlink.cpp:7
bool __EnableProcPrivilege(IN CSTR Privilege, OUT LUID_AND_ATTRIBUTES *pPrv, OUT HANDLE *pToken)
Definition: _Internal.cpp:16
DWORD __GetVolumeId(CSTR PathName)
Definition: _Internal.cpp:56
UINT EnumerateHardLinks(CSTR FileName, PFnEnumHardLinks Action, PVOID usrData)
Definition: Hardlink.cpp:236