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