1 /*
2 * SHFileOperation
3 *
4 * Copyright 2000 Juergen Schmied
5 * Copyright 2002 Andriy Palamarchuk
6 * Copyright 2004 Dietrich Teickner (from Odin)
7 * Copyright 2004 Rolf Kalbermatter
8 * Copyright 2019-2023 Katayama Hirofumi MZ <katayama.hirofumi.mz@gmail.com>
9 *
10 * This library is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Lesser General Public
12 * License as published by the Free Software Foundation; either
13 * version 2.1 of the License, or (at your option) any later version.
14 *
15 * This library is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Lesser General Public License for more details.
19 *
20 * You should have received a copy of the GNU Lesser General Public
21 * License along with this library; if not, write to the Free Software
22 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
23 */
24
25 #include "precomp.h"
26
27 WINE_DEFAULT_DEBUG_CHANNEL(shell);
28
29 #define IsAttrib(x, y) ((INVALID_FILE_ATTRIBUTES != (x)) && ((x) & (y)))
30 #define IsAttribFile(x) (!((x) & FILE_ATTRIBUTE_DIRECTORY))
31 #define IsAttribDir(x) IsAttrib(x, FILE_ATTRIBUTE_DIRECTORY)
32 #define IsDotDir(x) ((x[0] == '.') && ((x[1] == 0) || ((x[1] == '.') && (x[2] == 0))))
33
34 #define FO_MASK 0xF
35
36 #define NEW_FILENAME_ON_COPY_TRIES 100
37
38 typedef struct
39 {
40 SHFILEOPSTRUCTW *req;
41 DWORD dwYesToAllMask;
42 BOOL bManyItems;
43 BOOL bCancelled;
44 IProgressDialog *progress;
45 ULARGE_INTEGER completedSize;
46 ULARGE_INTEGER totalSize;
47 WCHAR szBuilderString[50];
48 } FILE_OPERATION;
49
50 #define ERROR_SHELL_INTERNAL_FILE_NOT_FOUND 1026
51
52 typedef struct
53 {
54 DWORD attributes;
55 LPWSTR szDirectory;
56 LPWSTR szFilename;
57 LPWSTR szFullPath;
58 BOOL bFromWildcard;
59 BOOL bFromRelative;
60 BOOL bExists;
61 } FILE_ENTRY;
62
63 typedef struct
64 {
65 FILE_ENTRY *feFiles;
66 DWORD num_alloc;
67 DWORD dwNumFiles;
68 BOOL bAnyFromWildcard;
69 BOOL bAnyDirectories;
70 BOOL bAnyDontExist;
71 } FILE_LIST;
72
73 static DWORD SHNotifyCreateDirectoryW(LPCWSTR path, LPSECURITY_ATTRIBUTES sec);
74 static DWORD SHNotifyRemoveDirectoryW(LPCWSTR path);
75 static DWORD SHNotifyDeleteFileW(FILE_OPERATION *op, LPCWSTR path);
76 static DWORD SHNotifyMoveFileW(FILE_OPERATION *op, LPCWSTR src, LPCWSTR dest, BOOL isdir);
77 static DWORD SHNotifyCopyFileW(FILE_OPERATION *op, LPCWSTR src, LPCWSTR dest, BOOL bFailIfExists);
78 static DWORD SHFindAttrW(LPCWSTR pName, BOOL fileOnly);
79 static HRESULT copy_files(FILE_OPERATION *op, BOOL multiDest, const FILE_LIST *flFrom, FILE_LIST *flTo);
80 static DWORD move_files(FILE_OPERATION *op, BOOL multiDest, const FILE_LIST *flFrom, const FILE_LIST *flTo);
81
82 DWORD WINAPI _FileOpCountManager(FILE_OPERATION *op, const FILE_LIST *flFrom);
83 static BOOL _FileOpCount(FILE_OPERATION *op, LPWSTR pwszBuf, BOOL bFolder, DWORD *ticks);
84
85 /* Confirm dialogs with an optional "Yes To All" as used in file operations confirmations
86 */
87 struct confirm_msg_info
88 {
89 LPWSTR lpszText;
90 LPWSTR lpszCaption;
91 HICON hIcon;
92 BOOL bYesToAll;
93 };
94
95 /* as some buttons may be hidden and the dialog height may change we may need
96 * to move the controls */
confirm_msg_move_button(HWND hDlg,INT iId,INT * xPos,INT yOffset,BOOL bShow)97 static void confirm_msg_move_button(HWND hDlg, INT iId, INT *xPos, INT yOffset, BOOL bShow)
98 {
99 HWND hButton = GetDlgItem(hDlg, iId);
100 RECT r;
101
102 if (bShow)
103 {
104 POINT pt;
105 int width;
106
107 GetWindowRect(hButton, &r);
108 width = r.right - r.left;
109 pt.x = r.left;
110 pt.y = r.top;
111 ScreenToClient(hDlg, &pt);
112 MoveWindow(hButton, *xPos - width, pt.y - yOffset, width, r.bottom - r.top, FALSE);
113 *xPos -= width + 5;
114 }
115 else
116 ShowWindow(hButton, SW_HIDE);
117 }
118
119 /* Note: we paint the text manually and don't use the static control to make
120 * sure the text has the same height as the one computed in WM_INITDIALOG
121 */
ConfirmMsgBox_Paint(HWND hDlg)122 static INT_PTR ConfirmMsgBox_Paint(HWND hDlg)
123 {
124 PAINTSTRUCT ps;
125 HFONT hOldFont;
126 RECT r;
127 HDC hdc;
128
129 BeginPaint(hDlg, &ps);
130 hdc = ps.hdc;
131 SetBkMode(hdc, TRANSPARENT);
132 SetTextColor(hdc, GetSysColor(COLOR_BTNTEXT));
133
134 GetClientRect(GetDlgItem(hDlg, IDC_YESTOALL_MESSAGE), &r);
135 /* this will remap the rect to dialog coords */
136 MapWindowPoints(GetDlgItem(hDlg, IDC_YESTOALL_MESSAGE), hDlg, (LPPOINT)&r, 2);
137 hOldFont = (HFONT)SelectObject(hdc, (HFONT)SendDlgItemMessageW(hDlg, IDC_YESTOALL_MESSAGE, WM_GETFONT, 0, 0));
138 DrawTextW(hdc, (LPWSTR)GetWindowLongPtrW(hDlg, DWLP_USER), -1, &r, DT_NOPREFIX | DT_PATH_ELLIPSIS | DT_WORDBREAK);
139 SelectObject(hdc, hOldFont);
140 EndPaint(hDlg, &ps);
141
142 return TRUE;
143 }
144
ConfirmMsgBox_Init(HWND hDlg,LPARAM lParam)145 static INT_PTR ConfirmMsgBox_Init(HWND hDlg, LPARAM lParam)
146 {
147 struct confirm_msg_info *info = (struct confirm_msg_info *)lParam;
148 INT xPos, yOffset;
149 int width, height;
150 HFONT hOldFont;
151 HDC hdc;
152 RECT r;
153
154 SetWindowTextW(hDlg, info->lpszCaption);
155 ShowWindow(GetDlgItem(hDlg, IDC_YESTOALL_MESSAGE), SW_HIDE);
156 SetWindowLongPtrW(hDlg, DWLP_USER, (LONG_PTR)info->lpszText);
157 SendDlgItemMessageW(hDlg, IDC_YESTOALL_ICON, STM_SETICON, (WPARAM)info->hIcon, 0);
158
159 /* compute the text height and resize the dialog */
160 GetClientRect(GetDlgItem(hDlg, IDC_YESTOALL_MESSAGE), &r);
161 hdc = GetDC(hDlg);
162 yOffset = r.bottom;
163 hOldFont = (HFONT)SelectObject(hdc, (HFONT)SendDlgItemMessageW(hDlg, IDC_YESTOALL_MESSAGE, WM_GETFONT, 0, 0));
164 DrawTextW(hdc, info->lpszText, -1, &r, DT_NOPREFIX | DT_PATH_ELLIPSIS | DT_WORDBREAK | DT_CALCRECT);
165 SelectObject(hdc, hOldFont);
166 yOffset -= r.bottom;
167 yOffset = min(yOffset, 35); /* don't make the dialog too small */
168 ReleaseDC(hDlg, hdc);
169
170 GetClientRect(hDlg, &r);
171 xPos = r.right - 7;
172 GetWindowRect(hDlg, &r);
173 width = r.right - r.left;
174 height = r.bottom - r.top - yOffset;
175 MoveWindow(hDlg, (GetSystemMetrics(SM_CXSCREEN) - width)/2,
176 (GetSystemMetrics(SM_CYSCREEN) - height)/2, width, height, FALSE);
177
178 confirm_msg_move_button(hDlg, IDCANCEL, &xPos, yOffset, info->bYesToAll);
179 confirm_msg_move_button(hDlg, IDNO, &xPos, yOffset, TRUE);
180 confirm_msg_move_button(hDlg, IDC_YESTOALL, &xPos, yOffset, info->bYesToAll);
181 confirm_msg_move_button(hDlg, IDYES, &xPos, yOffset, TRUE);
182
183 return TRUE;
184 }
185
ConfirmMsgBoxProc(HWND hDlg,UINT uMsg,WPARAM wParam,LPARAM lParam)186 static INT_PTR CALLBACK ConfirmMsgBoxProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
187 {
188 switch (uMsg)
189 {
190 case WM_INITDIALOG:
191 return ConfirmMsgBox_Init(hDlg, lParam);
192 case WM_PAINT:
193 return ConfirmMsgBox_Paint(hDlg);
194 case WM_COMMAND:
195 EndDialog(hDlg, wParam);
196 break;
197 case WM_CLOSE:
198 EndDialog(hDlg, IDCANCEL);
199 break;
200 }
201 return FALSE;
202 }
203
SHELL_ConfirmMsgBox(HWND hWnd,LPWSTR lpszText,LPWSTR lpszCaption,HICON hIcon,BOOL bYesToAll)204 int SHELL_ConfirmMsgBox(HWND hWnd, LPWSTR lpszText, LPWSTR lpszCaption, HICON hIcon, BOOL bYesToAll)
205 {
206 struct confirm_msg_info info;
207
208 info.lpszText = lpszText;
209 info.lpszCaption = lpszCaption;
210 info.hIcon = hIcon;
211 info.bYesToAll = bYesToAll;
212 return DialogBoxParamW(shell32_hInstance, MAKEINTRESOURCEW(IDD_YESTOALL_MSGBOX), hWnd, ConfirmMsgBoxProc, (LPARAM)&info);
213 }
214
215 /* confirmation dialogs content */
216 typedef struct
217 {
218 HINSTANCE hIconInstance;
219 UINT icon_resource_id;
220 UINT caption_resource_id, text_resource_id;
221 } SHELL_ConfirmIDstruc;
222
SHELL_ConfirmIDs(int nKindOfDialog,SHELL_ConfirmIDstruc * ids)223 static BOOL SHELL_ConfirmIDs(int nKindOfDialog, SHELL_ConfirmIDstruc *ids)
224 {
225 ids->hIconInstance = shell32_hInstance;
226 switch (nKindOfDialog)
227 {
228 case ASK_DELETE_FILE:
229 ids->icon_resource_id = IDI_SHELL_CONFIRM_DELETE;
230 ids->caption_resource_id = IDS_DELETEITEM_CAPTION;
231 ids->text_resource_id = IDS_DELETEITEM_TEXT;
232 return TRUE;
233
234 case ASK_DELETE_FOLDER:
235 ids->icon_resource_id = IDI_SHELL_CONFIRM_DELETE;
236 ids->caption_resource_id = IDS_DELETEFOLDER_CAPTION;
237 ids->text_resource_id = IDS_DELETEITEM_TEXT;
238 return TRUE;
239
240 case ASK_DELETE_MULTIPLE_ITEM:
241 ids->icon_resource_id = IDI_SHELL_CONFIRM_DELETE;
242 ids->caption_resource_id = IDS_DELETEITEM_CAPTION;
243 ids->text_resource_id = IDS_DELETEMULTIPLE_TEXT;
244 return TRUE;
245
246 case ASK_TRASH_FILE:
247 ids->icon_resource_id = IDI_SHELL_TRASH_FILE;
248 ids->caption_resource_id = IDS_DELETEITEM_CAPTION;
249 ids->text_resource_id = IDS_TRASHITEM_TEXT;
250 return TRUE;
251
252 case ASK_TRASH_FOLDER:
253 ids->icon_resource_id = IDI_SHELL_TRASH_FILE;
254 ids->caption_resource_id = IDS_DELETEFOLDER_CAPTION;
255 ids->text_resource_id = IDS_TRASHFOLDER_TEXT;
256 return TRUE;
257
258 case ASK_TRASH_MULTIPLE_ITEM:
259 ids->icon_resource_id = IDI_SHELL_TRASH_FILE;
260 ids->caption_resource_id = IDS_DELETEITEM_CAPTION;
261 ids->text_resource_id = IDS_TRASHMULTIPLE_TEXT;
262 return TRUE;
263
264 case ASK_CANT_TRASH_ITEM:
265 ids->icon_resource_id = IDI_SHELL_CONFIRM_DELETE;
266 ids->caption_resource_id = IDS_DELETEITEM_CAPTION;
267 ids->text_resource_id = IDS_CANTTRASH_TEXT;
268 return TRUE;
269
270 case ASK_DELETE_SELECTED:
271 ids->icon_resource_id = IDI_SHELL_CONFIRM_DELETE;
272 ids->caption_resource_id = IDS_DELETEITEM_CAPTION;
273 ids->text_resource_id = IDS_DELETESELECTED_TEXT;
274 return TRUE;
275
276 case ASK_OVERWRITE_FILE:
277 ids->icon_resource_id = IDI_SHELL_FOLDER_MOVE2;
278 ids->caption_resource_id = IDS_OVERWRITEFILE_CAPTION;
279 ids->text_resource_id = IDS_OVERWRITEFILE_TEXT;
280 return TRUE;
281
282 case ASK_OVERWRITE_FOLDER:
283 ids->icon_resource_id = IDI_SHELL_FOLDER_MOVE2;
284 ids->caption_resource_id = IDS_OVERWRITEFILE_CAPTION;
285 ids->text_resource_id = IDS_OVERWRITEFOLDER_TEXT;
286 return TRUE;
287
288 default:
289 FIXME(" Unhandled nKindOfDialog %d stub\n", nKindOfDialog);
290 }
291 return FALSE;
292 }
293
SHELL_ConfirmDialogW(HWND hWnd,int nKindOfDialog,LPCWSTR szDir,FILE_OPERATION * op)294 static BOOL SHELL_ConfirmDialogW(HWND hWnd, int nKindOfDialog, LPCWSTR szDir, FILE_OPERATION *op)
295 {
296 WCHAR szCaption[255], szText[255], szBuffer[MAX_PATH + 256];
297 SHELL_ConfirmIDstruc ids;
298 DWORD_PTR args[1];
299 HICON hIcon;
300 int ret;
301
302 assert(nKindOfDialog >= 0 && nKindOfDialog < 32);
303 if (op && (op->dwYesToAllMask & (1 << nKindOfDialog)))
304 return TRUE;
305
306 if (!SHELL_ConfirmIDs(nKindOfDialog, &ids)) return FALSE;
307
308 LoadStringW(shell32_hInstance, ids.caption_resource_id, szCaption, sizeof(szCaption)/sizeof(WCHAR));
309 LoadStringW(shell32_hInstance, ids.text_resource_id, szText, sizeof(szText)/sizeof(WCHAR));
310
311 args[0] = (DWORD_PTR)szDir;
312 FormatMessageW(FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ARGUMENT_ARRAY,
313 szText, 0, 0, szBuffer, sizeof(szBuffer), (va_list*)args);
314 hIcon = LoadIconW(ids.hIconInstance, (LPWSTR)MAKEINTRESOURCE(ids.icon_resource_id));
315
316 ret = SHELL_ConfirmMsgBox(hWnd, szBuffer, szCaption, hIcon, op && op->bManyItems);
317 if (op)
318 {
319 if (ret == IDC_YESTOALL)
320 {
321 op->dwYesToAllMask |= (1 << nKindOfDialog);
322 ret = IDYES;
323 }
324 if (ret == IDCANCEL)
325 op->bCancelled = TRUE;
326 if (ret != IDYES)
327 op->req->fAnyOperationsAborted = TRUE;
328 }
329 return ret == IDYES;
330 }
331
SHELL_ConfirmYesNoW(HWND hWnd,int nKindOfDialog,LPCWSTR szDir)332 BOOL SHELL_ConfirmYesNoW(HWND hWnd, int nKindOfDialog, LPCWSTR szDir)
333 {
334 return SHELL_ConfirmDialogW(hWnd, nKindOfDialog, szDir, NULL);
335 }
336
SHELL32_AnsiToUnicodeBuf(LPCSTR aPath,LPWSTR * wPath,DWORD minChars)337 static DWORD SHELL32_AnsiToUnicodeBuf(LPCSTR aPath, LPWSTR *wPath, DWORD minChars)
338 {
339 DWORD len = MultiByteToWideChar(CP_ACP, 0, aPath, -1, NULL, 0);
340
341 if (len < minChars)
342 len = minChars;
343
344 *wPath = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
345 if (*wPath)
346 {
347 MultiByteToWideChar(CP_ACP, 0, aPath, -1, *wPath, len);
348 return NO_ERROR;
349 }
350 return E_OUTOFMEMORY;
351 }
352
SHELL32_FreeUnicodeBuf(LPWSTR wPath)353 static void SHELL32_FreeUnicodeBuf(LPWSTR wPath)
354 {
355 HeapFree(GetProcessHeap(), 0, wPath);
356 }
357
SHIsFileAvailableOffline(LPCWSTR path,LPDWORD status)358 EXTERN_C HRESULT WINAPI SHIsFileAvailableOffline(LPCWSTR path, LPDWORD status)
359 {
360 FIXME("(%s, %p) stub\n", debugstr_w(path), status);
361 return E_FAIL;
362 }
363
364 /**************************************************************************
365 * SHELL_DeleteDirectory() [internal]
366 *
367 * Asks for confirmation when bShowUI is true and deletes the directory and
368 * all its subdirectories and files if necessary.
369 */
SHELL_DeleteDirectoryW(FILE_OPERATION * op,LPCWSTR pszDir,BOOL bShowUI)370 BOOL SHELL_DeleteDirectoryW(FILE_OPERATION *op, LPCWSTR pszDir, BOOL bShowUI)
371 {
372 BOOL ret = TRUE;
373 HANDLE hFind;
374 WIN32_FIND_DATAW wfd;
375 WCHAR szTemp[MAX_PATH];
376
377 /* Make sure the directory exists before eventually prompting the user */
378 PathCombineW(szTemp, pszDir, L"*");
379 hFind = FindFirstFileW(szTemp, &wfd);
380 if (hFind == INVALID_HANDLE_VALUE)
381 return FALSE;
382
383 if (!bShowUI || (ret = SHELL_ConfirmDialogW(op->req->hwnd, ASK_DELETE_FOLDER, pszDir, NULL)))
384 {
385 do
386 {
387 if (IsDotDir(wfd.cFileName))
388 continue;
389 PathCombineW(szTemp, pszDir, wfd.cFileName);
390 if (FILE_ATTRIBUTE_DIRECTORY & wfd.dwFileAttributes)
391 ret = SHELL_DeleteDirectoryW(op, szTemp, FALSE);
392 else
393 ret = (SHNotifyDeleteFileW(op, szTemp) == ERROR_SUCCESS);
394
395 if (op->progress != NULL)
396 op->bCancelled |= op->progress->HasUserCancelled();
397 } while (ret && FindNextFileW(hFind, &wfd) && !op->bCancelled);
398 }
399 FindClose(hFind);
400 if (ret)
401 ret = (SHNotifyRemoveDirectoryW(pszDir) == ERROR_SUCCESS);
402 return ret;
403 }
404
405 /**************************************************************************
406 * Win32CreateDirectory [SHELL32.93]
407 *
408 * Creates a directory. Also triggers a change notify if one exists.
409 *
410 * PARAMS
411 * path [I] path to directory to create
412 *
413 * RETURNS
414 * TRUE if successful, FALSE otherwise
415 */
416
SHNotifyCreateDirectoryW(LPCWSTR path,LPSECURITY_ATTRIBUTES sec)417 static DWORD SHNotifyCreateDirectoryW(LPCWSTR path, LPSECURITY_ATTRIBUTES sec)
418 {
419 TRACE("(%s, %p)\n", debugstr_w(path), sec);
420
421 if (CreateDirectoryW(path, sec))
422 {
423 SHChangeNotify(SHCNE_MKDIR, SHCNF_PATHW, path, NULL);
424 return ERROR_SUCCESS;
425 }
426 return GetLastError();
427 }
428
429 /**********************************************************************/
430
Win32CreateDirectoryW(LPCWSTR path,LPSECURITY_ATTRIBUTES sec)431 EXTERN_C BOOL WINAPI Win32CreateDirectoryW(LPCWSTR path, LPSECURITY_ATTRIBUTES sec)
432 {
433 return (SHNotifyCreateDirectoryW(path, sec) == ERROR_SUCCESS);
434 }
435
436 /************************************************************************
437 * Win32RemoveDirectory [SHELL32.94]
438 *
439 * Deletes a directory. Also triggers a change notify if one exists.
440 *
441 * PARAMS
442 * path [I] path to directory to delete
443 *
444 * RETURNS
445 * TRUE if successful, FALSE otherwise
446 */
SHNotifyRemoveDirectoryW(LPCWSTR path)447 static DWORD SHNotifyRemoveDirectoryW(LPCWSTR path)
448 {
449 BOOL ret;
450 TRACE("(%s)\n", debugstr_w(path));
451
452 ret = RemoveDirectoryW(path);
453 if (!ret)
454 {
455 /* Directory may be write protected */
456 DWORD dwAttr = GetFileAttributesW(path);
457 if (IsAttrib(dwAttr, FILE_ATTRIBUTE_READONLY))
458 if (SetFileAttributesW(path, dwAttr & ~FILE_ATTRIBUTE_READONLY))
459 ret = RemoveDirectoryW(path);
460 }
461 if (ret)
462 {
463 SHChangeNotify(SHCNE_RMDIR, SHCNF_PATHW, path, NULL);
464 return ERROR_SUCCESS;
465 }
466 return GetLastError();
467 }
468
469 /***********************************************************************/
470
Win32RemoveDirectoryW(LPCWSTR path)471 EXTERN_C BOOL WINAPI Win32RemoveDirectoryW(LPCWSTR path)
472 {
473 return (SHNotifyRemoveDirectoryW(path) == ERROR_SUCCESS);
474 }
475
_SetOperationTitle(FILE_OPERATION * op)476 static void _SetOperationTitle(FILE_OPERATION *op) {
477 if (op->progress == NULL)
478 return;
479 WCHAR szTitle[50], szPreflight[50];
480 UINT animation_id = NULL;
481
482 switch (op->req->wFunc)
483 {
484 case FO_COPY:
485 LoadStringW(shell32_hInstance, IDS_FILEOOP_COPYING, szTitle, sizeof(szTitle)/sizeof(WCHAR));
486 LoadStringW(shell32_hInstance, IDS_FILEOOP_FROM_TO, op->szBuilderString, sizeof( op->szBuilderString)/sizeof(WCHAR));
487 animation_id = IDA_SHELL_COPY;
488 break;
489 case FO_DELETE:
490 LoadStringW(shell32_hInstance, IDS_FILEOOP_DELETING, szTitle, sizeof(szTitle)/sizeof(WCHAR));
491 LoadStringW(shell32_hInstance, IDS_FILEOOP_FROM, op->szBuilderString, sizeof( op->szBuilderString)/sizeof(WCHAR));
492 animation_id = IDA_SHELL_DELETE;
493 break;
494 case FO_MOVE:
495 LoadStringW(shell32_hInstance, IDS_FILEOOP_MOVING, szTitle, sizeof(szTitle)/sizeof(WCHAR));
496 LoadStringW(shell32_hInstance, IDS_FILEOOP_FROM_TO, op->szBuilderString, sizeof( op->szBuilderString)/sizeof(WCHAR));
497 animation_id = IDA_SHELL_COPY;
498 break;
499 default:
500 return;
501 }
502 LoadStringW(shell32_hInstance, IDS_FILEOOP_PREFLIGHT, szPreflight, sizeof(szPreflight)/sizeof(WCHAR));
503
504 op->progress->SetTitle(szTitle);
505 op->progress->SetLine(1, szPreflight, false, NULL);
506 op->progress->SetAnimation(shell32_hInstance, animation_id);
507 }
508
_SetOperationTexts(FILE_OPERATION * op,LPCWSTR src,LPCWSTR dest)509 static void _SetOperationTexts(FILE_OPERATION *op, LPCWSTR src, LPCWSTR dest) {
510 if (op->progress == NULL || src == NULL)
511 return;
512 LPWSTR fileSpecS, pathSpecS, fileSpecD, pathSpecD;
513 WCHAR szFolderS[50], szFolderD[50], szFinalString[260];
514
515 DWORD_PTR args[2];
516
517 fileSpecS = (pathSpecS = (LPWSTR) src);
518 fileSpecD = (pathSpecD = (LPWSTR) dest);
519
520 // March across the string to get the file path and it's parent dir.
521 for (LPWSTR ptr = (LPWSTR) src; *ptr; ptr++) {
522 if (*ptr == '\\') {
523 pathSpecS = fileSpecS;
524 fileSpecS = ptr+1;
525 }
526 }
527 lstrcpynW(szFolderS, pathSpecS, min(50, fileSpecS - pathSpecS));
528 args[0] = (DWORD_PTR) szFolderS;
529
530 switch (op->req->wFunc)
531 {
532 case FO_COPY:
533 case FO_MOVE:
534 if (dest == NULL)
535 return;
536 for (LPWSTR ptr = (LPWSTR) dest; *ptr; ptr++) {
537 if (*ptr == '\\') {
538 pathSpecD = fileSpecD;
539 fileSpecD = ptr + 1;
540 }
541 }
542 lstrcpynW(szFolderD, pathSpecD, min(50, fileSpecD - pathSpecD));
543 args[1] = (DWORD_PTR) szFolderD;
544 break;
545 case FO_DELETE:
546 break;
547 default:
548 return;
549 }
550
551 FormatMessageW(FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ARGUMENT_ARRAY,
552 op->szBuilderString, 0, 0, szFinalString, sizeof(szFinalString), (va_list*)args);
553
554 op->progress->SetLine(1, fileSpecS, false, NULL);
555 op->progress->SetLine(2, szFinalString, false, NULL);
556 }
557
558
SHCopyProgressRoutine(LARGE_INTEGER TotalFileSize,LARGE_INTEGER TotalBytesTransferred,LARGE_INTEGER StreamSize,LARGE_INTEGER StreamBytesTransferred,DWORD dwStreamNumber,DWORD dwCallbackReason,HANDLE hSourceFile,HANDLE hDestinationFile,LPVOID lpData)559 DWORD CALLBACK SHCopyProgressRoutine(
560 LARGE_INTEGER TotalFileSize,
561 LARGE_INTEGER TotalBytesTransferred,
562 LARGE_INTEGER StreamSize,
563 LARGE_INTEGER StreamBytesTransferred,
564 DWORD dwStreamNumber,
565 DWORD dwCallbackReason,
566 HANDLE hSourceFile,
567 HANDLE hDestinationFile,
568 LPVOID lpData
569 ) {
570 FILE_OPERATION *op = (FILE_OPERATION *) lpData;
571
572 if (op->progress) {
573 /*
574 * This is called at the start of each file. To keop less state,
575 * I'm adding the file to the completed size here, and the re-subtracting
576 * it when drawing the progress bar.
577 */
578 if (dwCallbackReason & CALLBACK_STREAM_SWITCH)
579 op->completedSize.QuadPart += TotalFileSize.QuadPart;
580
581 op->progress->SetProgress64(op->completedSize.QuadPart -
582 TotalFileSize.QuadPart +
583 TotalBytesTransferred.QuadPart
584 , op->totalSize.QuadPart);
585
586
587 op->bCancelled = op->progress->HasUserCancelled();
588 }
589
590 return 0;
591 }
592
593
594 /************************************************************************
595 * SHNotifyDeleteFileW [internal]
596 *
597 * Deletes a file. Also triggers a change notify if one exists.
598 *
599 * PARAMS
600 * op [I] File Operation context
601 * path [I] path to source file to move
602 *
603 * RETURNS
604 * ERORR_SUCCESS if successful
605 */
SHNotifyDeleteFileW(FILE_OPERATION * op,LPCWSTR path)606 static DWORD SHNotifyDeleteFileW(FILE_OPERATION *op, LPCWSTR path)
607 {
608 BOOL ret;
609
610 TRACE("(%s)\n", debugstr_w(path));
611
612 _SetOperationTexts(op, path, NULL);
613
614 LARGE_INTEGER FileSize;
615 FileSize.QuadPart = 0;
616
617 WIN32_FIND_DATAW wfd;
618 HANDLE hFile = FindFirstFileW(path, &wfd);
619 if (hFile != INVALID_HANDLE_VALUE && IsAttribFile(wfd.dwFileAttributes)) {
620 ULARGE_INTEGER tmp;
621 tmp.u.LowPart = wfd.nFileSizeLow;
622 tmp.u.HighPart = wfd.nFileSizeHigh;
623 FileSize.QuadPart = tmp.QuadPart;
624 }
625 FindClose(hFile);
626
627 ret = DeleteFileW(path);
628 if (!ret)
629 {
630 /* File may be write protected or a system file */
631 DWORD dwAttr = GetFileAttributesW(path);
632 if (IsAttrib(dwAttr, FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM))
633 if (SetFileAttributesW(path, dwAttr & ~(FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM)))
634 ret = DeleteFileW(path);
635 }
636 if (ret)
637 {
638 // Bit of a hack to make the progress bar move. We don't have progress inside the file, so inform when done.
639 SHCopyProgressRoutine(FileSize, FileSize, FileSize, FileSize, 0, CALLBACK_STREAM_SWITCH, NULL, NULL, op);
640 SHChangeNotify(SHCNE_DELETE, SHCNF_PATHW, path, NULL);
641 return ERROR_SUCCESS;
642 }
643 return GetLastError();
644 }
645
646 /************************************************************************
647 * Win32DeleteFile [SHELL32.164]
648 *
649 * Deletes a file. Also triggers a change notify if one exists.
650 *
651 * PARAMS
652 * path [I] path to file to delete
653 *
654 * RETURNS
655 * TRUE if successful, FALSE otherwise
656 */
Win32DeleteFileW(LPCWSTR path)657 EXTERN_C DWORD WINAPI Win32DeleteFileW(LPCWSTR path)
658 {
659 return (SHNotifyDeleteFileW(NULL, path) == ERROR_SUCCESS);
660 }
661
662 /************************************************************************
663 * CheckForError [internal]
664 *
665 * Show message box if operation failed
666 *
667 * PARAMS
668 * op [I] File Operation context
669 * error [I] Error code
670 * src [I] Source file full name
671 *
672 */
CheckForError(FILE_OPERATION * op,DWORD error,LPCWSTR src)673 static DWORD CheckForError(FILE_OPERATION *op, DWORD error, LPCWSTR src)
674 {
675 CStringW strTitle, strMask, strText;
676 LPWSTR lpMsgBuffer;
677
678 if (error == ERROR_SUCCESS || (op->req->fFlags & (FOF_NOERRORUI | FOF_SILENT)))
679 goto exit;
680
681 strTitle.LoadStringW(op->req->wFunc == FO_COPY ? IDS_COPYERRORTITLE : IDS_MOVEERRORTITLE);
682
683 FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
684 NULL,
685 error,
686 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
687 (LPWSTR)&lpMsgBuffer,
688 0,
689 NULL);
690
691 strText.Format(op->req->wFunc == FO_COPY ? IDS_COPYERROR : IDS_MOVEERROR,
692 PathFindFileNameW(src),
693 lpMsgBuffer);
694
695 MessageBoxW(op->req->hwnd, strText, strTitle, MB_ICONERROR);
696 LocalFree(lpMsgBuffer);
697
698 exit:
699 return error;
700 }
701
702 /************************************************************************
703 * SHNotifyMoveFile [internal]
704 *
705 * Moves a file. Also triggers a change notify if one exists.
706 *
707 * PARAMS
708 * op [I] File Operation context
709 * src [I] path to source file to move
710 * dest [I] path to target file to move to
711 *
712 * RETURNS
713 * ERROR_SUCCESS if successful
714 */
SHNotifyMoveFileW(FILE_OPERATION * op,LPCWSTR src,LPCWSTR dest,BOOL isdir)715 static DWORD SHNotifyMoveFileW(FILE_OPERATION *op, LPCWSTR src, LPCWSTR dest, BOOL isdir)
716 {
717 BOOL ret;
718
719 TRACE("(%s %s)\n", debugstr_w(src), debugstr_w(dest));
720
721 _SetOperationTexts(op, src, dest);
722
723 ret = MoveFileWithProgressW(src, dest, SHCopyProgressRoutine, op, MOVEFILE_REPLACE_EXISTING);
724
725 /* MOVEFILE_REPLACE_EXISTING fails with dirs, so try MoveFile */
726 if (!ret)
727 ret = MoveFileW(src, dest);
728
729 if (!ret)
730 {
731 DWORD dwAttr;
732
733 dwAttr = SHFindAttrW(dest, FALSE);
734 if (INVALID_FILE_ATTRIBUTES == dwAttr)
735 {
736 /* Source file may be write protected or a system file */
737 dwAttr = GetFileAttributesW(src);
738 if (IsAttrib(dwAttr, FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM))
739 if (SetFileAttributesW(src, dwAttr & ~(FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM)))
740 ret = MoveFileW(src, dest);
741 }
742 }
743 if (ret)
744 {
745 SHChangeNotify(isdir ? SHCNE_MKDIR : SHCNE_CREATE, SHCNF_PATHW, dest, NULL);
746 SHChangeNotify(isdir ? SHCNE_RMDIR : SHCNE_DELETE, SHCNF_PATHW, src, NULL);
747 return ERROR_SUCCESS;
748 }
749
750 return CheckForError(op, GetLastError(), src);
751 }
752
SHIsCdRom(LPCWSTR path)753 static BOOL SHIsCdRom(LPCWSTR path)
754 {
755 WCHAR tmp[] = { L"A:\\" };
756
757 if (!path || !path[0])
758 return FALSE;
759
760 if (path[1] != UNICODE_NULL && path[1] != ':')
761 return FALSE;
762
763 tmp[0] = path[0];
764
765 return GetDriveTypeW(tmp) == DRIVE_CDROM;
766 }
767
768 /************************************************************************
769 * SHNotifyCopyFile [internal]
770 *
771 * Copies a file. Also triggers a change notify if one exists.
772 *
773 * PARAMS
774 * src [I] path to source file to move
775 * dest [I] path to target file to move to
776 * bFailIfExists [I] if TRUE, the target file will not be overwritten if
777 * a file with this name already exists
778 *
779 * RETURNS
780 * ERROR_SUCCESS if successful
781 */
SHNotifyCopyFileW(FILE_OPERATION * op,LPCWSTR src,LPCWSTR dest,BOOL bFailIfExists)782 static DWORD SHNotifyCopyFileW(FILE_OPERATION *op, LPCWSTR src, LPCWSTR dest, BOOL bFailIfExists)
783 {
784 BOOL ret;
785 DWORD attribs;
786
787 TRACE("(%s %s %s)\n", debugstr_w(src), debugstr_w(dest), bFailIfExists ? "failIfExists" : "");
788
789 _SetOperationTexts(op, src, dest);
790
791 /* Destination file may already exist with read only attribute */
792 attribs = GetFileAttributesW(dest);
793 if (IsAttrib(attribs, FILE_ATTRIBUTE_READONLY))
794 SetFileAttributesW(dest, attribs & ~FILE_ATTRIBUTE_READONLY);
795
796 if (GetFileAttributesW(dest) & FILE_ATTRIBUTE_READONLY)
797 {
798 SetFileAttributesW(dest, attribs & ~FILE_ATTRIBUTE_READONLY);
799 if (GetFileAttributesW(dest) & FILE_ATTRIBUTE_READONLY)
800 {
801 TRACE("[shell32, SHNotifyCopyFileW] STILL SHIT\n");
802 }
803 }
804
805 ret = CopyFileExW(src, dest, SHCopyProgressRoutine, op, &op->bCancelled, bFailIfExists);
806 if (ret)
807 {
808 // We are copying from a CD-ROM volume, which is readonly
809 if (SHIsCdRom(src))
810 {
811 attribs = GetFileAttributesW(dest);
812 attribs &= ~FILE_ATTRIBUTE_READONLY;
813 SetFileAttributesW(dest, attribs);
814 }
815
816 SHChangeNotify(SHCNE_CREATE, SHCNF_PATHW, dest, NULL);
817 return ERROR_SUCCESS;
818 }
819
820 return CheckForError(op, GetLastError(), src);
821 }
822
823 /*************************************************************************
824 * SHCreateDirectory [SHELL32.165]
825 *
826 * This function creates a file system folder whose fully qualified path is
827 * given by path. If one or more of the intermediate folders do not exist,
828 * they will be created as well.
829 *
830 * PARAMS
831 * hWnd [I]
832 * path [I] path of directory to create
833 *
834 * RETURNS
835 * ERROR_SUCCESS or one of the following values:
836 * ERROR_BAD_PATHNAME if the path is relative
837 * ERROR_FILE_EXISTS when a file with that name exists
838 * ERROR_PATH_NOT_FOUND can't find the path, probably invalid
839 * ERROR_INVALID_NAME if the path contains invalid chars
840 * ERROR_ALREADY_EXISTS when the directory already exists
841 * ERROR_FILENAME_EXCED_RANGE if the filename was to long to process
842 *
843 * NOTES
844 * exported by ordinal
845 * Win9x exports ANSI
846 * WinNT/2000 exports Unicode
847 */
SHCreateDirectory(HWND hWnd,LPCWSTR path)848 int WINAPI SHCreateDirectory(HWND hWnd, LPCWSTR path)
849 {
850 return SHCreateDirectoryExW(hWnd, path, NULL);
851 }
852
853 /*************************************************************************
854 * SHCreateDirectoryExA [SHELL32.@]
855 *
856 * This function creates a file system folder whose fully qualified path is
857 * given by path. If one or more of the intermediate folders do not exist,
858 * they will be created as well.
859 *
860 * PARAMS
861 * hWnd [I]
862 * path [I] path of directory to create
863 * sec [I] security attributes to use or NULL
864 *
865 * RETURNS
866 * ERROR_SUCCESS or one of the following values:
867 * ERROR_BAD_PATHNAME or ERROR_PATH_NOT_FOUND if the path is relative
868 * ERROR_INVALID_NAME if the path contains invalid chars
869 * ERROR_FILE_EXISTS when a file with that name exists
870 * ERROR_ALREADY_EXISTS when the directory already exists
871 * ERROR_FILENAME_EXCED_RANGE if the filename was too long to process
872 *
873 * FIXME: Not implemented yet;
874 * SHCreateDirectoryEx also verifies that the files in the directory will be visible
875 * if the path is a network path to deal with network drivers which might have a limited
876 * but unknown maximum path length. If not:
877 *
878 * If hWnd is set to a valid window handle, a message box is displayed warning
879 * the user that the files may not be accessible. If the user chooses not to
880 * proceed, the function returns ERROR_CANCELLED.
881 *
882 * If hWnd is set to NULL, no user interface is displayed and the function
883 * returns ERROR_CANCELLED.
884 */
SHCreateDirectoryExA(HWND hWnd,LPCSTR path,LPSECURITY_ATTRIBUTES sec)885 int WINAPI SHCreateDirectoryExA(HWND hWnd, LPCSTR path, LPSECURITY_ATTRIBUTES sec)
886 {
887 LPWSTR wPath;
888 DWORD retCode;
889
890 TRACE("(%s, %p)\n", debugstr_a(path), sec);
891
892 retCode = SHELL32_AnsiToUnicodeBuf(path, &wPath, 0);
893 if (!retCode)
894 {
895 retCode = SHCreateDirectoryExW(hWnd, wPath, sec);
896 SHELL32_FreeUnicodeBuf(wPath);
897 }
898 return retCode;
899 }
900
901 /*************************************************************************
902 * SHCreateDirectoryExW [SHELL32.@]
903 *
904 * See SHCreateDirectoryExA.
905 */
SHCreateDirectoryExW(HWND hWnd,LPCWSTR path,LPSECURITY_ATTRIBUTES sec)906 int WINAPI SHCreateDirectoryExW(HWND hWnd, LPCWSTR path, LPSECURITY_ATTRIBUTES sec)
907 {
908 int ret = ERROR_BAD_PATHNAME;
909 TRACE("(%p, %s, %p)\n", hWnd, debugstr_w(path), sec);
910
911 if (PathIsRelativeW(path))
912 {
913 SetLastError(ret);
914 }
915 else
916 {
917 ret = SHNotifyCreateDirectoryW(path, sec);
918 /* Refuse to work on certain error codes before trying to create directories recursively */
919 if (ret != ERROR_SUCCESS &&
920 ret != ERROR_FILE_EXISTS &&
921 ret != ERROR_ALREADY_EXISTS &&
922 ret != ERROR_FILENAME_EXCED_RANGE)
923 {
924 WCHAR *pEnd, *pSlash, szTemp[MAX_PATH + 1]; /* extra for PathAddBackslash() */
925
926 lstrcpynW(szTemp, path, MAX_PATH);
927 pEnd = PathAddBackslashW(szTemp);
928 pSlash = szTemp + 3;
929
930 while (*pSlash)
931 {
932 while (*pSlash && *pSlash != '\\') pSlash++;
933 if (*pSlash)
934 {
935 *pSlash = 0; /* terminate path at separator */
936
937 ret = SHNotifyCreateDirectoryW(szTemp, pSlash + 1 == pEnd ? sec : NULL);
938 }
939 *pSlash++ = '\\'; /* put the separator back */
940 }
941 }
942
943 if (ret && hWnd && (ERROR_CANCELLED != ret && ERROR_ALREADY_EXISTS != ret))
944 {
945 ShellMessageBoxW(shell32_hInstance, hWnd, MAKEINTRESOURCEW(IDS_CREATEFOLDER_DENIED), MAKEINTRESOURCEW(IDS_CREATEFOLDER_CAPTION),
946 MB_ICONEXCLAMATION | MB_OK, path);
947 ret = ERROR_CANCELLED;
948 }
949 }
950
951 return ret;
952 }
953
954 /*************************************************************************
955 * SHFindAttrW [internal]
956 *
957 * Get the Attributes for a file or directory. The difference to GetAttributes()
958 * is that this function will also work for paths containing wildcard characters
959 * in its filename.
960
961 * PARAMS
962 * path [I] path of directory or file to check
963 * fileOnly [I] TRUE if only files should be found
964 *
965 * RETURNS
966 * INVALID_FILE_ATTRIBUTES if the path does not exist, the actual attributes of
967 * the first file or directory found otherwise
968 */
SHFindAttrW(LPCWSTR pName,BOOL fileOnly)969 static DWORD SHFindAttrW(LPCWSTR pName, BOOL fileOnly)
970 {
971 WIN32_FIND_DATAW wfd;
972 BOOL b_FileMask = fileOnly && (NULL != StrPBrkW(pName, L"*?"));
973 DWORD dwAttr = INVALID_FILE_ATTRIBUTES;
974 HANDLE hFind = FindFirstFileW(pName, &wfd);
975
976 TRACE("%s %d\n", debugstr_w(pName), fileOnly);
977 if (INVALID_HANDLE_VALUE != hFind)
978 {
979 do
980 {
981 if (b_FileMask && IsAttribDir(wfd.dwFileAttributes))
982 continue;
983 dwAttr = wfd.dwFileAttributes;
984 break;
985 } while (FindNextFileW(hFind, &wfd));
986
987 FindClose(hFind);
988 }
989 return dwAttr;
990 }
991
992 /*************************************************************************
993 *
994 * _ConvertAtoW helper function for SHFileOperationA
995 *
996 * Converts a string or string-list to unicode.
997 */
_ConvertAtoW(PCSTR strSrc,PCWSTR * pStrDest,BOOL isList)998 static DWORD _ConvertAtoW(PCSTR strSrc, PCWSTR* pStrDest, BOOL isList)
999 {
1000 *pStrDest = NULL;
1001
1002 // If the input is null, nothing to convert.
1003 if (!strSrc)
1004 return 0;
1005
1006 // Measure the total size, depending on if it's a zero-terminated list.
1007 int sizeA = 0;
1008 if (isList)
1009 {
1010 PCSTR tmpSrc = strSrc;
1011 int size;
1012 do
1013 {
1014 size = lstrlenA(tmpSrc) + 1;
1015 sizeA += size;
1016 tmpSrc += size;
1017 } while (size != 1);
1018 }
1019 else
1020 {
1021 sizeA = lstrlenA(strSrc) + 1;
1022 }
1023
1024 // Measure the needed allocation size.
1025 int sizeW = MultiByteToWideChar(CP_ACP, 0, strSrc, sizeA, NULL, 0);
1026 if (!sizeW)
1027 return GetLastError();
1028
1029 PWSTR strDest = (PWSTR) HeapAlloc(GetProcessHeap(), 0, sizeW * sizeof(WCHAR));
1030 if (!strDest)
1031 return ERROR_OUTOFMEMORY;
1032
1033 int err = MultiByteToWideChar(CP_ACP, 0, strSrc, sizeA, strDest, sizeW);
1034 if (!err)
1035 {
1036 HeapFree(GetProcessHeap(), 0, strDest);
1037 return GetLastError();
1038 }
1039
1040 *pStrDest = strDest;
1041 return 0;
1042 }
1043
1044 /*************************************************************************
1045 * SHFileOperationA [SHELL32.@]
1046 *
1047 * Function to copy, move, delete and create one or more files with optional
1048 * user prompts.
1049 *
1050 * PARAMS
1051 * lpFileOp [I/O] pointer to a structure containing all the necessary information
1052 *
1053 * RETURNS
1054 * Success: ERROR_SUCCESS.
1055 * Failure: ERROR_CANCELLED.
1056 *
1057 * NOTES
1058 * exported by name
1059 */
SHFileOperationA(LPSHFILEOPSTRUCTA lpFileOp)1060 int WINAPI SHFileOperationA(LPSHFILEOPSTRUCTA lpFileOp)
1061 {
1062 int errCode, retCode;
1063 SHFILEOPSTRUCTW nFileOp = { 0 };
1064
1065 // Convert A information to W
1066 nFileOp.hwnd = lpFileOp->hwnd;
1067 nFileOp.wFunc = lpFileOp->wFunc;
1068 nFileOp.fFlags = lpFileOp->fFlags;
1069
1070 errCode = _ConvertAtoW(lpFileOp->pFrom, &nFileOp.pFrom, TRUE);
1071 if (errCode != 0)
1072 goto cleanup;
1073
1074 if (FO_DELETE != (nFileOp.wFunc & FO_MASK))
1075 {
1076 errCode = _ConvertAtoW(lpFileOp->pTo, &nFileOp.pTo, TRUE);
1077 if (errCode != 0)
1078 goto cleanup;
1079 }
1080
1081 if (nFileOp.fFlags & FOF_SIMPLEPROGRESS)
1082 {
1083 errCode = _ConvertAtoW(lpFileOp->lpszProgressTitle, &nFileOp.lpszProgressTitle, FALSE);
1084 if (errCode != 0)
1085 goto cleanup;
1086 }
1087
1088 // Call the actual function
1089 retCode = SHFileOperationW(&nFileOp);
1090 if (retCode)
1091 {
1092 ERR("SHFileOperationW failed with 0x%x\n", retCode);
1093 }
1094
1095 // Cleanup
1096 cleanup:
1097 if (nFileOp.pFrom)
1098 HeapFree(GetProcessHeap(), 0, (PVOID) nFileOp.pFrom);
1099 if (nFileOp.pTo)
1100 HeapFree(GetProcessHeap(), 0, (PVOID) nFileOp.pTo);
1101 if (nFileOp.lpszProgressTitle)
1102 HeapFree(GetProcessHeap(), 0, (PVOID) nFileOp.lpszProgressTitle);
1103
1104 if (errCode != 0)
1105 {
1106 lpFileOp->fAnyOperationsAborted = TRUE;
1107 SetLastError(errCode);
1108
1109 return errCode;
1110 }
1111
1112 // Thankfully, starting with NT4 the name mappings are always unicode, so no need to convert.
1113 lpFileOp->hNameMappings = nFileOp.hNameMappings;
1114 lpFileOp->fAnyOperationsAborted = nFileOp.fAnyOperationsAborted;
1115 return retCode;
1116 }
1117
grow_list(FILE_LIST * list)1118 static void __inline grow_list(FILE_LIST *list)
1119 {
1120 FILE_ENTRY *newx = (FILE_ENTRY *)HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, list->feFiles,
1121 list->num_alloc * 2 * sizeof(*newx) );
1122 list->feFiles = newx;
1123 list->num_alloc *= 2;
1124 }
1125
1126 /* adds a file to the FILE_ENTRY struct
1127 */
add_file_to_entry(FILE_ENTRY * feFile,LPCWSTR szFile)1128 static void add_file_to_entry(FILE_ENTRY *feFile, LPCWSTR szFile)
1129 {
1130 DWORD dwLen = lstrlenW(szFile) + 1;
1131 LPCWSTR ptr;
1132 LPCWSTR ptr2;
1133
1134 feFile->szFullPath = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, dwLen * sizeof(WCHAR));
1135 lstrcpyW(feFile->szFullPath, szFile);
1136
1137 ptr = StrRChrW(szFile, NULL, '\\');
1138 ptr2 = StrRChrW(szFile, NULL, '/');
1139 if (!ptr || ptr < ptr2)
1140 ptr = ptr2;
1141 if (ptr)
1142 {
1143 dwLen = ptr - szFile + 1;
1144 feFile->szDirectory = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, dwLen * sizeof(WCHAR));
1145 lstrcpynW(feFile->szDirectory, szFile, dwLen);
1146
1147 dwLen = lstrlenW(feFile->szFullPath) - dwLen + 1;
1148 feFile->szFilename = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, dwLen * sizeof(WCHAR));
1149 lstrcpyW(feFile->szFilename, ptr + 1); /* skip over backslash */
1150 }
1151 feFile->bFromWildcard = FALSE;
1152 }
1153
wildcard_to_file(LPCWSTR szWildCard,LPCWSTR szFileName)1154 static LPWSTR wildcard_to_file(LPCWSTR szWildCard, LPCWSTR szFileName)
1155 {
1156 LPCWSTR ptr;
1157 LPCWSTR ptr2;
1158 LPWSTR szFullPath;
1159 DWORD dwDirLen, dwFullLen;
1160
1161 ptr = StrRChrW(szWildCard, NULL, '\\');
1162 ptr2 = StrRChrW(szWildCard, NULL, '/');
1163 if (!ptr || ptr < ptr2)
1164 ptr = ptr2;
1165 dwDirLen = ptr - szWildCard + 1;
1166
1167 dwFullLen = dwDirLen + lstrlenW(szFileName) + 1;
1168 szFullPath = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, dwFullLen * sizeof(WCHAR));
1169
1170 lstrcpynW(szFullPath, szWildCard, dwDirLen + 1);
1171 lstrcatW(szFullPath, szFileName);
1172
1173 return szFullPath;
1174 }
1175
parse_wildcard_files(FILE_LIST * flList,LPCWSTR szFile,LPDWORD pdwListIndex)1176 static void parse_wildcard_files(FILE_LIST *flList, LPCWSTR szFile, LPDWORD pdwListIndex)
1177 {
1178 WIN32_FIND_DATAW wfd;
1179 HANDLE hFile = FindFirstFileW(szFile, &wfd);
1180 FILE_ENTRY *file;
1181 LPWSTR szFullPath;
1182 BOOL res;
1183
1184 if (hFile == INVALID_HANDLE_VALUE) return;
1185
1186 for (res = TRUE; res; res = FindNextFileW(hFile, &wfd))
1187 {
1188 if (IsDotDir(wfd.cFileName))
1189 continue;
1190
1191 if (*pdwListIndex >= flList->num_alloc)
1192 grow_list( flList );
1193
1194 szFullPath = wildcard_to_file(szFile, wfd.cFileName);
1195 file = &flList->feFiles[(*pdwListIndex)++];
1196 add_file_to_entry(file, szFullPath);
1197 file->bFromWildcard = TRUE;
1198 file->attributes = wfd.dwFileAttributes;
1199
1200 if (IsAttribDir(file->attributes))
1201 flList->bAnyDirectories = TRUE;
1202
1203 HeapFree(GetProcessHeap(), 0, szFullPath);
1204 }
1205
1206 FindClose(hFile);
1207 }
1208
1209 /* takes the null-separated file list and fills out the FILE_LIST */
parse_file_list(FILE_LIST * flList,LPCWSTR szFiles)1210 static HRESULT parse_file_list(FILE_LIST *flList, LPCWSTR szFiles)
1211 {
1212 LPCWSTR ptr = szFiles;
1213 WCHAR szCurFile[MAX_PATH];
1214 DWORD i = 0;
1215
1216 if (!szFiles)
1217 return ERROR_INVALID_PARAMETER;
1218
1219 flList->bAnyFromWildcard = FALSE;
1220 flList->bAnyDirectories = FALSE;
1221 flList->bAnyDontExist = FALSE;
1222 flList->num_alloc = 32;
1223 flList->dwNumFiles = 0;
1224
1225 /* empty list */
1226 if (!szFiles[0])
1227 return ERROR_ACCESS_DENIED;
1228
1229 flList->feFiles = (FILE_ENTRY *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
1230 flList->num_alloc * sizeof(FILE_ENTRY));
1231
1232 while (*ptr)
1233 {
1234 if (i >= flList->num_alloc) grow_list( flList );
1235
1236 /* change relative to absolute path */
1237 if (PathIsRelativeW(ptr))
1238 {
1239 GetCurrentDirectoryW(MAX_PATH, szCurFile);
1240 PathCombineW(szCurFile, szCurFile, ptr);
1241 flList->feFiles[i].bFromRelative = TRUE;
1242 }
1243 else
1244 {
1245 lstrcpyW(szCurFile, ptr);
1246 flList->feFiles[i].bFromRelative = FALSE;
1247 }
1248
1249 /* parse wildcard files if they are in the filename */
1250 if (StrPBrkW(szCurFile, L"*?"))
1251 {
1252 parse_wildcard_files(flList, szCurFile, &i);
1253 flList->bAnyFromWildcard = TRUE;
1254 i--;
1255 }
1256 else
1257 {
1258 FILE_ENTRY *file = &flList->feFiles[i];
1259 add_file_to_entry(file, szCurFile);
1260 file->attributes = GetFileAttributesW( file->szFullPath );
1261 file->bExists = (file->attributes != INVALID_FILE_ATTRIBUTES);
1262
1263 if (!file->bExists)
1264 flList->bAnyDontExist = TRUE;
1265
1266 if (IsAttribDir(file->attributes))
1267 flList->bAnyDirectories = TRUE;
1268 }
1269
1270 /* advance to the next string */
1271 ptr += lstrlenW(ptr) + 1;
1272 i++;
1273 }
1274 flList->dwNumFiles = i;
1275
1276 return S_OK;
1277 }
1278
1279 /* free the FILE_LIST */
destroy_file_list(FILE_LIST * flList)1280 static void destroy_file_list(FILE_LIST *flList)
1281 {
1282 DWORD i;
1283
1284 if (!flList || !flList->feFiles)
1285 return;
1286
1287 for (i = 0; i < flList->dwNumFiles; i++)
1288 {
1289 HeapFree(GetProcessHeap(), 0, flList->feFiles[i].szDirectory);
1290 HeapFree(GetProcessHeap(), 0, flList->feFiles[i].szFilename);
1291 HeapFree(GetProcessHeap(), 0, flList->feFiles[i].szFullPath);
1292 }
1293
1294 HeapFree(GetProcessHeap(), 0, flList->feFiles);
1295 }
1296
try_find_new_name(LPCWSTR szDestPath)1297 static CStringW try_find_new_name(LPCWSTR szDestPath)
1298 {
1299 CStringW mask(szDestPath);
1300 CStringW ext(PathFindExtensionW(szDestPath));
1301
1302 // cut off extension before inserting a "new file" mask
1303 if (!ext.IsEmpty())
1304 {
1305 mask = mask.Left(mask.GetLength() - ext.GetLength());
1306 }
1307 mask += L" (%d)" + ext;
1308
1309 CStringW newName;
1310
1311 // trying to find new file name
1312 for (int i = 1; i < NEW_FILENAME_ON_COPY_TRIES; i++)
1313 {
1314 newName.Format(mask, i);
1315
1316 if (!PathFileExistsW(newName))
1317 {
1318 return newName;
1319 }
1320 }
1321
1322 return CStringW();
1323 }
1324
copy_dir_to_dir(FILE_OPERATION * op,const FILE_ENTRY * feFrom,LPCWSTR szDestPath)1325 static void copy_dir_to_dir(FILE_OPERATION *op, const FILE_ENTRY *feFrom, LPCWSTR szDestPath)
1326 {
1327 WCHAR szFrom[MAX_PATH], szTo[MAX_PATH];
1328 FILE_LIST flFromNew, flToNew;
1329
1330 if (feFrom->szFilename && IsDotDir(feFrom->szFilename))
1331 return;
1332
1333 if (PathFileExistsW(szDestPath))
1334 PathCombineW(szTo, szDestPath, feFrom->szFilename);
1335 else
1336 lstrcpyW(szTo, szDestPath);
1337
1338 if (PathFileExistsW(szTo))
1339 {
1340 if (op->req->fFlags & FOF_RENAMEONCOLLISION)
1341 {
1342 CStringW newPath = try_find_new_name(szTo);
1343 if (!newPath.IsEmpty())
1344 {
1345 StringCchCopyW(szTo, _countof(szTo), newPath);
1346 }
1347 }
1348 else if (!(op->req->fFlags & FOF_NOCONFIRMATION))
1349 {
1350 if (!SHELL_ConfirmDialogW(op->req->hwnd, ASK_OVERWRITE_FOLDER, feFrom->szFilename, op))
1351 {
1352 /* Vista returns an ERROR_CANCELLED even if user pressed "No" */
1353 if (!op->bManyItems)
1354 op->bCancelled = TRUE;
1355 return;
1356 }
1357 }
1358 }
1359
1360 szTo[lstrlenW(szTo) + 1] = '\0';
1361 SHNotifyCreateDirectoryW(szTo, NULL);
1362
1363 PathCombineW(szFrom, feFrom->szFullPath, L"*.*");
1364 szFrom[lstrlenW(szFrom) + 1] = '\0';
1365
1366 ZeroMemory(&flFromNew, sizeof(FILE_LIST));
1367 ZeroMemory(&flToNew, sizeof(FILE_LIST));
1368 parse_file_list(&flFromNew, szFrom);
1369 parse_file_list(&flToNew, szTo);
1370
1371 copy_files(op, FALSE, &flFromNew, &flToNew);
1372
1373 destroy_file_list(&flFromNew);
1374 destroy_file_list(&flToNew);
1375 }
1376
copy_file_to_file(FILE_OPERATION * op,const WCHAR * szFrom,const WCHAR * szTo)1377 static BOOL copy_file_to_file(FILE_OPERATION *op, const WCHAR *szFrom, const WCHAR *szTo)
1378 {
1379 if (PathFileExistsW(szTo))
1380 {
1381 if (op->req->fFlags & FOF_RENAMEONCOLLISION)
1382 {
1383 CStringW newPath = try_find_new_name(szTo);
1384 if (!newPath.IsEmpty())
1385 {
1386 return SHNotifyCopyFileW(op, szFrom, newPath, FALSE) == 0;
1387 }
1388 }
1389 else if (!(op->req->fFlags & FOF_NOCONFIRMATION))
1390 {
1391 if (!SHELL_ConfirmDialogW(op->req->hwnd, ASK_OVERWRITE_FILE, PathFindFileNameW(szTo), op))
1392 return FALSE;
1393 }
1394 }
1395
1396 return SHNotifyCopyFileW(op, szFrom, szTo, FALSE) == 0;
1397 }
1398
1399 /* copy a file or directory to another directory */
copy_to_dir(FILE_OPERATION * op,const FILE_ENTRY * feFrom,const FILE_ENTRY * feTo)1400 static void copy_to_dir(FILE_OPERATION *op, const FILE_ENTRY *feFrom, const FILE_ENTRY *feTo)
1401 {
1402 if (!PathFileExistsW(feTo->szFullPath))
1403 SHNotifyCreateDirectoryW(feTo->szFullPath, NULL);
1404
1405 if (IsAttribFile(feFrom->attributes))
1406 {
1407 WCHAR szDestPath[MAX_PATH];
1408
1409 PathCombineW(szDestPath, feTo->szFullPath, feFrom->szFilename);
1410 copy_file_to_file(op, feFrom->szFullPath, szDestPath);
1411 }
1412 else if (!(op->req->fFlags & FOF_FILESONLY && feFrom->bFromWildcard))
1413 copy_dir_to_dir(op, feFrom, feTo->szFullPath);
1414 }
1415
create_dest_dirs(LPCWSTR szDestDir)1416 static void create_dest_dirs(LPCWSTR szDestDir)
1417 {
1418 WCHAR dir[MAX_PATH];
1419 LPCWSTR ptr = StrChrW(szDestDir, '\\');
1420
1421 /* make sure all directories up to last one are created */
1422 while (ptr && (ptr = StrChrW(ptr + 1, '\\')))
1423 {
1424 lstrcpynW(dir, szDestDir, ptr - szDestDir + 1);
1425
1426 if (!PathFileExistsW(dir))
1427 SHNotifyCreateDirectoryW(dir, NULL);
1428 }
1429
1430 /* create last directory */
1431 if (!PathFileExistsW(szDestDir))
1432 SHNotifyCreateDirectoryW(szDestDir, NULL);
1433 }
1434
1435 /* the FO_COPY operation */
copy_files(FILE_OPERATION * op,BOOL multiDest,const FILE_LIST * flFrom,FILE_LIST * flTo)1436 static HRESULT copy_files(FILE_OPERATION *op, BOOL multiDest, const FILE_LIST *flFrom, FILE_LIST *flTo)
1437 {
1438 DWORD i;
1439 const FILE_ENTRY *entryToCopy;
1440 const FILE_ENTRY *fileDest = &flTo->feFiles[0];
1441
1442 if (flFrom->bAnyDontExist)
1443 return ERROR_SHELL_INTERNAL_FILE_NOT_FOUND;
1444
1445 if (flTo->dwNumFiles == 0)
1446 {
1447 /* If the destination is empty, SHFileOperation should use the current directory */
1448 WCHAR curdir[MAX_PATH+1];
1449
1450 GetCurrentDirectoryW(MAX_PATH, curdir);
1451 curdir[lstrlenW(curdir)+1] = 0;
1452
1453 destroy_file_list(flTo);
1454 ZeroMemory(flTo, sizeof(FILE_LIST));
1455 parse_file_list(flTo, curdir);
1456 fileDest = &flTo->feFiles[0];
1457 }
1458
1459 if (multiDest)
1460 {
1461 if (flFrom->bAnyFromWildcard)
1462 return ERROR_CANCELLED;
1463
1464 if (flFrom->dwNumFiles != flTo->dwNumFiles)
1465 {
1466 if (flFrom->dwNumFiles != 1 && !IsAttribDir(fileDest->attributes))
1467 return ERROR_CANCELLED;
1468
1469 /* Free all but the first entry. */
1470 for (i = 1; i < flTo->dwNumFiles; i++)
1471 {
1472 HeapFree(GetProcessHeap(), 0, flTo->feFiles[i].szDirectory);
1473 HeapFree(GetProcessHeap(), 0, flTo->feFiles[i].szFilename);
1474 HeapFree(GetProcessHeap(), 0, flTo->feFiles[i].szFullPath);
1475 }
1476
1477 flTo->dwNumFiles = 1;
1478 }
1479 else if (IsAttribDir(fileDest->attributes))
1480 {
1481 for (i = 1; i < flTo->dwNumFiles; i++)
1482 if (!IsAttribDir(flTo->feFiles[i].attributes) ||
1483 !IsAttribDir(flFrom->feFiles[i].attributes))
1484 {
1485 return ERROR_CANCELLED;
1486 }
1487 }
1488 }
1489 else if (flFrom->dwNumFiles != 1)
1490 {
1491 if (flTo->dwNumFiles != 1 && !IsAttribDir(fileDest->attributes))
1492 return ERROR_CANCELLED;
1493
1494 if (PathFileExistsW(fileDest->szFullPath) &&
1495 IsAttribFile(fileDest->attributes))
1496 {
1497 return ERROR_CANCELLED;
1498 }
1499
1500 if (flTo->dwNumFiles == 1 && fileDest->bFromRelative &&
1501 !PathFileExistsW(fileDest->szFullPath))
1502 {
1503 return ERROR_CANCELLED;
1504 }
1505 }
1506
1507 for (i = 0; i < flFrom->dwNumFiles; i++)
1508 {
1509 entryToCopy = &flFrom->feFiles[i];
1510
1511 if ((multiDest) &&
1512 flTo->dwNumFiles > 1)
1513 {
1514 fileDest = &flTo->feFiles[i];
1515 }
1516
1517 if (IsAttribDir(entryToCopy->attributes) &&
1518 !lstrcmpiW(entryToCopy->szFullPath, fileDest->szDirectory))
1519 {
1520 return ERROR_SUCCESS;
1521 }
1522
1523 create_dest_dirs(fileDest->szDirectory);
1524
1525 if (!lstrcmpiW(entryToCopy->szFullPath, fileDest->szFullPath))
1526 {
1527 if (IsAttribFile(entryToCopy->attributes))
1528 return ERROR_NO_MORE_SEARCH_HANDLES;
1529 else
1530 return ERROR_SUCCESS;
1531 }
1532
1533 if ((flFrom->dwNumFiles > 1 && flTo->dwNumFiles == 1) ||
1534 IsAttribDir(fileDest->attributes))
1535 {
1536 copy_to_dir(op, entryToCopy, fileDest);
1537 }
1538 else if (IsAttribDir(entryToCopy->attributes))
1539 {
1540 copy_dir_to_dir(op, entryToCopy, fileDest->szFullPath);
1541 }
1542 else
1543 {
1544 if (!copy_file_to_file(op, entryToCopy->szFullPath, fileDest->szFullPath))
1545 {
1546 op->req->fAnyOperationsAborted = TRUE;
1547 return ERROR_CANCELLED;
1548 }
1549 }
1550
1551 if (op->progress != NULL)
1552 op->bCancelled |= op->progress->HasUserCancelled();
1553 /* Vista return code. XP would return e.g. ERROR_FILE_NOT_FOUND, ERROR_ALREADY_EXISTS */
1554 if (op->bCancelled)
1555 return ERROR_CANCELLED;
1556 }
1557
1558 /* Vista return code. On XP if the used pressed "No" for the last item,
1559 * ERROR_ARENA_TRASHED would be returned */
1560 return ERROR_SUCCESS;
1561 }
1562
confirm_delete_list(HWND hWnd,DWORD fFlags,BOOL fTrash,const FILE_LIST * flFrom)1563 static BOOL confirm_delete_list(HWND hWnd, DWORD fFlags, BOOL fTrash, const FILE_LIST *flFrom)
1564 {
1565 if (flFrom->dwNumFiles > 1)
1566 {
1567 WCHAR tmp[8];
1568
1569 wnsprintfW(tmp, sizeof(tmp)/sizeof(tmp[0]), L"%d", flFrom->dwNumFiles);
1570 return SHELL_ConfirmDialogW(hWnd, (fTrash?ASK_TRASH_MULTIPLE_ITEM:ASK_DELETE_MULTIPLE_ITEM), tmp, NULL);
1571 }
1572 else
1573 {
1574 const FILE_ENTRY *fileEntry = &flFrom->feFiles[0];
1575
1576 if (IsAttribFile(fileEntry->attributes))
1577 return SHELL_ConfirmDialogW(hWnd, (fTrash?ASK_TRASH_FILE:ASK_DELETE_FILE), fileEntry->szFullPath, NULL);
1578 else if (!(fFlags & FOF_FILESONLY && fileEntry->bFromWildcard))
1579 return SHELL_ConfirmDialogW(hWnd, (fTrash?ASK_TRASH_FOLDER:ASK_DELETE_FOLDER), fileEntry->szFullPath, NULL);
1580 }
1581 return TRUE;
1582 }
1583
1584 /* the FO_DELETE operation */
delete_files(FILE_OPERATION * op,const FILE_LIST * flFrom)1585 static HRESULT delete_files(FILE_OPERATION *op, const FILE_LIST *flFrom)
1586 {
1587 const FILE_ENTRY *fileEntry;
1588 DWORD i;
1589 BOOL bPathExists;
1590 BOOL bTrash;
1591
1592 if (!flFrom->dwNumFiles)
1593 return ERROR_SUCCESS;
1594
1595 /* Windows also checks only the first item */
1596 bTrash = (op->req->fFlags & FOF_ALLOWUNDO)
1597 && TRASH_CanTrashFile(flFrom->feFiles[0].szFullPath);
1598
1599 BOOL confirm = !(op->req->fFlags & FOF_NOCONFIRMATION);
1600 if (bTrash && SHELL_GetSetting(SSF_NOCONFIRMRECYCLE, fNoConfirmRecycle))
1601 confirm = FALSE;
1602 if (confirm || (!bTrash && op->req->fFlags & FOF_WANTNUKEWARNING))
1603 if (!confirm_delete_list(op->req->hwnd, op->req->fFlags, bTrash, flFrom))
1604 {
1605 op->req->fAnyOperationsAborted = TRUE;
1606 return 0;
1607 }
1608
1609 /* Check files. Do not delete one if one file does not exists */
1610 for (i = 0; i < flFrom->dwNumFiles; i++)
1611 {
1612 fileEntry = &flFrom->feFiles[i];
1613
1614 if (fileEntry->attributes == (ULONG)-1)
1615 {
1616 // This is a windows 2003 server specific value which has been removed.
1617 // Later versions of windows return ERROR_FILE_NOT_FOUND.
1618 return ERROR_SHELL_INTERNAL_FILE_NOT_FOUND;
1619 }
1620 }
1621
1622 for (i = 0; i < flFrom->dwNumFiles; i++)
1623 {
1624 bPathExists = TRUE;
1625 fileEntry = &flFrom->feFiles[i];
1626
1627 if (!IsAttribFile(fileEntry->attributes) &&
1628 (op->req->fFlags & FOF_FILESONLY && fileEntry->bFromWildcard))
1629 continue;
1630
1631 if (bTrash)
1632 {
1633 BOOL bDelete;
1634 if (TRASH_TrashFile(fileEntry->szFullPath))
1635 {
1636 SHChangeNotify(SHCNE_DELETE, SHCNF_PATHW, fileEntry->szFullPath, NULL);
1637 continue;
1638 }
1639
1640 /* Note: Windows silently deletes the file in such a situation, we show a dialog */
1641 if (!(op->req->fFlags & FOF_NOCONFIRMATION) || (op->req->fFlags & FOF_WANTNUKEWARNING))
1642 bDelete = SHELL_ConfirmDialogW(op->req->hwnd, ASK_CANT_TRASH_ITEM, fileEntry->szFullPath, NULL);
1643 else
1644 bDelete = TRUE;
1645
1646 if (!bDelete)
1647 {
1648 op->req->fAnyOperationsAborted = TRUE;
1649 break;
1650 }
1651 }
1652
1653 /* delete the file or directory */
1654 if (IsAttribFile(fileEntry->attributes))
1655 {
1656 bPathExists = (ERROR_SUCCESS == SHNotifyDeleteFileW(op, fileEntry->szFullPath));
1657 }
1658 else
1659 bPathExists = SHELL_DeleteDirectoryW(op, fileEntry->szFullPath, FALSE);
1660
1661 if (!bPathExists)
1662 {
1663 DWORD err = GetLastError();
1664
1665 if (ERROR_FILE_NOT_FOUND == err)
1666 {
1667 // This is a windows 2003 server specific value which ahs been removed.
1668 // Later versions of windows return ERROR_FILE_NOT_FOUND.
1669 return ERROR_SHELL_INTERNAL_FILE_NOT_FOUND;
1670 }
1671 else
1672 {
1673 return err;
1674 }
1675 }
1676
1677 if (op->progress != NULL)
1678 op->bCancelled |= op->progress->HasUserCancelled();
1679 /* Should fire on progress dialog only */
1680 if (op->bCancelled)
1681 return ERROR_CANCELLED;
1682 }
1683
1684 return ERROR_SUCCESS;
1685 }
1686
move_dir_to_dir(FILE_OPERATION * op,const FILE_ENTRY * feFrom,LPCWSTR szDestPath)1687 static void move_dir_to_dir(FILE_OPERATION *op, const FILE_ENTRY *feFrom, LPCWSTR szDestPath)
1688 {
1689 WCHAR szFrom[MAX_PATH], szTo[MAX_PATH];
1690 FILE_LIST flFromNew, flToNew;
1691
1692 if (feFrom->szFilename && IsDotDir(feFrom->szFilename))
1693 return;
1694
1695 SHNotifyCreateDirectoryW(szDestPath, NULL);
1696
1697 PathCombineW(szFrom, feFrom->szFullPath, L"*.*");
1698 szFrom[lstrlenW(szFrom) + 1] = '\0';
1699
1700 lstrcpyW(szTo, szDestPath);
1701 szTo[lstrlenW(szDestPath) + 1] = '\0';
1702
1703 ZeroMemory(&flFromNew, sizeof(FILE_LIST));
1704 ZeroMemory(&flToNew, sizeof(FILE_LIST));
1705 parse_file_list(&flFromNew, szFrom);
1706 parse_file_list(&flToNew, szTo);
1707
1708 move_files(op, FALSE, &flFromNew, &flToNew);
1709
1710 destroy_file_list(&flFromNew);
1711 destroy_file_list(&flToNew);
1712
1713 if (PathIsDirectoryEmptyW(feFrom->szFullPath))
1714 Win32RemoveDirectoryW(feFrom->szFullPath);
1715 }
1716
move_file_to_file(FILE_OPERATION * op,const WCHAR * szFrom,const WCHAR * szTo)1717 static BOOL move_file_to_file(FILE_OPERATION *op, const WCHAR *szFrom, const WCHAR *szTo)
1718 {
1719 if (PathFileExistsW(szTo))
1720 {
1721 if (op->req->fFlags & FOF_RENAMEONCOLLISION)
1722 {
1723 CStringW newPath = try_find_new_name(szTo);
1724 if (!newPath.IsEmpty())
1725 {
1726 return SHNotifyMoveFileW(op, szFrom, newPath, FALSE) == 0;
1727 }
1728 }
1729 else if (!(op->req->fFlags & FOF_NOCONFIRMATION))
1730 {
1731 if (!SHELL_ConfirmDialogW(op->req->hwnd, ASK_OVERWRITE_FILE, PathFindFileNameW(szTo), op))
1732 return FALSE;
1733 }
1734 }
1735
1736 return SHNotifyMoveFileW(op, szFrom, szTo, FALSE) == 0;
1737 }
1738
1739 /* moves a file or directory to another directory */
move_to_dir(FILE_OPERATION * op,const FILE_ENTRY * feFrom,const FILE_ENTRY * feTo)1740 static void move_to_dir(FILE_OPERATION *op, const FILE_ENTRY *feFrom, const FILE_ENTRY *feTo)
1741 {
1742 if (feFrom->attributes == INVALID_FILE_ATTRIBUTES)
1743 return;
1744
1745 if (!PathFileExistsW(feTo->szFullPath))
1746 SHNotifyCreateDirectoryW(feTo->szFullPath, NULL);
1747
1748 WCHAR szDestPath[MAX_PATH];
1749 PathCombineW(szDestPath, feTo->szFullPath, feFrom->szFilename);
1750
1751 if (IsAttribFile(feFrom->attributes))
1752 move_file_to_file(op, feFrom->szFullPath, szDestPath);
1753 else if (!(op->req->fFlags & FOF_FILESONLY && feFrom->bFromWildcard))
1754 move_dir_to_dir(op, feFrom, szDestPath);
1755 }
1756
1757 /* the FO_MOVE operation */
move_files(FILE_OPERATION * op,BOOL multiDest,const FILE_LIST * flFrom,const FILE_LIST * flTo)1758 static DWORD move_files(FILE_OPERATION *op, BOOL multiDest, const FILE_LIST *flFrom, const FILE_LIST *flTo)
1759 {
1760 DWORD i;
1761 INT mismatched = 0;
1762
1763 const FILE_ENTRY *entryToMove;
1764 const FILE_ENTRY *fileDest;
1765
1766 if (!flFrom->dwNumFiles)
1767 return ERROR_SUCCESS;
1768
1769 if (!flTo->dwNumFiles)
1770 return ERROR_FILE_NOT_FOUND;
1771
1772 if (flFrom->bAnyDontExist)
1773 return ERROR_SHELL_INTERNAL_FILE_NOT_FOUND;
1774
1775 if (!(multiDest) &&
1776 flTo->dwNumFiles > 1 && flFrom->dwNumFiles > 1)
1777 {
1778 return ERROR_CANCELLED;
1779 }
1780
1781 if (!(multiDest) &&
1782 !flFrom->bAnyDirectories &&
1783 flFrom->dwNumFiles > flTo->dwNumFiles &&
1784 !(flTo->bAnyDirectories && flTo->dwNumFiles == 1))
1785 {
1786 return ERROR_CANCELLED;
1787 }
1788
1789 if (!PathFileExistsW(flTo->feFiles[0].szDirectory))
1790 return ERROR_CANCELLED;
1791
1792 if (multiDest)
1793 mismatched = flFrom->dwNumFiles - flTo->dwNumFiles;
1794
1795 fileDest = &flTo->feFiles[0];
1796 for (i = 0; i < flFrom->dwNumFiles; i++)
1797 {
1798 entryToMove = &flFrom->feFiles[i];
1799
1800 if (!PathFileExistsW(fileDest->szDirectory))
1801 return ERROR_CANCELLED;
1802
1803 if (multiDest)
1804 {
1805 if (i >= flTo->dwNumFiles)
1806 break;
1807 fileDest = &flTo->feFiles[i];
1808 if (mismatched && !fileDest->bExists)
1809 {
1810 create_dest_dirs(flTo->feFiles[i].szFullPath);
1811 flTo->feFiles[i].bExists = TRUE;
1812 flTo->feFiles[i].attributes = FILE_ATTRIBUTE_DIRECTORY;
1813 }
1814 }
1815
1816 if ((flFrom->dwNumFiles > 1 && flTo->dwNumFiles == 1) ||
1817 IsAttribDir(fileDest->attributes))
1818 {
1819 move_to_dir(op, entryToMove, fileDest);
1820 }
1821 else if (IsAttribDir(entryToMove->attributes))
1822 {
1823 move_dir_to_dir(op, entryToMove, fileDest->szFullPath);
1824 }
1825 else
1826 {
1827 if (!move_file_to_file(op, entryToMove->szFullPath, fileDest->szFullPath))
1828 {
1829 op->req->fAnyOperationsAborted = TRUE;
1830 return ERROR_CANCELLED;
1831 }
1832 }
1833
1834 if (op->progress != NULL)
1835 op->bCancelled |= op->progress->HasUserCancelled();
1836 /* Should fire on progress dialog only */
1837 if (op->bCancelled)
1838 return ERROR_CANCELLED;
1839
1840 }
1841
1842 if (mismatched > 0)
1843 {
1844 if (flFrom->bAnyDirectories)
1845 return DE_DESTSAMETREE;
1846 else
1847 return DE_SAMEFILE;
1848 }
1849
1850 return ERROR_SUCCESS;
1851 }
1852
1853 /* the FO_RENAME files */
rename_files(FILE_OPERATION * op,const FILE_LIST * flFrom,const FILE_LIST * flTo)1854 static HRESULT rename_files(FILE_OPERATION *op, const FILE_LIST *flFrom, const FILE_LIST *flTo)
1855 {
1856 const FILE_ENTRY *feFrom;
1857 const FILE_ENTRY *feTo;
1858
1859 if (flFrom->dwNumFiles != 1)
1860 return ERROR_GEN_FAILURE;
1861
1862 if (flTo->dwNumFiles != 1)
1863 return ERROR_CANCELLED;
1864
1865 feFrom = &flFrom->feFiles[0];
1866 feTo= &flTo->feFiles[0];
1867
1868 /* fail if destination doesn't exist */
1869 if (!feFrom->bExists)
1870 return ERROR_SHELL_INTERNAL_FILE_NOT_FOUND;
1871
1872 /* fail if destination already exists */
1873 if (feTo->bExists)
1874 return ERROR_ALREADY_EXISTS;
1875
1876 return SHNotifyMoveFileW(op, feFrom->szFullPath, feTo->szFullPath, IsAttribDir(feFrom->attributes));
1877 }
1878
1879 /* alert the user if an unsupported flag is used */
check_flags(FILEOP_FLAGS fFlags)1880 static void check_flags(FILEOP_FLAGS fFlags)
1881 {
1882 WORD wUnsupportedFlags = FOF_NO_CONNECTED_ELEMENTS |
1883 FOF_NOCOPYSECURITYATTRIBS | FOF_NORECURSEREPARSE |
1884 FOF_WANTMAPPINGHANDLE;
1885
1886 if (fFlags & wUnsupportedFlags)
1887 FIXME("Unsupported flags: %04x\n", fFlags);
1888 }
1889
1890 #define GET_FILENAME(fe) ((fe)->szFilename[0] ? (fe)->szFilename : (fe)->szFullPath)
1891
1892 static DWORD
validate_operation(LPSHFILEOPSTRUCTW lpFileOp,FILE_LIST * flFrom,FILE_LIST * flTo)1893 validate_operation(LPSHFILEOPSTRUCTW lpFileOp, FILE_LIST *flFrom, FILE_LIST *flTo)
1894 {
1895 DWORD i, k, dwNumDest;
1896 WCHAR szFrom[MAX_PATH], szTo[MAX_PATH];
1897 CStringW strTitle, strText;
1898 const FILE_ENTRY *feFrom;
1899 const FILE_ENTRY *feTo;
1900 UINT wFunc = lpFileOp->wFunc;
1901 HWND hwnd = lpFileOp->hwnd;
1902
1903 dwNumDest = flTo->dwNumFiles;
1904
1905 if (wFunc != FO_COPY && wFunc != FO_MOVE)
1906 return ERROR_SUCCESS;
1907
1908 for (k = 0; k < dwNumDest; ++k)
1909 {
1910 feTo = &flTo->feFiles[k];
1911 for (i = 0; i < flFrom->dwNumFiles; ++i)
1912 {
1913 feFrom = &flFrom->feFiles[i];
1914 StringCbCopyW(szFrom, sizeof(szFrom), feFrom->szFullPath);
1915 StringCbCopyW(szTo, sizeof(szTo), feTo->szFullPath);
1916 if (IsAttribDir(feTo->attributes))
1917 {
1918 PathAppendW(szTo, feFrom->szFilename);
1919 }
1920
1921 // same path?
1922 if (lstrcmpiW(szFrom, szTo) == 0 &&
1923 (wFunc == FO_MOVE || !(lpFileOp->fFlags & FOF_RENAMEONCOLLISION)))
1924 {
1925 if (!(lpFileOp->fFlags & (FOF_NOERRORUI | FOF_SILENT)))
1926 {
1927 if (wFunc == FO_MOVE)
1928 {
1929 strTitle.LoadStringW(IDS_MOVEERRORTITLE);
1930 if (IsAttribDir(feFrom->attributes))
1931 strText.Format(IDS_MOVEERRORSAMEFOLDER, GET_FILENAME(feFrom));
1932 else
1933 strText.Format(IDS_MOVEERRORSAME, GET_FILENAME(feFrom));
1934 }
1935 else
1936 {
1937 strTitle.LoadStringW(IDS_COPYERRORTITLE);
1938 strText.Format(IDS_COPYERRORSAME, GET_FILENAME(feFrom));
1939 return ERROR_SUCCESS;
1940 }
1941 MessageBoxW(hwnd, strText, strTitle, MB_ICONERROR);
1942 return DE_SAMEFILE;
1943 }
1944 return DE_OPCANCELLED;
1945 }
1946
1947 // subfolder?
1948 if (IsAttribDir(feFrom->attributes))
1949 {
1950 size_t cchFrom = PathAddBackslashW(szFrom) - szFrom;
1951 size_t cchTo = PathAddBackslashW(szTo) - szTo;
1952 if (cchFrom < cchTo)
1953 {
1954 WCHAR ch = szTo[cchFrom];
1955 szTo[cchFrom] = 0;
1956 int compare = lstrcmpiW(szFrom, szTo);
1957 szTo[cchFrom] = ch;
1958
1959 if (compare == 0)
1960 {
1961 if (!(lpFileOp->fFlags & (FOF_NOERRORUI | FOF_SILENT)))
1962 {
1963 if (wFunc == FO_MOVE)
1964 {
1965 strTitle.LoadStringW(IDS_MOVEERRORTITLE);
1966 strText.Format(IDS_MOVEERRORSUBFOLDER, GET_FILENAME(feFrom));
1967 }
1968 else
1969 {
1970 strTitle.LoadStringW(IDS_COPYERRORTITLE);
1971 strText.Format(IDS_COPYERRORSUBFOLDER, GET_FILENAME(feFrom));
1972 }
1973 MessageBoxW(hwnd, strText, strTitle, MB_ICONERROR);
1974 return DE_DESTSUBTREE;
1975 }
1976 return DE_OPCANCELLED;
1977 }
1978 }
1979 }
1980 }
1981 }
1982
1983 return ERROR_SUCCESS;
1984 }
1985
1986 /*************************************************************************
1987 * SHFileOperationW [SHELL32.@]
1988 *
1989 * See SHFileOperationA
1990 */
SHFileOperationW(LPSHFILEOPSTRUCTW lpFileOp)1991 int WINAPI SHFileOperationW(LPSHFILEOPSTRUCTW lpFileOp)
1992 {
1993 FILE_OPERATION op;
1994 FILE_LIST flFrom, flTo;
1995 int ret = 0;
1996
1997 if (!lpFileOp)
1998 return ERROR_INVALID_PARAMETER;
1999
2000 ret = CoInitialize(NULL);
2001 if (FAILED(ret))
2002 return ret;
2003
2004 lpFileOp->fAnyOperationsAborted = FALSE;
2005 check_flags(lpFileOp->fFlags);
2006
2007 ZeroMemory(&flFrom, sizeof(FILE_LIST));
2008 ZeroMemory(&flTo, sizeof(FILE_LIST));
2009
2010 if ((ret = parse_file_list(&flFrom, lpFileOp->pFrom)))
2011 return ret;
2012
2013 if (lpFileOp->wFunc != FO_DELETE)
2014 parse_file_list(&flTo, lpFileOp->pTo);
2015
2016 ZeroMemory(&op, sizeof(op));
2017 op.req = lpFileOp;
2018 op.totalSize.QuadPart = 0ull;
2019 op.completedSize.QuadPart = 0ull;
2020 op.bManyItems = (flFrom.dwNumFiles > 1);
2021
2022 ret = validate_operation(lpFileOp, &flFrom, &flTo);
2023 if (ret)
2024 goto cleanup;
2025
2026 if (lpFileOp->wFunc != FO_RENAME && !(lpFileOp->fFlags & FOF_SILENT)) {
2027 ret = CoCreateInstance(CLSID_ProgressDialog,
2028 NULL,
2029 CLSCTX_INPROC_SERVER,
2030 IID_PPV_ARG(IProgressDialog, &op.progress));
2031 if (FAILED(ret))
2032 goto cleanup;
2033
2034 op.progress->StartProgressDialog(op.req->hwnd, NULL, PROGDLG_NORMAL & PROGDLG_AUTOTIME, NULL);
2035 _SetOperationTitle(&op);
2036 _FileOpCountManager(&op, &flFrom);
2037 }
2038
2039 switch (lpFileOp->wFunc)
2040 {
2041 case FO_COPY:
2042 ret = copy_files(&op, op.req->fFlags & FOF_MULTIDESTFILES, &flFrom, &flTo);
2043 break;
2044 case FO_DELETE:
2045 ret = delete_files(&op, &flFrom);
2046 break;
2047 case FO_MOVE:
2048 ret = move_files(&op, op.req->fFlags & FOF_MULTIDESTFILES, &flFrom, &flTo);
2049 break;
2050 case FO_RENAME:
2051 ret = rename_files(&op, &flFrom, &flTo);
2052 break;
2053 default:
2054 ret = ERROR_INVALID_PARAMETER;
2055 break;
2056 }
2057
2058 if (op.progress) {
2059 op.progress->StopProgressDialog();
2060 op.progress->Release();
2061 }
2062
2063 cleanup:
2064 destroy_file_list(&flFrom);
2065
2066 if (lpFileOp->wFunc != FO_DELETE)
2067 destroy_file_list(&flTo);
2068 else if (lpFileOp->fFlags & FOF_ALLOWUNDO)
2069 SHUpdateRecycleBinIcon();
2070
2071 if (ret == ERROR_CANCELLED)
2072 lpFileOp->fAnyOperationsAborted = TRUE;
2073
2074 CoUninitialize();
2075
2076 return ret;
2077 }
2078
2079 // Used by SHFreeNameMappings
_DestroyCallback(void * p,void * pData)2080 static int CALLBACK _DestroyCallback(void *p, void *pData)
2081 {
2082 LPSHNAMEMAPPINGW lp = (SHNAMEMAPPINGW *)p;
2083
2084 SHFree(lp->pszOldPath);
2085 SHFree(lp->pszNewPath);
2086
2087 return TRUE;
2088 }
2089
2090 /*************************************************************************
2091 * SHFreeNameMappings [shell32.246]
2092 *
2093 * Free the mapping handle returned by SHFileOperation if FOF_WANTSMAPPINGHANDLE
2094 * was specified.
2095 *
2096 * PARAMS
2097 * hNameMapping [I] handle to the name mappings used during renaming of files
2098 *
2099 * RETURNS
2100 * Nothing
2101 */
SHFreeNameMappings(HANDLE hNameMapping)2102 void WINAPI SHFreeNameMappings(HANDLE hNameMapping)
2103 {
2104 if (hNameMapping)
2105 {
2106 DSA_DestroyCallback((HDSA) hNameMapping, _DestroyCallback, NULL);
2107 }
2108 }
2109
2110 /*************************************************************************
2111 * SheGetDirA [SHELL32.@]
2112 *
2113 * drive = 0: returns the current directory path
2114 * drive > 0: returns the current directory path of the specified drive
2115 * drive=1 -> A: drive=2 -> B: ...
2116 * returns 0 if successful
2117 */
SheGetDirA(DWORD drive,LPSTR buffer)2118 EXTERN_C DWORD WINAPI SheGetDirA(DWORD drive, LPSTR buffer)
2119 {
2120 WCHAR org_path[MAX_PATH];
2121 DWORD ret;
2122 char drv_path[3];
2123
2124 /* change current directory to the specified drive */
2125 if (drive) {
2126 strcpy(drv_path, "A:");
2127 drv_path[0] += (char)drive-1;
2128
2129 GetCurrentDirectoryW(MAX_PATH, org_path);
2130
2131 SetCurrentDirectoryA(drv_path);
2132 }
2133
2134 /* query current directory path of the specified drive */
2135 ret = GetCurrentDirectoryA(MAX_PATH, buffer);
2136
2137 /* back to the original drive */
2138 if (drive)
2139 SetCurrentDirectoryW(org_path);
2140
2141 if (!ret)
2142 return GetLastError();
2143
2144 return 0;
2145 }
2146
2147 /*************************************************************************
2148 * SheGetDirW [SHELL32.@]
2149 *
2150 * drive = 0: returns the current directory path
2151 * drive > 0: returns the current directory path of the specified drive
2152 * drive=1 -> A: drive=2 -> B: ...
2153 * returns 0 if successful
2154 */
SheGetDirW(DWORD drive,LPWSTR buffer)2155 EXTERN_C DWORD WINAPI SheGetDirW(DWORD drive, LPWSTR buffer)
2156 {
2157 WCHAR org_path[MAX_PATH];
2158 DWORD ret;
2159 char drv_path[3];
2160
2161 /* change current directory to the specified drive */
2162 if (drive)
2163 {
2164 strcpy(drv_path, "A:");
2165 drv_path[0] += (char)drive-1;
2166
2167 GetCurrentDirectoryW(MAX_PATH, org_path);
2168
2169 SetCurrentDirectoryA(drv_path);
2170 }
2171
2172 /* query current directory path of the specified drive */
2173 ret = GetCurrentDirectoryW(MAX_PATH, buffer);
2174
2175 /* back to the original drive */
2176 if (drive)
2177 SetCurrentDirectoryW(org_path);
2178
2179 if (!ret)
2180 return GetLastError();
2181
2182 return 0;
2183 }
2184
2185 /*************************************************************************
2186 * SheChangeDirA [SHELL32.@]
2187 *
2188 * changes the current directory to the specified path
2189 * and returns 0 if successful
2190 */
SheChangeDirA(LPSTR path)2191 EXTERN_C DWORD WINAPI SheChangeDirA(LPSTR path)
2192 {
2193 if (SetCurrentDirectoryA(path))
2194 return 0;
2195 else
2196 return GetLastError();
2197 }
2198
2199 /*************************************************************************
2200 * SheChangeDirW [SHELL32.@]
2201 *
2202 * changes the current directory to the specified path
2203 * and returns 0 if successful
2204 */
SheChangeDirW(LPWSTR path)2205 EXTERN_C DWORD WINAPI SheChangeDirW(LPWSTR path)
2206 {
2207 if (SetCurrentDirectoryW(path))
2208 return 0;
2209 else
2210 return GetLastError();
2211 }
2212
2213 /*************************************************************************
2214 * IsNetDrive [SHELL32.66]
2215 */
IsNetDrive(int drive)2216 EXTERN_C int WINAPI IsNetDrive(int drive)
2217 {
2218 char root[4];
2219 strcpy(root, "A:\\");
2220 root[0] += (char)drive;
2221 return (GetDriveTypeA(root) == DRIVE_REMOTE);
2222 }
2223
2224
2225 /*************************************************************************
2226 * RealDriveType [SHELL32.524]
2227 */
RealDriveType(INT drive,BOOL bQueryNet)2228 EXTERN_C INT WINAPI RealDriveType(INT drive, BOOL bQueryNet)
2229 {
2230 char root[] = "A:\\";
2231 root[0] += (char)drive;
2232 return GetDriveTypeA(root);
2233 }
2234
2235 /***********************************************************************
2236 * SHPathPrepareForWriteW (SHELL32.@)
2237 */
SHPathPrepareForWriteW(HWND hwnd,IUnknown * modless,LPCWSTR path,DWORD flags)2238 EXTERN_C HRESULT WINAPI SHPathPrepareForWriteW(HWND hwnd, IUnknown *modless, LPCWSTR path, DWORD flags)
2239 {
2240 DWORD res;
2241 DWORD err;
2242 LPCWSTR realpath;
2243 int len;
2244 WCHAR* last_slash;
2245 WCHAR* temppath=NULL;
2246
2247 TRACE("%p %p %s 0x%08x\n", hwnd, modless, debugstr_w(path), flags);
2248
2249 if (flags & ~(SHPPFW_DIRCREATE|SHPPFW_ASKDIRCREATE|SHPPFW_IGNOREFILENAME))
2250 FIXME("unimplemented flags 0x%08x\n", flags);
2251
2252 /* cut off filename if necessary */
2253 if (flags & SHPPFW_IGNOREFILENAME)
2254 {
2255 last_slash = StrRChrW(path, NULL, '\\');
2256 if (last_slash == NULL)
2257 len = 1;
2258 else
2259 len = last_slash - path + 1;
2260 temppath = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
2261 if (!temppath)
2262 return E_OUTOFMEMORY;
2263 StrCpyNW(temppath, path, len);
2264 realpath = temppath;
2265 }
2266 else
2267 {
2268 realpath = path;
2269 }
2270
2271 /* try to create the directory if asked to */
2272 if (flags & (SHPPFW_DIRCREATE|SHPPFW_ASKDIRCREATE))
2273 {
2274 if (flags & SHPPFW_ASKDIRCREATE)
2275 FIXME("treating SHPPFW_ASKDIRCREATE as SHPPFW_DIRCREATE\n");
2276
2277 SHCreateDirectoryExW(0, realpath, NULL);
2278 }
2279
2280 /* check if we can access the directory */
2281 res = GetFileAttributesW(realpath);
2282
2283 HeapFree(GetProcessHeap(), 0, temppath);
2284
2285 if (res == INVALID_FILE_ATTRIBUTES)
2286 {
2287 err = GetLastError();
2288 if (err == ERROR_FILE_NOT_FOUND)
2289 return HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND);
2290 return HRESULT_FROM_WIN32(err);
2291 }
2292 else if (res & FILE_ATTRIBUTE_DIRECTORY)
2293 return S_OK;
2294 else
2295 return HRESULT_FROM_WIN32(ERROR_DIRECTORY);
2296 }
2297
2298 /***********************************************************************
2299 * SHPathPrepareForWriteA (SHELL32.@)
2300 */
SHPathPrepareForWriteA(HWND hwnd,IUnknown * modless,LPCSTR path,DWORD flags)2301 EXTERN_C HRESULT WINAPI SHPathPrepareForWriteA(HWND hwnd, IUnknown *modless, LPCSTR path, DWORD flags)
2302 {
2303 WCHAR wpath[MAX_PATH];
2304 MultiByteToWideChar( CP_ACP, 0, path, -1, wpath, MAX_PATH);
2305 return SHPathPrepareForWriteW(hwnd, modless, wpath, flags);
2306 }
2307
2308
2309 /*
2310 * The two following background operations were modified from filedefext.cpp
2311 * They use an inordinate amount of mutable state across the string functions,
2312 * so are not easy to follow and care is required when modifying.
2313 */
2314
2315 DWORD WINAPI
_FileOpCountManager(FILE_OPERATION * op,const FILE_LIST * from)2316 _FileOpCountManager(FILE_OPERATION *op, const FILE_LIST *from)
2317 {
2318 DWORD ticks = GetTickCount();
2319 FILE_ENTRY *entryToCount;
2320
2321 for (UINT i = 0; i < from->dwNumFiles; i++)
2322 {
2323 entryToCount = &from->feFiles[i];
2324
2325 WCHAR theFileName[MAX_PATH];
2326 StringCchCopyW(theFileName, MAX_PATH, entryToCount->szFullPath);
2327 _FileOpCount(op, theFileName, IsAttribDir(entryToCount->attributes), &ticks);
2328 }
2329 return 0;
2330 }
2331
2332 // All path manipulations, even when this function is nested, occur on the one buffer.
2333 static BOOL
_FileOpCount(FILE_OPERATION * op,LPWSTR pwszBuf,BOOL bFolder,DWORD * ticks)2334 _FileOpCount(FILE_OPERATION *op, LPWSTR pwszBuf, BOOL bFolder, DWORD *ticks)
2335 {
2336 /* Find filename position */
2337 UINT cchBuf = wcslen(pwszBuf);
2338 WCHAR *pwszFilename = pwszBuf + cchBuf;
2339 size_t cchFilenameMax = MAX_PATH - cchBuf;
2340 if (!cchFilenameMax)
2341 return FALSE;
2342
2343 if (bFolder) {
2344 *(pwszFilename++) = '\\';
2345 --cchFilenameMax;
2346 /* Find all files, FIXME: shouldn't be "*"? */
2347 StringCchCopyW(pwszFilename, cchFilenameMax, L"*");
2348 }
2349
2350 WIN32_FIND_DATAW wfd;
2351 HANDLE hFind = FindFirstFileW(pwszBuf, &wfd);
2352 if (hFind == INVALID_HANDLE_VALUE)
2353 {
2354 ERR("FindFirstFileW %ls failed\n", pwszBuf);
2355 return FALSE;
2356 }
2357
2358 do
2359 {
2360 if (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
2361 {
2362 /* Don't process "." and ".." items */
2363 if (!wcscmp(wfd.cFileName, L".") || !wcscmp(wfd.cFileName, L".."))
2364 continue;
2365
2366 StringCchCopyW(pwszFilename, cchFilenameMax, wfd.cFileName);
2367 _FileOpCount(op, pwszBuf, TRUE, ticks);
2368 }
2369 else
2370 {
2371 ULARGE_INTEGER FileSize;
2372 FileSize.u.LowPart = wfd.nFileSizeLow;
2373 FileSize.u.HighPart = wfd.nFileSizeHigh;
2374 op->totalSize.QuadPart += FileSize.QuadPart;
2375 }
2376 if (GetTickCount() - *ticks > (DWORD) 500)
2377 {
2378 // Check if the dialog has ended. If it has, we'll spin down.
2379 if (op->progress != NULL)
2380 op->bCancelled = op->progress->HasUserCancelled();
2381
2382 if (op->bCancelled)
2383 break;
2384 *ticks = GetTickCount();
2385 }
2386 } while(FindNextFileW(hFind, &wfd));
2387
2388 FindClose(hFind);
2389 return TRUE;
2390 }
2391