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