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