xref: /haiku/src/bin/makebootable/platform/bios_ia32/makebootable.cpp (revision e711e6e42fd7ec3111ba9dc2324fa8efedd6674b)
1 /*
2  * Copyright 2005-2009, Ingo Weinhold, ingo_weinhold@gmx.de.
3  * Distributed under the terms of the MIT License.
4  */
5 
6 
7 #include <errno.h>
8 #include <fcntl.h>
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <string.h>
12 #include <unistd.h>
13 #include <sys/stat.h>
14 
15 #include <ByteOrder.h>
16 #include <Drivers.h>
17 #include <Entry.h>
18 #include <File.h>
19 #include <fs_info.h>
20 #include <Resources.h>
21 #include <TypeConstants.h>
22 
23 // Linux and FreeBSD support
24 #ifdef HAIKU_HOST_PLATFORM_LINUX
25 #	include <ctype.h>
26 #	include <linux/fs.h>
27 #	include <linux/hdreg.h>
28 #	include <sys/ioctl.h>
29 
30 #	define USE_PARTITION_MAP 1
31 #elif HAIKU_HOST_PLATFORM_FREEBSD
32 #	include <ctype.h>
33 #	include <sys/disklabel.h>
34 #	include <sys/disk.h>
35 #	include <sys/ioctl.h>
36 
37 #	define USE_PARTITION_MAP 1
38 #elif HAIKU_HOST_PLATFORM_DARWIN
39 #	include <ctype.h>
40 #	include <sys/disk.h>
41 #	include <sys/ioctl.h>
42 
43 #	define USE_PARTITION_MAP 1
44 #endif
45 
46 #ifdef HAIKU_TARGET_PLATFORM_HAIKU
47 #	include <image.h>
48 
49 #	include <DiskDevice.h>
50 #	include <DiskDeviceRoster.h>
51 #	include <Partition.h>
52 #	include <Path.h>
53 
54 #	include "bfs_control.h"
55 #endif
56 
57 #if USE_PARTITION_MAP
58 #	include "PartitionMap.h"
59 #	include "PartitionMapParser.h"
60 #endif
61 
62 
63 static const char *kCommandName = "makebootable";
64 
65 static const int kBootCodeSize				= 1024;
66 static const int kFirstBootCodePartSize		= 512;
67 static const int kSecondBootCodePartOffset	= 676;
68 static const int kSecondBootCodePartSize	= kBootCodeSize
69 												- kSecondBootCodePartOffset;
70 static const int kPartitionOffsetOffset		= 506;
71 
72 static int kArgc;
73 static const char *const *kArgv;
74 
75 // usage
76 const char *kUsage =
77 "Usage: %s [ options ] <file> ...\n"
78 "\n"
79 "Makes the specified BFS partitions/devices bootable by writing boot code\n"
80 "into the first two sectors. It doesn't mark the partition(s) active.\n"
81 "\n"
82 "If a given <file> refers to a directory, the partition/device on which the\n"
83 "directory resides will be made bootable. If it refers to a regular file,\n"
84 "the file is considered a disk image and the boot code will be written to\n"
85 "it.\n"
86 "\n"
87 "Options:\n"
88 "  -h, --help    - Print this help text and exit.\n"
89 "  --dry-run     - Do everything but actually writing the boot block to disk.\n"
90 ;
91 
92 
93 // print_usage
94 static void
95 print_usage(bool error)
96 {
97 	// get command name
98 	const char *commandName = NULL;
99 	if (kArgc > 0) {
100 		if (const char *lastSlash = strchr(kArgv[0], '/'))
101 			commandName = lastSlash + 1;
102 		else
103 			commandName = kArgv[0];
104 	}
105 
106 	if (!commandName || strlen(commandName) == 0)
107 		commandName = kCommandName;
108 
109 	// print usage
110 	fprintf((error ? stderr : stdout), kUsage, commandName, commandName,
111 		commandName);
112 }
113 
114 
115 // print_usage_and_exit
116 static void
117 print_usage_and_exit(bool error)
118 {
119 	print_usage(error);
120 	exit(error ? 1 : 0);
121 }
122 
123 
124 // read_boot_code_data
125 static uint8 *
126 read_boot_code_data(const char* programPath)
127 {
128 	// open our executable
129 	BFile executableFile;
130 	status_t error = executableFile.SetTo(programPath, B_READ_ONLY);
131 	if (error != B_OK) {
132 		fprintf(stderr, "Error: Failed to open my executable file (\"%s\": "
133 			"%s\n", programPath, strerror(error));
134 		exit(1);
135 	}
136 
137 	uint8 *bootCodeData = new uint8[kBootCodeSize];
138 
139 	// open our resources
140 	BResources resources;
141 	error = resources.SetTo(&executableFile);
142 	const void *resourceData = NULL;
143 	if (error == B_OK) {
144 		// read the boot block from the resources
145 		size_t resourceSize;
146 		resourceData = resources.LoadResource(B_RAW_TYPE, 666, &resourceSize);
147 
148 		if (resourceData && resourceSize != (size_t)kBootCodeSize) {
149 			resourceData = NULL;
150 			printf("Warning: Something is fishy with my resources! The boot "
151 				"code doesn't have the correct size. Trying the attribute "
152 				"instead ...\n");
153 		}
154 	}
155 
156 	if (resourceData) {
157 		// found boot data in the resources
158 		memcpy(bootCodeData, resourceData, kBootCodeSize);
159 	} else {
160 		// no boot data in the resources; try the attribute
161 		ssize_t bytesRead = executableFile.ReadAttr("BootCode", B_RAW_TYPE,
162 			0, bootCodeData, kBootCodeSize);
163 		if (bytesRead < 0) {
164 			fprintf(stderr, "Error: Failed to read boot code from resources "
165 				"or attribute.\n");
166 			exit(1);
167 		}
168 		if (bytesRead != kBootCodeSize) {
169 			fprintf(stderr, "Error: Failed to read boot code from resources, "
170 				"and the boot code in the attribute has the wrong size!\n");
171 			exit(1);
172 		}
173 	}
174 
175 	return bootCodeData;
176 }
177 
178 
179 // write_boot_code_part
180 static void
181 write_boot_code_part(const char *fileName, int fd, off_t imageOffset,
182 	const uint8 *bootCodeData, int offset, int size, bool dryRun)
183 {
184 	if (!dryRun) {
185 		ssize_t bytesWritten = write_pos(fd, imageOffset + offset,
186 			bootCodeData + offset, size);
187 		if (bytesWritten != size) {
188 			fprintf(stderr, "Error: Failed to write to \"%s\": %s\n", fileName,
189 				strerror(bytesWritten < 0 ? errno : B_ERROR));
190 		}
191 	}
192 }
193 
194 
195 #ifdef HAIKU_TARGET_PLATFORM_HAIKU
196 
197 static status_t
198 find_own_image(image_info *info)
199 {
200 	int32 cookie = 0;
201 	while (get_next_image_info(B_CURRENT_TEAM, &cookie, info) == B_OK) {
202 		if (((addr_t)info->text <= (addr_t)find_own_image
203 			&& (addr_t)info->text + info->text_size
204 				> (addr_t)find_own_image)) {
205 			return B_OK;
206 		}
207 	}
208 
209 	return B_NAME_NOT_FOUND;
210 }
211 
212 #endif
213 
214 
215 #if USE_PARTITION_MAP
216 
217 static void
218 dump_partition_map(const PartitionMap& map)
219 {
220 	fprintf(stderr, "partitions:\n");
221 	int32 count = map.CountPartitions();
222 	for (int i = 0; i < count; i++) {
223 		const Partition* partition = map.PartitionAt(i);
224 		fprintf(stderr, "%2d: ", i);
225 		if (partition == NULL) {
226 			fprintf(stderr, "<null>\n");
227 			continue;
228 		}
229 
230 		if (partition->IsEmpty()) {
231 			fprintf(stderr, "<empty>\n");
232 			continue;
233 		}
234 
235 		fprintf(stderr, "offset: %16" B_PRIdOFF ", size: %16" B_PRIdOFF
236 			", type: %x%s\n", partition->Offset(), partition->Size(),
237 			partition->Type(), partition->IsExtended() ? " (extended)" : "");
238 	}
239 }
240 
241 #endif
242 
243 
244 // main
245 int
246 main(int argc, const char *const *argv)
247 {
248 	kArgc = argc;
249 	kArgv = argv;
250 
251 	if (argc < 2)
252 		print_usage_and_exit(true);
253 
254 	// parameters
255 	const char **files = new const char*[argc];
256 	int fileCount = 0;
257 	bool dryRun = false;
258 	off_t startOffset = 0;
259 
260 	// parse arguments
261 	for (int argi = 1; argi < argc;) {
262 		const char *arg = argv[argi++];
263 
264 		if (arg[0] == '-') {
265 			if (strcmp(arg, "-h") == 0 || strcmp(arg, "--help") == 0) {
266 				print_usage_and_exit(false);
267 			} else if (strcmp(arg, "--dry-run") == 0) {
268 				dryRun = true;
269 			} else if (strcmp(arg, "--start-offset") == 0) {
270 				if (argi >= argc)
271 					print_usage_and_exit(true);
272 				startOffset = strtoll(argv[argi++], NULL, 0);
273 			} else {
274 				print_usage_and_exit(true);
275 			}
276 
277 		} else {
278 			files[fileCount++] = arg;
279 		}
280 	}
281 
282 	// we need at least one file
283 	if (fileCount == 0)
284 		print_usage_and_exit(true);
285 
286 	// read the boot code
287 	uint8 *bootCodeData = NULL;
288 #ifndef HAIKU_TARGET_PLATFORM_HAIKU
289 	bootCodeData = read_boot_code_data(argv[0]);
290 #else
291 	image_info info;
292 	if (find_own_image(&info) == B_OK)
293 		bootCodeData = read_boot_code_data(info.name);
294 #endif
295 	if (!bootCodeData) {
296 		fprintf(stderr, "Error: Failed to read \n");
297 		exit(1);
298 	}
299 
300 	// iterate through the files and make them bootable
301 	status_t error;
302 	for (int i = 0; i < fileCount; i++) {
303 		const char *fileName = files[i];
304 		BEntry entry;
305 		error = entry.SetTo(fileName, true);
306 		if (error != B_OK) {
307 			fprintf(stderr, "Error: Failed to open \"%s\": %s\n",
308 				fileName, strerror(error));
309 			exit(1);
310 		}
311 
312 		// get stat to check the type of the file
313 		struct stat st;
314 		error = entry.GetStat(&st);
315 		if (error != B_OK) {
316 			fprintf(stderr, "Error: Failed to stat \"%s\": %s\n",
317 				fileName, strerror(error));
318 			exit(1);
319 		}
320 
321 		bool noPartition = false;
322 		int64 partitionOffset = 0;
323 		fs_info info;	// needs to be here (we use the device name later)
324 		if (S_ISDIR(st.st_mode)) {
325 			#ifdef HAIKU_TARGET_PLATFORM_HAIKU
326 
327 				// a directory: get the device
328 				error = fs_stat_dev(st.st_dev, &info);
329 				if (error != B_OK) {
330 					fprintf(stderr, "Error: Failed to determine device for "
331 						"\"%s\": %s\n", fileName, strerror(error));
332 					exit(1);
333 				}
334 
335 				fileName = info.device_name;
336 
337 			#else
338 
339 				(void)info;
340 				fprintf(stderr, "Error: Specifying directories not supported "
341 					"on this platform!\n");
342 				exit(1);
343 
344 			#endif
345 
346 		} else if (S_ISREG(st.st_mode)) {
347 			// a regular file: fine
348 			noPartition = true;
349 		} else if (S_ISCHR(st.st_mode)) {
350 			// character special: a device or partition under BeOS
351 			// or under FreeBSD
352 			#if !defined(HAIKU_TARGET_PLATFORM_HAIKU) \
353 				&& !defined(HAIKU_HOST_PLATFORM_FREEBSD)
354 
355 				fprintf(stderr, "Error: Character special devices not "
356 					"supported on this platform.\n");
357 				exit(1);
358 
359 			#endif
360 
361 			#ifdef HAIKU_HOST_PLATFORM_FREEBSD
362 
363 				// chop off the trailing number
364 				int fileNameLen = strlen(fileName);
365 				int baseNameLen = -1;
366 				for (int k = fileNameLen - 1; k >= 0; k--) {
367 					if (!isdigit(fileName[k])) {
368 						baseNameLen = k + 1;
369 						break;
370 					}
371 				}
372 
373 				// Remove de 's' from 'ad2s2' slice device (partition for DOS
374 				// users) to get 'ad2' base device
375 				baseNameLen--;
376 
377 				if (baseNameLen < 0) {
378 					// only digits?
379 					fprintf(stderr, "Error: Failed to get base device name.\n");
380 					exit(1);
381 				}
382 
383 				if (baseNameLen < fileNameLen) {
384 					// get base device name and partition index
385 					char baseDeviceName[B_PATH_NAME_LENGTH];
386 					int partitionIndex = atoi(fileName + baseNameLen + 1);
387 						// Don't forget the 's' of slice :)
388 					memcpy(baseDeviceName, fileName, baseNameLen);
389 					baseDeviceName[baseNameLen] = '\0';
390 
391 					// open base device
392 					int baseFD = open(baseDeviceName, O_RDONLY);
393 					if (baseFD < 0) {
394 						fprintf(stderr, "Error: Failed to open \"%s\": %s\n",
395 							baseDeviceName, strerror(errno));
396 						exit(1);
397 					}
398 
399 					// get device size
400 					int64 deviceSize;
401 					if (ioctl(baseFD, DIOCGMEDIASIZE, &deviceSize) == -1) {
402 						fprintf(stderr, "Error: Failed to get device geometry "
403 							"for \"%s\": %s\n", baseDeviceName,
404 							strerror(errno));
405 						exit(1);
406 					}
407 
408 					// parse the partition map
409 					// TODO: block size!
410 					PartitionMapParser parser(baseFD, 0, deviceSize, 512);
411 					PartitionMap map;
412 					error = parser.Parse(NULL, &map);
413 					if (error != B_OK) {
414 						fprintf(stderr, "Error: Parsing partition table on "
415 							"device \"%s\" failed: %s\n", baseDeviceName,
416 							strerror(error));
417 						exit(1);
418 					}
419 
420 					close(baseFD);
421 
422 					// check the partition we are supposed to write at
423 					Partition *partition = map.PartitionAt(partitionIndex - 1);
424 					if (!partition || partition->IsEmpty()) {
425 						fprintf(stderr, "Error: Invalid partition index %d.\n",
426 							partitionIndex);
427 						dump_partition_map(map);
428 						exit(1);
429 					}
430 
431 					if (partition->IsExtended()) {
432 						fprintf(stderr, "Error: Partition %d is an extended "
433 							"partition.\n", partitionIndex);
434 						dump_partition_map(map);
435 						exit(1);
436 					}
437 
438 					partitionOffset = partition->Offset();
439 
440 				} else {
441 					// The given device is the base device. We'll write at
442 					// offset 0.
443 				}
444 
445 			#endif // HAIKU_HOST_PLATFORM_FREEBSD
446 
447 		} else if (S_ISBLK(st.st_mode)) {
448 			// block device: a device or partition under Linux or Darwin
449 			#ifdef HAIKU_HOST_PLATFORM_LINUX
450 
451 				// chop off the trailing number
452 				int fileNameLen = strlen(fileName);
453 				int baseNameLen = -1;
454 				for (int k = fileNameLen - 1; k >= 0; k--) {
455 					if (!isdigit(fileName[k])) {
456 						baseNameLen = k + 1;
457 						break;
458 					}
459 				}
460 
461 				if (baseNameLen < 0) {
462 					// only digits?
463 					fprintf(stderr, "Error: Failed to get base device name.\n");
464 					exit(1);
465 				}
466 
467 				if (baseNameLen < fileNameLen) {
468 					// get base device name and partition index
469 					char baseDeviceName[B_PATH_NAME_LENGTH];
470 					int partitionIndex = atoi(fileName + baseNameLen);
471 					memcpy(baseDeviceName, fileName, baseNameLen);
472 					baseDeviceName[baseNameLen] = '\0';
473 
474 					// open base device
475 					int baseFD = open(baseDeviceName, O_RDONLY);
476 					if (baseFD < 0) {
477 						fprintf(stderr, "Error: Failed to open \"%s\": %s\n",
478 							baseDeviceName, strerror(errno));
479 						exit(1);
480 					}
481 
482 					// get device size -- try BLKGETSIZE64, but, if it doesn't
483 					// work, fall back to the obsolete HDIO_GETGEO
484 					int64 deviceSize;
485 					hd_geometry geometry;
486 					if (ioctl(baseFD, BLKGETSIZE64, &deviceSize) == 0
487 						&& deviceSize > 0) {
488 						// looks good
489 					} else if (ioctl(baseFD, HDIO_GETGEO, &geometry) == 0) {
490 						deviceSize = (int64)geometry.heads * geometry.sectors
491 							* geometry.cylinders * 512;
492 					} else {
493 						fprintf(stderr, "Error: Failed to get device geometry "
494 							"for \"%s\": %s\n", baseDeviceName,
495 							strerror(errno));
496 						exit(1);
497 					}
498 
499 					// parse the partition map
500 					// TODO: block size!
501 					PartitionMapParser parser(baseFD, 0, deviceSize, 512);
502 					PartitionMap map;
503 					error = parser.Parse(NULL, &map);
504 					if (error != B_OK) {
505 						fprintf(stderr, "Error: Parsing partition table on "
506 							"device \"%s\" failed: %s\n", baseDeviceName,
507 							strerror(error));
508 						exit(1);
509 					}
510 
511 					close(baseFD);
512 
513 					// check the partition we are supposed to write at
514 					Partition *partition = map.PartitionAt(partitionIndex - 1);
515 					if (!partition || partition->IsEmpty()) {
516 						fprintf(stderr, "Error: Invalid partition index %d.\n",
517 							partitionIndex);
518 						dump_partition_map(map);
519 						exit(1);
520 					}
521 
522 					if (partition->IsExtended()) {
523 						fprintf(stderr, "Error: Partition %d is an extended "
524 							"partition.\n", partitionIndex);
525 						dump_partition_map(map);
526 						exit(1);
527 					}
528 
529 					partitionOffset = partition->Offset();
530 				} else {
531 					// The given device is the base device. We'll write at
532 					// offset 0.
533 				}
534 
535 			#elif defined(HAIKU_HOST_PLATFORM_DARWIN)
536 				// chop off the trailing number
537 				int fileNameLen = strlen(fileName);
538 				int baseNameLen = fileNameLen - 2;
539 
540 				// get base device name and partition index
541 				char baseDeviceName[B_PATH_NAME_LENGTH];
542 				int partitionIndex = atoi(fileName + baseNameLen + 1);
543 				memcpy(baseDeviceName, fileName, baseNameLen);
544 				baseDeviceName[baseNameLen] = '\0';
545 
546 				// open base device
547 				int baseFD = open(baseDeviceName, O_RDONLY);
548 				if (baseFD < 0) {
549 					fprintf(stderr, "Error: Failed to open \"%s\": %s\n",
550 							baseDeviceName, strerror(errno));
551 					exit(1);
552 				}
553 
554 				// get device size
555 				int64 blockSize;
556 				int64 blockCount;
557 				int64 deviceSize;
558 				if (ioctl(baseFD, DKIOCGETBLOCKSIZE, &blockSize) == -1) {
559 					fprintf(stderr, "Error: Failed to get block size "
560 							"for \"%s\": %s\n", baseDeviceName,
561 							strerror(errno));
562 					exit(1);
563 				}
564 				if (ioctl(baseFD, DKIOCGETBLOCKCOUNT, &blockCount) == -1) {
565 					fprintf(stderr, "Error: Failed to get block count "
566 							"for \"%s\": %s\n", baseDeviceName,
567 							strerror(errno));
568 					exit(1);
569 				}
570 
571 				deviceSize = blockSize * blockCount;
572 
573 				// parse the partition map
574 				PartitionMapParser parser(baseFD, 0, deviceSize, blockSize);
575 				PartitionMap map;
576 				error = parser.Parse(NULL, &map);
577 				if (error != B_OK) {
578 					fprintf(stderr, "Error: Parsing partition table on "
579 							"device \"%s\" failed: %s\n", baseDeviceName,
580 							strerror(error));
581 					exit(1);
582 				}
583 
584 				close(baseFD);
585 
586 				// check the partition we are supposed to write at
587 				Partition *partition = map.PartitionAt(partitionIndex - 1);
588 				if (!partition || partition->IsEmpty()) {
589 					fprintf(stderr, "Error: Invalid partition index %d.\n",
590 						partitionIndex);
591 					dump_partition_map(map);
592 					exit(1);
593 				}
594 
595 				if (partition->IsExtended()) {
596 					fprintf(stderr, "Error: Partition %d is an extended "
597 						"partition.\n", partitionIndex);
598 					dump_partition_map(map);
599 					exit(1);
600 				}
601 				partitionOffset = partition->Offset();
602 			#else
603 			// partitions are block devices under Haiku, but not under BeOS
604 			#ifdef HAIKU_TARGET_PLATFORM_HAIKU
605 				fprintf(stderr, "Error: Block devices not supported on this "
606 					"platform!\n");
607 				exit(1);
608 			#endif	// HAIKU_TARGET_PLATFORM_HAIKU
609 
610 			#endif
611 		} else {
612 			fprintf(stderr, "Error: File type of \"%s\" is not supported.\n",
613 				fileName);
614 			exit(1);
615 		}
616 
617 		// open the file
618 		int fd = open(fileName, O_RDWR);
619 		if (fd < 0) {
620 			fprintf(stderr, "Error: Failed to open \"%s\": %s\n", fileName,
621 				strerror(errno));
622 			exit(1);
623 		}
624 
625 		#ifdef HAIKU_TARGET_PLATFORM_HAIKU
626 
627 			// get a partition info
628 			if (!noPartition
629 				&& strlen(fileName) >= 3
630 				&& strncmp("raw", fileName + strlen(fileName) - 3, 3)) {
631 				partition_info partitionInfo;
632 				if (ioctl(fd, B_GET_PARTITION_INFO, &partitionInfo,
633 						sizeof(partitionInfo)) == 0) {
634 					partitionOffset = partitionInfo.offset;
635 				} else {
636 					fprintf(stderr, "Error: Failed to get partition info: %s\n",
637 						strerror(errno));
638 					exit(1);
639 				}
640 			}
641 
642 		#endif	// __BEOS__
643 
644 		// adjust the partition offset in the boot code data
645 		// hard coded sector size: 512 bytes
646 		*(uint32*)(bootCodeData + kPartitionOffsetOffset)
647 			= B_HOST_TO_LENDIAN_INT32((uint32)(partitionOffset / 512));
648 
649 		// write the boot code
650 		printf("Writing boot code to \"%s\" (partition offset: %" B_PRId64
651 			" bytes, start offset = %" B_PRIdOFF ") "
652 			"...\n", fileName, partitionOffset, startOffset);
653 
654 		write_boot_code_part(fileName, fd, startOffset, bootCodeData, 0,
655 			kFirstBootCodePartSize, dryRun);
656 		write_boot_code_part(fileName, fd, startOffset, bootCodeData,
657 			kSecondBootCodePartOffset, kSecondBootCodePartSize,
658 			dryRun);
659 
660 #ifdef HAIKU_TARGET_PLATFORM_HAIKU
661 		// check if this partition is mounted
662 		BDiskDeviceRoster roster;
663 		BPartition* partition;
664 		BDiskDevice device;
665 		status_t status = roster.GetPartitionForPath(fileName, &device,
666 			&partition);
667 		if (status != B_OK) {
668 			status = roster.GetFileDeviceForPath(fileName, &device);
669 			if (status == B_OK)
670 				partition = &device;
671 		}
672 		if (status == B_OK && partition->IsMounted() && !dryRun) {
673 			// This partition is mounted, we need to tell BFS to update its
674 			// boot block (we are using part of the same logical block).
675 			BPath path;
676 			status = partition->GetMountPoint(&path);
677 			if (status == B_OK) {
678 				update_boot_block update;
679 				update.offset = kSecondBootCodePartOffset - 512;
680 				update.data = bootCodeData + kSecondBootCodePartOffset;
681 				update.length = kSecondBootCodePartSize;
682 
683 				int mountFD = open(path.Path(), O_RDONLY);
684 				if (ioctl(mountFD, BFS_IOCTL_UPDATE_BOOT_BLOCK, &update,
685 						sizeof(update_boot_block)) != 0) {
686 					fprintf(stderr, "Could not update BFS boot block: %s\n",
687 						strerror(errno));
688 				}
689 				close(mountFD);
690 			} else {
691 				fprintf(stderr, "Could not update BFS boot code while the "
692 					"partition is mounted!\n");
693 			}
694 		}
695 #endif	// HAIKU_TARGET_PLATFORM_HAIKU
696 
697 		close(fd);
698 	}
699 
700 	return 0;
701 }
702