1/*
2 * Copyright (c) 2016 Jeff Mahoney <jeffm@suse.com>
3 * Copyright (c) 2016-2017 The strace developers.
4 * All rights reserved.
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 * 3. The name of the author may not be used to endorse or promote products
15 *    derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29#include "defs.h"
30#include <linux/ioctl.h>
31#include <linux/fs.h>
32
33#ifdef HAVE_LINUX_FIEMAP_H
34# include <linux/fiemap.h>
35# include "xlat/fiemap_flags.h"
36# include "xlat/fiemap_extent_flags.h"
37#endif
38
39#ifndef FICLONE
40# define FICLONE	_IOW(0x94, 9, int)
41#endif
42
43#ifndef FICLONERANGE
44# define FICLONERANGE	_IOW(0x94, 13, struct file_clone_range)
45struct file_clone_range {
46	int64_t src_fd;
47	uint64_t src_offset;
48	uint64_t src_length;
49	uint64_t dest_offset;
50};
51#endif
52
53#ifndef FIDEDUPERANGE
54# define FIDEDUPERANGE	_IOWR(0x94, 54, struct file_dedupe_range)
55struct file_dedupe_range_info {
56	int64_t dest_fd;	/* in - destination file */
57	uint64_t dest_offset;	/* in - start of extent in destination */
58	uint64_t bytes_deduped;	/* out - total # of bytes we were able
59				 * to dedupe from this file. */
60	/* status of this dedupe operation:
61	 * < 0 for error
62	 * == FILE_DEDUPE_RANGE_SAME if dedupe succeeds
63	 * == FILE_DEDUPE_RANGE_DIFFERS if data differs
64	 */
65	int32_t status;		/* out - see above description */
66	uint32_t reserved;	/* must be zero */
67};
68
69struct file_dedupe_range {
70	uint64_t src_offset;	/* in - start of extent in source */
71	uint64_t src_length;	/* in - length of extent */
72	uint16_t dest_count;	/* in - total elements in info array */
73	uint16_t reserved1;	/* must be zero */
74	uint32_t reserved2;	/* must be zero */
75	struct file_dedupe_range_info info[0];
76};
77#endif
78
79static bool
80print_file_dedupe_range_info(struct tcb *tcp, void *elem_buf,
81			     size_t elem_size, void *data)
82{
83	const struct file_dedupe_range_info *info = elem_buf;
84	unsigned int *count = data;
85
86	if (count) {
87		if (*count == 0) {
88			tprints("...");
89			return false;
90		}
91		--*count;
92	}
93
94	if (entering(tcp)) {
95		tprints("{dest_fd=");
96		printfd(tcp, info->dest_fd);
97		tprintf(", dest_offset=%" PRIu64 "}",
98			(uint64_t) info->dest_offset);
99	} else {
100		tprintf("{bytes_deduped=%" PRIu64 ", status=%d}",
101			(uint64_t) info->bytes_deduped, info->status);
102	}
103
104	return true;
105}
106
107#ifdef HAVE_LINUX_FIEMAP_H
108static bool
109print_fiemap_extent(struct tcb *tcp, void *elem_buf, size_t elem_size, void *data)
110{
111	const struct fiemap_extent *fe = elem_buf;
112
113	tprintf("{fe_logical=%" PRI__u64
114		", fe_physical=%" PRI__u64
115		", fe_length=%" PRI__u64 ", ",
116		fe->fe_logical, fe->fe_physical, fe->fe_length);
117
118	printflags64(fiemap_extent_flags, fe->fe_flags,
119		     "FIEMAP_EXTENT_???");
120	tprints("}");
121
122	return true;
123}
124#endif /* HAVE_LINUX_FIEMAP_H */
125
126int
127file_ioctl(struct tcb *const tcp, const unsigned int code,
128	   const kernel_ulong_t arg)
129{
130	switch (code) {
131	case FICLONE:	/* W */
132		tprintf(", %d", (int) arg);
133		break;
134
135	case FICLONERANGE: { /* W */
136		struct file_clone_range args;
137
138		tprints(", ");
139		if (umove_or_printaddr(tcp, arg, &args))
140			break;
141
142		tprints("{src_fd=");
143		printfd(tcp, args.src_fd);
144		tprintf(", src_offset=%" PRIu64
145			", src_length=%" PRIu64
146			", dest_offset=%" PRIu64 "}",
147			(uint64_t) args.src_offset,
148			(uint64_t) args.src_length,
149			(uint64_t) args.dest_offset);
150		break;
151	}
152
153	case FIDEDUPERANGE: { /* RW */
154		struct file_dedupe_range args;
155		struct file_dedupe_range_info info;
156		unsigned int *limit = NULL;
157		unsigned int count = 2;
158		bool rc;
159
160		if (entering(tcp))
161			tprints(", ");
162		else if (syserror(tcp))
163			break;
164		else
165			tprints(" => ");
166
167		if (umove_or_printaddr(tcp, arg, &args))
168			break;
169
170		tprints("{");
171		if (entering(tcp)) {
172			tprintf("src_offset=%" PRIu64
173				", src_length=%" PRIu64
174				", dest_count=%hu, ",
175				(uint64_t) args.src_offset,
176				(uint64_t) args.src_length,
177				(uint16_t) args.dest_count);
178		}
179
180		tprints("info=");
181
182		/* Limit how many elements we print in abbrev mode. */
183		if (abbrev(tcp) && args.dest_count > count)
184			limit = &count;
185
186		rc = print_array(tcp, arg + offsetof(typeof(args), info),
187				 args.dest_count, &info, sizeof(info),
188				 umoven_or_printaddr,
189				 print_file_dedupe_range_info, limit);
190
191		tprints("}");
192		if (!rc || exiting(tcp))
193			break;
194
195		return 0;
196	}
197
198#ifdef HAVE_LINUX_FIEMAP_H
199	case FS_IOC_FIEMAP: {
200		struct fiemap args;
201
202		if (entering(tcp))
203			tprints(", ");
204		else if (syserror(tcp))
205			break;
206		else
207			tprints(" => ");
208
209		if (umove_or_printaddr(tcp, arg, &args))
210			break;
211
212		if (entering(tcp)) {
213			tprintf("{fm_start=%" PRI__u64 ", "
214				"fm_length=%" PRI__u64 ", "
215				"fm_flags=",
216				args.fm_start, args.fm_length);
217			printflags64(fiemap_flags, args.fm_flags,
218				     "FIEMAP_FLAG_???");
219			tprintf(", fm_extent_count=%u}", args.fm_extent_count);
220			return 0;
221		}
222
223		tprints("{fm_flags=");
224		printflags64(fiemap_flags, args.fm_flags,
225			     "FIEMAP_FLAG_???");
226		tprintf(", fm_mapped_extents=%u",
227			args.fm_mapped_extents);
228		tprints(", fm_extents=");
229		if (abbrev(tcp)) {
230			tprints("...");
231		} else {
232			struct fiemap_extent fe;
233			print_array(tcp,
234				    arg + offsetof(typeof(args), fm_extents),
235				    args.fm_mapped_extents, &fe, sizeof(fe),
236				    umoven_or_printaddr,
237				    print_fiemap_extent, 0);
238		}
239		tprints("}");
240
241		break;
242	}
243#endif /* HAVE_LINUX_FIEMAP_H */
244
245	default:
246		return RVAL_DECODED;
247	};
248
249	return RVAL_IOCTL_DECODED;
250}
251