From 69007029f122c3f77db044e879188cc12be3c2f6 Mon Sep 17 00:00:00 2001 From: Florian Zschocke Date: Thu, 15 Aug 2013 16:03:25 +0200 Subject: Add method JGitUtils.createRepository(folder, name, shared) to create a new repository as if it was created with the --shared command line switch of git. --- src/main/java/com/gitblit/utils/JGitUtils.java | 102 +++++++++++++++++++++++++ 1 file changed, 102 insertions(+) (limited to 'src/main/java/com/gitblit/utils/JGitUtils.java') diff --git a/src/main/java/com/gitblit/utils/JGitUtils.java b/src/main/java/com/gitblit/utils/JGitUtils.java index 8676d748..345375a9 100644 --- a/src/main/java/com/gitblit/utils/JGitUtils.java +++ b/src/main/java/com/gitblit/utils/JGitUtils.java @@ -58,6 +58,7 @@ import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.RefUpdate.Result; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.RepositoryCache.FileKey; +import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.lib.TreeFormatter; import org.eclipse.jgit.revwalk.RevBlob; import org.eclipse.jgit.revwalk.RevCommit; @@ -88,6 +89,8 @@ import com.gitblit.models.PathModel; import com.gitblit.models.PathModel.PathChangeModel; import com.gitblit.models.RefModel; import com.gitblit.models.SubmoduleModel; +import com.sun.jna.Library; +import com.sun.jna.Native; /** * Collection of static methods for retrieving information from a repository. @@ -267,6 +270,105 @@ public class JGitUtils { } } + /** + * Creates a bare, shared repository. + * + * @param repositoriesFolder + * @param name + * @param shared + * the setting for the --shared option of "git init". + * @return Repository + */ + public static Repository createRepository(File repositoriesFolder, String name, String shared) { + try { + Repository repo = createRepository(repositoriesFolder, name); + + GitConfigSharedRepository sharedRepository = new GitConfigSharedRepository(shared); + if (sharedRepository.isShared()) { + StoredConfig config = repo.getConfig(); + config.setString("core", null, "sharedRepository", sharedRepository.getValue()); + config.setBoolean("receive", null, "denyNonFastforwards", true); + config.save(); + + if (! System.getProperty("os.name").toLowerCase().startsWith("windows")) { + final CLibrary libc = (CLibrary) Native.loadLibrary("c", CLibrary.class); + + //libc.chmod("/path/to/file", 0755); + } + } + + return repo; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + interface CLibrary extends Library { + public int chmod(String path, int mode); + } + private enum GitConfigSharedRepositoryValue { + UMASK("0", 0), FALSE("0", 0), OFF("0", 0), NO("0", 0), + GROUP("1", 0660), TRUE("1", 0660), ON("1", 0660), YES("1", 0660), + ALL("2", 0664), WORLD("2", 0664), EVERYBODY("2", 0664), + Oxxx(null, -1); + + private String configValue; + private int permValue; + private GitConfigSharedRepositoryValue(String config, int perm) { configValue = config; permValue = perm; }; + + public String getConfigValue() { return configValue; }; + public int getPerm() { return permValue; }; + + } + private static class GitConfigSharedRepository + { + private int intValue; + GitConfigSharedRepositoryValue enumValue; + + GitConfigSharedRepository(String s) + { + if ( s == null || s.trim().isEmpty() ) { + enumValue = GitConfigSharedRepositoryValue.GROUP; + } + else { + try { + // Try one of the string values + enumValue = GitConfigSharedRepositoryValue.valueOf(s.trim().toUpperCase()); + } catch (IllegalArgumentException iae) { + try { + // Try if this is an octal number + int i = Integer.parseInt(s, 8); + if ( (i & 0600) != 0600 ) { + String msg = String.format("Problem with core.sharedRepository filemode value (0%03o).\nThe owner of files must always have read and write permissions.", i); + throw new IllegalArgumentException(msg); + } + intValue = i & 0666; + enumValue = GitConfigSharedRepositoryValue.Oxxx; + } catch (NumberFormatException nfe) { + throw new IllegalArgumentException("Bad configuration value for 'shared': '" + s + "'"); + } + } + } + } + + String getValue() + { + if ( enumValue == GitConfigSharedRepositoryValue.Oxxx ) return Integer.toOctalString(intValue); + return enumValue.getConfigValue(); + } + + int getPerm() + { + if ( enumValue == GitConfigSharedRepositoryValue.Oxxx ) return intValue; + return enumValue.getPerm(); + } + + boolean isShared() + { + return (enumValue.getPerm() > 0) || enumValue == GitConfigSharedRepositoryValue.Oxxx; + } + } + + /** * Returns a list of repository names in the specified folder. * -- cgit v1.2.3 From 8a67d9dcdcc63fe48c97e83ffbc2a643eee2ed60 Mon Sep 17 00:00:00 2001 From: Florian Zschocke Date: Sat, 17 Aug 2013 18:37:41 +0200 Subject: Add proper implementation of methods in JnaUtils. Implement the methods getFilemode and setFilemode in JnaUtils. Not using the libc names as we don't necessarily use JNA and because it is not necessarily a one to one mapping. --- src/main/java/com/gitblit/utils/JGitUtils.java | 192 +++++++++-------- src/main/java/com/gitblit/utils/JnaUtils.java | 226 +++++++++++++++++++++ src/test/java/com/gitblit/tests/JGitUtilsTest.java | 21 ++ src/test/java/com/gitblit/tests/JnaUtilsTest.java | 100 +++++++++ 4 files changed, 440 insertions(+), 99 deletions(-) create mode 100644 src/main/java/com/gitblit/utils/JnaUtils.java create mode 100644 src/test/java/com/gitblit/tests/JnaUtilsTest.java (limited to 'src/main/java/com/gitblit/utils/JGitUtils.java') diff --git a/src/main/java/com/gitblit/utils/JGitUtils.java b/src/main/java/com/gitblit/utils/JGitUtils.java index 345375a9..03b54ee9 100644 --- a/src/main/java/com/gitblit/utils/JGitUtils.java +++ b/src/main/java/com/gitblit/utils/JGitUtils.java @@ -89,8 +89,6 @@ import com.gitblit.models.PathModel; import com.gitblit.models.PathModel.PathChangeModel; import com.gitblit.models.RefModel; import com.gitblit.models.SubmoduleModel; -import com.sun.jna.Library; -import com.sun.jna.Native; /** * Collection of static methods for retrieving information from a repository. @@ -270,103 +268,99 @@ public class JGitUtils { } } - /** - * Creates a bare, shared repository. - * - * @param repositoriesFolder - * @param name - * @param shared - * the setting for the --shared option of "git init". - * @return Repository - */ - public static Repository createRepository(File repositoriesFolder, String name, String shared) { - try { - Repository repo = createRepository(repositoriesFolder, name); - - GitConfigSharedRepository sharedRepository = new GitConfigSharedRepository(shared); - if (sharedRepository.isShared()) { - StoredConfig config = repo.getConfig(); - config.setString("core", null, "sharedRepository", sharedRepository.getValue()); - config.setBoolean("receive", null, "denyNonFastforwards", true); - config.save(); - - if (! System.getProperty("os.name").toLowerCase().startsWith("windows")) { - final CLibrary libc = (CLibrary) Native.loadLibrary("c", CLibrary.class); - - //libc.chmod("/path/to/file", 0755); - } - } - - return repo; - } catch (IOException e) { - throw new RuntimeException(e); - } - } - interface CLibrary extends Library { - public int chmod(String path, int mode); - } - private enum GitConfigSharedRepositoryValue { - UMASK("0", 0), FALSE("0", 0), OFF("0", 0), NO("0", 0), - GROUP("1", 0660), TRUE("1", 0660), ON("1", 0660), YES("1", 0660), - ALL("2", 0664), WORLD("2", 0664), EVERYBODY("2", 0664), - Oxxx(null, -1); - - private String configValue; - private int permValue; - private GitConfigSharedRepositoryValue(String config, int perm) { configValue = config; permValue = perm; }; - - public String getConfigValue() { return configValue; }; - public int getPerm() { return permValue; }; - - } - private static class GitConfigSharedRepository - { - private int intValue; - GitConfigSharedRepositoryValue enumValue; - - GitConfigSharedRepository(String s) - { - if ( s == null || s.trim().isEmpty() ) { - enumValue = GitConfigSharedRepositoryValue.GROUP; - } - else { - try { - // Try one of the string values - enumValue = GitConfigSharedRepositoryValue.valueOf(s.trim().toUpperCase()); - } catch (IllegalArgumentException iae) { - try { - // Try if this is an octal number - int i = Integer.parseInt(s, 8); - if ( (i & 0600) != 0600 ) { - String msg = String.format("Problem with core.sharedRepository filemode value (0%03o).\nThe owner of files must always have read and write permissions.", i); - throw new IllegalArgumentException(msg); - } - intValue = i & 0666; - enumValue = GitConfigSharedRepositoryValue.Oxxx; - } catch (NumberFormatException nfe) { - throw new IllegalArgumentException("Bad configuration value for 'shared': '" + s + "'"); - } - } - } - } - - String getValue() - { - if ( enumValue == GitConfigSharedRepositoryValue.Oxxx ) return Integer.toOctalString(intValue); - return enumValue.getConfigValue(); - } - - int getPerm() - { - if ( enumValue == GitConfigSharedRepositoryValue.Oxxx ) return intValue; - return enumValue.getPerm(); - } - - boolean isShared() - { - return (enumValue.getPerm() > 0) || enumValue == GitConfigSharedRepositoryValue.Oxxx; - } - } + /** + * Creates a bare, shared repository. + * + * @param repositoriesFolder + * @param name + * @param shared + * the setting for the --shared option of "git init". + * @return Repository + */ + public static Repository createRepository(File repositoriesFolder, String name, String shared) { + try { + Repository repo = createRepository(repositoriesFolder, name); + + GitConfigSharedRepository sharedRepository = new GitConfigSharedRepository(shared); + if (sharedRepository.isShared()) { + StoredConfig config = repo.getConfig(); + config.setString("core", null, "sharedRepository", sharedRepository.getValue()); + config.setBoolean("receive", null, "denyNonFastforwards", true); + config.save(); + + if (! JnaUtils.isWindows()) { + + //libc.chmod("/path/to/file", 0755); + } + } + + return repo; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + private enum GitConfigSharedRepositoryValue { + UMASK("0", 0), FALSE("0", 0), OFF("0", 0), NO("0", 0), + GROUP("1", 0660), TRUE("1", 0660), ON("1", 0660), YES("1", 0660), + ALL("2", 0664), WORLD("2", 0664), EVERYBODY("2", 0664), + Oxxx(null, -1); + + private String configValue; + private int permValue; + private GitConfigSharedRepositoryValue(String config, int perm) { configValue = config; permValue = perm; }; + + public String getConfigValue() { return configValue; }; + public int getPerm() { return permValue; }; + + } + private static class GitConfigSharedRepository + { + private int intValue; + GitConfigSharedRepositoryValue enumValue; + + GitConfigSharedRepository(String s) + { + if ( s == null || s.trim().isEmpty() ) { + enumValue = GitConfigSharedRepositoryValue.GROUP; + } + else { + try { + // Try one of the string values + enumValue = GitConfigSharedRepositoryValue.valueOf(s.trim().toUpperCase()); + } catch (IllegalArgumentException iae) { + try { + // Try if this is an octal number + int i = Integer.parseInt(s, 8); + if ( (i & 0600) != 0600 ) { + String msg = String.format("Problem with core.sharedRepository filemode value (0%03o).\nThe owner of files must always have read and write permissions.", i); + throw new IllegalArgumentException(msg); + } + intValue = i & 0666; + enumValue = GitConfigSharedRepositoryValue.Oxxx; + } catch (NumberFormatException nfe) { + throw new IllegalArgumentException("Bad configuration value for 'shared': '" + s + "'"); + } + } + } + } + + String getValue() + { + if ( enumValue == GitConfigSharedRepositoryValue.Oxxx ) return Integer.toOctalString(intValue); + return enumValue.getConfigValue(); + } + + int getPerm() + { + if ( enumValue == GitConfigSharedRepositoryValue.Oxxx ) return intValue; + return enumValue.getPerm(); + } + + boolean isShared() + { + return (enumValue.getPerm() > 0) || enumValue == GitConfigSharedRepositoryValue.Oxxx; + } + } /** diff --git a/src/main/java/com/gitblit/utils/JnaUtils.java b/src/main/java/com/gitblit/utils/JnaUtils.java new file mode 100644 index 00000000..b7d7209f --- /dev/null +++ b/src/main/java/com/gitblit/utils/JnaUtils.java @@ -0,0 +1,226 @@ +/* + * Copyright 2013 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.utils; + +import com.sun.jna.Library; +import com.sun.jna.Native; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Collection of static methods to access native OS library functionality. + * + * @author Florian Zschocke + */ +public class JnaUtils { + public static final int S_IFMT = 0170000; + public static final int S_IFIFO = 0010000; + public static final int S_IFCHR = 0020000; + public static final int S_IFDIR = 0040000; + public static final int S_IFBLK = 0060000; + public static final int S_IFREG = 0100000; + public static final int S_IFLNK = 0120000; + public static final int S_IFSOCK = 0140000; + + public static final int S_ISUID = 0004000; + public static final int S_ISGID = 0002000; + public static final int S_ISVTX = 0001000; + + public static final int S_IRWXU = 0000700; + public static final int S_IRUSR = 0000400; + public static final int S_IWUSR = 0000200; + public static final int S_IXUSR = 0000100; + public static final int S_IRWXG = 0000070; + public static final int S_IRGRP = 0000040; + public static final int S_IWGRP = 0000020; + public static final int S_IXGRP = 0000010; + public static final int S_IRWXO = 0000007; + public static final int S_IROTH = 0000004; + public static final int S_IWOTH = 0000002; + public static final int S_IXOTH = 0000001; + + + private static final Logger LOGGER = LoggerFactory.getLogger(JGitUtils.class); + + private static UnixCLibrary unixlibc = null; + + + public static boolean isWindows() + { + return System.getProperty("os.name").toLowerCase().startsWith("windows"); + } + + + private interface UnixCLibrary extends Library { + public int chmod(String path, int mode); + } + + + public static int setFilemode(File path, int mode) + { + return setFilemode(path.getAbsolutePath(), mode); + } + + public static int setFilemode(String path, int mode) + { + if (isWindows()) { + throw new UnsupportedOperationException("The method JnaUtils.getFilemode is not supported under Windows."); + } + + return getUnixCLibrary().chmod(path, mode); + } + + + + public static int getFilemode(File path) + { + return getFilemode(path.getAbsolutePath()); + } + + public static int getFilemode(String path) + { + if (isWindows()) { + throw new UnsupportedOperationException("The method JnaUtils.getFilemode is not supported under Windows."); + } + + + int mode = 0; + + // Use a Runtime, because implementing stat() via JNA is just too much trouble. + String lsLine = runProcessLs(path); + if (lsLine == null) { + LOGGER.debug("Could not get file information for path " + path); + return -1; + } + + Pattern p = Pattern.compile("^(([-bcdlsp])([-r][-w][-xSs])([-r][-w][-xSs])([-r][-w][-xTt])) "); + Matcher m = p.matcher(lsLine); + if ( !m.lookingAt() ) { + LOGGER.debug("Could not parse valid file mode information for path " + path); + return -1; + } + + // Parse mode string to mode bits + String group = m.group(2); + switch (group.charAt(0)) { + case 'p' : + mode |= 0010000; break; + case 'c': + mode |= 0020000; break; + case 'd': + mode |= 0040000; break; + case 'b': + mode |= 0060000; break; + case '-': + mode |= 0100000; break; + case 'l': + mode |= 0120000; break; + case 's': + mode |= 0140000; break; + } + + for ( int i = 0; i < 3; i++) { + group = m.group(3 + i); + switch (group.charAt(0)) { + case 'r': + mode |= (0400 >> i*3); break; + case '-': + break; + } + + switch (group.charAt(1)) { + case 'w': + mode |= (0200 >> i*3); break; + case '-': + break; + } + + switch (group.charAt(2)) { + case 'x': + mode |= (0100 >> i*3); break; + case 'S': + mode |= (04000 >> i); break; + case 's': + mode |= (0100 >> i*3); + mode |= (04000 >> i); break; + case 'T': + mode |= 01000; break; + case 't': + mode |= (0100 >> i*3); + mode |= 01000; break; + case '-': + break; + } + } + + return mode; + } + + + private static String runProcessLs(String path) + { + String cmd = "ls -ldO " + path; + Runtime rt = Runtime.getRuntime(); + Process pr = null; + InputStreamReader ir = null; + BufferedReader br = null; + String output = null; + + try { + pr = rt.exec(cmd); + ir = new InputStreamReader(pr.getInputStream()); + br = new BufferedReader(ir); + + output = br.readLine(); + + while (br.readLine() != null) ; // Swallow remaining output + } + catch (IOException e) { + LOGGER.debug("Exception while running unix command '" + cmd + "': " + e); + } + finally { + if (pr != null) try { pr.waitFor(); } catch (Exception ignored) {} + + if (br != null) try { br.close(); } catch (Exception ignored) {} + if (ir != null) try { ir.close(); } catch (Exception ignored) {} + + if (pr != null) try { pr.getOutputStream().close(); } catch (Exception ignored) {} + if (pr != null) try { pr.getInputStream().close(); } catch (Exception ignored) {} + if (pr != null) try { pr.getErrorStream().close(); } catch (Exception ignored) {} + } + + return output; + } + + + private static UnixCLibrary getUnixCLibrary() + { + if (unixlibc == null) { + unixlibc = (UnixCLibrary) Native.loadLibrary("c", UnixCLibrary.class); + if (unixlibc == null) throw new RuntimeException("Could not initialize native C library."); + } + return unixlibc; + } + +} diff --git a/src/test/java/com/gitblit/tests/JGitUtilsTest.java b/src/test/java/com/gitblit/tests/JGitUtilsTest.java index 375dbd5a..4affca2d 100644 --- a/src/test/java/com/gitblit/tests/JGitUtilsTest.java +++ b/src/test/java/com/gitblit/tests/JGitUtilsTest.java @@ -148,6 +148,27 @@ public class JGitUtilsTest { } } + @Test + public void testCreateRepositoryShared() throws Exception { + String[] repositories = { "NewTestRepository.git", "NewTestRepository" }; + for (String repositoryName : repositories) { + Repository repository = JGitUtils.createRepository(GitBlitSuite.REPOSITORIES, + repositoryName, "group"); + File folder = FileKey.resolve(new File(GitBlitSuite.REPOSITORIES, repositoryName), + FS.DETECTED); + assertNotNull(repository); + assertFalse(JGitUtils.hasCommits(repository)); + assertNull(JGitUtils.getFirstCommit(repository, null)); + assertEquals(folder.lastModified(), JGitUtils.getFirstChange(repository, null) + .getTime()); + assertEquals(folder.lastModified(), JGitUtils.getLastChange(repository).when.getTime()); + assertNull(JGitUtils.getCommit(repository, null)); + repository.close(); + RepositoryCache.close(repository); +// FileUtils.delete(repository.getDirectory(), FileUtils.RECURSIVE); + } + } + @Test public void testRefs() throws Exception { Repository repository = GitBlitSuite.getJGitRepository(); diff --git a/src/test/java/com/gitblit/tests/JnaUtilsTest.java b/src/test/java/com/gitblit/tests/JnaUtilsTest.java new file mode 100644 index 00000000..574686ba --- /dev/null +++ b/src/test/java/com/gitblit/tests/JnaUtilsTest.java @@ -0,0 +1,100 @@ +/* + * Copyright 2011 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.tests; + +import com.gitblit.utils.JGitUtils; +import com.gitblit.utils.JnaUtils; +import java.io.File; +import java.io.IOException; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import org.apache.commons.io.FileUtils; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.RepositoryCache; +import org.eclipse.jgit.lib.RepositoryCache.FileKey; +import org.eclipse.jgit.util.FS; +import org.junit.Test; + +/** + * + * @author Florian Zschocke + */ +public class JnaUtilsTest { + + @Test + public void testGetFilemode() throws IOException { + String repositoryName = "NewJnaTestRepository.git"; + Repository repository = JGitUtils.createRepository(GitBlitSuite.REPOSITORIES, repositoryName); + File folder = FileKey.resolve(new File(GitBlitSuite.REPOSITORIES, repositoryName), FS.DETECTED); + assertTrue(folder.exists()); + + int mode = JnaUtils.getFilemode(folder); + assertTrue(mode > 0); + assertEquals(JnaUtils.S_IFDIR, (mode & JnaUtils.S_IFMT)); // directory + assertEquals(JnaUtils.S_IRUSR | JnaUtils.S_IWUSR | JnaUtils.S_IXUSR, (mode & JnaUtils.S_IRWXU)); // owner full access + + mode = JnaUtils.getFilemode(folder.getAbsolutePath() + "/config"); + assertTrue(mode > 0); + assertEquals(JnaUtils.S_IFREG, (mode & JnaUtils.S_IFMT)); // directory + assertEquals(JnaUtils.S_IRUSR | JnaUtils.S_IWUSR, (mode & JnaUtils.S_IRWXU)); // owner full access + + repository.close(); + RepositoryCache.close(repository); + FileUtils.deleteDirectory(repository.getDirectory()); + } + + + @Test + public void testSetFilemode() throws IOException { + String repositoryName = "NewJnaTestRepository.git"; + Repository repository = JGitUtils.createRepository(GitBlitSuite.REPOSITORIES, repositoryName); + File folder = FileKey.resolve(new File(GitBlitSuite.REPOSITORIES, repositoryName), FS.DETECTED); + assertTrue(folder.exists()); + + File path = new File(folder, "refs"); + int mode = JnaUtils.getFilemode(path); + assertTrue(mode > 0); + assertEquals(JnaUtils.S_IFDIR, (mode & JnaUtils.S_IFMT)); // directory + assertEquals(JnaUtils.S_IRUSR | JnaUtils.S_IWUSR | JnaUtils.S_IXUSR, (mode & JnaUtils.S_IRWXU)); // owner full access + + mode |= JnaUtils.S_ISGID; + mode |= JnaUtils.S_IRWXG; + int ret = JnaUtils.setFilemode(path, mode); + assertEquals(0, ret); + mode = JnaUtils.getFilemode(path); + assertTrue(mode > 0); + assertEquals(JnaUtils.S_ISGID, (mode & JnaUtils.S_ISGID)); // set-gid-bit set + assertEquals(JnaUtils.S_IRGRP | JnaUtils.S_IWGRP | JnaUtils.S_IXGRP, (mode & JnaUtils.S_IRWXG)); // group full access + + path = new File(folder, "config"); + mode = JnaUtils.getFilemode(path.getAbsolutePath()); + assertTrue(mode > 0); + assertEquals(JnaUtils.S_IFREG, (mode & JnaUtils.S_IFMT)); // directory + assertEquals(JnaUtils.S_IRUSR | JnaUtils.S_IWUSR, (mode & JnaUtils.S_IRWXU)); // owner full access + + mode |= (JnaUtils.S_IRGRP | JnaUtils.S_IWGRP); + ret = JnaUtils.setFilemode(path.getAbsolutePath(), mode); + assertEquals(0, ret); + mode = JnaUtils.getFilemode(path.getAbsolutePath()); + assertTrue(mode > 0); + assertEquals(JnaUtils.S_IRGRP | JnaUtils.S_IWGRP, (mode & JnaUtils.S_IRWXG)); // group full access + + repository.close(); + RepositoryCache.close(repository); + FileUtils.deleteDirectory(repository.getDirectory()); + } +} -- cgit v1.2.3 From e85277e1de9f59ac45df5ffd84c5d9be0e4d20d2 Mon Sep 17 00:00:00 2001 From: Florian Zschocke Date: Tue, 20 Aug 2013 10:58:51 +0200 Subject: Implement adjusting file permissions for shared repositories. Implement adjusting file and directory permissions for shared repositories in JGitUtils. --- src/main/java/com/gitblit/utils/JGitUtils.java | 82 +++++++++++++++++----- src/test/java/com/gitblit/tests/JGitUtilsTest.java | 21 ++++-- 2 files changed, 78 insertions(+), 25 deletions(-) (limited to 'src/main/java/com/gitblit/utils/JGitUtils.java') diff --git a/src/main/java/com/gitblit/utils/JGitUtils.java b/src/main/java/com/gitblit/utils/JGitUtils.java index 03b54ee9..49b3ad70 100644 --- a/src/main/java/com/gitblit/utils/JGitUtils.java +++ b/src/main/java/com/gitblit/utils/JGitUtils.java @@ -32,6 +32,7 @@ import java.util.Map; import java.util.Map.Entry; import java.util.regex.Pattern; +import org.apache.commons.io.filefilter.TrueFileFilter; import org.eclipse.jgit.api.CloneCommand; import org.eclipse.jgit.api.FetchCommand; import org.eclipse.jgit.api.Git; @@ -260,12 +261,7 @@ public class JGitUtils { * @return Repository */ public static Repository createRepository(File repositoriesFolder, String name) { - try { - Git git = Git.init().setDirectory(new File(repositoriesFolder, name)).setBare(true).call(); - return git.getRepository(); - } catch (GitAPIException e) { - throw new RuntimeException(e); - } + return createRepository(repositoriesFolder, name, "FALSE"); } /** @@ -279,7 +275,13 @@ public class JGitUtils { */ public static Repository createRepository(File repositoriesFolder, String name, String shared) { try { - Repository repo = createRepository(repositoriesFolder, name); + Repository repo = null; + try { + Git git = Git.init().setDirectory(new File(repositoriesFolder, name)).setBare(true).call(); + repo = git.getRepository(); + } catch (GitAPIException e) { + throw new RuntimeException(e); + } GitConfigSharedRepository sharedRepository = new GitConfigSharedRepository(shared); if (sharedRepository.isShared()) { @@ -289,8 +291,12 @@ public class JGitUtils { config.save(); if (! JnaUtils.isWindows()) { - - //libc.chmod("/path/to/file", 0755); + Iterator iter = org.apache.commons.io.FileUtils.iterateFilesAndDirs(repo.getDirectory(), + TrueFileFilter.INSTANCE, TrueFileFilter.INSTANCE); + // Adjust permissions on file/directory + while (iter.hasNext()) { + adjustSharedPerm(iter.next(), sharedRepository); + } } } @@ -299,7 +305,9 @@ public class JGitUtils { throw new RuntimeException(e); } } - private enum GitConfigSharedRepositoryValue { + + private enum GitConfigSharedRepositoryValue + { UMASK("0", 0), FALSE("0", 0), OFF("0", 0), NO("0", 0), GROUP("1", 0660), TRUE("1", 0660), ON("1", 0660), YES("1", 0660), ALL("2", 0664), WORLD("2", 0664), EVERYBODY("2", 0664), @@ -313,13 +321,13 @@ public class JGitUtils { public int getPerm() { return permValue; }; } + private static class GitConfigSharedRepository { private int intValue; - GitConfigSharedRepositoryValue enumValue; + private GitConfigSharedRepositoryValue enumValue; - GitConfigSharedRepository(String s) - { + GitConfigSharedRepository(String s) { if ( s == null || s.trim().isEmpty() ) { enumValue = GitConfigSharedRepositoryValue.GROUP; } @@ -344,25 +352,61 @@ public class JGitUtils { } } - String getValue() - { + String getValue() { if ( enumValue == GitConfigSharedRepositoryValue.Oxxx ) return Integer.toOctalString(intValue); return enumValue.getConfigValue(); } - int getPerm() - { + int getPerm() { if ( enumValue == GitConfigSharedRepositoryValue.Oxxx ) return intValue; return enumValue.getPerm(); } - boolean isShared() - { + boolean isCustom() { + return enumValue == GitConfigSharedRepositoryValue.Oxxx; + } + + boolean isShared() { return (enumValue.getPerm() > 0) || enumValue == GitConfigSharedRepositoryValue.Oxxx; } } + public static int adjustSharedPerm(File path, String configShared) { + return adjustSharedPerm(path, new GitConfigSharedRepository(configShared)); + } + + + public static int adjustSharedPerm(File path, GitConfigSharedRepository configShared) { + if (! configShared.isShared()) return 0; + + int perm = configShared.getPerm(); + int mode = JnaUtils.getFilemode(path); + if (mode < 0) return -1; + + // If the owner has no write access, delete it from group and other, too. + if ((mode & JnaUtils.S_IWUSR) == 0) perm &= ~0222; + // If the owner has execute access, set it for all blocks that have read access. + if ((mode & JnaUtils.S_IXUSR) == JnaUtils.S_IXUSR) perm |= (perm & 0444) >> 2; + + if (configShared.isCustom()) { + // Use the custom value for access permissions. + mode |= (mode & ~0777) | perm; + } + else { + // Just add necessary bits to existing permissions. + mode |= perm; + } + + if (path.isDirectory()) { + mode |= (mode & 0444) >> 2; + mode |= JnaUtils.S_ISGID; + } + + return JnaUtils.setFilemode(path, mode); + } + + /** * Returns a list of repository names in the specified folder. * diff --git a/src/test/java/com/gitblit/tests/JGitUtilsTest.java b/src/test/java/com/gitblit/tests/JGitUtilsTest.java index 4affca2d..6f86b9f7 100644 --- a/src/test/java/com/gitblit/tests/JGitUtilsTest.java +++ b/src/test/java/com/gitblit/tests/JGitUtilsTest.java @@ -52,6 +52,7 @@ import com.gitblit.models.PathModel.PathChangeModel; import com.gitblit.models.RefModel; import com.gitblit.utils.CompressionUtils; import com.gitblit.utils.JGitUtils; +import com.gitblit.utils.JnaUtils; import com.gitblit.utils.StringUtils; public class JGitUtilsTest { @@ -150,7 +151,7 @@ public class JGitUtilsTest { @Test public void testCreateRepositoryShared() throws Exception { - String[] repositories = { "NewTestRepository.git", "NewTestRepository" }; + String[] repositories = { "NewSharedTestRepository.git" }; for (String repositoryName : repositories) { Repository repository = JGitUtils.createRepository(GitBlitSuite.REPOSITORIES, repositoryName, "group"); @@ -159,13 +160,21 @@ public class JGitUtilsTest { assertNotNull(repository); assertFalse(JGitUtils.hasCommits(repository)); assertNull(JGitUtils.getFirstCommit(repository, null)); - assertEquals(folder.lastModified(), JGitUtils.getFirstChange(repository, null) - .getTime()); - assertEquals(folder.lastModified(), JGitUtils.getLastChange(repository).when.getTime()); - assertNull(JGitUtils.getCommit(repository, null)); + + assertTrue(folder.exists()); + int mode = JnaUtils.getFilemode(folder); + assertEquals(JnaUtils.S_ISGID, mode & JnaUtils.S_ISGID); + assertEquals(JnaUtils.S_IRWXG, mode & JnaUtils.S_IRWXG); + + mode = JnaUtils.getFilemode(folder.getAbsolutePath() + "/HEAD"); + assertEquals(JnaUtils.S_IRGRP | JnaUtils.S_IWGRP, mode & JnaUtils.S_IRWXG); + + mode = JnaUtils.getFilemode(folder.getAbsolutePath() + "/config"); + assertEquals(JnaUtils.S_IRGRP | JnaUtils.S_IWGRP, mode & JnaUtils.S_IRWXG); + repository.close(); RepositoryCache.close(repository); -// FileUtils.delete(repository.getDirectory(), FileUtils.RECURSIVE); + FileUtils.delete(repository.getDirectory(), FileUtils.RECURSIVE); } } -- cgit v1.2.3 From 1dee25369809919eba0d72e6811d5132f5ee9118 Mon Sep 17 00:00:00 2001 From: Florian Zschocke Date: Tue, 20 Aug 2013 14:32:23 +0200 Subject: Add JavaDoc. --- src/main/java/com/gitblit/utils/JGitUtils.java | 21 ++++- src/main/java/com/gitblit/utils/JnaUtils.java | 114 +++++++++++++++++++------ 2 files changed, 107 insertions(+), 28 deletions(-) (limited to 'src/main/java/com/gitblit/utils/JGitUtils.java') diff --git a/src/main/java/com/gitblit/utils/JGitUtils.java b/src/main/java/com/gitblit/utils/JGitUtils.java index 49b3ad70..57bb147f 100644 --- a/src/main/java/com/gitblit/utils/JGitUtils.java +++ b/src/main/java/com/gitblit/utils/JGitUtils.java @@ -266,7 +266,7 @@ public class JGitUtils { /** * Creates a bare, shared repository. - * + * * @param repositoriesFolder * @param name * @param shared @@ -372,13 +372,32 @@ public class JGitUtils { } + /** + * Adjust file permissions of a file/directory for shared repositories + * + * @param path + * File that should get its permissions changed. + * @param configShared + * Configuration string value for the shared mode. + * @return Upon successful completion, a value of 0 is returned. Otherwise, a value of -1 is returned. + */ public static int adjustSharedPerm(File path, String configShared) { return adjustSharedPerm(path, new GitConfigSharedRepository(configShared)); } + /** + * Adjust file permissions of a file/directory for shared repositories + * + * @param path + * File that should get its permissions changed. + * @param configShared + * Configuration setting for the shared mode. + * @return Upon successful completion, a value of 0 is returned. Otherwise, a value of -1 is returned. + */ public static int adjustSharedPerm(File path, GitConfigSharedRepository configShared) { if (! configShared.isShared()) return 0; + if (! path.exists()) return -1; int perm = configShared.getPerm(); int mode = JnaUtils.getFilemode(path); diff --git a/src/main/java/com/gitblit/utils/JnaUtils.java b/src/main/java/com/gitblit/utils/JnaUtils.java index b7d7209f..fa47b1c7 100644 --- a/src/main/java/com/gitblit/utils/JnaUtils.java +++ b/src/main/java/com/gitblit/utils/JnaUtils.java @@ -34,31 +34,31 @@ import org.slf4j.LoggerFactory; * @author Florian Zschocke */ public class JnaUtils { - public static final int S_IFMT = 0170000; - public static final int S_IFIFO = 0010000; - public static final int S_IFCHR = 0020000; - public static final int S_IFDIR = 0040000; - public static final int S_IFBLK = 0060000; - public static final int S_IFREG = 0100000; - public static final int S_IFLNK = 0120000; - public static final int S_IFSOCK = 0140000; - - public static final int S_ISUID = 0004000; - public static final int S_ISGID = 0002000; - public static final int S_ISVTX = 0001000; - - public static final int S_IRWXU = 0000700; - public static final int S_IRUSR = 0000400; - public static final int S_IWUSR = 0000200; - public static final int S_IXUSR = 0000100; - public static final int S_IRWXG = 0000070; - public static final int S_IRGRP = 0000040; - public static final int S_IWGRP = 0000020; - public static final int S_IXGRP = 0000010; - public static final int S_IRWXO = 0000007; - public static final int S_IROTH = 0000004; - public static final int S_IWOTH = 0000002; - public static final int S_IXOTH = 0000001; + public static final int S_ISUID = 0004000; // set user id on execution + public static final int S_ISGID = 0002000; // set group id on execution + public static final int S_ISVTX = 0001000; // sticky bit, save swapped text even after use + + public static final int S_IRWXU = 0000700; // RWX mask for owner + public static final int S_IRUSR = 0000400; // read permission for owner + public static final int S_IWUSR = 0000200; // write permission for owner + public static final int S_IXUSR = 0000100; // execute/search permission for owner + public static final int S_IRWXG = 0000070; // RWX mask for group + public static final int S_IRGRP = 0000040; // read permission for group + public static final int S_IWGRP = 0000020; // write permission for group + public static final int S_IXGRP = 0000010; // execute/search permission for group + public static final int S_IRWXO = 0000007; // RWX mask for other + public static final int S_IROTH = 0000004; // read permission for other + public static final int S_IWOTH = 0000002; // write permission for other + public static final int S_IXOTH = 0000001; // execute/search permission for other + + public static final int S_IFMT = 0170000; // type of file mask + public static final int S_IFIFO = 0010000; // named pipe (fifo) + public static final int S_IFCHR = 0020000; // character special device + public static final int S_IFDIR = 0040000; // directory + public static final int S_IFBLK = 0060000; // block special device + public static final int S_IFREG = 0100000; // regular file + public static final int S_IFLNK = 0120000; // symbolic link + public static final int S_IFSOCK = 0140000; // socket private static final Logger LOGGER = LoggerFactory.getLogger(JGitUtils.class); @@ -66,6 +66,11 @@ public class JnaUtils { private static UnixCLibrary unixlibc = null; + /** + * Utility method to check if the JVM is running on a Windows OS. + * + * @return true, if the system property 'os.name' starts with 'Windows'. + */ public static boolean isWindows() { return System.getProperty("os.name").toLowerCase().startsWith("windows"); @@ -77,11 +82,37 @@ public class JnaUtils { } - public static int setFilemode(File path, int mode) + /** + * Set the permission bits of a file. + * + * The permission bits are set to the provided mode. This method is only + * implemented for OSes of the Unix family and makes use of the 'chmod' + * function of the native C library. See 'man 2 chmod' for more information. + * + * @param path + * File/directory to set the permission bits for. + * @param mode + * A mode created from or'd permission bit masks S_I* + * @return Upon successful completion, a value of 0 returned. Otherwise, a value of -1 is returned. + */ + public static int setFilemode(File file, int mode) { - return setFilemode(path.getAbsolutePath(), mode); + return setFilemode(file.getAbsolutePath(), mode); } + /** + * Set the permission bits of a file. + * + * The permission bits are set to the provided mode. This method is only + * implemented for OSes of the Unix family and makes use of the 'chmod' + * function of the native C library. See 'man 2 chmod' for more information. + * + * @param path + * Path to a file/directory to set the permission bits for. + * @param mode + * A mode created from or'd permission bit masks S_I* + * @return Upon successful completion, a value of 0 returned. Otherwise, a value of -1 is returned. + */ public static int setFilemode(String path, int mode) { if (isWindows()) { @@ -93,11 +124,33 @@ public class JnaUtils { + /** + * Get the file mode bits of a file. + * + * This method is only implemented for OSes of the Unix family. It returns the file mode + * information as available in the st_mode member of the resulting struct stat when calling + * 'lstat' on a file. + * + * @param path + * File/directory to get the file mode from. + * @return Upon successful completion, the file mode bits are returned. Otherwise, a value of -1 is returned. + */ public static int getFilemode(File path) { return getFilemode(path.getAbsolutePath()); } + /** + * Get the file mode bits of a file. + * + * This method is only implemented for OSes of the Unix family. It returns the file mode + * information as available in the st_mode member of the resulting struct stat when calling + * 'lstat' on a file. + * + * @param path + * Path to a file/directory to get the file mode from. + * @return Upon successful completion, the file mode bits are returned. Otherwise, a value of -1 is returned. + */ public static int getFilemode(String path) { if (isWindows()) { @@ -178,6 +231,13 @@ public class JnaUtils { } + /** + * Run the unix command 'ls -ldO' on a single file and return the resulting output line. + * + * @param path + * Path to a single file or directory. + * @return The first line of output from the 'ls' command. Null, if an error occurred and no line could be read. + */ private static String runProcessLs(String path) { String cmd = "ls -ldO " + path; -- cgit v1.2.3 From 8b5730a0e32d5707b6ac6df5fb0906b7981853eb Mon Sep 17 00:00:00 2001 From: Florian Zschocke Date: Tue, 20 Aug 2013 21:20:17 +0200 Subject: Fix setting wrrong custom mode on file and in config. --- src/main/java/com/gitblit/utils/JGitUtils.java | 7 +++-- src/test/java/com/gitblit/tests/JGitUtilsTest.java | 36 ++++++++++++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) (limited to 'src/main/java/com/gitblit/utils/JGitUtils.java') diff --git a/src/main/java/com/gitblit/utils/JGitUtils.java b/src/main/java/com/gitblit/utils/JGitUtils.java index 57bb147f..66dbd60d 100644 --- a/src/main/java/com/gitblit/utils/JGitUtils.java +++ b/src/main/java/com/gitblit/utils/JGitUtils.java @@ -353,7 +353,10 @@ public class JGitUtils { } String getValue() { - if ( enumValue == GitConfigSharedRepositoryValue.Oxxx ) return Integer.toOctalString(intValue); + if ( enumValue == GitConfigSharedRepositoryValue.Oxxx ) { + if (intValue == 0) return "0"; + return String.format("0%o", intValue); + } return enumValue.getConfigValue(); } @@ -410,7 +413,7 @@ public class JGitUtils { if (configShared.isCustom()) { // Use the custom value for access permissions. - mode |= (mode & ~0777) | perm; + mode = (mode & ~0777) | perm; } else { // Just add necessary bits to existing permissions. diff --git a/src/test/java/com/gitblit/tests/JGitUtilsTest.java b/src/test/java/com/gitblit/tests/JGitUtilsTest.java index f18c05f8..cdf698d6 100644 --- a/src/test/java/com/gitblit/tests/JGitUtilsTest.java +++ b/src/test/java/com/gitblit/tests/JGitUtilsTest.java @@ -182,6 +182,42 @@ public class JGitUtilsTest { } } + @Test + public void testCreateRepositorySharedCustom() throws Exception { + String[] repositories = { "NewSharedTestRepository.git" }; + for (String repositoryName : repositories) { + Repository repository = JGitUtils.createRepository(GitBlitSuite.REPOSITORIES, + repositoryName, "660"); + File folder = FileKey.resolve(new File(GitBlitSuite.REPOSITORIES, repositoryName), + FS.DETECTED); + assertNotNull(repository); + assertFalse(JGitUtils.hasCommits(repository)); + assertNull(JGitUtils.getFirstCommit(repository, null)); + + assertEquals("0660", repository.getConfig().getString("core", null, "sharedRepository")); + + assertTrue(folder.exists()); + if (! JnaUtils.isWindows()) { + int mode = JnaUtils.getFilemode(folder); + assertEquals(JnaUtils.S_ISGID, mode & JnaUtils.S_ISGID); + assertEquals(JnaUtils.S_IRWXG, mode & JnaUtils.S_IRWXG); + assertEquals(0, mode & JnaUtils.S_IRWXO); + + mode = JnaUtils.getFilemode(folder.getAbsolutePath() + "/HEAD"); + assertEquals(JnaUtils.S_IRGRP | JnaUtils.S_IWGRP, mode & JnaUtils.S_IRWXG); + assertEquals(0, mode & JnaUtils.S_IRWXO); + + mode = JnaUtils.getFilemode(folder.getAbsolutePath() + "/config"); + assertEquals(JnaUtils.S_IRGRP | JnaUtils.S_IWGRP, mode & JnaUtils.S_IRWXG); + assertEquals(0, mode & JnaUtils.S_IRWXO); + } + + repository.close(); + RepositoryCache.close(repository); + FileUtils.delete(repository.getDirectory(), FileUtils.RECURSIVE); + } + } + @Test public void testRefs() throws Exception { Repository repository = GitBlitSuite.getJGitRepository(); -- cgit v1.2.3 From b724448b589d60a9a7dda60cf30741048c98e199 Mon Sep 17 00:00:00 2001 From: Florian Zschocke Date: Wed, 21 Aug 2013 10:39:43 +0200 Subject: Fix set-gid bit clearing under Linux when effective gid is different from file gid. --- src/main/java/com/gitblit/utils/JGitUtils.java | 16 +++- src/main/java/com/gitblit/utils/JnaUtils.java | 90 ++++++++++++++++++++-- src/test/java/com/gitblit/tests/JGitUtilsTest.java | 64 +++++++++++++++ src/test/java/com/gitblit/tests/JnaUtilsTest.java | 38 +++++++++ 4 files changed, 201 insertions(+), 7 deletions(-) (limited to 'src/main/java/com/gitblit/utils/JGitUtils.java') diff --git a/src/main/java/com/gitblit/utils/JGitUtils.java b/src/main/java/com/gitblit/utils/JGitUtils.java index 66dbd60d..2e448c36 100644 --- a/src/main/java/com/gitblit/utils/JGitUtils.java +++ b/src/main/java/com/gitblit/utils/JGitUtils.java @@ -403,9 +403,23 @@ public class JGitUtils { if (! path.exists()) return -1; int perm = configShared.getPerm(); - int mode = JnaUtils.getFilemode(path); + JnaUtils.Filestat stat = JnaUtils.getFilestat(path); + if (stat == null) return -1; + int mode = stat.mode; if (mode < 0) return -1; + // Now, here is the kicker: Under Linux, chmod'ing a sgid file whose guid is different from the process' + // effective guid will reset the sgid flag of the file. Since there is no way to get the sgid flag back in + // that case, we decide to rather not touch is and getting the right permissions will have to be achieved + // in a different way, e.g. by using an appropriate umask for the Gitblit process. + if (System.getProperty("os.name").toLowerCase().startsWith("linux")) { + if ( ((mode & (JnaUtils.S_ISGID | JnaUtils.S_ISUID)) != 0) + && stat.gid != JnaUtils.getegid() ) { + LOGGER.debug("Not adjusting permissions to prevent clearing suid/sgid bits for '" + path + "'" ); + return 0; + } + } + // If the owner has no write access, delete it from group and other, too. if ((mode & JnaUtils.S_IWUSR) == 0) perm &= ~0222; // If the owner has execute access, set it for all blocks that have read access. diff --git a/src/main/java/com/gitblit/utils/JnaUtils.java b/src/main/java/com/gitblit/utils/JnaUtils.java index 3bf1f73a..4009342a 100644 --- a/src/main/java/com/gitblit/utils/JnaUtils.java +++ b/src/main/java/com/gitblit/utils/JnaUtils.java @@ -79,6 +79,28 @@ public class JnaUtils { private interface UnixCLibrary extends Library { public int chmod(String path, int mode); + public int getgid(); + public int getegid(); + } + + + public static int getgid() + { + if (isWindows()) { + throw new UnsupportedOperationException("The method JnaUtils.getgid is not supported under Windows."); + } + + return getUnixCLibrary().getgid(); + } + + + public static int getegid() + { + if (isWindows()) { + throw new UnsupportedOperationException("The method JnaUtils.getegid is not supported under Windows."); + } + + return getUnixCLibrary().getegid(); } @@ -157,21 +179,77 @@ public class JnaUtils { throw new UnsupportedOperationException("The method JnaUtils.getFilemode is not supported under Windows."); } + Filestat stat = getFilestat(path); + if ( stat == null ) return -1; + return stat.mode; + } + + + /** + * Status information of a file. + */ + public static class Filestat + { + public int mode; // file mode, permissions, type + public int uid; // user Id of owner + public int gid; // group Id of owner + + Filestat(int mode, int uid, int gid) { + this.mode = mode; this.uid = uid; this.gid = gid; + } + } + + + /** + * Get Unix file status information for a file. + * + * This method is only implemented for OSes of the Unix family. It returns file status + * information for a file. Currently this is the file mode, the user id and group id of the owner. + * + * @param path + * File/directory to get the file status from. + * @return Upon successful completion, a Filestat object containing the file information is returned. + * Otherwise, null is returned. + */ + public static Filestat getFilestat(File path) + { + return getFilestat(path.getAbsolutePath()); + } + + + /** + * Get Unix file status information for a file. + * + * This method is only implemented for OSes of the Unix family. It returns file status + * information for a file. Currently this is the file mode, the user id and group id of the owner. + * + * @param path + * Path to a file/directory to get the file status from. + * @return Upon successful completion, a Filestat object containing the file information is returned. + * Otherwise, null is returned. + */ + public static Filestat getFilestat(String path) + { + if (isWindows()) { + throw new UnsupportedOperationException("The method JnaUtils.getFilestat is not supported under Windows."); + } + int mode = 0; // Use a Runtime, because implementing stat() via JNA is just too much trouble. + // This could be done with the 'stat' command, too. But that may have a shell specific implementation, so we use 'ls' instead. String lsLine = runProcessLs(path); if (lsLine == null) { LOGGER.debug("Could not get file information for path " + path); - return -1; + return null; } - Pattern p = Pattern.compile("^(([-bcdlsp])([-r][-w][-xSs])([-r][-w][-xSs])([-r][-w][-xTt])) "); + Pattern p = Pattern.compile("^(([-bcdlspCDMnP?])([-r][-w][-xSs])([-r][-w][-xSs])([-r][-w][-xTt]))[@+.]? +[0-9]+ +([0-9]+) +([0-9]+) "); Matcher m = p.matcher(lsLine); if ( !m.lookingAt() ) { LOGGER.debug("Could not parse valid file mode information for path " + path); - return -1; + return null; } // Parse mode string to mode bits @@ -227,12 +305,12 @@ public class JnaUtils { } } - return mode; + return new Filestat(mode, Integer.parseInt(m.group(6)), Integer.parseInt(m.group(7))); } /** - * Run the unix command 'ls -ldO' on a single file and return the resulting output line. + * Run the unix command 'ls -ldn' on a single file and return the resulting output line. * * @param path * Path to a single file or directory. @@ -240,7 +318,7 @@ public class JnaUtils { */ private static String runProcessLs(String path) { - String cmd = "ls -ld " + path; + String cmd = "ls -ldn " + path; Runtime rt = Runtime.getRuntime(); Process pr = null; InputStreamReader ir = null; diff --git a/src/test/java/com/gitblit/tests/JGitUtilsTest.java b/src/test/java/com/gitblit/tests/JGitUtilsTest.java index cdf698d6..463c0a84 100644 --- a/src/test/java/com/gitblit/tests/JGitUtilsTest.java +++ b/src/test/java/com/gitblit/tests/JGitUtilsTest.java @@ -218,6 +218,70 @@ public class JGitUtilsTest { } } + @Test + public void testCreateRepositorySharedSgidParent() throws Exception { + if (! JnaUtils.isWindows()) { + String repositoryAll = "NewTestRepositoryAll.git"; + String repositoryUmask = "NewTestRepositoryUmask.git"; + String sgidParent = "sgid"; + + File parent = new File(GitBlitSuite.REPOSITORIES, sgidParent); + File folder = null; + boolean parentExisted = parent.exists(); + try { + if (!parentExisted) { + assertTrue("Could not create SGID parent folder.", parent.mkdir()); + } + int mode = JnaUtils.getFilemode(parent); + assertTrue(mode > 0); + assertEquals(0, JnaUtils.setFilemode(parent, mode | JnaUtils.S_ISGID | JnaUtils.S_IWGRP)); + + Repository repository = JGitUtils.createRepository(parent, repositoryAll, "all"); + folder = FileKey.resolve(new File(parent, repositoryAll), FS.DETECTED); + assertNotNull(repository); + + assertEquals("2", repository.getConfig().getString("core", null, "sharedRepository")); + + assertTrue(folder.exists()); + mode = JnaUtils.getFilemode(folder); + assertEquals(JnaUtils.S_ISGID, mode & JnaUtils.S_ISGID); + + mode = JnaUtils.getFilemode(folder.getAbsolutePath() + "/HEAD"); + assertEquals(JnaUtils.S_IRGRP | JnaUtils.S_IWGRP, mode & JnaUtils.S_IRWXG); + assertEquals(JnaUtils.S_IROTH, mode & JnaUtils.S_IRWXO); + + mode = JnaUtils.getFilemode(folder.getAbsolutePath() + "/config"); + assertEquals(JnaUtils.S_IRGRP | JnaUtils.S_IWGRP, mode & JnaUtils.S_IRWXG); + assertEquals(JnaUtils.S_IROTH, mode & JnaUtils.S_IRWXO); + + repository.close(); + RepositoryCache.close(repository); + + + + repository = JGitUtils.createRepository(parent, repositoryUmask, "umask"); + folder = FileKey.resolve(new File(parent, repositoryUmask), FS.DETECTED); + assertNotNull(repository); + + assertEquals(null, repository.getConfig().getString("core", null, "sharedRepository")); + + assertTrue(folder.exists()); + mode = JnaUtils.getFilemode(folder); + assertEquals(JnaUtils.S_ISGID, mode & JnaUtils.S_ISGID); + + repository.close(); + RepositoryCache.close(repository); + } + finally { + FileUtils.delete(new File(parent, repositoryAll), FileUtils.RECURSIVE | FileUtils.IGNORE_ERRORS); + FileUtils.delete(new File(parent, repositoryUmask), FileUtils.RECURSIVE | FileUtils.IGNORE_ERRORS); + if (!parentExisted) { + FileUtils.delete(parent, FileUtils.RECURSIVE | FileUtils.IGNORE_ERRORS); + } + } + } + } + @Test public void testRefs() throws Exception { Repository repository = GitBlitSuite.getJGitRepository(); diff --git a/src/test/java/com/gitblit/tests/JnaUtilsTest.java b/src/test/java/com/gitblit/tests/JnaUtilsTest.java index 2430b6ea..25d1ccf5 100644 --- a/src/test/java/com/gitblit/tests/JnaUtilsTest.java +++ b/src/test/java/com/gitblit/tests/JnaUtilsTest.java @@ -20,6 +20,7 @@ import com.gitblit.utils.JnaUtils; import java.io.File; import java.io.IOException; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import org.apache.commons.io.FileUtils; @@ -35,6 +36,24 @@ import org.junit.Test; */ public class JnaUtilsTest { + @Test + public void testGetgid() { + if (JnaUtils.isWindows()) { + try { + JnaUtils.getFilemode(GitBlitSuite.REPOSITORIES); + } catch(UnsupportedOperationException e) {} + } + else { + int gid = JnaUtils.getgid(); + assertTrue(gid >= 0); + int egid = JnaUtils.getegid(); + assertTrue(egid >= 0); + assertTrue("Really? You're running unit tests as root?!", gid > 0); + System.out.println("gid: " + gid + " egid: " + egid); + } + } + + @Test public void testGetFilemode() throws IOException { if (JnaUtils.isWindows()) { @@ -111,4 +130,23 @@ public class JnaUtilsTest { FileUtils.deleteDirectory(repository.getDirectory()); } } + + + @Test + public void testGetFilestat() { + if (JnaUtils.isWindows()) { + try { + JnaUtils.getFilemode(GitBlitSuite.REPOSITORIES); + } catch(UnsupportedOperationException e) {} + } + else { + JnaUtils.Filestat stat = JnaUtils.getFilestat(GitBlitSuite.REPOSITORIES); + assertNotNull(stat); + assertTrue(stat.mode > 0); + assertTrue(stat.uid > 0); + assertTrue(stat.gid > 0); + } + } + + } -- cgit v1.2.3