1 /* GNU Mailutils -- a suite of utilities for electronic mail
2    Copyright (C) 2020-2021 Free Software Foundation, Inc.
3 
4    This library is free software; you can redistribute it and/or modify
5    it under the terms of the GNU Lesser General Public License as published by
6    the Free Software Foundation; either version 3, or (at your option)
7    any later version.
8 
9    This library is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU Lesser General Public License for more details.
13 
14    You should have received a copy of the GNU Lesser General Public License
15    along with GNU Mailutils.  If not, see <http://www.gnu.org/licenses/>. */
16 
17 /* Implementation of temp_stream.
18 
19    Temp_stream combines the functionality of memory and temp_file streams.
20    Streams of that type function as memory streams until their size reaches
21    a preconfigured threshold value.  Once it is reached, the stream storage
22    is automatically converted to temporary file, and all data written so far
23    are transferred to the new storage.  If the temporary file cannot be
24    created, the stream continues to operate in memory-based mode.
25 
26    The stream is created using the following call:
27 
28       int mu_temp_stream_create (mu_stream_t *pstream, size_t threshold)
29 
30    If threshold is 0, the threshold value is first looked up in the
31    environment variable MU_TEMP_FILE_THRESHOLD (which should contain
32    a string suitable for input to mu_strtosize).  If it is not set or
33    unparsable, the mu_temp_file_threshold_size global is used instead.
34 
35    Two special values of MU_TEMP_FILE_THRESHOLD alter the behavior of
36    mu_temp_stream_create:
37 
38      "0"    -  the function creates a pure tempfile-based stream
39                (equivalent to mu_temp_file_stream_create).
40      "inf"  -  the function returns a pure memory-based stream
41                (equivalent to mu_memory_stream_create).
42  */
43 #include <stdlib.h>
44 #include <errno.h>
45 #include <mailutils/stream.h>
46 #include <mailutils/sys/temp_stream.h>
47 #include <mailutils/cstr.h>
48 #include <mailutils/diag.h>
49 #include <mailutils/debug.h>
50 #include <mailutils/errno.h>
51 
52 static int
temp_stream_write(struct _mu_stream * str,const char * buf,size_t size,size_t * ret_size)53 temp_stream_write (struct _mu_stream *str, const char *buf, size_t size,
54 		   size_t *ret_size)
55 {
56   struct _mu_temp_stream *ts = (struct _mu_temp_stream *)str;
57 
58   if (ts->s.mem.offset + size > ts->max_size)
59     {
60       int rc;
61       mu_stream_t temp_file;
62       rc = mu_temp_file_stream_create (&temp_file, NULL, 0);
63       if (rc == 0)
64 	{
65 	  if (ts->s.mem.ptr == NULL)
66 	    rc = 0;
67 	  else
68 	    {
69 	      size_t s = 0;
70 
71 	      while (s < ts->s.mem.size)
72 		{
73 		  size_t n = ts->s.mem.size - s;
74 		  size_t wrn;
75 
76 		  rc = temp_file->write (temp_file, ts->s.mem.ptr + s, n, &wrn);
77 		  if (rc)
78 		    break;
79 		  s += wrn;
80 		}
81 
82 	      if (rc == 0)
83 		{
84 		  mu_off_t res;
85 		  rc = temp_file->seek (temp_file, str->offset, &res);
86 		}
87 	    }
88 
89 	  if (rc == 0)
90 	    {
91 	      /* Preserve the necessary stream data */
92 	      temp_file->ref_count = str->ref_count;
93 	      if (temp_file->buftype != mu_buffer_none)
94 		free (temp_file->buffer);
95 	      temp_file->buftype = str->buftype;
96 	      temp_file->buffer = str->buffer;
97 	      temp_file->level = str->level;
98 	      temp_file->pos = str->pos;
99 
100 	      temp_file->statmask = str->statmask;
101 	      temp_file->statbuf = str->statbuf;
102 
103 	      /* Deinitialize previous stream backend */
104 	      ts->s.stream.done (str);
105 
106 	      /* Replace it with the newly created one. */
107 	      memcpy (&ts->s.file, temp_file, sizeof (ts->s.file));
108 
109 	      /* Reclaim the memory used by the stream object */
110 	      free (temp_file);
111 
112 	      /* Write data to the new stream. */
113 	      return ts->s.stream.write (str, buf, size, ret_size);
114 	    }
115 	}
116       else
117 	{
118 	  mu_diag_funcall (MU_DIAG_WARNING, "mu_temp_file_stream_create",
119 			   NULL, rc);
120 	  /* Switch to plain memory stream mode */
121 	  ts->s.stream.write = ts->saved_write;
122 	}
123     }
124 
125   return ts->saved_write (str, buf, size, ret_size);
126 }
127 
128 size_t mu_temp_file_threshold_size = 4096;
129 
130 int
mu_temp_stream_create(mu_stream_t * pstream,size_t max_size)131 mu_temp_stream_create (mu_stream_t *pstream, size_t max_size)
132 {
133   int rc;
134   mu_stream_t stream;
135   struct _mu_temp_stream *str;
136 
137   if (max_size == 0)
138     {
139       char *s;
140       if ((s = getenv ("MU_TEMP_FILE_THRESHOLD")) != NULL)
141 	{
142 	  char *p;
143 
144 	  if (strcmp(p, "inf") == 0)
145 	    return mu_memory_stream_create (&stream, MU_STREAM_RDWR);
146 
147 	  rc = mu_strtosize (s, &p, &max_size);
148 	  if (rc == 0)
149 	    {
150 	      if (max_size == 0)
151 		return mu_temp_file_stream_create (pstream, NULL, 0);
152 	    }
153 	  else
154 	    mu_debug (MU_DEBCAT_STREAM, MU_DEBUG_ERROR,
155 		      ("failed parsing MU_TEMP_FILE_THRESHOLD value: %s near %s",
156 		       mu_strerror (rc), p));
157 	}
158       if (max_size == 0)
159 	max_size = mu_temp_file_threshold_size;
160     }
161 
162   rc = mu_memory_stream_create (&stream, MU_STREAM_RDWR);
163   if (rc)
164     return rc;
165 
166   str = realloc (stream, sizeof (*str));
167   if (!str)
168     {
169       mu_stream_destroy (&stream);
170       return ENOMEM;
171     }
172 
173   str->max_size = max_size;
174   str->saved_write = str->s.stream.write;
175   str->s.stream.write = temp_stream_write;
176 
177   *pstream = (mu_stream_t) str;
178   return rc;
179 }
180 
181