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