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.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertTrue;
|
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 static org.junit.Assume.assumeTrue;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.nio.charset.Charset;
|
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.attribute.PosixFileAttributeView;
|
import java.nio.file.attribute.PosixFileAttributeView;
|
||||||
import java.nio.file.attribute.PosixFilePermission;
|
import java.nio.file.attribute.PosixFilePermission;
|
||||||
|
@ -123,29 +118,15 @@ public void testSymlinkAttributes() throws IOException, InterruptedException {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testExecutableAttributes() throws Exception {
|
public void testExecutableAttributes() throws Exception {
|
||||||
FS fs = FS.DETECTED;
|
FS fs = FS.DETECTED.newInstance();
|
||||||
// If this assumption fails the test is halted and ignored.
|
// If this assumption fails the test is halted and ignored.
|
||||||
assumeTrue(fs instanceof FS_POSIX);
|
assumeTrue(fs instanceof FS_POSIX);
|
||||||
|
((FS_POSIX) fs).setUmask(0022);
|
||||||
|
|
||||||
File f = new File(trash, "bla");
|
File f = new File(trash, "bla");
|
||||||
assertTrue(f.createNewFile());
|
assertTrue(f.createNewFile());
|
||||||
assertFalse(fs.canExecute(f));
|
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);
|
Set<PosixFilePermission> permissions = readPermissions(f);
|
||||||
assertTrue(!permissions.contains(PosixFilePermission.OTHERS_EXECUTE));
|
assertTrue(!permissions.contains(PosixFilePermission.OTHERS_EXECUTE));
|
||||||
assertTrue(!permissions.contains(PosixFilePermission.GROUP_EXECUTE));
|
assertTrue(!permissions.contains(PosixFilePermission.GROUP_EXECUTE));
|
||||||
|
@ -158,27 +139,21 @@ public void testExecutableAttributes() throws Exception {
|
||||||
permissions.contains(PosixFilePermission.OWNER_EXECUTE));
|
permissions.contains(PosixFilePermission.OWNER_EXECUTE));
|
||||||
assertTrue("'group' execute permission not set",
|
assertTrue("'group' execute permission not set",
|
||||||
permissions.contains(PosixFilePermission.GROUP_EXECUTE));
|
permissions.contains(PosixFilePermission.GROUP_EXECUTE));
|
||||||
if (badUmask) {
|
assertTrue("'others' execute permission not set",
|
||||||
assertFalse("'others' execute permission set",
|
permissions.contains(PosixFilePermission.OTHERS_EXECUTE));
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String readUmask() throws Exception {
|
((FS_POSIX) fs).setUmask(0033);
|
||||||
Process p = Runtime.getRuntime().exec(
|
fs.setExecute(f, false);
|
||||||
new String[] { "sh", "-c", "umask" }, null, null);
|
assertFalse(fs.canExecute(f));
|
||||||
final BufferedReader lineRead = new BufferedReader(
|
fs.setExecute(f, true);
|
||||||
new InputStreamReader(p.getInputStream(), Charset
|
|
||||||
.defaultCharset().name()));
|
permissions = readPermissions(f);
|
||||||
p.waitFor();
|
assertTrue("'owner' execute permission not set",
|
||||||
return lineRead.readLine();
|
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 {
|
private Set<PosixFilePermission> readPermissions(File f) throws IOException {
|
||||||
|
|
|
@ -66,140 +66,70 @@
|
||||||
* @since 3.0
|
* @since 3.0
|
||||||
*/
|
*/
|
||||||
public class FS_POSIX extends FS {
|
public class FS_POSIX extends FS {
|
||||||
|
private static final int DEFAULT_UMASK = 0022;
|
||||||
|
private volatile int umask = -1;
|
||||||
|
|
||||||
static {
|
/** Default constructor. */
|
||||||
String umask = readUmask();
|
protected FS_POSIX() {
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @since 4.0
|
* Constructor
|
||||||
|
*
|
||||||
|
* @param src
|
||||||
|
* FS to copy some settings from
|
||||||
*/
|
*/
|
||||||
protected static final Boolean EXECUTE_FOR_OTHERS;
|
protected FS_POSIX(FS src) {
|
||||||
|
super(src);
|
||||||
/**
|
if (src instanceof FS_POSIX) {
|
||||||
* @since 4.0
|
umask = ((FS_POSIX) src).umask;
|
||||||
*/
|
}
|
||||||
protected static final Boolean EXECUTE_FOR_GROUP;
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public FS newInstance() {
|
public FS newInstance() {
|
||||||
return new FS_POSIX();
|
return new FS_POSIX(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Derives requested permission from given octal umask value as defined e.g.
|
* Set the umask, overriding any value observed from the shell.
|
||||||
* 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:
|
|
||||||
*
|
*
|
||||||
* <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
|
* @param umask
|
||||||
* octal umask value represented by at least three digits. The
|
* mask to apply when creating files.
|
||||||
* 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
|
|
||||||
* @since 4.0
|
* @since 4.0
|
||||||
*/
|
*/
|
||||||
protected static Boolean isGranted(PosixFilePermission p, String umask) {
|
public void setUmask(int umask) {
|
||||||
char val;
|
this.umask = umask;
|
||||||
switch (p) {
|
}
|
||||||
case OTHERS_EXECUTE:
|
|
||||||
// Read last digit, because umask is ordered as: User/Group/Others.
|
private int umask() {
|
||||||
val = umask.charAt(umask.length() - 1);
|
int u = umask;
|
||||||
return isExecuteGranted(val);
|
if (u == -1) {
|
||||||
case GROUP_EXECUTE:
|
u = readUmask();
|
||||||
val = umask.charAt(umask.length() - 2);
|
umask = u;
|
||||||
return isExecuteGranted(val);
|
|
||||||
default:
|
|
||||||
throw new UnsupportedOperationException(
|
|
||||||
"isGranted() for " + p + " is not implemented!"); //$NON-NLS-1$ //$NON-NLS-2$
|
|
||||||
}
|
}
|
||||||
|
return u;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** @return mask returned from running {@code umask} command in shell. */
|
||||||
* @param c
|
private static int readUmask() {
|
||||||
* 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;
|
|
||||||
try {
|
try {
|
||||||
p = Runtime.getRuntime().exec(
|
Process p = Runtime.getRuntime().exec(
|
||||||
new String[] { "sh", "-c", "umask" }, null, null); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
|
new String[] { "sh", "-c", "umask" }, //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
|
||||||
|
null, null);
|
||||||
try (BufferedReader lineRead = new BufferedReader(
|
try (BufferedReader lineRead = new BufferedReader(
|
||||||
new InputStreamReader(p.getInputStream(), Charset
|
new InputStreamReader(p.getInputStream(), Charset
|
||||||
.defaultCharset().name()))) {
|
.defaultCharset().name()))) {
|
||||||
p.waitFor();
|
if (p.waitFor() == 0) {
|
||||||
return lineRead.readLine();
|
String s = lineRead.readLine();
|
||||||
|
if (s.matches("0?\\d{3}")) { //$NON-NLS-1$
|
||||||
|
return Integer.parseInt(s, 8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return DEFAULT_UMASK;
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return null;
|
return DEFAULT_UMASK;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -229,23 +159,6 @@ protected File discoverGitPrefix() {
|
||||||
return null;
|
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
|
@Override
|
||||||
public boolean isCaseSensitive() {
|
public boolean isCaseSensitive() {
|
||||||
return !SystemReader.getInstance().isMacOS();
|
return !SystemReader.getInstance().isMacOS();
|
||||||
|
@ -265,35 +178,40 @@ public boolean canExecute(File f) {
|
||||||
public boolean setExecute(File f, boolean canExecute) {
|
public boolean setExecute(File f, boolean canExecute) {
|
||||||
if (!isFile(f))
|
if (!isFile(f))
|
||||||
return false;
|
return false;
|
||||||
// only if the execute has to be set, and we know the umask
|
if (!canExecute)
|
||||||
if (canExecute && EXECUTE_FOR_OTHERS != null) {
|
return f.setExecutable(false);
|
||||||
try {
|
|
||||||
Path path = f.toPath();
|
|
||||||
Set<PosixFilePermission> pset = Files
|
|
||||||
.getPosixFilePermissions(path);
|
|
||||||
// user is always allowed to set execute
|
|
||||||
pset.add(PosixFilePermission.OWNER_EXECUTE);
|
|
||||||
|
|
||||||
if (EXECUTE_FOR_GROUP.booleanValue())
|
try {
|
||||||
pset.add(PosixFilePermission.GROUP_EXECUTE);
|
Path path = f.toPath();
|
||||||
|
Set<PosixFilePermission> pset = Files.getPosixFilePermissions(path);
|
||||||
|
|
||||||
if (EXECUTE_FOR_OTHERS.booleanValue())
|
// owner (user) is always allowed to execute.
|
||||||
pset.add(PosixFilePermission.OTHERS_EXECUTE);
|
pset.add(PosixFilePermission.OWNER_EXECUTE);
|
||||||
|
|
||||||
Files.setPosixFilePermissions(path, pset);
|
int mask = umask();
|
||||||
return true;
|
apply(pset, mask, PosixFilePermission.GROUP_EXECUTE, 1 << 3);
|
||||||
} catch (IOException e) {
|
apply(pset, mask, PosixFilePermission.OTHERS_EXECUTE, 1);
|
||||||
// The interface doesn't allow to throw IOException
|
Files.setPosixFilePermissions(path, pset);
|
||||||
final boolean debug = Boolean.parseBoolean(SystemReader
|
return true;
|
||||||
.getInstance().getProperty("jgit.fs.debug")); //$NON-NLS-1$
|
} catch (IOException e) {
|
||||||
if (debug)
|
// The interface doesn't allow to throw IOException
|
||||||
System.err.println(e);
|
final boolean debug = Boolean.parseBoolean(SystemReader
|
||||||
return false;
|
.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
|
@Override
|
||||||
|
|
Loading…
Reference in New Issue