xref: /freebsd/bin/cp/tests/cp_test.sh (revision c9fcffff6c596b56484ce58b89850c5fef45acdc)
1#
2# SPDX-License-Identifier: BSD-2-Clause
3#
4# Copyright (c) 2020 Kyle Evans <kevans@FreeBSD.org>
5#
6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions
8# are met:
9# 1. Redistributions of source code must retain the above copyright
10#    notice, this list of conditions and the following disclaimer.
11# 2. Redistributions in binary form must reproduce the above copyright
12#    notice, this list of conditions and the following disclaimer in the
13#    documentation and/or other materials provided with the distribution.
14#
15# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25# SUCH DAMAGE.
26#
27
28check_size()
29{
30	file=$1
31	sz=$2
32
33	atf_check -o inline:"$sz\n" stat -f '%z' $file
34}
35
36atf_test_case basic
37basic_body()
38{
39	echo "foo" > bar
40
41	atf_check cp bar baz
42	check_size baz 4
43}
44
45atf_test_case basic_symlink
46basic_symlink_body()
47{
48	echo "foo" > bar
49	ln -s bar baz
50
51	atf_check cp baz foo
52	atf_check test '!' -L foo
53
54	atf_check cmp foo bar
55}
56
57atf_test_case chrdev
58chrdev_body()
59{
60	echo "foo" > bar
61
62	check_size bar 4
63	atf_check cp /dev/null trunc
64	check_size trunc 0
65	atf_check cp bar trunc
66	check_size trunc 4
67	atf_check cp /dev/null trunc
68	check_size trunc 0
69}
70
71atf_test_case hardlink
72hardlink_body()
73{
74	echo "foo" >foo
75	atf_check cp -l foo bar
76	atf_check -o inline:"foo\n" cat bar
77	atf_check_equal "$(stat -f%d,%i foo)" "$(stat -f%d,%i bar)"
78}
79
80atf_test_case hardlink_exists
81hardlink_exists_body()
82{
83	echo "foo" >foo
84	echo "bar" >bar
85	atf_check -s not-exit:0 -e match:exists cp -l foo bar
86	atf_check -o inline:"bar\n" cat bar
87	atf_check_not_equal "$(stat -f%d,%i foo)" "$(stat -f%d,%i bar)"
88}
89
90atf_test_case hardlink_exists_force
91hardlink_exists_force_body()
92{
93	echo "foo" >foo
94	echo "bar" >bar
95	atf_check cp -fl foo bar
96	atf_check -o inline:"foo\n" cat bar
97	atf_check_equal "$(stat -f%d,%i foo)" "$(stat -f%d,%i bar)"
98}
99
100atf_test_case matching_srctgt
101matching_srctgt_body()
102{
103
104	# PR235438: `cp -R foo foo` would previously infinitely recurse and
105	# eventually error out.
106	mkdir foo
107	echo "qux" > foo/bar
108	cp foo/bar foo/zoo
109
110	atf_check cp -R foo foo
111	atf_check -o inline:"qux\n" cat foo/foo/bar
112	atf_check -o inline:"qux\n" cat foo/foo/zoo
113	atf_check -e not-empty -s not-exit:0 stat foo/foo/foo
114}
115
116atf_test_case matching_srctgt_contained
117matching_srctgt_contained_body()
118{
119
120	# Let's do the same thing, except we'll try to recursively copy foo into
121	# one of its subdirectories.
122	mkdir foo
123	ln -s foo coo
124	echo "qux" > foo/bar
125	mkdir foo/moo
126	touch foo/moo/roo
127	cp foo/bar foo/zoo
128
129	atf_check cp -R foo foo/moo
130	atf_check cp -RH coo foo/moo
131	atf_check -o inline:"qux\n" cat foo/moo/foo/bar
132	atf_check -o inline:"qux\n" cat foo/moo/coo/bar
133	atf_check -o inline:"qux\n" cat foo/moo/foo/zoo
134	atf_check -o inline:"qux\n" cat foo/moo/coo/zoo
135
136	# We should have copied the contents of foo/moo before foo, coo started
137	# getting copied in.
138	atf_check -o not-empty stat foo/moo/foo/moo/roo
139	atf_check -o not-empty stat foo/moo/coo/moo/roo
140	atf_check -e not-empty -s not-exit:0 stat foo/moo/foo/moo/foo
141	atf_check -e not-empty -s not-exit:0 stat foo/moo/coo/moo/coo
142}
143
144atf_test_case matching_srctgt_link
145matching_srctgt_link_body()
146{
147
148	mkdir foo
149	echo "qux" > foo/bar
150	cp foo/bar foo/zoo
151
152	atf_check ln -s foo roo
153	atf_check cp -RH roo foo
154	atf_check -o inline:"qux\n" cat foo/roo/bar
155	atf_check -o inline:"qux\n" cat foo/roo/zoo
156}
157
158atf_test_case matching_srctgt_nonexistent
159matching_srctgt_nonexistent_body()
160{
161
162	# We'll copy foo to a nonexistent subdirectory; ideally, we would
163	# skip just the directory and end up with a layout like;
164	#
165	# foo/
166	#     bar
167	#     dne/
168	#         bar
169	#         zoo
170	#     zoo
171	#
172	mkdir foo
173	echo "qux" > foo/bar
174	cp foo/bar foo/zoo
175
176	atf_check cp -R foo foo/dne
177	atf_check -o inline:"qux\n" cat foo/dne/bar
178	atf_check -o inline:"qux\n" cat foo/dne/zoo
179	atf_check -e not-empty -s not-exit:0 stat foo/dne/foo
180}
181
182atf_test_case pflag_acls
183pflag_acls_body()
184{
185	mkdir dir
186	ln -s dir lnk
187	echo "hello" >dir/file
188	if ! setfacl -m g:staff:D::allow dir ||
189	   ! setfacl -m g:staff:d::allow dir/file ; then
190		atf_skip "file system does not support ACLs"
191	fi
192	atf_check -o match:"group:staff:-+D-+" getfacl dir
193	atf_check -o match:"group:staff:-+d-+" getfacl dir/file
194	# file-to-file copy without -p
195	atf_check cp dir/file dst1
196	atf_check -o not-match:"group:staff:-+d-+" getfacl dst1
197	# file-to-file copy with -p
198	atf_check cp -p dir/file dst2
199	atf_check -o match:"group:staff:-+d-+" getfacl dst2
200	# recursive copy without -p
201	atf_check cp -r dir dst3
202	atf_check -o not-match:"group:staff:-+D-+" getfacl dst3
203	atf_check -o not-match:"group:staff:-+d-+" getfacl dst3/file
204	# recursive copy with -p
205	atf_check cp -rp dir dst4
206	atf_check -o match:"group:staff:-+D-+" getfacl dst4
207	atf_check -o match:"group:staff:-+d-+" getfacl dst4/file
208	# source is a link without -p
209	atf_check cp -r lnk dst5
210	atf_check -o not-match:"group:staff:-+D-+" getfacl dst5
211	atf_check -o not-match:"group:staff:-+d-+" getfacl dst5/file
212	# source is a link with -p
213	atf_check cp -rp lnk dst6
214	atf_check -o match:"group:staff:-+D-+" getfacl dst6
215	atf_check -o match:"group:staff:-+d-+" getfacl dst6/file
216}
217
218atf_test_case pflag_flags
219pflag_flags_body()
220{
221	mkdir dir
222	ln -s dir lnk
223	echo "hello" >dir/file
224	if ! chflags nodump dir ||
225	   ! chflags nodump dir/file ; then
226		atf_skip "file system does not support flags"
227	fi
228	atf_check -o match:"nodump" stat -f%Sf dir
229	atf_check -o match:"nodump" stat -f%Sf dir/file
230	# file-to-file copy without -p
231	atf_check cp dir/file dst1
232	atf_check -o not-match:"nodump" stat -f%Sf dst1
233	# file-to-file copy with -p
234	atf_check cp -p dir/file dst2
235	atf_check -o match:"nodump" stat -f%Sf dst2
236	# recursive copy without -p
237	atf_check cp -r dir dst3
238	atf_check -o not-match:"nodump" stat -f%Sf dst3
239	atf_check -o not-match:"nodump" stat -f%Sf dst3/file
240	# recursive copy with -p
241	atf_check cp -rp dir dst4
242	atf_check -o match:"nodump" stat -f%Sf dst4
243	atf_check -o match:"nodump" stat -f%Sf dst4/file
244	# source is a link without -p
245	atf_check cp -r lnk dst5
246	atf_check -o not-match:"nodump" stat -f%Sf dst5
247	atf_check -o not-match:"nodump" stat -f%Sf dst5/file
248	# source is a link with -p
249	atf_check cp -rp lnk dst6
250	atf_check -o match:"nodump" stat -f%Sf dst6
251	atf_check -o match:"nodump" stat -f%Sf dst6/file
252}
253
254recursive_link_setup()
255{
256	extra_cpflag=$1
257
258	mkdir -p foo/bar
259	ln -s bar foo/baz
260
261	mkdir foo-mirror
262	eval "cp -R $extra_cpflag foo foo-mirror"
263}
264
265atf_test_case recursive_link_dflt
266recursive_link_dflt_body()
267{
268	recursive_link_setup
269
270	# -P is the default, so this should work and preserve the link.
271	atf_check cp -R foo foo-mirror
272	atf_check test -L foo-mirror/foo/baz
273}
274
275atf_test_case recursive_link_Hflag
276recursive_link_Hflag_body()
277{
278	recursive_link_setup
279
280	# -H will not follow either, so this should also work and preserve the
281	# link.
282	atf_check cp -RH foo foo-mirror
283	atf_check test -L foo-mirror/foo/baz
284}
285
286atf_test_case recursive_link_Lflag
287recursive_link_Lflag_body()
288{
289	recursive_link_setup -L
290
291	# -L will work, but foo/baz ends up expanded to a directory.
292	atf_check test -d foo-mirror/foo/baz -a \
293	    '(' ! -L foo-mirror/foo/baz ')'
294	atf_check cp -RL foo foo-mirror
295	atf_check test -d foo-mirror/foo/baz -a \
296	    '(' ! -L foo-mirror/foo/baz ')'
297}
298
299atf_test_case samefile
300samefile_body()
301{
302	echo "foo" >foo
303	ln foo bar
304	ln -s bar baz
305	atf_check -e match:"baz and baz are identical" \
306	    -s exit:1 cp baz baz
307	atf_check -e match:"bar and baz are identical" \
308	    -s exit:1 cp baz bar
309	atf_check -e match:"foo and baz are identical" \
310	    -s exit:1 cp baz foo
311	atf_check -e match:"bar and foo are identical" \
312	    -s exit:1 cp foo bar
313}
314
315file_is_sparse()
316{
317	atf_check ${0%/*}/sparse "$1"
318}
319
320files_are_equal()
321{
322	atf_check_not_equal "$(stat -f%d,%i "$1")" "$(stat -f%d,%i "$2")"
323	atf_check cmp "$1" "$2"
324}
325
326atf_test_case sparse_leading_hole
327sparse_leading_hole_body()
328{
329	# A 16-megabyte hole followed by one megabyte of data
330	truncate -s 16M foo
331	seq -f%015g 65536 >>foo
332	file_is_sparse foo
333
334	atf_check cp foo bar
335	files_are_equal foo bar
336	file_is_sparse bar
337}
338
339atf_test_case sparse_multiple_holes
340sparse_multiple_holes_body()
341{
342	# Three one-megabyte blocks of data preceded, separated, and
343	# followed by 16-megabyte holes
344	truncate -s 16M foo
345	seq -f%015g 65536 >>foo
346	truncate -s 33M foo
347	seq -f%015g 65536 >>foo
348	truncate -s 50M foo
349	seq -f%015g 65536 >>foo
350	truncate -s 67M foo
351	file_is_sparse foo
352
353	atf_check cp foo bar
354	files_are_equal foo bar
355	file_is_sparse bar
356}
357
358atf_test_case sparse_only_hole
359sparse_only_hole_body()
360{
361	# A 16-megabyte hole
362	truncate -s 16M foo
363	file_is_sparse foo
364
365	atf_check cp foo bar
366	files_are_equal foo bar
367	file_is_sparse bar
368}
369
370atf_test_case sparse_to_dev
371sparse_to_dev_body()
372{
373	# Three one-megabyte blocks of data preceded, separated, and
374	# followed by 16-megabyte holes
375	truncate -s 16M foo
376	seq -f%015g 65536 >>foo
377	truncate -s 33M foo
378	seq -f%015g 65536 >>foo
379	truncate -s 50M foo
380	seq -f%015g 65536 >>foo
381	truncate -s 67M foo
382	file_is_sparse foo
383
384	atf_check -o file:foo cp foo /dev/stdout
385}
386
387atf_test_case sparse_trailing_hole
388sparse_trailing_hole_body()
389{
390	# One megabyte of data followed by a 16-megabyte hole
391	seq -f%015g 65536 >foo
392	truncate -s 17M foo
393	file_is_sparse foo
394
395	atf_check cp foo bar
396	files_are_equal foo bar
397	file_is_sparse bar
398}
399
400atf_test_case standalone_Pflag
401standalone_Pflag_body()
402{
403	echo "foo" > bar
404	ln -s bar foo
405
406	atf_check cp -P foo baz
407	atf_check -o inline:'Symbolic Link\n' stat -f %SHT baz
408}
409
410atf_test_case symlink
411symlink_body()
412{
413	echo "foo" >foo
414	atf_check cp -s foo bar
415	atf_check -o inline:"foo\n" cat bar
416	atf_check -o inline:"foo\n" readlink bar
417}
418
419atf_test_case symlink_exists
420symlink_exists_body()
421{
422	echo "foo" >foo
423	echo "bar" >bar
424	atf_check -s not-exit:0 -e match:exists cp -s foo bar
425	atf_check -o inline:"bar\n" cat bar
426}
427
428atf_test_case symlink_exists_force
429symlink_exists_force_body()
430{
431	echo "foo" >foo
432	echo "bar" >bar
433	atf_check cp -fs foo bar
434	atf_check -o inline:"foo\n" cat bar
435	atf_check -o inline:"foo\n" readlink bar
436}
437
438atf_test_case directory_to_symlink
439directory_to_symlink_body()
440{
441	mkdir -p foo
442	ln -s .. foo/bar
443	mkdir bar
444	touch bar/baz
445	atf_check -s not-exit:0 -e match:"Not a directory" \
446	    cp -R bar foo
447	atf_check -s not-exit:0 -e match:"Not a directory" \
448	    cp -r bar foo
449}
450
451atf_test_case overwrite_directory
452overwrite_directory_body()
453{
454	mkdir -p foo/bar/baz
455	touch bar
456	atf_check -s not-exit:0 -e match:"Is a directory" \
457	    cp bar foo
458	rm bar
459	mkdir bar
460	touch bar/baz
461	atf_check -s not-exit:0 -e match:"Is a directory" \
462	    cp -R bar foo
463	atf_check -s not-exit:0 -e match:"Is a directory" \
464	    cp -r bar foo
465}
466
467atf_test_case to_dir_dne
468to_dir_dne_body()
469{
470	mkdir dir
471	echo "foo" >dir/foo
472	atf_check cp -r dir dne
473	atf_check test -d dne
474	atf_check test -f dne/foo
475	atf_check cmp dir/foo dne/foo
476}
477
478atf_test_case to_nondir
479to_nondir_body()
480{
481	echo "foo" >foo
482	echo "bar" >bar
483	echo "baz" >baz
484	# This is described as “case 1” in source code comments
485	atf_check cp foo bar
486	atf_check cmp -s foo bar
487	# This is “case 2”, the target must be a directory
488	atf_check -s not-exit:0 -e match:"Not a directory" \
489	    cp foo bar baz
490}
491
492atf_test_case to_deadlink
493to_deadlink_body()
494{
495	echo "foo" >foo
496	ln -s bar baz
497	atf_check cp foo baz
498	atf_check cmp -s foo bar
499}
500
501atf_test_case to_deadlink_append
502to_deadlink_append_body()
503{
504	echo "foo" >foo
505	mkdir bar
506	ln -s baz bar/foo
507	atf_check cp foo bar
508	atf_check cmp -s foo bar/baz
509	rm -f bar/foo bar/baz
510	ln -s baz bar/foo
511	atf_check cp foo bar/
512	atf_check cmp -s foo bar/baz
513	rm -f bar/foo bar/baz
514	ln -s $PWD/baz bar/foo
515	atf_check cp foo bar/
516	atf_check cmp -s foo baz
517}
518
519atf_test_case to_dirlink
520to_dirlink_body()
521{
522	mkdir src dir
523	echo "foo" >src/file
524	ln -s dir dst
525	atf_check cp -r src dst
526	atf_check cmp -s src/file dir/src/file
527	rm -r dir/*
528	atf_check cp -r src dst/
529	atf_check cmp -s src/file dir/src/file
530	rm -r dir/*
531	# If the source is a directory and ends in a slash, our cp has
532	# traditionally copied the contents of the source rather than
533	# the source itself.  It is unclear whether this is intended
534	# or simply a consequence of how FTS handles the situation.
535	# Notably, GNU cp does not behave in this manner.
536	atf_check cp -r src/ dst
537	atf_check cmp -s src/file dir/file
538	rm -r dir/*
539	atf_check cp -r src/ dst/
540	atf_check cmp -s src/file dir/file
541	rm -r dir/*
542}
543
544atf_test_case to_deaddirlink
545to_deaddirlink_body()
546{
547	mkdir src
548	echo "foo" >src/file
549	ln -s dir dst
550	# It is unclear which error we should expect in these cases.
551	# Our current implementation always reports ENOTDIR, but one
552	# might be equally justified in expecting EEXIST or ENOENT.
553	# GNU cp reports EEXIST when the destination is given with a
554	# trailing slash and “cannot overwrite non-directory with
555	# directory” otherwise.
556	atf_check -s not-exit:0 -e ignore \
557	    cp -r src dst
558	atf_check -s not-exit:0 -e ignore \
559	    cp -r src dst/
560	atf_check -s not-exit:0 -e ignore \
561	    cp -r src/ dst
562	atf_check -s not-exit:0 -e ignore \
563	    cp -r src/ dst/
564	atf_check -s not-exit:0 -e ignore \
565	    cp -R src dst
566	atf_check -s not-exit:0 -e ignore \
567	    cp -R src dst/
568	atf_check -s not-exit:0 -e ignore \
569	    cp -R src/ dst
570	atf_check -s not-exit:0 -e ignore \
571	    cp -R src/ dst/
572}
573
574atf_test_case to_link_outside
575to_link_outside_body()
576{
577	mkdir dir dst dst/dir
578	echo "foo" >dir/file
579	ln -s ../../file dst/dir/file
580	atf_check \
581	    -s exit:1 \
582	    -e match:"dst/dir/file: Permission denied" \
583	    cp -r dir dst
584}
585
586atf_test_case dstmode
587dstmode_body()
588{
589	mkdir -m 0755 dir
590	echo "foo" >dir/file
591	umask 0177
592	atf_check cp -R dir dst
593	umask 022
594	atf_check -o inline:"40600\n" stat -f%p dst
595	atf_check chmod 0750 dst
596	atf_check cmp dir/file dst/file
597}
598
599atf_test_case to_root cleanup
600to_root_head()
601{
602	atf_set "require.user" "unprivileged"
603}
604to_root_body()
605{
606	dst="test.$(atf_get ident).$$"
607	echo "$dst" >dst
608	echo "foo" >"$dst"
609	atf_check -s not-exit:0 \
610	    -e match:"^cp: /$dst: (Permission|Read-only)" \
611	    cp "$dst" /
612	atf_check -s not-exit:0 \
613	    -e match:"^cp: /$dst: (Permission|Read-only)" \
614	    cp "$dst" //
615}
616to_root_cleanup()
617{
618	(dst=$(cat dst) && rm "/$dst") 2>/dev/null || true
619}
620
621atf_test_case dirloop
622dirloop_head()
623{
624	atf_set "descr" "Test cycle detection when recursing"
625}
626dirloop_body()
627{
628	mkdir -p src/a src/b
629	ln -s ../b src/a
630	ln -s ../a src/b
631	atf_check \
632	    -s exit:1 \
633	    -e match:"src/a/b/a: directory causes a cycle" \
634	    -e match:"src/b/a/b: directory causes a cycle" \
635	    cp -r src dst
636	atf_check test -d dst
637	atf_check test -d dst/a
638	atf_check test -d dst/b
639	atf_check test -d dst/a/b
640	atf_check test ! -e dst/a/b/a
641	atf_check test -d dst/b/a
642	atf_check test ! -e dst/b/a/b
643}
644
645atf_test_case unrdir
646unrdir_head()
647{
648	atf_set "descr" "Test handling of unreadable directories"
649}
650unrdir_body()
651{
652	for d in a b c ; do
653		mkdir -p src/$d
654		echo "$d" >src/$d/f
655	done
656	chmod 0 src/b
657	atf_check \
658	    -s exit:1 \
659	    -e match:"^cp: src/b: Permission denied" \
660	    cp -R src dst
661	atf_check test -d dst/a
662	atf_check cmp src/a/f dst/a/f
663	atf_check test -d dst/b
664	atf_check test ! -e dst/b/f
665	atf_check test -d dst/c
666	atf_check cmp src/c/f dst/c/f
667}
668
669atf_test_case unrfile
670unrfile_head()
671{
672	atf_set "descr" "Test handling of unreadable files"
673}
674unrfile_body()
675{
676	mkdir src
677	for d in a b c ; do
678		echo "$d" >src/$d
679	done
680	chmod 0 src/b
681	atf_check \
682	    -s exit:1 \
683	    -e match:"^cp: src/b: Permission denied" \
684	    cp -R src dst
685	atf_check test -d dst
686	atf_check cmp src/a dst/a
687	atf_check test ! -e dst/b
688	atf_check cmp src/c dst/c
689}
690
691atf_init_test_cases()
692{
693	atf_add_test_case basic
694	atf_add_test_case basic_symlink
695	atf_add_test_case chrdev
696	atf_add_test_case hardlink
697	atf_add_test_case hardlink_exists
698	atf_add_test_case hardlink_exists_force
699	atf_add_test_case matching_srctgt
700	atf_add_test_case matching_srctgt_contained
701	atf_add_test_case matching_srctgt_link
702	atf_add_test_case matching_srctgt_nonexistent
703	atf_add_test_case pflag_acls
704	atf_add_test_case pflag_flags
705	atf_add_test_case recursive_link_dflt
706	atf_add_test_case recursive_link_Hflag
707	atf_add_test_case recursive_link_Lflag
708	atf_add_test_case samefile
709	atf_add_test_case sparse_leading_hole
710	atf_add_test_case sparse_multiple_holes
711	atf_add_test_case sparse_only_hole
712	atf_add_test_case sparse_to_dev
713	atf_add_test_case sparse_trailing_hole
714	atf_add_test_case standalone_Pflag
715	atf_add_test_case symlink
716	atf_add_test_case symlink_exists
717	atf_add_test_case symlink_exists_force
718	atf_add_test_case directory_to_symlink
719	atf_add_test_case overwrite_directory
720	atf_add_test_case to_dir_dne
721	atf_add_test_case to_nondir
722	atf_add_test_case to_deadlink
723	atf_add_test_case to_deadlink_append
724	atf_add_test_case to_dirlink
725	atf_add_test_case to_deaddirlink
726	atf_add_test_case to_link_outside
727	atf_add_test_case dstmode
728	atf_add_test_case to_root
729	atf_add_test_case dirloop
730	atf_add_test_case unrdir
731	atf_add_test_case unrfile
732}
733