├── LICENSE ├── README.md ├── byenow.cpp ├── delete_file.cpp ├── delete_file.h ├── folder.cpp ├── folder.h ├── ultra_machine.cpp ├── ultra_machine.h ├── ultra_machine_internals.h ├── utils.cpp ├── utils.h └── wmain.cpp /LICENSE: -------------------------------------------------------------------------------- 1 | +-------------------------------------------------------------+ 2 | | | 3 | | Byenow, a faster folder deleter | 4 | | https://iobureau.com/byenow | 5 | | | 6 | +-------------------------------------------------------------+ 7 | 8 | LICENSE 9 | 10 | The 2-clause BSD license with the Commons Clause condition. 11 | 12 | IN SHORT 13 | 14 | You can use, change and distribute the software free of charge 15 | provided that you do not sell it or make money off it in 16 | certain less direct ways as specified below. When distributed, 17 | the software must include a complete copy of this license. 18 | 19 | IN FULL 20 | 21 | The Software is provided to you by the Licensor under the 22 | License, as defined below, subject to the following condition. 23 | 24 | Without limiting other conditions in the License, the grant of 25 | rights under the License will not include, and the License does 26 | not grant to you, the right to Sell the Software. 27 | 28 | For purposes of the foregoing, "Sell" means practicing any or 29 | all of the rights granted to you under the License to provide 30 | to third parties, for a fee or other consideration (including 31 | without limitation fees for hosting or consulting/ support 32 | services related to the Software), a product or service whose 33 | value derives, entirely or substantially, from the functionality 34 | of the Software. Any license notice or attribution required 35 | by the License must also include this Commons Clause License 36 | Condition notice. 37 | 38 | Software: Byenow 39 | License: The 2-clause BSD License 40 | Licensor: Alexander Pankratov / IO Bureau SA, Switzerland 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # byenow 2 | 3 | This is the source code of `byenow`, a multithreaded folder removal utility for Windows. 4 | 5 | https://iobureau.com/byenow 6 | 7 | The code is shared to allow anyone interested to see how exactly the program works. The source is **incomplete**, because it depends on several libraries that are not a part of this distribution. However, with an exception of `simple_work_queue`, these libraries are simple Win32 API wrappers and their functionality should be fairly obvious from their usage. 8 | 9 | To read through the code, start with `wmain_app()` in `byenow.cpp` and go from there. 10 | -------------------------------------------------------------------------------- /byenow.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is a part of the source code of "byenow" program. 3 | * 4 | * Copyright (c) 2020- Alexander Pankratov and IO Bureau SA. 5 | * All rights reserved. 6 | * 7 | * The source code is distributed under the terms of 2-clause 8 | * BSD license with the Commons Clause condition. See LICENSE 9 | * file for details. 10 | */ 11 | #include "libp/types.h" 12 | #include "libp/string_utils.h" 13 | #include "libp/enforce.h" 14 | #include "libp/atomic.h" 15 | #include "libp/_windows.h" 16 | #include "libp/_elpify.h" 17 | #include "libp/_console.h" 18 | #include "libp/_filesys.h" 19 | #include "libp/_system_api.h" 20 | #include "libp/time.h" 21 | 22 | #include "ultra_machine.h" 23 | #include "delete_file.h" 24 | #include "utils.h" 25 | 26 | // 27 | #define HEADER "Faster folder deleter, ver 0.12, freeware, https://iobureau.com/byenow\n" 28 | #define SYNTAX "Syntax: byenow.exe [options] \n" \ 29 | "\n" \ 30 | " Deletes a folder. Similar to 'rmdir /s ...', but multi-threaded.\n" \ 31 | "\n" \ 32 | " -p --preview enumerate contents, but don\'t delete anything\n" \ 33 | " -s --staged enumerate contents first, then delete them\n" \ 34 | "\n" \ 35 | " -1 --one-liner show progress as a single line\n" \ 36 | " -b --show-bytes show total/deleted byte counts\n" \ 37 | " -e --list-errors list all errors upon completion\n" \ 38 | " -y --yes don't ask to confirm the deletion\n" \ 39 | " -x --yolo don't block deletion in restricted paths\n" \ 40 | "\n" \ 41 | " -o --omni-delete allow to point at a file\n" \ 42 | " -k --keep-folder don't delete the folder itself, just its contents\n" \ 43 | "\n" \ 44 | " -t --threads use specified number of threads\n" \ 45 | " -n --delete-ntapi use NtDeleteFile to remove files\n" \ 46 | "\n" \ 47 | " * By default the thread count is set to the number of CPU cores.\n" \ 48 | " For local folders it doesn't make sense to go above that, but\n" \ 49 | " for folders on network shares raising the thread count may be\n" \ 50 | " a good thing to try, especially for high-latency connections.\n" 51 | 52 | // 53 | enum EXIT_CODES 54 | { 55 | RC_ok = 0, 56 | RC_cancelled = 1, 57 | RC_whoops_seh = 2, // see wmain_seh() 58 | RC_whoops_cpp = 3, // see wmain() 59 | RC_unlikely = 4, 60 | RC_ok_with_errors = 10, // ... + log10(error_count) 61 | 62 | // init errors 63 | RC_no_path = 50, 64 | RC_invalid_arg = 51, 65 | RC_not_confirmed = 52, 66 | 67 | // path errors 68 | RC_path_not_found = 60, 69 | RC_path_is_file = 61, 70 | RC_path_is_root = 62, 71 | RC_path_restricted = 63, 72 | RC_path_cant_expand = 64, 73 | RC_path_cant_check = 65, 74 | }; 75 | 76 | // 77 | struct context : ultra_mach_cb 78 | { 79 | // config 80 | 81 | wstring path; 82 | string path_utf8; 83 | 84 | bool preview; // scan only 85 | bool staged; // scan, then delete 86 | bool confirm; // confirm delete 87 | bool yolo; // don't block deletion in c:\windows and c:\users 88 | bool omni; // path can point at a file 89 | 90 | ultra_mach_conf mach_conf; 91 | bool cryptic; 92 | bool show_bytes; 93 | bool list_errors; 94 | 95 | // state 96 | 97 | bool interactive; 98 | bool enough; 99 | 100 | folder root; 101 | dword path_attrs; 102 | bool is_a_file; 103 | api_error_vec scanner_err; 104 | api_error_vec deleter_err; 105 | usec_t started; 106 | usec_t finished; 107 | usec_t reported; 108 | 109 | uint mode; // 0x01 - scanning, 0x02 - deleting 110 | ultra_mach_info info; 111 | 112 | uint exit_rc; 113 | 114 | // 115 | static context * self; 116 | 117 | // 118 | context(); 119 | 120 | void init(); 121 | void parse_args(int argc, wchar_t ** argv); 122 | void parse_uint(int argc, wchar_t ** argv, size_t & next, size_t & val); 123 | 124 | void syntax(int rc); 125 | void abort(int rc, const char * format, ...); 126 | void confirm_it(); 127 | 128 | // 129 | bool on_ultra_mach_tick(const ultra_mach_info & info); // ultra_mach_cb 130 | void init_progress(); 131 | void update_progress(); 132 | 133 | void print_verbose_stats(bool scan); 134 | void print_cryptic_stats(); 135 | 136 | void check_path(); 137 | void process(); 138 | void delete_file(); 139 | 140 | void report(); 141 | void report_errors(); 142 | 143 | // 144 | static BOOL __stdcall on_console_event_proxy(dword type); 145 | BOOL on_console_event(dword type); 146 | }; 147 | 148 | // 149 | context * context::self = NULL; 150 | 151 | // 152 | context::context() 153 | { 154 | preview = false; 155 | staged = false; 156 | confirm = true; 157 | yolo = false; 158 | omni = false; 159 | 160 | cryptic = false; 161 | show_bytes = false; 162 | list_errors = false; 163 | 164 | interactive = false; 165 | enough = false; 166 | path_attrs = 0; 167 | is_a_file = false; 168 | 169 | started.raw = 0; 170 | finished.raw = 0; 171 | reported = usec(); 172 | 173 | mode = 0x00; 174 | 175 | exit_rc = RC_ok; 176 | } 177 | 178 | void context::init() 179 | { 180 | context::self = this; 181 | 182 | init_ext_system_api(NULL); 183 | 184 | if (! ntdll.NtQueryDirectoryFile || 185 | ! ntdll.NtDeleteFile || 186 | ! ntdll.RtlInitUnicodeString || 187 | ! ntdll.RtlDosPathNameToNtPathName_U_WithStatus || 188 | ! ntdll.RtlFreeUnicodeString) 189 | { 190 | abort(RC_unlikely, "Failed to locate required NT API entry points.\n"); 191 | } 192 | 193 | interactive = ! is_interactive_console(); 194 | 195 | if (interactive) 196 | { 197 | SetConsoleCtrlHandler(on_console_event_proxy, TRUE); 198 | show_console_cursor(false); 199 | } 200 | 201 | SetConsoleOutputCP(CP_UTF8); 202 | } 203 | 204 | void context::parse_args(int argc, wchar_t ** argv) 205 | { 206 | for (size_t i=1; i < (size_t)argc; i++) 207 | { 208 | const wchar_t * arg = argv[i]; 209 | 210 | if (! wcscmp(arg, L"-p") || ! wcscmp(arg, L"--preview")) 211 | { 212 | preview = true; 213 | continue; 214 | } 215 | 216 | if (! wcscmp(arg, L"-s") || ! wcscmp(arg, L"--staged")) 217 | { 218 | staged = true; 219 | continue; 220 | } 221 | 222 | if (! wcscmp(arg, L"-y") || ! wcscmp(arg, L"--yes")) 223 | { 224 | confirm = false; 225 | continue; 226 | } 227 | 228 | if (! wcscmp(arg, L"-x") || ! wcscmp(arg, L"--yolo")) 229 | { 230 | yolo = true; 231 | continue; 232 | } 233 | 234 | if (! wcscmp(arg, L"-o") || ! wcscmp(arg, L"--omni-delete")) 235 | { 236 | omni = true; 237 | continue; 238 | } 239 | 240 | if (! wcscmp(arg, L"-k") || ! wcscmp(arg, L"--keep-folder")) 241 | { 242 | mach_conf.keep_root = true; 243 | continue; 244 | } 245 | 246 | if (! wcscmp(arg, L"-1") || ! wcscmp(arg, L"--one-liner")) 247 | { 248 | cryptic = true; 249 | continue; 250 | } 251 | 252 | if (! wcscmp(arg, L"-b") || ! wcscmp(arg, L"--show-bytes")) 253 | { 254 | show_bytes = true; 255 | continue; 256 | } 257 | 258 | if (! wcscmp(arg, L"-e") || ! wcscmp(arg, L"--list-errors")) 259 | { 260 | list_errors = true; 261 | continue; 262 | } 263 | 264 | if (! wcscmp(arg, L"-t") || ! wcscmp(arg, L"--threads")) 265 | { 266 | parse_uint(argc, argv, i, mach_conf.threads); 267 | continue; 268 | } 269 | 270 | if (! wcscmp(arg, L"--scan-buf-kb")) 271 | { 272 | parse_uint(argc, argv, i, mach_conf.scanner_buf_size); 273 | 274 | if (mach_conf.scanner_buf_size > 64*1024) 275 | abort(RC_invalid_arg, "Maximum supported scan buffer size is 64MB."); 276 | 277 | mach_conf.scanner_buf_size *= 1024; 278 | continue; 279 | } 280 | 281 | if (! wcscmp(arg, L"-n") || ! wcscmp(arg, L"--delete-ntapi")) 282 | { 283 | mach_conf.deleter_ntapi = true; 284 | continue; 285 | } 286 | 287 | if (! wcscmp(arg, L"--delete-batch")) 288 | { 289 | parse_uint(argc, argv, i, mach_conf.deleter_batch); 290 | continue; 291 | } 292 | 293 | if (arg[0] == L'-' || arg[0] == L'/') 294 | syntax(RC_invalid_arg); 295 | 296 | // otherwise it's a path 297 | 298 | if (path.size()) 299 | syntax(RC_invalid_arg); 300 | 301 | path = arg; 302 | } 303 | 304 | if (path.empty()) 305 | syntax(RC_no_path); 306 | 307 | if (path.size() == 2 && path[1] == L':' || 308 | path.size() == 3 && path[1] == L':' && path[2] == L'\\') 309 | { 310 | abort(RC_path_is_root, "Root of a drive is not supported as a target."); 311 | } 312 | 313 | if (path.back() == L'\\') 314 | path.pop_back(); 315 | 316 | // 317 | path_utf8 = to_utf8(path); 318 | 319 | // 320 | if (_wcsnicmp(path.c_str(), L"C:\\Windows", 10) == 0 || 321 | _wcsnicmp(path.c_str(), L"C:\\Users", 8) == 0) 322 | { 323 | if (! yolo) 324 | abort(RC_path_restricted, "Restricted path - %s\n", path_utf8.c_str()); 325 | } 326 | } 327 | 328 | void context::parse_uint(int argc, wchar_t ** argv, size_t & i, size_t & val) 329 | { 330 | if (++i == argc) 331 | syntax(RC_invalid_arg); 332 | 333 | if (! swscanf(argv[i], L"%zu", &val)) 334 | syntax(RC_invalid_arg); 335 | } 336 | 337 | // 338 | void context::syntax(int rc) 339 | { 340 | printf(HEADER); 341 | printf(SYNTAX); 342 | exit(rc); 343 | } 344 | 345 | void context::abort(int rc, const char * format, ...) 346 | { 347 | va_list m; 348 | printf(HEADER); 349 | va_start(m, format); 350 | vprintf(format, m); 351 | va_end(m); 352 | exit(rc); 353 | } 354 | 355 | void context::confirm_it() 356 | { 357 | static const char * yes[] = { "y", "yes", "yep", "yup" }; 358 | char line[32]; 359 | 360 | if (preview || ! confirm) 361 | return; 362 | 363 | if (! is_a_file) printf("Remove [%s] and all its contents? ", path_utf8.c_str()); 364 | else printf("Delete [%s] file? ", path_utf8.c_str()); 365 | 366 | fflush(stdout); 367 | 368 | if (! gets_s(line, sizeof line)) 369 | exit(RC_not_confirmed); 370 | 371 | for (auto & str : yes) 372 | if (! strcmp(line, str)) 373 | return; 374 | 375 | exit(RC_not_confirmed); 376 | } 377 | 378 | // 379 | BOOL __stdcall context::on_console_event_proxy(dword type) 380 | { 381 | __enforce(context::self); 382 | return context::self->on_console_event(type); 383 | } 384 | 385 | BOOL context::on_console_event(dword type) 386 | { 387 | if (type == CTRL_C_EVENT) 388 | { 389 | printf("Ctrl-C\n"); 390 | enough = true; 391 | return TRUE; 392 | } 393 | 394 | if (type == CTRL_CLOSE_EVENT) 395 | return TRUE; // terminate 396 | 397 | // if (type == CTRL_BREAK_EVENT) 398 | // // dump stats, return FALSE 399 | 400 | // CTRL_LOGOFF_EVENT, CTRL_SHUTDOWN_EVENT 401 | return FALSE; // terminate 402 | } 403 | 404 | /* 405 | * 406 | */ 407 | bool context::on_ultra_mach_tick(const ultra_mach_info & _info) 408 | { 409 | if (enough) 410 | return false; 411 | 412 | if (mode == 0x01 || // scan 413 | mode == 0x03) // scan & delete 414 | { 415 | info = _info; 416 | } 417 | else 418 | if (mode == 0x02) // delete-after-scan 419 | { 420 | info.f_deleted = _info.f_deleted; 421 | info.d_deleted = _info.d_deleted; 422 | info.done = _info.done; 423 | } 424 | else 425 | { 426 | __enforce(false); 427 | } 428 | 429 | if (_info.scanner_err) append(scanner_err, *_info.scanner_err); 430 | if (_info.deleter_err) append(deleter_err, *_info.deleter_err); 431 | 432 | if (interactive) 433 | update_progress(); 434 | 435 | return true; 436 | } 437 | 438 | void context::init_progress() 439 | { 440 | if (! cryptic) 441 | { 442 | printf("%s [%s] %s\n", preview ? "Scanning" : "Deleting", path_utf8.c_str(), (staged && ! preview) ? "[staged]" : ""); 443 | printf("\n"); 444 | if (show_bytes) printf(" %10s %10s %10s %10s\n", "Folders", "Files", "Bytes", "Errors"); 445 | else printf(" %10s %10s %10s\n", "Folders", "Files", "Errors"); 446 | } 447 | 448 | if (! interactive) 449 | return; 450 | 451 | if (! cryptic) 452 | { 453 | if (show_bytes) 454 | { 455 | printf(" Found %10s %10s %10s %10s\n", "-", "-", "-", "-"); 456 | printf(" Deleted %10s %10s %10s %10s\n", "-", "-", "-", "-"); 457 | } 458 | else 459 | { 460 | printf(" Found %10s %10s %10s\n", "-", "-", "-"); 461 | printf(" Deleted %10s %10s %10s\n", "-", "-", "-"); 462 | } 463 | } 464 | else 465 | { 466 | printf("\n"); 467 | } 468 | } 469 | 470 | void context::update_progress() 471 | { 472 | usec_t now = usec(); 473 | 474 | if (now - reported < 100*1000 && ! info.done) 475 | return; 476 | 477 | if (cryptic) 478 | { 479 | move_console_cursor(0, false, -1, true); 480 | 481 | print_cryptic_stats(); 482 | wipe_console_line(); 483 | printf("\n"); 484 | } 485 | else 486 | { 487 | move_console_cursor(0, false, -2, true); 488 | 489 | print_verbose_stats(true); 490 | wipe_console_line(); 491 | printf("\n"); 492 | 493 | if (! preview) 494 | { 495 | print_verbose_stats(false); 496 | wipe_console_line(); 497 | } 498 | 499 | printf("\n"); 500 | } 501 | 502 | reported = now; 503 | } 504 | 505 | void context::print_verbose_stats(bool scan) 506 | { 507 | const char * label = scan ? " Found " : " Deleted"; 508 | const size_t & d = scan ? info.d_found : info.d_deleted; 509 | const size_t & f = scan ? info.f_found : info.f_deleted; 510 | const size_t & b = scan ? info.b_found : info.b_deleted; 511 | const size_t e = scan ? scanner_err.size() : deleter_err.size(); 512 | 513 | if (show_bytes) printf("%s %10zu %10zu %10s %10zu", label, d, f, format_bytes(b).c_str(), e); 514 | else printf("%s %10zu %10zu %10zu", label, d, f, e); 515 | 516 | if (scan && info.folders_togo) printf(" [%zu to go]", info.folders_togo); 517 | } 518 | 519 | void context::print_cryptic_stats() 520 | { 521 | if (show_bytes) 522 | printf("%zu / %zu folders, %zu / %zu files, %s / %s, %zu / %zu errors", 523 | info.d_found, info.d_deleted, 524 | info.f_found, info.f_deleted, 525 | format_bytes(info.b_found).c_str(), 526 | format_bytes(info.b_deleted).c_str(), 527 | scanner_err.size(), deleter_err.size()); 528 | else 529 | printf("%zu / %zu folders, %zu / %zu files, %zu / %zu errors", 530 | info.d_found, info.d_deleted, 531 | info.f_found, info.f_deleted, 532 | scanner_err.size(), deleter_err.size()); 533 | 534 | if (info.folders_togo) printf(" - %zu to go", info.folders_togo); 535 | } 536 | 537 | // 538 | void context::check_path() 539 | { 540 | wstring full; 541 | 542 | if (! get_full_pathname(path, full)) 543 | { 544 | printf("Error: failed to get full path name for [%s].\n", path_utf8.c_str()); 545 | exit(RC_path_cant_expand); 546 | } 547 | 548 | path = full; 549 | 550 | // 551 | path_attrs = elp->GetFileAttributes(path.c_str()); 552 | 553 | if (path_attrs == -1) 554 | { 555 | api_error e; 556 | 557 | e.code = GetLastError(); 558 | if (__not_found(e.code)) 559 | { 560 | printf("Error: specified path not found - [%s].\n", path_utf8.c_str()); 561 | exit(RC_path_not_found); 562 | } 563 | 564 | e.func = "GetFileAttributes"; 565 | printf("Error: %s\n", error_to_str(e).c_str()); 566 | printf("Path: [%s]\n", path_utf8.c_str()); 567 | exit(RC_path_cant_check); 568 | } 569 | 570 | is_a_file = ! (path_attrs & FILE_ATTRIBUTE_DIRECTORY); 571 | 572 | if (is_a_file && ! omni) 573 | { 574 | printf("Error: specified path points at a file - [%s]\n", path_utf8.c_str()); 575 | exit(RC_path_is_file); 576 | } 577 | } 578 | 579 | void context::process() 580 | { 581 | folder root; 582 | 583 | started = usec(); 584 | root.self.name = path; 585 | root.self.info.attrs = path_attrs; 586 | 587 | init_progress(); 588 | 589 | if (is_a_file) 590 | { 591 | delete_file(); 592 | } 593 | else 594 | if (preview) 595 | { 596 | mode = 0x01; 597 | 598 | if (! ultra_mach_scan(root, mach_conf, this)) 599 | exit(enough ? RC_unlikely : RC_cancelled); 600 | } 601 | else 602 | if (staged) 603 | { 604 | mode = 0x01; 605 | if (! ultra_mach_scan(root, mach_conf, this)) 606 | exit(enough ? RC_unlikely : RC_cancelled); 607 | 608 | mode = 0x02; 609 | if (! ultra_mach_delete(root, true, mach_conf, this)) // prescanned 610 | exit(enough ? RC_unlikely : RC_cancelled); 611 | } 612 | else 613 | { 614 | mode = 0x03; 615 | if (! ultra_mach_delete(root, false, mach_conf, this)) // scan & delete 616 | exit(enough ? RC_unlikely : RC_cancelled); 617 | } 618 | 619 | finished = usec(); 620 | } 621 | 622 | void context::delete_file() 623 | { 624 | api_error_trace err; 625 | WIN32_FIND_DATA data; 626 | ultra_mach_info temp; 627 | 628 | mode = preview ? 0x01 : 0x03; 629 | 630 | temp.f_found = 1; 631 | 632 | if (! get_file_info(path, data, &err)) 633 | { 634 | temp.scanner_err = &err.all; 635 | goto out; 636 | } 637 | 638 | info.b_found = data.nFileSizeHigh; 639 | info.b_found <<= 32; 640 | info.b_found += data.nFileSizeLow; 641 | on_ultra_mach_tick(temp); 642 | 643 | if (preview) 644 | return; 645 | 646 | if (! ::delete_file(path, data.dwFileAttributes, mach_conf.deleter_ntapi, &err)) 647 | { 648 | temp.deleter_err = &err.all; 649 | goto out; 650 | } 651 | 652 | info.f_deleted = 1; 653 | info.b_deleted += info.b_found; 654 | 655 | out: 656 | on_ultra_mach_tick(temp); 657 | } 658 | 659 | void context::report() 660 | { 661 | string elapsed = format_usecs(finished - started); 662 | size_t err_count = scanner_err.size() + deleter_err.size(); 663 | 664 | if (interactive) 665 | { 666 | if (cryptic) 667 | { 668 | move_console_cursor(0, false, -1, true); 669 | print_cryptic_stats(); 670 | wipe_console_line(); 671 | printf(" - done in %s\n", elapsed.c_str()); 672 | } 673 | else 674 | { 675 | printf("\n"); 676 | if (err_count && ! list_errors) 677 | printf("Completed in %s. To list errors use '--list-errors'.\n", elapsed.c_str()); 678 | else 679 | printf("Completed in %s\n", elapsed.c_str()); 680 | } 681 | } 682 | else 683 | { 684 | if (cryptic) 685 | { 686 | print_cryptic_stats(); 687 | printf(" - done in %s\n", elapsed.c_str()); 688 | } 689 | else 690 | { 691 | print_verbose_stats(true); 692 | print_verbose_stats(false); 693 | printf("\n"); 694 | printf("Completed in %s\n", elapsed.c_str()); 695 | } 696 | } 697 | 698 | if (err_count) 699 | { 700 | if (list_errors) 701 | report_errors(); 702 | 703 | for (exit_rc = RC_ok_with_errors; err_count >= 10; err_count /= 10) 704 | exit_rc++; 705 | } 706 | } 707 | 708 | // 709 | bool operator < (const api_error & a, const api_error & b) 710 | { 711 | if (a.code != b.code) 712 | return a.code < b.code; 713 | 714 | if (a.args != b.args) 715 | return a.args < b.args; 716 | 717 | return a.func < b.func; 718 | } 719 | 720 | void context::report_errors() 721 | { 722 | set all; 723 | dword current = -1; 724 | 725 | for (auto & e : scanner_err) all.insert(e); 726 | for (auto & e : deleter_err) all.insert(e); 727 | 728 | printf("Errors:\n"); 729 | 730 | for (auto & e : all) 731 | { 732 | if (e.code != current) 733 | { 734 | const char * fmt = (e.code < 0x10000000) ? " Code %lu - %s\n" : " Code %08lx - %s\n"; 735 | wstring desc; 736 | 737 | if (! get_error_desc(e.code, desc)) 738 | desc = L""; 739 | 740 | printf(fmt, e.code, to_utf8(desc).c_str()); 741 | 742 | current = e.code; 743 | } 744 | 745 | printf(" %s\n", e.args.c_str()); 746 | } 747 | } 748 | 749 | /* 750 | * 751 | */ 752 | int wmain_app(int argc, wchar_t ** argv) 753 | { 754 | context x; 755 | 756 | x.init(); 757 | 758 | x.parse_args(argc, argv); 759 | 760 | x.check_path(); 761 | 762 | x.confirm_it(); 763 | 764 | x.process(); 765 | 766 | x.report(); 767 | 768 | return x.exit_rc; 769 | } 770 | -------------------------------------------------------------------------------- /delete_file.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is a part of the source code of "byenow" program. 3 | * 4 | * Copyright (c) 2020- Alexander Pankratov and IO Bureau SA. 5 | * All rights reserved. 6 | * 7 | * The source code is distributed under the terms of 2-clause 8 | * BSD license with the Commons Clause condition. See LICENSE 9 | * file for details. 10 | */ 11 | #include "delete_file.h" 12 | 13 | #include "libp/_elpify.h" 14 | #include "libp/_system_api.h" 15 | #include "libp/_ntstatus.h" 16 | 17 | #define HSRO (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN) 18 | 19 | // 20 | static 21 | bool delete_file_win32(const wstring & file, dword attrs, api_error_cb * err) 22 | { 23 | if (elp->DeleteFile(file.c_str())) 24 | return true; 25 | 26 | if (GetLastError() == ERROR_FILE_NOT_FOUND) 27 | return true; 28 | 29 | __on_api_error("DeleteFile", file); 30 | return false; 31 | } 32 | 33 | static 34 | bool delete_file_ntapi(const wstring & file, dword attrs, api_error_cb * err) 35 | { 36 | UNICODE_STRING name; 37 | OBJECT_ATTRIBUTES attr; 38 | NTSTATUS status; 39 | 40 | status = ntdll.RtlDosPathNameToNtPathName_U_WithStatus(elpify(file).c_str(), &name, NULL, NULL); 41 | if (status != STATUS_SUCCESS) 42 | { 43 | __on_api_error_ex("RtlDosPathNameToNtPathName_U", status, file); 44 | return false; 45 | } 46 | 47 | InitializeObjectAttributes(&attr, &name, OBJ_CASE_INSENSITIVE, NULL, NULL); 48 | 49 | status = ntdll.NtDeleteFile(&attr); 50 | ntdll.RtlFreeUnicodeString(&name); 51 | 52 | if (status == STATUS_SUCCESS) 53 | return true; 54 | 55 | if (status == 0xC0000034) // STATUS_OBJECT_NAME_NOT_FOUND 56 | return false; 57 | 58 | __on_api_error_ex("NtDeleteFile", status, file); 59 | return false; 60 | } 61 | 62 | bool delete_file(const wstring & file, dword attrs, bool ntapi, api_error_cb * err) 63 | { 64 | if ( (attrs & HSRO) && 65 | ! elp->SetFileAttributes(file.c_str(), attrs & ~HSRO)) 66 | { 67 | __on_api_error("SetFileAttributes", file); 68 | } 69 | 70 | return ntapi ? delete_file_ntapi(file, attrs, err) 71 | : delete_file_win32(file, attrs, err); 72 | } 73 | 74 | // 75 | bool delete_folder(const wstring & folder, dword attrs, api_error_cb * err) 76 | { 77 | if ( (attrs & HSRO) && 78 | ! elp->SetFileAttributes(folder.c_str(), attrs & ~HSRO)) 79 | { 80 | __on_api_error("SetFileAttributes", folder); 81 | } 82 | 83 | if (elp->RemoveDirectory(folder.c_str())) 84 | return true; 85 | 86 | if (GetLastError() == ERROR_FILE_NOT_FOUND || 87 | GetLastError() == ERROR_PATH_NOT_FOUND) 88 | return true; 89 | 90 | __on_api_error("RemoveDirectory", folder); 91 | return false; 92 | } 93 | -------------------------------------------------------------------------------- /delete_file.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is a part of the source code of "byenow" program. 3 | * 4 | * Copyright (c) 2020- Alexander Pankratov and IO Bureau SA. 5 | * All rights reserved. 6 | * 7 | * The source code is distributed under the terms of 2-clause 8 | * BSD license with the Commons Clause condition. See LICENSE 9 | * file for details. 10 | */ 11 | #ifndef _ULTRA_DELETE_FILE_H_ 12 | #define _ULTRA_DELETE_FILE_H_ 13 | 14 | #include "libp/_windows.h" 15 | #include "libp/api_error.h" 16 | 17 | bool delete_file(const wstring & file, dword attrs, bool ntapi, api_error_cb * err); 18 | 19 | bool delete_folder(const wstring & folder, dword attrs, api_error_cb * err); 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /folder.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is a part of the source code of "byenow" program. 3 | * 4 | * Copyright (c) 2020- Alexander Pankratov and IO Bureau SA. 5 | * All rights reserved. 6 | * 7 | * The source code is distributed under the terms of 2-clause 8 | * BSD license with the Commons Clause condition. See LICENSE 9 | * file for details. 10 | */ 11 | #include "folder.h" 12 | 13 | // 14 | fsi_item::fsi_item() 15 | { 16 | } 17 | 18 | fsi_item::fsi_item(const wc_range & _name, const fsi_info & _info) 19 | { 20 | _name.to_str(name); 21 | info = _info; 22 | } 23 | 24 | // 25 | folder::folder() 26 | { 27 | parent = NULL; 28 | items = 0; 29 | } 30 | 31 | folder::~folder() 32 | { 33 | for (auto & d : folders) 34 | delete d; 35 | } 36 | 37 | wstring folder::get_path() const 38 | { 39 | const folder * d = this; 40 | wstring path; 41 | 42 | for ( ; d->parent; d = d->parent) 43 | path = L'\\' + d->self.name + path; 44 | 45 | return d->self.name + path; 46 | } 47 | 48 | void folder::census(folder_vec & vec) 49 | { 50 | for (auto & x : folders) 51 | x->census(vec); 52 | 53 | vec.push_back(this); 54 | } 55 | 56 | bool folder::ready_for_delete() const 57 | { 58 | return (items == 0); 59 | } 60 | -------------------------------------------------------------------------------- /folder.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is a part of the source code of "byenow" program. 3 | * 4 | * Copyright (c) 2020- Alexander Pankratov and IO Bureau SA. 5 | * All rights reserved. 6 | * 7 | * The source code is distributed under the terms of 2-clause 8 | * BSD license with the Commons Clause condition. See LICENSE 9 | * file for details. 10 | */ 11 | #ifndef _ULTRA_FOLDER_H_ 12 | #define _ULTRA_FOLDER_H_ 13 | 14 | #include "libp/types.h" 15 | #include "libp/api_error.h" 16 | #include "libp/_scan_folder_nt.h" 17 | 18 | // 19 | struct folder; 20 | 21 | typedef deque folder_deq; 22 | typedef vector folder_vec; 23 | 24 | // 25 | struct fsi_item 26 | { 27 | wstring name; 28 | fsi_info info; 29 | 30 | fsi_item(); 31 | fsi_item(const wc_range & name, const fsi_info & info); 32 | }; 33 | 34 | typedef vector fsi_item_vec; 35 | 36 | // 37 | struct folder 38 | { 39 | folder * parent; 40 | fsi_item self; 41 | 42 | folder_vec folders; 43 | fsi_item_vec files; 44 | 45 | uint32_t items; 46 | 47 | // 48 | folder(); 49 | ~folder(); 50 | 51 | wstring get_path() const; 52 | void census(folder_vec & vec); 53 | bool ready_for_delete() const; 54 | }; 55 | 56 | #endif 57 | -------------------------------------------------------------------------------- /ultra_machine.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is a part of the source code of "byenow" program. 3 | * 4 | * Copyright (c) 2020- Alexander Pankratov and IO Bureau SA. 5 | * All rights reserved. 6 | * 7 | * The source code is distributed under the terms of 2-clause 8 | * BSD license with the Commons Clause condition. See LICENSE 9 | * file for details. 10 | */ 11 | #include "ultra_machine.h" 12 | #include "ultra_machine_internals.h" 13 | 14 | #include "delete_file.h" 15 | 16 | #include "libp/enforce.h" 17 | #include "libp/atomic.h" 18 | 19 | #include "libp/_elpify.h" 20 | #include "libp/_cpu_info.h" 21 | #include "libp/_simple_work_queue.h" 22 | 23 | // 24 | ultra_mach_conf::ultra_mach_conf() 25 | { 26 | threads = 0; 27 | scanner_buf_size = 0; 28 | deleter_ntapi = false; 29 | deleter_batch = 128; 30 | keep_root = false; 31 | } 32 | 33 | // 34 | ultra_mach_info::ultra_mach_info() 35 | { 36 | d_found = d_deleted = 0; 37 | f_found = f_deleted = 0; 38 | b_found = b_deleted = 0; 39 | 40 | scanner_err = NULL; 41 | deleter_err = NULL; 42 | 43 | folders_togo = 0; 44 | done = false; 45 | } 46 | 47 | /* 48 | * ultra_task 49 | */ 50 | ultra_task::ultra_task(ultra_mach * _mach) 51 | { 52 | mach = _mach; 53 | phase = -1; 54 | ph2_first = 0; 55 | ph2_count = -1; 56 | } 57 | 58 | // 59 | void ultra_task::execute() 60 | { 61 | __enforce(curr && errors.empty()); 62 | 63 | path = curr->get_path(); 64 | 65 | if (phase == 1) 66 | { 67 | // scan folder 68 | scan_folder_nt(path.c_str(), mach->conf.scanner_buf_size, this, this); 69 | } 70 | else 71 | if (phase == 2) 72 | { 73 | // delete files 74 | 75 | if (ph2_first == 0 && ph2_count == -1) 76 | ph2_count = curr->files.size(); 77 | 78 | __enforce(ph2_first + ph2_count <= curr->files.size()); 79 | 80 | for (size_t i = 0; i < ph2_count && ! mach->enough; i++) 81 | { 82 | do_delete_file( curr->files[ph2_first+i] ); 83 | } 84 | } 85 | else 86 | if (phase == 3) 87 | { 88 | do_delete_self(); 89 | } 90 | else 91 | { 92 | __enforce(false); 93 | } 94 | 95 | path.clear(); 96 | } 97 | 98 | void ultra_task::do_delete_file(const fsi_item & f) 99 | { 100 | wstring file = path + L'\\' + f.name; 101 | 102 | if (delete_file(file, f.info.attrs, mach->conf.deleter_ntapi, this)) 103 | { 104 | atomic_inc(&mach->info.f_deleted); 105 | atomic_add(&mach->info.b_deleted, f.info.bytes); 106 | } 107 | 108 | atomic_dec(&curr->items); 109 | } 110 | 111 | void ultra_task::do_delete_self() 112 | { 113 | if (delete_folder(path, curr->self.info.attrs, this)) 114 | atomic_inc(&mach->info.d_deleted); 115 | 116 | if (curr->parent) 117 | atomic_dec(&curr->parent->items); 118 | } 119 | 120 | // 121 | bool ultra_task::on_fsi_scan(const wc_range & name, const fsi_info & info) 122 | { 123 | if (info.attrs & FILE_ATTRIBUTE_DIRECTORY) 124 | { 125 | folder * sub; 126 | 127 | sub = new folder(); 128 | sub->parent = curr; 129 | sub->self = fsi_item(name, info); 130 | 131 | curr->folders.push_back(sub); 132 | curr->items++; 133 | 134 | atomic_inc(&mach->info.d_found); 135 | } 136 | else 137 | { 138 | curr->files.push_back( fsi_item(name, info) ); 139 | curr->items++; 140 | 141 | atomic_inc(&mach->info.f_found); 142 | atomic_add(&mach->info.b_found, info.bytes); 143 | } 144 | 145 | return true; 146 | } 147 | 148 | // 149 | void ultra_task::on_api_error_x(const api_error & e) 150 | { 151 | errors.push_back(e); 152 | } 153 | 154 | /* 155 | * ultra_task_pool 156 | */ 157 | ultra_task_pool::ultra_task_pool() 158 | { 159 | mach = NULL; 160 | allocated = 0; 161 | } 162 | 163 | ultra_task_pool::~ultra_task_pool() 164 | { 165 | __enforce( unused() ); 166 | for (auto & x : cache) delete x; 167 | } 168 | 169 | ultra_task * ultra_task_pool::get(folder * d, int phase) 170 | { 171 | ultra_task * w; 172 | 173 | if (cache.size()) { w = cache.back(); cache.pop_back(); } 174 | else { w = new ultra_task(mach); allocated++; } 175 | 176 | w->phase = phase; 177 | w->curr = d; 178 | return w; 179 | } 180 | 181 | void ultra_task_pool::put(ultra_task * w) 182 | { 183 | w->curr = NULL; 184 | w->phase = -1; 185 | w->errors.clear(); 186 | 187 | cache.push_back(w); 188 | } 189 | 190 | bool ultra_task_pool::unused() const 191 | { 192 | return cache.size() == allocated; 193 | } 194 | 195 | /* 196 | * ultra_mach 197 | */ 198 | ultra_mach::ultra_mach() 199 | { 200 | ph1_only = false; 201 | enough = false; 202 | ph1_work = ph2_work = ph3_work = 0; 203 | ph1_done = ph2_done = ph3_done = 0; 204 | } 205 | 206 | ultra_mach::~ultra_mach() 207 | { 208 | term(); 209 | } 210 | 211 | // 212 | bool ultra_mach::init(const ultra_mach_conf & _conf, ultra_mach_cb * _cb) 213 | { 214 | conf = _conf; 215 | cb = _cb; 216 | 217 | if (! conf.scanner_buf_size) 218 | conf.scanner_buf_size = 8*1024; 219 | 220 | if (! conf.deleter_batch) 221 | conf.deleter_batch = -1; 222 | 223 | if (conf.threads == 0 || conf.threads == -1) 224 | conf.threads = get_cpu_count(); 225 | 226 | pool.mach = this; 227 | 228 | return swq.init(conf.threads, NULL); 229 | } 230 | 231 | void ultra_mach::term() 232 | { 233 | work_item_vec out; 234 | 235 | swq.cancel(out); 236 | 237 | for (auto & wi : out) 238 | pool.put( (ultra_task*)wi ); 239 | } 240 | 241 | // 242 | bool ultra_mach::keep_going() const 243 | { 244 | if (enough) 245 | return false; 246 | 247 | return (ph1_done < ph1_work) || (ph2_done < ph2_work) || (ph3_done < ph3_work); 248 | } 249 | 250 | // 251 | void ultra_mach::enqueue_ph1(folder * x) 252 | { 253 | swq.enqueue( pool.get(x, 1) ); 254 | ph1_work++; 255 | } 256 | 257 | void ultra_mach::enqueue_ph2(folder * x) 258 | { 259 | ultra_task * w; 260 | size_t total = x->files.size(); 261 | 262 | for (size_t chunk, start = 0; start < total; start += chunk) 263 | { 264 | chunk = min(total - start, conf.deleter_batch); 265 | 266 | w = pool.get(x, 2); 267 | w->ph2_first = start; 268 | w->ph2_count = chunk; 269 | 270 | swq.enqueue(w); 271 | ph2_work++; 272 | } 273 | } 274 | 275 | void ultra_mach::enqueue_ph3(folder * x) 276 | { 277 | __enforce(x->items == 0); 278 | 279 | // don't delete the root folder if asked 280 | if (! x->parent && conf.keep_root) 281 | return; 282 | 283 | x->items = -1; // being deleted 284 | 285 | swq.enqueue( pool.get(x, 3) ); 286 | ph3_work++; 287 | } 288 | 289 | // 290 | void ultra_mach::complete_ph1(ultra_task * w) 291 | { 292 | __enforce(! enough); 293 | __enforce(w->phase == 1); 294 | 295 | // w->curr scanned 296 | 297 | ph1_done++; 298 | 299 | for (auto & x : w->curr->folders) 300 | { 301 | if (x->self.info.attrs & FILE_ATTRIBUTE_REPARSE_POINT) 302 | continue; 303 | 304 | enqueue_ph1(x); // scan subfolders 305 | } 306 | 307 | if (! ph1_only) 308 | { 309 | if (w->curr->files.size()) 310 | enqueue_ph2(w->curr); 311 | else 312 | if (w->curr->folders.empty()) 313 | enqueue_ph3(w->curr); 314 | 315 | // ^ same as in ultra_mach_delete() 316 | } 317 | 318 | // 319 | info.folders_togo = ph1_work - ph1_done; 320 | 321 | info.scanner_err = &w->errors; 322 | enough = ! cb->on_ultra_mach_tick(info); 323 | info.scanner_err = NULL; 324 | 325 | // 326 | pool.put(w); 327 | } 328 | 329 | void ultra_mach::complete_ph2(ultra_task * w) 330 | { 331 | __enforce(! enough); 332 | __enforce(w->phase == 2); 333 | 334 | // w->curr->files deleted 335 | 336 | ph2_done++; 337 | 338 | // if fully processed 339 | if (w->curr->items == 0) 340 | { 341 | // save some space 342 | w->curr->files.clear(); 343 | 344 | // delete the folder 345 | enqueue_ph3(w->curr); 346 | } 347 | 348 | // 349 | info.deleter_err = &w->errors; 350 | enough = ! cb->on_ultra_mach_tick(info); 351 | info.deleter_err = NULL; 352 | 353 | pool.put(w); 354 | } 355 | 356 | void ultra_mach::complete_ph3(ultra_task * w) 357 | { 358 | __enforce(! enough); 359 | __enforce(w->phase == 3); 360 | 361 | // w->curr deleted 362 | 363 | ph3_done++; 364 | 365 | // if parent is fully processed 366 | if (w->curr->parent && 367 | w->curr->parent->items == 0) 368 | { 369 | enqueue_ph3(w->curr->parent); 370 | } 371 | 372 | pool.put(w); 373 | } 374 | 375 | void ultra_mach::loop() 376 | { 377 | work_item_vec out; 378 | 379 | while ( keep_going() ) 380 | { 381 | swq.collect(out, 50); 382 | 383 | for (auto & wi : out) 384 | { 385 | ultra_task * w = (ultra_task *)wi; 386 | 387 | if (enough) 388 | { 389 | pool.put(w); 390 | continue; 391 | } 392 | 393 | switch (w->phase) 394 | { 395 | case 1: complete_ph1(w); break; 396 | case 2: complete_ph2(w); break; 397 | case 3: complete_ph3(w); break; 398 | default: __enforce(false); 399 | } 400 | } 401 | 402 | out.clear(); 403 | } 404 | 405 | if (! enough) 406 | { 407 | info.done = true; 408 | cb->on_ultra_mach_tick(info); 409 | } 410 | } 411 | 412 | /* 413 | * 414 | */ 415 | bool ultra_mach_scan(folder & root, const ultra_mach_conf & conf, ultra_mach_cb * cb) 416 | { 417 | ultra_mach mach; 418 | 419 | // 420 | __enforce(! root.self.name.empty()); // path is set 421 | 422 | if (! mach.init(conf, cb)) 423 | return false; 424 | 425 | mach.ph1_only = true; 426 | 427 | mach.enqueue_ph1(&root); 428 | mach.info.d_found = 1; 429 | 430 | mach.loop(); 431 | mach.term(); 432 | 433 | return ! mach.enough; 434 | } 435 | 436 | // 437 | static 438 | bool ultra_mach_delete(folder & root, const ultra_mach_conf & conf, ultra_mach_cb * cb) 439 | { 440 | ultra_mach mach; 441 | folder_vec list; 442 | work_item_vec out; 443 | 444 | // 445 | __enforce(! root.self.name.empty()); // path is set 446 | 447 | if (! mach.init(conf, cb)) 448 | return false; 449 | 450 | root.census(list); 451 | 452 | for (auto & x : list) 453 | { 454 | if (x->files.size()) 455 | mach.enqueue_ph2(x); 456 | else 457 | if (x->folders.empty()) 458 | mach.enqueue_ph3(x); 459 | 460 | // ^ same as complete_ph1() 461 | } 462 | 463 | mach.loop(); 464 | mach.term(); 465 | 466 | return ! mach.enough; 467 | } 468 | 469 | // 470 | static 471 | bool ultra_mach_scan_and_delete(folder & root, const ultra_mach_conf & conf, ultra_mach_cb * cb) 472 | { 473 | ultra_mach mach; 474 | 475 | // 476 | __enforce(! root.self.name.empty()); // path is set 477 | 478 | if (! mach.init(conf, cb)) 479 | return false; 480 | 481 | mach.ph1_only = false; 482 | 483 | mach.enqueue_ph1(&root); 484 | mach.info.d_found = 1; 485 | 486 | mach.loop(); 487 | mach.term(); 488 | 489 | return ! mach.enough; 490 | } 491 | 492 | // 493 | bool ultra_mach_delete(folder & root, bool prescanned, const ultra_mach_conf & conf, ultra_mach_cb * cb) 494 | { 495 | return prescanned ? ultra_mach_delete(root, conf, cb) 496 | : ultra_mach_scan_and_delete(root, conf, cb); 497 | } 498 | -------------------------------------------------------------------------------- /ultra_machine.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is a part of the source code of "byenow" program. 3 | * 4 | * Copyright (c) 2020- Alexander Pankratov and IO Bureau SA. 5 | * All rights reserved. 6 | * 7 | * The source code is distributed under the terms of 2-clause 8 | * BSD license with the Commons Clause condition. See LICENSE 9 | * file for details. 10 | */ 11 | #ifndef _ULTRA_MACHINE_H_ 12 | #define _ULTRA_MACHINE_H_ 13 | 14 | #include "folder.h" 15 | 16 | // 17 | struct ultra_mach_conf 18 | { 19 | size_t threads; 20 | size_t scanner_buf_size; 21 | bool deleter_ntapi; 22 | size_t deleter_batch; 23 | bool keep_root; 24 | 25 | ultra_mach_conf(); 26 | }; 27 | 28 | struct ultra_mach_info 29 | { 30 | size_t d_found, d_deleted; 31 | size_t f_found, f_deleted; 32 | uint64_t b_found, b_deleted; 33 | 34 | api_error_vec * scanner_err; 35 | api_error_vec * deleter_err; 36 | 37 | size_t folders_togo; 38 | bool done; 39 | 40 | ultra_mach_info(); 41 | }; 42 | 43 | // 44 | struct ultra_mach_cb 45 | { 46 | __interface(ultra_mach_cb); 47 | 48 | virtual bool on_ultra_mach_tick(const ultra_mach_info & info) = 0; 49 | }; 50 | 51 | // 52 | bool ultra_mach_scan(folder & root, const ultra_mach_conf & conf, ultra_mach_cb * cb); 53 | 54 | bool ultra_mach_delete(folder & root, bool prescanned, const ultra_mach_conf & conf, ultra_mach_cb * cb); 55 | 56 | #endif 57 | -------------------------------------------------------------------------------- /ultra_machine_internals.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is a part of the source code of "byenow" program. 3 | * 4 | * Copyright (c) 2020- Alexander Pankratov and IO Bureau SA. 5 | * All rights reserved. 6 | * 7 | * The source code is distributed under the terms of 2-clause 8 | * BSD license with the Commons Clause condition. See LICENSE 9 | * file for details. 10 | */ 11 | #ifndef _ULTRA_MACHINE_INTERNAL_H_ 12 | #define _ULTRA_MACHINE_INTERNAL_H_ 13 | 14 | #include "ultra_machine.h" 15 | #include "libp/_simple_work_queue.h" 16 | 17 | // 18 | struct ultra_mach; 19 | 20 | // 21 | struct ultra_task : work_item, fsi_scan_cb, api_error_cb 22 | { 23 | ultra_task(ultra_mach * mach); 24 | 25 | __no_copying(ultra_task); 26 | 27 | /* 28 | * work_item 29 | */ 30 | void execute(); 31 | void do_delete_file(const fsi_item & f); 32 | void do_delete_self(); 33 | 34 | /* 35 | * fsi_scan_cb 36 | */ 37 | void on_fsi_open(HANDLE h) { } 38 | bool on_fsi_scan(const wc_range & name, const fsi_info & info); 39 | 40 | /* 41 | * api_error_cb 42 | */ 43 | void on_api_error_x(const api_error & e); 44 | 45 | // 46 | ultra_mach * mach; 47 | folder * curr; 48 | int phase; 49 | 50 | size_t ph2_first; // delete curr->files[first, first+count-1] 51 | size_t ph2_count; 52 | 53 | wstring path; 54 | api_error_vec errors; 55 | }; 56 | 57 | typedef vector ultra_task_vec; 58 | 59 | // 60 | struct ultra_task_pool 61 | { 62 | ultra_task_pool(); 63 | ~ultra_task_pool(); 64 | 65 | __no_copying(ultra_task_pool); 66 | 67 | // 68 | ultra_task * get(folder * d, int phase); 69 | void put(ultra_task * w); 70 | 71 | bool unused() const; 72 | 73 | ultra_mach * mach; 74 | ultra_task_vec cache; 75 | size_t allocated; 76 | }; 77 | 78 | // 79 | struct ultra_mach 80 | { 81 | ultra_mach_conf conf; 82 | ultra_mach_cb * cb; 83 | bool ph1_only; // aka 'just_scan' 84 | 85 | simple_work_queue swq; 86 | ultra_task_pool pool; 87 | bool enough; 88 | 89 | ultra_mach_info info; 90 | size_t ph1_work, ph2_work, ph3_work; 91 | size_t ph1_done, ph2_done, ph3_done; 92 | 93 | // 94 | ultra_mach(); 95 | ~ultra_mach(); 96 | 97 | bool init(const ultra_mach_conf & conf, ultra_mach_cb * cb); 98 | void term(); 99 | 100 | bool keep_going() const; 101 | 102 | void enqueue_ph1(folder * x); 103 | void enqueue_ph2(folder * x); 104 | void enqueue_ph3(folder * x); 105 | 106 | void complete_ph1(ultra_task * w); 107 | void complete_ph2(ultra_task * w); 108 | void complete_ph3(ultra_task * w); 109 | 110 | void loop(); 111 | }; 112 | 113 | #endif 114 | -------------------------------------------------------------------------------- /utils.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is a part of the source code of "byenow" program. 3 | * 4 | * Copyright (c) 2020- Alexander Pankratov and IO Bureau SA. 5 | * All rights reserved. 6 | * 7 | * The source code is distributed under the terms of 2-clause 8 | * BSD license with the Commons Clause condition. See LICENSE 9 | * file for details. 10 | */ 11 | #include "utils.h" 12 | 13 | #include "libp/string_utils.h" 14 | #include "libp/_system_api.h" 15 | 16 | // 17 | #define __KB (1024ULL) 18 | #define __MB (1024ULL*1024) 19 | #define __GB (1024ULL*1024*1024) 20 | #define __TB (1024ULL*1024*1024*1024) 21 | #define __PB (1024ULL*1024*1024*1024*1024) 22 | 23 | // 24 | string format_count(uint64_t val, const char * unit) 25 | { 26 | return stringf("%I64u %s%s", val, unit, (val == 1) ? "" : "s"); 27 | } 28 | 29 | string format_bytes(uint64_t bytes) 30 | { 31 | if (bytes < 64*__KB) return stringf("%I64u B", bytes); 32 | if (bytes < 2*__MB) return stringf("%.1lf KB", bytes/(double)__KB); 33 | if (bytes < 2*__GB) return stringf("%.1lf MB", bytes/(double)__MB); 34 | if (bytes < 2*__TB) return stringf("%.1lf GB", bytes/(double)__GB); 35 | if (bytes < 2*__PB) return stringf("%.1lf TB", bytes/(double)__TB); 36 | 37 | return stringf("%.1lf PB", bytes/(double)__PB); 38 | } 39 | 40 | string format_usecs(uint64_t usecs) 41 | { 42 | if (usecs <= 1000) 43 | return "1 ms"; 44 | 45 | if (usecs < 1000*1000) 46 | return stringf("%I64u ms", usecs/1000); 47 | 48 | // if (usecs < 60*1000*1000) 49 | if (usecs < 10*1000*1000) 50 | return stringf("%.2lf sec", usecs/1000./1000.); 51 | 52 | size_t ms, sec, min, hr; 53 | 54 | usecs /= 1000; 55 | ms = usecs % 1000; usecs /= 1000; 56 | sec = usecs % 60; usecs /= 60; 57 | min = usecs % 60; usecs /= 60; 58 | hr = usecs; 59 | 60 | return stringf("%02zu:%02zu:%02zu.%03zu", hr, min, sec, ms); 61 | } 62 | 63 | // 64 | template 65 | void replace(std::basic_string & str, const E * a, const E * b) 66 | { 67 | typedef std::basic_string::traits_type E_ops; 68 | 69 | size_t a_len = E_ops::length(a); 70 | size_t b_len = E_ops::length(b); 71 | size_t pos = 0; 72 | 73 | for (;;) 74 | { 75 | pos = str.find(a, pos); 76 | if (pos == -1) 77 | break; 78 | 79 | str.replace(pos, a_len, b, b_len); 80 | pos += b_len; 81 | } 82 | } 83 | 84 | /* 85 | * https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-erref/596a1078-e883-4972-9bbc-49e60bebca55 86 | */ 87 | bool get_error_desc(dword code, wstring & mesg) 88 | { 89 | HANDLE mod = NULL; 90 | dword flags = FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER; 91 | dword lang = MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT); 92 | bool ntapi = (code & 0xF0000000); 93 | wchar_t * wstr; 94 | 95 | if (ntapi) 96 | { 97 | flags |= FORMAT_MESSAGE_FROM_HMODULE; 98 | mod = ntdll.mod; 99 | } 100 | 101 | if (! FormatMessageW(flags, mod, code, lang, (LPWSTR)&wstr, sizeof wstr, NULL)) 102 | return false; 103 | 104 | mesg = wstr; 105 | LocalFree(wstr); 106 | 107 | // remove line breaks 108 | replace(mesg, L"\r\n", L"\n"); 109 | replace(mesg, L" \n", L"\n"); 110 | replace(mesg, L"\n ", L"\n"); 111 | replace(mesg, L"\n", L" "); 112 | 113 | if (mesg.size() && mesg.back() == L' ') mesg.pop_back(); 114 | 115 | return true; 116 | } 117 | 118 | string error_to_str(const api_error & e) 119 | { 120 | string mesg = e.func + "() " + failed_with(e.code); 121 | wstring foo; 122 | 123 | if (get_error_desc(e.code, foo)) 124 | mesg += ". " + to_utf8(foo); 125 | 126 | return mesg; 127 | } 128 | -------------------------------------------------------------------------------- /utils.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is a part of the source code of "byenow" program. 3 | * 4 | * Copyright (c) 2020- Alexander Pankratov and IO Bureau SA. 5 | * All rights reserved. 6 | * 7 | * The source code is distributed under the terms of 2-clause 8 | * BSD license with the Commons Clause condition. See LICENSE 9 | * file for details. 10 | */ 11 | #ifndef _NUKE_UTILS_H_ 12 | #define _NUKE_UTILS_H_ 13 | 14 | #include "libp/types.h" 15 | #include "libp/api_error.h" 16 | #include "libp/_windows.h" 17 | 18 | // 19 | #define __not_found(err) ( ((err) == ERROR_FILE_NOT_FOUND) || ((err) == ERROR_PATH_NOT_FOUND) ) 20 | #define __not_empty(err) ((err) == ERROR_DIR_NOT_EMPTY ) 21 | 22 | // 23 | string format_count(uint64_t val, const char * unit); 24 | string format_bytes(uint64_t bytes); 25 | string format_usecs(uint64_t usecs); 26 | 27 | bool get_error_desc(dword code, wstring & mesg); 28 | string error_to_str(const api_error & e); 29 | 30 | // 31 | template 32 | void append(std::vector & dst, const std::vector & src) 33 | { 34 | if (src.empty()) 35 | return; 36 | 37 | dst.insert(dst.end(), src.begin(), src.end()); 38 | } 39 | 40 | #endif 41 | 42 | -------------------------------------------------------------------------------- /wmain.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is a part of the source code of "byenow" program. 3 | * 4 | * Copyright (c) 2020- Alexander Pankratov and IO Bureau SA. 5 | * All rights reserved. 6 | * 7 | * The source code is distributed under the terms of 2-clause 8 | * BSD license with the Commons Clause condition. See LICENSE 9 | * file for details. 10 | */ 11 | #include "libp/_windows.h" 12 | #include "libp/enforce.h" 13 | 14 | /* 15 | * 16 | */ 17 | #define __try_cpp_exceptions__ try 18 | #define __catch_cpp_exceptions__ catch ( std::exception & ) { printf("\nWhoops - std::exception\n"); } 19 | 20 | // 21 | #define __try_seh_exceptions__ __try 22 | #define __catch_seh_exceptions__ __except ( EXCEPTION_EXECUTE_HANDLER ) { printf("\nWhoops - seh::exception\n"); } 23 | 24 | // 25 | void on_assert(const char * exp, const char * file, const char * func, int line) 26 | { 27 | printf("\nWhoops - assertion failed - line %d\n", line); 28 | exit(1); 29 | } 30 | 31 | /* 32 | * 33 | */ 34 | int wmain_app(int argc, wchar_t ** argv); 35 | 36 | int wmain_seh(int argc, wchar_t ** argv) 37 | { 38 | int r = 3; // RC_whoops_seh 39 | 40 | __try_seh_exceptions__ 41 | { 42 | r = wmain_app(argc, argv); 43 | } 44 | __catch_seh_exceptions__ 45 | 46 | return r; 47 | } 48 | 49 | int wmain(int argc, wchar_t ** argv) 50 | { 51 | int r = 4; // RC_whoops_cpp 52 | 53 | __try_cpp_exceptions__ 54 | { 55 | r = wmain_seh(argc, argv); 56 | } 57 | __catch_cpp_exceptions__ 58 | 59 | return r; 60 | } 61 | 62 | // 63 | #if defined _M_IX86 64 | #pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='x86' publicKeyToken='6595b64144ccf1df' language='*'\"") 65 | #elif defined _M_IA64 66 | #pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='ia64' publicKeyToken='6595b64144ccf1df' language='*'\"") 67 | #elif defined _M_X64 68 | #pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='amd64' publicKeyToken='6595b64144ccf1df' language='*'\"") 69 | #else 70 | #pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"") 71 | #endif 72 | --------------------------------------------------------------------------------