xref: /illumos-gate/usr/src/uts/common/fs/smbsrv/smb_vss.c (revision b1352070d318187b41b088da3533692976f3f225)
189dc44ceSjose borrego /*
289dc44ceSjose borrego  * CDDL HEADER START
389dc44ceSjose borrego  *
489dc44ceSjose borrego  * The contents of this file are subject to the terms of the
589dc44ceSjose borrego  * Common Development and Distribution License (the "License").
689dc44ceSjose borrego  * You may not use this file except in compliance with the License.
789dc44ceSjose borrego  *
889dc44ceSjose borrego  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
989dc44ceSjose borrego  * or http://www.opensolaris.org/os/licensing.
1089dc44ceSjose borrego  * See the License for the specific language governing permissions
1189dc44ceSjose borrego  * and limitations under the License.
1289dc44ceSjose borrego  *
1389dc44ceSjose borrego  * When distributing Covered Code, include this CDDL HEADER in each
1489dc44ceSjose borrego  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
1589dc44ceSjose borrego  * If applicable, add the following below this CDDL HEADER, with the
1689dc44ceSjose borrego  * fields enclosed by brackets "[]" replaced with your own identifying
1789dc44ceSjose borrego  * information: Portions Copyright [yyyy] [name of copyright owner]
1889dc44ceSjose borrego  *
1989dc44ceSjose borrego  * CDDL HEADER END
2089dc44ceSjose borrego  */
2189dc44ceSjose borrego /*
2289dc44ceSjose borrego  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
2389dc44ceSjose borrego  * Use is subject to license terms.
2489dc44ceSjose borrego  */
2589dc44ceSjose borrego 
2689dc44ceSjose borrego /*
2789dc44ceSjose borrego  * Volume Copy Shadow Services (VSS) provides a way for users to
2889dc44ceSjose borrego  * restore/recover deleted files/directories.
2989dc44ceSjose borrego  * For the server to support VSS for Microsoft clients, there is
3089dc44ceSjose borrego  * two basic functions that need to be implemented.
3189dc44ceSjose borrego  * The first is to intercept the NT_TRANSACT_IOCTL command with
3289dc44ceSjose borrego  * the function code of FSCTL_SRV_ENUMERATE_SNAPSHOTS (0x00144064).
3389dc44ceSjose borrego  * This is to report the count or the count and list of snapshots
3489dc44ceSjose borrego  * for that share.
3589dc44ceSjose borrego  * The second function need to trap commands with the
3689dc44ceSjose borrego  * SMB_FLAGS2_REPARSE_PATH bit set in the smb header.  This bit
3789dc44ceSjose borrego  * means that there is a @GMT token in path that needs to be
3889dc44ceSjose borrego  * processed.  The @GMT token means to process this command, but
3989dc44ceSjose borrego  * in the snapshot.
4089dc44ceSjose borrego  */
4189dc44ceSjose borrego 
4289dc44ceSjose borrego #include <smbsrv/smb_incl.h>
4389dc44ceSjose borrego #include <smbsrv/winioctl.h>
4489dc44ceSjose borrego #include <smbsrv/ntstatus.h>
4589dc44ceSjose borrego #include <smbsrv/smb_door_svc.h>
4689dc44ceSjose borrego 
4789dc44ceSjose borrego /* Size of the token on the wire due to encoding */
4889dc44ceSjose borrego #define	SMB_VSS_GMT_NET_SIZE(sr) (smb_ascii_or_unicode_null_len(sr) * \
4989dc44ceSjose borrego     SMB_VSS_GMT_SIZE)
5089dc44ceSjose borrego 
5189dc44ceSjose borrego #define	SMB_VSS_COUNT_SIZE 16
5289dc44ceSjose borrego 
5389dc44ceSjose borrego static boolean_t smb_vss_is_gmttoken(const char *str);
5489dc44ceSjose borrego static const char *smb_vss_find_gmttoken(const char *path);
5589dc44ceSjose borrego static int smb_vss_get_fsmountpath(smb_request_t *sr, char *buf,
5689dc44ceSjose borrego     uint32_t buflen);
5789dc44ceSjose borrego static uint32_t smb_vss_encode_gmttokens(smb_request_t *sr, smb_xa_t *xa,
5889dc44ceSjose borrego     int32_t count, smb_dr_return_gmttokens_t *snap_data);
5989dc44ceSjose borrego static void smb_vss_remove_first_token_from_path(char *c);
6089dc44ceSjose borrego 
6189dc44ceSjose borrego /*
6289dc44ceSjose borrego  * This is to respond to the nt_transact_ioctl to either respond with the
6389dc44ceSjose borrego  * number of snapshots, or to respond with the list.  It needs to be sorted
6489dc44ceSjose borrego  * before the reply.  If the the max data bytes to return is
6589dc44ceSjose borrego  * SMB_VSS_COUNT_SIZE, then all that is requested is the count, otherwise
6689dc44ceSjose borrego  * return the count and the list of @GMT tokens (one token for each
6789dc44ceSjose borrego  * snapshot).
6889dc44ceSjose borrego  */
6989dc44ceSjose borrego uint32_t
7089dc44ceSjose borrego smb_vss_ioctl_enumerate_snaps(smb_request_t *sr, smb_xa_t *xa)
7189dc44ceSjose borrego {
7289dc44ceSjose borrego 	uint32_t count = 0;
7389dc44ceSjose borrego 	char *root_path;
74*b1352070SAlan Wright 	uint32_t status = NT_STATUS_SUCCESS;
7589dc44ceSjose borrego 	smb_dr_return_gmttokens_t gmttokens;
7689dc44ceSjose borrego 
77*b1352070SAlan Wright 	if (xa->smb_mdrcnt < SMB_VSS_COUNT_SIZE)
78*b1352070SAlan Wright 		return (NT_STATUS_INVALID_PARAMETER);
7989dc44ceSjose borrego 
8089dc44ceSjose borrego 	root_path  = kmem_zalloc(MAXPATHLEN, KM_SLEEP);
81*b1352070SAlan Wright 	if (smb_vss_get_fsmountpath(sr, root_path, MAXPATHLEN) != 0)
82*b1352070SAlan Wright 		return (NT_STATUS_INVALID_PARAMETER);
8389dc44ceSjose borrego 
8489dc44ceSjose borrego 	if (xa->smb_mdrcnt == SMB_VSS_COUNT_SIZE) {
8589dc44ceSjose borrego 		count = smb_upcall_vss_get_count(root_path);
8689dc44ceSjose borrego 		if (smb_mbc_encodef(&xa->rep_data_mb, "lllw", count, 0,
8789dc44ceSjose borrego 		    (count * SMB_VSS_GMT_NET_SIZE(sr) +
8889dc44ceSjose borrego 		    smb_ascii_or_unicode_null_len(sr)), 0) != 0) {
89*b1352070SAlan Wright 			status = NT_STATUS_INVALID_PARAMETER;
9089dc44ceSjose borrego 		}
9189dc44ceSjose borrego 	} else {
9289dc44ceSjose borrego 		count = xa->smb_mdrcnt / SMB_VSS_GMT_NET_SIZE(sr);
9389dc44ceSjose borrego 
9489dc44ceSjose borrego 		smb_upcall_vss_get_snapshots(root_path, count, &gmttokens);
9589dc44ceSjose borrego 
96*b1352070SAlan Wright 		status = smb_vss_encode_gmttokens(sr, xa, count, &gmttokens);
9789dc44ceSjose borrego 
9889dc44ceSjose borrego 		smb_upcall_vss_get_snapshots_free(&gmttokens);
9989dc44ceSjose borrego 	}
10089dc44ceSjose borrego 
10189dc44ceSjose borrego 	kmem_free(root_path, MAXPATHLEN);
102*b1352070SAlan Wright 	return (status);
10389dc44ceSjose borrego }
10489dc44ceSjose borrego 
10589dc44ceSjose borrego /*
10689dc44ceSjose borrego  * sr - the request info, used to find root of dataset,
10789dc44ceSjose borrego  *      unicode or ascii, where the share is rooted in the
10889dc44ceSjose borrego  *      dataset
10989dc44ceSjose borrego  * root_node - root of the share
11089dc44ceSjose borrego  * cur_node - where in the share for the command
11189dc44ceSjose borrego  * buf - is the path for the command to be processed
11289dc44ceSjose borrego  *       returned without @GMT if processed
11389dc44ceSjose borrego  * vss_cur_node - returned value for the snapshot version
11489dc44ceSjose borrego  *                of the cur_node
11589dc44ceSjose borrego  * vss_root_node - returned value for the snapshot version
11689dc44ceSjose borrego  *                 of the root_node
11789dc44ceSjose borrego  *
11889dc44ceSjose borrego  * This routine is the processing for handling the
11989dc44ceSjose borrego  * SMB_FLAGS2_REPARSE_PATH bit being set in the smb header.
12089dc44ceSjose borrego  *
12189dc44ceSjose borrego  * By using the cur_node passed in, a new node is found or
12289dc44ceSjose borrego  * created that is the same place in the directory tree, but
12389dc44ceSjose borrego  * in the snapshot. We also use root_node to do the same for
12489dc44ceSjose borrego  * the root.
12589dc44ceSjose borrego  * One the new smb node is found, the path is modified by
12689dc44ceSjose borrego  * removing the @GMT token from the path in the buf.
12789dc44ceSjose borrego  */
12889dc44ceSjose borrego int
12989dc44ceSjose borrego smb_vss_lookup_nodes(smb_request_t *sr, smb_node_t *root_node,
13089dc44ceSjose borrego     smb_node_t *cur_node, char *buf, smb_node_t **vss_cur_node,
13189dc44ceSjose borrego     smb_node_t **vss_root_node)
13289dc44ceSjose borrego {
13389dc44ceSjose borrego 	const char *p;
13489dc44ceSjose borrego 	char *rootpath;
13589dc44ceSjose borrego 	char *snapname;
13689dc44ceSjose borrego 	char *nodepath;
13789dc44ceSjose borrego 	char gmttoken[SMB_VSS_GMT_SIZE];
13889dc44ceSjose borrego 	smb_attr_t attr;
13989dc44ceSjose borrego 	vnode_t *fsrootvp;
14089dc44ceSjose borrego 	vnode_t *vp = NULL;
14189dc44ceSjose borrego 	int err = 0;
14289dc44ceSjose borrego 
14389dc44ceSjose borrego 	if (sr->tid_tree == NULL)
14489dc44ceSjose borrego 		return (ESTALE);
14589dc44ceSjose borrego 
14689dc44ceSjose borrego 	ASSERT(sr->tid_tree->t_snode);
14789dc44ceSjose borrego 	ASSERT(sr->tid_tree->t_snode->vp);
14889dc44ceSjose borrego 	ASSERT(sr->tid_tree->t_snode->vp->v_vfsp);
14989dc44ceSjose borrego 
1506e3e9d9cSafshin salek ardakani - Sun Microsystems - Irvine United States 	if ((p = smb_vss_find_gmttoken(buf)) == NULL)
15189dc44ceSjose borrego 		return (ENOENT);
15289dc44ceSjose borrego 
15389dc44ceSjose borrego 	bcopy(p, gmttoken, SMB_VSS_GMT_SIZE);
15489dc44ceSjose borrego 	gmttoken[SMB_VSS_GMT_SIZE - 1] = '\0';
15589dc44ceSjose borrego 
1566e3e9d9cSafshin salek ardakani - Sun Microsystems - Irvine United States 	err = VFS_ROOT(sr->tid_tree->t_snode->vp->v_vfsp, &fsrootvp);
1576e3e9d9cSafshin salek ardakani - Sun Microsystems - Irvine United States 	if (err != 0)
1586e3e9d9cSafshin salek ardakani - Sun Microsystems - Irvine United States 		return (err);
15989dc44ceSjose borrego 
16089dc44ceSjose borrego 	rootpath = kmem_alloc(MAXPATHLEN, KM_SLEEP);
16189dc44ceSjose borrego 	snapname = kmem_alloc(MAXNAMELEN, KM_SLEEP);
16289dc44ceSjose borrego 	nodepath = kmem_alloc(MAXPATHLEN, KM_SLEEP);
16389dc44ceSjose borrego 
16489dc44ceSjose borrego 	err = smb_vss_get_fsmountpath(sr, rootpath, MAXPATHLEN);
1656e3e9d9cSafshin salek ardakani - Sun Microsystems - Irvine United States 	if (err != 0)
16689dc44ceSjose borrego 		goto error;
16789dc44ceSjose borrego 
16889dc44ceSjose borrego 	*snapname = '\0';
16989dc44ceSjose borrego 
17089dc44ceSjose borrego 	smb_upcall_vss_map_gmttoken(rootpath, gmttoken, snapname);
17189dc44ceSjose borrego 
17289dc44ceSjose borrego 	if (!*snapname) {
17389dc44ceSjose borrego 		err = ENOENT;
17489dc44ceSjose borrego 		goto error;
17589dc44ceSjose borrego 	}
17689dc44ceSjose borrego 
17789dc44ceSjose borrego 	/* note the value of root_node->vp */
17889dc44ceSjose borrego 	err = vnodetopath(fsrootvp, root_node->vp, nodepath,
17989dc44ceSjose borrego 	    MAXPATHLEN, kcred);
18089dc44ceSjose borrego 
18189dc44ceSjose borrego 	if (err != 0)
18289dc44ceSjose borrego 		goto error;
18389dc44ceSjose borrego 
18489dc44ceSjose borrego 	(void) snprintf(rootpath, MAXPATHLEN, ".zfs/snapshot/%s/%s",
18589dc44ceSjose borrego 	    snapname, nodepath);
18689dc44ceSjose borrego 
18789dc44ceSjose borrego 	vp = smb_lookuppathvptovp(sr, rootpath, fsrootvp, fsrootvp);
18889dc44ceSjose borrego 
18989dc44ceSjose borrego 	if (vp) {
19089dc44ceSjose borrego 		/* note the value of cur_node->vp */
19189dc44ceSjose borrego 		err = vnodetopath(fsrootvp, cur_node->vp, nodepath,
19289dc44ceSjose borrego 		    MAXPATHLEN, kcred);
1937f667e74Sjose borrego 		if (err != 0) {
1947f667e74Sjose borrego 			VN_RELE(vp);
19589dc44ceSjose borrego 			goto error;
1967f667e74Sjose borrego 		}
19789dc44ceSjose borrego 
19889dc44ceSjose borrego 		*vss_root_node = smb_node_lookup(sr, NULL, kcred, vp,
19989dc44ceSjose borrego 		    gmttoken, cur_node, NULL, &attr);
2007f667e74Sjose borrego 		VN_RELE(vp);
2017f667e74Sjose borrego 
2027f667e74Sjose borrego 		if (*vss_root_node == NULL) {
2037f667e74Sjose borrego 			err = ENOENT;
2047f667e74Sjose borrego 			goto error;
2057f667e74Sjose borrego 		}
20689dc44ceSjose borrego 
20789dc44ceSjose borrego 		(void) snprintf(rootpath, MAXPATHLEN, ".zfs/snapshot/%s/%s",
20889dc44ceSjose borrego 		    snapname, nodepath);
20989dc44ceSjose borrego 
21089dc44ceSjose borrego 
21189dc44ceSjose borrego 		vp = smb_lookuppathvptovp(sr, rootpath, fsrootvp, fsrootvp);
21289dc44ceSjose borrego 
21389dc44ceSjose borrego 		if (vp) {
21489dc44ceSjose borrego 			*vss_cur_node = smb_node_lookup(sr, NULL, kcred, vp,
21589dc44ceSjose borrego 			    gmttoken, cur_node, NULL, &attr);
2167f667e74Sjose borrego 			VN_RELE(vp);
2177f667e74Sjose borrego 
21889dc44ceSjose borrego 			if (*vss_cur_node != NULL) {
21989dc44ceSjose borrego 				smb_vss_remove_first_token_from_path(buf);
22089dc44ceSjose borrego 			} else {
22189dc44ceSjose borrego 				(void) smb_node_release(*vss_root_node);
22289dc44ceSjose borrego 				err = ENOENT;
22389dc44ceSjose borrego 			}
22489dc44ceSjose borrego 		} else {
22589dc44ceSjose borrego 			(void) smb_node_release(*vss_root_node);
22689dc44ceSjose borrego 			err = ENOENT;
22789dc44ceSjose borrego 		}
22889dc44ceSjose borrego 	} else {
22989dc44ceSjose borrego 		err = ENOENT;
23089dc44ceSjose borrego 	}
23189dc44ceSjose borrego 
23289dc44ceSjose borrego error:
23389dc44ceSjose borrego 	VN_RELE(fsrootvp);
23489dc44ceSjose borrego 	kmem_free(rootpath, MAXPATHLEN);
23589dc44ceSjose borrego 	kmem_free(snapname, MAXNAMELEN);
23689dc44ceSjose borrego 	kmem_free(nodepath, MAXPATHLEN);
23789dc44ceSjose borrego 
23889dc44ceSjose borrego 	return (err);
23989dc44ceSjose borrego }
24089dc44ceSjose borrego 
24189dc44ceSjose borrego static boolean_t
24289dc44ceSjose borrego smb_vss_is_gmttoken(const char *s)
24389dc44ceSjose borrego {
24489dc44ceSjose borrego 	char *t = "@GMT-NNNN.NN.NN-NN.NN.NN";
24589dc44ceSjose borrego 	const char *str;
24689dc44ceSjose borrego 	char *template;
24789dc44ceSjose borrego 
24889dc44ceSjose borrego 	template = t;
24989dc44ceSjose borrego 	str = s;
25089dc44ceSjose borrego 
25189dc44ceSjose borrego 	while (*template) {
25289dc44ceSjose borrego 		if (*template == 'N') {
25389dc44ceSjose borrego 			if (!mts_isdigit(*str))
25489dc44ceSjose borrego 				return (B_FALSE);
25589dc44ceSjose borrego 		} else if (*template != *str) {
25689dc44ceSjose borrego 			return (B_FALSE);
25789dc44ceSjose borrego 		}
25889dc44ceSjose borrego 
25989dc44ceSjose borrego 		template++;
26089dc44ceSjose borrego 		str++;
26189dc44ceSjose borrego 	}
26289dc44ceSjose borrego 
26389dc44ceSjose borrego 	/* Make sure it is JUST the @GMT token */
26489dc44ceSjose borrego 	if ((*str == '\0') || (*str == '/'))
26589dc44ceSjose borrego 		return (B_TRUE);
26689dc44ceSjose borrego 
26789dc44ceSjose borrego 	return (B_FALSE);
26889dc44ceSjose borrego }
26989dc44ceSjose borrego 
27089dc44ceSjose borrego static const char *
27189dc44ceSjose borrego smb_vss_find_gmttoken(const char *path)
27289dc44ceSjose borrego {
27389dc44ceSjose borrego 	const char *p;
27489dc44ceSjose borrego 
27589dc44ceSjose borrego 	p = path;
27689dc44ceSjose borrego 
27789dc44ceSjose borrego 	while (*p) {
27889dc44ceSjose borrego 		if (smb_vss_is_gmttoken(p))
27989dc44ceSjose borrego 			return (p);
28089dc44ceSjose borrego 		p++;
28189dc44ceSjose borrego 	}
28289dc44ceSjose borrego 	return (NULL);
28389dc44ceSjose borrego }
28489dc44ceSjose borrego 
28589dc44ceSjose borrego static int
28689dc44ceSjose borrego smb_vss_get_fsmountpath(smb_request_t *sr, char *buf, uint32_t buflen)
28789dc44ceSjose borrego {
28889dc44ceSjose borrego 	vnode_t *vp, *root_vp;
28989dc44ceSjose borrego 	vfs_t *vfsp;
29089dc44ceSjose borrego 	int err;
29189dc44ceSjose borrego 
29289dc44ceSjose borrego 	ASSERT(sr->tid_tree);
29389dc44ceSjose borrego 	ASSERT(sr->tid_tree->t_snode);
29489dc44ceSjose borrego 	ASSERT(sr->tid_tree->t_snode->vp);
29589dc44ceSjose borrego 	ASSERT(sr->tid_tree->t_snode->vp->v_vfsp);
29689dc44ceSjose borrego 
29789dc44ceSjose borrego 	vp = sr->tid_tree->t_snode->vp;
29889dc44ceSjose borrego 	vfsp = vp->v_vfsp;
29989dc44ceSjose borrego 
30089dc44ceSjose borrego 	if (VFS_ROOT(vfsp, &root_vp))
30189dc44ceSjose borrego 		return (ENOENT);
30289dc44ceSjose borrego 
30389dc44ceSjose borrego 	VN_HOLD(vp);
30489dc44ceSjose borrego 
30589dc44ceSjose borrego 	/* NULL is passed in as we want to start at "/" */
30689dc44ceSjose borrego 	err = vnodetopath(NULL, root_vp, buf, buflen, sr->user_cr);
30789dc44ceSjose borrego 
30889dc44ceSjose borrego 	VN_RELE(vp);
30989dc44ceSjose borrego 	VN_RELE(root_vp);
31089dc44ceSjose borrego 	return (err);
31189dc44ceSjose borrego }
31289dc44ceSjose borrego 
31389dc44ceSjose borrego static uint32_t
31489dc44ceSjose borrego smb_vss_encode_gmttokens(smb_request_t *sr, smb_xa_t *xa,
31589dc44ceSjose borrego     int32_t count, smb_dr_return_gmttokens_t *snap_data)
31689dc44ceSjose borrego {
31789dc44ceSjose borrego 	uint32_t i;
31889dc44ceSjose borrego 	uint32_t returned_count;
31989dc44ceSjose borrego 	uint32_t num_gmttokens;
32089dc44ceSjose borrego 	char **gmttokens;
321*b1352070SAlan Wright 	uint32_t status = NT_STATUS_SUCCESS;
32289dc44ceSjose borrego 	uint32_t data_size;
32389dc44ceSjose borrego 
32489dc44ceSjose borrego 	returned_count = snap_data->rg_count;
32589dc44ceSjose borrego 	num_gmttokens = snap_data->rg_gmttokens.rg_gmttokens_len;
32689dc44ceSjose borrego 	gmttokens = snap_data->rg_gmttokens.rg_gmttokens_val;
32789dc44ceSjose borrego 
328*b1352070SAlan Wright 	if (returned_count > count)
329*b1352070SAlan Wright 		status = NT_STATUS_BUFFER_TOO_SMALL;
33089dc44ceSjose borrego 
33189dc44ceSjose borrego 	data_size = returned_count * SMB_VSS_GMT_NET_SIZE(sr) +
33289dc44ceSjose borrego 	    smb_ascii_or_unicode_null_len(sr);
33389dc44ceSjose borrego 
33489dc44ceSjose borrego 	if (smb_mbc_encodef(&xa->rep_data_mb, "lll", returned_count,
335*b1352070SAlan Wright 	    num_gmttokens, data_size) != 0)
336*b1352070SAlan Wright 		return (NT_STATUS_INVALID_PARAMETER);
33789dc44ceSjose borrego 
338*b1352070SAlan Wright 	if (status == NT_STATUS_SUCCESS) {
33989dc44ceSjose borrego 		for (i = 0; i < num_gmttokens; i++) {
34089dc44ceSjose borrego 			if (smb_mbc_encodef(&xa->rep_data_mb, "%u", sr,
341*b1352070SAlan Wright 			    *gmttokens) != 0)
342*b1352070SAlan Wright 				status = NT_STATUS_INVALID_PARAMETER;
34389dc44ceSjose borrego 			gmttokens++;
34489dc44ceSjose borrego 		}
34589dc44ceSjose borrego 	}
34689dc44ceSjose borrego 
347*b1352070SAlan Wright 	return (status);
34889dc44ceSjose borrego }
34989dc44ceSjose borrego 
35089dc44ceSjose borrego /* This removes the first @GMT from the path */
35189dc44ceSjose borrego static void
35289dc44ceSjose borrego smb_vss_remove_first_token_from_path(char *path)
35389dc44ceSjose borrego {
35489dc44ceSjose borrego 	boolean_t found;
35589dc44ceSjose borrego 	char *src, *dest;
35689dc44ceSjose borrego 
35789dc44ceSjose borrego 	src = path;
35889dc44ceSjose borrego 	dest = path;
35989dc44ceSjose borrego 
36089dc44ceSjose borrego 	found = B_FALSE;
36189dc44ceSjose borrego 
36289dc44ceSjose borrego 	while (*src != '\0') {
36389dc44ceSjose borrego 		if (!found && smb_vss_is_gmttoken(src)) {
36489dc44ceSjose borrego 			src += SMB_VSS_GMT_SIZE - 1;
36589dc44ceSjose borrego 			if (*src == '/')
36689dc44ceSjose borrego 				src += 1;
36789dc44ceSjose borrego 			found = B_TRUE;
36889dc44ceSjose borrego 			continue;
36989dc44ceSjose borrego 		}
37089dc44ceSjose borrego 		*dest = *src;
37189dc44ceSjose borrego 		src++;
37289dc44ceSjose borrego 		dest++;
37389dc44ceSjose borrego 	}
37489dc44ceSjose borrego 	*dest = *src;
37589dc44ceSjose borrego }
376