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