1- use std:: { ffi:: CStr , panic, sync:: Arc } ;
1+ use std:: { ffi:: CStr , fs :: File , os :: fd :: FromRawFd , panic, sync:: Arc } ;
22
33use arrow:: datatypes:: SchemaRef ;
44use object_store:: { path:: Path , ObjectStoreScheme } ;
@@ -15,8 +15,9 @@ use pgrx::{
1515 ereport,
1616 ffi:: c_char,
1717 pg_sys:: {
18- get_role_oid, has_privs_of_role, palloc0, superuser, AsPgCStr , DataDir , FileClose ,
19- FilePathName , GetUserId , InvalidOid , OpenTemporaryFile , TempTablespacePath , MAXPGPATH ,
18+ get_role_oid, has_privs_of_role, palloc0, superuser, AsPgCStr , ClosePipeStream , DataDir ,
19+ FileClose , FilePathName , GetUserId , InvalidOid , OpenPipeStream , OpenTemporaryFile ,
20+ TempTablespacePath , __sFILE, MAXPGPATH , PG_BINARY_R , PG_BINARY_W ,
2021 } ,
2122} ;
2223use url:: Url ;
@@ -39,22 +40,42 @@ pub(crate) struct ParsedUriInfo {
3940 pub ( crate ) bucket : Option < String > ,
4041 pub ( crate ) path : Path ,
4142 pub ( crate ) scheme : ObjectStoreScheme ,
42- pub ( crate ) stdio_tmp_fd : Option < i32 > ,
43- pub ( crate ) program : Option < * mut c_char > ,
43+ // tmp_fd is used as intermediate file for copying data to/from stdin/out or program pipes
44+ pub ( crate ) tmp_fd : Option < i32 > ,
45+ // pipe_file is used to hold the pipe file descriptor for copying data to/from a program
46+ // call open_program_pipe to open the pipe to a program
47+ pub ( crate ) is_program : bool ,
48+ pub ( crate ) pipe_file : * mut __sFILE ,
4449}
4550
4651impl ParsedUriInfo {
4752 pub ( crate ) fn for_std_inout ( ) -> Self {
48- Self :: create_tmp_file ( )
53+ Self :: with_tmp_file ( )
4954 }
5055
51- pub ( crate ) fn for_program ( program : * mut c_char ) -> Self {
52- let mut uri_info = Self :: create_tmp_file ( ) ;
53- uri_info. program = Some ( program ) ;
56+ pub ( crate ) fn for_program ( ) -> Self {
57+ let mut uri_info = Self :: with_tmp_file ( ) ;
58+ uri_info. is_program = true ;
5459 uri_info
5560 }
5661
57- fn create_tmp_file ( ) -> Self {
62+ pub ( crate ) fn open_program_pipe ( & mut self , program : & str , copy_from : bool ) -> File {
63+ let pipe_mode = if copy_from { PG_BINARY_R } else { PG_BINARY_W } ;
64+
65+ let pipe_file = unsafe { OpenPipeStream ( program. as_pg_cstr ( ) , pipe_mode. as_ptr ( ) ) } ;
66+
67+ if pipe_file. is_null ( ) {
68+ panic ! ( "Failed to open pipe stream for program: {}" , program) ;
69+ }
70+
71+ self . pipe_file = pipe_file;
72+
73+ let pipe_fd = ( unsafe { * self . pipe_file } ) . _file ;
74+
75+ unsafe { File :: from_raw_fd ( pipe_fd as _ ) }
76+ }
77+
78+ fn with_tmp_file ( ) -> Self {
5879 // open temp postgres file, which is removed after transaction ends
5980 let tmp_path_fd = unsafe { OpenTemporaryFile ( false ) } ;
6081
@@ -81,11 +102,15 @@ impl ParsedUriInfo {
81102
82103 let mut parsed_uri = Self :: try_from ( tmp_path. as_str ( ) ) . unwrap_or_else ( |e| panic ! ( "{}" , e) ) ;
83104
84- parsed_uri. stdio_tmp_fd = Some ( tmp_path_fd) ;
105+ parsed_uri. tmp_fd = Some ( tmp_path_fd) ;
85106
86107 parsed_uri
87108 }
88109
110+ fn is_std_inout ( & self ) -> bool {
111+ self . tmp_fd . is_some ( ) && !self . is_program
112+ }
113+
89114 fn try_parse_uri ( uri : & str ) -> Result < Url , String > {
90115 if !uri. contains ( "://" ) {
91116 // local file
@@ -139,17 +164,23 @@ impl TryFrom<&str> for ParsedUriInfo {
139164 bucket,
140165 path,
141166 scheme,
142- stdio_tmp_fd : None ,
143- program : None ,
167+ tmp_fd : None ,
168+ is_program : false ,
169+ pipe_file : std:: ptr:: null_mut ( ) ,
144170 } )
145171 }
146172}
147173
148174impl Drop for ParsedUriInfo {
149175 fn drop ( & mut self ) {
150- if let Some ( stdio_tmp_fd ) = self . stdio_tmp_fd {
176+ if let Some ( tmp_fd ) = self . tmp_fd {
151177 // close temp file, postgres api will remove it on close
152- unsafe { FileClose ( stdio_tmp_fd) } ;
178+ unsafe { FileClose ( tmp_fd) } ;
179+ }
180+
181+ if !self . pipe_file . is_null ( ) {
182+ // close pipe file, postgres api will remove it on close
183+ unsafe { ClosePipeStream ( self . pipe_file ) } ;
153184 }
154185 }
155186}
@@ -286,17 +317,15 @@ pub(crate) fn ensure_access_privilege_to_uri(uri_info: &ParsedUriInfo, copy_from
286317 return ;
287318 }
288319
289- let is_program = uri_info. program . is_some ( ) ;
290-
291320 // permission check is not needed for stdin/out
292- if uri_info. stdio_tmp_fd . is_some ( ) && !is_program {
321+ if uri_info. is_std_inout ( ) {
293322 return ;
294323 }
295324
296325 let user_id = unsafe { GetUserId ( ) } ;
297326 let is_file = uri_info. uri . scheme ( ) == "file" ;
298327
299- let required_role_name = if is_program {
328+ let required_role_name = if uri_info . is_program {
300329 "pg_execute_server_program"
301330 } else if is_file {
302331 if copy_from {
@@ -317,7 +346,7 @@ pub(crate) fn ensure_access_privilege_to_uri(uri_info: &ParsedUriInfo, copy_from
317346 unsafe { get_role_oid ( required_role_name. to_string ( ) . as_pg_cstr ( ) , false ) } ;
318347
319348 let operation_str = if copy_from { "from" } else { "to" } ;
320- let object_type = if is_program {
349+ let object_type = if uri_info . is_program {
321350 "program"
322351 } else if is_file {
323352 "file"
0 commit comments