dmake: do not set MAKEFLAGS=k
[unleashed/tickless.git] / usr / src / cmd / fs.d / pcfs / fsck / clusters.c
blob32b7d73a8e7eadb613501b738f9a9575888069d0
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.
29 * fsck_pcfs -- routines for manipulating clusters.
31 #include <stdio.h>
32 #include <string.h>
33 #include <unistd.h>
34 #include <stdlib.h>
35 #include <libintl.h>
36 #include <errno.h>
37 #include <sys/dktp/fdisk.h>
38 #include <sys/fs/pc_fs.h>
39 #include <sys/fs/pc_dir.h>
40 #include <sys/fs/pc_label.h>
41 #include "pcfs_common.h"
42 #include "fsck_pcfs.h"
44 extern ClusterContents TheRootDir;
45 extern off64_t FirstClusterOffset;
46 extern off64_t PartitionOffset;
47 extern int32_t BytesPerCluster;
48 extern int32_t TotalClusters;
49 extern int32_t LastCluster;
50 extern int32_t RootDirSize;
51 extern int32_t FATSize;
52 extern bpb_t TheBIOSParameterBlock;
53 extern short FATEntrySize;
54 extern int RootDirModified;
55 extern int OkayToRelink;
56 extern int ReadOnly;
57 extern int IsFAT32;
58 extern int Verbose;
60 static struct pcdir BlankPCDIR;
61 static CachedCluster *ClusterCache;
62 static ClusterInfo **InUse;
63 static int32_t ReservedClusterCount;
64 static int32_t AllocedClusterCount;
65 static int32_t FreeClusterCount;
66 static int32_t BadClusterCount;
69 * Internal statistics
71 static int32_t CachedClusterCount;
73 int32_t HiddenClusterCount;
74 int32_t FileClusterCount;
75 int32_t DirClusterCount;
76 int32_t HiddenFileCount;
77 int32_t FileCount;
78 int32_t DirCount;
80 static int32_t orphanSizeLookup(int32_t clusterNum);
82 static void
83 freeNameInfo(int32_t clusterNum)
85 /* silent failure for bogus clusters */
86 if (clusterNum < FIRST_CLUSTER || clusterNum > LastCluster)
87 return;
88 if (InUse[clusterNum - FIRST_CLUSTER]->path != NULL) {
89 if (InUse[clusterNum - FIRST_CLUSTER]->path->references > 1) {
90 InUse[clusterNum - FIRST_CLUSTER]->path->references--;
91 } else {
92 free(InUse[clusterNum - FIRST_CLUSTER]->path->fullName);
93 free(InUse[clusterNum - FIRST_CLUSTER]->path);
95 InUse[clusterNum - FIRST_CLUSTER]->path = NULL;
99 static void
100 printOrphanPath(int32_t clusterNum)
102 /* silent failure for bogus clusters */
103 if (clusterNum < FIRST_CLUSTER || clusterNum > LastCluster)
104 return;
105 if (InUse[clusterNum - FIRST_CLUSTER]->path != NULL) {
106 (void) printf(gettext("\nOrphaned allocation units originally "
107 "allocated to:\n"));
108 (void) printf("%s\n",
109 InUse[clusterNum - FIRST_CLUSTER]->path->fullName);
110 freeNameInfo(clusterNum);
111 } else {
112 (void) printf(gettext("\nOrphaned allocation units originally "
113 "allocated to an unknown file or directory:\n"));
114 (void) printf(gettext("Orphaned chain begins with allocation "
115 "unit %d.\n"), clusterNum);
119 static void
120 printOrphanSize(int32_t clusterNum)
122 int32_t size = orphanSizeLookup(clusterNum);
124 if (size > 0) {
125 (void) printf(gettext("%d bytes in the orphaned chain of "
126 "allocation units.\n"), size);
127 if (Verbose) {
128 (void) printf(gettext("[Starting at allocation "
129 "unit %d]\n"), clusterNum);
134 static void
135 printOrphanInfo(int32_t clusterNum)
137 printOrphanPath(clusterNum);
138 printOrphanSize(clusterNum);
141 static int
142 askAboutFreeing(int32_t clusterNum)
145 * If it is not OkayToRelink, we haven't already printed the size
146 * of the orphaned chain.
148 if (!OkayToRelink)
149 printOrphanInfo(clusterNum);
151 * If we are in preen mode, preenBail won't return.
153 preenBail("Need user confirmation to free orphaned chain.\n");
155 (void) printf(
156 gettext("Free the allocation units in the orphaned chain ? "
157 "(y/n) "));
158 return (yes());
161 static int
162 askAboutRelink(int32_t clusterNum)
165 * Display the size of the chain for the user to consider.
167 printOrphanInfo(clusterNum);
169 * If we are in preen mode, preenBail won't return.
171 preenBail("Need user confirmation to re-link orphaned chain.\n");
173 (void) printf(gettext("Re-link orphaned chain into file system ? "
174 "(y/n) "));
176 return (yes());
179 static int
180 isHidden(int32_t clusterNum)
182 /* silent failure for bogus clusters */
183 if (clusterNum < FIRST_CLUSTER || clusterNum > LastCluster)
184 return (0);
186 if (InUse[clusterNum - FIRST_CLUSTER] == NULL)
187 return (0);
189 return (InUse[clusterNum - FIRST_CLUSTER]->flags & CLINFO_HIDDEN);
192 static int
193 isInUse(int32_t clusterNum)
195 /* silent failure for bogus clusters */
196 if (clusterNum < FIRST_CLUSTER || clusterNum > LastCluster)
197 return (0);
199 return ((InUse[clusterNum - FIRST_CLUSTER] != NULL) &&
200 (InUse[clusterNum - FIRST_CLUSTER]->dirent != NULL));
204 * Caller's may request that we cache the data from a readCluster.
205 * The xxxClusterxxxCachexxx routines handle looking for cached data
206 * or initially caching the data.
208 * XXX - facilitate releasing cached data for low memory situations.
210 static CachedCluster *
211 findClusterCacheEntry(int32_t clusterNum)
213 CachedCluster *loop = ClusterCache;
215 while (loop != NULL) {
216 if (loop->clusterNum == clusterNum)
217 return (loop);
218 loop = loop->next;
220 return (NULL);
223 static uchar_t *
224 findClusterDataInTheCache(int32_t clusterNum)
226 CachedCluster *loop = ClusterCache;
228 while (loop) {
229 if (loop->clusterNum == clusterNum)
230 return (loop->clusterData.bytes);
231 loop = loop->next;
233 return (NULL);
236 static uchar_t *
237 addToCache(int32_t clusterNum, uchar_t *buf, int32_t *datasize)
239 CachedCluster *new;
240 uchar_t *cp;
242 if ((new = (CachedCluster *)malloc(sizeof (CachedCluster))) == NULL) {
243 perror(gettext("No memory for cached cluster info"));
244 return (buf);
246 new->clusterNum = clusterNum;
247 new->modified = 0;
249 if ((cp = (uchar_t *)calloc(1, BytesPerCluster)) == NULL) {
250 perror(gettext("No memory for cached copy of cluster"));
251 free(new);
252 return (buf);
254 (void) memcpy(cp, buf, *datasize);
255 new->clusterData.bytes = cp;
257 if (Verbose) {
258 (void) fprintf(stderr,
259 gettext("Allocation unit %d cached.\n"), clusterNum);
261 if (ClusterCache == NULL) {
262 ClusterCache = new;
263 new->next = NULL;
264 } else if (new->clusterNum < ClusterCache->clusterNum) {
265 new->next = ClusterCache;
266 ClusterCache = new;
267 } else {
268 CachedCluster *loop = ClusterCache;
269 CachedCluster *trailer = NULL;
271 while (loop && new->clusterNum > loop->clusterNum) {
272 trailer = loop;
273 loop = loop->next;
275 trailer->next = new;
276 if (loop) {
277 new->next = loop;
278 } else {
279 new->next = NULL;
282 CachedClusterCount++;
283 return (new->clusterData.bytes);
286 static int
287 seekCluster(int fd, int32_t clusterNum)
289 off64_t seekto;
290 int saveError;
292 seekto = FirstClusterOffset +
293 ((off64_t)clusterNum - FIRST_CLUSTER) * BytesPerCluster;
294 if (lseek64(fd, seekto, SEEK_SET) != seekto) {
295 saveError = errno;
296 (void) fprintf(stderr,
297 gettext("Seek to Allocation unit #%d failed: "),
298 clusterNum);
299 (void) fprintf(stderr, strerror(saveError));
300 (void) fprintf(stderr, "\n");
301 return (0);
303 return (1);
307 * getcluster
308 * Get cluster bytes off the disk. We always read those bytes into
309 * the same static buffer. If the caller wants its own copy of the
310 * data it'll have to make its own copy. We'll return all the data
311 * read, even if it's short of a full cluster. This is for future use
312 * when we might want to relocate any salvagable data from bad clusters.
314 static int
315 getCluster(int fd, int32_t clusterNum, uchar_t **data, int32_t *datasize)
317 static uchar_t *clusterBuffer = NULL;
318 int saveError;
319 int try;
321 *datasize = 0;
322 *data = NULL;
324 if (clusterNum < FIRST_CLUSTER || clusterNum > LastCluster)
325 return (RDCLUST_BADINPUT);
327 if (clusterBuffer == NULL &&
328 (clusterBuffer = (uchar_t *)malloc(BytesPerCluster)) == NULL) {
329 perror(gettext("No memory for a cluster data buffer"));
330 return (RDCLUST_MEMERR);
333 for (try = 0; try < RDCLUST_MAX_RETRY; try++) {
334 if (!seekCluster(fd, clusterNum))
335 return (RDCLUST_FAIL);
336 if ((*datasize = read(fd, clusterBuffer, BytesPerCluster)) ==
337 BytesPerCluster) {
338 *data = clusterBuffer;
339 return (RDCLUST_GOOD);
342 if (*datasize >= 0) {
343 *data = clusterBuffer;
344 (void) fprintf(stderr,
345 gettext("Short read of allocation unit #%d\n"), clusterNum);
346 } else {
347 saveError = errno;
348 (void) fprintf(stderr, "Allocation unit %d:", clusterNum);
349 (void) fprintf(stderr, strerror(saveError));
350 (void) fprintf(stderr, "\n");
352 return (RDCLUST_FAIL);
355 static void
356 writeCachedCluster(int fd, CachedCluster *clustInfo)
358 ssize_t bytesWritten;
360 if (ReadOnly)
361 return;
363 if (Verbose)
364 (void) fprintf(stderr,
365 gettext("Allocation unit %d modified.\n"),
366 clustInfo->clusterNum);
368 if (seekCluster(fd, clustInfo->clusterNum) == 0)
369 return;
371 if ((bytesWritten = write(fd, clustInfo->clusterData.bytes,
372 BytesPerCluster)) != BytesPerCluster) {
373 if (bytesWritten < 0) {
374 perror(gettext("Failed to write modified "
375 "allocation unit"));
376 } else {
377 (void) fprintf(stderr,
378 gettext("Short write of allocation unit %d\n"),
379 clustInfo->clusterNum);
381 (void) close(fd);
382 exit(13);
387 * It's cheaper to allocate a lot at a time; malloc overhead pushes
388 * you over the brink much more quickly if you don't.
389 * This numbers seems to be a fair trade-off between reduced malloc overhead
390 * and additional overhead by over-allocating.
393 #define CHUNKSIZE 1024
395 static ClusterInfo *pool;
397 static ClusterInfo *
398 newClusterInfo(void)
401 ClusterInfo *ret;
403 if (pool == NULL) {
404 int i;
406 pool = (ClusterInfo *)malloc(sizeof (ClusterInfo) * CHUNKSIZE);
408 if (pool == NULL) {
409 perror(
410 gettext("Out of memory for cluster information"));
411 exit(9);
414 for (i = 0; i < CHUNKSIZE - 1; i++)
415 pool[i].nextfree = &pool[i+1];
417 pool[CHUNKSIZE-1].nextfree = NULL;
419 ret = pool;
420 pool = pool->nextfree;
422 memset(ret, 0, sizeof (*ret));
424 return (ret);
427 /* Should be called with verified arguments */
429 static ClusterInfo *
430 cloneClusterInfo(int32_t clusterNum)
432 ClusterInfo *cl = InUse[clusterNum - FIRST_CLUSTER];
434 if (cl->refcnt > 1) {
435 ClusterInfo *newCl = newClusterInfo();
436 cl->refcnt--;
437 *newCl = *cl;
438 newCl->refcnt = 1;
439 if (newCl->path)
440 newCl->path->references++;
441 InUse[clusterNum - FIRST_CLUSTER] = newCl;
443 return (InUse[clusterNum - FIRST_CLUSTER]);
446 static void
447 updateFlags(int32_t clusterNum, int newflags)
449 ClusterInfo *cl = InUse[clusterNum - FIRST_CLUSTER];
451 if (cl->flags != newflags && cl->refcnt > 1)
452 cl = cloneClusterInfo(clusterNum);
454 cl->flags = newflags;
457 static void
458 freeClusterInfo(ClusterInfo *old)
460 if (--old->refcnt <= 0) {
461 if (old->path && --old->path->references <= 0) {
462 free(old->path->fullName);
463 free(old->path);
465 old->nextfree = pool;
466 pool = old;
471 * Allocate entries in our sparse array of cluster information.
472 * Returns non-zero if the structure already has been allocated
473 * (for those keeping score at home).
475 * The template parameter, if non-NULL, is used to facilitate sharing
476 * the ClusterInfo nodes for the clusters belonging to the same file.
477 * The first call to allocInUse for a new file should have *template
478 * set to 0; on return, *template then points to the newly allocated
479 * ClusterInfo. Second and further calls keep the same value
480 * in *template and that ClusterInfo ndoe is then used for all
481 * entries in the file. Code that modifies the ClusterInfo nodes
482 * should take care proper sharing semantics are maintained (i.e.,
483 * copy-on-write using cloneClusterInfo())
485 * The ClusterInfo used in the template is guaranted to be in use in
486 * at least one other cluster as we never return a value if we didn't
487 * set it first. So we can overwrite it without the possibility of a leak.
489 static int
490 allocInUse(int32_t clusterNum, ClusterInfo **template)
492 ClusterInfo *newCl;
494 if (InUse[clusterNum - FIRST_CLUSTER] != NULL)
495 return (CLINFO_PREVIOUSLY_ALLOCED);
497 if (template != NULL && *template != NULL)
498 newCl = *template;
499 else {
500 newCl = newClusterInfo();
501 if (template)
502 *template = newCl;
505 InUse[clusterNum - FIRST_CLUSTER] = newCl;
506 newCl->refcnt++;
508 return (CLINFO_NEWLY_ALLOCED);
511 static void
512 markFree(int32_t clusterNum)
514 /* silent failure for bogus clusters */
515 if (clusterNum < FIRST_CLUSTER || clusterNum > LastCluster)
516 return;
518 if (InUse[clusterNum - FIRST_CLUSTER]) {
519 free(InUse[clusterNum - FIRST_CLUSTER]->saved);
520 freeClusterInfo(InUse[clusterNum - FIRST_CLUSTER]);
521 InUse[clusterNum - FIRST_CLUSTER] = NULL;
525 static void
526 markOrphan(int fd, int32_t clusterNum, struct pcdir *dp)
528 /* silent failure for bogus clusters */
529 if (clusterNum < FIRST_CLUSTER || clusterNum > LastCluster)
530 return;
532 (void) markInUse(fd, clusterNum, dp, NULL, 0, VISIBLE, NULL);
533 if (InUse[clusterNum - FIRST_CLUSTER] != NULL)
534 updateFlags(clusterNum,
535 InUse[clusterNum - FIRST_CLUSTER]->flags | CLINFO_ORPHAN);
538 static void
539 markBad(int32_t clusterNum, uchar_t *recovered, int32_t recoveredLen)
541 /* silent failure for bogus clusters */
542 if (clusterNum < FIRST_CLUSTER || clusterNum > LastCluster)
543 return;
545 (void) allocInUse(clusterNum, NULL);
547 if (recoveredLen) {
548 (void) cloneClusterInfo(clusterNum);
549 InUse[clusterNum - FIRST_CLUSTER]->saved = recovered;
551 updateFlags(clusterNum,
552 InUse[clusterNum - FIRST_CLUSTER]->flags | CLINFO_BAD);
554 BadClusterCount++;
555 if (Verbose)
556 (void) fprintf(stderr,
557 gettext("Allocation unit %d marked bad.\n"), clusterNum);
560 static void
561 clearOrphan(int32_t c)
563 /* silent failure for bogus clusters */
564 if (c < FIRST_CLUSTER || c > LastCluster)
565 return;
566 if (InUse[c - FIRST_CLUSTER] != NULL)
567 updateFlags(c,
568 InUse[c - FIRST_CLUSTER]->flags & ~CLINFO_ORPHAN);
571 static void
572 clearInUse(int32_t c)
574 ClusterInfo **clp;
576 /* silent failure for bogus clusters */
577 if (c < FIRST_CLUSTER || c > LastCluster)
578 return;
580 clp = &InUse[c - FIRST_CLUSTER];
581 if (*clp != NULL) {
582 freeClusterInfo(*clp);
583 *clp = NULL;
587 static void
588 clearAllClusters_InUse()
590 int32_t cc;
591 for (cc = FIRST_CLUSTER; cc < LastCluster; cc++) {
592 clearInUse(cc);
596 static void
597 makeUseTable(void)
599 if (InUse != NULL) {
600 clearAllClusters_InUse();
601 return;
603 if ((InUse = (ClusterInfo **)
604 calloc(TotalClusters, sizeof (ClusterInfo *))) == NULL) {
605 perror(gettext("No memory for internal table"));
606 exit(9);
610 static void
611 countClusters(void)
613 int32_t c;
615 BadClusterCount = HiddenClusterCount =
616 AllocedClusterCount = FreeClusterCount = 0;
618 for (c = FIRST_CLUSTER; c < LastCluster; c++) {
619 if (badInFAT(c)) {
620 BadClusterCount++;
621 } else if (isMarkedBad(c)) {
623 * This catches the bad sectors found
624 * during thorough verify that have never been
625 * allocated to a file. Without this check, we
626 * count these guys as free.
628 BadClusterCount++;
629 markBadInFAT(c);
630 } else if (isHidden(c)) {
631 HiddenClusterCount++;
632 } else if (isInUse(c)) {
633 AllocedClusterCount++;
634 } else {
635 FreeClusterCount++;
641 * summarizeFAT
642 * Mark orphans without directory entries as allocated.
643 * XXX - these chains should be reclaimed!
644 * XXX - merge this routine with countClusters (same loop, duh.)
646 static void
647 summarizeFAT(int fd)
649 int32_t c;
650 ClusterInfo *tmpl = NULL;
652 for (c = FIRST_CLUSTER; c < LastCluster; c++) {
653 if (!freeInFAT(c) && !badInFAT(c) && !reservedInFAT(c) &&
654 !isInUse(c)) {
655 (void) markInUse(fd, c, &BlankPCDIR, NULL, 0, VISIBLE,
656 &tmpl);
661 static void
662 getReadyToSearch(int fd)
664 getFAT(fd);
665 if (!IsFAT32)
666 getRootDirectory(fd);
670 static char PathName[MAXPATHLEN];
672 static void
673 summarize(int fd, int includeFAT)
675 struct pcdir *ignorep1, *ignorep2 = NULL;
676 int32_t ignore32;
677 char ignore;
678 int pathlen;
680 ReservedClusterCount = 0;
681 AllocedClusterCount = 0;
682 HiddenClusterCount = 0;
683 FileClusterCount = 0;
684 FreeClusterCount = 0;
685 DirClusterCount = 0;
686 BadClusterCount = 0;
687 HiddenFileCount = 0;
688 FileCount = 0;
689 DirCount = 0;
690 ignorep1 = ignorep2 = NULL;
691 ignore = '\0';
693 PathName[0] = '\0';
694 pathlen = 0;
696 getReadyToSearch(fd);
698 * Traverse the full meta-data tree to talley what clusters
699 * are in use. The root directory is an area outside of the
700 * file space on FAT12 and FAT16 file systems. On FAT32 file
701 * systems, the root directory is in a file area cluster just
702 * like any other directory.
704 if (!IsFAT32) {
705 traverseFromRoot(fd, 0, PCFS_VISIT_SUBDIRS, PCFS_TRAVERSE_ALL,
706 ignore, &ignorep1, &ignore32, &ignorep2, PathName,
707 &pathlen);
708 } else {
709 DirCount++;
710 traverseDir(fd, TheBIOSParameterBlock.bpb32.root_dir_clust,
711 0, PCFS_VISIT_SUBDIRS, PCFS_TRAVERSE_ALL, ignore,
712 &ignorep1, &ignore32, &ignorep2, PathName, &pathlen);
715 if (includeFAT)
716 summarizeFAT(fd);
717 countClusters();
721 isMarkedBad(int32_t clusterNum)
723 /* silent failure for bogus clusters */
724 if (clusterNum < FIRST_CLUSTER || clusterNum > LastCluster)
725 return (0);
727 if (InUse[clusterNum - FIRST_CLUSTER] == NULL)
728 return (0);
730 return (InUse[clusterNum - FIRST_CLUSTER]->flags & CLINFO_BAD);
733 static int
734 isMarkedOrphan(int32_t clusterNum)
736 /* silent failure for bogus clusters */
737 if (clusterNum < FIRST_CLUSTER || clusterNum > LastCluster)
738 return (0);
740 if (InUse[clusterNum - FIRST_CLUSTER] == NULL)
741 return (0);
743 return (InUse[clusterNum - FIRST_CLUSTER]->flags & CLINFO_ORPHAN);
746 static void
747 orphanChain(int fd, int32_t c, struct pcdir *ndp)
749 ClusterInfo *tmpl = NULL;
751 /* silent failure for bogus clusters */
752 if (c < FIRST_CLUSTER || c > LastCluster)
753 return;
754 clearInUse(c);
755 markOrphan(fd, c, ndp);
756 c = nextInChain(c);
757 while (c != 0) {
758 clearInUse(c);
759 clearOrphan(c);
760 (void) markInUse(fd, c, ndp, NULL, 0, VISIBLE, &tmpl);
761 c = nextInChain(c);
765 static int32_t
766 findAFreeCluster(int32_t startAt)
768 int32_t look = startAt;
770 for (;;) {
771 if (freeInFAT(look)) {
772 break;
774 if (look == LastCluster)
775 look = FIRST_CLUSTER;
776 else
777 look++;
778 if (look == startAt)
779 break;
781 if (look != startAt)
782 return (look);
783 else
784 return (0);
787 static void
788 setEndOfDirectory(struct pcdir *dp)
790 dp->pcd_filename[0] = PCD_UNUSED;
793 static void
794 emergencyEndOfDirectory(int fd, int32_t secondToLast)
796 ClusterContents dirdata;
797 int32_t dirdatasize = 0;
799 if (readCluster(fd, secondToLast, &(dirdata.bytes), &dirdatasize,
800 RDCLUST_DO_CACHE) != RDCLUST_GOOD) {
801 (void) fprintf(stderr,
802 gettext("Unable to read allocation unit %d.\n"),
803 secondToLast);
804 (void) fprintf(stderr,
805 gettext("Cannot allocate a new allocation unit to hold an"
806 " end-of-directory marker.\nCannot access allocation unit"
807 " to overwrite existing directory entry with\nthe marker."
808 " Needed directory truncation has failed. Giving up.\n"));
809 (void) close(fd);
810 exit(11);
812 setEndOfDirectory(dirdata.dirp);
813 markClusterModified(secondToLast);
816 static void
817 makeNewEndOfDirectory(struct pcdir *entry, int32_t secondToLast,
818 int32_t newCluster, ClusterContents *newData)
820 setEndOfDirectory(newData->dirp);
821 markClusterModified(newCluster);
823 * There are two scenarios. One is that we truncated the
824 * directory in the very beginning. The other is that we
825 * truncated it in the middle or at the end. In the first
826 * scenario, the secondToLast argument is not a valid cluster
827 * (it's zero), and so we actually need to change the start
828 * cluster for the directory to this new start cluster. In
829 * the second scenario, the secondToLast cluster we received
830 * as an argument needs to be pointed at the new end of
831 * directory.
833 if (secondToLast == 0) {
834 updateDirEnt_Start(entry, newCluster);
835 } else {
836 writeFATEntry(secondToLast, newCluster);
838 markLastInFAT(newCluster);
841 static void
842 createNewEndOfDirectory(int fd, struct pcdir *entry, int32_t secondToLast)
844 ClusterContents dirdata;
845 int32_t dirdatasize = 0;
846 int32_t freeCluster;
848 if (((freeCluster = findAFreeCluster(secondToLast)) != 0)) {
849 if (readCluster(fd, freeCluster, &(dirdata.bytes),
850 &dirdatasize, RDCLUST_DO_CACHE) == RDCLUST_GOOD) {
851 if (Verbose) {
852 (void) fprintf(stderr,
853 gettext("Grabbed allocation unit #%d "
854 "for truncated\ndirectory's new end "
855 "of directory.\n"), freeCluster);
857 makeNewEndOfDirectory(entry, secondToLast,
858 freeCluster, &dirdata);
859 return;
862 if (secondToLast == 0) {
863 if (freeCluster == 0) {
864 (void) fprintf(stderr, gettext("File system full.\n"));
865 } else {
866 (void) fprintf(stderr,
867 gettext("Unable to read allocation unit %d.\n"),
868 freeCluster);
870 (void) fprintf(stderr,
871 gettext("Cannot allocate a new allocation unit to hold "
872 "an end-of-directory marker.\nNo existing directory "
873 "entries can be overwritten with the marker,\n"
874 "the only unit allocated to the directory is "
875 "inaccessible.\nNeeded directory truncation has failed. "
876 "Giving up.\n"));
877 (void) close(fd);
878 exit(11);
880 emergencyEndOfDirectory(fd, secondToLast);
884 * truncAtCluster
885 * Given a directory entry and a cluster number, search through
886 * the cluster chain for the entry and make the cluster previous
887 * to the given cluster in the chain the last cluster in the file.
888 * The number of orphaned bytes is returned. For a chain that's
889 * a directory we need to do some special handling, since we'll be
890 * getting rid of the end of directory notice by truncating.
892 static int64_t
893 truncAtCluster(int fd, struct pcdir *entry, int32_t cluster)
895 uint32_t oldSize, newSize;
896 int32_t prev, count, follow;
897 int dir = (entry->pcd_attr & PCA_DIR);
899 prev = 0; count = 0;
900 follow = extractStartCluster(entry);
901 while (follow != cluster && follow >= FIRST_CLUSTER &&
902 follow <= LastCluster) {
903 prev = follow;
904 count++;
905 follow = nextInChain(follow);
907 if (follow != cluster) {
909 * We didn't find the cluster they wanted to trunc at
910 * anywhere in the entry's chain. So we'll leave the
911 * entry alone, and return a negative value so they
912 * can know something is wrong.
914 return (-1);
916 if (Verbose) {
917 (void) fprintf(stderr,
918 gettext("Chain truncation at unit #%d\n"), cluster);
920 if (!dir) {
921 oldSize = extractSize(entry);
922 newSize = count *
923 TheBIOSParameterBlock.bpb.sectors_per_cluster *
924 TheBIOSParameterBlock.bpb.bytes_per_sector;
925 if (newSize == 0)
926 updateDirEnt_Start(entry, 0);
927 } else {
928 newSize = 0;
930 updateDirEnt_Size(entry, newSize);
931 if (dir) {
932 createNewEndOfDirectory(fd, entry, prev);
933 } else if (prev != 0) {
934 markLastInFAT(prev);
936 if (dir) {
938 * We don't really know what the size of a directory is
939 * but it is important for us to know if this truncation
940 * results in an orphan with any size. The value we
941 * return from this routine for a normal file is the
942 * number of bytes left in the chain. For a directory
943 * we can't be exact, and the caller doesn't really
944 * expect us to be. For a directory the caller only
945 * cares if there are zero bytes left or more than
946 * zero bytes left. We'll return 1 to indicate
947 * more than zero.
949 if ((follow = nextInChain(follow)) != 0)
950 return (1);
951 else
952 return (0);
955 * newSize should always be smaller than the old one, since
956 * we are decreasing the number of clusters allocated to the file.
958 return ((int64_t)oldSize - (int64_t)newSize);
961 static struct pcdir *
962 updateOrphanedChainMetadata(int fd, struct pcdir *dp, int32_t endCluster,
963 int isBad)
965 struct pcdir *ndp = NULL;
966 int64_t remainder;
967 char *newName = NULL;
968 int chosenName;
969 int dir = (dp->pcd_attr & PCA_DIR);
972 * If the truncation fails, (which ought not to happen),
973 * there's no need to go any further, we just return
974 * a null value for the new directory entry pointer.
976 remainder = truncAtCluster(fd, dp, endCluster);
977 if (remainder < 0)
978 return (ndp);
979 if (!dir && isBad) {
981 * Subtract out the bad cluster from the remaining size
982 * We always assume the cluster being deleted from the
983 * file is full size, but that might not be the case
984 * for the last cluster of the file, so that is why
985 * we check for negative remainder value.
987 remainder -= TheBIOSParameterBlock.bpb.sectors_per_cluster *
988 TheBIOSParameterBlock.bpb.bytes_per_sector;
989 if (remainder < 0)
990 remainder = 0;
993 * Build a new directory entry for the rest of the chain.
994 * Later, if the user okays it, we'll link this entry into the
995 * root directory. The new entry will start out as a
996 * copy of the truncated entry.
998 if ((remainder != 0) &&
999 ((newName = nextAvailableCHKName(&chosenName)) != NULL) &&
1000 ((ndp = newDirEnt(dp)) != NULL)) {
1001 if (Verbose) {
1002 if (dir)
1003 (void) fprintf(stderr,
1004 gettext("Orphaned directory chain.\n"));
1005 else
1006 (void) fprintf(stderr,
1007 gettext("Orphaned chain, %u bytes.\n"),
1008 (uint32_t)remainder);
1010 if (!dir)
1011 updateDirEnt_Size(ndp, (uint32_t)remainder);
1012 if (isBad)
1013 updateDirEnt_Start(ndp, nextInChain(endCluster));
1014 else
1015 updateDirEnt_Start(ndp, endCluster);
1016 updateDirEnt_Name(ndp, newName);
1017 addEntryToCHKList(chosenName);
1019 return (ndp);
1023 * splitChain()
1025 * split a cluster allocation chain into two cluster chains
1026 * around a given cluster (problemCluster). This results in two
1027 * separate directory entries; the original (dp), and one we hope
1028 * to create and return a pointer to to the caller (*newdp).
1029 * This second entry is the orphan chain, and it may end up in
1030 * the root directory as a FILEnnnn.CHK file. We also return the
1031 * starting cluster of the orphan chain to the caller (*orphanStart).
1033 void
1034 splitChain(int fd, struct pcdir *dp, int32_t problemCluster,
1035 struct pcdir **newdp, int32_t *orphanStart)
1037 struct pcdir *ndp = NULL;
1038 int isBad = isMarkedBad(problemCluster);
1040 ndp = updateOrphanedChainMetadata(fd, dp, problemCluster, isBad);
1041 *newdp = ndp;
1042 clearInUse(problemCluster);
1043 if (isBad) {
1044 clearOrphan(problemCluster);
1045 *orphanStart = nextInChain(problemCluster);
1046 orphanChain(fd, *orphanStart, ndp);
1047 markBadInFAT(problemCluster);
1048 } else {
1049 *orphanStart = problemCluster;
1050 orphanChain(fd, problemCluster, ndp);
1055 * freeOrphan
1057 * User has requested that an orphaned cluster chain be freed back
1058 * into the file area.
1060 static void
1061 freeOrphan(int32_t c)
1063 int32_t n;
1066 * Free the directory entry we explicitly created for
1067 * the orphaned clusters.
1069 free(InUse[c - FIRST_CLUSTER]->dirent);
1071 * Then mark the clusters themselves as available.
1073 do {
1074 n = nextInChain(c);
1075 markFreeInFAT(c);
1076 markFree(c);
1077 c = n;
1078 } while (c != 0);
1082 * Rewrite the InUse field for a cluster chain. Can be used on a partial
1083 * chain if provided with a stopAtCluster.
1085 static void
1086 redoInUse(int fd, int32_t c, struct pcdir *ndp, int32_t stopAtCluster)
1088 while (c && c != stopAtCluster) {
1089 clearInUse(c);
1090 (void) markInUse(fd, c, ndp, NULL, 0, VISIBLE, NULL);
1091 c = nextInChain(c);
1095 static struct pcdir *
1096 orphanDirEntLookup(int32_t clusterNum)
1098 if (clusterNum < FIRST_CLUSTER || clusterNum > LastCluster)
1099 return (NULL);
1101 if (isInUse(clusterNum)) {
1102 return (InUse[clusterNum - FIRST_CLUSTER]->dirent);
1103 } else {
1104 return (NULL);
1108 static int32_t
1109 orphanSizeLookup(int32_t clusterNum)
1111 /* silent failure for bogus clusters */
1112 if (clusterNum < FIRST_CLUSTER || clusterNum > LastCluster)
1113 return (-1);
1115 if (isInUse(clusterNum)) {
1116 return (extractSize(InUse[clusterNum - FIRST_CLUSTER]->dirent));
1117 } else {
1118 return (-1);
1123 * linkOrphan
1125 * User has requested that an orphaned cluster chain be brought back
1126 * into the file system. So we have to make a new directory entry
1127 * in the root directory and point it at the cluster chain.
1129 static void
1130 linkOrphan(int fd, int32_t start)
1132 struct pcdir *newEnt = NULL;
1133 struct pcdir *dp;
1135 if ((dp = orphanDirEntLookup(start)) != NULL) {
1136 newEnt = addRootDirEnt(fd, dp);
1137 } else {
1138 (void) printf(gettext("Re-link of orphaned chain failed."
1139 " Allocation units will remain orphaned.\n"));
1142 * A cluster isn't really InUse() unless it is referenced,
1143 * so if newEnt is NULL here, we are in effect using markInUse()
1144 * to note that the cluster is NOT in use.
1146 redoInUse(fd, start, newEnt, 0);
1150 * relinkCreatedOrphans
1152 * While marking clusters as bad, we can create orphan cluster
1153 * chains. Since we were the ones doing the marking, we were able to
1154 * keep track of the orphans we created. Now we want to go through
1155 * all those chains and either get them back into the file system or
1156 * free them depending on the user's input.
1158 static void
1159 relinkCreatedOrphans(int fd)
1161 int32_t c;
1163 for (c = FIRST_CLUSTER; c < LastCluster; c++) {
1164 if (isMarkedOrphan(c)) {
1165 if (OkayToRelink && askAboutRelink(c)) {
1166 linkOrphan(fd, c);
1167 } else if (askAboutFreeing(c)) {
1168 freeOrphan(c);
1170 clearOrphan(c);
1176 * relinkFATOrphans
1178 * We want to find orphans not represented in the meta-data.
1179 * These are chains marked in the FAT as being in use but
1180 * not referenced anywhere by any directory entries.
1181 * We'll go through the whole FAT and mark the first cluster
1182 * in any such chain as an orphan. Then we can just use
1183 * the relinkCreatedOrphans routine to get them back into the
1184 * file system or free'ed depending on the user's input.
1186 static void
1187 relinkFATOrphans(int fd)
1189 struct pcdir *ndp = NULL;
1190 int32_t cc, c, n;
1191 int32_t bpc, newSize;
1192 char *newName;
1193 int chosenName;
1195 for (c = FIRST_CLUSTER; c < LastCluster; c++) {
1196 if (freeInFAT(c) || badInFAT(c) ||
1197 reservedInFAT(c) || isInUse(c))
1198 continue;
1199 cc = 1;
1200 n = c;
1201 while (n = nextInChain(n))
1202 cc++;
1203 bpc = TheBIOSParameterBlock.bpb.sectors_per_cluster *
1204 TheBIOSParameterBlock.bpb.bytes_per_sector;
1205 newSize = cc * bpc;
1206 if (((newName = nextAvailableCHKName(&chosenName)) != NULL) &&
1207 ((ndp = newDirEnt(NULL)) != NULL)) {
1208 updateDirEnt_Size(ndp, newSize);
1209 updateDirEnt_Start(ndp, c);
1210 updateDirEnt_Name(ndp, newName);
1211 addEntryToCHKList(chosenName);
1213 orphanChain(fd, c, ndp);
1215 relinkCreatedOrphans(fd);
1218 static void
1219 relinkOrphans(int fd)
1221 relinkCreatedOrphans(fd);
1222 relinkFATOrphans(fd);
1225 static void
1226 checkForFATLoop(int32_t clusterNum)
1228 int32_t prev = clusterNum;
1229 int32_t follow;
1231 if (clusterNum < FIRST_CLUSTER || clusterNum > LastCluster)
1232 return;
1234 follow = nextInChain(clusterNum);
1235 while (follow != clusterNum && follow >= FIRST_CLUSTER &&
1236 follow <= LastCluster) {
1237 prev = follow;
1238 follow = nextInChain(follow);
1240 if (follow == clusterNum) {
1242 * We found a loop. Eradicate it by changing
1243 * the last cluster in the loop to be last
1244 * in the chain instead instead of pointing
1245 * back to the first cluster.
1247 markLastInFAT(prev);
1251 static void
1252 sharedChainError(int fd, int32_t clusterNum, struct pcdir *badEntry)
1255 * If we have shared clusters, it is either because the
1256 * cluster somehow got assigned to multiple files and/or
1257 * because of a loop in the cluster chain. In either
1258 * case we want to truncate the offending file at the
1259 * cluster of contention. Then, we will want to run
1260 * through the remainder of the chain. If we find ourselves
1261 * back at the top, we will know there is a loop in the
1262 * FAT we need to remove.
1264 if (Verbose)
1265 (void) fprintf(stderr,
1266 gettext("Truncating chain due to duplicate allocation of "
1267 "unit %d.\n"), clusterNum);
1269 * Note that we don't orphan anything here, because the duplicate
1270 * part of the chain may be part of another valid chain.
1272 (void) truncAtCluster(fd, badEntry, clusterNum);
1273 checkForFATLoop(clusterNum);
1276 void
1277 truncChainWithBadCluster(int fd, struct pcdir *dp, int32_t startCluster)
1279 struct pcdir *orphanEntry;
1280 int32_t orphanStartCluster;
1281 int32_t c = startCluster;
1283 while (c != 0) {
1284 if (isMarkedBad(c)) {
1286 * splitChain() truncates the current guy and
1287 * then makes an orphan chain out of the remaining
1288 * clusters. When we come back from the split
1289 * we'll want to continue looking for bad clusters
1290 * in the orphan chain.
1292 splitChain(fd, dp, c,
1293 &orphanEntry, &orphanStartCluster);
1295 * There is a chance that we weren't able or weren't
1296 * required to make a directory entry for the
1297 * remaining clusters. In that case we won't go
1298 * on, because we couldn't make any more splits
1299 * anyway.
1301 if (orphanEntry == NULL)
1302 break;
1303 c = orphanStartCluster;
1304 dp = orphanEntry;
1305 continue;
1307 c = nextInChain(c);
1311 int32_t
1312 nextInChain(int32_t currentCluster)
1314 int32_t nextCluster;
1316 /* silent failure for bogus clusters */
1317 if (currentCluster < FIRST_CLUSTER || currentCluster > LastCluster)
1318 return (0);
1321 * Look up FAT entry of next link in cluster chain,
1322 * if this one is the last one return 0 as the next link.
1324 nextCluster = readFATEntry(currentCluster);
1325 if (nextCluster < FIRST_CLUSTER || nextCluster > LastCluster)
1326 return (0);
1328 return (nextCluster);
1332 * findImpactedCluster
1334 * Called when someone modifies what they believe might be a cached
1335 * cluster entry, but when they only have a directory entry pointer
1336 * and not the cluster number. We have to go dig up what cluster
1337 * they are modifying.
1339 int32_t
1340 findImpactedCluster(struct pcdir *modified)
1342 CachedCluster *loop;
1344 * Check to see if it's in the root directory first
1346 if (!IsFAT32 && ((uchar_t *)modified >= TheRootDir.bytes) &&
1347 ((uchar_t *)modified < TheRootDir.bytes + RootDirSize))
1348 return (FAKE_ROOTDIR_CLUST);
1350 loop = ClusterCache;
1351 while (loop) {
1352 if (((uchar_t *)modified >= loop->clusterData.bytes) &&
1353 ((uchar_t *)modified <
1354 (loop->clusterData.bytes + BytesPerCluster))) {
1355 return (loop->clusterNum);
1357 loop = loop->next;
1360 * Guess it wasn't cached after all...
1362 return (0);
1365 void
1366 writeClusterMods(int fd)
1368 CachedCluster *loop = ClusterCache;
1370 while (loop) {
1371 if (loop->modified)
1372 writeCachedCluster(fd, loop);
1373 loop = loop->next;
1377 void
1378 squirrelPath(struct nameinfo *pathInfo, int32_t clusterNum)
1380 /* silent failure for bogus clusters */
1381 if (clusterNum < FIRST_CLUSTER || clusterNum > LastCluster)
1382 return;
1383 if (InUse[clusterNum - FIRST_CLUSTER] == NULL)
1384 return;
1385 InUse[clusterNum - FIRST_CLUSTER]->path = pathInfo;
1389 markInUse(int fd, int32_t clusterNum, struct pcdir *referencer, struct
1390 pcdir *longRef, int32_t longStartCluster, int isHiddenFile,
1391 ClusterInfo **template)
1393 int alreadyMarked;
1394 ClusterInfo *cl;
1396 /* silent failure for bogus clusters */
1397 if (clusterNum < FIRST_CLUSTER || clusterNum > LastCluster)
1398 return (CLINFO_NEWLY_ALLOCED);
1400 alreadyMarked = allocInUse(clusterNum, template);
1401 if ((alreadyMarked == CLINFO_PREVIOUSLY_ALLOCED) &&
1402 (isInUse(clusterNum))) {
1403 sharedChainError(fd, clusterNum, referencer);
1404 return (CLINFO_PREVIOUSLY_ALLOCED);
1406 cl = InUse[clusterNum - FIRST_CLUSTER];
1408 * If Cl is newly allocated (refcnt <= 1) we must fill in the fields.
1409 * If Cl has different fields, we must clone it.
1412 if (cl->refcnt <= 1 || cl->dirent != referencer ||
1413 cl->longent != longRef ||
1414 cl->longEntStartClust != longStartCluster) {
1415 if (cl->refcnt > 1)
1416 cl = cloneClusterInfo(clusterNum);
1417 cl->dirent = referencer;
1418 cl->longent = longRef;
1419 cl->longEntStartClust = longStartCluster;
1420 if (isHiddenFile)
1421 cl->flags |= CLINFO_HIDDEN;
1424 * Return cl as the template to use for other clusters in
1425 * this file
1427 if (template)
1428 *template = cl;
1430 return (CLINFO_NEWLY_ALLOCED);
1433 void
1434 markClusterModified(int32_t clusterNum)
1436 CachedCluster *c;
1438 if (clusterNum == FAKE_ROOTDIR_CLUST) {
1439 RootDirModified = 1;
1440 return;
1443 /* silent failure for bogus clusters */
1444 if (clusterNum < FIRST_CLUSTER || clusterNum > LastCluster)
1445 return;
1447 if (c = findClusterCacheEntry(clusterNum)) {
1448 c->modified = 1;
1449 } else {
1450 (void) fprintf(stderr,
1451 gettext("Unexpected internal error: "
1452 "Missing cache entry [%d]\n"), clusterNum);
1453 exit(10);
1458 * readCluster
1459 * caller wants to read cluster clusterNum. We should return
1460 * a pointer to the read data in "data", and fill in the number
1461 * of bytes read in "datasize". If shouldCache is non-zero
1462 * we should allocate cache space to the cluster, otherwise we
1463 * just return a pointer to a buffer we re-use whenever cacheing
1464 * is not requested.
1467 readCluster(int fd, int32_t clusterNum, uchar_t **data, int32_t *datasize,
1468 int shouldCache)
1470 uchar_t *newBuf;
1471 int rv;
1473 *data = NULL;
1474 if ((*data = findClusterDataInTheCache(clusterNum)) != NULL) {
1475 *datasize = BytesPerCluster;
1476 return (RDCLUST_GOOD);
1479 rv = getCluster(fd, clusterNum, &newBuf, datasize);
1480 if (rv != RDCLUST_GOOD)
1481 return (rv);
1484 * Caller requested we NOT cache the data from this read.
1485 * So, we just return a pointer to the common data buffer.
1487 if (shouldCache == 0) {
1488 *data = newBuf;
1489 return (rv);
1493 * Caller requested we cache the data from this read.
1494 * So, if we have some data, add it to the cache by
1495 * copying it out of the common buffer into new storage.
1497 if (*datasize > 0)
1498 *data = addToCache(clusterNum, newBuf, datasize);
1499 return (rv);
1502 void
1503 findBadClusters(int fd)
1505 int32_t clusterCount;
1506 int32_t datasize;
1507 uchar_t *data;
1509 BadClusterCount = 0;
1510 makeUseTable();
1511 (void) printf(gettext("** Scanning allocation units\n"));
1512 for (clusterCount = FIRST_CLUSTER;
1513 clusterCount < LastCluster; clusterCount++) {
1514 if (readCluster(fd, clusterCount,
1515 &data, &datasize, RDCLUST_DONT_CACHE) < 0) {
1516 if (Verbose)
1517 (void) fprintf(stderr,
1518 gettext("\nUnreadable allocation unit %d.\n"),
1519 clusterCount);
1520 markBad(clusterCount, data, datasize);
1523 * Progress meter, display a '.' for every 1000 clusters
1524 * processed. We don't want to display this when
1525 * we are in verbose mode; verbose mode progress is
1526 * shown by displaying each file name as it is found.
1528 if (!Verbose && clusterCount % 1000 == 0)
1529 (void) printf(".");
1531 (void) printf(gettext("..done\n"));
1534 void
1535 scanAndFixMetadata(int fd)
1538 * First we initialize a few things.
1540 makeUseTable();
1541 getReadyToSearch(fd);
1542 createCHKNameList(fd);
1545 * Make initial scan, taking into account any effect that
1546 * the bad clusters we may have already discovered have
1547 * on meta-data. We may break up some cluster chains
1548 * during this period. The relinkCreatedOrphans() call
1549 * will then give the user the chance to recover stuff
1550 * we've created.
1552 (void) printf(gettext("** Scanning file system meta-data\n"));
1553 summarize(fd, NO_FAT_IN_SUMMARY);
1554 if (Verbose)
1555 printSummary(stderr);
1556 (void) printf(gettext("** Correcting any meta-data discrepancies\n"));
1557 relinkCreatedOrphans(fd);
1560 * Clear our usage table and go back over everything, this
1561 * time including looking for clusters floating free in the FAT.
1562 * This may include clusters the user chose to free during the
1563 * relink phase.
1565 makeUseTable();
1566 summarize(fd, INCLUDE_FAT_IN_SUMMARY);
1567 relinkOrphans(fd);
1570 void
1571 printSummary(FILE *outDest)
1573 (void) fprintf(outDest,
1574 gettext("%llu bytes.\n"),
1575 (uint64_t)
1576 TotalClusters * TheBIOSParameterBlock.bpb.sectors_per_cluster *
1577 TheBIOSParameterBlock.bpb.bytes_per_sector);
1578 (void) fprintf(outDest,
1579 gettext("%llu bytes in bad sectors.\n"),
1580 (uint64_t)
1581 BadClusterCount * TheBIOSParameterBlock.bpb.sectors_per_cluster *
1582 TheBIOSParameterBlock.bpb.bytes_per_sector);
1583 (void) fprintf(outDest,
1584 gettext("%llu bytes in %d directories.\n"),
1585 (uint64_t)
1586 DirClusterCount * TheBIOSParameterBlock.bpb.sectors_per_cluster *
1587 TheBIOSParameterBlock.bpb.bytes_per_sector, DirCount);
1588 if (HiddenClusterCount) {
1589 (void) fprintf(outDest,
1590 gettext("%llu bytes in %d hidden files.\n"),
1591 (uint64_t)HiddenClusterCount *
1592 TheBIOSParameterBlock.bpb.sectors_per_cluster *
1593 TheBIOSParameterBlock.bpb.bytes_per_sector,
1594 HiddenFileCount);
1596 (void) fprintf(outDest,
1597 gettext("%llu bytes in %d files.\n"),
1598 (uint64_t)
1599 FileClusterCount * TheBIOSParameterBlock.bpb.sectors_per_cluster *
1600 TheBIOSParameterBlock.bpb.bytes_per_sector, FileCount);
1601 (void) fprintf(outDest,
1602 gettext("%llu bytes free.\n"), (uint64_t)FreeClusterCount *
1603 TheBIOSParameterBlock.bpb.sectors_per_cluster *
1604 TheBIOSParameterBlock.bpb.bytes_per_sector);
1605 (void) fprintf(outDest,
1606 gettext("%d bytes per allocation unit.\n"),
1607 TheBIOSParameterBlock.bpb.sectors_per_cluster *
1608 TheBIOSParameterBlock.bpb.bytes_per_sector);
1609 (void) fprintf(outDest,
1610 gettext("%d total allocation units.\n"), TotalClusters);
1611 if (ReservedClusterCount)
1612 (void) fprintf(outDest, gettext("%d reserved allocation units.\n"),
1613 ReservedClusterCount);
1614 (void) fprintf(outDest,
1615 gettext("%d available allocation units.\n"), FreeClusterCount);