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, Version 1.0 only
6 * (the "License"). You may not use this file except in compliance
7 * with the License.
8 *
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.
13 *
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]
19 *
20 * CDDL HEADER END
21 */
22 /*
23 * Copyright (c) 1999,2001 by Sun Microsystems, Inc.
24 * All rights reserved.
25 * Copyright 2024 MNX Cloud, Inc.
26 */
27
28 /*
29 * fsck_pcfs -- main routines.
30 */
31
32 #include <stdio.h>
33 #include <errno.h>
34 #include <stdlib.h>
35 #include <sys/types.h>
36 #include <sys/stat.h>
37 #include <fcntl.h>
38 #include <strings.h>
39 #include <libintl.h>
40 #include <locale.h>
41 #include <unistd.h>
42 #include <stropts.h>
43 #include <sys/fcntl.h>
44 #include <sys/dktp/fdisk.h>
45 #include <sys/dkio.h>
46 #include "pcfs_common.h"
47 #include "fsck_pcfs.h"
48 #include "pcfs_bpb.h"
49
50 size_t bpsec = MINBPS;
51 int32_t BytesPerCluster;
52 int32_t TotalClusters;
53 int32_t LastCluster;
54 off64_t FirstClusterOffset;
55 off64_t PartitionOffset;
56 bpb_t TheBIOSParameterBlock;
57
58 /*
59 * {Output,Input}Image are the file names where we should write the
60 * checked fs image and from which we should read the initial fs.
61 * The image capability is designed for debugging purposes.
62 */
63 static char *OutputImage = NULL;
64 static char *InputImage = NULL;
65 static int WritableOnly = 0; /* -o w, check writable fs' only */
66 static int Mflag = 0; /* -m, sanity check if fs is mountable */
67 static int Preen = 0; /* -o p, preen; non-interactive */
68 /*
69 * By default be quick; skip verify reads.
70 * If the user wants more exhaustive checking,
71 * they should run with the -o v option.
72 */
73 static int Quick = 1;
74
75 int ReadOnly = 0;
76 int IsFAT32 = 0;
77 int Verbose = 0;
78
79 int AlwaysYes = 0; /* -y or -Y, assume a yes answer to all questions */
80 int AlwaysNo = 0; /* -n or -N, assume a no answer to all questions */
81
82 extern ClusterContents TheRootDir;
83
84 /*
85 * Function definitions
86 */
87 /*
88 * Use DKIOCGMEDIAINFO to get sector size.
89 */
90 static int
get_media_sector_size(int fd,size_t * sizep)91 get_media_sector_size(int fd, size_t *sizep)
92 {
93 struct dk_minfo dkminfo;
94
95 if (ioctl(fd, DKIOCGMEDIAINFO, &dkminfo) != -1) {
96 *sizep = dkminfo.dki_lbsize;
97 return (0);
98 }
99 /* In case the DKIOCGMEDIAINFO is not supported, return MINBPS. */
100 if (errno == ENOTTY) {
101 *sizep = MINBPS;
102 return (0);
103 }
104
105 return (errno);
106 }
107
108 static void
passOne(int fd)109 passOne(int fd)
110 {
111 if (!Quick)
112 findBadClusters(fd);
113 scanAndFixMetadata(fd);
114 }
115
116 static void
writeBackChanges(int fd)117 writeBackChanges(int fd)
118 {
119 writeFATMods(fd);
120 if (!IsFAT32)
121 writeRootDirMods(fd);
122 writeClusterMods(fd);
123 }
124
125 static void
tryOpen(int * fd,char * openMe,int oflag,int exitOnFailure)126 tryOpen(int *fd, char *openMe, int oflag, int exitOnFailure)
127 {
128 int saveError;
129
130 if ((*fd = open(openMe, oflag)) < 0) {
131 if (exitOnFailure == RETURN_ON_OPEN_FAILURE)
132 return;
133 saveError = errno;
134 mountSanityCheckFails();
135 (void) fprintf(stderr, "%s: ", openMe);
136 (void) fprintf(stderr, strerror(saveError));
137 (void) fprintf(stderr, "\n");
138 exit(1);
139 }
140 }
141
142 static void
doOpen(int * inFD,int * outFD,char * name,char * outName)143 doOpen(int *inFD, int *outFD, char *name, char *outName)
144 {
145 if (ReadOnly) {
146 tryOpen(inFD, name, O_RDONLY, EXIT_ON_OPEN_FAILURE);
147 *outFD = -1;
148 } else {
149 tryOpen(inFD, name, O_RDWR, RETURN_ON_OPEN_FAILURE);
150 if (*inFD < 0) {
151 if (errno != EACCES || WritableOnly) {
152 int saveError = errno;
153 mountSanityCheckFails();
154 (void) fprintf(stderr,
155 gettext("%s: "), name);
156 (void) fprintf(stderr, strerror(saveError));
157 (void) fprintf(stderr, "\n");
158 exit(2);
159 } else {
160 tryOpen(inFD, name, O_RDONLY,
161 EXIT_ON_OPEN_FAILURE);
162 AlwaysYes = 0;
163 AlwaysNo = 1;
164 ReadOnly = 1;
165 *outFD = -1;
166 }
167 } else {
168 *outFD = *inFD;
169 }
170 }
171
172 if (outName != NULL) {
173 tryOpen(outFD, outName, (O_RDWR | O_CREAT),
174 EXIT_ON_OPEN_FAILURE);
175 }
176
177 (void) printf("** %s %s\n", name,
178 ReadOnly ? gettext("(NO WRITE)") : "");
179 }
180
181 static void
openFS(char * special,int * inFD,int * outFD)182 openFS(char *special, int *inFD, int *outFD)
183 {
184 struct stat dinfo;
185 char *actualDisk = NULL;
186 char *suffix = NULL;
187 int rv;
188
189 if (Verbose)
190 (void) fprintf(stderr, gettext("Opening file system.\n"));
191
192 if (InputImage == NULL) {
193 actualDisk = stat_actual_disk(special, &dinfo, &suffix);
194 /*
195 * Destination exists, now find more about it.
196 */
197 if (!(S_ISCHR(dinfo.st_mode))) {
198 mountSanityCheckFails();
199 (void) fprintf(stderr,
200 gettext("\n%s: device name must be a "
201 "character special device.\n"), actualDisk);
202 exit(2);
203 }
204 } else {
205 actualDisk = InputImage;
206 }
207 doOpen(inFD, outFD, actualDisk, OutputImage);
208 rv = get_media_sector_size(*inFD, &bpsec);
209 if (rv != 0) {
210 (void) fprintf(stderr,
211 gettext("error detecting device sector size: %s\n"),
212 strerror(rv));
213 exit(2);
214 }
215 if (bpsec != 512 && bpsec != 1024 && bpsec != 2048 && bpsec != 4096) {
216 (void) fprintf(stderr,
217 gettext("unsupported sector size: %zu\n"), bpsec);
218 exit(2);
219 }
220
221 if (suffix) {
222 if ((PartitionOffset =
223 findPartitionOffset(*inFD, suffix)) < 0) {
224 mountSanityCheckFails();
225 (void) fprintf(stderr,
226 gettext("Unable to find logical drive %s\n"),
227 suffix);
228 exit(2);
229 } else if (Verbose) {
230 (void) fprintf(stderr,
231 gettext("Partition starts at offset %lld\n"),
232 PartitionOffset);
233 }
234 } else {
235 PartitionOffset = 0;
236 }
237 }
238
239 void
usage(void)240 usage(void)
241 {
242 (void) fprintf(stderr,
243 gettext("pcfs Usage: fsck -F pcfs [-o v|p|w] special-file\n"));
244 exit(1);
245 }
246
247 static
248 char *LegalOpts[] = {
249 #define VFLAG 0
250 "v",
251 #define PFLAG 1
252 "p",
253 #define WFLAG 2
254 "w",
255 #define DFLAG 3
256 "d",
257 #define IFLAG 4
258 "i",
259 #define OFLAG 5
260 "o",
261 NULL
262 };
263
264 static void
parseSubOptions(char * optsstr)265 parseSubOptions(char *optsstr)
266 {
267 char *value;
268 int c;
269
270 while (*optsstr != '\0') {
271 switch (c = getsubopt(&optsstr, LegalOpts, &value)) {
272 case VFLAG:
273 Quick = 0;
274 break;
275 case PFLAG:
276 Preen++;
277 break;
278 case WFLAG:
279 WritableOnly++;
280 break;
281 case DFLAG:
282 Verbose++;
283 break;
284 case IFLAG:
285 if (value == NULL) {
286 missing_arg(LegalOpts[c]);
287 } else {
288 InputImage = value;
289 }
290 break;
291 case OFLAG:
292 if (value == NULL) {
293 missing_arg(LegalOpts[c]);
294 } else {
295 OutputImage = value;
296 }
297 break;
298 default:
299 bad_arg(value);
300 break;
301 }
302 }
303 }
304
305 static void
sanityCheckOpts(void)306 sanityCheckOpts(void)
307 {
308 if (WritableOnly && ReadOnly) {
309 (void) fprintf(stderr,
310 gettext("-w option may not be used with the -n "
311 "or -m options\n"));
312 exit(4);
313 }
314 }
315
316 static void
confirmMountable(char * special,int fd)317 confirmMountable(char *special, int fd)
318 {
319 char *printName;
320 int okayToMount = 1;
321
322 printName = InputImage ? InputImage : special;
323
324 if (!IsFAT32) {
325 /* make sure we can at least read the root directory */
326 getRootDirectory(fd);
327 if (TheRootDir.bytes == NULL)
328 okayToMount = 0;
329 } else {
330 /* check the bit designed into FAT32 for this purpose */
331 okayToMount = checkFAT32CleanBit(fd);
332 }
333 if (okayToMount) {
334 (void) fprintf(stderr,
335 gettext("pcfs fsck: sanity check: %s okay\n"), printName);
336 exit(0);
337 } else {
338 (void) fprintf(stderr,
339 gettext("pcfs fsck: sanity check: %s needs checking\n"),
340 printName);
341 exit(32);
342 }
343 }
344
345 void
mountSanityCheckFails(void)346 mountSanityCheckFails(void)
347 {
348 if (Mflag) {
349 (void) fprintf(stderr,
350 gettext("pcfs fsck: sanity check failed: "));
351 }
352 }
353
354 /*
355 * preenBail
356 * Routine that other routines can call if they would go into a
357 * state where they need user input. They can send an optional
358 * message string to be printed before the exit. Caller should
359 * send a NULL string if they don't have an exit message.
360 */
361 void
preenBail(char * outString)362 preenBail(char *outString)
363 {
364 /*
365 * If we are running in the 'preen' mode, we got here because
366 * we reached a situation that would require user intervention.
367 * We have no choice but to bail at this point.
368 */
369 if (Preen) {
370 if (outString)
371 (void) printf("%s", outString);
372 (void) printf(gettext("FILE SYSTEM FIX REQUIRES USER "
373 "INTERVENTION; RUN fsck MANUALLY.\n"));
374 exit(36);
375 }
376 }
377
378 int
main(int argc,char * argv[])379 main(int argc, char *argv[])
380 {
381 char *string;
382 int ifd, ofd;
383 int c;
384
385 (void) setlocale(LC_ALL, "");
386
387 #if !defined(TEXT_DOMAIN)
388 #define TEXT_DOMAIN "SYS_TEST"
389 #endif
390 (void) textdomain(TEXT_DOMAIN);
391
392 if (argc < 2)
393 usage();
394
395 while ((c = getopt(argc, argv, "F:VYNynmo:")) != EOF) {
396 switch (c) {
397 case 'F':
398 string = optarg;
399 if (strcmp(string, "pcfs") != 0)
400 usage();
401 break;
402 case 'V': {
403 char *opt_text;
404 int opt_count;
405
406 (void) printf(gettext("fsck -F pcfs "));
407 for (opt_count = 1; opt_count < argc;
408 opt_count++) {
409 opt_text = argv[opt_count];
410 if (opt_text)
411 (void) printf(" %s ",
412 opt_text);
413 }
414 (void) printf("\n");
415 exit(0);
416 }
417 break;
418 case 'N':
419 case 'n':
420 AlwaysYes = 0;
421 AlwaysNo = 1;
422 ReadOnly = 1;
423 break;
424 case 'Y':
425 case 'y':
426 AlwaysYes = 1;
427 AlwaysNo = 0;
428 break;
429 case 'm':
430 Mflag++;
431 ReadOnly = 1;
432 break;
433 case 'o':
434 string = optarg;
435 parseSubOptions(string);
436 break;
437 }
438 }
439
440 sanityCheckOpts();
441 if (InputImage == NULL && (optind < 0 || optind >= argc))
442 usage();
443
444 openFS(argv[optind], &ifd, &ofd);
445 readBPB(ifd);
446
447 /*
448 * -m mountable fs check. This call will not return.
449 */
450 if (Mflag)
451 confirmMountable(argv[optind], ifd);
452
453 /*
454 * Pass 1: Find any bad clusters and adjust the FAT and directory
455 * entries accordingly
456 */
457 passOne(ifd);
458
459 /*
460 * XXX - future passes?
461 * Ideas:
462 * Data relocation for bad clusters with partial read success?
463 * Syncing backup FAT copies with main copy?
464 * Syncing backup root sector for FAT32?
465 */
466
467 /*
468 * No problems if we made it this far.
469 */
470 printSummary(stdout);
471 writeBackChanges(ofd);
472 return (0);
473 }
474