From 8a67d9dcdcc63fe48c97e83ffbc2a643eee2ed60 Mon Sep 17 00:00:00 2001
From: Florian Zschocke <florian.zschocke@cycos.com>
Date: Mon, 26 Aug 2013 06:39:57 -0400
Subject: [PATCH] Add proper implementation of methods in JnaUtils.

---
 src/main/java/com/gitblit/utils/JGitUtils.java     |  172 +++++++++---------
 src/test/java/com/gitblit/tests/JGitUtilsTest.java |   21 ++
 src/test/java/com/gitblit/tests/JnaUtilsTest.java  |  100 +++++++++++
 src/main/java/com/gitblit/utils/JnaUtils.java      |  226 +++++++++++++++++++++++++
 4 files changed, 430 insertions(+), 89 deletions(-)

diff --git a/src/main/java/com/gitblit/utils/JGitUtils.java b/src/main/java/com/gitblit/utils/JGitUtils.java
index 345375a..03b54ee 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.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 @@
 		}
 	}
 
-    /**
-     * 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);
+	/**
+	 * 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();
+			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);
+				if (! JnaUtils.isWindows()) {
 
-                    //libc.chmod("/path/to/file", 0755);
-                }
-            }
+					//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);
+			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; };
+		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; };
+		public String getConfigValue() { return configValue; };
+		public int getPerm() { return permValue; };
 
-    }
-    private static class GitConfigSharedRepository
-    {
-        private int intValue;
-        GitConfigSharedRepositoryValue enumValue;
+	}
+	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();
-        }
+		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 + "'");
+					}
+				}
+			}
+		}
 
-        int getPerm()
-        {
-            if ( enumValue == GitConfigSharedRepositoryValue.Oxxx ) return intValue;
-            return enumValue.getPerm();
-        }
+		String getValue()
+		{
+			if ( enumValue == GitConfigSharedRepositoryValue.Oxxx ) return Integer.toOctalString(intValue);
+			return enumValue.getConfigValue();
+		}
 
-        boolean isShared()
-        {
-            return (enumValue.getPerm() > 0) || enumValue == GitConfigSharedRepositoryValue.Oxxx;
-        }
-    }
+		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 0000000..b7d7209
--- /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 375dbd5..4affca2 100644
--- a/src/test/java/com/gitblit/tests/JGitUtilsTest.java
+++ b/src/test/java/com/gitblit/tests/JGitUtilsTest.java
@@ -149,6 +149,27 @@
 	}
 
 	@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();
 		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 0000000..574686b
--- /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());
+	}
+}

--
Gitblit v1.9.1