]> git.draconx.ca Git - liblbx.git/blob - src/lbx.c
Fix lbx_extract's seeking to maintain a correct offset even on errors.
[liblbx.git] / src / lbx.c
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <stdint.h>
5 #include <errno.h>
6
7 #include "byteorder.h"
8 #include "lbx.h"
9
10 #define LBX_MAGIC 0x0000fead
11 #define MIN(a,b) (((a)<(b))?(a):(b))
12
13 int lbx_errno = 0;
14
15 struct lbx_state {
16         const char *name;
17
18         FILE *f;
19         long foff;
20
21         uint16_t nfiles;
22         uint32_t offsets[];
23 };
24
25 struct lbx_state *lbx_fopen(FILE *f, const char *name)
26 {
27         struct lbx_state *new = NULL;
28         uint16_t nfiles, version;
29         uint32_t magic;
30
31         if (fread(&nfiles,  sizeof nfiles,  1, f) != 1) goto readerr;
32         if (fread(&magic,   sizeof magic,   1, f) != 1) goto readerr;
33         if (fread(&version, sizeof version, 1, f) != 1) goto readerr;
34
35         nfiles  = letohs(nfiles);
36         magic   = letohl(magic);
37         version = letohs(version);
38
39         if (magic != LBX_MAGIC) {
40                 lbx_errno = LBX_EMAGIC;
41                 return NULL;
42         }
43         
44         new = malloc(sizeof *new + (nfiles+1)*(sizeof *new->offsets));
45         if (!new) {
46                 lbx_errno = -errno;
47                 return NULL;
48         }
49         
50         *new = (struct lbx_state){
51                 .name   = name,
52                 .nfiles = nfiles,
53                 .f      = f,
54                 .foff   = sizeof nfiles + sizeof magic + sizeof version,
55         };
56         
57         if (fread(new->offsets, sizeof *new->offsets, nfiles+1, f) != nfiles+1)
58                 goto readerr;
59         new->foff += sizeof *new->offsets * (nfiles+1);
60
61         return new;
62 readerr:
63         if (feof(f)) {
64                 lbx_errno = LBX_EEOF;
65         } else {
66                 lbx_errno = -errno;
67         }
68
69         free(new);
70         return NULL;
71 }
72
73 struct lbx_state *lbx_open(const char *path)
74 {
75         struct lbx_state *new = NULL;
76         FILE *f;
77         
78         if ((f = fopen(path, "rb"))) {
79                 const char *name = strrchr(path, '/');
80                 new = lbx_fopen(f, name ? name+1 : path);
81         } else {
82                 lbx_errno = -errno;
83         }
84
85         return new;
86 }
87
88 size_t lbx_numfiles(struct lbx_state *lbx)
89 {
90         return lbx->nfiles;
91 }
92
93 int lbx_stat(struct lbx_state *lbx, size_t index, struct lbx_statbuf *buf)
94 {
95         static char str[256]; /* FIXME */
96
97         if (index >= lbx->nfiles) {
98                 buf->name = NULL;
99                 lbx_errno = LBX_ERANGE;
100                 return -1;
101         }
102
103         snprintf(str, sizeof str, "%s.%03d", lbx->name, index);
104         buf->name = str;
105         buf->size = lbx->offsets[index+1] - lbx->offsets[index];
106         return 0;
107 }
108
109 /* Advance to the beginning of the index'th file by either fseek or reading. */
110 static int _lbx_fseek(struct lbx_state *lbx, size_t base)
111 {
112         static unsigned char oblivion[1024];
113         long dist;
114
115         if (lbx->foff < base) {
116                 dist = base - lbx->foff;
117         } else if (lbx->foff > base) {
118                 dist = -(long)(lbx->foff - base);
119         } else {
120                 return 0;
121         }
122
123         if (fseek(lbx->f, dist, SEEK_CUR) == 0) {
124                 lbx->foff += dist;
125         } else if (lbx->foff < base) {
126                 while (dist) {
127                         size_t rc, amt = MIN(sizeof oblivion, dist);
128                         rc = fread(oblivion, 1, amt, lbx->f);
129                         lbx->foff += rc;
130                         dist -= rc;
131                         if (rc < amt) {
132                                 if (feof(lbx->f))
133                                         lbx_errno = LBX_EEOF;
134                                 else
135                                         lbx_errno = -errno;
136                                 return -1;
137                         }
138                 }
139         } else {
140                 lbx_errno = -errno;
141                 return -1;
142         }
143         return 0;
144 }
145
146 size_t lbx_extract(struct lbx_state *lbx, size_t index, FILE *of)
147 {
148         unsigned char buf[1024];
149         size_t rc, written = 0, base, len;
150
151         if (index >= lbx->nfiles) {
152                 lbx_errno = LBX_ERANGE;
153                 return 0;
154         }
155         
156         base = lbx->offsets[index];
157         len  = lbx->offsets[index+1] - lbx->offsets[index];
158
159         if (_lbx_fseek(lbx, base) == -1) {
160                 /* lbx_errno set by _lbx_fseek(). */
161                 return 0;
162         }
163
164         /* Copy file data */
165         while (len) {
166                 size_t amt = MIN(sizeof buf, len);
167
168                 rc = fread(buf, 1, amt, lbx->f);
169                 lbx->foff += rc;
170                 len -= rc;
171                 if (rc < amt) {
172                         if (feof(lbx->f)) {
173                                 lbx_errno = LBX_EEOF;
174                         } else {
175                                 lbx_errno = -errno;
176                         }
177                         break;
178                 }
179
180                 rc = fwrite(buf, 1, amt, of);
181                 written += rc;
182                 if (rc < amt) {
183                         lbx_errno = -errno;
184                         break;
185                 }
186         }
187
188         return written;
189 }
190
191 void lbx_close(struct lbx_state *lbx)
192 {
193         if (!lbx) return;
194
195         fclose(lbx->f);
196         free(lbx);
197 }
198
199 const char *lbx_strerror(void)
200 {
201         if (lbx_errno < 0)
202                 return strerror(-lbx_errno);
203
204         switch (lbx_errno) {
205         case LBX_ESUCCESS: return "Success";
206         case LBX_EMAGIC:   return "Bad magic number";
207         case LBX_EEOF:     return "Unexpected end-of-file";
208         case LBX_ERANGE:   return "Index out of range";
209         }
210
211         return "Unknown error";
212 }