001/** 002 * Copyright 2010-2014 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 */ 016package org.kuali.common.util; 017 018import java.io.File; 019import java.io.IOException; 020import java.util.ArrayList; 021import java.util.Arrays; 022import java.util.Collections; 023import java.util.List; 024 025import org.apache.commons.io.FileUtils; 026import org.apache.commons.lang3.StringUtils; 027import org.codehaus.plexus.util.cli.CommandLineException; 028import org.codehaus.plexus.util.cli.CommandLineUtils; 029import org.codehaus.plexus.util.cli.Commandline; 030import org.codehaus.plexus.util.cli.StreamConsumer; 031import org.slf4j.Logger; 032import org.slf4j.LoggerFactory; 033 034/** 035 * Execute Unix utilities using java. 036 */ 037public 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 already exist. <code>destination</code> will 055 * 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 already exist. <code>destination</code> will 098 * 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 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}