1 /* $Id: msdosfs_conv.c,v 1.9 1996/04/05 18:59:06 ache Exp $ */ 2 /* $NetBSD: msdosfs_conv.c,v 1.6.2.1 1994/08/30 02:27:57 cgd Exp $ */ 3 4 /* 5 * Written by Paul Popelka (paulp@uts.amdahl.com) 6 * 7 * You can do anything you want with this software, just don't say you wrote 8 * it, and don't remove this notice. 9 * 10 * This software is provided "as is". 11 * 12 * The author supplies this software to be publicly redistributed on the 13 * understanding that the author is not responsible for the correct 14 * functioning of this software in any circumstances and is not liable for 15 * any damages caused by this software. 16 * 17 * October 1992 18 */ 19 20 /* 21 * System include files. 22 */ 23 #include <sys/param.h> 24 #include <sys/time.h> 25 #include <sys/kernel.h> /* defines tz */ 26 #include <sys/systm.h> /* defines tz */ 27 #include <machine/clock.h> 28 29 /* 30 * MSDOSFS include files. 31 */ 32 #include <msdosfs/direntry.h> 33 34 /* 35 * Total number of days that have passed for each month in a regular year. 36 */ 37 static u_short regyear[] = { 38 31, 59, 90, 120, 151, 181, 39 212, 243, 273, 304, 334, 365 40 }; 41 42 /* 43 * Total number of days that have passed for each month in a leap year. 44 */ 45 static u_short leapyear[] = { 46 31, 60, 91, 121, 152, 182, 47 213, 244, 274, 305, 335, 366 48 }; 49 50 /* 51 * Variables used to remember parts of the last time conversion. Maybe we 52 * can avoid a full conversion. 53 */ 54 u_long lasttime; 55 u_long lastday; 56 u_short lastddate; 57 u_short lastdtime; 58 59 /* 60 * Convert the unix version of time to dos's idea of time to be used in 61 * file timestamps. The passed in unix time is assumed to be in GMT. 62 */ 63 void 64 unix2dostime(tsp, ddp, dtp) 65 struct timespec *tsp; 66 u_short *ddp; 67 u_short *dtp; 68 { 69 u_long t; 70 u_long days; 71 u_long inc; 72 u_long year; 73 u_long month; 74 u_short *months; 75 76 /* 77 * If the time from the last conversion is the same as now, then 78 * skip the computations and use the saved result. 79 */ 80 t = tsp->tv_sec - (tz.tz_minuteswest * 60) 81 - (wall_cmos_clock ? adjkerntz : 0); 82 /* - daylight savings time correction */ 83 if (lasttime != t) { 84 lasttime = t; 85 lastdtime = (((t % 60) >> 1) << DT_2SECONDS_SHIFT) 86 + (((t / 60) % 60) << DT_MINUTES_SHIFT) 87 + (((t / 3600) % 24) << DT_HOURS_SHIFT); 88 89 /* 90 * If the number of days since 1970 is the same as the last 91 * time we did the computation then skip all this leap year 92 * and month stuff. 93 */ 94 days = t / (24 * 60 * 60); 95 if (days != lastday) { 96 lastday = days; 97 for (year = 1970;; year++) { 98 inc = year & 0x03 ? 365 : 366; 99 if (days < inc) 100 break; 101 days -= inc; 102 } 103 months = year & 0x03 ? regyear : leapyear; 104 for (month = 0; days > months[month]; month++) 105 ; 106 if (month > 0) 107 days -= months[month - 1]; 108 lastddate = ((days + 1) << DD_DAY_SHIFT) 109 + ((month + 1) << DD_MONTH_SHIFT); 110 /* 111 * Remember dos's idea of time is relative to 1980. 112 * unix's is relative to 1970. If somehow we get a 113 * time before 1980 then don't give totally crazy 114 * results. 115 */ 116 if (year > 1980) 117 lastddate += (year - 1980) << DD_YEAR_SHIFT; 118 } 119 } 120 *dtp = lastdtime; 121 *ddp = lastddate; 122 } 123 124 /* 125 * The number of seconds between Jan 1, 1970 and Jan 1, 1980. In that 126 * interval there were 8 regular years and 2 leap years. 127 */ 128 #define SECONDSTO1980 (((8 * 365) + (2 * 366)) * (24 * 60 * 60)) 129 130 u_short lastdosdate; 131 u_long lastseconds; 132 133 /* 134 * Convert from dos' idea of time to unix'. This will probably only be 135 * called from the stat(), and fstat() system calls and so probably need 136 * not be too efficient. 137 */ 138 void 139 dos2unixtime(dd, dt, tsp) 140 u_short dd; 141 u_short dt; 142 struct timespec *tsp; 143 { 144 u_long seconds; 145 u_long month; 146 u_long year; 147 u_long days; 148 u_short *months; 149 150 seconds = (((dt & DT_2SECONDS_MASK) >> DT_2SECONDS_SHIFT) << 1) 151 + ((dt & DT_MINUTES_MASK) >> DT_MINUTES_SHIFT) * 60 152 + ((dt & DT_HOURS_MASK) >> DT_HOURS_SHIFT) * 3600; 153 /* 154 * If the year, month, and day from the last conversion are the 155 * same then use the saved value. 156 */ 157 if (lastdosdate != dd) { 158 lastdosdate = dd; 159 days = 0; 160 year = (dd & DD_YEAR_MASK) >> DD_YEAR_SHIFT; 161 days = year * 365; 162 days += year / 4 + 1; /* add in leap days */ 163 if ((year & 0x03) == 0) 164 days--; /* if year is a leap year */ 165 months = year & 0x03 ? regyear : leapyear; 166 month = (dd & DD_MONTH_MASK) >> DD_MONTH_SHIFT; 167 if (month < 1 || month > 12) { 168 printf( 169 "dos2unixtime(): month value out of range (%ld)\n", 170 month); 171 month = 1; 172 } 173 if (month > 1) 174 days += months[month - 2]; 175 days += ((dd & DD_DAY_MASK) >> DD_DAY_SHIFT) - 1; 176 lastseconds = (days * 24 * 60 * 60) + SECONDSTO1980; 177 } 178 tsp->tv_sec = seconds + lastseconds + (tz.tz_minuteswest * 60) 179 + adjkerntz; 180 /* + daylight savings time correction */ 181 tsp->tv_nsec = 0; 182 } 183 184 /* 185 * Cheezy macros to do case detection and conversion for the ascii 186 * character set. DOESN'T work for ebcdic. 187 */ 188 #define isupper(c) (c >= 'A' && c <= 'Z') 189 #define islower(c) (c >= 'a' && c <= 'z') 190 #define toupper(c) (c & ~' ') 191 #define tolower(c) (c | ' ') 192 193 /* 194 * DOS filenames are made of 2 parts, the name part and the extension part. 195 * The name part is 8 characters long and the extension part is 3 196 * characters long. They may contain trailing blanks if the name or 197 * extension are not long enough to fill their respective fields. 198 */ 199 200 /* 201 * Convert a DOS filename to a unix filename. And, return the number of 202 * characters in the resulting unix filename excluding the terminating 203 * null. 204 */ 205 int 206 dos2unixfn(dn, un) 207 u_char dn[11]; 208 u_char *un; 209 { 210 int i; 211 int ni; 212 int ei; 213 int thislong = 0; 214 u_char c; 215 u_char *origun = un; 216 217 /* 218 * Find the last character in the name portion of the dos filename. 219 */ 220 for (ni = 7; ni >= 0; ni--) 221 if (dn[ni] != ' ') 222 break; 223 224 /* 225 * Find the last character in the extension portion of the 226 * filename. 227 */ 228 for (ei = 10; ei >= 8; ei--) 229 if (dn[ei] != ' ') 230 break; 231 232 /* 233 * Copy the name portion into the unix filename string. NOTE: DOS 234 * filenames are usually kept in upper case. To make it more unixy 235 * we convert all DOS filenames to lower case. Some may like this, 236 * some may not. 237 */ 238 for (i = 0; i <= ni; i++) { 239 c = dn[i]; 240 *un++ = isupper(c) ? tolower(c) : c; 241 thislong++; 242 } 243 244 /* 245 * Now, if there is an extension then put in a period and copy in 246 * the extension. 247 */ 248 if (ei >= 8) { 249 *un++ = '.'; 250 thislong++; 251 for (i = 8; i <= ei; i++) { 252 c = dn[i]; 253 *un++ = isupper(c) ? tolower(c) : c; 254 thislong++; 255 } 256 } 257 *un++ = 0; 258 259 /* 260 * If first char of the filename is SLOT_E5 (0x05), then the real 261 * first char of the filename should be 0xe5. But, they couldn't 262 * just have a 0xe5 mean 0xe5 because that is used to mean a freed 263 * directory slot. Another dos quirk. 264 */ 265 if (*origun == SLOT_E5) 266 *origun = 0xe5; 267 268 return thislong; 269 } 270 271 /* 272 * Convert a unix filename to a DOS filename. This function does not ensure 273 * that valid characters for a dos filename are supplied. 274 */ 275 void 276 unix2dosfn(un, dn, unlen) 277 u_char *un; 278 u_char dn[11]; 279 int unlen; 280 { 281 int i; 282 u_char c; 283 284 /* 285 * Fill the dos filename string with blanks. These are DOS's pad 286 * characters. 287 */ 288 for (i = 0; i <= 10; i++) 289 dn[i] = ' '; 290 291 /* 292 * The filenames "." and ".." are handled specially, since they 293 * don't follow dos filename rules. 294 */ 295 if (un[0] == '.' && unlen == 1) { 296 dn[0] = '.'; 297 return; 298 } 299 if (un[0] == '.' && un[1] == '.' && unlen == 2) { 300 dn[0] = '.'; 301 dn[1] = '.'; 302 return; 303 } 304 305 /* 306 * Copy the unix filename into the dos filename string upto the end 307 * of string, a '.', or 8 characters. Whichever happens first stops 308 * us. This forms the name portion of the dos filename. Fold to 309 * upper case. 310 */ 311 for (i = 0; i <= 7 && unlen && (c = *un) && c != '.'; i++) { 312 dn[i] = islower(c) ? toupper(c) : c; 313 un++; 314 unlen--; 315 } 316 317 /* 318 * If the first char of the filename is 0xe5, then translate it to 319 * 0x05. This is because 0xe5 is the marker for a deleted 320 * directory slot. I guess this means you can't have filenames 321 * that start with 0x05. I suppose we should check for this and 322 * doing something about it. 323 */ 324 if (dn[0] == SLOT_DELETED) 325 dn[0] = SLOT_E5; 326 327 /* 328 * Strip any further characters up to a '.' or the end of the 329 * string. 330 */ 331 while (unlen && (c = *un)) { 332 un++; 333 unlen--; 334 /* Make sure we've skipped over the dot before stopping. */ 335 if (c == '.') 336 break; 337 } 338 339 /* 340 * Copy in the extension part of the name, if any. Force to upper 341 * case. Note that the extension is allowed to contain '.'s. 342 * Filenames in this form are probably inaccessable under dos. 343 */ 344 for (i = 8; i <= 10 && unlen && (c = *un); i++) { 345 dn[i] = islower(c) ? toupper(c) : c; 346 un++; 347 unlen--; 348 } 349 } 350 351 /* 352 * Get rid of these macros before someone discovers we are using such 353 * hideous things. 354 */ 355 #undef isupper 356 #undef islower 357 #undef toupper 358 #undef tolower 359