xref: /aosp_15_r20/external/lzma/CPP/7zip/UI/FileManager/LinkDialog.cpp (revision f6dc9357d832569d4d1f5d24eacdb3935a1ae8e6)
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