]> git.ipfire.org Git - thirdparty/e2fsprogs.git/blob - misc/create_inode.c
misc: copy extended attributes in populate_fs
[thirdparty/e2fsprogs.git] / misc / create_inode.c
1 /*
2 * create_inode.c --- create an inode
3 *
4 * Copyright (C) 2014 Robert Yang <liezhi.yang@windriver.com>
5 *
6 * %Begin-Header%
7 * This file may be redistributed under the terms of the GNU library
8 * General Public License, version 2.
9 * %End-Header%
10 */
11
12 #include <time.h>
13 #include <unistd.h>
14 #include <limits.h> /* for PATH_MAX */
15 #ifdef HAVE_ATTR_XATTR_H
16 #include <attr/xattr.h>
17 #endif
18
19 #include "create_inode.h"
20
21 #if __STDC_VERSION__ < 199901L
22 # if __GNUC__ >= 2
23 # define __func__ __FUNCTION__
24 # else
25 # define __func__ "<unknown>"
26 # endif
27 #endif
28
29 /* 64KiB is the minimium blksize to best minimize system call overhead. */
30 #ifndef IO_BUFSIZE
31 #define IO_BUFSIZE 64*1024
32 #endif
33
34 /* Block size for `st_blocks' */
35 #ifndef S_BLKSIZE
36 #define S_BLKSIZE 512
37 #endif
38
39 /* Link an inode number to a directory */
40 static errcode_t add_link(ext2_filsys fs, ext2_ino_t parent_ino,
41 ext2_ino_t ino, const char *name)
42 {
43 struct ext2_inode inode;
44 errcode_t retval;
45
46 retval = ext2fs_read_inode(fs, ino, &inode);
47 if (retval) {
48 com_err(__func__, retval, "while reading inode %u", ino);
49 return retval;
50 }
51
52 retval = ext2fs_link(fs, parent_ino, name, ino, inode.i_flags);
53 if (retval == EXT2_ET_DIR_NO_SPACE) {
54 retval = ext2fs_expand_dir(fs, parent_ino);
55 if (retval) {
56 com_err(__func__, retval, "while expanding directory");
57 return retval;
58 }
59 retval = ext2fs_link(fs, parent_ino, name, ino, inode.i_flags);
60 }
61 if (retval) {
62 com_err(__func__, retval, "while linking %s", name);
63 return retval;
64 }
65
66 inode.i_links_count++;
67
68 retval = ext2fs_write_inode(fs, ino, &inode);
69 if (retval)
70 com_err(__func__, retval, "while writing inode %u", ino);
71
72 return retval;
73 }
74
75 /* Fill the uid, gid, mode and time for the inode */
76 static void fill_inode(struct ext2_inode *inode, struct stat *st)
77 {
78 if (st != NULL) {
79 inode->i_uid = st->st_uid;
80 inode->i_gid = st->st_gid;
81 inode->i_mode |= st->st_mode;
82 inode->i_atime = st->st_atime;
83 inode->i_mtime = st->st_mtime;
84 inode->i_ctime = st->st_ctime;
85 }
86 }
87
88 /* Set the uid, gid, mode and time for the inode */
89 static errcode_t set_inode_extra(ext2_filsys fs, ext2_ino_t cwd,
90 ext2_ino_t ino, struct stat *st)
91 {
92 errcode_t retval;
93 struct ext2_inode inode;
94
95 retval = ext2fs_read_inode(fs, ino, &inode);
96 if (retval) {
97 com_err(__func__, retval, "while reading inode %u", ino);
98 return retval;
99 }
100
101 fill_inode(&inode, st);
102
103 retval = ext2fs_write_inode(fs, ino, &inode);
104 if (retval)
105 com_err(__func__, retval, "while writing inode %u", ino);
106 return retval;
107 }
108
109 static errcode_t set_inode_xattr(ext2_filsys fs, ext2_ino_t ino, const char *filename)
110 {
111 #ifdef HAVE_LLISTXATTR
112 errcode_t retval, close_retval;
113 struct ext2_inode inode;
114 struct ext2_xattr_handle *handle;
115 ssize_t size, value_size;
116 char *list;
117 int i;
118
119 size = llistxattr(filename, NULL, 0);
120 if (size == -1) {
121 com_err(__func__, errno, "llistxattr failed on %s", filename);
122 return errno;
123 } else if (size == 0) {
124 return 0;
125 }
126
127 retval = ext2fs_xattrs_open(fs, ino, &handle);
128 if (retval) {
129 if (retval == EXT2_ET_MISSING_EA_FEATURE)
130 return 0;
131 com_err(__func__, retval, "while opening inode %u", ino);
132 return retval;
133 }
134
135 retval = ext2fs_get_mem(size, &list);
136 if (retval) {
137 com_err(__func__, retval, "whilst allocating memory");
138 goto out;
139 }
140
141 size = llistxattr(filename, list, size);
142 if (size == -1) {
143 com_err(__func__, errno, "llistxattr failed on %s", filename);
144 retval = errno;
145 goto out;
146 }
147
148 for (i = 0; i < size; i += strlen(&list[i]) + 1) {
149 const char *name = &list[i];
150 char *value;
151
152 value_size = getxattr(filename, name, NULL, 0);
153 if (value_size == -1) {
154 com_err(__func__, errno, "getxattr failed on %s",
155 filename);
156 retval = errno;
157 break;
158 }
159
160 retval = ext2fs_get_mem(value_size, &value);
161 if (retval) {
162 com_err(__func__, retval, "whilst allocating memory");
163 break;
164 }
165
166 value_size = getxattr(filename, name, value, value_size);
167 if (value_size == -1) {
168 ext2fs_free_mem(&value);
169 com_err(__func__, errno, "getxattr failed on %s",
170 filename);
171 retval = errno;
172 break;
173 }
174
175 retval = ext2fs_xattr_set(handle, name, value, value_size);
176 ext2fs_free_mem(&value);
177 if (retval) {
178 com_err(__func__, retval,
179 "while writing xattr %u", ino);
180 break;
181 }
182
183 }
184 out:
185 ext2fs_free_mem(&list);
186 close_retval = ext2fs_xattrs_close(&handle);
187 if (close_retval) {
188 com_err(__func__, retval, "while closing inode %u", ino);
189 retval = retval ? retval : close_retval;
190 }
191 return retval;
192 #else /* HAVE_LLISTXATTR */
193 return 0;
194 #endif /* HAVE_LLISTXATTR */
195 }
196
197 /* Make a special files (block and character devices), fifo's, and sockets */
198 errcode_t do_mknod_internal(ext2_filsys fs, ext2_ino_t cwd, const char *name,
199 struct stat *st)
200 {
201 ext2_ino_t ino;
202 errcode_t retval;
203 struct ext2_inode inode;
204 unsigned long devmajor, devminor, mode;
205 int filetype;
206
207 switch(st->st_mode & S_IFMT) {
208 case S_IFCHR:
209 mode = LINUX_S_IFCHR;
210 filetype = EXT2_FT_CHRDEV;
211 break;
212 case S_IFBLK:
213 mode = LINUX_S_IFBLK;
214 filetype = EXT2_FT_BLKDEV;
215 break;
216 case S_IFIFO:
217 mode = LINUX_S_IFIFO;
218 filetype = EXT2_FT_FIFO;
219 break;
220 case S_IFSOCK:
221 mode = LINUX_S_IFSOCK;
222 filetype = EXT2_FT_SOCK;
223 break;
224 default:
225 return EXT2_ET_INVALID_ARGUMENT;
226 }
227
228 if (!(fs->flags & EXT2_FLAG_RW)) {
229 com_err(__func__, 0, "Filesystem opened read/only");
230 return EROFS;
231 }
232 retval = ext2fs_new_inode(fs, cwd, 010755, 0, &ino);
233 if (retval) {
234 com_err(__func__, retval, 0);
235 return retval;
236 }
237
238 #ifdef DEBUGFS
239 printf("Allocated inode: %u\n", ino);
240 #endif
241 retval = ext2fs_link(fs, cwd, name, ino, filetype);
242 if (retval == EXT2_ET_DIR_NO_SPACE) {
243 retval = ext2fs_expand_dir(fs, cwd);
244 if (retval) {
245 com_err(__func__, retval, "while expanding directory");
246 return retval;
247 }
248 retval = ext2fs_link(fs, cwd, name, ino, filetype);
249 }
250 if (retval) {
251 com_err(name, retval, 0);
252 return retval;
253 }
254 if (ext2fs_test_inode_bitmap2(fs->inode_map, ino))
255 com_err(__func__, 0, "Warning: inode already set");
256 ext2fs_inode_alloc_stats2(fs, ino, +1, 0);
257 memset(&inode, 0, sizeof(inode));
258 inode.i_mode = mode;
259 inode.i_atime = inode.i_ctime = inode.i_mtime =
260 fs->now ? fs->now : time(0);
261
262 if (filetype != S_IFIFO) {
263 devmajor = major(st->st_rdev);
264 devminor = minor(st->st_rdev);
265
266 if ((devmajor < 256) && (devminor < 256)) {
267 inode.i_block[0] = devmajor * 256 + devminor;
268 inode.i_block[1] = 0;
269 } else {
270 inode.i_block[0] = 0;
271 inode.i_block[1] = (devminor & 0xff) | (devmajor << 8) |
272 ((devminor & ~0xff) << 12);
273 }
274 }
275 inode.i_links_count = 1;
276
277 retval = ext2fs_write_new_inode(fs, ino, &inode);
278 if (retval)
279 com_err(__func__, retval, "while creating inode %u", ino);
280
281 return retval;
282 }
283
284 /* Make a symlink name -> target */
285 errcode_t do_symlink_internal(ext2_filsys fs, ext2_ino_t cwd, const char *name,
286 char *target, ext2_ino_t root)
287 {
288 char *cp;
289 ext2_ino_t parent_ino;
290 errcode_t retval;
291 struct ext2_inode inode;
292 struct stat st;
293
294 cp = strrchr(name, '/');
295 if (cp) {
296 *cp = 0;
297 retval = ext2fs_namei(fs, root, cwd, name, &parent_ino);
298 if (retval) {
299 com_err(name, retval, 0);
300 return retval;
301 }
302 name = cp+1;
303 } else
304 parent_ino = cwd;
305
306 try_again:
307 retval = ext2fs_symlink(fs, parent_ino, 0, name, target);
308 if (retval == EXT2_ET_DIR_NO_SPACE) {
309 retval = ext2fs_expand_dir(fs, parent_ino);
310 if (retval) {
311 com_err("do_symlink_internal", retval,
312 "while expanding directory");
313 return retval;
314 }
315 goto try_again;
316 }
317 if (retval)
318 com_err("ext2fs_symlink", retval, 0);
319 return retval;
320 }
321
322 /* Make a directory in the fs */
323 errcode_t do_mkdir_internal(ext2_filsys fs, ext2_ino_t cwd, const char *name,
324 struct stat *st, ext2_ino_t root)
325 {
326 char *cp;
327 ext2_ino_t parent_ino, ino;
328 errcode_t retval;
329 struct ext2_inode inode;
330
331
332 cp = strrchr(name, '/');
333 if (cp) {
334 *cp = 0;
335 retval = ext2fs_namei(fs, root, cwd, name, &parent_ino);
336 if (retval) {
337 com_err(name, retval, 0);
338 return retval;
339 }
340 name = cp+1;
341 } else
342 parent_ino = cwd;
343
344 try_again:
345 retval = ext2fs_mkdir(fs, parent_ino, 0, name);
346 if (retval == EXT2_ET_DIR_NO_SPACE) {
347 retval = ext2fs_expand_dir(fs, parent_ino);
348 if (retval) {
349 com_err(__func__, retval, "while expanding directory");
350 return retval;
351 }
352 goto try_again;
353 }
354 if (retval)
355 com_err("ext2fs_mkdir", retval, 0);
356 return retval;
357 }
358
359 static errcode_t copy_file(ext2_filsys fs, int fd, ext2_ino_t newfile,
360 int bufsize, int make_holes)
361 {
362 ext2_file_t e2_file;
363 errcode_t retval, close_ret;
364 int got;
365 unsigned int written;
366 char *buf;
367 char *ptr;
368 char *zero_buf;
369 int cmp;
370
371 retval = ext2fs_file_open(fs, newfile,
372 EXT2_FILE_WRITE, &e2_file);
373 if (retval)
374 return retval;
375
376 retval = ext2fs_get_mem(bufsize, &buf);
377 if (retval) {
378 com_err("copy_file", retval, "can't allocate buffer\n");
379 goto out_close;
380 }
381
382 /* This is used for checking whether the whole block is zero */
383 retval = ext2fs_get_memzero(bufsize, &zero_buf);
384 if (retval) {
385 com_err("copy_file", retval, "can't allocate zero buffer\n");
386 goto out_free_buf;
387 }
388
389 while (1) {
390 got = read(fd, buf, bufsize);
391 if (got == 0)
392 break;
393 if (got < 0) {
394 retval = errno;
395 goto fail;
396 }
397 ptr = buf;
398
399 /* Sparse copy */
400 if (make_holes) {
401 /* Check whether all is zero */
402 cmp = memcmp(ptr, zero_buf, got);
403 if (cmp == 0) {
404 /* The whole block is zero, make a hole */
405 retval = ext2fs_file_lseek(e2_file, got,
406 EXT2_SEEK_CUR,
407 NULL);
408 if (retval)
409 goto fail;
410 got = 0;
411 }
412 }
413
414 /* Normal copy */
415 while (got > 0) {
416 retval = ext2fs_file_write(e2_file, ptr,
417 got, &written);
418 if (retval)
419 goto fail;
420
421 got -= written;
422 ptr += written;
423 }
424 }
425
426 fail:
427 ext2fs_free_mem(&zero_buf);
428 out_free_buf:
429 ext2fs_free_mem(&buf);
430 out_close:
431 close_ret = ext2fs_file_close(e2_file);
432 if (retval == 0)
433 retval = close_ret;
434 return retval;
435 }
436
437 static int is_hardlink(struct hdlinks_s *hdlinks, dev_t dev, ino_t ino)
438 {
439 int i;
440
441 for (i = 0; i < hdlinks->count; i++) {
442 if (hdlinks->hdl[i].src_dev == dev &&
443 hdlinks->hdl[i].src_ino == ino)
444 return i;
445 }
446 return -1;
447 }
448
449 /* Copy the native file to the fs */
450 errcode_t do_write_internal(ext2_filsys fs, ext2_ino_t cwd, const char *src,
451 const char *dest, ext2_ino_t root)
452 {
453 int fd;
454 struct stat statbuf;
455 ext2_ino_t newfile;
456 errcode_t retval;
457 struct ext2_inode inode;
458 int bufsize = IO_BUFSIZE;
459 int make_holes = 0;
460
461 fd = ext2fs_open_file(src, O_RDONLY, 0);
462 if (fd < 0) {
463 com_err(src, errno, 0);
464 return errno;
465 }
466 if (fstat(fd, &statbuf) < 0) {
467 com_err(src, errno, 0);
468 close(fd);
469 return errno;
470 }
471
472 retval = ext2fs_namei(fs, root, cwd, dest, &newfile);
473 if (retval == 0) {
474 close(fd);
475 return EXT2_ET_FILE_EXISTS;
476 }
477
478 retval = ext2fs_new_inode(fs, cwd, 010755, 0, &newfile);
479 if (retval) {
480 com_err(__func__, retval, 0);
481 close(fd);
482 return retval;
483 }
484 #ifdef DEBUGFS
485 printf("Allocated inode: %u\n", newfile);
486 #endif
487 retval = ext2fs_link(fs, cwd, dest, newfile,
488 EXT2_FT_REG_FILE);
489 if (retval == EXT2_ET_DIR_NO_SPACE) {
490 retval = ext2fs_expand_dir(fs, cwd);
491 if (retval) {
492 com_err(__func__, retval, "while expanding directory");
493 close(fd);
494 return retval;
495 }
496 retval = ext2fs_link(fs, cwd, dest, newfile,
497 EXT2_FT_REG_FILE);
498 }
499 if (retval) {
500 com_err(dest, retval, 0);
501 close(fd);
502 return errno;
503 }
504 if (ext2fs_test_inode_bitmap2(fs->inode_map, newfile))
505 com_err(__func__, 0, "Warning: inode already set");
506 ext2fs_inode_alloc_stats2(fs, newfile, +1, 0);
507 memset(&inode, 0, sizeof(inode));
508 inode.i_mode = (statbuf.st_mode & ~LINUX_S_IFMT) | LINUX_S_IFREG;
509 inode.i_atime = inode.i_ctime = inode.i_mtime =
510 fs->now ? fs->now : time(0);
511 inode.i_links_count = 1;
512 retval = ext2fs_inode_size_set(fs, &inode, statbuf.st_size);
513 if (retval) {
514 com_err(dest, retval, 0);
515 close(fd);
516 return retval;
517 }
518 if (EXT2_HAS_INCOMPAT_FEATURE(fs->super,
519 EXT4_FEATURE_INCOMPAT_INLINE_DATA)) {
520 inode.i_flags |= EXT4_INLINE_DATA_FL;
521 } else if (fs->super->s_feature_incompat &
522 EXT3_FEATURE_INCOMPAT_EXTENTS) {
523 int i;
524 struct ext3_extent_header *eh;
525
526 eh = (struct ext3_extent_header *) &inode.i_block[0];
527 eh->eh_depth = 0;
528 eh->eh_entries = 0;
529 eh->eh_magic = ext2fs_cpu_to_le16(EXT3_EXT_MAGIC);
530 i = (sizeof(inode.i_block) - sizeof(*eh)) /
531 sizeof(struct ext3_extent);
532 eh->eh_max = ext2fs_cpu_to_le16(i);
533 inode.i_flags |= EXT4_EXTENTS_FL;
534 }
535
536 retval = ext2fs_write_new_inode(fs, newfile, &inode);
537 if (retval) {
538 com_err(__func__, retval, "while creating inode %u", newfile);
539 close(fd);
540 return retval;
541 }
542 if (inode.i_flags & EXT4_INLINE_DATA_FL) {
543 retval = ext2fs_inline_data_init(fs, newfile);
544 if (retval) {
545 com_err("copy_file", retval, 0);
546 close(fd);
547 return retval;
548 }
549 }
550 if (LINUX_S_ISREG(inode.i_mode)) {
551 if (statbuf.st_blocks < statbuf.st_size / S_BLKSIZE) {
552 make_holes = 1;
553 /*
554 * Use I/O blocksize as buffer size when
555 * copying sparse files.
556 */
557 bufsize = statbuf.st_blksize;
558 }
559 retval = copy_file(fs, fd, newfile, bufsize, make_holes);
560 if (retval)
561 com_err("copy_file", retval, 0);
562 }
563 close(fd);
564
565 return retval;
566 }
567
568 /* Copy files from source_dir to fs */
569 static errcode_t __populate_fs(ext2_filsys fs, ext2_ino_t parent_ino,
570 const char *source_dir, ext2_ino_t root,
571 struct hdlinks_s *hdlinks)
572 {
573 const char *name;
574 DIR *dh;
575 struct dirent *dent;
576 struct stat st;
577 char ln_target[PATH_MAX];
578 unsigned int save_inode;
579 ext2_ino_t ino;
580 errcode_t retval = 0;
581 int read_cnt;
582 int hdlink;
583
584 if (chdir(source_dir) < 0) {
585 com_err(__func__, errno,
586 _("while changing working directory to \"%s\""),
587 source_dir);
588 return errno;
589 }
590
591 if (!(dh = opendir("."))) {
592 com_err(__func__, errno,
593 _("while opening directory \"%s\""), source_dir);
594 return errno;
595 }
596
597 while ((dent = readdir(dh))) {
598 if ((!strcmp(dent->d_name, ".")) ||
599 (!strcmp(dent->d_name, "..")))
600 continue;
601 if (lstat(dent->d_name, &st)) {
602 com_err(__func__, errno, _("while lstat \"%s\""),
603 dent->d_name);
604 goto out;
605 }
606 name = dent->d_name;
607
608 /* Check for hardlinks */
609 save_inode = 0;
610 if (!S_ISDIR(st.st_mode) && !S_ISLNK(st.st_mode) &&
611 st.st_nlink > 1) {
612 hdlink = is_hardlink(hdlinks, st.st_dev, st.st_ino);
613 if (hdlink >= 0) {
614 retval = add_link(fs, parent_ino,
615 hdlinks->hdl[hdlink].dst_ino,
616 name);
617 if (retval) {
618 com_err(__func__, retval,
619 "while linking %s", name);
620 goto out;
621 }
622 continue;
623 } else
624 save_inode = 1;
625 }
626
627 switch(st.st_mode & S_IFMT) {
628 case S_IFCHR:
629 case S_IFBLK:
630 case S_IFIFO:
631 case S_IFSOCK:
632 retval = do_mknod_internal(fs, parent_ino, name, &st);
633 if (retval) {
634 com_err(__func__, retval,
635 _("while creating special file "
636 "\"%s\""), name);
637 goto out;
638 }
639 break;
640 case S_IFLNK:
641 read_cnt = readlink(name, ln_target,
642 sizeof(ln_target) - 1);
643 if (read_cnt == -1) {
644 com_err(__func__, errno,
645 _("while trying to readlink \"%s\""),
646 name);
647 retval = errno;
648 goto out;
649 }
650 ln_target[read_cnt] = '\0';
651 retval = do_symlink_internal(fs, parent_ino, name,
652 ln_target, root);
653 if (retval) {
654 com_err(__func__, retval,
655 _("while writing symlink\"%s\""),
656 name);
657 goto out;
658 }
659 break;
660 case S_IFREG:
661 retval = do_write_internal(fs, parent_ino, name, name,
662 root);
663 if (retval) {
664 com_err(__func__, retval,
665 _("while writing file \"%s\""), name);
666 goto out;
667 }
668 break;
669 case S_IFDIR:
670 retval = do_mkdir_internal(fs, parent_ino, name, &st,
671 root);
672 if (retval) {
673 com_err(__func__, retval,
674 _("while making dir \"%s\""), name);
675 goto out;
676 }
677 retval = ext2fs_namei(fs, root, parent_ino,
678 name, &ino);
679 if (retval) {
680 com_err(name, retval, 0);
681 goto out;
682 }
683 /* Populate the dir recursively*/
684 retval = __populate_fs(fs, ino, name, root, hdlinks);
685 if (retval) {
686 com_err(__func__, retval,
687 _("while adding dir \"%s\""), name);
688 goto out;
689 }
690 if (chdir("..")) {
691 com_err(__func__, errno, _("during cd .."));
692 retval = errno;
693 goto out;
694 }
695 break;
696 default:
697 com_err(__func__, 0,
698 _("ignoring entry \"%s\""), name);
699 }
700
701 retval = ext2fs_namei(fs, root, parent_ino, name, &ino);
702 if (retval) {
703 com_err(name, retval, 0);
704 goto out;
705 }
706
707 retval = set_inode_extra(fs, parent_ino, ino, &st);
708 if (retval) {
709 com_err(__func__, retval,
710 _("while setting inode for \"%s\""), name);
711 goto out;
712 }
713
714 retval = set_inode_xattr(fs, ino, name);
715 if (retval) {
716 com_err(__func__, retval,
717 _("while setting xattrs for \"%s\""), name);
718 goto out;
719 }
720
721 /* Save the hardlink ino */
722 if (save_inode) {
723 /*
724 * Check whether need more memory, and we don't need
725 * free() since the lifespan will be over after the fs
726 * populated.
727 */
728 if (hdlinks->count == hdlinks->size) {
729 void *p = realloc(hdlinks->hdl,
730 (hdlinks->size + HDLINK_CNT) *
731 sizeof(struct hdlink_s));
732 if (p == NULL) {
733 com_err(name, errno,
734 _("Not enough memory"));
735 retval = EXT2_ET_NO_MEMORY;
736 goto out;
737 }
738 hdlinks->hdl = p;
739 hdlinks->size += HDLINK_CNT;
740 }
741 hdlinks->hdl[hdlinks->count].src_dev = st.st_dev;
742 hdlinks->hdl[hdlinks->count].src_ino = st.st_ino;
743 hdlinks->hdl[hdlinks->count].dst_ino = ino;
744 hdlinks->count++;
745 }
746 }
747
748 out:
749 closedir(dh);
750 return retval;
751 }
752
753 errcode_t populate_fs(ext2_filsys fs, ext2_ino_t parent_ino,
754 const char *source_dir, ext2_ino_t root)
755 {
756 struct hdlinks_s hdlinks;
757 errcode_t retval;
758
759 hdlinks.count = 0;
760 hdlinks.size = HDLINK_CNT;
761 hdlinks.hdl = realloc(NULL, hdlinks.size * sizeof(struct hdlink_s));
762 if (hdlinks.hdl == NULL) {
763 com_err(__func__, errno, "Not enough memory");
764 return errno;
765 }
766
767 retval = __populate_fs(fs, parent_ino, source_dir, root, &hdlinks);
768
769 free(hdlinks.hdl);
770 return retval;
771 }