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