src/common.c: Make really sure BYTE_ORDER and BIG_ENDIAN are defined.
[collectd.git] / src / common.c
1 /**
2  * collectd - src/common.c
3  * Copyright (C) 2005-2007  Florian octo Forster
4  *
5  * This program is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License as published by the
7  * Free Software Foundation; only version 2 of the License is applicable.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
17  *
18  * Authors:
19  *   Florian octo Forster <octo at verplant.org>
20  *   Niki W. Waibel <niki.waibel@gmx.net>
21 **/
22
23 #if HAVE_CONFIG_H
24 # include "config.h"
25 #endif
26
27 #include "collectd.h"
28 #include "common.h"
29 #include "plugin.h"
30
31 #if HAVE_PTHREAD_H
32 # include <pthread.h>
33 #endif
34
35 #ifdef HAVE_MATH_H
36 # include <math.h>
37 #endif
38
39 /* for ntohl and htonl */
40 #if HAVE_ARPA_INET_H
41 # include <arpa/inet.h>
42 #endif
43
44 #ifdef HAVE_LIBKSTAT
45 extern kstat_ctl_t *kc;
46 #endif
47
48 #if !HAVE_GETPWNAM_R
49 static pthread_mutex_t getpwnam_r_lock = PTHREAD_MUTEX_INITIALIZER;
50 #endif
51
52 #if !HAVE_STRERROR_R
53 static pthread_mutex_t strerror_r_lock = PTHREAD_MUTEX_INITIALIZER;
54 #endif
55
56 void sstrncpy (char *d, const char *s, int len)
57 {
58         strncpy (d, s, len);
59         d[len - 1] = '\0';
60 }
61
62 char *sstrdup (const char *s)
63 {
64         char *r;
65
66         if (s == NULL)
67                 return (NULL);
68
69         if((r = strdup (s)) == NULL)
70         {
71                 DEBUG ("Not enough memory.");
72                 exit(3);
73         }
74
75         return (r);
76 }
77
78 /* Even though Posix requires "strerror_r" to return an "int",
79  * some systems (e.g. the GNU libc) return a "char *" _and_
80  * ignore the second argument ... -tokkee */
81 char *sstrerror (int errnum, char *buf, size_t buflen)
82 {
83         buf[0] = '\0';
84
85 #if !HAVE_STRERROR_R
86         {
87                 char *temp;
88
89                 pthread_mutex_lock (&strerror_r_lock);
90
91                 temp = strerror (errnum);
92                 strncpy (buf, temp, buflen);
93
94                 pthread_mutex_unlock (&strerror_r_lock);
95         }
96 /* #endif !HAVE_STRERROR_R */
97
98 #elif STRERROR_R_CHAR_P
99         {
100                 char *temp;
101                 temp = strerror_r (errnum, buf, buflen);
102                 if (buf[0] == '\0')
103                 {
104                         if ((temp != NULL) && (temp != buf) && (temp[0] != '\0'))
105                                 strncpy (buf, temp, buflen);
106                         else
107                                 strncpy (buf, "strerror_r did not return "
108                                                 "an error message", buflen);
109                 }
110         }
111 /* #endif STRERROR_R_CHAR_P */
112
113 #else
114         if (strerror_r (errnum, buf, buflen) != 0)
115         {
116                 snprintf (buf, buflen, "Error #%i; "
117                                 "Additionally, strerror_r failed.",
118                                 errnum);
119         }
120 #endif /* STRERROR_R_CHAR_P */
121
122         buf[buflen - 1] = '\0';
123         return (buf);
124 } /* char *sstrerror */
125
126 void *smalloc (size_t size)
127 {
128         void *r;
129
130         if ((r = malloc (size)) == NULL)
131         {
132                 DEBUG("Not enough memory.");
133                 exit(3);
134         }
135
136         return r;
137 }
138
139 #if 0
140 void sfree (void **ptr)
141 {
142         if (ptr == NULL)
143                 return;
144
145         if (*ptr != NULL)
146                 free (*ptr);
147
148         *ptr = NULL;
149 }
150 #endif
151
152 ssize_t sread (int fd, void *buf, size_t count)
153 {
154         char    *ptr;
155         size_t   nleft;
156         ssize_t  status;
157
158         ptr   = (char *) buf;
159         nleft = count;
160
161         while (nleft > 0)
162         {
163                 status = read (fd, (void *) ptr, nleft);
164
165                 if ((status < 0) && ((errno == EAGAIN) || (errno == EINTR)))
166                         continue;
167
168                 if (status < 0)
169                         return (status);
170
171                 if (status == 0)
172                 {
173                         DEBUG ("Received EOF from fd %i. "
174                                         "Closing fd and returning error.",
175                                         fd);
176                         close (fd);
177                         return (-1);
178                 }
179
180                 assert (nleft >= status);
181
182                 nleft = nleft - status;
183                 ptr   = ptr   + status;
184         }
185
186         return (0);
187 }
188
189
190 ssize_t swrite (int fd, const void *buf, size_t count)
191 {
192         const char *ptr;
193         size_t      nleft;
194         ssize_t     status;
195
196         ptr   = (const char *) buf;
197         nleft = count;
198
199         while (nleft > 0)
200         {
201                 status = write (fd, (const void *) ptr, nleft);
202
203                 if ((status < 0) && ((errno == EAGAIN) || (errno == EINTR)))
204                         continue;
205
206                 if (status < 0)
207                         return (status);
208
209                 nleft = nleft - status;
210                 ptr   = ptr   + status;
211         }
212
213         return (0);
214 }
215
216 int strsplit (char *string, char **fields, size_t size)
217 {
218         size_t i;
219         char *ptr;
220         char *saveptr;
221
222         i = 0;
223         ptr = string;
224         saveptr = NULL;
225         while ((fields[i] = strtok_r (ptr, " \t", &saveptr)) != NULL)
226         {
227                 ptr = NULL;
228                 i++;
229
230                 if (i >= size)
231                         break;
232         }
233
234         return (i);
235 }
236
237 int strjoin (char *dst, size_t dst_len,
238                 char **fields, size_t fields_num,
239                 const char *sep)
240 {
241         int field_len;
242         int sep_len;
243         int i;
244
245         memset (dst, '\0', dst_len);
246
247         if (fields_num <= 0)
248                 return (-1);
249
250         sep_len = 0;
251         if (sep != NULL)
252                 sep_len = strlen (sep);
253
254         for (i = 0; i < fields_num; i++)
255         {
256                 if ((i > 0) && (sep_len > 0))
257                 {
258                         if (dst_len <= sep_len)
259                                 return (-1);
260
261                         strncat (dst, sep, dst_len);
262                         dst_len -= sep_len;
263                 }
264
265                 field_len = strlen (fields[i]);
266
267                 if (dst_len <= field_len)
268                         return (-1);
269
270                 strncat (dst, fields[i], dst_len);
271                 dst_len -= field_len;
272         }
273
274         return (strlen (dst));
275 }
276
277 int strsubstitute (char *str, char c_from, char c_to)
278 {
279         int ret;
280
281         if (str == NULL)
282                 return (-1);
283
284         ret = 0;
285         while (*str != '\0')
286         {
287                 if (*str == c_from)
288                 {
289                         *str = c_to;
290                         ret++;
291                 }
292                 str++;
293         }
294
295         return (ret);
296 } /* int strsubstitute */
297
298 int escape_slashes (char *buf, int buf_len)
299 {
300         int i;
301
302         if (strcmp (buf, "/") == 0)
303         {
304                 if (buf_len < 5)
305                         return (-1);
306
307                 strncpy (buf, "root", buf_len);
308                 return (0);
309         }
310
311         if (buf_len <= 1)
312                 return (0);
313
314         /* Move one to the left */
315         if (buf[0] == '/')
316                 memmove (buf, buf + 1, buf_len - 1);
317
318         for (i = 0; i < buf_len - 1; i++)
319         {
320                 if (buf[i] == '\0')
321                         break;
322                 else if (buf[i] == '/')
323                         buf[i] = '_';
324         }
325         buf[i] = '\0';
326
327         return (0);
328 } /* int escape_slashes */
329
330 int timeval_sub_timespec (struct timeval *tv0, struct timeval *tv1, struct timespec *ret)
331 {
332         if ((tv0 == NULL) || (tv1 == NULL) || (ret == NULL))
333                 return (-2);
334
335         if ((tv0->tv_sec < tv1->tv_sec)
336                         || ((tv0->tv_sec == tv1->tv_sec) && (tv0->tv_usec < tv1->tv_usec)))
337                 return (-1);
338
339         ret->tv_sec  = tv0->tv_sec - tv1->tv_sec;
340         ret->tv_nsec = 1000 * ((long) (tv0->tv_usec - tv1->tv_usec));
341
342         if (ret->tv_nsec < 0)
343         {
344                 assert (ret->tv_sec > 0);
345
346                 ret->tv_nsec += 1000000000;
347                 ret->tv_sec  -= 1;
348         }
349
350         return (0);
351 }
352
353 int check_create_dir (const char *file_orig)
354 {
355         struct stat statbuf;
356
357         char  file_copy[512];
358         char  dir[512];
359         int   dir_len = 512;
360         char *fields[16];
361         int   fields_num;
362         char *ptr;
363         char *saveptr;
364         int   last_is_file = 1;
365         int   path_is_absolute = 0;
366         int   len;
367         int   i;
368
369         /*
370          * Sanity checks first
371          */
372         if (file_orig == NULL)
373                 return (-1);
374
375         if ((len = strlen (file_orig)) < 1)
376                 return (-1);
377         else if (len >= 512)
378                 return (-1);
379
380         /*
381          * If `file_orig' ends in a slash the last component is a directory,
382          * otherwise it's a file. Act accordingly..
383          */
384         if (file_orig[len - 1] == '/')
385                 last_is_file = 0;
386         if (file_orig[0] == '/')
387                 path_is_absolute = 1;
388
389         /*
390          * Create a copy for `strtok_r' to destroy
391          */
392         strncpy (file_copy, file_orig, 512);
393         file_copy[511] = '\0';
394
395         /*
396          * Break into components. This will eat up several slashes in a row and
397          * remove leading and trailing slashes..
398          */
399         ptr = file_copy;
400         saveptr = NULL;
401         fields_num = 0;
402         while ((fields[fields_num] = strtok_r (ptr, "/", &saveptr)) != NULL)
403         {
404                 ptr = NULL;
405                 fields_num++;
406
407                 if (fields_num >= 16)
408                         break;
409         }
410
411         /*
412          * For each component, do..
413          */
414         for (i = 0; i < (fields_num - last_is_file); i++)
415         {
416                 /*
417                  * Do not create directories that start with a dot. This
418                  * prevents `../../' attacks and other likely malicious
419                  * behavior.
420                  */
421                 if (fields[i][0] == '.')
422                 {
423                         ERROR ("Cowardly refusing to create a directory that begins with a `.' (dot): `%s'", file_orig);
424                         return (-2);
425                 }
426
427                 /*
428                  * Join the components together again
429                  */
430                 dir[0] = '/';
431                 if (strjoin (dir + path_is_absolute, dir_len - path_is_absolute,
432                                         fields, i + 1, "/") < 0)
433                 {
434                         ERROR ("strjoin failed: `%s', component #%i", file_orig, i);
435                         return (-1);
436                 }
437
438                 if (stat (dir, &statbuf) == -1)
439                 {
440                         if (errno == ENOENT)
441                         {
442                                 if (mkdir (dir, 0755) == -1)
443                                 {
444                                         char errbuf[1024];
445                                         ERROR ("check_create_dir: mkdir (%s): %s", dir,
446                                                         sstrerror (errno,
447                                                                 errbuf, sizeof (errbuf)));
448                                         return (-1);
449                                 }
450                         }
451                         else
452                         {
453                                 char errbuf[1024];
454                                 ERROR ("stat (%s): %s", dir,
455                                                 sstrerror (errno, errbuf,
456                                                         sizeof (errbuf)));
457                                 return (-1);
458                         }
459                 }
460                 else if (!S_ISDIR (statbuf.st_mode))
461                 {
462                         ERROR ("stat (%s): Not a directory!", dir);
463                         return (-1);
464                 }
465         }
466
467         return (0);
468 } /* check_create_dir */
469
470 #ifdef HAVE_LIBKSTAT
471 int get_kstat (kstat_t **ksp_ptr, char *module, int instance, char *name)
472 {
473         char ident[128];
474         
475         if (kc == NULL)
476                 return (-1);
477
478         snprintf (ident, 128, "%s,%i,%s", module, instance, name);
479         ident[127] = '\0';
480
481         if (*ksp_ptr == NULL)
482         {
483                 if ((*ksp_ptr = kstat_lookup (kc, module, instance, name)) == NULL)
484                 {
485                         ERROR ("Cound not find kstat %s", ident);
486                         return (-1);
487                 }
488
489                 if ((*ksp_ptr)->ks_type != KSTAT_TYPE_NAMED)
490                 {
491                         WARNING ("kstat %s has wrong type", ident);
492                         *ksp_ptr = NULL;
493                         return (-1);
494                 }
495         }
496
497 #ifdef assert
498         assert (*ksp_ptr != NULL);
499         assert ((*ksp_ptr)->ks_type == KSTAT_TYPE_NAMED);
500 #endif
501
502         if (kstat_read (kc, *ksp_ptr, NULL) == -1)
503         {
504                 WARNING ("kstat %s could not be read", ident);
505                 return (-1);
506         }
507
508         if ((*ksp_ptr)->ks_type != KSTAT_TYPE_NAMED)
509         {
510                 WARNING ("kstat %s has wrong type", ident);
511                 return (-1);
512         }
513
514         return (0);
515 }
516
517 long long get_kstat_value (kstat_t *ksp, char *name)
518 {
519         kstat_named_t *kn;
520         long long retval = -1LL;
521
522 #ifdef assert
523         assert (ksp != NULL);
524         assert (ksp->ks_type == KSTAT_TYPE_NAMED);
525 #else
526         if (ksp == NULL)
527         {
528                 fprintf (stderr, "ERROR: %s:%i: ksp == NULL\n", __FILE__, __LINE__);
529                 return (-1LL);
530         }
531         else if (ksp->ks_type != KSTAT_TYPE_NAMED)
532         {
533                 fprintf (stderr, "ERROR: %s:%i: ksp->ks_type != KSTAT_TYPE_NAMED\n", __FILE__, __LINE__);
534                 return (-1LL);
535         }
536 #endif
537
538         if ((kn = (kstat_named_t *) kstat_data_lookup (ksp, name)) == NULL)
539                 return (retval);
540
541         if (kn->data_type == KSTAT_DATA_INT32)
542                 retval = (long long) kn->value.i32;
543         else if (kn->data_type == KSTAT_DATA_UINT32)
544                 retval = (long long) kn->value.ui32;
545         else if (kn->data_type == KSTAT_DATA_INT64)
546                 retval = (long long) kn->value.i64; /* According to ANSI C99 `long long' must hold at least 64 bits */
547         else if (kn->data_type == KSTAT_DATA_UINT64)
548                 retval = (long long) kn->value.ui64; /* XXX: Might overflow! */
549         else
550                 WARNING ("get_kstat_value: Not a numeric value: %s", name);
551                  
552         return (retval);
553 }
554 #endif /* HAVE_LIBKSTAT */
555
556 unsigned long long ntohll (unsigned long long n)
557 {
558 #if BYTE_ORDER == BIG_ENDIAN
559         return (n);
560 #else
561         return (((unsigned long long) ntohl (n)) << 32) + ntohl (n >> 32);
562 #endif
563 } /* unsigned long long ntohll */
564
565 unsigned long long htonll (unsigned long long n)
566 {
567 #if BYTE_ORDER == BIG_ENDIAN
568         return (n);
569 #else
570         return (((unsigned long long) htonl (n)) << 32) + htonl (n >> 32);
571 #endif
572 } /* unsigned long long htonll */
573
574 #if FP_LAYOUT_NEED_NOTHING
575 /* Well, we need nothing.. */
576 /* #endif FP_LAYOUT_NEED_NOTHING */
577
578 #elif FP_LAYOUT_NEED_ENDIANFLIP || FP_LAYOUT_NEED_INTSWAP
579 # if FP_LAYOUT_NEED_ENDIANFLIP
580 #  define FP_CONVERT(A) ((((uint64_t)(A) & 0xff00000000000000LL) >> 56) | \
581                          (((uint64_t)(A) & 0x00ff000000000000LL) >> 40) | \
582                          (((uint64_t)(A) & 0x0000ff0000000000LL) >> 24) | \
583                          (((uint64_t)(A) & 0x000000ff00000000LL) >> 8)  | \
584                          (((uint64_t)(A) & 0x00000000ff000000LL) << 8)  | \
585                          (((uint64_t)(A) & 0x0000000000ff0000LL) << 24) | \
586                          (((uint64_t)(A) & 0x000000000000ff00LL) << 40) | \
587                          (((uint64_t)(A) & 0x00000000000000ffLL) << 56))
588 # else
589 #  define FP_CONVERT(A) ((((uint64_t)(A) & 0xffffffff00000000LL) >> 32) | \
590                          (((uint64_t)(A) & 0x00000000ffffffffLL) << 32))
591 # endif
592
593 double ntohd (double d)
594 {
595         union
596         {
597                 uint8_t  byte[8];
598                 uint64_t integer;
599                 double   floating;
600         } ret;
601
602         ret.floating = d;
603
604         /* NAN in x86 byte order */
605         if ((ret.byte[0] == 0x00) && (ret.byte[1] == 0x00)
606                         && (ret.byte[2] == 0x00) && (ret.byte[3] == 0x00)
607                         && (ret.byte[4] == 0x00) && (ret.byte[5] == 0x00)
608                         && (ret.byte[6] == 0xf8) && (ret.byte[7] == 0x7f))
609         {
610                 return (NAN);
611         }
612         else
613         {
614                 uint64_t tmp;
615
616                 tmp = ret.integer;
617                 ret.integer = FP_CONVERT (tmp);
618                 return (ret.floating);
619         }
620 } /* double ntohd */
621
622 double htond (double d)
623 {
624         union
625         {
626                 uint8_t  byte[8];
627                 uint64_t integer;
628                 double   floating;
629         } ret;
630
631         if (isnan (d))
632         {
633                 ret.byte[0] = ret.byte[1] = ret.byte[2] = ret.byte[3] = 0x00;
634                 ret.byte[4] = ret.byte[5] = 0x00;
635                 ret.byte[6] = 0xf8;
636                 ret.byte[7] = 0x7f;
637                 return (ret.floating);
638         }
639         else
640         {
641                 uint64_t tmp;
642
643                 ret.floating = d;
644                 tmp = FP_CONVERT (ret.integer);
645                 ret.integer = tmp;
646                 return (ret.floating);
647         }
648 } /* double htond */
649 #endif /* FP_LAYOUT_NEED_ENDIANFLIP || FP_LAYOUT_NEED_INTSWAP */
650
651 int format_name (char *ret, int ret_len,
652                 const char *hostname,
653                 const char *plugin, const char *plugin_instance,
654                 const char *type, const char *type_instance)
655 {
656         int  status;
657
658         assert (plugin != NULL);
659         assert (type != NULL);
660
661         if ((plugin_instance == NULL) || (strlen (plugin_instance) == 0))
662         {
663                 if ((type_instance == NULL) || (strlen (type_instance) == 0))
664                         status = snprintf (ret, ret_len, "%s/%s/%s",
665                                         hostname, plugin, type);
666                 else
667                         status = snprintf (ret, ret_len, "%s/%s/%s-%s",
668                                         hostname, plugin, type,
669                                         type_instance);
670         }
671         else
672         {
673                 if ((type_instance == NULL) || (strlen (type_instance) == 0))
674                         status = snprintf (ret, ret_len, "%s/%s-%s/%s",
675                                         hostname, plugin, plugin_instance,
676                                         type);
677                 else
678                         status = snprintf (ret, ret_len, "%s/%s-%s/%s-%s",
679                                         hostname, plugin, plugin_instance,
680                                         type, type_instance);
681         }
682
683         if ((status < 1) || (status >= ret_len))
684                 return (-1);
685         return (0);
686 } /* int format_name */
687
688 int parse_identifier (char *str, char **ret_host,
689                 char **ret_plugin, char **ret_plugin_instance,
690                 char **ret_type, char **ret_type_instance)
691 {
692         char *hostname = NULL;
693         char *plugin = NULL;
694         char *plugin_instance = NULL;
695         char *type = NULL;
696         char *type_instance = NULL;
697
698         hostname = str;
699         if (hostname == NULL)
700                 return (-1);
701
702         plugin = strchr (hostname, '/');
703         if (plugin == NULL)
704                 return (-1);
705         *plugin = '\0'; plugin++;
706
707         type = strchr (plugin, '/');
708         if (type == NULL)
709                 return (-1);
710         *type = '\0'; type++;
711
712         plugin_instance = strchr (plugin, '-');
713         if (plugin_instance != NULL)
714         {
715                 *plugin_instance = '\0';
716                 plugin_instance++;
717         }
718
719         type_instance = strchr (type, '-');
720         if (type_instance != NULL)
721         {
722                 *type_instance = '\0';
723                 type_instance++;
724         }
725
726         *ret_host = hostname;
727         *ret_plugin = plugin;
728         *ret_plugin_instance = plugin_instance;
729         *ret_type = type;
730         *ret_type_instance = type_instance;
731         return (0);
732 } /* int parse_identifier */
733
734 int parse_values (char *buffer, value_list_t *vl, const data_set_t *ds)
735 {
736         int i;
737         char *dummy;
738         char *ptr;
739         char *saveptr;
740
741         i = -1;
742         dummy = buffer;
743         saveptr = NULL;
744         while ((ptr = strtok_r (dummy, ":", &saveptr)) != NULL)
745         {
746                 dummy = NULL;
747
748                 if (i >= vl->values_len)
749                         break;
750
751                 if (i == -1)
752                 {
753                         if (strcmp ("N", ptr) == 0)
754                                 vl->time = time (NULL);
755                         else
756                                 vl->time = (time_t) atoi (ptr);
757                 }
758                 else
759                 {
760                         if (strcmp ("U", ptr) == 0)
761                                 vl->values[i].gauge = NAN;
762                         else if (ds->ds[i].type == DS_TYPE_COUNTER)
763                                 vl->values[i].counter = atoll (ptr);
764                         else if (ds->ds[i].type == DS_TYPE_GAUGE)
765                                 vl->values[i].gauge = atof (ptr);
766                 }
767
768                 i++;
769         } /* while (strtok_r) */
770
771         if ((ptr != NULL) || (i != vl->values_len))
772                 return (-1);
773         return (0);
774 } /* int parse_values */
775
776 #if !HAVE_GETPWNAM_R
777 int getpwnam_r (const char *name, struct passwd *pwbuf, char *buf,
778                 size_t buflen, struct passwd **pwbufp)
779 {
780         int status = 0;
781         struct passwd *pw;
782
783         memset (pwbuf, '\0', sizeof (struct passwd));
784
785         pthread_mutex_lock (&getpwnam_r_lock);
786
787         do
788         {
789                 pw = getpwnam (name);
790                 if (pw == NULL)
791                 {
792                         status = (errno != 0) ? errno : ENOENT;
793                         break;
794                 }
795
796 #define GETPWNAM_COPY_MEMBER(member) \
797                 if (pw->member != NULL) \
798                 { \
799                         int len = strlen (pw->member); \
800                         if (len >= buflen) \
801                         { \
802                                 status = ENOMEM; \
803                                 break; \
804                         } \
805                         sstrncpy (buf, pw->member, buflen); \
806                         pwbuf->member = buf; \
807                         buf    += (len + 1); \
808                         buflen -= (len + 1); \
809                 }
810                 GETPWNAM_COPY_MEMBER(pw_name);
811                 GETPWNAM_COPY_MEMBER(pw_passwd);
812                 GETPWNAM_COPY_MEMBER(pw_gecos);
813                 GETPWNAM_COPY_MEMBER(pw_dir);
814                 GETPWNAM_COPY_MEMBER(pw_shell);
815
816                 pwbuf->pw_uid = pw->pw_uid;
817                 pwbuf->pw_gid = pw->pw_gid;
818
819                 if (pwbufp != NULL)
820                         *pwbufp = pwbuf;
821         } while (0);
822
823         pthread_mutex_unlock (&getpwnam_r_lock);
824
825         return (status);
826 } /* int getpwnam_r */
827 #endif