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