8 import cephfs
as libcephfs
20 from cmd2
import __version__
as cmd2_version
21 from distutils
.version
import LooseVersion
23 if sys
.version_info
.major
< 3:
24 raise RuntimeError("cephfs-shell is only compatible with python3")
27 from cmd2
import with_argparser
29 def with_argparser(argparser
):
32 def argparser_decorator(func
):
33 @functools.wraps(func
)
34 def wrapper(thiz
, cmdline
):
35 if isinstance(cmdline
, list):
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
]
43 args
= argparser
.parse_args(arglist
)
46 # argparse exits at seeing bad arguments
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
__
56 return argparser_decorator
59 cephfs
= None # holds CephFS Python bindings
60 shell
= None # holds instance of class CephFSShell
61 exit_codes
= {'Misc': 1,
62 'KeyboardInterrupt': 2,
73 errno
.EWOULDBLOCK
: 13,
79 errno
.ECONNABORTED
: 19,
80 errno
.ECONNREFUSED
: 20,
85 #########################################################################
87 # Following are methods are generically useful through class CephFSShell
89 #######################################################################
92 def poutput(s
, end
='\n'):
93 shell
.poutput(s
, end
=end
)
96 def perror(msg
, **kwargs
):
97 shell
.perror(msg
, **kwargs
)
100 def set_exit_code_msg(errcode
='Misc', msg
=''):
102 Set exit code and print error message
104 if isinstance(msg
, libcephfs
.Error
):
105 shell
.exit_code
= exit_codes
[msg
.get_error_code()]
107 shell
.exit_code
= exit_codes
[errcode
]
112 def mode_notation(mode
):
115 permission_bits
= {'0': '---',
123 mode
= str(oct(mode
))
127 elif mode
[2:4] == '12':
130 notation
+= permission_bits
[i
]
134 def get_chunks(file_size
):
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
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
):
150 elif isinstance(param
, str):
151 return bytes(param
, encoding
='utf-8')
152 elif isinstance(param
, list):
153 return [i
.encode('utf-8') if isinstance(i
, str) else to_bytes(i
) for
155 elif isinstance(param
, int) or isinstance(param
, float):
156 return str(param
).encode('utf-8')
161 def ls(path
, opts
=''):
162 # opts tries to be like /bin/ls opts
163 almost_all
= 'A' in opts
165 with cephfs
.opendir(path
) as d
:
167 dent
= cephfs
.readdir(d
)
170 elif almost_all
and dent
.d_name
in (b
'.', b
'..'):
173 except libcephfs
.ObjectNotFound
as e
:
174 set_exit_code_msg(msg
=e
)
177 def glob(path
, pattern
):
179 parent_dir
= os
.path
.dirname(path
)
180 if parent_dir
== b
'':
182 if path
== b
'/' or is_dir_exists(os
.path
.basename(path
), parent_dir
):
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
))
189 def locate_file(name
, case_sensitive
=True):
190 dir_list
= sorted(set(dirwalk(cephfs
.getcwd())))
191 if not case_sensitive
:
192 return [dname
for dname
in dir_list
if name
.lower() in dname
.lower()]
194 return [dname
for dname
in dir_list
if name
in dname
]
197 def get_all_possible_paths(pattern
):
198 complete_pattern
= pattern
[:]
200 is_rel_path
= not os
.path
.isabs(pattern
)
202 dir_
= cephfs
.getcwd()
205 pattern
= pattern
[1:]
206 patterns
= pattern
.split(b
'/')
207 paths
.extend(glob(dir_
, patterns
[0]))
209 for pattern
in patterns
:
211 paths
.extend(glob(path
, pattern
))
213 complete_pattern
= os
.path
.join(cephfs
.getcwd(), complete_pattern
)
214 return [path
for path
in paths
if fnmatch
.fnmatch(path
, complete_pattern
)]
217 suffixes
= ['B', 'K', 'M', 'G', 'T', 'P']
220 def humansize(nbytes
):
222 while nbytes
>= 1024 and i
< len(suffixes
) - 1:
225 nbytes
= math
.ceil(nbytes
)
226 f
= ('%d' % nbytes
).rstrip('.')
227 return '%s%s' % (f
, suffixes
[i
])
230 def style_listing(path
, is_dir
, is_symlink
, ls_long
=False):
231 if not (is_dir
or is_symlink
):
233 pretty
= colorama
.Style
.BRIGHT
235 pretty
+= colorama
.Fore
.CYAN
+ path
238 pretty
+= ' -> ' + cephfs
.readlink(path
, size
=255).decode('utf-8')
240 pretty
+= colorama
.Fore
.BLUE
+ path
+ '/'
241 pretty
+= colorama
.Style
.RESET_ALL
245 def 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)
249 sizefmt
= '\t {:10s}'.format(humansize(info
.st_size
))
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}'
258 Returns the word length, minus any color codes.
260 if word
[0] == '\x1b':
265 def is_dir_exists(path
, dir_
=b
''):
266 path_to_stat
= os
.path
.join(dir_
, path
)
268 return ((cephfs
.stat(path_to_stat
).st_mode
& 0o0040000) != 0)
269 except libcephfs
.Error
:
273 def is_file_exists(path
, dir_
=b
''):
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
:
281 def print_list(words
, termwidth
=79):
284 words
= [word
.decode('utf-8') if isinstance(word
, bytes
) else word
for word
in words
]
285 width
= max([word_len(word
) for word
in words
]) + 2
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
):
293 if word
[0] == '\x1b':
294 print_width
= print_width
+ 10
296 poutput('%-*s' % (print_width
, words
[i
]),
297 end
='\n' if i
+ nrows
>= nwords
else '')
300 def copy_from_local(local_path
, remote_path
):
304 convert_to_bytes
= False
305 if local_path
== b
'-':
307 convert_to_bytes
= True
310 file_
= open(local_path
, 'rb')
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')))
317 fd
= cephfs
.open(remote_path
, 'w', 0o666)
318 except libcephfs
.Error
as e
:
319 set_exit_code_msg(msg
=e
)
323 data
= file_
.read(65536)
324 if not data
or len(data
) == 0:
327 data
= to_bytes(data
)
328 wrote
= cephfs
.write(fd
, data
, progress
)
338 def copy_to_local(remote_path
, local_path
):
340 if local_path
!= b
'-':
341 local_dir
= os
.path
.dirname(local_path
)
342 dir_list
= remote_path
.rsplit(b
'/', 1)
343 if not os
.path
.exists(local_dir
):
344 os
.makedirs(local_dir
)
345 if len(dir_list
) > 2 and dir_list
[1] == b
'':
347 fd
= open(local_path
, 'wb+')
348 file_
= cephfs
.open(remote_path
, 'r')
349 file_size
= cephfs
.stat(remote_path
).st_size
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
)
359 poutput(file_chunk
.decode('utf-8'))
367 walk a directory tree, using a generator
369 path
= os
.path
.normpath(path
)
370 for item
in ls(path
, opts
='A'):
371 fullpath
= os
.path
.join(path
, item
.d_name
)
372 src_path
= fullpath
.rsplit(b
'/', 1)[0]
374 yield os
.path
.normpath(fullpath
)
375 if is_dir_exists(item
.d_name
, src_path
):
376 for x
in dirwalk(fullpath
):
380 ##################################################################
382 # Following methods are implementation for CephFS Shell commands
384 #################################################################
386 class CephFSShell(Cmd
):
390 self
.working_dir
= cephfs
.getcwd().decode('utf-8')
392 self
.interactive
= False
395 def default(self
, line
):
396 perror('Unrecognized command')
398 def set_prompt(self
):
399 self
.prompt
= ('\033[01;33mCephFS:~' + colorama
.Fore
.LIGHTCYAN_EX
400 + self
.working_dir
+ colorama
.Style
.RESET_ALL
401 + '\033[01;33m>>>\033[00m ')
403 def create_argparser(self
, command
):
405 argparse_args
= getattr(self
, 'argparse_' + command
)
406 except AttributeError:
410 self
, 'do_' + command
).__doc
__.expandtabs().splitlines()
412 blank_idx
= doc_lines
.index('')
413 usage
= doc_lines
[:blank_idx
]
414 description
= doc_lines
[blank_idx
+ 1:]
418 parser
= argparse
.ArgumentParser(
420 usage
='\n'.join(usage
),
421 description
='\n'.join(description
),
422 formatter_class
=argparse
.ArgumentDefaultsHelpFormatter
424 for args
, kwargs
in argparse_args
:
425 parser
.add_argument(*args
, **kwargs
)
428 def complete_filenames(self
, text
, line
, begidx
, endidx
):
430 completions
= [x
.d_name
.decode('utf-8') + '/' * int(x
.is_dir())
431 for x
in ls(b
".", opts
='A')]
433 if text
.count('/') > 0:
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])]
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
)]
444 if len(completions
) == 1 and completions
[0][-1] == '/':
445 dir_
, file_
= completions
[0].rsplit('/', 1)
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_
)])
450 return self
.delimiter_complete(text
, line
, begidx
, endidx
, completions
, '/')
453 def onecmd(self
, line
, **kwargs
):
458 res
= Cmd
.onecmd(self
, line
, **kwargs
)
462 except ConnectionError
as e
:
463 set_exit_code_msg(e
.errno
, f
'***\n{e}')
464 except KeyboardInterrupt:
465 set_exit_code_msg('KeyboardInterrupt', 'Command aborted')
466 except (libcephfs
.Error
, Exception) as e
:
468 traceback
.print_exc(file=sys
.stdout
)
469 set_exit_code_msg(msg
=e
)
471 class path_to_bytes(argparse
.Action
):
472 def __call__(self
, parser
, namespace
, values
, option_string
=None):
473 values
= to_bytes(values
)
474 setattr(namespace
, self
.dest
, values
)
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
)
482 values
= cephfs
.getcwd()
486 values
[values
.index(i
)] = cephfs
.getcwd()
488 setattr(namespace
, self
.dest
, values
)
490 def complete_mkdir(self
, text
, line
, begidx
, endidx
):
492 auto complete of file name.
494 return self
.complete_filenames(text
, line
, begidx
, endidx
)
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
)
502 def __call__(self
, parser
, namespace
, values
, option_string
=None):
506 o_mode
= int(values
, base
=8)
508 res
= re
.match('((u?g?o?)|(a?))(=)(r?w?x?)', values
)
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?)" %
515 # we are supporting only assignment of mode and not + or -
516 # as is generally available with the chmod command
518 # >>> res = re.match('((u?g?o?)|(a?))(=)(r?w?x?)', 'go=')
520 # ('go', 'go', None, '=', '')
524 parser
.error("need assignment operator between user "
525 "and mode specifiers")
527 parser
.error("invalid mode: %s\n"
528 "mode must be combination of: r | w | x" %
548 o_mode |
= (t_mode
<< 6)
550 o_mode |
= (t_mode
<< 3)
555 parser
.error("invalid mode: %s\n"
556 "mode cannot be negative" % values
)
558 parser
.error("invalid mode: %s\n"
559 "mode cannot be greater than octal 0777" % values
)
561 setattr(namespace
, self
.dest
, str(oct(o_mode
)))
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,
566 action
=path_to_bytes
,
568 help='Name of new_directory.',
570 mkdir_parser
.add_argument('-m', '--mode', type=str,
572 help='Sets the access mode for the new directory.')
573 mkdir_parser
.add_argument('-p', '--parent', action
='store_true',
574 help='Create parent directories as necessary. '
575 'When this option is specified, no error is'
576 'reported if a directory already exists.')
578 @with_argparser(mkdir_parser
)
579 def do_mkdir(self
, args
):
583 for path
in args
.dirs
:
585 permission
= int(args
.mode
, 8)
589 cephfs
.mkdirs(path
, permission
)
592 cephfs
.mkdir(path
, permission
)
593 except libcephfs
.Error
as e
:
596 def complete_put(self
, text
, line
, begidx
, endidx
):
598 auto complete of file name.
600 index_dict
= {1: self
.path_complete
}
601 return self
.index_based_complete(text
, line
, begidx
, endidx
, index_dict
)
603 put_parser
= argparse
.ArgumentParser(
604 description
='Copy a file/directory to Ceph File System from Local File System.')
605 put_parser
.add_argument('local_path', type=str, action
=path_to_bytes
,
606 help='Path of the file in the local system')
607 put_parser
.add_argument('remote_path', type=str, action
=path_to_bytes
,
608 help='Path of the file in the remote system')
609 put_parser
.add_argument('-f', '--force', action
='store_true',
610 help='Overwrites the destination if it already exists.')
612 @with_argparser(put_parser
)
613 def do_put(self
, args
):
615 Copy a local file/directory to CephFS.
617 if args
.local_path
!= b
'-' and not os
.path
.isfile(args
.local_path
) \
618 and not os
.path
.isdir(args
.local_path
):
619 set_exit_code_msg(errno
.ENOENT
,
621 f
"{args.local_path.decode('utf-8')}: "
622 f
"No such file or directory")
625 if (is_file_exists(args
.remote_path
) or is_dir_exists(
626 args
.remote_path
)) and not args
.force
:
627 set_exit_code_msg(msg
=f
"error: file/directory "
628 f
"{args.remote_path.decode('utf-8')} "
629 f
"exists, use --force to overwrite")
632 root_src_dir
= args
.local_path
633 root_dst_dir
= args
.remote_path
634 if args
.local_path
== b
'.' or args
.local_path
== b
'./':
635 root_src_dir
= os
.getcwdb()
636 elif len(args
.local_path
.rsplit(b
'/', 1)) < 2:
637 root_src_dir
= os
.path
.join(os
.getcwdb(), args
.local_path
)
639 p
= args
.local_path
.split(b
'/')
641 root_src_dir
= os
.getcwdb()
644 root_src_dir
+= b
'/' + p
.pop(0)
646 if root_dst_dir
== b
'.':
647 if args
.local_path
!= b
'-':
648 root_dst_dir
= root_src_dir
.rsplit(b
'/', 1)[1]
649 if root_dst_dir
== b
'':
650 root_dst_dir
= root_src_dir
.rsplit(b
'/', 1)[0]
651 a
= root_dst_dir
.rsplit(b
'/', 1)
657 set_exit_code_msg(errno
.EINVAL
, 'error: no filename specified '
661 if root_dst_dir
[-1] != b
'/':
664 if args
.local_path
== b
'-' or os
.path
.isfile(root_src_dir
):
665 if args
.local_path
== b
'-':
667 copy_from_local(root_src_dir
, root_dst_dir
)
669 for src_dir
, dirs
, files
in os
.walk(root_src_dir
):
670 if isinstance(src_dir
, str):
671 src_dir
= to_bytes(src_dir
)
672 dst_dir
= src_dir
.replace(root_src_dir
, root_dst_dir
, 1)
673 dst_dir
= re
.sub(rb
'\/+', b
'/', cephfs
.getcwd()
675 if args
.force
and dst_dir
!= b
'/' and not is_dir_exists(
676 dst_dir
[:-1]) and not locate_file(dst_dir
):
678 cephfs
.mkdirs(dst_dir
, 0o777)
679 except libcephfs
.Error
:
681 if (not args
.force
) and dst_dir
!= b
'/' and not is_dir_exists(
682 dst_dir
) and not os
.path
.isfile(root_src_dir
):
684 cephfs
.mkdirs(dst_dir
, 0o777)
685 except libcephfs
.Error
:
686 # TODO: perhaps, set retval to 1?
690 dir_name
= os
.path
.join(dst_dir
, dir_
)
691 if not is_dir_exists(dir_name
):
693 cephfs
.mkdirs(dir_name
, 0o777)
694 except libcephfs
.Error
:
695 # TODO: perhaps, set retval to 1?
699 src_file
= os
.path
.join(src_dir
, file_
)
700 dst_file
= re
.sub(rb
'\/+', b
'/', b
'/' + dst_dir
+ b
'/' + file_
)
701 if (not args
.force
) and is_file_exists(dst_file
):
703 copy_from_local(src_file
, os
.path
.join(cephfs
.getcwd(),
706 def complete_get(self
, text
, line
, begidx
, endidx
):
708 auto complete of file name.
710 return self
.complete_filenames(text
, line
, begidx
, endidx
)
712 get_parser
= argparse
.ArgumentParser(
713 description
='Copy a file from Ceph File System to Local Directory.')
714 get_parser
.add_argument('remote_path', type=str, action
=path_to_bytes
,
715 help='Path of the file in the remote system')
716 get_parser
.add_argument('local_path', type=str, action
=path_to_bytes
,
717 help='Path of the file in the local system')
718 get_parser
.add_argument('-f', '--force', action
='store_true',
719 help='Overwrites the destination if it already exists.')
721 @with_argparser(get_parser
)
722 def do_get(self
, args
):
724 Copy a file/directory from CephFS to given path.
726 if not is_file_exists(args
.remote_path
) and not \
727 is_dir_exists(args
.remote_path
):
728 set_exit_code_msg(errno
.ENOENT
, "error: no file/directory"
729 " found at specified remote "
732 if (os
.path
.isfile(args
.local_path
) or os
.path
.isdir(
733 args
.local_path
)) and not args
.force
:
734 set_exit_code_msg(msg
=f
"error: file/directory "
735 f
"{args.local_path.decode('utf-8')}"
736 f
" already exists, use --force to "
739 root_src_dir
= args
.remote_path
740 root_dst_dir
= args
.local_path
741 fname
= root_src_dir
.rsplit(b
'/', 1)
742 if args
.local_path
== b
'.':
743 root_dst_dir
= os
.getcwdb()
744 if args
.remote_path
== b
'.':
745 root_src_dir
= cephfs
.getcwd()
746 if args
.local_path
== b
'-':
747 if args
.remote_path
== b
'.' or args
.remote_path
== b
'./':
748 set_exit_code_msg(errno
.EINVAL
, 'error: no remote file name specified')
750 copy_to_local(root_src_dir
, b
'-')
751 elif is_file_exists(args
.remote_path
):
752 copy_to_local(root_src_dir
, root_dst_dir
)
753 elif b
'/' in root_src_dir
and is_file_exists(fname
[1], fname
[0]):
754 copy_to_local(root_src_dir
, root_dst_dir
)
756 files
= list(reversed(sorted(dirwalk(root_src_dir
))))
758 dst_dirpath
, dst_file
= file_
.rsplit(b
'/', 1)
759 if dst_dirpath
in files
:
760 files
.remove(dst_dirpath
)
761 dst_path
= os
.path
.join(root_dst_dir
, dst_dirpath
, dst_file
)
762 dst_path
= os
.path
.normpath(dst_path
)
763 if is_dir_exists(file_
):
765 os
.makedirs(dst_path
)
769 copy_to_local(file_
, dst_path
)
773 def complete_ls(self
, text
, line
, begidx
, endidx
):
775 auto complete of file name.
777 return self
.complete_filenames(text
, line
, begidx
, endidx
)
779 ls_parser
= argparse
.ArgumentParser(
780 description
='Copy a file from Ceph File System from Local Directory.')
781 ls_parser
.add_argument('-l', '--long', action
='store_true',
782 help='Detailed list of items in the directory.')
783 ls_parser
.add_argument('-r', '--reverse', action
='store_true',
784 help='Reverse order of listing items in the directory.')
785 ls_parser
.add_argument('-H', action
='store_true', help='Human Readable')
786 ls_parser
.add_argument('-a', '--all', action
='store_true',
787 help='Do not Ignore entries starting with .')
788 ls_parser
.add_argument('-S', action
='store_true', help='Sort by file_size')
789 ls_parser
.add_argument('paths', help='Name of Directories',
790 action
=path_to_bytes
, nargs
='*', default
=['.'])
792 @with_argparser(ls_parser
)
793 def do_ls(self
, args
):
795 List all the files and directories in the current working directory
802 if path
.count(b
'*') > 0:
803 all_items
= get_all_possible_paths(path
)
804 if len(all_items
) == 0:
806 path
= all_items
[0].rsplit(b
'/', 1)[0]
811 for item
in ls(path
):
813 if os
.path
.basename(i
) == d_name
:
815 dirs
.append(os
.path
.join(path
, d_name
))
821 poutput(path
.decode('utf-8'), end
=':\n')
822 items
= sorted(items
, key
=lambda item
: item
.d_name
)
824 if path
!= b
'' and path
!= cephfs
.getcwd() and len(paths
) > 1:
825 poutput(path
.decode('utf-8'), end
=':\n')
826 items
= sorted(ls(path
), key
=lambda item
: item
.d_name
)
828 items
= [i
for i
in items
if not i
.d_name
.startswith(b
'.')]
830 items
= sorted(items
, key
=lambda item
: cephfs
.stat(
831 path
+ b
'/' + item
.d_name
, follow_symlink
=(
832 not item
.is_symbol_file())).st_size
)
834 items
= reversed(items
)
836 filepath
= item
.d_name
837 is_dir
= item
.is_dir()
838 is_sym_lnk
= item
.is_symbol_file()
840 if args
.long and args
.H
:
841 print_long(os
.path
.join(cephfs
.getcwd(), path
, filepath
), is_dir
,
844 print_long(os
.path
.join(cephfs
.getcwd(), path
, filepath
), is_dir
,
846 elif is_sym_lnk
or is_dir
:
847 values
.append(style_listing(filepath
.decode('utf-8'), is_dir
,
850 values
.append(filepath
)
851 except libcephfs
.Error
as e
:
852 set_exit_code_msg(msg
=e
)
854 print_list(values
, shutil
.get_terminal_size().columns
)
855 if path
!= paths
[-1]:
857 except libcephfs
.Error
as e
:
858 set_exit_code_msg(msg
=e
)
860 def complete_rmdir(self
, text
, line
, begidx
, endidx
):
862 auto complete of file name.
864 return self
.complete_filenames(text
, line
, begidx
, endidx
)
866 rmdir_parser
= argparse
.ArgumentParser(description
='Remove Directory.')
867 rmdir_parser
.add_argument('paths', help='Directory Path.', nargs
='+',
868 action
=path_to_bytes
)
869 rmdir_parser
.add_argument('-p', '--parent', action
='store_true',
870 help='Remove parent directories as necessary. '
871 'When this option is specified, no error '
872 'is reported if a directory has any '
873 'sub-directories, files')
875 @with_argparser(rmdir_parser
)
876 def do_rmdir(self
, args
):
877 self
.do_rmdir_helper(args
)
879 def do_rmdir_helper(self
, args
):
881 Remove a specific Directory
886 if path
.count(b
'*') > 0:
888 all_items
= get_all_possible_paths(path
)
889 if len(all_items
) > 0:
890 path
= all_items
[0].rsplit(b
'/', 1)[0]
895 for item
in ls(path
):
897 if os
.path
.basename(i
) == d_name
:
899 dirs
.append(os
.path
.join(path
, d_name
))
906 path
= os
.path
.join(cephfs
.getcwd(), path
.rsplit(b
'/')[0])
907 files
= list(sorted(set(dirwalk(path
)), reverse
=True))
910 for filepath
in files
:
912 cephfs
.rmdir(os
.path
.normpath(filepath
))
913 except libcephfs
.Error
as e
:
918 path
= os
.path
.normpath(os
.path
.join(cephfs
.getcwd(), path
))
919 if not is_pattern
and path
!= os
.path
.normpath(b
''):
922 except libcephfs
.Error
as e
:
923 set_exit_code_msg(msg
=e
)
925 def complete_rm(self
, text
, line
, begidx
, endidx
):
927 auto complete of file name.
929 return self
.complete_filenames(text
, line
, begidx
, endidx
)
931 rm_parser
= argparse
.ArgumentParser(description
='Remove File.')
932 rm_parser
.add_argument('paths', help='File Path.', nargs
='+',
933 action
=path_to_bytes
)
935 @with_argparser(rm_parser
)
936 def do_rm(self
, args
):
938 Remove a specific file
940 file_paths
= args
.paths
941 for path
in file_paths
:
942 if path
.count(b
'*') > 0:
943 file_paths
.extend([i
for i
in get_all_possible_paths(
944 path
) if is_file_exists(i
)])
948 except libcephfs
.Error
as e
:
949 # NOTE: perhaps we need a better msg here
950 set_exit_code_msg(msg
=e
)
952 def complete_mv(self
, text
, line
, begidx
, endidx
):
954 auto complete of file name.
956 return self
.complete_filenames(text
, line
, begidx
, endidx
)
958 mv_parser
= argparse
.ArgumentParser(description
='Move File.')
959 mv_parser
.add_argument('src_path', type=str, action
=path_to_bytes
,
960 help='Source File Path.')
961 mv_parser
.add_argument('dest_path', type=str, action
=path_to_bytes
,
962 help='Destination File Path.')
964 @with_argparser(mv_parser
)
965 def do_mv(self
, args
):
967 Rename a file or Move a file from source path to the destination
969 cephfs
.rename(args
.src_path
, args
.dest_path
)
971 def complete_cd(self
, text
, line
, begidx
, endidx
):
973 auto complete of file name.
975 return self
.complete_filenames(text
, line
, begidx
, endidx
)
977 cd_parser
= argparse
.ArgumentParser(description
='Change working directory')
978 cd_parser
.add_argument('path', type=str, help='Name of the directory.',
979 action
=path_to_bytes
, nargs
='?', default
='/')
981 @with_argparser(cd_parser
)
982 def do_cd(self
, args
):
984 Change working directory
986 cephfs
.chdir(args
.path
)
987 self
.working_dir
= cephfs
.getcwd().decode('utf-8')
990 def do_cwd(self
, arglist
):
992 Get current working directory.
994 poutput(cephfs
.getcwd().decode('utf-8'))
996 def complete_chmod(self
, text
, line
, begidx
, endidx
):
998 auto complete of file name.
1000 return self
.complete_filenames(text
, line
, begidx
, endidx
)
1002 chmod_parser
= argparse
.ArgumentParser(description
='Create Directory.')
1003 chmod_parser
.add_argument('mode', type=str, action
=ModeAction
, help='Mode')
1004 chmod_parser
.add_argument('paths', type=str, action
=path_to_bytes
,
1005 help='Name of the file', nargs
='+')
1007 @with_argparser(chmod_parser
)
1008 def do_chmod(self
, args
):
1010 Change permission of a file
1012 for path
in args
.paths
:
1013 mode
= int(args
.mode
, base
=8)
1015 cephfs
.chmod(path
, mode
)
1016 except libcephfs
.Error
as e
:
1017 set_exit_code_msg(msg
=e
)
1019 def complete_cat(self
, text
, line
, begidx
, endidx
):
1021 auto complete of file name.
1023 return self
.complete_filenames(text
, line
, begidx
, endidx
)
1025 cat_parser
= argparse
.ArgumentParser(description
='')
1026 cat_parser
.add_argument('paths', help='Name of Files', action
=path_to_bytes
,
1029 @with_argparser(cat_parser
)
1030 def do_cat(self
, args
):
1032 Print contents of a file
1034 for path
in args
.paths
:
1035 if is_file_exists(path
):
1036 copy_to_local(path
, b
'-')
1038 set_exit_code_msg(errno
.ENOENT
, '{}: no such file'.format(
1039 path
.decode('utf-8')))
1041 umask_parser
= argparse
.ArgumentParser(description
='Set umask value.')
1042 umask_parser
.add_argument('mode', help='Mode', type=str, action
=ModeAction
,
1043 nargs
='?', default
='')
1045 @with_argparser(umask_parser
)
1046 def do_umask(self
, args
):
1051 poutput(self
.umask
.zfill(4))
1053 mode
= int(args
.mode
, 8)
1054 self
.umask
= str(oct(cephfs
.umask(mode
))[2:])
1056 def complete_write(self
, text
, line
, begidx
, endidx
):
1058 auto complete of file name.
1060 return self
.complete_filenames(text
, line
, begidx
, endidx
)
1062 write_parser
= argparse
.ArgumentParser(description
='Writes data into a file')
1063 write_parser
.add_argument('path', type=str, action
=path_to_bytes
,
1064 help='Name of File')
1066 @with_argparser(write_parser
)
1067 def do_write(self
, args
):
1069 Write data into a file.
1072 copy_from_local(b
'-', args
.path
)
1074 def complete_lcd(self
, text
, line
, begidx
, endidx
):
1076 auto complete of file name.
1078 index_dict
= {1: self
.path_complete
}
1079 return self
.index_based_complete(text
, line
, begidx
, endidx
, index_dict
)
1081 lcd_parser
= argparse
.ArgumentParser(description
='')
1082 lcd_parser
.add_argument('path', type=str, action
=path_to_bytes
, help='Path')
1084 @with_argparser(lcd_parser
)
1085 def do_lcd(self
, args
):
1087 Moves into the given local directory
1090 os
.chdir(os
.path
.expanduser(args
.path
))
1091 except OSError as e
:
1092 set_exit_code_msg(e
.errno
, "Cannot change to "
1093 f
"{e.filename.decode('utf-8')}: {e.strerror}")
1095 def complete_lls(self
, text
, line
, begidx
, endidx
):
1097 auto complete of file name.
1099 index_dict
= {1: self
.path_complete
}
1100 return self
.index_based_complete(text
, line
, begidx
, endidx
, index_dict
)
1102 lls_parser
= argparse
.ArgumentParser(
1103 description
='List files in local system.')
1104 lls_parser
.add_argument('paths', help='Paths', action
=path_to_bytes
,
1107 @with_argparser(lls_parser
)
1108 def do_lls(self
, args
):
1110 Lists all files and folders in the current local directory
1113 print_list(os
.listdir(os
.getcwdb()))
1115 for path
in args
.paths
:
1117 items
= os
.listdir(path
)
1118 poutput("{}:".format(path
.decode('utf-8')))
1120 except OSError as e
:
1121 set_exit_code_msg(e
.errno
, f
"{e.filename.decode('utf-8')}: "
1123 # Arguments to the with_argpaser decorator function are sticky.
1124 # The items in args.path do not get overwritten in subsequent calls.
1125 # The arguments remain in args.paths after the function exits and we
1126 # neeed to clean it up to ensure the next call works as expected.
1129 def do_lpwd(self
, arglist
):
1131 Prints the absolute path of the current local directory
1133 poutput(os
.getcwd())
1135 def complete_df(self
, text
, line
, begidx
, endidx
):
1137 auto complete of file name.
1139 return self
.complete_filenames(text
, line
, begidx
, endidx
)
1141 df_parser
= argparse
.ArgumentParser(description
='Show information about\
1142 the amount of available disk space')
1143 df_parser
.add_argument('file', help='Name of the file', nargs
='*',
1144 default
=['.'], action
=path_to_bytes
)
1146 @with_argparser(df_parser
)
1147 def do_df(self
, arglist
):
1149 Display the amount of available disk space for file systems
1151 header
= True # Set to true for printing header only once
1152 if b
'.' == arglist
.file[0]:
1153 arglist
.file = ls(b
'.')
1155 for file in arglist
.file:
1156 if isinstance(file, libcephfs
.DirEntry
):
1158 if file == b
'.' or file == b
'..':
1161 statfs
= cephfs
.statfs(file)
1162 stat
= cephfs
.stat(file)
1163 block_size
= (statfs
['f_blocks'] * statfs
['f_bsize']) // 1024
1164 available
= block_size
- stat
.st_size
1168 use
= (stat
.st_size
* 100) // block_size
1172 poutput('{:25s}\t{:5s}\t{:15s}{:10s}{}'.format(
1173 "1K-blocks", "Used", "Available", "Use%",
1176 poutput('{:d}\t{:18d}\t{:8d}\t{:10s} {}'.format(block_size
,
1177 stat
.st_size
, available
, str(int(use
)) + '%',
1178 file.decode('utf-8')))
1179 except libcephfs
.OSError as e
:
1180 set_exit_code_msg(e
.get_error_code(), "could not statfs {}: {}".format(
1181 file.decode('utf-8'), e
.strerror
))
1183 locate_parser
= argparse
.ArgumentParser(
1184 description
='Find file within file system')
1185 locate_parser
.add_argument('name', help='name', type=str,
1186 action
=path_to_bytes
)
1187 locate_parser
.add_argument('-c', '--count', action
='store_true',
1188 help='Count list of items located.')
1189 locate_parser
.add_argument(
1190 '-i', '--ignorecase', action
='store_true', help='Ignore case')
1192 @with_argparser(locate_parser
)
1193 def do_locate(self
, args
):
1195 Find a file within the File System
1197 if args
.name
.count(b
'*') == 1:
1198 if args
.name
[0] == b
'*':
1200 elif args
.name
[-1] == '*':
1201 args
.name
= b
'/' + args
.name
1202 args
.name
= args
.name
.replace(b
'*', b
'')
1204 locations
= locate_file(args
.name
, False)
1206 locations
= locate_file(args
.name
)
1208 poutput(len(locations
))
1210 poutput((b
'\n'.join(locations
)).decode('utf-8'))
1212 def complete_du(self
, text
, line
, begidx
, endidx
):
1214 auto complete of file name.
1216 return self
.complete_filenames(text
, line
, begidx
, endidx
)
1218 du_parser
= argparse
.ArgumentParser(
1219 description
='Disk Usage of a Directory')
1220 du_parser
.add_argument('paths', type=str, action
=get_list_of_bytes_path
,
1221 help='Name of the directory.', nargs
='*',
1223 du_parser
.add_argument('-r', action
='store_true',
1224 help='Recursive Disk usage of all directories.')
1226 @with_argparser(du_parser
)
1227 def do_du(self
, args
):
1229 Print disk usage of a given path(s).
1231 def print_disk_usage(files
):
1232 if isinstance(files
, bytes
):
1237 st
= cephfs
.lstat(f
)
1239 if stat
.S_ISDIR(st
.st_mode
):
1240 dusage
= int(cephfs
.getxattr(f
,
1241 'ceph.dir.rbytes').decode('utf-8'))
1245 # print path in local context
1246 f
= os
.path
.normpath(f
)
1247 if f
[0] is ord('/'):
1249 poutput('{:10s} {}'.format(humansize(dusage
),
1251 except libcephfs
.Error
as e
:
1252 set_exit_code_msg(msg
=e
)
1255 for path
in args
.paths
:
1257 print_disk_usage(sorted(set(dirwalk(path
)).union({path}
)))
1259 print_disk_usage(path
)
1261 quota_parser
= argparse
.ArgumentParser(
1262 description
='Quota management for a Directory')
1263 quota_parser
.add_argument('op', choices
=['get', 'set'],
1264 help='Quota operation type.')
1265 quota_parser
.add_argument('path', type=str, action
=path_to_bytes
,
1266 help='Name of the directory.')
1267 quota_parser
.add_argument('--max_bytes', type=int, default
=-1, nargs
='?',
1268 help='Max cumulative size of the data under '
1270 quota_parser
.add_argument('--max_files', type=int, default
=-1, nargs
='?',
1271 help='Total number of files under this '
1274 @with_argparser(quota_parser
)
1275 def do_quota(self
, args
):
1279 if not is_dir_exists(args
.path
):
1280 set_exit_code_msg(errno
.ENOENT
, 'error: no such directory {}'.format(
1281 args
.path
.decode('utf-8')))
1284 if args
.op
== 'set':
1285 if (args
.max_bytes
== -1) and (args
.max_files
== -1):
1286 set_exit_code_msg(errno
.EINVAL
, 'please specify either '
1287 '--max_bytes or --max_files or both')
1290 if args
.max_bytes
>= 0:
1291 max_bytes
= to_bytes(str(args
.max_bytes
))
1293 cephfs
.setxattr(args
.path
, 'ceph.quota.max_bytes',
1294 max_bytes
, os
.XATTR_CREATE
)
1295 poutput('max_bytes set to %d' % args
.max_bytes
)
1296 except libcephfs
.Error
as e
:
1297 cephfs
.setxattr(args
.path
, 'ceph.quota.max_bytes',
1298 max_bytes
, os
.XATTR_REPLACE
)
1299 set_exit_code_msg(e
.get_error_code(), 'max_bytes reset to '
1300 f
'{args.max_bytes}')
1302 if args
.max_files
>= 0:
1303 max_files
= to_bytes(str(args
.max_files
))
1305 cephfs
.setxattr(args
.path
, 'ceph.quota.max_files',
1306 max_files
, os
.XATTR_CREATE
)
1307 poutput('max_files set to %d' % args
.max_files
)
1308 except libcephfs
.Error
as e
:
1309 cephfs
.setxattr(args
.path
, 'ceph.quota.max_files',
1310 max_files
, os
.XATTR_REPLACE
)
1311 set_exit_code_msg(e
.get_error_code(), 'max_files reset to '
1312 f
'{args.max_files}')
1313 elif args
.op
== 'get':
1317 max_bytes
= cephfs
.getxattr(args
.path
, 'ceph.quota.max_bytes')
1318 poutput('max_bytes: {}'.format(max_bytes
.decode('utf-8')))
1319 except libcephfs
.Error
as e
:
1320 set_exit_code_msg(e
.get_error_code(), 'max_bytes is not set')
1323 max_files
= cephfs
.getxattr(args
.path
, 'ceph.quota.max_files')
1324 poutput('max_files: {}'.format(max_files
.decode('utf-8')))
1325 except libcephfs
.Error
as e
:
1326 set_exit_code_msg(e
.get_error_code(), 'max_files is not set')
1328 snap_parser
= argparse
.ArgumentParser(description
='Snapshot Management')
1329 snap_parser
.add_argument('op', type=str,
1330 help='Snapshot operation: create or delete')
1331 snap_parser
.add_argument('name', type=str, action
=path_to_bytes
,
1332 help='Name of snapshot')
1333 snap_parser
.add_argument('dir', type=str, action
=path_to_bytes
,
1334 help='Directory for which snapshot '
1335 'needs to be created or deleted')
1337 @with_argparser(snap_parser
)
1338 def do_snap(self
, args
):
1340 Snapshot management for the volume
1342 # setting self.colors to None turns off colorizing and
1343 # perror emits plain text
1347 conf_snapdir
= cephfs
.conf_get('client_snapdir')
1348 if conf_snapdir
is not None:
1349 snapdir
= conf_snapdir
1350 snapdir
= to_bytes(snapdir
)
1351 if args
.op
== 'create':
1353 if is_dir_exists(args
.dir):
1354 cephfs
.mkdir(os
.path
.join(args
.dir, snapdir
, args
.name
), 0o755)
1356 set_exit_code_msg(errno
.ENOENT
, "'{}': no such directory".format(
1357 args
.dir.decode('utf-8')))
1358 except libcephfs
.Error
as e
:
1359 set_exit_code_msg(e
.get_error_code(),
1360 "snapshot '{}' already exists".format(
1361 args
.name
.decode('utf-8')))
1362 elif args
.op
== 'delete':
1363 snap_dir
= os
.path
.join(args
.dir, snapdir
, args
.name
)
1365 if is_dir_exists(snap_dir
):
1366 newargs
= argparse
.Namespace(paths
=[snap_dir
], parent
=False)
1367 self
.do_rmdir_helper(newargs
)
1369 set_exit_code_msg(errno
.ENOENT
, "'{}': no such snapshot".format(
1370 args
.name
.decode('utf-8')))
1371 except libcephfs
.Error
as e
:
1372 set_exit_code_msg(e
.get_error_code(), "error while deleting "
1373 "'{}'".format(snap_dir
.decode('utf-8')))
1375 set_exit_code_msg(errno
.EINVAL
, "snapshot can only be created or "
1376 "deleted; check - help snap")
1378 def do_help(self
, line
):
1380 Get details about a command.
1381 Usage: help <cmd> - for a specific command
1382 help all - for all the commands
1386 if k
.startswith('do_'):
1388 super().do_help(k
[3:])
1390 parser
= self
.create_argparser(line
)
1394 super().do_help(line
)
1396 def complete_stat(self
, text
, line
, begidx
, endidx
):
1398 auto complete of file name.
1400 return self
.complete_filenames(text
, line
, begidx
, endidx
)
1402 stat_parser
= argparse
.ArgumentParser(
1403 description
='Display file or file system status')
1404 stat_parser
.add_argument('paths', type=str, help='file paths',
1405 action
=path_to_bytes
, nargs
='+')
1407 @with_argparser(stat_parser
)
1408 def do_stat(self
, args
):
1410 Display file or file system status
1412 for path
in args
.paths
:
1414 stat
= cephfs
.stat(path
)
1415 atime
= stat
.st_atime
.isoformat(' ')
1416 mtime
= stat
.st_mtime
.isoformat(' ')
1417 ctime
= stat
.st_mtime
.isoformat(' ')
1419 poutput("File: {}\nSize: {:d}\nBlocks: {:d}\nIO Block: {:d}\n"
1420 "Device: {:d}\tInode: {:d}\tLinks: {:d}\nPermission: "
1421 "{:o}/{}\tUid: {:d}\tGid: {:d}\nAccess: {}\nModify: "
1422 "{}\nChange: {}".format(path
.decode('utf-8'),
1423 stat
.st_size
, stat
.st_blocks
,
1424 stat
.st_blksize
, stat
.st_dev
,
1425 stat
.st_ino
, stat
.st_nlink
,
1427 mode_notation(stat
.st_mode
),
1428 stat
.st_uid
, stat
.st_gid
, atime
,
1430 except libcephfs
.Error
as e
:
1431 set_exit_code_msg(msg
=e
)
1433 setxattr_parser
= argparse
.ArgumentParser(
1434 description
='Set extended attribute for a file')
1435 setxattr_parser
.add_argument('path', type=str, action
=path_to_bytes
, help='Name of the file')
1436 setxattr_parser
.add_argument('name', type=str, help='Extended attribute name')
1437 setxattr_parser
.add_argument('value', type=str, help='Extended attribute value')
1439 @with_argparser(setxattr_parser
)
1440 def do_setxattr(self
, args
):
1442 Set extended attribute for a file
1444 val_bytes
= to_bytes(args
.value
)
1445 name_bytes
= to_bytes(args
.name
)
1447 cephfs
.setxattr(args
.path
, name_bytes
, val_bytes
, os
.XATTR_CREATE
)
1448 poutput('{} is successfully set to {}'.format(args
.name
, args
.value
))
1449 except libcephfs
.ObjectExists
:
1450 cephfs
.setxattr(args
.path
, name_bytes
, val_bytes
, os
.XATTR_REPLACE
)
1451 poutput('{} is successfully reset to {}'.format(args
.name
, args
.value
))
1452 except libcephfs
.Error
as e
:
1453 set_exit_code_msg(msg
=e
)
1455 getxattr_parser
= argparse
.ArgumentParser(
1456 description
='Get extended attribute set for a file')
1457 getxattr_parser
.add_argument('path', type=str, action
=path_to_bytes
,
1458 help='Name of the file')
1459 getxattr_parser
.add_argument('name', type=str, help='Extended attribute name')
1461 @with_argparser(getxattr_parser
)
1462 def do_getxattr(self
, args
):
1464 Get extended attribute for a file
1467 poutput('{}'.format(cephfs
.getxattr(args
.path
,
1468 to_bytes(args
.name
)).decode('utf-8')))
1469 except libcephfs
.Error
as e
:
1470 set_exit_code_msg(msg
=e
)
1472 listxattr_parser
= argparse
.ArgumentParser(
1473 description
='List extended attributes set for a file')
1474 listxattr_parser
.add_argument('path', type=str, action
=path_to_bytes
,
1475 help='Name of the file')
1477 @with_argparser(listxattr_parser
)
1478 def do_listxattr(self
, args
):
1480 List extended attributes for a file
1483 size
, xattr_list
= cephfs
.listxattr(args
.path
)
1485 poutput('{}'.format(xattr_list
.replace(b
'\x00', b
' ').decode('utf-8')))
1487 poutput('No extended attribute is set')
1488 except libcephfs
.Error
as e
:
1489 set_exit_code_msg(msg
=e
)
1492 #######################################################
1494 # Following are methods that get cephfs-shell started.
1496 #####################################################
1498 def setup_cephfs(args
):
1504 cephfs
= libcephfs
.LibCephFS(conffile
='')
1505 cephfs
.mount(filesystem_name
=args
.fs
)
1506 except libcephfs
.ObjectNotFound
as e
:
1507 print('couldn\'t find ceph configuration not found')
1508 sys
.exit(e
.get_error_code())
1509 except libcephfs
.Error
as e
:
1511 sys
.exit(e
.get_error_code())
1514 def str_to_bool(val
):
1516 Return corresponding bool values for strings like 'true' or 'false'.
1518 if not isinstance(val
, str):
1521 val
= val
.replace('\n', '')
1522 if val
.lower() in ['true', 'yes']:
1524 elif val
.lower() in ['false', 'no']:
1530 def read_shell_conf(shell
, shell_conf_file
):
1533 sec
= 'cephfs-shell'
1535 if LooseVersion(cmd2_version
) >= LooseVersion("0.10.0"):
1536 for attr
in shell
.settables
.keys():
1539 if LooseVersion(cmd2_version
) <= LooseVersion("0.9.13"):
1540 # hardcoding options for 0.7.9 because -
1541 # 1. we use cmd2 v0.7.9 with teuthology and
1542 # 2. there's no way distinguish between a shell setting and shell
1543 # object attribute until v0.10.0
1544 opts
= ['abbrev', 'autorun_on_edit', 'colors',
1545 'continuation_prompt', 'debug', 'echo', 'editor',
1546 'feedback_to_output', 'locals_in_py', 'prompt', 'quiet',
1548 elif LooseVersion(cmd2_version
) >= LooseVersion("0.9.23"):
1549 opts
.append('allow_style')
1550 # no equivalent option was defined by cmd2.
1554 # default and only section in our conf file.
1555 cp
= configparser
.ConfigParser(default_section
=sec
, strict
=False)
1556 cp
.read(shell_conf_file
)
1558 if cp
.has_option(sec
, opt
):
1559 setattr(shell
, opt
, str_to_bool(cp
.get(sec
, opt
)))
1562 def get_shell_conffile_path(arg_conf
=''):
1563 conf_filename
= 'cephfs-shell.conf'
1564 env_var
= 'CEPHFS_SHELL_CONF'
1566 arg_conf
= '' if not arg_conf
else arg_conf
1567 home_dir_conf
= os
.path
.expanduser('~/.' + conf_filename
)
1568 env_conf
= os
.environ
[env_var
] if env_var
in os
.environ
else ''
1570 # here's the priority by which conf gets read.
1571 for path
in (arg_conf
, env_conf
, home_dir_conf
):
1572 if os
.path
.isfile(path
):
1579 main_parser
= argparse
.ArgumentParser(description
='')
1580 main_parser
.add_argument('-b', '--batch', action
='store',
1581 help='Path to CephFS shell script/batch file'
1582 'containing CephFS shell commands',
1584 main_parser
.add_argument('-c', '--config', action
='store',
1585 help='Path to Ceph configuration file.',
1587 main_parser
.add_argument('-f', '--fs', action
='store',
1588 help='Name of filesystem to mount.',
1590 main_parser
.add_argument('-t', '--test', action
='store',
1591 help='Test against transcript(s) in FILE',
1593 main_parser
.add_argument('commands', nargs
='*', help='Comma delimited '
1594 'commands. The shell executes the given command '
1595 'and quits immediately with the return value of '
1596 'command. In case no commands are provided, the '
1597 'shell is launched.', default
=[])
1599 args
= main_parser
.parse_args()
1600 args
.exe_and_quit
= False # Execute and quit, don't launch the shell.
1603 if LooseVersion(cmd2_version
) <= LooseVersion("0.9.13"):
1604 args
.commands
= ['load ' + args
.batch
, ',quit']
1606 args
.commands
= ['run_script ' + args
.batch
, ',quit']
1608 args
.commands
.extend(['-t,'] + [arg
+ ',' for arg
in args
.test
])
1609 if not args
.batch
and len(args
.commands
) > 0:
1610 args
.exe_and_quit
= True
1612 manage_sys_argv(args
)
1617 def manage_sys_argv(args
):
1620 sys
.argv
.append(exe
)
1621 sys
.argv
.extend([i
.strip() for i
in ' '.join(args
.commands
).split(',')])
1626 def execute_cmd_args(args
):
1628 Launch a shell session if no arguments were passed, else just execute
1629 the given argument as a shell command and exit the shell session
1630 immediately at (last) command's termination with the (last) command's
1633 if not args
.exe_and_quit
:
1634 return shell
.cmdloop()
1635 return execute_cmds_and_quit(args
)
1638 def execute_cmds_and_quit(args
):
1640 Multiple commands might be passed separated by commas, feed onecmd()
1641 one command at a time.
1643 # do_* methods triggered by cephfs-shell commands return None when they
1644 # complete running successfully. Until 0.9.6, shell.onecmd() returned this
1645 # value to indicate whether the execution of the commands should stop, but
1646 # since 0.9.7 it returns the return value of do_* methods only if it's
1647 # not None. When it is None it returns False instead of None.
1648 if LooseVersion(cmd2_version
) <= LooseVersion("0.9.6"):
1649 stop_exec_val
= None
1651 stop_exec_val
= False
1654 if len(args
.commands
) <= 1:
1655 args
.commands
= args
.commands
[0].split(' ')
1656 for cmdarg
in args
.commands
:
1658 args_to_onecmd
+= ' ' + cmdarg
[0:-1]
1659 onecmd_retval
= shell
.onecmd(args_to_onecmd
)
1660 # if the curent command failed, let's abort the execution of
1661 # series of commands passed.
1662 if onecmd_retval
is not stop_exec_val
:
1663 return onecmd_retval
1664 if shell
.exit_code
!= 0:
1665 return shell
.exit_code
1670 args_to_onecmd
+= ' ' + cmdarg
1671 return shell
.onecmd(args_to_onecmd
)
1674 if __name__
== '__main__':
1675 args
= manage_args()
1677 shell
= CephFSShell()
1678 # TODO: perhaps, we should add an option to pass ceph.conf?
1679 read_shell_conf(shell
, get_shell_conffile_path(args
.config
))
1680 # XXX: setting shell.exit_code to zero so that in case there are no errors
1681 # and exceptions, it is not set by any method or function of cephfs-shell
1682 # and return values from shell.cmdloop() or shell.onecmd() is not an
1683 # integer, we can treat it as the return value of cephfs-shell.
1686 retval
= execute_cmd_args(args
)
1687 sys
.exit(retval
if retval
else shell
.exit_code
)