]> git.proxmox.com Git - ceph.git/blame - ceph/src/tools/cephfs/cephfs-shell
bump version to 17.2.0
[ceph.git] / ceph / src / tools / cephfs / cephfs-shell
CommitLineData
11fdf7f2
TL
1#!/usr/bin/python3
2# coding = utf-8
3
4import argparse
5import os
6import os.path
7import sys
11fdf7f2
TL
8import cephfs as libcephfs
9import shutil
10import traceback
11import colorama
11fdf7f2
TL
12import fnmatch
13import math
14import re
15import shlex
9f95a23c 16import stat
f67539c2 17import errno
9f95a23c
TL
18
19from cmd2 import Cmd
20from cmd2 import __version__ as cmd2_version
21from distutils.version import LooseVersion
11fdf7f2 22
81eedcae 23if sys.version_info.major < 3:
9f95a23c 24 raise RuntimeError("cephfs-shell is only compatible with python3")
81eedcae 25
11fdf7f2
TL
26try:
27 from cmd2 import with_argparser
28except ImportError:
29 def with_argparser(argparser):
30 import functools
31
32 def argparser_decorator(func):
33 @functools.wraps(func)
34 def wrapper(thiz, cmdline):
35 if isinstance(cmdline, list):
36 arglist = cmdline
37 else:
38 # do not split if it's already a list
39 arglist = shlex.split(cmdline, posix=False)
40 # in case user quotes the command args
41 arglist = [arg.strip('\'""') for arg in arglist]
42 try:
43 args = argparser.parse_args(arglist)
44 except SystemExit:
9f95a23c 45 shell.exit_code = 1
11fdf7f2
TL
46 # argparse exits at seeing bad arguments
47 return
48 else:
49 return func(thiz, args)
50 argparser.prog = func.__name__[3:]
51 if argparser.description is None and func.__doc__:
52 argparser.description = func.__doc__
53
54 return wrapper
55
56 return argparser_decorator
57
58
9f95a23c
TL
59cephfs = None # holds CephFS Python bindings
60shell = None # holds instance of class CephFSShell
f67539c2
TL
61exit_codes = {'Misc': 1,
62 'KeyboardInterrupt': 2,
63 errno.EPERM: 3,
64 errno.EACCES: 4,
65 errno.ENOENT: 5,
66 errno.EIO: 6,
67 errno.ENOSPC: 7,
68 errno.EEXIST: 8,
69 errno.ENODATA: 9,
70 errno.EINVAL: 10,
71 errno.EOPNOTSUPP: 11,
72 errno.ERANGE: 12,
73 errno.EWOULDBLOCK: 13,
74 errno.ENOTEMPTY: 14,
75 errno.ENOTDIR: 15,
76 errno.EDQUOT: 16,
77 errno.EPIPE: 17,
78 errno.ESHUTDOWN: 18,
79 errno.ECONNABORTED: 19,
80 errno.ECONNREFUSED: 20,
81 errno.ECONNRESET: 21,
82 errno.EINTR: 22}
83
81eedcae 84
9f95a23c
TL
85#########################################################################
86#
87# Following are methods are generically useful through class CephFSShell
88#
89#######################################################################
81eedcae 90
e306af50 91
81eedcae 92def poutput(s, end='\n'):
494da23a 93 shell.poutput(s, end=end)
11fdf7f2
TL
94
95
9f95a23c
TL
96def perror(msg, **kwargs):
97 shell.perror(msg, **kwargs)
11fdf7f2
TL
98
99
f67539c2
TL
100def set_exit_code_msg(errcode='Misc', msg=''):
101 """
102 Set exit code and print error message
103 """
104 if isinstance(msg, libcephfs.Error):
105 shell.exit_code = exit_codes[msg.get_error_code()]
106 else:
107 shell.exit_code = exit_codes[errcode]
108 if msg:
109 perror(msg)
110
111
11fdf7f2
TL
112def mode_notation(mode):
113 """
114 """
115 permission_bits = {'0': '---',
116 '1': '--x',
117 '2': '-w-',
9f95a23c 118 '3': '-wx',
11fdf7f2
TL
119 '4': 'r--',
120 '5': 'r-x',
121 '6': 'rw-',
122 '7': 'rwx'}
123 mode = str(oct(mode))
124 notation = '-'
125 if mode[2] == '4':
126 notation = 'd'
f67539c2
TL
127 elif mode[2:4] == '12':
128 notation = 'l'
11fdf7f2
TL
129 for i in mode[-3:]:
130 notation += permission_bits[i]
131 return notation
132
133
134def get_chunks(file_size):
135 chunk_start = 0
136 chunk_size = 0x20000 # 131072 bytes, default max ssl buffer size
137 while chunk_start + chunk_size < file_size:
138 yield(chunk_start, chunk_size)
139 chunk_start += chunk_size
140 final_chunk_size = file_size - chunk_start
141 yield(chunk_start, final_chunk_size)
142
143
9f95a23c
TL
144def to_bytes(param):
145 # don't convert as follows as it can lead unusable results like coverting
146 # [1, 2, 3, 4] to '[1, 2, 3, 4]' -
147 # str(param).encode('utf-8')
148 if isinstance(param, bytes):
149 return param
150 elif isinstance(param, str):
151 return bytes(param, encoding='utf-8')
152 elif isinstance(param, list):
e306af50 153 return [i.encode('utf-8') if isinstance(i, str) else to_bytes(i) for
9f95a23c
TL
154 i in param]
155 elif isinstance(param, int) or isinstance(param, float):
156 return str(param).encode('utf-8')
157 elif param is None:
158 return None
11fdf7f2 159
e306af50 160
81eedcae
TL
161def ls(path, opts=''):
162 # opts tries to be like /bin/ls opts
163 almost_all = 'A' in opts
164 try:
165 with cephfs.opendir(path) as d:
166 while True:
167 dent = cephfs.readdir(d)
168 if dent is None:
169 return
170 elif almost_all and dent.d_name in (b'.', b'..'):
171 continue
172 yield dent
9f95a23c 173 except libcephfs.ObjectNotFound as e:
f67539c2 174 set_exit_code_msg(msg=e)
11fdf7f2 175
e306af50 176
81eedcae 177def glob(path, pattern):
11fdf7f2 178 paths = []
81eedcae 179 parent_dir = os.path.dirname(path)
eafe8130
TL
180 if parent_dir == b'':
181 parent_dir = b'/'
182 if path == b'/' or is_dir_exists(os.path.basename(path), parent_dir):
81eedcae
TL
183 for i in ls(path, opts='A'):
184 if fnmatch.fnmatch(i.d_name, pattern):
185 paths.append(os.path.join(path, i.d_name))
11fdf7f2
TL
186 return paths
187
188
189def locate_file(name, case_sensitive=True):
eafe8130 190 dir_list = sorted(set(dirwalk(cephfs.getcwd())))
11fdf7f2 191 if not case_sensitive:
81eedcae 192 return [dname for dname in dir_list if name.lower() in dname.lower()]
11fdf7f2 193 else:
81eedcae 194 return [dname for dname in dir_list if name in dname]
11fdf7f2
TL
195
196
197def get_all_possible_paths(pattern):
198 complete_pattern = pattern[:]
199 paths = []
200 is_rel_path = not os.path.isabs(pattern)
201 if is_rel_path:
202 dir_ = cephfs.getcwd()
203 else:
eafe8130 204 dir_ = b'/'
11fdf7f2 205 pattern = pattern[1:]
eafe8130 206 patterns = pattern.split(b'/')
11fdf7f2
TL
207 paths.extend(glob(dir_, patterns[0]))
208 patterns.pop(0)
209 for pattern in patterns:
210 for path in paths:
211 paths.extend(glob(path, pattern))
9f95a23c
TL
212 if is_rel_path:
213 complete_pattern = os.path.join(cephfs.getcwd(), complete_pattern)
214 return [path for path in paths if fnmatch.fnmatch(path, complete_pattern)]
11fdf7f2
TL
215
216
217suffixes = ['B', 'K', 'M', 'G', 'T', 'P']
218
219
220def humansize(nbytes):
221 i = 0
9f95a23c 222 while nbytes >= 1024 and i < len(suffixes) - 1:
11fdf7f2
TL
223 nbytes /= 1024.
224 i += 1
225 nbytes = math.ceil(nbytes)
226 f = ('%d' % nbytes).rstrip('.')
227 return '%s%s' % (f, suffixes[i])
228
229
f67539c2
TL
230def style_listing(path, is_dir, is_symlink, ls_long=False):
231 if not (is_dir or is_symlink):
232 return path
233 pretty = colorama.Style.BRIGHT
234 if is_symlink:
235 pretty += colorama.Fore.CYAN + path
236 if ls_long:
237 # Add target path
238 pretty += ' -> ' + cephfs.readlink(path, size=255).decode('utf-8')
239 elif is_dir:
240 pretty += colorama.Fore.BLUE + path + '/'
241 pretty += colorama.Style.RESET_ALL
242 return pretty
243
244
245def print_long(path, is_dir, is_symlink, human_readable):
246 info = cephfs.stat(path, follow_symlink=(not is_symlink))
247 pretty = style_listing(os.path.basename(path.decode('utf-8')), is_dir, is_symlink, True)
11fdf7f2 248 if human_readable:
f67539c2 249 sizefmt = '\t {:10s}'.format(humansize(info.st_size))
11fdf7f2 250 else:
f67539c2
TL
251 sizefmt = '{:12d}'.format(info.st_size)
252 poutput(f'{mode_notation(info.st_mode)} {sizefmt} {info.st_uid} {info.st_gid} {info.st_mtime}'
253 f' {pretty}')
11fdf7f2
TL
254
255
256def word_len(word):
257 """
258 Returns the word length, minus any color codes.
259 """
260 if word[0] == '\x1b':
261 return len(word) - 9
262 return len(word)
263
264
eafe8130 265def is_dir_exists(path, dir_=b''):
81eedcae
TL
266 path_to_stat = os.path.join(dir_, path)
267 try:
268 return ((cephfs.stat(path_to_stat).st_mode & 0o0040000) != 0)
269 except libcephfs.Error:
270 return False
11fdf7f2
TL
271
272
eafe8130 273def is_file_exists(path, dir_=b''):
81eedcae
TL
274 try:
275 # if its not a directory, then its a file
276 return ((cephfs.stat(os.path.join(dir_, path)).st_mode & 0o0040000) == 0)
277 except libcephfs.Error:
278 return False
11fdf7f2
TL
279
280
81eedcae 281def print_list(words, termwidth=79):
11fdf7f2
TL
282 if not words:
283 return
eafe8130 284 words = [word.decode('utf-8') if isinstance(word, bytes) else word for word in words]
11fdf7f2
TL
285 width = max([word_len(word) for word in words]) + 2
286 nwords = len(words)
287 ncols = max(1, (termwidth + 1) // (width + 1))
288 nrows = (nwords + ncols - 1) // ncols
289 for row in range(nrows):
290 for i in range(row, nwords, nrows):
291 word = words[i]
81eedcae 292 print_width = width
11fdf7f2 293 if word[0] == '\x1b':
81eedcae
TL
294 print_width = print_width + 10
295
296 poutput('%-*s' % (print_width, words[i]),
297 end='\n' if i + nrows >= nwords else '')
11fdf7f2
TL
298
299
81eedcae 300def copy_from_local(local_path, remote_path):
11fdf7f2 301 stdin = -1
81eedcae
TL
302 file_ = None
303 fd = None
304 convert_to_bytes = False
eafe8130 305 if local_path == b'-':
81eedcae
TL
306 file_ = sys.stdin
307 convert_to_bytes = True
11fdf7f2 308 else:
81eedcae
TL
309 try:
310 file_ = open(local_path, 'rb')
f67539c2
TL
311 except PermissionError as e:
312 set_exit_code_msg(e.errno, 'error: no permission to read local file {}'.format(
313 local_path.decode('utf-8')))
81eedcae 314 return
11fdf7f2 315 stdin = 1
81eedcae
TL
316 try:
317 fd = cephfs.open(remote_path, 'w', 0o666)
9f95a23c 318 except libcephfs.Error as e:
f67539c2 319 set_exit_code_msg(msg=e)
11fdf7f2
TL
320 return
321 progress = 0
322 while True:
323 data = file_.read(65536)
81eedcae 324 if not data or len(data) == 0:
11fdf7f2 325 break
81eedcae
TL
326 if convert_to_bytes:
327 data = to_bytes(data)
11fdf7f2
TL
328 wrote = cephfs.write(fd, data, progress)
329 if wrote < 0:
330 break
331 progress += wrote
332 cephfs.close(fd)
333 if stdin > 0:
334 file_.close()
81eedcae 335 poutput('')
11fdf7f2
TL
336
337
81eedcae 338def copy_to_local(remote_path, local_path):
11fdf7f2 339 fd = None
eafe8130 340 if local_path != b'-':
11fdf7f2 341 local_dir = os.path.dirname(local_path)
eafe8130 342 dir_list = remote_path.rsplit(b'/', 1)
11fdf7f2
TL
343 if not os.path.exists(local_dir):
344 os.makedirs(local_dir)
eafe8130 345 if len(dir_list) > 2 and dir_list[1] == b'':
11fdf7f2
TL
346 return
347 fd = open(local_path, 'wb+')
81eedcae 348 file_ = cephfs.open(remote_path, 'r')
11fdf7f2
TL
349 file_size = cephfs.stat(remote_path).st_size
350 if file_size <= 0:
351 return
352 progress = 0
353 for chunk_start, chunk_size in get_chunks(file_size):
354 file_chunk = cephfs.read(file_, chunk_start, chunk_size)
355 progress += len(file_chunk)
356 if fd:
357 fd.write(file_chunk)
358 else:
81eedcae 359 poutput(file_chunk.decode('utf-8'))
11fdf7f2
TL
360 cephfs.close(file_)
361 if fd:
362 fd.close()
363
364
81eedcae 365def dirwalk(path):
11fdf7f2
TL
366 """
367 walk a directory tree, using a generator
368 """
81eedcae
TL
369 path = os.path.normpath(path)
370 for item in ls(path, opts='A'):
eafe8130
TL
371 fullpath = os.path.join(path, item.d_name)
372 src_path = fullpath.rsplit(b'/', 1)[0]
81eedcae
TL
373
374 yield os.path.normpath(fullpath)
375 if is_dir_exists(item.d_name, src_path):
376 for x in dirwalk(fullpath):
377 yield x
11fdf7f2
TL
378
379
9f95a23c
TL
380##################################################################
381#
382# Following methods are implementation for CephFS Shell commands
383#
384#################################################################
385
11fdf7f2
TL
386class CephFSShell(Cmd):
387
388 def __init__(self):
20effc67 389 super().__init__()
11fdf7f2
TL
390 self.working_dir = cephfs.getcwd().decode('utf-8')
391 self.set_prompt()
392 self.interactive = False
393 self.umask = '2'
394
395 def default(self, line):
9f95a23c 396 perror('Unrecognized command')
11fdf7f2
TL
397
398 def set_prompt(self):
81eedcae
TL
399 self.prompt = ('\033[01;33mCephFS:~' + colorama.Fore.LIGHTCYAN_EX
400 + self.working_dir + colorama.Style.RESET_ALL
401 + '\033[01;33m>>>\033[00m ')
11fdf7f2
TL
402
403 def create_argparser(self, command):
404 try:
405 argparse_args = getattr(self, 'argparse_' + command)
406 except AttributeError:
f67539c2 407 set_exit_code_msg()
11fdf7f2
TL
408 return None
409 doc_lines = getattr(
410 self, 'do_' + command).__doc__.expandtabs().splitlines()
e306af50 411 if '' in doc_lines:
11fdf7f2
TL
412 blank_idx = doc_lines.index('')
413 usage = doc_lines[:blank_idx]
414 description = doc_lines[blank_idx + 1:]
415 else:
416 usage = doc_lines
417 description = []
418 parser = argparse.ArgumentParser(
419 prog=command,
420 usage='\n'.join(usage),
421 description='\n'.join(description),
422 formatter_class=argparse.ArgumentDefaultsHelpFormatter
423 )
424 for args, kwargs in argparse_args:
425 parser.add_argument(*args, **kwargs)
426 return parser
427
428 def complete_filenames(self, text, line, begidx, endidx):
429 if not text:
81eedcae
TL
430 completions = [x.d_name.decode('utf-8') + '/' * int(x.is_dir())
431 for x in ls(b".", opts='A')]
11fdf7f2
TL
432 else:
433 if text.count('/') > 0:
81eedcae
TL
434 completions = [text.rsplit('/', 1)[0] + '/'
435 + x.d_name.decode('utf-8') + '/'
436 * int(x.is_dir()) for x in ls('/'
437 + text.rsplit('/', 1)[0], opts='A')
438 if x.d_name.decode('utf-8').startswith(
439 text.rsplit('/', 1)[1])]
11fdf7f2 440 else:
81eedcae
TL
441 completions = [x.d_name.decode('utf-8') + '/'
442 * int(x.is_dir()) for x in ls(b".", opts='A')
443 if x.d_name.decode('utf-8').startswith(text)]
11fdf7f2
TL
444 if len(completions) == 1 and completions[0][-1] == '/':
445 dir_, file_ = completions[0].rsplit('/', 1)
81eedcae
TL
446 completions.extend([dir_ + '/' + x.d_name.decode('utf-8')
447 + '/' * int(x.is_dir()) for x in
448 ls('/' + dir_, opts='A')
449 if x.d_name.decode('utf-8').startswith(file_)])
11fdf7f2
TL
450 return self.delimiter_complete(text, line, begidx, endidx, completions, '/')
451 return completions
452
9f95a23c 453 def onecmd(self, line, **kwargs):
11fdf7f2
TL
454 """
455 Global error catcher
456 """
457 try:
9f95a23c 458 res = Cmd.onecmd(self, line, **kwargs)
11fdf7f2
TL
459 if self.interactive:
460 self.set_prompt()
461 return res
462 except ConnectionError as e:
f67539c2 463 set_exit_code_msg(e.errno, f'***\n{e}')
11fdf7f2 464 except KeyboardInterrupt:
f67539c2 465 set_exit_code_msg('KeyboardInterrupt', 'Command aborted')
9f95a23c 466 except (libcephfs.Error, Exception) as e:
9f95a23c
TL
467 if shell.debug:
468 traceback.print_exc(file=sys.stdout)
f67539c2 469 set_exit_code_msg(msg=e)
11fdf7f2 470
eafe8130
TL
471 class path_to_bytes(argparse.Action):
472 def __call__(self, parser, namespace, values, option_string=None):
9f95a23c
TL
473 values = to_bytes(values)
474 setattr(namespace, self.dest, values)
475
476 # TODO: move the necessary contents from here to `class path_to_bytes`.
477 class get_list_of_bytes_path(argparse.Action):
478 def __call__(self, parser, namespace, values, option_string=None):
479 values = to_bytes(values)
480
481 if values == b'.':
482 values = cephfs.getcwd()
483 else:
484 for i in values:
485 if i == b'.':
486 values[values.index(i)] = cephfs.getcwd()
487
eafe8130
TL
488 setattr(namespace, self.dest, values)
489
11fdf7f2
TL
490 def complete_mkdir(self, text, line, begidx, endidx):
491 """
492 auto complete of file name.
493 """
494 return self.complete_filenames(text, line, begidx, endidx)
495
81eedcae
TL
496 class ModeAction(argparse.Action):
497 def __init__(self, option_strings, dest, nargs=None, **kwargs):
498 if nargs is not None and nargs != '?':
499 raise ValueError("more than one modes not allowed")
500 super().__init__(option_strings, dest, **kwargs)
501
502 def __call__(self, parser, namespace, values, option_string=None):
503 o_mode = 0
504 res = None
505 try:
506 o_mode = int(values, base=8)
507 except ValueError:
508 res = re.match('((u?g?o?)|(a?))(=)(r?w?x?)', values)
509 if res is None:
510 parser.error("invalid mode: %s\n"
511 "mode must be a numeric octal literal\n"
512 "or ((u?g?o?)|(a?))(=)(r?w?x?)" %
513 values)
514 else:
515 # we are supporting only assignment of mode and not + or -
516 # as is generally available with the chmod command
517 # eg.
518 # >>> res = re.match('((u?g?o?)|(a?))(=)(r?w?x?)', 'go=')
519 # >>> res.groups()
520 # ('go', 'go', None, '=', '')
521 val = res.groups()
522
523 if val[3] != '=':
524 parser.error("need assignment operator between user "
525 "and mode specifiers")
526 if val[4] == '':
527 parser.error("invalid mode: %s\n"
528 "mode must be combination of: r | w | x" %
529 values)
530 users = ''
531 if val[2] is None:
532 users = val[1]
533 else:
534 users = val[2]
535
536 t_mode = 0
537 if users == 'a':
538 users = 'ugo'
539
540 if 'r' in val[4]:
541 t_mode |= 4
542 if 'w' in val[4]:
543 t_mode |= 2
544 if 'x' in val[4]:
545 t_mode |= 1
546
547 if 'u' in users:
548 o_mode |= (t_mode << 6)
549 if 'g' in users:
550 o_mode |= (t_mode << 3)
551 if 'o' in users:
552 o_mode |= t_mode
553
554 if o_mode < 0:
555 parser.error("invalid mode: %s\n"
556 "mode cannot be negative" % values)
557 if o_mode > 0o777:
558 parser.error("invalid mode: %s\n"
559 "mode cannot be greater than octal 0777" % values)
560
561 setattr(namespace, self.dest, str(oct(o_mode)))
562
11fdf7f2
TL
563 mkdir_parser = argparse.ArgumentParser(
564 description='Create the directory(ies), if they do not already exist.')
565 mkdir_parser.add_argument('dirs', type=str,
eafe8130 566 action=path_to_bytes,
11fdf7f2
TL
567 metavar='DIR_NAME',
568 help='Name of new_directory.',
569 nargs='+')
570 mkdir_parser.add_argument('-m', '--mode', type=str,
81eedcae 571 action=ModeAction,
11fdf7f2
TL
572 help='Sets the access mode for the new directory.')
573 mkdir_parser.add_argument('-p', '--parent', action='store_true',
9f95a23c
TL
574 help='Create parent directories as necessary. '
575 'When this option is specified, no error is'
576 'reported if a directory already exists.')
11fdf7f2
TL
577
578 @with_argparser(mkdir_parser)
579 def do_mkdir(self, args):
580 """
581 Create directory.
582 """
81eedcae 583 for path in args.dirs:
11fdf7f2
TL
584 if args.mode:
585 permission = int(args.mode, 8)
586 else:
587 permission = 0o777
588 if args.parent:
589 cephfs.mkdirs(path, permission)
590 else:
81eedcae
TL
591 try:
592 cephfs.mkdir(path, permission)
9f95a23c 593 except libcephfs.Error as e:
f67539c2 594 set_exit_code_msg(e)
11fdf7f2
TL
595
596 def complete_put(self, text, line, begidx, endidx):
597 """
598 auto complete of file name.
599 """
600 index_dict = {1: self.path_complete}
601 return self.index_based_complete(text, line, begidx, endidx, index_dict)
602
603 put_parser = argparse.ArgumentParser(
604 description='Copy a file/directory to Ceph File System from Local File System.')
eafe8130 605 put_parser.add_argument('local_path', type=str, action=path_to_bytes,
11fdf7f2 606 help='Path of the file in the local system')
eafe8130 607 put_parser.add_argument('remote_path', type=str, action=path_to_bytes,
81eedcae
TL
608 help='Path of the file in the remote system.',
609 nargs='?', default='.')
11fdf7f2
TL
610 put_parser.add_argument('-f', '--force', action='store_true',
611 help='Overwrites the destination if it already exists.')
612
613 @with_argparser(put_parser)
614 def do_put(self, args):
615 """
9f95a23c 616 Copy a local file/directory to CephFS.
11fdf7f2
TL
617 """
618 root_src_dir = args.local_path
619 root_dst_dir = args.remote_path
eafe8130
TL
620 if args.local_path == b'.' or args.local_path == b'./':
621 root_src_dir = os.getcwdb()
622 elif len(args.local_path.rsplit(b'/', 1)) < 2:
623 root_src_dir = os.path.join(os.getcwdb(), args.local_path)
81eedcae 624 else:
eafe8130
TL
625 p = args.local_path.split(b'/')
626 if p[0] == b'.':
627 root_src_dir = os.getcwdb()
81eedcae
TL
628 p.pop(0)
629 while len(p) > 0:
eafe8130
TL
630 root_src_dir += b'/' + p.pop(0)
631
632 if root_dst_dir == b'.':
633 if args.local_path != b'-':
634 root_dst_dir = root_src_dir.rsplit(b'/', 1)[1]
635 if root_dst_dir == b'':
636 root_dst_dir = root_src_dir.rsplit(b'/', 1)[0]
637 a = root_dst_dir.rsplit(b'/', 1)
81eedcae
TL
638 if len(a) > 1:
639 root_dst_dir = a[1]
640 else:
641 root_dst_dir = a[0]
642 else:
f67539c2
TL
643 set_exit_code_msg(errno.EINVAL, 'error: no filename specified '
644 'for destination')
81eedcae
TL
645 return
646
eafe8130
TL
647 if root_dst_dir[-1] != b'/':
648 root_dst_dir += b'/'
81eedcae 649
eafe8130 650 if args.local_path == b'-' or os.path.isfile(root_src_dir):
81eedcae
TL
651 if not args.force:
652 if os.path.isfile(root_src_dir):
653 dst_file = root_dst_dir
654 if is_file_exists(dst_file):
f67539c2
TL
655 set_exit_code_msg(errno.EEXIST,
656 f"{dst_file.decode('utf-8')}: file "
657 "exists! use --force to overwrite")
81eedcae 658 return
eafe8130
TL
659 if args.local_path == b'-':
660 root_src_dir = b'-'
81eedcae 661 copy_from_local(root_src_dir, root_dst_dir)
11fdf7f2
TL
662 else:
663 for src_dir, dirs, files in os.walk(root_src_dir):
eafe8130
TL
664 if isinstance(src_dir, str):
665 src_dir = to_bytes(src_dir)
11fdf7f2 666 dst_dir = src_dir.replace(root_src_dir, root_dst_dir, 1)
eafe8130 667 dst_dir = re.sub(rb'\/+', b'/', cephfs.getcwd()
81eedcae 668 + dst_dir)
eafe8130 669 if args.force and dst_dir != b'/' and not is_dir_exists(
81eedcae
TL
670 dst_dir[:-1]) and not locate_file(dst_dir):
671 try:
672 cephfs.mkdirs(dst_dir, 0o777)
673 except libcephfs.Error:
674 pass
eafe8130 675 if (not args.force) and dst_dir != b'/' and not is_dir_exists(
81eedcae
TL
676 dst_dir) and not os.path.isfile(root_src_dir):
677 try:
678 cephfs.mkdirs(dst_dir, 0o777)
679 except libcephfs.Error:
9f95a23c 680 # TODO: perhaps, set retval to 1?
81eedcae
TL
681 pass
682
11fdf7f2 683 for dir_ in dirs:
81eedcae
TL
684 dir_name = os.path.join(dst_dir, dir_)
685 if not is_dir_exists(dir_name):
686 try:
687 cephfs.mkdirs(dir_name, 0o777)
688 except libcephfs.Error:
9f95a23c 689 # TODO: perhaps, set retval to 1?
81eedcae
TL
690 pass
691
11fdf7f2
TL
692 for file_ in files:
693 src_file = os.path.join(src_dir, file_)
eafe8130 694 dst_file = re.sub(rb'\/+', b'/', b'/' + dst_dir + b'/' + file_)
11fdf7f2
TL
695 if (not args.force) and is_file_exists(dst_file):
696 return
eafe8130
TL
697 copy_from_local(src_file, os.path.join(cephfs.getcwd(),
698 dst_file))
11fdf7f2
TL
699
700 def complete_get(self, text, line, begidx, endidx):
701 """
702 auto complete of file name.
703 """
704 return self.complete_filenames(text, line, begidx, endidx)
705
706 get_parser = argparse.ArgumentParser(
707 description='Copy a file from Ceph File System from Local Directory.')
eafe8130 708 get_parser.add_argument('remote_path', type=str, action=path_to_bytes,
11fdf7f2 709 help='Path of the file in the remote system')
eafe8130 710 get_parser.add_argument('local_path', type=str, action=path_to_bytes,
81eedcae
TL
711 help='Path of the file in the local system',
712 nargs='?', default='.')
713 get_parser.add_argument('-f', '--force', action='store_true',
714 help='Overwrites the destination if it already exists.')
11fdf7f2
TL
715
716 @with_argparser(get_parser)
717 def do_get(self, args):
718 """
9f95a23c 719 Copy a file/directory from CephFS to given path.
11fdf7f2
TL
720 """
721 root_src_dir = args.remote_path
722 root_dst_dir = args.local_path
eafe8130
TL
723 fname = root_src_dir.rsplit(b'/', 1)
724 if args.local_path == b'.':
725 root_dst_dir = os.getcwdb()
726 if args.remote_path == b'.':
727 root_src_dir = cephfs.getcwd()
728 if args.local_path == b'-':
729 if args.remote_path == b'.' or args.remote_path == b'./':
f67539c2 730 set_exit_code_msg(errno.EINVAL, 'error: no remote file name specified')
81eedcae 731 return
eafe8130 732 copy_to_local(root_src_dir, b'-')
11fdf7f2 733 elif is_file_exists(args.remote_path):
81eedcae 734 copy_to_local(root_src_dir,
eafe8130 735 root_dst_dir + b'/' + root_src_dir)
e306af50 736 elif b'/' in root_src_dir and is_file_exists(fname[1], fname[0]):
81eedcae 737 copy_to_local(root_src_dir, root_dst_dir)
11fdf7f2
TL
738 else:
739 files = list(reversed(sorted(dirwalk(root_src_dir))))
740 if len(files) == 0:
81eedcae 741 try:
eafe8130 742 os.makedirs(root_dst_dir + b'/' + root_src_dir)
9f95a23c 743 except OSError as e:
81eedcae
TL
744 if args.force:
745 pass
746 else:
f67539c2
TL
747 set_exit_code_msg(e.errno, f"{root_src_dir.decode('utf-8')}: "
748 "already exists! use --force to overwrite")
81eedcae
TL
749 return
750
11fdf7f2 751 for file_ in files:
eafe8130 752 dst_dirpath, dst_file = file_.rsplit(b'/', 1)
11fdf7f2
TL
753 if dst_dirpath in files:
754 files.remove(dst_dirpath)
755 dst_path = os.path.join(root_dst_dir, dst_dirpath, dst_file)
756 dst_path = os.path.normpath(dst_path)
11fdf7f2 757 if is_dir_exists(file_):
81eedcae
TL
758 try:
759 os.makedirs(dst_path)
760 except OSError:
761 pass
11fdf7f2 762 else:
81eedcae
TL
763 if not args.force:
764 try:
765 os.stat(dst_path)
f67539c2
TL
766 set_exit_code_msg(errno.EEXIST, f"{file_.decode('utf-8')}: "
767 "file already exists! use --force to override")
81eedcae
TL
768 return
769 except OSError:
770 copy_to_local(file_, dst_path)
771 else:
772 copy_to_local(file_, dst_path)
773
11fdf7f2
TL
774 return 0
775
776 def complete_ls(self, text, line, begidx, endidx):
777 """
778 auto complete of file name.
779 """
780 return self.complete_filenames(text, line, begidx, endidx)
781
782 ls_parser = argparse.ArgumentParser(
783 description='Copy a file from Ceph File System from Local Directory.')
784 ls_parser.add_argument('-l', '--long', action='store_true',
785 help='Detailed list of items in the directory.')
786 ls_parser.add_argument('-r', '--reverse', action='store_true',
787 help='Reverse order of listing items in the directory.')
788 ls_parser.add_argument('-H', action='store_true', help='Human Readable')
789 ls_parser.add_argument('-a', '--all', action='store_true',
790 help='Do not Ignore entries starting with .')
791 ls_parser.add_argument('-S', action='store_true', help='Sort by file_size')
eafe8130
TL
792 ls_parser.add_argument('paths', help='Name of Directories',
793 action=path_to_bytes, nargs='*', default=['.'])
11fdf7f2
TL
794
795 @with_argparser(ls_parser)
796 def do_ls(self, args):
797 """
798 List all the files and directories in the current working directory
799 """
81eedcae
TL
800 paths = args.paths
801 for path in paths:
11fdf7f2
TL
802 values = []
803 items = []
f67539c2
TL
804 try:
805 if path.count(b'*') > 0:
806 all_items = get_all_possible_paths(path)
807 if len(all_items) == 0:
808 continue
809 path = all_items[0].rsplit(b'/', 1)[0]
810 if path == b'':
811 path = b'/'
812 dirs = []
813 for i in all_items:
814 for item in ls(path):
815 d_name = item.d_name
816 if os.path.basename(i) == d_name:
817 if item.is_dir():
818 dirs.append(os.path.join(path, d_name))
819 else:
820 items.append(item)
821 if dirs:
822 paths.extend(dirs)
823 else:
824 poutput(path.decode('utf-8'), end=':\n')
825 items = sorted(items, key=lambda item: item.d_name)
11fdf7f2 826 else:
f67539c2
TL
827 if path != b'' and path != cephfs.getcwd() and len(paths) > 1:
828 poutput(path.decode('utf-8'), end=':\n')
829 items = sorted(ls(path), key=lambda item: item.d_name)
830 if not args.all:
831 items = [i for i in items if not i.d_name.startswith(b'.')]
832 if args.S:
833 items = sorted(items, key=lambda item: cephfs.stat(
834 path + b'/' + item.d_name, follow_symlink=(
835 not item.is_symbol_file())).st_size)
836 if args.reverse:
837 items = reversed(items)
838 for item in items:
839 filepath = item.d_name
840 is_dir = item.is_dir()
841 is_sym_lnk = item.is_symbol_file()
842 try:
843 if args.long and args.H:
844 print_long(os.path.join(cephfs.getcwd(), path, filepath), is_dir,
845 is_sym_lnk, True)
846 elif args.long:
847 print_long(os.path.join(cephfs.getcwd(), path, filepath), is_dir,
848 is_sym_lnk, False)
849 elif is_sym_lnk or is_dir:
850 values.append(style_listing(filepath.decode('utf-8'), is_dir,
851 is_sym_lnk))
852 else:
853 values.append(filepath)
854 except libcephfs.Error as e:
855 set_exit_code_msg(msg=e)
856 if not args.long:
857 print_list(values, shutil.get_terminal_size().columns)
858 if path != paths[-1]:
859 poutput('')
860 except libcephfs.Error as e:
861 set_exit_code_msg(msg=e)
11fdf7f2
TL
862
863 def complete_rmdir(self, text, line, begidx, endidx):
864 """
865 auto complete of file name.
866 """
867 return self.complete_filenames(text, line, begidx, endidx)
868
869 rmdir_parser = argparse.ArgumentParser(description='Remove Directory.')
eafe8130
TL
870 rmdir_parser.add_argument('paths', help='Directory Path.', nargs='+',
871 action=path_to_bytes)
11fdf7f2 872 rmdir_parser.add_argument('-p', '--parent', action='store_true',
9f95a23c
TL
873 help='Remove parent directories as necessary. '
874 'When this option is specified, no error '
875 'is reported if a directory has any '
876 'sub-directories, files')
11fdf7f2
TL
877
878 @with_argparser(rmdir_parser)
879 def do_rmdir(self, args):
9f95a23c
TL
880 self.do_rmdir_helper(args)
881
882 def do_rmdir_helper(self, args):
11fdf7f2
TL
883 """
884 Remove a specific Directory
885 """
886 is_pattern = False
81eedcae
TL
887 paths = args.paths
888 for path in paths:
eafe8130 889 if path.count(b'*') > 0:
11fdf7f2 890 is_pattern = True
81eedcae 891 all_items = get_all_possible_paths(path)
11fdf7f2 892 if len(all_items) > 0:
eafe8130
TL
893 path = all_items[0].rsplit(b'/', 1)[0]
894 if path == b'':
895 path = b'/'
11fdf7f2
TL
896 dirs = []
897 for i in all_items:
81eedcae
TL
898 for item in ls(path):
899 d_name = item.d_name
11fdf7f2
TL
900 if os.path.basename(i) == d_name:
901 if item.is_dir():
81eedcae
TL
902 dirs.append(os.path.join(path, d_name))
903 paths.extend(dirs)
11fdf7f2
TL
904 continue
905 else:
906 is_pattern = False
9f95a23c 907
11fdf7f2 908 if args.parent:
9f95a23c
TL
909 path = os.path.join(cephfs.getcwd(), path.rsplit(b'/')[0])
910 files = list(sorted(set(dirwalk(path)), reverse=True))
911 if not files:
912 path = b'.'
eafe8130 913 for filepath in files:
9f95a23c
TL
914 try:
915 cephfs.rmdir(os.path.normpath(filepath))
916 except libcephfs.Error as e:
917 perror(e)
918 path = b'.'
919 break
920 else:
921 path = os.path.normpath(os.path.join(cephfs.getcwd(), path))
eafe8130 922 if not is_pattern and path != os.path.normpath(b''):
81eedcae
TL
923 try:
924 cephfs.rmdir(path)
9f95a23c 925 except libcephfs.Error as e:
f67539c2 926 set_exit_code_msg(msg=e)
11fdf7f2
TL
927
928 def complete_rm(self, text, line, begidx, endidx):
929 """
930 auto complete of file name.
931 """
932 return self.complete_filenames(text, line, begidx, endidx)
933
934 rm_parser = argparse.ArgumentParser(description='Remove File.')
eafe8130
TL
935 rm_parser.add_argument('paths', help='File Path.', nargs='+',
936 action=path_to_bytes)
11fdf7f2
TL
937
938 @with_argparser(rm_parser)
939 def do_rm(self, args):
940 """
941 Remove a specific file
942 """
494da23a
TL
943 file_paths = args.paths
944 for path in file_paths:
eafe8130 945 if path.count(b'*') > 0:
494da23a 946 file_paths.extend([i for i in get_all_possible_paths(
81eedcae 947 path) if is_file_exists(i)])
11fdf7f2 948 else:
81eedcae
TL
949 try:
950 cephfs.unlink(path)
9f95a23c
TL
951 except libcephfs.Error as e:
952 # NOTE: perhaps we need a better msg here
f67539c2 953 set_exit_code_msg(msg=e)
11fdf7f2
TL
954
955 def complete_mv(self, text, line, begidx, endidx):
956 """
957 auto complete of file name.
958 """
959 return self.complete_filenames(text, line, begidx, endidx)
960
961 mv_parser = argparse.ArgumentParser(description='Move File.')
eafe8130
TL
962 mv_parser.add_argument('src_path', type=str, action=path_to_bytes,
963 help='Source File Path.')
964 mv_parser.add_argument('dest_path', type=str, action=path_to_bytes,
11fdf7f2
TL
965 help='Destination File Path.')
966
967 @with_argparser(mv_parser)
968 def do_mv(self, args):
969 """
970 Rename a file or Move a file from source path to the destination
971 """
9f95a23c 972 cephfs.rename(args.src_path, args.dest_path)
11fdf7f2
TL
973
974 def complete_cd(self, text, line, begidx, endidx):
975 """
976 auto complete of file name.
977 """
978 return self.complete_filenames(text, line, begidx, endidx)
979
980 cd_parser = argparse.ArgumentParser(description='Change working directory')
eafe8130
TL
981 cd_parser.add_argument('path', type=str, help='Name of the directory.',
982 action=path_to_bytes, nargs='?', default='/')
11fdf7f2
TL
983
984 @with_argparser(cd_parser)
985 def do_cd(self, args):
986 """
987 Change working directory
988 """
9f95a23c
TL
989 cephfs.chdir(args.path)
990 self.working_dir = cephfs.getcwd().decode('utf-8')
991 self.set_prompt()
11fdf7f2
TL
992
993 def do_cwd(self, arglist):
994 """
995 Get current working directory.
996 """
9f95a23c 997 poutput(cephfs.getcwd().decode('utf-8'))
11fdf7f2
TL
998
999 def complete_chmod(self, text, line, begidx, endidx):
1000 """
1001 auto complete of file name.
1002 """
1003 return self.complete_filenames(text, line, begidx, endidx)
1004
1005 chmod_parser = argparse.ArgumentParser(description='Create Directory.')
81eedcae 1006 chmod_parser.add_argument('mode', type=str, action=ModeAction, help='Mode')
eafe8130
TL
1007 chmod_parser.add_argument('paths', type=str, action=path_to_bytes,
1008 help='Name of the file', nargs='+')
11fdf7f2
TL
1009
1010 @with_argparser(chmod_parser)
1011 def do_chmod(self, args):
1012 """
1013 Change permission of a file
1014 """
81eedcae
TL
1015 for path in args.paths:
1016 mode = int(args.mode, base=8)
1017 try:
1018 cephfs.chmod(path, mode)
9f95a23c 1019 except libcephfs.Error as e:
f67539c2 1020 set_exit_code_msg(msg=e)
11fdf7f2
TL
1021
1022 def complete_cat(self, text, line, begidx, endidx):
1023 """
1024 auto complete of file name.
1025 """
1026 return self.complete_filenames(text, line, begidx, endidx)
1027
1028 cat_parser = argparse.ArgumentParser(description='')
eafe8130
TL
1029 cat_parser.add_argument('paths', help='Name of Files', action=path_to_bytes,
1030 nargs='+')
11fdf7f2
TL
1031
1032 @with_argparser(cat_parser)
1033 def do_cat(self, args):
1034 """
1035 Print contents of a file
1036 """
81eedcae
TL
1037 for path in args.paths:
1038 if is_file_exists(path):
eafe8130 1039 copy_to_local(path, b'-')
81eedcae 1040 else:
f67539c2
TL
1041 set_exit_code_msg(errno.ENOENT, '{}: no such file'.format(
1042 path.decode('utf-8')))
11fdf7f2
TL
1043
1044 umask_parser = argparse.ArgumentParser(description='Set umask value.')
81eedcae
TL
1045 umask_parser.add_argument('mode', help='Mode', type=str, action=ModeAction,
1046 nargs='?', default='')
11fdf7f2
TL
1047
1048 @with_argparser(umask_parser)
1049 def do_umask(self, args):
1050 """
1051 Set Umask value.
1052 """
1053 if args.mode == '':
9f95a23c 1054 poutput(self.umask.zfill(4))
11fdf7f2
TL
1055 else:
1056 mode = int(args.mode, 8)
1057 self.umask = str(oct(cephfs.umask(mode))[2:])
1058
1059 def complete_write(self, text, line, begidx, endidx):
1060 """
1061 auto complete of file name.
1062 """
1063 return self.complete_filenames(text, line, begidx, endidx)
1064
eafe8130
TL
1065 write_parser = argparse.ArgumentParser(description='Writes data into a file')
1066 write_parser.add_argument('path', type=str, action=path_to_bytes,
1067 help='Name of File')
11fdf7f2
TL
1068
1069 @with_argparser(write_parser)
1070 def do_write(self, args):
1071 """
1072 Write data into a file.
1073 """
1074
eafe8130 1075 copy_from_local(b'-', args.path)
11fdf7f2
TL
1076
1077 def complete_lcd(self, text, line, begidx, endidx):
1078 """
1079 auto complete of file name.
1080 """
1081 index_dict = {1: self.path_complete}
1082 return self.index_based_complete(text, line, begidx, endidx, index_dict)
1083
1084 lcd_parser = argparse.ArgumentParser(description='')
eafe8130 1085 lcd_parser.add_argument('path', type=str, action=path_to_bytes, help='Path')
11fdf7f2
TL
1086
1087 @with_argparser(lcd_parser)
1088 def do_lcd(self, args):
1089 """
1090 Moves into the given local directory
1091 """
81eedcae
TL
1092 try:
1093 os.chdir(os.path.expanduser(args.path))
1094 except OSError as e:
f67539c2
TL
1095 set_exit_code_msg(e.errno, "Cannot change to "
1096 f"{e.filename.decode('utf-8')}: {e.strerror}")
11fdf7f2
TL
1097
1098 def complete_lls(self, text, line, begidx, endidx):
1099 """
1100 auto complete of file name.
1101 """
1102 index_dict = {1: self.path_complete}
1103 return self.index_based_complete(text, line, begidx, endidx, index_dict)
1104
1105 lls_parser = argparse.ArgumentParser(
1106 description='List files in local system.')
eafe8130
TL
1107 lls_parser.add_argument('paths', help='Paths', action=path_to_bytes,
1108 nargs='*')
11fdf7f2
TL
1109
1110 @with_argparser(lls_parser)
1111 def do_lls(self, args):
1112 """
1113 Lists all files and folders in the current local directory
1114 """
81eedcae 1115 if not args.paths:
eafe8130 1116 print_list(os.listdir(os.getcwdb()))
81eedcae
TL
1117 else:
1118 for path in args.paths:
1119 try:
1120 items = os.listdir(path)
9f95a23c 1121 poutput("{}:".format(path.decode('utf-8')))
81eedcae
TL
1122 print_list(items)
1123 except OSError as e:
f67539c2
TL
1124 set_exit_code_msg(e.errno, f"{e.filename.decode('utf-8')}: "
1125 f"{e.strerror}")
81eedcae
TL
1126 # Arguments to the with_argpaser decorator function are sticky.
1127 # The items in args.path do not get overwritten in subsequent calls.
1128 # The arguments remain in args.paths after the function exits and we
1129 # neeed to clean it up to ensure the next call works as expected.
1130 args.paths.clear()
11fdf7f2
TL
1131
1132 def do_lpwd(self, arglist):
1133 """
1134 Prints the absolute path of the current local directory
1135 """
9f95a23c
TL
1136 poutput(os.getcwd())
1137
1138 def complete_df(self, text, line, begidx, endidx):
1139 """
1140 auto complete of file name.
1141 """
1142 return self.complete_filenames(text, line, begidx, endidx)
11fdf7f2 1143
9f95a23c
TL
1144 df_parser = argparse.ArgumentParser(description='Show information about\
1145 the amount of available disk space')
1146 df_parser.add_argument('file', help='Name of the file', nargs='*',
1147 default=['.'], action=path_to_bytes)
1148
1149 @with_argparser(df_parser)
11fdf7f2
TL
1150 def do_df(self, arglist):
1151 """
1152 Display the amount of available disk space for file systems
1153 """
9f95a23c
TL
1154 header = True # Set to true for printing header only once
1155 if b'.' == arglist.file[0]:
1156 arglist.file = ls(b'.')
1157
1158 for file in arglist.file:
1159 if isinstance(file, libcephfs.DirEntry):
1160 file = file.d_name
1161 if file == b'.' or file == b'..':
1162 continue
1163 try:
1164 statfs = cephfs.statfs(file)
1165 stat = cephfs.stat(file)
e306af50 1166 block_size = (statfs['f_blocks'] * statfs['f_bsize']) // 1024
11fdf7f2
TL
1167 available = block_size - stat.st_size
1168 use = 0
9f95a23c 1169
11fdf7f2 1170 if block_size > 0:
e306af50 1171 use = (stat.st_size * 100) // block_size
9f95a23c
TL
1172
1173 if header:
1174 header = False
1175 poutput('{:25s}\t{:5s}\t{:15s}{:10s}{}'.format(
1176 "1K-blocks", "Used", "Available", "Use%",
1177 "Stored on"))
1178
1179 poutput('{:d}\t{:18d}\t{:8d}\t{:10s} {}'.format(block_size,
1180 stat.st_size, available, str(int(use)) + '%',
1181 file.decode('utf-8')))
1182 except libcephfs.OSError as e:
f67539c2
TL
1183 set_exit_code_msg(e.get_error_code(), "could not statfs {}: {}".format(
1184 file.decode('utf-8'), e.strerror))
11fdf7f2
TL
1185
1186 locate_parser = argparse.ArgumentParser(
1187 description='Find file within file system')
eafe8130
TL
1188 locate_parser.add_argument('name', help='name', type=str,
1189 action=path_to_bytes)
81eedcae
TL
1190 locate_parser.add_argument('-c', '--count', action='store_true',
1191 help='Count list of items located.')
11fdf7f2
TL
1192 locate_parser.add_argument(
1193 '-i', '--ignorecase', action='store_true', help='Ignore case')
1194
1195 @with_argparser(locate_parser)
1196 def do_locate(self, args):
1197 """
1198 Find a file within the File System
1199 """
eafe8130
TL
1200 if args.name.count(b'*') == 1:
1201 if args.name[0] == b'*':
1202 args.name += b'/'
11fdf7f2 1203 elif args.name[-1] == '*':
eafe8130
TL
1204 args.name = b'/' + args.name
1205 args.name = args.name.replace(b'*', b'')
11fdf7f2
TL
1206 if args.ignorecase:
1207 locations = locate_file(args.name, False)
1208 else:
1209 locations = locate_file(args.name)
1210 if args.count:
9f95a23c 1211 poutput(len(locations))
11fdf7f2 1212 else:
9f95a23c 1213 poutput((b'\n'.join(locations)).decode('utf-8'))
11fdf7f2
TL
1214
1215 def complete_du(self, text, line, begidx, endidx):
1216 """
1217 auto complete of file name.
1218 """
1219 return self.complete_filenames(text, line, begidx, endidx)
1220
1221 du_parser = argparse.ArgumentParser(
1222 description='Disk Usage of a Directory')
9f95a23c
TL
1223 du_parser.add_argument('paths', type=str, action=get_list_of_bytes_path,
1224 help='Name of the directory.', nargs='*',
1225 default=[b'.'])
11fdf7f2
TL
1226 du_parser.add_argument('-r', action='store_true',
1227 help='Recursive Disk usage of all directories.')
1228
1229 @with_argparser(du_parser)
1230 def do_du(self, args):
1231 """
9f95a23c 1232 Print disk usage of a given path(s).
11fdf7f2 1233 """
9f95a23c
TL
1234 def print_disk_usage(files):
1235 if isinstance(files, bytes):
1236 files = (files, )
1237
1238 for f in files:
1239 try:
1240 st = cephfs.lstat(f)
1241
1242 if stat.S_ISDIR(st.st_mode):
1243 dusage = int(cephfs.getxattr(f,
1244 'ceph.dir.rbytes').decode('utf-8'))
1245 else:
1246 dusage = st.st_size
1247
1248 # print path in local context
1249 f = os.path.normpath(f)
1250 if f[0] is ord('/'):
1251 f = b'.' + f
1252 poutput('{:10s} {}'.format(humansize(dusage),
e306af50 1253 f.decode('utf-8')))
9f95a23c 1254 except libcephfs.Error as e:
f67539c2 1255 set_exit_code_msg(msg=e)
9f95a23c
TL
1256 continue
1257
1258 for path in args.paths:
11fdf7f2 1259 if args.r:
9f95a23c 1260 print_disk_usage(sorted(set(dirwalk(path)).union({path})))
11fdf7f2 1261 else:
9f95a23c 1262 print_disk_usage(path)
81eedcae
TL
1263
1264 quota_parser = argparse.ArgumentParser(
1265 description='Quota management for a Directory')
1266 quota_parser.add_argument('op', choices=['get', 'set'],
1267 help='Quota operation type.')
eafe8130
TL
1268 quota_parser.add_argument('path', type=str, action=path_to_bytes,
1269 help='Name of the directory.')
81eedcae
TL
1270 quota_parser.add_argument('--max_bytes', type=int, default=-1, nargs='?',
1271 help='Max cumulative size of the data under '
1272 'this directory.')
1273 quota_parser.add_argument('--max_files', type=int, default=-1, nargs='?',
1274 help='Total number of files under this '
1275 'directory tree.')
1276
1277 @with_argparser(quota_parser)
1278 def do_quota(self, args):
1279 """
1280 Quota management.
1281 """
1282 if not is_dir_exists(args.path):
f67539c2
TL
1283 set_exit_code_msg(errno.ENOENT, 'error: no such directory {}'.format(
1284 args.path.decode('utf-8')))
81eedcae
TL
1285 return
1286
1287 if args.op == 'set':
1288 if (args.max_bytes == -1) and (args.max_files == -1):
f67539c2
TL
1289 set_exit_code_msg(errno.EINVAL, 'please specify either '
1290 '--max_bytes or --max_files or both')
81eedcae
TL
1291 return
1292
1293 if args.max_bytes >= 0:
1294 max_bytes = to_bytes(str(args.max_bytes))
1295 try:
1296 cephfs.setxattr(args.path, 'ceph.quota.max_bytes',
9f95a23c
TL
1297 max_bytes, os.XATTR_CREATE)
1298 poutput('max_bytes set to %d' % args.max_bytes)
1299 except libcephfs.Error as e:
81eedcae 1300 cephfs.setxattr(args.path, 'ceph.quota.max_bytes',
9f95a23c 1301 max_bytes, os.XATTR_REPLACE)
f67539c2
TL
1302 set_exit_code_msg(e.get_error_code(), 'max_bytes reset to '
1303 f'{args.max_bytes}')
81eedcae
TL
1304
1305 if args.max_files >= 0:
1306 max_files = to_bytes(str(args.max_files))
1307 try:
1308 cephfs.setxattr(args.path, 'ceph.quota.max_files',
9f95a23c
TL
1309 max_files, os.XATTR_CREATE)
1310 poutput('max_files set to %d' % args.max_files)
1311 except libcephfs.Error as e:
81eedcae 1312 cephfs.setxattr(args.path, 'ceph.quota.max_files',
9f95a23c 1313 max_files, os.XATTR_REPLACE)
f67539c2
TL
1314 set_exit_code_msg(e.get_error_code(), 'max_files reset to '
1315 f'{args.max_files}')
81eedcae
TL
1316 elif args.op == 'get':
1317 max_bytes = '0'
1318 max_files = '0'
1319 try:
9f95a23c
TL
1320 max_bytes = cephfs.getxattr(args.path, 'ceph.quota.max_bytes')
1321 poutput('max_bytes: {}'.format(max_bytes.decode('utf-8')))
1322 except libcephfs.Error as e:
f67539c2 1323 set_exit_code_msg(e.get_error_code(), 'max_bytes is not set')
81eedcae
TL
1324
1325 try:
9f95a23c
TL
1326 max_files = cephfs.getxattr(args.path, 'ceph.quota.max_files')
1327 poutput('max_files: {}'.format(max_files.decode('utf-8')))
1328 except libcephfs.Error as e:
f67539c2 1329 set_exit_code_msg(e.get_error_code(), 'max_files is not set')
9f95a23c
TL
1330
1331 snap_parser = argparse.ArgumentParser(description='Snapshot Management')
1332 snap_parser.add_argument('op', type=str,
1333 help='Snapshot operation: create or delete')
1334 snap_parser.add_argument('name', type=str, action=path_to_bytes,
1335 help='Name of snapshot')
1336 snap_parser.add_argument('dir', type=str, action=path_to_bytes,
1337 help='Directory for which snapshot '
1338 'needs to be created or deleted')
1339
1340 @with_argparser(snap_parser)
1341 def do_snap(self, args):
1342 """
1343 Snapshot management for the volume
1344 """
1345 # setting self.colors to None turns off colorizing and
1346 # perror emits plain text
1347 self.colors = None
1348
1349 snapdir = '.snap'
1350 conf_snapdir = cephfs.conf_get('client_snapdir')
1351 if conf_snapdir is not None:
1352 snapdir = conf_snapdir
1353 snapdir = to_bytes(snapdir)
1354 if args.op == 'create':
1355 try:
1356 if is_dir_exists(args.dir):
1357 cephfs.mkdir(os.path.join(args.dir, snapdir, args.name), 0o755)
1358 else:
f67539c2
TL
1359 set_exit_code_msg(errno.ENOENT, "'{}': no such directory".format(
1360 args.dir.decode('utf-8')))
9f95a23c 1361 except libcephfs.Error as e:
f67539c2
TL
1362 set_exit_code_msg(e.get_error_code(),
1363 "snapshot '{}' already exists".format(
1364 args.name.decode('utf-8')))
9f95a23c
TL
1365 elif args.op == 'delete':
1366 snap_dir = os.path.join(args.dir, snapdir, args.name)
1367 try:
1368 if is_dir_exists(snap_dir):
1369 newargs = argparse.Namespace(paths=[snap_dir], parent=False)
1370 self.do_rmdir_helper(newargs)
1371 else:
f67539c2 1372 set_exit_code_msg(errno.ENOENT, "'{}': no such snapshot".format(
9f95a23c 1373 args.name.decode('utf-8')))
9f95a23c 1374 except libcephfs.Error as e:
f67539c2
TL
1375 set_exit_code_msg(e.get_error_code(), "error while deleting "
1376 "'{}'".format(snap_dir.decode('utf-8')))
9f95a23c 1377 else:
f67539c2
TL
1378 set_exit_code_msg(errno.EINVAL, "snapshot can only be created or "
1379 "deleted; check - help snap")
11fdf7f2
TL
1380
1381 def do_help(self, line):
1382 """
1383 Get details about a command.
1384 Usage: help <cmd> - for a specific command
1385 help all - for all the commands
1386 """
1387 if line == 'all':
1388 for k in dir(self):
1389 if k.startswith('do_'):
9f95a23c 1390 poutput('-' * 80)
11fdf7f2
TL
1391 super().do_help(k[3:])
1392 return
1393 parser = self.create_argparser(line)
1394 if parser:
1395 parser.print_help()
1396 else:
1397 super().do_help(line)
1398
81eedcae
TL
1399 def complete_stat(self, text, line, begidx, endidx):
1400 """
1401 auto complete of file name.
1402 """
1403 return self.complete_filenames(text, line, begidx, endidx)
1404
1405 stat_parser = argparse.ArgumentParser(
9f95a23c 1406 description='Display file or file system status')
81eedcae 1407 stat_parser.add_argument('paths', type=str, help='file paths',
eafe8130 1408 action=path_to_bytes, nargs='+')
81eedcae
TL
1409
1410 @with_argparser(stat_parser)
1411 def do_stat(self, args):
1412 """
1413 Display file or file system status
1414 """
1415 for path in args.paths:
1416 try:
1417 stat = cephfs.stat(path)
1418 atime = stat.st_atime.isoformat(' ')
1419 mtime = stat.st_mtime.isoformat(' ')
1420 ctime = stat.st_mtime.isoformat(' ')
1421
9f95a23c
TL
1422 poutput("File: {}\nSize: {:d}\nBlocks: {:d}\nIO Block: {:d}\n"
1423 "Device: {:d}\tInode: {:d}\tLinks: {:d}\nPermission: "
e306af50 1424 "{:o}/{}\tUid: {:d}\tGid: {:d}\nAccess: {}\nModify: "
9f95a23c 1425 "{}\nChange: {}".format(path.decode('utf-8'),
e306af50
TL
1426 stat.st_size, stat.st_blocks,
1427 stat.st_blksize, stat.st_dev,
1428 stat.st_ino, stat.st_nlink,
1429 stat.st_mode,
1430 mode_notation(stat.st_mode),
1431 stat.st_uid, stat.st_gid, atime,
1432 mtime, ctime))
9f95a23c 1433 except libcephfs.Error as e:
f67539c2 1434 set_exit_code_msg(msg=e)
9f95a23c
TL
1435
1436 setxattr_parser = argparse.ArgumentParser(
1437 description='Set extended attribute for a file')
1438 setxattr_parser.add_argument('path', type=str, action=path_to_bytes, help='Name of the file')
1439 setxattr_parser.add_argument('name', type=str, help='Extended attribute name')
1440 setxattr_parser.add_argument('value', type=str, help='Extended attribute value')
1441
1442 @with_argparser(setxattr_parser)
1443 def do_setxattr(self, args):
1444 """
1445 Set extended attribute for a file
1446 """
1447 val_bytes = to_bytes(args.value)
1448 name_bytes = to_bytes(args.name)
1449 try:
1450 cephfs.setxattr(args.path, name_bytes, val_bytes, os.XATTR_CREATE)
1451 poutput('{} is successfully set to {}'.format(args.name, args.value))
1452 except libcephfs.ObjectExists:
1453 cephfs.setxattr(args.path, name_bytes, val_bytes, os.XATTR_REPLACE)
1454 poutput('{} is successfully reset to {}'.format(args.name, args.value))
1455 except libcephfs.Error as e:
f67539c2 1456 set_exit_code_msg(msg=e)
9f95a23c
TL
1457
1458 getxattr_parser = argparse.ArgumentParser(
1459 description='Get extended attribute set for a file')
1460 getxattr_parser.add_argument('path', type=str, action=path_to_bytes,
1461 help='Name of the file')
1462 getxattr_parser.add_argument('name', type=str, help='Extended attribute name')
1463
1464 @with_argparser(getxattr_parser)
1465 def do_getxattr(self, args):
1466 """
1467 Get extended attribute for a file
1468 """
1469 try:
1470 poutput('{}'.format(cephfs.getxattr(args.path,
1471 to_bytes(args.name)).decode('utf-8')))
1472 except libcephfs.Error as e:
f67539c2 1473 set_exit_code_msg(msg=e)
9f95a23c
TL
1474
1475 listxattr_parser = argparse.ArgumentParser(
1476 description='List extended attributes set for a file')
1477 listxattr_parser.add_argument('path', type=str, action=path_to_bytes,
1478 help='Name of the file')
1479
1480 @with_argparser(listxattr_parser)
1481 def do_listxattr(self, args):
1482 """
1483 List extended attributes for a file
1484 """
1485 try:
1486 size, xattr_list = cephfs.listxattr(args.path)
1487 if size > 0:
1488 poutput('{}'.format(xattr_list.replace(b'\x00', b' ').decode('utf-8')))
1489 else:
1490 poutput('No extended attribute is set')
1491 except libcephfs.Error as e:
f67539c2 1492 set_exit_code_msg(msg=e)
9f95a23c
TL
1493
1494
1495#######################################################
1496#
1497# Following are methods that get cephfs-shell started.
1498#
1499#####################################################
1500
20effc67 1501def setup_cephfs(args):
9f95a23c
TL
1502 """
1503 Mounting a cephfs
1504 """
1505 global cephfs
1506 try:
f67539c2 1507 cephfs = libcephfs.LibCephFS(conffile='')
20effc67 1508 cephfs.mount(filesystem_name=args.fs)
f67539c2 1509 except libcephfs.ObjectNotFound as e:
9f95a23c 1510 print('couldn\'t find ceph configuration not found')
f67539c2 1511 sys.exit(e.get_error_code())
9f95a23c
TL
1512 except libcephfs.Error as e:
1513 print(e)
f67539c2 1514 sys.exit(e.get_error_code())
9f95a23c 1515
e306af50 1516
f67539c2
TL
1517def str_to_bool(val):
1518 """
1519 Return corresponding bool values for strings like 'true' or 'false'.
1520 """
9f95a23c
TL
1521 if not isinstance(val, str):
1522 return val
1523
1524 val = val.replace('\n', '')
1525 if val.lower() in ['true', 'yes']:
1526 return True
1527 elif val.lower() in ['false', 'no']:
1528 return False
1529 else:
1530 return val
81eedcae 1531
e306af50 1532
f67539c2
TL
1533def read_shell_conf(shell, shell_conf_file):
1534 import configparser
11fdf7f2 1535
f67539c2
TL
1536 sec = 'cephfs-shell'
1537 opts = []
1538 if LooseVersion(cmd2_version) >= LooseVersion("0.10.0"):
1539 for attr in shell.settables.keys():
1540 opts.append(attr)
1541 else:
1542 if LooseVersion(cmd2_version) <= LooseVersion("0.9.13"):
1543 # hardcoding options for 0.7.9 because -
1544 # 1. we use cmd2 v0.7.9 with teuthology and
1545 # 2. there's no way distinguish between a shell setting and shell
1546 # object attribute until v0.10.0
1547 opts = ['abbrev', 'autorun_on_edit', 'colors',
1548 'continuation_prompt', 'debug', 'echo', 'editor',
1549 'feedback_to_output', 'locals_in_py', 'prompt', 'quiet',
1550 'timing']
1551 elif LooseVersion(cmd2_version) >= LooseVersion("0.9.23"):
1552 opts.append('allow_style')
1553 # no equivalent option was defined by cmd2.
1554 else:
1555 pass
e306af50 1556
f67539c2
TL
1557 # default and only section in our conf file.
1558 cp = configparser.ConfigParser(default_section=sec, strict=False)
1559 cp.read(shell_conf_file)
1560 for opt in opts:
1561 if cp.has_option(sec, opt):
1562 setattr(shell, opt, str_to_bool(cp.get(sec, opt)))
1563
1564
1565def get_shell_conffile_path(arg_conf=''):
1566 conf_filename = 'cephfs-shell.conf'
1567 env_var = 'CEPHFS_SHELL_CONF'
1568
1569 arg_conf = '' if not arg_conf else arg_conf
1570 home_dir_conf = os.path.expanduser('~/.' + conf_filename)
1571 env_conf = os.environ[env_var] if env_var in os.environ else ''
1572
1573 # here's the priority by which conf gets read.
1574 for path in (arg_conf, env_conf, home_dir_conf):
1575 if os.path.isfile(path):
1576 return path
1577 else:
1578 return ''
9f95a23c 1579
9f95a23c 1580
f67539c2 1581def manage_args():
11fdf7f2 1582 main_parser = argparse.ArgumentParser(description='')
f67539c2
TL
1583 main_parser.add_argument('-b', '--batch', action='store',
1584 help='Path to CephFS shell script/batch file'
1585 'containing CephFS shell commands',
1586 type=str)
20effc67
TL
1587 main_parser.add_argument('-c', '--config', action='store',
1588 help='Path to Ceph configuration file.',
1589 type=str)
1590 main_parser.add_argument('-f', '--fs', action='store',
1591 help='Name of filesystem to mount.',
1592 type=str)
11fdf7f2 1593 main_parser.add_argument('-t', '--test', action='store',
81eedcae
TL
1594 help='Test against transcript(s) in FILE',
1595 nargs='+')
f67539c2
TL
1596 main_parser.add_argument('commands', nargs='*', help='Comma delimited '
1597 'commands. The shell executes the given command '
1598 'and quits immediately with the return value of '
1599 'command. In case no commands are provided, the '
1600 'shell is launched.', default=[])
1601
11fdf7f2 1602 args = main_parser.parse_args()
f67539c2
TL
1603 args.exe_and_quit = False # Execute and quit, don't launch the shell.
1604
11fdf7f2 1605 if args.batch:
9f95a23c
TL
1606 if LooseVersion(cmd2_version) <= LooseVersion("0.9.13"):
1607 args.commands = ['load ' + args.batch, ',quit']
1608 else:
e306af50 1609 args.commands = ['run_script ' + args.batch, ',quit']
11fdf7f2 1610 if args.test:
9f95a23c 1611 args.commands.extend(['-t,'] + [arg + ',' for arg in args.test])
f67539c2
TL
1612 if not args.batch and len(args.commands) > 0:
1613 args.exe_and_quit = True
9f95a23c 1614
f67539c2
TL
1615 manage_sys_argv(args)
1616
1617 return args
1618
1619
1620def manage_sys_argv(args):
1621 exe = sys.argv[0]
11fdf7f2
TL
1622 sys.argv.clear()
1623 sys.argv.append(exe)
1624 sys.argv.extend([i.strip() for i in ' '.join(args.commands).split(',')])
9f95a23c 1625
20effc67 1626 setup_cephfs(args)
f67539c2
TL
1627
1628
1629def execute_cmd_args(args):
1630 """
1631 Launch a shell session if no arguments were passed, else just execute
1632 the given argument as a shell command and exit the shell session
1633 immediately at (last) command's termination with the (last) command's
1634 return value.
1635 """
1636 if not args.exe_and_quit:
1637 return shell.cmdloop()
1638 return execute_cmds_and_quit(args)
1639
1640
1641def execute_cmds_and_quit(args):
1642 """
1643 Multiple commands might be passed separated by commas, feed onecmd()
1644 one command at a time.
1645 """
1646 # do_* methods triggered by cephfs-shell commands return None when they
1647 # complete running successfully. Until 0.9.6, shell.onecmd() returned this
1648 # value to indicate whether the execution of the commands should stop, but
1649 # since 0.9.7 it returns the return value of do_* methods only if it's
1650 # not None. When it is None it returns False instead of None.
1651 if LooseVersion(cmd2_version) <= LooseVersion("0.9.6"):
1652 stop_exec_val = None
1653 else:
1654 stop_exec_val = False
1655
1656 args_to_onecmd = ''
1657 if len(args.commands) <= 1:
1658 args.commands = args.commands[0].split(' ')
1659 for cmdarg in args.commands:
1660 if ',' in cmdarg:
1661 args_to_onecmd += ' ' + cmdarg[0:-1]
1662 onecmd_retval = shell.onecmd(args_to_onecmd)
1663 # if the curent command failed, let's abort the execution of
1664 # series of commands passed.
1665 if onecmd_retval is not stop_exec_val:
1666 return onecmd_retval
1667 if shell.exit_code != 0:
1668 return shell.exit_code
1669
1670 args_to_onecmd = ''
1671 continue
1672
1673 args_to_onecmd += ' ' + cmdarg
1674 return shell.onecmd(args_to_onecmd)
1675
1676
1677if __name__ == '__main__':
1678 args = manage_args()
1679
81eedcae 1680 shell = CephFSShell()
f67539c2
TL
1681 # TODO: perhaps, we should add an option to pass ceph.conf?
1682 read_shell_conf(shell, get_shell_conffile_path(args.config))
1683 # XXX: setting shell.exit_code to zero so that in case there are no errors
1684 # and exceptions, it is not set by any method or function of cephfs-shell
1685 # and return values from shell.cmdloop() or shell.onecmd() is not an
1686 # integer, we can treat it as the return value of cephfs-shell.
9f95a23c 1687 shell.exit_code = 0
9f95a23c 1688
f67539c2
TL
1689 retval = execute_cmd_args(args)
1690 sys.exit(retval if retval else shell.exit_code)