UNPKG

9.58 kBJavaScriptView Raw
1var fs = require('fs');
2var path = require('path');
3var common = require('./common');
4
5common.register('cp', _cp, {
6 cmdOptions: {
7 'f': '!no_force',
8 'n': 'no_force',
9 'u': 'update',
10 'R': 'recursive',
11 'r': 'recursive',
12 'L': 'followsymlink',
13 'P': 'noFollowsymlink',
14 },
15 wrapOutput: false,
16});
17
18// Buffered file copy, synchronous
19// (Using readFileSync() + writeFileSync() could easily cause a memory overflow
20// with large files)
21function copyFileSync(srcFile, destFile, options) {
22 if (!fs.existsSync(srcFile)) {
23 common.error('copyFileSync: no such file or directory: ' + srcFile);
24 }
25
26 var isWindows = process.platform === 'win32';
27
28 // Check the mtimes of the files if the '-u' flag is provided
29 try {
30 if (options.update && common.statFollowLinks(srcFile).mtime < fs.statSync(destFile).mtime) {
31 return;
32 }
33 } catch (e) {
34 // If we're here, destFile probably doesn't exist, so just do a normal copy
35 }
36
37 if (common.statNoFollowLinks(srcFile).isSymbolicLink() && !options.followsymlink) {
38 try {
39 common.statNoFollowLinks(destFile);
40 common.unlinkSync(destFile); // re-link it
41 } catch (e) {
42 // it doesn't exist, so no work needs to be done
43 }
44
45 var symlinkFull = fs.readlinkSync(srcFile);
46 fs.symlinkSync(symlinkFull, destFile, isWindows ? 'junction' : null);
47 } else {
48 var buf = common.buffer();
49 var bufLength = buf.length;
50 var bytesRead = bufLength;
51 var pos = 0;
52 var fdr = null;
53 var fdw = null;
54
55 try {
56 fdr = fs.openSync(srcFile, 'r');
57 } catch (e) {
58 /* istanbul ignore next */
59 common.error('copyFileSync: could not read src file (' + srcFile + ')');
60 }
61
62 try {
63 fdw = fs.openSync(destFile, 'w');
64 } catch (e) {
65 /* istanbul ignore next */
66 common.error('copyFileSync: could not write to dest file (code=' + e.code + '):' + destFile);
67 }
68
69 while (bytesRead === bufLength) {
70 bytesRead = fs.readSync(fdr, buf, 0, bufLength, pos);
71 fs.writeSync(fdw, buf, 0, bytesRead);
72 pos += bytesRead;
73 }
74
75 fs.closeSync(fdr);
76 fs.closeSync(fdw);
77
78 fs.chmodSync(destFile, common.statFollowLinks(srcFile).mode);
79 }
80}
81
82// Recursively copies 'sourceDir' into 'destDir'
83// Adapted from https://github.com/ryanmcgrath/wrench-js
84//
85// Copyright (c) 2010 Ryan McGrath
86// Copyright (c) 2012 Artur Adib
87//
88// Licensed under the MIT License
89// http://www.opensource.org/licenses/mit-license.php
90function cpdirSyncRecursive(sourceDir, destDir, currentDepth, opts) {
91 if (!opts) opts = {};
92
93 // Ensure there is not a run away recursive copy
94 if (currentDepth >= common.config.maxdepth) return;
95 currentDepth++;
96
97 var isWindows = process.platform === 'win32';
98
99 // Create the directory where all our junk is moving to; read the mode of the
100 // source directory and mirror it
101 try {
102 var checkDir = common.statFollowLinks(sourceDir);
103 fs.mkdirSync(destDir, checkDir.mode);
104 } catch (e) {
105 // if the directory already exists, that's okay
106 if (e.code !== 'EEXIST') throw e;
107 }
108
109 var files = fs.readdirSync(sourceDir);
110
111 for (var i = 0; i < files.length; i++) {
112 var srcFile = sourceDir + '/' + files[i];
113 var destFile = destDir + '/' + files[i];
114 var srcFileStat = common.statNoFollowLinks(srcFile);
115
116 var symlinkFull;
117 if (opts.followsymlink) {
118 if (cpcheckcycle(sourceDir, srcFile)) {
119 // Cycle link found.
120 console.error('Cycle link found.');
121 symlinkFull = fs.readlinkSync(srcFile);
122 fs.symlinkSync(symlinkFull, destFile, isWindows ? 'junction' : null);
123 continue;
124 }
125 }
126 if (srcFileStat.isDirectory()) {
127 /* recursion this thing right on back. */
128 cpdirSyncRecursive(srcFile, destFile, currentDepth, opts);
129 } else if (srcFileStat.isSymbolicLink() && !opts.followsymlink) {
130 symlinkFull = fs.readlinkSync(srcFile);
131 try {
132 common.statNoFollowLinks(destFile);
133 common.unlinkSync(destFile); // re-link it
134 } catch (e) {
135 // it doesn't exist, so no work needs to be done
136 }
137 fs.symlinkSync(symlinkFull, destFile, isWindows ? 'junction' : null);
138 } else if (srcFileStat.isSymbolicLink() && opts.followsymlink) {
139 srcFileStat = common.statFollowLinks(srcFile);
140 if (srcFileStat.isDirectory()) {
141 cpdirSyncRecursive(srcFile, destFile, currentDepth, opts);
142 } else {
143 copyFileSync(srcFile, destFile, opts);
144 }
145 } else {
146 /* At this point, we've hit a file actually worth copying... so copy it on over. */
147 if (fs.existsSync(destFile) && opts.no_force) {
148 common.log('skipping existing file: ' + files[i]);
149 } else {
150 copyFileSync(srcFile, destFile, opts);
151 }
152 }
153 } // for files
154} // cpdirSyncRecursive
155
156// Checks if cureent file was created recently
157function checkRecentCreated(sources, index) {
158 var lookedSource = sources[index];
159 return sources.slice(0, index).some(function (src) {
160 return path.basename(src) === path.basename(lookedSource);
161 });
162}
163
164function cpcheckcycle(sourceDir, srcFile) {
165 var srcFileStat = common.statNoFollowLinks(srcFile);
166 if (srcFileStat.isSymbolicLink()) {
167 // Do cycle check. For example:
168 // $ mkdir -p 1/2/3/4
169 // $ cd 1/2/3/4
170 // $ ln -s ../../3 link
171 // $ cd ../../../..
172 // $ cp -RL 1 copy
173 var cyclecheck = common.statFollowLinks(srcFile);
174 if (cyclecheck.isDirectory()) {
175 var sourcerealpath = fs.realpathSync(sourceDir);
176 var symlinkrealpath = fs.realpathSync(srcFile);
177 var re = new RegExp(symlinkrealpath);
178 if (re.test(sourcerealpath)) {
179 return true;
180 }
181 }
182 }
183 return false;
184}
185
186//@
187//@ ### cp([options,] source [, source ...], dest)
188//@ ### cp([options,] source_array, dest)
189//@ Available options:
190//@
191//@ + `-f`: force (default behavior)
192//@ + `-n`: no-clobber
193//@ + `-u`: only copy if source is newer than dest
194//@ + `-r`, `-R`: recursive
195//@ + `-L`: follow symlinks
196//@ + `-P`: don't follow symlinks
197//@
198//@ Examples:
199//@
200//@ ```javascript
201//@ cp('file1', 'dir1');
202//@ cp('-R', 'path/to/dir/', '~/newCopy/');
203//@ cp('-Rf', '/tmp/*', '/usr/local/*', '/home/tmp');
204//@ cp('-Rf', ['/tmp/*', '/usr/local/*'], '/home/tmp'); // same as above
205//@ ```
206//@
207//@ Copies files.
208function _cp(options, sources, dest) {
209 // If we're missing -R, it actually implies -L (unless -P is explicit)
210 if (options.followsymlink) {
211 options.noFollowsymlink = false;
212 }
213 if (!options.recursive && !options.noFollowsymlink) {
214 options.followsymlink = true;
215 }
216
217 // Get sources, dest
218 if (arguments.length < 3) {
219 common.error('missing <source> and/or <dest>');
220 } else {
221 sources = [].slice.call(arguments, 1, arguments.length - 1);
222 dest = arguments[arguments.length - 1];
223 }
224
225 var destExists = fs.existsSync(dest);
226 var destStat = destExists && common.statFollowLinks(dest);
227
228 // Dest is not existing dir, but multiple sources given
229 if ((!destExists || !destStat.isDirectory()) && sources.length > 1) {
230 common.error('dest is not a directory (too many sources)');
231 }
232
233 // Dest is an existing file, but -n is given
234 if (destExists && destStat.isFile() && options.no_force) {
235 return new common.ShellString('', '', 0);
236 }
237
238 sources.forEach(function (src, srcIndex) {
239 if (!fs.existsSync(src)) {
240 if (src === '') src = "''"; // if src was empty string, display empty string
241 common.error('no such file or directory: ' + src, { continue: true });
242 return; // skip file
243 }
244 var srcStat = common.statFollowLinks(src);
245 if (!options.noFollowsymlink && srcStat.isDirectory()) {
246 if (!options.recursive) {
247 // Non-Recursive
248 common.error("omitting directory '" + src + "'", { continue: true });
249 } else {
250 // Recursive
251 // 'cp /a/source dest' should create 'source' in 'dest'
252 var newDest = (destStat && destStat.isDirectory()) ?
253 path.join(dest, path.basename(src)) :
254 dest;
255
256 try {
257 common.statFollowLinks(path.dirname(dest));
258 cpdirSyncRecursive(src, newDest, 0, { no_force: options.no_force, followsymlink: options.followsymlink });
259 } catch (e) {
260 /* istanbul ignore next */
261 common.error("cannot create directory '" + dest + "': No such file or directory");
262 }
263 }
264 } else {
265 // If here, src is a file
266
267 // When copying to '/path/dir':
268 // thisDest = '/path/dir/file1'
269 var thisDest = dest;
270 if (destStat && destStat.isDirectory()) {
271 thisDest = path.normalize(dest + '/' + path.basename(src));
272 }
273
274 var thisDestExists = fs.existsSync(thisDest);
275 if (thisDestExists && checkRecentCreated(sources, srcIndex)) {
276 // cannot overwrite file created recently in current execution, but we want to continue copying other files
277 if (!options.no_force) {
278 common.error("will not overwrite just-created '" + thisDest + "' with '" + src + "'", { continue: true });
279 }
280 return;
281 }
282
283 if (thisDestExists && options.no_force) {
284 return; // skip file
285 }
286
287 if (path.relative(src, thisDest) === '') {
288 // a file cannot be copied to itself, but we want to continue copying other files
289 common.error("'" + thisDest + "' and '" + src + "' are the same file", { continue: true });
290 return;
291 }
292
293 copyFileSync(src, thisDest, options);
294 }
295 }); // forEach(src)
296
297 return new common.ShellString('', common.state.error, common.state.errorCode);
298}
299module.exports = _cp;