View Javadoc
1   /**
2    * Copyright 2010-2013 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.common.util;
17  
18  import java.io.File;
19  import java.io.IOException;
20  import java.util.ArrayList;
21  import java.util.Arrays;
22  import java.util.Collections;
23  import java.util.List;
24  
25  import org.apache.commons.io.FileUtils;
26  import org.apache.commons.lang3.StringUtils;
27  import org.codehaus.plexus.util.cli.CommandLineException;
28  import org.codehaus.plexus.util.cli.CommandLineUtils;
29  import org.codehaus.plexus.util.cli.Commandline;
30  import org.codehaus.plexus.util.cli.StreamConsumer;
31  import org.slf4j.Logger;
32  import org.slf4j.LoggerFactory;
33  
34  /**
35   * Execute Unix utilities using java.
36   */
37  public class UnixUtils {
38  
39  	private static final Logger logger = LoggerFactory.getLogger(UnixUtils.class);
40  
41  	private static final String SCP = "scp";
42  	private static final String SSH = "ssh";
43  	private static final String RSYNC = "rsync";
44  	private static final String FORWARD_SLASH = "/";
45  	public static final int SUCCESS = 0;
46  
47  	private static final UnixCmds cmds = new UnixCmds();
48  
49  	/**
50  	 * <pre>
51  	 *  rsync source destination
52  	 * </pre>
53  	 * 
54  	 * Where <code>source</code> and <code>destination</code> are both directories on the local file system. <code>source</code> must already exist. <code>destination</code> will
55  	 * be created if it does not exist.
56  	 */
57  	public static final int rsyncdirs(File source, File destination) {
58  		String sourcePath = validateRsyncSourceDir(source);
59  		String destinationPath = validateRsyncDestinationDir(destination);
60  
61  		// Make sure source is a different directory than destination
62  		boolean different = !source.equals(destination);
63  		Assert.isTrue(different);
64  
65  		return rsyncdirs(null, sourcePath, destinationPath);
66  	}
67  
68  	/**
69  	 * <pre>
70  	 *  rsync source [user@]hostname:destination
71  	 * </pre>
72  	 * 
73  	 * Where <code>source</code> is a directory on the local file system. <code>source</code> must already exist.
74  	 */
75  	public static final int rsyncdirs(File source, String destination) {
76  		String sourcePath = validateRsyncSourceDir(source);
77  		return rsyncdirs(null, sourcePath, destination);
78  	}
79  
80  	/**
81  	 * <pre>
82  	 *  rsync [user@]hostname:source destination
83  	 * </pre>
84  	 * 
85  	 * Where <code>destination</code> is a directory on the local file system. <code>destination</code> will be created if it does not exist
86  	 */
87  	public static final int rsyncdirs(String source, File destination) {
88  		String destinationPath = validateRsyncDestinationDir(destination);
89  		return rsyncdirs(null, source, destinationPath);
90  	}
91  
92  	/**
93  	 * <pre>
94  	 *  rsync [options] source destination
95  	 * </pre>
96  	 * 
97  	 * Where <code>source</code> and <code>destination</code> are both directories on the local file system. <code>source</code> must already exist. <code>destination</code> will
98  	 * be created if it does not exist.
99  	 */
100 	public static final int rsyncdirs(List<String> options, File source, File destination) {
101 		String sourcePath = validateRsyncSourceDir(source);
102 		String destinationPath = validateRsyncDestinationDir(destination);
103 		return rsyncdirs(options, sourcePath, destinationPath);
104 	}
105 
106 	/**
107 	 * <pre>
108 	 *  rsync [options] source [user@]hostname:destination
109 	 * </pre>
110 	 * 
111 	 * Where <code>source</code> is a directory on the local file system. <code>source</code> must already exist.
112 	 */
113 	public static final int rsync(List<String> options, File source, String destination) {
114 		String sourcePath = validateRsyncSourceDir(source);
115 		return rsyncdirs(options, sourcePath, destination);
116 	}
117 
118 	/**
119 	 * <pre>
120 	 *  rsync [options] [user@]hostname:source destination
121 	 * </pre>
122 	 * 
123 	 * Where <code>destination</code> is a directory on the local file system. <code>destination</code> will be created if it does not exist
124 	 */
125 	public static final int rsyncdirs(List<String> options, String source, File destination) {
126 		String destinationPath = validateRsyncDestinationDir(destination);
127 		return rsyncdirs(options, source, destinationPath);
128 	}
129 
130 	/**
131 	 * <pre>
132 	 *  rsync [options] source destination
133 	 *  rsync [options] source [user@]hostname:destination
134 	 *  rsync [options] [user@]hostname:source destination
135 	 * </pre>
136 	 * 
137 	 * Always add a trailing slash to source when sync'ing directories.<br>
138 	 * This forces rsync to behave like <code>cp</code>
139 	 * 
140 	 * <pre>
141 	 * cp -R /tmp/foo/bar  /tmp/xyz  -  creates files in /tmp/xyz
142 	 * rsync /tmp/foo/bar/ /tmp/xyz  -  creates files in /tmp/xyz
143 	 * 
144 	 * rsync /tmp/foo/bar  /tmp/xyz  -  creates files in /tmp/xyz/bar
145 	 * </pre>
146 	 */
147 	public static final int rsyncdirs(List<String> options, String source, String destination) {
148 		List<String> rsyncDirOptions = getRsyncDirOptions(options);
149 		// Make sure source always has a trailing slash
150 		String trailingSlashSource = StringUtils.endsWith(source, FORWARD_SLASH) ? source : source + FORWARD_SLASH;
151 		return rsync(rsyncDirOptions, trailingSlashSource, destination);
152 	}
153 
154 	/**
155 	 * <pre>
156 	 *  rsync [options] source destination
157 	 *  rsync [options] source [user@]hostname:destination
158 	 *  rsync [options] [user@]hostname:source destination
159 	 * </pre>
160 	 */
161 	public static final int rsyncdirs(String source, String destination) {
162 		return rsyncdirs(null, source, destination);
163 	}
164 
165 	/**
166 	 * <pre>
167 	 *  rsync source destination
168 	 *  rsync source [user@]hostname:destination
169 	 *  rsync [user@]hostname:source destination
170 	 * </pre>
171 	 */
172 	public static final int rsync(String source, String destination) {
173 		return rsync(null, source, destination);
174 	}
175 
176 	/**
177 	 * <pre>
178 	 *  rsync [options] source destination
179 	 *  rsync [options] source [user@]hostname:destination
180 	 *  rsync [options] [user@]hostname:source destination
181 	 * </pre>
182 	 */
183 	public static final int rsync(List<String> options, String source, String destination) {
184 		Assert.notNull(source);
185 		Assert.notNull(destination);
186 		List<String> arguments = new ArrayList<String>();
187 		arguments.addAll(CollectionUtils.toEmptyList(options));
188 		arguments.add(source);
189 		arguments.add(destination);
190 		Commandline cl = new Commandline();
191 		cl.setExecutable(RSYNC);
192 		cl.addArguments(CollectionUtils.toStringArray(arguments));
193 		return execute(cl);
194 	}
195 
196 	/**
197 	 * <pre>
198 	 * ssh [args] [user@]hostname chown [chownargs] owner:group file
199 	 * </pre>
200 	 */
201 	public static final int sshchown(List<String> args, String user, String hostname, List<String> options, String owner, String group, String file) {
202 		Assert.notNull(owner);
203 		Assert.notNull(group);
204 		Assert.notNull(file);
205 		String command = cmds.chown(options, owner, group, file);
206 		return ssh(args, user, hostname, command);
207 	}
208 
209 	/**
210 	 * <pre>
211 	 * ssh hostname chown -R owner:group file
212 	 * </pre>
213 	 */
214 	public static final int sshchownr(String hostname, String owner, String group, String file) {
215 		return sshchownr(null, null, hostname, owner, group, file);
216 	}
217 
218 	/**
219 	 * <pre>
220 	 * ssh [user@]hostname chown -R owner:group file
221 	 * </pre>
222 	 */
223 	public static final int sshchownr(String user, String hostname, String owner, String group, String file) {
224 		return sshchownr(null, user, hostname, owner, group, file);
225 	}
226 
227 	/**
228 	 * <pre>
229 	 * ssh [args] hostname chown -R owner:group file
230 	 * </pre>
231 	 */
232 	public static final int sshchownr(List<String> args, String hostname, String owner, String group, String file) {
233 		return sshchownr(args, null, hostname, owner, group, file);
234 	}
235 
236 	/**
237 	 * <pre>
238 	 * ssh [args] [user@]hostname chown -R owner:group file
239 	 * </pre>
240 	 */
241 	public static final int sshchownr(List<String> args, String user, String hostname, String owner, String group, String file) {
242 		return sshchown(args, user, hostname, Arrays.asList("-R"), owner, group, file);
243 	}
244 
245 	/**
246 	 * <pre>
247 	 * ssh [args] hostname chown owner:group file
248 	 * </pre>
249 	 */
250 	public static final int sshchown(List<String> args, String hostname, String owner, String group, String file) {
251 		return sshchown(args, null, hostname, null, owner, group, file);
252 	}
253 
254 	/**
255 	 * <pre>
256 	 * ssh [args] [user@]hostname chown owner:group file
257 	 * </pre>
258 	 */
259 	public static final int sshchown(List<String> args, String user, String hostname, String owner, String group, String file) {
260 		return sshchown(args, user, hostname, null, owner, group, file);
261 	}
262 
263 	/**
264 	 * <pre>
265 	 * ssh [user@]hostname chown owner:group file
266 	 * </pre>
267 	 */
268 	public static final int sshchown(String user, String hostname, String owner, String group, String file) {
269 		return sshchown(null, user, hostname, null, owner, group, file);
270 	}
271 
272 	/**
273 	 * <pre>
274 	 * ssh hostname chown owner:group file
275 	 * </pre>
276 	 */
277 	public static final int sshchown(String hostname, String owner, String group, String file) {
278 		return sshchown(null, null, hostname, owner, group, file);
279 	}
280 
281 	/**
282 	 * <pre>
283 	 * ssh hostname rm -rf file
284 	 * </pre>
285 	 */
286 	public static final int sshrm(String hostname, String file) {
287 		return sshrm(null, null, hostname, file);
288 	}
289 
290 	/**
291 	 * <pre>
292 	 * ssh [user@]hostname rm -rf file
293 	 * </pre>
294 	 */
295 	public static final int sshrm(String user, String hostname, String file) {
296 		return sshrm(null, user, hostname, file);
297 	}
298 
299 	/**
300 	 * <pre>
301 	 * ssh [args] hostname rm -rf file
302 	 * </pre>
303 	 */
304 	public static final int sshrm(List<String> args, String hostname, String file) {
305 		return sshrm(args, null, hostname, file);
306 	}
307 
308 	/**
309 	 * <pre>
310 	 * ssh [args] [user@]hostname rm -rf file
311 	 * </pre>
312 	 */
313 	public static final int sshrm(List<String> args, String user, String hostname, String file) {
314 		Assert.notNull(file);
315 		return sshrm(args, user, hostname, Collections.singletonList(file));
316 	}
317 
318 	/**
319 	 * <pre>
320 	 * ssh hostname rm -rf file ...
321 	 * </pre>
322 	 */
323 	public static final int sshrm(String hostname, List<String> paths) {
324 		return sshrm(null, null, hostname, paths);
325 	}
326 
327 	/**
328 	 * <pre>
329 	 * ssh [user@]hostname rm -rf file ...
330 	 * </pre>
331 	 */
332 	public static final int sshrm(String user, String hostname, List<String> paths) {
333 		return sshrm(null, user, hostname, paths);
334 	}
335 
336 	/**
337 	 * <pre>
338 	 * ssh [args] hostname rm -rf file ...
339 	 * </pre>
340 	 */
341 	public static final int sshrm(List<String> args, String hostname, List<String> paths) {
342 		return sshrm(args, null, hostname, paths);
343 	}
344 
345 	/**
346 	 * <pre>
347 	 * ssh [args] [user@]hostname rm -rf file ...
348 	 * </pre>
349 	 */
350 	public static final int sshrm(List<String> args, String user, String hostname, List<String> paths) {
351 		return sshrm(args, user, hostname, Arrays.asList("-r", "-f"), paths);
352 	}
353 
354 	/**
355 	 * <pre>
356 	 * ssh [args] [user@]hostname rm [rmargs] file ...
357 	 * </pre>
358 	 */
359 	public static final int sshrm(List<String> args, String user, String hostname, List<String> options, List<String> paths) {
360 		Assert.notEmpty(paths);
361 		String command = cmds.rm(options, paths);
362 		return ssh(args, user, hostname, command);
363 	}
364 
365 	/**
366 	 * <pre>
367 	 * ssh [args] [user@]hostname chmod mode file
368 	 * </pre>
369 	 */
370 	public static final int sshchmod(List<String> args, String user, String hostname, String mode, String path) {
371 		Assert.hasLength(mode);
372 		Assert.notNull(path);
373 		return ssh(args, user, hostname, cmds.chmod(mode, path));
374 	}
375 
376 	/**
377 	 * <pre>
378 	 * ssh [user@]hostname chmod mode file
379 	 * </pre>
380 	 */
381 	public static final int sshchmod(String user, String hostname, String mode, String file) {
382 		return sshchmod(null, user, hostname, mode, file);
383 	}
384 
385 	/**
386 	 * <pre>
387 	 * ssh [user@]hostname mkdir -p directory
388 	 * </pre>
389 	 */
390 	public static final int sshmkdir(String user, String hostname, String path) {
391 		return sshmkdir(null, user, hostname, path);
392 	}
393 
394 	/**
395 	 * <pre>
396 	 * ssh [args] [user@]hostname mkdir -p directory
397 	 * </pre>
398 	 */
399 	public static final int sshmkdir(List<String> args, String user, String hostname, String path) {
400 		Assert.notBlank(path);
401 		return ssh(args, user, hostname, cmds.mkdirp(path));
402 	}
403 
404 	/**
405 	 * <pre>
406 	 * ssh hostname mkdir -p directory
407 	 * </pre>
408 	 */
409 	public static final int sshmkdir(String hostname, String path) {
410 		return sshmkdir(null, null, hostname, path);
411 	}
412 
413 	/**
414 	 * <pre>
415 	 * ssh [args] hostname mkdir -p directory
416 	 * </pre>
417 	 */
418 	public static final int sshmkdir(List<String> args, String hostname, String path) {
419 		return sshmkdir(args, null, hostname, path);
420 	}
421 
422 	/**
423 	 * <pre>
424 	 * ssh hostname su - login command
425 	 * </pre>
426 	 */
427 	public static final int sshsu(String hostname, String login, String command) {
428 		return sshsu(null, null, hostname, login, command);
429 	}
430 
431 	/**
432 	 * <pre>
433 	 * ssh [args] hostname su - login command
434 	 * </pre>
435 	 */
436 	public static final int sshsu(List<String> args, String hostname, String login, String command) {
437 		return sshsu(args, null, hostname, login, command);
438 	}
439 
440 	/**
441 	 * <pre>
442 	 * ssh [user@]hostname su - login command
443 	 * </pre>
444 	 */
445 	public static final int sshsu(String user, String hostname, String login, String command) {
446 		return sshsu(null, user, hostname, login, command);
447 	}
448 
449 	/**
450 	 * <pre>
451 	 * ssh [args] [user@]hostname su - login command
452 	 * </pre>
453 	 */
454 	public static final int sshsu(List<String> args, String user, String hostname, String login, String command) {
455 		Assert.notNull(login);
456 		Assert.notNull(command);
457 		return ssh(user, hostname, cmds.su(login, command));
458 	}
459 
460 	/**
461 	 * <pre>
462 	 * ssh hostname command
463 	 * </pre>
464 	 */
465 	public static final int ssh(String hostname, String command) {
466 		return ssh(null, null, hostname, command);
467 	}
468 
469 	/**
470 	 * <pre>
471 	 * ssh [user@]hostname command
472 	 * </pre>
473 	 */
474 	public static final int ssh(String user, String hostname, String command) {
475 		return ssh(null, user, hostname, command);
476 	}
477 
478 	/**
479 	 * <pre>
480 	 * ssh [args] hostname command
481 	 * </pre>
482 	 */
483 	public static final int ssh(List<String> args, String hostname, String command) {
484 		return ssh(args, null, hostname, command);
485 	}
486 
487 	/**
488 	 * <pre>
489 	 * ssh [args] [user@]hostname command
490 	 * </pre>
491 	 */
492 	public static final int ssh(List<String> args, String user, String hostname, String command) {
493 		Assert.notNull(hostname);
494 		Assert.notNull(command);
495 		List<String> arguments = new ArrayList<String>();
496 		arguments.addAll(CollectionUtils.toEmptyList(args));
497 		if (!StringUtils.isBlank(user)) {
498 			arguments.add(user + "@" + hostname);
499 		} else {
500 			arguments.add(hostname);
501 		}
502 		arguments.add(command);
503 		Commandline cl = new Commandline();
504 		cl.setExecutable(SSH);
505 		cl.addArguments(CollectionUtils.toStringArray(arguments));
506 		return execute(cl);
507 	}
508 
509 	/**
510 	 * <pre>
511 	 * scp source destination
512 	 * </pre>
513 	 * 
514 	 * Where both <code>source</code> and <code>destination</code> are in the format
515 	 * 
516 	 * <pre>
517 	 * [[user@]host:]file
518 	 * </pre>
519 	 */
520 	public static final int scp(String source, String destination) {
521 		return scp(null, source, destination);
522 	}
523 
524 	/**
525 	 * <pre>
526 	 * scp [args] source destination
527 	 * </pre>
528 	 * 
529 	 * Where both <code>source</code> and <code>destination</code> are in the format
530 	 * 
531 	 * <pre>
532 	 * [[user@]host:]file
533 	 * </pre>
534 	 */
535 	public static final int scp(List<String> args, String source, String destination) {
536 		Assert.notNull(source);
537 		Assert.notNull(destination);
538 		List<String> arguments = new ArrayList<String>();
539 		arguments.addAll(CollectionUtils.toEmptyList(args));
540 		arguments.add(source);
541 		arguments.add(destination);
542 		Commandline cl = new Commandline();
543 		cl.setExecutable(SCP);
544 		cl.addArguments(CollectionUtils.toStringArray(arguments));
545 		return execute(cl);
546 	}
547 
548 	/**
549 	 * <pre>
550 	 * scp [args] source destination
551 	 * </pre>
552 	 * 
553 	 * Where <code>source</code> is a file on the local file system and <code>destination</code> is in the format
554 	 * 
555 	 * <pre>
556 	 * [[user@]host:]file
557 	 * </pre>
558 	 */
559 	public static final int scp(List<String> args, File source, String destination) {
560 		Assert.notNull(source);
561 		String sourcePath = LocationUtils.getCanonicalPath(source);
562 		if (!source.exists()) {
563 			throw new IllegalArgumentException(sourcePath + " does not exist");
564 		}
565 		return scp(args, sourcePath, destination);
566 	}
567 
568 	/**
569 	 * <pre>
570 	 * scp [args] source destination
571 	 * </pre>
572 	 * 
573 	 * Where <code>destination</code> is a file on the local file system and <code>source</code> is in the format
574 	 * 
575 	 * <pre>
576 	 * [[user@]host:]file
577 	 * </pre>
578 	 */
579 	public static final int scp(List<String> args, String source, File destination) {
580 		try {
581 			FileUtils.touch(destination);
582 		} catch (IOException e) {
583 			throw new IllegalStateException("Unexpected IO error", e);
584 		}
585 		String localPath = LocationUtils.getCanonicalPath(destination);
586 		return scp(args, source, localPath);
587 	}
588 
589 	/**
590 	 * <pre>
591 	 * scp source destination
592 	 * </pre>
593 	 * 
594 	 * Where <code>source</code> is a file on the local file system and <code>destination</code> is in the format
595 	 * 
596 	 * <pre>
597 	 * [[user@]host:]file
598 	 * </pre>
599 	 */
600 	public static final int scp(File source, String destination) {
601 		return scp(null, source, destination);
602 	}
603 
604 	/**
605 	 * <pre>
606 	 * scp source destination
607 	 * </pre>
608 	 * 
609 	 * Where <code>destination</code> is a file on the local file system and <code>source</code> is in the format
610 	 * 
611 	 * <pre>
612 	 * [[user@]host:]file
613 	 * </pre>
614 	 */
615 	public static final int scp(String source, File destination) {
616 		return scp(null, source, destination);
617 	}
618 
619 	public static final void validate(int exitValue, String message, Mode mode) {
620 		if (exitValue != UnixUtils.SUCCESS) {
621 			ModeUtils.validate(mode, message + " Exit value=[" + exitValue + "]");
622 		}
623 	}
624 
625 	public static final void validate(int exitValue, String message) {
626 		validate(exitValue, message, Mode.ERROR);
627 	}
628 
629 	public static final int execute(Commandline cl) {
630 		try {
631 			StreamConsumer stdout = new org.kuali.common.util.stream.LoggingStreamConsumer(logger, org.kuali.common.util.log.LoggerLevel.INFO);
632 			StreamConsumer stderr = new org.kuali.common.util.stream.LoggingStreamConsumer(logger, org.kuali.common.util.log.LoggerLevel.WARN);
633 			logger.info(cl.toString());
634 			return CommandLineUtils.executeCommandLine(cl, stdout, stderr);
635 		} catch (CommandLineException e) {
636 			throw new IllegalStateException(e);
637 		}
638 	}
639 
640 	public static final String getLocation(String user, String hostname, String filename) {
641 		Assert.notNull(user);
642 		Assert.notNull(filename);
643 		StringBuilder sb = new StringBuilder();
644 		if (!StringUtils.isBlank(user)) {
645 			sb.append(user + "@");
646 		}
647 		sb.append(hostname);
648 		sb.append(":");
649 		sb.append(filename);
650 		return sb.toString();
651 	}
652 
653 	protected static final String validateRsyncSourceDir(File dir) {
654 		String path = LocationUtils.getCanonicalPath(dir);
655 		if (!dir.exists()) {
656 			throw new IllegalArgumentException(path + " does not exist");
657 		}
658 		if (!dir.isDirectory()) {
659 			throw new IllegalArgumentException(path + " is not a directory");
660 		}
661 		if (!StringUtils.endsWith(path, FORWARD_SLASH)) {
662 			return path + FORWARD_SLASH;
663 		} else {
664 			return path;
665 		}
666 	}
667 
668 	protected static final String validateRsyncDestinationDir(File dir) {
669 		try {
670 			FileUtils.forceMkdir(dir);
671 			return dir.getCanonicalPath();
672 		} catch (IOException e) {
673 			throw new IllegalArgumentException("Unexpected IO error", e);
674 		}
675 	}
676 
677 	/**
678 	 * Return a list containing the options <code>--recursive</code>, <code>--archive</code>, and <code>--delete</code> as the first 3 elements, with additional options coming
679 	 * after.
680 	 */
681 	protected static final List<String> getRsyncDirOptions(List<String> options) {
682 		List<String> rsyncDirOptions = new ArrayList<String>();
683 		rsyncDirOptions.add("--recursive");
684 		rsyncDirOptions.add("--archive");
685 		rsyncDirOptions.add("--delete");
686 		for (String option : CollectionUtils.toEmptyList(options)) {
687 			if (!rsyncDirOptions.contains(option)) {
688 				rsyncDirOptions.add(option);
689 			}
690 		}
691 		return rsyncDirOptions;
692 	}
693 
694 }