File: img_io.c

package info (click to toggle)
sleuthkit 4.12.1%2Bdfsg-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 18,608 kB
  • sloc: ansic: 143,795; cpp: 52,225; java: 37,892; xml: 2,416; python: 1,076; perl: 874; makefile: 439; sh: 184
file content (250 lines) | stat: -rwxr-xr-x 8,815 bytes parent folder | download
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
238
239
240
241
242
243
244
245
246
247
248
249
250
/*
 * Brian Carrier [carrier <at> sleuthkit [dot] org]
 * Copyright (c) 2011 Brian Carrier.  All Rights reserved
 *
 * This software is distributed under the Common Public License 1.0
 */

/**
 * \file img_io.c
 * Contains the basic img reading API redirection functions.
 */

#include "tsk_img_i.h"

// This function assumes that we hold the cache_lock even though we're not modyfying
// the cache.  This is because the lower-level read callbacks make the same assumption.
static ssize_t tsk_img_read_no_cache(TSK_IMG_INFO * a_img_info, TSK_OFF_T a_off,
    char *a_buf, size_t a_len)
{
    ssize_t nbytes;

    /* Some of the lower-level methods like block-sized reads.
        * So if the len is not that multiple, then make it. */
    if ((a_img_info->sector_size > 0) && (a_len % a_img_info->sector_size)) {
        char *buf2 = a_buf;

        size_t len_tmp;
        len_tmp = roundup(a_len, a_img_info->sector_size);
        if ((buf2 = (char *) tsk_malloc(len_tmp)) == NULL) {
            return -1;
        }
        nbytes = a_img_info->read(a_img_info, a_off, buf2, len_tmp);
        if ((nbytes > 0) && (nbytes < (ssize_t) a_len)) {
            memcpy(a_buf, buf2, nbytes);
        }
        else {
            memcpy(a_buf, buf2, a_len);
            nbytes = (ssize_t)a_len;
        }
        free(buf2);
    }
    else {
        nbytes = a_img_info->read(a_img_info, a_off, a_buf, a_len);
    }
    return nbytes;
}

/**
 * \ingroup imglib
 * Reads data from an open disk image
 * @param a_img_info Disk image to read from
 * @param a_off Byte offset to start reading from
 * @param a_buf Buffer to read into
 * @param a_len Number of bytes to read into buffer
 * @returns -1 on error or number of bytes read
 */
