mv.cpp 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. /*
  2. * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <AK/LexicalPath.h>
  7. #include <AK/String.h>
  8. #include <LibCore/ArgsParser.h>
  9. #include <LibCore/File.h>
  10. #include <stdio.h>
  11. #include <sys/stat.h>
  12. #include <unistd.h>
  13. int main(int argc, char** argv)
  14. {
  15. if (pledge("stdio rpath wpath cpath fattr", nullptr) < 0) {
  16. perror("pledge");
  17. return 1;
  18. }
  19. // NOTE: The "force" option is a dummy for now, it's just here to silence scripts that use "mv -f"
  20. // In the future, it might be used to cancel out an "-i" interactive option.
  21. bool force = false;
  22. bool verbose = false;
  23. Vector<const char*> paths;
  24. Core::ArgsParser args_parser;
  25. args_parser.add_option(force, "Force", "force", 'f');
  26. args_parser.add_option(verbose, "Verbose", "verbose", 'v');
  27. args_parser.add_positional_argument(paths, "Paths to files being moved followed by target location", "paths");
  28. args_parser.parse(argc, argv);
  29. if (paths.size() < 2) {
  30. args_parser.print_usage(stderr, argv[0]);
  31. return 1;
  32. }
  33. auto original_new_path = paths.take_last();
  34. struct stat st;
  35. int rc = lstat(original_new_path, &st);
  36. if (rc != 0 && errno != ENOENT) {
  37. perror("lstat");
  38. return 1;
  39. }
  40. if (paths.size() > 1 && !S_ISDIR(st.st_mode)) {
  41. warnln("Target is not a directory: {}", original_new_path);
  42. return 1;
  43. }
  44. auto my_umask = umask(0);
  45. umask(my_umask);
  46. for (auto& old_path : paths) {
  47. String combined_new_path;
  48. const char* new_path = original_new_path;
  49. if (S_ISDIR(st.st_mode)) {
  50. auto old_basename = LexicalPath::basename(old_path);
  51. combined_new_path = String::formatted("{}/{}", original_new_path, old_basename);
  52. new_path = combined_new_path.characters();
  53. }
  54. rc = rename(old_path, new_path);
  55. if (rc < 0) {
  56. if (errno == EXDEV) {
  57. auto result = Core::File::copy_file_or_directory(
  58. new_path, old_path,
  59. Core::File::RecursionMode::Allowed,
  60. Core::File::LinkMode::Disallowed,
  61. Core::File::AddDuplicateFileMarker::No);
  62. if (result.is_error()) {
  63. warnln("mv: could not move '{}': {}", old_path, static_cast<Error const&>(result.error()));
  64. return 1;
  65. }
  66. rc = unlink(old_path);
  67. if (rc < 0)
  68. warnln("mv: unlink '{}': {}", old_path, strerror(errno));
  69. } else {
  70. warnln("mv: cannot move '{}' : {}", old_path, strerror(errno));
  71. }
  72. }
  73. if (verbose && rc == 0)
  74. outln("renamed '{}' -> '{}'", old_path, new_path);
  75. }
  76. return 0;
  77. }