7812 Remove gender specific language
[unleashed.git] / usr / src / cmd / fs.d / pcfs / fsck / clusters.c
blob43225fea91e33cdfd5e9c8d2199b0649d7bf4d55
1 /*
2 * CDDL HEADER START
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License, Version 1.0 only
6 * (the "License"). You may not use this file except in compliance
7 * with the License.
9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10 * or http://www.opensolaris.org/os/licensing.
11 * See the License for the specific language governing permissions
12 * and limitations under the License.
14 * When distributing Covered Code, include this CDDL HEADER in each
15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16 * If applicable, add the following below this CDDL HEADER, with the
17 * fields enclosed by brackets "[]" replaced with your own identifying
18 * information: Portions Copyright [yyyy] [name of copyright owner]
20 * CDDL HEADER END
23 * Copyright (c) 1999,2000 by Sun Microsystems, Inc.
24 * All rights reserved.
25 * Copyright (c) 2016 by Delphix. All rights reserved.
28 #pragma ident "%Z%%M% %I% %E% SMI"
31 * fsck_pcfs -- routines for manipulating clusters.
33 #include <stdio.h>
34 #include <string.h>
35 #include <unistd.h>
36 #include <stdlib.h>
37 #include <libintl.h>
38 #include <errno.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"
46 extern ClusterContents TheRootDir;
47 extern off64_t FirstClusterOffset;
48 extern off64_t PartitionOffset;
49 extern int32_t BytesPerCluster;
50 extern int32_t TotalClusters;
51 extern int32_t LastCluster;
52 extern int32_t RootDirSize;
53 extern int32_t FATSize;
54 extern bpb_t TheBIOSParameterBlock;
55 extern short FATEntrySize;
56 extern int RootDirModified;
57 extern int OkayToRelink;
58 extern int ReadOnly;
59 extern int IsFAT32;
60 extern int Verbose;
62 static struct pcdir BlankPCDIR;
63 static CachedCluster *ClusterCache;
64 static ClusterInfo **InUse;
65 static int32_t ReservedClusterCount;
66 static int32_t AllocedClusterCount;
67 static int32_t FreeClusterCount;
68 static int32_t BadClusterCount;
71 * Internal statistics
73 static int32_t CachedClusterCount;
75 int32_t HiddenClusterCount;
76 int32_t FileClusterCount;
77 int32_t DirClusterCount;
78 int32_t HiddenFileCount;
79 int32_t FileCount;
80 int32_t DirCount;
82 static int32_t orphanSizeLookup(int32_t clusterNum);
84 static void
85 freeNameInfo(int32_t clusterNum)
87 /* silent failure for bogus clusters */
88 if (clusterNum < FIRST_CLUSTER || clusterNum > LastCluster)
89 return;
90 if (InUse[clusterNum - FIRST_CLUSTER]->path != NULL) {
91 if (InUse[clusterNum - FIRST_CLUSTER]->path->references > 1) {
92 InUse[clusterNum - FIRST_CLUSTER]->path->references--;
93 } else {
94 free(InUse[clusterNum - FIRST_CLUSTER]->path->fullName);
95 free(InUse[clusterNum - FIRST_CLUSTER]->path);
97 InUse[clusterNum - FIRST_CLUSTER]->path = NULL;
101 static void
102 printOrphanPath(int32_t clusterNum)
104 /* silent failure for bogus clusters */
105 if (clusterNum < FIRST_CLUSTER || clusterNum > LastCluster)
106 return;
107 if (InUse[clusterNum - FIRST_CLUSTER]->path != NULL) {
108 (void) printf(gettext("\nOrphaned allocation units originally "
109 "allocated to:\n"));
110 (void) printf("%s\n",
111 InUse[clusterNum - FIRST_CLUSTER]->path->fullName);
112 freeNameInfo(clusterNum);
113 } else {
114 (void) printf(gettext("\nOrphaned allocation units originally "
115 "allocated to an unknown file or directory:\n"));
116 (void) printf(gettext("Orphaned chain begins with allocation "
117 "unit %d.\n"), clusterNum);
121 static void
122 printOrphanSize(int32_t clusterNum)
124 int32_t size = orphanSizeLookup(clusterNum);
126 if (size > 0) {
127 (void) printf(gettext("%d bytes in the orphaned chain of "
128 "allocation units.\n"), size);
129 if (Verbose) {
130 (void) printf(gettext("[Starting at allocation "
131 "unit %d]\n"), clusterNum);
136 static void
137 printOrphanInfo(int32_t clusterNum)
139 printOrphanPath(clusterNum);
140 printOrphanSize(clusterNum);
143 static int
144 askAboutFreeing(int32_t clusterNum)
147 * If it is not OkayToRelink, we haven't already printed the size
148 * of the orphaned chain.
150 if (!OkayToRelink)
151 printOrphanInfo(clusterNum);
153 * If we are in preen mode, preenBail won't return.
155 preenBail("Need user confirmation to free orphaned chain.\n");
157 (void) printf(
158 gettext("Free the allocation units in the orphaned chain ? "
159 "(y/n) "));
160 return (yes());
163 static int
164 askAboutRelink(int32_t clusterNum)
167 * Display the size of the chain for the user to consider.
169 printOrphanInfo(clusterNum);
171 * If we are in preen mode, preenBail won't return.
173 preenBail("Need user confirmation to re-link orphaned chain.\n");
175 (void) printf(gettext("Re-link orphaned chain into file system ? "
176 "(y/n) "));
178 return (yes());
181 static int
182 isHidden(int32_t clusterNum)
184 /* silent failure for bogus clusters */
185 if (clusterNum < FIRST_CLUSTER || clusterNum > LastCluster)
186 return (0);
188 if (InUse[clusterNum - FIRST_CLUSTER] == NULL)
189 return (0);
191 return (InUse[clusterNum - FIRST_CLUSTER]->flags & CLINFO_HIDDEN);
194 static int
195 isInUse(int32_t clusterNum)
197 /* silent failure for bogus clusters */
198 if (clusterNum < FIRST_CLUSTER || clusterNum > LastCluster)
199 return (0);
201 return ((InUse[clusterNum - FIRST_CLUSTER] != NULL) &&
202 (InUse[clusterNum - FIRST_CLUSTER]->dirent != NULL));
206 * Caller's may request that we cache the data from a readCluster.
207 * The xxxClusterxxxCachexxx routines handle looking for cached data
208 * or initially caching the data.
210 * XXX - facilitate releasing cached data for low memory situations.
212 static CachedCluster *
213 findClusterCacheEntry(int32_t clusterNum)
215 CachedCluster *loop = ClusterCache;
217 while (loop != NULL) {
218 if (loop->clusterNum == clusterNum)
219 return (loop);
220 loop = loop->next;
222 return (NULL);
225 static uchar_t *
226 findClusterDataInTheCache(int32_t clusterNum)
228 CachedCluster *loop = ClusterCache;
230 while (loop) {
231 if (loop->clusterNum == clusterNum)
232 return (loop->clusterData.bytes);
233 loop = loop->next;
235 return (NULL);
238 static uchar_t *
239 addToCache(int32_t clusterNum, uchar_t *buf, int32_t *datasize)
241 CachedCluster *new;
242 uchar_t *cp;
244 if ((new = (CachedCluster *)malloc(sizeof (CachedCluster))) == NULL) {
245 perror(gettext("No memory for cached cluster info"));
246 return (buf);
248 new->clusterNum = clusterNum;
249 new->modified = 0;
251 if ((cp = (uchar_t *)calloc(1, BytesPerCluster)) == NULL) {
252 perror(gettext("No memory for cached copy of cluster"));
253 free(new);
254 return (buf);
256 (void) memcpy(cp, buf, *datasize);
257 new->clusterData.bytes = cp;
259 if (Verbose) {
260 (void) fprintf(stderr,
261 gettext("Allocation unit %d cached.\n"), clusterNum);
263 if (ClusterCache == NULL) {
264 ClusterCache = new;
265 new->next = NULL;
266 } else if (new->clusterNum < ClusterCache->clusterNum) {
267 new->next = ClusterCache;
268 ClusterCache = new;
269 } else {
270 CachedCluster *loop = ClusterCache;
271 CachedCluster *trailer = NULL;
273 while (loop && new->clusterNum > loop->clusterNum) {
274 trailer = loop;
275 loop = loop->next;
277 trailer->next = new;
278 if (loop) {
279 new->next = loop;
280 } else {
281 new->next = NULL;
284 CachedClusterCount++;
285 return (new->clusterData.bytes);
288 static int
289 seekCluster(int fd, int32_t clusterNum)
291 off64_t seekto;
292 int saveError;
294 seekto = FirstClusterOffset +
295 ((off64_t)clusterNum - FIRST_CLUSTER) * BytesPerCluster;
296 if (lseek64(fd, seekto, SEEK_SET) != seekto) {
297 saveError = errno;
298 (void) fprintf(stderr,
299 gettext("Seek to Allocation unit #%d failed: "),
300 clusterNum);
301 (void) fprintf(stderr, strerror(saveError));
302 (void) fprintf(stderr, "\n");
303 return (0);
305 return (1);
309 * getcluster
310 * Get cluster bytes off the disk. We always read those bytes into
311 * the same static buffer. If the caller wants its own copy of the
312 * data it'll have to make its own copy. We'll return all the data
313 * read, even if it's short of a full cluster. This is for future use
314 * when we might want to relocate any salvagable data from bad clusters.
316 static int
317 getCluster(int fd, int32_t clusterNum, uchar_t **data, int32_t *datasize)
319 static uchar_t *clusterBuffer = NULL;
320 int saveError;
321 int try;
323 *datasize = 0;
324 *data = NULL;
326 if (clusterNum < FIRST_CLUSTER || clusterNum > LastCluster)
327 return (RDCLUST_BADINPUT);
329 if (clusterBuffer == NULL &&
330 (clusterBuffer = (uchar_t *)malloc(BytesPerCluster)) == NULL) {
331 perror(gettext("No memory for a cluster data buffer"));
332 return (RDCLUST_MEMERR);
335 for (try = 0; try < RDCLUST_MAX_RETRY; try++) {
336 if (!seekCluster(fd, clusterNum))
337 return (RDCLUST_FAIL);
338 if ((*datasize = read(fd, clusterBuffer, BytesPerCluster)) ==
339 BytesPerCluster) {
340 *data = clusterBuffer;
341 return (RDCLUST_GOOD);
344 if (*datasize >= 0) {
345 *data = clusterBuffer;
346 (void) fprintf(stderr,
347 gettext("Short read of allocation unit #%d\n"), clusterNum);
348 } else {
349 saveError = errno;
350 (void) fprintf(stderr, "Allocation unit %d:", clusterNum);
351 (void) fprintf(stderr, strerror(saveError));
352 (void) fprintf(stderr, "\n");
354 return (RDCLUST_FAIL);
357 static void
358 writeCachedCluster(int fd, CachedCluster *clustInfo)
360 ssize_t bytesWritten;
362 if (ReadOnly)
363 return;
365 if (Verbose)
366 (void) fprintf(stderr,
367 gettext("Allocation unit %d modified.\n"),
368 clustInfo->clusterNum);
370 if (seekCluster(fd, clustInfo->clusterNum) == NULL)
371 return;
373 if ((bytesWritten = write(fd, clustInfo->clusterData.bytes,
374 BytesPerCluster)) != BytesPerCluster) {
375 if (bytesWritten < 0) {
376 perror(gettext("Failed to write modified "
377 "allocation unit"));
378 } else {
379 (void) fprintf(stderr,
380 gettext("Short write of allocation unit %d\n"),
381 clustInfo->clusterNum);
383 (void) close(fd);
384 exit(13);
389 * It's cheaper to allocate a lot at a time; malloc overhead pushes
390 * you over the brink much more quickly if you don't.
391 * This numbers seems to be a fair trade-off between reduced malloc overhead
392 * and additional overhead by over-allocating.
395 #define CHUNKSIZE 1024
397 static ClusterInfo *pool;
399 static ClusterInfo *
400 newClusterInfo(void)
403 ClusterInfo *ret;
405 if (pool == NULL) {
406 int i;
408 pool = (ClusterInfo *)malloc(sizeof (ClusterInfo) * CHUNKSIZE);
410 if (pool == NULL) {
411 perror(
412 gettext("Out of memory for cluster information"));
413 exit(9);
416 for (i = 0; i < CHUNKSIZE - 1; i++)
417 pool[i].nextfree = &pool[i+1];
419 pool[CHUNKSIZE-1].nextfree = NULL;
421 ret = pool;
422 pool = pool->nextfree;
424 memset(ret, 0, sizeof (*ret));
426 return (ret);
429 /* Should be called with verified arguments */
431 static ClusterInfo *
432 cloneClusterInfo(int32_t clusterNum)
434 ClusterInfo *cl = InUse[clusterNum - FIRST_CLUSTER];
436 if (cl->refcnt > 1) {
437 ClusterInfo *newCl = newClusterInfo();
438 cl->refcnt--;
439 *newCl = *cl;
440 newCl->refcnt = 1;
441 if (newCl->path)
442 newCl->path->references++;
443 InUse[clusterNum - FIRST_CLUSTER] = newCl;
445 return (InUse[clusterNum - FIRST_CLUSTER]);
448 static void
449 updateFlags(int32_t clusterNum, int newflags)
451 ClusterInfo *cl = InUse[clusterNum - FIRST_CLUSTER];
453 if (cl->flags != newflags && cl->refcnt > 1)
454 cl = cloneClusterInfo(clusterNum);
456 cl->flags = newflags;
459 static void
460 freeClusterInfo(ClusterInfo *old)
462 if (--old->refcnt <= 0) {
463 if (old->path && --old->path->references <= 0) {
464 free(old->path->fullName);
465 free(old->path);
467 old->nextfree = pool;
468 pool = old;
473 * Allocate entries in our sparse array of cluster information.
474 * Returns non-zero if the structure already has been allocated
475 * (for those keeping score at home).
477 * The template parameter, if non-NULL, is used to facilitate sharing
478 * the ClusterInfo nodes for the clusters belonging to the same file.
479 * The first call to allocInUse for a new file should have *template
480 * set to 0; on return, *template then points to the newly allocated
481 * ClusterInfo. Second and further calls keep the same value
482 * in *template and that ClusterInfo ndoe is then used for all
483 * entries in the file. Code that modifies the ClusterInfo nodes
484 * should take care proper sharing semantics are maintained (i.e.,
485 * copy-on-write using cloneClusterInfo())
487 * The ClusterInfo used in the template is guaranted to be in use in
488 * at least one other cluster as we never return a value if we didn't
489 * set it first. So we can overwrite it without the possibility of a leak.
491 static int
492 allocInUse(int32_t clusterNum, ClusterInfo **template)
494 ClusterInfo *newCl;
496 if (InUse[clusterNum - FIRST_CLUSTER] != NULL)
497 return (CLINFO_PREVIOUSLY_ALLOCED);
499 if (template != NULL && *template != NULL)
500 newCl = *template;
501 else {
502 newCl = newClusterInfo();
503 if (template)
504 *template = newCl;
507 InUse[clusterNum - FIRST_CLUSTER] = newCl;
508 newCl->refcnt++;
510 return (CLINFO_NEWLY_ALLOCED);
513 static void
514 markFree(int32_t clusterNum)
516 /* silent failure for bogus clusters */
517 if (clusterNum < FIRST_CLUSTER || clusterNum > LastCluster)
518 return;
520 if (InUse[clusterNum - FIRST_CLUSTER]) {
521 if (InUse[clusterNum - FIRST_CLUSTER]->saved)
522 free(InUse[clusterNum - FIRST_CLUSTER]->saved);
523 freeClusterInfo(InUse[clusterNum - FIRST_CLUSTER]);
524 InUse[clusterNum - FIRST_CLUSTER] = NULL;
528 static void
529 markOrphan(int fd, int32_t clusterNum, struct pcdir *dp)
531 /* silent failure for bogus clusters */
532 if (clusterNum < FIRST_CLUSTER || clusterNum > LastCluster)
533 return;
535 (void) markInUse(fd, clusterNum, dp, NULL, 0, VISIBLE, NULL);
536 if (InUse[clusterNum - FIRST_CLUSTER] != NULL)
537 updateFlags(clusterNum,
538 InUse[clusterNum - FIRST_CLUSTER]->flags | CLINFO_ORPHAN);
541 static void
542 markBad(int32_t clusterNum, uchar_t *recovered, int32_t recoveredLen)
544 /* silent failure for bogus clusters */
545 if (clusterNum < FIRST_CLUSTER || clusterNum > LastCluster)
546 return;
548 (void) allocInUse(clusterNum, NULL);
550 if (recoveredLen) {
551 (void) cloneClusterInfo(clusterNum);
552 InUse[clusterNum - FIRST_CLUSTER]->saved = recovered;
554 updateFlags(clusterNum,
555 InUse[clusterNum - FIRST_CLUSTER]->flags | CLINFO_BAD);
557 BadClusterCount++;
558 if (Verbose)
559 (void) fprintf(stderr,
560 gettext("Allocation unit %d marked bad.\n"), clusterNum);
563 static void
564 clearOrphan(int32_t c)
566 /* silent failure for bogus clusters */
567 if (c < FIRST_CLUSTER || c > LastCluster)
568 return;
569 if (InUse[c - FIRST_CLUSTER] != NULL)
570 updateFlags(c,
571 InUse[c - FIRST_CLUSTER]->flags & ~CLINFO_ORPHAN);
574 static void
575 clearInUse(int32_t c)
577 ClusterInfo **clp;
579 /* silent failure for bogus clusters */
580 if (c < FIRST_CLUSTER || c > LastCluster)
581 return;
583 clp = &InUse[c - FIRST_CLUSTER];
584 if (*clp != NULL) {
585 freeClusterInfo(*clp);
586 *clp = NULL;
590 static void
591 clearAllClusters_InUse()
593 int32_t cc;
594 for (cc = FIRST_CLUSTER; cc < LastCluster; cc++) {
595 clearInUse(cc);
599 static void
600 makeUseTable(void)
602 if (InUse != NULL) {
603 clearAllClusters_InUse();
604 return;
606 if ((InUse = (ClusterInfo **)
607 calloc(TotalClusters, sizeof (ClusterInfo *))) == NULL) {
608 perror(gettext("No memory for internal table"));
609 exit(9);
613 static void
614 countClusters(void)
616 int32_t c;
618 BadClusterCount = HiddenClusterCount =
619 AllocedClusterCount = FreeClusterCount = 0;
621 for (c = FIRST_CLUSTER; c < LastCluster; c++) {
622 if (badInFAT(c)) {
623 BadClusterCount++;
624 } else if (isMarkedBad(c)) {
626 * This catches the bad sectors found
627 * during thorough verify that have never been
628 * allocated to a file. Without this check, we
629 * count these guys as free.
631 BadClusterCount++;
632 markBadInFAT(c);
633 } else if (isHidden(c)) {
634 HiddenClusterCount++;
635 } else if (isInUse(c)) {
636 AllocedClusterCount++;
637 } else {
638 FreeClusterCount++;
644 * summarizeFAT
645 * Mark orphans without directory entries as allocated.
646 * XXX - these chains should be reclaimed!
647 * XXX - merge this routine with countClusters (same loop, duh.)
649 static void
650 summarizeFAT(int fd)
652 int32_t c;
653 ClusterInfo *tmpl = NULL;
655 for (c = FIRST_CLUSTER; c < LastCluster; c++) {
656 if (!freeInFAT(c) && !badInFAT(c) && !reservedInFAT(c) &&
657 !isInUse(c)) {
658 (void) markInUse(fd, c, &BlankPCDIR, NULL, 0, VISIBLE,
659 &tmpl);
664 static void
665 getReadyToSearch(int fd)
667 getFAT(fd);
668 if (!IsFAT32)
669 getRootDirectory(fd);
673 static char PathName[MAXPATHLEN];
675 static void
676 summarize(int fd, int includeFAT)
678 struct pcdir *ignorep1, *ignorep2 = NULL;
679 int32_t ignore32;
680 char ignore;
681 int pathlen;
683 ReservedClusterCount = 0;
684 AllocedClusterCount = 0;
685 HiddenClusterCount = 0;
686 FileClusterCount = 0;
687 FreeClusterCount = 0;
688 DirClusterCount = 0;
689 BadClusterCount = 0;
690 HiddenFileCount = 0;
691 FileCount = 0;
692 DirCount = 0;
693 ignorep1 = ignorep2 = NULL;
694 ignore = '\0';
696 PathName[0] = '\0';
697 pathlen = 0;
699 getReadyToSearch(fd);
701 * Traverse the full meta-data tree to talley what clusters
702 * are in use. The root directory is an area outside of the
703 * file space on FAT12 and FAT16 file systems. On FAT32 file
704 * systems, the root directory is in a file area cluster just
705 * like any other directory.
707 if (!IsFAT32) {
708 traverseFromRoot(fd, 0, PCFS_VISIT_SUBDIRS, PCFS_TRAVERSE_ALL,
709 ignore, &ignorep1, &ignore32, &ignorep2, PathName,
710 &pathlen);
711 } else {
712 DirCount++;
713 traverseDir(fd, TheBIOSParameterBlock.bpb32.root_dir_clust,
714 0, PCFS_VISIT_SUBDIRS, PCFS_TRAVERSE_ALL, ignore,
715 &ignorep1, &ignore32, &ignorep2, PathName, &pathlen);
718 if (includeFAT)
719 summarizeFAT(fd);
720 countClusters();
724 isMarkedBad(int32_t clusterNum)
726 /* silent failure for bogus clusters */
727 if (clusterNum < FIRST_CLUSTER || clusterNum > LastCluster)
728 return (0);
730 if (InUse[clusterNum - FIRST_CLUSTER] == NULL)
731 return (0);
733 return (InUse[clusterNum - FIRST_CLUSTER]->flags & CLINFO_BAD);
736 static int
737 isMarkedOrphan(int32_t clusterNum)
739 /* silent failure for bogus clusters */
740 if (clusterNum < FIRST_CLUSTER || clusterNum > LastCluster)
741 return (0);
743 if (InUse[clusterNum - FIRST_CLUSTER] == NULL)
744 return (0);
746 return (InUse[clusterNum - FIRST_CLUSTER]->flags & CLINFO_ORPHAN);
749 static void
750 orphanChain(int fd, int32_t c, struct pcdir *ndp)
752 ClusterInfo *tmpl = NULL;
754 /* silent failure for bogus clusters */
755 if (c < FIRST_CLUSTER || c > LastCluster)
756 return;
757 clearInUse(c);
758 markOrphan(fd, c, ndp);
759 c = nextInChain(c);
760 while (c != 0) {
761 clearInUse(c);
762 clearOrphan(c);
763 (void) markInUse(fd, c, ndp, NULL, 0, VISIBLE, &tmpl);
764 c = nextInChain(c);
768 static int32_t
769 findAFreeCluster(int32_t startAt)
771 int32_t look = startAt;
773 for (;;) {
774 if (freeInFAT(look)) {
775 break;
777 if (look == LastCluster)
778 look = FIRST_CLUSTER;
779 else
780 look++;
781 if (look == startAt)
782 break;
784 if (look != startAt)
785 return (look);
786 else
787 return (0);
790 static void
791 setEndOfDirectory(struct pcdir *dp)
793 dp->pcd_filename[0] = PCD_UNUSED;
796 static void
797 emergencyEndOfDirectory(int fd, int32_t secondToLast)
799 ClusterContents dirdata;
800 int32_t dirdatasize = 0;
802 if (readCluster(fd, secondToLast, &(dirdata.bytes), &dirdatasize,
803 RDCLUST_DO_CACHE) != RDCLUST_GOOD) {
804 (void) fprintf(stderr,
805 gettext("Unable to read allocation unit %d.\n"),
806 secondToLast);
807 (void) fprintf(stderr,
808 gettext("Cannot allocate a new allocation unit to hold an"
809 " end-of-directory marker.\nCannot access allocation unit"
810 " to overwrite existing directory entry with\nthe marker."
811 " Needed directory truncation has failed. Giving up.\n"));
812 (void) close(fd);
813 exit(11);
815 setEndOfDirectory(dirdata.dirp);
816 markClusterModified(secondToLast);
819 static void
820 makeNewEndOfDirectory(struct pcdir *entry, int32_t secondToLast,
821 int32_t newCluster, ClusterContents *newData)
823 setEndOfDirectory(newData->dirp);
824 markClusterModified(newCluster);
826 * There are two scenarios. One is that we truncated the
827 * directory in the very beginning. The other is that we
828 * truncated it in the middle or at the end. In the first
829 * scenario, the secondToLast argument is not a valid cluster
830 * (it's zero), and so we actually need to change the start
831 * cluster for the directory to this new start cluster. In
832 * the second scenario, the secondToLast cluster we received
833 * as an argument needs to be pointed at the new end of
834 * directory.
836 if (secondToLast == 0) {
837 updateDirEnt_Start(entry, newCluster);
838 } else {
839 writeFATEntry(secondToLast, newCluster);
841 markLastInFAT(newCluster);
844 static void
845 createNewEndOfDirectory(int fd, struct pcdir *entry, int32_t secondToLast)
847 ClusterContents dirdata;
848 int32_t dirdatasize = 0;
849 int32_t freeCluster;
851 if (((freeCluster = findAFreeCluster(secondToLast)) != 0)) {
852 if (readCluster(fd, freeCluster, &(dirdata.bytes),
853 &dirdatasize, RDCLUST_DO_CACHE) == RDCLUST_GOOD) {
854 if (Verbose) {
855 (void) fprintf(stderr,
856 gettext("Grabbed allocation unit #%d "
857 "for truncated\ndirectory's new end "
858 "of directory.\n"), freeCluster);
860 makeNewEndOfDirectory(entry, secondToLast,
861 freeCluster, &dirdata);
862 return;
865 if (secondToLast == 0) {
866 if (freeCluster == 0) {
867 (void) fprintf(stderr, gettext("File system full.\n"));
868 } else {
869 (void) fprintf(stderr,
870 gettext("Unable to read allocation unit %d.\n"),
871 freeCluster);
873 (void) fprintf(stderr,
874 gettext("Cannot allocate a new allocation unit to hold "
875 "an end-of-directory marker.\nNo existing directory "
876 "entries can be overwritten with the marker,\n"
877 "the only unit allocated to the directory is "
878 "inaccessible.\nNeeded directory truncation has failed. "
879 "Giving up.\n"));
880 (void) close(fd);
881 exit(11);
883 emergencyEndOfDirectory(fd, secondToLast);
887 * truncAtCluster
888 * Given a directory entry and a cluster number, search through
889 * the cluster chain for the entry and make the cluster previous
890 * to the given cluster in the chain the last cluster in the file.
891 * The number of orphaned bytes is returned. For a chain that's
892 * a directory we need to do some special handling, since we'll be
893 * getting rid of the end of directory notice by truncating.
895 static int64_t
896 truncAtCluster(int fd, struct pcdir *entry, int32_t cluster)
898 uint32_t oldSize, newSize;
899 int32_t prev, count, follow;
900 int dir = (entry->pcd_attr & PCA_DIR);
902 prev = 0; count = 0;
903 follow = extractStartCluster(entry);
904 while (follow != cluster && follow >= FIRST_CLUSTER &&
905 follow <= LastCluster) {
906 prev = follow;
907 count++;
908 follow = nextInChain(follow);
910 if (follow != cluster) {
912 * We didn't find the cluster they wanted to trunc at
913 * anywhere in the entry's chain. So we'll leave the
914 * entry alone, and return a negative value so they
915 * can know something is wrong.
917 return (-1);
919 if (Verbose) {
920 (void) fprintf(stderr,
921 gettext("Chain truncation at unit #%d\n"), cluster);
923 if (!dir) {
924 oldSize = extractSize(entry);
925 newSize = count *
926 TheBIOSParameterBlock.bpb.sectors_per_cluster *
927 TheBIOSParameterBlock.bpb.bytes_per_sector;
928 if (newSize == 0)
929 updateDirEnt_Start(entry, 0);
930 } else {
931 newSize = 0;
933 updateDirEnt_Size(entry, newSize);
934 if (dir) {
935 createNewEndOfDirectory(fd, entry, prev);
936 } else if (prev != 0) {
937 markLastInFAT(prev);
939 if (dir) {
941 * We don't really know what the size of a directory is
942 * but it is important for us to know if this truncation
943 * results in an orphan with any size. The value we
944 * return from this routine for a normal file is the
945 * number of bytes left in the chain. For a directory
946 * we can't be exact, and the caller doesn't really
947 * expect us to be. For a directory the caller only
948 * cares if there are zero bytes left or more than
949 * zero bytes left. We'll return 1 to indicate
950 * more than zero.
952 if ((follow = nextInChain(follow)) != 0)
953 return (1);
954 else
955 return (0);
958 * newSize should always be smaller than the old one, since
959 * we are decreasing the number of clusters allocated to the file.
961 return ((int64_t)oldSize - (int64_t)newSize);
964 static struct pcdir *
965 updateOrphanedChainMetadata(int fd, struct pcdir *dp, int32_t endCluster,
966 int isBad)
968 struct pcdir *ndp = NULL;
969 int64_t remainder;
970 char *newName = NULL;
971 int chosenName;
972 int dir = (dp->pcd_attr & PCA_DIR);
975 * If the truncation fails, (which ought not to happen),
976 * there's no need to go any further, we just return
977 * a null value for the new directory entry pointer.
979 remainder = truncAtCluster(fd, dp, endCluster);
980 if (remainder < 0)
981 return (ndp);
982 if (!dir && isBad) {
984 * Subtract out the bad cluster from the remaining size
985 * We always assume the cluster being deleted from the
986 * file is full size, but that might not be the case
987 * for the last cluster of the file, so that is why
988 * we check for negative remainder value.
990 remainder -= TheBIOSParameterBlock.bpb.sectors_per_cluster *
991 TheBIOSParameterBlock.bpb.bytes_per_sector;
992 if (remainder < 0)
993 remainder = 0;
996 * Build a new directory entry for the rest of the chain.
997 * Later, if the user okays it, we'll link this entry into the
998 * root directory. The new entry will start out as a
999 * copy of the truncated entry.
1001 if ((remainder != 0) &&
1002 ((newName = nextAvailableCHKName(&chosenName)) != NULL) &&
1003 ((ndp = newDirEnt(dp)) != NULL)) {
1004 if (Verbose) {
1005 if (dir)
1006 (void) fprintf(stderr,
1007 gettext("Orphaned directory chain.\n"));
1008 else
1009 (void) fprintf(stderr,
1010 gettext("Orphaned chain, %u bytes.\n"),
1011 (uint32_t)remainder);
1013 if (!dir)
1014 updateDirEnt_Size(ndp, (uint32_t)remainder);
1015 if (isBad)
1016 updateDirEnt_Start(ndp, nextInChain(endCluster));
1017 else
1018 updateDirEnt_Start(ndp, endCluster);
1019 updateDirEnt_Name(ndp, newName);
1020 addEntryToCHKList(chosenName);
1022 return (ndp);
1026 * splitChain()
1028 * split a cluster allocation chain into two cluster chains
1029 * around a given cluster (problemCluster). This results in two
1030 * separate directory entries; the original (dp), and one we hope
1031 * to create and return a pointer to to the caller (*newdp).
1032 * This second entry is the orphan chain, and it may end up in
1033 * the root directory as a FILEnnnn.CHK file. We also return the
1034 * starting cluster of the orphan chain to the caller (*orphanStart).
1036 void
1037 splitChain(int fd, struct pcdir *dp, int32_t problemCluster,
1038 struct pcdir **newdp, int32_t *orphanStart)
1040 struct pcdir *ndp = NULL;
1041 int isBad = isMarkedBad(problemCluster);
1043 ndp = updateOrphanedChainMetadata(fd, dp, problemCluster, isBad);
1044 *newdp = ndp;
1045 clearInUse(problemCluster);
1046 if (isBad) {
1047 clearOrphan(problemCluster);
1048 *orphanStart = nextInChain(problemCluster);
1049 orphanChain(fd, *orphanStart, ndp);
1050 markBadInFAT(problemCluster);
1051 } else {
1052 *orphanStart = problemCluster;
1053 orphanChain(fd, problemCluster, ndp);
1058 * freeOrphan
1060 * User has requested that an orphaned cluster chain be freed back
1061 * into the file area.
1063 static void
1064 freeOrphan(int32_t c)
1066 int32_t n;
1069 * Free the directory entry we explicitly created for
1070 * the orphaned clusters.
1072 if (InUse[c - FIRST_CLUSTER]->dirent != NULL)
1073 free(InUse[c - FIRST_CLUSTER]->dirent);
1075 * Then mark the clusters themselves as available.
1077 do {
1078 n = nextInChain(c);
1079 markFreeInFAT(c);
1080 markFree(c);
1081 c = n;
1082 } while (c != 0);
1086 * Rewrite the InUse field for a cluster chain. Can be used on a partial
1087 * chain if provided with a stopAtCluster.
1089 static void
1090 redoInUse(int fd, int32_t c, struct pcdir *ndp, int32_t stopAtCluster)
1092 while (c && c != stopAtCluster) {
1093 clearInUse(c);
1094 (void) markInUse(fd, c, ndp, NULL, 0, VISIBLE, NULL);
1095 c = nextInChain(c);
1099 static struct pcdir *
1100 orphanDirEntLookup(int32_t clusterNum)
1102 if (clusterNum < FIRST_CLUSTER || clusterNum > LastCluster)
1103 return (NULL);
1105 if (isInUse(clusterNum)) {
1106 return (InUse[clusterNum - FIRST_CLUSTER]->dirent);
1107 } else {
1108 return (NULL);
1112 static int32_t
1113 orphanSizeLookup(int32_t clusterNum)
1115 /* silent failure for bogus clusters */
1116 if (clusterNum < FIRST_CLUSTER || clusterNum > LastCluster)
1117 return (-1);
1119 if (isInUse(clusterNum)) {
1120 return (extractSize(InUse[clusterNum - FIRST_CLUSTER]->dirent));
1121 } else {
1122 return (-1);
1127 * linkOrphan
1129 * User has requested that an orphaned cluster chain be brought back
1130 * into the file system. So we have to make a new directory entry
1131 * in the root directory and point it at the cluster chain.
1133 static void
1134 linkOrphan(int fd, int32_t start)
1136 struct pcdir *newEnt = NULL;
1137 struct pcdir *dp;
1139 if ((dp = orphanDirEntLookup(start)) != NULL) {
1140 newEnt = addRootDirEnt(fd, dp);
1141 } else {
1142 (void) printf(gettext("Re-link of orphaned chain failed."
1143 " Allocation units will remain orphaned.\n"));
1146 * A cluster isn't really InUse() unless it is referenced,
1147 * so if newEnt is NULL here, we are in effect using markInUse()
1148 * to note that the cluster is NOT in use.
1150 redoInUse(fd, start, newEnt, 0);
1154 * relinkCreatedOrphans
1156 * While marking clusters as bad, we can create orphan cluster
1157 * chains. Since we were the ones doing the marking, we were able to
1158 * keep track of the orphans we created. Now we want to go through
1159 * all those chains and either get them back into the file system or
1160 * free them depending on the user's input.
1162 static void
1163 relinkCreatedOrphans(int fd)
1165 int32_t c;
1167 for (c = FIRST_CLUSTER; c < LastCluster; c++) {
1168 if (isMarkedOrphan(c)) {
1169 if (OkayToRelink && askAboutRelink(c)) {
1170 linkOrphan(fd, c);
1171 } else if (askAboutFreeing(c)) {
1172 freeOrphan(c);
1174 clearOrphan(c);
1180 * relinkFATOrphans
1182 * We want to find orphans not represented in the meta-data.
1183 * These are chains marked in the FAT as being in use but
1184 * not referenced anywhere by any directory entries.
1185 * We'll go through the whole FAT and mark the first cluster
1186 * in any such chain as an orphan. Then we can just use
1187 * the relinkCreatedOrphans routine to get them back into the
1188 * file system or free'ed depending on the user's input.
1190 static void
1191 relinkFATOrphans(int fd)
1193 struct pcdir *ndp = NULL;
1194 int32_t cc, c, n;
1195 int32_t bpc, newSize;
1196 char *newName;
1197 int chosenName;
1199 for (c = FIRST_CLUSTER; c < LastCluster; c++) {
1200 if (freeInFAT(c) || badInFAT(c) ||
1201 reservedInFAT(c) || isInUse(c))
1202 continue;
1203 cc = 1;
1204 n = c;
1205 while (n = nextInChain(n))
1206 cc++;
1207 bpc = TheBIOSParameterBlock.bpb.sectors_per_cluster *
1208 TheBIOSParameterBlock.bpb.bytes_per_sector;
1209 newSize = cc * bpc;
1210 if (((newName = nextAvailableCHKName(&chosenName)) != NULL) &&
1211 ((ndp = newDirEnt(NULL)) != NULL)) {
1212 updateDirEnt_Size(ndp, newSize);
1213 updateDirEnt_Start(ndp, c);
1214 updateDirEnt_Name(ndp, newName);
1215 addEntryToCHKList(chosenName);
1217 orphanChain(fd, c, ndp);
1219 relinkCreatedOrphans(fd);
1222 static void
1223 relinkOrphans(int fd)
1225 relinkCreatedOrphans(fd);
1226 relinkFATOrphans(fd);
1229 static void
1230 checkForFATLoop(int32_t clusterNum)
1232 int32_t prev = clusterNum;
1233 int32_t follow;
1235 if (clusterNum < FIRST_CLUSTER || clusterNum > LastCluster)
1236 return;
1238 follow = nextInChain(clusterNum);
1239 while (follow != clusterNum && follow >= FIRST_CLUSTER &&
1240 follow <= LastCluster) {
1241 prev = follow;
1242 follow = nextInChain(follow);
1244 if (follow == clusterNum) {
1246 * We found a loop. Eradicate it by changing
1247 * the last cluster in the loop to be last
1248 * in the chain instead instead of pointing
1249 * back to the first cluster.
1251 markLastInFAT(prev);
1255 static void
1256 sharedChainError(int fd, int32_t clusterNum, struct pcdir *badEntry)
1259 * If we have shared clusters, it is either because the
1260 * cluster somehow got assigned to multiple files and/or
1261 * because of a loop in the cluster chain. In either
1262 * case we want to truncate the offending file at the
1263 * cluster of contention. Then, we will want to run
1264 * through the remainder of the chain. If we find ourselves
1265 * back at the top, we will know there is a loop in the
1266 * FAT we need to remove.
1268 if (Verbose)
1269 (void) fprintf(stderr,
1270 gettext("Truncating chain due to duplicate allocation of "
1271 "unit %d.\n"), clusterNum);
1273 * Note that we don't orphan anything here, because the duplicate
1274 * part of the chain may be part of another valid chain.
1276 (void) truncAtCluster(fd, badEntry, clusterNum);
1277 checkForFATLoop(clusterNum);
1280 void
1281 truncChainWithBadCluster(int fd, struct pcdir *dp, int32_t startCluster)
1283 struct pcdir *orphanEntry;
1284 int32_t orphanStartCluster;
1285 int32_t c = startCluster;
1287 while (c != 0) {
1288 if (isMarkedBad(c)) {
1290 * splitChain() truncates the current guy and
1291 * then makes an orphan chain out of the remaining
1292 * clusters. When we come back from the split
1293 * we'll want to continue looking for bad clusters
1294 * in the orphan chain.
1296 splitChain(fd, dp, c,
1297 &orphanEntry, &orphanStartCluster);
1299 * There is a chance that we weren't able or weren't
1300 * required to make a directory entry for the
1301 * remaining clusters. In that case we won't go
1302 * on, because we couldn't make any more splits
1303 * anyway.
1305 if (orphanEntry == NULL)
1306 break;
1307 c = orphanStartCluster;
1308 dp = orphanEntry;
1309 continue;
1311 c = nextInChain(c);
1315 int32_t
1316 nextInChain(int32_t currentCluster)
1318 int32_t nextCluster;
1320 /* silent failure for bogus clusters */
1321 if (currentCluster < FIRST_CLUSTER || currentCluster > LastCluster)
1322 return (0);
1325 * Look up FAT entry of next link in cluster chain,
1326 * if this one is the last one return 0 as the next link.
1328 nextCluster = readFATEntry(currentCluster);
1329 if (nextCluster < FIRST_CLUSTER || nextCluster > LastCluster)
1330 return (0);
1332 return (nextCluster);
1336 * findImpactedCluster
1338 * Called when someone modifies what they believe might be a cached
1339 * cluster entry, but when they only have a directory entry pointer
1340 * and not the cluster number. We have to go dig up what cluster
1341 * they are modifying.
1343 int32_t
1344 findImpactedCluster(struct pcdir *modified)
1346 CachedCluster *loop;
1348 * Check to see if it's in the root directory first
1350 if (!IsFAT32 && ((uchar_t *)modified >= TheRootDir.bytes) &&
1351 ((uchar_t *)modified < TheRootDir.bytes + RootDirSize))
1352 return (FAKE_ROOTDIR_CLUST);
1354 loop = ClusterCache;
1355 while (loop) {
1356 if (((uchar_t *)modified >= loop->clusterData.bytes) &&
1357 ((uchar_t *)modified <
1358 (loop->clusterData.bytes + BytesPerCluster))) {
1359 return (loop->clusterNum);
1361 loop = loop->next;
1364 * Guess it wasn't cached after all...
1366 return (0);
1369 void
1370 writeClusterMods(int fd)
1372 CachedCluster *loop = ClusterCache;
1374 while (loop) {
1375 if (loop->modified)
1376 writeCachedCluster(fd, loop);
1377 loop = loop->next;
1381 void
1382 squirrelPath(struct nameinfo *pathInfo, int32_t clusterNum)
1384 /* silent failure for bogus clusters */
1385 if (clusterNum < FIRST_CLUSTER || clusterNum > LastCluster)
1386 return;
1387 if (InUse[clusterNum - FIRST_CLUSTER] == NULL)
1388 return;
1389 InUse[clusterNum - FIRST_CLUSTER]->path = pathInfo;
1393 markInUse(int fd, int32_t clusterNum, struct pcdir *referencer, struct
1394 pcdir *longRef, int32_t longStartCluster, int isHiddenFile,
1395 ClusterInfo **template)
1397 int alreadyMarked;
1398 ClusterInfo *cl;
1400 /* silent failure for bogus clusters */
1401 if (clusterNum < FIRST_CLUSTER || clusterNum > LastCluster)
1402 return (CLINFO_NEWLY_ALLOCED);
1404 alreadyMarked = allocInUse(clusterNum, template);
1405 if ((alreadyMarked == CLINFO_PREVIOUSLY_ALLOCED) &&
1406 (isInUse(clusterNum))) {
1407 sharedChainError(fd, clusterNum, referencer);
1408 return (CLINFO_PREVIOUSLY_ALLOCED);
1410 cl = InUse[clusterNum - FIRST_CLUSTER];
1412 * If Cl is newly allocated (refcnt <= 1) we must fill in the fields.
1413 * If Cl has different fields, we must clone it.
1416 if (cl->refcnt <= 1 || cl->dirent != referencer ||
1417 cl->longent != longRef ||
1418 cl->longEntStartClust != longStartCluster) {
1419 if (cl->refcnt > 1)
1420 cl = cloneClusterInfo(clusterNum);
1421 cl->dirent = referencer;
1422 cl->longent = longRef;
1423 cl->longEntStartClust = longStartCluster;
1424 if (isHiddenFile)
1425 cl->flags |= CLINFO_HIDDEN;
1428 * Return cl as the template to use for other clusters in
1429 * this file
1431 if (template)
1432 *template = cl;
1434 return (CLINFO_NEWLY_ALLOCED);
1437 void
1438 markClusterModified(int32_t clusterNum)
1440 CachedCluster *c;
1442 if (clusterNum == FAKE_ROOTDIR_CLUST) {
1443 RootDirModified = 1;
1444 return;
1447 /* silent failure for bogus clusters */
1448 if (clusterNum < FIRST_CLUSTER || clusterNum > LastCluster)
1449 return;
1451 if (c = findClusterCacheEntry(clusterNum)) {
1452 c->modified = 1;
1453 } else {
1454 (void) fprintf(stderr,
1455 gettext("Unexpected internal error: "
1456 "Missing cache entry [%d]\n"), clusterNum);
1457 exit(10);
1462 * readCluster
1463 * caller wants to read cluster clusterNum. We should return
1464 * a pointer to the read data in "data", and fill in the number
1465 * of bytes read in "datasize". If shouldCache is non-zero
1466 * we should allocate cache space to the cluster, otherwise we
1467 * just return a pointer to a buffer we re-use whenever cacheing
1468 * is not requested.
1471 readCluster(int fd, int32_t clusterNum, uchar_t **data, int32_t *datasize,
1472 int shouldCache)
1474 uchar_t *newBuf;
1475 int rv;
1477 *data = NULL;
1478 if ((*data = findClusterDataInTheCache(clusterNum)) != NULL) {
1479 *datasize = BytesPerCluster;
1480 return (RDCLUST_GOOD);
1483 rv = getCluster(fd, clusterNum, &newBuf, datasize);
1484 if (rv != RDCLUST_GOOD)
1485 return (rv);
1488 * Caller requested we NOT cache the data from this read.
1489 * So, we just return a pointer to the common data buffer.
1491 if (shouldCache == 0) {
1492 *data = newBuf;
1493 return (rv);
1497 * Caller requested we cache the data from this read.
1498 * So, if we have some data, add it to the cache by
1499 * copying it out of the common buffer into new storage.
1501 if (*datasize > 0)
1502 *data = addToCache(clusterNum, newBuf, datasize);
1503 return (rv);
1506 void
1507 findBadClusters(int fd)
1509 int32_t clusterCount;
1510 int32_t datasize;
1511 uchar_t *data;
1513 BadClusterCount = 0;
1514 makeUseTable();
1515 (void) printf(gettext("** Scanning allocation units\n"));
1516 for (clusterCount = FIRST_CLUSTER;
1517 clusterCount < LastCluster; clusterCount++) {
1518 if (readCluster(fd, clusterCount,
1519 &data, &datasize, RDCLUST_DONT_CACHE) < 0) {
1520 if (Verbose)
1521 (void) fprintf(stderr,
1522 gettext("\nUnreadable allocation unit %d.\n"),
1523 clusterCount);
1524 markBad(clusterCount, data, datasize);
1527 * Progress meter, display a '.' for every 1000 clusters
1528 * processed. We don't want to display this when
1529 * we are in verbose mode; verbose mode progress is
1530 * shown by displaying each file name as it is found.
1532 if (!Verbose && clusterCount % 1000 == 0)
1533 (void) printf(".");
1535 (void) printf(gettext("..done\n"));
1538 void
1539 scanAndFixMetadata(int fd)
1542 * First we initialize a few things.
1544 makeUseTable();
1545 getReadyToSearch(fd);
1546 createCHKNameList(fd);
1549 * Make initial scan, taking into account any effect that
1550 * the bad clusters we may have already discovered have
1551 * on meta-data. We may break up some cluster chains
1552 * during this period. The relinkCreatedOrphans() call
1553 * will then give the user the chance to recover stuff
1554 * we've created.
1556 (void) printf(gettext("** Scanning file system meta-data\n"));
1557 summarize(fd, NO_FAT_IN_SUMMARY);
1558 if (Verbose)
1559 printSummary(stderr);
1560 (void) printf(gettext("** Correcting any meta-data discrepancies\n"));
1561 relinkCreatedOrphans(fd);
1564 * Clear our usage table and go back over everything, this
1565 * time including looking for clusters floating free in the FAT.
1566 * This may include clusters the user chose to free during the
1567 * relink phase.
1569 makeUseTable();
1570 summarize(fd, INCLUDE_FAT_IN_SUMMARY);
1571 relinkOrphans(fd);
1574 void
1575 printSummary(FILE *outDest)
1577 (void) fprintf(outDest,
1578 gettext("%llu bytes.\n"),
1579 (uint64_t)
1580 TotalClusters * TheBIOSParameterBlock.bpb.sectors_per_cluster *
1581 TheBIOSParameterBlock.bpb.bytes_per_sector);
1582 (void) fprintf(outDest,
1583 gettext("%llu bytes in bad sectors.\n"),
1584 (uint64_t)
1585 BadClusterCount * TheBIOSParameterBlock.bpb.sectors_per_cluster *
1586 TheBIOSParameterBlock.bpb.bytes_per_sector);
1587 (void) fprintf(outDest,
1588 gettext("%llu bytes in %d directories.\n"),
1589 (uint64_t)
1590 DirClusterCount * TheBIOSParameterBlock.bpb.sectors_per_cluster *
1591 TheBIOSParameterBlock.bpb.bytes_per_sector, DirCount);
1592 if (HiddenClusterCount) {
1593 (void) fprintf(outDest,
1594 gettext("%llu bytes in %d hidden files.\n"),
1595 (uint64_t)HiddenClusterCount *
1596 TheBIOSParameterBlock.bpb.sectors_per_cluster *
1597 TheBIOSParameterBlock.bpb.bytes_per_sector,
1598 HiddenFileCount);
1600 (void) fprintf(outDest,
1601 gettext("%llu bytes in %d files.\n"),
1602 (uint64_t)
1603 FileClusterCount * TheBIOSParameterBlock.bpb.sectors_per_cluster *
1604 TheBIOSParameterBlock.bpb.bytes_per_sector, FileCount);
1605 (void) fprintf(outDest,
1606 gettext("%llu bytes free.\n"), (uint64_t)FreeClusterCount *
1607 TheBIOSParameterBlock.bpb.sectors_per_cluster *
1608 TheBIOSParameterBlock.bpb.bytes_per_sector);
1609 (void) fprintf(outDest,
1610 gettext("%d bytes per allocation unit.\n"),
1611 TheBIOSParameterBlock.bpb.sectors_per_cluster *
1612 TheBIOSParameterBlock.bpb.bytes_per_sector);
1613 (void) fprintf(outDest,
1614 gettext("%d total allocation units.\n"), TotalClusters);
1615 if (ReservedClusterCount)
1616 (void) fprintf(outDest, gettext("%d reserved allocation units.\n"),
1617 ReservedClusterCount);
1618 (void) fprintf(outDest,
1619 gettext("%d available allocation units.\n"), FreeClusterCount);