xref: /illumos-gate/usr/src/uts/common/fs/pcfs/pc_subr.c (revision 264a6e7478846334593be7663fb6b1a8f37784a0)
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 2006 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 #pragma ident	"%Z%%M%	%I%	%E% SMI"
33 
34 #ifndef KERNEL
35 #define	KERNEL
36 #endif
37 
38 #include <sys/param.h>
39 #include <sys/time.h>
40 #include <sys/conf.h>
41 #include <sys/sysmacros.h>
42 #include <sys/vfs.h>
43 #include <sys/debug.h>
44 #include <sys/errno.h>
45 #include <sys/cmn_err.h>
46 #include <sys/ddi.h>
47 #include <sys/sunddi.h>
48 #include <sys/byteorder.h>
49 #include <sys/fs/pc_fs.h>
50 #include <sys/fs/pc_label.h>
51 #include <sys/fs/pc_dir.h>
52 #include <sys/fs/pc_node.h>
53 
54 /*
55  * Convert time between DOS formats:
56  *	- years since 1980
57  *	- months/days/hours/minutes/seconds, local TZ
58  * and the UNIX format (seconds since 01/01/1970, 00:00:00 UT).
59  *
60  * Timezones are adjusted for via mount option arg (secondswest),
61  * but daylight savings time corrections are not made. Calculated
62  * time may therefore end up being wrong by an hour, but this:
63  *	a) will happen as well if media is interchanged between
64  *	   two DOS/Windows-based systems that use different
65  *	   timezone settings
66  *	b) is the best option we have unless we decide to put
67  *	   a full ctime(3C) framework into the kernel, including
68  *	   all conversion tables - AND keeping them current ...
69  */
70 
71 int pc_tvtopct(timestruc_t *, struct pctime *);
72 void pc_pcttotv(struct pctime *, int64_t *);
73 
74 /*
75  * Macros/Definitons required to convert between DOS-style and
76  * UNIX-style time recording.
77  * DOS year zero is 1980.
78  */
79 static int daysinmonth[] =
80 	    { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
81 
82 #define	YEAR_ZERO	1980
83 #define	YZ_SECS	(((8 * 365) + (2 * 366)) * 86400)
84 #define	FAT_ENDOFTIME	\
85 	LE_16(23 << HOURSHIFT | 59 << MINSHIFT | (59/2) << SECSHIFT)
86 #define	FAT_ENDOFDATE	\
87 	LE_16(127 << YEARSHIFT | 12 << MONSHIFT | 31 << DAYSHIFT)
88 #define	leap_year(y) \
89 	(((y) % 4) == 0 && (((y) % 100) != 0 || ((y) % 400) == 0))
90 
91 static int
92 days_in_year(int y)
93 {
94 	return (leap_year((y)) ? 366 : 365);
95 }
96 
97 static int
98 days_in_month(int m, int y)
99 {
100 	if (m == 2 && leap_year(y))
101 		return (29);
102 	else
103 		return (daysinmonth[m-1]);
104 }
105 
106 struct pcfs_args pc_tz; /* this is set by pcfs_mount */
107 
108 /*
109  * Convert time from UNIX to DOS format.
110  * Return EOVERFLOW in case no valid DOS time representation
111  * exists for the given UNIX time.
112  */
113 int
114 pc_tvtopct(
115 	timestruc_t	*tvp,		/* UNIX time input */
116 	struct pctime *pctp)		/* pctime output */
117 {
118 	uint_t year, month, day, hour, min, sec;
119 	int64_t unixtime;
120 
121 	unixtime = (int64_t)tvp->tv_sec;
122 	unixtime -= YZ_SECS;
123 	unixtime -= pc_tz.secondswest;
124 	if (unixtime <= 0) {
125 		/*
126 		 * "before beginning of all time" for DOS ...
127 		 */
128 		return (EOVERFLOW);
129 	}
130 	for (year = YEAR_ZERO; unixtime >= days_in_year(year) * 86400;
131 	    year++)
132 		unixtime -= 86400 * days_in_year(year);
133 
134 	if (year > 127 + YEAR_ZERO) {
135 		/*
136 		 * "past end of all time" for DOS - can happen
137 		 * on a 64bit kernel via utimes() syscall ...
138 		 */
139 		return (EOVERFLOW);
140 	}
141 
142 	for (month = 1; unixtime >= 86400 * days_in_month(month, year);
143 	    month++)
144 		unixtime -= 86400 * days_in_month(month, year);
145 
146 	year -= YEAR_ZERO;
147 
148 	day = (int)(unixtime / 86400);
149 	unixtime -= 86400 * day++;	/* counting starts at 1 */
150 
151 	hour = (int)(unixtime / 3600);
152 	unixtime -= 3600 * hour;
153 
154 	min = (int)(unixtime / 60);
155 	unixtime -= 60 * min;
156 
157 	sec = (int)unixtime;
158 
159 	PC_DPRINTF3(1, "ux2pc date: %d.%d.%d\n", day, month, YEAR_ZERO + year);
160 	PC_DPRINTF3(1, "ux2pc time: %dh%dm%ds\n", hour, min, sec);
161 	PC_DPRINTF1(1, "ux2pc unixtime: %lld\n", (long long)(unixtime));
162 
163 	ASSERT(year >= 0 && year < 128);
164 	ASSERT(month >= 1 && month <= 12);
165 	ASSERT(day >= 1 && day <= days_in_month(month, year));
166 	ASSERT(hour < 24);
167 	ASSERT(min < 60);
168 	ASSERT(sec < 60);
169 
170 	pctp->pct_time =
171 	    LE_16(hour << HOURSHIFT | min << MINSHIFT | (sec / 2) << SECSHIFT);
172 	pctp->pct_date =
173 	    LE_16(year << YEARSHIFT | month << MONSHIFT | day << DAYSHIFT);
174 
175 	return (0);
176 }
177 
178 /*
179  * Convert time from DOS to UNIX time format.
180  * Since FAT timestamps cannot be expressed in 32bit time_t,
181  * the calculation is performed using 64bit values. It's up to
182  * the caller to decide what to do for out-of-UNIX-range values.
183  */
184 void
185 pc_pcttotv(
186 	struct pctime *pctp,		/* DOS time input */
187 	int64_t *unixtime)		/* caller converts to time_t */
188 {
189 	uint_t year, month, day, hour, min, sec;
190 
191 	sec = 2 * ((LE_16(pctp->pct_time) >> SECSHIFT) & SECMASK);
192 	min = (LE_16(pctp->pct_time) >> MINSHIFT) & MINMASK;
193 	hour = (LE_16(pctp->pct_time) >> HOURSHIFT) & HOURMASK;
194 	day = (LE_16(pctp->pct_date) >> DAYSHIFT) & DAYMASK;
195 	month = (LE_16(pctp->pct_date) >> MONSHIFT) & MONMASK;
196 	year = (LE_16(pctp->pct_date) >> YEARSHIFT) & YEARMASK;
197 	year += YEAR_ZERO;
198 
199 	/*
200 	 * Basic sanity checks. The FAT timestamp bitfields allow for
201 	 * impossible dates/times - return the "FAT epoch" for these.
202 	 */
203 	if (pctp->pct_date == 0) {
204 		year = YEAR_ZERO;
205 		month = 1;
206 		day = 1;
207 	}
208 	if (month > 12 || month < 1 ||
209 	    day < 1 || day > days_in_month(month, year) ||
210 	    hour > 23 || min > 59 || sec > 59) {
211 		cmn_err(CE_NOTE, "impossible FAT timestamp, "
212 		    "d/m/y %d/%d/%d, h:m:s %d:%d:%d",
213 		    day, month, year, hour, min, sec);
214 		*unixtime = YZ_SECS + pc_tz.secondswest;
215 		return;
216 	}
217 
218 	PC_DPRINTF3(1, "pc2ux date: %d.%d.%d\n", day, month, year);
219 	PC_DPRINTF3(1, "pc2ux time: %dh%dm%ds\n", hour, min, sec);
220 
221 	*unixtime = (int64_t)sec;
222 	*unixtime += 60 * (int64_t)min;
223 	*unixtime += 3600 * (int64_t)hour;
224 	*unixtime += 86400 * (int64_t)day;
225 	while (month > 1) {
226 		month--;
227 		*unixtime += 86400 * (int64_t)days_in_month(month, year);
228 	}
229 	while (year > YEAR_ZERO) {
230 		*unixtime += 86400 * (int64_t)days_in_year(year);
231 		year--;
232 	}
233 	/*
234 	 * For FAT, the beginning of all time is 01/01/1980,
235 	 * and years are counted relative to that.
236 	 * We adjust this base value by the timezone offset
237 	 * that is passed in to pcfs at mount time.
238 	 */
239 	*unixtime += YZ_SECS;
240 	*unixtime += pc_tz.secondswest;
241 
242 	/*
243 	 * FAT epoch is past UNIX epoch - negative UNIX times
244 	 * cannot result from the conversion.
245 	 */
246 	ASSERT(*unixtime > 0);
247 	PC_DPRINTF1(1, "pc2ux unixtime: %lld\n", (long long)(*unixtime));
248 }
249 
250 /*
251  * Determine whether a character is valid for a pc 8.3 file system file name.
252  * The Windows 95 Resource Kit claims that these are valid:
253  *	uppercase letters and numbers
254  *	blank
255  *	ASCII characters greater than 127
256  *	$%'-_@~`!()^#&
257  * Long file names can also have
258  *	lowercase letters
259  *	+,;=[].
260  */
261 int
262 pc_valid_lfn_char(char c)
263 {
264 	char *cp;
265 	int n;
266 	static char valtab[] = {
267 		"+,;=[].$#&@!%()-{}<>`_^~|' "
268 	};
269 
270 	if (c >= 'a' && c <= 'z')
271 		return (1);
272 	if (c >= 'A' && c <= 'Z')
273 		return (1);
274 	if (c >= '0' && c <= '9')
275 		return (1);
276 	cp = valtab;
277 	n = sizeof (valtab);
278 	while (n--) {
279 		if (c == *cp++)
280 			return (1);
281 	}
282 	return (0);
283 }
284 
285 int
286 pc_valid_long_fn(char *namep)
287 {
288 	char *tmp;
289 
290 	for (tmp = namep; *tmp != '\0'; tmp++)
291 		if (!pc_valid_lfn_char(*tmp))
292 			return (0);
293 	if ((tmp - namep) >= PCMAXNAMLEN)
294 		return (0);
295 	return (1);
296 }
297 
298 int
299 pc_fname_ext_to_name(char *namep, char *fname, char *ext, int foldcase)
300 {
301 	int	i;
302 	char	*tp = namep;
303 	char	c;
304 
305 	i = PCFNAMESIZE;
306 	while (i-- && ((c = *fname) != ' ')) {
307 		if (!(c == '.' || pc_validchar(c))) {
308 			return (-1);
309 		}
310 		if (foldcase)
311 			*tp++ = tolower(c);
312 		else
313 			*tp++ = c;
314 		fname++;
315 	}
316 	if (*ext != ' ') {
317 		*tp++ = '.';
318 		i = PCFEXTSIZE;
319 		while (i-- && ((c = *ext) != ' ')) {
320 			if (!pc_validchar(c)) {
321 				return (-1);
322 			}
323 			if (foldcase)
324 				*tp++ = tolower(c);
325 			else
326 				*tp++ = c;
327 			ext++;
328 		}
329 	}
330 	*tp = '\0';
331 	return (0);
332 }
333