1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21/*
22 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
23 * Use is subject to license terms.
24 */
25
26/*
27 * Copyright (c) 1980 Regents of the University of California.
28 * All rights reserved. The Berkeley software License Agreement
29 * specifies the terms and conditions for redistribution.
30 */
31
32#ifndef KERNEL
33#define	KERNEL
34#endif
35
36#include <sys/param.h>
37#include <sys/time.h>
38#include <sys/conf.h>
39#include <sys/sysmacros.h>
40#include <sys/vfs.h>
41#include <sys/debug.h>
42#include <sys/errno.h>
43#include <sys/cmn_err.h>
44#include <sys/ddi.h>
45#include <sys/sunddi.h>
46#include <sys/byteorder.h>
47#include <sys/types.h>
48#include <sys/fs/pc_fs.h>
49#include <sys/fs/pc_label.h>
50#include <sys/fs/pc_dir.h>
51#include <sys/fs/pc_node.h>
52
53/*
54 * Convert time between DOS formats:
55 *	- years since 1980
56 *	- months/days/hours/minutes/seconds, local TZ
57 * and the UNIX format (seconds since 01/01/1970, 00:00:00 UT).
58 *
59 * Timezones are adjusted for via mount option arg (secondswest),
60 * but daylight savings time corrections are not made. Calculated
61 * time may therefore end up being wrong by an hour, but this:
62 *	a) will happen as well if media is interchanged between
63 *	   two DOS/Windows-based systems that use different
64 *	   timezone settings
65 *	b) is the best option we have unless we decide to put
66 *	   a full ctime(3C) framework into the kernel, including
67 *	   all conversion tables - AND keeping them current ...
68 */
69
70int pc_tvtopct(timestruc_t *, struct pctime *);
71void pc_pcttotv(struct pctime *, int64_t *);
72
73/*
74 * Macros/Definitons required to convert between DOS-style and
75 * UNIX-style time recording.
76 * DOS year zero is 1980.
77 */
78static int daysinmonth[] =
79	    { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
80
81#define	YEAR_ZERO	1980
82#define	YZ_SECS	(((8 * 365) + (2 * 366)) * 86400)
83#define	FAT_ENDOFTIME	\
84	LE_16(23 << HOURSHIFT | 59 << MINSHIFT | (59/2) << SECSHIFT)
85#define	FAT_ENDOFDATE	\
86	LE_16(127 << YEARSHIFT | 12 << MONSHIFT | 31 << DAYSHIFT)
87#define	leap_year(y) \
88	(((y) % 4) == 0 && (((y) % 100) != 0 || ((y) % 400) == 0))
89
90#define	YEN	"\xc2\xa5"	/* Yen Sign UTF-8 character */
91#define	LRO	"\xe2\x80\xad"	/* Left-To-Right Override UTF-8 character */
92#define	RLO	"\xe2\x80\xae"	/* Right-To-Left Override UTF-8 character */
93
94static int
95days_in_year(int y)
96{
97	return (leap_year((y)) ? 366 : 365);
98}
99
100static int
101days_in_month(int m, int y)
102{
103	if (m == 2 && leap_year(y))
104		return (29);
105	else
106		return (daysinmonth[m-1]);
107}
108
109struct pcfs_args pc_tz; /* this is set by pcfs_mount */
110
111/*
112 * Convert time from UNIX to DOS format.
113 * Return EOVERFLOW in case no valid DOS time representation
114 * exists for the given UNIX time.
115 */
116int
117pc_tvtopct(
118	timestruc_t	*tvp,		/* UNIX time input */
119	struct pctime *pctp)		/* pctime output */
120{
121	uint_t year, month, day, hour, min, sec;
122	int64_t unixtime;
123
124	unixtime = (int64_t)tvp->tv_sec;
125	unixtime -= YZ_SECS;
126	unixtime -= pc_tz.secondswest;
127	if (unixtime <= 0) {
128		/*
129		 * "before beginning of all time" for DOS ...
130		 */
131		return (EOVERFLOW);
132	}
133	for (year = YEAR_ZERO; unixtime >= days_in_year(year) * 86400;
134	    year++)
135		unixtime -= 86400 * days_in_year(year);
136
137	if (year > 127 + YEAR_ZERO) {
138		/*
139		 * "past end of all time" for DOS - can happen
140		 * on a 64bit kernel via utimes() syscall ...
141		 */
142		return (EOVERFLOW);
143	}
144
145	for (month = 1; unixtime >= 86400 * days_in_month(month, year);
146	    month++)
147		unixtime -= 86400 * days_in_month(month, year);
148
149	year -= YEAR_ZERO;
150
151	day = (int)(unixtime / 86400);
152	unixtime -= 86400 * day++;	/* counting starts at 1 */
153
154	hour = (int)(unixtime / 3600);
155	unixtime -= 3600 * hour;
156
157	min = (int)(unixtime / 60);
158	unixtime -= 60 * min;
159
160	sec = (int)unixtime;
161
162	PC_DPRINTF3(1, "ux2pc date: %d.%d.%d\n", day, month, YEAR_ZERO + year);
163	PC_DPRINTF3(1, "ux2pc time: %dh%dm%ds\n", hour, min, sec);
164	PC_DPRINTF1(1, "ux2pc unixtime: %lld\n", (long long)(unixtime));
165
166	ASSERT(year >= 0 && year < 128);
167	ASSERT(month >= 1 && month <= 12);
168	ASSERT(day >= 1 && day <= days_in_month(month, year));
169	ASSERT(hour < 24);
170	ASSERT(min < 60);
171	ASSERT(sec < 60);
172
173	pctp->pct_time =
174	    LE_16(hour << HOURSHIFT | min << MINSHIFT | (sec / 2) << SECSHIFT);
175	pctp->pct_date =
176	    LE_16(year << YEARSHIFT | month << MONSHIFT | day << DAYSHIFT);
177
178	return (0);
179}
180
181/*
182 * Convert time from DOS to UNIX time format.
183 * Since FAT timestamps cannot be expressed in 32bit time_t,
184 * the calculation is performed using 64bit values. It's up to
185 * the caller to decide what to do for out-of-UNIX-range values.
186 */
187void
188pc_pcttotv(
189	struct pctime *pctp,		/* DOS time input */
190	int64_t *unixtime)		/* caller converts to time_t */
191{
192	uint_t year, month, day, hour, min, sec;
193
194	sec = 2 * ((LE_16(pctp->pct_time) >> SECSHIFT) & SECMASK);
195	min = (LE_16(pctp->pct_time) >> MINSHIFT) & MINMASK;
196	hour = (LE_16(pctp->pct_time) >> HOURSHIFT) & HOURMASK;
197	day = (LE_16(pctp->pct_date) >> DAYSHIFT) & DAYMASK;
198	month = (LE_16(pctp->pct_date) >> MONSHIFT) & MONMASK;
199	year = (LE_16(pctp->pct_date) >> YEARSHIFT) & YEARMASK;
200	year += YEAR_ZERO;
201
202	/*
203	 * Basic sanity checks. The FAT timestamp bitfields allow for
204	 * impossible dates/times - return the "FAT epoch" for these.
205	 */
206	if (pctp->pct_date == 0) {
207		year = YEAR_ZERO;
208		month = 1;
209		day = 1;
210	}
211	if (month > 12 || month < 1 ||
212	    day < 1 || day > days_in_month(month, year) ||
213	    hour > 23 || min > 59 || sec > 59) {
214		cmn_err(CE_NOTE, "impossible FAT timestamp, "
215		    "d/m/y %d/%d/%d, h:m:s %d:%d:%d",
216		    day, month, year, hour, min, sec);
217		*unixtime = YZ_SECS + pc_tz.secondswest;
218		return;
219	}
220
221	PC_DPRINTF3(1, "pc2ux date: %d.%d.%d\n", day, month, year);
222	PC_DPRINTF3(1, "pc2ux time: %dh%dm%ds\n", hour, min, sec);
223
224	*unixtime = (int64_t)sec;
225	*unixtime += 60 * (int64_t)min;
226	*unixtime += 3600 * (int64_t)hour;
227	*unixtime += 86400 * (int64_t)(day -1);
228	while (month > 1) {
229		month--;
230		*unixtime += 86400 * (int64_t)days_in_month(month, year);
231	}
232	while (year > YEAR_ZERO) {
233		year--;
234		*unixtime += 86400 * (int64_t)days_in_year(year);
235	}
236	/*
237	 * For FAT, the beginning of all time is 01/01/1980,
238	 * and years are counted relative to that.
239	 * We adjust this base value by the timezone offset
240	 * that is passed in to pcfs at mount time.
241	 */
242	*unixtime += YZ_SECS;
243	*unixtime += pc_tz.secondswest;
244
245	/*
246	 * FAT epoch is past UNIX epoch - negative UNIX times
247	 * cannot result from the conversion.
248	 */
249	ASSERT(*unixtime > 0);
250	PC_DPRINTF1(1, "pc2ux unixtime: %lld\n", (long long)(*unixtime));
251}
252
253/*
254 * Determine whether a character is valid for a long file name.
255 * It is easier to determine by filtering out invalid characters.
256 * Following are invalid characters in a long filename.
257 *	/ \ : * ? < > | "
258 */
259int
260pc_valid_lfn_char(char c)
261{
262	const char *cp;
263	int n;
264
265	static const char invaltab[] = {
266		"/\\:*?<>|\""
267	};
268
269	cp = invaltab;
270	n = sizeof (invaltab) - 1;
271	while (n--) {
272		if (c == *cp++)
273			return (0);
274	}
275	return (1);
276}
277
278int
279pc_valid_long_fn(char *namep, int utf8)
280{
281	char *tmp;
282	int len, error;
283	char *prohibited[13] = {
284		"/", "\\", ":", "*", "?", "<", ">", "|", "\"", YEN, LRO, RLO,
285		    NULL
286	};
287
288	if (utf8) {
289		/* UTF-8 */
290		if ((len = u8_validate(namep, strlen(namep), prohibited,
291		    (U8_VALIDATE_ENTIRE|U8_VALIDATE_CHECK_ADDITIONAL),
292		    &error)) < 0)
293			return (0);
294		if (len > PCMAXNAMLEN)
295			return (0);
296	} else {
297		/* UTF-16 */
298		for (tmp = namep; (*tmp != '\0') || (*(tmp+1) != '\0');
299		    tmp += 2) {
300			if ((*(tmp+1) == '\0') && !pc_valid_lfn_char(*tmp))
301				return (0);
302
303			/* Prohibit the Yen character */
304			if ((*(tmp+1) == '\0') && (*tmp == '\xa5'))
305				return (0);
306
307			/* Prohibit the left-to-right override control char */
308			if ((*(tmp+1) == '\x20') && (*tmp == '\x2d'))
309				return (0);
310
311			/* Prohibit the right-to-left override control char */
312			if ((*(tmp+1) == '\x20') && (*tmp == '\x2e'))
313				return (0);
314		}
315		if ((tmp - namep) > (PCMAXNAMLEN * sizeof (uint16_t)))
316			return (0);
317	}
318	return (1);
319}
320
321int
322pc_fname_ext_to_name(char *namep, char *fname, char *ext, int foldcase)
323{
324	int	i;
325	char	*tp = namep;
326	char	c;
327
328	i = PCFNAMESIZE;
329	while (i-- && ((c = *fname) != ' ')) {
330		if (!(c == '.' || pc_validchar(c))) {
331			return (-1);
332		}
333		if (foldcase)
334			*tp++ = tolower(c);
335		else
336			*tp++ = c;
337		fname++;
338	}
339	if (*ext != ' ') {
340		*tp++ = '.';
341		i = PCFEXTSIZE;
342		while (i-- && ((c = *ext) != ' ')) {
343			if (!pc_validchar(c)) {
344				return (-1);
345			}
346			if (foldcase)
347				*tp++ = tolower(c);
348			else
349				*tp++ = c;
350			ext++;
351		}
352	}
353	*tp = '\0';
354	return (0);
355}
356