Blame src/logic/tpax_archive_enqueue.c

5874a9
/**************************************************************/
5874a9
/*  tpax: a topological pax implementation                    */
bef28d
/*  Copyright (C) 2020--2024  SysDeer Technologies, LLC       */
5874a9
/*  Released under GPLv2 and GPLv3; see COPYING.TPAX.         */
5874a9
/**************************************************************/
3db888
3db888
#include <stdint.h>
3db888
#include <stdlib.h>
3db888
#include <string.h>
3db888
#include <unistd.h>
3db888
#include <fcntl.h>
3db888
#include <errno.h>
3db888
#include <grp.h>
3db888
#include <pwd.h>
022508
#include <sys/mman.h>
3db888
#include <sys/stat.h>
3db888
3db888
#include <tpax/tpax.h>
3db888
#include <tpax/tpax_specs.h>
3db888
#include "tpax_driver_impl.h"
022508
#include "tpax_getdents_impl.h"
e50240
#include "tpax_readlink_impl.h"
3db888
#include "tpax_tmpfile_impl.h"
3db888
#include "tpax_errinfo_impl.h"
3db888
43c39b
static char * tpax_add_prefix_item(
9f55a1
	const struct tpax_driver_ctx *  dctx,
9f55a1
	const char *                    prefix)
9f55a1
{
9f55a1
	struct tpax_driver_ctx_impl *   ictx;
9f55a1
	char **                         prefv;
9f55a1
	char **                         psrc;
9f55a1
	char **                         pdst;
9f55a1
	off_t                           elements;
9f55a1
	char *                          pitem;
9f55a1
9f55a1
	ictx = tpax_get_driver_ictx(dctx);
9f55a1
9f55a1
	for (psrc=ictx->prefixv; *psrc; psrc++)
9f55a1
		if (!strcmp(*psrc,prefix))
9f55a1
			return *psrc;
9f55a1
9f55a1
	if (ictx->prefixp == ictx->prefcap) {
9f55a1
		elements  = ictx->prefcap - ictx->prefixv;
9f55a1
		elements += 256;
9f55a1
9f55a1
		if (!(prefv = calloc(elements,sizeof(char *))))
9f55a1
			return 0;
9f55a1
9f55a1
		for (psrc=ictx->prefixv,pdst=prefv; *psrc; psrc++,pdst++)
9f55a1
			*pdst = *psrc;
9f55a1
9f55a1
		if (ictx->prefixv != ictx->prefptr)
9f55a1
			free(ictx->prefixv);
9f55a1
9f55a1
		ictx->prefixv = prefv;
9f55a1
		ictx->prefixp = pdst;
9f55a1
		ictx->prefcap = &prefv[--elements];
9f55a1
	}
9f55a1
9f55a1
	if (!(pitem = strdup(prefix)))
9f55a1
		return 0;
9f55a1
9f55a1
	*ictx->prefixp++ = pitem;
9f55a1
9f55a1
	return pitem;
9f55a1
}
9f55a1
43c39b
static char * tpax_add_prefix_item_from_path(
76f93a
	const struct tpax_driver_ctx *  dctx,
76f93a
	const char *                    path,
76f93a
	const char *                    mark)
76f93a
{
76f93a
	off_t   nbytes;
76f93a
	char    pathbuf[PATH_MAX];
76f93a
76f93a
	if ((nbytes = (mark - path)) >= (PATH_MAX - 1))
76f93a
		return 0;
76f93a
76f93a
	memcpy(pathbuf,path,nbytes);
76f93a
	pathbuf[nbytes++] = '/';
76f93a
	pathbuf[nbytes]   = '\0';
76f93a
43c39b
	return tpax_add_prefix_item(dctx,pathbuf);
76f93a
}
76f93a
b56498
static int tpax_dirent_init_from_stat(
8034d0
	const struct stat * st,
8034d0
	const char *        basename,
8034d0
	struct dirent *     dirent)
