UNPKG

9.74 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 fs.mkdirSync(destDir);
103 } catch (e) {
104 // if the directory already exists, that's okay
105 if (e.code !== 'EEXIST') throw e;
106 }
107
108 var files = fs.readdirSync(sourceDir);
109
110 for (var i = 0; i < files.length; i++) {
111 var srcFile = sourceDir + '/' + files[i];
112 var destFile = destDir + '/' + files[i];
113 var srcFileStat = common.statNoFollowLinks(srcFile);
114
115 var symlinkFull;
116 if (opts.followsymlink) {
117 if (cpcheckcycle(sourceDir, srcFile)) {
118 // Cycle link found.
119 console.error('Cycle link found.');
120 symlinkFull = fs.readlinkSync(srcFile);
121 fs.symlinkSync(symlinkFull, destFile, isWindows ? 'junction' : null);
122 continue;
123 }
124 }
125 if (srcFileStat.isDirectory()) {
126 /* recursion this thing right on back. */
127 cpdirSyncRecursive(srcFile, destFile, currentDepth, opts);
128 } else if (srcFileStat.isSymbolicLink() && !opts.followsymlink) {
129 symlinkFull = fs.readlinkSync(srcFile);
130 try {
131 common.statNoFollowLinks(destFile);
132 common.unlinkSync(destFile); // re-link it
133 } catch (e) {
134 // it doesn't exist, so no work needs to be done
135 }
136 fs.symlinkSync(symlinkFull, destFile, isWindows ? 'junction' : null);
137 } else if (srcFileStat.isSymbolicLink() && opts.followsymlink) {
138 srcFileStat = common.statFollowLinks(srcFile);
139 if (srcFileStat.isDirectory()) {
140 cpdirSyncRecursive(srcFile, destFile, currentDepth, opts);
141 } else {
142 copyFileSync(srcFile, destFile, opts);
143 }
144 } else {
145 /* At this point, we've hit a file actually worth copying... so copy it on over. */
146 if (fs.existsSync(destFile) && opts.no_force) {
147 common.log('skipping existing file: ' + files[i]);
148 } else {
149 copyFileSync(srcFile, destFile, opts);
150 }
151 }
152 } // for files
153
154 // finally change the mode for the newly created directory (otherwise, we
155 // couldn't add files to a read-only directory).
156 var checkDir = common.statFollowLinks(sourceDir);
157 fs.chmodSync(destDir, checkDir.mode);
158} // cpdirSyncRecursive
159
160// Checks if cureent file was created recently
161function checkRecentCreated(sources, index) {
162 var lookedSource = sources[index];
163 return sources.slice(0, index).some(function (src) {
164 return path.basename(src) === path.basename(lookedSource);
165 });
166}
167
168function cpcheckcycle(sourceDir, srcFile) {
169 var srcFileStat = common.statNoFollowLinks(srcFile);
170 if (srcFileStat.isSymbolicLink()) {
171 // Do cycle check. For example:
172 // $ mkdir -p 1/2/3/4
173 // $ cd 1/2/3/4
174 // $ ln -s ../../3 link
175 // $ cd ../../../..
176 // $ cp -RL 1 copy
177 var cyclecheck = common.statFollowLinks(srcFile);
178 if (cyclecheck.isDirectory()) {
179 var sourcerealpath = fs.realpathSync(sourceDir);
180 var symlinkrealpath = fs.realpathSync(srcFile);
181 var re = new RegExp(symlinkrealpath);
182 if (re.test(sourcerealpath)) {
183 return true;
184 }
185 }
186 }
187 return false;
188}
189
190//@
191//@ ### cp([options,] source [, source ...], dest)
192//@ ### cp([options,] source_array, dest)
193//@
194//@ Available options:
195//@
196//@ + `-f`: force (default behavior)
197//@ + `-n`: no-clobber
198//@ + `-u`: only copy if `source` is newer than `dest`
199//@ + `-r`, `-R`: recursive
200//@ + `-L`: follow symlinks
201//@ + `-P`: don't follow symlinks
202//@
203//@ Examples:
204//@
205//@ ```javascript
206//@ cp('file1', 'dir1');
207//@ cp('-R', 'path/to/dir/', '~/newCopy/');
208//@ cp('-Rf', '/tmp/*', '/usr/local/*', '/home/tmp');
209//@ cp('-Rf', ['/tmp/*', '/usr/local/*'], '/home/tmp'); // same as above
210//@ ```
211//@
212//@ Copies files.
213function _cp(options, sources, dest) {
214 // If we're missing -R, it actually implies -L (unless -P is explicit)
215 if (options.followsymlink) {
216 options.noFollowsymlink = false;
217 }
218 if (!options.recursive && !options.noFollowsymlink) {
219 options.followsymlink = true;
220 }
221
222 // Get sources, dest
223 if (arguments.length < 3) {
224 common.error('missing <source> and/or <dest>');
225 } else {
226 sources = [].slice.call(arguments, 1, arguments.length - 1);
227 dest = arguments[arguments.length - 1];
228 }
229
230 var destExists = fs.existsSync(dest);
231 var destStat = destExists && common.statFollowLinks(dest);
232
233 // Dest is not existing dir, but multiple sources given
234 if ((!destExists || !destStat.isDirectory()) && sources.length > 1) {
235 common.error('dest is not a directory (too many sources)');
236 }
237
238 // Dest is an existing file, but -n is given
239 if (destExists && destStat.isFile() && options.no_force) {
240 return new common.ShellString('', '', 0);
241 }
242
243 sources.forEach(function (src, srcIndex) {
244 if (!fs.existsSync(src)) {
245 if (src === '') src = "''"; // if src was empty string, display empty string
246 common.error('no such file or directory: ' + src, { continue: true });
247 return; // skip file
248 }
249 var srcStat = common.statFollowLinks(src);
250 if (!options.noFollowsymlink && srcStat.isDirectory()) {
251 if (!options.recursive) {
252 // Non-Recursive
253 common.error("omitting directory '" + src + "'", { continue: true });
254 } else {
255 // Recursive
256 // 'cp /a/source dest' should create 'source' in 'dest'
257 var newDest = (destStat && destStat.isDirectory()) ?
258 path.join(dest, path.basename(src)) :
259 dest;
260
261 try {
262 common.statFollowLinks(path.dirname(dest));
263 cpdirSyncRecursive(src, newDest, 0, { no_force: options.no_force, followsymlink: options.followsymlink });
264 } catch (e) {
265 /* istanbul ignore next */
266 common.error("cannot create directory '" + dest + "': No such file or directory");
267 }
268 }
269 } else {
270 // If here, src is a file
271
272 // When copying to '/path/dir':
273 // thisDest = '/path/dir/file1'
274 var thisDest = dest;
275 if (destStat && destStat.isDirectory()) {
276 thisDest = path.normalize(dest + '/' + path.basename(src));
277 }
278
279 var thisDestExists = fs.existsSync(thisDest);
280 if (thisDestExists && checkRecentCreated(sources, srcIndex)) {
281 // cannot overwrite file created recently in current execution, but we want to continue copying other files
282 if (!options.no_force) {
283 common.error("will not overwrite just-created '" + thisDest + "' with '" + src + "'", { continue: true });
284 }
285 return;
286 }
287
288 if (thisDestExists && options.no_force) {
289 return; // skip file
290 }
291
292 if (path.relative(src, thisDest) === '') {
293 // a file cannot be copied to itself, but we want to continue copying other files
294 common.error("'" + thisDest + "' and '" + src + "' are the same file", { continue: true });
295 return;
296 }
297
298 copyFileSync(src, thisDest, options);
299 }
300 }); // forEach(src)
301
302 return new common.ShellString('', common.state.error, common.state.errorCode);
303}
304module.exports = _cp;