You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

FS.java 42KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432
  1. /*
  2. * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
  3. * and other copyright owners as documented in the project's IP log.
  4. *
  5. * This program and the accompanying materials are made available
  6. * under the terms of the Eclipse Distribution License v1.0 which
  7. * accompanies this distribution, is reproduced below, and is
  8. * available at http://www.eclipse.org/org/documents/edl-v10.php
  9. *
  10. * All rights reserved.
  11. *
  12. * Redistribution and use in source and binary forms, with or
  13. * without modification, are permitted provided that the following
  14. * conditions are met:
  15. *
  16. * - Redistributions of source code must retain the above copyright
  17. * notice, this list of conditions and the following disclaimer.
  18. *
  19. * - Redistributions in binary form must reproduce the above
  20. * copyright notice, this list of conditions and the following
  21. * disclaimer in the documentation and/or other materials provided
  22. * with the distribution.
  23. *
  24. * - Neither the name of the Eclipse Foundation, Inc. nor the
  25. * names of its contributors may be used to endorse or promote
  26. * products derived from this software without specific prior
  27. * written permission.
  28. *
  29. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
  30. * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  31. * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  32. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  33. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  34. * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  35. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  36. * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  37. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  38. * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  39. * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  40. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  41. * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  42. */
  43. package org.eclipse.jgit.util;
  44. import java.io.BufferedReader;
  45. import java.io.ByteArrayInputStream;
  46. import java.io.File;
  47. import java.io.IOException;
  48. import java.io.InputStream;
  49. import java.io.InputStreamReader;
  50. import java.io.OutputStream;
  51. import java.io.PrintStream;
  52. import java.nio.charset.Charset;
  53. import java.security.AccessController;
  54. import java.security.PrivilegedAction;
  55. import java.text.MessageFormat;
  56. import java.util.Arrays;
  57. import java.util.HashMap;
  58. import java.util.Map;
  59. import java.util.Objects;
  60. import java.util.concurrent.ExecutorService;
  61. import java.util.concurrent.Executors;
  62. import java.util.concurrent.TimeUnit;
  63. import java.util.concurrent.atomic.AtomicBoolean;
  64. import java.util.concurrent.atomic.AtomicReference;
  65. import org.eclipse.jgit.annotations.Nullable;
  66. import org.eclipse.jgit.api.errors.JGitInternalException;
  67. import org.eclipse.jgit.errors.CommandFailedException;
  68. import org.eclipse.jgit.internal.JGitText;
  69. import org.eclipse.jgit.lib.Constants;
  70. import org.eclipse.jgit.lib.Repository;
  71. import org.eclipse.jgit.util.ProcessResult.Status;
  72. import org.slf4j.Logger;
  73. import org.slf4j.LoggerFactory;
  74. /** Abstraction to support various file system operations not in Java. */
  75. public abstract class FS {
  76. private static final Logger LOG = LoggerFactory.getLogger(FS.class);
  77. /**
  78. * This class creates FS instances. It will be overridden by a Java7 variant
  79. * if such can be detected in {@link #detect(Boolean)}.
  80. *
  81. * @since 3.0
  82. */
  83. public static class FSFactory {
  84. /**
  85. * Constructor
  86. */
  87. protected FSFactory() {
  88. // empty
  89. }
  90. /**
  91. * Detect the file system
  92. *
  93. * @param cygwinUsed
  94. * @return FS instance
  95. */
  96. public FS detect(Boolean cygwinUsed) {
  97. if (SystemReader.getInstance().isWindows()) {
  98. if (cygwinUsed == null)
  99. cygwinUsed = Boolean.valueOf(FS_Win32_Cygwin.isCygwin());
  100. if (cygwinUsed.booleanValue())
  101. return new FS_Win32_Cygwin();
  102. else
  103. return new FS_Win32();
  104. } else {
  105. return new FS_POSIX();
  106. }
  107. }
  108. }
  109. /**
  110. * Result of an executed process. The caller is responsible to close the
  111. * contained {@link TemporaryBuffer}s
  112. *
  113. * @since 4.2
  114. */
  115. public static class ExecutionResult {
  116. private TemporaryBuffer stdout;
  117. private TemporaryBuffer stderr;
  118. private int rc;
  119. /**
  120. * @param stdout
  121. * @param stderr
  122. * @param rc
  123. */
  124. public ExecutionResult(TemporaryBuffer stdout, TemporaryBuffer stderr,
  125. int rc) {
  126. this.stdout = stdout;
  127. this.stderr = stderr;
  128. this.rc = rc;
  129. }
  130. /**
  131. * @return buffered standard output stream
  132. */
  133. public TemporaryBuffer getStdout() {
  134. return stdout;
  135. }
  136. /**
  137. * @return buffered standard error stream
  138. */
  139. public TemporaryBuffer getStderr() {
  140. return stderr;
  141. }
  142. /**
  143. * @return the return code of the process
  144. */
  145. public int getRc() {
  146. return rc;
  147. }
  148. }
  149. /** The auto-detected implementation selected for this operating system and JRE. */
  150. public static final FS DETECTED = detect();
  151. private volatile static FSFactory factory;
  152. /**
  153. * Auto-detect the appropriate file system abstraction.
  154. *
  155. * @return detected file system abstraction
  156. */
  157. public static FS detect() {
  158. return detect(null);
  159. }
  160. /**
  161. * Auto-detect the appropriate file system abstraction, taking into account
  162. * the presence of a Cygwin installation on the system. Using jgit in
  163. * combination with Cygwin requires a more elaborate (and possibly slower)
  164. * resolution of file system paths.
  165. *
  166. * @param cygwinUsed
  167. * <ul>
  168. * <li><code>Boolean.TRUE</code> to assume that Cygwin is used in
  169. * combination with jgit</li>
  170. * <li><code>Boolean.FALSE</code> to assume that Cygwin is
  171. * <b>not</b> used with jgit</li>
  172. * <li><code>null</code> to auto-detect whether a Cygwin
  173. * installation is present on the system and in this case assume
  174. * that Cygwin is used</li>
  175. * </ul>
  176. *
  177. * Note: this parameter is only relevant on Windows.
  178. *
  179. * @return detected file system abstraction
  180. */
  181. public static FS detect(Boolean cygwinUsed) {
  182. if (factory == null) {
  183. factory = new FS.FSFactory();
  184. }
  185. return factory.detect(cygwinUsed);
  186. }
  187. private volatile Holder<File> userHome;
  188. private volatile Holder<File> gitSystemConfig;
  189. /**
  190. * Constructs a file system abstraction.
  191. */
  192. protected FS() {
  193. // Do nothing by default.
  194. }
  195. /**
  196. * Initialize this FS using another's current settings.
  197. *
  198. * @param src
  199. * the source FS to copy from.
  200. */
  201. protected FS(FS src) {
  202. userHome = src.userHome;
  203. gitSystemConfig = src.gitSystemConfig;
  204. }
  205. /** @return a new instance of the same type of FS. */
  206. public abstract FS newInstance();
  207. /**
  208. * Does this operating system and JRE support the execute flag on files?
  209. *
  210. * @return true if this implementation can provide reasonably accurate
  211. * executable bit information; false otherwise.
  212. */
  213. public abstract boolean supportsExecute();
  214. /**
  215. * Does this file system support atomic file creation via
  216. * java.io.File#createNewFile()? In certain environments (e.g. on NFS) it is
  217. * not guaranteed that when two file system clients run createNewFile() in
  218. * parallel only one will succeed. In such cases both clients may think they
  219. * created a new file.
  220. *
  221. * @return true if this implementation support atomic creation of new
  222. * Files by {@link File#createNewFile()}
  223. * @since 4.5
  224. */
  225. public boolean supportsAtomicCreateNewFile() {
  226. return true;
  227. }
  228. /**
  229. * Does this operating system and JRE supports symbolic links. The
  230. * capability to handle symbolic links is detected at runtime.
  231. *
  232. * @return true if symbolic links may be used
  233. * @since 3.0
  234. */
  235. public boolean supportsSymlinks() {
  236. return false;
  237. }
  238. /**
  239. * Is this file system case sensitive
  240. *
  241. * @return true if this implementation is case sensitive
  242. */
  243. public abstract boolean isCaseSensitive();
  244. /**
  245. * Determine if the file is executable (or not).
  246. * <p>
  247. * Not all platforms and JREs support executable flags on files. If the
  248. * feature is unsupported this method will always return false.
  249. * <p>
  250. * <em>If the platform supports symbolic links and <code>f</code> is a symbolic link
  251. * this method returns false, rather than the state of the executable flags
  252. * on the target file.</em>
  253. *
  254. * @param f
  255. * abstract path to test.
  256. * @return true if the file is believed to be executable by the user.
  257. */
  258. public abstract boolean canExecute(File f);
  259. /**
  260. * Set a file to be executable by the user.
  261. * <p>
  262. * Not all platforms and JREs support executable flags on files. If the
  263. * feature is unsupported this method will always return false and no
  264. * changes will be made to the file specified.
  265. *
  266. * @param f
  267. * path to modify the executable status of.
  268. * @param canExec
  269. * true to enable execution; false to disable it.
  270. * @return true if the change succeeded; false otherwise.
  271. */
  272. public abstract boolean setExecute(File f, boolean canExec);
  273. /**
  274. * Get the last modified time of a file system object. If the OS/JRE support
  275. * symbolic links, the modification time of the link is returned, rather
  276. * than that of the link target.
  277. *
  278. * @param f
  279. * @return last modified time of f
  280. * @throws IOException
  281. * @since 3.0
  282. */
  283. public long lastModified(File f) throws IOException {
  284. return FileUtils.lastModified(f);
  285. }
  286. /**
  287. * Set the last modified time of a file system object. If the OS/JRE support
  288. * symbolic links, the link is modified, not the target,
  289. *
  290. * @param f
  291. * @param time
  292. * @throws IOException
  293. * @since 3.0
  294. */
  295. public void setLastModified(File f, long time) throws IOException {
  296. FileUtils.setLastModified(f, time);
  297. }
  298. /**
  299. * Get the length of a file or link, If the OS/JRE supports symbolic links
  300. * it's the length of the link, else the length of the target.
  301. *
  302. * @param path
  303. * @return length of a file
  304. * @throws IOException
  305. * @since 3.0
  306. */
  307. public long length(File path) throws IOException {
  308. return FileUtils.getLength(path);
  309. }
  310. /**
  311. * Delete a file. Throws an exception if delete fails.
  312. *
  313. * @param f
  314. * @throws IOException
  315. * this may be a Java7 subclass with detailed information
  316. * @since 3.3
  317. */
  318. public void delete(File f) throws IOException {
  319. FileUtils.delete(f);
  320. }
  321. /**
  322. * Resolve this file to its actual path name that the JRE can use.
  323. * <p>
  324. * This method can be relatively expensive. Computing a translation may
  325. * require forking an external process per path name translated. Callers
  326. * should try to minimize the number of translations necessary by caching
  327. * the results.
  328. * <p>
  329. * Not all platforms and JREs require path name translation. Currently only
  330. * Cygwin on Win32 require translation for Cygwin based paths.
  331. *
  332. * @param dir
  333. * directory relative to which the path name is.
  334. * @param name
  335. * path name to translate.
  336. * @return the translated path. <code>new File(dir,name)</code> if this
  337. * platform does not require path name translation.
  338. */
  339. public File resolve(final File dir, final String name) {
  340. final File abspn = new File(name);
  341. if (abspn.isAbsolute())
  342. return abspn;
  343. return new File(dir, name);
  344. }
  345. /**
  346. * Determine the user's home directory (location where preferences are).
  347. * <p>
  348. * This method can be expensive on the first invocation if path name
  349. * translation is required. Subsequent invocations return a cached result.
  350. * <p>
  351. * Not all platforms and JREs require path name translation. Currently only
  352. * Cygwin on Win32 requires translation of the Cygwin HOME directory.
  353. *
  354. * @return the user's home directory; null if the user does not have one.
  355. */
  356. public File userHome() {
  357. Holder<File> p = userHome;
  358. if (p == null) {
  359. p = new Holder<>(userHomeImpl());
  360. userHome = p;
  361. }
  362. return p.value;
  363. }
  364. /**
  365. * Set the user's home directory location.
  366. *
  367. * @param path
  368. * the location of the user's preferences; null if there is no
  369. * home directory for the current user.
  370. * @return {@code this}.
  371. */
  372. public FS setUserHome(File path) {
  373. userHome = new Holder<>(path);
  374. return this;
  375. }
  376. /**
  377. * Does this file system have problems with atomic renames?
  378. *
  379. * @return true if the caller should retry a failed rename of a lock file.
  380. */
  381. public abstract boolean retryFailedLockFileCommit();
  382. /**
  383. * Determine the user's home directory (location where preferences are).
  384. *
  385. * @return the user's home directory; null if the user does not have one.
  386. */
  387. protected File userHomeImpl() {
  388. final String home = AccessController
  389. .doPrivileged(new PrivilegedAction<String>() {
  390. @Override
  391. public String run() {
  392. return System.getProperty("user.home"); //$NON-NLS-1$
  393. }
  394. });
  395. if (home == null || home.length() == 0)
  396. return null;
  397. return new File(home).getAbsoluteFile();
  398. }
  399. /**
  400. * Searches the given path to see if it contains one of the given files.
  401. * Returns the first it finds. Returns null if not found or if path is null.
  402. *
  403. * @param path
  404. * List of paths to search separated by File.pathSeparator
  405. * @param lookFor
  406. * Files to search for in the given path
  407. * @return the first match found, or null
  408. * @since 3.0
  409. **/
  410. protected static File searchPath(final String path, final String... lookFor) {
  411. if (path == null)
  412. return null;
  413. for (final String p : path.split(File.pathSeparator)) {
  414. for (String command : lookFor) {
  415. final File e = new File(p, command);
  416. if (e.isFile())
  417. return e.getAbsoluteFile();
  418. }
  419. }
  420. return null;
  421. }
  422. /**
  423. * Execute a command and return a single line of output as a String
  424. *
  425. * @param dir
  426. * Working directory for the command
  427. * @param command
  428. * as component array
  429. * @param encoding
  430. * to be used to parse the command's output
  431. * @return the one-line output of the command or {@code null} if there is
  432. * none
  433. * @throws CommandFailedException
  434. * thrown when the command failed (return code was non-zero)
  435. */
  436. @Nullable
  437. protected static String readPipe(File dir, String[] command,
  438. String encoding) throws CommandFailedException {
  439. return readPipe(dir, command, encoding, null);
  440. }
  441. /**
  442. * Execute a command and return a single line of output as a String
  443. *
  444. * @param dir
  445. * Working directory for the command
  446. * @param command
  447. * as component array
  448. * @param encoding
  449. * to be used to parse the command's output
  450. * @param env
  451. * Map of environment variables to be merged with those of the
  452. * current process
  453. * @return the one-line output of the command or {@code null} if there is
  454. * none
  455. * @throws CommandFailedException
  456. * thrown when the command failed (return code was non-zero)
  457. * @since 4.0
  458. */
  459. @Nullable
  460. protected static String readPipe(File dir, String[] command,
  461. String encoding, Map<String, String> env)
  462. throws CommandFailedException {
  463. final boolean debug = LOG.isDebugEnabled();
  464. try {
  465. if (debug) {
  466. LOG.debug("readpipe " + Arrays.asList(command) + "," //$NON-NLS-1$ //$NON-NLS-2$
  467. + dir);
  468. }
  469. ProcessBuilder pb = new ProcessBuilder(command);
  470. pb.directory(dir);
  471. if (env != null) {
  472. pb.environment().putAll(env);
  473. }
  474. Process p;
  475. try {
  476. p = pb.start();
  477. } catch (IOException e) {
  478. // Process failed to start
  479. throw new CommandFailedException(-1, e.getMessage(), e);
  480. }
  481. p.getOutputStream().close();
  482. GobblerThread gobbler = new GobblerThread(p, command, dir);
  483. gobbler.start();
  484. String r = null;
  485. try (BufferedReader lineRead = new BufferedReader(
  486. new InputStreamReader(p.getInputStream(), encoding))) {
  487. r = lineRead.readLine();
  488. if (debug) {
  489. LOG.debug("readpipe may return '" + r + "'"); //$NON-NLS-1$ //$NON-NLS-2$
  490. LOG.debug("remaining output:\n"); //$NON-NLS-1$
  491. String l;
  492. while ((l = lineRead.readLine()) != null) {
  493. LOG.debug(l);
  494. }
  495. }
  496. }
  497. for (;;) {
  498. try {
  499. int rc = p.waitFor();
  500. gobbler.join();
  501. if (rc == 0 && !gobbler.fail.get()) {
  502. return r;
  503. } else {
  504. if (debug) {
  505. LOG.debug("readpipe rc=" + rc); //$NON-NLS-1$
  506. }
  507. throw new CommandFailedException(rc,
  508. gobbler.errorMessage.get(),
  509. gobbler.exception.get());
  510. }
  511. } catch (InterruptedException ie) {
  512. // Stop bothering me, I have a zombie to reap.
  513. }
  514. }
  515. } catch (IOException e) {
  516. LOG.error("Caught exception in FS.readPipe()", e); //$NON-NLS-1$
  517. }
  518. if (debug) {
  519. LOG.debug("readpipe returns null"); //$NON-NLS-1$
  520. }
  521. return null;
  522. }
  523. private static class GobblerThread extends Thread {
  524. /* The process has 5 seconds to exit after closing stderr */
  525. private static final int PROCESS_EXIT_TIMEOUT = 5;
  526. private final Process p;
  527. private final String desc;
  528. private final String dir;
  529. final AtomicBoolean fail = new AtomicBoolean();
  530. final AtomicReference<String> errorMessage = new AtomicReference<>();
  531. final AtomicReference<Throwable> exception = new AtomicReference<>();
  532. GobblerThread(Process p, String[] command, File dir) {
  533. this.p = p;
  534. this.desc = Arrays.toString(command);
  535. this.dir = Objects.toString(dir);
  536. }
  537. @Override
  538. public void run() {
  539. StringBuilder err = new StringBuilder();
  540. try (InputStream is = p.getErrorStream()) {
  541. int ch;
  542. while ((ch = is.read()) != -1) {
  543. err.append((char) ch);
  544. }
  545. } catch (IOException e) {
  546. if (waitForProcessCompletion(e) && p.exitValue() != 0) {
  547. setError(e, e.getMessage(), p.exitValue());
  548. fail.set(true);
  549. } else {
  550. // ignore. command terminated faster and stream was just closed
  551. // or the process didn't terminate within timeout
  552. }
  553. } finally {
  554. if (waitForProcessCompletion(null) && err.length() > 0) {
  555. setError(null, err.toString(), p.exitValue());
  556. if (p.exitValue() != 0) {
  557. fail.set(true);
  558. }
  559. }
  560. }
  561. }
  562. @SuppressWarnings("boxing")
  563. private boolean waitForProcessCompletion(IOException originalError) {
  564. try {
  565. if (!p.waitFor(PROCESS_EXIT_TIMEOUT, TimeUnit.SECONDS)) {
  566. setError(originalError, MessageFormat.format(
  567. JGitText.get().commandClosedStderrButDidntExit,
  568. desc, PROCESS_EXIT_TIMEOUT), -1);
  569. fail.set(true);
  570. }
  571. } catch (InterruptedException e) {
  572. LOG.error(MessageFormat.format(
  573. JGitText.get().threadInterruptedWhileRunning, desc), e);
  574. }
  575. return false;
  576. }
  577. private void setError(IOException e, String message, int exitCode) {
  578. exception.set(e);
  579. errorMessage.set(MessageFormat.format(
  580. JGitText.get().exceptionCaughtDuringExecutionOfCommand,
  581. desc, dir, Integer.valueOf(exitCode), message));
  582. }
  583. }
  584. /**
  585. * @return the path to the Git executable or {@code null} if it cannot be
  586. * determined.
  587. * @since 4.0
  588. */
  589. protected abstract File discoverGitExe();
  590. /**
  591. * @return the path to the system-wide Git configuration file or
  592. * {@code null} if it cannot be determined.
  593. * @since 4.0
  594. */
  595. protected File discoverGitSystemConfig() {
  596. File gitExe = discoverGitExe();
  597. if (gitExe == null) {
  598. return null;
  599. }
  600. // Bug 480782: Check if the discovered git executable is JGit CLI
  601. String v;
  602. try {
  603. v = readPipe(gitExe.getParentFile(),
  604. new String[] { "git", "--version" }, //$NON-NLS-1$ //$NON-NLS-2$
  605. Charset.defaultCharset().name());
  606. } catch (CommandFailedException e) {
  607. LOG.warn(e.getMessage());
  608. return null;
  609. }
  610. if (StringUtils.isEmptyOrNull(v)
  611. || (v != null && v.startsWith("jgit"))) { //$NON-NLS-1$
  612. return null;
  613. }
  614. // Trick Git into printing the path to the config file by using "echo"
  615. // as the editor.
  616. Map<String, String> env = new HashMap<>();
  617. env.put("GIT_EDITOR", "echo"); //$NON-NLS-1$ //$NON-NLS-2$
  618. String w;
  619. try {
  620. w = readPipe(gitExe.getParentFile(),
  621. new String[] { "git", "config", "--system", "--edit" }, //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
  622. Charset.defaultCharset().name(), env);
  623. } catch (CommandFailedException e) {
  624. LOG.warn(e.getMessage());
  625. return null;
  626. }
  627. if (StringUtils.isEmptyOrNull(w)) {
  628. return null;
  629. }
  630. return new File(w);
  631. }
  632. /**
  633. * @return the currently used path to the system-wide Git configuration
  634. * file or {@code null} if none has been set.
  635. * @since 4.0
  636. */
  637. public File getGitSystemConfig() {
  638. if (gitSystemConfig == null) {
  639. gitSystemConfig = new Holder<>(discoverGitSystemConfig());
  640. }
  641. return gitSystemConfig.value;
  642. }
  643. /**
  644. * Set the path to the system-wide Git configuration file to use.
  645. *
  646. * @param configFile
  647. * the path to the config file.
  648. * @return {@code this}
  649. * @since 4.0
  650. */
  651. public FS setGitSystemConfig(File configFile) {
  652. gitSystemConfig = new Holder<>(configFile);
  653. return this;
  654. }
  655. /**
  656. * @param grandchild
  657. * @return the parent directory of this file's parent directory or
  658. * {@code null} in case there's no grandparent directory
  659. * @since 4.0
  660. */
  661. protected static File resolveGrandparentFile(File grandchild) {
  662. if (grandchild != null) {
  663. File parent = grandchild.getParentFile();
  664. if (parent != null)
  665. return parent.getParentFile();
  666. }
  667. return null;
  668. }
  669. /**
  670. * Check if a file is a symbolic link and read it
  671. *
  672. * @param path
  673. * @return target of link or null
  674. * @throws IOException
  675. * @since 3.0
  676. */
  677. public String readSymLink(File path) throws IOException {
  678. return FileUtils.readSymLink(path);
  679. }
  680. /**
  681. * @param path
  682. * @return true if the path is a symbolic link (and we support these)
  683. * @throws IOException
  684. * @since 3.0
  685. */
  686. public boolean isSymLink(File path) throws IOException {
  687. return FileUtils.isSymlink(path);
  688. }
  689. /**
  690. * Tests if the path exists, in case of a symbolic link, true even if the
  691. * target does not exist
  692. *
  693. * @param path
  694. * @return true if path exists
  695. * @since 3.0
  696. */
  697. public boolean exists(File path) {
  698. return FileUtils.exists(path);
  699. }
  700. /**
  701. * Check if path is a directory. If the OS/JRE supports symbolic links and
  702. * path is a symbolic link to a directory, this method returns false.
  703. *
  704. * @param path
  705. * @return true if file is a directory,
  706. * @since 3.0
  707. */
  708. public boolean isDirectory(File path) {
  709. return FileUtils.isDirectory(path);
  710. }
  711. /**
  712. * Examine if path represents a regular file. If the OS/JRE supports
  713. * symbolic links the test returns false if path represents a symbolic link.
  714. *
  715. * @param path
  716. * @return true if path represents a regular file
  717. * @since 3.0
  718. */
  719. public boolean isFile(File path) {
  720. return FileUtils.isFile(path);
  721. }
  722. /**
  723. * @param path
  724. * @return true if path is hidden, either starts with . on unix or has the
  725. * hidden attribute in windows
  726. * @throws IOException
  727. * @since 3.0
  728. */
  729. public boolean isHidden(File path) throws IOException {
  730. return FileUtils.isHidden(path);
  731. }
  732. /**
  733. * Set the hidden attribute for file whose name starts with a period.
  734. *
  735. * @param path
  736. * @param hidden
  737. * @throws IOException
  738. * @since 3.0
  739. */
  740. public void setHidden(File path, boolean hidden) throws IOException {
  741. FileUtils.setHidden(path, hidden);
  742. }
  743. /**
  744. * Create a symbolic link
  745. *
  746. * @param path
  747. * @param target
  748. * @throws IOException
  749. * @since 3.0
  750. */
  751. public void createSymLink(File path, String target) throws IOException {
  752. FileUtils.createSymLink(path, target);
  753. }
  754. /**
  755. * Create a new file. See {@link File#createNewFile()}. Subclasses of this
  756. * class may take care to provide a safe implementation for this even if
  757. * {@link #supportsAtomicCreateNewFile()} is <code>false</code>
  758. *
  759. * @param path
  760. * the file to be created
  761. * @return <code>true</code> if the file was created, <code>false</code> if
  762. * the file already existed
  763. * @throws IOException
  764. * @since 4.5
  765. */
  766. public boolean createNewFile(File path) throws IOException {
  767. return path.createNewFile();
  768. }
  769. /**
  770. * See {@link FileUtils#relativizePath(String, String, String, boolean)}.
  771. *
  772. * @param base
  773. * The path against which <code>other</code> should be
  774. * relativized.
  775. * @param other
  776. * The path that will be made relative to <code>base</code>.
  777. * @return A relative path that, when resolved against <code>base</code>,
  778. * will yield the original <code>other</code>.
  779. * @see FileUtils#relativizePath(String, String, String, boolean)
  780. * @since 3.7
  781. */
  782. public String relativize(String base, String other) {
  783. return FileUtils.relativizePath(base, other, File.separator, this.isCaseSensitive());
  784. }
  785. /**
  786. * Checks whether the given hook is defined for the given repository, then
  787. * runs it with the given arguments.
  788. * <p>
  789. * The hook's standard output and error streams will be redirected to
  790. * <code>System.out</code> and <code>System.err</code> respectively. The
  791. * hook will have no stdin.
  792. * </p>
  793. *
  794. * @param repository
  795. * The repository for which a hook should be run.
  796. * @param hookName
  797. * The name of the hook to be executed.
  798. * @param args
  799. * Arguments to pass to this hook. Cannot be <code>null</code>,
  800. * but can be an empty array.
  801. * @return The ProcessResult describing this hook's execution.
  802. * @throws JGitInternalException
  803. * if we fail to run the hook somehow. Causes may include an
  804. * interrupted process or I/O errors.
  805. * @since 4.0
  806. */
  807. public ProcessResult runHookIfPresent(Repository repository,
  808. final String hookName,
  809. String[] args) throws JGitInternalException {
  810. return runHookIfPresent(repository, hookName, args, System.out, System.err,
  811. null);
  812. }
  813. /**
  814. * Checks whether the given hook is defined for the given repository, then
  815. * runs it with the given arguments.
  816. *
  817. * @param repository
  818. * The repository for which a hook should be run.
  819. * @param hookName
  820. * The name of the hook to be executed.
  821. * @param args
  822. * Arguments to pass to this hook. Cannot be <code>null</code>,
  823. * but can be an empty array.
  824. * @param outRedirect
  825. * A print stream on which to redirect the hook's stdout. Can be
  826. * <code>null</code>, in which case the hook's standard output
  827. * will be lost.
  828. * @param errRedirect
  829. * A print stream on which to redirect the hook's stderr. Can be
  830. * <code>null</code>, in which case the hook's standard error
  831. * will be lost.
  832. * @param stdinArgs
  833. * A string to pass on to the standard input of the hook. May be
  834. * <code>null</code>.
  835. * @return The ProcessResult describing this hook's execution.
  836. * @throws JGitInternalException
  837. * if we fail to run the hook somehow. Causes may include an
  838. * interrupted process or I/O errors.
  839. * @since 4.0
  840. */
  841. public ProcessResult runHookIfPresent(Repository repository,
  842. final String hookName,
  843. String[] args, PrintStream outRedirect, PrintStream errRedirect,
  844. String stdinArgs) throws JGitInternalException {
  845. return new ProcessResult(Status.NOT_SUPPORTED);
  846. }
  847. /**
  848. * See
  849. * {@link #runHookIfPresent(Repository, String, String[], PrintStream, PrintStream, String)}
  850. * . Should only be called by FS supporting shell scripts execution.
  851. *
  852. * @param repository
  853. * The repository for which a hook should be run.
  854. * @param hookName
  855. * The name of the hook to be executed.
  856. * @param args
  857. * Arguments to pass to this hook. Cannot be <code>null</code>,
  858. * but can be an empty array.
  859. * @param outRedirect
  860. * A print stream on which to redirect the hook's stdout. Can be
  861. * <code>null</code>, in which case the hook's standard output
  862. * will be lost.
  863. * @param errRedirect
  864. * A print stream on which to redirect the hook's stderr. Can be
  865. * <code>null</code>, in which case the hook's standard error
  866. * will be lost.
  867. * @param stdinArgs
  868. * A string to pass on to the standard input of the hook. May be
  869. * <code>null</code>.
  870. * @return The ProcessResult describing this hook's execution.
  871. * @throws JGitInternalException
  872. * if we fail to run the hook somehow. Causes may include an
  873. * interrupted process or I/O errors.
  874. * @since 4.0
  875. */
  876. protected ProcessResult internalRunHookIfPresent(Repository repository,
  877. final String hookName, String[] args, PrintStream outRedirect,
  878. PrintStream errRedirect, String stdinArgs)
  879. throws JGitInternalException {
  880. final File hookFile = findHook(repository, hookName);
  881. if (hookFile == null)
  882. return new ProcessResult(Status.NOT_PRESENT);
  883. final String hookPath = hookFile.getAbsolutePath();
  884. final File runDirectory;
  885. if (repository.isBare())
  886. runDirectory = repository.getDirectory();
  887. else
  888. runDirectory = repository.getWorkTree();
  889. final String cmd = relativize(runDirectory.getAbsolutePath(),
  890. hookPath);
  891. ProcessBuilder hookProcess = runInShell(cmd, args);
  892. hookProcess.directory(runDirectory);
  893. try {
  894. return new ProcessResult(runProcess(hookProcess, outRedirect,
  895. errRedirect, stdinArgs), Status.OK);
  896. } catch (IOException e) {
  897. throw new JGitInternalException(MessageFormat.format(
  898. JGitText.get().exceptionCaughtDuringExecutionOfHook,
  899. hookName), e);
  900. } catch (InterruptedException e) {
  901. throw new JGitInternalException(MessageFormat.format(
  902. JGitText.get().exceptionHookExecutionInterrupted,
  903. hookName), e);
  904. }
  905. }
  906. /**
  907. * Tries to find a hook matching the given one in the given repository.
  908. *
  909. * @param repository
  910. * The repository within which to find a hook.
  911. * @param hookName
  912. * The name of the hook we're trying to find.
  913. * @return The {@link File} containing this particular hook if it exists in
  914. * the given repository, <code>null</code> otherwise.
  915. * @since 4.0
  916. */
  917. public File findHook(Repository repository, final String hookName) {
  918. File gitDir = repository.getDirectory();
  919. if (gitDir == null)
  920. return null;
  921. final File hookFile = new File(new File(gitDir,
  922. Constants.HOOKS), hookName);
  923. return hookFile.isFile() ? hookFile : null;
  924. }
  925. /**
  926. * Runs the given process until termination, clearing its stdout and stderr
  927. * streams on-the-fly.
  928. *
  929. * @param processBuilder
  930. * The process builder configured for this process.
  931. * @param outRedirect
  932. * A OutputStream on which to redirect the processes stdout. Can
  933. * be <code>null</code>, in which case the processes standard
  934. * output will be lost.
  935. * @param errRedirect
  936. * A OutputStream on which to redirect the processes stderr. Can
  937. * be <code>null</code>, in which case the processes standard
  938. * error will be lost.
  939. * @param stdinArgs
  940. * A string to pass on to the standard input of the hook. Can be
  941. * <code>null</code>.
  942. * @return the exit value of this process.
  943. * @throws IOException
  944. * if an I/O error occurs while executing this process.
  945. * @throws InterruptedException
  946. * if the current thread is interrupted while waiting for the
  947. * process to end.
  948. * @since 4.2
  949. */
  950. public int runProcess(ProcessBuilder processBuilder,
  951. OutputStream outRedirect, OutputStream errRedirect, String stdinArgs)
  952. throws IOException, InterruptedException {
  953. InputStream in = (stdinArgs == null) ? null : new ByteArrayInputStream(
  954. stdinArgs.getBytes(Constants.CHARACTER_ENCODING));
  955. return runProcess(processBuilder, outRedirect, errRedirect, in);
  956. }
  957. /**
  958. * Runs the given process until termination, clearing its stdout and stderr
  959. * streams on-the-fly.
  960. *
  961. * @param processBuilder
  962. * The process builder configured for this process.
  963. * @param outRedirect
  964. * An OutputStream on which to redirect the processes stdout. Can
  965. * be <code>null</code>, in which case the processes standard
  966. * output will be lost.
  967. * @param errRedirect
  968. * An OutputStream on which to redirect the processes stderr. Can
  969. * be <code>null</code>, in which case the processes standard
  970. * error will be lost.
  971. * @param inRedirect
  972. * An InputStream from which to redirect the processes stdin. Can
  973. * be <code>null</code>, in which case the process doesn't get
  974. * any data over stdin. It is assumed that the whole InputStream
  975. * will be consumed by the process. The method will close the
  976. * inputstream after all bytes are read.
  977. * @return the return code of this process.
  978. * @throws IOException
  979. * if an I/O error occurs while executing this process.
  980. * @throws InterruptedException
  981. * if the current thread is interrupted while waiting for the
  982. * process to end.
  983. * @since 4.2
  984. */
  985. public int runProcess(ProcessBuilder processBuilder,
  986. OutputStream outRedirect, OutputStream errRedirect,
  987. InputStream inRedirect) throws IOException,
  988. InterruptedException {
  989. final ExecutorService executor = Executors.newFixedThreadPool(2);
  990. Process process = null;
  991. // We'll record the first I/O exception that occurs, but keep on trying
  992. // to dispose of our open streams and file handles
  993. IOException ioException = null;
  994. try {
  995. process = processBuilder.start();
  996. executor.execute(
  997. new StreamGobbler(process.getErrorStream(), errRedirect));
  998. executor.execute(
  999. new StreamGobbler(process.getInputStream(), outRedirect));
  1000. OutputStream outputStream = process.getOutputStream();
  1001. if (inRedirect != null) {
  1002. new StreamGobbler(inRedirect, outputStream).copy();
  1003. }
  1004. try {
  1005. outputStream.close();
  1006. } catch (IOException e) {
  1007. // When the process exits before consuming the input, the OutputStream
  1008. // is replaced with the null output stream. This null output stream
  1009. // throws IOException for all write calls. When StreamGobbler fails to
  1010. // flush the buffer because of this, this close call tries to flush it
  1011. // again. This causes another IOException. Since we ignore the
  1012. // IOException in StreamGobbler, we also ignore the exception here.
  1013. }
  1014. return process.waitFor();
  1015. } catch (IOException e) {
  1016. ioException = e;
  1017. } finally {
  1018. shutdownAndAwaitTermination(executor);
  1019. if (process != null) {
  1020. try {
  1021. process.waitFor();
  1022. } catch (InterruptedException e) {
  1023. // Thrown by the outer try.
  1024. // Swallow this one to carry on our cleanup, and clear the
  1025. // interrupted flag (processes throw the exception without
  1026. // clearing the flag).
  1027. Thread.interrupted();
  1028. }
  1029. // A process doesn't clean its own resources even when destroyed
  1030. // Explicitly try and close all three streams, preserving the
  1031. // outer I/O exception if any.
  1032. if (inRedirect != null) {
  1033. inRedirect.close();
  1034. }
  1035. try {
  1036. process.getErrorStream().close();
  1037. } catch (IOException e) {
  1038. ioException = ioException != null ? ioException : e;
  1039. }
  1040. try {
  1041. process.getInputStream().close();
  1042. } catch (IOException e) {
  1043. ioException = ioException != null ? ioException : e;
  1044. }
  1045. try {
  1046. process.getOutputStream().close();
  1047. } catch (IOException e) {
  1048. ioException = ioException != null ? ioException : e;
  1049. }
  1050. process.destroy();
  1051. }
  1052. }
  1053. // We can only be here if the outer try threw an IOException.
  1054. throw ioException;
  1055. }
  1056. /**
  1057. * Shuts down an {@link ExecutorService} in two phases, first by calling
  1058. * {@link ExecutorService#shutdown() shutdown} to reject incoming tasks, and
  1059. * then calling {@link ExecutorService#shutdownNow() shutdownNow}, if
  1060. * necessary, to cancel any lingering tasks. Returns true if the pool has
  1061. * been properly shutdown, false otherwise.
  1062. * <p>
  1063. *
  1064. * @param pool
  1065. * the pool to shutdown
  1066. * @return <code>true</code> if the pool has been properly shutdown,
  1067. * <code>false</code> otherwise.
  1068. */
  1069. private static boolean shutdownAndAwaitTermination(ExecutorService pool) {
  1070. boolean hasShutdown = true;
  1071. pool.shutdown(); // Disable new tasks from being submitted
  1072. try {
  1073. // Wait a while for existing tasks to terminate
  1074. if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
  1075. pool.shutdownNow(); // Cancel currently executing tasks
  1076. // Wait a while for tasks to respond to being canceled
  1077. if (!pool.awaitTermination(60, TimeUnit.SECONDS))
  1078. hasShutdown = false;
  1079. }
  1080. } catch (InterruptedException ie) {
  1081. // (Re-)Cancel if current thread also interrupted
  1082. pool.shutdownNow();
  1083. // Preserve interrupt status
  1084. Thread.currentThread().interrupt();
  1085. hasShutdown = false;
  1086. }
  1087. return hasShutdown;
  1088. }
  1089. /**
  1090. * Initialize a ProcessBuilder to run a command using the system shell.
  1091. *
  1092. * @param cmd
  1093. * command to execute. This string should originate from the
  1094. * end-user, and thus is platform specific.
  1095. * @param args
  1096. * arguments to pass to command. These should be protected from
  1097. * shell evaluation.
  1098. * @return a partially completed process builder. Caller should finish
  1099. * populating directory, environment, and then start the process.
  1100. */
  1101. public abstract ProcessBuilder runInShell(String cmd, String[] args);
  1102. /**
  1103. * Execute a command defined by a {@link ProcessBuilder}.
  1104. *
  1105. * @param pb
  1106. * The command to be executed
  1107. * @param in
  1108. * The standard input stream passed to the process
  1109. * @return The result of the executed command
  1110. * @throws InterruptedException
  1111. * @throws IOException
  1112. * @since 4.2
  1113. */
  1114. public ExecutionResult execute(ProcessBuilder pb, InputStream in)
  1115. throws IOException, InterruptedException {
  1116. TemporaryBuffer stdout = new TemporaryBuffer.LocalFile(null);
  1117. TemporaryBuffer stderr = new TemporaryBuffer.Heap(1024, 1024 * 1024);
  1118. try {
  1119. int rc = runProcess(pb, stdout, stderr, in);
  1120. return new ExecutionResult(stdout, stderr, rc);
  1121. } finally {
  1122. stdout.close();
  1123. stderr.close();
  1124. }
  1125. }
  1126. private static class Holder<V> {
  1127. final V value;
  1128. Holder(V value) {
  1129. this.value = value;
  1130. }
  1131. }
  1132. /**
  1133. * File attributes we typically care for.
  1134. *
  1135. * @since 3.3
  1136. */
  1137. public static class Attributes {
  1138. /**
  1139. * @return true if this are the attributes of a directory
  1140. */
  1141. public boolean isDirectory() {
  1142. return isDirectory;
  1143. }
  1144. /**
  1145. * @return true if this are the attributes of an executable file
  1146. */
  1147. public boolean isExecutable() {
  1148. return isExecutable;
  1149. }
  1150. /**
  1151. * @return true if this are the attributes of a symbolic link
  1152. */
  1153. public boolean isSymbolicLink() {
  1154. return isSymbolicLink;
  1155. }
  1156. /**
  1157. * @return true if this are the attributes of a regular file
  1158. */
  1159. public boolean isRegularFile() {
  1160. return isRegularFile;
  1161. }
  1162. /**
  1163. * @return the time when the file was created
  1164. */
  1165. public long getCreationTime() {
  1166. return creationTime;
  1167. }
  1168. /**
  1169. * @return the time (milliseconds since 1970-01-01) when this object was
  1170. * last modified
  1171. */
  1172. public long getLastModifiedTime() {
  1173. return lastModifiedTime;
  1174. }
  1175. private final boolean isDirectory;
  1176. private final boolean isSymbolicLink;
  1177. private final boolean isRegularFile;
  1178. private final long creationTime;
  1179. private final long lastModifiedTime;
  1180. private final boolean isExecutable;
  1181. private final File file;
  1182. private final boolean exists;
  1183. /**
  1184. * file length
  1185. */
  1186. protected long length = -1;
  1187. final FS fs;
  1188. Attributes(FS fs, File file, boolean exists, boolean isDirectory,
  1189. boolean isExecutable, boolean isSymbolicLink,
  1190. boolean isRegularFile, long creationTime,
  1191. long lastModifiedTime, long length) {
  1192. this.fs = fs;
  1193. this.file = file;
  1194. this.exists = exists;
  1195. this.isDirectory = isDirectory;
  1196. this.isExecutable = isExecutable;
  1197. this.isSymbolicLink = isSymbolicLink;
  1198. this.isRegularFile = isRegularFile;
  1199. this.creationTime = creationTime;
  1200. this.lastModifiedTime = lastModifiedTime;
  1201. this.length = length;
  1202. }
  1203. /**
  1204. * Constructor when there are issues with reading. All attributes except
  1205. * given will be set to the default values.
  1206. *
  1207. * @param fs
  1208. * @param path
  1209. */
  1210. public Attributes(File path, FS fs) {
  1211. this(fs, path, false, false, false, false, false, 0L, 0L, 0L);
  1212. }
  1213. /**
  1214. * @return length of this file object
  1215. */
  1216. public long getLength() {
  1217. if (length == -1)
  1218. return length = file.length();
  1219. return length;
  1220. }
  1221. /**
  1222. * @return the filename
  1223. */
  1224. public String getName() {
  1225. return file.getName();
  1226. }
  1227. /**
  1228. * @return the file the attributes apply to
  1229. */
  1230. public File getFile() {
  1231. return file;
  1232. }
  1233. boolean exists() {
  1234. return exists;
  1235. }
  1236. }
  1237. /**
  1238. * @param path
  1239. * @return the file attributes we care for
  1240. * @since 3.3
  1241. */
  1242. public Attributes getAttributes(File path) {
  1243. boolean isDirectory = isDirectory(path);
  1244. boolean isFile = !isDirectory && path.isFile();
  1245. assert path.exists() == isDirectory || isFile;
  1246. boolean exists = isDirectory || isFile;
  1247. boolean canExecute = exists && !isDirectory && canExecute(path);
  1248. boolean isSymlink = false;
  1249. long lastModified = exists ? path.lastModified() : 0L;
  1250. long createTime = 0L;
  1251. return new Attributes(this, path, exists, isDirectory, canExecute,
  1252. isSymlink, isFile, createTime, lastModified, -1);
  1253. }
  1254. /**
  1255. * Normalize the unicode path to composed form.
  1256. *
  1257. * @param file
  1258. * @return NFC-format File
  1259. * @since 3.3
  1260. */
  1261. public File normalize(File file) {
  1262. return file;
  1263. }
  1264. /**
  1265. * Normalize the unicode path to composed form.
  1266. *
  1267. * @param name
  1268. * @return NFC-format string
  1269. * @since 3.3
  1270. */
  1271. public String normalize(String name) {
  1272. return name;
  1273. }
  1274. /**
  1275. * This runnable will consume an input stream's content into an output
  1276. * stream as soon as it gets available.
  1277. * <p>
  1278. * Typically used to empty processes' standard output and error, preventing
  1279. * them to choke.
  1280. * </p>
  1281. * <p>
  1282. * <b>Note</b> that a {@link StreamGobbler} will never close either of its
  1283. * streams.
  1284. * </p>
  1285. */
  1286. private static class StreamGobbler implements Runnable {
  1287. private InputStream in;
  1288. private OutputStream out;
  1289. public StreamGobbler(InputStream stream, OutputStream output) {
  1290. this.in = stream;
  1291. this.out = output;
  1292. }
  1293. @Override
  1294. public void run() {
  1295. try {
  1296. copy();
  1297. } catch (IOException e) {
  1298. // Do nothing on read failure; leave streams open.
  1299. }
  1300. }
  1301. void copy() throws IOException {
  1302. boolean writeFailure = false;
  1303. byte buffer[] = new byte[4096];
  1304. int readBytes;
  1305. while ((readBytes = in.read(buffer)) != -1) {
  1306. // Do not try to write again after a failure, but keep
  1307. // reading as long as possible to prevent the input stream
  1308. // from choking.
  1309. if (!writeFailure && out != null) {
  1310. try {
  1311. out.write(buffer, 0, readBytes);
  1312. out.flush();
  1313. } catch (IOException e) {
  1314. writeFailure = true;
  1315. }
  1316. }
  1317. }
  1318. }
  1319. }
  1320. }