Line data Source code
1 : /* -------------------------------------------------------------------------
2 : *
3 : * seclabel.c
4 : * routines to support security label feature.
5 : *
6 : * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
7 : * Portions Copyright (c) 1994, Regents of the University of California
8 : *
9 : * -------------------------------------------------------------------------
10 : */
11 : #include "postgres.h"
12 :
13 : #include "access/genam.h"
14 : #include "access/htup_details.h"
15 : #include "access/relation.h"
16 : #include "access/table.h"
17 : #include "catalog/catalog.h"
18 : #include "catalog/indexing.h"
19 : #include "catalog/pg_seclabel.h"
20 : #include "catalog/pg_shseclabel.h"
21 : #include "commands/seclabel.h"
22 : #include "miscadmin.h"
23 : #include "utils/builtins.h"
24 : #include "utils/fmgroids.h"
25 : #include "utils/memutils.h"
26 : #include "utils/rel.h"
27 :
28 : typedef struct
29 : {
30 : const char *provider_name;
31 : check_object_relabel_type hook;
32 : } LabelProvider;
33 :
34 : static List *label_provider_list = NIL;
35 :
36 : static bool
37 33 : SecLabelSupportsObjectType(ObjectType objtype)
38 : {
39 33 : switch (objtype)
40 : {
41 33 : case OBJECT_AGGREGATE:
42 : case OBJECT_COLUMN:
43 : case OBJECT_DATABASE:
44 : case OBJECT_DOMAIN:
45 : case OBJECT_EVENT_TRIGGER:
46 : case OBJECT_FOREIGN_TABLE:
47 : case OBJECT_FUNCTION:
48 : case OBJECT_LANGUAGE:
49 : case OBJECT_LARGEOBJECT:
50 : case OBJECT_MATVIEW:
51 : case OBJECT_PROCEDURE:
52 : case OBJECT_PUBLICATION:
53 : case OBJECT_ROLE:
54 : case OBJECT_ROUTINE:
55 : case OBJECT_SCHEMA:
56 : case OBJECT_SEQUENCE:
57 : case OBJECT_SUBSCRIPTION:
58 : case OBJECT_TABLE:
59 : case OBJECT_TABLESPACE:
60 : case OBJECT_TYPE:
61 : case OBJECT_VIEW:
62 33 : return true;
63 :
64 0 : case OBJECT_ACCESS_METHOD:
65 : case OBJECT_AMOP:
66 : case OBJECT_AMPROC:
67 : case OBJECT_ATTRIBUTE:
68 : case OBJECT_CAST:
69 : case OBJECT_COLLATION:
70 : case OBJECT_CONVERSION:
71 : case OBJECT_DEFAULT:
72 : case OBJECT_DEFACL:
73 : case OBJECT_DOMCONSTRAINT:
74 : case OBJECT_EXTENSION:
75 : case OBJECT_FDW:
76 : case OBJECT_FOREIGN_SERVER:
77 : case OBJECT_INDEX:
78 : case OBJECT_OPCLASS:
79 : case OBJECT_OPERATOR:
80 : case OBJECT_OPFAMILY:
81 : case OBJECT_PARAMETER_ACL:
82 : case OBJECT_POLICY:
83 : case OBJECT_PROPGRAPH:
84 : case OBJECT_PUBLICATION_NAMESPACE:
85 : case OBJECT_PUBLICATION_REL:
86 : case OBJECT_RULE:
87 : case OBJECT_STATISTIC_EXT:
88 : case OBJECT_TABCONSTRAINT:
89 : case OBJECT_TRANSFORM:
90 : case OBJECT_TRIGGER:
91 : case OBJECT_TSCONFIGURATION:
92 : case OBJECT_TSDICTIONARY:
93 : case OBJECT_TSPARSER:
94 : case OBJECT_TSTEMPLATE:
95 : case OBJECT_USER_MAPPING:
96 0 : return false;
97 :
98 : /*
99 : * There's intentionally no default: case here; we want the
100 : * compiler to warn if a new ObjectType hasn't been handled above.
101 : */
102 : }
103 :
104 : /* Shouldn't get here, but if we do, say "no support" */
105 0 : return false;
106 : }
107 :
108 : /*
109 : * ExecSecLabelStmt --
110 : *
111 : * Apply a security label to a database object.
112 : *
113 : * Returns the ObjectAddress of the object to which the policy was applied.
114 : */
115 : ObjectAddress
116 67 : ExecSecLabelStmt(SecLabelStmt *stmt)
117 : {
118 67 : LabelProvider *provider = NULL;
119 : ObjectAddress address;
120 : Relation relation;
121 : ListCell *lc;
122 : bool missing_ok;
123 :
124 : /*
125 : * Find the named label provider, or if none specified, check whether
126 : * there's exactly one, and if so use it.
127 : */
128 67 : if (stmt->provider == NULL)
129 : {
130 51 : if (label_provider_list == NIL)
131 24 : ereport(ERROR,
132 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
133 : errmsg("no security label providers have been loaded")));
134 27 : if (list_length(label_provider_list) != 1)
135 0 : ereport(ERROR,
136 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
137 : errmsg("must specify provider when multiple security label providers have been loaded")));
138 27 : provider = (LabelProvider *) linitial(label_provider_list);
139 : }
140 : else
141 : {
142 18 : foreach(lc, label_provider_list)
143 : {
144 8 : LabelProvider *lp = lfirst(lc);
145 :
146 8 : if (strcmp(stmt->provider, lp->provider_name) == 0)
147 : {
148 6 : provider = lp;
149 6 : break;
150 : }
151 : }
152 16 : if (provider == NULL)
153 10 : ereport(ERROR,
154 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
155 : errmsg("security label provider \"%s\" is not loaded",
156 : stmt->provider)));
157 : }
158 :
159 33 : if (!SecLabelSupportsObjectType(stmt->objtype))
160 0 : ereport(ERROR,
161 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
162 : errmsg("security labels are not supported for this type of object")));
163 :
164 : /*
165 : * During binary upgrade, allow nonexistent large objects so that we don't
166 : * have to create them during schema restoration. pg_upgrade will
167 : * transfer the contents of pg_largeobject_metadata via COPY or by
168 : * copying/linking its files from the old cluster later on.
169 : */
170 33 : missing_ok = IsBinaryUpgrade && stmt->objtype == OBJECT_LARGEOBJECT;
171 :
172 : /*
173 : * Translate the parser representation which identifies this object into
174 : * an ObjectAddress. get_object_address() will throw an error if the
175 : * object does not exist, and will also acquire a lock on the target to
176 : * guard against concurrent modifications.
177 : */
178 33 : address = get_object_address(stmt->objtype, stmt->object,
179 : &relation, ShareUpdateExclusiveLock,
180 : missing_ok);
181 :
182 : /* Require ownership of the target object. */
183 30 : check_object_ownership(GetUserId(), stmt->objtype, address,
184 : stmt->object, relation);
185 :
186 : /* Perform other integrity checks as needed. */
187 27 : switch (stmt->objtype)
188 : {
189 1 : case OBJECT_COLUMN:
190 :
191 : /*
192 : * Allow security labels only on columns of tables, views,
193 : * materialized views, composite types, and foreign tables (which
194 : * are the only relkinds for which pg_dump will dump labels).
195 : */
196 1 : if (relation->rd_rel->relkind != RELKIND_RELATION &&
197 0 : relation->rd_rel->relkind != RELKIND_VIEW &&
198 0 : relation->rd_rel->relkind != RELKIND_MATVIEW &&
199 0 : relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
200 0 : relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
201 0 : relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
202 0 : ereport(ERROR,
203 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
204 : errmsg("cannot set security label on relation \"%s\"",
205 : RelationGetRelationName(relation)),
206 : errdetail_relkind_not_supported(relation->rd_rel->relkind)));
207 1 : break;
208 26 : default:
209 26 : break;
210 : }
211 :
212 : /* Provider gets control here, may throw ERROR to veto new label. */
213 27 : provider->hook(&address, stmt->label);
214 :
215 : /* Apply new label. */
216 23 : SetSecurityLabel(&address, provider->provider_name, stmt->label);
217 :
218 : /*
219 : * If get_object_address() opened the relation for us, we close it to keep
220 : * the reference count correct - but we retain any locks acquired by
221 : * get_object_address() until commit time, to guard against concurrent
222 : * activity.
223 : */
224 23 : if (relation != NULL)
225 7 : relation_close(relation, NoLock);
226 :
227 23 : return address;
228 : }
229 :
230 : /*
231 : * GetSharedSecurityLabel returns the security label for a shared object for
232 : * a given provider, or NULL if there is no such label.
233 : */
234 : static char *
235 0 : GetSharedSecurityLabel(const ObjectAddress *object, const char *provider)
236 : {
237 : Relation pg_shseclabel;
238 : ScanKeyData keys[3];
239 : SysScanDesc scan;
240 : HeapTuple tuple;
241 : Datum datum;
242 : bool isnull;
243 0 : char *seclabel = NULL;
244 :
245 0 : ScanKeyInit(&keys[0],
246 : Anum_pg_shseclabel_objoid,
247 : BTEqualStrategyNumber, F_OIDEQ,
248 0 : ObjectIdGetDatum(object->objectId));
249 0 : ScanKeyInit(&keys[1],
250 : Anum_pg_shseclabel_classoid,
251 : BTEqualStrategyNumber, F_OIDEQ,
252 0 : ObjectIdGetDatum(object->classId));
253 0 : ScanKeyInit(&keys[2],
254 : Anum_pg_shseclabel_provider,
255 : BTEqualStrategyNumber, F_TEXTEQ,
256 0 : CStringGetTextDatum(provider));
257 :
258 0 : pg_shseclabel = table_open(SharedSecLabelRelationId, AccessShareLock);
259 :
260 0 : scan = systable_beginscan(pg_shseclabel, SharedSecLabelObjectIndexId,
261 : criticalSharedRelcachesBuilt, NULL, 3, keys);
262 :
263 0 : tuple = systable_getnext(scan);
264 0 : if (HeapTupleIsValid(tuple))
265 : {
266 0 : datum = heap_getattr(tuple, Anum_pg_shseclabel_label,
267 : RelationGetDescr(pg_shseclabel), &isnull);
268 0 : if (!isnull)
269 0 : seclabel = TextDatumGetCString(datum);
270 : }
271 0 : systable_endscan(scan);
272 :
273 0 : table_close(pg_shseclabel, AccessShareLock);
274 :
275 0 : return seclabel;
276 : }
277 :
278 : /*
279 : * GetSecurityLabel returns the security label for a shared or database object
280 : * for a given provider, or NULL if there is no such label.
281 : */
282 : char *
283 0 : GetSecurityLabel(const ObjectAddress *object, const char *provider)
284 : {
285 : Relation pg_seclabel;
286 : ScanKeyData keys[4];
287 : SysScanDesc scan;
288 : HeapTuple tuple;
289 : Datum datum;
290 : bool isnull;
291 0 : char *seclabel = NULL;
292 :
293 : /* Shared objects have their own security label catalog. */
294 0 : if (IsSharedRelation(object->classId))
295 0 : return GetSharedSecurityLabel(object, provider);
296 :
297 : /* Must be an unshared object, so examine pg_seclabel. */
298 0 : ScanKeyInit(&keys[0],
299 : Anum_pg_seclabel_objoid,
300 : BTEqualStrategyNumber, F_OIDEQ,
301 0 : ObjectIdGetDatum(object->objectId));
302 0 : ScanKeyInit(&keys[1],
303 : Anum_pg_seclabel_classoid,
304 : BTEqualStrategyNumber, F_OIDEQ,
305 0 : ObjectIdGetDatum(object->classId));
306 0 : ScanKeyInit(&keys[2],
307 : Anum_pg_seclabel_objsubid,
308 : BTEqualStrategyNumber, F_INT4EQ,
309 0 : Int32GetDatum(object->objectSubId));
310 0 : ScanKeyInit(&keys[3],
311 : Anum_pg_seclabel_provider,
312 : BTEqualStrategyNumber, F_TEXTEQ,
313 0 : CStringGetTextDatum(provider));
314 :
315 0 : pg_seclabel = table_open(SecLabelRelationId, AccessShareLock);
316 :
317 0 : scan = systable_beginscan(pg_seclabel, SecLabelObjectIndexId, true,
318 : NULL, 4, keys);
319 :
320 0 : tuple = systable_getnext(scan);
321 0 : if (HeapTupleIsValid(tuple))
322 : {
323 0 : datum = heap_getattr(tuple, Anum_pg_seclabel_label,
324 : RelationGetDescr(pg_seclabel), &isnull);
325 0 : if (!isnull)
326 0 : seclabel = TextDatumGetCString(datum);
327 : }
328 0 : systable_endscan(scan);
329 :
330 0 : table_close(pg_seclabel, AccessShareLock);
331 :
332 0 : return seclabel;
333 : }
334 :
335 : /*
336 : * SetSharedSecurityLabel is a helper function of SetSecurityLabel to
337 : * handle shared database objects.
338 : */
339 : static void
340 3 : SetSharedSecurityLabel(const ObjectAddress *object,
341 : const char *provider, const char *label)
342 : {
343 : Relation pg_shseclabel;
344 : ScanKeyData keys[4];
345 : SysScanDesc scan;
346 : HeapTuple oldtup;
347 3 : HeapTuple newtup = NULL;
348 : Datum values[Natts_pg_shseclabel];
349 : bool nulls[Natts_pg_shseclabel];
350 : bool replaces[Natts_pg_shseclabel];
351 :
352 : /* Prepare to form or update a tuple, if necessary. */
353 3 : memset(nulls, false, sizeof(nulls));
354 3 : memset(replaces, false, sizeof(replaces));
355 3 : values[Anum_pg_shseclabel_objoid - 1] = ObjectIdGetDatum(object->objectId);
356 3 : values[Anum_pg_shseclabel_classoid - 1] = ObjectIdGetDatum(object->classId);
357 3 : values[Anum_pg_shseclabel_provider - 1] = CStringGetTextDatum(provider);
358 3 : if (label != NULL)
359 3 : values[Anum_pg_shseclabel_label - 1] = CStringGetTextDatum(label);
360 :
361 : /* Use the index to search for a matching old tuple */
362 3 : ScanKeyInit(&keys[0],
363 : Anum_pg_shseclabel_objoid,
364 : BTEqualStrategyNumber, F_OIDEQ,
365 3 : ObjectIdGetDatum(object->objectId));
366 3 : ScanKeyInit(&keys[1],
367 : Anum_pg_shseclabel_classoid,
368 : BTEqualStrategyNumber, F_OIDEQ,
369 3 : ObjectIdGetDatum(object->classId));
370 3 : ScanKeyInit(&keys[2],
371 : Anum_pg_shseclabel_provider,
372 : BTEqualStrategyNumber, F_TEXTEQ,
373 3 : CStringGetTextDatum(provider));
374 :
375 3 : pg_shseclabel = table_open(SharedSecLabelRelationId, RowExclusiveLock);
376 :
377 3 : scan = systable_beginscan(pg_shseclabel, SharedSecLabelObjectIndexId, true,
378 : NULL, 3, keys);
379 :
380 3 : oldtup = systable_getnext(scan);
381 3 : if (HeapTupleIsValid(oldtup))
382 : {
383 0 : if (label == NULL)
384 0 : CatalogTupleDelete(pg_shseclabel, &oldtup->t_self);
385 : else
386 : {
387 0 : replaces[Anum_pg_shseclabel_label - 1] = true;
388 0 : newtup = heap_modify_tuple(oldtup, RelationGetDescr(pg_shseclabel),
389 : values, nulls, replaces);
390 0 : CatalogTupleUpdate(pg_shseclabel, &oldtup->t_self, newtup);
391 : }
392 : }
393 3 : systable_endscan(scan);
394 :
395 : /* If we didn't find an old tuple, insert a new one */
396 3 : if (newtup == NULL && label != NULL)
397 : {
398 3 : newtup = heap_form_tuple(RelationGetDescr(pg_shseclabel),
399 : values, nulls);
400 3 : CatalogTupleInsert(pg_shseclabel, newtup);
401 : }
402 :
403 3 : if (newtup != NULL)
404 3 : heap_freetuple(newtup);
405 :
406 3 : table_close(pg_shseclabel, RowExclusiveLock);
407 3 : }
408 :
409 : /*
410 : * SetSecurityLabel attempts to set the security label for the specified
411 : * provider on the specified object to the given value. NULL means that any
412 : * existing label should be deleted.
413 : */
414 : void
415 23 : SetSecurityLabel(const ObjectAddress *object,
416 : const char *provider, const char *label)
417 : {
418 : Relation pg_seclabel;
419 : ScanKeyData keys[4];
420 : SysScanDesc scan;
421 : HeapTuple oldtup;
422 23 : HeapTuple newtup = NULL;
423 : Datum values[Natts_pg_seclabel];
424 : bool nulls[Natts_pg_seclabel];
425 : bool replaces[Natts_pg_seclabel];
426 :
427 : /* Shared objects have their own security label catalog. */
428 23 : if (IsSharedRelation(object->classId))
429 : {
430 3 : SetSharedSecurityLabel(object, provider, label);
431 3 : return;
432 : }
433 :
434 : /* Prepare to form or update a tuple, if necessary. */
435 20 : memset(nulls, false, sizeof(nulls));
436 20 : memset(replaces, false, sizeof(replaces));
437 20 : values[Anum_pg_seclabel_objoid - 1] = ObjectIdGetDatum(object->objectId);
438 20 : values[Anum_pg_seclabel_classoid - 1] = ObjectIdGetDatum(object->classId);
439 20 : values[Anum_pg_seclabel_objsubid - 1] = Int32GetDatum(object->objectSubId);
440 20 : values[Anum_pg_seclabel_provider - 1] = CStringGetTextDatum(provider);
441 20 : if (label != NULL)
442 20 : values[Anum_pg_seclabel_label - 1] = CStringGetTextDatum(label);
443 :
444 : /* Use the index to search for a matching old tuple */
445 20 : ScanKeyInit(&keys[0],
446 : Anum_pg_seclabel_objoid,
447 : BTEqualStrategyNumber, F_OIDEQ,
448 20 : ObjectIdGetDatum(object->objectId));
449 20 : ScanKeyInit(&keys[1],
450 : Anum_pg_seclabel_classoid,
451 : BTEqualStrategyNumber, F_OIDEQ,
452 20 : ObjectIdGetDatum(object->classId));
453 20 : ScanKeyInit(&keys[2],
454 : Anum_pg_seclabel_objsubid,
455 : BTEqualStrategyNumber, F_INT4EQ,
456 20 : Int32GetDatum(object->objectSubId));
457 20 : ScanKeyInit(&keys[3],
458 : Anum_pg_seclabel_provider,
459 : BTEqualStrategyNumber, F_TEXTEQ,
460 20 : CStringGetTextDatum(provider));
461 :
462 20 : pg_seclabel = table_open(SecLabelRelationId, RowExclusiveLock);
463 :
464 20 : scan = systable_beginscan(pg_seclabel, SecLabelObjectIndexId, true,
465 : NULL, 4, keys);
466 :
467 20 : oldtup = systable_getnext(scan);
468 20 : if (HeapTupleIsValid(oldtup))
469 : {
470 3 : if (label == NULL)
471 0 : CatalogTupleDelete(pg_seclabel, &oldtup->t_self);
472 : else
473 : {
474 3 : replaces[Anum_pg_seclabel_label - 1] = true;
475 3 : newtup = heap_modify_tuple(oldtup, RelationGetDescr(pg_seclabel),
476 : values, nulls, replaces);
477 3 : CatalogTupleUpdate(pg_seclabel, &oldtup->t_self, newtup);
478 : }
479 : }
480 20 : systable_endscan(scan);
481 :
482 : /* If we didn't find an old tuple, insert a new one */
483 20 : if (newtup == NULL && label != NULL)
484 : {
485 17 : newtup = heap_form_tuple(RelationGetDescr(pg_seclabel),
486 : values, nulls);
487 17 : CatalogTupleInsert(pg_seclabel, newtup);
488 : }
489 :
490 : /* Update indexes, if necessary */
491 20 : if (newtup != NULL)
492 20 : heap_freetuple(newtup);
493 :
494 20 : table_close(pg_seclabel, RowExclusiveLock);
495 : }
496 :
497 : /*
498 : * DeleteSharedSecurityLabel is a helper function of DeleteSecurityLabel
499 : * to handle shared database objects.
500 : */
501 : void
502 1062 : DeleteSharedSecurityLabel(Oid objectId, Oid classId)
503 : {
504 : Relation pg_shseclabel;
505 : ScanKeyData skey[2];
506 : SysScanDesc scan;
507 : HeapTuple oldtup;
508 :
509 1062 : ScanKeyInit(&skey[0],
510 : Anum_pg_shseclabel_objoid,
511 : BTEqualStrategyNumber, F_OIDEQ,
512 : ObjectIdGetDatum(objectId));
513 1062 : ScanKeyInit(&skey[1],
514 : Anum_pg_shseclabel_classoid,
515 : BTEqualStrategyNumber, F_OIDEQ,
516 : ObjectIdGetDatum(classId));
517 :
518 1062 : pg_shseclabel = table_open(SharedSecLabelRelationId, RowExclusiveLock);
519 :
520 1062 : scan = systable_beginscan(pg_shseclabel, SharedSecLabelObjectIndexId, true,
521 : NULL, 2, skey);
522 1064 : while (HeapTupleIsValid(oldtup = systable_getnext(scan)))
523 2 : CatalogTupleDelete(pg_shseclabel, &oldtup->t_self);
524 1062 : systable_endscan(scan);
525 :
526 1062 : table_close(pg_shseclabel, RowExclusiveLock);
527 1062 : }
528 :
529 : /*
530 : * DeleteSecurityLabel removes all security labels for an object (and any
531 : * sub-objects, if applicable).
532 : */
533 : void
534 143626 : DeleteSecurityLabel(const ObjectAddress *object)
535 : {
536 : Relation pg_seclabel;
537 : ScanKeyData skey[3];
538 : SysScanDesc scan;
539 : HeapTuple oldtup;
540 : int nkeys;
541 :
542 : /* Shared objects have their own security label catalog. */
543 143626 : if (IsSharedRelation(object->classId))
544 : {
545 : Assert(object->objectSubId == 0);
546 4 : DeleteSharedSecurityLabel(object->objectId, object->classId);
547 4 : return;
548 : }
549 :
550 143622 : ScanKeyInit(&skey[0],
551 : Anum_pg_seclabel_objoid,
552 : BTEqualStrategyNumber, F_OIDEQ,
553 143622 : ObjectIdGetDatum(object->objectId));
554 143622 : ScanKeyInit(&skey[1],
555 : Anum_pg_seclabel_classoid,
556 : BTEqualStrategyNumber, F_OIDEQ,
557 143622 : ObjectIdGetDatum(object->classId));
558 143622 : if (object->objectSubId != 0)
559 : {
560 1384 : ScanKeyInit(&skey[2],
561 : Anum_pg_seclabel_objsubid,
562 : BTEqualStrategyNumber, F_INT4EQ,
563 1384 : Int32GetDatum(object->objectSubId));
564 1384 : nkeys = 3;
565 : }
566 : else
567 142238 : nkeys = 2;
568 :
569 143622 : pg_seclabel = table_open(SecLabelRelationId, RowExclusiveLock);
570 :
571 143622 : scan = systable_beginscan(pg_seclabel, SecLabelObjectIndexId, true,
572 : NULL, nkeys, skey);
573 143627 : while (HeapTupleIsValid(oldtup = systable_getnext(scan)))
574 5 : CatalogTupleDelete(pg_seclabel, &oldtup->t_self);
575 143622 : systable_endscan(scan);
576 :
577 143622 : table_close(pg_seclabel, RowExclusiveLock);
578 : }
579 :
580 : void
581 15 : register_label_provider(const char *provider_name, check_object_relabel_type hook)
582 : {
583 : LabelProvider *provider;
584 : MemoryContext oldcxt;
585 :
586 15 : oldcxt = MemoryContextSwitchTo(TopMemoryContext);
587 15 : provider = palloc_object(LabelProvider);
588 15 : provider->provider_name = pstrdup(provider_name);
589 15 : provider->hook = hook;
590 15 : label_provider_list = lappend(label_provider_list, provider);
591 15 : MemoryContextSwitchTo(oldcxt);
592 15 : }
|