synapse 2.186.0__py311-none-any.whl → 2.187.0__py311-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of synapse might be problematic. Click here for more details.

synapse/lib/spooled.py CHANGED
@@ -7,6 +7,8 @@ import synapse.lib.const as s_const
7
7
  import synapse.lib.msgpack as s_msgpack
8
8
  import synapse.lib.lmdbslab as s_lmdbslab
9
9
 
10
+ MAX_SPOOL_SIZE = 10000
11
+
10
12
  class Spooled(s_base.Base):
11
13
  '''
12
14
  A Base class that can be used to implement objects which fallback to lmdb.
@@ -15,7 +17,7 @@ class Spooled(s_base.Base):
15
17
  together. Under memory pressure, these objects have a better shot of getting paged out.
16
18
  '''
17
19
 
18
- async def __anit__(self, dirn=None, size=10000, cell=None):
20
+ async def __anit__(self, dirn=None, size=MAX_SPOOL_SIZE, cell=None):
19
21
  '''
20
22
  Args:
21
23
  dirn(Optional[str]): base directory used for backing slab. If None, system temporary directory is used
@@ -55,7 +57,7 @@ class Set(Spooled):
55
57
  A minimal set-like implementation that will spool to a slab on large growth.
56
58
  '''
57
59
 
58
- async def __anit__(self, dirn=None, size=10000, cell=None):
60
+ async def __anit__(self, dirn=None, size=MAX_SPOOL_SIZE, cell=None):
59
61
  await Spooled.__anit__(self, dirn=dirn, size=size, cell=cell)
60
62
  self.realset = set()
61
63
  self.len = 0
@@ -84,6 +86,27 @@ class Set(Spooled):
84
86
 
85
87
  return len(self.realset)
86
88
 
89
+ async def copy(self):
90
+ newset = await Set.anit(dirn=self.dirn, size=self.size, cell=self.cell)
91
+
92
+ if self.fallback:
93
+ await newset._initFallBack()
94
+ await self.slab.copydb(None, newset.slab)
95
+ newset.len = self.len
96
+
97
+ else:
98
+ newset.realset = self.realset.copy()
99
+
100
+ return newset
101
+
102
+ async def clear(self):
103
+ if self.fallback:
104
+ self.len = 0
105
+ await self.slab.trash()
106
+ await self._initFallBack()
107
+ else:
108
+ self.realset.clear()
109
+
87
110
  async def add(self, valu):
88
111
 
89
112
  if self.fallback:
@@ -117,7 +140,7 @@ class Set(Spooled):
117
140
 
118
141
  class Dict(Spooled):
119
142
 
120
- async def __anit__(self, dirn=None, size=10000, cell=None):
143
+ async def __anit__(self, dirn=None, size=MAX_SPOOL_SIZE, cell=None):
121
144
 
122
145
  await Spooled.__anit__(self, dirn=dirn, size=size, cell=cell)
123
146
  self.realdict = {}
synapse/lib/storm.py CHANGED
@@ -5979,6 +5979,13 @@ class RunAsCmd(Cmd):
5979
5979
 
5980
5980
  NOTE: This command requires admin privileges.
5981
5981
 
5982
+ NOTE: Heavy objects (for example a View or Layer) are bound to the context which they
5983
+ are instantiated in and methods on them will be run using the user in that
5984
+ context. This means that executing a method on a variable containing a heavy
5985
+ object which was instantiated outside of the runas command and then used
5986
+ within the runas command will check the permissions of the outer user, not
5987
+ the one specified by the runas command.
5988
+
5982
5989
  Examples:
5983
5990
 
5984
5991
  // Create a node as another user.
@@ -2,12 +2,11 @@ import synapse.exc as s_exc
2
2
  import synapse.common as s_common
3
3
 
4
4
  import synapse.lib.node as s_node
5
+ import synapse.lib.time as s_time
5
6
  import synapse.lib.cache as s_cache
6
7
  import synapse.lib.layer as s_layer
7
8
  import synapse.lib.stormtypes as s_stormtypes
8
9
 
