1 /*
2  * File I/O extension to the State Threads Library.
3  */
4 
5 /*
6  * The contents of this file are subject to the Mozilla Public
7  * License Version 1.1 (the "License"); you may not use this file
8  * except in compliance with the License. You may obtain a copy of
9  * the License at http://www.mozilla.org/MPL/
10  *
11  * Software distributed under the License is distributed on an "AS
12  * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
13  * implied. See the License for the specific language governing
14  * rights and limitations under the License.
15  *
16  * The Original Code is the file I/O extension to the State Threads Library.
17  *
18  * The Initial Developer of the Original Code is Jeff
19  * <jlb-st@houseofdistraction.com>.  Portions created by the Initial
20  * Developer are Copyright (C) 2002 the Initial Developer.  All Rights
21  * Reserved.
22  *
23  * Contributor(s): (none)
24  *
25  * Alternatively, the contents of this file may be used under the
26  * terms of the GNU General Public License Version 2 or later (the
27  * "GPL"), in which case the provisions of the GPL are applicable
28  * instead of those above.  If you wish to allow use of your
29  * version of this file only under the terms of the GPL and not to
30  * allow others to use your version of this file under the MPL,
31  * indicate your decision by deleting the provisions above and
32  * replace them with the notice and other provisions required by
33  * the GPL.  If you do not delete the provisions above, a recipient
34  * may use your version of this file under either the MPL or the
35  * GPL.
36  */
37 
38 #include <stdlib.h>
39 
40 #include "stx_fileio.h"
41 
42 #define STX_FILEIO_SIGNUM SIGUSR2
43 
44 typedef struct {
45     st_netfd_t data_fd;
46     st_netfd_t control_fd;
47     pid_t pid;
48 } fileio_data_t;
49 
50 #define FILEREADER_MAX_READ 1024
51 
52 typedef struct {
53     off_t offset;
54     ssize_t nbytes;
55 } file_reader_cb_t;
56 
57 /**
58  * Fork a process to read a file and return its pid.  Receives
59  * offset/length commands from control stream and sends corresponding data
60  * to out stream.  A zero length on the control stream signals an end.
61  *
62  * @param fd stream from which to read
63  * @param control_out receives the file descriptor to which control commands can be sent
64  * @param fd_out receives the file descriptor from which the output of the command can be read.
65  * @return PID of the process created to execute the command
66  */
67 pid_t
file_reader(int fd,int * fd_control,int * fd_out)68 file_reader(int fd, int *fd_control, int *fd_out)
69 {
70     pid_t pid;
71     int control_pipe[2], out_pipe[2];
72 
73     if (pipe(control_pipe) < 0 || pipe(out_pipe) < 0)
74         return (pid_t)-1;
75 
76     pid = fork();
77     if (pid == (pid_t) -1)
78     {
79         close(control_pipe[0]);
80         close(control_pipe[1]);
81         close(out_pipe[0]);
82         close(out_pipe[1]);
83         return pid;
84     }
85     else if (pid == (pid_t) 0)
86     {
87         // child
88         off_t pos = 0;
89         file_reader_cb_t cb;
90         char buf[FILEREADER_MAX_READ];
91         if (fd == -1)
92             _exit(EXIT_FAILURE);
93 
94         while (sizeof(cb) == read(control_pipe[0], &cb, sizeof(cb))) {
95             ssize_t nb;
96             if (0 >= cb.nbytes)
97                 goto clean_exit;
98             if (pos != cb.offset) {
99                 pos = lseek(fd, cb.offset, SEEK_SET);
100                 if (pos == (off_t)-1)
101                     break;
102             }
103             nb = read(fd, buf, cb.nbytes);
104             if (nb == (ssize_t)-1)
105                 break;
106             pos += nb;
107             write(out_pipe[1], (char *)&nb, sizeof(nb));
108             write(out_pipe[1], buf, nb);
109         }
110         perror("ERROR: file_reader: ");
111     clean_exit:
112         close(control_pipe[0]);
113         close(control_pipe[1]);
114         close(out_pipe[0]);
115         close(out_pipe[1]);
116         _exit(EXIT_SUCCESS);
117     }
118 
119     // parent
120     close(out_pipe[1]);
121     close(control_pipe[0]);
122     *fd_out = out_pipe[0];
123     *fd_control = control_pipe[1];
124     return pid;
125 }
126 
127 /**
128  * fileio_data_t destructor callback
129  */
130 static void
fileio_data_destructor(void * dat_in)131 fileio_data_destructor(void *dat_in)
132 {
133     if (dat_in) {
134         fileio_data_t *dat = (fileio_data_t *)dat_in;
135         file_reader_cb_t cb;
136         cb.offset = 0;
137         cb.nbytes = 0;
138         st_write(dat->control_fd, (char *)&cb, sizeof(cb),
139 	 ST_UTIME_NO_TIMEOUT);
140         waitpid(dat->pid, NULL, 0);
141         st_netfd_close(dat->control_fd);
142         st_netfd_close(dat->data_fd);
143         free(dat_in);
144     }
145 }
146 
147 /**
148  * Retrieve fileio_data_t struct from an st descriptor.  Create and store
149  * a new one if needed.
150  */
get_fileio_data(st_netfd_t fd)151 static fileio_data_t *get_fileio_data(st_netfd_t fd)
152 {
153     fileio_data_t *dat = (fileio_data_t *)st_netfd_getspecific(fd);
154     if (!dat) {
155         int fd_control, fd_out;
156         pid_t pid = file_reader(st_netfd_fileno(fd), &fd_control, &fd_out);
157         if (pid != (pid_t)-1) {
158             dat = (fileio_data_t *)calloc(1, sizeof(fileio_data_t));
159             dat->control_fd = st_netfd_open(fd_control);
160             dat->data_fd = st_netfd_open(fd_out);
161             dat->pid = pid;
162             st_netfd_setspecific(fd, dat, fileio_data_destructor);
163         }
164     }
165     return dat;
166 }
167 
168 /**
169  * Read data from the specified section of a file.  Uses a forked
170  * file_reader process to do the actual reading so as to avoid causing all
171  * State Threads to block.
172  *
173  * @param fd must refer to a seekable file.
174  * @param offset absolute offset within the file
175  * @param buf output buffer
176  * @param nbytes size of the output buffer
177  * @param timeout
178  */
179 ssize_t
stx_file_read(st_netfd_t fd,off_t offset,void * buf,size_t nbytes,st_utime_t timeout)180 stx_file_read(st_netfd_t fd, off_t offset, void *buf, size_t nbytes, st_utime_t timeout)
181 {
182     fileio_data_t *dat = get_fileio_data(fd);
183     if (dat) {
184         file_reader_cb_t cb;
185         ssize_t ret = (ssize_t)-1;
186         cb.offset = offset;
187         cb.nbytes = nbytes;
188         st_write(dat->control_fd, (char *)&cb, sizeof(cb), timeout);
189         if (sizeof(ret) == st_read(dat->data_fd, (char *)&ret, sizeof(ret), timeout) && 0 < ret && ret <= nbytes) {
190             return st_read(dat->data_fd, buf, ret, timeout);
191         } else {
192             return ret;
193         }
194     }
195 
196     return (ssize_t)-1;
197 }
198