1
2
3
4
5
6
7
8
9
10
11 package org.eclipse.jgit.transport;
12
13 import java.io.IOException;
14 import java.io.InputStream;
15 import java.io.OutputStream;
16 import java.net.InetAddress;
17 import java.net.InetSocketAddress;
18 import java.net.ServerSocket;
19 import java.net.Socket;
20 import java.net.SocketAddress;
21 import java.net.SocketException;
22 import java.util.concurrent.atomic.AtomicBoolean;
23 import java.util.Collection;
24
25 import org.eclipse.jgit.annotations.Nullable;
26 import org.eclipse.jgit.errors.RepositoryNotFoundException;
27 import org.eclipse.jgit.internal.JGitText;
28 import org.eclipse.jgit.lib.PersonIdent;
29 import org.eclipse.jgit.lib.Repository;
30 import org.eclipse.jgit.storage.pack.PackConfig;
31 import org.eclipse.jgit.transport.resolver.ReceivePackFactory;
32 import org.eclipse.jgit.transport.resolver.RepositoryResolver;
33 import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException;
34 import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
35 import org.eclipse.jgit.transport.resolver.UploadPackFactory;
36
37
38
39
40 public class Daemon {
41
42 public static final int DEFAULT_PORT = 9418;
43
44 private static final int BACKLOG = 5;
45
46 private InetSocketAddress myAddress;
47
48 private final DaemonService[] services;
49
50 private final ThreadGroup processors;
51
52 private Acceptor acceptThread;
53
54 private int timeout;
55
56 private PackConfig packConfig;
57
58 private volatile RepositoryResolver<DaemonClient> repositoryResolver;
59
60 volatile UploadPackFactory<DaemonClient> uploadPackFactory;
61
62 volatile ReceivePackFactory<DaemonClient> receivePackFactory;
63
64
65
66
67 public Daemon() {
68 this(null);
69 }
70
71
72
73
74
75
76
77
78 @SuppressWarnings("unchecked")
79 public Daemon(InetSocketAddress addr) {
80 myAddress = addr;
81 processors = new ThreadGroup("Git-Daemon");
82
83 repositoryResolver = (RepositoryResolver<DaemonClient>) RepositoryResolver.NONE;
84
85 uploadPackFactory = (DaemonClient req, Repository db) -> {
86 UploadPack up = new UploadPack(db);
87 up.setTimeout(getTimeout());
88 up.setPackConfig(getPackConfig());
89 return up;
90 };
91
92 receivePackFactory = (DaemonClient req, Repository db) -> {
93 ReceivePack rp = new ReceivePack(db);
94
95 InetAddress peer = req.getRemoteAddress();
96 String host = peer.getCanonicalHostName();
97 if (host == null)
98 host = peer.getHostAddress();
99 String name = "anonymous";
100 String email = name + "@" + host;
101 rp.setRefLogIdent(new PersonIdent(name, email));
102 rp.setTimeout(getTimeout());
103
104 return rp;
105 };
106
107 services = new DaemonService[] {
108 new DaemonService("upload-pack", "uploadpack") {
109 {
110 setEnabled(true);
111 }
112
113 @Override
114 protected void execute(final DaemonClient dc,
115 final Repository db,
116 @Nullable Collection<String> extraParameters)
117 throws IOException,
118 ServiceNotEnabledException,
119 ServiceNotAuthorizedException {
120 UploadPack up = uploadPackFactory.create(dc, db);
121 InputStream in = dc.getInputStream();
122 OutputStream out = dc.getOutputStream();
123 if (extraParameters != null) {
124 up.setExtraParameters(extraParameters);
125 }
126 up.upload(in, out, null);
127 }
128 }, new DaemonService("receive-pack", "receivepack") {
129 {
130 setEnabled(false);
131 }
132
133 @Override
134 protected void execute(final DaemonClient dc,
135 final Repository db,
136 @Nullable Collection<String> extraParameters)
137 throws IOException,
138 ServiceNotEnabledException,
139 ServiceNotAuthorizedException {
140 ReceivePack rp = receivePackFactory.create(dc, db);
141 InputStream in = dc.getInputStream();
142 OutputStream out = dc.getOutputStream();
143 rp.receive(in, out, null);
144 }
145 } };
146 }
147
148
149
150
151
152
153 public synchronized InetSocketAddress getAddress() {
154 return myAddress;
155 }
156
157
158
159
160
161
162
163
164
165
166 public synchronized DaemonService getService(String name) {
167 if (!name.startsWith("git-"))
168 name = "git-" + name;
169 for (DaemonService s : services) {
170 if (s.getCommandName().equals(name))
171 return s;
172 }
173 return null;
174 }
175
176
177
178
179
180
181 public int getTimeout() {
182 return timeout;
183 }
184
185
186
187
188
189
190
191
192
193 public void setTimeout(int seconds) {
194 timeout = seconds;
195 }
196
197
198
199
200
201
202 public PackConfig getPackConfig() {
203 return packConfig;
204 }
205
206
207
208
209
210
211
212
213 public void setPackConfig(PackConfig pc) {
214 this.packConfig = pc;
215 }
216
217
218
219
220
221
222
223 public void setRepositoryResolver(RepositoryResolver<DaemonClient> resolver) {
224 repositoryResolver = resolver;
225 }
226
227
228
229
230
231
232
233 @SuppressWarnings("unchecked")
234 public void setUploadPackFactory(UploadPackFactory<DaemonClient> factory) {
235 if (factory != null)
236 uploadPackFactory = factory;
237 else
238 uploadPackFactory = (UploadPackFactory<DaemonClient>) UploadPackFactory.DISABLED;
239 }
240
241
242
243
244
245
246
247 public ReceivePackFactory<DaemonClient> getReceivePackFactory() {
248 return receivePackFactory;
249 }
250
251
252
253
254
255
256
257 @SuppressWarnings("unchecked")
258 public void setReceivePackFactory(ReceivePackFactory<DaemonClient> factory) {
259 if (factory != null)
260 receivePackFactory = factory;
261 else
262 receivePackFactory = (ReceivePackFactory<DaemonClient>) ReceivePackFactory.DISABLED;
263 }
264
265 private class Acceptor extends Thread {
266
267 private final ServerSocket listenSocket;
268
269 private final AtomicBoolean running = new AtomicBoolean(true);
270
271 public Acceptor(ThreadGroup group, String name, ServerSocket socket) {
272 super(group, name);
273 this.listenSocket = socket;
274 }
275
276 @Override
277 public void run() {
278 setUncaughtExceptionHandler((thread, throwable) -> terminate());
279 while (isRunning()) {
280 try {
281 startClient(listenSocket.accept());
282 } catch (SocketException e) {
283
284 } catch (IOException e) {
285 break;
286 }
287 }
288
289 terminate();
290 }
291
292 private void terminate() {
293 try {
294 shutDown();
295 } finally {
296 clearThread();
297 }
298 }
299
300 public boolean isRunning() {
301 return running.get();
302 }
303
304 public void shutDown() {
305 running.set(false);
306 try {
307 listenSocket.close();
308 } catch (IOException err) {
309
310 }
311 }
312
313 }
314
315
316
317
318
319
320
321
322
323 public synchronized void start() throws IOException {
324 if (acceptThread != null) {
325 throw new IllegalStateException(JGitText.get().daemonAlreadyRunning);
326 }
327 ServerSocket socket = new ServerSocket();
328 socket.setReuseAddress(true);
329 if (myAddress != null) {
330 socket.bind(myAddress, BACKLOG);
331 } else {
332 socket.bind(new InetSocketAddress((InetAddress) null, 0), BACKLOG);
333 }
334 myAddress = (InetSocketAddress) socket.getLocalSocketAddress();
335
336 acceptThread = new Acceptor(processors, "Git-Daemon-Accept", socket);
337 acceptThread.start();
338 }
339
340 private synchronized void clearThread() {
341 acceptThread = null;
342 }
343
344
345
346
347
348
349 public synchronized boolean isRunning() {
350 return acceptThread != null && acceptThread.isRunning();
351 }
352
353
354
355
356 public synchronized void stop() {
357 if (acceptThread != null) {
358 acceptThread.shutDown();
359 }
360 }
361
362
363
364
365
366
367
368
369 public void stopAndWait() throws InterruptedException {
370 Thread acceptor = null;
371 synchronized (this) {
372 acceptor = acceptThread;
373 stop();
374 }
375 if (acceptor != null) {
376 acceptor.join();
377 }
378 }
379
380 void startClient(Socket s) {
381 final DaemonClient dc = new DaemonClient(this);
382
383 final SocketAddress peer = s.getRemoteSocketAddress();
384 if (peer instanceof InetSocketAddress)
385 dc.setRemoteAddress(((InetSocketAddress) peer).getAddress());
386
387 new Thread(processors, "Git-Daemon-Client " + peer.toString()) {
388 @Override
389 public void run() {
390 try {
391 dc.execute(s);
392 } catch (ServiceNotEnabledException e) {
393
394 } catch (ServiceNotAuthorizedException e) {
395
396 } catch (IOException e) {
397
398 } finally {
399 try {
400 s.getInputStream().close();
401 } catch (IOException e) {
402
403 }
404 try {
405 s.getOutputStream().close();
406 } catch (IOException e) {
407
408 }
409 }
410 }
411 }.start();
412 }
413
414 synchronized DaemonService matchService(String cmd) {
415 for (DaemonService d : services) {
416 if (d.handles(cmd))
417 return d;
418 }
419 return null;
420 }
421
422 Repository openRepository(DaemonClient client, String name)
423 throws ServiceMayNotContinueException {
424
425
426
427 name = name.replace('\\', '/');
428
429
430
431 if (!name.startsWith("/"))
432 return null;
433
434 try {
435 return repositoryResolver.open(client, name.substring(1));
436 } catch (RepositoryNotFoundException e) {
437
438
439 return null;
440 } catch (ServiceNotAuthorizedException e) {
441
442
443 return null;
444 } catch (ServiceNotEnabledException e) {
445
446
447 return null;
448 }
449 }
450 }