Florian Zschocke
2013-08-17 8a67d9dcdcc63fe48c97e83ffbc2a643eee2ed60
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.
2 files added
2 files modified
519 ■■■■ changed files
src/main/java/com/gitblit/utils/JGitUtils.java 172 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/utils/JnaUtils.java 226 ●●●●● patch | view | raw | blame | history
src/test/java/com/gitblit/tests/JGitUtilsTest.java 21 ●●●●● patch | view | raw | blame | history
src/test/java/com/gitblit/tests/JnaUtilsTest.java 100 ●●●●● patch | view | raw | blame | history
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;
        }
    }
    /**
src/main/java/com/gitblit/utils/JnaUtils.java
New file
@@ -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;
    }
}
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);
src/test/java/com/gitblit/tests/JnaUtilsTest.java
New file
@@ -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());
    }
}