ssize_t
tsk_img_read(TSK_IMG_INFO * a_img_info, TSK_OFF_T a_off,
    char *a_buf, size_t a_len)
{
#define CACHE_AGE   1000
    ssize_t read_count = 0;
    int cache_index = 0;
    int cache_next = 0;         // index to lowest age cache (to use next)
    size_t len2 = 0;

    if (a_img_info == NULL) {
        tsk_error_reset();
        tsk_error_set_errno(TSK_ERR_IMG_ARG);
        tsk_error_set_errstr("tsk_img_read: a_img_info: NULL");
        return -1;
    }

    // Do not allow a_buf to be NULL.
    if (a_buf == NULL) {
        tsk_error_reset();
        tsk_error_set_errno(TSK_ERR_IMG_ARG);
        tsk_error_set_errstr("tsk_img_read: a_buf: NULL");
        return -1;
    }

    // The function cannot handle negative offsets.
    if (a_off < 0) {
        tsk_error_reset();
        tsk_error_set_errno(TSK_ERR_IMG_ARG);
        tsk_error_set_errstr("tsk_img_read: a_off: %" PRIdOFF, a_off);
        return -1;
    }

    // Protect a_off against overflowing when a_len is added since TSK_OFF_T
    // maps to an int64 we prefer it over size_t although likely checking
    // for ( a_len > SSIZE_MAX ) is better but the code does not seem to
    // use that approach.

    if ((TSK_OFF_T) a_len < 0) {
        tsk_error_reset();
        tsk_error_set_errno(TSK_ERR_IMG_ARG);
        tsk_error_set_errstr("tsk_img_read: a_len: %zd", a_len);
        return -1;
    }

    /* cache_lock is used for both the cache in IMG_INFO and 
     * the shared variables in the img type specific INFO structs.
     * grab it now so that it is held before any reads.
     */
    tsk_take_lock(&(a_img_info->cache_lock));

    // if they ask for more than the cache length, skip the cache
    if ((a_len + (a_off % 512)) > TSK_IMG_INFO_CACHE_LEN) {
        read_count = tsk_img_read_no_cache(a_img_info, a_off, a_buf, a_len);
        tsk_release_lock(&(a_img_info->cache_lock));
        return read_count;
    }

    // TODO: why not just return 0 here (and be POSIX compliant)?
    // and why not check earlier for this condition?
    if (a_off >= a_img_info->size) {
        tsk_release_lock(&(a_img_info->cache_lock));
        tsk_error_reset();
        tsk_error_set_errno(TSK_ERR_IMG_READ_OFF);
        tsk_error_set_errstr("tsk_img_read - %" PRIdOFF, a_off);
        return -1;
    }

    /* See if the requested length is going to be too long.
     * we'll use this length when checking the cache. */
    len2 = a_len;

    // Protect against INT64_MAX + INT64_MAX > value
    if (((TSK_OFF_T) len2 > a_img_info->size)
        || (a_off >= (a_img_info->size - (TSK_OFF_T)len2))) {
        len2 = (size_t) (a_img_info->size - a_off);
    }

    // check if it is in the cache
    for (cache_index = 0;
        cache_index < TSK_IMG_INFO_CACHE_NUM; cache_index++) {

        // Look into the in-use cache entries
        if (a_img_info->cache_len[cache_index] > 0) {

            // the read_count check makes sure we don't go back in after data was read
            if ((read_count == 0)
                && (a_img_info->cache_off[cache_index] <= a_off)
                && (a_img_info->cache_off[cache_index] +
                    a_img_info->cache_len[cache_index] >= a_off + len2)) {

                /*
                   if (tsk_verbose)
                   fprintf(stderr,
                   "tsk_img_read: Read found in cache %d\n",  cache_index );
                 */

                // We found it...
                memcpy(a_buf,
                    &a_img_info->cache[cache_index][a_off -
                        a_img_info->cache_off[cache_index]], len2);
                read_count = (ssize_t) len2;

                // reset its "age" since it was useful
                a_img_info->cache_age[cache_index] = CACHE_AGE;

                // we don't break out of the loop so that we update all ages
            }
            else {
                /* decrease its "age" since it was not useful.
                 * We don't let used ones go below 1 so that they are not
                 * confused with entries that have never been used. */
                a_img_info->cache_age[cache_index]--;

                // see if this is the most eligible replacement
                if ((a_img_info->cache_len[cache_next] > 0)
                    && (a_img_info->cache_age[cache_index] <
                        a_img_info->cache_age[cache_next]))
                    cache_next = cache_index;
            }
        }
        else {
            cache_next = cache_index;
        }
    }

    // if we didn't find it, then load it into the cache_next entry
    if (read_count == 0) {
        size_t read_size = 0;

        // round the offset down to a sector boundary
        a_img_info->cache_off[cache_next] = (a_off / 512) * 512;

        /*
           if (tsk_verbose)
           fprintf(stderr,
           "tsk_img_read: Loading data into cache %d (%" PRIdOFF
           ")\n", cache_next, a_img_info->cache_off[cache_next]);
         */

        // Read a full cache block or the remaining data.
        read_size = TSK_IMG_INFO_CACHE_LEN;

        if ((a_img_info->cache_off[cache_next] + (TSK_OFF_T)read_size) >
            a_img_info->size) {
            read_size =
                (size_t) (a_img_info->size -
                a_img_info->cache_off[cache_next]);
        }

        read_count = a_img_info->read(a_img_info,
            a_img_info->cache_off[cache_next],
            a_img_info->cache[cache_next], read_size);

        // if no error, then set the variables and copy the data
        // Although a read_count of -1 indicates an error,
        // since read_count is used in the calculation it may not be negative.
        // Also it does not make sense to copy data when the read_count is 0.
        if (read_count > 0) {

            TSK_OFF_T rel_off = 0;
            a_img_info->cache_age[cache_next] = CACHE_AGE;
            a_img_info->cache_len[cache_next] = read_count;

            // Determine the offset relative to the start of the cached data.
            rel_off = a_off - a_img_info->cache_off[cache_next];

            // Make sure we were able to read sufficient data into the cache.
            if (rel_off > (TSK_OFF_T) read_count) {
                len2 = 0;
            }
            // Make sure not to copy more than is available in the cache.
            else if ((rel_off + (TSK_OFF_T) len2) > (TSK_OFF_T) read_count) {
                len2 = (size_t) (read_count - rel_off);
            }
            // Only copy data when we have something to copy.
            if (len2 > 0) {
                memcpy(a_buf, &(a_img_info->cache[cache_next][rel_off]), len2);
            }
            read_count = (ssize_t) len2;
        }
        else {
            a_img_info->cache_len[cache_next] = 0;
            a_img_info->cache_age[cache_next] = 0;
            a_img_info->cache_off[cache_next] = 0;

            // Something went wrong so let's try skipping the cache
            read_count = tsk_img_read_no_cache(a_img_info, a_off, a_buf, a_len);
        }
    }

    tsk_release_lock(&(a_img_info->cache_lock));
    return read_count;
}