blob: 8f7ec59ecbf3eec26a334d79d58b62c10255399c [file] [log] [blame]
Roger Meier3bef8c22012-10-06 06:58:00 +00001(*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 *)
19unit Thrift.Transport.Pipes;
20
21{$WARN SYMBOL_PLATFORM OFF}
22
23interface
24
25uses
26 Windows, SysUtils, Math, AccCtrl, AclAPI,
27 Thrift.Transport,
28 Thrift.Console,
29 Thrift.Stream;
30
31const
32 DEFAULT_THRIFT_PIPE_TIMEOUT = 5 * 1000; // ms
33
34
35type
36 IPipe = interface( IStreamTransport)
37 ['{5E05CC85-434F-428F-BFB2-856A168B5558}']
38 end;
39
40
41 TPipeStreamImpl = class( TThriftStreamImpl)
42 private
43 FPipe : THandle;
44 FOwner : Boolean;
45 FPipeName : string;
46 FTimeout : DWORD;
47 FShareMode: DWORD;
48 FSecurityAttribs : PSecurityAttributes;
49
50 protected
51 procedure Write( const buffer: TBytes; offset: Integer; count: Integer); override;
52 function Read( var buffer: TBytes; offset: Integer; count: Integer): Integer; override;
53 procedure Open; override;
54 procedure Close; override;
55 procedure Flush; override;
56
57 function IsOpen: Boolean; override;
58 function ToArray: TBytes; override;
59 public
60 constructor Create( const aPipeHandle : THandle; aOwnsHandle : Boolean); overload;
61 constructor Create( const aPipeName : string;
62 const aShareMode: DWORD = 0;
63 const aSecurityAttributes: PSecurityAttributes = nil;
64 const aTimeOut : DWORD = DEFAULT_THRIFT_PIPE_TIMEOUT); overload;
65 destructor Destroy; override;
66 end;
67
68
69 TNamedPipeImpl = class( TStreamTransportImpl, IPipe)
70 public
71 FOwner : Boolean;
72
73 // Constructs a new pipe object.
74 constructor Create(); overload;
75 // Named pipe constructors
76 constructor Create( aPipe : THandle; aOwnsHandle : Boolean); overload;
77 constructor Create( const aPipeName : string;
78 const aShareMode: DWORD = 0;
79 const aSecurityAttributes: PSecurityAttributes = nil;
80 const aTimeOut : DWORD = DEFAULT_THRIFT_PIPE_TIMEOUT); overload;
81
82 // ITransport
83 function GetIsOpen: Boolean; override;
84 procedure Open; override;
85 procedure Close; override;
86 end;
87
88
89 TAnonymousPipeImpl = class( TStreamTransportImpl, IPipe)
90 public
91 FOwner : Boolean;
92
93 // Constructs a new pipe object.
94 constructor Create(); overload;
95 // Anonymous pipe constructor
96 constructor Create( const aPipeRead, aPipeWrite : THandle; aOwnsHandles : Boolean); overload;
97
98 // ITransport
99 function GetIsOpen: Boolean; override;
100 procedure Open; override;
101 procedure Close; override;
102 end;
103
104
105 IPipeServer = interface( IServerTransport)
106 ['{7AEE6793-47B9-4E49-981A-C39E9108E9AD}']
107 // Server side anonymous pipe ends
108 function Handle : THandle;
109 function WriteHandle : THandle;
110 // Client side anonymous pipe ends
111 function ClientAnonRead : THandle;
112 function ClientAnonWrite : THandle;
113 end;
114
115
116 TServerPipeImpl = class( TServerTransportImpl, IPipeServer)
117 private
118 FPipeName : string;
119 FMaxConns : DWORD;
120 FBufSize : DWORD;
121 FAnonymous : Boolean;
122
123 FHandle,
124 FWriteHandle : THandle;
125
126 //Client side anonymous pipe handles
127 FClientAnonRead,
128 FClientAnonWrite : THandle;
129
130 protected
131 function AcceptImpl: ITransport; override;
132
133 function CreateNamedPipe : Boolean;
134 function CreateAnonPipe : Boolean;
135
136 // IPipeServer
137 function Handle : THandle;
138 function WriteHandle : THandle;
139 function ClientAnonRead : THandle;
140 function ClientAnonWrite : THandle;
141
142 public
143 // Constructors
144 constructor Create(); overload;
145 // Named Pipe
146 constructor Create( aPipename : string); overload;
147 constructor Create( aPipename : string; aBufsize : Cardinal); overload;
148 constructor Create( aPipename : string; aBufsize, aMaxConns : Cardinal); overload;
149 // Anonymous pipe
150 constructor Create( aBufsize : Cardinal); overload;
151
152 procedure Listen; override;
153 procedure Close; override;
154 end;
155
156
157const
158 TPIPE_SERVER_MAX_CONNS_DEFAULT = 10;
159
160
161implementation
162
163
164{ TPipeStreamImpl }
165
166
167constructor TPipeStreamImpl.Create( const aPipeHandle : THandle; aOwnsHandle : Boolean);
168begin
169 FPipe := aPipeHandle;
170 FOwner := aOwnsHandle;
171 FPipeName := '';
172 FTimeout := DEFAULT_THRIFT_PIPE_TIMEOUT;
173 FShareMode := 0;
174 FSecurityAttribs := nil;
175end;
176
177
178constructor TPipeStreamImpl.Create( const aPipeName : string; const aShareMode: DWORD;
179 const aSecurityAttributes: PSecurityAttributes;
180 const aTimeOut : DWORD);
181begin
182 FPipe := INVALID_HANDLE_VALUE;
183 FOwner := TRUE;
184 FPipeName := aPipeName;
185 FTimeout := aTimeOut;
186 FShareMode := aShareMode;
187 FSecurityAttribs := aSecurityAttributes;
188
189 if Copy(FPipeName,1,2) <> '\\'
190 then FPipeName := '\\.\pipe\' + FPipeName; // assume localhost
191end;
192
193
194destructor TPipeStreamImpl.Destroy;
195begin
196 try
197 Close;
198 finally
199 inherited Destroy;
200 end;
201end;
202
203
204procedure TPipeStreamImpl.Close;
205begin
206 if IsOpen then try
207 if FOwner
208 then CloseHandle( FPipe);
209 finally
210 FPipe := INVALID_HANDLE_VALUE;
211 end;
212end;
213
214
215procedure TPipeStreamImpl.Flush;
216begin
217 // nothing to do
218end;
219
220
221function TPipeStreamImpl.IsOpen: Boolean;
222begin
223 result := (FPipe <> INVALID_HANDLE_VALUE);
224end;
225
226
227procedure TPipeStreamImpl.Open;
228var retries : Integer;
229 hPipe : THandle;
230 dwMode : DWORD;
231const INTERVAL = 500; // ms
232begin
233 if IsOpen then Exit;
234
235 // open that thingy
236 retries := Max( 1, Round( 1.0 * FTimeout / INTERVAL));
237 hPipe := INVALID_HANDLE_VALUE;
238 while TRUE do begin
239 hPipe := CreateFile( PChar( FPipeName),
240 GENERIC_READ or GENERIC_WRITE,
241 FShareMode, // sharing
242 FSecurityAttribs, // security attributes
243 OPEN_EXISTING, // opens existing pipe
244 0, // default attributes
245 0); // no template file
246
247 if hPipe <> INVALID_HANDLE_VALUE
248 then Break;
249
250 Dec( retries);
251 if (retries > 0) or (FTimeout = INFINITE)
252 then Sleep( INTERVAL)
253 else raise TTransportException.Create( TTransportException.TExceptionType.NotOpen,
254 'Unable to open pipe');
255 end;
256
257 // pipe connected; change to message-read mode.
258 dwMode := PIPE_READMODE_MESSAGE;
259 if not SetNamedPipeHandleState( hPipe, dwMode, nil, nil) then begin
260 Close;
261 raise TTransportException.Create( TTransportException.TExceptionType.NotOpen,
262 'SetNamedPipeHandleState failed');
263 end;
264
265 // everything fine
266 FPipe := hPipe;
267end;
268
269
270procedure TPipeStreamImpl.Write(const buffer: TBytes; offset, count: Integer);
271var cbWritten : DWORD;
272begin
273 if not IsOpen
274 then raise TTransportException.Create( TTransportException.TExceptionType.NotOpen,
275 'Called write on non-open pipe');
276
277 if not WriteFile( FPipe, buffer[offset], count, cbWritten, nil)
278 then raise TTransportException.Create( TTransportException.TExceptionType.NotOpen,
279 'Write to pipe failed');
280end;
281
282
283function TPipeStreamImpl.Read( var buffer: TBytes; offset, count: Integer): Integer;
284var cbRead : DWORD;
285 bytes, retries : LongInt;
286 bOk : Boolean;
287const INTERVAL = 10; // ms
288begin
289 if not IsOpen
290 then raise TTransportException.Create( TTransportException.TExceptionType.NotOpen,
291 'Called read on non-open pipe');
292
293 // MSDN: Handle can be a handle to a named pipe instance,
294 // or it can be a handle to the read end of an anonymous pipe,
295 // The handle must have GENERIC_READ access to the pipe.
296 if FTimeOut <> INFINITE then begin
297 retries := Max( 1, Round( 1.0 * FTimeOut / INTERVAL));
298 while TRUE do begin
299 if IsOpen
300 and PeekNamedPipe( FPipe, nil, 0, nil, @bytes, nil)
301 and (bytes > 0)
302 then Break; // there are data
303
304 Dec( retries);
305 if retries > 0
306 then Sleep( INTERVAL)
307 else raise TTransportException.Create( TTransportException.TExceptionType.TimedOut,
308 'Pipe read timed out');
309 end;
310 end;
311
312 // read the data (or block INFINITE-ly)
313 bOk := ReadFile( FPipe, buffer[offset], count, cbRead, nil);
314 if (not bOk) and (GetLastError() <> ERROR_MORE_DATA)
315 then result := 0 // No more data, possibly because client disconnected.
316 else result := cbRead;
317end;
318
319
320function TPipeStreamImpl.ToArray: TBytes;
321var bytes : LongInt;
322begin
323 SetLength( result, 0);
324 bytes := 0;
325
326 if IsOpen
327 and PeekNamedPipe( FPipe, nil, 0, nil, @bytes, nil)
328 and (bytes > 0)
329 then begin
330 SetLength( result, bytes);
331 Read( result, 0, bytes);
332 end;
333end;
334
335
336{ TNamedPipeImpl }
337
338
339constructor TNamedPipeImpl.Create();
340// Constructs a new pipe object / provides defaults
341begin
342 inherited Create( nil, nil);
343 FOwner := FALSE;
344end;
345
346
347constructor TNamedPipeImpl.Create( const aPipeName : string; const aShareMode: DWORD;
348 const aSecurityAttributes: PSecurityAttributes;
349 const aTimeOut : DWORD);
350// Named pipe constructor
351begin
352 Create();
353 FInputStream := TPipeStreamImpl.Create( aPipeName, aShareMode, aSecurityAttributes, aTimeOut);
354 FOutputStream := FInputStream; // true for named pipes
355 FOwner := TRUE;
356end;
357
358
359constructor TNamedPipeImpl.Create( aPipe : THandle; aOwnsHandle : Boolean);
360// Named pipe constructor
361begin
362 Create();
363 FInputStream := TPipeStreamImpl.Create( aPipe, aOwnsHandle);
364 FOutputStream := FInputStream; // true for named pipes
365 FOwner := aOwnsHandle;
366end;
367
368
369function TNamedPipeImpl.GetIsOpen: Boolean;
370begin
371 result := (FInputStream <> nil);
372end;
373
374
375procedure TNamedPipeImpl.Open;
376begin
377 if FOwner then begin
378 FInputStream.Open;
379 if (FOutputStream <> nil) and (FOutputStream <> FInputStream)
380 then FOutputStream.Open;
381 end;
382end;
383
384
385procedure TNamedPipeImpl.Close;
386begin
387 if FOwner then begin
388 FInputStream.Close;
389 if (FOutputStream <> nil) and (FOutputStream <> FInputStream)
390 then FOutputStream.Close;
391 end;
392end;
393
394
395{ TAnonymousPipeImpl }
396
397
398constructor TAnonymousPipeImpl.Create();
399// Constructs a new pipe object / provides defaults
400begin
401 inherited Create( nil, nil);
402 FOwner := FALSE;
403end;
404
405
406constructor TAnonymousPipeImpl.Create( const aPipeRead, aPipeWrite : THandle; aOwnsHandles : Boolean);
407// Anonymous pipe constructor
408begin
409 Create();
410 FInputStream := TPipeStreamImpl.Create( aPipeRead, aOwnsHandles);
411 FOutputStream := TPipeStreamImpl.Create( aPipeWrite, aOwnsHandles);
412 FOwner := aOwnsHandles;
413end;
414
415
416function TAnonymousPipeImpl.GetIsOpen: Boolean;
417begin
418 result := (FInputStream <> nil) or (FOutputStream <> nil);
419end;
420
421
422procedure TAnonymousPipeImpl.Open;
423begin
424 if FOwner then begin
425 FInputStream.Open;
426 if (FOutputStream <> nil) and (FOutputStream <> FInputStream)
427 then FOutputStream.Open;
428 end;
429end;
430
431
432procedure TAnonymousPipeImpl.Close;
433begin
434 if FOwner then begin
435 FInputStream.Close;
436 if (FOutputStream <> nil) and (FOutputStream <> FInputStream)
437 then FOutputStream.Close;
438 end;
439end;
440
441
442{ TServerPipeImpl }
443
444
445constructor TServerPipeImpl.Create( aPipename : string; aBufsize, aMaxConns : Cardinal);
446// Named Pipe CTOR
447begin
448 inherited Create;
449 FPipeName := aPipename;
450 FBufsize := aBufSize;
451 FMaxConns := Max( 1, Min( 255, aMaxConns)); // restrict to 1-255 connections
452 FAnonymous := FALSE;
453 FHandle := INVALID_HANDLE_VALUE;
454 FWriteHandle := INVALID_HANDLE_VALUE;
455 FClientAnonRead := INVALID_HANDLE_VALUE;
456 FClientAnonWrite := INVALID_HANDLE_VALUE;
457
458 if Copy(FPipeName,1,2) <> '\\'
459 then FPipeName := '\\.\pipe\' + FPipeName; // assume localhost
460end;
461
462
463constructor TServerPipeImpl.Create( aPipename : string; aBufsize : Cardinal);
464// Named Pipe CTOR
465begin
466 Create( aPipename, aBufSize, TPIPE_SERVER_MAX_CONNS_DEFAULT);
467end;
468
469
470constructor TServerPipeImpl.Create( aPipename : string);
471// Named Pipe CTOR
472begin
473 Create( aPipename, 1024, TPIPE_SERVER_MAX_CONNS_DEFAULT);
474end;
475
476
477constructor TServerPipeImpl.Create( aBufsize : Cardinal);
478// Anonymous pipe CTOR
479begin
480 inherited Create;
481 FPipeName := '';
482 FBufsize := aBufSize;
483 FMaxConns := 1;
484 FAnonymous := TRUE;
485 FHandle := INVALID_HANDLE_VALUE;
486 FWriteHandle := INVALID_HANDLE_VALUE;
487 FClientAnonRead := INVALID_HANDLE_VALUE;
488 FClientAnonWrite := INVALID_HANDLE_VALUE;
489
490 // The anonymous pipe needs to be created first so that the server can
491 // pass the handles on to the client before the serve (acceptImpl)
492 // blocking call.
493 if not CreateAnonPipe
494 then raise TTransportException.Create( TTransportException.TExceptionType.NotOpen,
495 ClassName+'.Create() failed');
496end;
497
498
499constructor TServerPipeImpl.Create();
500// Anonymous pipe CTOR
501begin
502 Create( 1024);
503end;
504
505
506function TServerPipeImpl.AcceptImpl: ITransport;
507var buf : Byte;
508 br : DWORD;
509 connectRet : Boolean;
510begin
511 if FAnonymous then begin //Anonymous Pipe
512
513 // This 0-byte read serves merely as a blocking call.
514 if not ReadFile( FHandle, buf, 0, br, nil)
515 and (GetLastError() <> ERROR_MORE_DATA)
516 then raise TTransportException.Create( TTransportException.TExceptionType.NotOpen,
517 'TPipeServer unable to initiate pipe communication');
518 result := TAnonymousPipeImpl.Create( FHandle, FWriteHandle, FALSE);
519
520 end
521 else begin //Named Pipe
522
523 while TRUE do begin
524 if not CreateNamedPipe()
525 then raise TTransportException.Create( TTransportException.TExceptionType.NotOpen,
526 'TPipeServer CreateNamedPipe failed');
527
528 // Wait for the client to connect; if it succeeds, the
529 // function returns a nonzero value. If the function returns
530 // zero, GetLastError should return ERROR_PIPE_CONNECTED.
531 if ConnectNamedPipe( FHandle,nil)
532 then connectRet := TRUE
533 else connectRet := (GetLastError() = ERROR_PIPE_CONNECTED);
534
535 if connectRet
536 then Break;
537
538 Close;
539 raise TTransportException.Create( TTransportException.TExceptionType.NotOpen,
540 'TPipeServer: client connection failed');
541 end;
542
543 result := TNamedPipeImpl.Create( FHandle, TRUE);
544 end;
545end;
546
547
548procedure TServerPipeImpl.Listen;
549begin
550 // not much to do here
551end;
552
553
554procedure TServerPipeImpl.Close;
555begin
556 if not FAnonymous then begin
557
558 if FHandle <> INVALID_HANDLE_VALUE then begin
559 DisconnectNamedPipe( FHandle);
560 CloseHandle( FHandle);
561 FHandle := INVALID_HANDLE_VALUE;
562 end;
563
564 end
565 else begin
566
567 if FHandle <> INVALID_HANDLE_VALUE then begin
568 CloseHandle( FHandle);
569 FHandle := INVALID_HANDLE_VALUE;
570 end;
571 if FWriteHandle <> INVALID_HANDLE_VALUE then begin
572 CloseHandle( FWriteHandle);
573 FWriteHandle := INVALID_HANDLE_VALUE;
574 end;
575 if FClientAnonRead <> INVALID_HANDLE_VALUE then begin
576 CloseHandle( FClientAnonRead);
577 FClientAnonRead := INVALID_HANDLE_VALUE;
578 end;
579 if FClientAnonWrite <> INVALID_HANDLE_VALUE then begin
580 CloseHandle( FClientAnonWrite);
581 FClientAnonWrite := INVALID_HANDLE_VALUE;
582 end;
583 end;
584end;
585
586
587function TServerPipeImpl.Handle : THandle;
588begin
589 result := FHandle;
590end;
591
592
593function TServerPipeImpl.WriteHandle : THandle;
594begin
595 result := FWriteHandle;
596end;
597
598
599function TServerPipeImpl.ClientAnonRead : THandle;
600begin
601 result := FClientAnonRead;
602end;
603
604
605function TServerPipeImpl.ClientAnonWrite : THandle;
606begin
607 result := FClientAnonWrite;
608end;
609
610
611function TServerPipeImpl.CreateNamedPipe : Boolean;
612var SIDAuthWorld : SID_IDENTIFIER_AUTHORITY ;
613 everyone_sid : PSID;
614 ea : EXPLICIT_ACCESS;
615 acl : PACL;
616 sd : PSECURITY_DESCRIPTOR;
617 sa : SECURITY_ATTRIBUTES;
618 hPipe : THandle;
619const
620 SECURITY_WORLD_SID_AUTHORITY : TSIDIdentifierAuthority = (Value : (0,0,0,0,0,1));
621 SECURITY_WORLD_RID = $00000000;
622begin
623 // Windows - set security to allow non-elevated apps
624 // to access pipes created by elevated apps.
625 SIDAuthWorld := SECURITY_WORLD_SID_AUTHORITY;
626 everyone_sid := nil;
627 AllocateAndInitializeSid( SIDAuthWorld, 1, SECURITY_WORLD_RID, 0, 0, 0, 0, 0, 0, 0, everyone_sid);
628
629 ZeroMemory( @ea, SizeOf(ea));
630 ea.grfAccessPermissions := SPECIFIC_RIGHTS_ALL or STANDARD_RIGHTS_ALL;
631 ea.grfAccessMode := SET_ACCESS;
632 ea.grfInheritance := NO_INHERITANCE;
633 ea.Trustee.TrusteeForm := TRUSTEE_IS_SID;
634 ea.Trustee.TrusteeType := TRUSTEE_IS_WELL_KNOWN_GROUP;
635 ea.Trustee.ptstrName := PChar(everyone_sid);
636
637 acl := nil;
638 SetEntriesInAcl( 1, @ea, nil, acl);
639
640 sd := PSECURITY_DESCRIPTOR( LocalAlloc( LPTR,SECURITY_DESCRIPTOR_MIN_LENGTH));
641 Win32Check( InitializeSecurityDescriptor( sd, SECURITY_DESCRIPTOR_REVISION));
642 Win32Check( SetSecurityDescriptorDacl( sd, TRUE, acl, FALSE));
643
644 sa.nLength := SizeOf(sa);
645 sa.lpSecurityDescriptor := sd;
646 sa.bInheritHandle := FALSE;
647
648 // Create an instance of the named pipe
649 hPipe := Windows.CreateNamedPipe( PChar( FPipeName), // pipe name
650 PIPE_ACCESS_DUPLEX, // read/write access
651 PIPE_TYPE_MESSAGE or // message type pipe
652 PIPE_READMODE_MESSAGE, // message-read mode
653 FMaxConns, // max. instances
654 FBufSize, // output buffer size
655 FBufSize, // input buffer size
656 0, // client time-out
657 @sa); // default security attribute
658
659 if( hPipe = INVALID_HANDLE_VALUE) then begin
660 FHandle := INVALID_HANDLE_VALUE;
661 raise TTransportException.Create( TTransportException.TExceptionType.NotOpen,
662 'CreateNamedPipe() failed ' + IntToStr(GetLastError));
663 end;
664
665 FHandle := hPipe;
666 result := TRUE;
667end;
668
669
670function TServerPipeImpl.CreateAnonPipe : Boolean;
671var sd : PSECURITY_DESCRIPTOR;
672 sa : SECURITY_ATTRIBUTES; //TSecurityAttributes;
673 hCAR, hPipeW, hCAW, hPipe : THandle;
674begin
675 result := FALSE;
676
677 sd := PSECURITY_DESCRIPTOR( LocalAlloc( LPTR,SECURITY_DESCRIPTOR_MIN_LENGTH));
678 Win32Check( InitializeSecurityDescriptor( sd, SECURITY_DESCRIPTOR_REVISION));
679 Win32Check( SetSecurityDescriptorDacl( sd, TRUE, nil, FALSE));
680
681 sa.nLength := sizeof( sa);
682 sa.lpSecurityDescriptor := sd;
683 sa.bInheritHandle := TRUE; //allow passing handle to child
684
685 if not CreatePipe( hCAR, hPipeW, @sa, FBufSize) then begin //create stdin pipe
686 Console.WriteLine( 'TPipeServer CreatePipe (anon) failed, '+SysErrorMessage(GetLastError));
687 Exit;
688 end;
689
690 if not CreatePipe( hPipe, hCAW, @sa, FBufSize) then begin //create stdout pipe
691 Console.WriteLine( 'TPipeServer CreatePipe (anon) failed, '+SysErrorMessage(GetLastError));
692 CloseHandle( hCAR);
693 CloseHandle( hPipeW);
694 Exit;
695 end;
696
697 FClientAnonRead := hCAR;
698 FClientAnonWrite := hCAW;
699 FHandle := hPipe;
700 FWriteHandle := hPipeW;
701
702 result := TRUE;
703end;
704
705
706
707end.
708
709
710