8034d0
{
8034d0
	/* st_mode to d_type translation */
8034d0
	if (S_ISREG(st->st_mode))
8034d0
		dirent->d_type = DT_REG;
8034d0
	else if (S_ISLNK(st->st_mode))
8034d0
		dirent->d_type = DT_LNK;
8034d0
	else if (S_ISDIR(st->st_mode))
8034d0
		dirent->d_type = DT_DIR;
8034d0
	else if (S_ISCHR(st->st_mode))
8034d0
		dirent->d_type = DT_CHR;
8034d0
	else if (S_ISBLK(st->st_mode))
8034d0
		dirent->d_type = DT_CHR;
8034d0
	else if (S_ISFIFO(st->st_mode))
8034d0
		dirent->d_type = DT_CHR;
8034d0
	else
8034d0
		return -1;
8034d0
8034d0
	/* d_off, d_ino */
8034d0
	dirent->d_off    = 0;
8034d0
	dirent->d_ino    = st->st_ino;
8034d0
8034d0
	/* d_reclen */
8034d0
	dirent->d_reclen  = offsetof(struct dirent,d_name);
8034d0
	dirent->d_reclen += strlen(basename) + 1;
8034d0
8034d0
	dirent->d_reclen += 0x1;
8034d0
	dirent->d_reclen |= 0x1;
8034d0
	dirent->d_reclen ^= 0x1;
8034d0
8034d0
	/* d_name */
8034d0
	strcpy(dirent->d_name,basename);
8034d0
8034d0
	return 0;
8034d0
}
8034d0
022508
static struct tpax_dirent_buffer * tpax_dirent_buf_first_alloc(
022508
	const struct tpax_driver_ctx * dctx)
022508
{
022508
	void * addr;
022508
	struct tpax_driver_ctx_impl * ictx;
022508
022508
	addr = (struct tpax_dirent_buffer *)mmap(
022508
		0,TPAX_DIRENT_BUFLEN,
022508
		PROT_READ|PROT_WRITE,
022508
		MAP_PRIVATE|MAP_ANONYMOUS,
022508
		-1,0);
022508
022508
	if (addr == MAP_FAILED)
022508
		return 0;
022508
022508
	ictx                  = tpax_get_driver_ictx(dctx);
022508
	ictx->dirents         = (struct tpax_dirent_buffer *)addr;
022508
	ictx->dirents->cdent  = ictx->dirents->dbuf;
022508
022508
	ictx->dirents->size   = TPAX_DIRENT_BUFLEN;
022508
	ictx->dirents->next   = 0;
022508
022508
	ictx->dirents->nfree  = TPAX_DIRENT_BUFLEN;
022508
	ictx->dirents->nfree -= offsetof(struct tpax_dirent_buffer,dbuf);
022508
022508
	return ictx->dirents;
022508
}
022508
022508
static struct tpax_dirent_buffer * tpax_dirent_buf_next_alloc(
022508
	struct tpax_dirent_buffer * current)
022508
{
022508
	void * addr;
022508
022508
	addr = (struct tpax_dirent_buffer *)mmap(
022508
		0,TPAX_DIRENT_BUFLEN,
022508
		PROT_READ|PROT_WRITE,
022508
		MAP_PRIVATE|MAP_ANONYMOUS,
022508
		-1,0);
022508
022508
	if (addr == MAP_FAILED)
022508
		return 0;
022508
022508
	current->next         = (struct tpax_dirent_buffer *)addr;
022508
	current->next->cdent  = current->next->dbuf;
022508
022508
	current->next->size   = TPAX_DIRENT_BUFLEN;
022508
	current->next->next   = 0;
022508
022508
	current->next->nfree  = TPAX_DIRENT_BUFLEN;
022508
	current->next->nfree -= offsetof(struct tpax_dirent_buffer,dbuf);
022508
022508
	return current->next;
022508
}
022508
43c39b
static int tpax_archive_enqueue_ret(
022508
	int                     ret,
022508
	struct tpax_unit_ctx *  unit)
022508
{
022508
	if (unit)
c9eeca
		tpax_lib_free_unit_ctx(unit);
022508
022508
	return ret;
022508
}
022508
43c39b
static int tpax_archive_add_queue_item(
3179bb
	const struct tpax_driver_ctx *  dctx,
3179bb
	const struct dirent *           dirent,
3179bb
	const struct tpax_dirent *      parent,
9f55a1
	const char *                    prefix,
f9e2cb
	dev_t                           stdev,
3179bb
	int                             depth,
9f55a1
	int                             flags,
733811
	int                             fdat,
3179bb
	bool *                          fkeep)
