xref: /freebsd/lib/libc/stdtime/timelocal.c (revision 4cf49a43559ed9fdad601bdcccd2c55963008675)
1 /*-
2  * Copyright (c) 1997 FreeBSD Inc.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  *
26  * $FreeBSD$
27  */
28 
29 #include <sys/types.h>
30 #include <sys/stat.h>
31 #include <sys/syslimits.h>
32 #include <fcntl.h>
33 #include <locale.h>
34 #include <stddef.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include "setlocale.h"
38 #include "timelocal.h"
39 
40 static int split_lines(char *, const char *);
41 static void set_from_buf(const char *, int);
42 
43 struct lc_time_T _time_localebuf;
44 int _time_using_locale;
45 
46 #define	LCTIME_SIZE_FULL (sizeof(struct lc_time_T) / sizeof(char *))
47 #define	LCTIME_SIZE_1 \
48 	(offsetof(struct lc_time_T, alt_month[0]) / sizeof(char *))
49 
50 const struct lc_time_T	_C_time_locale = {
51 	{
52 		"Jan", "Feb", "Mar", "Apr", "May", "Jun",
53 		"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
54 	}, {
55 		"January", "February", "March", "April", "May", "June",
56 		"July", "August", "September", "October", "November", "December"
57 	}, {
58 		"Sun", "Mon", "Tue", "Wed",
59 		"Thu", "Fri", "Sat"
60 	}, {
61 		"Sunday", "Monday", "Tuesday", "Wednesday",
62 		"Thursday", "Friday", "Saturday"
63 	},
64 
65 	/* X_fmt */
66 	"%H:%M:%S",
67 
68 	/*
69 	** x_fmt
70 	** Since the C language standard calls for
71 	** "date, using locale's date format," anything goes.
72 	** Using just numbers (as here) makes Quakers happier;
73 	** it's also compatible with SVR4.
74 	*/
75 	"%m/%d/%y",
76 
77 	/*
78 	** c_fmt (ctime-compatible)
79 	** Note that
80 	**	"%a %b %d %H:%M:%S %Y"
81 	** is used by Solaris 2.3.
82 	*/
83 	"%a %b %e %X %Y",
84 
85 	/* am */
86 	"AM",
87 
88 	/* pm */
89 	"PM",
90 
91 	/* date_fmt */
92 	"%a %b %e %X %Z %Y",
93 
94 	{
95 		"January", "February", "March", "April", "May", "June",
96 		"July", "August", "September", "October", "November", "December"
97 	}
98 };
99 
100 
101 int
102 __time_load_locale(const char *name)
103 {
104 	static char *		locale_buf;
105 	static char		locale_buf_C[] = "C";
106 	static int		num_lines;
107 
108 	int			fd;
109 	char *			lbuf;
110 	char *			p;
111 	const char *		plim;
112 	char                    filename[PATH_MAX];
113 	struct stat		st;
114 	size_t			namesize;
115 	size_t			bufsize;
116 	int                     save_using_locale;
117 
118 	save_using_locale = _time_using_locale;
119 	_time_using_locale = 0;
120 
121 	if (name == NULL)
122 		goto no_locale;
123 
124 	if (!strcmp(name, "C") || !strcmp(name, "POSIX"))
125 		return 0;
126 
127 	/*
128 	** If the locale name is the same as our cache, use the cache.
129 	*/
130 	lbuf = locale_buf;
131 	if (lbuf != NULL && strcmp(name, lbuf) == 0) {
132 		set_from_buf(lbuf, num_lines);
133 		_time_using_locale = 1;
134 		return 0;
135 	}
136 	/*
137 	** Slurp the locale file into the cache.
138 	*/
139 	namesize = strlen(name) + 1;
140 
141 	if (!_PathLocale)
142 		goto no_locale;
143 	/* Range checking not needed, 'name' size is limited */
144 	strcpy(filename, _PathLocale);
145 	strcat(filename, "/");
146 	strcat(filename, name);
147 	strcat(filename, "/LC_TIME");
148 	fd = open(filename, O_RDONLY);
149 	if (fd < 0)
150 		goto no_locale;
151 	if (fstat(fd, &st) != 0)
152 		goto bad_locale;
153 	if (st.st_size <= 0)
154 		goto bad_locale;
155 	bufsize = namesize + st.st_size;
156 	locale_buf = NULL;
157 	lbuf = (lbuf == NULL || lbuf == locale_buf_C) ?
158 		malloc(bufsize) : reallocf(lbuf, bufsize);
159 	if (lbuf == NULL)
160 		goto bad_locale;
161 	(void) strcpy(lbuf, name);
162 	p = lbuf + namesize;
163 	plim = p + st.st_size;
164 	if (read(fd, p, (size_t) st.st_size) != st.st_size)
165 		goto bad_lbuf;
166 	if (close(fd) != 0)
167 		goto bad_lbuf;
168 	/*
169 	** Parse the locale file into localebuf.
170 	*/
171 	if (plim[-1] != '\n')
172 		goto bad_lbuf;
173 	num_lines = split_lines(p, plim);
174 	if (num_lines >= LCTIME_SIZE_FULL)
175 		num_lines = LCTIME_SIZE_FULL;
176 	else if (num_lines >= LCTIME_SIZE_1)
177 		num_lines = LCTIME_SIZE_1;
178 	else
179 		goto reset_locale;
180 	set_from_buf(lbuf, num_lines);
181 	/*
182 	** Record the successful parse in the cache.
183 	*/
184 	locale_buf = lbuf;
185 
186 	_time_using_locale = 1;
187 	return 0;
188 
189 reset_locale:
190 	/*
191 	 * XXX - This may not be the correct thing to do in this case.
192 	 * setlocale() assumes that we left the old locale alone.
193 	 */
194 	locale_buf = locale_buf_C;
195 	_time_localebuf = _C_time_locale;
196 	save_using_locale = 0;
197 bad_lbuf:
198 	free(lbuf);
199 bad_locale:
200 	(void) close(fd);
201 no_locale:
202 	_time_using_locale = save_using_locale;
203 	return -1;
204 }
205 
206 static int
207 split_lines(char *p, const char *plim)
208 {
209 	int i;
210 
211 	for (i = 0; p < plim; i++) {
212 		p = strchr(p, '\n');
213 		*p++ = '\0';
214 	}
215 	return i;
216 }
217 
218 static void
219 set_from_buf(const char *p, int num_lines)
220 {
221 	const char **ap;
222 	int i;
223 
224 	for (ap = (const char **) &_time_localebuf, i = 0;
225 	    i < num_lines; ++ap, ++i)
226 		*ap = p += strlen(p) + 1;
227 	if (num_lines == LCTIME_SIZE_FULL)
228 		return;
229 	for (i = 0; i < 12; i++)
230 		_time_localebuf.alt_month[i] = _time_localebuf.month[i];
231 }
232