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