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 * fsck_pcfs -- routines for manipulating directories.
28 */
29 #include <stdio.h>
30 #include <string.h>
31 #include <unistd.h>
32 #include <stdlib.h>
33 #include <libintl.h>
34 #include <ctype.h>
35 #include <time.h>
36 #include <sys/param.h>
37 #include <sys/time.h>
38 #include <sys/byteorder.h>
39 #include <sys/dktp/fdisk.h>
40 #include <sys/fs/pc_fs.h>
41 #include <sys/fs/pc_dir.h>
42 #include <sys/fs/pc_label.h>
43 #include "pcfs_common.h"
44 #include "fsck_pcfs.h"
45
46 extern int32_t HiddenClusterCount;
47 extern int32_t FileClusterCount;
48 extern int32_t DirClusterCount;
49 extern int32_t HiddenFileCount;
50 extern int32_t LastCluster;
51 extern int32_t FileCount;
52 extern int32_t BadCount;
53 extern int32_t DirCount;
54 extern int32_t FATSize;
55 extern off64_t PartitionOffset;
56 extern bpb_t TheBIOSParameterBlock;
57 extern int ReadOnly;
58 extern int IsFAT32;
59 extern int Verbose;
60
61 static uchar_t *CHKsList = NULL;
62
63 ClusterContents TheRootDir;
64 int32_t RootDirSize;
65 int RootDirModified;
66 int OkayToRelink = 1;
67
68 /*
69 * We have a bunch of routines for handling CHK names. A CHK name is
70 * simply a file name of the form "FILEnnnn.CHK", where the n's are the
71 * digits in the numbers from 1 to 9999. There are always four digits
72 * used, leading zeros are added as necessary.
73 *
74 * We use CHK names to link orphaned cluster chains back into the file
75 * system's root directory under an auspicious name so that the user
76 * may be able to recover some of their data.
77 *
78 * We use these routines to ensure CHK names we use don't conflict
79 * with any already present in the file system.
80 */
81 static int
hasCHKName(struct pcdir * dp)82 hasCHKName(struct pcdir *dp)
83 {
84 return (dp->pcd_filename[CHKNAME_F] == 'F' &&
85 dp->pcd_filename[CHKNAME_I] == 'I' &&
86 dp->pcd_filename[CHKNAME_L] == 'L' &&
87 dp->pcd_filename[CHKNAME_E] == 'E' &&
88 isdigit(dp->pcd_filename[CHKNAME_THOUSANDS]) &&
89 isdigit(dp->pcd_filename[CHKNAME_HUNDREDS]) &&
90 isdigit(dp->pcd_filename[CHKNAME_TENS]) &&
91 isdigit(dp->pcd_filename[CHKNAME_ONES]) &&
92 dp->pcd_ext[CHKNAME_C] == 'C' &&
93 dp->pcd_ext[CHKNAME_H] == 'H' &&
94 dp->pcd_ext[CHKNAME_K] == 'K');
95 }
96
97 void
addEntryToCHKList(int chkNumber)98 addEntryToCHKList(int chkNumber)
99 {
100 /* silent failure on bogus value */
101 if (chkNumber < 0 || chkNumber > MAXCHKVAL)
102 return;
103 CHKsList[chkNumber / NBBY] |= (1 << (chkNumber % NBBY));
104 }
105
106 static void
addToCHKList(struct pcdir * dp)107 addToCHKList(struct pcdir *dp)
108 {
109 int chknum;
110
111 chknum = 1000 * (dp->pcd_filename[CHKNAME_THOUSANDS] - '0');
112 chknum += 100 * (dp->pcd_filename[CHKNAME_HUNDREDS] - '0');
113 chknum += 10 * (dp->pcd_filename[CHKNAME_TENS] - '0');
114 chknum += (dp->pcd_filename[CHKNAME_ONES] - '0');
115 addEntryToCHKList(chknum);
116 }
117
118 static int
inUseCHKName(int chkNumber)119 inUseCHKName(int chkNumber)
120 {
121 return (CHKsList[chkNumber / NBBY] & (1 << (chkNumber % NBBY)));
122 }
123
124 static void
appendToPath(struct pcdir * dp,char * thePath,int * theLen)125 appendToPath(struct pcdir *dp, char *thePath, int *theLen)
126 {
127 int i = 0;
128
129 /*
130 * Sometimes caller doesn't care about keeping track of the path
131 */
132 if (thePath == NULL)
133 return;
134
135 /*
136 * Prepend /
137 */
138 if (*theLen < MAXPATHLEN)
139 *(thePath + (*theLen)++) = '/';
140 /*
141 * Print out the file name part, but only up to the first
142 * space.
143 */
144 while (*theLen < MAXPATHLEN && i < PCFNAMESIZE) {
145 /*
146 * When we start seeing spaces we assume that's the
147 * end of the interesting characters in the name.
148 */
149 if ((dp->pcd_filename[i] == ' ') ||
150 !(pc_validchar(dp->pcd_filename[i])))
151 break;
152 *(thePath + (*theLen)++) = dp->pcd_filename[i++];
153 }
154 /*
155 * Leave now, if we don't have an extension (or room for one)
156 */
157 if ((dp->pcd_ext[i] == ' ') || ((*theLen) >= MAXPATHLEN) ||
158 (!(pc_validchar(dp->pcd_ext[i]))))
159 return;
160 /*
161 * Tack on the extension
162 */
163 *(thePath + (*theLen)++) = '.';
164 i = 0;
165 while ((*theLen < MAXPATHLEN) && (i < PCFEXTSIZE)) {
166 if ((dp->pcd_ext[i] == ' ') || !(pc_validchar(dp->pcd_ext[i])))
167 break;
168 *(thePath + (*theLen)++) = dp->pcd_ext[i++];
169 }
170 }
171
172 static void
printName(FILE * outDest,struct pcdir * dp)173 printName(FILE *outDest, struct pcdir *dp)
174 {
175 int i;
176 for (i = 0; i < PCFNAMESIZE; i++) {
177 if ((dp->pcd_filename[i] == ' ') ||
178 !(pc_validchar(dp->pcd_filename[i])))
179 break;
180 (void) fprintf(outDest, "%c", dp->pcd_filename[i]);
181 }
182 (void) fprintf(outDest, ".");
183 for (i = 0; i < PCFEXTSIZE; i++) {
184 if (!(pc_validchar(dp->pcd_ext[i])))
185 break;
186 (void) fprintf(outDest, "%c", dp->pcd_ext[i]);
187 }
188 }
189
190 /*
191 * sanityCheckSize
192 * Make sure the size in the directory entry matches what is
193 * actually allocated. If there is a mismatch, orphan all
194 * the allocated clusters. Returns SIZE_MATCHED if everything matches
195 * up, TRUNCATED to indicate truncation was necessary.
196 */
197 static int
sanityCheckSize(int fd,struct pcdir * dp,int32_t actualClusterCount,int isDir,int32_t startCluster,struct nameinfo * fullPathName,struct pcdir ** orphanEntry)198 sanityCheckSize(int fd, struct pcdir *dp, int32_t actualClusterCount,
199 int isDir, int32_t startCluster, struct nameinfo *fullPathName,
200 struct pcdir **orphanEntry)
201 {
202 uint32_t sizeFromDir;
203 int32_t ignorei = 0;
204 int64_t bpc;
205
206 bpc = TheBIOSParameterBlock.bpb.sectors_per_cluster *
207 TheBIOSParameterBlock.bpb.bytes_per_sector;
208 sizeFromDir = extractSize(dp);
209 if (isDir) {
210 if (sizeFromDir == 0)
211 return (SIZE_MATCHED);
212 } else {
213 if ((sizeFromDir > ((actualClusterCount - 1) * bpc)) &&
214 (sizeFromDir <= (actualClusterCount * bpc)))
215 return (SIZE_MATCHED);
216 }
217 if (fullPathName != NULL) {
218 fullPathName->references++;
219 (void) fprintf(stderr, "%s\n", fullPathName->fullName);
220 }
221 squirrelPath(fullPathName, startCluster);
222 (void) fprintf(stderr,
223 gettext("Truncating chain due to incorrect size "
224 "in directory. Size from directory = %u bytes,\n"), sizeFromDir);
225 if (actualClusterCount == 0) {
226 (void) fprintf(stderr,
227 gettext("Zero bytes are allocated to the file.\n"));
228 } else {
229 (void) fprintf(stderr,
230 gettext("Allocated size in range %llu - %llu bytes.\n"),
231 ((actualClusterCount - 1) * bpc) + 1,
232 (actualClusterCount * bpc));
233 }
234 /*
235 * Use splitChain() to make an orphan that is the entire allocation
236 * chain.
237 */
238 splitChain(fd, dp, startCluster, orphanEntry, &ignorei);
239 return (TRUNCATED);
240 }
241
242 static int
noteUsage(int fd,int32_t startAt,struct pcdir * dp,struct pcdir * lp,int32_t longEntryStartCluster,int isHidden,int isDir,struct nameinfo * fullPathName)243 noteUsage(int fd, int32_t startAt, struct pcdir *dp, struct pcdir *lp,
244 int32_t longEntryStartCluster, int isHidden, int isDir,
245 struct nameinfo *fullPathName)
246 {
247 struct pcdir *orphanEntry;
248 int32_t chain = startAt;
249 int32_t count = 0;
250 int savePathNextIteration = 0;
251 int haveBad = 0;
252 ClusterInfo *tmpl = NULL;
253
254 while ((chain >= FIRST_CLUSTER) && (chain <= LastCluster)) {
255 if ((markInUse(fd, chain, dp, lp, longEntryStartCluster,
256 isHidden ? HIDDEN : VISIBLE, &tmpl))
257 != CLINFO_NEWLY_ALLOCED)
258 break;
259 count++;
260 if (savePathNextIteration == 1) {
261 savePathNextIteration = 0;
262 if (fullPathName != NULL)
263 fullPathName->references++;
264 squirrelPath(fullPathName, chain);
265 }
266 if (isMarkedBad(chain)) {
267 haveBad = 1;
268 savePathNextIteration = 1;
269 }
270 if (isHidden)
271 HiddenClusterCount++;
272 else if (isDir)
273 DirClusterCount++;
274 else
275 FileClusterCount++;
276 chain = nextInChain(chain);
277 }
278 /*
279 * Do a sanity check on the file size in the directory entry.
280 * This may create an orphaned cluster chain.
281 */
282 if (sanityCheckSize(fd, dp, count, isDir, startAt,
283 fullPathName, &orphanEntry) == TRUNCATED) {
284 /*
285 * The pre-existing directory entry has been truncated,
286 * so the chain associated with it no longer has any
287 * bad clusters. Instead, the new orphan has them.
288 */
289 if (haveBad > 0) {
290 truncChainWithBadCluster(fd, orphanEntry, startAt);
291 }
292 haveBad = 0;
293 }
294 return (haveBad);
295 }
296
297 static void
storeInfoAboutEntry(int fd,struct pcdir * dp,struct pcdir * ldp,int depth,int32_t longEntryStartCluster,char * fullPath,int * fullLen)298 storeInfoAboutEntry(int fd, struct pcdir *dp, struct pcdir *ldp, int depth,
299 int32_t longEntryStartCluster, char *fullPath, int *fullLen)
300 {
301 struct nameinfo *pathCopy;
302 int32_t start;
303 int haveBad;
304 int hidden = (dp->pcd_attr & PCA_HIDDEN || dp->pcd_attr & PCA_SYSTEM);
305 int dir = (dp->pcd_attr & PCA_DIR);
306 int i;
307
308 if (hidden)
309 HiddenFileCount++;
310 else if (dir)
311 DirCount++;
312 else
313 FileCount++;
314 appendToPath(dp, fullPath, fullLen);
315
316 /*
317 * Make a copy of the name at this point. We may want it to
318 * note the original source of an orphaned cluster.
319 */
320 if ((pathCopy =
321 (struct nameinfo *)malloc(sizeof (struct nameinfo))) != NULL) {
322 if ((pathCopy->fullName =
323 (char *)malloc(*fullLen + 1)) != NULL) {
324 pathCopy->references = 0;
325 (void) strncpy(pathCopy->fullName, fullPath, *fullLen);
326 pathCopy->fullName[*fullLen] = '\0';
327 } else {
328 free(pathCopy);
329 pathCopy = NULL;
330 }
331 }
332 if (Verbose) {
333 for (i = 0; i < depth; i++)
334 (void) fprintf(stderr, " ");
335 if (hidden)
336 (void) fprintf(stderr, "[");
337 else if (dir)
338 (void) fprintf(stderr, "|_");
339 else
340 (void) fprintf(stderr, gettext("(%06d) "), FileCount);
341 printName(stderr, dp);
342 if (hidden)
343 (void) fprintf(stderr, "]");
344 (void) fprintf(stderr,
345 gettext(", %u bytes, start cluster %d"),
346 extractSize(dp), extractStartCluster(dp));
347 (void) fprintf(stderr, "\n");
348 }
349 start = extractStartCluster(dp);
350 haveBad = noteUsage(fd, start, dp, ldp, longEntryStartCluster,
351 hidden, dir, pathCopy);
352 if (haveBad > 0) {
353 if (dir && pathCopy->fullName != NULL) {
354 (void) fprintf(stderr,
355 gettext("Adjusting for bad allocation units in "
356 "the meta-data of:\n "));
357 (void) fprintf(stderr, pathCopy->fullName);
358 (void) fprintf(stderr, "\n");
359 }
360 truncChainWithBadCluster(fd, dp, start);
361 }
362 if ((pathCopy != NULL) && (pathCopy->references == 0)) {
363 free(pathCopy->fullName);
364 free(pathCopy);
365 }
366 }
367
368 static void
storeInfoAboutLabel(struct pcdir * dp)369 storeInfoAboutLabel(struct pcdir *dp)
370 {
371 /*
372 * XXX eventually depth should be passed to this routine just
373 * as it is with storeInfoAboutEntry(). If it isn't zero, then
374 * we've got a bogus directory entry.
375 */
376 if (Verbose) {
377 (void) fprintf(stderr, gettext("** "));
378 printName(stderr, dp);
379 (void) fprintf(stderr, gettext(" **\n"));
380 }
381 }
382
383 static void
searchChecks(struct pcdir * dp,int operation,char matchRequired,struct pcdir ** found)384 searchChecks(struct pcdir *dp, int operation, char matchRequired,
385 struct pcdir **found)
386 {
387 /*
388 * We support these searching operations:
389 *
390 * PCFS_FIND_ATTR
391 * look for the first file with a certain attribute
392 * (e.g, find all hidden files)
393 * PCFS_FIND_STATUS
394 * look for the first file with a certain status
395 * (e.g., the file has been marked deleted; making
396 * its directory entry reusable)
397 * PCFS_FIND_CHKS
398 * look for all files with short names of the form
399 * FILENNNN.CHK. These are the file names we give
400 * to chains of orphaned clusters we relink into the
401 * file system. This find facility allows us to seek
402 * out all existing files of this naming form so that
403 * we may create unique file names for new orphans.
404 */
405 if (operation == PCFS_FIND_ATTR && dp->pcd_attr == matchRequired) {
406 *found = dp;
407 } else if (operation == PCFS_FIND_STATUS &&
408 dp->pcd_filename[0] == matchRequired) {
409 *found = dp;
410 } else if (operation == PCFS_FIND_CHKS && hasCHKName(dp)) {
411 addToCHKList(dp);
412 }
413 }
414
415 static void
catalogEntry(int fd,struct pcdir * dp,struct pcdir * longdp,int32_t currentCluster,int depth,char * recordPath,int * pathLen)416 catalogEntry(int fd, struct pcdir *dp, struct pcdir *longdp,
417 int32_t currentCluster, int depth, char *recordPath, int *pathLen)
418 {
419 if (dp->pcd_attr & PCA_LABEL) {
420 storeInfoAboutLabel(dp);
421 } else {
422 storeInfoAboutEntry(fd, dp, longdp, depth, currentCluster,
423 recordPath, pathLen);
424 }
425 }
426
427 /*
428 * visitNodes()
429 *
430 * This is the main workhouse routine for traversing pcfs metadata.
431 * There isn't a lot to the metadata. Basically there is a root
432 * directory somewhere (either in its own special place outside the
433 * data area or in a data cluster). The root directory (and all other
434 * directories) are filled with a number of fixed size entries. An
435 * entry has the filename and extension, the file's attributes, the
436 * file's size, and the starting data cluster of the storage allocated
437 * to the file. To determine which clusters are assigned to the file,
438 * you start at the starting cluster entry in the FAT, and follow the
439 * chain of entries in the FAT.
440 *
441 * Arguments are:
442 * fd
443 * descriptor for accessing the raw file system data
444 * currentCluster
445 * original caller supplies the initial starting cluster,
446 * subsequent recursive calls are made with updated
447 * cluster numbers for the sub-directories.
448 * dirData
449 * pointer to the directory data bytes
450 * dirDataLen
451 * size of the whole buffer of data bytes (usually it is
452 * the size of a cluster, but the root directory on
453 * FAT12/16 is not necessarily the same size as a cluster).
454 * depth
455 * original caller should set it to zero (assuming they are
456 * starting from the root directory). This number is used to
457 * change the indentation of file names presented as debug info.
458 * descend
459 * boolean indicates if we should descend into subdirectories.
460 * operation
461 * what, if any, matching should be performed.
462 * The PCFS_TRAVERSE_ALL operation is a depth first traversal
463 * of all nodes in the metadata tree, that tracks all the
464 * clusters in use (according to the meta-data, at least)
465 * matchRequired
466 * value to be matched (if any)
467 * found
468 * output parameter
469 * used to return pointer to a directory entry that matches
470 * the search requirement
471 * original caller should pass in a pointer to a NULL pointer.
472 * lastDirCluster
473 * output parameter
474 * if no match found, last cluster num of starting directory
475 * dirEnd
476 * output parameter
477 * if no match found, return parameter stores pointer to where
478 * new directory entry could be appended to existing directory
479 * recordPath
480 * output parameter
481 * as files are discovered, and directories traversed, this
482 * buffer is used to store the current full path name.
483 * pathLen
484 * output parameter
485 * this is in the integer length of the current full path name.
486 */
487 static void
visitNodes(int fd,int32_t currentCluster,ClusterContents * dirData,int32_t dirDataLen,int depth,int descend,int operation,char matchRequired,struct pcdir ** found,int32_t * lastDirCluster,struct pcdir ** dirEnd,char * recordPath,int * pathLen)488 visitNodes(int fd, int32_t currentCluster, ClusterContents *dirData,
489 int32_t dirDataLen, int depth, int descend, int operation,
490 char matchRequired, struct pcdir **found, int32_t *lastDirCluster,
491 struct pcdir **dirEnd, char *recordPath, int *pathLen)
492 {
493 struct pcdir *longdp = NULL;
494 struct pcdir *dp;
495 int32_t longStart;
496 int withinLongName = 0;
497 int saveLen = *pathLen;
498
499 dp = dirData->dirp;
500
501 /*
502 * A directory entry where the first character of the name is
503 * PCD_UNUSED indicates the end of the directory.
504 */
505 while ((uchar_t *)dp < dirData->bytes + dirDataLen &&
506 dp->pcd_filename[0] != PCD_UNUSED) {
507 /*
508 * Handle the special case find operations.
509 */
510 searchChecks(dp, operation, matchRequired, found);
511 if (*found)
512 break;
513 /*
514 * Are we looking at part of a long file name entry?
515 * If so, we may need to note the start of the name.
516 * We don't do any further processing of long file
517 * name entries.
518 *
519 * We also skip deleted entries and the '.' and '..'
520 * entries.
521 */
522 if ((dp->pcd_attr & PCDL_LFN_BITS) == PCDL_LFN_BITS) {
523 if (!withinLongName) {
524 withinLongName++;
525 longStart = currentCluster;
526 longdp = dp;
527 }
528 dp++;
529 continue;
530 } else if ((dp->pcd_filename[0] == PCD_ERASED) ||
531 (dp->pcd_filename[0] == '.')) {
532 /*
533 * XXX - if we were within a long name, then
534 * its existence is bogus, because it is not
535 * attached to any real file.
536 */
537 withinLongName = 0;
538 dp++;
539 continue;
540 }
541 withinLongName = 0;
542 if (operation == PCFS_TRAVERSE_ALL)
543 catalogEntry(fd, dp, longdp, longStart, depth,
544 recordPath, pathLen);
545 longdp = NULL;
546 longStart = 0;
547 if (dp->pcd_attr & PCA_DIR && descend == PCFS_VISIT_SUBDIRS) {
548 traverseDir(fd, extractStartCluster(dp), depth + 1,
549 descend, operation, matchRequired, found,
550 lastDirCluster, dirEnd, recordPath, pathLen);
551 if (*found)
552 break;
553 }
554 dp++;
555 *pathLen = saveLen;
556 }
557 if (*found)
558 return;
559 if ((uchar_t *)dp < dirData->bytes + dirDataLen) {
560 /*
561 * We reached the end of directory before the end of
562 * our provided data (a cluster). That means this cluster
563 * is the last one in this directory's chain. It also
564 * means we've just looked at the last directory entry.
565 */
566 *lastDirCluster = currentCluster;
567 *dirEnd = dp;
568 return;
569 }
570 /*
571 * If there is more to the directory we'll go get it otherwise we
572 * are done traversing this directory.
573 */
574 if ((currentCluster == FAKE_ROOTDIR_CLUST) ||
575 (lastInFAT(currentCluster))) {
576 *lastDirCluster = currentCluster;
577 return;
578 } else {
579 traverseDir(fd, nextInChain(currentCluster),
580 depth, descend, operation, matchRequired,
581 found, lastDirCluster, dirEnd, recordPath, pathLen);
582 *pathLen = saveLen;
583 }
584 }
585
586 /*
587 * traverseFromRoot()
588 * For use with 12 and 16 bit FATs that have a root directory outside
589 * of the file system. This is a general purpose routine that
590 * can be used simply to visit all of the nodes in the metadata or
591 * to find the first instance of something, e.g., the first directory
592 * entry where the file is marked deleted.
593 *
594 * Inputs are described in the commentary for visitNodes() above.
595 */
596 void
traverseFromRoot(int fd,int depth,int descend,int operation,char matchRequired,struct pcdir ** found,int32_t * lastDirCluster,struct pcdir ** dirEnd,char * recordPath,int * pathLen)597 traverseFromRoot(int fd, int depth, int descend, int operation,
598 char matchRequired, struct pcdir **found, int32_t *lastDirCluster,
599 struct pcdir **dirEnd, char *recordPath, int *pathLen)
600 {
601 visitNodes(fd, FAKE_ROOTDIR_CLUST, &TheRootDir, RootDirSize, depth,
602 descend, operation, matchRequired, found, lastDirCluster, dirEnd,
603 recordPath, pathLen);
604 }
605
606 /*
607 * traverseDir()
608 * For use with all FATs outside of the initial root directory on
609 * 12 and 16 bit FAT file systems. This is a general purpose routine
610 * that can be used simply to visit all of the nodes in the metadata or
611 * to find the first instance of something, e.g., the first directory
612 * entry where the file is marked deleted.
613 *
614 * Unique Input is:
615 * startAt
616 * starting cluster of the directory
617 *
618 * This is the cluster that is the first one in this directory.
619 * We read it right away, so we can provide it as data to visitNodes().
620 * Note that we cache this cluster as we read it, because it is
621 * metadata and we cache all metadata. By doing so, we can
622 * keep pointers to directory entries for quickly moving around and
623 * fixing up any problems we find. Of course if we get a big
624 * filesystem with a huge amount of metadata we may be hosed, as
625 * we'll likely run out of memory.
626 *
627 * I believe in the future this will have to be addressed. It
628 * may be possible to do more of the processing of problems
629 * within directories as they are cached, so that when memory
630 * runs short we can free cached directories we are already
631 * finished visiting.
632 *
633 * The remainder of inputs are described in visitNodes() comments.
634 */
635 void
traverseDir(int fd,int32_t startAt,int depth,int descend,int operation,char matchRequired,struct pcdir ** found,int32_t * lastDirCluster,struct pcdir ** dirEnd,char * recordPath,int * pathLen)636 traverseDir(int fd, int32_t startAt, int depth, int descend, int operation,
637 char matchRequired, struct pcdir **found, int32_t *lastDirCluster,
638 struct pcdir **dirEnd, char *recordPath, int *pathLen)
639 {
640 ClusterContents dirdata;
641 int32_t dirdatasize = 0;
642
643 if (startAt < FIRST_CLUSTER || startAt > LastCluster)
644 return;
645
646 if (readCluster(fd, startAt, &(dirdata.bytes), &dirdatasize,
647 RDCLUST_DO_CACHE) != RDCLUST_GOOD) {
648 (void) fprintf(stderr,
649 gettext("Unable to get more directory entries!\n"));
650 return;
651 }
652
653 if (operation == PCFS_TRAVERSE_ALL) {
654 if (Verbose)
655 (void) fprintf(stderr,
656 gettext("Directory traversal enters "
657 "allocation unit %d.\n"), startAt);
658 }
659 visitNodes(fd, startAt, &dirdata, dirdatasize, depth, descend,
660 operation, matchRequired, found, lastDirCluster, dirEnd,
661 recordPath, pathLen);
662 }
663
664 void
createCHKNameList(int fd)665 createCHKNameList(int fd)
666 {
667 struct pcdir *ignorep1, *ignorep2;
668 int32_t ignore32;
669 char *ignorecp = NULL;
670 char ignore = '\0';
671 int ignoreint = 0;
672
673 ignorep1 = ignorep2 = NULL;
674 if (!OkayToRelink || CHKsList != NULL)
675 return;
676
677 /*
678 * Allocate an array to keep a bit map of the integer
679 * values used in CHK names.
680 */
681 if ((CHKsList =
682 (uchar_t *)calloc(1, idivceil(MAXCHKVAL, NBBY))) == NULL) {
683 OkayToRelink = 0;
684 return;
685 }
686
687 /*
688 * Search the root directory for all the files with names of
689 * the form FILEXXXX.CHK. The root directory is an area
690 * outside of the file space on FAT12 and FAT16 file systems.
691 * On FAT32 file systems, the root directory is in a file
692 * area cluster just like any other directory.
693 */
694 if (!IsFAT32) {
695 traverseFromRoot(fd, 0, PCFS_NO_SUBDIRS, PCFS_FIND_CHKS,
696 ignore, &ignorep1, &ignore32, &ignorep2, ignorecp,
697 &ignoreint);
698 } else {
699 DirCount++;
700 traverseDir(fd, TheBIOSParameterBlock.bpb32.root_dir_clust,
701 0, PCFS_NO_SUBDIRS, PCFS_FIND_CHKS, ignore,
702 &ignorep1, &ignore32, &ignorep2, ignorecp, &ignoreint);
703 }
704 }
705
706
707 char *
nextAvailableCHKName(int * chosen)708 nextAvailableCHKName(int *chosen)
709 {
710 static char nameBuf[PCFNAMESIZE];
711 int i;
712
713 if (!OkayToRelink)
714 return (NULL);
715
716 nameBuf[CHKNAME_F] = 'F';
717 nameBuf[CHKNAME_I] = 'I';
718 nameBuf[CHKNAME_L] = 'L';
719 nameBuf[CHKNAME_E] = 'E';
720
721 for (i = 1; i <= MAXCHKVAL; i++) {
722 if (!inUseCHKName(i))
723 break;
724 }
725 if (i <= MAXCHKVAL) {
726 nameBuf[CHKNAME_THOUSANDS] = '0' + (i / 1000);
727 nameBuf[CHKNAME_HUNDREDS] = '0' + ((i % 1000) / 100);
728 nameBuf[CHKNAME_TENS] = '0' + ((i % 100) / 10);
729 nameBuf[CHKNAME_ONES] = '0' + (i % 10);
730 *chosen = i;
731 return (nameBuf);
732 } else {
733 (void) fprintf(stderr,
734 gettext("Sorry, no names available for "
735 "relinking orphan chains!\n"));
736 OkayToRelink = 0;
737 return (NULL);
738 }
739 }
740
741 uint32_t
extractSize(struct pcdir * dp)742 extractSize(struct pcdir *dp)
743 {
744 uint32_t returnMe;
745
746 read_32_bits((uchar_t *)&(dp->pcd_size), &returnMe);
747 return (returnMe);
748 }
749
750 int32_t
extractStartCluster(struct pcdir * dp)751 extractStartCluster(struct pcdir *dp)
752 {
753 uint32_t lo, hi;
754
755 if (IsFAT32) {
756 read_16_bits((uchar_t *)&(dp->un.pcd_scluster_hi), &hi);
757 read_16_bits((uchar_t *)&(dp->pcd_scluster_lo), &lo);
758 return ((int32_t)((hi << 16) | lo));
759 } else {
760 read_16_bits((uchar_t *)&(dp->pcd_scluster_lo), &lo);
761 return ((int32_t)lo);
762 }
763 }
764
765 static struct pcdir *
findAvailableRootDirEntSlot(int fd,int32_t * clusterWithSlot)766 findAvailableRootDirEntSlot(int fd, int32_t *clusterWithSlot)
767 {
768 struct pcdir *deletedEntry = NULL;
769 struct pcdir *appendPoint = NULL;
770 char *ignorecp = NULL;
771 int ignore = 0;
772
773 *clusterWithSlot = 0;
774
775 /*
776 * First off, try to find an erased entry in the root
777 * directory. The root directory is an area outside of the
778 * file space on FAT12 and FAT16 file systems. On FAT32 file
779 * systems, the root directory is in a file area cluster just
780 * like any other directory.
781 */
782 if (!IsFAT32) {
783 traverseFromRoot(fd, 0, PCFS_NO_SUBDIRS, PCFS_FIND_STATUS,
784 PCD_ERASED, &deletedEntry, clusterWithSlot,
785 &appendPoint, ignorecp, &ignore);
786 } else {
787 DirCount++;
788 traverseDir(fd, TheBIOSParameterBlock.bpb32.root_dir_clust,
789 0, PCFS_NO_SUBDIRS, PCFS_FIND_STATUS, PCD_ERASED,
790 &deletedEntry, clusterWithSlot, &appendPoint, ignorecp,
791 &ignore);
792 }
793 /*
794 * If we found a deleted file in the directory we'll overwrite
795 * that entry.
796 */
797 if (deletedEntry)
798 return (deletedEntry);
799 /*
800 * If there is room at the end of the existing directory, we
801 * should place the new entry there.
802 */
803 if (appendPoint)
804 return (appendPoint);
805 /*
806 * XXX need to grow the directory
807 */
808 return (NULL);
809 }
810
811 static void
insertDirEnt(struct pcdir * slot,struct pcdir * entry,int32_t clusterWithSlot)812 insertDirEnt(struct pcdir *slot, struct pcdir *entry, int32_t clusterWithSlot)
813 {
814 (void) memcpy(slot, entry, sizeof (struct pcdir));
815 markClusterModified(clusterWithSlot);
816 }
817
818 /*
819 * Convert current UNIX time into a PCFS timestamp (which is in local time).
820 *
821 * Since the "seconds" field of that is only accurate to 2sec precision,
822 * we allow for the optional (used only for creation times on FAT) "msec"
823 * parameter that takes the fractional part.
824 */
825 static void
getNow(struct pctime * pctp,uchar_t * msec)826 getNow(struct pctime *pctp, uchar_t *msec)
827 {
828 time_t now;
829 struct tm tm;
830 ushort_t tim, dat;
831
832 /*
833 * Disable daylight savings corrections - Solaris PCFS doesn't
834 * support such conversions yet. Save timestamps in local time.
835 */
836 daylight = 0;
837
838 (void) time(&now);
839 (void) localtime_r(&now, &tm);
840
841 dat = (tm.tm_year - 80) << YEARSHIFT;
842 dat |= tm.tm_mon << MONSHIFT;
843 dat |= tm.tm_mday << DAYSHIFT;
844 tim = tm.tm_hour << HOURSHIFT;
845 tim |= tm.tm_min << MINSHIFT;
846 tim |= (tm.tm_sec / 2) << SECSHIFT;
847
848 /*
849 * Sanity check. If we overflow the PCFS timestamp range
850 * we set the time to 01/01/1980, 00:00:00
851 */
852 if (dat < 80 || dat > 227)
853 dat = tim = 0;
854
855 pctp->pct_date = LE_16(dat);
856 pctp->pct_time = LE_16(tim);
857 if (msec)
858 *msec = (tm.tm_sec & 1) ? 100 : 0;
859 }
860
861 /*
862 * FAT file systems store the following time information in a directory
863 * entry:
864 * timestamp member of "struct pcdir"
865 * ======================================================================
866 * creation time pcd_crtime.pct_time
867 * creation date pcd_crtime.pct_date
868 * last access date pcd_ladate
869 * last modify time pcd_mtime.pct_time
870 * last modify date pcd_mtime.pct_date
871 *
872 * No access time is kept.
873 */
874 static void
updateDirEnt_CreatTime(struct pcdir * dp)875 updateDirEnt_CreatTime(struct pcdir *dp)
876 {
877 getNow(&dp->pcd_crtime, &dp->pcd_crtime_msec);
878 markClusterModified(findImpactedCluster(dp));
879 }
880
881 static void
updateDirEnt_ModTimes(struct pcdir * dp)882 updateDirEnt_ModTimes(struct pcdir *dp)
883 {
884 timestruc_t ts;
885
886 getNow(&dp->pcd_mtime, NULL);
887 dp->pcd_ladate = dp->pcd_mtime.pct_date;
888 dp->pcd_attr |= PCA_ARCH;
889 markClusterModified(findImpactedCluster(dp));
890 }
891
892 struct pcdir *
addRootDirEnt(int fd,struct pcdir * new)893 addRootDirEnt(int fd, struct pcdir *new)
894 {
895 struct pcdir *added;
896 int32_t inCluster;
897
898 if ((added = findAvailableRootDirEntSlot(fd, &inCluster)) != NULL) {
899 insertDirEnt(added, new, inCluster);
900 return (added);
901 }
902 return (NULL);
903 }
904
905 /*
906 * FAT12 and FAT16 have a root directory outside the normal file space,
907 * so we have separate routines for finding and reading the root directory.
908 */
909 static off64_t
seekRootDirectory(int fd)910 seekRootDirectory(int fd)
911 {
912 off64_t seekto;
913
914 /*
915 * The RootDir immediately follows the FATs, which in
916 * turn immediately follow the reserved sectors.
917 */
918 seekto = (off64_t)TheBIOSParameterBlock.bpb.resv_sectors *
919 TheBIOSParameterBlock.bpb.bytes_per_sector +
920 (off64_t)FATSize * TheBIOSParameterBlock.bpb.num_fats +
921 (off64_t)PartitionOffset;
922 if (Verbose)
923 (void) fprintf(stderr,
924 gettext("Seeking root directory @%lld.\n"), seekto);
925 return (lseek64(fd, seekto, SEEK_SET));
926 }
927
928 void
getRootDirectory(int fd)929 getRootDirectory(int fd)
930 {
931 ssize_t bytesRead;
932
933 if (TheRootDir.bytes != NULL)
934 return;
935 else if ((TheRootDir.bytes = (uchar_t *)malloc(RootDirSize)) == NULL) {
936 mountSanityCheckFails();
937 perror(gettext("No memory for a copy of the root directory"));
938 (void) close(fd);
939 exit(8);
940 }
941
942 if (seekRootDirectory(fd) < 0) {
943 mountSanityCheckFails();
944 perror(gettext("Cannot seek to RootDir"));
945 (void) close(fd);
946 exit(8);
947 }
948
949 if (Verbose)
950 (void) fprintf(stderr,
951 gettext("Reading root directory.\n"));
952 if ((bytesRead = read(fd, TheRootDir.bytes, RootDirSize)) !=
953 RootDirSize) {
954 mountSanityCheckFails();
955 if (bytesRead < 0) {
956 perror(gettext("Cannot read a RootDir"));
957 } else {
958 (void) fprintf(stderr,
959 gettext("Short read of RootDir\n"));
960 }
961 (void) close(fd);
962 exit(8);
963 }
964 if (Verbose) {
965 (void) fprintf(stderr,
966 gettext("Dump of root dir's first 256 bytes.\n"));
967 header_for_dump();
968 dump_bytes(TheRootDir.bytes, 256);
969 }
970 }
971
972 void
writeRootDirMods(int fd)973 writeRootDirMods(int fd)
974 {
975 ssize_t bytesWritten;
976
977 if (!TheRootDir.bytes) {
978 (void) fprintf(stderr,
979 gettext("Internal error: No Root directory to write\n"));
980 (void) close(fd);
981 exit(12);
982 }
983 if (!RootDirModified) {
984 if (Verbose) {
985 (void) fprintf(stderr,
986 gettext("No root directory changes need to "
987 "be written.\n"));
988 }
989 return;
990 }
991 if (ReadOnly)
992 return;
993 if (Verbose)
994 (void) fprintf(stderr,
995 gettext("Writing root directory.\n"));
996 if (seekRootDirectory(fd) < 0) {
997 perror(gettext("Cannot write the RootDir (seek failed)"));
998 (void) close(fd);
999 exit(12);
1000 }
1001 if ((bytesWritten = write(fd, TheRootDir.bytes, RootDirSize)) !=
1002 RootDirSize) {
1003 if (bytesWritten < 0) {
1004 perror(gettext("Cannot write the RootDir"));
1005 } else {
1006 (void) fprintf(stderr,
1007 gettext("Short write of root directory\n"));
1008 }
1009 (void) close(fd);
1010 exit(12);
1011 }
1012 RootDirModified = 0;
1013 }
1014
1015 struct pcdir *
newDirEnt(struct pcdir * copyme)1016 newDirEnt(struct pcdir *copyme)
1017 {
1018 struct pcdir *ndp;
1019
1020 if ((ndp = (struct pcdir *)calloc(1, sizeof (struct pcdir))) == NULL) {
1021 (void) fprintf(stderr, gettext("Out of memory to create a "
1022 "new directory entry!\n"));
1023 return (ndp);
1024 }
1025 if (copyme)
1026 (void) memcpy(ndp, copyme, sizeof (struct pcdir));
1027 ndp->pcd_ext[CHKNAME_C] = 'C';
1028 ndp->pcd_ext[CHKNAME_H] = 'H';
1029 ndp->pcd_ext[CHKNAME_K] = 'K';
1030 updateDirEnt_CreatTime(ndp);
1031 updateDirEnt_ModTimes(ndp);
1032 return (ndp);
1033 }
1034
1035 void
updateDirEnt_Size(struct pcdir * dp,uint32_t newSize)1036 updateDirEnt_Size(struct pcdir *dp, uint32_t newSize)
1037 {
1038 uchar_t *p = (uchar_t *)&(dp->pcd_size);
1039 store_32_bits(&p, newSize);
1040 markClusterModified(findImpactedCluster(dp));
1041 }
1042
1043 void
updateDirEnt_Start(struct pcdir * dp,int32_t newStart)1044 updateDirEnt_Start(struct pcdir *dp, int32_t newStart)
1045 {
1046 uchar_t *p = (uchar_t *)&(dp->pcd_scluster_lo);
1047 store_16_bits(&p, newStart & 0xffff);
1048 if (IsFAT32) {
1049 p = (uchar_t *)&(dp->un.pcd_scluster_hi);
1050 store_16_bits(&p, newStart >> 16);
1051 }
1052 markClusterModified(findImpactedCluster(dp));
1053 }
1054
1055 void
updateDirEnt_Name(struct pcdir * dp,char * newName)1056 updateDirEnt_Name(struct pcdir *dp, char *newName)
1057 {
1058 int i;
1059
1060 for (i = 0; i < PCFNAMESIZE; i++) {
1061 if (*newName)
1062 dp->pcd_filename[i] = *newName++;
1063 else
1064 dp->pcd_filename[i] = ' ';
1065 }
1066 markClusterModified(findImpactedCluster(dp));
1067 }
1068