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