1/* Copyright (c) 2000, 2011, Oracle and/or its affiliates.
2 Copyright (c) 2009-2011, Monty Program Ab
3
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; version 2 of the License.
7
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 GNU General Public License for more details.
12
13 You should have received a copy of the GNU General Public License
14 along with this program; if not, write to the Free Software
15 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
16
17#include "strings_def.h"
18#include <m_ctype.h>
19#include <my_sys.h>
20#include <my_base.h>
21#include <my_handler_errors.h>
22#include <mysql_com.h> /* For FLOATING_POINT_DECIMALS */
23
24#define MAX_ARGS 32 /* max positional args count*/
25#define MAX_PRINT_INFO 32 /* max print position count */
26#define MAX_WIDTH 65535
27
28#define LENGTH_ARG 1
29#define WIDTH_ARG 2
30#define PREZERO_ARG 4
31#define ESCAPED_ARG 8
32
33typedef struct pos_arg_info ARGS_INFO;
34typedef struct print_info PRINT_INFO;
35
36struct pos_arg_info
37{
38 char arg_type; /* argument type */
39 uint have_longlong; /* used from integer values */
40 char *str_arg; /* string value of the arg */
41 longlong longlong_arg; /* integer value of the arg */
42 double double_arg; /* double value of the arg */
43};
44
45
46struct print_info
47{
48 char arg_type; /* argument type */
49 size_t arg_idx; /* index of the positional arg */
50 size_t length; /* print width or arg index */
51 size_t width; /* print width or arg index */
52 uint flags;
53 const char *begin; /**/
54 const char *end; /**/
55};
56
57
58/**
59 Calculates print length or index of positional argument
60
61 @param fmt processed string
62 @param length print length or index of positional argument
63 @param pre_zero returns flags with PREZERO_ARG set if necessary
64
65 @retval
66 string position right after length digits
67*/
68
69static const char *get_length(const char *fmt, size_t *length, uint *pre_zero)
70{
71
72 for (; my_isdigit(&my_charset_latin1, *fmt); fmt++)
73 {
74 *length= *length * 10 + (uint)(*fmt - '0');
75 if (!*length)
76 *pre_zero|= PREZERO_ARG; /* first digit was 0 */
77 }
78 return fmt;
79}
80
81
82/*
83 Get argument for '*' parameter
84
85 @param fmt processed string
86 @param args_arr Arguments to printf
87 @param arg_count Number of arguments to printf
88 @param length returns length of argument
89 @param flag returns flags with PREZERO_ARG set if necessary
90
91 @return new fmt
92*/
93
94static const char *get_length_arg(const char *fmt, ARGS_INFO *args_arr,
95 size_t *arg_count, size_t *length, uint *flags)
96{
97 fmt= get_length(fmt+1, length, flags);
98 *arg_count= MY_MAX(*arg_count, *length);
99 (*length)--;
100 DBUG_ASSERT(*fmt == '$' && *length < MAX_ARGS);
101 args_arr[*length].arg_type= 'd';
102 args_arr[*length].have_longlong= 0;
103 return fmt+1;
104}
105
106/**
107 Calculates print width or index of positional argument
108
109 @param fmt processed string
110 @param have_longlong TRUE if longlong is required
111
112 @retval
113 string position right after modifier symbol
114*/
115
116static const char *check_longlong(const char *fmt, uint *have_longlong)
117{
118 *have_longlong= 0;
119 if (*fmt == 'l')
120 {
121 fmt++;
122 if (*fmt != 'l')
123 *have_longlong= (sizeof(long) == sizeof(longlong));
124 else
125 {
126 fmt++;
127 *have_longlong= 1;
128 }
129 }
130 else if (*fmt == 'z')
131 {
132 fmt++;
133 *have_longlong= (sizeof(size_t) == sizeof(longlong));
134 }
135 else if (*fmt == 'p')
136 *have_longlong= (sizeof(void *) == sizeof(longlong));
137 return fmt;
138}
139
140
141/**
142 Returns escaped string
143
144 @param cs string charset
145 @param to buffer where escaped string will be placed
146 @param end end of buffer
147 @param par string to escape
148 @param par_len string length
149 @param quote_char character for quoting
150
151 @retval
152 position in buffer which points on the end of escaped string
153*/
154
155static char *backtick_string(CHARSET_INFO *cs, char *to, const char *end,
156 char *par, size_t par_len, char quote_char)
157{
158 uint char_len;
159 char *start= to;
160 char *par_end= par + par_len;
161 size_t buff_length= (size_t) (end - to);
162
163 if (buff_length <= par_len)
164 goto err;
165 *start++= quote_char;
166
167 for ( ; par < par_end; par+= char_len)
168 {
169 uchar c= *(uchar *) par;
170 char_len= my_charlen_fix(cs, par, par_end);
171 if (char_len == 1 && c == (uchar) quote_char )
172 {
173 if (start + 1 >= end)
174 goto err;
175 *start++= quote_char;
176 }
177 if (start + char_len >= end)
178 goto err;
179 start= strnmov(start, par, char_len);
180 }
181
182 if (start + 1 >= end)
183 goto err;
184 *start++= quote_char;
185 return start;
186
187err:
188 *to='\0';
189 return to;
190}
191
192
193/**
194 Prints string argument
195*/
196
197static char *process_str_arg(CHARSET_INFO *cs, char *to, const char *end,
198 size_t width, char *par, uint print_type)
199{
200 int well_formed_error;
201 size_t plen, left_len= (size_t) (end - to) + 1;
202 if (!par)
203 par = (char*) "(null)";
204
205 plen= strnlen(par, width);
206 if (left_len <= plen)
207 plen = left_len - 1;
208 plen= my_well_formed_length(cs, par, par + plen, width, &well_formed_error);
209 if (print_type & ESCAPED_ARG)
210 to= backtick_string(cs, to, end, par, plen, '`');
211 else
212 to= strnmov(to,par,plen);
213 return to;
214}
215
216
217/**
218 Prints binary argument
219*/
220
221static char *process_bin_arg(char *to, char *end, size_t width, char *par)
222{
223 DBUG_ASSERT(to <= end);
224 if (to + width + 1 > end)
225 width= end - to - 1; /* sign doesn't matter */
226 memmove(to, par, width);
227 to+= width;
228 return to;
229}
230
231
232/**
233 Prints double or float argument
234*/
235
236static char *process_dbl_arg(char *to, char *end, size_t width,
237 double par, char arg_type)
238{
239 if (width == MAX_WIDTH)
240 width= FLT_DIG; /* width not set, use default */
241 else if (width >= FLOATING_POINT_DECIMALS)
242 width= FLOATING_POINT_DECIMALS - 1; /* max.precision for my_fcvt() */
243 width= MY_MIN(width, (size_t)(end-to) - 1);
244
245 if (arg_type == 'f')
246 to+= my_fcvt(par, (int)width , to, NULL);
247 else
248 to+= my_gcvt(par, MY_GCVT_ARG_DOUBLE, (int) width , to, NULL);
249 return to;
250}
251
252
253/**
254 Prints integer argument
255*/
256
257static char *process_int_arg(char *to, const char *end, size_t length,
258 longlong par, char arg_type, uint print_type)
259{
260 size_t res_length, to_length;
261 char *store_start= to, *store_end;
262 char buff[32];
263
264 if ((to_length= (size_t) (end-to)) < 16 || length)
265 store_start= buff;
266
267 if (arg_type == 'd' || arg_type == 'i')
268 store_end= longlong10_to_str(par, store_start, -10);
269 else if (arg_type == 'u')
270 store_end= longlong10_to_str(par, store_start, 10);
271 else if (arg_type == 'p')
272 {
273 store_start[0]= '0';
274 store_start[1]= 'x';
275 store_end= ll2str(par, store_start + 2, 16, 0);
276 }
277 else if (arg_type == 'o')
278 {
279 store_end= ll2str(par, store_start, 8, 0);
280 }
281 else
282 {
283 DBUG_ASSERT(arg_type == 'X' || arg_type =='x');
284 store_end= ll2str(par, store_start, 16, (arg_type == 'X'));
285 }
286
287 if ((res_length= (size_t) (store_end - store_start)) > to_length)
288 return to; /* num doesn't fit in output */
289 /* If %#d syntax was used, we have to pre-zero/pre-space the string */
290 if (store_start == buff)
291 {
292 length= MY_MIN(length, to_length);
293 if (res_length < length)
294 {
295 size_t diff= (length- res_length);
296 bfill(to, diff, (print_type & PREZERO_ARG) ? '0' : ' ');
297 if (arg_type == 'p' && print_type & PREZERO_ARG)
298 {
299 if (diff > 1)
300 to[1]= 'x';
301 else
302 store_start[0]= 'x';
303 store_start[1]= '0';
304 }
305 to+= diff;
306 }
307 bmove(to, store_start, res_length);
308 }
309 to+= res_length;
310 return to;
311}
312
313
314/**
315 Processed positional arguments.
316
317 @param cs string charset
318 @param to buffer where processed string will be place
319 @param end end of buffer
320 @param par format string
321 @param arg_index arg index of the first occurrence of positional arg
322 @param ap list of parameters
323
324 @retval
325 end of buffer where processed string is placed
326*/
327
328static char *process_args(CHARSET_INFO *cs, char *to, char *end,
329 const char* fmt, size_t arg_index, va_list ap)
330{
331 ARGS_INFO args_arr[MAX_ARGS];
332 PRINT_INFO print_arr[MAX_PRINT_INFO];
333 size_t idx= 0, arg_count= arg_index;
334
335start:
336 /* Here we are at the beginning of positional argument, right after $ */
337 arg_index--;
338 print_arr[idx].flags= 0;
339 if (*fmt == '`')
340 {
341 print_arr[idx].flags|= ESCAPED_ARG;
342 fmt++;
343 }
344 if (*fmt == '-')
345 fmt++;
346 print_arr[idx].length= print_arr[idx].width= 0;
347 /* Get print length */
348 if (*fmt == '*')
349 {
350 fmt= get_length_arg(fmt, args_arr, &arg_count, &print_arr[idx].length,
351 &print_arr[idx].flags);
352 print_arr[idx].flags|= LENGTH_ARG;
353 }
354 else
355 fmt= get_length(fmt, &print_arr[idx].length, &print_arr[idx].flags);
356
357 if (*fmt == '.')
358 {
359 uint unused_flags= 0;
360 fmt++;
361 /* Get print width */
362 if (*fmt == '*')
363 {
364 fmt= get_length_arg(fmt, args_arr, &arg_count, &print_arr[idx].width,
365 &unused_flags);
366 print_arr[idx].flags|= WIDTH_ARG;
367 }
368 else
369 fmt= get_length(fmt, &print_arr[idx].width, &unused_flags);
370 }
371 else
372 print_arr[idx].width= MAX_WIDTH;
373
374 fmt= check_longlong(fmt, &args_arr[arg_index].have_longlong);
375 args_arr[arg_index].arg_type= print_arr[idx].arg_type= *fmt;
376
377 print_arr[idx].arg_idx= arg_index;
378 print_arr[idx].begin= ++fmt;
379
380 while (*fmt && *fmt != '%')
381 fmt++;
382
383 if (!*fmt) /* End of format string */
384 {
385 uint i;
386 print_arr[idx].end= fmt;
387 /* Obtain parameters from the list */
388 for (i= 0 ; i < arg_count; i++)
389 {
390 switch (args_arr[i].arg_type) {
391 case 's':
392 case 'b':
393 args_arr[i].str_arg= va_arg(ap, char *);
394 break;
395 case 'f':
396 case 'g':
397 args_arr[i].double_arg= va_arg(ap, double);
398 break;
399 case 'd':
400 case 'i':
401 case 'u':
402 case 'x':
403 case 'X':
404 case 'o':
405 case 'p':
406 if (args_arr[i].have_longlong)
407 args_arr[i].longlong_arg= va_arg(ap,longlong);
408 else if (args_arr[i].arg_type == 'd' || args_arr[i].arg_type == 'i')
409 args_arr[i].longlong_arg= va_arg(ap, int);
410 else
411 args_arr[i].longlong_arg= va_arg(ap, uint);
412 break;
413 case 'M':
414 case 'c':
415 args_arr[i].longlong_arg= va_arg(ap, int);
416 break;
417 default:
418 DBUG_ASSERT(0);
419 }
420 }
421 /* Print result string */
422 for (i= 0; i <= idx; i++)
423 {
424 size_t width= 0, length= 0;
425 switch (print_arr[i].arg_type) {
426 case 's':
427 {
428 char *par= args_arr[print_arr[i].arg_idx].str_arg;
429 width= (print_arr[i].flags & WIDTH_ARG)
430 ? (size_t)args_arr[print_arr[i].width].longlong_arg
431 : print_arr[i].width;
432 to= process_str_arg(cs, to, end, width, par, print_arr[i].flags);
433 break;
434 }
435 case 'b':
436 {
437 char *par = args_arr[print_arr[i].arg_idx].str_arg;
438 width= (print_arr[i].flags & WIDTH_ARG)
439 ? (size_t)args_arr[print_arr[i].width].longlong_arg
440 : print_arr[i].width;
441 to= process_bin_arg(to, end, width, par);
442 break;
443 }
444 case 'c':
445 {
446 if (to == end)
447 break;
448 *to++= (char) args_arr[print_arr[i].arg_idx].longlong_arg;
449 break;
450 }
451 case 'f':
452 case 'g':
453 {
454 double d= args_arr[print_arr[i].arg_idx].double_arg;
455 width= (print_arr[i].flags & WIDTH_ARG) ?
456 (uint)args_arr[print_arr[i].width].longlong_arg : print_arr[i].width;
457 to= process_dbl_arg(to, end, width, d, print_arr[i].arg_type);
458 break;
459 }
460 case 'd':
461 case 'i':
462 case 'u':
463 case 'x':
464 case 'X':
465 case 'o':
466 case 'p':
467 {
468 /* Integer parameter */
469 longlong larg;
470 length= (print_arr[i].flags & LENGTH_ARG)
471 ? (size_t)args_arr[print_arr[i].length].longlong_arg
472 : print_arr[i].length;
473
474 larg = args_arr[print_arr[i].arg_idx].longlong_arg;
475 to= process_int_arg(to, end, length, larg, print_arr[i].arg_type,
476 print_arr[i].flags);
477 break;
478 }
479 case 'M':
480 {
481 longlong larg;
482 const char *real_end;
483
484 width= (print_arr[i].flags & WIDTH_ARG)
485 ? (size_t)args_arr[print_arr[i].width].longlong_arg
486 : print_arr[i].width;
487
488 real_end= MY_MIN(to + width, end);
489
490 larg = args_arr[print_arr[i].arg_idx].longlong_arg;
491 to= process_int_arg(to, real_end, 0, larg, 'd', print_arr[i].flags);
492 if (real_end - to >= 3)
493 {
494 char errmsg_buff[MYSYS_STRERROR_SIZE];
495 *to++= ' ';
496 *to++= '"';
497 my_strerror(errmsg_buff, sizeof(errmsg_buff), (int) larg);
498 to= process_str_arg(cs, to, real_end, width, errmsg_buff,
499 print_arr[i].flags);
500 if (real_end > to) *to++= '"';
501 }
502 break;
503 }
504 default:
505 break;
506 }
507
508 if (to == end)
509 break;
510
511 /* Copy data after the % format expression until next % */
512 length= MY_MIN(end - to , print_arr[i].end - print_arr[i].begin);
513 if (to + length < end)
514 length++;
515 to= strnmov(to, print_arr[i].begin, length);
516 }
517 DBUG_ASSERT(to <= end);
518 *to='\0'; /* End of errmessage */
519 return to;
520 }
521 else
522 {
523 uint unused_flags= 0;
524 /* Process next positional argument*/
525 DBUG_ASSERT(*fmt == '%');
526 print_arr[idx].end= fmt - 1;
527 idx++;
528 fmt++;
529 arg_index= 0;
530 fmt= get_length(fmt, &arg_index, &unused_flags);
531 DBUG_ASSERT(*fmt == '$');
532 fmt++;
533 arg_count= MY_MAX(arg_count, arg_index);
534 goto start;
535 }
536
537 return 0;
538}
539
540
541
542/**
543 Produces output string according to a format string
544
545 See the detailed documentation around my_snprintf_service_st
546
547 @param cs string charset
548 @param to buffer where processed string will be place
549 @param n size of buffer
550 @param par format string
551 @param ap list of parameters
552
553 @retval
554 length of result string
555*/
556
557size_t my_vsnprintf_ex(CHARSET_INFO *cs, char *to, size_t n,
558 const char* fmt, va_list ap)
559{
560 char *start=to, *end=to+n-1;
561 size_t length, width;
562 uint print_type, have_longlong;
563
564 for (; *fmt ; fmt++)
565 {
566 if (*fmt != '%')
567 {
568 if (to == end) /* End of buffer */
569 break;
570 *to++= *fmt; /* Copy ordinary char */
571 continue;
572 }
573 fmt++; /* skip '%' */
574
575 length= width= 0;
576 print_type= 0;
577
578 /* Read max fill size (only used with %d and %u) */
579 if (my_isdigit(&my_charset_latin1, *fmt))
580 {
581 fmt= get_length(fmt, &length, &print_type);
582 if (*fmt == '$')
583 {
584 to= process_args(cs, to, end, (fmt+1), length, ap);
585 return (size_t) (to - start);
586 }
587 }
588 else
589 {
590 if (*fmt == '`')
591 {
592 print_type|= ESCAPED_ARG;
593 fmt++;
594 }
595 if (*fmt == '-')
596 fmt++;
597 if (*fmt == '*')
598 {
599 fmt++;
600 length= va_arg(ap, int);
601 }
602 else
603 fmt= get_length(fmt, &length, &print_type);
604 }
605
606 if (*fmt == '.')
607 {
608 uint unused_flags= 0;
609 fmt++;
610 if (*fmt == '*')
611 {
612 fmt++;
613 width= va_arg(ap, int);
614 }
615 else
616 fmt= get_length(fmt, &width, &unused_flags);
617 }
618 else
619 width= MAX_WIDTH;
620
621 fmt= check_longlong(fmt, &have_longlong);
622
623 if (*fmt == 's') /* String parameter */
624 {
625 reg2 char *par= va_arg(ap, char *);
626 to= process_str_arg(cs, to, end, width, par, print_type);
627 continue;
628 }
629 else if (*fmt == 'b') /* Buffer parameter */
630 {
631 char *par = va_arg(ap, char *);
632 to= process_bin_arg(to, end, width, par);
633 continue;
634 }
635 else if (*fmt == 'f' || *fmt == 'g')
636 {
637 double d= va_arg(ap, double);
638 to= process_dbl_arg(to, end, width, d, *fmt);
639 continue;
640 }
641 else if (*fmt == 'd' || *fmt == 'i' || *fmt == 'u' || *fmt == 'x' ||
642 *fmt == 'X' || *fmt == 'p' || *fmt == 'o')
643 {
644 /* Integer parameter */
645 longlong larg;
646
647 if (have_longlong)
648 larg = va_arg(ap,longlong);
649 else if (*fmt == 'd' || *fmt == 'i')
650 larg = va_arg(ap, int);
651 else
652 larg= va_arg(ap, uint);
653
654 to= process_int_arg(to, end, length, larg, *fmt, print_type);
655 continue;
656 }
657 else if (*fmt == 'c') /* Character parameter */
658 {
659 register int larg;
660 if (to == end)
661 break;
662 larg = va_arg(ap, int);
663 *to++= (char) larg;
664 continue;
665 }
666 else if (*fmt == 'M')
667 {
668 int larg= va_arg(ap, int);
669 const char *real_end= MY_MIN(to + width, end);
670
671 to= process_int_arg(to, real_end, 0, larg, 'd', print_type);
672 if (real_end - to >= 3)
673 {
674 char errmsg_buff[MYSYS_STRERROR_SIZE];
675 *to++= ' ';
676 *to++= '"';
677 my_strerror(errmsg_buff, sizeof(errmsg_buff), (int) larg);
678 to= process_str_arg(cs, to, real_end, width, errmsg_buff, print_type);
679 if (real_end > to) *to++= '"';
680 }
681 continue;
682 }
683
684 /* We come here on '%%', unknown code or too long parameter */
685 if (to >= end)
686 break;
687 *to++='%'; /* % used as % or unknown code */
688 }
689 DBUG_ASSERT(to <= end);
690 *to='\0'; /* End of errmessage */
691 return (size_t) (to - start);
692}
693
694
695/*
696 Limited snprintf() implementations
697
698 exported to plugins as a service, see the detailed documentation
699 around my_snprintf_service_st
700*/
701
702size_t my_vsnprintf(char *to, size_t n, const char* fmt, va_list ap)
703{
704 return my_vsnprintf_ex(&my_charset_latin1, to, n, fmt, ap);
705}
706
707
708size_t my_snprintf(char* to, size_t n, const char* fmt, ...)
709{
710 size_t result;
711 va_list args;
712 va_start(args,fmt);
713 result= my_vsnprintf(to, n, fmt, args);
714 va_end(args);
715 return result;
716}
717
718
719/**
720 Writes output to the stream according to a format string.
721
722 @param stream file to write to
723 @param format string format
724 @param args list of parameters
725
726 @retval
727 number of the characters written.
728*/
729
730int my_vfprintf(FILE *stream, const char* format, va_list args)
731{
732 char cvtbuf[1024];
733 int alloc= 0;
734 char *p= cvtbuf;
735 size_t cur_len= sizeof(cvtbuf), actual;
736 int ret;
737
738 /*
739 We do not know how much buffer we need.
740 So start with a reasonably-sized stack-allocated buffer, and increase
741 it exponentially until it is big enough.
742 */
743 for (;;)
744 {
745 size_t new_len;
746 actual= my_vsnprintf(p, cur_len, format, args);
747 if (actual < cur_len - 1)
748 break;
749 /*
750 Not enough space (or just enough with nothing to spare - but we cannot
751 distinguish this case from the return value). Allocate a bigger buffer
752 and try again.
753 */
754 if (alloc)
755 my_free(p);
756 else
757 alloc= 1;
758 new_len= cur_len*2;
759 if (new_len < cur_len)
760 return 0; /* Overflow */
761 cur_len= new_len;
762 p= my_malloc(cur_len, MYF(MY_FAE));
763 if (!p)
764 return 0;
765 }
766 ret= (int) actual;
767 if (fputs(p, stream) < 0)
768 ret= -1;
769 if (alloc)
770 my_free(p);
771 return ret;
772}
773
774int my_fprintf(FILE *stream, const char* format, ...)
775{
776 int result;
777 va_list args;
778 va_start(args, format);
779 result= my_vfprintf(stream, format, args);
780 va_end(args);
781 return result;
782}
783
784
785/*
786 Return system error text for given error number
787
788 @param buf Buffer (of size MYSYS_STRERROR_SIZE)
789 @param len Length of buffer
790 @param nr Error number
791*/
792
793const char* my_strerror(char *buf, size_t len, int nr)
794{
795 char *msg= NULL;
796
797 buf[0]= '\0'; /* failsafe */
798
799 if (nr <= 0)
800 {
801 strmake(buf, (nr == 0 ?
802 "Internal error/check (Not system error)" :
803 "Internal error < 0 (Not system error)"),
804 len-1);
805 return buf;
806 }
807
808 /*
809 These (handler-) error messages are shared by perror, as required
810 by the principle of least surprise.
811 */
812 if ((nr >= HA_ERR_FIRST) && (nr <= HA_ERR_LAST))
813 {
814 msg= (char *) handler_error_messages[nr - HA_ERR_FIRST];
815 strmake(buf, msg, len - 1);
816 }
817 else
818 {
819 /*
820 On Windows, do things the Windows way. On a system that supports both
821 the GNU and the XSI variant, use whichever was configured (GNU); if
822 this choice is not advertised, use the default (POSIX/XSI). Testing
823 for __GNUC__ is not sufficient to determine whether this choice exists.
824 */
825#if defined(__WIN__)
826 strerror_s(buf, len, nr);
827#elif ((defined _POSIX_C_SOURCE && (_POSIX_C_SOURCE >= 200112L)) || \
828 (defined _XOPEN_SOURCE && (_XOPEN_SOURCE >= 600))) && \
829 ! defined _GNU_SOURCE
830 strerror_r(nr, buf, len); /* I can build with or without GNU */
831#elif defined(__GLIBC__) && defined (_GNU_SOURCE)
832 char *r= strerror_r(nr, buf, len);
833 if (r != buf) /* Want to help, GNU? */
834 strmake(buf, r, len - 1); /* Then don't. */
835#else
836 strerror_r(nr, buf, len);
837#endif
838 }
839
840 /*
841 strerror() return values are implementation-dependent, so let's
842 be pragmatic.
843 */
844 if (!buf[0])
845 strmake(buf, "unknown error", len - 1);
846 return buf;
847}
848