File: virtfile.c

package info (click to toggle)
lookup 1.08b-5
  • links: PTS
  • area: contrib
  • in suites: woody
  • size: 1,108 kB
  • ctags: 1,305
  • sloc: ansic: 12,634; makefile: 236; perl: 174; sh: 53
file content (237 lines) | stat: -rw-r--r-- 5,807 bytes parent folder | download | duplicates (9)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
/*
 * Jeffrey Friedl
 * Omron Corporation			ʳ
 * Nagaokakyoshi, Japan			617Ĺ
 *
 * jfriedl@nff.ncl.omron.co.jp
 *
 * This work is placed under the terms of the GNU General Purpose License
 * (the "GNU Copyleft").
 *
 * July 1996
 *
 * Routine to access a file with virtualmemory-like access.
 */
#include "config.h"
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/file.h>
#include "output.h"
#include "xmalloc.h"
#include "strsave.h"
#include "virtfile.h"

#define PAGE_VALID(p)  ((p)->text)

/*
 * Given a filename, return the length of the file.
 */
long int filesize(const char *filename)
{
    struct stat statbuf;
    if (stat(filename, &statbuf) < 0)
	return -1L;
    else
	return statbuf.st_size;
}

/*
 * PAGE_SIZE -- size of pages loaded. The page size limits the
 * length of the longest line.
 */
static unsigned PAGE_SIZE = 0x2000;

/*
 * Number of pages to keep in memory at once, for all files.
 */
static unsigned PAGE_COUNT = 40;

/*
 * pointer to list of pages....
 */
static Page *common = 0;
static unsigned default_blast = 0;

/*
 * Init routine -- fills 'common'. Can change PAGE_COUNT beforehand....
 */
static void init_common(void)
{
    int i;
    if (common)
	return;

    common = xmalloc(sizeof(Page) * PAGE_COUNT);
    bzero(common, sizeof(Page) * PAGE_COUNT);
}


VirtFile *
OpenVertFile(const char *filename)
{
    VirtFile *v;
    int fd = open(filename, 0);

    if (fd < 0)
	return NULL;

    if (!common)
	init_common();

    v = xmalloc(sizeof *v);
    v->fd = fd;
    v->filename = strsave(filename);
    v->length = filesize(filename);

    return v;
}

static Page *
LoadPage(VirtFile *v, fileloc start)
{
     int i;
     Page *empty = 0, *p = 0;

     /* See if a page holding the location is already available */
     for (i = 0; i < PAGE_COUNT; i++)
     {
	 if (!empty && !PAGE_VALID(&common[i]))
	     empty = &common[i];
	 if (common[i].owner == v && common[i].start < start) {
	     p = &common[i];
	     break;
	 }
     }

     if (!p)
     {
	 /* if not, either pick an empty one, or blast a currently-used one */
	 if (empty)
	     p = empty;
	 else {
	     p = &common[default_blast];
	     default_blast = (default_blast + 1) % PAGE_COUNT;
	 }
     }

     /*
      * If page has no text yet, grab some memory */
     if (!PAGE_VALID(p))
	 p->text = xmalloc(PAGE_SIZE);

     /* go to appropriate place in file and read data */
     if (lseek(v->fd, start, SEEK_SET) < 0)
	 die("bad lseek to %ld of %s: %n\n", (long)start);
     if (i = read(v->fd, p->text, PAGE_SIZE), i < 0)
	 die("bad read of %ld bytes starting at %ld of %s: %n\n",
	     (long)PAGE_SIZE, (long)start, v->filename);

     /* fill in other page stuff */
     p->start = start;
     p->end   = start + i;
     p->owner = v;
     return p;
}

/*
 * Given a file an a location, return a page that holds the location
 * somewhere.
 */
static Page *
EnsurePositionInMemory(VirtFile *v, fileloc pos)
{
    Page *p = 0;
    int i;

    /* see if page is already there */
    for (i = 0; i < PAGE_COUNT; i++)
    {
	/* must be valid and owned by me */
	if (!PAGE_VALID(&common[i]) || (common[i].owner != v))
	    continue;
	/* must have a position in range [start .. end) */
	if (pos < common[i].start || pos > common[i].end)
	    continue;

	/* if we don't have a pointer yet, or if this page has the
	   desired location earlier in the page than the one we already
	   found, use this new page instead */
	if (!p || (pos - p->start) > (pos - common[i].start))
	    p = &common[i];
    }

    /* if it's not already here, get a new page */
    if (p)
	return p;
    else
	return LoadPage(v, pos);
}

/*
 * Given a page and an index into the file (which the given page MUST
 * represent), return the length of the line.
 */
struct ScanInfo {
    const unsigned char *str_start;  /* pointer in memory where line starts */
    unsigned short length;           /* length of line */
    char no_end;                     /* true if line extends after page */
};

static __inline__ struct ScanInfo *
ScanStrInPage(Page *p, fileloc start)
{
    static struct ScanInfo result;

    /* PTR is string we'll scan, END is end of the page */
    const unsigned char *ptr = &p->text[start - p->start];
    const unsigned char *end = &p->text[(int)(p->end - p->start)];

    result.str_start = ptr;

    /* scan string looking for newline */
    while ((ptr < end) && (*ptr != '\n'))
	ptr++;
    result.length = ptr - result.str_start;

    /*
     * Past end of page, and... page is full size (i.e. not last page of file)
     */
    result.no_end = (ptr >= end && (p->end - p->start >= PAGE_SIZE));

    return &result;
}

/*
 * Given a file and a location, return a pointer to memory holding the
 * string starting at (or spanning) the given location.
 * Fill *pCount with the length of the string if not NULL.
 */
const unsigned char *
VirtPos2Str(VirtFile *v, fileloc start, unsigned *pCount)
{
    struct ScanInfo *info;

    /* some small sanity check */
    if (start < 0 || start > v->length)
	die("oops: %ld vs. length=%ld.\n", start, v->length);

    /* Get the page with the string, and get the string */
    info = ScanStrInPage(EnsurePositionInMemory(v, start), start);

    /*
     * If we hit the end of the page (and the page is a full page,
     * and not the last one in the file where a newline might not end
     * things), we need to re-load the page such that the target line
     * is at the beginning of the page. This will ensure the whole line
     * is in memory. Of course, if the line is longer than PAGE_LENGTH
     * then life is tough and we return a chopped line.
     */
    if (info->no_end)
	info = ScanStrInPage(LoadPage(v, start), start);

    if (pCount)
	*pCount = info->length;

    return info->str_start;
}