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