| 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 | |