aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/Region/CoreModules
diff options
context:
space:
mode:
authorJustin Clark-Casey (justincc)2013-03-05 23:47:36 +0000
committerJustin Clark-Casey (justincc)2013-03-05 23:47:36 +0000
commitccd6f443e1092cb410f565e921f7cf4dd8cd2dac (patch)
treee033f15e5498655b9184f4818b1e415a9f545501 /OpenSim/Region/CoreModules
parentFix issue in the mesh upload flag module where the ID of the last agent to re... (diff)
downloadopensim-SC_OLD-ccd6f443e1092cb410f565e921f7cf4dd8cd2dac.zip
opensim-SC_OLD-ccd6f443e1092cb410f565e921f7cf4dd8cd2dac.tar.gz
opensim-SC_OLD-ccd6f443e1092cb410f565e921f7cf4dd8cd2dac.tar.bz2
opensim-SC_OLD-ccd6f443e1092cb410f565e921f7cf4dd8cd2dac.tar.xz
Get attachment script state before taking sp.AttachmentsSyncLock() to avoid race conditions between closing agents and scripts that may be doing attachment manipulation.
This is in an effort to resolve http://opensimulator.org/mantis/view.php?id=6557
Diffstat (limited to 'OpenSim/Region/CoreModules')
-rw-r--r--OpenSim/Region/CoreModules/Avatar/Attachments/AttachmentsModule.cs301
1 files changed, 161 insertions, 140 deletions
diff --git a/OpenSim/Region/CoreModules/Avatar/Attachments/AttachmentsModule.cs b/OpenSim/Region/CoreModules/Avatar/Attachments/AttachmentsModule.cs
index 8a3eeaa..3ccf9f4 100644
--- a/OpenSim/Region/CoreModules/Avatar/Attachments/AttachmentsModule.cs
+++ b/OpenSim/Region/CoreModules/Avatar/Attachments/AttachmentsModule.cs
@@ -241,12 +241,27 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments
241 241
242// m_log.DebugFormat("[ATTACHMENTS MODULE]: Saving changed attachments for {0}", sp.Name); 242// m_log.DebugFormat("[ATTACHMENTS MODULE]: Saving changed attachments for {0}", sp.Name);
243 243
244 List<SceneObjectGroup> attachments = sp.GetAttachments();
245
246 if (attachments.Count <= 0)
247 return;
248
249 Dictionary<SceneObjectGroup, string> scriptStates = new Dictionary<SceneObjectGroup, string>();
250
251 foreach (SceneObjectGroup so in attachments)
252 {
253 // Scripts MUST be snapshotted before the object is
254 // removed from the scene because doing otherwise will
255 // clobber the run flag
256 // This must be done outside the sp.AttachmentSyncLock so that there is no risk of a deadlock from
257 // scripts performing attachment operations at the same time. Getting object states stops the scripts.
258 scriptStates[so] = PrepareScriptInstanceForSave(so, false);
259 }
260
244 lock (sp.AttachmentsSyncLock) 261 lock (sp.AttachmentsSyncLock)
245 { 262 {
246 foreach (SceneObjectGroup so in sp.GetAttachments()) 263 foreach (SceneObjectGroup so in attachments)
247 { 264 UpdateDetachedObject(sp, so, scriptStates[so]);
248 UpdateDetachedObject(sp, so);
249 }
250 265
251 sp.ClearAttachments(); 266 sp.ClearAttachments();
252 } 267 }
@@ -285,32 +300,40 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments
285 300
286 private bool AttachObjectInternal(IScenePresence sp, SceneObjectGroup group, uint attachmentPt, bool silent, bool temp) 301 private bool AttachObjectInternal(IScenePresence sp, SceneObjectGroup group, uint attachmentPt, bool silent, bool temp)
287 { 302 {
288 lock (sp.AttachmentsSyncLock)
289 {
290// m_log.DebugFormat( 303// m_log.DebugFormat(
291// "[ATTACHMENTS MODULE]: Attaching object {0} {1} to {2} point {3} from ground (silent = {4})", 304// "[ATTACHMENTS MODULE]: Attaching object {0} {1} to {2} point {3} from ground (silent = {4})",
292// group.Name, group.LocalId, sp.Name, attachmentPt, silent); 305// group.Name, group.LocalId, sp.Name, attachmentPt, silent);
293 306
294 if (group.GetSittingAvatarsCount() != 0) 307 if (group.GetSittingAvatarsCount() != 0)
295 { 308 {
296// m_log.WarnFormat( 309// m_log.WarnFormat(
297// "[ATTACHMENTS MODULE]: Ignoring request to attach {0} {1} to {2} on {3} since {4} avatars are still sitting on it", 310// "[ATTACHMENTS MODULE]: Ignoring request to attach {0} {1} to {2} on {3} since {4} avatars are still sitting on it",
298// group.Name, group.LocalId, sp.Name, attachmentPt, group.GetSittingAvatarsCount()); 311// group.Name, group.LocalId, sp.Name, attachmentPt, group.GetSittingAvatarsCount());
299 312
300 return false; 313 return false;
301 } 314 }
302 315
303 if (sp.GetAttachments(attachmentPt).Contains(group)) 316 if (sp.GetAttachments(attachmentPt).Contains(group))
304 { 317 {
305 // m_log.WarnFormat( 318// m_log.WarnFormat(
306 // "[ATTACHMENTS MODULE]: Ignoring request to attach {0} {1} to {2} on {3} since it's already attached", 319// "[ATTACHMENTS MODULE]: Ignoring request to attach {0} {1} to {2} on {3} since it's already attached",
307 // group.Name, group.LocalId, sp.Name, AttachmentPt); 320// group.Name, group.LocalId, sp.Name, AttachmentPt);
308 321
309 return false; 322 return false;
310 } 323 }
311 324
325 // Remove any previous attachments
326 List<SceneObjectGroup> existingAttachments = sp.GetAttachments(attachmentPt);
327 string existingAttachmentScriptState = null;
328
329 // At the moment we can only deal with a single attachment
330 if (existingAttachments.Count != 0 && existingAttachments[0].FromItemID != UUID.Zero)
331 DetachSingleAttachmentToInv(sp, group);
332
333 lock (sp.AttachmentsSyncLock)
334 {
312 Vector3 attachPos = group.AbsolutePosition; 335 Vector3 attachPos = group.AbsolutePosition;
313 336
314 // TODO: this short circuits multiple attachments functionality in LL viewer 2.1+ and should 337 // TODO: this short circuits multiple attachments functionality in LL viewer 2.1+ and should
315 // be removed when that functionality is implemented in opensim 338 // be removed when that functionality is implemented in opensim
316 attachmentPt &= 0x7f; 339 attachmentPt &= 0x7f;
@@ -322,14 +345,14 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments
322 { 345 {
323 attachPos = Vector3.Zero; 346 attachPos = Vector3.Zero;
324 } 347 }
325 348
326 // AttachmentPt 0 means the client chose to 'wear' the attachment. 349 // AttachmentPt 0 means the client chose to 'wear' the attachment.
327 if (attachmentPt == 0) 350 if (attachmentPt == 0)
328 { 351 {
329 // Check object for stored attachment point 352 // Check object for stored attachment point
330 attachmentPt = group.AttachmentPoint; 353 attachmentPt = group.AttachmentPoint;
331 } 354 }
332 355
333 // if we still didn't find a suitable attachment point....... 356 // if we still didn't find a suitable attachment point.......
334 if (attachmentPt == 0) 357 if (attachmentPt == 0)
335 { 358 {
@@ -337,13 +360,13 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments
337 attachmentPt = (uint)AttachmentPoint.LeftHand; 360 attachmentPt = (uint)AttachmentPoint.LeftHand;
338 attachPos = Vector3.Zero; 361 attachPos = Vector3.Zero;
339 } 362 }
340 363
341 group.AttachmentPoint = attachmentPt; 364 group.AttachmentPoint = attachmentPt;
342 group.AbsolutePosition = attachPos; 365 group.AbsolutePosition = attachPos;
343 366
344 if (sp.PresenceType != PresenceType.Npc) 367 if (sp.PresenceType != PresenceType.Npc)
345 UpdateUserInventoryWithAttachment(sp, group, attachmentPt, temp); 368 UpdateUserInventoryWithAttachment(sp, group, attachmentPt, temp);
346 369
347 AttachToAgent(sp, group, attachmentPt, attachPos, silent); 370 AttachToAgent(sp, group, attachmentPt, attachPos, silent);
348 } 371 }
349 372
@@ -352,21 +375,6 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments
352 375
353 private void UpdateUserInventoryWithAttachment(IScenePresence sp, SceneObjectGroup group, uint attachmentPt, bool temp) 376 private void UpdateUserInventoryWithAttachment(IScenePresence sp, SceneObjectGroup group, uint attachmentPt, bool temp)
354 { 377 {
355 // Remove any previous attachments
356 List<SceneObjectGroup> attachments = sp.GetAttachments(attachmentPt);
357
358 // At the moment we can only deal with a single attachment
359 if (attachments.Count != 0)
360 {
361 if (attachments[0].FromItemID != UUID.Zero)
362 DetachSingleAttachmentToInvInternal(sp, attachments[0]);
363 // Error logging commented because UUID.Zero now means temp attachment
364// else
365// m_log.WarnFormat(
366// "[ATTACHMENTS MODULE]: When detaching existing attachment {0} {1} at point {2} to make way for {3} {4} for {5}, couldn't find the associated item ID to adjust inventory attachment record!",
367// attachments[0].Name, attachments[0].LocalId, attachmentPt, group.Name, group.LocalId, sp.Name);
368 }
369
370 // Add the new attachment to inventory if we don't already have it. 378 // Add the new attachment to inventory if we don't already have it.
371 if (!temp) 379 if (!temp)
372 { 380 {
@@ -426,12 +434,10 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments
426 return; 434 return;
427 435
428 // m_log.DebugFormat("[ATTACHMENTS MODULE]: Rezzing multiple attachments from inventory for {0}", sp.Name); 436 // m_log.DebugFormat("[ATTACHMENTS MODULE]: Rezzing multiple attachments from inventory for {0}", sp.Name);
429 lock (sp.AttachmentsSyncLock) 437
438 foreach (KeyValuePair<UUID, uint> rez in rezlist)
430 { 439 {
431 foreach (KeyValuePair<UUID, uint> rez in rezlist) 440 RezSingleAttachmentFromInventory(sp, rez.Key, rez.Value);
432 {
433 RezSingleAttachmentFromInventory(sp, rez.Key, rez.Value);
434 }
435 } 441 }
436 } 442 }
437 443
@@ -511,25 +517,33 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments
511 517
512 public void DetachSingleAttachmentToInv(IScenePresence sp, SceneObjectGroup so) 518 public void DetachSingleAttachmentToInv(IScenePresence sp, SceneObjectGroup so)
513 { 519 {
520 if (so.AttachedAvatar != sp.UUID)
521 {
522 m_log.WarnFormat(
523 "[ATTACHMENTS MODULE]: Tried to detach object {0} from {1} {2} but attached avatar id was {3} in {4}",
524 so.Name, sp.Name, sp.UUID, so.AttachedAvatar, m_scene.RegionInfo.RegionName);
525
526 return;
527 }
528
529 // Scripts MUST be snapshotted before the object is
530 // removed from the scene because doing otherwise will
531 // clobber the run flag
532 // This must be done outside the sp.AttachmentSyncLock so that there is no risk of a deadlock from
533 // scripts performing attachment operations at the same time. Getting object states stops the scripts.
534 string scriptedState = PrepareScriptInstanceForSave(so, true);
535
514 lock (sp.AttachmentsSyncLock) 536 lock (sp.AttachmentsSyncLock)
515 { 537 {
516 // Save avatar attachment information 538 // Save avatar attachment information
517// m_log.Debug("[ATTACHMENTS MODULE]: Detaching from UserID: " + sp.UUID + ", ItemID: " + itemID); 539// m_log.Debug("[ATTACHMENTS MODULE]: Detaching from UserID: " + sp.UUID + ", ItemID: " + itemID);
518 540
519 if (so.AttachedAvatar != sp.UUID)
520 {
521 m_log.WarnFormat(
522 "[ATTACHMENTS MODULE]: Tried to detach object {0} from {1} {2} but attached avatar id was {3} in {4}",
523 so.Name, sp.Name, sp.UUID, so.AttachedAvatar, m_scene.RegionInfo.RegionName);
524
525 return;
526 }
527
528 bool changed = sp.Appearance.DetachAttachment(so.FromItemID); 541 bool changed = sp.Appearance.DetachAttachment(so.FromItemID);
529 if (changed && m_scene.AvatarFactory != null) 542 if (changed && m_scene.AvatarFactory != null)
530 m_scene.AvatarFactory.QueueAppearanceSave(sp.UUID); 543 m_scene.AvatarFactory.QueueAppearanceSave(sp.UUID);
531 544
532 DetachSingleAttachmentToInvInternal(sp, so); 545 sp.RemoveAttachment(so);
546 UpdateDetachedObject(sp, so, scriptedState);
533 } 547 }
534 } 548 }
535 549
@@ -739,8 +753,27 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments
739 return newItem; 753 return newItem;
740 } 754 }
741 755
742 private string GetObjectScriptStates(SceneObjectGroup grp) 756 /// <summary>
757 /// Prepares the script instance for save.
758 /// </summary>
759 /// <remarks>
760 /// This involves triggering the detach event and getting the script state (which also stops the script)
761 /// This MUST be done outside sp.AttachmentsSyncLock, since otherwise there is a chance of deadlock if a
762 /// running script is performing attachment operations.
763 /// </remarks>
764 /// <returns>
765 /// The script state ready for persistence.
766 /// </returns>
767 /// <param name='grp'>
768 /// </param>
769 /// <param name='fireDetachEvent'>
770 /// If true, then fire the script event before we save its state.
771 /// </param>
772 private string PrepareScriptInstanceForSave(SceneObjectGroup grp, bool fireDetachEvent)
743 { 773 {
774 if (fireDetachEvent)
775 m_scene.EventManager.TriggerOnAttach(grp.LocalId, grp.FromItemID, UUID.Zero);
776
744 using (StringWriter sw = new StringWriter()) 777 using (StringWriter sw = new StringWriter())
745 { 778 {
746 using (XmlTextWriter writer = new XmlTextWriter(sw)) 779 using (XmlTextWriter writer = new XmlTextWriter(sw))
@@ -752,7 +785,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments
752 } 785 }
753 } 786 }
754 787
755 private void UpdateDetachedObject(IScenePresence sp, SceneObjectGroup so) 788 private void UpdateDetachedObject(IScenePresence sp, SceneObjectGroup so, string scriptedState)
756 { 789 {
757 // Don't save attachments for HG visitors, it 790 // Don't save attachments for HG visitors, it
758 // messes up their inventory. When a HG visitor logs 791 // messes up their inventory. When a HG visitor logs
@@ -765,11 +798,6 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments
765 && (m_scene.UserManagementModule == null 798 && (m_scene.UserManagementModule == null
766 || m_scene.UserManagementModule.IsLocalGridUser(sp.UUID)); 799 || m_scene.UserManagementModule.IsLocalGridUser(sp.UUID));
767 800
768 // Scripts MUST be snapshotted before the object is
769 // removed from the scene because doing otherwise will
770 // clobber the run flag
771 string scriptedState = GetObjectScriptStates(so);
772
773 // Remove the object from the scene so no more updates 801 // Remove the object from the scene so no more updates
774 // are sent. Doing this before the below changes will ensure 802 // are sent. Doing this before the below changes will ensure
775 // updates can't cause "HUD artefacts" 803 // updates can't cause "HUD artefacts"
@@ -793,91 +821,87 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments
793 so.RemoveScriptInstances(true); 821 so.RemoveScriptInstances(true);
794 } 822 }
795 823
796 private void DetachSingleAttachmentToInvInternal(IScenePresence sp, SceneObjectGroup so)
797 {
798 // m_log.DebugFormat("[ATTACHMENTS MODULE]: Detaching item {0} to inventory for {1}", itemID, sp.Name);
799
800 m_scene.EventManager.TriggerOnAttach(so.LocalId, so.FromItemID, UUID.Zero);
801 sp.RemoveAttachment(so);
802
803 UpdateDetachedObject(sp, so);
804 }
805
806 private SceneObjectGroup RezSingleAttachmentFromInventoryInternal( 824 private SceneObjectGroup RezSingleAttachmentFromInventoryInternal(
807 IScenePresence sp, UUID itemID, UUID assetID, uint attachmentPt) 825 IScenePresence sp, UUID itemID, UUID assetID, uint attachmentPt)
808 { 826 {
809 if (m_invAccessModule == null) 827 if (m_invAccessModule == null)
810 return null; 828 return null;
811 829
812 lock (sp.AttachmentsSyncLock) 830 SceneObjectGroup objatt;
831
832 if (itemID != UUID.Zero)
833 objatt = m_invAccessModule.RezObject(sp.ControllingClient,
834 itemID, Vector3.Zero, Vector3.Zero, UUID.Zero, (byte)1, true,
835 false, false, sp.UUID, true);
836 else
837 objatt = m_invAccessModule.RezObject(sp.ControllingClient,
838 null, assetID, Vector3.Zero, Vector3.Zero, UUID.Zero, (byte)1, true,
839 false, false, sp.UUID, true);
840
841 if (objatt == null)
813 { 842 {
814 SceneObjectGroup objatt; 843 m_log.WarnFormat(
844 "[ATTACHMENTS MODULE]: Could not retrieve item {0} for attaching to avatar {1} at point {2}",
845 itemID, sp.Name, attachmentPt);
815 846
816 if (itemID != UUID.Zero) 847 return null;
817 objatt = m_invAccessModule.RezObject(sp.ControllingClient, 848 }
818 itemID, Vector3.Zero, Vector3.Zero, UUID.Zero, (byte)1, true,
819 false, false, sp.UUID, true);
820 else
821 objatt = m_invAccessModule.RezObject(sp.ControllingClient,
822 null, assetID, Vector3.Zero, Vector3.Zero, UUID.Zero, (byte)1, true,
823 false, false, sp.UUID, true);
824 849
825 if (objatt != null) 850 // Remove any previous attachments
826 { 851 List<SceneObjectGroup> attachments = sp.GetAttachments(attachmentPt);
852 string previousAttachmentScriptedState = null;
853
854 // At the moment we can only deal with a single attachment
855 if (attachments.Count != 0)
856 DetachSingleAttachmentToInv(sp, attachments[0]);
857
858 lock (sp.AttachmentsSyncLock)
859 {
827// m_log.DebugFormat( 860// m_log.DebugFormat(
828// "[ATTACHMENTS MODULE]: Rezzed single object {0} for attachment to {1} on point {2} in {3}", 861// "[ATTACHMENTS MODULE]: Rezzed single object {0} for attachment to {1} on point {2} in {3}",
829// objatt.Name, sp.Name, attachmentPt, m_scene.Name); 862// objatt.Name, sp.Name, attachmentPt, m_scene.Name);
830 863
831 // HasGroupChanged is being set from within RezObject. Ideally it would be set by the caller. 864 // HasGroupChanged is being set from within RezObject. Ideally it would be set by the caller.
832 objatt.HasGroupChanged = false; 865 objatt.HasGroupChanged = false;
833 bool tainted = false; 866 bool tainted = false;
834 if (attachmentPt != 0 && attachmentPt != objatt.AttachmentPoint) 867 if (attachmentPt != 0 && attachmentPt != objatt.AttachmentPoint)
835 tainted = true; 868 tainted = true;
836 869
837 // FIXME: Detect whether it's really likely for AttachObject to throw an exception in the normal 870 // FIXME: Detect whether it's really likely for AttachObject to throw an exception in the normal
838 // course of events. If not, then it's probably not worth trying to recover the situation 871 // course of events. If not, then it's probably not worth trying to recover the situation
839 // since this is more likely to trigger further exceptions and confuse later debugging. If 872 // since this is more likely to trigger further exceptions and confuse later debugging. If
840 // exceptions can be thrown in expected error conditions (not NREs) then make this consistent 873 // exceptions can be thrown in expected error conditions (not NREs) then make this consistent
841 // since other normal error conditions will simply return false instead. 874 // since other normal error conditions will simply return false instead.
842 // This will throw if the attachment fails 875 // This will throw if the attachment fails
843 try 876 try
844 { 877 {
845 AttachObjectInternal(sp, objatt, attachmentPt, false, false); 878 AttachObjectInternal(sp, objatt, attachmentPt, false, false);
846 } 879 }
847 catch (Exception e) 880 catch (Exception e)
848 { 881 {
849 m_log.ErrorFormat( 882 m_log.ErrorFormat(
850 "[ATTACHMENTS MODULE]: Failed to attach {0} {1} for {2}, exception {3}{4}", 883 "[ATTACHMENTS MODULE]: Failed to attach {0} {1} for {2}, exception {3}{4}",
851 objatt.Name, objatt.UUID, sp.Name, e.Message, e.StackTrace); 884 objatt.Name, objatt.UUID, sp.Name, e.Message, e.StackTrace);
852
853 // Make sure the object doesn't stick around and bail
854 sp.RemoveAttachment(objatt);
855 m_scene.DeleteSceneObject(objatt, false);
856 return null;
857 }
858 885
859 if (tainted) 886 // Make sure the object doesn't stick around and bail
860 objatt.HasGroupChanged = true; 887 sp.RemoveAttachment(objatt);
888 m_scene.DeleteSceneObject(objatt, false);
889 return null;
890 }
861 891
862 // Fire after attach, so we don't get messy perms dialogs 892 if (tainted)
863 // 4 == AttachedRez 893 objatt.HasGroupChanged = true;
864 objatt.CreateScriptInstances(0, true, m_scene.DefaultScriptEngine, 4);
865 objatt.ResumeScripts();
866 894
867 // Do this last so that event listeners have access to all the effects of the attachment 895 // Fire after attach, so we don't get messy perms dialogs
868 m_scene.EventManager.TriggerOnAttach(objatt.LocalId, itemID, sp.UUID); 896 // 4 == AttachedRez
897 objatt.CreateScriptInstances(0, true, m_scene.DefaultScriptEngine, 4);
898 objatt.ResumeScripts();
869 899
870 return objatt; 900 // Do this last so that event listeners have access to all the effects of the attachment
871 } 901 m_scene.EventManager.TriggerOnAttach(objatt.LocalId, itemID, sp.UUID);
872 else
873 {
874 m_log.WarnFormat(
875 "[ATTACHMENTS MODULE]: Could not retrieve item {0} for attaching to avatar {1} at point {2}",
876 itemID, sp.Name, attachmentPt);
877 }
878 }
879 902
880 return null; 903 return objatt;
904 }
881 } 905 }
882 906
883 /// <summary> 907 /// <summary>
@@ -1027,17 +1051,14 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments
1027 ScenePresence sp = m_scene.GetScenePresence(remoteClient.AgentId); 1051 ScenePresence sp = m_scene.GetScenePresence(remoteClient.AgentId);
1028 if (sp != null) 1052 if (sp != null)
1029 { 1053 {
1030 lock (sp.AttachmentsSyncLock) 1054 List<SceneObjectGroup> attachments = sp.GetAttachments();
1055
1056 foreach (SceneObjectGroup group in attachments)
1031 { 1057 {
1032 List<SceneObjectGroup> attachments = sp.GetAttachments(); 1058 if (group.FromItemID == itemID && group.FromItemID != UUID.Zero)
1033
1034 foreach (SceneObjectGroup group in attachments)
1035 { 1059 {
1036 if (group.FromItemID == itemID && group.FromItemID != UUID.Zero) 1060 DetachSingleAttachmentToInv(sp, group);
1037 { 1061 return;
1038 DetachSingleAttachmentToInv(sp, group);
1039 return;
1040 }
1041 } 1062 }
1042 } 1063 }
1043 } 1064 }
@@ -1055,4 +1076,4 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments
1055 1076
1056 #endregion 1077 #endregion
1057 } 1078 }
1058} 1079} \ No newline at end of file