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