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