1########################################################################
2##
3## Copyright (C) 2005-2021 The Octave Project Developers
4##
5## See the file COPYRIGHT.md in the top-level directory of this
6## distribution or <https://octave.org/copyright/>.
7##
8## This file is part of Octave.
9##
10## Octave is free software: you can redistribute it and/or modify it
11## under the terms of the GNU General Public License as published by
12## the Free Software Foundation, either version 3 of the License, or
13## (at your option) any later version.
14##
15## Octave is distributed in the hope that it will be useful, but
16## WITHOUT ANY WARRANTY; without even the implied warranty of
17## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18## GNU General Public License for more details.
19##
20## You should have received a copy of the GNU General Public License
21## along with Octave; see the file COPYING.  If not, see
22## <https://www.gnu.org/licenses/>.
23##
24########################################################################
25
26## -*- texinfo -*-
27## @deftypefn  {} {} copyfile @var{f1} @var{f2}
28## @deftypefnx {} {} copyfile @var{f1} @var{f2} f
29## @deftypefnx {} {} copyfile (@var{f1}, @var{f2})
30## @deftypefnx {} {} copyfile (@var{f1}, @var{f2}, 'f')
31## @deftypefnx {} {[@var{status}, @var{msg}, @var{msgid}] =} copyfile (@dots{})
32## Copy the source file(s) or directory @var{f1} to the destination @var{f2}.
33##
34## The name @var{f1} may contain globbing patterns, or may be a cell array of
35## strings.  If @var{f1} expands to multiple filenames, @var{f2} must be a
36## directory.
37##
38## When the force flag @qcode{'f'} is given any existing files will be
39## overwritten without prompting.
40##
41## If successful, @var{status} is 1, and @var{msg}, @var{msgid} are empty
42## character strings ("").  Otherwise, @var{status} is 0, @var{msg} contains a
43## system-dependent error message, and @var{msgid} contains a unique message
44## identifier.  Note that the status code is exactly opposite that of the
45## @code{system} command.
46## @seealso{movefile, rename, unlink, delete, glob}
47## @end deftypefn
48
49function [status, msg, msgid] = copyfile (f1, f2, force)
50
51  if (nargin < 2 || nargin > 3)
52    print_usage ();
53  endif
54
55  max_cmd_line = 1024;
56  status = true;
57  msg = "";
58  msgid = "";
59
60  ## FIXME: Maybe use the same method as in ls to allow users control
61  ##        over the command that is executed.
62
63  if (ispc () && ! isunix ()
64      && isempty (file_in_path (getenv ("PATH"), "cp.exe")))
65    ## Windows.
66    cmd = "cmd /C xcopy /E";
67    cmd_force_flag = "/Y";
68  else
69    cmd = "cp -r";
70    cmd_force_flag = "-f";
71  endif
72
73  ## Input type check.
74  if (ischar (f1))
75    f1 = cellstr (f1);
76  elseif (! iscellstr (f1))
77    error ("copyfile: F1 must be a string or a cell array of strings");
78  endif
79  if (! ischar (f2))
80    error ("copyfile: F2 must be a string");
81  endif
82
83  if (nargin == 3 && strcmp (force, "f"))
84    cmd = [cmd " " cmd_force_flag];
85  endif
86
87  ## If f1 has more than 1 element then f2 must be a directory
88  isdir = isfolder (f2);
89  if (numel (f1) > 1 && ! isdir)
90    error ("copyfile: when copying multiple files, F2 must be a directory");
91  endif
92
93  ## Protect the filename(s).
94  f1 = glob (f1);
95  if (isempty (f1))
96    error ("copyfile: no files to move");
97  endif
98  p1 = sprintf ('"%s" ', f1{:});
99  p2 = tilde_expand (f2);
100
101  if (isdir && length (p1) > max_cmd_line)
102    l2 = length (p2) + length (cmd) + 6;
103    while (! isempty (f1))
104      p1 = sprintf ('"%s" ', f1{1});
105      f1(1) = [];
106      while (! isempty (f1)
107             && (length (p1) + length (f1{1}) + l2 < max_cmd_line))
108        p1 = sprintf ('%s"%s" ', p1, f1{1});
109        f1(1) = [];
110      endwhile
111
112      if (ispc () && ! isunix ()
113          && ! isempty (file_in_path (getenv ("PATH"), "cp.exe")))
114        p1 = strrep (p1, '\', '/');
115        p2 = strrep (p2, '\', '/');
116      endif
117
118      ## Copy the files.
119      [err, msg] = system (sprintf ('%s %s"%s"', cmd, p1, p2));
120      if (err != 0)
121        status = false;
122        msgid = "copyfile";
123        break;
124      endif
125    endwhile
126  else
127    if (ispc () && ! isunix ()
128        && ! isempty (file_in_path (getenv ("PATH"), "cp.exe")))
129      p1 = strrep (p1, '\', '/');
130      p2 = strrep (p2, '\', '/');
131    endif
132
133    ## Copy the files.
134    [err, msg] = system (sprintf ('%s %s"%s"', cmd, p1, p2));
135    if (err != 0)
136      status = false;
137      msgid = "copyfile";
138    endif
139  endif
140
141endfunction
142
143
144%!test
145%! unwind_protect
146%!   f1 = tempname;
147%!   tmp_var = pi;
148%!   save (f1, "tmp_var");
149%!   f2 = tempname;
150%!   assert (copyfile (f1, f2));
151%!   assert (exist (f2, "file"));
152%!   fid = fopen (f1, "rb");
153%!   assert (fid >= 0);
154%!   orig_data = fread (fid);
155%!   fclose (fid);
156%!   fid = fopen (f2, "rb");
157%!   assert (fid >= 0);
158%!   new_data = fread (fid);
159%!   fclose (fid);
160%!   if (orig_data != new_data)
161%!     error ("copied file not equal to original file!");
162%!   endif
163%! unwind_protect_cleanup
164%!   delete (f1);
165%!   delete (f2);
166%! end_unwind_protect
167
168## Test input validation
169%!error copyfile ()
170%!error copyfile (1)
171%!error copyfile (1,2,3,4)
172%!error <F1 must be a string> copyfile (1, "foobar")
173%!error <F2 must be a string> copyfile ("foobar", 1)
174%!error <F2 must be a directory> copyfile ({"a", "b"}, "%_NOT_A_DIR_%")
175