Merge "FS_POSIX: Rework umask detection to make it settable"
This commit is contained in:
commit
3852a085bd
|
@ -46,15 +46,10 @@
|
|||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assume.assumeFalse;
|
||||
import static org.junit.Assume.assumeNotNull;
|
||||
import static org.junit.Assume.assumeTrue;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.attribute.PosixFileAttributeView;
|
||||
import java.nio.file.attribute.PosixFilePermission;
|
||||
|
@ -123,29 +118,15 @@ public void testSymlinkAttributes() throws IOException, InterruptedException {
|
|||
|
||||
@Test
|
||||
public void testExecutableAttributes() throws Exception {
|
||||
FS fs = FS.DETECTED;
|
||||
FS fs = FS.DETECTED.newInstance();
|
||||
// If this assumption fails the test is halted and ignored.
|
||||
assumeTrue(fs instanceof FS_POSIX);
|
||||
((FS_POSIX) fs).setUmask(0022);
|
||||
|
||||
File f = new File(trash, "bla");
|
||||
assertTrue(f.createNewFile());
|
||||
assertFalse(fs.canExecute(f));
|
||||
|
||||
String umask = readUmask();
|
||||
assumeNotNull(umask);
|
||||
|
||||
char others = umask.charAt(umask.length() - 1);
|
||||
|
||||
boolean badUmask;
|
||||
if (others != '0' && others != '2' && others != '4' && others != '6') {
|
||||
// umask is set in the way that "others" can not "execute" => git
|
||||
// CLI will not set "execute" attribute for "others", so we also
|
||||
// don't care
|
||||
badUmask = true;
|
||||
} else {
|
||||
badUmask = false;
|
||||
}
|
||||
|
||||
Set<PosixFilePermission> permissions = readPermissions(f);
|
||||
assertTrue(!permissions.contains(PosixFilePermission.OTHERS_EXECUTE));
|
||||
assertTrue(!permissions.contains(PosixFilePermission.GROUP_EXECUTE));
|
||||
|
@ -158,27 +139,21 @@ public void testExecutableAttributes() throws Exception {
|
|||
permissions.contains(PosixFilePermission.OWNER_EXECUTE));
|
||||
assertTrue("'group' execute permission not set",
|
||||
permissions.contains(PosixFilePermission.GROUP_EXECUTE));
|
||||
if (badUmask) {
|
||||
assertFalse("'others' execute permission set",
|
||||
permissions.contains(PosixFilePermission.OTHERS_EXECUTE));
|
||||
System.err.println("WARNING: your system's umask: \"" + umask
|
||||
+ "\" doesn't allow FSJava7Test to test if setting posix"
|
||||
+ " permissions for \"others\" works properly");
|
||||
assumeFalse(badUmask);
|
||||
} else {
|
||||
assertTrue("'others' execute permission not set",
|
||||
permissions.contains(PosixFilePermission.OTHERS_EXECUTE));
|
||||
}
|
||||
}
|
||||
assertTrue("'others' execute permission not set",
|
||||
permissions.contains(PosixFilePermission.OTHERS_EXECUTE));
|
||||
|
||||
private String readUmask() throws Exception {
|
||||
Process p = Runtime.getRuntime().exec(
|
||||
new String[] { "sh", "-c", "umask" }, null, null);
|
||||
final BufferedReader lineRead = new BufferedReader(
|
||||
new InputStreamReader(p.getInputStream(), Charset
|
||||
.defaultCharset().name()));
|
||||
p.waitFor();
|
||||
return lineRead.readLine();
|
||||
((FS_POSIX) fs).setUmask(0033);
|
||||
fs.setExecute(f, false);
|
||||
assertFalse(fs.canExecute(f));
|
||||
fs.setExecute(f, true);
|
||||
|
||||
permissions = readPermissions(f);
|
||||
assertTrue("'owner' execute permission not set",
|
||||
permissions.contains(PosixFilePermission.OWNER_EXECUTE));
|
||||
assertFalse("'group' execute permission set",
|
||||
permissions.contains(PosixFilePermission.GROUP_EXECUTE));
|
||||
assertFalse("'others' execute permission set",
|
||||
permissions.contains(PosixFilePermission.OTHERS_EXECUTE));
|
||||
}
|
||||
|
||||
private Set<PosixFilePermission> readPermissions(File f) throws IOException {
|
||||
|
|
|
@ -66,140 +66,70 @@
|
|||
* @since 3.0
|
||||
*/
|
||||
public class FS_POSIX extends FS {
|
||||
private static final int DEFAULT_UMASK = 0022;
|
||||
private volatile int umask = -1;
|
||||
|
||||
static {
|
||||
String umask = readUmask();
|
||||
|
||||
// umask return value consists of 3 or 4 digits, like "002" or "0002"
|
||||
if (umask != null && umask.length() > 0 && umask.matches("\\d{3,4}")) { //$NON-NLS-1$
|
||||
EXECUTE_FOR_OTHERS = isGranted(PosixFilePermission.OTHERS_EXECUTE,
|
||||
umask);
|
||||
EXECUTE_FOR_GROUP = isGranted(PosixFilePermission.GROUP_EXECUTE,
|
||||
umask);
|
||||
} else {
|
||||
EXECUTE_FOR_OTHERS = null;
|
||||
EXECUTE_FOR_GROUP = null;
|
||||
}
|
||||
/** Default constructor. */
|
||||
protected FS_POSIX() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 4.0
|
||||
* Constructor
|
||||
*
|
||||
* @param src
|
||||
* FS to copy some settings from
|
||||
*/
|
||||
protected static final Boolean EXECUTE_FOR_OTHERS;
|
||||
|
||||
/**
|
||||
* @since 4.0
|
||||
*/
|
||||
protected static final Boolean EXECUTE_FOR_GROUP;
|
||||
protected FS_POSIX(FS src) {
|
||||
super(src);
|
||||
if (src instanceof FS_POSIX) {
|
||||
umask = ((FS_POSIX) src).umask;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public FS newInstance() {
|
||||
return new FS_POSIX();
|
||||
return new FS_POSIX(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Derives requested permission from given octal umask value as defined e.g.
|
||||
* in <a href="http://linux.die.net/man/2/umask">http://linux.die.net/man/2/
|
||||
* umask</a>.
|
||||
* <p>
|
||||
* The umask expected here must consist of 3 or 4 digits. Last three digits
|
||||
* are significant here because they represent file permissions granted to
|
||||
* the "owner", "group" and "others" (in this order).
|
||||
* <p>
|
||||
* Each single digit from the umask represents 3 bits of the mask standing
|
||||
* for "<b>r</b>ead, <b>w</b>rite, e<b>x</b>ecute" permissions (in this
|
||||
* order).
|
||||
* <p>
|
||||
* The possible umask values table:
|
||||
* Set the umask, overriding any value observed from the shell.
|
||||
*
|
||||
* <pre>
|
||||
* Value : Bits:Abbr.: Permission
|
||||
* 0 : 000 :rwx : read, write and execute
|
||||
* 1 : 001 :rw : read and write
|
||||
* 2 : 010 :rx : read and execute
|
||||
* 3 : 011 :r : read only
|
||||
* 4 : 100 :wx : write and execute
|
||||
* 5 : 101 :w : write only
|
||||
* 6 : 110 :x : execute only
|
||||
* 7 : 111 : : no permissions
|
||||
* </pre>
|
||||
* <p>
|
||||
* Note, that umask value is used to "mask" the requested permissions on
|
||||
* file creation by combining the requested permission bit with the
|
||||
* <b>negated</b> value of the umask bit.
|
||||
* <p>
|
||||
* Simply speaking, if a bit is <b>not</b> set in the umask, then the
|
||||
* appropriate right <b>will</b> be granted <b>if</b> requested. If a bit is
|
||||
* set in the umask value, then the appropriate permission will be not
|
||||
* granted.
|
||||
* <p>
|
||||
* Example:
|
||||
* <li>umask 023 ("000 010 011" or rwx rx r) combined with the request to
|
||||
* create an executable file with full set of permissions for everyone (777)
|
||||
* results in the file with permissions 754 (rwx rx r).
|
||||
* <li>umask 002 ("000 000 010" or rwx rwx rx) combined with the request to
|
||||
* create an executable file with full set of permissions for everyone (777)
|
||||
* results in the file with permissions 775 (rwx rwx rx).
|
||||
* <li>umask 002 ("000 000 010" or rwx rwx rx) combined with the request to
|
||||
* create a file without executable rights for everyone (666) results in the
|
||||
* file with permissions 664 (rw rw r).
|
||||
*
|
||||
* @param p
|
||||
* non null permission
|
||||
* @param umask
|
||||
* octal umask value represented by at least three digits. The
|
||||
* digits (read from the end to beginning of the umask) represent
|
||||
* permissions for "others", "group" and "owner".
|
||||
*
|
||||
* @return true if the requested permission is set according to given umask
|
||||
* mask to apply when creating files.
|
||||
* @since 4.0
|
||||
*/
|
||||
protected static Boolean isGranted(PosixFilePermission p, String umask) {
|
||||
char val;
|
||||
switch (p) {
|
||||
case OTHERS_EXECUTE:
|
||||
// Read last digit, because umask is ordered as: User/Group/Others.
|
||||
val = umask.charAt(umask.length() - 1);
|
||||
return isExecuteGranted(val);
|
||||
case GROUP_EXECUTE:
|
||||
val = umask.charAt(umask.length() - 2);
|
||||
return isExecuteGranted(val);
|
||||
default:
|
||||
throw new UnsupportedOperationException(
|
||||
"isGranted() for " + p + " is not implemented!"); //$NON-NLS-1$ //$NON-NLS-2$
|
||||
public void setUmask(int umask) {
|
||||
this.umask = umask;
|
||||
}
|
||||
|
||||
private int umask() {
|
||||
int u = umask;
|
||||
if (u == -1) {
|
||||
u = readUmask();
|
||||
umask = u;
|
||||
}
|
||||
return u;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param c
|
||||
* character representing octal permission value from the table
|
||||
* in {@link #isGranted(PosixFilePermission, String)}
|
||||
* @return true if the "execute" permission is granted according to given
|
||||
* character
|
||||
*/
|
||||
private static Boolean isExecuteGranted(char c) {
|
||||
if (c == '0' || c == '2' || c == '4' || c == '6')
|
||||
return Boolean.TRUE;
|
||||
return Boolean.FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return umask returned from running umask command in a shell
|
||||
* @since 4.0
|
||||
*/
|
||||
protected static String readUmask() {
|
||||
Process p;
|
||||
/** @return mask returned from running {@code umask} command in shell. */
|
||||
private static int readUmask() {
|
||||
try {
|
||||
p = Runtime.getRuntime().exec(
|
||||
new String[] { "sh", "-c", "umask" }, null, null); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
|
||||
Process p = Runtime.getRuntime().exec(
|
||||
new String[] { "sh", "-c", "umask" }, //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
|
||||
null, null);
|
||||
try (BufferedReader lineRead = new BufferedReader(
|
||||
new InputStreamReader(p.getInputStream(), Charset
|
||||
.defaultCharset().name()))) {
|
||||
p.waitFor();
|
||||
return lineRead.readLine();
|
||||
if (p.waitFor() == 0) {
|
||||
String s = lineRead.readLine();
|
||||
if (s.matches("0?\\d{3}")) { //$NON-NLS-1$
|
||||
return Integer.parseInt(s, 8);
|
||||
}
|
||||
}
|
||||
return DEFAULT_UMASK;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
return DEFAULT_UMASK;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -229,23 +159,6 @@ protected File discoverGitPrefix() {
|
|||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default constructor
|
||||
*/
|
||||
protected FS_POSIX() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param src
|
||||
* FS to copy some settings from
|
||||
*/
|
||||
protected FS_POSIX(FS src) {
|
||||
super(src);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCaseSensitive() {
|
||||
return !SystemReader.getInstance().isMacOS();
|
||||
|
@ -265,35 +178,40 @@ public boolean canExecute(File f) {
|
|||
public boolean setExecute(File f, boolean canExecute) {
|
||||
if (!isFile(f))
|
||||
return false;
|
||||
// only if the execute has to be set, and we know the umask
|
||||
if (canExecute && EXECUTE_FOR_OTHERS != null) {
|
||||
try {
|
||||
Path path = f.toPath();
|
||||
Set<PosixFilePermission> pset = Files
|
||||
.getPosixFilePermissions(path);
|
||||
// user is always allowed to set execute
|
||||
pset.add(PosixFilePermission.OWNER_EXECUTE);
|
||||
if (!canExecute)
|
||||
return f.setExecutable(false);
|
||||
|
||||
if (EXECUTE_FOR_GROUP.booleanValue())
|
||||
pset.add(PosixFilePermission.GROUP_EXECUTE);
|
||||
try {
|
||||
Path path = f.toPath();
|
||||
Set<PosixFilePermission> pset = Files.getPosixFilePermissions(path);
|
||||
|
||||
if (EXECUTE_FOR_OTHERS.booleanValue())
|
||||
pset.add(PosixFilePermission.OTHERS_EXECUTE);
|
||||
// owner (user) is always allowed to execute.
|
||||
pset.add(PosixFilePermission.OWNER_EXECUTE);
|
||||
|
||||
Files.setPosixFilePermissions(path, pset);
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
// The interface doesn't allow to throw IOException
|
||||
final boolean debug = Boolean.parseBoolean(SystemReader
|
||||
.getInstance().getProperty("jgit.fs.debug")); //$NON-NLS-1$
|
||||
if (debug)
|
||||
System.err.println(e);
|
||||
return false;
|
||||
}
|
||||
int mask = umask();
|
||||
apply(pset, mask, PosixFilePermission.GROUP_EXECUTE, 1 << 3);
|
||||
apply(pset, mask, PosixFilePermission.OTHERS_EXECUTE, 1);
|
||||
Files.setPosixFilePermissions(path, pset);
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
// The interface doesn't allow to throw IOException
|
||||
final boolean debug = Boolean.parseBoolean(SystemReader
|
||||
.getInstance().getProperty("jgit.fs.debug")); //$NON-NLS-1$
|
||||
if (debug)
|
||||
System.err.println(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static void apply(Set<PosixFilePermission> set,
|
||||
int umask, PosixFilePermission perm, int test) {
|
||||
if ((umask & test) == 0) {
|
||||
// If bit is clear in umask, permission is allowed.
|
||||
set.add(perm);
|
||||
} else {
|
||||
// If bit is set in umask, permission is denied.
|
||||
set.remove(perm);
|
||||
}
|
||||
// if umask is not working for some reason: fall back to default (buggy)
|
||||
// implementation which does not consider umask: see bug 424395
|
||||
return f.setExecutable(canExecute);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
Loading…
Reference in New Issue