File size: 8,373 Bytes
78d2150
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
#include "../../unity/unity.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <errno.h>
#include <limits.h>

/* Access to dd.c internals since this test file is included into dd.c */
extern intmax_t skip (int fdesc, char const *file, intmax_t records, idx_t blocksize, idx_t *bytes);

/* The following variables/functions are defined in dd.c and are accessible here */
extern ssize_t iread (int fd, char *buf, idx_t size);
extern int ifd_reopen (int desired_fd, char const *file, int flag, mode_t mode);

/* Globals from dd.c (file-scope static there, but visible here via inclusion) */
extern char const *input_file;
extern char const *output_file;
extern idx_t page_size;
extern idx_t input_blocksize;
extern idx_t output_blocksize;
extern char *ibuf;
extern char *obuf;
extern off_t input_offset;
extern ssize_t (*iread_fnc) (int fd, char *buf, idx_t size);

/* Helpers */
static long get_pagesize_fallback(void)
{
    long ps = sysconf(_SC_PAGESIZE);
    if (ps <= 0) ps = 4096;
    return ps;
}

static int save_fd(int fd)
{
    int dupfd = dup(fd);
    return dupfd;
}

static void restore_fd(int dstfd, int saved)
{
    if (saved >= 0) {
        (void)dup2(saved, dstfd);
        close(saved);
    }
}

static void make_temp_file(char *tmpl_buf, size_t bufsz)
{
    /* Caller should provide a buffer like "/tmp/ddtest-XXXXXX" */
    int fd = mkstemp(tmpl_buf);
    TEST_ASSERT_MESSAGE(fd >= 0, "mkstemp failed");
    close(fd);
}

static void set_file_size(const char *path, off_t size)
{
    int fd = open(path, O_RDWR);
    TEST_ASSERT_MESSAGE(fd >= 0, "open temp file failed");
    int r = ftruncate(fd, size);
    TEST_ASSERT_MESSAGE(r == 0, "ftruncate failed");
    close(fd);
}

static void reopen_stdin_to(const char *path)
{
    int r = ifd_reopen(STDIN_FILENO, path, O_RDONLY, 0);
    TEST_ASSERT_MESSAGE(r == 0, "ifd_reopen(stdin) failed");
}

static void reopen_stdout_to(const char *path)
{
    int r = ifd_reopen(STDOUT_FILENO, path, O_WRONLY | O_CREAT | O_TRUNC, 0600);
    TEST_ASSERT_MESSAGE(r == 0, "ifd_reopen(stdout) failed");
}

void setUp(void) {
    /* Ensure environment */
    page_size = (idx_t)get_pagesize_fallback();

    /* Set a consistent input/output blocksize before any allocation occurs. */
    input_blocksize = 4;
    output_blocksize = 4;

    /* Ensure buffers are not yet allocated by our tests unless needed. */
    /* ibuf/obuf are static in dd.c; if already allocated by previous tests, 
       we'll keep using the same blocksize (4) to avoid mismatch. */

    /* Set default function pointer for reading. */
    iread_fnc = iread;

    input_offset = 0;
    input_file = NULL;
    output_file = NULL;
}

void tearDown(void) {
    /* Nothing special to clean here; each test restores fds and unlinks files. */
}

/* Test 1: seekable STDIN, lseek succeeds within file (no EOF overrun). */
void test_skip_stdin_seek_within_file(void)
{
    int saved_stdin = save_fd(STDIN_FILENO);

    char path[64] = "/tmp/ddtest1-XXXXXX";
    make_temp_file(path, sizeof(path));
    set_file_size(path, 100);

    input_file = path;
    input_offset = 0;

    reopen_stdin_to(path);

    /* Target: records=3, blocksize=10, bytes=5 => offset 35 (within 100). */
    idx_t bytes = 5;
    intmax_t rem = skip(STDIN_FILENO, input_file, 3, (idx_t)10, &bytes);

    TEST_ASSERT_EQUAL_INT64(0, rem);
    /* bytes is not specified to change for STDIN lseek path; ensure it's unchanged */
    TEST_ASSERT_EQUAL_UINT64(5, (unsigned long long)bytes);

    off_t pos = lseek(STDIN_FILENO, 0, SEEK_CUR);
    TEST_ASSERT_EQUAL_INT64(35, (long long)pos);

    /* input_offset should have advanced by 35 */
    TEST_ASSERT_EQUAL_INT64(35, (long long)input_offset);

    restore_fd(STDIN_FILENO, saved_stdin);
    unlink(path);
}

/* Test 2: seekable STDIN, lseek succeeds but skipping past EOF.
   Expect returned remaining full records equal to overrun/blocksize,
   and input_offset advanced only up to EOF. */
