001 /**
002 * Copyright 2010-2012 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016 package org.kuali.common.util;
017
018 import java.io.File;
019 import java.io.IOException;
020 import java.util.ArrayList;
021 import java.util.Arrays;
022 import java.util.Collections;
023 import java.util.List;
024
025 import org.apache.commons.io.FileUtils;
026 import org.apache.commons.lang3.StringUtils;
027 import org.codehaus.plexus.util.cli.CommandLineException;
028 import org.codehaus.plexus.util.cli.CommandLineUtils;
029 import org.codehaus.plexus.util.cli.Commandline;
030 import org.codehaus.plexus.util.cli.StreamConsumer;
031 import org.slf4j.Logger;
032 import org.slf4j.LoggerFactory;
033
034 /**
035 * Execute Unix utilities using java.
036 */
037 public class UnixUtils {
038
039 private static final Logger logger = LoggerFactory.getLogger(UnixUtils.class);
040
041 private static final String SCP = "scp";
042 private static final String SSH = "ssh";
043 private static final String RSYNC = "rsync";
044 private static final String FORWARD_SLASH = "/";
045 public static final int SUCCESS = 0;
046
047 private static final UnixCmds cmds = new UnixCmds();
048
049 /**
050 * <pre>
051 * rsync source destination
052 * </pre>
053 *
054 * Where <code>source</code> and <code>destination</code> are both directories on the local file system. <code>source</code> must
055 * already exist. <code>destination</code> will be created if it does not exist.
056 */
057 public static final int rsyncdirs(File source, File destination) {
058 String sourcePath = validateRsyncSourceDir(source);
059 String destinationPath = validateRsyncDestinationDir(destination);
060
061 // Make sure source is a different directory than destination
062 boolean different = !source.equals(destination);
063 Assert.isTrue(different);
064
065 return rsyncdirs(null, sourcePath, destinationPath);
066 }
067
068 /**
069 * <pre>
070 * rsync source [user@]hostname:destination
071 * </pre>
072 *
073 * Where <code>source</code> is a directory on the local file system. <code>source</code> must already exist.
074 */
075 public static final int rsyncdirs(File source, String destination) {
076 String sourcePath = validateRsyncSourceDir(source);
077 return rsyncdirs(null, sourcePath, destination);
078 }
079
080 /**
081 * <pre>
082 * rsync [user@]hostname:source destination
083 * </pre>
084 *
085 * Where <code>destination</code> is a directory on the local file system. <code>destination</code> will be created if it does not exist
086 */
087 public static final int rsyncdirs(String source, File destination) {
088 String destinationPath = validateRsyncDestinationDir(destination);
089 return rsyncdirs(null, source, destinationPath);
090 }
091
092 /**
093 * <pre>
094 * rsync [options] source destination
095 * </pre>
096 *
097 * Where <code>source</code> and <code>destination</code> are both directories on the local file system. <code>source</code> must
098 * already exist. <code>destination</code> will be created if it does not exist.
099 */
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 LoggingStreamConsumer(logger, LoggerLevel.INFO);
632 StreamConsumer stderr = new LoggingStreamConsumer(logger, 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
679 * elements, with additional options coming 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 }