1 1.1 jmmv // Copyright 2010 Google Inc. 2 1.1 jmmv // All rights reserved. 3 1.1 jmmv // 4 1.1 jmmv // Redistribution and use in source and binary forms, with or without 5 1.1 jmmv // modification, are permitted provided that the following conditions are 6 1.1 jmmv // met: 7 1.1 jmmv // 8 1.1 jmmv // * Redistributions of source code must retain the above copyright 9 1.1 jmmv // notice, this list of conditions and the following disclaimer. 10 1.1 jmmv // * Redistributions in binary form must reproduce the above copyright 11 1.1 jmmv // notice, this list of conditions and the following disclaimer in the 12 1.1 jmmv // documentation and/or other materials provided with the distribution. 13 1.1 jmmv // * Neither the name of Google Inc. nor the names of its contributors 14 1.1 jmmv // may be used to endorse or promote products derived from this software 15 1.1 jmmv // without specific prior written permission. 16 1.1 jmmv // 17 1.1 jmmv // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 1.1 jmmv // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 1.1 jmmv // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 1.1 jmmv // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 1.1 jmmv // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 1.1 jmmv // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 1.1 jmmv // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 1.1 jmmv // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 1.1 jmmv // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 1.1 jmmv // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 1.1 jmmv // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 1.1 jmmv 29 1.1 jmmv #include "utils/process/child.ipp" 30 1.1 jmmv 31 1.1 jmmv extern "C" { 32 1.1 jmmv #include <sys/stat.h> 33 1.1 jmmv #include <sys/wait.h> 34 1.1 jmmv 35 1.1 jmmv #include <fcntl.h> 36 1.1 jmmv #include <signal.h> 37 1.1 jmmv #include <unistd.h> 38 1.1 jmmv } 39 1.1 jmmv 40 1.1 jmmv #include <cassert> 41 1.1 jmmv #include <cerrno> 42 1.1 jmmv #include <cstdlib> 43 1.1 jmmv #include <cstring> 44 1.1 jmmv #include <iostream> 45 1.1 jmmv #include <memory> 46 1.1 jmmv 47 1.1 jmmv #include "utils/defs.hpp" 48 1.1 jmmv #include "utils/format/macros.hpp" 49 1.1 jmmv #include "utils/logging/macros.hpp" 50 1.1 jmmv #include "utils/process/exceptions.hpp" 51 1.1 jmmv #include "utils/process/fdstream.hpp" 52 1.1 jmmv #include "utils/process/system.hpp" 53 1.1 jmmv #include "utils/process/status.hpp" 54 1.1 jmmv #include "utils/sanity.hpp" 55 1.1 jmmv #include "utils/signals/interrupts.hpp" 56 1.1 jmmv 57 1.1 jmmv 58 1.1 jmmv namespace utils { 59 1.1 jmmv namespace process { 60 1.1 jmmv 61 1.1 jmmv 62 1.1 jmmv /// Private implementation fields for child objects. 63 1.1 jmmv struct child::impl { 64 1.1 jmmv /// The process identifier. 65 1.1 jmmv pid_t _pid; 66 1.1 jmmv 67 1.1 jmmv /// The input stream for the process' stdout and stderr. May be NULL. 68 1.2 lukem std::unique_ptr< process::ifdstream > _output; 69 1.1 jmmv 70 1.1 jmmv /// Initializes private implementation data. 71 1.1 jmmv /// 72 1.1 jmmv /// \param pid The process identifier. 73 1.1 jmmv /// \param output The input stream. Grabs ownership of the pointer. 74 1.1 jmmv impl(const pid_t pid, process::ifdstream* output) : 75 1.1 jmmv _pid(pid), _output(output) {} 76 1.1 jmmv }; 77 1.1 jmmv 78 1.1 jmmv 79 1.1 jmmv } // namespace process 80 1.1 jmmv } // namespace utils 81 1.1 jmmv 82 1.1 jmmv 83 1.1 jmmv namespace fs = utils::fs; 84 1.1 jmmv namespace process = utils::process; 85 1.1 jmmv namespace signals = utils::signals; 86 1.1 jmmv 87 1.1 jmmv 88 1.1 jmmv namespace { 89 1.1 jmmv 90 1.1 jmmv 91 1.1 jmmv /// Exception-based version of dup(2). 92 1.1 jmmv /// 93 1.1 jmmv /// \param old_fd The file descriptor to duplicate. 94 1.1 jmmv /// \param new_fd The file descriptor to use as the duplicate. This is 95 1.1 jmmv /// closed if it was open before the copy happens. 96 1.1 jmmv /// 97 1.1 jmmv /// \throw process::system_error If the call to dup2(2) fails. 98 1.1 jmmv static void 99 1.1 jmmv safe_dup(const int old_fd, const int new_fd) 100 1.1 jmmv { 101 1.1 jmmv if (process::detail::syscall_dup2(old_fd, new_fd) == -1) { 102 1.1 jmmv const int original_errno = errno; 103 1.1 jmmv throw process::system_error(F("dup2(%s, %s) failed") % old_fd % new_fd, 104 1.1 jmmv original_errno); 105 1.1 jmmv } 106 1.1 jmmv } 107 1.1 jmmv 108 1.1 jmmv 109 1.1 jmmv /// Exception-based version of open(2) to open (or create) a file for append. 110 1.1 jmmv /// 111 1.1 jmmv /// \param filename The file to open in append mode. 112 1.1 jmmv /// 113 1.1 jmmv /// \return The file descriptor for the opened or created file. 114 1.1 jmmv /// 115 1.1 jmmv /// \throw process::system_error If the call to open(2) fails. 116 1.1 jmmv static int 117 1.1 jmmv open_for_append(const fs::path& filename) 118 1.1 jmmv { 119 1.1 jmmv const int fd = process::detail::syscall_open( 120 1.1 jmmv filename.c_str(), O_CREAT | O_WRONLY | O_APPEND, 121 1.1 jmmv S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); 122 1.1 jmmv if (fd == -1) { 123 1.1 jmmv const int original_errno = errno; 124 1.1 jmmv throw process::system_error(F("Failed to create %s because open(2) " 125 1.1 jmmv "failed") % filename, original_errno); 126 1.1 jmmv } 127 1.1 jmmv return fd; 128 1.1 jmmv } 129 1.1 jmmv 130 1.1 jmmv 131 1.1 jmmv /// Exception-based, type-improved version of wait(2). 132 1.1 jmmv /// 133 1.1 jmmv /// Because we are waiting for the termination of a process, and because this is 134 1.1 jmmv /// the canonical way to call wait(2) for this module, we ensure from here that 135 1.1 jmmv /// any subprocess of the process we are killing is terminated. 136 1.1 jmmv /// 137 1.1 jmmv /// \param pid The identifier of the process to wait for. 138 1.1 jmmv /// 139 1.1 jmmv /// \return The termination status of the process. 140 1.1 jmmv /// 141 1.1 jmmv /// \throw process::system_error If the call to waitpid(2) fails. 142 1.1 jmmv static process::status 143 1.1 jmmv safe_wait(const pid_t pid) 144 1.1 jmmv { 145 1.1 jmmv LD(F("Waiting for pid=%s") % pid); 146 1.1 jmmv int stat_loc; 147 1.1 jmmv if (process::detail::syscall_waitpid(pid, &stat_loc, 0) == -1) { 148 1.1 jmmv const int original_errno = errno; 149 1.1 jmmv throw process::system_error(F("Failed to wait for PID %s") % pid, 150 1.1 jmmv original_errno); 151 1.1 jmmv } 152 1.1 jmmv return process::status(pid, stat_loc); 153 1.1 jmmv } 154 1.1 jmmv 155 1.1 jmmv 156 1.1 jmmv /// Logs the execution of another program. 157 1.1 jmmv /// 158 1.1 jmmv /// \param program The binary to execute. 159 1.1 jmmv /// \param args The arguments to pass to the binary, without the program name. 160 1.1 jmmv static void 161 1.1 jmmv log_exec(const fs::path& program, const process::args_vector& args) 162 1.1 jmmv { 163 1.1 jmmv std::string plain_command = program.str(); 164 1.1 jmmv for (process::args_vector::const_iterator iter = args.begin(); 165 1.1 jmmv iter != args.end(); ++iter) 166 1.1 jmmv plain_command += F(" %s") % *iter; 167 1.2 lukem LD(F("Executing %s") % plain_command); 168 1.1 jmmv } 169 1.1 jmmv 170 1.1 jmmv 171 1.1 jmmv /// Maximum number of arguments supported by cxx_exec. 172 1.1 jmmv /// 173 1.1 jmmv /// We need this limit to avoid having to allocate dynamic memory in the child 174 1.1 jmmv /// process to construct the arguments list, which would have side-effects in 175 1.1 jmmv /// the parent's memory if we use vfork(). 176 1.1 jmmv #define MAX_ARGS 128 177 1.1 jmmv 178 1.1 jmmv 179 1.1 jmmv static void cxx_exec(const fs::path& program, const process::args_vector& args) 180 1.1 jmmv throw() UTILS_NORETURN; 181 1.1 jmmv 182 1.1 jmmv 183 1.1 jmmv /// Executes an external binary and replaces the current process. 184 1.1 jmmv /// 185 1.1 jmmv /// This function must not use any of the logging features, so that the output 186 1.1 jmmv /// of the subprocess is not "polluted" by our own messages. 187 1.1 jmmv /// 188 1.1 jmmv /// This function must also not affect the global state of the current process 189 1.1 jmmv /// as otherwise we would not be able to use vfork(). Only state stored in the 190 1.1 jmmv /// stack can be touched. 191 1.1 jmmv /// 192 1.1 jmmv /// \param program The binary to execute. 193 1.1 jmmv /// \param args The arguments to pass to the binary, without the program name. 194 1.1 jmmv static void 195 1.1 jmmv cxx_exec(const fs::path& program, const process::args_vector& args) throw() 196 1.1 jmmv { 197 1.1 jmmv assert(args.size() < MAX_ARGS); 198 1.1 jmmv try { 199 1.1 jmmv const char* argv[MAX_ARGS + 1]; 200 1.1 jmmv 201 1.1 jmmv argv[0] = program.c_str(); 202 1.1 jmmv for (process::args_vector::size_type i = 0; i < args.size(); i++) 203 1.1 jmmv argv[1 + i] = args[i].c_str(); 204 1.1 jmmv argv[1 + args.size()] = NULL; 205 1.1 jmmv 206 1.1 jmmv const int ret = ::execv(program.c_str(), 207 1.1 jmmv (char* const*)(unsigned long)(const void*)argv); 208 1.1 jmmv const int original_errno = errno; 209 1.1 jmmv assert(ret == -1); 210 1.1 jmmv 211 1.1 jmmv std::cerr << "Failed to execute " << program << ": " 212 1.1 jmmv << std::strerror(original_errno) << "\n"; 213 1.1 jmmv std::abort(); 214 1.1 jmmv } catch (const std::runtime_error& error) { 215 1.1 jmmv std::cerr << "Failed to execute " << program << ": " 216 1.1 jmmv << error.what() << "\n"; 217 1.1 jmmv std::abort(); 218 1.1 jmmv } catch (...) { 219 1.1 jmmv std::cerr << "Failed to execute " << program << "; got unexpected " 220 1.1 jmmv "exception during exec\n"; 221 1.1 jmmv std::abort(); 222 1.1 jmmv } 223 1.1 jmmv } 224 1.1 jmmv 225 1.1 jmmv 226 1.1 jmmv } // anonymous namespace 227 1.1 jmmv 228 1.1 jmmv 229 1.1 jmmv /// Creates a new child. 230 1.1 jmmv /// 231 1.1 jmmv /// \param implptr A dynamically-allocated impl object with the contents of the 232 1.1 jmmv /// new child. 233 1.1 jmmv process::child::child(impl *implptr) : 234 1.1 jmmv _pimpl(implptr) 235 1.1 jmmv { 236 1.1 jmmv } 237 1.1 jmmv 238 1.1 jmmv 239 1.1 jmmv /// Destructor for child. 240 1.1 jmmv process::child::~child(void) 241 1.1 jmmv { 242 1.1 jmmv } 243 1.1 jmmv 244 1.1 jmmv 245 1.1 jmmv /// Helper function for fork(). 246 1.1 jmmv /// 247 1.1 jmmv /// Please note: if you update this function to change the return type or to 248 1.1 jmmv /// raise different errors, do not forget to update fork() accordingly. 249 1.1 jmmv /// 250 1.1 jmmv /// \return In the case of the parent, a new child object returned as a 251 1.1 jmmv /// dynamically-allocated object because children classes are unique and thus 252 1.1 jmmv /// noncopyable. In the case of the child, a NULL pointer. 253 1.1 jmmv /// 254 1.1 jmmv /// \throw process::system_error If the calls to pipe(2) or fork(2) fail. 255 1.2 lukem std::unique_ptr< process::child > 256 1.1 jmmv process::child::fork_capture_aux(void) 257 1.1 jmmv { 258 1.1 jmmv std::cout.flush(); 259 1.1 jmmv std::cerr.flush(); 260 1.1 jmmv 261 1.1 jmmv int fds[2]; 262 1.1 jmmv if (detail::syscall_pipe(fds) == -1) 263 1.1 jmmv throw process::system_error("pipe(2) failed", errno); 264 1.1 jmmv 265 1.2 lukem std::unique_ptr< signals::interrupts_inhibiter > inhibiter( 266 1.1 jmmv new signals::interrupts_inhibiter); 267 1.1 jmmv pid_t pid = detail::syscall_fork(); 268 1.1 jmmv if (pid == -1) { 269 1.1 jmmv inhibiter.reset(NULL); // Unblock signals. 270 1.1 jmmv ::close(fds[0]); 271 1.1 jmmv ::close(fds[1]); 272 1.1 jmmv throw process::system_error("fork(2) failed", errno); 273 1.1 jmmv } else if (pid == 0) { 274 1.1 jmmv inhibiter.reset(NULL); // Unblock signals. 275 1.1 jmmv ::setpgid(::getpid(), ::getpid()); 276 1.1 jmmv 277 1.1 jmmv try { 278 1.1 jmmv ::close(fds[0]); 279 1.1 jmmv safe_dup(fds[1], STDOUT_FILENO); 280 1.1 jmmv safe_dup(fds[1], STDERR_FILENO); 281 1.1 jmmv ::close(fds[1]); 282 1.1 jmmv } catch (const system_error& e) { 283 1.1 jmmv std::cerr << F("Failed to set up subprocess: %s\n") % e.what(); 284 1.1 jmmv std::abort(); 285 1.1 jmmv } 286 1.2 lukem return std::unique_ptr< process::child >(); 287 1.1 jmmv } else { 288 1.1 jmmv ::close(fds[1]); 289 1.1 jmmv LD(F("Spawned process %s: stdout and stderr inherited") % pid); 290 1.1 jmmv signals::add_pid_to_kill(pid); 291 1.1 jmmv inhibiter.reset(NULL); // Unblock signals. 292 1.2 lukem return std::unique_ptr< process::child >( 293 1.1 jmmv new process::child(new impl(pid, new process::ifdstream(fds[0])))); 294 1.1 jmmv } 295 1.1 jmmv } 296 1.1 jmmv 297 1.1 jmmv 298 1.1 jmmv /// Helper function for fork(). 299 1.1 jmmv /// 300 1.1 jmmv /// Please note: if you update this function to change the return type or to 301 1.1 jmmv /// raise different errors, do not forget to update fork() accordingly. 302 1.1 jmmv /// 303 1.1 jmmv /// \param stdout_file The name of the file in which to store the stdout. 304 1.1 jmmv /// If this has the magic value /dev/stdout, then the parent's stdout is 305 1.1 jmmv /// reused without applying any redirection. 306 1.1 jmmv /// \param stderr_file The name of the file in which to store the stderr. 307 1.1 jmmv /// If this has the magic value /dev/stderr, then the parent's stderr is 308 1.1 jmmv /// reused without applying any redirection. 309 1.1 jmmv /// 310 1.1 jmmv /// \return In the case of the parent, a new child object returned as a 311 1.1 jmmv /// dynamically-allocated object because children classes are unique and thus 312 1.1 jmmv /// noncopyable. In the case of the child, a NULL pointer. 313 1.1 jmmv /// 314 1.1 jmmv /// \throw process::system_error If the call to fork(2) fails. 315 1.2 lukem std::unique_ptr< process::child > 316 1.1 jmmv process::child::fork_files_aux(const fs::path& stdout_file, 317 1.1 jmmv const fs::path& stderr_file) 318 1.1 jmmv { 319 1.1 jmmv std::cout.flush(); 320 1.1 jmmv std::cerr.flush(); 321 1.1 jmmv 322 1.2 lukem std::unique_ptr< signals::interrupts_inhibiter > inhibiter( 323 1.1 jmmv new signals::interrupts_inhibiter); 324 1.1 jmmv pid_t pid = detail::syscall_fork(); 325 1.1 jmmv if (pid == -1) { 326 1.1 jmmv inhibiter.reset(NULL); // Unblock signals. 327 1.1 jmmv throw process::system_error("fork(2) failed", errno); 328 1.1 jmmv } else if (pid == 0) { 329 1.1 jmmv inhibiter.reset(NULL); // Unblock signals. 330 1.1 jmmv ::setpgid(::getpid(), ::getpid()); 331 1.1 jmmv 332 1.1 jmmv try { 333 1.1 jmmv if (stdout_file != fs::path("/dev/stdout")) { 334 1.1 jmmv const int stdout_fd = open_for_append(stdout_file); 335 1.1 jmmv safe_dup(stdout_fd, STDOUT_FILENO); 336 1.1 jmmv ::close(stdout_fd); 337 1.1 jmmv } 338 1.1 jmmv if (stderr_file != fs::path("/dev/stderr")) { 339 1.1 jmmv const int stderr_fd = open_for_append(stderr_file); 340 1.1 jmmv safe_dup(stderr_fd, STDERR_FILENO); 341 1.1 jmmv ::close(stderr_fd); 342 1.1 jmmv } 343 1.1 jmmv } catch (const system_error& e) { 344 1.1 jmmv std::cerr << F("Failed to set up subprocess: %s\n") % e.what(); 345 1.1 jmmv std::abort(); 346 1.1 jmmv } 347 1.2 lukem return std::unique_ptr< process::child >(); 348 1.1 jmmv } else { 349 1.1 jmmv LD(F("Spawned process %s: stdout=%s, stderr=%s") % pid % stdout_file % 350 1.1 jmmv stderr_file); 351 1.1 jmmv signals::add_pid_to_kill(pid); 352 1.1 jmmv inhibiter.reset(NULL); // Unblock signals. 353 1.2 lukem return std::unique_ptr< process::child >( 354 1.1 jmmv new process::child(new impl(pid, NULL))); 355 1.1 jmmv } 356 1.1 jmmv } 357 1.1 jmmv 358 1.1 jmmv 359 1.1 jmmv /// Spawns a new binary and multiplexes and captures its stdout and stderr. 360 1.1 jmmv /// 361 1.1 jmmv /// If the subprocess cannot be completely set up for any reason, it attempts to 362 1.1 jmmv /// dump an error message to its stderr channel and it then calls std::abort(). 363 1.1 jmmv /// 364 1.1 jmmv /// \param program The binary to execute. 365 1.1 jmmv /// \param args The arguments to pass to the binary, without the program name. 366 1.1 jmmv /// 367 1.1 jmmv /// \return A new child object, returned as a dynamically-allocated object 368 1.1 jmmv /// because children classes are unique and thus noncopyable. 369 1.1 jmmv /// 370 1.1 jmmv /// \throw process::system_error If the process cannot be spawned due to a 371 1.1 jmmv /// system call error. 372 1.2 lukem std::unique_ptr< process::child > 373 1.1 jmmv process::child::spawn_capture(const fs::path& program, const args_vector& args) 374 1.1 jmmv { 375 1.2 lukem std::unique_ptr< child > child = fork_capture_aux(); 376 1.1 jmmv if (child.get() == NULL) 377 1.1 jmmv cxx_exec(program, args); 378 1.1 jmmv log_exec(program, args); 379 1.1 jmmv return child; 380 1.1 jmmv } 381 1.1 jmmv 382 1.1 jmmv 383 1.1 jmmv /// Spawns a new binary and redirects its stdout and stderr to files. 384 1.1 jmmv /// 385 1.1 jmmv /// If the subprocess cannot be completely set up for any reason, it attempts to 386 1.1 jmmv /// dump an error message to its stderr channel and it then calls std::abort(). 387 1.1 jmmv /// 388 1.1 jmmv /// \param program The binary to execute. 389 1.1 jmmv /// \param args The arguments to pass to the binary, without the program name. 390 1.1 jmmv /// \param stdout_file The name of the file in which to store the stdout. 391 1.1 jmmv /// \param stderr_file The name of the file in which to store the stderr. 392 1.1 jmmv /// 393 1.1 jmmv /// \return A new child object, returned as a dynamically-allocated object 394 1.1 jmmv /// because children classes are unique and thus noncopyable. 395 1.1 jmmv /// 396 1.1 jmmv /// \throw process::system_error If the process cannot be spawned due to a 397 1.1 jmmv /// system call error. 398 1.2 lukem std::unique_ptr< process::child > 399 1.1 jmmv process::child::spawn_files(const fs::path& program, 400 1.1 jmmv const args_vector& args, 401 1.1 jmmv const fs::path& stdout_file, 402 1.1 jmmv const fs::path& stderr_file) 403 1.1 jmmv { 404 1.2 lukem std::unique_ptr< child > child = fork_files_aux(stdout_file, stderr_file); 405 1.1 jmmv if (child.get() == NULL) 406 1.1 jmmv cxx_exec(program, args); 407 1.1 jmmv log_exec(program, args); 408 1.1 jmmv return child; 409 1.1 jmmv } 410 1.1 jmmv 411 1.1 jmmv 412 1.1 jmmv /// Returns the process identifier of this child. 413 1.1 jmmv /// 414 1.1 jmmv /// \return A process identifier. 415 1.1 jmmv int 416 1.1 jmmv process::child::pid(void) const 417 1.1 jmmv { 418 1.1 jmmv return _pimpl->_pid; 419 1.1 jmmv } 420 1.1 jmmv 421 1.1 jmmv 422 1.1 jmmv /// Gets the input stream corresponding to the stdout and stderr of the child. 423 1.1 jmmv /// 424 1.1 jmmv /// \pre The child must have been started by fork_capture(). 425 1.1 jmmv /// 426 1.1 jmmv /// \return A reference to the input stream connected to the output of the test 427 1.1 jmmv /// case. 428 1.1 jmmv std::istream& 429 1.1 jmmv process::child::output(void) 430 1.1 jmmv { 431 1.1 jmmv PRE(_pimpl->_output.get() != NULL); 432 1.1 jmmv return *_pimpl->_output; 433 1.1 jmmv } 434 1.1 jmmv 435 1.1 jmmv 436 1.1 jmmv /// Blocks to wait for completion. 437 1.1 jmmv /// 438 1.1 jmmv /// \return The termination status of the child process. 439 1.1 jmmv /// 440 1.1 jmmv /// \throw process::system_error If the call to waitpid(2) fails. 441 1.1 jmmv process::status 442 1.1 jmmv process::child::wait(void) 443 1.1 jmmv { 444 1.1 jmmv const process::status status = safe_wait(_pimpl->_pid); 445 1.1 jmmv { 446 1.1 jmmv signals::interrupts_inhibiter inhibiter; 447 1.1 jmmv signals::remove_pid_to_kill(_pimpl->_pid); 448 1.1 jmmv } 449 1.1 jmmv return status; 450 1.1 jmmv } 451