1 /*
2    Copyright (C) 2003 Commonwealth Scientific and Industrial Research
3    Organisation (CSIRO) Australia
4 
5    Redistribution and use in source and binary forms, with or without
6    modification, are permitted provided that the following conditions
7    are met:
8 
9    - Redistributions of source code must retain the above copyright
10    notice, this list of conditions and the following disclaimer.
11 
12    - Redistributions in binary form must reproduce the above copyright
13    notice, this list of conditions and the following disclaimer in the
14    documentation and/or other materials provided with the distribution.
15 
16    - Neither the name of CSIRO Australia nor the names of its
17    contributors may be used to endorse or promote products derived from
18    this software without specific prior written permission.
19 
20    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21    ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
23    PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE ORGANISATION OR
24    CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25    EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26    PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27    PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28    LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29    NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30    SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 */
32 
33 /*
34  * oggz_seek.c
35  *
36  * Conrad Parker <conrad@annodex.net>
37  */
38 
39 #include "config.h"
40 
41 #if OGGZ_CONFIG_READ
42 
43 #include <assert.h>
44 #include <stdlib.h>
45 #include <stdio.h>
46 #include <sys/types.h>
47 #include <sys/stat.h>
48 
49 #ifdef HAVE_UNISTD_H
50 #include <unistd.h>
51 #endif
52 
53 #include <fcntl.h>
54 #include <errno.h>
55 #include <string.h>
56 #include <time.h>
57 
58 #include <ogg/ogg.h>
59 
60 #include "oggz_compat.h"
61 #include "oggz_private.h"
62 
63 /*#define DEBUG*/
64 /*#define DEBUG_VERBOSE*/
65 
66 #define CHUNKSIZE 4096
67 
68 /*
69  * The typical usage is:
70  *
71  *   oggz_set_data_start (oggz, oggz_tell (oggz));
72  */
73 int
oggz_set_data_start(OGGZ * oggz,oggz_off_t offset)74 oggz_set_data_start (OGGZ * oggz, oggz_off_t offset)
75 {
76   if (oggz == NULL) return -1;
77 
78   if (offset < 0) return -1;
79 
80   oggz->offset_data_begin = offset;
81 
82   return 0;
83 }
84 
85 static oggz_off_t
oggz_tell_raw(OGGZ * oggz)86 oggz_tell_raw (OGGZ * oggz)
87 {
88   oggz_off_t offset_at;
89 
90   offset_at = oggz_io_tell (oggz);
91 
92   return offset_at;
93 }
94 
95 /*
96  * seeks and syncs
97  */
98 
99 int
oggz_seek_reset_stream(void * data)100 oggz_seek_reset_stream(void *data) {
101   ((oggz_stream_t *)data)->last_granulepos = -1L;
102   return 0;
103 }
104 
105 static oggz_off_t
oggz_seek_raw(OGGZ * oggz,oggz_off_t offset,int whence)106 oggz_seek_raw (OGGZ * oggz, oggz_off_t offset, int whence)
107 {
108   OggzReader  * reader = &oggz->x.reader;
109   oggz_off_t    offset_at;
110 
111   if (oggz_io_seek (oggz, offset, whence) == -1) {
112     return -1;
113   }
114 
115   offset_at = oggz_io_tell (oggz);
116 
117   oggz->offset = offset_at;
118 
119   ogg_sync_reset (&reader->ogg_sync);
120 
121   oggz_vector_foreach(oggz->streams, oggz_seek_reset_stream);
122 
123   return offset_at;
124 }
125 
126 static int
oggz_stream_reset(void * data)127 oggz_stream_reset (void * data)
128 {
129   oggz_stream_t * stream = (oggz_stream_t *) data;
130 
131   if (stream->ogg_stream.serialno != -1) {
132     ogg_stream_reset (&stream->ogg_stream);
133   }
134 
135   return 0;
136 }
137 
138 static void
oggz_reset_streams(OGGZ * oggz)139 oggz_reset_streams (OGGZ * oggz)
140 {
141   oggz_vector_foreach (oggz->streams, oggz_stream_reset);
142 }
143 
144 static long
oggz_reset_seek(OGGZ * oggz,oggz_off_t offset,ogg_int64_t unit,int whence)145 oggz_reset_seek (OGGZ * oggz, oggz_off_t offset, ogg_int64_t unit, int whence)
146 {
147   OggzReader * reader = &oggz->x.reader;
148 
149   oggz_off_t offset_at;
150 
151   offset_at = oggz_seek_raw (oggz, offset, whence);
152   if (offset_at == -1) return -1;
153 
154   oggz->offset = offset_at;
155 
156 #ifdef DEBUG
157   printf ("reset to %" PRI_OGGZ_OFF_T "d\n", offset_at);
158 #endif
159 
160   if (unit != -1) reader->current_unit = unit;
161 
162   return offset_at;
163 }
164 
165 static long
oggz_reset(OGGZ * oggz,oggz_off_t offset,ogg_int64_t unit,int whence)166 oggz_reset (OGGZ * oggz, oggz_off_t offset, ogg_int64_t unit, int whence)
167 {
168   oggz_reset_streams (oggz);
169   return oggz_reset_seek (oggz, offset, unit, whence);
170 }
171 
172 int
oggz_purge(OGGZ * oggz)173 oggz_purge (OGGZ * oggz)
174 {
175   if (oggz == NULL) return OGGZ_ERR_BAD_OGGZ;
176 
177   if (oggz->flags & OGGZ_WRITE) {
178     return OGGZ_ERR_INVALID;
179   }
180 
181   oggz_reset_streams (oggz);
182 
183   if (oggz->file && oggz_reset (oggz, oggz->offset, -1, SEEK_SET) < 0) {
184     return OGGZ_ERR_SYSTEM;
185   }
186 
187   return 0;
188 }
189 
190 /*
191  * oggz_get_next_page (oggz, og, do_read)
192  *
193  * retrieves the next page.
194  * returns >= 0 if found; return value is offset of page start
195  * returns -1 on error
196  * returns -2 if EOF was encountered
197  */
198 static oggz_off_t
oggz_get_next_page(OGGZ * oggz,ogg_page * og)199 oggz_get_next_page (OGGZ * oggz, ogg_page * og)
200 {
201   OggzReader * reader = &oggz->x.reader;
202   char * buffer;
203   long bytes = 0, more;
204   oggz_off_t page_offset = 0, ret;
205   int found = 0;
206 
207   do {
208     more = ogg_sync_pageseek (&reader->ogg_sync, og);
209 
210     if (more == 0) {
211       page_offset = 0;
212 
213       buffer = ogg_sync_buffer (&reader->ogg_sync, CHUNKSIZE);
214       if ((bytes = (long) oggz_io_read (oggz, buffer, CHUNKSIZE)) == 0) {
215 	if (oggz->file && feof (oggz->file)) {
216 #ifdef DEBUG_VERBOSE
217 	  printf ("get_next_page: feof (oggz->file), returning -2\n");
218 #endif
219 	  clearerr (oggz->file);
220 	  return -2;
221 	}
222       }
223       if (bytes == OGGZ_ERR_SYSTEM) {
224 	  /*oggz_set_error (oggz, OGGZ_ERR_SYSTEM);*/
225 	  return -1;
226       }
227 
228       if (bytes == 0) {
229 #ifdef DEBUG_VERBOSE
230 	printf ("get_next_page: bytes == 0, returning -2\n");
231 #endif
232 	return -2;
233       }
234 
235       ogg_sync_wrote(&reader->ogg_sync, bytes);
236 
237     } else if (more < 0) {
238 #ifdef DEBUG_VERBOSE
239       printf ("get_next_page: skipped %ld bytes\n", -more);
240 #endif
241       page_offset -= more;
242     } else {
243 #ifdef DEBUG_VERBOSE
244       printf ("get_next_page: page has %ld bytes\n", more);
245 #endif
246       found = 1;
247     }
248 
249   } while (!found);
250 
251   /* Calculate the byte offset of the page which was found */
252   if (bytes > 0) {
253     oggz->offset = oggz_tell_raw (oggz) - bytes + page_offset;
254   } else {
255     /* didn't need to do any reading -- accumulate the page_offset */
256     oggz->offset += page_offset;
257   }
258 
259   ret = oggz->offset + more;
260 
261   return ret;
262 }
263 
264 static oggz_off_t
oggz_get_next_start_page(OGGZ * oggz,ogg_page * og)265 oggz_get_next_start_page (OGGZ * oggz, ogg_page * og)
266 {
267   oggz_off_t page_offset;
268   int found = 0;
269 
270   do {
271     page_offset = oggz_get_next_page (oggz, og);
272 
273     /* Return this value if one of the following conditions is met:
274      *
275      *   page_offset < 0     : error or EOF
276      *   page_offset == 0    : start of stream
277      *   !ogg_page_continued : page contains start of a packet
278      *   ogg_page_packets > 1: page contains start of a packet
279      */
280     /*if (page_offset <= 0 || !ogg_page_continued (og) ||
281 	ogg_page_packets (og) > 1)*/
282     if (page_offset <= 0 || ogg_page_granulepos(og) > -1)
283       found = 1;
284   }
285   while (!found);
286 
287   return page_offset;
288 }
289 
290 static oggz_off_t
oggz_get_prev_start_page(OGGZ * oggz,ogg_page * og,ogg_int64_t * granule,long * serialno)291 oggz_get_prev_start_page (OGGZ * oggz, ogg_page * og,
292 			 ogg_int64_t * granule, long * serialno)
293 {
294   oggz_off_t offset_at, offset_start;
295   oggz_off_t page_offset, found_offset = 0;
296   ogg_int64_t unit_at;
297   ogg_int64_t granule_at = -1;
298 
299 #if 0
300   offset_at = oggz_tell_raw (oggz);
301   if (offset_at == -1) return -1;
302 #else
303   offset_at = oggz->offset;
304 #endif
305 
306   offset_start = offset_at;
307 
308   do {
309 
310     offset_start = offset_start - CHUNKSIZE;
311     if (offset_start < 0) offset_start = 0;
312 
313     offset_start = oggz_seek_raw (oggz, offset_start, SEEK_SET);
314     if (offset_start == -1) return -1;
315 
316 #ifdef DEBUG
317 
318     printf ("get_prev_start_page: [A] offset_at: @%" PRI_OGGZ_OFF_T "d\toffset_start: @%" PRI_OGGZ_OFF_T "d\n",
319 	    offset_at, offset_start);
320 
321     printf ("get_prev_start_page: seeked to %" PRI_OGGZ_OFF_T "d\n", offset_start);
322 #endif
323 
324     page_offset = 0;
325 
326     do {
327       page_offset = oggz_get_next_start_page (oggz, og);
328       if (page_offset == -1) {
329 #ifdef DEBUG
330 	printf ("get_prev_start_page: page_offset = -1\n");
331 #endif
332 	return -1;
333       }
334       if (page_offset == -2) {
335 #ifdef DEBUG
336 	printf ("*** get_prev_start_page: page_offset = -2\n");
337 #endif
338 	break;
339       }
340 
341       granule_at = ogg_page_granulepos (og);
342 
343 #ifdef DEBUG_VERBOSE
344       printf ("get_prev_start_page: GOT page (%lld) @%" PRI_OGGZ_OFF_T "d\tat @%" PRI_OGGZ_OFF_T  "d\n",
345 	      granule_at, page_offset, offset_at);
346 #endif
347 
348       /* Need to stash the granule and serialno of this page because og
349        * will be overwritten by the time we realise this was the desired
350        * prev page */
351       if (page_offset >= 0 && page_offset < offset_at) {
352 	found_offset = page_offset;
353 	*granule = granule_at;
354 	*serialno = ogg_page_serialno (og);
355       }
356 
357     } while (page_offset >= 0 && page_offset < offset_at);
358 
359 #ifdef DEBUG
360     printf ("get_prev_start_page: [B] offset_at: @%" PRI_OGGZ_OFF_T "d\toffset_start: @%" PRI_OGGZ_OFF_T "d\n"
361 	    "found_offset: @%" PRI_OGGZ_OFF_T "d\tpage_offset: @%" PRI_OGGZ_OFF_T "d\n",
362 	    offset_at, offset_start, found_offset, page_offset);
363 #endif
364     /* reset the file offset */
365     /*offset_at = offset_start;*/
366 
367   } while (found_offset == 0 && offset_start > 0);
368 
369   unit_at = oggz_get_unit (oggz, *serialno, *granule);
370   offset_at = oggz_reset (oggz, found_offset, unit_at, SEEK_SET);
371 
372 #ifdef DEBUG
373     printf ("get_prev_start_page: [C] offset_at: @%" PRI_OGGZ_OFF_T "d\t"
374 	    "found_offset: @%" PRI_OGGZ_OFF_T "d\tunit_at: %lld\n",
375 	    offset_at, found_offset, unit_at);
376 #endif
377 
378   if (offset_at == -1) return -1;
379 
380   if (offset_at >= 0)
381     return found_offset;
382   else
383     return -1;
384 }
385 
386 static oggz_off_t
oggz_scan_for_page(OGGZ * oggz,ogg_page * og,ogg_int64_t unit_target,oggz_off_t offset_begin,oggz_off_t offset_end)387 oggz_scan_for_page (OGGZ * oggz, ogg_page * og, ogg_int64_t unit_target,
388 		   oggz_off_t offset_begin, oggz_off_t offset_end)
389 {
390   oggz_off_t offset_at, offset_next;
391   oggz_off_t offset_prev = -1;
392   ogg_int64_t granule_at;
393   ogg_int64_t unit_at;
394   long serialno;
395 
396 #ifdef DEBUG
397   printf (" SCANNING from %" PRI_OGGZ_OFF_T "d...", offset_begin);
398 #endif
399 
400   for ( ; ; ) {
401     offset_at = oggz_seek_raw (oggz, offset_begin, SEEK_SET);
402     if (offset_at == -1) return -1;
403 
404 #ifdef DEBUG
405     printf (" scan @%" PRI_OGGZ_OFF_T "d\n", offset_at);
406 #endif
407 
408     offset_next = oggz_get_next_start_page (oggz, og);
409 
410     if (offset_next < 0) {
411       return offset_next;
412     }
413 
414     if (offset_next == 0 && offset_begin != 0) {
415 #ifdef DEBUG
416       printf (" ... scanned past EOF\n");
417 #endif
418       return -1;
419     }
420     if (offset_next > offset_end) {
421 #ifdef DEBUG
422       printf (" ... scanned to page %ld\n", (long)ogg_page_granulepos (og));
423 #endif
424 
425 #if 0
426       if (offset_prev != -1) {
427 	offset_at = oggz_seek_raw (oggz, offset_prev, SEEK_SET);
428 	if (offset_at == -1) return -1;
429 
430 	offset_next = oggz_get_next_start_page (oggz, og);
431 	if (offset_next < 0) return offset_next;
432 
433 	serialno = ogg_page_serialno (og);
434 	granule_at = ogg_page_granulepos (og);
435 	unit_at = oggz_get_unit (oggz, serialno, granule_at);
436 
437 	return offset_at;
438       } else {
439 	return -1;
440       }
441 #else
442       serialno = ogg_page_serialno (og);
443       granule_at = ogg_page_granulepos (og);
444       unit_at = oggz_get_unit (oggz, serialno, granule_at);
445 
446       return offset_at;
447 #endif
448     }
449 
450     offset_at = offset_next;
451 
452     serialno = ogg_page_serialno (og);
453     granule_at = ogg_page_granulepos (og);
454     unit_at = oggz_get_unit (oggz, serialno, granule_at);
455 
456     if (unit_at < unit_target) {
457 #ifdef DEBUG
458       printf (" scan: (%lld) < (%lld)\n", unit_at, unit_target);
459 #endif
460       offset_prev = offset_next;
461       offset_begin = offset_next+1;
462     } else if (unit_at > unit_target) {
463 #ifdef DEBUG
464       printf (" scan: (%lld) > (%lld)\n", unit_at, unit_target);
465 #endif
466 #if 0
467       /* hole ? */
468       offset_at = oggz_seek_raw (oggz, offset_begin, SEEK_SET);
469       if (offset_at == -1) return -1;
470 
471       offset_next = oggz_get_next_start_page (oggz, og);
472       if (offset_next < 0) return offset_next;
473 
474       serialno = ogg_page_serialno (og);
475       granule_at = ogg_page_granulepos (og);
476       unit_at = oggz_get_unit (oggz, serialno, granule_at);
477 
478       break;
479 #else
480       do {
481         offset_at = oggz_get_prev_start_page(oggz, og, &granule_at, &serialno);
482         if (offset_at < 0)
483           break;
484         unit_at = oggz_get_unit(oggz, serialno, granule_at);
485       } while (unit_at > unit_target);
486       return offset_at;
487 #endif
488     } else if (unit_at == unit_target) {
489 #ifdef DEBUG
490       printf (" scan: (%lld) == (%lld)\n", unit_at, unit_target);
491 #endif
492       break;
493     }
494   }
495 
496   return offset_at;
497 }
498 
499 #define GUESS_MULTIPLIER (1<<16)
500 
501 static oggz_off_t
guess(ogg_int64_t unit_at,ogg_int64_t unit_target,ogg_int64_t unit_begin,ogg_int64_t unit_end,oggz_off_t offset_begin,oggz_off_t offset_end)502 guess (ogg_int64_t unit_at, ogg_int64_t unit_target,
503        ogg_int64_t unit_begin, ogg_int64_t unit_end,
504        oggz_off_t offset_begin, oggz_off_t offset_end)
505 {
506   ogg_int64_t guess_ratio;
507   oggz_off_t offset_guess;
508 
509   if (unit_at == unit_begin) return offset_begin;
510 
511   if (unit_end != -1) {
512     guess_ratio =
513       GUESS_MULTIPLIER * (unit_target - unit_begin) /
514       (unit_end - unit_begin);
515   } else {
516     guess_ratio =
517       GUESS_MULTIPLIER * (unit_target - unit_begin) /
518       (unit_at - unit_begin);
519   }
520 
521 #ifdef DEBUG
522   printf ("oggz_seek::guess: guess_ratio %lld = (%lld - %lld) / (%lld - %lld)\n",
523 	  guess_ratio, unit_target, unit_begin, unit_at, unit_begin);
524 #endif
525 
526   offset_guess = offset_begin +
527     (oggz_off_t)(((offset_end - offset_begin) * guess_ratio) /
528 		 GUESS_MULTIPLIER);
529 
530   return offset_guess;
531 }
532 
533 static oggz_off_t
oggz_seek_guess(ogg_int64_t unit_at,ogg_int64_t unit_target,ogg_int64_t unit_begin,ogg_int64_t unit_end,oggz_off_t offset_at,oggz_off_t offset_begin,oggz_off_t offset_end)534 oggz_seek_guess (ogg_int64_t unit_at, ogg_int64_t unit_target,
535 		 ogg_int64_t unit_begin, ogg_int64_t unit_end,
536 		 oggz_off_t offset_at,
537 		 oggz_off_t offset_begin, oggz_off_t offset_end)
538 {
539   oggz_off_t offset_guess;
540 
541   if (unit_at == unit_begin) {
542     offset_guess = offset_begin + (offset_end - offset_begin)/2;
543   } else if (unit_end == -1) {
544     offset_guess = guess (unit_at, unit_target, unit_begin, unit_end,
545 			  offset_begin, offset_at);
546   } else if (unit_end <= unit_begin) {
547 #ifdef DEBUG
548     printf ("oggz_seek_guess: unit_end <= unit_begin (ERROR)\n");
549 #endif
550     offset_guess = -1;
551   } else {
552     offset_guess = guess (unit_at, unit_target, unit_begin, unit_end,
553 			  offset_begin, offset_end);
554   }
555 
556 #ifdef DEBUG
557     printf ("oggz_seek_guess: guessed %" PRI_OGGZ_OFF_T "d\n", offset_guess);
558 #endif
559 
560   return offset_guess;
561 }
562 
563 static oggz_off_t
oggz_offset_end(OGGZ * oggz)564 oggz_offset_end (OGGZ * oggz)
565 {
566   int fd;
567   struct stat statbuf;
568   oggz_off_t offset_end = -1;
569 
570   if (oggz->file != NULL) {
571     if ((fd = fileno (oggz->file)) == -1) {
572       /*oggz_set_error (oggz, OGGZ_ERR_SYSTEM);*/
573       return -1;
574     }
575 
576     if (fstat (fd, &statbuf) == -1) {
577       /*oggz_set_error (oggz, OGGZ_ERR_SYSTEM);*/
578       return -1;
579     }
580 
581     if (oggz_stat_regular (statbuf.st_mode)) {
582       offset_end = statbuf.st_size;
583 #ifdef DEBUG
584       printf ("oggz_offset_end: stat size %" PRI_OGGZ_OFF_T "d\n", offset_end);
585 #endif
586     } else {
587       /*oggz_set_error (oggz, OGGZ_ERR_NOSEEK);*/
588 
589       /* XXX: should be able to just carry on and guess, as per io */
590       /*return -1;*/
591     }
592   } else {
593     oggz_off_t offset_save;
594 
595     if (oggz->io == NULL || oggz->io->seek == NULL) {
596       /* No file, and no io seek method */
597       return -1;
598     }
599 
600     /* Get the offset of the end by querying the io seek method */
601     offset_save = oggz_io_tell (oggz);
602     if (oggz_io_seek (oggz, 0, SEEK_END) == -1) {
603       return -1;
604     }
605     offset_end = oggz_io_tell (oggz);
606     if (oggz_io_seek (oggz, offset_save, SEEK_SET) == -1) {
607       return -1; /* fubar */
608     }
609   }
610 
611   return offset_end;
612 }
613 
614 ogg_int64_t
oggz_bounded_seek_set(OGGZ * oggz,ogg_int64_t unit_target,ogg_int64_t offset_begin,ogg_int64_t offset_end)615 oggz_bounded_seek_set (OGGZ * oggz,
616                        ogg_int64_t unit_target,
617                        ogg_int64_t offset_begin,
618                        ogg_int64_t offset_end)
619 {
620   OggzReader * reader;
621   oggz_off_t offset_orig, offset_at, offset_guess;
622   oggz_off_t offset_next;
623   ogg_int64_t granule_at;
624   ogg_int64_t unit_at, unit_begin = -1, unit_end = -1, unit_last_iter = -1;
625   long serialno;
626   ogg_page * og;
627   int hit_eof = 0;
628 
629   if (oggz == NULL) {
630     return -1;
631   }
632 
633   if (unit_target > 0 && !oggz_has_metrics (oggz)) {
634 #ifdef DEBUG
635     printf ("oggz_bounded_seek_set: No metric defined, FAIL\n");
636 #endif
637     return -1;
638   }
639 
640   if (offset_end == -1 && (offset_end = oggz_offset_end (oggz)) == -1) {
641 #ifdef DEBUG
642     printf ("oggz_bounded_seek_set: oggz_offset_end == -1, FAIL\n");
643 #endif
644     return -1;
645   }
646 
647   reader = &oggz->x.reader;
648 
649   if (unit_target == reader->current_unit) {
650 #ifdef DEBUG
651     printf ("oggz_bounded_seek_set: unit_target == reader->current_unit, SKIP\n");
652 #endif
653     return (long)reader->current_unit;
654   }
655 
656   if (unit_target == 0) {
657     offset_at = oggz_reset (oggz, oggz->offset_data_begin, 0, SEEK_SET);
658     if (offset_at == -1) return -1;
659     return 0;
660   }
661 
662   offset_at = oggz_tell_raw (oggz);
663   if (offset_at == -1) return -1;
664 
665   offset_orig = oggz->offset;
666 
667   unit_at = reader->current_unit;
668 
669   og = &oggz->current_page;
670 
671   if (unit_end == -1 && oggz_seek_raw (oggz, offset_end, SEEK_SET) >= 0) {
672     ogg_int64_t granulepos;
673 
674     if (oggz_get_prev_start_page (oggz, og, &granulepos, &serialno) >= 0) {
675       unit_end = oggz_get_unit (oggz, serialno, granulepos);
676     }
677   }
678 
679   if (unit_begin == -1 && oggz_seek_raw (oggz, offset_begin, SEEK_SET) >= 0) {
680     ogg_int64_t granulepos;
681     if (oggz_get_next_start_page (oggz, og) >= 0) {
682       serialno = ogg_page_serialno (og);
683       granulepos = ogg_page_granulepos (og);
684       unit_begin = oggz_get_unit (oggz, serialno, granulepos);
685     }
686   }
687 
688   /* Fail if target isn't in specified range. */
689   if (unit_target < unit_begin || unit_target > unit_end)
690     return -1;
691 
692   /* Reduce the search range if possible using read cursor position. */
693   if (unit_at > unit_begin && unit_at < unit_end) {
694     if (unit_target < unit_at) {
695       unit_end = unit_at;
696       offset_end = offset_at;
697     } else {
698       unit_begin = unit_at;
699       offset_begin = offset_at;
700     }
701   }
702 
703   og = &oggz->current_page;
704 
705   for ( ; ; ) {
706 
707     unit_last_iter = unit_at;
708     hit_eof = 0;
709 
710 #ifdef DEBUG
711     printf ("oggz_bounded_seek_set: [A] want u%lld: (u%lld - u%lld) [@%" PRI_OGGZ_OFF_T "d - @%" PRI_OGGZ_OFF_T "d]\n",
712 	    unit_target, unit_begin, unit_end, offset_begin, offset_end);
713 #endif
714 
715     offset_guess = oggz_seek_guess (unit_at, unit_target,
716 				    unit_begin, unit_end,
717 				    offset_at,
718 				    offset_begin, offset_end);
719     if (offset_guess == -1) break;
720 
721     if (offset_guess == offset_at) {
722       /* Already there, looping */
723       break;
724     }
725 
726     if (offset_guess > offset_end) {
727       offset_guess = offset_end;
728       offset_at = oggz_seek_raw (oggz, offset_guess, SEEK_SET);
729       offset_next = oggz_get_prev_start_page (oggz, og, &granule_at, &serialno);
730     } else {
731       offset_at = oggz_seek_raw (oggz, offset_guess, SEEK_SET);
732       offset_next = oggz_get_next_start_page (oggz, og);
733       serialno = ogg_page_serialno (og);
734       granule_at = ogg_page_granulepos (og);
735     }
736 
737     unit_at = oggz_get_unit (oggz, serialno, granule_at);
738 
739 #ifdef DEBUG
740     printf ("oggz_bounded_seek_set: offset_next %" PRI_OGGZ_OFF_T "d\n", offset_next);
741 #endif
742     if (unit_at == unit_last_iter) break;
743 
744 #ifdef DEBUG
745     printf ("oggz_bounded_seek_set: [D] want u%lld, got page u%lld @%" PRI_OGGZ_OFF_T "d g%lld\n",
746 	    unit_target, unit_at, offset_at, granule_at);
747 #endif
748 
749     if (unit_at < unit_target) {
750       offset_begin = offset_at;
751       unit_begin = unit_at;
752       if (unit_end == unit_begin) break;
753     } else if (unit_at > unit_target) {
754       offset_end = offset_at-1;
755       unit_end = unit_at;
756       if (unit_end == unit_begin) break;
757     } else {
758       break;
759     }
760   }
761 
762   do {
763     offset_at = oggz_get_prev_start_page (oggz, og, &granule_at, &serialno);
764     unit_at = oggz_get_unit (oggz, serialno, granule_at);
765   } while (unit_at > unit_target);
766 
767   if (offset_at < 0) {
768     oggz_reset (oggz, offset_orig, -1, SEEK_SET);
769     return -1;
770   }
771 
772   offset_at = oggz_reset (oggz, offset_at, unit_at, SEEK_SET);
773   if (offset_at == -1) return -1;
774 
775 #ifdef DEBUG
776   printf ("oggz_bounded_seek_set: FOUND (%lld)\n", unit_at);
777 #endif
778 
779   return (long)reader->current_unit;
780 }
781 
782 static ogg_int64_t
oggz_seek_end(OGGZ * oggz,ogg_int64_t unit_offset)783 oggz_seek_end (OGGZ * oggz, ogg_int64_t unit_offset)
784 {
785   oggz_off_t offset_orig, offset_at, offset_end;
786   ogg_int64_t granulepos;
787   ogg_int64_t unit_end;
788   long serialno;
789   ogg_page * og;
790 
791   og = &oggz->current_page;
792 
793   offset_orig = oggz->offset;
794 
795   offset_at = oggz_seek_raw (oggz, 0, SEEK_END);
796   if (offset_at == -1) return -1;
797 
798   offset_end = oggz_get_prev_start_page (oggz, og, &granulepos, &serialno);
799 
800   if (offset_end < 0) {
801     oggz_reset (oggz, offset_orig, -1, SEEK_SET);
802     return -1;
803   }
804 
805   unit_end = oggz_get_unit (oggz, serialno, granulepos);
806 
807 #ifdef DEBUG
808   printf ("*** oggz_seek_end: found packet (%lld) at @%" PRI_OGGZ_OFF_T "d [%lld]\n",
809 	  unit_end, offset_end, granulepos);
810 #endif
811 
812   return oggz_bounded_seek_set (oggz, unit_end + unit_offset, 0, -1);
813 }
814 
815 off_t
oggz_seek(OGGZ * oggz,oggz_off_t offset,int whence)816 oggz_seek (OGGZ * oggz, oggz_off_t offset, int whence)
817 {
818   OggzReader * reader;
819   ogg_int64_t units = -1;
820 
821   if (oggz == NULL) return -1;
822 
823   if (oggz->flags & OGGZ_WRITE) {
824     return -1;
825   }
826 
827   if (offset == 0 && whence == SEEK_SET) units = 0;
828 
829   reader = &oggz->x.reader;
830 
831   if (!(offset == 0 && whence == SEEK_CUR)) {
832     /* Invalidate current_unit */
833     reader->current_unit = -1;
834   }
835 
836   return (off_t)oggz_reset (oggz, offset, units, whence);
837 }
838 
839 ogg_int64_t
oggz_seek_units(OGGZ * oggz,ogg_int64_t units,int whence)840 oggz_seek_units (OGGZ * oggz, ogg_int64_t units, int whence)
841 {
842   OggzReader * reader;
843 
844   ogg_int64_t r;
845 
846   if (oggz == NULL) {
847 #ifdef DEBUG
848     printf ("oggz_seek_units: oggz NULL, FAIL\n");
849 #endif
850     return -1;
851   }
852 
853   if (oggz->flags & OGGZ_WRITE) {
854 #ifdef DEBUG
855     printf ("oggz_seek_units: is OGGZ_WRITE, FAIL\n");
856 #endif
857     return -1;
858   }
859 
860   if (!oggz_has_metrics (oggz)) {
861 #ifdef DEBUG
862     printf ("oggz_seek_units: !has_metrics, FAIL\n");
863 #endif
864     return -1;
865   }
866 
867   reader = &oggz->x.reader;
868 
869   switch (whence) {
870   case SEEK_SET:
871     r = oggz_bounded_seek_set (oggz, units, 0, -1);
872     break;
873   case SEEK_CUR:
874     units += reader->current_unit;
875     r = oggz_bounded_seek_set (oggz, units, 0, -1);
876     break;
877   case SEEK_END:
878     r = oggz_seek_end (oggz, units);
879     break;
880   default:
881     /*oggz_set_error (oggz, OGGZ_EINVALID);*/
882     r = -1;
883     break;
884   }
885 
886   reader->current_granulepos = -1;
887   return r;
888 }
889 
890 long
oggz_seek_byorder(OGGZ * oggz,void * target)891 oggz_seek_byorder (OGGZ * oggz, void * target)
892 {
893   return -1;
894 }
895 
896 long
oggz_seek_packets(OGGZ * oggz,long serialno,long packets,int whence)897 oggz_seek_packets (OGGZ * oggz, long serialno, long packets, int whence)
898 {
899   return -1;
900 }
901 
902 #else
903 
904 #include <ogg/ogg.h>
905 #include "oggz_private.h"
906 
907 off_t
oggz_seek(OGGZ * oggz,oggz_off_t offset,int whence)908 oggz_seek (OGGZ * oggz, oggz_off_t offset, int whence)
909 {
910   return OGGZ_ERR_DISABLED;
911 }
912 
913 long
oggz_seek_units(OGGZ * oggz,ogg_int64_t units,int whence)914 oggz_seek_units (OGGZ * oggz, ogg_int64_t units, int whence)
915 {
916   return OGGZ_ERR_DISABLED;
917 }
918 
919 long
oggz_seek_byorder(OGGZ * oggz,void * target)920 oggz_seek_byorder (OGGZ * oggz, void * target)
921 {
922   return OGGZ_ERR_DISABLED;
923 }
924 
925 long
oggz_seek_packets(OGGZ * oggz,long serialno,long packets,int whence)926 oggz_seek_packets (OGGZ * oggz, long serialno, long packets, int whence)
927 {
928   return OGGZ_ERR_DISABLED;
929 }
930 
931 #endif
932