9
- import synapse.models.infotech as s_infotech
10
-
11
10
  RISK_HASVULN_VULNPROPS = (
12
11
  'hardware',
13
12
  'host',
@@ -818,8 +817,6 @@ class LibModelMigration(s_stormtypes.Lib, MigrationEditorMixin):
818
817
  'copyEdges': self._methCopyEdges,
819
818
  'copyTags': self._methCopyTags,
820
819
  'copyExtProps': self._methCopyExtProps,
821
- 'liftByPropValuNoNorm': self._methLiftByPropValuNoNorm,
822
- 'setNodePropValuNoNorm': self._methSetNodePropValuNoNorm,
823
820
  }
824
821
 
825
822
  async def _methCopyData(self, src, dst, overwrite=False):
@@ -876,125 +873,12 @@ class LibModelMigration(s_stormtypes.Lib, MigrationEditorMixin):
876
873
  proto = editor.loadNode(dst)
877
874
  await self.copyExtProps(src, proto)
878
875
 
879
- async def _methLiftByPropValuNoNorm(self, formname, propname, valu, cmpr='=', reverse=False):
880
- '''
881
- No storm docs for this on purpose. It is restricted for use during model migrations only.
882
- '''
883
- formname = await s_stormtypes.tostr(formname)
884
- propname = await s_stormtypes.tostr(propname)
885
- valu = await s_stormtypes.toprim(valu)
886
-
887
- prop = self.runt.snap.core.model.prop(f'{formname}:{propname}')
888
- if prop is None:
889
- mesg = f'Could not find prop: {formname}:{propname}'
890
- raise s_exc.NoSuchProp(mesg=mesg, formname=formname, propname=propname)
891
-
892
- if not self.runt.snap.core.migration:
893
- mesg = '$lib.model.migration.liftByPropValuNoNorm() is restricted to model migrations only.'
894
- raise s_exc.AuthDeny(mesg=mesg, user=self.runt.user.iden, username=self.runt.user.name)
895
-
896
- stortype = prop.type.stortype
897
-
898
- # Normally we'd call proptype.getStorCmprs() here to get the cmprvals
899
- # but getStorCmprs() calls norm() which we're trying to avoid so build
900
- # cmprvals manually here.
901
-
902
- if prop.type.isarray:
903
- stortype &= (~s_layer.STOR_FLAG_ARRAY)
904
- liftfunc = self.runt.snap.wlyr.liftByPropArray
905
- else:
906
- liftfunc = self.runt.snap.wlyr.liftByPropValu
907
-
908
- cmprvals = ((cmpr, valu, stortype),)
909
-
910
- layriden = self.runt.snap.wlyr.iden
911
- async for _, buid, sode in liftfunc(formname, propname, cmprvals, reverse=reverse):
912
- yield await self.runt.snap._joinStorNode(buid, {layriden: sode})
913
-
914
- async def _methSetNodePropValuNoNorm(self, n, propname, valu):
915
- '''
916
- No storm docs for this on purpose. It is restricted for use during model migrations only.
917
- '''
918
-
919
- # NB: I'm sure there are all kinds of edges cases that this function doesn't account for. At the time of it's
920
- # creation, this was intended to be used to update array properties with bad it:sec:cpe values in them. It works
921
- # for that use case (see model migration 0.2.28). Any additional use of this function should perform heavy
922
- # testing.
923
-
924
- if not isinstance(n, s_node.Node):
925
- raise s_exc.BadArg(mesg='$lib.model.migration.setNodePropValuNoNorm() argument must be a node.')
926
-
927
- if not self.runt.snap.core.migration:
928
- mesg = '$lib.model.migration.setNodePropValuNoNorm() is restricted to model migrations only.'
929
- raise s_exc.AuthDeny(mesg=mesg, user=self.runt.user.iden, username=self.runt.user.name)
930
-
931
- propname = await s_stormtypes.tostr(propname)
932
- valu = await s_stormtypes.toprim(valu)
933
-
934
- async with self.runt.snap.getNodeEditor(n) as proto:
935
- await proto.set(propname, valu, norminfo={})
936
-
937
- return n
938
-
939
876
  @s_stormtypes.registry.registerLib
940
877
  class LibModelMigrations(s_stormtypes.Lib, MigrationEditorMixin):
