1 | /* Copyright (c) 2000, 2014, Oracle and/or its affiliates. All rights reserved. |
2 | |
3 | This program is free software; you can redistribute it and/or modify |
4 | it under the terms of the GNU General Public License as published by |
5 | the Free Software Foundation; version 2 of the License. |
6 | |
7 | This program is distributed in the hope that it will be useful, |
8 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
9 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
10 | GNU General Public License for more details. |
11 | |
12 | You should have received a copy of the GNU General Public License |
13 | along with this program; if not, write to the Free Software |
14 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ |
15 | |
16 | /* |
17 | More functions to be used with IO_CACHE files |
18 | */ |
19 | |
20 | #include "mysys_priv.h" |
21 | #include <m_string.h> |
22 | #include <stdarg.h> |
23 | #include <m_ctype.h> |
24 | |
25 | /* |
26 | Copy contents of an IO_CACHE to a file. |
27 | |
28 | SYNOPSIS |
29 | my_b_copy_to_file() |
30 | cache IO_CACHE to copy from |
31 | file File to copy to |
32 | |
33 | DESCRIPTION |
34 | Copy the contents of the cache to the file. The cache will be |
35 | re-inited to a read cache and will read from the beginning of the |
36 | cache. |
37 | |
38 | If a failure to write fully occurs, the cache is only copied |
39 | partially. |
40 | |
41 | TODO |
42 | Make this function solid by handling partial reads from the cache |
43 | in a correct manner: it should be atomic. |
44 | |
45 | RETURN VALUE |
46 | 0 All OK |
47 | 1 An error occurred |
48 | */ |
49 | int |
50 | my_b_copy_to_file(IO_CACHE *cache, FILE *file) |
51 | { |
52 | size_t bytes_in_cache; |
53 | DBUG_ENTER("my_b_copy_to_file" ); |
54 | |
55 | /* Reinit the cache to read from the beginning of the cache */ |
56 | if (reinit_io_cache(cache, READ_CACHE, 0L, FALSE, FALSE)) |
57 | DBUG_RETURN(1); |
58 | bytes_in_cache= my_b_bytes_in_cache(cache); |
59 | do |
60 | { |
61 | if (my_fwrite(file, cache->read_pos, bytes_in_cache, |
62 | MYF(MY_WME | MY_NABP)) == (size_t) -1) |
63 | DBUG_RETURN(1); |
64 | } while ((bytes_in_cache= my_b_fill(cache))); |
65 | if(cache->error == -1) |
66 | DBUG_RETURN(1); |
67 | DBUG_RETURN(0); |
68 | } |
69 | |
70 | |
71 | my_off_t my_b_append_tell(IO_CACHE* info) |
72 | { |
73 | /* |
74 | Sometimes we want to make sure that the variable is not put into |
75 | a register in debugging mode so we can see its value in the core |
76 | */ |
77 | #ifndef DBUG_OFF |
78 | # define dbug_volatile volatile |
79 | #else |
80 | # define dbug_volatile |
81 | #endif |
82 | |
83 | /* |
84 | Prevent optimizer from putting res in a register when debugging |
85 | we need this to be able to see the value of res when the assert fails |
86 | */ |
87 | dbug_volatile my_off_t res; |
88 | |
89 | /* |
90 | We need to lock the append buffer mutex to keep flush_io_cache() |
91 | from messing with the variables that we need in order to provide the |
92 | answer to the question. |
93 | */ |
94 | mysql_mutex_lock(&info->append_buffer_lock); |
95 | |
96 | #ifndef DBUG_OFF |
97 | /* |
98 | Make sure EOF is where we think it is. Note that we cannot just use |
99 | my_tell() because we have a reader thread that could have left the |
100 | file offset in a non-EOF location |
101 | */ |
102 | { |
103 | volatile my_off_t save_pos; |
104 | save_pos= mysql_file_tell(info->file, MYF(0)); |
105 | mysql_file_seek(info->file, 0, MY_SEEK_END, MYF(0)); |
106 | /* |
107 | Save the value of my_tell in res so we can see it when studying coredump |
108 | */ |
109 | DBUG_ASSERT(info->end_of_file - (info->append_read_pos-info->write_buffer) |
110 | == (res= mysql_file_tell(info->file, MYF(0)))); |
111 | mysql_file_seek(info->file, save_pos, MY_SEEK_SET, MYF(0)); |
112 | } |
113 | #endif |
114 | res = info->end_of_file + (info->write_pos-info->append_read_pos); |
115 | mysql_mutex_unlock(&info->append_buffer_lock); |
116 | return res; |
117 | } |
118 | |
119 | my_off_t my_b_safe_tell(IO_CACHE *info) |
120 | { |
121 | if (unlikely(info->type == SEQ_READ_APPEND)) |
122 | return my_b_append_tell(info); |
123 | return my_b_tell(info); |
124 | } |
125 | |
126 | /* |
127 | Make next read happen at the given position |
128 | For write cache, make next write happen at the given position |
129 | */ |
130 | |
131 | void my_b_seek(IO_CACHE *info,my_off_t pos) |
132 | { |
133 | my_off_t offset; |
134 | DBUG_ENTER("my_b_seek" ); |
135 | DBUG_PRINT("enter" ,("pos: %lu" , (ulong) pos)); |
136 | |
137 | /* |
138 | TODO: |
139 | Verify that it is OK to do seek in the non-append |
140 | area in SEQ_READ_APPEND cache |
141 | a) see if this always works |
142 | b) see if there is a better way to make it work |
143 | */ |
144 | if (info->type == SEQ_READ_APPEND) |
145 | (void) flush_io_cache(info); |
146 | |
147 | offset=(pos - info->pos_in_file); |
148 | |
149 | if (info->type == READ_CACHE || info->type == SEQ_READ_APPEND) |
150 | { |
151 | /* TODO: explain why this works if pos < info->pos_in_file */ |
152 | if ((ulonglong) offset < (ulonglong) (info->read_end - info->buffer)) |
153 | { |
154 | /* The read is in the current buffer; Reuse it */ |
155 | info->read_pos = info->buffer + offset; |
156 | DBUG_VOID_RETURN; |
157 | } |
158 | else |
159 | { |
160 | /* Force a new read on next my_b_read */ |
161 | info->read_pos=info->read_end=info->buffer; |
162 | } |
163 | } |
164 | else if (info->type == WRITE_CACHE) |
165 | { |
166 | /* If write is in current buffer, reuse it */ |
167 | if ((ulonglong) offset < |
168 | (ulonglong) (info->write_end - info->write_buffer)) |
169 | { |
170 | info->write_pos = info->write_buffer + offset; |
171 | DBUG_VOID_RETURN; |
172 | } |
173 | (void) flush_io_cache(info); |
174 | /* Correct buffer end so that we write in increments of IO_SIZE */ |
175 | info->write_end=(info->write_buffer+info->buffer_length- |
176 | (pos & (IO_SIZE-1))); |
177 | } |
178 | info->pos_in_file=pos; |
179 | info->seek_not_done=1; |
180 | DBUG_VOID_RETURN; |
181 | } |
182 | |
183 | int my_b_pread(IO_CACHE *info, uchar *Buffer, size_t Count, my_off_t pos) |
184 | { |
185 | if (info->myflags & MY_ENCRYPT) |
186 | { |
187 | my_b_seek(info, pos); |
188 | return my_b_read(info, Buffer, Count); |
189 | } |
190 | |
191 | /* backward compatibility behavior. XXX remove it? */ |
192 | if (mysql_file_pread(info->file, Buffer, Count, pos, info->myflags | MY_NABP)) |
193 | return info->error= -1; |
194 | return 0; |
195 | } |
196 | |
197 | /* |
198 | Read a string ended by '\n' into a buffer of 'max_length' size. |
199 | Returns number of characters read, 0 on error. |
200 | last byte is set to '\0' |
201 | If buffer is full then to[max_length-1] will be set to \0. |
202 | */ |
203 | |
204 | size_t my_b_gets(IO_CACHE *info, char *to, size_t max_length) |
205 | { |
206 | char *start = to; |
207 | size_t length; |
208 | max_length--; /* Save place for end \0 */ |
209 | |
210 | /* Calculate number of characters in buffer */ |
211 | if (!(length= my_b_bytes_in_cache(info)) && |
212 | !(length= my_b_fill(info))) |
213 | return 0; |
214 | |
215 | for (;;) |
216 | { |
217 | uchar *pos, *end; |
218 | if (length > max_length) |
219 | length=max_length; |
220 | for (pos=info->read_pos,end=pos+length ; pos < end ;) |
221 | { |
222 | if ((*to++ = *pos++) == '\n') |
223 | { |
224 | info->read_pos=pos; |
225 | *to='\0'; |
226 | return (size_t) (to-start); |
227 | } |
228 | } |
229 | if (!(max_length-=length)) |
230 | { |
231 | /* Found enough charcters; Return found string */ |
232 | info->read_pos=pos; |
233 | *to='\0'; |
234 | return (size_t) (to-start); |
235 | } |
236 | if (!(length=my_b_fill(info))) |
237 | return 0; |
238 | } |
239 | } |
240 | |
241 | |
242 | my_off_t my_b_filelength(IO_CACHE *info) |
243 | { |
244 | if (info->type == WRITE_CACHE) |
245 | return my_b_tell(info); |
246 | |
247 | info->seek_not_done= 1; |
248 | return mysql_file_seek(info->file, 0, MY_SEEK_END, MYF(0)); |
249 | } |
250 | |
251 | |
252 | my_bool |
253 | my_b_write_backtick_quote(IO_CACHE *info, const char *str, size_t len) |
254 | { |
255 | const uchar *start; |
256 | const uchar *p= (const uchar *)str; |
257 | const uchar *end= p + len; |
258 | size_t count; |
259 | |
260 | if (my_b_write(info, (uchar *)"`" , 1)) |
261 | return 1; |
262 | for (;;) |
263 | { |
264 | start= p; |
265 | while (p < end && *p != '`') |
266 | ++p; |
267 | count= p - start; |
268 | if (count && my_b_write(info, start, count)) |
269 | return 1; |
270 | if (p >= end) |
271 | break; |
272 | if (my_b_write(info, (uchar *)"``" , 2)) |
273 | return 1; |
274 | ++p; |
275 | } |
276 | return (my_b_write(info, (uchar *)"`" , 1)); |
277 | } |
278 | |
279 | /* |
280 | Simple printf version. Supports '%s', '%d', '%u', "%ld" and "%lu" |
281 | Used for logging in MariaDB |
282 | |
283 | @return 0 ok |
284 | 1 error |
285 | */ |
286 | |
287 | my_bool my_b_printf(IO_CACHE *info, const char* fmt, ...) |
288 | { |
289 | size_t result; |
290 | va_list args; |
291 | va_start(args,fmt); |
292 | result=my_b_vprintf(info, fmt, args); |
293 | va_end(args); |
294 | return result == (size_t) -1; |
295 | } |
296 | |
297 | |
298 | size_t my_b_vprintf(IO_CACHE *info, const char* fmt, va_list args) |
299 | { |
300 | size_t out_length= 0; |
301 | uint minimum_width; /* as yet unimplemented */ |
302 | uint minimum_width_sign; |
303 | uint precision; /* as yet unimplemented for anything but %b */ |
304 | my_bool is_zero_padded; |
305 | my_bool backtick_quoting; |
306 | |
307 | /* |
308 | Store the location of the beginning of a format directive, for the |
309 | case where we learn we shouldn't have been parsing a format string |
310 | at all, and we don't want to lose the flag/precision/width/size |
311 | information. |
312 | */ |
313 | const char* backtrack; |
314 | |
315 | for (; *fmt != '\0'; fmt++) |
316 | { |
317 | /* Copy everything until '%' or end of string */ |
318 | const char *start=fmt; |
319 | size_t length; |
320 | |
321 | for (; (*fmt != '\0') && (*fmt != '%'); fmt++) ; |
322 | |
323 | length= (size_t) (fmt - start); |
324 | out_length+=length; |
325 | if (my_b_write(info, (const uchar*) start, length)) |
326 | goto err; |
327 | |
328 | if (*fmt == '\0') /* End of format */ |
329 | return out_length; |
330 | |
331 | /* |
332 | By this point, *fmt must be a percent; Keep track of this location and |
333 | skip over the percent character. |
334 | */ |
335 | DBUG_ASSERT(*fmt == '%'); |
336 | backtrack= fmt; |
337 | fmt++; |
338 | |
339 | is_zero_padded= FALSE; |
340 | backtick_quoting= FALSE; |
341 | minimum_width_sign= 1; |
342 | minimum_width= 0; |
343 | precision= 0; |
344 | /* Skip if max size is used (to be compatible with printf) */ |
345 | |
346 | process_flags: |
347 | switch (*fmt) |
348 | { |
349 | case '-': |
350 | minimum_width_sign= -1; fmt++; goto process_flags; |
351 | case '0': |
352 | is_zero_padded= TRUE; fmt++; goto process_flags; |
353 | case '`': |
354 | backtick_quoting= TRUE; fmt++; goto process_flags; |
355 | case '#': |
356 | /** @todo Implement "#" conversion flag. */ fmt++; goto process_flags; |
357 | case ' ': |
358 | /** @todo Implement " " conversion flag. */ fmt++; goto process_flags; |
359 | case '+': |
360 | /** @todo Implement "+" conversion flag. */ fmt++; goto process_flags; |
361 | } |
362 | |
363 | if (*fmt == '*') |
364 | { |
365 | precision= (int) va_arg(args, int); |
366 | fmt++; |
367 | } |
368 | else |
369 | { |
370 | while (my_isdigit(&my_charset_latin1, *fmt)) { |
371 | minimum_width=(minimum_width * 10) + (*fmt - '0'); |
372 | fmt++; |
373 | } |
374 | } |
375 | minimum_width*= minimum_width_sign; |
376 | |
377 | if (*fmt == '.') |
378 | { |
379 | fmt++; |
380 | if (*fmt == '*') { |
381 | precision= (int) va_arg(args, int); |
382 | fmt++; |
383 | } |
384 | else |
385 | { |
386 | while (my_isdigit(&my_charset_latin1, *fmt)) { |
387 | precision=(precision * 10) + (*fmt - '0'); |
388 | fmt++; |
389 | } |
390 | } |
391 | } |
392 | |
393 | if (*fmt == 's') /* String parameter */ |
394 | { |
395 | reg2 char *par = va_arg(args, char *); |
396 | size_t length2 = strlen(par); |
397 | /* TODO: implement precision */ |
398 | if (backtick_quoting) |
399 | { |
400 | size_t total= my_b_write_backtick_quote(info, par, length2); |
401 | if (total == (size_t)-1) |
402 | goto err; |
403 | out_length+= total; |
404 | } |
405 | else |
406 | { |
407 | out_length+= length2; |
408 | if (my_b_write(info, (uchar*) par, length2)) |
409 | goto err; |
410 | } |
411 | } |
412 | else if (*fmt == 'c') /* char type parameter */ |
413 | { |
414 | char par[2]; |
415 | par[0] = va_arg(args, int); |
416 | if (my_b_write(info, (uchar*) par, 1)) |
417 | goto err; |
418 | } |
419 | else if (*fmt == 'b') /* Sized buffer parameter, only precision makes sense */ |
420 | { |
421 | char *par = va_arg(args, char *); |
422 | out_length+= precision; |
423 | if (my_b_write(info, (uchar*) par, precision)) |
424 | goto err; |
425 | } |
426 | else if (*fmt == 'd' || *fmt == 'u') /* Integer parameter */ |
427 | { |
428 | register int iarg; |
429 | size_t length2; |
430 | char buff[32]; |
431 | |
432 | iarg = va_arg(args, int); |
433 | if (*fmt == 'd') |
434 | length2= (size_t) (int10_to_str((long) iarg,buff, -10) - buff); |
435 | else |
436 | length2= (uint) (int10_to_str((long) (uint) iarg,buff,10)- buff); |
437 | |
438 | /* minimum width padding */ |
439 | if (minimum_width > length2) |
440 | { |
441 | uchar *buffz; |
442 | |
443 | buffz= (uchar*) my_alloca(minimum_width - length2); |
444 | if (is_zero_padded) |
445 | memset(buffz, '0', minimum_width - length2); |
446 | else |
447 | memset(buffz, ' ', minimum_width - length2); |
448 | if (my_b_write(info, buffz, minimum_width - length2)) |
449 | { |
450 | my_afree(buffz); |
451 | goto err; |
452 | } |
453 | my_afree(buffz); |
454 | } |
455 | |
456 | out_length+= length2; |
457 | if (my_b_write(info, (uchar*) buff, length2)) |
458 | goto err; |
459 | } |
460 | else if ((*fmt == 'l' && (fmt[1] == 'd' || fmt[1] == 'u'))) |
461 | /* long parameter */ |
462 | { |
463 | register long iarg; |
464 | size_t length2; |
465 | char buff[32]; |
466 | |
467 | iarg = va_arg(args, long); |
468 | if (*++fmt == 'd') |
469 | length2= (size_t) (int10_to_str(iarg,buff, -10) - buff); |
470 | else |
471 | length2= (size_t) (int10_to_str(iarg,buff,10)- buff); |
472 | out_length+= length2; |
473 | if (my_b_write(info, (uchar*) buff, length2)) |
474 | goto err; |
475 | } |
476 | else |
477 | { |
478 | /* %% or unknown code */ |
479 | if (my_b_write(info, (uchar*) backtrack, (size_t) (fmt-backtrack))) |
480 | goto err; |
481 | out_length+= fmt-backtrack; |
482 | } |
483 | } |
484 | return out_length; |
485 | |
486 | err: |
487 | return (size_t) -1; |
488 | } |
489 | |
490 | |