Module Name: src Committed By: yamt Date: Sun Sep 26 02:27:00 UTC 2010
Modified Files: src/include: dirent.h src/lib/libc/gen: Makefile.inc closedir.c dirent_private.h opendir.c rewinddir.c Added Files: src/lib/libc/gen: initdir.c Log Message: fix rewinddir on nfs. fix PR/42879 (and probably PR/40229.) To generate a diff of this commit: cvs rdiff -u -r1.33 -r1.34 src/include/dirent.h cvs rdiff -u -r1.171 -r1.172 src/lib/libc/gen/Makefile.inc cvs rdiff -u -r1.15 -r1.16 src/lib/libc/gen/closedir.c cvs rdiff -u -r1.3 -r1.4 src/lib/libc/gen/dirent_private.h cvs rdiff -u -r0 -r1.1 src/lib/libc/gen/initdir.c cvs rdiff -u -r1.36 -r1.37 src/lib/libc/gen/opendir.c cvs rdiff -u -r1.12 -r1.13 src/lib/libc/gen/rewinddir.c Please note that diffs are not public domain; they are subject to the copyright notices on the relevant files.
Modified files: Index: src/include/dirent.h diff -u src/include/dirent.h:1.33 src/include/dirent.h:1.34 --- src/include/dirent.h:1.33 Tue Feb 24 18:41:40 2009 +++ src/include/dirent.h Sun Sep 26 02:26:59 2010 @@ -1,4 +1,4 @@ -/* $NetBSD: dirent.h,v 1.33 2009/02/24 18:41:40 christos Exp $ */ +/* $NetBSD: dirent.h,v 1.34 2010/09/26 02:26:59 yamt Exp $ */ /*- * Copyright (c) 1989, 1993 @@ -74,6 +74,8 @@ #define DTF_NODUP 0x0002 /* don't return duplicate names */ #define DTF_REWIND 0x0004 /* rewind after reading union stack */ #define __DTF_READALL 0x0008 /* everything has been read */ +#define __DTF_RETRY_ON_BADCOOKIE 0x0001 /* retry on EINVAL + (only valid with __DTF_READALL) */ #include <sys/null.h> Index: src/lib/libc/gen/Makefile.inc diff -u src/lib/libc/gen/Makefile.inc:1.171 src/lib/libc/gen/Makefile.inc:1.172 --- src/lib/libc/gen/Makefile.inc:1.171 Fri Aug 27 08:38:41 2010 +++ src/lib/libc/gen/Makefile.inc Sun Sep 26 02:26:59 2010 @@ -1,4 +1,4 @@ -# $NetBSD: Makefile.inc,v 1.171 2010/08/27 08:38:41 christos Exp $ +# $NetBSD: Makefile.inc,v 1.172 2010/09/26 02:26:59 yamt Exp $ # from: @(#)Makefile.inc 8.6 (Berkeley) 5/4/95 # gen sources @@ -15,7 +15,7 @@ getloadavg.c getlogin.c getmntinfo.c \ getnetgrent.c getpagesize.c \ getpass.c getprogname.c getpwent.c getttyent.c \ - getusershell.c glob.c humanize_number.c initgroups.c \ + getusershell.c glob.c humanize_number.c initdir.c initgroups.c \ isascii.c isatty.c isctype.c lockf.c nftw.c \ nice.c nlist.c nlist_aout.c \ nlist_coff.c nlist_ecoff.c nlist_elf32.c nlist_elf64.c opendir.c \ Index: src/lib/libc/gen/closedir.c diff -u src/lib/libc/gen/closedir.c:1.15 src/lib/libc/gen/closedir.c:1.16 --- src/lib/libc/gen/closedir.c:1.15 Wed May 17 20:36:50 2006 +++ src/lib/libc/gen/closedir.c Sun Sep 26 02:26:59 2010 @@ -1,4 +1,4 @@ -/* $NetBSD: closedir.c,v 1.15 2006/05/17 20:36:50 christos Exp $ */ +/* $NetBSD: closedir.c,v 1.16 2010/09/26 02:26:59 yamt Exp $ */ /* * Copyright (c) 1983, 1993 @@ -34,7 +34,7 @@ #if 0 static char sccsid[] = "@(#)closedir.c 8.1 (Berkeley) 6/10/93"; #else -__RCSID("$NetBSD: closedir.c,v 1.15 2006/05/17 20:36:50 christos Exp $"); +__RCSID("$NetBSD: closedir.c,v 1.16 2010/09/26 02:26:59 yamt Exp $"); #endif #endif /* LIBC_SCCS and not lint */ @@ -62,7 +62,6 @@ closedir(DIR *dirp) { int fd; - struct dirpos *poslist; _DIAGASSERT(dirp != NULL); @@ -72,15 +71,7 @@ #endif fd = dirp->dd_fd; dirp->dd_fd = -1; - dirp->dd_loc = 0; - free(dirp->dd_buf); - - /* free seekdir/telldir storage */ - for (poslist = dirp->dd_internal; poslist; ) { - struct dirpos *nextpos = poslist->dp_next; - free(poslist); - poslist = nextpos; - } + _finidir(dirp); #ifdef _REENTRANT if (__isthreaded) { Index: src/lib/libc/gen/dirent_private.h diff -u src/lib/libc/gen/dirent_private.h:1.3 src/lib/libc/gen/dirent_private.h:1.4 --- src/lib/libc/gen/dirent_private.h:1.3 Thu Sep 16 02:38:50 2010 +++ src/lib/libc/gen/dirent_private.h Sun Sep 26 02:26:59 2010 @@ -1,4 +1,4 @@ -/* $NetBSD: dirent_private.h,v 1.3 2010/09/16 02:38:50 yamt Exp $ */ +/* $NetBSD: dirent_private.h,v 1.4 2010/09/26 02:26:59 yamt Exp $ */ /* * One struct _dirpos is malloced to describe the current directory @@ -15,6 +15,8 @@ struct _dirdesc; void _seekdir_unlocked(struct _dirdesc *, long); long _telldir_unlocked(struct _dirdesc *); +int _initdir(DIR *, int, const char *); +void _finidir(DIR *); #ifndef __LIBC12_SOURCE__ struct dirent; struct dirent *_readdir_unlocked(struct _dirdesc *, int) Index: src/lib/libc/gen/opendir.c diff -u src/lib/libc/gen/opendir.c:1.36 src/lib/libc/gen/opendir.c:1.37 --- src/lib/libc/gen/opendir.c:1.36 Thu Sep 16 02:38:50 2010 +++ src/lib/libc/gen/opendir.c Sun Sep 26 02:26:59 2010 @@ -1,4 +1,4 @@ -/* $NetBSD: opendir.c,v 1.36 2010/09/16 02:38:50 yamt Exp $ */ +/* $NetBSD: opendir.c,v 1.37 2010/09/26 02:26:59 yamt Exp $ */ /* * Copyright (c) 1983, 1993 @@ -34,7 +34,7 @@ #if 0 static char sccsid[] = "@(#)opendir.c 8.7 (Berkeley) 12/10/94"; #else -__RCSID("$NetBSD: opendir.c,v 1.36 2010/09/16 02:38:50 yamt Exp $"); +__RCSID("$NetBSD: opendir.c,v 1.37 2010/09/26 02:26:59 yamt Exp $"); #endif #endif /* LIBC_SCCS and not lint */ @@ -56,8 +56,6 @@ #include "dirent_private.h" -#define MAXITERATIONS 100 - static DIR *__opendir_common(int, const char *, int); __weak_alias(fdopendir,_fdopendir) @@ -99,10 +97,8 @@ DIR *dirp = NULL; int serrno; struct stat sb; - int pagesz; - int incr; - int unionstack, nfsdir; struct statvfs sfb; + int error; if (fcntl(fd, F_SETFD, FD_CLOEXEC) == -1) goto error; @@ -113,232 +109,54 @@ if ((dirp = (DIR *)malloc(sizeof(DIR))) == NULL) goto error; dirp->dd_buf = NULL; + dirp->dd_internal = NULL; +#ifdef _REENTRANT + if (__isthreaded) { + if ((dirp->dd_lock = malloc(sizeof(mutex_t))) == NULL) + goto error; + mutex_init((mutex_t *)dirp->dd_lock, NULL); + } +#endif /* - * If the machine's page size is an exact multiple of DIRBLKSIZ, - * use a buffer that is cluster boundary aligned. - * Hopefully this can be a big win someday by allowing page trades - * to user space to be done by getdents() - */ - if (((pagesz = getpagesize()) % DIRBLKSIZ) == 0) - incr = pagesz; - else - incr = DIRBLKSIZ; - - /* - * Determine whether this directory is the top of a union stack. + * Tweak flags for the underlying filesystem. */ if (fstatvfs1(fd, &sfb, ST_NOWAIT) < 0) goto error; - - if (flags & DTF_NODUP) - unionstack = !(strncmp(sfb.f_fstypename, MOUNT_UNION, - sizeof(sfb.f_fstypename))) || (sfb.f_flag & MNT_UNION); - else - unionstack = 0; - - nfsdir = !(strncmp(sfb.f_fstypename, MOUNT_NFS, - sizeof(sfb.f_fstypename))); - - if (unionstack || nfsdir) { - size_t len; - size_t space; - char *buf, *nbuf; - char *ddptr; - char *ddeptr; - int n; - struct dirent **dpv; - int i; - - /* - * The strategy here for directories on top of a union stack - * is to read all the directory entries into a buffer, sort - * the buffer, and remove duplicate entries by setting the - * inode number to zero. - * - * For directories on an NFS mounted filesystem, we try - * to get a consistent snapshot by trying until we have - * successfully read all of the directory without errors - * (i.e. 'bad cookie' errors from the server because - * the directory was modified). These errors should not - * happen often, but need to be dealt with. - */ - i = 0; -retry: - len = 0; - space = 0; - buf = 0; - ddptr = 0; - - do { - /* - * Always make at least DIRBLKSIZ bytes - * available to getdents - */ - if (space < DIRBLKSIZ) { - space += incr; - len += incr; - nbuf = realloc(buf, len); - if (nbuf == NULL) { - dirp->dd_buf = buf; - goto error; - } - buf = nbuf; - ddptr = buf + (len - space); - } - - dirp->dd_seek = lseek(fd, (off_t)0, SEEK_CUR); - n = getdents(fd, ddptr, space); - /* - * For NFS: EINVAL means a bad cookie error - * from the server. Keep trying to get a - * consistent view, in this case this means - * starting all over again. - */ - if (n == -1 && errno == EINVAL && nfsdir) { - free(buf); - lseek(fd, (off_t)0, SEEK_SET); - if (++i > MAXITERATIONS) - goto error; - goto retry; - } - if (n > 0) { - ddptr += n; - space -= n; - } - } while (n > 0); - - ddeptr = ddptr; - flags |= __DTF_READALL; - - /* - * Re-open the directory. - * This has the effect of rewinding back to the - * top of the union stack and is needed by - * programs which plan to fchdir to a descriptor - * which has also been read -- see fts.c. - */ - if (flags & DTF_REWIND) { - (void) close(fd); - if ((fd = open(name, O_RDONLY)) == -1 || - fcntl(fd, F_SETFD, FD_CLOEXEC) == -1) { - dirp->dd_buf = buf; - goto error; - } + if ((flags & DTF_NODUP) != 0) { + if (!strncmp(sfb.f_fstypename, MOUNT_UNION, + sizeof(sfb.f_fstypename)) || + (sfb.f_flag & MNT_UNION) != 0) { + flags |= __DTF_READALL; + } else { + flags &= ~DTF_NODUP; } - - /* - * There is now a buffer full of (possibly) duplicate - * names. - */ - dirp->dd_buf = buf; - - /* - * Go round this loop twice... - * - * Scan through the buffer, counting entries. - * On the second pass, save pointers to each one. - * Then sort the pointers and remove duplicate names. - */ - if (!nfsdir) { - for (dpv = 0;;) { - for (n = 0, ddptr = buf; ddptr < ddeptr;) { - struct dirent *dp; - - dp = (struct dirent *)(void *)ddptr; - if ((long)dp & _DIRENT_ALIGN(dp)) - break; - /* - * d_reclen is unsigned, - * so no need to compare <= 0 - */ - if (dp->d_reclen > (ddeptr + 1 - ddptr)) - break; - ddptr += dp->d_reclen; - if (dp->d_fileno) { - if (dpv) - dpv[n] = dp; - n++; - } - } - - if (dpv) { - struct dirent *xp; - - /* - * This sort must be stable. - */ - mergesort(dpv, (size_t)n, sizeof(*dpv), - alphasort); - - dpv[n] = NULL; - xp = NULL; - - /* - * Scan through the buffer in sort - * order, zapping the inode number - * of any duplicate names. - */ - for (n = 0; dpv[n]; n++) { - struct dirent *dp = dpv[n]; - - if ((xp == NULL) || - strcmp(dp->d_name, - xp->d_name)) - xp = dp; - else - dp->d_fileno = 0; - if (dp->d_type == DT_WHT && - (flags & DTF_HIDEW)) - dp->d_fileno = 0; - } - - free(dpv); - break; - } else { - dpv = malloc((n + 1) * - sizeof(struct dirent *)); - if (dpv == NULL) - break; - } - } - } - - dirp->dd_len = len; - dirp->dd_size = ddptr - dirp->dd_buf; - } else { - dirp->dd_len = incr; - dirp->dd_buf = malloc((size_t)dirp->dd_len); - if (dirp->dd_buf == NULL) - goto error; - dirp->dd_seek = 0; - flags &= ~DTF_REWIND; + } + if (!strncmp(sfb.f_fstypename, MOUNT_NFS, sizeof(sfb.f_fstypename))) { + flags |= __DTF_READALL | __DTF_RETRY_ON_BADCOOKIE; } - dirp->dd_loc = 0; - dirp->dd_fd = fd; dirp->dd_flags = flags; - - /* - * Set up seek point for rewinddir. - */ -#ifdef _REENTRANT - if (__isthreaded) { - if ((dirp->dd_lock = malloc(sizeof(mutex_t))) == NULL) - goto error; - mutex_init((mutex_t *)dirp->dd_lock, NULL); + error = _initdir(dirp, fd, name); + if (error) { + errno = error; + goto error; } -#endif - dirp->dd_internal = NULL; - (void)_telldir_unlocked(dirp); + return (dirp); error: serrno = errno; - if (dirp && dirp->dd_buf) + if (dirp != NULL) { +#ifdef _REENTRANT + if (__isthreaded) { + mutex_destroy((mutex_t *)dirp->dd_lock); + free(dirp->dd_lock); + } +#endif free(dirp->dd_buf); - if (dirp) - free(dirp); + } + free(dirp); if (fd != -1) (void)close(fd); errno = serrno; Index: src/lib/libc/gen/rewinddir.c diff -u src/lib/libc/gen/rewinddir.c:1.12 src/lib/libc/gen/rewinddir.c:1.13 --- src/lib/libc/gen/rewinddir.c:1.12 Wed May 17 20:36:50 2006 +++ src/lib/libc/gen/rewinddir.c Sun Sep 26 02:26:59 2010 @@ -1,4 +1,4 @@ -/* $NetBSD: rewinddir.c,v 1.12 2006/05/17 20:36:50 christos Exp $ */ +/* $NetBSD: rewinddir.c,v 1.13 2010/09/26 02:26:59 yamt Exp $ */ /*- * Copyright (c) 1990, 1993 @@ -34,7 +34,7 @@ #if 0 static char sccsid[] = "@(#)rewinddir.c 8.1 (Berkeley) 6/8/93"; #else -__RCSID("$NetBSD: rewinddir.c,v 1.12 2006/05/17 20:36:50 christos Exp $"); +__RCSID("$NetBSD: rewinddir.c,v 1.13 2010/09/26 02:26:59 yamt Exp $"); #endif #endif /* LIBC_SCCS and not lint */ @@ -43,6 +43,7 @@ #include "extern.h" #include <sys/types.h> +#include <unistd.h> #include <dirent.h> #include "dirent_private.h" @@ -54,18 +55,20 @@ void rewinddir(DIR *dirp) { - struct dirpos *dp = dirp->dd_internal; - - while (dp->dp_next) - dp = dp->dp_next; + int fd; #ifdef _REENTRANT if (__isthreaded) { mutex_lock((mutex_t *)dirp->dd_lock); - _seekdir_unlocked(dirp, (long)(intptr_t)dp); + } +#endif + fd = dirp->dd_fd; + _finidir(dirp); + dirp->dd_seek = lseek(fd, (off_t)0, SEEK_SET); + _initdir(dirp, fd, NULL); +#ifdef _REENTRANT + if (__isthreaded) { mutex_unlock((mutex_t *)dirp->dd_lock); - return; } #endif - _seekdir_unlocked(dirp, (long)(intptr_t)dp); } Added files: Index: src/lib/libc/gen/initdir.c diff -u /dev/null src/lib/libc/gen/initdir.c:1.1 --- /dev/null Sun Sep 26 02:27:00 2010 +++ src/lib/libc/gen/initdir.c Sun Sep 26 02:26:59 2010 @@ -0,0 +1,274 @@ +/* $NetBSD: initdir.c,v 1.1 2010/09/26 02:26:59 yamt Exp $ */ + +/* + * Copyright (c) 1983, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +#if defined(LIBC_SCCS) && !defined(lint) +__RCSID("$NetBSD: initdir.c,v 1.1 2010/09/26 02:26:59 yamt Exp $"); +#endif /* LIBC_SCCS and not lint */ + +#include "namespace.h" +#include "reentrant.h" +#include "extern.h" + +#include <sys/param.h> + +#include <assert.h> +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "dirent_private.h" + +#define MAXITERATIONS 100 + +int +_initdir(DIR *dirp, int fd, const char *name) +{ + int flags = dirp->dd_flags; + int pagesz; + int incr; + + /* + * If the machine's page size is an exact multiple of DIRBLKSIZ, + * use a buffer that is cluster boundary aligned. + * Hopefully this can be a big win someday by allowing page trades + * to user space to be done by getdents() + */ + if (((pagesz = getpagesize()) % DIRBLKSIZ) == 0) + incr = pagesz; + else + incr = DIRBLKSIZ; + + if ((flags & DTF_REWIND) && name == NULL) { + return EINVAL; + } + if ((flags & __DTF_READALL) != 0) { + size_t len; + size_t space; + char *buf, *nbuf; + char *ddptr; + char *ddeptr; + int n; + struct dirent **dpv; + int i; + + /* + * The strategy here for directories on top of a union stack + * is to read all the directory entries into a buffer, sort + * the buffer, and remove duplicate entries by setting the + * inode number to zero. + * + * For directories on an NFS mounted filesystem, we try + * to get a consistent snapshot by trying until we have + * successfully read all of the directory without errors + * (i.e. 'bad cookie' errors from the server because + * the directory was modified). These errors should not + * happen often, but need to be dealt with. + */ + i = 0; +retry: + len = 0; + space = 0; + buf = 0; + ddptr = 0; + + do { + /* + * Always make at least DIRBLKSIZ bytes + * available to getdents + */ + if (space < DIRBLKSIZ) { + space += incr; + len += incr; + nbuf = realloc(buf, len); + if (nbuf == NULL) { + dirp->dd_buf = buf; + return errno; + } + buf = nbuf; + ddptr = buf + (len - space); + } + + dirp->dd_seek = lseek(fd, (off_t)0, SEEK_CUR); + n = getdents(fd, ddptr, space); + /* + * For NFS: EINVAL means a bad cookie error + * from the server. Keep trying to get a + * consistent view, in this case this means + * starting all over again. + */ + if (n == -1 && errno == EINVAL && + (flags & __DTF_RETRY_ON_BADCOOKIE) != 0) { + free(buf); + lseek(fd, (off_t)0, SEEK_SET); + if (++i > MAXITERATIONS) + return EINVAL; + goto retry; + } + if (n > 0) { + ddptr += n; + space -= n; + } + } while (n > 0); + + ddeptr = ddptr; + + /* + * Re-open the directory. + * This has the effect of rewinding back to the + * top of the union stack and is needed by + * programs which plan to fchdir to a descriptor + * which has also been read -- see fts.c. + */ + if (flags & DTF_REWIND) { + (void) close(fd); + if ((fd = open(name, O_RDONLY)) == -1 || + fcntl(fd, F_SETFD, FD_CLOEXEC) == -1) { + dirp->dd_buf = buf; + return errno; + } + } + + /* + * There is now a buffer full of (possibly) duplicate + * names. + */ + dirp->dd_buf = buf; + + /* + * Go round this loop twice... + * + * Scan through the buffer, counting entries. + * On the second pass, save pointers to each one. + * Then sort the pointers and remove duplicate names. + */ + if ((flags & DTF_NODUP) != 0) { + for (dpv = 0;;) { + for (n = 0, ddptr = buf; ddptr < ddeptr;) { + struct dirent *dp; + + dp = (struct dirent *)(void *)ddptr; + if ((long)dp & _DIRENT_ALIGN(dp)) + break; + /* + * d_reclen is unsigned, + * so no need to compare <= 0 + */ + if (dp->d_reclen > (ddeptr + 1 - ddptr)) + break; + ddptr += dp->d_reclen; + if (dp->d_fileno) { + if (dpv) + dpv[n] = dp; + n++; + } + } + + if (dpv) { + struct dirent *xp; + + /* + * This sort must be stable. + */ + mergesort(dpv, (size_t)n, sizeof(*dpv), + alphasort); + + dpv[n] = NULL; + xp = NULL; + + /* + * Scan through the buffer in sort + * order, zapping the inode number + * of any duplicate names. + */ + for (n = 0; dpv[n]; n++) { + struct dirent *dp = dpv[n]; + + if ((xp == NULL) || + strcmp(dp->d_name, + xp->d_name)) + xp = dp; + else + dp->d_fileno = 0; + if (dp->d_type == DT_WHT && + (flags & DTF_HIDEW)) + dp->d_fileno = 0; + } + + free(dpv); + break; + } else { + dpv = malloc((n + 1) * + sizeof(struct dirent *)); + if (dpv == NULL) + break; + } + } + } + + dirp->dd_len = len; + dirp->dd_size = ddptr - dirp->dd_buf; + } else { + dirp->dd_len = incr; + dirp->dd_buf = malloc((size_t)dirp->dd_len); + if (dirp->dd_buf == NULL) + return errno; + dirp->dd_seek = 0; + flags &= ~DTF_REWIND; + } + dirp->dd_loc = 0; + dirp->dd_fd = fd; + dirp->dd_flags = flags; + /* + * Set up seek point for rewinddir. + */ + (void)_telldir_unlocked(dirp); + return 0; +} + +void +_finidir(DIR *dirp) +{ + struct dirpos *poslist; + + free(dirp->dd_buf); + + /* free seekdir/telldir storage */ + for (poslist = dirp->dd_internal; poslist; ) { + struct dirpos *nextpos = poslist->dp_next; + free(poslist); + poslist = nextpos; + } + dirp->dd_internal = NULL; +}