1 #define SYMLINK_FLAG_RELATIVE 1
2 
3 typedef struct _REPARSE_DATA_BUFFER {
4   ULONG  ReparseTag;
5   USHORT ReparseDataLength;
6   USHORT Reserved;
7   union {
8     struct {
9       USHORT SubstituteNameOffset;
10       USHORT SubstituteNameLength;
11       USHORT PrintNameOffset;
12       USHORT PrintNameLength;
13       ULONG  Flags;
14       WCHAR  PathBuffer[1];
15     } SymbolicLinkReparseBuffer;
16     struct {
17       USHORT SubstituteNameOffset;
18       USHORT SubstituteNameLength;
19       USHORT PrintNameOffset;
20       USHORT PrintNameLength;
21       WCHAR  PathBuffer[1];
22     } MountPointReparseBuffer;
23     struct {
24       UCHAR DataBuffer[1];
25     } GenericReparseBuffer;
26   };
27 } REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER;
28 
29 
30 
31 
CreateReparsePoint(CommandData * Cmd,const wchar * Name,FileHeader * hd)32 bool CreateReparsePoint(CommandData *Cmd,const wchar *Name,FileHeader *hd)
33 {
34   static bool PrivSet=false;
35   if (!PrivSet)
36   {
37     SetPrivilege(SE_RESTORE_NAME);
38     // Not sure if we really need it, but let's request anyway.
39     SetPrivilege(SE_CREATE_SYMBOLIC_LINK_NAME);
40     PrivSet=true;
41   }
42 
43   const DWORD BufSize=sizeof(REPARSE_DATA_BUFFER)+2*NM+1024;
44   Array<byte> Buf(BufSize);
45   REPARSE_DATA_BUFFER *rdb=(REPARSE_DATA_BUFFER *)&Buf[0];
46 
47   wchar SubstName[NM];
48   wcsncpyz(SubstName,hd->RedirName,ASIZE(SubstName));
49   size_t SubstLength=wcslen(SubstName);
50 
51   wchar PrintName[NM],*PrintNameSrc=SubstName,*PrintNameDst=PrintName;
52   bool WinPrefix=wcsncmp(PrintNameSrc,L"\\??\\",4)==0;
53   if (WinPrefix)
54     PrintNameSrc+=4;
55   if (WinPrefix && wcsncmp(PrintNameSrc,L"UNC\\",4)==0)
56   {
57     *(PrintNameDst++)='\\'; // Insert second \ in beginning of share name.
58     PrintNameSrc+=3;
59   }
60   wcscpy(PrintNameDst,PrintNameSrc);
61 
62   size_t PrintLength=wcslen(PrintName);
63 
64   bool AbsPath=WinPrefix;
65   // IsFullPath is not really needed here, AbsPath check is enough.
66   // We added it just for extra safety, in case some Windows version would
67   // allow to create absolute targets with SYMLINK_FLAG_RELATIVE.
68   // Use hd->FileName instead of Name, since Name can include the destination
69   // path as a prefix, which can confuse IsRelativeSymlinkSafe algorithm.
70   if (!Cmd->AbsoluteLinks && (AbsPath || IsFullPath(hd->RedirName) ||
71       !IsRelativeSymlinkSafe(Cmd,hd->FileName,Name,hd->RedirName)))
72     return false;
73 
74   CreatePath(Name,true,Cmd->DisableNames);
75 
76   // Overwrite prompt was already issued and confirmed earlier, so we can
77   // remove existing symlink or regular file here. PrepareToDelete was also
78   // called earlier inside of uiAskReplaceEx.
79   if (FileExist(Name))
80     if (IsDir(GetFileAttr(Name)))
81       DelDir(Name);
82     else
83       DelFile(Name);
84 
85   // 'DirTarget' check is important for Unix symlinks to directories.
86   // Unix symlinks do not have their own 'directory' attribute.
87   if (hd->Dir || hd->DirTarget)
88   {
89     if (!CreateDirectory(Name,NULL))
90     {
91       uiMsg(UIERROR_DIRCREATE,UINULL,Name);
92       ErrHandler.SetErrorCode(RARX_CREATE);
93       return false;
94     }
95   }
96   else
97   {
98     HANDLE hFile=CreateFile(Name,GENERIC_WRITE,0,NULL,CREATE_NEW,FILE_ATTRIBUTE_NORMAL,NULL);
99     if (hFile == INVALID_HANDLE_VALUE)
100     {
101       ErrHandler.CreateErrorMsg(Name);
102       return false;
103     }
104     CloseHandle(hFile);
105   }
106 
107 
108   if (hd->RedirType==FSREDIR_JUNCTION)
109   {
110     rdb->ReparseTag=IO_REPARSE_TAG_MOUNT_POINT;
111     rdb->ReparseDataLength=USHORT(
112       sizeof(rdb->MountPointReparseBuffer.SubstituteNameOffset)+
113       sizeof(rdb->MountPointReparseBuffer.SubstituteNameLength)+
114       sizeof(rdb->MountPointReparseBuffer.PrintNameOffset)+
115       sizeof(rdb->MountPointReparseBuffer.PrintNameLength)+
116       (SubstLength+1)*sizeof(WCHAR)+(PrintLength+1)*sizeof(WCHAR));
117     rdb->Reserved=0;
118 
119     rdb->MountPointReparseBuffer.SubstituteNameOffset=0;
120     rdb->MountPointReparseBuffer.SubstituteNameLength=USHORT(SubstLength*sizeof(WCHAR));
121     wcscpy(rdb->MountPointReparseBuffer.PathBuffer,SubstName);
122 
123     rdb->MountPointReparseBuffer.PrintNameOffset=USHORT((SubstLength+1)*sizeof(WCHAR));
124     rdb->MountPointReparseBuffer.PrintNameLength=USHORT(PrintLength*sizeof(WCHAR));
125     wcscpy(rdb->MountPointReparseBuffer.PathBuffer+SubstLength+1,PrintName);
126   }
127   else
128     if (hd->RedirType==FSREDIR_WINSYMLINK || hd->RedirType==FSREDIR_UNIXSYMLINK)
129     {
130       rdb->ReparseTag=IO_REPARSE_TAG_SYMLINK;
131       rdb->ReparseDataLength=USHORT(
132         sizeof(rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset)+
133         sizeof(rdb->SymbolicLinkReparseBuffer.SubstituteNameLength)+
134         sizeof(rdb->SymbolicLinkReparseBuffer.PrintNameOffset)+
135         sizeof(rdb->SymbolicLinkReparseBuffer.PrintNameLength)+
136         sizeof(rdb->SymbolicLinkReparseBuffer.Flags)+
137         (SubstLength+1)*sizeof(WCHAR)+(PrintLength+1)*sizeof(WCHAR));
138       rdb->Reserved=0;
139 
140       rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset=0;
141       rdb->SymbolicLinkReparseBuffer.SubstituteNameLength=USHORT(SubstLength*sizeof(WCHAR));
142       wcscpy(rdb->SymbolicLinkReparseBuffer.PathBuffer,SubstName);
143 
144       rdb->SymbolicLinkReparseBuffer.PrintNameOffset=USHORT((SubstLength+1)*sizeof(WCHAR));
145       rdb->SymbolicLinkReparseBuffer.PrintNameLength=USHORT(PrintLength*sizeof(WCHAR));
146       wcscpy(rdb->SymbolicLinkReparseBuffer.PathBuffer+SubstLength+1,PrintName);
147 
148       rdb->SymbolicLinkReparseBuffer.Flags=AbsPath ? 0:SYMLINK_FLAG_RELATIVE;
149     }
150     else
151       return false;
152 
153   HANDLE hFile=CreateFile(Name,GENERIC_READ|GENERIC_WRITE,0,NULL,
154                OPEN_EXISTING,FILE_FLAG_OPEN_REPARSE_POINT|
155                FILE_FLAG_BACKUP_SEMANTICS,NULL);
156   if (hFile==INVALID_HANDLE_VALUE)
157   {
158     ErrHandler.CreateErrorMsg(Name);
159     ErrHandler.SetErrorCode(RARX_CREATE);
160     return false;
161   }
162 
163   DWORD Returned;
164   if (!DeviceIoControl(hFile,FSCTL_SET_REPARSE_POINT,rdb,
165       FIELD_OFFSET(REPARSE_DATA_BUFFER,GenericReparseBuffer)+
166       rdb->ReparseDataLength,NULL,0,&Returned,NULL))
167   {
168     CloseHandle(hFile);
169     uiMsg(UIERROR_SLINKCREATE,UINULL,Name);
170 
171     DWORD LastError=GetLastError();
172     if ((LastError==ERROR_ACCESS_DENIED || LastError==ERROR_PRIVILEGE_NOT_HELD) &&
173         !IsUserAdmin())
174       uiMsg(UIERROR_NEEDADMIN);
175     ErrHandler.SysErrMsg();
176     ErrHandler.SetErrorCode(RARX_CREATE);
177 
178     if (hd->Dir)
179       RemoveDirectory(Name);
180     else
181       DeleteFile(Name);
182     return false;
183   }
184   File LinkFile;
185   LinkFile.SetHandle(hFile);
186   LinkFile.SetOpenFileTime(
187     Cmd->xmtime==EXTTIME_NONE ? NULL:&hd->mtime,
188     Cmd->xctime==EXTTIME_NONE ? NULL:&hd->ctime,
189     Cmd->xatime==EXTTIME_NONE ? NULL:&hd->atime);
190   LinkFile.Close();
191   if (!Cmd->IgnoreGeneralAttr)
192     SetFileAttr(Name,hd->FileAttr);
193   return true;
194 }
195