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.

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