Landed sourcepawn-1.2. The big changes:
1) JIT compilation/optimization now occurs per-function, and only when functions are first used. We're now officially a whole-method JIT rather than an AOT compiler (albiet, still a simple JIT). This has two implications: Functions are now much better abstracted internally, and loading a plugin is now much less expensive. If a function contains calls to other functions, THOSE functions are only compiled when they're invoked as well.
2) I've removed debug mode. We always show full backtraces now, as there was a very cheap way to implement this which really cleaned up everything. This is great for a number of reasons -- there's less code, the JIT is better designed, we don't need to relocate debug tables, and best of all we no longer have to tell users to enable debug mode at their own expense.
3 * =============================================================================
5 * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved.
6 * =============================================================================
8 * This program is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU General Public License, version 3.0, as published by the
10 * Free Software Foundation.
12 * This program is distributed in the hope that it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
14 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
17 * You should have received a copy of the GNU General Public License along with
18 * this program. If not, see <http://www.gnu.org/licenses/>.
20 * As a special exception, AlliedModders LLC gives you permission to link the
21 * code of this program (as well as its derivative works) to "Half-Life 2," the
22 * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
23 * by the Valve Corporation. You must obey the GNU General Public License in
24 * all respects for all other code used. Additionally, AlliedModders LLC grants
25 * this exception to all derivative works. AlliedModders LLC defines further
26 * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
27 * or <http://www.sourcemod.net/license.php>.
32 #include "HandleSys.h"
34 #include "PluginSys.h"
35 #include "ExtensionSys.h"
39 #include "sm_stringutil.h"
41 HandleSystem g_HandleSys;
43 QHandle *ignore_handle;
45 inline HandleType_t TypeParent(HandleType_t type)
47 return (type & ~HANDLESYS_SUBTYPE_MASK);
50 inline HandleError IdentityHandle(IdentityToken_t *token, unsigned int *index)
52 return g_HandleSys.GetHandle(token->ident, g_ShareSys.GetIdentRoot(), &ignore_handle, index);
55 HandleSystem::HandleSystem()
57 m_Handles = new QHandle[HANDLESYS_MAX_HANDLES + 1];
58 memset(m_Handles, 0, sizeof(QHandle) * (HANDLESYS_MAX_HANDLES + 1));
60 m_Types = new QHandleType[HANDLESYS_TYPEARRAY_SIZE];
61 memset(m_Types, 0, sizeof(QHandleType) * HANDLESYS_TYPEARRAY_SIZE);
63 m_TypeLookup = sm_trie_create();
64 m_strtab = new BaseStringTable(512);
69 HandleSystem::~HandleSystem()
73 sm_trie_destroy(m_TypeLookup);
78 HandleType_t HandleSystem::CreateType(const char *name,
79 IHandleTypeDispatch *dispatch,
81 const TypeAccess *typeAccess,
82 const HandleAccess *hndlAccess,
83 IdentityToken_t *ident,
90 *err = HandleError_Parameter;
95 if (typeAccess && typeAccess->hsVersion > SMINTERFACE_HANDLESYSTEM_VERSION)
99 *err = HandleError_Version;
104 if (hndlAccess && hndlAccess->hsVersion > SMINTERFACE_HANDLESYSTEM_VERSION)
108 *err = HandleError_Version;
113 bool isChild = false;
118 if (parent & HANDLESYS_SUBTYPE_MASK)
122 *err = HandleError_NoInherit;
126 if (parent >= HANDLESYS_TYPEARRAY_SIZE
127 || m_Types[parent].dispatch == NULL)
131 *err = HandleError_Parameter;
135 if (m_Types[parent].typeSec.access[HTypeAccess_Inherit] == false
136 && (m_Types[parent].typeSec.ident != ident))
140 *err = HandleError_Access;
146 if (name && name[0] != '\0')
148 if (sm_trie_retrieve(m_TypeLookup, name, NULL))
152 *err = HandleError_Parameter;
162 QHandleType *pParent = &m_Types[parent];
163 if (pParent->children >= HANDLESYS_MAX_SUBTYPES)
167 *err = HandleError_Limit;
172 for (unsigned int i=1; i<=HANDLESYS_MAX_SUBTYPES; i++)
174 if (m_Types[parent + i].dispatch == NULL)
184 *err = HandleError_Limit;
190 if (m_FreeTypes == 0)
192 /* Reserve another index */
193 if (m_TypeTail >= HANDLESYS_TYPEARRAY_SIZE)
197 *err = HandleError_Limit;
201 m_TypeTail += (HANDLESYS_MAX_SUBTYPES + 1);
205 /* The "free array" is compacted into the normal array for easiness */
206 index = m_Types[m_FreeTypes--].freeID;
210 QHandleType *pType = &m_Types[index];
212 pType->dispatch = dispatch;
213 if (name && name[0] != '\0')
215 pType->nameIdx = m_strtab->AddString(name);
216 sm_trie_insert(m_TypeLookup, name, (void *)pType);
225 pType->typeSec = *typeAccess;
227 InitAccessDefaults(&pType->typeSec, NULL);
228 pType->typeSec.ident = ident;
233 pType->hndlSec = *hndlAccess;
235 InitAccessDefaults(NULL, &pType->hndlSec);
246 bool HandleSystem::FindHandleType(const char *name, HandleType_t *type)
250 if (!sm_trie_retrieve(m_TypeLookup, name, (void **)&_type))
255 unsigned int offset = _type - m_Types;
265 HandleError HandleSystem::TryAllocHandle(unsigned int *handle)
267 if (m_FreeHandles == 0)
269 if (m_HandleTail >= HANDLESYS_MAX_HANDLES)
271 return HandleError_Limit;;
273 *handle = ++m_HandleTail;
277 *handle = m_Handles[m_FreeHandles--].freeID;
280 return HandleError_None;
283 HandleError HandleSystem::MakePrimHandle(HandleType_t type,
284 QHandle **in_pHandle,
285 unsigned int *in_index,
287 IdentityToken_t *owner,
291 unsigned int owner_index = 0;
293 if (owner && (IdentityHandle(owner, &owner_index) != HandleError_None))
295 return HandleError_Identity;
299 if ((err = TryAllocHandle(&handle)) != HandleError_None)
301 if (!TryAndFreeSomeHandles()
302 || (err = TryAllocHandle(&handle)) != HandleError_None)
308 QHandle *pHandle = &m_Handles[handle];
310 assert(pHandle->set == false);
312 if (++m_HSerial >= HANDLESYS_MAX_SERIALS)
317 /* Set essential information */
318 pHandle->set = identity ? HandleSet_Identity : HandleSet_Used;
319 pHandle->refcount = 1;
320 pHandle->type = type;
321 pHandle->serial = m_HSerial;
322 pHandle->owner = owner;
323 pHandle->ch_next = 0;
324 pHandle->access_special = false;
325 pHandle->is_destroying = false;
327 /* Create the hash value */
328 Handle_t hash = pHandle->serial;
332 /* Add a reference count to the type */
333 m_Types[type].opened++;
336 *in_pHandle = pHandle;
340 /* Decode the identity token
341 * For now, we don't allow nested ownership
343 if (owner && !identity)
345 QHandle *pIdentity = &m_Handles[owner_index];
346 if (pIdentity->ch_prev == 0)
348 pIdentity->ch_prev = handle;
349 pIdentity->ch_next = handle;
350 pHandle->ch_prev = 0;
354 /* Link previous node to us (forward) */
355 m_Handles[pIdentity->ch_next].ch_next = handle;
356 /* Link us to previous node (backwards) */
357 pHandle->ch_prev = pIdentity->ch_next;
359 pIdentity->ch_next = handle;
361 pIdentity->refcount++;
365 pHandle->ch_prev = 0;
368 return HandleError_None;
371 void HandleSystem::SetTypeSecurityOwner(HandleType_t type, IdentityToken_t *pToken)
374 || type >= HANDLESYS_TYPEARRAY_SIZE
375 || m_Types[type].dispatch == NULL)
380 m_Types[type].typeSec.ident = pToken;
383 Handle_t HandleSystem::CreateHandleInt(HandleType_t type,
385 const HandleSecurity *pSec,
387 const HandleAccess *pAccess,
390 IdentityToken_t *ident;
391 IdentityToken_t *owner;
395 ident = pSec->pIdentity;
396 owner = pSec->pOwner;
403 || type >= HANDLESYS_TYPEARRAY_SIZE
404 || m_Types[type].dispatch == NULL)
408 *err = HandleError_Parameter;
413 /* Check to see if we're allowed to create this handle type */
414 QHandleType *pType = &m_Types[type];
415 if (!pType->typeSec.access[HTypeAccess_Create]
416 && (!pType->typeSec.ident
417 || pType->typeSec.ident != ident))
421 *err = HandleError_Access;
431 if ((_err=MakePrimHandle(type, &pHandle, &index, &handle, owner, identity)) != HandleError_None)
442 pHandle->access_special = true;
443 pHandle->sec = *pAccess;
446 pHandle->object = object;
452 Handle_t HandleSystem::CreateHandleEx(HandleType_t type, void *object, const HandleSecurity *pSec, const HandleAccess *pAccess, HandleError *err)
454 return CreateHandleInt(type, object, pSec, err, pAccess, false);
457 Handle_t HandleSystem::CreateHandle(HandleType_t type, void *object, IdentityToken_t *owner, IdentityToken_t *ident, HandleError *err)
461 sec.pIdentity = ident;
464 return CreateHandleEx(type, object, &sec, NULL, err);
467 bool HandleSystem::TypeCheck(HandleType_t intype, HandleType_t outtype)
469 /* Check the type inheritance */
470 if (intype & HANDLESYS_SUBTYPE_MASK)
472 if (intype != outtype
473 && (TypeParent(intype) != TypeParent(outtype)))
478 if (intype != outtype)
487 HandleError HandleSystem::GetHandle(Handle_t handle,
488 IdentityToken_t *ident,
489 QHandle **in_pHandle,
490 unsigned int *in_index,
493 unsigned int serial = (handle >> 16);
494 unsigned int index = (handle & HANDLESYS_HANDLE_MASK);
496 if (index == 0 || index == 0xFFFF)
498 return HandleError_Index;
501 QHandle *pHandle = &m_Handles[index];
504 || (pHandle->set == HandleSet_Freed && !ignoreFree))
506 return HandleError_Freed;
507 } else if (pHandle->set == HandleSet_Identity
508 && ident != g_ShareSys.GetIdentRoot())
510 /* Only IdentityHandle() can read this! */
511 return HandleError_Identity;
513 if (pHandle->serial != serial)
515 return HandleError_Changed;
518 *in_pHandle = pHandle;
521 return HandleError_None;
524 bool HandleSystem::CheckAccess(QHandle *pHandle, HandleAccessRight right, const HandleSecurity *pSecurity)
526 QHandleType *pType = &m_Types[pHandle->type];
529 if (pHandle->access_special)
531 access = pHandle->sec.access[right];
533 access = pType->hndlSec.access[right];
536 /* Check if the type's identity matches */
537 if (access & HANDLE_RESTRICT_IDENTITY)
539 IdentityToken_t *owner = pType->typeSec.ident;
541 || (!pSecurity || pSecurity->pIdentity != owner))
547 /* Check if the owner is allowed */
548 if (access & HANDLE_RESTRICT_OWNER)
550 IdentityToken_t *owner = pHandle->owner;
552 && (!pSecurity || pSecurity->pOwner != owner))
561 HandleError HandleSystem::CloneHandle(QHandle *pHandle, unsigned int index, Handle_t *newhandle, IdentityToken_t *newOwner)
563 /* Get a new Handle ID */
564 unsigned int new_index;
569 if ((err=MakePrimHandle(pHandle->type, &pNewHandle, &new_index, &new_handle, newOwner)) != HandleError_None)
574 /* Assign permissions from parent */
575 if (pHandle->access_special)
577 pNewHandle->access_special = true;
578 pNewHandle->sec = pHandle->sec;
581 pNewHandle->clone = index;
584 *newhandle = new_handle;
586 return HandleError_None;
589 HandleError HandleSystem::CloneHandle(Handle_t handle, Handle_t *newhandle, IdentityToken_t *newOwner, const HandleSecurity *pSecurity)
594 IdentityToken_t *ident = pSecurity ? pSecurity->pIdentity : NULL;
596 if ((err=GetHandle(handle, ident, &pHandle, &index)) != HandleError_None)
601 /* Identities cannot be cloned */
602 if (pHandle->set == HandleSet_Identity)
604 return HandleError_Identity;
607 /* Check if the handle can be cloned */
608 if (!CheckAccess(pHandle, HandleAccess_Clone, pSecurity))
610 return HandleError_Access;
613 /* Make sure we're not cloning a clone */
616 QHandle *pParent = &m_Handles[pHandle->clone];
617 return CloneHandle(pParent, pHandle->clone, newhandle, newOwner);
620 return CloneHandle(pHandle, index, newhandle, newOwner);
623 HandleError HandleSystem::FreeHandle(QHandle *pHandle, unsigned int index)
625 QHandleType *pType = &m_Types[pHandle->type];
629 /* If we're a clone, decrease the parent reference count */
633 /* Note that if we ever have per-handle security, we would need to re-check
634 * the access on this Handle. */
635 master = pHandle->clone;
636 pMaster = &m_Handles[master];
638 /* Release the clone now */
639 ReleasePrimHandle(index);
641 /* Decrement the master's reference count */
642 if (--pMaster->refcount == 0)
644 /* Type should be the same but do this anyway... */
645 pType = &m_Types[pMaster->type];
646 pMaster->is_destroying = true;
647 pType->dispatch->OnHandleDestroy(pMaster->type, pMaster->object);
648 ReleasePrimHandle(master);
650 } else if (pHandle->set == HandleSet_Identity) {
651 /* If we're an identity, skip all this stuff!
652 * NOTE: SHARESYS DOES NOT CARE ABOUT THE DESTRUCTOR
654 ReleasePrimHandle(index);
656 /* Decrement, free if necessary */
657 if (--pHandle->refcount == 0)
659 pHandle->is_destroying = true;
660 pType->dispatch->OnHandleDestroy(pHandle->type, pHandle->object);
661 ReleasePrimHandle(index);
663 /* We must be cloned, so mark ourselves as freed */
664 pHandle->set = HandleSet_Freed;
665 /* Now, unlink us, so we're not being tracked by the owner */
668 UnlinkHandleFromOwner(pHandle, index);
673 return HandleError_None;
676 HandleError HandleSystem::FreeHandle(Handle_t handle, const HandleSecurity *pSecurity)
681 IdentityToken_t *ident = pSecurity ? pSecurity->pIdentity : NULL;
683 if ((err=GetHandle(handle, ident, &pHandle, &index)) != HandleError_None)
688 if (!CheckAccess(pHandle, HandleAccess_Delete, pSecurity))
690 return HandleError_Access;
693 if (pHandle->is_destroying)
695 /* Someone tried to free this recursively.
696 * We'll just ignore this safely.
698 return HandleError_None;
701 return FreeHandle(pHandle, index);
704 HandleError HandleSystem::ReadHandle(Handle_t handle, HandleType_t type, const HandleSecurity *pSecurity, void **object)
709 IdentityToken_t *ident = pSecurity ? pSecurity->pIdentity : NULL;
711 if ((err=GetHandle(handle, ident, &pHandle, &index)) != HandleError_None)
716 if (!CheckAccess(pHandle, HandleAccess_Read, pSecurity))
718 return HandleError_Access;
721 /* Check the type inheritance */
722 if (pHandle->type & HANDLESYS_SUBTYPE_MASK)
724 if (pHandle->type != type
725 && (TypeParent(pHandle->type) != TypeParent(type)))
727 return HandleError_Type;
730 if (pHandle->type != type)
732 return HandleError_Type;
738 /* if we're a clone, the rules change - object is ONLY in our reference */
741 pHandle = &m_Handles[pHandle->clone];
743 *object = pHandle->object;
746 return HandleError_None;
749 void HandleSystem::UnlinkHandleFromOwner(QHandle *pHandle, unsigned int index)
751 /* Unlink us if necessary */
752 unsigned int ident_index;
753 if (IdentityHandle(pHandle->owner, &ident_index) != HandleError_None)
756 assert(pHandle->owner == 0);
759 /* Note that since 0 is an invalid handle, if any of these links are 0,
760 * the data can still be set.
762 QHandle *pIdentity = &m_Handles[ident_index];
764 /* Unlink case: We're the head AND tail node */
765 if (index == pIdentity->ch_prev && index == pIdentity->ch_next)
767 pIdentity->ch_prev = 0;
768 pIdentity->ch_next = 0;
770 /* Unlink case: We're the head node */
771 else if (index == pIdentity->ch_prev) {
772 /* Link us to the next in the chain */
773 pIdentity->ch_prev = pHandle->ch_next;
774 /* Patch up the previous link */
775 m_Handles[pHandle->ch_next].ch_prev = 0;
777 /* Unlink case: We're the tail node */
778 else if (index == pIdentity->ch_next) {
779 /* Link us to the previous in the chain */
780 pIdentity->ch_next = pHandle->ch_prev;
781 /* Patch up the next link */
782 m_Handles[pHandle->ch_prev].ch_next = 0;
784 /* Unlink case: We're in the middle! */
786 /* Patch the forward reference */
787 m_Handles[pHandle->ch_next].ch_prev = pHandle->ch_prev;
788 /* Patch the backward reference */
789 m_Handles[pHandle->ch_prev].ch_next = pHandle->ch_next;
792 /* Lastly, decrease the reference count */
793 pIdentity->refcount--;
796 void HandleSystem::ReleasePrimHandle(unsigned int index)
798 QHandle *pHandle = &m_Handles[index];
799 HandleSet set = pHandle->set;
801 if (pHandle->owner && (set != HandleSet_Identity))
803 UnlinkHandleFromOwner(pHandle, index);
806 /* Were we an identity ourself? */
808 if (set == HandleSet_Identity)
810 /* Extra work to do. We need to find everything connected to this identity and release it. */
811 unsigned int ch_index;
813 unsigned int old_index = 0;
815 while ((ch_index = pHandle->ch_next) != 0)
817 pLocal = &m_Handles[ch_index];
819 assert(old_index != ch_index);
820 assert(pLocal->set == HandleSet_Used);
821 old_index = ch_index;
823 FreeHandle(pLocal, ch_index);
827 pHandle->set = HandleSet_None;
828 m_Types[pHandle->type].opened--;
829 m_Handles[++m_FreeHandles].freeID = index;
832 bool HandleSystem::RemoveType(HandleType_t type, IdentityToken_t *ident)
834 if (type == 0 || type >= HANDLESYS_TYPEARRAY_SIZE)
839 QHandleType *pType = &m_Types[type];
841 if (pType->typeSec.ident
842 && pType->typeSec.ident != ident)
847 if (pType->dispatch == NULL)
852 /* Remove children if we have to */
853 if (!(type & HANDLESYS_SUBTYPE_MASK))
855 QHandleType *childType;
856 for (unsigned int i=1; i<=HANDLESYS_MAX_SUBTYPES; i++)
858 childType = &m_Types[type + i];
859 if (childType->dispatch)
861 RemoveType(type + i, childType->typeSec.ident);
864 /* Link us into the free chain */
865 m_Types[++m_FreeTypes].freeID = type;
868 /* Invalidate the type now */
869 IHandleTypeDispatch *dispatch = pType->dispatch;
870 pType->dispatch = NULL;
872 /* Make sure nothing is using this type. */
876 for (unsigned int i=1; i<=m_HandleTail; i++)
878 pHandle = &m_Handles[i];
879 if (!pHandle->set || pHandle->type != type)
886 QHandle *pOther = &m_Handles[pHandle->clone];
887 if (--pOther->refcount == 0)
890 dispatch->OnHandleDestroy(type, pOther->object);
891 ReleasePrimHandle(pHandle->clone);
893 /* Unlink ourselves since we don't have a reference count */
894 ReleasePrimHandle(i);
896 /* If it's not a clone, we still have to check the reference count.
897 * Either way, we'll be destroyed eventually because the handle types do not change.
899 if (--pHandle->refcount == 0)
902 dispatch->OnHandleDestroy(type, pHandle->object);
903 ReleasePrimHandle(i);
906 if (pType->opened == 0)
916 bool HandleSystem::InitAccessDefaults(TypeAccess *pTypeAccess, HandleAccess *pHandleAccess)
920 if (pTypeAccess->hsVersion > SMINTERFACE_HANDLESYSTEM_VERSION)
924 pTypeAccess->access[HTypeAccess_Create] = false;
925 pTypeAccess->access[HTypeAccess_Inherit] = false;
926 pTypeAccess->ident = NULL;
931 if (pHandleAccess->hsVersion > SMINTERFACE_HANDLESYSTEM_VERSION)
936 pHandleAccess->access[HandleAccess_Clone] = 0;
937 pHandleAccess->access[HandleAccess_Delete] = HANDLE_RESTRICT_OWNER;
938 pHandleAccess->access[HandleAccess_Read] = HANDLE_RESTRICT_IDENTITY;
944 bool HandleSystem::TryAndFreeSomeHandles()
946 IPluginIterator *pl_iter = g_PluginSys.GetPluginIterator();
947 IPlugin *highest_owner = NULL;
948 unsigned int highest_handle_count = 0;
950 /* Search all plugins */
951 while (pl_iter->MorePlugins())
953 IPlugin *plugin = pl_iter->GetPlugin();
954 IdentityToken_t *identity = plugin->GetIdentity();
955 unsigned int handle_count = 0;
957 if (identity == NULL)
962 /* Search all handles */
963 for (unsigned int i = 1; i <= m_HandleTail; i++)
965 if (m_Handles[i].set != HandleSet_Used)
969 if (m_Handles[i].owner == identity)
975 if (handle_count > highest_handle_count)
977 highest_owner = plugin;
978 highest_handle_count = handle_count;
981 pl_iter->NextPlugin();
984 if (highest_owner == NULL || highest_handle_count == 0)
989 g_Logger.LogFatal("[SM] MEMORY LEAK DETECTED IN PLUGIN (file \"%s\")", highest_owner->GetFilename());
990 g_Logger.LogFatal("[SM] Reloading plugin to free %d handles.", highest_handle_count);
991 g_Logger.LogFatal("[SM] Contact the author(s) of this plugin to correct this error.", highest_handle_count);
993 highest_owner->GetBaseContext()->ThrowNativeErrorEx(SP_ERROR_MEMACCESS, "Memory leak");
995 return g_PluginSys.UnloadPlugin(highest_owner);
998 void HandleSystem::Dump(HANDLE_REPORTER rep)
1000 unsigned int total_size = 0;
1001 rep("%-10.10s\t%-20.20s\t%-20.20s\t%-10.10s", "Handle", "Owner", "Type", "Memory");
1002 rep("--------------------------------------------------------------------------");
1003 for (unsigned int i = 1; i <= m_HandleTail; i++)
1005 if (m_Handles[i].set != HandleSet_Used)
1010 unsigned int index = (m_Handles[i].serial << 16) | i;
1011 /* Determine the owner */
1012 const char *owner = "UNKNOWN";
1013 if (m_Handles[i].owner)
1015 IdentityToken_t *pOwner = m_Handles[i].owner;
1016 if (pOwner == g_pCoreIdent)
1020 else if (pOwner == g_PluginSys.GetIdentity())
1022 owner = "PLUGINSYS";
1026 CExtension *ext = g_Extensions.GetExtensionFromIdent(pOwner);
1029 owner = ext->GetFilename();
1033 CPlugin *pPlugin = g_PluginSys.GetPluginFromIdentity(pOwner);
1036 owner = pPlugin->GetFilename();
1045 const char *type = "ANON";
1046 QHandleType *pType = &m_Types[m_Handles[i].type];
1047 unsigned int size = 0;
1048 if (pType->nameIdx != -1)
1050 type = m_strtab->GetString(pType->nameIdx);
1052 if (pType->dispatch->GetDispatchVersion() < HANDLESYS_MEMUSAGE_MIN_VERSION
1053 || !pType->dispatch->GetHandleApproxSize(m_Handles[i].type, m_Handles[i].object, &size))
1055 rep("0x%08x\t%-20.20s\t%-20.20s\t%-10.10s", index, owner, type, "-1");
1060 UTIL_Format(buffer, sizeof(buffer), "%d", size);
1061 rep("0x%08x\t%-20.20s\t%-20.20s\t%-10.10s", index, owner, type, buffer);
1065 rep("-- Approximately %d bytes of memory are in use by Handles.\n", total_size);