1 |
|
|
/* $OpenBSD: traverse.c,v 1.38 2015/01/20 18:22:20 deraadt Exp $ */ |
2 |
|
|
/* $NetBSD: traverse.c,v 1.17 1997/06/05 11:13:27 lukem Exp $ */ |
3 |
|
|
|
4 |
|
|
/*- |
5 |
|
|
* Copyright (c) 1980, 1988, 1991, 1993 |
6 |
|
|
* The Regents of the University of California. All rights reserved. |
7 |
|
|
* |
8 |
|
|
* Redistribution and use in source and binary forms, with or without |
9 |
|
|
* modification, are permitted provided that the following conditions |
10 |
|
|
* are met: |
11 |
|
|
* 1. Redistributions of source code must retain the above copyright |
12 |
|
|
* notice, this list of conditions and the following disclaimer. |
13 |
|
|
* 2. Redistributions in binary form must reproduce the above copyright |
14 |
|
|
* notice, this list of conditions and the following disclaimer in the |
15 |
|
|
* documentation and/or other materials provided with the distribution. |
16 |
|
|
* 3. Neither the name of the University nor the names of its contributors |
17 |
|
|
* may be used to endorse or promote products derived from this software |
18 |
|
|
* without specific prior written permission. |
19 |
|
|
* |
20 |
|
|
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND |
21 |
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
22 |
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
23 |
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE |
24 |
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
25 |
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
26 |
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
27 |
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
28 |
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
29 |
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
30 |
|
|
* SUCH DAMAGE. |
31 |
|
|
*/ |
32 |
|
|
|
33 |
|
|
#include <sys/param.h> /* MAXBSIZE DEV_BSIZE dbtob */ |
34 |
|
|
#include <sys/time.h> |
35 |
|
|
#include <sys/stat.h> |
36 |
|
|
#include <sys/disklabel.h> |
37 |
|
|
#include <ufs/ffs/fs.h> |
38 |
|
|
#include <ufs/ufs/dir.h> |
39 |
|
|
#include <ufs/ufs/dinode.h> |
40 |
|
|
|
41 |
|
|
#include <protocols/dumprestore.h> |
42 |
|
|
|
43 |
|
|
#include <ctype.h> |
44 |
|
|
#include <errno.h> |
45 |
|
|
#include <fts.h> |
46 |
|
|
#include <stdio.h> |
47 |
|
|
#include <stdlib.h> |
48 |
|
|
#include <string.h> |
49 |
|
|
#include <unistd.h> |
50 |
|
|
#include <limits.h> |
51 |
|
|
|
52 |
|
|
#include "dump.h" |
53 |
|
|
|
54 |
|
|
extern struct disklabel lab; |
55 |
|
|
|
56 |
|
|
union dinode { |
57 |
|
|
struct ufs1_dinode dp1; |
58 |
|
|
struct ufs2_dinode dp2; |
59 |
|
|
}; |
60 |
|
|
#define DIP(dp, field) \ |
61 |
|
|
((sblock->fs_magic == FS_UFS1_MAGIC) ? \ |
62 |
|
|
(dp)->dp1.field : (dp)->dp2.field) |
63 |
|
|
|
64 |
|
|
#define HASDUMPEDFILE 0x1 |
65 |
|
|
#define HASSUBDIRS 0x2 |
66 |
|
|
|
67 |
|
|
static int dirindir(ino_t, daddr_t, int, off_t *, int64_t *, int); |
68 |
|
|
static void dmpindir(ino_t, daddr_t, int, off_t *); |
69 |
|
|
static int searchdir(ino_t, daddr_t, long, off_t, int64_t *, int); |
70 |
|
|
void fs_mapinodes(ino_t maxino, off_t *tapesize, int *anydirskipped); |
71 |
|
|
|
72 |
|
|
/* |
73 |
|
|
* This is an estimation of the number of TP_BSIZE blocks in the file. |
74 |
|
|
* It estimates the number of blocks in files with holes by assuming |
75 |
|
|
* that all of the blocks accounted for by di_blocks are data blocks |
76 |
|
|
* (when some of the blocks are usually used for indirect pointers); |
77 |
|
|
* hence the estimate may be high. |
78 |
|
|
*/ |
79 |
|
|
int64_t |
80 |
|
|
blockest(union dinode *dp) |
81 |
|
|
{ |
82 |
|
|
int64_t blkest, sizeest; |
83 |
|
|
|
84 |
|
|
/* |
85 |
|
|
* dp->di_size is the size of the file in bytes. |
86 |
|
|
* dp->di_blocks stores the number of sectors actually in the file. |
87 |
|
|
* If there are more sectors than the size would indicate, this just |
88 |
|
|
* means that there are indirect blocks in the file or unused |
89 |
|
|
* sectors in the last file block; we can safely ignore these |
90 |
|
|
* (blkest = sizeest below). |
91 |
|
|
* If the file is bigger than the number of sectors would indicate, |
92 |
|
|
* then the file has holes in it. In this case we must use the |
93 |
|
|
* block count to estimate the number of data blocks used, but |
94 |
|
|
* we use the actual size for estimating the number of indirect |
95 |
|
|
* dump blocks (sizeest vs. blkest in the indirect block |
96 |
|
|
* calculation). |
97 |
|
|
*/ |
98 |
|
|
blkest = howmany(dbtob((int64_t)DIP(dp, di_blocks)), TP_BSIZE); |
99 |
|
|
sizeest = howmany((int64_t)DIP(dp, di_size), TP_BSIZE); |
100 |
|
|
if (blkest > sizeest) |
101 |
|
|
blkest = sizeest; |
102 |
|
|
if (DIP(dp, di_size) > sblock->fs_bsize * NDADDR) { |
103 |
|
|
/* calculate the number of indirect blocks on the dump tape */ |
104 |
|
|
blkest += |
105 |
|
|
howmany(sizeest - NDADDR * sblock->fs_bsize / TP_BSIZE, |
106 |
|
|
TP_NINDIR); |
107 |
|
|
} |
108 |
|
|
return (blkest + 1); |
109 |
|
|
} |
110 |
|
|
|
111 |
|
|
/* true if "nodump" flag has no effect here, i.e. dumping allowed */ |
112 |
|
|
#define CHECKNODUMP(dp) \ |
113 |
|
|
(nonodump || (DIP((dp), di_flags) & UF_NODUMP) != UF_NODUMP) |
114 |
|
|
|
115 |
|
|
/* |
116 |
|
|
* Determine if given inode should be dumped |
117 |
|
|
*/ |
118 |
|
|
void |
119 |
|
|
mapfileino(ino_t ino, int64_t *tapesize, int *dirskipped) |
120 |
|
|
{ |
121 |
|
|
int mode; |
122 |
|
|
union dinode *dp; |
123 |
|
|
|
124 |
|
|
dp = getino(ino, &mode); |
125 |
|
|
if (mode == 0) |
126 |
|
|
return; |
127 |
|
|
SETINO(ino, usedinomap); |
128 |
|
|
if (mode == IFDIR) |
129 |
|
|
SETINO(ino, dumpdirmap); |
130 |
|
|
if (CHECKNODUMP(dp) && |
131 |
|
|
(DIP(dp, di_mtime) >= spcl.c_ddate || |
132 |
|
|
DIP(dp, di_ctime) >= spcl.c_ddate)) { |
133 |
|
|
SETINO(ino, dumpinomap); |
134 |
|
|
if (mode != IFREG && mode != IFDIR && mode != IFLNK) |
135 |
|
|
*tapesize += 1; |
136 |
|
|
else |
137 |
|
|
*tapesize += blockest(dp); |
138 |
|
|
return; |
139 |
|
|
} |
140 |
|
|
if (mode == IFDIR) { |
141 |
|
|
if (!CHECKNODUMP(dp)) |
142 |
|
|
CLRINO(ino, usedinomap); |
143 |
|
|
*dirskipped = 1; |
144 |
|
|
} |
145 |
|
|
} |
146 |
|
|
|
147 |
|
|
void |
148 |
|
|
fs_mapinodes(ino_t maxino, int64_t *tapesize, int *anydirskipped) |
149 |
|
|
{ |
150 |
|
|
int i, cg, inosused; |
151 |
|
|
struct cg *cgp; |
152 |
|
|
ino_t ino; |
153 |
|
|
char *cp; |
154 |
|
|
|
155 |
|
|
if ((cgp = malloc(sblock->fs_cgsize)) == NULL) |
156 |
|
|
quit("fs_mapinodes: cannot allocate memory.\n"); |
157 |
|
|
|
158 |
|
|
for (cg = 0; cg < sblock->fs_ncg; cg++) { |
159 |
|
|
ino = cg * sblock->fs_ipg; |
160 |
|
|
bread(fsbtodb(sblock, cgtod(sblock, cg)), (char *)cgp, |
161 |
|
|
sblock->fs_cgsize); |
162 |
|
|
if (sblock->fs_magic == FS_UFS2_MAGIC) |
163 |
|
|
inosused = cgp->cg_initediblk; |
164 |
|
|
else |
165 |
|
|
inosused = sblock->fs_ipg; |
166 |
|
|
/* |
167 |
|
|
* If we are using soft updates, then we can trust the |
168 |
|
|
* cylinder group inode allocation maps to tell us which |
169 |
|
|
* inodes are allocated. We will scan the used inode map |
170 |
|
|
* to find the inodes that are really in use, and then |
171 |
|
|
* read only those inodes in from disk. |
172 |
|
|
*/ |
173 |
|
|
if (sblock->fs_flags & FS_DOSOFTDEP) { |
174 |
|
|
if (!cg_chkmagic(cgp)) |
175 |
|
|
quit("mapfiles: cg %d: bad magic number\n", cg); |
176 |
|
|
cp = &cg_inosused(cgp)[(inosused - 1) / CHAR_BIT]; |
177 |
|
|
for ( ; inosused > 0; inosused -= CHAR_BIT, cp--) { |
178 |
|
|
if (*cp == 0) |
179 |
|
|
continue; |
180 |
|
|
for (i = 1 << (CHAR_BIT - 1); i > 0; i >>= 1) { |
181 |
|
|
if (*cp & i) |
182 |
|
|
break; |
183 |
|
|
inosused--; |
184 |
|
|
} |
185 |
|
|
break; |
186 |
|
|
} |
187 |
|
|
if (inosused <= 0) |
188 |
|
|
continue; |
189 |
|
|
} |
190 |
|
|
for (i = 0; i < inosused; i++, ino++) { |
191 |
|
|
if (ino < ROOTINO) |
192 |
|
|
continue; |
193 |
|
|
mapfileino(ino, tapesize, anydirskipped); |
194 |
|
|
} |
195 |
|
|
} |
196 |
|
|
|
197 |
|
|
free(cgp); |
198 |
|
|
} |
199 |
|
|
|
200 |
|
|
/* |
201 |
|
|
* Dump pass 1. |
202 |
|
|
* |
203 |
|
|
* Walk the inode list for a filesystem to find all allocated inodes |
204 |
|
|
* that have been modified since the previous dump time. Also, find all |
205 |
|
|
* the directories in the filesystem. |
206 |
|
|
*/ |
207 |
|
|
int |
208 |
|
|
mapfiles(ino_t maxino, int64_t *tapesize, char *disk, char * const *dirv) |
209 |
|
|
{ |
210 |
|
|
int anydirskipped = 0; |
211 |
|
|
|
212 |
|
|
if (dirv != NULL) { |
213 |
|
|
char curdir[PATH_MAX]; |
214 |
|
|
FTS *dirh; |
215 |
|
|
FTSENT *entry; |
216 |
|
|
int d; |
217 |
|
|
|
218 |
|
|
if (getcwd(curdir, sizeof(curdir)) == NULL) { |
219 |
|
|
msg("Can't determine cwd: %s\n", strerror(errno)); |
220 |
|
|
dumpabort(0); |
221 |
|
|
} |
222 |
|
|
if ((dirh = fts_open(dirv, FTS_PHYSICAL|FTS_SEEDOT|FTS_XDEV, |
223 |
|
|
NULL)) == NULL) { |
224 |
|
|
msg("fts_open failed: %s\n", strerror(errno)); |
225 |
|
|
dumpabort(0); |
226 |
|
|
} |
227 |
|
|
while ((entry = fts_read(dirh)) != NULL) { |
228 |
|
|
switch (entry->fts_info) { |
229 |
|
|
case FTS_DNR: /* an error */ |
230 |
|
|
case FTS_ERR: |
231 |
|
|
case FTS_NS: |
232 |
|
|
msg("Can't fts_read %s: %s\n", entry->fts_path, |
233 |
|
|
strerror(errno)); |
234 |
|
|
/* FALLTHROUGH */ |
235 |
|
|
case FTS_DP: /* already seen dir */ |
236 |
|
|
continue; |
237 |
|
|
} |
238 |
|
|
mapfileino(entry->fts_statp->st_ino, tapesize, |
239 |
|
|
&anydirskipped); |
240 |
|
|
} |
241 |
|
|
if (errno) { |
242 |
|
|
msg("fts_read failed: %s\n", strerror(errno)); |
243 |
|
|
dumpabort(0); |
244 |
|
|
} |
245 |
|
|
(void)fts_close(dirh); |
246 |
|
|
|
247 |
|
|
/* |
248 |
|
|
* Add any parent directories |
249 |
|
|
*/ |
250 |
|
|
for (d = 0 ; dirv[d] != NULL ; d++) { |
251 |
|
|
char path[PATH_MAX]; |
252 |
|
|
|
253 |
|
|
if (dirv[d][0] != '/') |
254 |
|
|
(void)snprintf(path, sizeof(path), "%s/%s", |
255 |
|
|
curdir, dirv[d]); |
256 |
|
|
else |
257 |
|
|
(void)snprintf(path, sizeof(path), "%s", |
258 |
|
|
dirv[d]); |
259 |
|
|
while (strcmp(path, disk) != 0) { |
260 |
|
|
char *p; |
261 |
|
|
struct stat sb; |
262 |
|
|
|
263 |
|
|
if (*path == '\0') |
264 |
|
|
break; |
265 |
|
|
if ((p = strrchr(path, '/')) == NULL) |
266 |
|
|
break; |
267 |
|
|
if (p == path) |
268 |
|
|
break; |
269 |
|
|
*p = '\0'; |
270 |
|
|
if (stat(path, &sb) == -1) { |
271 |
|
|
msg("Can't stat %s: %s\n", path, |
272 |
|
|
strerror(errno)); |
273 |
|
|
break; |
274 |
|
|
} |
275 |
|
|
mapfileino(sb.st_ino, tapesize, &anydirskipped); |
276 |
|
|
} |
277 |
|
|
} |
278 |
|
|
|
279 |
|
|
/* |
280 |
|
|
* Ensure that the root inode actually appears in the |
281 |
|
|
* file list for a subdir |
282 |
|
|
*/ |
283 |
|
|
mapfileino(ROOTINO, tapesize, &anydirskipped); |
284 |
|
|
} else { |
285 |
|
|
fs_mapinodes(maxino, tapesize, &anydirskipped); |
286 |
|
|
} |
287 |
|
|
/* |
288 |
|
|
* Restore gets very upset if the root is not dumped, |
289 |
|
|
* so ensure that it always is dumped. |
290 |
|
|
*/ |
291 |
|
|
SETINO(ROOTINO, dumpinomap); |
292 |
|
|
return (anydirskipped); |
293 |
|
|
} |
294 |
|
|
|
295 |
|
|
/* |
296 |
|
|
* Dump pass 2. |
297 |
|
|
* |
298 |
|
|
* Scan each directory on the filesystem to see if it has any modified |
299 |
|
|
* files in it. If it does, and has not already been added to the dump |
300 |
|
|
* list (because it was itself modified), then add it. If a directory |
301 |
|
|
* has not been modified itself, contains no modified files and has no |
302 |
|
|
* subdirectories, then it can be deleted from the dump list and from |
303 |
|
|
* the list of directories. By deleting it from the list of directories, |
304 |
|
|
* its parent may now qualify for the same treatment on this or a later |
305 |
|
|
* pass using this algorithm. |
306 |
|
|
*/ |
307 |
|
|
int |
308 |
|
|
mapdirs(ino_t maxino, int64_t *tapesize) |
309 |
|
|
{ |
310 |
|
|
union dinode *dp; |
311 |
|
|
int i, isdir, nodump; |
312 |
|
|
char *map; |
313 |
|
|
ino_t ino; |
314 |
|
|
union dinode di; |
315 |
|
|
off_t filesize; |
316 |
|
|
int ret, change = 0; |
317 |
|
|
|
318 |
|
|
isdir = 0; /* XXX just to get gcc to shut up */ |
319 |
|
|
for (map = dumpdirmap, ino = 1; ino < maxino; ino++) { |
320 |
|
|
if (((ino - 1) % NBBY) == 0) /* map is offset by 1 */ |
321 |
|
|
isdir = *map++; |
322 |
|
|
else |
323 |
|
|
isdir >>= 1; |
324 |
|
|
/* |
325 |
|
|
* If a directory has been removed from usedinomap, it |
326 |
|
|
* either has the nodump flag set, or has inherited |
327 |
|
|
* it. Although a directory can't be in dumpinomap if |
328 |
|
|
* it isn't in usedinomap, we have to go through it to |
329 |
|
|
* propagate the nodump flag. |
330 |
|
|
*/ |
331 |
|
|
nodump = !nonodump && !TSTINO(ino, usedinomap); |
332 |
|
|
if ((isdir & 1) == 0 || (TSTINO(ino, dumpinomap) && !nodump)) |
333 |
|
|
continue; |
334 |
|
|
dp = getino(ino, &i); |
335 |
|
|
/* |
336 |
|
|
* inode buf may change in searchdir(). |
337 |
|
|
*/ |
338 |
|
|
if (sblock->fs_magic == FS_UFS1_MAGIC) |
339 |
|
|
di.dp1 = dp->dp1; |
340 |
|
|
else |
341 |
|
|
di.dp2 = dp->dp2; |
342 |
|
|
filesize = (off_t)DIP(dp, di_size); |
343 |
|
|
for (ret = 0, i = 0; filesize > 0 && i < NDADDR; i++) { |
344 |
|
|
if (DIP(&di, di_db[i]) != 0) |
345 |
|
|
ret |= searchdir(ino, DIP(&di, di_db[i]), |
346 |
|
|
sblksize(sblock, DIP(dp, di_size), i), |
347 |
|
|
filesize, tapesize, nodump); |
348 |
|
|
if (ret & HASDUMPEDFILE) |
349 |
|
|
filesize = 0; |
350 |
|
|
else |
351 |
|
|
filesize -= sblock->fs_bsize; |
352 |
|
|
} |
353 |
|
|
for (i = 0; filesize > 0 && i < NIADDR; i++) { |
354 |
|
|
if (DIP(&di, di_ib[i]) == 0) |
355 |
|
|
continue; |
356 |
|
|
ret |= dirindir(ino, DIP(&di, di_ib[i]), i, &filesize, |
357 |
|
|
tapesize, nodump); |
358 |
|
|
} |
359 |
|
|
if (ret & HASDUMPEDFILE) { |
360 |
|
|
SETINO(ino, dumpinomap); |
361 |
|
|
*tapesize += blockest(dp); |
362 |
|
|
change = 1; |
363 |
|
|
continue; |
364 |
|
|
} |
365 |
|
|
if (nodump) { |
366 |
|
|
if (ret & HASSUBDIRS) |
367 |
|
|
change = 1; /* subdirs inherit nodump */ |
368 |
|
|
CLRINO(ino, dumpdirmap); |
369 |
|
|
} else if ((ret & HASSUBDIRS) == 0) { |
370 |
|
|
if (!TSTINO(ino, dumpinomap)) { |
371 |
|
|
CLRINO(ino, dumpdirmap); |
372 |
|
|
change = 1; |
373 |
|
|
} |
374 |
|
|
} |
375 |
|
|
} |
376 |
|
|
return (change); |
377 |
|
|
} |
378 |
|
|
|
379 |
|
|
/* |
380 |
|
|
* Read indirect blocks, and pass the data blocks to be searched |
381 |
|
|
* as directories. Quit as soon as any entry is found that will |
382 |
|
|
* require the directory to be dumped. |
383 |
|
|
*/ |
384 |
|
|
static int |
385 |
|
|
dirindir(ino_t ino, daddr_t blkno, int ind_level, off_t *filesize, |
386 |
|
|
int64_t *tapesize, int nodump) |
387 |
|
|
{ |
388 |
|
|
int ret = 0; |
389 |
|
|
int i; |
390 |
|
|
char idblk[MAXBSIZE]; |
391 |
|
|
|
392 |
|
|
bread(fsbtodb(sblock, blkno), idblk, (int)sblock->fs_bsize); |
393 |
|
|
if (ind_level <= 0) { |
394 |
|
|
for (i = 0; *filesize > 0 && i < NINDIR(sblock); i++) { |
395 |
|
|
if (sblock->fs_magic == FS_UFS1_MAGIC) |
396 |
|
|
blkno = ((int32_t *)idblk)[i]; |
397 |
|
|
else |
398 |
|
|
blkno = ((int64_t *)idblk)[i]; |
399 |
|
|
if (blkno != 0) |
400 |
|
|
ret |= searchdir(ino, blkno, sblock->fs_bsize, |
401 |
|
|
*filesize, tapesize, nodump); |
402 |
|
|
if (ret & HASDUMPEDFILE) |
403 |
|
|
*filesize = 0; |
404 |
|
|
else |
405 |
|
|
*filesize -= sblock->fs_bsize; |
406 |
|
|
} |
407 |
|
|
return (ret); |
408 |
|
|
} |
409 |
|
|
ind_level--; |
410 |
|
|
for (i = 0; *filesize > 0 && i < NINDIR(sblock); i++) { |
411 |
|
|
if (sblock->fs_magic == FS_UFS1_MAGIC) |
412 |
|
|
blkno = ((int32_t *)idblk)[i]; |
413 |
|
|
else |
414 |
|
|
blkno = ((int64_t *)idblk)[i]; |
415 |
|
|
if (blkno != 0) |
416 |
|
|
ret |= dirindir(ino, blkno, ind_level, filesize, |
417 |
|
|
tapesize, nodump); |
418 |
|
|
} |
419 |
|
|
return (ret); |
420 |
|
|
} |
421 |
|
|
|
422 |
|
|
/* |
423 |
|
|
* Scan a disk block containing directory information looking to see if |
424 |
|
|
* any of the entries are on the dump list and to see if the directory |
425 |
|
|
* contains any subdirectories. |
426 |
|
|
*/ |
427 |
|
|
static int |
428 |
|
|
searchdir(ino_t ino, daddr_t blkno, long size, off_t filesize, |
429 |
|
|
int64_t *tapesize, int nodump) |
430 |
|
|
{ |
431 |
|
|
struct direct *dp; |
432 |
|
|
union dinode *ip; |
433 |
|
|
long loc; |
434 |
|
|
static caddr_t dblk; |
435 |
|
|
int mode, ret = 0; |
436 |
|
|
|
437 |
|
|
if (dblk == NULL && (dblk = malloc(sblock->fs_bsize)) == NULL) |
438 |
|
|
quit("searchdir: cannot allocate indirect memory.\n"); |
439 |
|
|
bread(fsbtodb(sblock, blkno), dblk, (int)size); |
440 |
|
|
if (filesize < size) |
441 |
|
|
size = filesize; |
442 |
|
|
for (loc = 0; loc < size; ) { |
443 |
|
|
dp = (struct direct *)(dblk + loc); |
444 |
|
|
if (dp->d_reclen == 0) { |
445 |
|
|
msg("corrupted directory, inumber %llu\n", |
446 |
|
|
(unsigned long long)ino); |
447 |
|
|
break; |
448 |
|
|
} |
449 |
|
|
loc += dp->d_reclen; |
450 |
|
|
if (dp->d_ino == 0) |
451 |
|
|
continue; |
452 |
|
|
if (dp->d_name[0] == '.') { |
453 |
|
|
if (dp->d_name[1] == '\0') |
454 |
|
|
continue; |
455 |
|
|
if (dp->d_name[1] == '.' && dp->d_name[2] == '\0') |
456 |
|
|
continue; |
457 |
|
|
} |
458 |
|
|
if (nodump) { |
459 |
|
|
ip = getino(dp->d_ino, &mode); |
460 |
|
|
if (TSTINO(dp->d_ino, dumpinomap)) { |
461 |
|
|
CLRINO(dp->d_ino, dumpinomap); |
462 |
|
|
*tapesize -= blockest(ip); |
463 |
|
|
} |
464 |
|
|
/* |
465 |
|
|
* Add back to dumpdirmap and remove from usedinomap |
466 |
|
|
* to propagate nodump. |
467 |
|
|
*/ |
468 |
|
|
if (mode == IFDIR) { |
469 |
|
|
SETINO(dp->d_ino, dumpdirmap); |
470 |
|
|
CLRINO(dp->d_ino, usedinomap); |
471 |
|
|
ret |= HASSUBDIRS; |
472 |
|
|
} |
473 |
|
|
} else { |
474 |
|
|
if (TSTINO(dp->d_ino, dumpinomap)) { |
475 |
|
|
ret |= HASDUMPEDFILE; |
476 |
|
|
if (ret & HASSUBDIRS) |
477 |
|
|
break; |
478 |
|
|
} |
479 |
|
|
if (TSTINO(dp->d_ino, dumpdirmap)) { |
480 |
|
|
ret |= HASSUBDIRS; |
481 |
|
|
if (ret & HASDUMPEDFILE) |
482 |
|
|
break; |
483 |
|
|
} |
484 |
|
|
} |
485 |
|
|
} |
486 |
|
|
return (ret); |
487 |
|
|
} |
488 |
|
|
|
489 |
|
|
/* |
490 |
|
|
* Dump passes 3 and 4. |
491 |
|
|
* |
492 |
|
|
* Dump the contents of an inode to tape. |
493 |
|
|
*/ |
494 |
|
|
void |
495 |
|
|
dumpino(union dinode *dp, ino_t ino) |
496 |
|
|
{ |
497 |
|
|
int ind_level, cnt; |
498 |
|
|
off_t size; |
499 |
|
|
char buf[TP_BSIZE]; |
500 |
|
|
|
501 |
|
|
if (newtape) { |
502 |
|
|
newtape = 0; |
503 |
|
|
dumpmap(dumpinomap, TS_BITS, ino); |
504 |
|
|
} |
505 |
|
|
CLRINO(ino, dumpinomap); |
506 |
|
|
if (sblock->fs_magic == FS_UFS1_MAGIC) { |
507 |
|
|
spcl.c_mode = dp->dp1.di_mode; |
508 |
|
|
spcl.c_size = dp->dp1.di_size; |
509 |
|
|
spcl.c_old_atime = (time_t)dp->dp1.di_atime; |
510 |
|
|
spcl.c_atime = dp->dp1.di_atime; |
511 |
|
|
spcl.c_atimensec = dp->dp1.di_atimensec; |
512 |
|
|
spcl.c_old_mtime = (time_t)dp->dp1.di_mtime; |
513 |
|
|
spcl.c_mtime = dp->dp1.di_mtime; |
514 |
|
|
spcl.c_mtimensec = dp->dp1.di_mtimensec; |
515 |
|
|
spcl.c_birthtime = 0; |
516 |
|
|
spcl.c_birthtimensec = 0; |
517 |
|
|
spcl.c_rdev = dp->dp1.di_rdev; |
518 |
|
|
spcl.c_file_flags = dp->dp1.di_flags; |
519 |
|
|
spcl.c_uid = dp->dp1.di_uid; |
520 |
|
|
spcl.c_gid = dp->dp1.di_gid; |
521 |
|
|
} else { |
522 |
|
|
spcl.c_mode = dp->dp2.di_mode; |
523 |
|
|
spcl.c_size = dp->dp2.di_size; |
524 |
|
|
spcl.c_atime = dp->dp2.di_atime; |
525 |
|
|
spcl.c_atimensec = dp->dp2.di_atimensec; |
526 |
|
|
spcl.c_mtime = dp->dp2.di_mtime; |
527 |
|
|
spcl.c_mtimensec = dp->dp2.di_mtimensec; |
528 |
|
|
spcl.c_birthtime = dp->dp2.di_birthtime; |
529 |
|
|
spcl.c_birthtimensec = dp->dp2.di_birthnsec; |
530 |
|
|
spcl.c_rdev = dp->dp2.di_rdev; |
531 |
|
|
spcl.c_file_flags = dp->dp2.di_flags; |
532 |
|
|
spcl.c_uid = dp->dp2.di_uid; |
533 |
|
|
spcl.c_gid = dp->dp2.di_gid; |
534 |
|
|
} |
535 |
|
|
spcl.c_type = TS_INODE; |
536 |
|
|
spcl.c_count = 0; |
537 |
|
|
switch (DIP(dp, di_mode) & S_IFMT) { |
538 |
|
|
|
539 |
|
|
case 0: |
540 |
|
|
/* |
541 |
|
|
* Freed inode. |
542 |
|
|
*/ |
543 |
|
|
return; |
544 |
|
|
|
545 |
|
|
case IFLNK: |
546 |
|
|
/* |
547 |
|
|
* Check for short symbolic link. |
548 |
|
|
*/ |
549 |
|
|
if (DIP(dp, di_size) > 0 && |
550 |
|
|
#ifdef FS_44INODEFMT |
551 |
|
|
(DIP(dp, di_size) < sblock->fs_maxsymlinklen || |
552 |
|
|
(sblock->fs_maxsymlinklen == 0 && |
553 |
|
|
DIP(dp, di_blocks) == 0))) { |
554 |
|
|
#else |
555 |
|
|
DIP(dp, di_blocks) == 0) { |
556 |
|
|
#endif |
557 |
|
|
void *shortlink; |
558 |
|
|
|
559 |
|
|
spcl.c_addr[0] = 1; |
560 |
|
|
spcl.c_count = 1; |
561 |
|
|
writeheader(ino); |
562 |
|
|
if (sblock->fs_magic == FS_UFS1_MAGIC) |
563 |
|
|
shortlink = dp->dp1.di_shortlink; |
564 |
|
|
else |
565 |
|
|
shortlink = dp->dp2.di_shortlink; |
566 |
|
|
memcpy(buf, shortlink, DIP(dp, di_size)); |
567 |
|
|
buf[DIP(dp, di_size)] = '\0'; |
568 |
|
|
writerec(buf, 0); |
569 |
|
|
return; |
570 |
|
|
} |
571 |
|
|
/* FALLTHROUGH */ |
572 |
|
|
|
573 |
|
|
case IFDIR: |
574 |
|
|
case IFREG: |
575 |
|
|
if (DIP(dp, di_size) > 0) |
576 |
|
|
break; |
577 |
|
|
/* FALLTHROUGH */ |
578 |
|
|
|
579 |
|
|
case IFIFO: |
580 |
|
|
case IFSOCK: |
581 |
|
|
case IFCHR: |
582 |
|
|
case IFBLK: |
583 |
|
|
writeheader(ino); |
584 |
|
|
return; |
585 |
|
|
|
586 |
|
|
default: |
587 |
|
|
msg("Warning: undefined file type 0%o\n", |
588 |
|
|
DIP(dp, di_mode) & IFMT); |
589 |
|
|
return; |
590 |
|
|
} |
591 |
|
|
if (DIP(dp, di_size) > NDADDR * sblock->fs_bsize) |
592 |
|
|
cnt = NDADDR * sblock->fs_frag; |
593 |
|
|
else |
594 |
|
|
cnt = howmany(DIP(dp, di_size), sblock->fs_fsize); |
595 |
|
|
if (sblock->fs_magic == FS_UFS1_MAGIC) |
596 |
|
|
ufs1_blksout(&dp->dp1.di_db[0], cnt, ino); |
597 |
|
|
else |
598 |
|
|
ufs2_blksout(&dp->dp2.di_db[0], cnt, ino); |
599 |
|
|
if ((size = DIP(dp, di_size) - NDADDR * sblock->fs_bsize) <= 0) |
600 |
|
|
return; |
601 |
|
|
for (ind_level = 0; ind_level < NIADDR; ind_level++) { |
602 |
|
|
dmpindir(ino, DIP(dp, di_ib[ind_level]), ind_level, &size); |
603 |
|
|
if (size <= 0) |
604 |
|
|
return; |
605 |
|
|
} |
606 |
|
|
} |
607 |
|
|
|
608 |
|
|
/* |
609 |
|
|
* Read indirect blocks, and pass the data blocks to be dumped. |
610 |
|
|
*/ |
611 |
|
|
static void |
612 |
|
|
dmpindir(ino_t ino, daddr_t blk, int ind_level, off_t *size) |
613 |
|
|
{ |
614 |
|
|
int i, cnt; |
615 |
|
|
char idblk[MAXBSIZE]; |
616 |
|
|
|
617 |
|
|
if (blk != 0) |
618 |
|
|
bread(fsbtodb(sblock, blk), idblk, (int) sblock->fs_bsize); |
619 |
|
|
else |
620 |
|
|
memset(idblk, 0, (int)sblock->fs_bsize); |
621 |
|
|
if (ind_level <= 0) { |
622 |
|
|
if (*size < NINDIR(sblock) * sblock->fs_bsize) |
623 |
|
|
cnt = howmany(*size, sblock->fs_fsize); |
624 |
|
|
else |
625 |
|
|
cnt = NINDIR(sblock) * sblock->fs_frag; |
626 |
|
|
*size -= NINDIR(sblock) * sblock->fs_bsize; |
627 |
|
|
if (sblock->fs_magic == FS_UFS1_MAGIC) |
628 |
|
|
ufs1_blksout((int32_t *)idblk, cnt, ino); |
629 |
|
|
else |
630 |
|
|
ufs2_blksout((int64_t *)idblk, cnt, ino); |
631 |
|
|
return; |
632 |
|
|
} |
633 |
|
|
ind_level--; |
634 |
|
|
for (i = 0; i < NINDIR(sblock); i++) { |
635 |
|
|
if (sblock->fs_magic == FS_UFS1_MAGIC) |
636 |
|
|
dmpindir(ino, ((int32_t *)idblk)[i], ind_level, |
637 |
|
|
size); |
638 |
|
|
else |
639 |
|
|
dmpindir(ino, ((int64_t *)idblk)[i], ind_level, |
640 |
|
|
size); |
641 |
|
|
if (*size <= 0) |
642 |
|
|
return; |
643 |
|
|
} |
644 |
|
|
} |
645 |
|
|
|
646 |
|
|
/* |
647 |
|
|
* Collect up the data into tape record sized buffers and output them. |
648 |
|
|
*/ |
649 |
|
|
void |
650 |
|
|
ufs1_blksout(int32_t *blkp, int frags, ino_t ino) |
651 |
|
|
{ |
652 |
|
|
int32_t *bp; |
653 |
|
|
int i, j, count, blks, tbperdb; |
654 |
|
|
|
655 |
|
|
blks = howmany(frags * sblock->fs_fsize, TP_BSIZE); |
656 |
|
|
tbperdb = sblock->fs_bsize >> tp_bshift; |
657 |
|
|
for (i = 0; i < blks; i += TP_NINDIR) { |
658 |
|
|
if (i + TP_NINDIR > blks) |
659 |
|
|
count = blks; |
660 |
|
|
else |
661 |
|
|
count = i + TP_NINDIR; |
662 |
|
|
for (j = i; j < count; j++) |
663 |
|
|
if (blkp[j / tbperdb] != 0) |
664 |
|
|
spcl.c_addr[j - i] = 1; |
665 |
|
|
else |
666 |
|
|
spcl.c_addr[j - i] = 0; |
667 |
|
|
spcl.c_count = count - i; |
668 |
|
|
writeheader(ino); |
669 |
|
|
bp = &blkp[i / tbperdb]; |
670 |
|
|
for (j = i; j < count; j += tbperdb, bp++) |
671 |
|
|
if (*bp != 0) { |
672 |
|
|
if (j + tbperdb <= count) |
673 |
|
|
dumpblock(*bp, (int)sblock->fs_bsize); |
674 |
|
|
else |
675 |
|
|
dumpblock(*bp, (count - j) * TP_BSIZE); |
676 |
|
|
} |
677 |
|
|
spcl.c_type = TS_ADDR; |
678 |
|
|
} |
679 |
|
|
} |
680 |
|
|
|
681 |
|
|
/* |
682 |
|
|
* Collect up the data into tape record sized buffers and output them. |
683 |
|
|
*/ |
684 |
|
|
void |
685 |
|
|
ufs2_blksout(daddr_t *blkp, int frags, ino_t ino) |
686 |
|
|
{ |
687 |
|
|
daddr_t *bp; |
688 |
|
|
int i, j, count, blks, tbperdb; |
689 |
|
|
|
690 |
|
|
blks = howmany(frags * sblock->fs_fsize, TP_BSIZE); |
691 |
|
|
tbperdb = sblock->fs_bsize >> tp_bshift; |
692 |
|
|
for (i = 0; i < blks; i += TP_NINDIR) { |
693 |
|
|
if (i + TP_NINDIR > blks) |
694 |
|
|
count = blks; |
695 |
|
|
else |
696 |
|
|
count = i + TP_NINDIR; |
697 |
|
|
for (j = i; j < count; j++) |
698 |
|
|
if (blkp[j / tbperdb] != 0) |
699 |
|
|
spcl.c_addr[j - i] = 1; |
700 |
|
|
else |
701 |
|
|
spcl.c_addr[j - i] = 0; |
702 |
|
|
spcl.c_count = count - i; |
703 |
|
|
writeheader(ino); |
704 |
|
|
bp = &blkp[i / tbperdb]; |
705 |
|
|
for (j = i; j < count; j += tbperdb, bp++) |
706 |
|
|
if (*bp != 0) { |
707 |
|
|
if (j + tbperdb <= count) |
708 |
|
|
dumpblock(*bp, (int)sblock->fs_bsize); |
709 |
|
|
else |
710 |
|
|
dumpblock(*bp, (count - j) * TP_BSIZE); |
711 |
|
|
} |
712 |
|
|
spcl.c_type = TS_ADDR; |
713 |
|
|
} |
714 |
|
|
} |
715 |
|
|
|
716 |
|
|
/* |
717 |
|
|
* Dump a map to the tape. |
718 |
|
|
*/ |
719 |
|
|
void |
720 |
|
|
dumpmap(map, type, ino) |
721 |
|
|
char *map; |
722 |
|
|
int type; |
723 |
|
|
ino_t ino; |
724 |
|
|
{ |
725 |
|
|
int i; |
726 |
|
|
char *cp; |
727 |
|
|
|
728 |
|
|
spcl.c_type = type; |
729 |
|
|
spcl.c_count = howmany(mapsize * sizeof(char), TP_BSIZE); |
730 |
|
|
writeheader(ino); |
731 |
|
|
for (i = 0, cp = map; i < spcl.c_count; i++, cp += TP_BSIZE) |
732 |
|
|
writerec(cp, 0); |
733 |
|
|
} |
734 |
|
|
|
735 |
|
|
/* |
736 |
|
|
* Write a header record to the dump tape. |
737 |
|
|
*/ |
738 |
|
|
void |
739 |
|
|
writeheader(ino) |
740 |
|
|
ino_t ino; |
741 |
|
|
{ |
742 |
|
|
int32_t sum, cnt, *lp; |
743 |
|
|
|
744 |
|
|
spcl.c_inumber = ino; |
745 |
|
|
if (sblock->fs_magic == FS_UFS2_MAGIC) { |
746 |
|
|
spcl.c_magic = FS_UFS2_MAGIC; |
747 |
|
|
} else { |
748 |
|
|
spcl.c_magic = NFS_MAGIC; |
749 |
|
|
spcl.c_old_date = (int32_t)spcl.c_date; |
750 |
|
|
spcl.c_old_ddate = (int32_t)spcl.c_ddate; |
751 |
|
|
spcl.c_old_tapea = (int32_t)spcl.c_tapea; |
752 |
|
|
spcl.c_old_firstrec = (int32_t)spcl.c_firstrec; |
753 |
|
|
} |
754 |
|
|
spcl.c_checksum = 0; |
755 |
|
|
lp = (int32_t *)&spcl; |
756 |
|
|
sum = 0; |
757 |
|
|
cnt = sizeof(union u_spcl) / (4 * sizeof(int32_t)); |
758 |
|
|
while (--cnt >= 0) { |
759 |
|
|
sum += *lp++; |
760 |
|
|
sum += *lp++; |
761 |
|
|
sum += *lp++; |
762 |
|
|
sum += *lp++; |
763 |
|
|
} |
764 |
|
|
spcl.c_checksum = CHECKSUM - sum; |
765 |
|
|
writerec((char *)&spcl, 1); |
766 |
|
|
} |
767 |
|
|
|
768 |
|
|
union dinode * |
769 |
|
|
getino(ino_t inum, int *modep) |
770 |
|
|
{ |
771 |
|
|
static ino_t minino, maxino; |
772 |
|
|
static void *inoblock; |
773 |
|
|
struct ufs1_dinode *dp1; |
774 |
|
|
struct ufs2_dinode *dp2; |
775 |
|
|
|
776 |
|
|
if (inoblock == NULL && (inoblock = malloc(sblock->fs_bsize)) == NULL) |
777 |
|
|
quit("cannot allocate inode memory.\n"); |
778 |
|
|
curino = inum; |
779 |
|
|
if (inum >= minino && inum < maxino) |
780 |
|
|
goto gotit; |
781 |
|
|
bread(fsbtodb(sblock, ino_to_fsba(sblock, inum)), inoblock, |
782 |
|
|
(int)sblock->fs_bsize); |
783 |
|
|
minino = inum - (inum % INOPB(sblock)); |
784 |
|
|
maxino = minino + INOPB(sblock); |
785 |
|
|
gotit: |
786 |
|
|
if (sblock->fs_magic == FS_UFS1_MAGIC) { |
787 |
|
|
dp1 = &((struct ufs1_dinode *)inoblock)[inum - minino]; |
788 |
|
|
*modep = (dp1->di_mode & IFMT); |
789 |
|
|
return ((union dinode *)dp1); |
790 |
|
|
} |
791 |
|
|
dp2 = &((struct ufs2_dinode *)inoblock)[inum - minino]; |
792 |
|
|
*modep = (dp2->di_mode & IFMT); |
793 |
|
|
return ((union dinode *)dp2); |
794 |
|
|
} |
795 |
|
|
|
796 |
|
|
/* |
797 |
|
|
* Read a chunk of data from the disk. |
798 |
|
|
* Try to recover from hard errors by reading in sector sized pieces. |
799 |
|
|
* Error recovery is attempted at most BREADEMAX times before seeking |
800 |
|
|
* consent from the operator to continue. |
801 |
|
|
*/ |
802 |
|
|
int breaderrors = 0; |
803 |
|
|
#define BREADEMAX 32 |
804 |
|
|
|
805 |
|
|
void |
806 |
|
|
bread(daddr_t blkno, char *buf, int size) |
807 |
|
|
{ |
808 |
|
|
static char *mybuf = NULL; |
809 |
|
|
char *mybufp, *bufp, *np; |
810 |
|
|
static size_t mybufsz = 0; |
811 |
|
|
off_t offset; |
812 |
|
|
int cnt, i; |
813 |
|
|
u_int64_t secno, seccount; |
814 |
|
|
u_int32_t secoff, secsize = lab.d_secsize; |
815 |
|
|
|
816 |
|
|
/* |
817 |
|
|
* We must read an integral number of sectors large enough to contain |
818 |
|
|
* all the requested data. The read must begin at a sector. |
819 |
|
|
*/ |
820 |
|
|
if (DL_BLKOFFSET(&lab, blkno) == 0 && size % secsize == 0) { |
821 |
|
|
secno = DL_BLKTOSEC(&lab, blkno); |
822 |
|
|
secoff = 0; |
823 |
|
|
seccount = size / secsize; |
824 |
|
|
bufp = buf; |
825 |
|
|
} else { |
826 |
|
|
secno = DL_BLKTOSEC(&lab, blkno); |
827 |
|
|
secoff = DL_BLKOFFSET(&lab, blkno); |
828 |
|
|
seccount = DL_BLKTOSEC(&lab, (size + secoff) / DEV_BSIZE); |
829 |
|
|
if (seccount * secsize < (size + secoff)) |
830 |
|
|
seccount++; |
831 |
|
|
if (mybufsz < seccount * secsize) { |
832 |
|
|
np = reallocarray(mybuf, seccount, secsize); |
833 |
|
|
if (np == NULL) { |
834 |
|
|
msg("No memory to read %llu %u-byte sectors", |
835 |
|
|
seccount, secsize); |
836 |
|
|
dumpabort(0); |
837 |
|
|
} |
838 |
|
|
mybufsz = seccount * secsize; |
839 |
|
|
mybuf = np; |
840 |
|
|
} |
841 |
|
|
bufp = mybuf; |
842 |
|
|
} |
843 |
|
|
|
844 |
|
|
offset = secno * secsize; |
845 |
|
|
|
846 |
|
|
loop: |
847 |
|
|
if ((cnt = pread(diskfd, bufp, seccount * secsize, offset)) == |
848 |
|
|
seccount * secsize) |
849 |
|
|
goto done; |
850 |
|
|
if (blkno + (size / DEV_BSIZE) > |
851 |
|
|
fsbtodb(sblock, sblock->fs_ffs1_size)) { |
852 |
|
|
/* |
853 |
|
|
* Trying to read the final fragment. |
854 |
|
|
* |
855 |
|
|
* NB - dump only works in TP_BSIZE blocks, hence |
856 |
|
|
* rounds `DEV_BSIZE' fragments up to TP_BSIZE pieces. |
857 |
|
|
* It should be smarter about not actually trying to |
858 |
|
|
* read more than it can get, but for the time being |
859 |
|
|
* we punt and scale back the read only when it gets |
860 |
|
|
* us into trouble. (mkm 9/25/83) |
861 |
|
|
*/ |
862 |
|
|
size -= secsize; |
863 |
|
|
seccount--; |
864 |
|
|
goto loop; |
865 |
|
|
} |
866 |
|
|
if (cnt == -1) |
867 |
|
|
msg("read error from %s: %s: [block %lld]: count=%d\n", |
868 |
|
|
disk, strerror(errno), (long long)blkno, size); |
869 |
|
|
else |
870 |
|
|
msg("short read error from %s: [block %lld]: count=%d, " |
871 |
|
|
"got=%d\n", disk, (long long)blkno, size, cnt); |
872 |
|
|
if (++breaderrors > BREADEMAX) { |
873 |
|
|
msg("More than %d block read errors from %s\n", |
874 |
|
|
BREADEMAX, disk); |
875 |
|
|
broadcast("DUMP IS AILING!\n"); |
876 |
|
|
msg("This is an unrecoverable error.\n"); |
877 |
|
|
if (!query("Do you want to attempt to continue?")){ |
878 |
|
|
dumpabort(0); |
879 |
|
|
/*NOTREACHED*/ |
880 |
|
|
} else |
881 |
|
|
breaderrors = 0; |
882 |
|
|
} |
883 |
|
|
/* |
884 |
|
|
* Zero buffer, then try to read each sector of buffer separately. |
885 |
|
|
*/ |
886 |
|
|
if (bufp == mybuf) |
887 |
|
|
memset(bufp, 0, mybufsz); |
888 |
|
|
else |
889 |
|
|
memset(bufp, 0, size); |
890 |
|
|
for (i = 0, mybufp = bufp; i < size; i += secsize, mybufp += secsize) { |
891 |
|
|
if ((cnt = pread(diskfd, mybufp, secsize, offset + i)) == |
892 |
|
|
secsize) |
893 |
|
|
continue; |
894 |
|
|
if (cnt == -1) { |
895 |
|
|
msg("read error from %s: %s: [block %lld]: " |
896 |
|
|
"count=%u\n", disk, strerror(errno), |
897 |
|
|
(long long)(offset + i) / DEV_BSIZE, secsize); |
898 |
|
|
continue; |
899 |
|
|
} |
900 |
|
|
msg("short read error from %s: [block %lld]: count=%u, " |
901 |
|
|
"got=%d\n", disk, (long long)(offset + i) / DEV_BSIZE, |
902 |
|
|
secsize, cnt); |
903 |
|
|
} |
904 |
|
|
|
905 |
|
|
done: |
906 |
|
|
/* If necessary, copy out data that was read. */ |
907 |
|
|
if (bufp == mybuf) |
908 |
|
|
memcpy(buf, bufp + secoff, size); |
909 |
|
|
} |