1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab
4 * Ceph - scalable distributed file system
6 * Copyright (C) 2013 Cloudwatt <libre.licensing@cloudwatt.com>
8 * Author: Loic Dachary <loic@dachary.org>
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU Library Public License as published by
12 * the Free Software Foundation; either version 2, or (at your option)
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU Library Public License for more details.
24 #include "os/filestore/chain_xattr.h"
25 #include "include/Context.h"
26 #include "include/coredumpctl.h"
27 #include "common/errno.h"
28 #include "common/ceph_argparse.h"
29 #include "global/global_init.h"
30 #include <gtest/gtest.h>
32 #define LARGE_BLOCK_LEN CHAIN_XATTR_MAX_BLOCK_LEN + 1024
33 #define FILENAME "chain_xattr"
35 TEST(chain_xattr
, get_and_set
) {
36 const char* file
= FILENAME
;
38 int fd
= ::open(file
, O_CREAT
|O_WRONLY
|O_TRUNC
, 0700);
39 const string
user("user.");
42 const string name
= user
+ string(CHAIN_XATTR_MAX_NAME_LEN
- user
.size(), '@');
43 const string
x(LARGE_BLOCK_LEN
, 'X');
46 char y
[LARGE_BLOCK_LEN
];
47 ASSERT_EQ(LARGE_BLOCK_LEN
, chain_setxattr(file
, name
.c_str(), x
.c_str(), LARGE_BLOCK_LEN
));
48 ASSERT_EQ(LARGE_BLOCK_LEN
, chain_getxattr(file
, name
.c_str(), 0, 0));
49 ASSERT_EQ(LARGE_BLOCK_LEN
, chain_getxattr(file
, name
.c_str(), y
, LARGE_BLOCK_LEN
));
50 ASSERT_EQ(0, chain_removexattr(file
, name
.c_str()));
51 ASSERT_EQ(0, memcmp(x
.c_str(), y
, LARGE_BLOCK_LEN
));
55 char y
[LARGE_BLOCK_LEN
];
56 ASSERT_EQ(LARGE_BLOCK_LEN
, chain_fsetxattr(fd
, name
.c_str(), x
.c_str(), LARGE_BLOCK_LEN
));
57 ASSERT_EQ(LARGE_BLOCK_LEN
, chain_fgetxattr(fd
, name
.c_str(), 0, 0));
58 ASSERT_EQ(LARGE_BLOCK_LEN
, chain_fgetxattr(fd
, name
.c_str(), y
, LARGE_BLOCK_LEN
));
59 ASSERT_EQ(0, chain_fremovexattr(fd
, name
.c_str()));
60 ASSERT_EQ(0, memcmp(x
.c_str(), y
, LARGE_BLOCK_LEN
));
65 // when chain_setxattr is used to store value that is
66 // CHAIN_XATTR_MAX_BLOCK_LEN * 2 + 10 bytes long it
68 // add user.foo => CHAIN_XATTR_MAX_BLOCK_LEN bytes
69 // add user.foo@1 => CHAIN_XATTR_MAX_BLOCK_LEN bytes
70 // add user.foo@2 => 10 bytes
72 // then ( no chain_removexattr in between ) when it is used to
73 // override with a value that is exactly CHAIN_XATTR_MAX_BLOCK_LEN
76 // replace user.foo => CHAIN_XATTR_MAX_BLOCK_LEN bytes
77 // remove user.foo@1 => CHAIN_XATTR_MAX_BLOCK_LEN bytes
78 // leak user.foo@2 => 10 bytes
80 // see http://marc.info/?l=ceph-devel&m=136027076615853&w=4 for the
84 const string name
= user
+ string(CHAIN_XATTR_MAX_NAME_LEN
- user
.size(), '@');
85 const string
x(LARGE_BLOCK_LEN
, 'X');
88 char y
[CHAIN_XATTR_MAX_BLOCK_LEN
];
89 ASSERT_EQ(LARGE_BLOCK_LEN
, chain_setxattr(file
, name
.c_str(), x
.c_str(), LARGE_BLOCK_LEN
));
90 ASSERT_EQ(CHAIN_XATTR_MAX_BLOCK_LEN
, chain_setxattr(file
, name
.c_str(), x
.c_str(), CHAIN_XATTR_MAX_BLOCK_LEN
));
91 ASSERT_EQ(CHAIN_XATTR_MAX_BLOCK_LEN
, chain_getxattr(file
, name
.c_str(), 0, 0));
92 ASSERT_EQ(CHAIN_XATTR_MAX_BLOCK_LEN
, chain_getxattr(file
, name
.c_str(), y
, CHAIN_XATTR_MAX_BLOCK_LEN
));
93 ASSERT_EQ(0, chain_removexattr(file
, name
.c_str()));
94 ASSERT_EQ(0, memcmp(x
.c_str(), y
, CHAIN_XATTR_MAX_BLOCK_LEN
));
98 char y
[CHAIN_XATTR_MAX_BLOCK_LEN
];
99 ASSERT_EQ(LARGE_BLOCK_LEN
, chain_fsetxattr(fd
, name
.c_str(), x
.c_str(), LARGE_BLOCK_LEN
));
100 ASSERT_EQ(CHAIN_XATTR_MAX_BLOCK_LEN
, chain_fsetxattr(fd
, name
.c_str(), x
.c_str(), CHAIN_XATTR_MAX_BLOCK_LEN
));
101 ASSERT_EQ(CHAIN_XATTR_MAX_BLOCK_LEN
, chain_fgetxattr(fd
, name
.c_str(), 0, 0));
102 ASSERT_EQ(CHAIN_XATTR_MAX_BLOCK_LEN
, chain_fgetxattr(fd
, name
.c_str(), y
, CHAIN_XATTR_MAX_BLOCK_LEN
));
103 ASSERT_EQ(0, chain_fremovexattr(fd
, name
.c_str()));
104 ASSERT_EQ(0, memcmp(x
.c_str(), y
, CHAIN_XATTR_MAX_BLOCK_LEN
));
110 ASSERT_EQ(-ENOENT
, chain_setxattr("UNLIKELY_TO_EXIST", "NAME", &x
, sizeof(x
)));
111 ASSERT_EQ(-ENOENT
, chain_getxattr("UNLIKELY_TO_EXIST", "NAME", 0, 0));
112 ASSERT_EQ(-ENOENT
, chain_getxattr("UNLIKELY_TO_EXIST", "NAME", &x
, sizeof(x
)));
113 ASSERT_EQ(-ENOENT
, chain_removexattr("UNLIKELY_TO_EXIST", "NAME"));
114 int unlikely_to_be_a_valid_fd
= 400;
115 ASSERT_EQ(-EBADF
, chain_fsetxattr(unlikely_to_be_a_valid_fd
, "NAME", &x
, sizeof(x
)));
116 ASSERT_EQ(-EBADF
, chain_fgetxattr(unlikely_to_be_a_valid_fd
, "NAME", 0, 0));
117 ASSERT_EQ(-EBADF
, chain_fgetxattr(unlikely_to_be_a_valid_fd
, "NAME", &x
, sizeof(x
)));
118 ASSERT_EQ(-EBADF
, chain_fremovexattr(unlikely_to_be_a_valid_fd
, "NAME"));
123 const string name
= user
+ string(CHAIN_XATTR_MAX_NAME_LEN
* 2, '@');
124 PrCtl unset_dumpable
;
125 ASSERT_DEATH(chain_setxattr(file
, name
.c_str(), &x
, sizeof(x
)), "");
126 ASSERT_DEATH(chain_fsetxattr(fd
, name
.c_str(), &x
, sizeof(x
)), "");
130 const string name
= user
+ string(CHAIN_XATTR_MAX_NAME_LEN
- user
.size(), '@');
131 const string
x(LARGE_BLOCK_LEN
, 'X');
133 char y
[LARGE_BLOCK_LEN
];
134 ASSERT_EQ(LARGE_BLOCK_LEN
, chain_setxattr(file
, name
.c_str(), x
.c_str(), LARGE_BLOCK_LEN
));
135 ASSERT_EQ(-ERANGE
, chain_getxattr(file
, name
.c_str(), y
, LARGE_BLOCK_LEN
- 1));
136 ASSERT_EQ(-ERANGE
, chain_getxattr(file
, name
.c_str(), y
, CHAIN_XATTR_MAX_BLOCK_LEN
));
137 ASSERT_EQ(0, chain_removexattr(file
, name
.c_str()));
141 char y
[LARGE_BLOCK_LEN
];
142 ASSERT_EQ(LARGE_BLOCK_LEN
, chain_fsetxattr(fd
, name
.c_str(), x
.c_str(), LARGE_BLOCK_LEN
));
143 ASSERT_EQ(-ERANGE
, chain_fgetxattr(fd
, name
.c_str(), y
, LARGE_BLOCK_LEN
- 1));
144 ASSERT_EQ(-ERANGE
, chain_fgetxattr(fd
, name
.c_str(), y
, CHAIN_XATTR_MAX_BLOCK_LEN
));
145 ASSERT_EQ(0, chain_fremovexattr(fd
, name
.c_str()));
153 TEST(chain_xattr
, chunk_aligned
) {
154 const char* file
= FILENAME
;
156 int fd
= ::open(file
, O_CREAT
|O_WRONLY
|O_TRUNC
, 0700);
157 const string
user("user.");
160 const string name
= "user.foo";
161 const string name2
= "user.bar";
163 for (int len
= CHAIN_XATTR_MAX_BLOCK_LEN
- 10;
164 len
< CHAIN_XATTR_MAX_BLOCK_LEN
+ 10;
166 cout
<< len
<< std::endl
;
167 const string
x(len
, 'x');
169 ASSERT_EQ(len
, chain_setxattr(file
, name
.c_str(), x
.c_str(), len
));
171 int l
= ceph_os_listxattr(file
, attrbuf
, sizeof(attrbuf
));
172 for (char *p
= attrbuf
; p
- attrbuf
< l
; p
+= strlen(p
) + 1) {
173 cout
<< " attr " << p
<< std::endl
;
175 ASSERT_EQ(len
, chain_getxattr(file
, name
.c_str(), buf
, len
*2));
176 ASSERT_EQ(0, chain_removexattr(file
, name
.c_str()));
178 ASSERT_EQ(len
, chain_fsetxattr(fd
, name2
.c_str(), x
.c_str(), len
));
179 l
= ceph_os_flistxattr(fd
, attrbuf
, sizeof(attrbuf
));
180 for (char *p
= attrbuf
; p
- attrbuf
< l
; p
+= strlen(p
) + 1) {
181 cout
<< " attr " << p
<< std::endl
;
183 ASSERT_EQ(len
, chain_fgetxattr(fd
, name2
.c_str(), buf
, len
*2));
184 ASSERT_EQ(0, chain_fremovexattr(fd
, name2
.c_str()));
187 for (int len
= CHAIN_XATTR_SHORT_BLOCK_LEN
- 10;
188 len
< CHAIN_XATTR_SHORT_BLOCK_LEN
+ 10;
190 cout
<< len
<< std::endl
;
191 const string
x(len
, 'x');
193 ASSERT_EQ(len
, chain_setxattr(file
, name
.c_str(), x
.c_str(), len
));
195 int l
= ceph_os_listxattr(file
, attrbuf
, sizeof(attrbuf
));
196 for (char *p
= attrbuf
; p
- attrbuf
< l
; p
+= strlen(p
) + 1) {
197 cout
<< " attr " << p
<< std::endl
;
199 ASSERT_EQ(len
, chain_getxattr(file
, name
.c_str(), buf
, len
*2));
203 // test tail path in chain_getxattr
204 const char *aname
= "user.baz";
205 char buf
[CHAIN_XATTR_SHORT_BLOCK_LEN
*3];
206 memset(buf
, 'x', sizeof(buf
));
207 ASSERT_EQ((int)sizeof(buf
), chain_setxattr(file
, aname
, buf
, sizeof(buf
)));
208 ASSERT_EQ(-ERANGE
, chain_getxattr(file
, aname
, buf
,
209 CHAIN_XATTR_SHORT_BLOCK_LEN
*2));
212 // test tail path in chain_fgetxattr
213 const char *aname
= "user.biz";
214 char buf
[CHAIN_XATTR_SHORT_BLOCK_LEN
*3];
215 memset(buf
, 'x', sizeof(buf
));
216 ASSERT_EQ((int)sizeof(buf
), chain_fsetxattr(fd
, aname
, buf
, sizeof(buf
)));
217 ASSERT_EQ(-ERANGE
, chain_fgetxattr(fd
, aname
, buf
,
218 CHAIN_XATTR_SHORT_BLOCK_LEN
*2));
225 void get_vector_from_xattr(vector
<string
> &xattrs
, char* xattr
, int size
) {
226 char *end
= xattr
+ size
;
227 while (xattr
< end
) {
230 xattrs
.push_back(xattr
);
231 xattr
+= strlen(xattr
) + 1;
235 bool listxattr_cmp(char* xattr1
, char* xattr2
, int size
) {
236 vector
<string
> xattrs1
;
237 vector
<string
> xattrs2
;
238 get_vector_from_xattr(xattrs1
, xattr1
, size
);
239 get_vector_from_xattr(xattrs2
, xattr2
, size
);
241 if (xattrs1
.size() != xattrs2
.size())
244 std::sort(xattrs1
.begin(), xattrs1
.end());
245 std::sort(xattrs2
.begin(), xattrs2
.end());
246 std::vector
<string
> diff
;
247 std::set_difference(xattrs1
.begin(), xattrs1
.end(),
248 xattrs2
.begin(), xattrs2
.end(),
254 TEST(chain_xattr
, listxattr
) {
255 const char* file
= FILENAME
;
257 int fd
= ::open(file
, O_CREAT
|O_WRONLY
|O_TRUNC
, 0700);
258 const string
user("user.");
259 const string name1
= user
+ string(CHAIN_XATTR_MAX_NAME_LEN
- user
.size(), '1');
260 const string name2
= user
+ string(CHAIN_XATTR_MAX_NAME_LEN
- user
.size(), '@');
261 const string
x(LARGE_BLOCK_LEN
, 'X');
264 int orig_size
= chain_listxattr(file
, NULL
, 0);
265 char *orig_buffer
= NULL
;
268 orig_buffer
= (char*)malloc(orig_size
);
269 chain_flistxattr(fd
, orig_buffer
, orig_size
);
270 orig_str
= string(orig_buffer
);
271 orig_size
= orig_str
.size();
274 ASSERT_EQ(LARGE_BLOCK_LEN
, chain_setxattr(file
, name1
.c_str(), x
.c_str(), LARGE_BLOCK_LEN
));
275 ASSERT_EQ((int)sizeof(y
), chain_setxattr(file
, name2
.c_str(), &y
, sizeof(y
)));
279 buffer_size
+= orig_size
+ sizeof(char);
280 buffer_size
+= name1
.size() + sizeof(char) + name2
.size() + sizeof(char);
283 char* expected
= (char*)malloc(buffer_size
);
284 ::memset(expected
, '\0', buffer_size
);
286 ::strcpy(expected
, orig_str
.c_str());
287 index
= orig_size
+ 1;
289 ::strcpy(expected
+ index
, name1
.c_str());
290 ::strcpy(expected
+ index
+ name1
.size() + 1, name2
.c_str());
291 char* actual
= (char*)malloc(buffer_size
);
292 ::memset(actual
, '\0', buffer_size
);
293 ASSERT_LT(buffer_size
, chain_listxattr(file
, NULL
, 0)); // size evaluation is conservative
294 chain_listxattr(file
, actual
, buffer_size
);
295 ASSERT_TRUE(listxattr_cmp(expected
, actual
, buffer_size
));
296 ::memset(actual
, '\0', buffer_size
);
297 chain_flistxattr(fd
, actual
, buffer_size
);
298 ASSERT_TRUE(listxattr_cmp(expected
, actual
, buffer_size
));
300 int unlikely_to_be_a_valid_fd
= 400;
301 ASSERT_GT(0, chain_listxattr("UNLIKELY_TO_EXIST", actual
, 0));
302 ASSERT_GT(0, chain_listxattr("UNLIKELY_TO_EXIST", actual
, buffer_size
));
303 ASSERT_GT(0, chain_flistxattr(unlikely_to_be_a_valid_fd
, actual
, 0));
304 ASSERT_GT(0, chain_flistxattr(unlikely_to_be_a_valid_fd
, actual
, buffer_size
));
305 ASSERT_EQ(-ERANGE
, chain_listxattr(file
, actual
, 1));
306 ASSERT_EQ(-ERANGE
, chain_flistxattr(fd
, actual
, 1));
308 ASSERT_EQ(0, chain_removexattr(file
, name1
.c_str()));
309 ASSERT_EQ(0, chain_removexattr(file
, name2
.c_str()));
317 list
<string
> get_xattrs(int fd
)
321 int len
= sys_flistxattr(fd
, _buf
, sizeof(_buf
));
323 return list
<string
>();
326 size_t next_len
= strlen(buf
);
327 ret
.push_back(string(buf
, buf
+ next_len
));
328 assert(len
>= (int)(next_len
+ 1));
329 buf
+= (next_len
+ 1);
330 len
-= (next_len
+ 1);
335 list
<string
> get_xattrs(string fn
)
337 int fd
= ::open(fn
.c_str(), O_RDONLY
);
339 return list
<string
>();
340 auto ret
= get_xattrs(fd
);
345 TEST(chain_xattr
, fskip_chain_cleanup_and_ensure_single_attr
)
347 const char *name
= "user.foo";
348 const char *file
= FILENAME
;
350 int fd
= ::open(file
, O_CREAT
|O_RDWR
|O_TRUNC
, 0700);
352 std::size_t existing_xattrs
= get_xattrs(fd
).size();
354 memset(buf
, 0x1F, sizeof(buf
));
355 // set chunked without either
357 std::size_t r
= chain_fsetxattr(fd
, name
, buf
, sizeof(buf
));
358 ASSERT_EQ(sizeof(buf
), r
);
359 ASSERT_GT(get_xattrs(fd
).size(), existing_xattrs
+ 1UL);
364 char buf2
[sizeof(buf
)*2];
365 std::size_t r
= chain_fgetxattr(fd
, name
, buf2
, sizeof(buf2
));
366 ASSERT_EQ(sizeof(buf
), r
);
367 ASSERT_EQ(0, memcmp(buf
, buf2
, sizeof(buf
)));
372 std::size_t r
= chain_fsetxattr
<false, true>(fd
, name
, buf
, sizeof(buf
));
373 ASSERT_EQ(sizeof(buf
), r
);
374 ASSERT_EQ(existing_xattrs
+ 1UL, get_xattrs(fd
).size());
379 char buf2
[sizeof(buf
)*2];
380 std::size_t r
= chain_fgetxattr(fd
, name
, buf2
, sizeof(buf2
));
381 ASSERT_EQ(sizeof(buf
), r
);
382 ASSERT_EQ(0, memcmp(buf
, buf2
, sizeof(buf
)));
389 TEST(chain_xattr
, skip_chain_cleanup_and_ensure_single_attr
)
391 const char *name
= "user.foo";
392 const char *file
= FILENAME
;
394 int fd
= ::open(file
, O_CREAT
|O_RDWR
|O_TRUNC
, 0700);
395 std::size_t existing_xattrs
= get_xattrs(fd
).size();
399 memset(buf
, 0x1F, sizeof(buf
));
400 // set chunked without either
402 std::size_t r
= chain_setxattr(file
, name
, buf
, sizeof(buf
));
403 ASSERT_EQ(sizeof(buf
), r
);
404 ASSERT_GT(get_xattrs(file
).size(), existing_xattrs
+ 1UL);
409 char buf2
[sizeof(buf
)*2];
410 std::size_t r
= chain_getxattr(file
, name
, buf2
, sizeof(buf2
));
411 ASSERT_EQ(sizeof(buf
), r
);
412 ASSERT_EQ(0, memcmp(buf
, buf2
, sizeof(buf
)));
417 std::size_t r
= chain_setxattr
<false, true>(file
, name
, buf
, sizeof(buf
));
418 ASSERT_EQ(sizeof(buf
), r
);
419 ASSERT_EQ(existing_xattrs
+ 1UL, get_xattrs(file
).size());
424 char buf2
[sizeof(buf
)*2];
425 std::size_t r
= chain_getxattr(file
, name
, buf2
, sizeof(buf2
));
426 ASSERT_EQ(sizeof(buf
), r
);
427 ASSERT_EQ(0, memcmp(buf
, buf2
, sizeof(buf
)));
433 int main(int argc
, char **argv
) {
434 vector
<const char*> args
;
435 argv_to_vec(argc
, (const char **)argv
, args
);
437 auto cct
= global_init(NULL
, args
, CEPH_ENTITY_TYPE_CLIENT
,
438 CODE_ENVIRONMENT_UTILITY
, 0);
439 common_init_finish(g_ceph_context
);
440 g_ceph_context
->_conf
->set_val("err_to_stderr", "false");
441 g_ceph_context
->_conf
->set_val("log_to_stderr", "false");
442 g_ceph_context
->_conf
->apply_changes(NULL
);
444 const char* file
= FILENAME
;
447 int tmpfd
= ::open(file
, O_CREAT
|O_WRONLY
|O_TRUNC
, 0700);
448 int ret
= ::ceph_os_fsetxattr(tmpfd
, "user.test", &x
, sizeof(x
));
450 ret
= ::ceph_os_fgetxattr(tmpfd
, "user.test", &y
, sizeof(y
));
453 if ((ret
< 0) || (x
!= y
)) {
454 cerr
<< "SKIP all tests because extended attributes don't appear to work in the file system in which the tests are run: " << cpp_strerror(ret
) << std::endl
;
456 ::testing::InitGoogleTest(&argc
, argv
);
457 return RUN_ALL_TESTS();
462 // compile-command: "cd ../.. ; make unittest_chain_xattr ; valgrind --tool=memcheck ./unittest_chain_xattr # --gtest_filter=chain_xattr.get_and_set"