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