timelocal.c revision 53940
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: head/lib/libc/stdtime/timelocal.c 53940 1999-11-30 07:33:37Z ache $
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
40static int split_lines(char *, const char *);
41static void set_from_buf(const char *, int);
42
43struct lc_time_T _time_localebuf;
44int _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#define LCTIME_SIZE_2 \
50	(offsetof(struct lc_time_T, Ex_fmt) / sizeof(char *))
51
52const struct lc_time_T	_C_time_locale = {
53	{
54		"Jan", "Feb", "Mar", "Apr", "May", "Jun",
55		"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
56	}, {
57		"January", "February", "March", "April", "May", "June",
58		"July", "August", "September", "October", "November", "December"
59	}, {
60		"Sun", "Mon", "Tue", "Wed",
61		"Thu", "Fri", "Sat"
62	}, {
63		"Sunday", "Monday", "Tuesday", "Wednesday",
64		"Thursday", "Friday", "Saturday"
65	},
66
67	/* X_fmt */
68	"%H:%M:%S",
69
70	/*
71	** x_fmt
72	** Since the C language standard calls for
73	** "date, using locale's date format," anything goes.
74	** Using just numbers (as here) makes Quakers happier;
75	** it's also compatible with SVR4.
76	*/
77	"%m/%d/%y",
78
79	/*
80	** c_fmt (ctime-compatible)
81	** Note that
82	**	"%a %b %d %H:%M:%S %Y"
83	** is used by Solaris 2.3.
84	*/
85	"%a %Ex %X %Y",
86
87	/* am */
88	"AM",
89
90	/* pm */
91	"PM",
92
93	/* date_fmt */
94	"%a %Ex %X %Z %Y",
95
96	{
97		"January", "February", "March", "April", "May", "June",
98		"July", "August", "September", "October", "November", "December"
99	},
100
101	/* Ex_fmt
102	** To determine months / day order
103	*/
104	"%b %e"
105};
106
107
108int
109__time_load_locale(const char *name)
110{
111	static char *		locale_buf;
112	static char		locale_buf_C[] = "C";
113	static int		num_lines;
114
115	int			fd;
116	char *			lbuf;
117	char *			p;
118	const char *		plim;
119	char                    filename[PATH_MAX];
120	struct stat		st;
121	size_t			namesize;
122	size_t			bufsize;
123	int                     save_using_locale;
124
125	save_using_locale = _time_using_locale;
126	_time_using_locale = 0;
127
128	if (name == NULL)
129		goto no_locale;
130
131	if (!strcmp(name, "C") || !strcmp(name, "POSIX"))
132		return 0;
133
134	/*
135	** If the locale name is the same as our cache, use the cache.
136	*/
137	lbuf = locale_buf;
138	if (lbuf != NULL && strcmp(name, lbuf) == 0) {
139		set_from_buf(lbuf, num_lines);
140		_time_using_locale = 1;
141		return 0;
142	}
143	/*
144	** Slurp the locale file into the cache.
145	*/
146	namesize = strlen(name) + 1;
147
148	if (!_PathLocale)
149		goto no_locale;
150	/* Range checking not needed, 'name' size is limited */
151	strcpy(filename, _PathLocale);
152	strcat(filename, "/");
153	strcat(filename, name);
154	strcat(filename, "/LC_TIME");
155	fd = open(filename, O_RDONLY);
156	if (fd < 0)
157		goto no_locale;
158	if (fstat(fd, &st) != 0)
159		goto bad_locale;
160	if (st.st_size <= 0)
161		goto bad_locale;
162	bufsize = namesize + st.st_size;
163	locale_buf = NULL;
164	lbuf = (lbuf == NULL || lbuf == locale_buf_C) ?
165		malloc(bufsize) : reallocf(lbuf, bufsize);
166	if (lbuf == NULL)
167		goto bad_locale;
168	(void) strcpy(lbuf, name);
169	p = lbuf + namesize;
170	plim = p + st.st_size;
171	if (read(fd, p, (size_t) st.st_size) != st.st_size)
172		goto bad_lbuf;
173	if (close(fd) != 0)
174		goto bad_lbuf;
175	/*
176	** Parse the locale file into localebuf.
177	*/
178	if (plim[-1] != '\n')
179		goto bad_lbuf;
180	num_lines = split_lines(p, plim);
181	if (num_lines >= LCTIME_SIZE_FULL)
182		num_lines = LCTIME_SIZE_FULL;
183	else if (num_lines >= LCTIME_SIZE_2)
184		num_lines = LCTIME_SIZE_2;
185	else if (num_lines >= LCTIME_SIZE_1)
186		num_lines = LCTIME_SIZE_1;
187	else
188		goto reset_locale;
189	set_from_buf(lbuf, num_lines);
190	/*
191	** Record the successful parse in the cache.
192	*/
193	locale_buf = lbuf;
194
195	_time_using_locale = 1;
196	return 0;
197
198reset_locale:
199	/*
200	 * XXX - This may not be the correct thing to do in this case.
201	 * setlocale() assumes that we left the old locale alone.
202	 */
203	locale_buf = locale_buf_C;
204	_time_localebuf = _C_time_locale;
205	save_using_locale = 0;
206bad_lbuf:
207	free(lbuf);
208bad_locale:
209	(void) close(fd);
210no_locale:
211	_time_using_locale = save_using_locale;
212	return -1;
213}
214
215static int
216split_lines(char *p, const char *plim)
217{
218	int i;
219
220	for (i = 0; p < plim; i++) {
221		p = strchr(p, '\n');
222		*p++ = '\0';
223	}
224	return i;
225}
226
227static void
228set_from_buf(const char *p, int num_lines)
229{
230	const char **ap;
231	int i;
232
233	for (ap = (const char **) &_time_localebuf, i = 0;
234	    i < num_lines; ++ap, ++i)
235		*ap = p += strlen(p) + 1;
236	if (num_lines == LCTIME_SIZE_FULL)
237		return;
238	for (i = 0; i < 12; i++)
239		_time_localebuf.alt_month[i] = _time_localebuf.month[i];
240}
241