Merge branch 'pathbased' of ssh://terminus.zytor.com/pub/git/syslinux/syslinux into...
[profile/ivi/syslinux.git] / libinstaller / setadv.c
1 /* ----------------------------------------------------------------------- *
2  *
3  *   Copyright 2007-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  * setadv.c
15  *
16  * (Over)write a data item in the auxilliary data vector.  To
17  * delete an item, set its length to zero.
18  *
19  * Return 0 on success, -1 on error, and set errno.
20  *
21  */
22 #define  _GNU_SOURCE
23
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <stddef.h>
27 #include <stdint.h>
28 #include <string.h>
29 #include <getopt.h>
30 #include <unistd.h>
31 #include <errno.h>
32 #include <fcntl.h>
33 #include <sys/ioctl.h>
34 #include <sys/stat.h>
35 #include <sys/types.h>
36 #include "syslxint.h"
37 #include "syslxcom.h"
38
39 unsigned char syslinux_adv[2 * ADV_SIZE];
40
41 #define ADV_MAGIC1      0x5a2d2fa5      /* Head signature */
42 #define ADV_MAGIC2      0xa3041767      /* Total checksum */
43 #define ADV_MAGIC3      0xdd28bf64      /* Tail signature */
44
45 static void cleanup_adv(unsigned char *advbuf)
46 {
47     int i;
48     uint32_t csum;
49
50     /* Make sure both copies agree, and update the checksum */
51     set_32((uint32_t *) advbuf, ADV_MAGIC1);
52
53     csum = ADV_MAGIC2;
54     for (i = 8; i < ADV_SIZE - 4; i += 4)
55         csum -= get_32((uint32_t *) (advbuf + i));
56
57     set_32((uint32_t *) (advbuf + 4), csum);
58     set_32((uint32_t *) (advbuf + ADV_SIZE - 4), ADV_MAGIC3);
59
60     memcpy(advbuf + ADV_SIZE, advbuf, ADV_SIZE);
61 }
62
63 int syslinux_setadv(int tag, size_t size, const void *data)
64 {
65     uint8_t *p;
66     size_t left;
67     uint8_t advtmp[ADV_SIZE];
68
69     if ((unsigned)tag - 1 > 254) {
70         errno = EINVAL;
71         return -1;              /* Impossible tag value */
72     }
73
74     if (size > 255) {
75         errno = ENOSPC;         /* Max 255 bytes for a data item */
76         return -1;
77     }
78
79     left = ADV_LEN;
80     p = advtmp;
81     memcpy(p, syslinux_adv + 2 * 4, left);      /* Make working copy */
82
83     while (left >= 2) {
84         uint8_t ptag = p[0];
85         size_t plen = p[1] + 2;
86
87         if (ptag == ADV_END)
88             break;
89
90         if (ptag == tag) {
91             /* Found our tag.  Delete it. */
92
93             if (plen >= left) {
94                 /* Entire remainder is our tag */
95                 break;
96             }
97             memmove(p, p + plen, left - plen);
98         } else {
99             /* Not our tag */
100             if (plen > left)
101                 break;          /* Corrupt tag (overrun) - overwrite it */
102
103             left -= plen;
104             p += plen;
105         }
106     }
107
108     /* Now (p, left) reflects the position to write in and how much space
109        we have for our data. */
110
111     if (size) {
112         if (left < size + 2) {
113             errno = ENOSPC;     /* Not enough space for data */
114             return -1;
115         }
116
117         *p++ = tag;
118         *p++ = size;
119         memcpy(p, data, size);
120         p += size;
121         left -= size + 2;
122     }
123
124     memset(p, 0, left);
125
126     /* If we got here, everything went OK, commit the write */
127     memcpy(syslinux_adv + 2 * 4, advtmp, ADV_LEN);
128     cleanup_adv(syslinux_adv);
129
130     return 0;
131 }
132
133 void syslinux_reset_adv(unsigned char *advbuf)
134 {
135     /* Create an all-zero ADV */
136     memset(advbuf + 2 * 4, 0, ADV_LEN);
137     cleanup_adv(advbuf);
138 }
139
140 static int adv_consistent(const unsigned char *p)
141 {
142     int i;
143     uint32_t csum;
144
145     if (get_32((uint32_t *) p) != ADV_MAGIC1 ||
146         get_32((uint32_t *) (p + ADV_SIZE - 4)) != ADV_MAGIC3)
147         return 0;
148
149     csum = 0;
150     for (i = 4; i < ADV_SIZE - 4; i += 4)
151         csum += get_32((uint32_t *) (p + i));
152
153     return csum == ADV_MAGIC2;
154 }
155
156 /*
157  * Verify that an in-memory ADV is consistent, making the copies consistent.
158  * If neither copy is OK, return -1 and call syslinux_reset_adv().
159  */
160 int syslinux_validate_adv(unsigned char *advbuf)
161 {
162     if (adv_consistent(advbuf + 0 * ADV_SIZE)) {
163         memcpy(advbuf + ADV_SIZE, advbuf, ADV_SIZE);
164         return 0;
165     } else if (adv_consistent(advbuf + 1 * ADV_SIZE)) {
166         memcpy(advbuf, advbuf + ADV_SIZE, ADV_SIZE);
167         return 0;
168     } else {
169         syslinux_reset_adv(advbuf);
170         return -1;
171     }
172 }
173
174 /*
175  * Read the ADV from an existing instance, or initialize if invalid.
176  * Returns -1 on fatal errors, 0 if ADV is okay, and 1 if no valid
177  * ADV was found.
178  */
179 int read_adv(const char *path, const char *cfg)
180 {
181     char *file;
182     int fd = -1;
183     struct stat st;
184     int err = 0;
185
186     asprintf(&file, "%s%s%s",
187              path, path[0] && path[strlen(path) - 1] == '/' ? "" : "/", cfg);
188
189     if (!file) {
190         perror(program);
191         return -1;
192     }
193
194     fd = open(file, O_RDONLY);
195     if (fd < 0) {
196         if (errno != ENOENT) {
197             err = -1;
198         } else {
199             syslinux_reset_adv(syslinux_adv);
200         }
201     } else if (fstat(fd, &st)) {
202         err = -1;
203     } else if (st.st_size < 2 * ADV_SIZE) {
204         /* Too small to be useful */
205         syslinux_reset_adv(syslinux_adv);
206         err = 0;                /* Nothing to read... */
207     } else if (xpread(fd, syslinux_adv, 2 * ADV_SIZE,
208                       st.st_size - 2 * ADV_SIZE) != 2 * ADV_SIZE) {
209         err = -1;
210     } else {
211         /* We got it... maybe? */
212         err = syslinux_validate_adv(syslinux_adv) ? 1 : 0;
213     }
214
215     if (err < 0)
216         perror(file);
217
218     if (fd >= 0)
219         close(fd);
220
221     free(file);
222
223     return err;
224 }
225
226 /*
227  * Update the ADV in an existing installation.
228  */
229 int write_adv(const char *path, const char *cfg)
230 {
231     unsigned char advtmp[2 * ADV_SIZE];
232     char *file;
233     int fd = -1;
234     struct stat st, xst;
235     int err = 0;
236
237     err = asprintf(&file, "%s%s%s",
238         path, path[0] && path[strlen(path) - 1] == '/' ? "" : "/", cfg);
239
240     if (!file) {
241         perror(program);
242         return -1;
243     }
244
245     fd = open(file, O_RDONLY);
246     if (fd < 0) {
247         err = -1;
248     } else if (fstat(fd, &st)) {
249         err = -1;
250     } else if (st.st_size < 2 * ADV_SIZE) {
251         /* Too small to be useful */
252         err = -2;
253     } else if (xpread(fd, advtmp, 2 * ADV_SIZE,
254                       st.st_size - 2 * ADV_SIZE) != 2 * ADV_SIZE) {
255         err = -1;
256     } else {
257         /* We got it... maybe? */
258         err = syslinux_validate_adv(advtmp) ? -2 : 0;
259         if (!err) {
260             /* Got a good one, write our own ADV here */
261             clear_attributes(fd);
262
263             /* Need to re-open read-write */
264             close(fd);
265             fd = open(file, O_RDWR | O_SYNC);
266             if (fd < 0) {
267                 err = -1;
268             } else if (fstat(fd, &xst) || xst.st_ino != st.st_ino ||
269                        xst.st_dev != st.st_dev || xst.st_size != st.st_size) {
270                 fprintf(stderr, "%s: race condition on write\n", file);
271                 err = -2;
272             }
273             /* Write our own version ... */
274             if (xpwrite(fd, syslinux_adv, 2 * ADV_SIZE,
275                         st.st_size - 2 * ADV_SIZE) != 2 * ADV_SIZE) {
276                 err = -1;
277             }
278
279             sync();
280             set_attributes(fd);
281         }
282     }
283
284     if (err == -2)
285         fprintf(stderr, "%s: cannot write auxilliary data (need --update)?\n",
286                 file);
287     else if (err == -1)
288         perror(file);
289
290     if (fd >= 0)
291         close(fd);
292     if (file)
293         free(file);
294
295     return err;
296 }