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 46KB

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