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