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