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