maint: update copyright
[gnulib.git] / lib / astrxfrm.c
1 /* Locale dependent string transformation for comparison.
2    Copyright (C) 2010-2014 Free Software Foundation, Inc.
3    Written by Bruno Haible <bruno@clisp.org>, 2010.
4
5    This program is free software: you can redistribute it and/or modify it
6    under the terms of the GNU Lesser General Public License as published
7    by the Free Software Foundation; either version 3 of the License, or
8    (at your option) any later version.
9
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    Lesser General Public License for more details.
14
15    You should have received a copy of the GNU Lesser General Public License
16    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
17
18 #include <config.h>
19
20 /* Specification.  */
21 #include "astrxfrm.h"
22
23 #include <errno.h>
24 #include <stdlib.h>
25 #include <string.h>
26
27 char *
28 astrxfrm (const char *s, char *resultbuf, size_t *lengthp)
29 {
30   char tmpbuf[4000];
31   char *result;      /* either == resultbuf or == tmpbuf or freshly allocated
32                         or NULL.  */
33   size_t allocated;  /* number of bytes allocated at result */
34   size_t length;
35
36   if (resultbuf != NULL)
37     {
38       result = resultbuf;
39       allocated = *lengthp;
40     }
41   else
42     {
43       result = NULL;
44       allocated = 0;
45     }
46
47   {
48     size_t l = strlen (s);
49     size_t k;
50
51     /* A call to strxfrm costs about 20 times more than a call to strdup of
52        the result.  Therefore it is worth to try to avoid calling strxfrm
53        more than once on a given string, by making enough room before calling
54        strxfrm.  The size of the strxfrm result, k, is likely to be between
55        l and 3 * l.  */
56     if (3 * l + 1 > allocated)
57       {
58         /* Grow the result buffer.  */
59         if (3 * l + 1 <= sizeof (tmpbuf))
60           {
61             result = tmpbuf;
62             allocated = sizeof (tmpbuf);
63           }
64         else
65           {
66             size_t new_allocated;
67             char *new_result;
68
69             new_allocated = 3 * l + 1;
70             if (new_allocated < 2 * allocated)
71               new_allocated = 2 * allocated;
72             new_result = (char *) malloc (new_allocated);
73             if (new_result != NULL)
74               {
75                 allocated = new_allocated;
76                 result = new_result;
77               }
78           }
79       }
80
81     errno = 0;
82     k = strxfrm (result, s, allocated);
83     if (errno != 0)
84       goto fail;
85     if (k >= allocated)
86       {
87         /* Grow the result buffer.  */
88         if (result != resultbuf && result != tmpbuf)
89           free (result);
90         if (k + 1 <= sizeof (tmpbuf))
91           {
92             result = tmpbuf;
93             allocated = sizeof (tmpbuf);
94           }
95         else
96           {
97             size_t new_allocated;
98             char *new_result;
99
100             new_allocated = k + 1;
101             new_result = (char *) malloc (new_allocated);
102             if (new_result == NULL)
103               goto out_of_memory;
104             allocated = new_allocated;
105             result = new_result;
106           }
107         /* Here k < allocated.  */
108
109         /* Try again.  */
110         errno = 0;
111         if (strxfrm (result, s, allocated) != k)
112           /* strxfrm() is not producing reproducible results.  */
113           abort ();
114         if (errno != 0)
115           goto fail;
116       }
117
118     /* Verify that strxfrm() has NUL-terminated the result.  */
119     if (result[k] != '\0')
120       abort ();
121     length = k + 1;
122   }
123
124   /* Here length > 0.  */
125
126   if (result == tmpbuf)
127     {
128       if (resultbuf != NULL && length <= *lengthp)
129         {
130           memcpy (resultbuf, result, length);
131           result = resultbuf;
132         }
133       else
134         {
135           char *memory = (char *) malloc (length);
136
137           if (memory == NULL)
138             goto out_of_memory;
139           memcpy (memory, result, length);
140           result = memory;
141         }
142     }
143   else
144     {
145       /* Shrink the allocated memory if possible.  */
146       if (result != resultbuf && length < allocated)
147         {
148           if (length <= *lengthp)
149             {
150               memcpy (resultbuf, result, length);
151               free (result);
152               result = resultbuf;
153             }
154           else
155             {
156               char *memory = (char *) realloc (result, length);
157               if (memory != NULL)
158                 {
159                   memcpy (memory, result, length);
160                   result = memory;
161                 }
162             }
163         }
164     }
165
166   *lengthp = length;
167   return result;
168
169  fail:
170   {
171     int saved_errno = errno;
172     if (result != resultbuf && result != tmpbuf)
173       free (result);
174     errno = saved_errno;
175     return NULL;
176   }
177
178  out_of_memory:
179   errno = ENOMEM;
180   return NULL;
181 }