941
878
  '''
942
879
  A Storm library for selectively migrating nodes in the current view.
943
880
  '''
944
881
  _storm_locals = (
945
- {'name': 'itSecCpe_2_170_0',
946
- 'desc': '''
947
- Versions of Synapse prior to v2.169.0 did not correctly parse and
948
- convert CPE strings from 2.2 -> 2.3 or 2.3 -> 2.2. This migration
949
- attempts to re-normalize `it:sec:cpe` nodes that may be fixable.
950
-
951
- NOTE: It is highly recommended to test the `it:sec:cpe` migrations
952
- in a fork first and confirm the migration was successful without any
953
- issues. Then run the migration in view deporder to migrate the
954
- entire cortex. E.g.::
955
-
956
- for $view in $lib.view.list(deporder=$lib.true) {
957
- view.exec $view.iden {
958
- for $n in $lib.layer.get().liftByProp(it:sec:cpe) {
959
- $lib.model.migration.s.itSecCpe_2_170_0($n)
960
- }
961
- }
962
- }
963
-
964
- Upon completion of the migration, nodedata will contain a
965
- `migration.s.itSecCpe_2_170_0` dict with information about the
966
- migration status. This dict may contain the following:
967
-
968
- - `status`: (required str) "success" or "failed"
969
- - `reason`: (optional str) if "status" is "failed", this key will
970
- explain why the migration failed.
971
- - `valu`: (optional str) if this key is present, it will contain
972
- an updated CPE2.3 string since the primary property cannot be
973
- changed.
974
- - `updated`: (optional list[str]) A list of properties that were
975
- updated by the migration.
976
-
977
- Failed or incorrect migrations may be helped by updating the :v2_2
978
- property to be a valid CPE2.2 string and then re-running the
979
- migration with `force=$lib.true`. If the primary property (CPE2.3)
980
- is valid but incorrect, users may update the :v2_2 property and then
981
- run the migration with `prefer_v22=$lib.true` to make the migration
982
- use the `:v2_2` string instead of the primary property for the
983
- migration process.
984
- ''',
985
- 'type': {'type': 'function', '_funcname': '_itSecCpe_2_170_0',
986
- 'args': (
987
- {'name': 'n', 'type': 'node', 'desc': 'The it:sec:cpe node to migrate.'},
988
- {'name': 'prefer_v22', 'type': 'bool', 'default': False,
989
- 'desc': '''
990
- Try to renormalize using the :v2_2 prop instead of the
991
- primary property. This can be especially useful when the
992
- primary property is a valid but incorrect CPE string.
993
- '''},
994
- {'name': 'force', 'type': 'bool', 'default': False,
995
- 'desc': 'Perform fixups even if the primary property and :v2_2 are valid.'},
996
- ),
997
- 'returns': {'type': 'boolean', 'desc': 'Boolean indicating if the migration was successful.'}}},
998
882
  {'name': 'riskHasVulnToVulnerable', 'desc': '''
999
883
  Create a risk:vulnerable node from the provided risk:hasvuln node.
1000
884
 
@@ -1076,142 +960,9 @@ class LibModelMigrations(s_stormtypes.Lib, MigrationEditorMixin):
1076
960
 
1077
961
  def getObjLocals(self):
1078
962
  return {
1079
- 'itSecCpe_2_170_0': self._itSecCpe_2_170_0,
1080
- 'itSecCpe_2_170_0_internal': self._itSecCpe_2_170_0_internal,
1081
963
  'riskHasVulnToVulnerable': self._riskHasVulnToVulnerable,
1082
964
  }
1083
965
 
1084
- async def _itSecCpe_2_170_0(self, n, prefer_v22=False, force=False):
1085
- info = await self._itSecCpe_2_170_0_internal(n, prefer_v22=prefer_v22, force=force, set_nodedata=True)
1086
- return info.get('status') == 'success'
1087
-
1088
- async def _itSecCpe_2_170_0_internal(self, n, prefer_v22=False, force=False, set_nodedata=False):
1089
-
1090
- if not isinstance(n, s_node.Node):
1091
- raise s_exc.BadArg(mesg='$lib.model.migration.s.itSecCpe_2_170_0() argument must be a node.')
1092
-
1093
- if n.form.name != 'it:sec:cpe':
1094
- raise s_exc.BadArg(f'itSecCpeFix only accepts it:sec:cpe nodes, not {n.form.name}')
1095
-
1096
- prefer_v22 = await s_stormtypes.tobool(prefer_v22)
1097
- force = await s_stormtypes.tobool(force)
1098
-
1099
- layr = self.runt.snap.wlyr
1100
- # We only need to check :v2_2 since that's the only property that's
1101
- # writable. Everthing else is readonly. And we can do it here once
1102
- # instead of in the loop below which will cause a perf hit.
1103
- self.runt.confirmPropSet(n.form.prop('v2_2'), layriden=layr.iden)
1104
-
1105
- curv = n.repr()
1106
- reprvalu = f'it:sec:cpe={curv}'
1107
-
1108
- nodedata = await n.getData('migration.s.itSecCpe_2_170_0', {})
1109
- if nodedata.get('status') == 'success' and not force:
1110
- if self.runt.debug:
1111
- mesg = f'DEBUG: itSecCpe_2_170_0({reprvalu}): Node already migrated.'
1112
- await self.runt.printf(mesg)
1113
- return nodedata
1114
-
1115
- modl = self.runt.model.type('it:sec:cpe')
1116
-
1117
- valu23 = None
1118
- valu22 = None
1119
-
1120
- # Check the primary property for validity.
1121
- cpe23 = s_infotech.cpe23_regex.match(curv)
1122
- if cpe23 is not None and cpe23.group() == curv:
1123
- valu23 = curv
1124
-
1125
- # Check the v2_2 property for validity.
1126
- v2_2 = n.props.get('v2_2')
1127
- if v2_2 is not None:
1128
- rgx = s_infotech.cpe22_regex.match(v2_2)
1129
- if rgx is not None and rgx.group() == v2_2:
1130
- valu22 = v2_2
1131
-
1132
- async with self.runt.snap.getNodeEditor(n) as proto:
1133
-
1134
- # If both values are populated, this node is valid
1135
- if valu23 is not None and valu22 is not None and not force:
1136
- if self.runt.debug:
1137
- mesg = f'DEBUG: itSecCpe_2_170_0({reprvalu}): Node is valid, no migration necessary.'
1138
- await self.runt.printf(mesg)
1139
-
1140
- nodedata = {'status': 'success'}
1141
- if set_nodedata:
1142
- await proto.setData('migration.s.itSecCpe_2_170_0', nodedata)
1143
-
1144
- return nodedata
1145
-
1146
- if valu23 is None and valu22 is None:
1147
- reason = 'Unable to migrate due to invalid data. Primary property and :v2_2 are both invalid.'
1148
- # Invalid 2.3 string and no/invalid v2_2 prop. Nothing
1149
- # we can do here so log, mark, and go around.
1150
- mesg = f'itSecCpe_2_170_0({reprvalu}): {reason}'
1151
- await self.runt.warn(mesg)
1152
-
1153
- nodedata = {
1154
- 'status': 'failed',
1155
- 'reason': reason,
1156
- }
1157
- if set_nodedata:
1158
- await proto.setData('migration.s.itSecCpe_2_170_0', nodedata)
1159
-
1160
- return nodedata
1161
-
1162
- if prefer_v22:
1163
- valu = valu22 or valu23
1164
- else:
1165
- valu = valu23 or valu22
1166
-
1167
- # Re-normalize the data from the 2.3 or 2.2 string, whichever was valid.
1168
- norm, info = modl.norm(valu)
1169
- subs = info.get('subs')
1170
-
1171
- nodedata = {'status': 'success'}
1172
-
1173
- if norm != curv:
1174
- # The re-normed value is not the same as the current value.
1175
- # Since we can't change the primary property, store the
1176
- # updated value in nodedata.
1177
- if self.runt.debug:
1178
- mesg = f'DEBUG: itSecCpe_2_170_0({reprvalu}): Stored updated primary property value to nodedata: {curv} -> {norm}.'
1179
- await self.runt.printf(mesg)
1180
-
1181
- nodedata['valu'] = norm
1182
-
1183
- # Iterate over the existing properties
1184
- for propname, propcurv in n.props.items():
1185
- subscurv = subs.get(propname)
1186
- if subscurv is None:
1187
- continue
1188
-
1189
- if propname == 'v2_2' and isinstance(subscurv, list):
1190
- subscurv = s_infotech.zipCpe22(subscurv)
1191
-
1192
- # Values are the same, go around
1193
- if propcurv == subscurv:
1194
- continue
1195
-
1196
- nodedata.setdefault('updated', [])
1197
- nodedata['updated'].append(propname)
1198
-
1199
- # Update the existing property with the re-normalized property value.
1200
- await proto.set(propname, subscurv, ignore_ro=True)
1201
-
1202
- if set_nodedata:
1203
- await proto.setData('migration.s.itSecCpe_2_170_0', nodedata)
1204
-
1205
- if self.runt.debug:
1206
- if nodedata.get('updated'):
1207
- mesg = f'DEBUG: itSecCpe_2_170_0({reprvalu}): Updated properties: {", ".join(nodedata["updated"])}.'
1208
- await self.runt.printf(mesg)
1209
- else:
1210
- mesg = f'DEBUG: itSecCpe_2_170_0({reprvalu}): No property updates required.'
1211
- await self.runt.printf(mesg)
1212
-
1213
- return nodedata
1214
-
1215
966
  async def _riskHasVulnToVulnerable(self, n, nodata=False):
1216
967
 
1217
968
  nodata = await s_stormtypes.tobool(nodata)
@@ -1269,3 +1020,322 @@ class LibModelMigrations(s_stormtypes.Lib, MigrationEditorMixin):
1269
1020
  await self.copyData(n, proto, overwrite=False)
1270
1021
 
1271
1022
  return retidens
1023
+
1024
+ @s_stormtypes.registry.registerLib
1025
+ class LibModelMigrations_0_2_31(s_stormtypes.Lib):
1026
+ '''
1027
+ A Storm library with helper functions for the 0.2.31 model it:sec:cpe migration.
1028
+ '''
1029
+ _storm_locals = (
1030
+ {'name': 'listNodes', 'desc': 'Yield queued nodes.',
1031
+ 'type': {'type': 'function', '_funcname': '_methListNodes',
1032
+ 'args': (
1033
+ {'name': 'form', 'type': 'form', 'default': None,
1034
+ 'desc': 'Only yield entries matching the specified form.'},
1035
+ {'name': 'source', 'type': 'str', 'default': None,
1036
+ 'desc': 'Only yield entries that were seen by the specified source.'},
1037
+ {'name': 'offset', 'type': 'int', 'default': 0,
1038
+ 'desc': 'Skip this many entries.'},
1039
+ {'name': 'size', 'type': 'int', 'default': None,
1040
+ 'desc': 'Only yield up to this many entries.'},
1041
+ ),
1042
+ 'returns': {'name': 'Yields', 'type': 'list',
1043
+ 'desc': 'A tuple of (offset, form, valu, sources) values for the specified node.', }}},
1044
+ {'name': 'printNode', 'desc': 'Print detailed queued node information.',
1045
+ 'type': {'type': 'function', '_funcname': '_methPrintNode',
1046
+ 'args': (
1047
+ {'name': 'offset', 'type': 'into', 'desc': 'The offset of the queued node to print.'},
1048
+ ),
1049
+ 'returns': {'type': 'null'}}},
1050
+ {'name': 'repairNode', 'desc': 'Repair a queued node.',
1051
+ 'type': {'type': 'function', '_funcname': '_methRepairNode',
1052
+ 'args': (
1053
+ {'name': 'offset', 'type': 'str', 'desc': 'The node queue offset to repair.'},
1054
+ {'name': 'newvalu', 'type': 'any', 'desc': 'The new (corrected) node value.'},
1055
+ {'name': 'remove', 'type': 'boolean', 'default': False,
1056
+ 'desc': 'Specify whether to delete the repaired node from the queue.'},
1057
+ ),
1058
+ 'returns': {'type': 'dict', 'desc': 'The queue node information'}}},
1059
+ )
1060
+ _storm_lib_path = ('model', 'migration', 's', 'model_0_2_31')
1061
+
1062
+ def getObjLocals(self):
1063
+ return {
1064
+ 'listNodes': self._methListNodes,
1065
+ 'printNode': self._methPrintNode,
1066
+ 'repairNode': self._methRepairNode,
1067
+ }
1068
+
1069
+ async def _hasCoreQueue(self, name):
1070
+ try:
1071
+ await self.runt.snap.core.getCoreQueue(name)
1072
+ return True
1073
+ except s_exc.NoSuchName:
1074
+ return False
1075
+
1076
+ async def _methListNodes(self, form=None, source=None, offset=0, size=None):
1077
+ form = await s_stormtypes.tostr(form, noneok=True)
1078
+ source = await s_stormtypes.tostr(source, noneok=True)
1079
+ offset = await s_stormtypes.toint(offset)
1080
+ size = await s_stormtypes.toint(size, noneok=True)
1081
+
1082
+ if not await self._hasCoreQueue('model_0_2_31:nodes'):
1083
+ await self.runt.printf('Queue model_0_2_31:nodes not found, no nodes to list.')
1084
+ return
1085
+
1086
+ nodes = self.runt.snap.core.coreQueueGets('model_0_2_31:nodes', offs=offset, cull=False, size=size)
1087
+ async for offs, node in nodes:
1088
+ if form is not None and node['formname'] != form:
1089
+ continue
1090
+
1091
+ if source is not None and source not in node['sources']:
1092
+ continue
1093
+
1094
+ yield (offs, node['formname'], node['formvalu'], node['sources'])
1095
+
1096
+ async def _methPrintNode(self, offset):
1097
+ offset = await s_stormtypes.toint(offset)
1098
+
1099
+ if not await self._hasCoreQueue('model_0_2_31:nodes'):
1100
+ await self.runt.printf('Queue model_0_2_31:nodes not found, no nodes to print.')
1101
+ return
1102
+
1103
+ node = await self.runt.snap.core.coreQueueGet('model_0_2_31:nodes', offs=offset, cull=False)
1104
+ if not node:
1105
+ await self.runt.warn(f'Queued node with offset {offset} not found.')
1106
+ return
1107
+
1108
+ node = node[1]
1109
+
1110
+ await self.runt.printf(f'{node["formname"]}={repr(node["formvalu"])}')
1111
+
1112
+ for layriden, sode in node['sodes'].items():
1113
+ await self.runt.printf(f' layer: {layriden}')
1114
+
1115
+ for propname, propvalu in sode.get('props', {}).items():
1116
+ if propname == '.seen':
1117
+ mintime, maxtime = propvalu[0]
1118
+ mindt = s_time.repr(mintime)
1119
+ maxdt = s_time.repr(maxtime)
1120
+ await self.runt.printf(f' .seen = ({mindt}, {maxdt})')
1121
+ else:
1122
+ await self.runt.printf(f' :{propname} = {propvalu[0]}')
1123
+
1124
+ for tagname, tagvalu in sode.get('tags', {}).items():
1125
+ if tagvalu == (None, None):
1126
+ await self.runt.printf(f' #{tagname}')
1127
+ else:
1128
+ mintime, maxtime = tagvalu
1129
+ mindt = s_time.repr(mintime)
1130
+ maxdt = s_time.repr(maxtime)
1131
+ await self.runt.printf(f' #{tagname} = ({mindt}, {maxdt})')
1132
+
1133
+ for tagprop, tagpropvalu in sode.get('tagprops', {}).items():
1134
+ for prop, valu in tagpropvalu.items():
1135
+ await self.runt.printf(f' #{tagprop}:{prop} = {valu[0]}')
1136
+
1137
+ if sources := node['sources']:
1138
+ await self.runt.printf(f' sources: {sorted(sources)}')
1139
+
1140
+ if noderefs := node['refs']:
1141
+ await self.runt.printf(' refs:')
1142
+
1143
+ for layriden, reflist in noderefs.items():
1144
+ await self.runt.printf(f' layer: {layriden}')
1145
+ for iden, refinfo in reflist:
1146
+ form, prop, *_ = refinfo
1147
+ await self.runt.printf(f' - {form}:{prop} (iden: {iden}')
1148
+
1149
+ n1edges = node['n1edges']
1150
+ n2edges = node['n2edges']
1151
+
1152
+ if n1edges or n2edges:
1153
+ await self.runt.printf(' edges:')
1154
+
1155
+ for layriden, edges in n1edges.items():
1156
+ for verb, iden in edges:
1157
+ await self.runt.printf(f' -({verb})> {iden}')
1158
+
1159
+ for layriden, edges in n2edges.items():
1160
+ for verb, iden, n2form in edges:
1161
+ await self.runt.printf(f' <({verb})- {iden}')
1162
+
1163
+ async def _repairNode(self, offset, newvalu):
1164
+ item = await self.runt.snap.core.coreQueueGet('model_0_2_31:nodes', offset, cull=False)
1165
+ if item is None:
1166
+ await self.runt.warn(f'Queued node with offset {offset} not found.')
1167
+ return False
1168
+
1169
+ node = item[1]
1170
+
1171
+ nodeform = node['formname']
1172
+ form = self.runt.snap.core.model.form(nodeform)
1173
+
1174
+ norm, info = form.type.norm(newvalu)
1175
+
1176
+ buid = s_common.buid((nodeform, norm))
1177
+
1178
+ nodeedits = {}
1179
+
1180
+ for layriden in node['layers']:
1181
+ nodeedits.setdefault(layriden, {})
1182
+
1183
+ layer = self.runt.snap.core.getLayer(layriden)
1184
+ if layer is None: # pragma: no cover
1185
+ await self.runt.warn(f'Layer does not exist to recreate node: {layriden}.')
1186
+ return False
1187
+
1188
+ await self.runt.printf(f'Repairing node at offset {offset} from {node["formvalu"]} -> {norm}')
1189
+
1190
+ # Create the node in the right layers
1191
+ for layriden in node['layers']:
1192
+ nodeedits[layriden][buid] = (
1193
+ buid, nodeform, [
1194
+ (s_layer.EDIT_NODE_ADD, (norm, form.type.stortype), ()),
1195
+ ])
1196
+
1197
+ for propname, propvalu in info.get('subs', {}).items():
1198
+ prop = form.prop(propname)
1199
+ if prop is None:
1200
+ continue
1201
+
1202
+ stortype = prop.type.stortype
1203
+
1204
+ nodeedits[layriden][buid][2].append(
1205
+ (s_layer.EDIT_PROP_SET, (propname, propvalu, None, stortype), ()),
1206
+ )
1207
+
1208
+ for layriden, sode in node['sodes'].items():
1209
+ nodeedits.setdefault(layriden, {})
1210
+ nodeedits[layriden].setdefault(buid, (buid, nodeform, []))
1211
+
1212
+ for propname, propvalu in sode.get('props', {}).items():
1213
+ propvalu, stortype = propvalu
1214
+
1215
+ nodeedits[layriden][buid][2].append(
1216
+ (s_layer.EDIT_PROP_SET, (propname, propvalu, None, stortype), ()),
1217
+ )
1218
+
1219
+ for tagname, tagvalu in sode.get('tags', {}).items():
1220
+ nodeedits[layriden][buid][2].append(
1221
+ (s_layer.EDIT_TAG_SET, (tagname, tagvalu, None), ()),
1222
+ )
1223
+
1224
+ for tagprop, tagpropvalu in sode.get('tagprops', {}).items():
1225
+ for propname, propvalu in tagpropvalu.items():
1226
+ propvalu, stortype = propvalu
1227
+ nodeedits[layriden][buid][2].append(
1228
+ (s_layer.EDIT_TAGPROP_SET, (tagname, propname, propvalu, None, stortype), ()),
1229
+ )
1230
+
1231
+ for layriden, data in node['nodedata'].items():
1232
+ nodeedits.setdefault(layriden, {})
1233
+ nodeedits[layriden].setdefault(buid, (buid, nodeform, []))
1234
+
1235
+ for name, valu in data:
1236
+ nodeedits[layriden][buid][2].append(
1237
+ (s_layer.EDIT_NODEDATA_SET, (name, valu, None), ()),
1238
+ )
1239
+
1240
+ for layriden, edges in node['n1edges'].items():
1241
+ nodeedits.setdefault(layriden, {})
1242
+ nodeedits[layriden].setdefault(buid, (buid, nodeform, []))
1243
+
1244
+ for verb, iden in edges:
1245
+ nodeedits[layriden][buid][2].append(
1246
+ (s_layer.EDIT_EDGE_ADD, (verb, iden), ()),
1247
+ )
1248
+
1249
+ for layriden, edges in node['n2edges'].items():
1250
+ n1iden = s_common.ehex(buid)
1251
+
1252
+ for verb, iden, n2form in edges:
1253
+ n2buid = s_common.uhex(iden)
1254
+
1255
+ nodeedits.setdefault(layriden, {})
1256
+ nodeedits[layriden].setdefault(n2buid, (n2buid, n2form, []))
1257
+
1258
+ nodeedits[layriden][n2buid][2].append(
1259
+ (s_layer.EDIT_EDGE_ADD, (verb, n1iden), ()),
1260
+ )
1261
+
1262
+ for layriden, reflist in node['refs'].items():
1263
+ layer = self.runt.snap.core.getLayer(layriden)
1264
+ if layer is None:
1265
+ continue
1266
+
1267
+ for iden, refinfo in reflist:
1268
+ refform, refprop, reftype, isarray, isro = refinfo
1269
+
1270
+ if isro:
1271
+ continue
1272
+
1273
+ refbuid = s_common.uhex(iden)
1274
+
1275
+ nodeedits.setdefault(layriden, {})
1276
+ nodeedits[layriden].setdefault(refbuid, (refbuid, refform, []))
1277
+
1278
+ if reftype == 'ndef':
1279
+ propvalu = (nodeform, norm)
1280
+ else:
1281
+ propvalu = norm
1282
+
1283
+ stortype = self.runt.snap.core.model.type(reftype).stortype
1284
+
1285
+ if isarray:
1286
+
1287
+ sode = await layer.getStorNode(refbuid)
1288
+ if not sode:
1289
+ continue
1290
+
1291
+ props = sode.get('props', {})
1292
+
1293
+ curv, _ = props.get(refprop, (None, None))
1294
+ _curv = curv
1295
+
1296
+ if _curv is None:
1297
+ _curv = []
1298
+
1299
+ newv = list(_curv).copy()
1300
+ newv.append(propvalu)
1301
+
1302
+ nodeedits[layriden][refbuid][2].append(
1303
+ (s_layer.EDIT_PROP_SET, (refprop, newv, curv, stortype | s_layer.STOR_FLAG_ARRAY), ()),
1304
+ )
1305
+
1306
+ else:
1307
+
1308
+ nodeedits[layriden][refbuid][2].append(
1309
+ (s_layer.EDIT_PROP_SET, (refprop, propvalu, None, stortype), ()),
1310
+ )
1311
+
1312
+ meta = {'time': s_common.now(), 'user': self.runt.snap.core.auth.rootuser.iden}
1313
+
1314
+ # Process all layer edits as a single batch
1315
+ for layriden, edits in nodeedits.items():
1316
+ layer = self.runt.snap.core.getLayer(layriden)
1317
+ if layer is None: # pragma: no cover
1318
+ continue
1319
+
1320
+ await layer.storNodeEditsNoLift(list(edits.values()), meta)
1321
+
1322
+ return True
1323
+
1324
+ async def _methRepairNode(self, offset, newvalu, remove=False):
1325
+ ok = False
1326
+
1327
+ if not await self._hasCoreQueue('model_0_2_31:nodes'):
1328
+ await self.runt.printf('Queue model_0_2_31:nodes not found, no nodes to repair.')
1329
+ return False
1330
+
1331
+ try:
1332
+ ok = await self._repairNode(offset, newvalu)
1333
+ except s_exc.SynErr as exc: # pragma: no cover
1334
+ mesg = exc.get('mesg')
1335
+ await self.runt.warn(f'Error when restoring node {offset}: {mesg}')
1336
+
1337
+ if ok and remove:
1338
+ await self.runt.printf(f'Removing queued node: {offset}.')
1339
+ await self.runt.snap.core.coreQueuePop('model_0_2_31:nodes', offset)
1340
+
1341
+ return ok