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