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