Path: blob/main/sys/contrib/openzfs/lib/libzfs/libzfs_changelist.c
109341 views
// SPDX-License-Identifier: CDDL-1.01/*2* CDDL HEADER START3*4* The contents of this file are subject to the terms of the5* Common Development and Distribution License (the "License").6* You may not use this file except in compliance with the License.7*8* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE9* or https://opensource.org/licenses/CDDL-1.0.10* See the License for the specific language governing permissions11* and limitations under the License.12*13* When distributing Covered Code, include this CDDL HEADER in each14* file and include the License file at usr/src/OPENSOLARIS.LICENSE.15* If applicable, add the following below this CDDL HEADER, with the16* fields enclosed by brackets "[]" replaced with your own identifying17* information: Portions Copyright [yyyy] [name of copyright owner]18*19* CDDL HEADER END20*/2122/*23* Copyright 2010 Sun Microsystems, Inc. All rights reserved.24* Use is subject to license terms.25*26* Portions Copyright 2007 Ramprakash Jelari27* Copyright (c) 2014, 2020 by Delphix. All rights reserved.28* Copyright 2016 Igor Kozhukhov <[email protected]>29* Copyright (c) 2018 Datto Inc.30*/3132#include <libintl.h>33#include <stddef.h>34#include <stdlib.h>35#include <string.h>36#include <unistd.h>37#include <zone.h>38#include <sys/avl.h>3940#include <libzfs.h>4142#include "libzfs_impl.h"4344/*45* Structure to keep track of dataset state. Before changing the 'sharenfs' or46* 'mountpoint' property, we record whether the filesystem was previously47* mounted/shared. This prior state dictates whether we remount/reshare the48* dataset after the property has been changed.49*50* The interface consists of the following sequence of functions:51*52* changelist_gather()53* changelist_prefix()54* < change property >55* changelist_postfix()56* changelist_free()57*58* Other interfaces:59*60* changelist_remove() - remove a node from a gathered list61* changelist_rename() - renames all datasets appropriately when doing a rename62* changelist_unshare() - unshares all the nodes in a given changelist63* changelist_haszonedchild() - check if there is any child exported to64* a local zone65*/66typedef struct prop_changenode {67zfs_handle_t *cn_handle;68int cn_shared;69int cn_mounted;70int cn_zoned;71boolean_t cn_needpost; /* is postfix() needed? */72avl_node_t cn_treenode;73} prop_changenode_t;7475struct prop_changelist {76zfs_prop_t cl_prop;77zfs_prop_t cl_realprop;78zfs_prop_t cl_shareprop; /* used with sharenfs/sharesmb */79avl_tree_t cl_tree;80boolean_t cl_waslegacy;81boolean_t cl_allchildren;82boolean_t cl_alldependents;83int cl_mflags; /* Mount flags */84int cl_gflags; /* Gather request flags */85boolean_t cl_haszonedchild;86};8788/*89* If the property is 'mountpoint', go through and unmount filesystems as90* necessary. We don't do the same for 'sharenfs', because we can just re-share91* with different options without interrupting service. We do handle 'sharesmb'92* since there may be old resource names that need to be removed.93*/94int95changelist_prefix(prop_changelist_t *clp)96{97prop_changenode_t *cn;98int ret = 0;99const enum sa_protocol smb[] = {SA_PROTOCOL_SMB, SA_NO_PROTOCOL};100boolean_t commit_smb_shares = B_FALSE;101102if (clp->cl_prop != ZFS_PROP_MOUNTPOINT &&103clp->cl_prop != ZFS_PROP_SHARESMB)104return (0);105106/*107* If CL_GATHER_DONT_UNMOUNT is set, don't want to unmount/unshare and108* later (re)mount/(re)share the filesystem in postfix phase, so we109* return from here. If filesystem is mounted or unmounted, leave it110* as it is.111*/112if (clp->cl_gflags & CL_GATHER_DONT_UNMOUNT)113return (0);114115for (cn = avl_first(&clp->cl_tree); cn != NULL;116cn = AVL_NEXT(&clp->cl_tree, cn)) {117118/* if a previous loop failed, set the remaining to false */119if (ret == -1) {120cn->cn_needpost = B_FALSE;121continue;122}123124/*125* If we are in the global zone, but this dataset is exported126* to a local zone, do nothing.127*/128if (getzoneid() == GLOBAL_ZONEID && cn->cn_zoned)129continue;130131if (!ZFS_IS_VOLUME(cn->cn_handle)) {132/*133* Do the property specific processing.134*/135switch (clp->cl_prop) {136case ZFS_PROP_MOUNTPOINT:137if (zfs_unmount(cn->cn_handle, NULL,138clp->cl_mflags) != 0) {139ret = -1;140cn->cn_needpost = B_FALSE;141}142break;143case ZFS_PROP_SHARESMB:144(void) zfs_unshare(cn->cn_handle, NULL,145smb);146commit_smb_shares = B_TRUE;147break;148149default:150break;151}152}153}154155if (commit_smb_shares)156zfs_commit_shares(smb);157158if (ret == -1)159(void) changelist_postfix(clp);160161return (ret);162}163164/*165* If the property is 'mountpoint' or 'sharenfs', go through and remount and/or166* reshare the filesystems as necessary. In changelist_gather() we recorded167* whether the filesystem was previously shared or mounted. The action we take168* depends on the previous state, and whether the value was previously 'legacy'.169* For non-legacy properties, we always remount/reshare the filesystem,170* if CL_GATHER_DONT_UNMOUNT is not set.171*/172int173changelist_postfix(prop_changelist_t *clp)174{175prop_changenode_t *cn;176char shareopts[ZFS_MAXPROPLEN];177boolean_t commit_smb_shares = B_FALSE;178boolean_t commit_nfs_shares = B_FALSE;179180/*181* If CL_GATHER_DONT_UNMOUNT is set, it means we don't want to (un)mount182* or (re/un)share the filesystem, so we return from here. If filesystem183* is mounted or unmounted, leave it as it is.184*/185if (clp->cl_gflags & CL_GATHER_DONT_UNMOUNT)186return (0);187188/*189* If we're changing the mountpoint, attempt to destroy the underlying190* mountpoint. All other datasets will have inherited from this dataset191* (in which case their mountpoints exist in the filesystem in the new192* location), or have explicit mountpoints set (in which case they won't193* be in the changelist).194*/195if ((cn = avl_last(&clp->cl_tree)) == NULL)196return (0);197198if (clp->cl_prop == ZFS_PROP_MOUNTPOINT &&199!(clp->cl_gflags & CL_GATHER_DONT_UNMOUNT))200remove_mountpoint(cn->cn_handle);201202/*203* We walk the datasets in reverse, because we want to mount any parent204* datasets before mounting the children. We walk all datasets even if205* there are errors.206*/207for (cn = avl_last(&clp->cl_tree); cn != NULL;208cn = AVL_PREV(&clp->cl_tree, cn)) {209210boolean_t sharenfs;211boolean_t sharesmb;212boolean_t mounted;213boolean_t needs_key;214215/*216* If we are in the global zone, but this dataset is exported217* to a local zone, do nothing.218*/219if (getzoneid() == GLOBAL_ZONEID && cn->cn_zoned)220continue;221222/* Only do post-processing if it's required */223if (!cn->cn_needpost)224continue;225cn->cn_needpost = B_FALSE;226227zfs_refresh_properties(cn->cn_handle);228229if (ZFS_IS_VOLUME(cn->cn_handle))230continue;231232/*233* Remount if previously mounted or mountpoint was legacy,234* or sharenfs or sharesmb property is set.235*/236sharenfs = ((zfs_prop_get(cn->cn_handle, ZFS_PROP_SHARENFS,237shareopts, sizeof (shareopts), NULL, NULL, 0,238B_FALSE) == 0) && (strcmp(shareopts, "off") != 0));239240sharesmb = ((zfs_prop_get(cn->cn_handle, ZFS_PROP_SHARESMB,241shareopts, sizeof (shareopts), NULL, NULL, 0,242B_FALSE) == 0) && (strcmp(shareopts, "off") != 0));243244needs_key = (zfs_prop_get_int(cn->cn_handle,245ZFS_PROP_KEYSTATUS) == ZFS_KEYSTATUS_UNAVAILABLE);246247mounted = zfs_is_mounted(cn->cn_handle, NULL);248249if (!mounted && !needs_key && (cn->cn_mounted ||250(((clp->cl_prop == ZFS_PROP_MOUNTPOINT &&251clp->cl_prop == clp->cl_realprop) ||252sharenfs || sharesmb || clp->cl_waslegacy) &&253(zfs_prop_get_int(cn->cn_handle,254ZFS_PROP_CANMOUNT) == ZFS_CANMOUNT_ON)))) {255256if (zfs_mount(cn->cn_handle, NULL, 0) == 0)257mounted = TRUE;258}259260/*261* If the file system is mounted we always re-share even262* if the filesystem is currently shared, so that we can263* adopt any new options.264*/265const enum sa_protocol nfs[] =266{SA_PROTOCOL_NFS, SA_NO_PROTOCOL};267if (sharenfs && mounted) {268zfs_share(cn->cn_handle, nfs);269commit_nfs_shares = B_TRUE;270} else if (cn->cn_shared || clp->cl_waslegacy) {271zfs_unshare(cn->cn_handle, NULL, nfs);272commit_nfs_shares = B_TRUE;273}274const enum sa_protocol smb[] =275{SA_PROTOCOL_SMB, SA_NO_PROTOCOL};276if (sharesmb && mounted) {277zfs_share(cn->cn_handle, smb);278commit_smb_shares = B_TRUE;279} else if (cn->cn_shared || clp->cl_waslegacy) {280zfs_unshare(cn->cn_handle, NULL, smb);281commit_smb_shares = B_TRUE;282}283}284285enum sa_protocol proto[SA_PROTOCOL_COUNT + 1], *p = proto;286if (commit_nfs_shares)287*p++ = SA_PROTOCOL_NFS;288if (commit_smb_shares)289*p++ = SA_PROTOCOL_SMB;290*p++ = SA_NO_PROTOCOL;291zfs_commit_shares(proto);292293return (0);294}295296/*297* Is this "dataset" a child of "parent"?298*/299static boolean_t300isa_child_of(const char *dataset, const char *parent)301{302int len;303304len = strlen(parent);305306if (strncmp(dataset, parent, len) == 0 &&307(dataset[len] == '@' || dataset[len] == '/' ||308dataset[len] == '\0'))309return (B_TRUE);310else311return (B_FALSE);312313}314315/*316* If we rename a filesystem, child filesystem handles are no longer valid317* since we identify each dataset by its name in the ZFS namespace. As a318* result, we have to go through and fix up all the names appropriately. We319* could do this automatically if libzfs kept track of all open handles, but320* this is a lot less work.321*/322void323changelist_rename(prop_changelist_t *clp, const char *src, const char *dst)324{325prop_changenode_t *cn;326char newname[ZFS_MAX_DATASET_NAME_LEN];327328for (cn = avl_first(&clp->cl_tree); cn != NULL;329cn = AVL_NEXT(&clp->cl_tree, cn)) {330/*331* Do not rename a clone that's not in the source hierarchy.332*/333if (!isa_child_of(cn->cn_handle->zfs_name, src))334continue;335336/*337* Destroy the previous mountpoint if needed.338*/339remove_mountpoint(cn->cn_handle);340341(void) strlcpy(newname, dst, sizeof (newname));342(void) strlcat(newname, cn->cn_handle->zfs_name + strlen(src),343sizeof (newname));344345(void) strlcpy(cn->cn_handle->zfs_name, newname,346sizeof (cn->cn_handle->zfs_name));347}348}349350/*351* Given a gathered changelist for the 'sharenfs' or 'sharesmb' property,352* unshare all the datasets in the list.353*/354int355changelist_unshare(prop_changelist_t *clp, const enum sa_protocol *proto)356{357prop_changenode_t *cn;358int ret = 0;359360if (clp->cl_prop != ZFS_PROP_SHARENFS &&361clp->cl_prop != ZFS_PROP_SHARESMB)362return (0);363364for (cn = avl_first(&clp->cl_tree); cn != NULL;365cn = AVL_NEXT(&clp->cl_tree, cn)) {366if (zfs_unshare(cn->cn_handle, NULL, proto) != 0)367ret = -1;368}369370for (const enum sa_protocol *p = proto; *p != SA_NO_PROTOCOL; ++p)371sa_commit_shares(*p);372373return (ret);374}375376/*377* Check if there is any child exported to a local zone in a given changelist.378* This information has already been recorded while gathering the changelist379* via changelist_gather().380*/381int382changelist_haszonedchild(prop_changelist_t *clp)383{384return (clp->cl_haszonedchild);385}386387/*388* Remove a node from a gathered list.389*/390void391changelist_remove(prop_changelist_t *clp, const char *name)392{393prop_changenode_t *cn;394395for (cn = avl_first(&clp->cl_tree); cn != NULL;396cn = AVL_NEXT(&clp->cl_tree, cn)) {397if (strcmp(cn->cn_handle->zfs_name, name) == 0) {398avl_remove(&clp->cl_tree, cn);399zfs_close(cn->cn_handle);400free(cn);401return;402}403}404}405406/*407* Release any memory associated with a changelist.408*/409void410changelist_free(prop_changelist_t *clp)411{412prop_changenode_t *cn;413void *cookie = NULL;414415while ((cn = avl_destroy_nodes(&clp->cl_tree, &cookie)) != NULL) {416zfs_close(cn->cn_handle);417free(cn);418}419420avl_destroy(&clp->cl_tree);421free(clp);422}423424/*425* Add one dataset to changelist426*/427static int428changelist_add_mounted(zfs_handle_t *zhp, void *data)429{430prop_changelist_t *clp = data;431prop_changenode_t *cn;432avl_index_t idx;433434ASSERT3U(clp->cl_prop, ==, ZFS_PROP_MOUNTPOINT);435436cn = zfs_alloc(zfs_get_handle(zhp), sizeof (prop_changenode_t));437cn->cn_handle = zhp;438cn->cn_mounted = zfs_is_mounted(zhp, NULL);439ASSERT3U(cn->cn_mounted, ==, B_TRUE);440cn->cn_shared = zfs_is_shared(zhp, NULL, NULL);441cn->cn_zoned = zfs_prop_get_int(zhp, ZFS_PROP_ZONED);442cn->cn_needpost = B_TRUE;443444/* Indicate if any child is exported to a local zone. */445if (getzoneid() == GLOBAL_ZONEID && cn->cn_zoned)446clp->cl_haszonedchild = B_TRUE;447448if (avl_find(&clp->cl_tree, cn, &idx) == NULL) {449avl_insert(&clp->cl_tree, cn, idx);450} else {451free(cn);452zfs_close(zhp);453}454455return (0);456}457458static int459change_one(zfs_handle_t *zhp, void *data)460{461prop_changelist_t *clp = data;462char property[ZFS_MAXPROPLEN];463char where[64];464prop_changenode_t *cn = NULL;465zprop_source_t sourcetype = ZPROP_SRC_NONE;466zprop_source_t share_sourcetype = ZPROP_SRC_NONE;467int ret = 0;468469/*470* We only want to unmount/unshare those filesystems that may inherit471* from the target filesystem. If we find any filesystem with a472* locally set mountpoint, we ignore any children since changing the473* property will not affect them. If this is a rename, we iterate474* over all children regardless, since we need them unmounted in475* order to do the rename. Also, if this is a volume and we're doing476* a rename, then always add it to the changelist.477*/478479if (!(ZFS_IS_VOLUME(zhp) && clp->cl_realprop == ZFS_PROP_NAME) &&480zfs_prop_get(zhp, clp->cl_prop, property,481sizeof (property), &sourcetype, where, sizeof (where),482B_FALSE) != 0) {483goto out;484}485486/*487* If we are "watching" sharenfs or sharesmb488* then check out the companion property which is tracked489* in cl_shareprop490*/491if (clp->cl_shareprop != ZPROP_INVAL &&492zfs_prop_get(zhp, clp->cl_shareprop, property,493sizeof (property), &share_sourcetype, where, sizeof (where),494B_FALSE) != 0) {495goto out;496}497498if (clp->cl_alldependents || clp->cl_allchildren ||499sourcetype == ZPROP_SRC_DEFAULT ||500sourcetype == ZPROP_SRC_INHERITED ||501(clp->cl_shareprop != ZPROP_INVAL &&502(share_sourcetype == ZPROP_SRC_DEFAULT ||503share_sourcetype == ZPROP_SRC_INHERITED))) {504cn = zfs_alloc(zfs_get_handle(zhp), sizeof (prop_changenode_t));505cn->cn_handle = zhp;506cn->cn_mounted = (clp->cl_gflags & CL_GATHER_MOUNT_ALWAYS) ||507zfs_is_mounted(zhp, NULL);508cn->cn_shared = zfs_is_shared(zhp, NULL, NULL);509cn->cn_zoned = zfs_prop_get_int(zhp, ZFS_PROP_ZONED);510cn->cn_needpost = B_TRUE;511512/* Indicate if any child is exported to a local zone. */513if (getzoneid() == GLOBAL_ZONEID && cn->cn_zoned)514clp->cl_haszonedchild = B_TRUE;515516avl_index_t idx;517if (avl_find(&clp->cl_tree, cn, &idx) == NULL) {518avl_insert(&clp->cl_tree, cn, idx);519} else {520free(cn);521cn = NULL;522}523524if (!clp->cl_alldependents) {525if (clp->cl_prop != ZFS_PROP_MOUNTPOINT) {526ret = zfs_iter_filesystems_v2(zhp, 0,527change_one, data);528} else {529ret = zfs_iter_children_v2(zhp, 0, change_one,530data);531}532}533534/*535* If we added the handle to the changelist, we will re-use it536* later so return without closing it.537*/538if (cn != NULL)539return (ret);540}541542out:543zfs_close(zhp);544return (ret);545}546547static int548compare_props(const void *a, const void *b, zfs_prop_t prop)549{550const prop_changenode_t *ca = a;551const prop_changenode_t *cb = b;552553char propa[MAXPATHLEN];554char propb[MAXPATHLEN];555556boolean_t haspropa, haspropb;557558haspropa = (zfs_prop_get(ca->cn_handle, prop, propa, sizeof (propa),559NULL, NULL, 0, B_FALSE) == 0);560haspropb = (zfs_prop_get(cb->cn_handle, prop, propb, sizeof (propb),561NULL, NULL, 0, B_FALSE) == 0);562563if (!haspropa && haspropb)564return (-1);565else if (haspropa && !haspropb)566return (1);567else if (!haspropa && !haspropb)568return (0);569else570return (TREE_ISIGN(strcmp(propb, propa)));571}572573static int574compare_mountpoints(const void *a, const void *b)575{576/*577* When unsharing or unmounting filesystems, we need to do it in578* mountpoint order. This allows the user to have a mountpoint579* hierarchy that is different from the dataset hierarchy, and still580* allow it to be changed.581*/582return (compare_props(a, b, ZFS_PROP_MOUNTPOINT));583}584585static int586compare_dataset_names(const void *a, const void *b)587{588return (compare_props(a, b, ZFS_PROP_NAME));589}590591/*592* Given a ZFS handle and a property, construct a complete list of datasets593* that need to be modified as part of this process. For anything but the594* 'mountpoint' and 'sharenfs' properties, this just returns an empty list.595* Otherwise, we iterate over all children and look for any datasets that596* inherit the property. For each such dataset, we add it to the list and597* mark whether it was shared beforehand.598*/599prop_changelist_t *600changelist_gather(zfs_handle_t *zhp, zfs_prop_t prop, int gather_flags,601int mnt_flags)602{603prop_changelist_t *clp;604prop_changenode_t *cn;605zfs_handle_t *temp;606char property[ZFS_MAXPROPLEN];607boolean_t legacy = B_FALSE;608609clp = zfs_alloc(zhp->zfs_hdl, sizeof (prop_changelist_t));610611/*612* For mountpoint-related tasks, we want to sort everything by613* mountpoint, so that we mount and unmount them in the appropriate614* order, regardless of their position in the hierarchy.615*/616if (prop == ZFS_PROP_NAME || prop == ZFS_PROP_ZONED ||617prop == ZFS_PROP_MOUNTPOINT || prop == ZFS_PROP_SHARENFS ||618prop == ZFS_PROP_SHARESMB) {619620if (zfs_prop_get(zhp, ZFS_PROP_MOUNTPOINT,621property, sizeof (property),622NULL, NULL, 0, B_FALSE) == 0 &&623(strcmp(property, "legacy") == 0 ||624strcmp(property, "none") == 0)) {625legacy = B_TRUE;626}627}628629avl_create(&clp->cl_tree,630legacy ? compare_dataset_names : compare_mountpoints,631sizeof (prop_changenode_t),632offsetof(prop_changenode_t, cn_treenode));633634clp->cl_gflags = gather_flags;635clp->cl_mflags = mnt_flags;636637/*638* If this is a rename or the 'zoned' property, we pretend we're639* changing the mountpoint and flag it so we can catch all children in640* change_one().641*642* Flag cl_alldependents to catch all children plus the dependents643* (clones) that are not in the hierarchy.644*/645if (prop == ZFS_PROP_NAME) {646clp->cl_prop = ZFS_PROP_MOUNTPOINT;647clp->cl_alldependents = B_TRUE;648} else if (prop == ZFS_PROP_ZONED) {649clp->cl_prop = ZFS_PROP_MOUNTPOINT;650clp->cl_allchildren = B_TRUE;651} else if (prop == ZFS_PROP_CANMOUNT) {652clp->cl_prop = ZFS_PROP_MOUNTPOINT;653} else if (prop == ZFS_PROP_VOLSIZE) {654clp->cl_prop = ZFS_PROP_MOUNTPOINT;655} else {656clp->cl_prop = prop;657}658clp->cl_realprop = prop;659660if (clp->cl_prop != ZFS_PROP_MOUNTPOINT &&661clp->cl_prop != ZFS_PROP_SHARENFS &&662clp->cl_prop != ZFS_PROP_SHARESMB)663return (clp);664665/*666* If watching SHARENFS or SHARESMB then667* also watch its companion property.668*/669if (clp->cl_prop == ZFS_PROP_SHARENFS)670clp->cl_shareprop = ZFS_PROP_SHARESMB;671else if (clp->cl_prop == ZFS_PROP_SHARESMB)672clp->cl_shareprop = ZFS_PROP_SHARENFS;673674if (clp->cl_prop == ZFS_PROP_MOUNTPOINT &&675(clp->cl_gflags & CL_GATHER_ITER_MOUNTED)) {676/*677* Instead of iterating through all of the dataset children we678* gather mounted dataset children from MNTTAB679*/680if (zfs_iter_mounted(zhp, changelist_add_mounted, clp) != 0) {681changelist_free(clp);682return (NULL);683}684} else if (clp->cl_alldependents) {685if (zfs_iter_dependents_v2(zhp, 0, B_TRUE, change_one,686clp) != 0) {687changelist_free(clp);688return (NULL);689}690} else if (clp->cl_prop != ZFS_PROP_MOUNTPOINT) {691if (zfs_iter_filesystems_v2(zhp, 0, change_one, clp) != 0) {692changelist_free(clp);693return (NULL);694}695} else if (zfs_iter_children_v2(zhp, 0, change_one, clp) != 0) {696changelist_free(clp);697return (NULL);698}699700/*701* We have to re-open ourselves because we auto-close all the handles702* and can't tell the difference.703*/704if ((temp = zfs_open(zhp->zfs_hdl, zfs_get_name(zhp),705ZFS_TYPE_DATASET)) == NULL) {706changelist_free(clp);707return (NULL);708}709710/*711* Always add ourself to the list. We add ourselves to the end so that712* we're the last to be unmounted.713*/714cn = zfs_alloc(zhp->zfs_hdl, sizeof (prop_changenode_t));715cn->cn_handle = temp;716cn->cn_mounted = (clp->cl_gflags & CL_GATHER_MOUNT_ALWAYS) ||717zfs_is_mounted(temp, NULL);718cn->cn_shared = zfs_is_shared(temp, NULL, NULL);719cn->cn_zoned = zfs_prop_get_int(zhp, ZFS_PROP_ZONED);720cn->cn_needpost = B_TRUE;721722avl_index_t idx;723if (avl_find(&clp->cl_tree, cn, &idx) == NULL) {724avl_insert(&clp->cl_tree, cn, idx);725} else {726free(cn);727zfs_close(temp);728}729730/*731* If the mountpoint property was previously 'legacy', or 'none',732* record it as the behavior of changelist_postfix() will be different.733*/734if ((clp->cl_prop == ZFS_PROP_MOUNTPOINT) && legacy) {735/*736* do not automatically mount ex-legacy datasets if737* we specifically set canmount to noauto738*/739if (zfs_prop_get_int(zhp, ZFS_PROP_CANMOUNT) !=740ZFS_CANMOUNT_NOAUTO)741clp->cl_waslegacy = B_TRUE;742}743744return (clp);745}746747748