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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622
  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.File;
  46. import java.io.IOException;
  47. import java.io.InputStream;
  48. import java.io.InputStreamReader;
  49. import java.security.AccessController;
  50. import java.security.PrivilegedAction;
  51. import java.util.Arrays;
  52. import java.util.concurrent.atomic.AtomicBoolean;
  53. import org.eclipse.jgit.errors.SymlinksNotSupportedException;
  54. import org.eclipse.jgit.internal.JGitText;
  55. /** Abstraction to support various file system operations not in Java. */
  56. public abstract class FS {
  57. /**
  58. * This class creates FS instances. It will be overridden by a Java7 variant
  59. * if such can be detected in {@link #detect(Boolean)}.
  60. *
  61. * @since 3.0
  62. */
  63. public static class FSFactory {
  64. /**
  65. * Constructor
  66. */
  67. protected FSFactory() {
  68. // empty
  69. }
  70. /**
  71. * Detect the file system
  72. *
  73. * @param cygwinUsed
  74. * @return FS instance
  75. */
  76. public FS detect(Boolean cygwinUsed) {
  77. if (SystemReader.getInstance().isWindows()) {
  78. if (cygwinUsed == null)
  79. cygwinUsed = Boolean.valueOf(FS_Win32_Cygwin.isCygwin());
  80. if (cygwinUsed.booleanValue())
  81. return new FS_Win32_Cygwin();
  82. else
  83. return new FS_Win32();
  84. } else if (FS_POSIX_Java6.hasExecute())
  85. return new FS_POSIX_Java6();
  86. else
  87. return new FS_POSIX_Java5();
  88. }
  89. }
  90. /** The auto-detected implementation selected for this operating system and JRE. */
  91. public static final FS DETECTED = detect();
  92. private static FSFactory factory;
  93. /**
  94. * Auto-detect the appropriate file system abstraction.
  95. *
  96. * @return detected file system abstraction
  97. */
  98. public static FS detect() {
  99. return detect(null);
  100. }
  101. /**
  102. * Auto-detect the appropriate file system abstraction, taking into account
  103. * the presence of a Cygwin installation on the system. Using jgit in
  104. * combination with Cygwin requires a more elaborate (and possibly slower)
  105. * resolution of file system paths.
  106. *
  107. * @param cygwinUsed
  108. * <ul>
  109. * <li><code>Boolean.TRUE</code> to assume that Cygwin is used in
  110. * combination with jgit</li>
  111. * <li><code>Boolean.FALSE</code> to assume that Cygwin is
  112. * <b>not</b> used with jgit</li>
  113. * <li><code>null</code> to auto-detect whether a Cygwin
  114. * installation is present on the system and in this case assume
  115. * that Cygwin is used</li>
  116. * </ul>
  117. *
  118. * Note: this parameter is only relevant on Windows.
  119. *
  120. * @return detected file system abstraction
  121. */
  122. public static FS detect(Boolean cygwinUsed) {
  123. if (factory == null) {
  124. try {
  125. Class<?> activatorClass = Class
  126. .forName("org.eclipse.jgit.util.Java7FSFactory"); //$NON-NLS-1$
  127. // found Java7
  128. factory = (FSFactory) activatorClass.newInstance();
  129. } catch (ClassNotFoundException e) {
  130. // Java7 module not found
  131. factory = new FS.FSFactory();
  132. // Silently ignore failure to find Java7 FS factory
  133. } catch (UnsupportedClassVersionError e) {
  134. // Java7 module not accessible
  135. factory = new FS.FSFactory();
  136. } catch (Exception e) {
  137. factory = new FS.FSFactory();
  138. throw new Error(e);
  139. }
  140. }
  141. return factory.detect(cygwinUsed);
  142. }
  143. private volatile Holder<File> userHome;
  144. private volatile Holder<File> gitPrefix;
  145. /**
  146. * Constructs a file system abstraction.
  147. */
  148. protected FS() {
  149. // Do nothing by default.
  150. }
  151. /**
  152. * Initialize this FS using another's current settings.
  153. *
  154. * @param src
  155. * the source FS to copy from.
  156. */
  157. protected FS(FS src) {
  158. userHome = src.userHome;
  159. gitPrefix = src.gitPrefix;
  160. }
  161. /** @return a new instance of the same type of FS. */
  162. public abstract FS newInstance();
  163. /**
  164. * Does this operating system and JRE support the execute flag on files?
  165. *
  166. * @return true if this implementation can provide reasonably accurate
  167. * executable bit information; false otherwise.
  168. */
  169. public abstract boolean supportsExecute();
  170. /**
  171. * Does this operating system and JRE supports symbolic links. The
  172. * capability to handle symbolic links is detected at runtime.
  173. *
  174. * @return true if symbolic links may be used
  175. * @since 3.0
  176. */
  177. public boolean supportsSymlinks() {
  178. return false;
  179. }
  180. /**
  181. * Is this file system case sensitive
  182. *
  183. * @return true if this implementation is case sensitive
  184. */
  185. public abstract boolean isCaseSensitive();
  186. /**
  187. * Determine if the file is executable (or not).
  188. * <p>
  189. * Not all platforms and JREs support executable flags on files. If the
  190. * feature is unsupported this method will always return false.
  191. * <p>
  192. * <em>If the platform supports symbolic links and <code>f</code> is a symbolic link
  193. * this method returns false, rather than the state of the executable flags
  194. * on the target file.</em>
  195. *
  196. * @param f
  197. * abstract path to test.
  198. * @return true if the file is believed to be executable by the user.
  199. */
  200. public abstract boolean canExecute(File f);
  201. /**
  202. * Set a file to be executable by the user.
  203. * <p>
  204. * Not all platforms and JREs support executable flags on files. If the
  205. * feature is unsupported this method will always return false and no
  206. * changes will be made to the file specified.
  207. *
  208. * @param f
  209. * path to modify the executable status of.
  210. * @param canExec
  211. * true to enable execution; false to disable it.
  212. * @return true if the change succeeded; false otherwise.
  213. */
  214. public abstract boolean setExecute(File f, boolean canExec);
  215. /**
  216. * Get the last modified time of a file system object. If the OS/JRE support
  217. * symbolic links, the modification time of the link is returned, rather
  218. * than that of the link target.
  219. *
  220. * @param f
  221. * @return last modified time of f
  222. * @throws IOException
  223. * @since 3.0
  224. */
  225. public long lastModified(File f) throws IOException {
  226. return f.lastModified();
  227. }
  228. /**
  229. * Set the last modified time of a file system object. If the OS/JRE support
  230. * symbolic links, the link is modified, not the target,
  231. *
  232. * @param f
  233. * @param time
  234. * @throws IOException
  235. * @since 3.0
  236. */
  237. public void setLastModified(File f, long time) throws IOException {
  238. f.setLastModified(time);
  239. }
  240. /**
  241. * Get the length of a file or link, If the OS/JRE supports symbolic links
  242. * it's the length of the link, else the length of the target.
  243. *
  244. * @param path
  245. * @return length of a file
  246. * @throws IOException
  247. * @since 3.0
  248. */
  249. public long length(File path) throws IOException {
  250. return path.length();
  251. }
  252. /**
  253. * Resolve this file to its actual path name that the JRE can use.
  254. * <p>
  255. * This method can be relatively expensive. Computing a translation may
  256. * require forking an external process per path name translated. Callers
  257. * should try to minimize the number of translations necessary by caching
  258. * the results.
  259. * <p>
  260. * Not all platforms and JREs require path name translation. Currently only
  261. * Cygwin on Win32 require translation for Cygwin based paths.
  262. *
  263. * @param dir
  264. * directory relative to which the path name is.
  265. * @param name
  266. * path name to translate.
  267. * @return the translated path. <code>new File(dir,name)</code> if this
  268. * platform does not require path name translation.
  269. */
  270. public File resolve(final File dir, final String name) {
  271. final File abspn = new File(name);
  272. if (abspn.isAbsolute())
  273. return abspn;
  274. return new File(dir, name);
  275. }
  276. /**
  277. * Determine the user's home directory (location where preferences are).
  278. * <p>
  279. * This method can be expensive on the first invocation if path name
  280. * translation is required. Subsequent invocations return a cached result.
  281. * <p>
  282. * Not all platforms and JREs require path name translation. Currently only
  283. * Cygwin on Win32 requires translation of the Cygwin HOME directory.
  284. *
  285. * @return the user's home directory; null if the user does not have one.
  286. */
  287. public File userHome() {
  288. Holder<File> p = userHome;
  289. if (p == null) {
  290. p = new Holder<File>(userHomeImpl());
  291. userHome = p;
  292. }
  293. return p.value;
  294. }
  295. /**
  296. * Set the user's home directory location.
  297. *
  298. * @param path
  299. * the location of the user's preferences; null if there is no
  300. * home directory for the current user.
  301. * @return {@code this}.
  302. */
  303. public FS setUserHome(File path) {
  304. userHome = new Holder<File>(path);
  305. return this;
  306. }
  307. /**
  308. * Does this file system have problems with atomic renames?
  309. *
  310. * @return true if the caller should retry a failed rename of a lock file.
  311. */
  312. public abstract boolean retryFailedLockFileCommit();
  313. /**
  314. * Determine the user's home directory (location where preferences are).
  315. *
  316. * @return the user's home directory; null if the user does not have one.
  317. */
  318. protected File userHomeImpl() {
  319. final String home = AccessController
  320. .doPrivileged(new PrivilegedAction<String>() {
  321. public String run() {
  322. return System.getProperty("user.home"); //$NON-NLS-1$
  323. }
  324. });
  325. if (home == null || home.length() == 0)
  326. return null;
  327. return new File(home).getAbsoluteFile();
  328. }
  329. /**
  330. * Searches the given path to see if it contains one of the given files.
  331. * Returns the first it finds. Returns null if not found or if path is null.
  332. *
  333. * @param path
  334. * List of paths to search separated by File.pathSeparator
  335. * @param lookFor
  336. * Files to search for in the given path
  337. * @return the first match found, or null
  338. * @since 3.0
  339. **/
  340. protected static File searchPath(final String path, final String... lookFor) {
  341. if (path == null)
  342. return null;
  343. for (final String p : path.split(File.pathSeparator)) {
  344. for (String command : lookFor) {
  345. final File e = new File(p, command);
  346. if (e.isFile())
  347. return e.getAbsoluteFile();
  348. }
  349. }
  350. return null;
  351. }
  352. /**
  353. * Execute a command and return a single line of output as a String
  354. *
  355. * @param dir
  356. * Working directory for the command
  357. * @param command
  358. * as component array
  359. * @param encoding
  360. * @return the one-line output of the command
  361. */
  362. protected static String readPipe(File dir, String[] command, String encoding) {
  363. final boolean debug = Boolean.parseBoolean(SystemReader.getInstance()
  364. .getProperty("jgit.fs.debug")); //$NON-NLS-1$
  365. try {
  366. if (debug)
  367. System.err.println("readpipe " + Arrays.asList(command) + "," //$NON-NLS-1$ //$NON-NLS-2$
  368. + dir);
  369. final Process p = Runtime.getRuntime().exec(command, null, dir);
  370. final BufferedReader lineRead = new BufferedReader(
  371. new InputStreamReader(p.getInputStream(), encoding));
  372. p.getOutputStream().close();
  373. final AtomicBoolean gooblerFail = new AtomicBoolean(false);
  374. Thread gobbler = new Thread() {
  375. public void run() {
  376. InputStream is = p.getErrorStream();
  377. try {
  378. int ch;
  379. if (debug)
  380. while ((ch = is.read()) != -1)
  381. System.err.print((char) ch);
  382. else
  383. while (is.read() != -1) {
  384. // ignore
  385. }
  386. } catch (IOException e) {
  387. // Just print on stderr for debugging
  388. if (debug)
  389. e.printStackTrace(System.err);
  390. gooblerFail.set(true);
  391. }
  392. try {
  393. is.close();
  394. } catch (IOException e) {
  395. // Just print on stderr for debugging
  396. if (debug)
  397. e.printStackTrace(System.err);
  398. gooblerFail.set(true);
  399. }
  400. }
  401. };
  402. gobbler.start();
  403. String r = null;
  404. try {
  405. r = lineRead.readLine();
  406. if (debug) {
  407. System.err.println("readpipe may return '" + r + "'"); //$NON-NLS-1$ //$NON-NLS-2$
  408. System.err.println("(ignoring remaing output:"); //$NON-NLS-1$
  409. }
  410. String l;
  411. while ((l = lineRead.readLine()) != null) {
  412. if (debug)
  413. System.err.println(l);
  414. }
  415. } finally {
  416. p.getErrorStream().close();
  417. lineRead.close();
  418. }
  419. for (;;) {
  420. try {
  421. int rc = p.waitFor();
  422. gobbler.join();
  423. if (rc == 0 && r != null && r.length() > 0
  424. && !gooblerFail.get())
  425. return r;
  426. if (debug)
  427. System.err.println("readpipe rc=" + rc); //$NON-NLS-1$
  428. break;
  429. } catch (InterruptedException ie) {
  430. // Stop bothering me, I have a zombie to reap.
  431. }
  432. }
  433. } catch (IOException e) {
  434. if (debug)
  435. System.err.println(e);
  436. // Ignore error (but report)
  437. }
  438. if (debug)
  439. System.err.println("readpipe returns null"); //$NON-NLS-1$
  440. return null;
  441. }
  442. /** @return the $prefix directory C Git would use. */
  443. public File gitPrefix() {
  444. Holder<File> p = gitPrefix;
  445. if (p == null) {
  446. String overrideGitPrefix = SystemReader.getInstance().getProperty(
  447. "jgit.gitprefix"); //$NON-NLS-1$
  448. if (overrideGitPrefix != null)
  449. p = new Holder<File>(new File(overrideGitPrefix));
  450. else
  451. p = new Holder<File>(discoverGitPrefix());
  452. gitPrefix = p;
  453. }
  454. return p.value;
  455. }
  456. /** @return the $prefix directory C Git would use. */
  457. protected abstract File discoverGitPrefix();
  458. /**
  459. * Set the $prefix directory C Git uses.
  460. *
  461. * @param path
  462. * the directory. Null if C Git is not installed.
  463. * @return {@code this}
  464. */
  465. public FS setGitPrefix(File path) {
  466. gitPrefix = new Holder<File>(path);
  467. return this;
  468. }
  469. /**
  470. * Check if a file is a symbolic link and read it
  471. *
  472. * @param path
  473. * @return target of link or null
  474. * @throws IOException
  475. * @since 3.0
  476. */
  477. public String readSymLink(File path) throws IOException {
  478. throw new SymlinksNotSupportedException(
  479. JGitText.get().errorSymlinksNotSupported);
  480. }
  481. /**
  482. * @param path
  483. * @return true if the path is a symbolic link (and we support these)
  484. * @throws IOException
  485. * @since 3.0
  486. */
  487. public boolean isSymLink(File path) throws IOException {
  488. return false;
  489. }
  490. /**
  491. * Tests if the path exists, in case of a symbolic link, true even if the
  492. * target does not exist
  493. *
  494. * @param path
  495. * @return true if path exists
  496. * @since 3.0
  497. */
  498. public boolean exists(File path) {
  499. return path.exists();
  500. }
  501. /**
  502. * Check if path is a directory. If the OS/JRE supports symbolic links and
  503. * path is a symbolic link to a directory, this method returns false.
  504. *
  505. * @param path
  506. * @return true if file is a directory,
  507. * @since 3.0
  508. */
  509. public boolean isDirectory(File path) {
  510. return path.isDirectory();
  511. }
  512. /**
  513. * Examine if path represents a regular file. If the OS/JRE supports
  514. * symbolic links the test returns false if path represents a symbolic link.
  515. *
  516. * @param path
  517. * @return true if path represents a regular file
  518. * @since 3.0
  519. */
  520. public boolean isFile(File path) {
  521. return path.isFile();
  522. }
  523. /**
  524. * @param path
  525. * @return true if path is hidden, either starts with . on unix or has the
  526. * hidden attribute in windows
  527. * @throws IOException
  528. * @since 3.0
  529. */
  530. public boolean isHidden(File path) throws IOException {
  531. return path.isHidden();
  532. }
  533. /**
  534. * Set the hidden attribute for file whose name starts with a period.
  535. *
  536. * @param path
  537. * @param hidden
  538. * @throws IOException
  539. * @since 3.0
  540. */
  541. public void setHidden(File path, boolean hidden) throws IOException {
  542. if (!path.getName().startsWith(".")) //$NON-NLS-1$
  543. throw new IllegalArgumentException(
  544. "Hiding only allowed for names that start with a period");
  545. }
  546. /**
  547. * Create a symbolic link
  548. *
  549. * @param path
  550. * @param target
  551. * @throws IOException
  552. * @since 3.0
  553. */
  554. public void createSymLink(File path, String target) throws IOException {
  555. throw new SymlinksNotSupportedException(
  556. JGitText.get().errorSymlinksNotSupported);
  557. }
  558. /**
  559. * Initialize a ProcesssBuilder to run a command using the system shell.
  560. *
  561. * @param cmd
  562. * command to execute. This string should originate from the
  563. * end-user, and thus is platform specific.
  564. * @param args
  565. * arguments to pass to command. These should be protected from
  566. * shell evaluation.
  567. * @return a partially completed process builder. Caller should finish
  568. * populating directory, environment, and then start the process.
  569. */
  570. public abstract ProcessBuilder runInShell(String cmd, String[] args);
  571. private static class Holder<V> {
  572. final V value;
  573. Holder(V value) {
  574. this.value = value;
  575. }
  576. }
  577. }