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    }