mv.c 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536
  1. /*++
  2. Copyright (c) 2013 Minoca Corp. All Rights Reserved
  3. Module Name:
  4. mv.c
  5. Abstract:
  6. This module implements the mv (move) file utility.
  7. Author:
  8. Evan Green 2-Jul-2013
  9. Environment:
  10. POSIX
  11. --*/
  12. //
  13. // ------------------------------------------------------------------- Includes
  14. //
  15. #include <minoca/lib/types.h>
  16. #include <assert.h>
  17. #include <dirent.h>
  18. #include <errno.h>
  19. #include <getopt.h>
  20. #include <libgen.h>
  21. #include <stdlib.h>
  22. #include <string.h>
  23. #include <sys/stat.h>
  24. #include <unistd.h>
  25. #include "swlib.h"
  26. //
  27. // ---------------------------------------------------------------- Definitions
  28. //
  29. #define MV_VERSION_MAJOR 1
  30. #define MV_VERSION_MINOR 0
  31. #define MV_USAGE \
  32. "usage: mv [-fiv] source... target\n\n" \
  33. "The mv utility moves files and directories.\n\n" \
  34. " -f, --force -- Skip all prompts.\n" \
  35. " -i, --interactive -- Interactive mode. Prompt for each file.\n" \
  36. " -v, --verbose -- Verbose, print each file being removed.\n" \
  37. " --help -- Display this help text.\n" \
  38. " --version -- Display version information and exit.\n\n"
  39. #define MV_OPTIONS_STRING ":fiv"
  40. //
  41. // Define rm options.
  42. //
  43. //
  44. // Set this option to disable all prompts.
  45. //
  46. #define MV_OPTION_FORCE 0x00000001
  47. //
  48. // Set this option to set prompts for all files.
  49. //
  50. #define MV_OPTION_INTERACTIVE 0x00000002
  51. //
  52. // Set this option to print each file that's deleted.
  53. //
  54. #define MV_OPTION_VERBOSE 0x00000004
  55. //
  56. // This internal option is set if standard in is a terminal device.
  57. //
  58. #define MV_OPTION_STDIN_IS_TERMINAL 0x00000008
  59. //
  60. // ------------------------------------------------------ Data Type Definitions
  61. //
  62. //
  63. // ----------------------------------------------- Internal Function Prototypes
  64. //
  65. INT
  66. MvMove (
  67. INT Options,
  68. PSTR Argument,
  69. PSTR Target
  70. );
  71. //
  72. // -------------------------------------------------------------------- Globals
  73. //
  74. struct option MvLongOptions[] = {
  75. {"force", no_argument, 0, 'f'},
  76. {"interactive", no_argument, 0, 'i'},
  77. {"verbose", no_argument, 0, 'v'},
  78. {"help", no_argument, 0, 'h'},
  79. {"version", no_argument, 0, 'V'},
  80. {NULL, 0, 0, 0}
  81. };
  82. //
  83. // ------------------------------------------------------------------ Functions
  84. //
  85. INT
  86. MvMain (
  87. INT ArgumentCount,
  88. CHAR **Arguments
  89. )
  90. /*++
  91. Routine Description:
  92. This routine is the main entry point for the mv utility.
  93. Arguments:
  94. ArgumentCount - Supplies the number of command line arguments the program
  95. was invoked with.
  96. Arguments - Supplies a tokenized array of command line arguments.
  97. Return Value:
  98. Returns an integer exit code. 0 for success, nonzero otherwise.
  99. --*/
  100. {
  101. PSTR AppendedPath;
  102. ULONG AppendedPathSize;
  103. PSTR Argument;
  104. ULONG ArgumentIndex;
  105. PSTR LinkDestination;
  106. INT Option;
  107. ULONG Options;
  108. PSTR Source;
  109. ULONG SourceCount;
  110. struct stat Stat;
  111. int Status;
  112. PSTR Target;
  113. BOOL TargetIsDirectory;
  114. int TotalStatus;
  115. LinkDestination = NULL;
  116. SourceCount = 0;
  117. Target = NULL;
  118. TotalStatus = 0;
  119. Options = 0;
  120. if (isatty(STDIN_FILENO)) {
  121. Options |= MV_OPTION_STDIN_IS_TERMINAL;
  122. }
  123. //
  124. // Process the control arguments.
  125. //
  126. while (TRUE) {
  127. Option = getopt_long(ArgumentCount,
  128. Arguments,
  129. MV_OPTIONS_STRING,
  130. MvLongOptions,
  131. NULL);
  132. if (Option == -1) {
  133. break;
  134. }
  135. if ((Option == '?') || (Option == ':')) {
  136. Status = 1;
  137. goto MainEnd;
  138. }
  139. switch (Option) {
  140. case 'f':
  141. Options |= MV_OPTION_FORCE;
  142. Options &= ~MV_OPTION_INTERACTIVE;
  143. break;
  144. case 'i':
  145. Options |= MV_OPTION_INTERACTIVE;
  146. Options &= ~MV_OPTION_FORCE;
  147. break;
  148. case 'v':
  149. Options |= MV_OPTION_VERBOSE;
  150. break;
  151. case 'V':
  152. SwPrintVersion(MV_VERSION_MAJOR, MV_VERSION_MINOR);
  153. return 1;
  154. case 'h':
  155. printf(MV_USAGE);
  156. return 1;
  157. default:
  158. assert(FALSE);
  159. Status = 1;
  160. goto MainEnd;
  161. }
  162. }
  163. ArgumentIndex = optind;
  164. if (ArgumentIndex > ArgumentCount) {
  165. ArgumentIndex = ArgumentCount;
  166. }
  167. if (ArgumentIndex < ArgumentCount) {
  168. Target = Arguments[ArgumentCount - 1];
  169. }
  170. SourceCount = ArgumentCount - ArgumentIndex;
  171. //
  172. // Fail if there were not enough arguments.
  173. //
  174. if ((Target == NULL) || (SourceCount <= 1)) {
  175. SwPrintError(0, NULL, "Argument expected. Try --help for usage");
  176. return 1;
  177. }
  178. SourceCount -= 1;
  179. //
  180. // Figure out if the target is a directory, or a link to a directory.
  181. //
  182. TargetIsDirectory = FALSE;
  183. Status = SwStat(Target, FALSE, &Stat);
  184. if (Status == 0) {
  185. if (S_ISLNK(Stat.st_mode)) {
  186. Status = SwReadLink(Target, &LinkDestination);
  187. if (Status == 0) {
  188. Target = LinkDestination;
  189. Status = SwStat(Target, FALSE, &Stat);
  190. if (Status == 0) {
  191. if (S_ISDIR(Stat.st_mode)) {
  192. TargetIsDirectory = TRUE;
  193. }
  194. }
  195. }
  196. } else if (S_ISDIR(Stat.st_mode)) {
  197. TargetIsDirectory = TRUE;
  198. }
  199. } else if (errno != ENOENT) {
  200. TotalStatus = errno;
  201. SwPrintError(TotalStatus, Target, "Failed to stat target");
  202. goto MainEnd;
  203. }
  204. //
  205. // If there's more than one source and the target is not a directory, that's
  206. // a problem.
  207. //
  208. if ((SourceCount > 1) && (TargetIsDirectory == FALSE)) {
  209. TotalStatus = ENOTDIR;
  210. SwPrintError(TotalStatus, Target, "Cannot move to");
  211. goto MainEnd;
  212. }
  213. //
  214. // Loop through the arguments again and perform the moves.
  215. //
  216. while (ArgumentIndex < ArgumentCount) {
  217. Argument = Arguments[ArgumentIndex];
  218. ArgumentIndex += 1;
  219. //
  220. // Skip the target.
  221. //
  222. if (Argument == Target) {
  223. continue;
  224. }
  225. //
  226. // Create an appended version of the path if the target is a directory.
  227. //
  228. if (TargetIsDirectory != FALSE) {
  229. Source = basename(Argument);
  230. if (Source == NULL) {
  231. SwPrintError(errno, Argument, "Unable to get base name of");
  232. TotalStatus = 1;
  233. continue;
  234. }
  235. Status = SwAppendPath(Target,
  236. strlen(Target) + 1,
  237. Source,
  238. strlen(Source) + 1,
  239. &AppendedPath,
  240. &AppendedPathSize);
  241. if (Status == FALSE) {
  242. TotalStatus = EINVAL;
  243. continue;
  244. }
  245. Status = MvMove(Options, Argument, AppendedPath);
  246. free(AppendedPath);
  247. } else {
  248. Status = MvMove(Options, Argument, Target);
  249. }
  250. if (Status != 0) {
  251. TotalStatus = Status;
  252. }
  253. }
  254. MainEnd:
  255. if (LinkDestination != NULL) {
  256. free(LinkDestination);
  257. }
  258. return TotalStatus;
  259. }
  260. //
  261. // --------------------------------------------------------- Internal Functions
  262. //
  263. INT
  264. MvMove (
  265. INT Options,
  266. PSTR Argument,
  267. PSTR Target
  268. )
  269. /*++
  270. Routine Description:
  271. This routine is the workhorse behind the mv application. It moves a source
  272. to a destination.
  273. Arguments:
  274. Options - Supplies the application options.
  275. Argument - Supplies the object to move.
  276. Target - Supplies the target to move it to.
  277. Return Value:
  278. Returns an integer exit code. 0 for success, nonzero otherwise.
  279. --*/
  280. {
  281. BOOL Answer;
  282. ULONG CopyOptions;
  283. PSTR QuotedArgument;
  284. PSTR QuotedTarget;
  285. int Result;
  286. struct stat Stat;
  287. struct stat TargetStat;
  288. int TargetStatResult;
  289. TargetStatResult = SwStat(Target, FALSE, &TargetStat);
  290. if ((TargetStatResult != 0) && (errno != ENOENT)) {
  291. Result = errno;
  292. SwPrintError(Result, Target, "Could not stat");
  293. goto MoveEnd;
  294. }
  295. //
  296. // If the destination exists, the force option is off, and either
  297. // 1) The permissions don't allow writing and stdin is a terminal, or
  298. // 2) The interactive option is enabled,
  299. //
  300. // then print a prompt.
  301. //
  302. if ((TargetStatResult == 0) && ((Options & MV_OPTION_FORCE) == 0) &&
  303. ((((TargetStat.st_mode & S_IWUSR) == 0) &&
  304. ((Options & MV_OPTION_STDIN_IS_TERMINAL) == 0)) ||
  305. ((Options & MV_OPTION_INTERACTIVE) != 0))) {
  306. QuotedArgument = SwQuoteArgument(Argument);
  307. fprintf(stderr, "mv: Overwrite file '%s'? ", QuotedArgument);
  308. if (QuotedArgument != Argument) {
  309. free(QuotedArgument);
  310. }
  311. Result = SwGetYesNoAnswer(&Answer);
  312. if ((Result != 0) || (Answer == FALSE)) {
  313. goto MoveEnd;
  314. }
  315. }
  316. //
  317. // In verbose mode, print out what's going on.
  318. //
  319. if ((Options & MV_OPTION_VERBOSE) != 0) {
  320. QuotedArgument = SwQuoteArgument(Argument);
  321. QuotedTarget = SwQuoteArgument(Target);
  322. printf("'%s' -> '%s'\n", QuotedArgument, QuotedTarget);
  323. if (QuotedArgument != Argument) {
  324. free(QuotedArgument);
  325. }
  326. if (QuotedTarget != Target) {
  327. free(QuotedTarget);
  328. }
  329. }
  330. //
  331. // First try out a rename, and happily exit if it worked.
  332. //
  333. Result = rename(Argument, Target);
  334. if (Result == 0) {
  335. goto MoveEnd;
  336. }
  337. Result = errno;
  338. if ((Result != EXDEV) &&
  339. ((Result != EEXIST) && ((Options & MV_OPTION_FORCE) == 0))) {
  340. SwPrintError(Result, Argument, "Could not move");
  341. goto MoveEnd;
  342. }
  343. //
  344. // Stat the source.
  345. //
  346. Result = SwStat(Argument, FALSE, &Stat);
  347. if (Result != 0) {
  348. Result = errno;
  349. SwPrintError(Result, Argument, "Could not stat");
  350. goto MoveEnd;
  351. }
  352. //
  353. // There's more work to be done if the destination exists.
  354. //
  355. if (TargetStatResult == 0) {
  356. //
  357. // If the destination is a directory and the source is not or vice
  358. // versa, fail.
  359. //
  360. if ((S_ISDIR(TargetStat.st_mode)) != (S_ISDIR(Stat.st_mode))) {
  361. Result = ENOTDIR;
  362. if (S_ISDIR(TargetStat.st_mode)) {
  363. Result = EISDIR;
  364. }
  365. SwPrintError(Result, Target, "Could not move to target");
  366. goto MoveEnd;
  367. }
  368. //
  369. // Try to remove the destination.
  370. //
  371. if (S_ISDIR(TargetStat.st_mode)) {
  372. Result = SwRemoveDirectory(Target);
  373. } else {
  374. Result = SwUnlink(Target);
  375. }
  376. if (Result != 0) {
  377. Result = errno;
  378. SwPrintError(Result, Target, "Could not remove target");
  379. goto MoveEnd;
  380. }
  381. //
  382. // Try rename one more time just for fun.
  383. //
  384. Result = rename(Argument, Target);
  385. if (Result == 0) {
  386. goto MoveEnd;
  387. }
  388. }
  389. //
  390. // Attempt to duplicate the file hierarchy, then delete the old file
  391. // hierarchy.
  392. //
  393. CopyOptions = COPY_OPTION_FOLLOW_OPERAND_LINKS |
  394. COPY_OPTION_PRESERVE_PERMISSIONS |
  395. COPY_OPTION_RECURSIVE;
  396. Result = SwCopy(CopyOptions, Argument, Target);
  397. if (Result != 0) {
  398. QuotedArgument = SwQuoteArgument(Argument);
  399. SwPrintError(Result, Target, "Failed to copy '%s' to", QuotedArgument);
  400. goto MoveEnd;
  401. }
  402. Result = SwDelete(DELETE_OPTION_FORCE | DELETE_OPTION_RECURSIVE, Argument);
  403. if (Result != 0) {
  404. SwPrintError(Result, Argument, "Failed to remove");
  405. goto MoveEnd;
  406. }
  407. MoveEnd:
  408. return Result;
  409. }