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