summaryrefslogtreecommitdiffstats
path: root/src/main/java/com/gitblit/git/GitDaemon.java
diff options
context:
space:
mode:
authorJames Moger <james.moger@gitblit.com>2013-05-03 19:06:50 -0400
committerJames Moger <james.moger@gitblit.com>2013-05-03 19:09:25 -0400
commit40aa84507640cd2a980757e7910a63310474eb87 (patch)
tree088278936eb8722e4f162f2b77b91bbbffaeec87 /src/main/java/com/gitblit/git/GitDaemon.java
parent9985ed23ae251c1d024a223f9ea8372b75a122f2 (diff)
downloadgitblit-40aa84507640cd2a980757e7910a63310474eb87.tar.gz
gitblit-40aa84507640cd2a980757e7910a63310474eb87.zip
Revised Git Daemon to improve thread stopping and to eliminate repository name hack
Diffstat (limited to 'src/main/java/com/gitblit/git/GitDaemon.java')
-rw-r--r--src/main/java/com/gitblit/git/GitDaemon.java332
1 files changed, 301 insertions, 31 deletions
diff --git a/src/main/java/com/gitblit/git/GitDaemon.java b/src/main/java/com/gitblit/git/GitDaemon.java
index 8ec05631..7050f878 100644
--- a/src/main/java/com/gitblit/git/GitDaemon.java
+++ b/src/main/java/com/gitblit/git/GitDaemon.java
@@ -1,36 +1,115 @@
/*
- * Copyright 2013 gitblit.com.
+ * Copyright (C) 2013 gitblit.com
+ * Copyright (C) 2008-2009, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
*
- * 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
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * All rights reserved.
*
- * 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.
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.gitblit.git;
import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InterruptedIOException;
+import java.io.OutputStream;
import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.text.MessageFormat;
+import java.util.concurrent.atomic.AtomicBoolean;
-import org.eclipse.jgit.transport.Daemon;
-import org.eclipse.jgit.transport.DaemonClient;
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.transport.ReceivePack;
+import org.eclipse.jgit.transport.ServiceMayNotContinueException;
+import org.eclipse.jgit.transport.UploadPack;
+import org.eclipse.jgit.transport.resolver.ReceivePackFactory;
+import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException;
+import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
+import org.eclipse.jgit.transport.resolver.UploadPackFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import com.gitblit.utils.StringUtils;
/**
- * Gitblit's Git Daemon ignores any and all per-repository daemon settings
- * and integrates into Gitblit's security model.
+ * Gitblit's Git Daemon ignores any and all per-repository daemon settings and
+ * integrates into Gitblit's security model.
*
* @author James Moger
- *
+ *
*/
-public class GitDaemon extends Daemon {
+public class GitDaemon {
+
+ private final Logger logger = LoggerFactory.getLogger(GitDaemon.class);
+
+ /** 9418: IANA assigned port number for Git. */
+ public static final int DEFAULT_PORT = 9418;
+
+ private static final int BACKLOG = 5;
+
+ private InetSocketAddress myAddress;
+
+ private final GitDaemonService[] services;
+
+ private final ThreadGroup processors;
+
+ private AtomicBoolean run;
+
+ private ServerSocket acceptSocket;
+
+ private Thread acceptThread;
+
+ private int timeout;
+
+ private RepositoryResolver<GitDaemonClient> repositoryResolver;
+
+ private UploadPackFactory<GitDaemonClient> uploadPackFactory;
+
+ private ReceivePackFactory<GitDaemonClient> receivePackFactory;
+
+ /** Configure a daemon to listen on any available network port. */
+ public GitDaemon() {
+ this(null);
+ }
/**
* Construct the Gitblit Git daemon.
@@ -43,22 +122,213 @@ public class GitDaemon extends Daemon {
* the folder to serve from
*/
public GitDaemon(String bindInterface, int port, File folder) {
- super(StringUtils.isEmpty(bindInterface) ? new InetSocketAddress(port) : new InetSocketAddress(bindInterface, port));
-
+ this(StringUtils.isEmpty(bindInterface) ? new InetSocketAddress(port)
+ : new InetSocketAddress(bindInterface, port));
+
// set the repository resolver and pack factories
- setRepositoryResolver(new RepositoryResolver<DaemonClient>(folder));
- setUploadPackFactory(new GitblitUploadPackFactory<DaemonClient>());
- setReceivePackFactory(new GitblitReceivePackFactory<DaemonClient>());
-
- // configure the git daemon to ignore the per-repository settings,
- // daemon.uploadpack and daemon.receivepack
- getService("git-upload-pack").setOverridable(false);
- getService("git-receive-pack").setOverridable(false);
-
- // enable both the upload and receive services and let the resolver,
- // pack factories, and receive hook handle security
- getService("git-upload-pack").setEnabled(true);
- getService("git-receive-pack").setEnabled(true);
+ repositoryResolver = new RepositoryResolver<GitDaemonClient>(folder);
}
+ /**
+ * Configure a new daemon for the specified network address.
+ *
+ * @param addr
+ * address to listen for connections on. If null, any available
+ * port will be chosen on all network interfaces.
+ */
+ public GitDaemon(final InetSocketAddress addr) {
+ myAddress = addr;
+ processors = new ThreadGroup("Git-Daemon");
+
+ run = new AtomicBoolean(false);
+ repositoryResolver = null;
+ uploadPackFactory = new GitblitUploadPackFactory<GitDaemonClient>();
+ receivePackFactory = new GitblitReceivePackFactory<GitDaemonClient>();
+
+ services = new GitDaemonService[] { new GitDaemonService("upload-pack", "uploadpack") {
+ {
+ setEnabled(true);
+ setOverridable(false);
+ }
+
+ @Override
+ protected void execute(final GitDaemonClient dc, final Repository db)
+ throws IOException, ServiceNotEnabledException,
+ ServiceNotAuthorizedException {
+ UploadPack up = uploadPackFactory.create(dc, db);
+ InputStream in = dc.getInputStream();
+ OutputStream out = dc.getOutputStream();
+ up.upload(in, out, null);
+ }
+ }, new GitDaemonService("receive-pack", "receivepack") {
+ {
+ setEnabled(true);
+ setOverridable(false);
+ }
+
+ @Override
+ protected void execute(final GitDaemonClient dc, final Repository db)
+ throws IOException, ServiceNotEnabledException,
+ ServiceNotAuthorizedException {
+ ReceivePack rp = receivePackFactory.create(dc, db);
+ InputStream in = dc.getInputStream();
+ OutputStream out = dc.getOutputStream();
+ rp.receive(in, out, null);
+ }
+ } };
+ }
+
+ /** @return timeout (in seconds) before aborting an IO operation. */
+ public int getTimeout() {
+ return timeout;
+ }
+
+ /**
+ * Set the timeout before willing to abort an IO call.
+ *
+ * @param seconds
+ * number of seconds to wait (with no data transfer occurring)
+ * before aborting an IO read or write operation with the
+ * connected client.
+ */
+ public void setTimeout(final int seconds) {
+ timeout = seconds;
+ }
+
+ /**
+ * Start this daemon on a background thread.
+ *
+ * @throws IOException
+ * the server socket could not be opened.
+ * @throws IllegalStateException
+ * the daemon is already running.
+ */
+ public synchronized void start() throws IOException {
+ if (acceptThread != null)
+ throw new IllegalStateException(JGitText.get().daemonAlreadyRunning);
+
+ final ServerSocket listenSock = new ServerSocket(myAddress != null ? myAddress.getPort()
+ : 0, BACKLOG, myAddress != null ? myAddress.getAddress() : null);
+ myAddress = (InetSocketAddress) listenSock.getLocalSocketAddress();
+
+ run.set(true);
+ acceptSocket = listenSock;
+ acceptThread = new Thread(processors, "Git-Daemon-Accept") {
+ public void run() {
+ while (isRunning()) {
+ try {
+ startClient(listenSock.accept());
+ } catch (InterruptedIOException e) {
+ // Test again to see if we should keep accepting.
+ } catch (IOException e) {
+ break;
+ }
+ }
+
+ try {
+ listenSock.close();
+ } catch (IOException err) {
+ //
+ } finally {
+ acceptSocket = null;
+ acceptThread = null;
+ }
+ }
+ };
+ acceptThread.start();
+
+ logger.info(MessageFormat.format("Git Daemon is listening on {0}:{1,number,0}", myAddress.getAddress().getHostAddress(), myAddress.getPort()));
+ }
+
+ /** @return true if this daemon is receiving connections. */
+ public boolean isRunning() {
+ return run.get();
+ }
+
+ /** Stop this daemon. */
+ public synchronized void stop() {
+ if (acceptThread != null) {
+ logger.info("Git Daemon stopping...");
+ run.set(false);
+ try {
+ // close the accept socket
+ // this throws a SocketException in the accept thread
+ acceptSocket.close();
+ } catch (IOException e1) {
+ }
+ try {
+ // join the accept thread
+ acceptThread.join();
+ logger.info("Git Daemon stopped.");
+ } catch (InterruptedException e) {
+ logger.error("Accept thread join interrupted", e);
+ }
+ }
+ }
+
+ private void startClient(final Socket s) {
+ final GitDaemonClient dc = new GitDaemonClient(this);
+
+ final SocketAddress peer = s.getRemoteSocketAddress();
+ if (peer instanceof InetSocketAddress)
+ dc.setRemoteAddress(((InetSocketAddress) peer).getAddress());
+
+ new Thread(processors, "Git-Daemon-Client " + peer.toString()) {
+ public void run() {
+ try {
+ dc.execute(s);
+ } catch (ServiceNotEnabledException e) {
+ // Ignored. Client cannot use this repository.
+ } catch (ServiceNotAuthorizedException e) {
+ // Ignored. Client cannot use this repository.
+ } catch (IOException e) {
+ // Ignore unexpected IO exceptions from clients
+ } finally {
+ try {
+ s.getInputStream().close();
+ } catch (IOException e) {
+ // Ignore close exceptions
+ }
+ try {
+ s.getOutputStream().close();
+ } catch (IOException e) {
+ // Ignore close exceptions
+ }
+ }
+ }
+ }.start();
+ }
+
+ synchronized GitDaemonService matchService(final String cmd) {
+ for (final GitDaemonService d : services) {
+ if (d.handles(cmd))
+ return d;
+ }
+ return null;
+ }
+
+ Repository openRepository(GitDaemonClient client, String name)
+ throws ServiceMayNotContinueException {
+ // Assume any attempt to use \ was by a Windows client
+ // and correct to the more typical / used in Git URIs.
+ //
+ name = name.replace('\\', '/');
+
+ // git://thishost/path should always be name="/path" here
+ //
+ if (!name.startsWith("/")) //$NON-NLS-1$
+ return null;
+
+ try {
+ return repositoryResolver.open(client, name.substring(1));
+ } catch (RepositoryNotFoundException e) {
+ // null signals it "wasn't found", which is all that is suitable
+ // for the remote client to know.
+ return null;
+ } catch (ServiceNotEnabledException e) {
+ // null signals it "wasn't found", which is all that is suitable
+ // for the remote client to know.
+ return null;
+ }
+ }
}