1 /*
2 Internal file viewer for the Midnight Commander
3 Function for work with growing buffers
4
5 Copyright (C) 1994-2025
6 Free Software Foundation, Inc.
7
8 Written by:
9 Miguel de Icaza, 1994, 1995, 1998
10 Janne Kukonlehto, 1994, 1995
11 Jakub Jelinek, 1995
12 Joseph M. Hinkle, 1996
13 Norbert Warmuth, 1997
14 Pavel Machek, 1998
15 Roland Illig <roland.illig@gmx.de>, 2004, 2005
16 Slava Zanko <slavazanko@google.com>, 2009
17 Andrew Borodin <aborodin@vmail.ru>, 2009, 2014
18 Ilia Maslakov <il.smind@gmail.com>, 2009
19
20 This file is part of the Midnight Commander.
21
22 The Midnight Commander is free software: you can redistribute it
23 and/or modify it under the terms of the GNU General Public License as
24 published by the Free Software Foundation, either version 3 of the License,
25 or (at your option) any later version.
26
27 The Midnight Commander is distributed in the hope that it will be useful,
28 but WITHOUT ANY WARRANTY; without even the implied warranty of
29 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
30 GNU General Public License for more details.
31
32 You should have received a copy of the GNU General Public License
33 along with this program. If not, see <https://www.gnu.org/licenses/>.
34 */
35
36 #include <config.h>
37 #include <errno.h>
38
39 #include "lib/global.h"
40 #include "lib/vfs/vfs.h"
41 #include "lib/util.h"
42 #include "lib/widget.h" // D_NORMAL
43
44 #include "internal.h"
45
46 /* Block size for reading files in parts */
47 #define VIEW_PAGE_SIZE ((size_t) 8192)
48
49 /*** global variables ****************************************************************************/
50
51 /*** file scope macro definitions ****************************************************************/
52
53 /*** file scope type declarations ****************************************************************/
54
55 /*** file scope variables ************************************************************************/
56
57 /*** file scope functions ************************************************************************/
58 /* --------------------------------------------------------------------------------------------- */
59
60 /* --------------------------------------------------------------------------------------------- */
61 /*** public functions ****************************************************************************/
62 /* --------------------------------------------------------------------------------------------- */
63
64 void
65 mcview_growbuf_init (WView *view)
/* ![[previous]](../icons/n_left.png)
![[next]](../icons/right.png)
![[first]](../icons/n_first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
66 {
67 view->growbuf_in_use = TRUE;
68 view->growbuf_blockptr = g_ptr_array_new_with_free_func (g_free);
69 view->growbuf_lastindex = VIEW_PAGE_SIZE;
70 view->growbuf_finished = FALSE;
71 }
72
73 /* --------------------------------------------------------------------------------------------- */
74
75 void
76 mcview_growbuf_done (WView *view)
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
77 {
78 view->growbuf_finished = TRUE;
79
80 if (view->datasource == DS_STDIO_PIPE)
81 {
82 mc_pclose (view->ds_stdio_pipe, NULL);
83 view->ds_stdio_pipe = NULL;
84 }
85 else // view->datasource == DS_VFS_PIPE
86 {
87 (void) mc_close (view->ds_vfs_pipe);
88 view->ds_vfs_pipe = -1;
89 }
90 }
91
92 /* --------------------------------------------------------------------------------------------- */
93
94 void
95 mcview_growbuf_free (WView *view)
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
96 {
97 g_assert (view->growbuf_in_use);
98
99 g_ptr_array_free (view->growbuf_blockptr, TRUE);
100 view->growbuf_blockptr = NULL;
101 view->growbuf_in_use = FALSE;
102 }
103
104 /* --------------------------------------------------------------------------------------------- */
105
106 off_t
107 mcview_growbuf_filesize (WView *view)
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
108 {
109 g_assert (view->growbuf_in_use);
110
111 if (view->growbuf_blockptr->len == 0)
112 return 0;
113 else
114 return ((off_t) view->growbuf_blockptr->len - 1) * VIEW_PAGE_SIZE + view->growbuf_lastindex;
115 }
116
117 /* --------------------------------------------------------------------------------------------- */
118 /** Copies the output from the pipe to the growing buffer, until either
119 * the end-of-pipe is reached or the interval [0..ofs) of the growing
120 * buffer is completely filled.
121 */
122
123 void
124 mcview_growbuf_read_until (WView *view, off_t ofs)
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
125 {
126 gboolean short_read = FALSE;
127
128 g_assert (view->growbuf_in_use);
129
130 if (view->growbuf_finished)
131 return;
132
133 while (mcview_growbuf_filesize (view) < ofs || short_read)
134 {
135 ssize_t nread = 0;
136 byte *p;
137 size_t bytesfree;
138
139 if (view->growbuf_lastindex == VIEW_PAGE_SIZE)
140 {
141 // Append a new block to the growing buffer
142 byte *newblock = g_try_malloc (VIEW_PAGE_SIZE);
143 if (newblock == NULL)
144 return;
145
146 g_ptr_array_add (view->growbuf_blockptr, newblock);
147 view->growbuf_lastindex = 0;
148 }
149
150 p = (byte *) g_ptr_array_index (view->growbuf_blockptr, view->growbuf_blockptr->len - 1)
151 + view->growbuf_lastindex;
152
153 bytesfree = VIEW_PAGE_SIZE - view->growbuf_lastindex;
154
155 if (view->datasource == DS_STDIO_PIPE)
156 {
157 mc_pipe_t *sp = view->ds_stdio_pipe;
158 GError *error = NULL;
159
160 if (bytesfree > MC_PIPE_BUFSIZE)
161 bytesfree = MC_PIPE_BUFSIZE;
162
163 sp->out.len = bytesfree;
164 sp->err.len = MC_PIPE_BUFSIZE;
165
166 mc_pread (sp, &error);
167
168 if (error != NULL)
169 {
170 mcview_show_error (view, NULL, error->message);
171 g_error_free (error);
172 mcview_growbuf_done (view);
173 return;
174 }
175
176 if (view->pipe_first_err_msg && sp->err.len > 0)
177 {
178 // ignore possible following errors
179 /* reset this flag before call of mcview_show_error() to break
180 * endless recursion: mcview_growbuf_read_until() -> mcview_show_error() ->
181 * MSG_DRAW -> mcview_display() -> mcview_get_byte() -> mcview_growbuf_read_until()
182 */
183 view->pipe_first_err_msg = FALSE;
184
185 mcview_show_error (view, NULL, sp->err.buf);
186
187 /* when switch from parse to raw mode and back,
188 * do not close the already closed pipe (see call to mcview_growbuf_done below).
189 * return from here since (sp == view->ds_stdio_pipe) would now be invalid.
190 * NOTE: this check was removed by ticket #4103 but the above call to
191 * mcview_show_error triggers the stdio pipe handle to be closed:
192 * mcview_close_datasource -> mcview_growbuf_done
193 */
194 if (view->ds_stdio_pipe == NULL)
195 return;
196 }
197
198 if (sp->out.len > 0)
199 {
200 memmove (p, sp->out.buf, sp->out.len);
201 nread = sp->out.len;
202 }
203 else if (sp->out.len == MC_PIPE_STREAM_EOF || sp->out.len == MC_PIPE_ERROR_READ)
204 {
205 if (sp->out.len == MC_PIPE_ERROR_READ)
206 {
207 errno = sp->out.error;
208 mcview_show_error (view, _ ("Failed to read data from child stdout"), "");
209 }
210
211 /* when switch from parse to raw mode and back,
212 * do not close the already closed pipe after following loop:
213 * mcview_growbuf_read_until() -> mcview_show_error() ->
214 * MSG_DRAW -> mcview_display() -> mcview_get_byte() -> mcview_growbuf_read_until()
215 */
216 mcview_growbuf_done (view);
217
218 mcview_display (view);
219 return;
220 }
221 }
222 else
223 {
224 g_assert (view->datasource == DS_VFS_PIPE);
225 do
226 {
227 nread = mc_read (view->ds_vfs_pipe, p, bytesfree);
228 }
229 while (nread == -1 && errno == EINTR);
230
231 if (nread <= 0)
232 {
233 mcview_growbuf_done (view);
234 return;
235 }
236 }
237 short_read = ((size_t) nread < bytesfree);
238 view->growbuf_lastindex += nread;
239 }
240 }
241
242 /* --------------------------------------------------------------------------------------------- */
243
244 gboolean
245 mcview_get_byte_growing_buffer (WView *view, off_t byte_index, int *retval)
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
246 {
247 char *p;
248
249 g_assert (view->growbuf_in_use);
250
251 if (retval != NULL)
252 *retval = -1;
253
254 if (byte_index < 0)
255 return FALSE;
256
257 p = mcview_get_ptr_growing_buffer (view, byte_index);
258 if (p == NULL)
259 return FALSE;
260
261 if (retval != NULL)
262 *retval = (unsigned char) (*p);
263
264 return TRUE;
265 }
266
267 /* --------------------------------------------------------------------------------------------- */
268
269 char *
270 mcview_get_ptr_growing_buffer (WView *view, off_t byte_index)
/* ![[previous]](../icons/left.png)
![[next]](../icons/n_right.png)
![[first]](../icons/first.png)
![[last]](../icons/n_last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
271 {
272 off_t pageno, pageindex;
273
274 g_assert (view->growbuf_in_use);
275
276 if (byte_index < 0)
277 return NULL;
278
279 pageno = byte_index / VIEW_PAGE_SIZE;
280 pageindex = byte_index % VIEW_PAGE_SIZE;
281
282 mcview_growbuf_read_until (view, byte_index + 1);
283 if (view->growbuf_blockptr->len == 0)
284 return NULL;
285 if (pageno < (off_t) view->growbuf_blockptr->len - 1)
286 return ((char *) g_ptr_array_index (view->growbuf_blockptr, pageno) + pageindex);
287 if (pageno == (off_t) view->growbuf_blockptr->len - 1
288 && pageindex < (off_t) view->growbuf_lastindex)
289 return ((char *) g_ptr_array_index (view->growbuf_blockptr, pageno) + pageindex);
290 return NULL;
291 }
292
293 /* --------------------------------------------------------------------------------------------- */