Document null streams
* doc/texinfo/nullstream.texi: New file. * mh/mh_init.c (mh_real_install): Use mu_stream_t instead of FILE. * mh/mh_whatnow.c (_whatnow): Likewise.
Showing
3 changed files
with
320 additions
and
23 deletions
doc/texinfo/nullstream.texi
0 → 100644
1 | @c This is part of the GNU Mailutils manual. | ||
2 | @c Copyright (C) 2010 Free Software Foundation, Inc. | ||
3 | @c See file mailutils.texi for copying conditions. | ||
4 | @c ******************************************************************* | ||
5 | |||
6 | @section Null stream | ||
7 | @cindex null stream | ||
8 | @cindex stream, null | ||
9 | |||
10 | A @dfn{null stream} is similar to the system @file{/dev/null} device. | ||
11 | It is not connected to any particular physical storage. Any data | ||
12 | written to such stream are irrevocably lost. Reading from such a | ||
13 | stream returns fixed data, depending on the mode of the stream. | ||
14 | |||
15 | An instance of the null stream is created using the following function: | ||
16 | |||
17 | @deffn {stream function} int mu_nullstream_create (mu_stream_t *pstr, int mode) | ||
18 | Create an instance of the null stream and return it in the memory | ||
19 | location pointed to by @var{pstr}. The @var{mode} argument specifies | ||
20 | the access mode for this stream. It can be a binary @samp{or} of the | ||
21 | following values: | ||
22 | |||
23 | @table @code | ||
24 | @item MU_STREAM_READ | ||
25 | Stream is opened for reading. | ||
26 | @item MU_STREAM_WRITE | ||
27 | Stream is opened for writing. | ||
28 | @end table | ||
29 | |||
30 | The returned stream is always seekable, so the @code{MU_STREAM_SEEK} | ||
31 | mode is implied. | ||
32 | |||
33 | Any other bits set in the @var{mode} argument are silently ignored. | ||
34 | @end deffn | ||
35 | |||
36 | Writing to an instance of null stream and seeking in such streams | ||
37 | always succeeds. If the stream was created with the | ||
38 | @code{MU_STREAM_READ} on, the reads from it normally behave as if | ||
39 | it were connected to an endless source of zero bytes, i.e. each call | ||
40 | to: | ||
41 | |||
42 | @smallexample | ||
43 | mu_stream_read (str, buf, size, &n); | ||
44 | @end smallexample | ||
45 | |||
46 | @noindent | ||
47 | results in filling @var{buf} with @var{size} zeroes. This is similar | ||
48 | to reading from the system @file{/dev/zero} device@footnote{Note that | ||
49 | the internal implementation of @dfn{null streams} has nothing to do | ||
50 | with @file{/dev/null}, or @file{/dev/zero}. We refer to these devices | ||
51 | only to illustrate the behavior of @dfn{null streams}.}. | ||
52 | |||
53 | This is the default behavior when reading. It can be altered using | ||
54 | the following @dfn{ioctls}. | ||
55 | |||
56 | @defvr {ioctl} MU_IOCTL_NULLSTREAM_SET_PATTERN | ||
57 | Set the @dfn{pattern} for reads. The argument is a pointer to | ||
58 | @dfn{struct mu_nullstream_pattern}, defined as: | ||
59 | |||
60 | @smallexample | ||
61 | @group | ||
62 | struct mu_nullstream_pattern | ||
63 | @{ | ||
64 | char *pattern; /* Return pattern */ | ||
65 | size_t size; /* Number of bytes in pattern */ | ||
66 | @}; | ||
67 | @end group | ||
68 | @end smallexample | ||
69 | |||
70 | The @samp{pattern} member points to @samp{size} bytes of data which | ||
71 | are returned cyclically at each read. For example, suppose that | ||
72 | @var{str} is a null stream instance, and consider the following code: | ||
73 | |||
74 | @smallexample | ||
75 | struct mu_nullstream_pattern pat; | ||
76 | char buf[16]; | ||
77 | size_t n; | ||
78 | |||
79 | pat.pattern = "01234567"; | ||
80 | pat.size = 8; | ||
81 | mu_stream_ioctl (str, MU_IOCTL_NULLSTREAM_SET_PATTERN, &pat); | ||
82 | |||
83 | mu_stream_read (str, buf, sizeof (buf), &n); | ||
84 | @end smallexample | ||
85 | |||
86 | Then, after the call to @code{mu_stream_read}, we will have: | ||
87 | |||
88 | @smallexample | ||
89 | @group | ||
90 | n @result{} 16 | ||
91 | buf @result{} "0123456701234567" | ||
92 | @end group | ||
93 | @end smallexample | ||
94 | |||
95 | Similarly, the following code: | ||
96 | |||
97 | @smallexample | ||
98 | mu_stream_seek (str, 3, MU_SEEK_SET, NULL); | ||
99 | mu_stream_read (str, buf, sizeof (buf), &n); | ||
100 | @end smallexample | ||
101 | |||
102 | @noindent | ||
103 | will yield: | ||
104 | |||
105 | @smallexample | ||
106 | @group | ||
107 | n @result{} 16 | ||
108 | buf @result{} "3456701234567012" | ||
109 | @end group | ||
110 | @end smallexample | ||
111 | |||
112 | The default behavior corresponds to the following initialization: | ||
113 | |||
114 | @smallexample | ||
115 | @group | ||
116 | pat.pattern = ""; | ||
117 | pat.size = 1; | ||
118 | mu_stream_ioctl (str, MU_IOCTL_NULLSTREAM_SET_PATTERN, &pat); | ||
119 | @end group | ||
120 | @end smallexample | ||
121 | |||
122 | Calling the @samp{MU_IOCTL_NULLSTREAM_SET_PATTERN} with a @samp{NULL} | ||
123 | argument causes all subsequent reads from that stream to return @samp{EOF}: | ||
124 | |||
125 | @smallexample | ||
126 | @group | ||
127 | mu_stream_ioctl (str, MU_IOCTL_NULLSTREAM_SET_PATTERN, NULL); | ||
128 | @dots{} | ||
129 | rc = mu_stream_read (str, buf, sizeof (buf), &n); | ||
130 | |||
131 | rc @result{} 0 | ||
132 | n @result{} 0 | ||
133 | @end group | ||
134 | @end smallexample | ||
135 | @end defvr | ||
136 | |||
137 | @defvr {ioctl} MU_IOCTL_NULLSTREAM_SET_PATCLASS | ||
138 | Set read pattern in terms of @dfn{C character classes} | ||
139 | (@FIXME-pxref{C character classes}). Argument is a pointer | ||
140 | to an integer containing a bitwise @samp{OR} of the desired | ||
141 | character classes from @file{mailutils/cctype.h}. For example, | ||
142 | the following code: | ||
143 | |||
144 | @smallexample | ||
145 | int class = MU_CTYPE_DIGIT|MU_CTYPE_XLETR; | ||
146 | mu_stream_ioctl (str, MU_IOCTL_NULLSTREAM_SET_PATCLASS, &class); | ||
147 | @end smallexample | ||
148 | |||
149 | @noindent | ||
150 | initializes the read pattern to the following string: | ||
151 | |||
152 | @smallexample | ||
153 | 0123456789ABCDEFabcdef | ||
154 | @end smallexample | ||
155 | @end defvr | ||
156 | |||
157 | Two ioctls are provided to control the @dfn{size} of a null stream | ||
158 | available to seek and read operations. | ||
159 | |||
160 | @defvr {ioctl} MU_IOCTL_NULLSTREAM_SETSIZE | ||
161 | Limit the addressable size of a null stream. Argument is a pointer | ||
162 | to @samp{mu_off_t} object specifying the new size. The example below | ||
163 | limits the stream size to 32 bytes: | ||
164 | |||
165 | @smallexample | ||
166 | @group | ||
167 | mu_off_t limit = 32; | ||
168 | mu_stream_ioctl (str, MU_IOCTL_NULLSTREAM_SETSIZE, &limit); | ||
169 | @end group | ||
170 | @end smallexample | ||
171 | @end defvr | ||
172 | |||
173 | Another way to set the null stream size is via the | ||
174 | @samp{mu_stream_truncate} function: | ||
175 | |||
176 | @smallexample | ||
177 | mu_stream_truncate (str, 32); | ||
178 | @end smallexample | ||
179 | |||
180 | Setting the stream size to @samp{0} causes all subsequent reads from | ||
181 | that stream to return @samp{EOF}. The similar effect has the | ||
182 | @samp{MU_IOCTL_NULLSTREAM_SET_PATTERN} ioctl with the @samp{NULL} argument. | ||
183 | |||
184 | @defvr {ioctl} MU_IOCTL_NULLSTREAM_CLRSIZE | ||
185 | Cancel the size limitation imposed by a previous | ||
186 | @samp{MU_IOCTL_NULLSTREAM_SETSIZE} ioctl or a call to | ||
187 | @samp{mu_stream_truncate}. Argument must be @samp{NULL}. | ||
188 | @end defvr | ||
189 | |||
190 | @menu | ||
191 | * null stream usage:: | ||
192 | @end menu | ||
193 | |||
194 | @node null stream usage | ||
195 | @subsection null stream usage | ||
196 | |||
197 | Due to their nature, null streams are not among the most used stream | ||
198 | flavors. They are mainly useful for testing or measuring purposes. | ||
199 | |||
200 | The @command{mhn} utility from GNU Mailutils MH suite gives a nice | ||
201 | example of using a null stream instance to compute the actual | ||
202 | (decoded) size of a part of a MIME message. In the example below | ||
203 | we present a simplified version of this code. It defines the | ||
204 | function @samp{decoded_size}: | ||
205 | |||
206 | @deffn {example function} mu_off_t decoded_size (mu_stream_t mime_str, @ | ||
207 | const char *encoding) | ||
208 | Return the size of the decoded input strem. Arguments are: | ||
209 | |||
210 | @table @var | ||
211 | @item mime_str | ||
212 | A stream obtained from the MIME part. | ||
213 | |||
214 | @item encoding | ||
215 | Encoding type, as obtained from the @samp{Content-Transfer-Encoding} | ||
216 | MIME header. | ||
217 | @end table | ||
218 | |||
219 | @example | ||
220 | mu_off_t | ||
221 | decoded_size (mu_stream_t mime_str, const char *encoding) | ||
222 | @{ | ||
223 | int rc; /* Result code */ | ||
224 | mu_stream_t fstr /* Filter stream */ | ||
225 | mu_stream_stat_buffer stat; /* Statistics buffer */ | ||
226 | mu_stream_t null; /* Null stream */ | ||
227 | @end example | ||
228 | |||
229 | First, create the filter stream for decoding the message part: | ||
230 | @example | ||
231 | rc = mu_filter_create (&fstr, mime_str, encoding, | ||
232 | MU_FILTER_DECODE, MU_STREAM_READ); | ||
233 | if (rc) | ||
234 | abort (); | ||
235 | @end example | ||
236 | |||
237 | Then, create an instance of the null stream which will act | ||
238 | as a receiver: | ||
239 | |||
240 | @example | ||
241 | mu_nullstream_create (&null, MU_STREAM_WRITE); | ||
242 | @end example | ||
243 | |||
244 | The data will be read from @var{fstr} and written to @var{null}. | ||
245 | Number of bytes written will give the decoded size of the message. | ||
246 | To get this number, attach the statistics buffer to the null | ||
247 | stream: | ||
248 | |||
249 | @example | ||
250 | mu_stream_set_stat (null, MU_STREAM_STAT_MASK (MU_STREAM_STAT_OUT), | ||
251 | stat); | ||
252 | @end example | ||
253 | |||
254 | The second argument instructs the stream to keep track of the | ||
255 | bytes output (i.e. written) to the stream. | ||
256 | |||
257 | Now, copy the entire contents of @var{fstr} to @var{null}: | ||
258 | |||
259 | @example | ||
260 | rc = mu_stream_copy (null, fstr, 0, NULL); | ||
261 | if (rc) | ||
262 | abort (); | ||
263 | @end example | ||
264 | |||
265 | When done, destroy both streams (they are not needed any more), and | ||
266 | return the value of @samp{MU_STREAM_STAT_OUT} element from @var{stat}: | ||
267 | |||
268 | @example | ||
269 | mu_stream_destroy (&null); | ||
270 | mu_stream_destroy (&fstr); | ||
271 | return stat[MU_STREAM_STAT_OUT]; | ||
272 | @} | ||
273 | @end example | ||
274 | |||
275 | |||
276 | |||
277 | |||
278 | |||
279 |
... | @@ -707,14 +707,21 @@ mh_real_install (char *name, int automode) | ... | @@ -707,14 +707,21 @@ mh_real_install (char *name, int automode) |
707 | char *home = mu_get_homedir (); | 707 | char *home = mu_get_homedir (); |
708 | char *mhdir; | 708 | char *mhdir; |
709 | char *ctx; | 709 | char *ctx; |
710 | mu_stream_t in; | ||
711 | int rc; | ||
710 | FILE *fp; | 712 | FILE *fp; |
711 | 713 | ||
714 | rc = mu_stdio_stream_create (&in, MU_STDIN_FD, 0); | ||
715 | if (rc) | ||
716 | { | ||
717 | mu_error (_("cannot create input stream: %s"), mu_strerror (rc)); | ||
718 | exit (1); | ||
719 | } | ||
720 | |||
712 | mhdir = mh_safe_make_file_name (home, "Mail"); | 721 | mhdir = mh_safe_make_file_name (home, "Mail"); |
713 | 722 | ||
714 | if (!automode) | 723 | if (!automode) |
715 | { | 724 | { |
716 | size_t n = 0; | ||
717 | |||
718 | /* TRANSLATORS: This is a question and will be followed | 725 | /* TRANSLATORS: This is a question and will be followed |
719 | by question mark on output. */ | 726 | by question mark on output. */ |
720 | if (mh_getyn_interactive (_("Do you need help"))) | 727 | if (mh_getyn_interactive (_("Do you need help"))) |
... | @@ -725,7 +732,8 @@ mh_real_install (char *name, int automode) | ... | @@ -725,7 +732,8 @@ mh_real_install (char *name, int automode) |
725 | if (!mh_getyn_interactive (_("Do you want the standard MH path \"%s\""), mhdir)) | 732 | if (!mh_getyn_interactive (_("Do you want the standard MH path \"%s\""), mhdir)) |
726 | { | 733 | { |
727 | int local; | 734 | int local; |
728 | char *p; | 735 | char *p, *buf = NULL; |
736 | size_t size = 0; | ||
729 | 737 | ||
730 | /* TRANSLATORS: This is a question and will be followed | 738 | /* TRANSLATORS: This is a question and will be followed |
731 | by question mark on output. */ | 739 | by question mark on output. */ |
... | @@ -734,16 +742,12 @@ mh_real_install (char *name, int automode) | ... | @@ -734,16 +742,12 @@ mh_real_install (char *name, int automode) |
734 | printf (_("What is the path? ")); | 742 | printf (_("What is the path? ")); |
735 | else | 743 | else |
736 | printf (_("What is the full path? ")); | 744 | printf (_("What is the full path? ")); |
737 | if (getline (&p, &n, stdin) <= 0) | 745 | if (mu_stream_getline (in, &buf, &size, NULL)) |
738 | exit (1); | ||
739 | |||
740 | n = strlen (p); | ||
741 | if (n == 0) | ||
742 | exit (1); | 746 | exit (1); |
743 | 747 | p = mu_str_stripws (buf); | |
744 | if (p[n-1] == '\n') | 748 | if (p > buf) |
745 | p[n-1] = 0; | 749 | memmove (buf, p, strlen (p) + 1); |
746 | 750 | ||
747 | free (mhdir); | 751 | free (mhdir); |
748 | if (local) | 752 | if (local) |
749 | { | 753 | { | ... | ... |
... | @@ -323,37 +323,51 @@ static int | ... | @@ -323,37 +323,51 @@ static int |
323 | _whatnow (struct mh_whatnow_env *wh, struct action_tab *tab) | 323 | _whatnow (struct mh_whatnow_env *wh, struct action_tab *tab) |
324 | { | 324 | { |
325 | int rc, status = 0; | 325 | int rc, status = 0; |
326 | mu_stream_t in; | ||
327 | char *line = NULL; | ||
328 | size_t size = 0; | ||
329 | struct mu_wordsplit ws; | ||
330 | int wsflags = MU_WRDSF_DEFFLAGS|MU_WRDSF_COMMENT; | ||
331 | |||
332 | rc = mu_stdio_stream_create (&in, MU_STDIN_FD, 0); | ||
333 | if (rc) | ||
334 | { | ||
335 | mu_error (_("cannot create input stream: %s"), mu_strerror (rc)); | ||
336 | exit (1); | ||
337 | } | ||
326 | 338 | ||
327 | do | 339 | do |
328 | { | 340 | { |
329 | char *line = NULL; | ||
330 | size_t size = 0; | ||
331 | struct mu_wordsplit ws; | ||
332 | handler_fp fun; | 341 | handler_fp fun; |
333 | 342 | ||
334 | printf ("%s ", wh->prompt); | 343 | printf ("%s ", wh->prompt); |
335 | getline (&line, &size, stdin); | 344 | fflush (stdout); |
336 | if (!line) | 345 | status = mu_stream_getline (in, &line, &size, NULL); |
337 | continue; | 346 | if (rc) |
347 | { | ||
348 | mu_error (_("cannot read input stream: %s"), mu_strerror (rc)); | ||
349 | break; | ||
350 | } | ||
338 | 351 | ||
339 | ws.ws_comment = "#"; | 352 | ws.ws_comment = "#"; |
340 | rc = mu_wordsplit (line, &ws, MU_WRDSF_DEFFLAGS|MU_WRDSF_COMMENT); | 353 | rc = mu_wordsplit (line, &ws, wsflags); |
341 | free (line); | ||
342 | if (rc) | 354 | if (rc) |
343 | { | 355 | { |
344 | mu_error (_("cannot split line `%s': %s"), line, | 356 | mu_error (_("cannot split line `%s': %s"), line, |
345 | mu_wordsplit_strerror (&ws)); | 357 | mu_wordsplit_strerror (&ws)); |
346 | break; | 358 | break; |
347 | } | 359 | } |
348 | 360 | wsflags |= MU_WRDSF_REUSE; | |
349 | fun = func (tab, ws.ws_wordv[0]); | 361 | fun = func (tab, ws.ws_wordv[0]); |
350 | if (fun) | 362 | if (fun) |
351 | rc = fun (wh, ws.ws_wordc, ws.ws_wordv, &status); | 363 | rc = fun (wh, ws.ws_wordc, ws.ws_wordv, &status); |
352 | else | 364 | else |
353 | rc = 0; | 365 | rc = 0; |
354 | mu_wordsplit_free (&ws); | ||
355 | } | 366 | } |
356 | while (rc == 0); | 367 | while (rc == 0); |
368 | if (wsflags & MU_WRDSF_REUSE) | ||
369 | mu_wordsplit_free (&ws); | ||
370 | free (line); | ||
357 | return status; | 371 | return status; |
358 | } | 372 | } |
359 | 373 | ... | ... |
-
Please register or sign in to post a comment