Return-Path: Delivered-To: apmail-httpd-dev-archive@httpd.apache.org Received: (qmail 23735 invoked by uid 500); 7 Nov 2001 05:49:10 -0000 Mailing-List: contact dev-help@httpd.apache.org; run by ezmlm Precedence: bulk Reply-To: dev@httpd.apache.org list-help: list-unsubscribe: list-post: Delivered-To: mailing list dev@httpd.apache.org Received: (qmail 23723 invoked from network); 7 Nov 2001 05:49:09 -0000 Message-ID: From: "MATHIHALLI,MADHUSUDAN (HP-Cupertino,ex1)" To: "'dev@httpd.apache.org'" Subject: [PATCH]1. SHMEM (repost) Date: Wed, 7 Nov 2001 00:49:13 -0500 MIME-Version: 1.0 X-Mailer: Internet Mail Service (5.5.2653.19) Content-Type: multipart/mixed; boundary="----_=_NextPart_000_01C1674F.EDAF71E0" X-Spam-Rating: daedalus.apache.org 1.6.2 0/1000/N This message is in MIME format. Since your mail reader does not understand this format, some or all of this message may not be legible. ------_=_NextPart_000_01C1674F.EDAF71E0 Content-Type: text/plain; charset="iso-8859-1" Hi, As you might be aware, the current code in apr_shmem.c doesn't do any memory management. I'd posted this patch sometime back, but I didn't get any feedback regarding whether it's accepted / rejected (but for the code structure comments from Justin - thanks).. Here's a repost of the SHMEM patch. It'd be great if somebody could pl. review and let me know you comments / suggestions.. This would be required for the SHMHT / SHMCB patch that follows.. -Madhu ------_=_NextPart_000_01C1674F.EDAF71E0 Content-Type: text/plain; name="patch_shmem_4.txt" Content-Transfer-Encoding: quoted-printable Content-Disposition: attachment; filename="patch_shmem_4.txt" Index: shmem.c =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D RCS file: /home/cvspublic/apr/shmem/unix/shmem.c,v retrieving revision 1.33 diff -u -r1.33 shmem.c --- shmem.c 2001/08/30 17:11:04 1.33 +++ shmem.c 2001/09/25 05:43:51 @@ -52,14 +52,6 @@ * . */ =20 -#include "apr_general.h" -#include "apr_shmem.h" -#include "apr_lock.h" -#include "apr_portable.h" -#include "apr_errno.h" -#define APR_WANT_MEMFUNC -#include "apr_want.h" - /* * This is the Unix implementation of shared memory. * @@ -72,45 +64,15 @@ * - shmget (SysV) * - create_area (BeOS) */ - -#if APR_USE_SHMEM_MMAP_TMP || APR_USE_SHMEM_MMAP_SHM || = APR_USE_SHMEM_MMAP_ZERO || APR_USE_SHMEM_MMAP_ANON -#include -#elif APR_USE_SHMEM_SHMGET -#include -#include -#if !defined(SHM_R) -#define SHM_R 0400 -#endif -#if !defined(SHM_W) -#define SHM_W 0200 -#endif -#include -#elif APR_USE_SHMEM_BEOS -#include -#endif =20 -struct shmem_t { - void *mem; - void *curmem; - apr_size_t length; - apr_lock_t *lock; - char *filename; -#if APR_USE_SHMEM_MMAP_TMP || APR_USE_SHMEM_MMAP_SHM || = APR_USE_SHMEM_MMAP_ZERO - apr_file_t *file;=20 -#elif APR_USE_SHMEM_MMAP_ANON - /* Nothing else. */ -#elif APR_USE_SHMEM_SHMGET - apr_os_file_t file; -#elif APR_USE_SHMEM_BEOS - area_id areaid;=20 -#endif -}; +#include "shmem.h" =20 APR_DECLARE(apr_status_t) apr_shm_init(apr_shmem_t **m, apr_size_t = reqsize,=20 const char *filename, = apr_pool_t *pool) { apr_shmem_t *new_m; - void *mem; + void *mem, *addr; + int i, listlen, listelem; #if APR_USE_SHMEM_SHMGET struct shmid_ds shmbuf; apr_uid_t uid; @@ -128,6 +90,9 @@ if (!new_m) return APR_ENOMEM; =20 + listelem =3D (reqsize / MIN_BLK_SIZE) + 1; + listlen =3D listelem * sizeof(memchunk_t) + sizeof(memoffsets_t); + /* These implementations are very similar except for opening the file. = */ #if APR_USE_SHMEM_MMAP_TMP || APR_USE_SHMEM_MMAP_SHM || = APR_USE_SHMEM_MMAP_ZERO /* FIXME: Ignore error for now. * @@ -168,10 +133,18 @@ status =3D apr_os_file_get(&tmpfd, new_m->file); #endif =20 - mem =3D mmap(NULL, reqsize, PROT_READ|PROT_WRITE, MAP_SHARED, = tmpfd, 0); + /* Not yet tested */ + addr =3D mmap(NULL, reqsize + listlen, + PROT_READ|PROT_WRITE, MAP_SHARED, tmpfd, 0); + new_m->listlen =3D listlen; + mem =3D addr + listlen; =20 #elif APR_USE_SHMEM_MMAP_ANON - mem =3D mmap(NULL, reqsize, PROT_READ|PROT_WRITE, = MAP_ANON|MAP_SHARED, -1, 0); + mem =3D mmap(NULL, reqsize, + PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_SHARED, -1, 0); + addr =3D mmap(NULL, listlen, + PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_SHARED, -1, 0); + new_m->listlen =3D listlen; #elif APR_USE_SHMEM_SHMGET tmpfd =3D shmget(IPC_PRIVATE, reqsize, (SHM_R|SHM_W|IPC_CREAT)); if (tmpfd =3D=3D -1) @@ -180,7 +153,16 @@ new_m->file =3D tmpfd; =20 mem =3D shmat(new_m->file, NULL, 0); + if (!mem) + return errno; =20 + tmpfd =3D shmget(IPC_PRIVATE, listlen, (SHM_R|SHM_W|IPC_CREAT)); + if (tmpfd =3D=3D -1) + return errno; + + new_m->listfd =3D tmpfd; + addr =3D shmat(new_m->listfd, NULL, 0); + /* FIXME: Handle errors. */ if (shmctl(new_m->file, IPC_STAT, &shmbuf) =3D=3D -1) return errno; @@ -192,8 +174,7 @@ if (shmctl(new_m->file, IPC_SET, &shmbuf) =3D=3D -1) return errno; =20 - /* remove in future (once use count hits zero) */ - if (shmctl(new_m->file, IPC_RMID, NULL) =3D=3D -1) + if (shmctl(new_m->listfd, IPC_SET, &shmbuf) =3D=3D -1) return errno; =20 #elif APR_USE_SHMEM_BEOS @@ -205,8 +186,36 @@ =20 #endif =20 + if (!addr) + return errno; + + new_m->offsets =3D addr; + new_m->list =3D addr + (sizeof(memoffsets_t)); + + memset(new_m->list, 0, listlen); + for (i =3D 0; i < listelem; i++) { + new_m->list[i].prev =3D -1; + new_m->list[i].next =3D -1; + } + + /* + * Initially, there's only one element c_free (=3D0, l_total =3D = 1). + * The size of this free element is the total size requested. + * The c_used list is set to NULL (c_used =3D -1). + */ + new_m->list[0].offset =3D 0; + new_m->list[0].size =3D reqsize; + new_m->list[0].next =3D 0; + new_m->list[0].prev =3D 0; + + new_m->offsets->l_total =3D 1; + new_m->offsets->c_free =3D 0; + new_m->offsets->c_used =3D -1; + new_m->offsets->shm_offset =3D 0; + new_m->offsets->shm_length =3D reqsize; + + new_m->p =3D pool; new_m->mem =3D mem; - new_m->curmem =3D mem; new_m->length =3D reqsize; =20 apr_lock_create(&new_m->lock, APR_MUTEX, APR_CROSS_PROCESS, NULL, = pool); @@ -219,48 +228,86 @@ =20 APR_DECLARE(apr_status_t) apr_shm_destroy(apr_shmem_t *m) { +#if APR_USE_SHMEM_SHMGET + struct shmid_ds shmbuf; + apr_uid_t uid; + apr_gid_t gid; +#endif + + if (!m) + return APR_SUCCESS; + #if APR_USE_SHMEM_MMAP_TMP || APR_USE_SHMEM_MMAP_SHM || = APR_USE_SHMEM_MMAP_ZERO - munmap(m->mem, m->length); + munmap(m->offsets, (m->length + m->listlen)); apr_file_close(m->file); #elif APR_USE_SHMEM_MMAP_ANON munmap(m->mem, m->length); + munmap(m->offsets,m->listlen); #elif APR_USE_SHMEM_SHMGET shmdt(m->mem); + apr_current_userid(&uid, &gid, m->p); + shmbuf.shm_perm.uid =3D uid; + shmbuf.shm_perm.gid =3D gid; + + if (shmctl(m->file, IPC_RMID, &shmbuf) =3D=3D -1) + return errno; + + shmdt(m->list); + if (shmctl(m->listfd, IPC_RMID, &shmbuf) =3D=3D -1) + return errno; + #elif APR_USE_SHMEM_BEOS - delete_area(new_m->area_id); + delete_area(m->area_id); #endif =20 + m->offsets =3D NULL; + m->mem =3D NULL; + return APR_SUCCESS; } =20 APR_DECLARE(void *) apr_shm_malloc(apr_shmem_t *m, apr_size_t reqsize) { - void *new; - new =3D NULL; + memchunk_t *b =3D NULL; =20 apr_lock_acquire(m->lock); - /* Do we have enough space? */ - if (((char *)m->curmem - (char *)m->mem + reqsize) <=3D m->length) - { - new =3D m->curmem; - m->curmem =3D (char *)m->curmem + reqsize; - } + b =3D apr_shm_alloc_chunk(m, reqsize); apr_lock_release(m->lock); - return new; + + return ((b) ? APR_SHM_ADDR(m, b->offset) : NULL); } =20 APR_DECLARE(void *) apr_shm_calloc(apr_shmem_t *m, apr_size_t reqsize) = +{ + memchunk_t *b =3D NULL; + + apr_lock_acquire(m->lock); + b =3D apr_shm_alloc_chunk(m, reqsize); + if (b !=3D NULL) + memset(APR_SHM_ADDR(m, b->offset), 0, reqsize); + apr_lock_release(m->lock); + return ((b) ? APR_SHM_ADDR(m, b->offset) : NULL); +} + +APR_DECLARE(void *) apr_shm_realloc(apr_shmem_t *m, void *p, = apr_size_t reqsize) { - void *new =3D apr_shm_malloc(m, reqsize); - if (new) - memset(new, '\0', reqsize); - return new; + memchunk_t *b =3D NULL; + =20 + apr_lock_acquire(m->lock); + if (p !=3D NULL) + b =3D apr_shm_realloc_chunk(m, p, reqsize); + else=20 + b =3D apr_shm_alloc_chunk(m, reqsize); + apr_lock_release(m->lock); + + return ((b) ? APR_SHM_ADDR(m, b->offset) : NULL); } =20 -APR_DECLARE(apr_status_t) apr_shm_free(apr_shmem_t *shared, void = *entity) +APR_DECLARE(apr_status_t) apr_shm_free(apr_shmem_t *m, void *entity) { - /* Without a memory management scheme within our shared memory, it - * is impossible to implement free. */ + apr_lock_acquire(m->lock); + apr_shm_free_chunk(m, entity); + apr_lock_release(m->lock); return APR_SUCCESS; } =20 @@ -315,16 +362,16 @@ =20 APR_DECLARE(apr_status_t) apr_shm_avail(apr_shmem_t *m, apr_size_t = *size) { - apr_status_t status; + apr_status_t status =3D APR_ENOSHMAVAIL; =20 - status =3D APR_ENOSHMAVAIL; - - apr_lock_acquire(m->lock); + if (m) + { + apr_lock_acquire(m->lock); + if ((*size =3D m->offsets->shm_length) > 0) + status =3D APR_SUCCESS; =20 - *size =3D m->length - ((char *)m->curmem - (char *)m->mem); - if (*size) - status =3D APR_SUCCESS; + apr_lock_release(m->lock); + } =20 - apr_lock_release(m->lock); return status; } Index: shmem_lib.c =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D file: /home/cvspublic/apr/shmem/unix/shmem_lib.c,v #include "shmem.h" /* * Find the insert position in a sorted list of elements. The sorting = is * based on the value of the list[idx].offset. * Input : Begining index of the sorted list (*first), elem to be = inserted. * Return : The index before which the elem is to be inserted. */ index_t apr_shm_pos_in_sorted_list(apr_shmem_t *m, index_t first, = index_t elem) { index_t idx =3D first; if (idx < 0) return -1; do { if (m->list[idx].offset > m->list[elem].offset) break; idx =3D m->list[idx].next; } while ((idx >=3D 0) && (idx < APR_SHM_MAX_LIST_ELEM(m)) && (idx = !=3D first)); return idx; } /* * Adds a list element (elem) to the list pointed by *first. If *first = points * to a freelist, it finds the appropirate insert position else appends = elem * to the end of the list. * Ex. Adds a listelement to the usedlist / freelist. */ void apr_shm_addlist(apr_shmem_t *m, index_t *first, index_t elem) { index_t prev, idx; if (*first =3D=3D m->offsets->c_free) idx =3D apr_shm_pos_in_sorted_list(m, *first, elem); else idx =3D *first; if (idx =3D=3D -1) { idx =3D *first =3D elem; prev =3D elem; } else prev =3D m->list[idx].prev; m->list[idx].prev =3D elem; m->list[prev].next =3D elem; m->list[elem].prev =3D prev; m->list[elem].next =3D idx; if (idx =3D=3D m->offsets->c_free) *first =3D elem; } /* * Removes the elem from the list pointed to by *first. * Ex. Removes a listelement from freelist so that it can be added to = usedlist. */ void apr_shm_removelist(apr_shmem_t *m, index_t *first, index_t elem) { index_t prev, next; =20 next =3D m->list[elem].next; prev =3D m->list[elem].prev; m->list[prev].next =3D next; m->list[next].prev =3D prev; if (next =3D=3D elem) *first =3D -1; } /* * Frees a list element. This is useful during garbage collection. If 2 = list * elements can be merged (to form a bigger chunk), the 2nd list = element has * to be freed. The freed list element is made available at the end of = the * list. (No need for maintaining a seperate list of available = listelements) */ void apr_shm_freelist(apr_shmem_t *m, index_t elem) { index_t idx; if (elem >=3D 0) { idx =3D m->offsets->l_total - 1; memcpy(&m->list[elem], &m->list[idx], sizeof(memchunk_t)); m->list[m->list[idx].prev].next =3D idx;=20 m->list[m->list[idx].next].prev =3D idx; m->offsets->l_total--; } } /* * Splits a memory chunk into two. The second mem chunk is allocated a = list * element, and filled with updated memory offsets / size. */ index_t apr_shm_split_chunk(apr_shmem_t *m, index_t elem, apr_size_t = size) { index_t nelem =3D m->offsets->l_total; if (nelem > APR_SHM_MAX_LIST_ELEM(m)) return -1; m->list[nelem].size =3D m->list[elem].size - size; m->list[elem].size =3D size; m->list[nelem].offset =3D m->list[elem].offset + size; apr_shm_addlist(m, &m->offsets->c_free, nelem); m->offsets->l_total++; return nelem; } /* * Finds the list element for a givem memory chunk */ index_t apr_shm_find_by_addr(apr_shmem_t *m, index_t first, void *addr) { index_t idx =3D first; if (idx < 0) return -1; do { if (APR_SHM_ADDR(m, m->list[idx].offset) =3D=3D addr) return idx; } while ((idx >=3D 0) && ((idx =3D m->list[idx].next) !=3D first)); return -1; } /* * Finds a list element that best satisfies the memory requirement. If = there's * no best match available, it splits the memchunk into two. */ index_t apr_shm_find_by_size(apr_shmem_t *m, index_t first, apr_size_t = size) { apr_size_t diff =3D -1; index_t idx =3D first, found =3D -1; if (idx < 0) return -1; do { if (m->list[idx].size =3D=3D size) return idx; if (m->list[idx].size > size) { if ((diff =3D=3D -1) || ((m->list[idx].size - size) < = diff)) { diff =3D m->list[idx].size - size; found =3D idx; } } } while ((idx >=3D 0) && (idx =3D m->list[idx].next) !=3D first); if (diff > MIN_BLK_SIZE) m->offsets->c_free =3D apr_shm_split_chunk(m, found, size); return found; } /* * Allocates a memory chunk. This also requires a list element to be = allocated. * It first checks the freelist to see if any match is available. If = none are * available, it allocates a new listelement. It adds the new = listelement * to the c_used list. */ memchunk_t *apr_shm_alloc_chunk(apr_shmem_t *m, apr_size_t size) { index_t idx; size =3D ROUND_UP(size); if (m->offsets->shm_length < size) return NULL; idx =3D apr_shm_find_by_size(m, m->offsets->c_free, size); if (idx !=3D -1) apr_shm_removelist(m, &m->offsets->c_free, idx); else { idx =3D m->offsets->l_total; if (idx >=3D APR_SHM_MAX_LIST_ELEM(m)) return NULL; m->list[idx].offset =3D m->offsets->shm_offset; m->list[idx].size =3D size; m->offsets->shm_offset +=3D m->list[idx].size; m->offsets->l_total++; } m->offsets->shm_length -=3D m->list[idx].size; apr_shm_addlist(m, &m->offsets->c_used, idx); return (&m->list[idx]); } /* * Frees a memory chunk pointed by entity. It removes the corresponding = * listelement from the c_used list and appends it to the c_free list */ void apr_shm_free_chunk(apr_shmem_t *m, void *entity) { index_t idx; if (entity =3D=3D NULL) return; idx =3D apr_shm_find_by_addr(m, m->offsets->c_used, entity); if (idx !=3D -1) { m->offsets->shm_length +=3D m->list[idx].size; apr_shm_removelist(m, &m->offsets->c_used, idx); apr_shm_addlist(m, &m->offsets->c_free, idx); } } /* * Reallocates (resize) the memory pointed to by entity. It's not a = true * replacement of the realloc() - in the sense if the entity is not = found * in the used_list, it doesn't allocate a new memory of the requested = size */ memchunk_t *apr_shm_realloc_chunk(apr_shmem_t *m, void *entity, = apr_size_t size) { index_t idx; memchunk_t *new_b; size =3D ROUND_UP(size); idx =3D apr_shm_find_by_addr(m, m->offsets->c_used, entity); if (idx !=3D -1) { if (m->list[idx].size > size) m->offsets->c_free =3D apr_shm_split_chunk(m, idx, size); else if ((m->list[idx].size < size) &&=20 (size < m->offsets->shm_length)) { new_b =3D apr_shm_alloc_chunk(m, size); memcpy(APR_SHM_ADDR(m, new_b->offset), APR_SHM_ADDR(m, m->list[idx].offset), = m->list[idx].size); apr_shm_free_chunk(m, entity); idx =3D APR_SHM_LIST_INDEX(m,new_b); } } return ((idx >=3D 0) ? &m->list[idx] : NULL); } Index: shmem.h =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D file: /home/cvspublic/apr/shmem/unix/shmem.h,v #ifndef SHMEM_H #define SHMEM_H #include "apr_general.h" #include "apr_shmem.h" #include "apr_lock.h" #include "apr_portable.h" #include "apr_errno.h" #define APR_WANT_MEMFUNC #include "apr_want.h" #ifdef __cplusplus extern "C" { #endif /* * Currently, this code supports the following shared memory = techniques: * * - mmap on a temporary file * - mmap/shm_open on a temporary file (POSIX.1) * - mmap with MAP_ANON (4.4BSD) * - mmap /dev/zero (SVR4) * - shmget (SysV) * - create_area (BeOS) */ #if APR_USE_SHMEM_MMAP_TMP || APR_USE_SHMEM_MMAP_SHM || = APR_USE_SHMEM_MMAP_ZERO || APR_USE_SHMEM_MMAP_ANON #include #elif APR_USE_SHMEM_SHMGET #include #include #if !defined(SHM_R) #define SHM_R 0400 #endif #if !defined(SHM_W) #define SHM_W 0200 #endif #include #elif APR_USE_SHMEM_BEOS #include #endif #define MIN_BLK_SIZE 256 typedef apr_int32_t index_t; typedef struct memoffsets_t { index_t l_total; /* Index to start of Free list elements */ index_t c_used; /* Index to start of the used chunk list */ index_t c_free; /* Index to start of the freed chunk list */ apr_off_t shm_offset; /* The current offset of the shared memory = */ apr_size_t shm_length; /* Total length of shared memory available = */ } memoffsets_t; typedef struct memchunk_t { apr_off_t offset; /* Offset of the memory - from m->mem */ apr_size_t size; /* Size of the chunk */ index_t next; /* Index of Next chunk in the list */ index_t prev; /* Index of Previous chunk in the list*/ } memchunk_t; struct shmem_t { apr_pool_t *p; void *mem; /* Starting address of the shared memory */ memoffsets_t *offsets; /* Begining of the set of offsets */ memchunk_t *list; /* Begining of the list elements */ apr_size_t length; apr_size_t listlen; apr_lock_t *lock; char *filename; #if APR_USE_SHMEM_MMAP_TMP || APR_USE_SHMEM_MMAP_SHM || = APR_USE_SHMEM_MMAP_ZERO apr_file_t *file;=20 #elif APR_USE_SHMEM_SHMGET apr_os_file_t file; apr_os_file_t listfd; #elif APR_USE_SHMEM_BEOS area_id areaid;=20 #endif }; #define APR_SHM_ADDR(m,offset) ((offset < 0) ? (void *)NULL : \ (void *)((unsigned char *)m->mem + offset)) #define APR_SHM_LIST_INDEX(m,ptr) ((ptr =3D=3D NULL) ? -1 : \ (((unsigned char *)ptr - (unsigned char = *)m->list)/sizeof(memchunk_t))) #define APR_SHM_MAX_LIST_ELEM(m) ((m->length / MIN_BLK_SIZE) + 1) #define ROUND_UP(size) ((size < MIN_BLK_SIZE) ? MIN_BLK_SIZE : \ ((1 + ((size - 1) / sizeof (void *))) * sizeof = (void *))) memchunk_t *apr_shm_alloc_chunk(apr_shmem_t *m, apr_size_t size); memchunk_t *apr_shm_realloc_chunk(apr_shmem_t *m, void *entity, apr_size_t size); void apr_shm_free_chunk(apr_shmem_t *m, void *entity); index_t apr_shm_split_chunk(apr_shmem_t *m, index_t elem, apr_size_t = size); index_t apr_shm_pos_in_sorted_list(apr_shmem_t *m, index_t first, = index_t elem); index_t apr_shm_find_by_addr(apr_shmem_t *m, index_t first, void = *addr); index_t apr_shm_find_by_size(apr_shmem_t *m, index_t first, apr_size_t = size); void apr_shm_addlist(apr_shmem_t *m, index_t *first, index_t elem); void apr_shm_removelist(apr_shmem_t *m, index_t *first, index_t = elem); void apr_shm_freelist(apr_shmem_t *m, index_t elem); #ifdef __cplusplus } #endif #endif /* !SHMEM_H */ ------_=_NextPart_000_01C1674F.EDAF71E0--