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