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