diff options
author | James Moger <james.moger@gitblit.com> | 2013-09-17 17:05:00 -0400 |
---|---|---|
committer | James Moger <james.moger@gitblit.com> | 2013-09-17 17:05:00 -0400 |
commit | 35f55ae7e034275811fa68908215b48bbf9df965 (patch) | |
tree | d8bd79fc7a7bc9d2d0b047c655f7e99f50fe303e | |
parent | bb6b148bfc2d4a22b4fd3bdbafebadccaccf6661 (diff) | |
parent | b724448b589d60a9a7dda60cf30741048c98e199 (diff) | |
download | gitblit-35f55ae7e034275811fa68908215b48bbf9df965.tar.gz gitblit-35f55ae7e034275811fa68908215b48bbf9df965.zip |
Merge branch 'init-shared' of https://github.com/fzs/gitblit into prefixes
-rw-r--r-- | .classpath | 2 | ||||
-rw-r--r-- | build.moxie | 1 | ||||
-rw-r--r-- | gitblit.iml | 22 | ||||
-rw-r--r-- | src/main/distrib/data/gitblit.properties | 9 | ||||
-rw-r--r-- | src/main/java/com/gitblit/GitBlit.java | 11 | ||||
-rw-r--r-- | src/main/java/com/gitblit/utils/JGitUtils.java | 182 | ||||
-rw-r--r-- | src/main/java/com/gitblit/utils/JnaUtils.java | 364 | ||||
-rw-r--r-- | src/test/java/com/gitblit/tests/JGitUtilsTest.java | 134 | ||||
-rw-r--r-- | src/test/java/com/gitblit/tests/JnaUtilsTest.java | 152 |
9 files changed, 860 insertions, 17 deletions
@@ -37,6 +37,7 @@ <classpathentry kind="lib" path="ext/jcalendar-1.3.2.jar" /> <classpathentry kind="lib" path="ext/commons-compress-1.4.1.jar" sourcepath="ext/src/commons-compress-1.4.1.jar" /> <classpathentry kind="lib" path="ext/xz-1.0.jar" sourcepath="ext/src/xz-1.0.jar" /> + <classpathentry kind="lib" path="ext/commons-io-2.2.jar" sourcepath="ext/src/commons-io-2.2.jar" /> <classpathentry kind="lib" path="ext/force-partner-api-24.0.0.jar" sourcepath="ext/src/force-partner-api-24.0.0.jar" /> <classpathentry kind="lib" path="ext/force-wsc-24.0.0.jar" sourcepath="ext/src/force-wsc-24.0.0.jar" /> <classpathentry kind="lib" path="ext/js-1.7R2.jar" sourcepath="ext/src/js-1.7R2.jar" /> @@ -60,7 +61,6 @@ <classpathentry kind="lib" path="ext/httpcore-4.2.1.jar" sourcepath="ext/src/httpcore-4.2.1.jar" /> <classpathentry kind="lib" path="ext/commons-logging-1.1.1.jar" sourcepath="ext/src/commons-logging-1.1.1.jar" /> <classpathentry kind="lib" path="ext/commons-exec-1.1.jar" sourcepath="ext/src/commons-exec-1.1.jar" /> - <classpathentry kind="lib" path="ext/commons-io-2.2.jar" sourcepath="ext/src/commons-io-2.2.jar" /> <classpathentry kind="output" path="bin/classes" /> <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER" /> </classpath>
\ No newline at end of file diff --git a/build.moxie b/build.moxie index fe4eeb80..4a3aa34e 100644 --- a/build.moxie +++ b/build.moxie @@ -148,6 +148,7 @@ dependencies: - compile 'org.apache.ivy:ivy:2.2.0' :war - compile 'com.toedter:jcalendar:1.3.2' :authority - compile 'org.apache.commons:commons-compress:1.4.1' :war +- compile 'commons-io:commons-io:2.2' :war - compile 'com.force.api:force-partner-api:24.0.0' :war - compile 'org.freemarker:freemarker:2.3.19' :war - compile 'com.github.dblock.waffle:waffle-jna:1.5' :war diff --git a/gitblit.iml b/gitblit.iml index d29b31af..82d37a98 100644 --- a/gitblit.iml +++ b/gitblit.iml @@ -381,6 +381,17 @@ </library> </orderEntry> <orderEntry type="module-library"> + <library name="commons-io-2.2.jar"> + <CLASSES> + <root url="jar://$MODULE_DIR$/ext/commons-io-2.2.jar!/" /> + </CLASSES> + <JAVADOC /> + <SOURCES> + <root url="jar://$MODULE_DIR$/ext/src/commons-io-2.2.jar!/" /> + </SOURCES> + </library> + </orderEntry> + <orderEntry type="module-library"> <library name="force-partner-api-24.0.0.jar"> <CLASSES> <root url="jar://$MODULE_DIR$/ext/force-partner-api-24.0.0.jar!/" /> @@ -633,17 +644,6 @@ </SOURCES> </library> </orderEntry> - <orderEntry type="module-library" scope="TEST"> - <library name="commons-io-2.2.jar"> - <CLASSES> - <root url="jar://$MODULE_DIR$/ext/commons-io-2.2.jar!/" /> - </CLASSES> - <JAVADOC /> - <SOURCES> - <root url="jar://$MODULE_DIR$/ext/src/commons-io-2.2.jar!/" /> - </SOURCES> - </library> - </orderEntry> <orderEntry type="inheritedJdk" /> </component> </module> diff --git a/src/main/distrib/data/gitblit.properties b/src/main/distrib/data/gitblit.properties index 665a90e7..e4e71533 100644 --- a/src/main/distrib/data/gitblit.properties +++ b/src/main/distrib/data/gitblit.properties @@ -191,6 +191,15 @@ git.userRepositoryPrefix = ~ # SINCE 1.3.0
git.defaultIncrementalPushTagPrefix = r
+# In an Unix environment where mixed access methods exist for shared repositories,
+# the repository should be created with 'git init --shared' to make sure that
+# it can be accessed e.g. via ssh (user git) and http (user www-data).
+# Valid values are the values available for the '--shared' option. The the manual
+# page for 'git init' for more information on shared repositories.
+#
+# SINCE 1.3.2
+git.createRepositoriesShared = false
+
# Enable JGit-based garbage collection. (!!EXPERIMENTAL!!)
#
# USE AT YOUR OWN RISK!
diff --git a/src/main/java/com/gitblit/GitBlit.java b/src/main/java/com/gitblit/GitBlit.java index 72a0e22a..1afbbc98 100644 --- a/src/main/java/com/gitblit/GitBlit.java +++ b/src/main/java/com/gitblit/GitBlit.java @@ -2427,7 +2427,8 @@ public class GitBlit implements ServletContextListener { } // create repository logger.info("create repository " + repository.name); - r = JGitUtils.createRepository(repositoriesFolder, repository.name); + String shared = getString(Keys.git.createRepositoriesShared, "FALSE"); + r = JGitUtils.createRepository(repositoriesFolder, repository.name, shared); } else { // rename repository if (!repositoryName.equalsIgnoreCase(repository.name)) { @@ -2529,7 +2530,13 @@ public class GitBlit implements ServletContextListener { // close the repository object r.close(); } - + + // Adjust permissions in case we updated the config files + JGitUtils.adjustSharedPerm(new File(r.getDirectory().getAbsolutePath(), "config"), + getString(Keys.git.createRepositoriesShared, "FALSE")); + JGitUtils.adjustSharedPerm(new File(r.getDirectory().getAbsolutePath(), "HEAD"), + getString(Keys.git.createRepositoriesShared, "FALSE")); + // update repository cache removeFromCachedRepositoryList(repositoryName); // model will actually be replaced on next load because config is stale diff --git a/src/main/java/com/gitblit/utils/JGitUtils.java b/src/main/java/com/gitblit/utils/JGitUtils.java index 3f01eeaf..cf6ec26d 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;
@@ -58,6 +59,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;
@@ -259,14 +261,188 @@ public class JGitUtils { * @return Repository
*/
public static Repository createRepository(File repositoriesFolder, String name) {
+ return createRepository(repositoriesFolder, name, "FALSE");
+ }
+
+ /**
+ * 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 {
- Git git = Git.init().setDirectory(new File(repositoriesFolder, name)).setBare(true).call();
- return git.getRepository();
- } catch (GitAPIException e) {
+ 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()) {
+ StoredConfig config = repo.getConfig();
+ config.setString("core", null, "sharedRepository", sharedRepository.getValue());
+ config.setBoolean("receive", null, "denyNonFastforwards", true);
+ config.save();
+
+ if (! JnaUtils.isWindows()) {
+ Iterator<File> 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);
+ }
+ }
+ }
+
+ 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;
+ private 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 ) {
+ if (intValue == 0) return "0";
+ return String.format("0%o", intValue);
+ }
+ return enumValue.getConfigValue();
+ }
+
+ int getPerm() {
+ if ( enumValue == GitConfigSharedRepositoryValue.Oxxx ) return intValue;
+ return enumValue.getPerm();
+ }
+
+ boolean isCustom() {
+ return enumValue == GitConfigSharedRepositoryValue.Oxxx;
+ }
+
+ boolean isShared() {
+ return (enumValue.getPerm() > 0) || enumValue == GitConfigSharedRepositoryValue.Oxxx;
+ }
+ }
+
+
+ /**
+ * 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();
+ 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.
+ 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/main/java/com/gitblit/utils/JnaUtils.java b/src/main/java/com/gitblit/utils/JnaUtils.java new file mode 100644 index 00000000..4009342a --- /dev/null +++ b/src/main/java/com/gitblit/utils/JnaUtils.java @@ -0,0 +1,364 @@ +/* + * 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_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); + + 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"); + } + + + 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(); + } + + + /** + * 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(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()) { + throw new UnsupportedOperationException("The method JnaUtils.getFilemode is not supported under Windows."); + } + + return getUnixCLibrary().chmod(path, mode); + } + + + + /** + * 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()) { + 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 null; + } + + 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 null; + } + + // 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 new Filestat(mode, Integer.parseInt(m.group(6)), Integer.parseInt(m.group(7))); + } + + + /** + * 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. + * @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 -ldn " + 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..463c0a84 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 {
@@ -149,6 +150,139 @@ public class JGitUtilsTest { }
@Test
+ public void testCreateRepositoryShared() throws Exception {
+ String[] repositories = { "NewSharedTestRepository.git" };
+ 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("1", 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);
+
+ 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);
+ }
+ }
+
+ @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 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();
Map<ObjectId, List<RefModel>> map = JGitUtils.getAllRefs(repository);
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..25d1ccf5 --- /dev/null +++ b/src/test/java/com/gitblit/tests/JnaUtilsTest.java @@ -0,0 +1,152 @@ +/* + * 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.assertNotNull; +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 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()) { + try { + JnaUtils.getFilemode(GitBlitSuite.REPOSITORIES); + } catch(UnsupportedOperationException e) {} + } + else { + 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 { + if (JnaUtils.isWindows()) { + try { + JnaUtils.getFilemode(GitBlitSuite.REPOSITORIES); + } catch(UnsupportedOperationException e) {} + } + else { + 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()); + } + } + + + @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); + } + } + + +} |