1
2
3
4
5
6
7
8
9
10 package org.eclipse.jgit.util;
11
12 import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_CORE_SECTION;
13 import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_SUPPORTSATOMICFILECREATION;
14
15 import java.io.BufferedReader;
16 import java.io.File;
17 import java.io.IOException;
18 import java.io.InputStreamReader;
19 import java.io.OutputStream;
20 import java.nio.file.FileAlreadyExistsException;
21 import java.nio.file.FileStore;
22 import java.nio.file.FileSystemException;
23 import java.nio.file.Files;
24 import java.nio.file.InvalidPathException;
25 import java.nio.file.Path;
26 import java.nio.file.Paths;
27 import java.nio.file.attribute.PosixFilePermission;
28 import java.text.MessageFormat;
29 import java.util.ArrayList;
30 import java.util.Arrays;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.Optional;
34 import java.util.Set;
35 import java.util.UUID;
36 import java.util.concurrent.ConcurrentHashMap;
37
38 import org.eclipse.jgit.annotations.Nullable;
39 import org.eclipse.jgit.api.errors.JGitInternalException;
40 import org.eclipse.jgit.errors.CommandFailedException;
41 import org.eclipse.jgit.errors.ConfigInvalidException;
42 import org.eclipse.jgit.internal.JGitText;
43 import org.eclipse.jgit.lib.Repository;
44 import org.eclipse.jgit.lib.StoredConfig;
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
47
48
49
50
51
52
53 public class FS_POSIX extends FS {
54 private static final Logger LOG = LoggerFactory.getLogger(FS_POSIX.class);
55
56 private static final String DEFAULT_GIT_LOCATION = "/usr/bin/git";
57
58 private static final int DEFAULT_UMASK = 0022;
59 private volatile int umask = -1;
60
61 private static final Map<FileStore, Boolean> CAN_HARD_LINK = new ConcurrentHashMap<>();
62
63 private volatile AtomicFileCreation supportsAtomicFileCreation = AtomicFileCreation.UNDEFINED;
64
65 private enum AtomicFileCreation {
66 SUPPORTED, NOT_SUPPORTED, UNDEFINED
67 }
68
69
70
71
72 protected FS_POSIX() {
73 }
74
75
76
77
78
79
80
81 protected FS_POSIX(FS src) {
82 super(src);
83 if (src instanceof FS_POSIX) {
84 umask = ((FS_POSIX) src).umask;
85 }
86 }
87
88
89 @Override
90 public FS newInstance() {
91 return new FS_POSIX(this);
92 }
93
94
95
96
97
98
99
100
101 public void setUmask(int umask) {
102 this.umask = umask;
103 }
104
105 private int umask() {
106 int u = umask;
107 if (u == -1) {
108 u = readUmask();
109 umask = u;
110 }
111 return u;
112 }
113
114
115 private static int readUmask() {
116 try {
117 Process p = Runtime.getRuntime().exec(
118 new String[] { "sh", "-c", "umask" },
119 null, null);
120 try (BufferedReader lineRead = new BufferedReader(
121 new InputStreamReader(p.getInputStream(), SystemReader
122 .getInstance().getDefaultCharset().name()))) {
123 if (p.waitFor() == 0) {
124 String s = lineRead.readLine();
125 if (s != null && s.matches("0?\\d{3}")) {
126 return Integer.parseInt(s, 8);
127 }
128 }
129 return DEFAULT_UMASK;
130 }
131 } catch (Exception e) {
132 return DEFAULT_UMASK;
133 }
134 }
135
136
137 @Override
138 protected File discoverGitExe() {
139 String path = SystemReader.getInstance().getenv("PATH");
140 File gitExe = searchPath(path, "git");
141
142 if (SystemReader.getInstance().isMacOS()) {
143 if (gitExe == null
144 || DEFAULT_GIT_LOCATION.equals(gitExe.getPath())) {
145 if (searchPath(path, "bash") != null) {
146
147
148
149 try {
150 String w = readPipe(userHome(),
151 new String[]{"bash", "--login", "-c", "which git"},
152 SystemReader.getInstance().getDefaultCharset()
153 .name());
154 if (!StringUtils.isEmptyOrNull(w)) {
155 gitExe = new File(w);
156 }
157 } catch (CommandFailedException e) {
158 LOG.warn(e.getMessage());
159 }
160 }
161 }
162 if (gitExe != null
163 && DEFAULT_GIT_LOCATION.equals(gitExe.getPath())) {
164
165
166
167
168 try {
169 String w = readPipe(userHome(),
170 new String[] { "xcode-select", "-p" },
171 SystemReader.getInstance().getDefaultCharset()
172 .name());
173 if (StringUtils.isEmptyOrNull(w)) {
174 gitExe = null;
175 } else {
176 File realGitExe = new File(new File(w),
177 DEFAULT_GIT_LOCATION.substring(1));
178 if (!realGitExe.exists()) {
179 gitExe = null;
180 }
181 }
182 } catch (CommandFailedException e) {
183 gitExe = null;
184 }
185 }
186 }
187
188 return gitExe;
189 }
190
191
192 @Override
193 public boolean isCaseSensitive() {
194 return !SystemReader.getInstance().isMacOS();
195 }
196
197
198 @Override
199 public boolean supportsExecute() {
200 return true;
201 }
202
203
204 @Override
205 public boolean canExecute(File f) {
206 return FileUtils.canExecute(f);
207 }
208
209
210 @Override
211 public boolean setExecute(File f, boolean canExecute) {
212 if (!isFile(f))
213 return false;
214 if (!canExecute)
215 return f.setExecutable(false, false);
216
217 try {
218 Path path = FileUtils.toPath(f);
219 Set<PosixFilePermission> pset = Files.getPosixFilePermissions(path);
220
221
222 pset.add(PosixFilePermission.OWNER_EXECUTE);
223
224 int mask = umask();
225 apply(pset, mask, PosixFilePermission.GROUP_EXECUTE, 1 << 3);
226 apply(pset, mask, PosixFilePermission.OTHERS_EXECUTE, 1);
227 Files.setPosixFilePermissions(path, pset);
228 return true;
229 } catch (IOException e) {
230
231 final boolean debug = Boolean.parseBoolean(SystemReader
232 .getInstance().getProperty("jgit.fs.debug"));
233 if (debug)
234 System.err.println(e);
235 return false;
236 }
237 }
238
239 private static void apply(Set<PosixFilePermission> set,
240 int umask, PosixFilePermission perm, int test) {
241 if ((umask & test) == 0) {
242
243 set.add(perm);
244 } else {
245
246 set.remove(perm);
247 }
248 }
249
250
251 @Override
252 public ProcessBuilder runInShell(String cmd, String[] args) {
253 List<String> argv = new ArrayList<>(4 + args.length);
254 argv.add("sh");
255 argv.add("-c");
256 argv.add(cmd + " \"$@\"");
257 argv.add(cmd);
258 argv.addAll(Arrays.asList(args));
259 ProcessBuilder proc = new ProcessBuilder();
260 proc.command(argv);
261 return proc;
262 }
263
264 @Override
265 String shellQuote(String cmd) {
266 return QuotedString.BOURNE.quote(cmd);
267 }
268
269
270 @Override
271 public ProcessResult runHookIfPresent(Repository repository, String hookName,
272 String[] args, OutputStream outRedirect, OutputStream errRedirect,
273 String stdinArgs) throws JGitInternalException {
274 return internalRunHookIfPresent(repository, hookName, args, outRedirect,
275 errRedirect, stdinArgs);
276 }
277
278
279 @Override
280 public boolean retryFailedLockFileCommit() {
281 return false;
282 }
283
284
285 @Override
286 public void setHidden(File path, boolean hidden) throws IOException {
287
288 }
289
290
291 @Override
292 public Attributes getAttributes(File path) {
293 return FileUtils.getFileAttributesPosix(this, path);
294 }
295
296
297 @Override
298 public File normalize(File file) {
299 return FileUtils.normalize(file);
300 }
301
302
303 @Override
304 public String normalize(String name) {
305 return FileUtils.normalize(name);
306 }
307
308
309 @Override
310 public boolean supportsAtomicCreateNewFile() {
311 if (supportsAtomicFileCreation == AtomicFileCreation.UNDEFINED) {
312 try {
313 StoredConfig config = SystemReader.getInstance().getUserConfig();
314 String value = config.getString(CONFIG_CORE_SECTION, null,
315 CONFIG_KEY_SUPPORTSATOMICFILECREATION);
316 if (value != null) {
317 supportsAtomicFileCreation = StringUtils.toBoolean(value)
318 ? AtomicFileCreation.SUPPORTED
319 : AtomicFileCreation.NOT_SUPPORTED;
320 } else {
321 supportsAtomicFileCreation = AtomicFileCreation.SUPPORTED;
322 }
323 } catch (IOException | ConfigInvalidException e) {
324 LOG.warn(JGitText.get().assumeAtomicCreateNewFile, e);
325 supportsAtomicFileCreation = AtomicFileCreation.SUPPORTED;
326 }
327 }
328 return supportsAtomicFileCreation == AtomicFileCreation.SUPPORTED;
329 }
330
331 @Override
332 @SuppressWarnings("boxing")
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352 @Deprecated
353 public boolean createNewFile(File lock) throws IOException {
354 if (!lock.createNewFile()) {
355 return false;
356 }
357 if (supportsAtomicCreateNewFile()) {
358 return true;
359 }
360 Path lockPath = lock.toPath();
361 Path link = null;
362 FileStore store = null;
363 try {
364 store = Files.getFileStore(lockPath);
365 } catch (SecurityException e) {
366 return true;
367 }
368 try {
369 Boolean canLink = CAN_HARD_LINK.computeIfAbsent(store,
370 s -> Boolean.TRUE);
371 if (Boolean.FALSE.equals(canLink)) {
372 return true;
373 }
374 link = Files.createLink(
375 Paths.get(lock.getAbsolutePath() + ".lnk"),
376 lockPath);
377 Integer nlink = (Integer) (Files.getAttribute(lockPath,
378 "unix:nlink"));
379 if (nlink > 2) {
380 LOG.warn(MessageFormat.format(
381 JGitText.get().failedAtomicFileCreation, lockPath,
382 nlink));
383 return false;
384 } else if (nlink < 2) {
385 CAN_HARD_LINK.put(store, Boolean.FALSE);
386 }
387 return true;
388 } catch (UnsupportedOperationException | IllegalArgumentException e) {
389 CAN_HARD_LINK.put(store, Boolean.FALSE);
390 return true;
391 } finally {
392 if (link != null) {
393 Files.delete(link);
394 }
395 }
396 }
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423 @Override
424 public LockToken createNewFileAtomic(File file) throws IOException {
425 Path path;
426 try {
427 path = file.toPath();
428 Files.createFile(path);
429 } catch (FileAlreadyExistsException | InvalidPathException e) {
430 return token(false, null);
431 }
432 if (supportsAtomicCreateNewFile()) {
433 return token(true, null);
434 }
435 Path link = null;
436 FileStore store = null;
437 try {
438 store = Files.getFileStore(path);
439 } catch (SecurityException e) {
440 return token(true, null);
441 }
442 try {
443 Boolean canLink = CAN_HARD_LINK.computeIfAbsent(store,
444 s -> Boolean.TRUE);
445 if (Boolean.FALSE.equals(canLink)) {
446 return token(true, null);
447 }
448 link = Files.createLink(Paths.get(uniqueLinkPath(file)), path);
449 Integer nlink = (Integer) (Files.getAttribute(path,
450 "unix:nlink"));
451 if (nlink.intValue() > 2) {
452 LOG.warn(MessageFormat.format(
453 JGitText.get().failedAtomicFileCreation, path, nlink));
454 return token(false, link);
455 } else if (nlink.intValue() < 2) {
456 CAN_HARD_LINK.put(store, Boolean.FALSE);
457 }
458 return token(true, link);
459 } catch (UnsupportedOperationException | IllegalArgumentException
460 | FileSystemException | SecurityException e) {
461 CAN_HARD_LINK.put(store, Boolean.FALSE);
462 return token(true, link);
463 }
464 }
465
466 private static LockToken token(boolean created, @Nullable Path p) {
467 return ((p != null) && Files.exists(p))
468 ? new LockToken(created, Optional.of(p))
469 : new LockToken(created, Optional.empty());
470 }
471
472 private static String uniqueLinkPath(File file) {
473 UUID id = UUID.randomUUID();
474 return file.getAbsolutePath() + "."
475 + Long.toHexString(id.getMostSignificantBits())
476 + Long.toHexString(id.getLeastSignificantBits());
477 }
478 }