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