1 /* ----------------------------------------------------------------------- *
2  *
3  *   Copyright 2003-2009 H. Peter Anvin - All Rights Reserved
4  *   Copyright 2009-2010 Intel Corporation; author: H. Peter Anvin
5  *   Copyright 2010 Shao Miller
6  *   Copyright 2010-2012 Michal Soltys
7  *
8  *   This program is free software; you can redistribute it and/or modify
9  *   it under the terms of the GNU General Public License as published by
10  *   the Free Software Foundation, Inc., 53 Temple Place Ste 330,
11  *   Boston MA 02111-1307, USA; either version 2 of the License, or
12  *   (at your option) any later version; incorporated herein by reference.
13  *
14  * ----------------------------------------------------------------------- */
15 
16 /*
17  * Please see doc/chain.txt for the detailed documentation.
18  */
19 
20 #include <com32.h>
21 #include <stdlib.h>
22 #include <stdio.h>
23 #include <ctype.h>
24 #include <string.h>
25 #include <console.h>
26 #include <consoles.h>
27 #include <minmax.h>
28 #include <stdbool.h>
29 #include <dprintf.h>
30 #include <errno.h>
31 #include <unistd.h>
32 #include <syslinux/loadfile.h>
33 #include <syslinux/bootrm.h>
34 #include <syslinux/config.h>
35 #include <syslinux/disk.h>
36 #include <syslinux/video.h>
37 #include "chain.h"
38 #include "utility.h"
39 #include "options.h"
40 #include "partiter.h"
41 #include "mangle.h"
42 
43 static int fixed_cnt = 128;   /* see comments in main() */
44 
overlap(const struct data_area * a,const struct data_area * b)45 static int overlap(const struct data_area *a, const struct data_area *b)
46 {
47     return
48 	a->base + a->size > b->base &&
49 	b->base + b->size > a->base;
50 }
51 
is_phys(uint8_t sdifs)52 static int is_phys(uint8_t sdifs)
53 {
54     return
55 	sdifs == SYSLINUX_FS_SYSLINUX ||
56 	sdifs == SYSLINUX_FS_EXTLINUX ||
57 	sdifs == SYSLINUX_FS_ISOLINUX;
58 }
59 
60 /*
61  * Search for a specific drive, based on the MBR signature.
62  * Return drive and iterator at 0th position.
63  */
find_by_sig(uint32_t mbr_sig,struct part_iter ** _boot_part)64 static int find_by_sig(uint32_t mbr_sig,
65 			struct part_iter **_boot_part)
66 {
67     struct part_iter *iter = NULL;
68     struct disk_info diskinfo;
69     int drive;
70 
71     for (drive = 0x80; drive < 0x80 + fixed_cnt; drive++) {
72 	if (disk_get_params(drive, &diskinfo))
73 	    continue;		/* Drive doesn't exist */
74 	if (!(iter = pi_begin(&diskinfo, opt.piflags)))
75 	    continue;
76 	/* Check for a matching MBR disk */
77 	if (iter->type == typedos && iter->dos.disk_sig == mbr_sig)
78 	    goto ok;
79 	pi_del(&iter);
80     }
81     drive = -1;
82 ok:
83     *_boot_part = iter;
84     return drive;
85 }
86 
87 /*
88  * Search for a specific drive/partition, based on the GPT GUID.
89  * Return drive and iterator at proper position.
90  */
find_by_guid(const struct guid * gpt_guid,struct part_iter ** _boot_part)91 static int find_by_guid(const struct guid *gpt_guid,
92 			struct part_iter **_boot_part)
93 {
94     struct part_iter *iter = NULL;
95     struct disk_info diskinfo;
96     int drive;
97 
98     for (drive = 0x80; drive < 0x80 + fixed_cnt; drive++) {
99 	if (disk_get_params(drive, &diskinfo))
100 	    continue;		/* Drive doesn't exist */
101 	if (!(iter = pi_begin(&diskinfo, opt.piflags)))
102 	    continue;
103 	/* Check for a matching GPT disk/partition guid */
104 	if (iter->type == typegpt)
105 	    do {
106 		if (!memcmp(&iter->gpt.part_guid, gpt_guid, sizeof *gpt_guid))
107 		    goto ok;
108 	    } while (!pi_next(iter));
109 	pi_del(&iter);
110     }
111     drive = -1;
112 ok:
113     *_boot_part = iter;
114     return drive;
115 }
116 
117 /*
118  * Search for a specific drive/partition, based on the GPT label.
119  * Return drive and iterator at proper position.
120  */
find_by_label(const char * label,struct part_iter ** _boot_part)121 static int find_by_label(const char *label, struct part_iter **_boot_part)
122 {
123     struct part_iter *iter = NULL;
124     struct disk_info diskinfo;
125     int drive;
126 
127     for (drive = 0x80; drive < 0x80 + fixed_cnt; drive++) {
128 	if (disk_get_params(drive, &diskinfo))
129 	    continue;		/* Drive doesn't exist */
130 	if (!(iter = pi_begin(&diskinfo, opt.piflags)))
131 	    continue;
132 	/* Check for a matching GPT partition label */
133 	if (iter->type == typegpt)
134 	    while (!pi_next(iter)) {
135 		if (!strcmp(label, iter->gpt.part_label))
136 		    goto ok;
137 	    }
138 	pi_del(&iter);
139     }
140     drive = -1;
141 ok:
142     *_boot_part = iter;
143     return drive;
144 }
145 
do_boot(struct data_area * data,int ndata)146 static void do_boot(struct data_area *data, int ndata)
147 {
148     struct syslinux_memmap *mmap;
149     struct syslinux_movelist *mlist = NULL;
150     addr_t endimage;
151     uint8_t driveno = opt.regs.edx.b[0];
152     uint8_t swapdrive = driveno & 0x80;
153     int i;
154 
155     mmap = syslinux_memory_map();
156 
157     if (!mmap) {
158 	error("Cannot read system memory map.");
159 	return;
160     }
161 
162     endimage = 0;
163     for (i = 0; i < ndata; i++) {
164 	if (data[i].base + data[i].size > endimage)
165 	    endimage = data[i].base + data[i].size;
166     }
167     if (endimage > dosmax)
168 	goto too_big;
169 
170     for (i = 0; i < ndata; i++) {
171 	if (syslinux_add_movelist(&mlist, data[i].base,
172 				  (addr_t) data[i].data, data[i].size))
173 	    goto enomem;
174     }
175 
176     if (opt.swap && driveno != swapdrive) {
177 	static const uint8_t swapstub_master[] = {
178 	    /* The actual swap code */
179 	    0x53,		/* 00: push bx */
180 	    0x0f, 0xb6, 0xda,	/* 01: movzx bx,dl */
181 	    0x2e, 0x8a, 0x57, 0x60,	/* 04: mov dl,[cs:bx+0x60] */
182 	    0x5b,		/* 08: pop bx */
183 	    0xea, 0, 0, 0, 0,	/* 09: jmp far 0:0 */
184 	    0x90, 0x90,		/* 0E: nop; nop */
185 	    /* Code to install this in the right location */
186 	    /* Entry with DS = CS; ES = SI = 0; CX = 256 */
187 	    0x26, 0x66, 0x8b, 0x7c, 0x4c,	/* 10: mov edi,[es:si+4*0x13] */
188 	    0x66, 0x89, 0x3e, 0x0a, 0x00,	/* 15: mov [0x0A],edi */
189 	    0x26, 0x8b, 0x3e, 0x13, 0x04,	/* 1A: mov di,[es:0x413] */
190 	    0x4f,		/* 1F: dec di */
191 	    0x26, 0x89, 0x3e, 0x13, 0x04,	/* 20: mov [es:0x413],di */
192 	    0x66, 0xc1, 0xe7, 0x16,	/* 25: shl edi,16+6 */
193 	    0x26, 0x66, 0x89, 0x7c, 0x4c,	/* 29: mov [es:si+4*0x13],edi */
194 	    0x66, 0xc1, 0xef, 0x10,	/* 2E: shr edi,16 */
195 	    0x8e, 0xc7,		/* 32: mov es,di */
196 	    0x31, 0xff,		/* 34: xor di,di */
197 	    0xf3, 0x66, 0xa5,	/* 36: rep movsd */
198 	    0xbe, 0, 0,		/* 39: mov si,0 */
199 	    0xbf, 0, 0,		/* 3C: mov di,0 */
200 	    0x8e, 0xde,		/* 3F: mov ds,si */
201 	    0x8e, 0xc7,		/* 41: mov es,di */
202 	    0x66, 0xb9, 0, 0, 0, 0,	/* 43: mov ecx,0 */
203 	    0x66, 0xbe, 0, 0, 0, 0,	/* 49: mov esi,0 */
204 	    0x66, 0xbf, 0, 0, 0, 0,	/* 4F: mov edi,0 */
205 	    0xea, 0, 0, 0, 0,	/* 55: jmp 0:0 */
206 	    /* pad out to segment boundary */
207 	    0x90, 0x90,		/* 5A: ... */
208 	    0x90, 0x90, 0x90, 0x90,	/* 5C: ... */
209 	};
210 	static uint8_t swapstub[1024];
211 	uint8_t *p;
212 
213 	/* Note: we can't rely on either INT 13h nor the dosmax
214 	   vector to be correct at this stage, so we have to use an
215 	   installer stub to put things in the right place.
216 	   Round the installer location to a 1K boundary so the only
217 	   possible overlap is the identity mapping. */
218 	endimage = (endimage + 1023u) & ~1023u;
219 
220 	/* Create swap stub */
221 	memcpy(swapstub, swapstub_master, sizeof swapstub_master);
222 	*(uint16_t *) & swapstub[0x3a] = opt.regs.ds;
223 	*(uint16_t *) & swapstub[0x3d] = opt.regs.es;
224 	*(uint32_t *) & swapstub[0x45] = opt.regs.ecx.l;
225 	*(uint32_t *) & swapstub[0x4b] = opt.regs.esi.l;
226 	*(uint32_t *) & swapstub[0x51] = opt.regs.edi.l;
227 	*(uint16_t *) & swapstub[0x56] = opt.regs.ip;
228 	*(uint16_t *) & swapstub[0x58] = opt.regs.cs;
229 	p = &swapstub[sizeof swapstub_master];
230 
231 	/* Mapping table; start out with identity mapping everything */
232 	for (i = 0; i < 256; i++)
233 	    p[i] = i;
234 
235 	/* And the actual swap */
236 	p[driveno] = swapdrive;
237 	p[swapdrive] = driveno;
238 
239 	/* Adjust registers */
240 	opt.regs.ds = opt.regs.cs = endimage >> 4;
241 	opt.regs.esi.l = opt.regs.es = 0;
242 	opt.regs.ecx.l = sizeof swapstub >> 2;
243 	opt.regs.ip = 0x10;	/* Installer offset */
244 	opt.regs.ebx.b[0] = opt.regs.edx.b[0] = swapdrive;
245 
246 	if (syslinux_add_movelist(&mlist, endimage, (addr_t) swapstub,
247 				  sizeof swapstub))
248 	    goto enomem;
249 
250 	endimage += sizeof swapstub;
251     }
252 
253     /* Tell the shuffler not to muck with this area... */
254     syslinux_add_memmap(&mmap, endimage, 0xa0000 - endimage, SMT_RESERVED);
255 
256     /* Force text mode */
257     syslinux_force_text_mode();
258 
259     puts("Booting...");
260     syslinux_shuffle_boot_rm(mlist, mmap, opt.keeppxe, &opt.regs);
261     error("Chainboot failed !");
262     return;
263 
264 too_big:
265     error("Loader file too large.");
266     return;
267 
268 enomem:
269     error("Out of memory.");
270     return;
271 }
272 
find_dp(struct part_iter ** _iter)273 int find_dp(struct part_iter **_iter)
274 {
275     struct part_iter *iter = NULL;
276     struct disk_info diskinfo;
277     struct guid gpt_guid;
278     uint64_t fs_lba;
279     int drive, hd, partition;
280     const union syslinux_derivative_info *sdi;
281 
282     sdi = syslinux_derivative_info();
283 
284     if (!strncmp(opt.drivename, "mbr", 3)) {
285 	if (find_by_sig(strtoul(opt.drivename + 4, NULL, 0), &iter) < 0) {
286 	    error("Unable to find requested MBR signature.");
287 	    goto bail;
288 	}
289     } else if (!strncmp(opt.drivename, "guid", 4)) {
290 	if (str_to_guid(opt.drivename + 5, &gpt_guid))
291 	    goto bail;
292 	if (find_by_guid(&gpt_guid, &iter) < 0) {
293 	    error("Unable to find requested GPT disk or partition by guid.");
294 	    goto bail;
295 	}
296     } else if (!strncmp(opt.drivename, "label", 5)) {
297 	if (!opt.drivename[6]) {
298 	    error("No label specified.");
299 	    goto bail;
300 	}
301 	if (find_by_label(opt.drivename + 6, &iter) < 0) {
302 	    error("Unable to find requested GPT partition by label.");
303 	    goto bail;
304 	}
305     } else if ((opt.drivename[0] == 'h' || opt.drivename[0] == 'f') &&
306 	       opt.drivename[1] == 'd') {
307 	hd = opt.drivename[0] == 'h' ? 0x80 : 0;
308 	opt.drivename += 2;
309 	drive = hd | strtol(opt.drivename, NULL, 0);
310 
311 	if (disk_get_params(drive, &diskinfo))
312 	    goto bail;
313 	/* this will start iteration over FDD, possibly raw */
314 	if (!(iter = pi_begin(&diskinfo, opt.piflags)))
315 	    goto bail;
316 
317     } else if (!strcmp(opt.drivename, "boot") || !strcmp(opt.drivename, "fs")) {
318 	if (!is_phys(sdi->c.filesystem)) {
319 	    error("When syslinux is not booted from physical disk (or its emulation),\n"
320 		   "'boot' and 'fs' are meaningless.");
321 	    goto bail;
322 	}
323 	/* offsets match, but in case it changes in the future */
324 	if (sdi->c.filesystem == SYSLINUX_FS_ISOLINUX) {
325 	    drive = sdi->iso.drive_number;
326 	    fs_lba = *sdi->iso.partoffset;
327 	} else {
328 	    drive = sdi->disk.drive_number;
329 	    fs_lba = *sdi->disk.partoffset;
330 	}
331 	if (disk_get_params(drive, &diskinfo))
332 	    goto bail;
333 	/* this will start iteration over disk emulation, possibly raw */
334 	if (!(iter = pi_begin(&diskinfo, opt.piflags)))
335 	    goto bail;
336 
337 	/* 'fs' => we should lookup the syslinux partition number and use it */
338 	if (!strcmp(opt.drivename, "fs")) {
339 	    do {
340 		if (iter->abs_lba == fs_lba)
341 		    break;
342 	    } while (!pi_next(iter));
343 	    /* broken part structure or other problems */
344 	    if (iter->status) {
345 		error("Unable to find partition with syslinux (fs).");
346 		goto bail;
347 	    }
348 	}
349     } else {
350 	error("Unparsable drive specification.");
351 	goto bail;
352     }
353     /* main options done - only thing left is explicit partition specification,
354      * if we're still at the disk stage with the iterator AND user supplied
355      * partition number (including disk pseudo-partition).
356      */
357     if (!iter->index && opt.partition) {
358 	partition = strtol(opt.partition, NULL, 0);
359 	/* search for matching part#, including disk */
360 	do {
361 	    if (iter->index == partition)
362 		break;
363 	} while (!pi_next(iter));
364 	if (iter->status) {
365 	    error("Unable to find requested disk / partition combination.");
366 	    goto bail;
367 	}
368     }
369 
370     if (!(iter->di.disk & 0x80) && iter->index) {
371 	warn("Partitions on floppy devices may not work.");
372     }
373 
374     *_iter = iter;
375 
376     return 0;
377 
378 bail:
379     pi_del(&iter);
380     return -1;
381 }
382 
setup_handover(const struct part_iter * iter,struct data_area * data)383 static int setup_handover(const struct part_iter *iter,
384 		   struct data_area *data)
385 {
386     struct disk_dos_part_entry *ha;
387     uint32_t synth_size = sizeof *ha;
388 
389     /*
390      * we have to cover both non-iterated but otherwise properly detected
391      * gpt/dos schemes as well as raw disks; checking index for 0 covers both
392      */
393     if (iter->index == 0) {
394 	uint32_t len;
395 	/* RAW handover protocol */
396 	ha = malloc(synth_size);
397 	if (!ha) {
398 	    critm();
399 	    goto bail;
400 	}
401 	len = ~0u;
402 	if (iter->length < len)
403 	    len = iter->length;
404 	lba2chs(&ha->start, &iter->di, 0, L2C_CADD);
405 	lba2chs(&ha->end, &iter->di, len - 1, L2C_CADD);
406 	ha->active_flag = 0x80;
407 	ha->ostype = 0xDA;	/* "Non-FS Data", anything is good here though ... */
408 	ha->start_lba = 0;
409 	ha->length = len;
410     } else if (iter->type == typegpt) {
411 	uint32_t *plen;
412 	/* GPT handover protocol */
413 	synth_size += sizeof *plen + iter->gpt.pe_size;
414 	ha = malloc(synth_size);
415 	if (!ha) {
416 	    critm();
417 	    goto bail;
418 	}
419 	lba2chs(&ha->start, &iter->di, iter->abs_lba, L2C_CADD);
420 	lba2chs(&ha->end, &iter->di, iter->abs_lba + iter->length - 1, L2C_CADD);
421 	ha->active_flag = 0x80;
422 	ha->ostype = 0xED;
423 	/* All bits set by default */
424 	ha->start_lba = ~0u;
425 	ha->length = ~0u;
426 	/* If these fit the precision, pass them on */
427 	if (iter->abs_lba < ha->start_lba)
428 	    ha->start_lba = iter->abs_lba;
429 	if (iter->length < ha->length)
430 	    ha->length = iter->length;
431 	/* Next comes the GPT partition record length */
432 	plen = (uint32_t *)(ha + 1);
433 	plen[0] = iter->gpt.pe_size;
434 	/* Next comes the GPT partition record copy */
435 	memcpy(plen + 1, iter->record, plen[0]);
436 #ifdef DEBUG
437 	dprintf("GPT handover:\n");
438 	disk_dos_part_dump(ha);
439 	disk_gpt_part_dump((struct disk_gpt_part_entry *)(plen + 1));
440 #endif
441     /* the only possible case left is dos scheme */
442     } else if (iter->type == typedos) {
443 	/* MBR handover protocol */
444 	ha = malloc(synth_size);
445 	if (!ha) {
446 	    critm();
447 	    goto bail;
448 	}
449 	memcpy(ha, iter->record, synth_size);
450 	/* make sure these match bios imaginations and are ebr agnostic */
451 	lba2chs(&ha->start, &iter->di, iter->abs_lba, L2C_CADD);
452 	lba2chs(&ha->end, &iter->di, iter->abs_lba + iter->length - 1, L2C_CADD);
453 	ha->start_lba = iter->abs_lba;
454 	ha->length = iter->length;
455 
456 #ifdef DEBUG
457 	dprintf("MBR handover:\n");
458 	disk_dos_part_dump(ha);
459 #endif
460     } else {
461 	/* shouldn't ever happen */
462 	goto bail;
463     }
464 
465     data->base = 0x7be;
466     data->size = synth_size;
467     data->data = (void *)ha;
468 
469     return 0;
470 bail:
471     return -1;
472 }
473 
main(int argc,char * argv[])474 int main(int argc, char *argv[])
475 {
476     struct part_iter *iter = NULL;
477     void *sbck = NULL;
478     struct data_area fdat, hdat, sdat, data[3];
479     int ndata = 0;
480 
481     console_ansi_raw();
482 
483     memset(&fdat, 0, sizeof fdat);
484     memset(&hdat, 0, sizeof hdat);
485     memset(&sdat, 0, sizeof sdat);
486 
487     opt_set_defs();
488     if (opt_parse_args(argc, argv))
489 	goto bail;
490 
491 #if 0
492     /* Get max fixed disk number */
493     fixed_cnt = *(uint8_t *)(0x475);
494 
495     /*
496      * hmm, looks like we can't do that -
497      * some bioses/vms just set it to 1
498      * and go on living happily
499      * any better options than hardcoded 0x80 - 0xFF ?
500      */
501 #endif
502 
503     /* Get disk/part iterator matching user supplied options */
504     if (find_dp(&iter))
505 	goto bail;
506 
507     /* Perform initial partition entry mangling */
508     if (manglepe_fixchs(iter))
509 	goto bail;
510     if (manglepe_hide(iter))
511 	goto bail;
512 
513     /* Load the boot file */
514     if (opt.file) {
515 	fdat.base = (opt.fseg << 4) + opt.foff;
516 
517 	if (loadfile(opt.file, &fdat.data, &fdat.size)) {
518 	    error("Couldn't read the boot file.");
519 	    goto bail;
520 	}
521 	if (fdat.base + fdat.size > dosmax) {
522 	    error("The boot file is too big to load at this address.");
523 	    goto bail;
524 	}
525     }
526 
527     /* Load the sector */
528     if (opt.sect) {
529 	sdat.base = (opt.sseg << 4) + opt.soff;
530 	sdat.size = iter->di.bps;
531 
532 	if (sdat.base + sdat.size > dosmax) {
533 	    error("The sector cannot be loaded at such high address.");
534 	    goto bail;
535 	}
536 	if (!(sdat.data = disk_read_sectors(&iter->di, iter->abs_lba, 1))) {
537 	    error("Couldn't read the sector.");
538 	    goto bail;
539 	}
540 	if (opt.save) {
541 	    if (!(sbck = malloc(sdat.size))) {
542 		critm();
543 		goto bail;
544 	    }
545 	    memcpy(sbck, sdat.data, sdat.size);
546 	}
547 	if (opt.file && opt.maps && overlap(&fdat, &sdat)) {
548 	    warn("The sector won't be mmapped, as it would conflict with the boot file.");
549 	    opt.maps = false;
550 	}
551     }
552 
553     /* Prep the handover */
554     if (opt.hand) {
555 	if (setup_handover(iter, &hdat))
556 	    goto bail;
557 	/* Verify possible conflicts */
558 	if ( ( opt.file && overlap(&fdat, &hdat)) ||
559 	     ( opt.maps && overlap(&sdat, &hdat)) ) {
560 	    warn("Handover area won't be prepared,\n"
561 		  "as it would conflict with the boot file and/or the sector.");
562 	    opt.hand = false;
563 	}
564     }
565 
566     /* Adjust registers */
567 
568     mangler_init(iter);
569     mangler_handover(iter, &hdat);
570     mangler_grldr(iter);
571 
572     /* Patching functions */
573 
574     if (manglef_isolinux(&fdat))
575 	goto bail;
576 
577     if (manglef_grub(iter, &fdat))
578 	goto bail;
579 #if 0
580     if (manglef_drmk(&fdat))
581 	goto bail;
582 #endif
583     if (manglef_bpb(iter, &fdat))
584 	goto bail;
585 
586     if (mangles_bpb(iter, &sdat))
587 	goto bail;
588 
589     if (mangles_save(iter, &sdat, sbck))
590 	goto bail;
591 
592     if (manglesf_bss(&sdat, &fdat))
593 	goto bail;
594 
595     /* This *must* be after BPB saving or copying */
596     if (mangles_cmldr(&sdat))
597 	goto bail;
598 
599     /*
600      * Prepare boot-time mmap data. We should to it here, as manglers could
601      * potentially alter some of the data.
602      */
603 
604     if (opt.file)
605 	memcpy(data + ndata++, &fdat, sizeof fdat);
606     if (opt.maps)
607 	memcpy(data + ndata++, &sdat, sizeof sdat);
608     if (opt.hand)
609 	memcpy(data + ndata++, &hdat, sizeof hdat);
610 
611 #ifdef DEBUG
612     dprintf("iter->di dsk, bps: %X, %u\niter->di lbacnt, C*H*S: %"PRIu64", %u\n"
613 	   "iter->di C, H, S: %u, %u, %u\n",
614 	iter->di.disk, iter->di.bps,
615 	iter->di.lbacnt, iter->di.cyl * iter->di.head * iter->di.spt,
616 	iter->di.cyl, iter->di.head, iter->di.spt);
617     dprintf("iter idx: %d\n", iter->index);
618     dprintf("iter lba: %"PRIu64"\n", iter->abs_lba);
619     if (opt.hand)
620 	dprintf("hand lba: %u\n",
621 		((struct disk_dos_part_entry *)hdat.data)->start_lba);
622 #endif
623 
624     if (opt.warn) {
625 	puts("Press any key to continue booting...");
626 	wait_key();
627     }
628 
629     if (ndata && !opt.brkchain) /* boot only if we actually chainload */
630 	do_boot(data, ndata);
631     else
632 	puts("Service-only run completed, exiting.");
633 bail:
634     pi_del(&iter);
635     /* Free allocated areas */
636     free(fdat.data);
637     free(sdat.data);
638     free(hdat.data);
639     free(sbck);
640     return 255;
641 }
642 
643 /* vim: set ts=8 sts=4 sw=4 noet: */
644