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