void test_skip_stdin_seek_past_eof(void)
{
    int saved_stdin = save_fd(STDIN_FILENO);

    char path[64] = "/tmp/ddtest2-XXXXXX";
    make_temp_file(path, sizeof(path));
    set_file_size(path, 10); /* 10 bytes */

    input_file = path;
    input_offset = 0;

    reopen_stdin_to(path);

    /* records=5, blocksize=4 => 20 bytes; past EOF by 10; so remaining records = (20-10)/4 = 2 */
    idx_t bytes = 0;
    intmax_t rem = skip(STDIN_FILENO, input_file, 5, (idx_t)4, &bytes);

    TEST_ASSERT_EQUAL_INT64(2, rem);

    /* File position was lseek'd to requested offset (20) */
    off_t pos = lseek(STDIN_FILENO, 0, SEEK_CUR);
    TEST_ASSERT_EQUAL_INT64(20, (long long)pos);

    /* input_offset advanced only by available data (10) */
    TEST_ASSERT_EQUAL_INT64(10, (long long)input_offset);

    restore_fd(STDIN_FILENO, saved_stdin);
    unlink(path);
}

/* Test 3: non-seekable STDIN (pipe), fallback reading.
   Provide less data than requested so that some extra bytes remain unskipped.
   Expect returned remaining records 0, but bytes remain nonzero. */
void test_skip_stdin_fallback_pipe_partial_blocks_and_bytes(void)
{
    int saved_stdin = save_fd(STDIN_FILENO);

    int p[2];
    TEST_ASSERT_MESSAGE(pipe(p) == 0, "pipe failed");

    /* Write 10 bytes total to the pipe */
    const char payload[10] = {0};
    ssize_t wr = write(p[1], payload, sizeof(payload));
    TEST_ASSERT_EQUAL_INT64(10, wr);
    close(p[1]); /* EOF after 10 bytes */

    /* Make read end be STDIN */
    (void)dup2(p[0], STDIN_FILENO);
    close(p[0]);

    input_file = "pipe";
    input_offset = 0;

    /* Request: records=3, blocksize=4 (12 bytes), then bytes=2 => total 14; only 10 available */
    idx_t bytes = 2;
    intmax_t rem = skip(STDIN_FILENO, input_file, 3, (idx_t)4, &bytes);

    /* With the implementation, records becomes 0 even though the last block was partial,
       and since EOF occurred before skipping the extra bytes, bytes remains nonzero (2). */
    TEST_ASSERT_EQUAL_INT64(0, rem);
    TEST_ASSERT_EQUAL_UINT64(2, (unsigned long long)bytes);

    /* input_offset advanced by exactly the bytes read */
    TEST_ASSERT_EQUAL_INT64(10, (long long)input_offset);

    restore_fd(STDIN_FILENO, saved_stdin);
}

/* Test 4: non-seekable STDIN (pipe), EOF before finishing all records.
   Expect returned remaining records to be >0. */
void test_skip_stdin_fallback_pipe_early_eof_records_remaining(void)
{
    int saved_stdin = save_fd(STDIN_FILENO);

    int p[2];
    TEST_ASSERT_MESSAGE(pipe(p) == 0, "pipe failed");

    /* Write only 5 bytes; request 3 records of 4 bytes (12 total) */
    const char payload[5] = {0};
    ssize_t wr = write(p[1], payload, sizeof(payload));
    TEST_ASSERT_EQUAL_INT64(5, wr);
    close(p[1]);

    (void)dup2(p[0], STDIN_FILENO);
    close(p[0]);

    input_file = "pipe";
    input_offset = 0;

    idx_t bytes = 0;
    intmax_t rem = skip(STDIN_FILENO, input_file, 3, (idx_t)4, &bytes);

    /* We read 4 (records->2), then 1 (records->1), then EOF => remaining records should be 1 */
    TEST_ASSERT_EQUAL_INT64(1, rem);
    TEST_ASSERT_EQUAL_INT64(5, (long long)input_offset);

    restore_fd(STDIN_FILENO, saved_stdin);
}

/* Test 5: seekable STDOUT, lseek succeeds; expect bytes cleared and 0 remaining records. */
void test_skip_stdout_seekable_with_bytes_zeroed(void)
{
    int saved_stdout = save_fd(STDOUT_FILENO);

    char path[64] = "/tmp/ddtest5-XXXXXX";
    make_temp_file(path, sizeof(path));
    set_file_size(path, 0);

    output_file = path;
    reopen_stdout_to(path);

    /* Seek 2 records of 4 plus 1 byte; on success for output, bytes is set to 0 */
    idx_t bytes = 1;
    intmax_t rem = skip(STDOUT_FILENO, output_file, 2, (idx_t)4, &bytes);

    TEST_ASSERT_EQUAL_INT64(0, rem);
    TEST_ASSERT_EQUAL_UINT64(0, (unsigned long long)bytes);

    off_t pos = lseek(STDOUT_FILENO, 0, SEEK_CUR);
    TEST_ASSERT_EQUAL_INT64(9, (long long)pos);

    restore_fd(STDOUT_FILENO, saved_stdout);
    unlink(path);
}

int main(void)
{
    UNITY_BEGIN();

    RUN_TEST(test_skip_stdin_seek_within_file);
    RUN_TEST(test_skip_stdin_seek_past_eof);
    RUN_TEST(test_skip_stdin_fallback_pipe_partial_blocks_and_bytes);
    RUN_TEST(test_skip_stdin_fallback_pipe_early_eof_records_remaining);
    RUN_TEST(test_skip_stdout_seekable_with_bytes_zeroed);

    return UNITY_END();
}