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 void setHidden(File path, boolean hidden) throws IOException {
291
292 }
293
294
295 @Override
296 public Attributes getAttributes(File path) {
297 return FileUtils.getFileAttributesPosix(this, path);
298 }
299
300
301 @Override
302 public File normalize(File file) {
303 return FileUtils.normalize(file);
304 }
305
306
307 @Override
308 public String normalize(String name) {
309 return FileUtils.normalize(name);
310 }
311
312
313 @Override
314 public File findHook(Repository repository, String hookName) {
315 final File gitdir = repository.getDirectory();
316 if (gitdir == null) {
317 return null;
318 }
319 final Path hookPath = gitdir.toPath().resolve(Constants.HOOKS)
320 .resolve(hookName);
321 if (Files.isExecutable(hookPath))
322 return hookPath.toFile();
323 return null;
324 }
325
326
327 @Override
328 public boolean supportsAtomicCreateNewFile() {
329 if (supportsAtomicFileCreation == AtomicFileCreation.UNDEFINED) {
330 try {
331 StoredConfig config = SystemReader.getInstance().getUserConfig();
332 String value = config.getString(CONFIG_CORE_SECTION, null,
333 CONFIG_KEY_SUPPORTSATOMICFILECREATION);
334 if (value != null) {
335 supportsAtomicFileCreation = StringUtils.toBoolean(value)
336 ? AtomicFileCreation.SUPPORTED
337 : AtomicFileCreation.NOT_SUPPORTED;
338 } else {
339 supportsAtomicFileCreation = AtomicFileCreation.SUPPORTED;
340 }
341 } catch (IOException | ConfigInvalidException e) {
342 LOG.warn(JGitText.get().assumeAtomicCreateNewFile, e);
343 supportsAtomicFileCreation = AtomicFileCreation.SUPPORTED;
344 }
345 }
346 return supportsAtomicFileCreation == AtomicFileCreation.SUPPORTED;
347 }
348
349 @Override
350 @SuppressWarnings("boxing")
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370 @Deprecated
371 public boolean createNewFile(File lock) throws IOException {
372 if (!lock.createNewFile()) {
373 return false;
374 }
375 if (supportsAtomicCreateNewFile()) {
376 return true;
377 }
378 Path lockPath = lock.toPath();
379 Path link = null;
380 FileStore store = null;
381 try {
382 store = Files.getFileStore(lockPath);
383 } catch (SecurityException e) {
384 return true;
385 }
386 try {
387 Boolean canLink = CAN_HARD_LINK.computeIfAbsent(store,
388 s -> Boolean.TRUE);
389 if (Boolean.FALSE.equals(canLink)) {
390 return true;
391 }
392 link = Files.createLink(
393 Paths.get(lock.getAbsolutePath() + ".lnk"),
394 lockPath);
395 Integer nlink = (Integer) (Files.getAttribute(lockPath,
396 "unix:nlink"));
397 if (nlink > 2) {
398 LOG.warn(MessageFormat.format(
399 JGitText.get().failedAtomicFileCreation, lockPath,
400 nlink));
401 return false;
402 } else if (nlink < 2) {
403 CAN_HARD_LINK.put(store, Boolean.FALSE);
404 }
405 return true;
406 } catch (UnsupportedOperationException | IllegalArgumentException e) {
407 CAN_HARD_LINK.put(store, Boolean.FALSE);
408 return true;
409 } finally {
410 if (link != null) {
411 Files.delete(link);
412 }
413 }
414 }
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441 @Override
442 public LockToken createNewFileAtomic(File file) throws IOException {
443 Path path;
444 try {
445 path = file.toPath();
446 Files.createFile(path);
447 } catch (FileAlreadyExistsException | InvalidPathException e) {
448 return token(false, null);
449 }
450 if (supportsAtomicCreateNewFile()) {
451 return token(true, null);
452 }
453 Path link = null;
454 FileStore store = null;
455 try {
456 store = Files.getFileStore(path);
457 } catch (SecurityException e) {
458 return token(true, null);
459 }
460 try {
461 Boolean canLink = CAN_HARD_LINK.computeIfAbsent(store,
462 s -> Boolean.TRUE);
463 if (Boolean.FALSE.equals(canLink)) {
464 return token(true, null);
465 }
466 link = Files.createLink(Paths.get(uniqueLinkPath(file)), path);
467 Integer nlink = (Integer) (Files.getAttribute(path,
468 "unix:nlink"));
469 if (nlink.intValue() > 2) {
470 LOG.warn(MessageFormat.format(
471 JGitText.get().failedAtomicFileCreation, path, nlink));
472 return token(false, link);
473 } else if (nlink.intValue() < 2) {
474 CAN_HARD_LINK.put(store, Boolean.FALSE);
475 }
476 return token(true, link);
477 } catch (UnsupportedOperationException | IllegalArgumentException
478 | FileSystemException | SecurityException e) {
479 CAN_HARD_LINK.put(store, Boolean.FALSE);
480 return token(true, link);
481 }
482 }
483
484 private static LockToken token(boolean created, @Nullable Path p) {
485 return ((p != null) && Files.exists(p))
486 ? new LockToken(created, Optional.of(p))
487 : new LockToken(created, Optional.empty());
488 }
489
490 private static String uniqueLinkPath(File file) {
491 UUID id = UUID.randomUUID();
492 return file.getAbsolutePath() + "."
493 + Long.toHexString(id.getMostSignificantBits())
494 + Long.toHexString(id.getLeastSignificantBits());
495 }
496 }