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("$0 \"$@\"");
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
240 @Override
241 public ProcessResult runHookIfPresent(Repository repository, String hookName,
242 String[] args, PrintStream outRedirect, PrintStream errRedirect,
243 String stdinArgs) throws JGitInternalException {
244 return internalRunHookIfPresent(repository, hookName, args, outRedirect,
245 errRedirect, stdinArgs);
246 }
247
248
249 @Override
250 public boolean retryFailedLockFileCommit() {
251 return false;
252 }
253
254
255 @Override
256 public void setHidden(File path, boolean hidden) throws IOException {
257
258 }
259
260
261 @Override
262 public Attributes getAttributes(File path) {
263 return FileUtils.getFileAttributesPosix(this, path);
264 }
265
266
267 @Override
268 public File normalize(File file) {
269 return FileUtils.normalize(file);
270 }
271
272
273 @Override
274 public String normalize(String name) {
275 return FileUtils.normalize(name);
276 }
277
278
279 @Override
280 public boolean supportsAtomicCreateNewFile() {
281 if (supportsAtomicFileCreation == AtomicFileCreation.UNDEFINED) {
282 try {
283 StoredConfig config = SystemReader.getInstance().getUserConfig();
284 String value = config.getString(CONFIG_CORE_SECTION, null,
285 CONFIG_KEY_SUPPORTSATOMICFILECREATION);
286 if (value != null) {
287 supportsAtomicFileCreation = StringUtils.toBoolean(value)
288 ? AtomicFileCreation.SUPPORTED
289 : AtomicFileCreation.NOT_SUPPORTED;
290 } else {
291 supportsAtomicFileCreation = AtomicFileCreation.SUPPORTED;
292 }
293 } catch (IOException | ConfigInvalidException e) {
294 LOG.warn(JGitText.get().assumeAtomicCreateNewFile, e);
295 supportsAtomicFileCreation = AtomicFileCreation.SUPPORTED;
296 }
297 }
298 return supportsAtomicFileCreation == AtomicFileCreation.SUPPORTED;
299 }
300
301 @Override
302 @SuppressWarnings("boxing")
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322 @Deprecated
323 public boolean createNewFile(File lock) throws IOException {
324 if (!lock.createNewFile()) {
325 return false;
326 }
327 if (supportsAtomicCreateNewFile()) {
328 return true;
329 }
330 Path lockPath = lock.toPath();
331 Path link = null;
332 FileStore store = null;
333 try {
334 store = Files.getFileStore(lockPath);
335 } catch (SecurityException e) {
336 return true;
337 }
338 try {
339 Boolean canLink = CAN_HARD_LINK.computeIfAbsent(store,
340 s -> Boolean.TRUE);
341 if (Boolean.FALSE.equals(canLink)) {
342 return true;
343 }
344 link = Files.createLink(
345 Paths.get(lock.getAbsolutePath() + ".lnk"),
346 lockPath);
347 Integer nlink = (Integer) (Files.getAttribute(lockPath,
348 "unix:nlink"));
349 if (nlink > 2) {
350 LOG.warn(MessageFormat.format(
351 JGitText.get().failedAtomicFileCreation, lockPath,
352 nlink));
353 return false;
354 } else if (nlink < 2) {
355 CAN_HARD_LINK.put(store, Boolean.FALSE);
356 }
357 return true;
358 } catch (UnsupportedOperationException | IllegalArgumentException e) {
359 CAN_HARD_LINK.put(store, Boolean.FALSE);
360 return true;
361 } finally {
362 if (link != null) {
363 Files.delete(link);
364 }
365 }
366 }
367
368
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 @Override
394 public LockToken createNewFileAtomic(File file) throws IOException {
395 Path path;
396 try {
397 path = file.toPath();
398 Files.createFile(path);
399 } catch (FileAlreadyExistsException | InvalidPathException e) {
400 return token(false, null);
401 }
402 if (supportsAtomicCreateNewFile()) {
403 return token(true, null);
404 }
405 Path link = null;
406 FileStore store = null;
407 try {
408 store = Files.getFileStore(path);
409 } catch (SecurityException e) {
410 return token(true, null);
411 }
412 try {
413 Boolean canLink = CAN_HARD_LINK.computeIfAbsent(store,
414 s -> Boolean.TRUE);
415 if (Boolean.FALSE.equals(canLink)) {
416 return token(true, null);
417 }
418 link = Files.createLink(Paths.get(uniqueLinkPath(file)), path);
419 Integer nlink = (Integer) (Files.getAttribute(path,
420 "unix:nlink"));
421 if (nlink.intValue() > 2) {
422 LOG.warn(MessageFormat.format(
423 JGitText.get().failedAtomicFileCreation, path, nlink));
424 return token(false, link);
425 } else if (nlink.intValue() < 2) {
426 CAN_HARD_LINK.put(store, Boolean.FALSE);
427 }
428 return token(true, link);
429 } catch (UnsupportedOperationException | IllegalArgumentException
430 | FileSystemException | SecurityException e) {
431 CAN_HARD_LINK.put(store, Boolean.FALSE);
432 return token(true, link);
433 }
434 }
435
436 private static LockToken token(boolean created, @Nullable Path p) {
437 return ((p != null) && Files.exists(p))
438 ? new LockToken(created, Optional.of(p))
439 : new LockToken(created, Optional.empty());
440 }
441
442 private static String uniqueLinkPath(File file) {
443 UUID id = UUID.randomUUID();
444 return file.getAbsolutePath() + "."
445 + Long.toHexString(id.getMostSignificantBits())
446 + Long.toHexString(id.getLeastSignificantBits());
447 }
448 }