diff --git a/changelog/unreleased/issue-3542 b/changelog/unreleased/issue-3542 new file mode 100644 index 000000000..87f908fc1 --- /dev/null +++ b/changelog/unreleased/issue-3542 @@ -0,0 +1,8 @@ +Enhancement: Add file mode in symbolic notation to `ls --json` + +Now `restic ls --json` provides mode in symbolic notation aligned +with `restic find --json`. + +https://github.com/restic/restic/issues/3542 +https://github.com/restic/restic/pull/3573 +https://forum.restic.net/t/restic-ls-understanding-file-mode-with-json/4371 diff --git a/cmd/restic/cmd_ls.go b/cmd/restic/cmd_ls.go index 228e7696c..9f1ab7ec6 100644 --- a/cmd/restic/cmd_ls.go +++ b/cmd/restic/cmd_ls.go @@ -77,31 +77,33 @@ type lsSnapshot struct { // Print node in our custom JSON format, followed by a newline. func lsNodeJSON(enc *json.Encoder, path string, node *restic.Node) error { n := &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" + 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"` + Permissions string `json:"permissions,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" size uint64 // Target for Size pointer. }{ - 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, - StructType: "node", + Name: node.Name, + Type: node.Type, + Path: path, + UID: node.UID, + GID: node.GID, + size: node.Size, + Mode: node.Mode, + Permissions: node.Mode.String(), + ModTime: node.ModTime, + AccessTime: node.AccessTime, + ChangeTime: node.ChangeTime, + StructType: "node", } // Always print size for regular files, even when empty, // but never for other types. diff --git a/cmd/restic/cmd_ls_test.go b/cmd/restic/cmd_ls_test.go index bef749c76..8a4fa51ee 100644 --- a/cmd/restic/cmd_ls_test.go +++ b/cmd/restic/cmd_ls_test.go @@ -18,6 +18,7 @@ func TestLsNodeJSON(t *testing.T) { expect string }{ // Mode is omitted when zero. + // Permissions, by convention is "-" per mode bit { path: "/bar/baz", Node: restic.Node{ @@ -31,7 +32,7 @@ func TestLsNodeJSON(t *testing.T) { Group: "nobodies", Links: 1, }, - expect: `{"name":"baz","type":"file","path":"/bar/baz","uid":10000000,"gid":20000000,"size":12345,"mtime":"0001-01-01T00:00:00Z","atime":"0001-01-01T00:00:00Z","ctime":"0001-01-01T00:00:00Z","struct_type":"node"}`, + expect: `{"name":"baz","type":"file","path":"/bar/baz","uid":10000000,"gid":20000000,"size":12345,"permissions":"----------","mtime":"0001-01-01T00:00:00Z","atime":"0001-01-01T00:00:00Z","ctime":"0001-01-01T00:00:00Z","struct_type":"node"}`, }, // Even empty files get an explicit size. @@ -48,7 +49,7 @@ func TestLsNodeJSON(t *testing.T) { Group: "not printed", Links: 0xF00, }, - expect: `{"name":"empty","type":"file","path":"/foo/empty","uid":1001,"gid":1001,"size":0,"mtime":"0001-01-01T00:00:00Z","atime":"0001-01-01T00:00:00Z","ctime":"0001-01-01T00:00:00Z","struct_type":"node"}`, + expect: `{"name":"empty","type":"file","path":"/foo/empty","uid":1001,"gid":1001,"size":0,"permissions":"----------","mtime":"0001-01-01T00:00:00Z","atime":"0001-01-01T00:00:00Z","ctime":"0001-01-01T00:00:00Z","struct_type":"node"}`, }, // Non-regular files do not get a size. @@ -61,7 +62,7 @@ func TestLsNodeJSON(t *testing.T) { Mode: os.ModeSymlink | 0777, LinkTarget: "not printed", }, - expect: `{"name":"link","type":"symlink","path":"/foo/link","uid":0,"gid":0,"mode":134218239,"mtime":"0001-01-01T00:00:00Z","atime":"0001-01-01T00:00:00Z","ctime":"0001-01-01T00:00:00Z","struct_type":"node"}`, + expect: `{"name":"link","type":"symlink","path":"/foo/link","uid":0,"gid":0,"mode":134218239,"permissions":"Lrwxrwxrwx","mtime":"0001-01-01T00:00:00Z","atime":"0001-01-01T00:00:00Z","ctime":"0001-01-01T00:00:00Z","struct_type":"node"}`, }, { @@ -74,7 +75,7 @@ func TestLsNodeJSON(t *testing.T) { AccessTime: time.Date(2021, 2, 3, 4, 5, 6, 7, time.UTC), ChangeTime: time.Date(2022, 3, 4, 5, 6, 7, 8, time.UTC), }, - expect: `{"name":"directory","type":"dir","path":"/some/directory","uid":0,"gid":0,"mode":2147484141,"mtime":"2020-01-02T03:04:05Z","atime":"2021-02-03T04:05:06.000000007Z","ctime":"2022-03-04T05:06:07.000000008Z","struct_type":"node"}`, + expect: `{"name":"directory","type":"dir","path":"/some/directory","uid":0,"gid":0,"mode":2147484141,"permissions":"drwxr-xr-x","mtime":"2020-01-02T03:04:05Z","atime":"2021-02-03T04:05:06.000000007Z","ctime":"2022-03-04T05:06:07.000000008Z","struct_type":"node"}`, }, } { buf := new(bytes.Buffer)