3179bb
{
3179bb
	struct tpax_dirent_buffer *     dentbuf;
3179bb
	struct tpax_dirent *            cdent;
3179bb
	const char *                    src;
3179bb
	char *                          dst;
3179bb
	char *                          cap;
3179bb
	size_t                          needed;
3179bb
3179bb
	if (!(dentbuf = tpax_get_driver_dirents(dctx)))
3179bb
		if (!(dentbuf = tpax_dirent_buf_first_alloc(dctx)))
43c39b
			return tpax_archive_enqueue_ret(
3179bb
				TPAX_SYSTEM_ERROR(dctx),
afec65
				0);
3179bb
3179bb
	needed  = dirent->d_reclen;
3179bb
	needed += offsetof(struct tpax_dirent,dirent);
3179bb
	needed += 0x7;
3179bb
	needed |= 0x7;
3179bb
	needed ^= 0x7;
3179bb
3179bb
	for (; dentbuf->next && (dentbuf->nfree < needed); )
3179bb
		dentbuf = dentbuf->next;
3179bb
3179bb
	if (dentbuf->nfree < needed)
3179bb
		if (!(dentbuf = tpax_dirent_buf_next_alloc(dentbuf)))
43c39b
			return tpax_archive_enqueue_ret(
3179bb
				TPAX_SYSTEM_ERROR(dctx),
afec65
				0);
3179bb
3179bb
	*fkeep        = true;
3179bb
	cdent         = dentbuf->cdent;
3179bb
733811
	cdent->fdat   = fdat;
3179bb
	cdent->depth  = depth;
9f55a1
	cdent->flags  = flags;
f9e2cb
	cdent->stdev  = stdev;
3179bb
	cdent->nsize  = needed;
3179bb
	cdent->parent = parent;
9f55a1
	cdent->prefix = prefix;
3179bb
3179bb
	memset(&cdent->dirent,0,offsetof(struct dirent,d_name));
3179bb
3179bb
	cdent->dirent.d_ino    = dirent->d_ino;
3179bb
	cdent->dirent.d_type   = dirent->d_type;
3179bb
	cdent->dirent.d_reclen = dirent->d_reclen;
3179bb
3179bb
	src  = dirent->d_name;
3179bb
	dst  = cdent->dirent.d_name;
3179bb
3179bb
	cap  = dst - offsetof(struct dirent,d_name);
3179bb
	cap -= offsetof(struct tpax_dirent,dirent);
3179bb
	cap += needed;
3179bb
3179bb
	for (; *src; )
3179bb
		*dst++ = *src++;
3179bb
3179bb
	for (; dst
3179bb
		*dst++ = 0;
3179bb
3179bb
	dentbuf->cdent  = (struct tpax_dirent *)cap;
3179bb
	dentbuf->nfree -= needed;
3179bb
a0e0a4
	tpax_set_driver_dirmark(dctx,cdent);
a0e0a4
3179bb
	return 0;
3179bb
}
3179bb
43c39b
static int tpax_archive_enqueue_dir_entries(
022508
	const struct tpax_driver_ctx *  dctx,
afec65
	struct tpax_dirent *            dent)
022508
{
022508
	int                             fd;
afec65
	int                             fdat;
e50240
	int                             fdlnk;
afec65
	int                             depth;
022508
	bool                            fkeep;
e50240
	bool                            flinks;
f9e2cb
	bool                            fstdev;
022508
	long                            nbytes;
e50240
	struct dirent *                 lnkent;
022508
	struct dirent *                 dirent;
022508
	struct dirent *                 dirents;
e50240
	struct tpax_dirent *            cdent;
afec65
	struct tpax_unit_ctx *          uctx;
022508
	struct stat                     st;
e50240
	struct stat                     lnkst;
022508
	uintptr_t                       addr;
e50240
	char                            lnktgt[PATH_MAX];
e50240
	char                            lnkbuf[PATH_MAX + sizeof(struct dirent)];
022508
afec65
	/* init */
afec65
	fdat  = dent->fdat;
afec65
	depth = dent->depth;
022508
afec65
	/* uctx on the fly */
c9eeca
	if (tpax_lib_get_unit_ctx(
afec65
			dctx,fdat,
afec65
			dent->dirent.d_name,
afec65
			&uctx) < 0)
022508
		return TPAX_NESTED_ERROR(dctx);
022508
022508
	/* verify that recursion item is still a directory */
afec65
	if (!S_ISDIR(uctx->st->st_mode))
43c39b
		return tpax_archive_enqueue_ret(
022508
			TPAX_CUSTOM_ERROR(dctx,TPAX_ERR_FLOW_ERROR),
afec65
			uctx);
022508
022508
	/* obtain buffer for file-system directory entries */
022508
	dirents = tpax_get_driver_getdents_buffer(dctx);
022508
	dirent  = dirents;
022508
	fkeep   = false;
022508
	nbytes  = 0;
022508
	depth++;
022508
022508
	/* open directory and obtain first directory entries */
afec65
	if ((fd = openat(fdat,dent->dirent.d_name,O_RDONLY|O_DIRECTORY|O_CLOEXEC,0)) < 0)
43c39b
		return tpax_archive_enqueue_ret(
022508
			TPAX_SYSTEM_ERROR(dctx),
afec65
			uctx);
022508
e50240
	lnkent = (struct dirent *)lnkbuf;
e50240
	flinks = (dctx->cctx->drvflags & TPAX_DRIVER_PAX_SYMLINK_ITEMS);
f9e2cb
	fstdev = (dctx->cctx->drvflags & TPAX_DRIVER_STRICT_DEVICE_ID);
022508
	nbytes = tpax_getdents(fd,dirents,TPAX_DIRENT_BUFLEN);
022508
f9e2cb
	/* debugging, struct stat initialization when fstdev is false */
f9e2cb
	memset(&st,0,sizeof(st));
f9e2cb
022508
	while ((nbytes == -EINTR) || ((nbytes < 0) && (errno == EINTR)))
022508
		nbytes = tpax_getdents(fd,dirents,TPAX_DIRENT_BUFLEN);
022508
022508
	if (nbytes < 0)
43c39b
		return tpax_archive_enqueue_ret(
022508
			TPAX_SYSTEM_ERROR(dctx),
afec65
			uctx);
022508
022508
	/* iterate */
24b5f4
	for (; nbytes; ) {
022508
		if (!strcmp(dirent->d_name,".")) {
022508
			(void)0;
022508
022508
		} else if (!strcmp(dirent->d_name,"..")) {
022508
			(void)0;
022508
022508
		} else {
022508
			if (dirent->d_type == DT_UNKNOWN) {
022508
				if (fstatat(fd,dirent->d_name,&st,AT_SYMLINK_NOFOLLOW))
43c39b
					return tpax_archive_enqueue_ret(
022508
						TPAX_SYSTEM_ERROR(dctx),
afec65
						uctx);
022508
022508
				if (S_ISDIR(st.st_mode)) {
022508
					dirent->d_type = DT_DIR;
022508
				}
f9e2cb
			} else if (fstdev) {
f9e2cb
				if (fstatat(fd,dirent->d_name,&st,AT_SYMLINK_NOFOLLOW))
f9e2cb
					return tpax_archive_enqueue_ret(
f9e2cb
						TPAX_SYSTEM_ERROR(dctx),
f9e2cb
						uctx);
022508
			}
022508
43c39b
			if (tpax_archive_add_queue_item(
f9e2cb
					dctx,dirent,dent,0,
f9e2cb
					st.st_dev,depth,
afec65
					TPAX_ITEM_IMPLICIT,
afec65
					fd,&fkeep) < 0)
43c39b
				return tpax_archive_enqueue_ret(
afec65
					TPAX_NESTED_ERROR(dctx),
afec65
					uctx);
e50240
e50240
			/* follow encountered symlink arguments as needed */
e50240
			fdlnk = (flinks && (dirent->d_type == DT_LNK))
e50240
				? openat(fd,dirent->d_name,O_RDONLY|O_CLOEXEC)
e50240
				: (-1);
e50240
e50240
			if (fdlnk >= 0) {
e50240
				if (fstat(fdlnk,&lnkst) <0) {
e50240
					close(fdlnk);
e50240
					return tpax_archive_enqueue_ret(
e50240
						TPAX_SYSTEM_ERROR(dctx),
e50240
						uctx);
e50240
				}
e50240
e50240
				if (tpax_readlinkat(fd,dirent->d_name,lnktgt,PATH_MAX) < 0)
e50240
					return tpax_archive_enqueue_ret(
e50240
						TPAX_SYSTEM_ERROR(dctx),
e50240
						uctx);
e50240
e50240
				close(fdlnk);
e50240
b56498
				if (tpax_dirent_init_from_stat(&lnkst,lnktgt,lnkent) < 0)
e50240
					return tpax_archive_enqueue_ret(
e50240
						TPAX_CUSTOM_ERROR(
e50240
							dctx,
e50240
							TPAX_ERR_FLOW_ERROR),
e50240
						0);
e50240
e50240
				cdent = tpax_get_driver_dirmark(dctx);
e50240
				cdent->flags |= TPAX_ITEM_NAMEREF;
e50240
e50240
				if (tpax_archive_add_queue_item(
f9e2cb
						dctx,lnkent,cdent,0,
f9e2cb
						lnkst.st_dev,depth+1,
e50240
						TPAX_ITEM_IMPLICIT|TPAX_ITEM_SYMLINK,
e50240
						fd,&fkeep) < 0)
e50240
					return tpax_archive_enqueue_ret(
e50240
						TPAX_NESTED_ERROR(dctx),
e50240
						0);
e50240
			}
022508
		}
022508
022508
		addr    = (uintptr_t)dirent;
022508
		addr   += dirent->d_reclen;
022508
		nbytes -= dirent->d_reclen;
022508
		dirent  = (struct dirent *)addr;
022508
022508
		if (nbytes == 0) {
022508
			nbytes = tpax_getdents(fd,dirents,TPAX_DIRENT_BUFLEN);
022508
022508
			while ((nbytes == -EINTR) || ((nbytes < 0) && (errno == EINTR)))
022508
				nbytes = tpax_getdents(fd,dirents,TPAX_DIRENT_BUFLEN);
022508
022508
			if (nbytes < 0)
43c39b
				tpax_archive_enqueue_ret(
022508
					TPAX_SYSTEM_ERROR(dctx),
afec65
					uctx);
022508
022508
			dirent = dirents;
022508
		}
3db888
	}
3db888
022508
	/* all done */
43c39b
	return tpax_archive_enqueue_ret(
022508
		fkeep ? 0 : close(fd),
afec65
		uctx);
022508
}
022508
76f93a
static const char * tpax_path_prefix_mark(const char * path)
76f93a
{
76f93a
	const char *    src;
76f93a
	char *          mark;
76f93a
	char            pathbuf[PATH_MAX];
76f93a
76f93a
	src  = path;
76f93a
	mark = pathbuf;
76f93a
76f93a
	for (; *src; )
76f93a
		*mark++ = *src++;
76f93a
76f93a
	for (--mark; (*mark == '/'); mark--)
76f93a
		(void)0;
76f93a
76f93a
	*++mark = '\0';
76f93a
76f93a
	for (; (mark > pathbuf) && (mark[-1] != '/'); )
76f93a
		mark--;
76f93a
76f93a
	return (mark <= pathbuf) ? 0 : &path[--mark-pathbuf];
76f93a
}
76f93a
43c39b
int tpax_archive_enqueue(
022508
	const struct tpax_driver_ctx *  dctx,
f5ae32
	const struct tpax_unit_ctx *    uctx)
022508
{
76f93a
	int                             fdat;
37f513
	int                             fdlnk;
1aa8ec
	uintptr_t                       addr;
76f93a
	const char *                    name;
76f93a
	const char *                    mark;
76f93a
	const char *                    prefix;
76f93a
	bool                            fkeep;
1aa8ec
	struct tpax_dirent_buffer *     dentbuf;
1aa8ec
	struct tpax_dirent *            cdent;
1aa8ec
	struct tpax_dirent *            cnext;
76f93a
	struct dirent *                 dirent;
37f513
	struct dirent *                 lnkent;
37f513
	struct stat                     lnkst;
76f93a
	char                            entbuf[PATH_MAX + sizeof(struct dirent)];
37f513
	char                            lnkbuf[PATH_MAX + sizeof(struct dirent)];
76f93a
76f93a
	/* init */
76f93a
	fdat   = tpax_driver_fdcwd(dctx);
76f93a
	dirent = (struct dirent *)entbuf;
37f513
	lnkent = (struct dirent *)lnkbuf;
76f93a
	prefix = 0;
76f93a
76f93a
	/* split path to prefix + basename */
76f93a
	if ((mark = tpax_path_prefix_mark(*uctx->path)))
43c39b
		if (!(prefix = tpax_add_prefix_item_from_path(
76f93a
				dctx,*uctx->path,mark)))
43c39b
			return tpax_archive_enqueue_ret(
76f93a
				TPAX_BUFFER_ERROR(dctx),
76f93a
				0);
76f93a
76f93a
	name = mark ? ++mark : *uctx->path;
76f93a
76f93a
	if (prefix)
76f93a
		if ((fdat = openat(fdat,prefix,O_RDONLY|O_DIRECTORY|O_CLOEXEC,0)) < 0)
43c39b
			return tpax_archive_enqueue_ret(
76f93a
				TPAX_SYSTEM_ERROR(dctx),
76f93a
				0);
76f93a
76f93a
	/* explicit item directory entry */
b56498
	if (tpax_dirent_init_from_stat(uctx->st,name,dirent) < 0)
43c39b
			return tpax_archive_enqueue_ret(
76f93a
				TPAX_CUSTOM_ERROR(
76f93a
					dctx,
76f93a
					TPAX_ERR_FLOW_ERROR),
76f93a
				0);
76f93a
76f93a
	/* add to queue */
43c39b
	if (tpax_archive_add_queue_item(
f9e2cb
			dctx,dirent,0,prefix,
f9e2cb
			uctx->st->st_dev,0,
76f93a
			TPAX_ITEM_EXPLICIT,
76f93a
			fdat,&fkeep) < 0)
43c39b
		return tpax_archive_enqueue_ret(
76f93a
			TPAX_NESTED_ERROR(dctx),
76f93a
			0);
022508
37f513
	/* follow command-line symlink arguments as needed */
37f513
	fdlnk = (uctx->link[0] && (dctx->cctx->drvflags & TPAX_DRIVER_PAX_SYMLINK_ARGS))
37f513
		? openat(fdat,uctx->link[0],O_RDONLY|O_CLOEXEC) : (-1);
37f513
37f513
	if (fdlnk >= 0) {
37f513
		if (fstat(fdlnk,&lnkst) <0) {
37f513
			close(fdlnk);
37f513
			return tpax_archive_enqueue_ret(
37f513
				TPAX_SYSTEM_ERROR(dctx),
37f513
				0);
37f513
		}
37f513
37f513
		close(fdlnk);
37f513
b56498
		if (tpax_dirent_init_from_stat(&lnkst,uctx->link[0],lnkent) < 0)
37f513
			return tpax_archive_enqueue_ret(
37f513
				TPAX_CUSTOM_ERROR(
37f513
					dctx,
37f513
					TPAX_ERR_FLOW_ERROR),
37f513
				0);
37f513
37f513
		cdent = tpax_get_driver_dirmark(dctx);
37f513
		cdent->flags |= TPAX_ITEM_NAMEREF;
37f513
37f513
		if (tpax_archive_add_queue_item(
f9e2cb
				dctx,lnkent,cdent,0,
f9e2cb
				lnkst.st_dev,1,
37f513
				TPAX_ITEM_EXPLICIT|TPAX_ITEM_SYMLINK,
37f513
				fdat,&fkeep) < 0)
37f513
			return tpax_archive_enqueue_ret(
37f513
				TPAX_NESTED_ERROR(dctx),
37f513
				0);
37f513
	}
37f513
1aa8ec
	/* queue directory child items */
1aa8ec
	dentbuf = tpax_get_driver_dirents(dctx);
1aa8ec
	cdent   = tpax_get_driver_dirmark(dctx);
1aa8ec
1aa8ec
	for (; cdent; ) {
1aa8ec
		if (cdent->dirent.d_type == DT_DIR)
dac847
			if (dctx->cctx->drvflags & TPAX_DRIVER_DIR_MEMBER_RECURSE)
43c39b
				if (tpax_archive_enqueue_dir_entries(dctx,cdent) < 0)
dac847
					return TPAX_NESTED_ERROR(dctx);
1aa8ec
1aa8ec
		addr  = (uintptr_t)cdent;
1aa8ec
		addr += cdent->nsize;
1aa8ec
		cnext = (struct tpax_dirent *)addr;
1aa8ec
1aa8ec
		if (cnext == dentbuf->cdent) {
1aa8ec
			dentbuf = dentbuf->next;
1aa8ec
			cnext   = dentbuf ? dentbuf->dbuf : 0;
1aa8ec
		}
1aa8ec
1aa8ec
		cdent = cnext;
1aa8ec
	}
1aa8ec
409008
	return 0;
3db888
}