2014-10-05 14:44:59 +02:00
package main
import (
2017-03-08 20:29:31 +01:00
"context"
2018-08-14 10:50:31 +02:00
"encoding/json"
"os"
2018-08-10 19:48:42 -06:00
"strings"
2018-08-14 10:50:31 +02:00
"time"
2014-10-05 14:44:59 +02:00
2016-09-17 12:36:05 +02:00
"github.com/spf13/cobra"
2017-07-23 14:21:03 +02:00
"github.com/restic/restic/internal/errors"
2018-08-11 15:25:22 -06:00
"github.com/restic/restic/internal/fs"
2017-07-24 17:42:25 +02:00
"github.com/restic/restic/internal/restic"
2018-06-09 23:31:31 +02:00
"github.com/restic/restic/internal/walker"
2014-10-05 14:44:59 +02:00
)
2016-09-17 12:36:05 +02:00
var cmdLs = & cobra . Command {
2020-08-29 14:28:53 +02:00
Use : "ls [flags] snapshotID [dir...]" ,
2017-09-11 09:32:44 -07:00
Short : "List files in a snapshot" ,
2016-09-17 12:36:05 +02:00
Long : `
2018-08-10 22:10:02 -06:00
The "ls" command lists files and directories in a snapshot .
The special snapshot ID "latest" can be used to list files and
directories of the latest snapshot in the repository . The
-- host flag can be used in conjunction to select the latest
snapshot originating from a certain host only .
File listings can optionally be filtered by directories . Any
positional arguments after the snapshot ID are interpreted as
2018-08-11 15:25:22 -06:00
absolute directory paths , and only files inside those directories
will be listed . If the -- recursive flag is used , then the filter
2018-08-10 22:10:02 -06:00
will allow traversing into matching directories ' subfolders .
2018-08-11 15:25:22 -06:00
Any directory paths specified must be absolute ( starting with
a path separator ) ; paths use the forward slash '/' as separator .
2019-11-04 22:03:38 -08:00
EXIT STATUS
== == == == == =
Exit status is 0 if the command was successful , and non - zero if there was any error .
2016-09-17 12:36:05 +02:00
` ,
2017-08-06 21:02:16 +02:00
DisableAutoGenTag : true ,
2016-09-17 12:36:05 +02:00
RunE : func ( cmd * cobra . Command , args [ ] string ) error {
2017-03-08 20:29:31 +01:00
return runLs ( lsOptions , globalOptions , args )
2016-09-17 12:36:05 +02:00
} ,
2015-06-21 13:02:56 +02:00
}
2014-12-07 16:30:52 +01:00
2017-01-14 11:19:47 +08:00
// LsOptions collects all options for the ls command.
type LsOptions struct {
2018-08-10 19:48:42 -06:00
ListLong bool
2020-02-26 22:17:59 +01:00
Hosts [ ] string
2018-08-10 19:48:42 -06:00
Tags restic . TagLists
Paths [ ] string
Recursive bool
2017-01-14 11:19:47 +08:00
}
var lsOptions LsOptions
2016-09-17 12:36:05 +02:00
2014-11-30 22:39:58 +01:00
func init ( ) {
2016-09-17 12:36:05 +02:00
cmdRoot . AddCommand ( cmdLs )
2017-01-14 11:19:47 +08:00
flags := cmdLs . Flags ( )
flags . BoolVarP ( & lsOptions . ListLong , "long" , "l" , false , "use a long listing format showing size and mode" )
2020-02-26 22:17:59 +01:00
flags . StringArrayVarP ( & lsOptions . Hosts , "host" , "H" , nil , "only consider snapshots for this `host`, when no snapshot ID is given (can be specified multiple times)" )
2017-07-09 12:45:49 +02:00
flags . Var ( & lsOptions . Tags , "tag" , "only consider snapshots which include this `taglist`, when no snapshot ID is given" )
2017-07-07 03:19:06 +02:00
flags . StringArrayVar ( & lsOptions . Paths , "path" , nil , "only consider snapshots which include this (absolute) `path`, when no snapshot ID is given" )
2018-08-10 19:48:42 -06:00
flags . BoolVar ( & lsOptions . Recursive , "recursive" , false , "include files in subfolders of the listed directories" )
2014-11-30 22:39:58 +01:00
}
2018-08-14 10:50:31 +02:00
type lsSnapshot struct {
* restic . Snapshot
ID * restic . ID ` json:"id" `
ShortID string ` json:"short_id" `
StructType string ` json:"struct_type" ` // "snapshot"
}
type lsNode struct {
Name string ` json:"name" `
Type string ` json:"type" `
Path string ` json:"path" `
UID uint32 ` json:"uid" `
GID uint32 ` json:"gid" `
Size uint64 ` json:"size,omitempty" `
Mode os . FileMode ` json:"mode,omitempty" `
ModTime time . Time ` json:"mtime,omitempty" `
AccessTime time . Time ` json:"atime,omitempty" `
ChangeTime time . Time ` json:"ctime,omitempty" `
StructType string ` json:"struct_type" ` // "node"
}
2017-03-08 20:29:31 +01:00
func runLs ( opts LsOptions , gopts GlobalOptions , args [ ] string ) error {
2020-08-29 14:28:53 +02:00
if len ( args ) == 0 {
return errors . Fatal ( "no snapshot ID specified" )
2014-12-07 16:30:52 +01:00
}
2018-08-10 19:48:42 -06:00
// extract any specific directories to walk
2018-08-10 22:10:02 -06:00
var dirs [ ] string
if len ( args ) > 1 {
dirs = args [ 1 : ]
2018-08-11 15:25:22 -06:00
for _ , dir := range dirs {
if ! strings . HasPrefix ( dir , "/" ) {
return errors . Fatal ( "All path filters must be absolute, starting with a forward slash '/'" )
}
}
2018-08-10 22:10:02 -06:00
}
2018-08-10 19:48:42 -06:00
2018-08-12 23:13:34 +02:00
withinDir := func ( nodepath string ) bool {
if len ( dirs ) == 0 {
return true
}
for _ , dir := range dirs {
// we're within one of the selected dirs, example:
// nodepath: "/test/foo"
// dir: "/test"
if fs . HasPathPrefix ( dir , nodepath ) {
return true
}
}
return false
}
approachingMatchingTree := func ( nodepath string ) bool {
if len ( dirs ) == 0 {
return true
}
for _ , dir := range dirs {
// the current node path is a prefix for one of the
// directories, so we're interested in something deeper in the
// tree. Example:
// nodepath: "/test"
// dir: "/test/foo"
if fs . HasPathPrefix ( nodepath , dir ) {
return true
}
}
return false
}
2018-08-12 21:59:57 +02:00
repo , err := OpenRepository ( gopts )
if err != nil {
return err
}
if err = repo . LoadIndex ( gopts . ctx ) ; err != nil {
return err
}
2017-03-08 20:29:31 +01:00
ctx , cancel := context . WithCancel ( gopts . ctx )
defer cancel ( )
2018-08-14 10:50:31 +02:00
var (
printSnapshot func ( sn * restic . Snapshot )
printNode func ( path string , node * restic . Node )
)
if gopts . JSON {
2018-08-19 00:18:43 -06:00
enc := json . NewEncoder ( gopts . stdout )
2018-08-14 10:50:31 +02:00
printSnapshot = func ( sn * restic . Snapshot ) {
2021-01-30 17:25:10 +01:00
err = enc . Encode ( lsSnapshot {
2018-08-14 10:50:31 +02:00
Snapshot : sn ,
ID : sn . ID ( ) ,
ShortID : sn . ID ( ) . Str ( ) ,
2018-08-14 11:15:17 +02:00
StructType : "snapshot" ,
2018-08-19 00:18:43 -06:00
} )
2021-01-30 17:25:10 +01:00
panic ( err )
2018-08-14 10:50:31 +02:00
}
printNode = func ( path string , node * restic . Node ) {
2021-01-30 17:25:10 +01:00
err = enc . Encode ( lsNode {
2018-08-14 10:50:31 +02:00
Name : node . Name ,
Type : node . Type ,
Path : path ,
UID : node . UID ,
GID : node . GID ,
Size : node . Size ,
Mode : node . Mode ,
ModTime : node . ModTime ,
AccessTime : node . AccessTime ,
ChangeTime : node . ChangeTime ,
2018-08-14 11:15:17 +02:00
StructType : "node" ,
2018-08-19 00:18:43 -06:00
} )
2021-01-30 17:25:10 +01:00
panic ( err )
2018-08-14 10:50:31 +02:00
}
} else {
printSnapshot = func ( sn * restic . Snapshot ) {
Verbosef ( "snapshot %s of %v filtered by %v at %s):\n" , sn . ID ( ) . Str ( ) , sn . Paths , dirs , sn . Time )
}
printNode = func ( path string , node * restic . Node ) {
Printf ( "%s\n" , formatNode ( path , node , lsOptions . ListLong ) )
}
}
2020-02-26 22:17:59 +01:00
for sn := range FindFilteredSnapshots ( ctx , repo , opts . Hosts , opts . Tags , opts . Paths , args [ : 1 ] ) {
2018-08-14 10:50:31 +02:00
printSnapshot ( sn )
2017-01-12 19:24:08 +08:00
2018-08-19 12:28:06 +02:00
err := walker . Walk ( ctx , repo , * sn . Tree , nil , func ( _ restic . ID , nodepath string , node * restic . Node , err error ) ( bool , error ) {
2018-06-09 23:31:31 +02:00
if err != nil {
return false , err
}
if node == nil {
return false , nil
}
2018-08-10 19:48:42 -06:00
2018-08-12 23:13:34 +02:00
if withinDir ( nodepath ) {
// if we're within a dir, print the node
2018-08-14 10:50:31 +02:00
printNode ( nodepath , node )
2018-08-11 15:25:22 -06:00
2018-08-12 23:13:34 +02:00
// if recursive listing is requested, signal the walker that it
// should continue walking recursively
if opts . Recursive {
2018-08-11 15:25:22 -06:00
return false , nil
2018-08-10 19:48:42 -06:00
}
}
2018-08-12 23:13:34 +02:00
// if there's an upcoming match deeper in the tree (but we're not
// there yet), signal the walker to descend into any subdirs
if approachingMatchingTree ( nodepath ) {
return false , nil
}
2018-08-11 15:25:22 -06:00
2018-08-12 23:13:34 +02:00
// otherwise, signal the walker to not walk recursively into any
// subdirs
if node . Type == "dir" {
2020-07-28 22:32:57 +02:00
return false , walker . ErrSkipNode
2018-08-12 23:13:34 +02:00
}
2018-06-09 23:31:31 +02:00
return false , nil
} )
2018-08-12 23:13:34 +02:00
2018-06-09 23:31:31 +02:00
if err != nil {
2017-03-08 20:29:31 +01:00
return err
2017-01-12 19:24:08 +08:00
}
2014-10-05 14:44:59 +02:00
}
2018-08-19 00:18:43 -06:00
return nil
2014-10-05 14:44:59 +02:00
}