1 // LinkDialog.cpp
2 
3 #include "StdAfx.h"
4 
5 #include "../../../Windows/ErrorMsg.h"
6 #include "../../../Windows/FileDir.h"
7 #include "../../../Windows/FileFind.h"
8 #include "../../../Windows/FileIO.h"
9 #include "../../../Windows/FileName.h"
10 
11 #ifdef LANG
12 #include "LangUtils.h"
13 #endif
14 
15 #include "BrowseDialog.h"
16 #include "CopyDialogRes.h"
17 #include "LinkDialog.h"
18 #include "resourceGui.h"
19 
20 #include "App.h"
21 
22 #include "resource.h"
23 
24 extern bool g_SymLink_Supported;
25 
26 using namespace NWindows;
27 using namespace NFile;
28 
29 #ifdef LANG
30 static const UInt32 kLangIDs[] =
31 {
32   IDB_LINK_LINK,
33   IDT_LINK_PATH_FROM,
34   IDT_LINK_PATH_TO,
35   IDG_LINK_TYPE,
36   IDR_LINK_TYPE_HARD,
37   IDR_LINK_TYPE_SYM_FILE,
38   IDR_LINK_TYPE_SYM_DIR,
39   IDR_LINK_TYPE_JUNCTION
40 };
41 #endif
42 
GetSymLink(CFSTR path,CReparseAttr & attr)43 static bool GetSymLink(CFSTR path, CReparseAttr &attr)
44 {
45   NIO::CInFile file;
46   if (!file.Open(path,
47       FILE_SHARE_READ,
48       OPEN_EXISTING,
49       FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS))
50     return false;
51 
52   const unsigned kBufSize = MAXIMUM_REPARSE_DATA_BUFFER_SIZE;
53   CByteArr buf(kBufSize);
54   DWORD returnedSize;
55   if (!file.DeviceIoControlOut(my_FSCTL_GET_REPARSE_POINT, buf, kBufSize, &returnedSize))
56     return false;
57 
58   DWORD errorCode = 0;
59   if (!attr.Parse(buf, returnedSize, errorCode))
60     return false;
61 
62   CByteBuffer data2;
63   if (!FillLinkData(data2, attr.GetPath(), attr.IsSymLink()))
64     return false;
65 
66   if (data2.Size() != returnedSize ||
67       memcmp(data2, buf, returnedSize) != 0)
68     return false;
69 
70   return true;
71 }
72 
73 static const int k_LinkType_Buttons[] =
74 {
75   IDR_LINK_TYPE_HARD,
76   IDR_LINK_TYPE_SYM_FILE,
77   IDR_LINK_TYPE_SYM_DIR,
78   IDR_LINK_TYPE_JUNCTION
79 };
80 
Set_LinkType_Radio(int idb)81 void CLinkDialog::Set_LinkType_Radio(int idb)
82 {
83   CheckRadioButton(k_LinkType_Buttons[0], k_LinkType_Buttons[ARRAY_SIZE(k_LinkType_Buttons) - 1], idb);
84 }
85 
OnInit()86 bool CLinkDialog::OnInit()
87 {
88   #ifdef LANG
89   LangSetWindowText(*this, IDD_LINK);
90   LangSetDlgItems(*this, kLangIDs, ARRAY_SIZE(kLangIDs));
91   #endif
92 
93   _pathFromCombo.Attach(GetItem(IDC_LINK_PATH_FROM));
94   _pathToCombo.Attach(GetItem(IDC_LINK_PATH_TO));
95 
96   if (!FilePath.IsEmpty())
97   {
98     NFind::CFileInfo fi;
99     int linkType = 0;
100     if (!fi.Find(us2fs(FilePath)))
101       linkType = IDR_LINK_TYPE_SYM_FILE;
102     else
103     {
104       if (fi.HasReparsePoint())
105       {
106         CReparseAttr attr;
107         bool res = GetSymLink(us2fs(FilePath), attr);
108 
109         UString s = attr.PrintName;
110         if (!attr.IsOkNamePair())
111         {
112           s += " : ";
113           s += attr.SubsName;
114         }
115         if (!res)
116           s.Insert(0, L"ERROR: ");
117 
118         SetItemText(IDT_LINK_PATH_TO_CUR, s);
119 
120         UString destPath = attr.GetPath();
121         _pathFromCombo.SetText(FilePath);
122         _pathToCombo.SetText(destPath);
123 
124         if (res)
125         {
126           if (attr.IsMountPoint())
127             linkType = IDR_LINK_TYPE_JUNCTION;
128           if (attr.IsSymLink())
129           {
130             linkType =
131               fi.IsDir() ?
132                 IDR_LINK_TYPE_SYM_DIR :
133                 IDR_LINK_TYPE_SYM_FILE;
134             // if (attr.IsRelative()) linkType = IDR_LINK_TYPE_SYM_RELATIVE;
135           }
136 
137           if (linkType != 0)
138             Set_LinkType_Radio(linkType);
139         }
140       }
141       else
142       {
143         _pathFromCombo.SetText(AnotherPath);
144         _pathToCombo.SetText(FilePath);
145         if (fi.IsDir())
146           linkType = g_SymLink_Supported ?
147               IDR_LINK_TYPE_SYM_DIR :
148               IDR_LINK_TYPE_JUNCTION;
149         else
150           linkType = IDR_LINK_TYPE_HARD;
151       }
152     }
153     if (linkType != 0)
154       Set_LinkType_Radio(linkType);
155   }
156 
157   NormalizeSize();
158   return CModalDialog::OnInit();
159 }
160 
OnSize(WPARAM,int xSize,int ySize)161 bool CLinkDialog::OnSize(WPARAM /* wParam */, int xSize, int ySize)
162 {
163   int mx, my;
164   GetMargins(8, mx, my);
165   int bx1, bx2, by;
166   GetItemSizes(IDCANCEL, bx1, by);
167   GetItemSizes(IDB_LINK_LINK, bx2, by);
168   int yPos = ySize - my - by;
169   int xPos = xSize - mx - bx1;
170 
171   InvalidateRect(NULL);
172 
173   {
174     RECT r, r2;
175     GetClientRectOfItem(IDB_LINK_PATH_FROM, r);
176     GetClientRectOfItem(IDB_LINK_PATH_TO, r2);
177     int bx = RECT_SIZE_X(r);
178     int newButtonXpos = xSize - mx - bx;
179 
180     MoveItem(IDB_LINK_PATH_FROM, newButtonXpos, r.top, bx, RECT_SIZE_Y(r));
181     MoveItem(IDB_LINK_PATH_TO, newButtonXpos, r2.top, bx, RECT_SIZE_Y(r2));
182 
183     int newComboXsize = newButtonXpos - mx - mx;
184     ChangeSubWindowSizeX(_pathFromCombo, newComboXsize);
185     ChangeSubWindowSizeX(_pathToCombo, newComboXsize);
186   }
187 
188   MoveItem(IDCANCEL, xPos, yPos, bx1, by);
189   MoveItem(IDB_LINK_LINK, xPos - mx - bx2, yPos, bx2, by);
190 
191   return false;
192 }
193 
OnButtonClicked(int buttonID,HWND buttonHWND)194 bool CLinkDialog::OnButtonClicked(int buttonID, HWND buttonHWND)
195 {
196   switch (buttonID)
197   {
198     case IDB_LINK_PATH_FROM:
199       OnButton_SetPath(false);
200       return true;
201     case IDB_LINK_PATH_TO:
202       OnButton_SetPath(true);
203       return true;
204     case IDB_LINK_LINK:
205       OnButton_Link();
206       return true;
207   }
208   return CModalDialog::OnButtonClicked(buttonID, buttonHWND);
209 }
210 
OnButton_SetPath(bool to)211 void CLinkDialog::OnButton_SetPath(bool to)
212 {
213   UString currentPath;
214   NWindows::NControl::CComboBox &combo = to ?
215     _pathToCombo :
216     _pathFromCombo;
217   combo.GetText(currentPath);
218   // UString title = "Specify a location for output folder";
219   UString title = LangString(IDS_SET_FOLDER);
220 
221   UString resultPath;
222   if (!MyBrowseForFolder(*this, title, currentPath, resultPath))
223     return;
224   NName::NormalizeDirPathPrefix(resultPath);
225   combo.SetCurSel(-1);
226   combo.SetText(resultPath);
227 }
228 
ShowError(const wchar_t * s)229 void CLinkDialog::ShowError(const wchar_t *s)
230 {
231   ::MessageBoxW(*this, s, L"7-Zip", MB_ICONERROR);
232 }
233 
ShowLastErrorMessage()234 void CLinkDialog::ShowLastErrorMessage()
235 {
236   ShowError(NError::MyFormatMessage(GetLastError()));
237 }
238 
OnButton_Link()239 void CLinkDialog::OnButton_Link()
240 {
241   UString from, to;
242   _pathFromCombo.GetText(from);
243   _pathToCombo.GetText(to);
244 
245   if (from.IsEmpty())
246     return;
247   if (!NName::IsAbsolutePath(from))
248     from.Insert(0, CurDirPrefix);
249 
250   int idb = -1;
251   for (unsigned i = 0;; i++)
252   {
253     if (i >= ARRAY_SIZE(k_LinkType_Buttons))
254       return;
255     idb = k_LinkType_Buttons[i];
256     if (IsButtonCheckedBool(idb))
257       break;
258   }
259 
260   NFind::CFileInfo info1, info2;
261   bool finded1 = info1.Find(us2fs(from));
262   bool finded2 = info2.Find(us2fs(to));
263 
264   bool isDirLink = (
265       idb == IDR_LINK_TYPE_SYM_DIR ||
266       idb == IDR_LINK_TYPE_JUNCTION);
267 
268   if (finded1 && info1.IsDir() != isDirLink ||
269       finded2 && info2.IsDir() != isDirLink)
270   {
271     ShowError(L"Incorrect link type");
272     return;
273   }
274 
275   if (idb == IDR_LINK_TYPE_HARD)
276   {
277     if (!NDir::MyCreateHardLink(us2fs(from), us2fs(to)))
278     {
279       ShowLastErrorMessage();
280       return;
281     }
282   }
283   else
284   {
285     bool isSymLink = (idb != IDR_LINK_TYPE_JUNCTION);
286 
287     CByteBuffer data;
288     if (!FillLinkData(data, to, isSymLink))
289     {
290       ShowError(L"Incorrect link");
291       return;
292     }
293 
294     CReparseAttr attr;
295     DWORD errorCode = 0;
296     if (!attr.Parse(data, data.Size(), errorCode))
297     {
298       ShowError(L"Internal conversion error");
299       return;
300     }
301 
302 
303     if (!NIO::SetReparseData(us2fs(from), isDirLink, data, (DWORD)data.Size()))
304     {
305       ShowLastErrorMessage();
306       return;
307     }
308   }
309 
310   End(IDOK);
311 }
312 
Link()313 void CApp::Link()
314 {
315   unsigned srcPanelIndex = GetFocusedPanelIndex();
316   CPanel &srcPanel = Panels[srcPanelIndex];
317   if (!srcPanel.IsFSFolder())
318   {
319     srcPanel.MessageBox_Error_UnsupportOperation();
320     return;
321   }
322   CRecordVector<UInt32> indices;
323   srcPanel.GetOperatedItemIndices(indices);
324   if (indices.IsEmpty())
325     return;
326   if (indices.Size() != 1)
327   {
328     srcPanel.MessageBox_Error_LangID(IDS_SELECT_ONE_FILE);
329     return;
330   }
331   int index = indices[0];
332   const UString itemName = srcPanel.GetItemName(index);
333 
334   const UString fsPrefix = srcPanel.GetFsPath();
335   const UString srcPath = fsPrefix + srcPanel.GetItemPrefix(index);
336   UString path = srcPath;
337   {
338     unsigned destPanelIndex = (NumPanels <= 1) ? srcPanelIndex : (1 - srcPanelIndex);
339     CPanel &destPanel = Panels[destPanelIndex];
340     if (NumPanels > 1)
341       if (destPanel.IsFSFolder())
342         path = destPanel.GetFsPath();
343   }
344 
345   CLinkDialog dlg;
346   dlg.CurDirPrefix = fsPrefix;
347   dlg.FilePath = srcPath + itemName;
348   dlg.AnotherPath = path;
349 
350   if (dlg.Create(srcPanel.GetParent()) != IDOK)
351     return;
352 
353   RefreshTitleAlways();
354 }
355