Document SERIAL fix
[people/xl0/syslinux-lua.git] / libinstaller / syslxmod.c
1 /* ----------------------------------------------------------------------- *
2  *
3  *   Copyright 1998-2008 H. Peter Anvin - All Rights Reserved
4  *
5  *   This program is free software; you can redistribute it and/or modify
6  *   it under the terms of the GNU General Public License as published by
7  *   the Free Software Foundation, Inc., 53 Temple Place Ste 330,
8  *   Boston MA 02111-1307, USA; either version 2 of the License, or
9  *   (at your option) any later version; incorporated herein by reference.
10  *
11  * ----------------------------------------------------------------------- */
12
13 /*
14  * syslxmod.c - Code to provide a SYSLINUX code set to an installer.
15  */
16
17 #define _XOPEN_SOURCE 500       /* Required on glibc 2.x */
18 #define _BSD_SOURCE
19 #include <stdio.h>
20 #include <inttypes.h>
21 #include <string.h>
22 #include <stddef.h>
23
24 #include "syslinux.h"
25
26 #define LDLINUX_MAGIC   0x3eb202fe
27
28 enum bs_offsets {
29   bsJump            = 0x00,
30   bsOemName         = 0x03,
31   bsBytesPerSec     = 0x0b,
32   bsSecPerClust     = 0x0d,
33   bsResSectors      = 0x0e,
34   bsFATs            = 0x10,
35   bsRootDirEnts     = 0x11,
36   bsSectors         = 0x13,
37   bsMedia           = 0x15,
38   bsFATsecs         = 0x16,
39   bsSecPerTrack     = 0x18,
40   bsHeads           = 0x1a,
41   bsHiddenSecs      = 0x1c,
42   bsHugeSectors     = 0x20,
43
44   /* FAT12/16 only */
45   bs16DriveNumber   = 0x24,
46   bs16Reserved1     = 0x25,
47   bs16BootSignature = 0x26,
48   bs16VolumeID      = 0x27,
49   bs16VolumeLabel   = 0x2b,
50   bs16FileSysType   = 0x36,
51   bs16Code          = 0x3e,
52
53   /* FAT32 only */
54   bs32FATSz32       = 36,
55   bs32ExtFlags      = 40,
56   bs32FSVer         = 42,
57   bs32RootClus      = 44,
58   bs32FSInfo        = 48,
59   bs32BkBootSec     = 50,
60   bs32Reserved      = 52,
61   bs32DriveNumber   = 64,
62   bs32Reserved1     = 65,
63   bs32BootSignature = 66,
64   bs32VolumeID      = 67,
65   bs32VolumeLabel   = 71,
66   bs32FileSysType   = 82,
67   bs32Code          = 90,
68
69   bsSignature     = 0x1fe
70 };
71
72 #define bsHead      bsJump
73 #define bsHeadLen   (bsOemName-bsHead)
74 #define bsCode      bs32Code    /* The common safe choice */
75 #define bsCodeLen   (bsSignature-bs32Code)
76
77 /*
78  * Access functions for littleendian numbers, possibly misaligned.
79  */
80 static inline uint8_t get_8(const unsigned char *p)
81 {
82   return *(const uint8_t *)p;
83 }
84
85 static inline uint16_t get_16(const unsigned char *p)
86 {
87 #if defined(__i386__) || defined(__x86_64__)
88   /* Littleendian and unaligned-capable */
89   return *(const uint16_t *)p;
90 #else
91   return (uint16_t)p[0] + ((uint16_t)p[1] << 8);
92 #endif
93 }
94
95 static inline uint32_t get_32(const unsigned char *p)
96 {
97 #if defined(__i386__) || defined(__x86_64__)
98   /* Littleendian and unaligned-capable */
99   return *(const uint32_t *)p;
100 #else
101   return (uint32_t)p[0] + ((uint32_t)p[1] << 8) +
102     ((uint32_t)p[2] << 16) + ((uint32_t)p[3] << 24);
103 #endif
104 }
105
106 static inline void set_16(unsigned char *p, uint16_t v)
107 {
108 #if defined(__i386__) || defined(__x86_64__)
109   /* Littleendian and unaligned-capable */
110   *(uint16_t *)p = v;
111 #else
112   p[0] = (v & 0xff);
113   p[1] = ((v >> 8) & 0xff);
114 #endif
115 }
116
117 static inline void set_32(unsigned char *p, uint32_t v)
118 {
119 #if defined(__i386__) || defined(__x86_64__)
120   /* Littleendian and unaligned-capable */
121   *(uint32_t *)p = v;
122 #else
123   p[0] = (v & 0xff);
124   p[1] = ((v >> 8) & 0xff);
125   p[2] = ((v >> 16) & 0xff);
126   p[3] = ((v >> 24) & 0xff);
127 #endif
128 }
129
130 void syslinux_make_bootsect(void *bs)
131 {
132   unsigned char *bootsect = bs;
133
134   memcpy(bootsect+bsHead, syslinux_bootsect+bsHead, bsHeadLen);
135   memcpy(bootsect+bsCode, syslinux_bootsect+bsCode, bsCodeLen);
136 }
137
138 /*
139  * Check to see that what we got was indeed an MS-DOS boot sector/superblock;
140  * Return NULL if OK and otherwise an error message;
141  */
142 const char *syslinux_check_bootsect(const void *bs)
143 {
144   int veryold;
145   int sectorsize;
146   long long sectors, fatsectors, dsectors;
147   long long clusters;
148   int rootdirents, clustersize;
149   const unsigned char *sectbuf = bs;
150
151   veryold = 0;
152
153   /* Must be 0xF0 or 0xF8..0xFF */
154   if ( get_8(sectbuf+bsMedia) != 0xF0 &&
155        get_8(sectbuf+bsMedia) < 0xF8 )
156     goto invalid;
157
158   sectorsize = get_16(sectbuf+bsBytesPerSec);
159   if ( sectorsize == 512 )
160     ; /* ok */
161   else if ( sectorsize == 1024 || sectorsize == 2048 || sectorsize == 4096 )
162     return "only 512-byte sectors are supported";
163   else
164     goto invalid;
165
166   clustersize = get_8(sectbuf+bsSecPerClust);
167   if ( clustersize == 0 || (clustersize & (clustersize-1)) )
168     goto invalid;               /* Must be nonzero and a power of 2 */
169
170   sectors = get_16(sectbuf+bsSectors);
171   sectors = sectors ? sectors : get_32(sectbuf+bsHugeSectors);
172
173   dsectors = sectors - get_16(sectbuf+bsResSectors);
174
175   fatsectors = get_16(sectbuf+bsFATsecs);
176   fatsectors = fatsectors ? fatsectors : get_32(sectbuf+bs32FATSz32);
177   fatsectors *= get_8(sectbuf+bsFATs);
178   dsectors -= fatsectors;
179
180   rootdirents = get_16(sectbuf+bsRootDirEnts);
181   dsectors -= (rootdirents+sectorsize/32-1)/sectorsize;
182
183   if ( dsectors < 0 || fatsectors == 0 )
184     goto invalid;
185
186   clusters = dsectors/clustersize;
187
188   if ( clusters < 0xFFF5 ) {
189     /* FAT12 or FAT16 */
190
191     if ( !get_16(sectbuf+bsFATsecs) )
192       goto invalid;
193
194     if ( get_8(sectbuf+bs16BootSignature) == 0x29 ) {
195       if ( !memcmp(sectbuf+bs16FileSysType, "FAT12   ", 8) ) {
196         if ( clusters >= 0xFF5 )
197           return "more than 4084 clusters but claims FAT12";
198       } else if ( !memcmp(sectbuf+bs16FileSysType, "FAT16   ", 8) ) {
199         if ( clusters < 0xFF5 )
200           return "less than 4084 clusters but claims FAT16";
201       } else if ( memcmp(sectbuf+bs16FileSysType, "FAT     ", 8) ) {
202         static char fserr[] = "filesystem type \"????????\" not supported";
203         memcpy(fserr+17, sectbuf+bs16FileSysType, 8);
204         return fserr;
205       }
206     }
207   } else if ( clusters < 0x0FFFFFF5 ) {
208     /* FAT32 */
209     /* Moving the FileSysType and BootSignature was a lovely stroke of M$ idiocy */
210     if ( get_8(sectbuf+bs32BootSignature) != 0x29 ||
211          memcmp(sectbuf+bs32FileSysType, "FAT32   ", 8) )
212       goto invalid;
213   } else {
214     goto invalid;
215   }
216
217   return NULL;
218
219  invalid:
220   return "this doesn't look like a valid FAT filesystem";
221 }
222
223 /*
224  * This patches the boot sector and the first sector of ldlinux.sys
225  * based on an ldlinux.sys sector map passed in.  Typically this is
226  * handled by writing ldlinux.sys, mapping it, and then overwrite it
227  * with the patched version.  If this isn't safe to do because of
228  * an OS which does block reallocation, then overwrite it with
229  * direct access since the location is known.
230  *
231  * Return 0 if successful, otherwise -1.
232  */
233 int syslinux_patch(const uint32_t *sectors, int nsectors,
234                    int stupid, int raid_mode)
235 {
236   unsigned char *patcharea, *p;
237   int nsect = (syslinux_ldlinux_len+511) >> 9;
238   uint32_t csum;
239   int i, dw;
240
241   if ( nsectors < nsect )
242     return -1;
243
244   /* Patch in options, as appropriate */
245   if (stupid) {
246     /* Access only one sector at a time */
247     set_16(syslinux_bootsect+0x1FC, 1);
248   }
249
250   i = get_16(syslinux_bootsect+0x1FE);
251   if (raid_mode)
252     set_16(syslinux_bootsect+i, 0x18CD); /* INT 18h */
253   set_16(syslinux_bootsect+0x1FE, 0xAA55);
254
255   /* First sector need pointer in boot sector */
256   set_32(syslinux_bootsect+0x1F8, *sectors++);
257   nsect--;
258
259   /* Search for LDLINUX_MAGIC to find the patch area */
260   for ( p = syslinux_ldlinux ; get_32(p) != LDLINUX_MAGIC ; p += 4 );
261   patcharea = p+8;
262
263   /* Set up the totals */
264   dw = syslinux_ldlinux_len >> 2; /* COMPLETE dwords! */
265   set_16(patcharea, dw);
266   set_16(patcharea+2, nsect);   /* Does not include the first sector! */
267
268   /* Set the sector pointers */
269   p = patcharea+8;
270
271   memset(p, 0, 64*4);
272   while ( nsect-- ) {
273     set_32(p, *sectors++);
274     p += 4;
275   }
276
277   /* Now produce a checksum */
278   set_32(patcharea+4, 0);
279
280   csum = LDLINUX_MAGIC;
281   for ( i = 0, p = syslinux_ldlinux ; i < dw ; i++, p += 4 )
282     csum -= get_32(p);          /* Negative checksum */
283
284   set_32(patcharea+4, csum);
285
286     return 0;
287 }