Skip to content

Commit 25ec661

Browse files
committed
feat(wrtag): chmod files with respect to umask
closes #204
1 parent 391d1ce commit 25ec661

File tree

7 files changed

+148
-33
lines changed

7 files changed

+148
-33
lines changed

cmd/internal/wrtagflag/wrtagflag.go

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -116,19 +116,6 @@ func ResearchLinks() *researchlink.Builder {
116116
return &r
117117
}
118118

119-
func OperationByName(name string, dryRun bool) (wrtag.FileSystemOperation, error) {
120-
switch name {
121-
case "copy":
122-
return wrtag.NewCopy(dryRun), nil
123-
case "move":
124-
return wrtag.NewMove(dryRun), nil
125-
case "reflink":
126-
return wrtag.NewReflink(dryRun), nil
127-
default:
128-
return nil, errors.New("unknown operation")
129-
}
130-
}
131-
132119
var _ flag.Value = (*pathFormatParser)(nil)
133120
var _ flag.Value = (*researchLinkParser)(nil)
134121
var _ flag.Value = (*notificationsParser)(nil)
@@ -345,3 +332,19 @@ func (rl *rateLimitParser) String() string {
345332
}
346333
return dur.String()
347334
}
335+
336+
// DefaultFileMode will be overridden in _unix.go to respect umask.
337+
var DefaultFileMode os.FileMode = 0666
338+
339+
func OperationByName(name string, dryRun bool) (wrtag.FileSystemOperation, error) {
340+
switch name {
341+
case "copy":
342+
return wrtag.NewCopy(dryRun, DefaultFileMode), nil
343+
case "move":
344+
return wrtag.NewMove(dryRun, DefaultFileMode), nil
345+
case "reflink":
346+
return wrtag.NewReflink(dryRun, DefaultFileMode), nil
347+
default:
348+
return nil, errors.New("unknown operation")
349+
}
350+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//go:build unix
2+
3+
package wrtagflag
4+
5+
import (
6+
"io/fs"
7+
"syscall"
8+
)
9+
10+
func init() {
11+
umask := syscall.Umask(0)
12+
syscall.Umask(umask)
13+
DefaultFileMode = fs.FileMode(0o666 &^ umask) //nolint:gosec
14+
}

cmd/wrtag/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ func runSync(ctx context.Context, cfg *wrtag.Config, stats *syncStats, dirs []st
236236
wg.Go(func() {
237237
ctxConsume(ctx, leaves, func(dir string) {
238238
stats.saw.Add(1)
239-
r, err := syncDir(ctx, cfg, ageYounger, ageOlder, wrtag.NewMove(dryRun), dir)
239+
r, err := syncDir(ctx, cfg, ageYounger, ageOlder, wrtag.NewMove(dryRun, wrtagflag.DefaultFileMode), dir)
240240
if err != nil && !errors.Is(err, context.Canceled) {
241241
stats.errors.Add(1)
242242
slog.ErrorContext(ctx, "processing dir", "dir", dir, "err", err)

cmd/wrtag/main_test.go

Lines changed: 56 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"testing"
1919

2020
"github.com/rogpeppe/go-internal/testscript"
21+
"go.senan.xyz/wrtag/cmd/internal/wrtagflag"
2122
"go.senan.xyz/wrtag/fileutil"
2223
"go.senan.xyz/wrtag/tags"
2324
"go.senan.xyz/wrtag/tags/normtag"
@@ -38,13 +39,15 @@ func TestMain(m *testing.M) {
3839
os.Setenv("WRTAG_CAA_RATE_LIMIT", "0")
3940

4041
testscript.Main(m, map[string]func(){
41-
"wrtag": main,
42-
"tag": mainTag,
43-
"find": mainFind,
44-
"touch": mainTouch,
45-
"mime": mainMIME,
46-
"mod-time": mainModTime,
47-
"rand": mainRand,
42+
"wrtag": main,
43+
"tag": mainTag,
44+
"find": mainFind,
45+
"touch": mainTouch,
46+
"mime": mainMIME,
47+
"chmod": mainChmod,
48+
"file-mode": mainFileMode,
49+
"mod-time": mainModTime,
50+
"rand": mainRand,
4851
})
4952
}
5053

@@ -54,6 +57,13 @@ func TestScripts(t *testing.T) {
5457
testscript.Run(t, testscript.Params{
5558
Dir: "testdata/scripts",
5659
RequireExplicitExec: true,
60+
Condition: func(cond string) (bool, error) {
61+
if mask, ok := strings.CutPrefix(cond, "defaultfilemode:"); ok {
62+
want, _ := strconv.ParseInt(mask, 8, 32)
63+
return wrtagflag.DefaultFileMode == os.FileMode(want), nil
64+
}
65+
return false, fmt.Errorf("unknown condition %q", cond)
66+
},
5767
})
5868
}
5969

@@ -156,6 +166,45 @@ func mainMIME() {
156166
fmt.Println(mime)
157167
}
158168

169+
func mainChmod() {
170+
flag.Parse()
171+
172+
mode, err := strconv.ParseInt(flag.Arg(0), 8, 32)
173+
if err != nil {
174+
log.Fatalf("parse mode: %v", err)
175+
}
176+
177+
pat := flag.Arg(1)
178+
paths := parsePattern(pat)
179+
if len(paths) == 0 {
180+
log.Fatalf("no paths to match pattern")
181+
}
182+
183+
for _, p := range paths {
184+
if err := os.Chmod(p, os.FileMode(mode)); err != nil {
185+
log.Fatalf("chmod %s: %v", p, err)
186+
}
187+
}
188+
}
189+
190+
func mainFileMode() {
191+
flag.Parse()
192+
193+
pat := flag.Arg(0)
194+
paths := parsePattern(pat)
195+
if len(paths) == 0 {
196+
log.Fatalf("no paths to match pattern")
197+
}
198+
199+
for _, p := range paths {
200+
info, err := os.Stat(p)
201+
if err != nil {
202+
log.Fatalf("error stating: %v", err)
203+
}
204+
fmt.Printf("%04o\n", info.Mode().Perm())
205+
}
206+
}
207+
159208
func mainModTime() {
160209
flag.Parse()
161210

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
[!defaultfilemode:644] skip 'assumes default permission mode 0644'
2+
3+
exec tag write kat_moda/01.flac
4+
exec tag write kat_moda/02.flac
5+
exec tag write kat_moda/03.flac
6+
exec touch kat_moda/keep-me
7+
exec touch kat_moda/cover.png
8+
9+
exec chmod 0612 kat_moda/*.flac
10+
exec chmod 0612 kat_moda/cover.png
11+
exec chmod 0612 kat_moda/keep-me
12+
13+
exec file-mode kat_moda/*.flac
14+
stdout -count=3 0612
15+
exec file-mode kat_moda/cover.png
16+
stdout 0612
17+
exec file-mode kat_moda/keep-me
18+
stdout 0612
19+
20+
env WRTAG_PATH_FORMAT='albums/{{ .Release.Title }}/{{ .Track.Position }}{{ .Ext }}'
21+
env WRTAG_KEEP_FILE=keep-me
22+
23+
# copy
24+
exec wrtag copy -yes -mbid e47d04a4-7460-427d-a731-cc82386d85f1 kat_moda/
25+
26+
# expect all respect umask
27+
exec file-mode 'albums/Kat Moda/*.flac'
28+
stdout -count=3 0644
29+
exec file-mode 'albums/Kat Moda/cover.png'
30+
stdout 0644
31+
exec file-mode 'albums/Kat Moda/keep-me'
32+
stdout 0644

wrtag.go

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -770,12 +770,13 @@ func NewDirContext() DirContext {
770770

771771
// Move implements FileSystemOperation to move files from source to destination.
772772
type Move struct {
773-
dryRun bool
773+
dryRun bool
774+
destMode os.FileMode
774775
}
775776

776777
// NewMove creates a new Move operation with the specified dry-run mode.
777778
// If dryRun is true, no files will actually be moved.
778-
func NewMove(dryRun bool) Move { return Move{dryRun: dryRun} }
779+
func NewMove(dryRun bool, destMode os.FileMode) Move { return Move{dryRun: dryRun, destMode: destMode} }
779780

780781
// CanModifyDest returns whether this operation can modify destination files.
781782
// For Move operations, this is determined by the dryRun setting.
@@ -811,10 +812,14 @@ func (m Move) ProcessPath(ctx context.Context, dc DirContext, src, dest string)
811812
if err := os.Remove(src); err != nil {
812813
return fmt.Errorf("remove from move: %w", err)
813814
}
815+
if err := os.Chmod(dest, m.destMode); err != nil {
816+
return fmt.Errorf("chmod dest: %w", err)
817+
}
814818

815819
slog.DebugContext(ctx, "moved path", "from", src, "to", dest)
816820
return nil
817821
}
822+
818823
return fmt.Errorf("rename: %w", err)
819824
}
820825

@@ -853,12 +858,13 @@ func (m Move) PostSource(ctx context.Context, dc DirContext, limit string, src s
853858

854859
// Copy implements FileSystemOperation to copy files from source to destination.
855860
type Copy struct {
856-
dryRun bool
861+
dryRun bool
862+
destMode os.FileMode
857863
}
858864

859865
// NewCopy creates a new Copy operation with the specified dry-run mode.
860866
// If dryRun is true, no files will actually be copied.
861-
func NewCopy(dryRun bool) Copy { return Copy{dryRun: dryRun} }
867+
func NewCopy(dryRun bool, destMode os.FileMode) Copy { return Copy{dryRun: dryRun, destMode: destMode} }
862868

863869
// CanModifyDest returns whether this operation can modify destination files.
864870
// For Copy operations, this is determined by the dryRun setting.
@@ -889,6 +895,10 @@ func (c Copy) ProcessPath(ctx context.Context, dc DirContext, src, dest string)
889895
return err
890896
}
891897

898+
if err := os.Chmod(dest, c.destMode); err != nil {
899+
return fmt.Errorf("chmod dest: %w", err)
900+
}
901+
892902
slog.DebugContext(ctx, "copied path", "from", src, "to", dest)
893903
return nil
894904
}
@@ -901,12 +911,15 @@ func (Copy) PostSource(ctx context.Context, dc DirContext, limit string, src str
901911

902912
// Reflink implements FileSystemOperation to copy files using reflink (copy-on-write) when supported.
903913
type Reflink struct {
904-
dryRun bool
914+
dryRun bool
915+
destMode os.FileMode
905916
}
906917

907918
// NewReflink creates a new Reflink operation with the specified dry-run mode.
908919
// If dryRun is true, no files will actually be reflinked.
909-
func NewReflink(dryRun bool) Reflink { return Reflink{dryRun: dryRun} }
920+
func NewReflink(dryRun bool, destMode os.FileMode) Reflink {
921+
return Reflink{dryRun: dryRun, destMode: destMode}
922+
}
910923

911924
// CanModifyDest returns whether this operation can modify destination files.
912925
// For Reflink operations, this is determined by the dryRun setting.
@@ -937,6 +950,10 @@ func (c Reflink) ProcessPath(ctx context.Context, dc DirContext, src, dest strin
937950
return fmt.Errorf("reflink file: %w", err)
938951
}
939952

953+
if err := os.Chmod(dest, c.destMode); err != nil {
954+
return fmt.Errorf("chmod dest: %w", err)
955+
}
956+
940957
slog.DebugContext(ctx, "reflinked path", "from", src, "to", dest)
941958
return nil
942959
}

wrtag_test.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -145,29 +145,29 @@ func TestNewDirContext(t *testing.T) {
145145
func TestMoveCanModifyDest(t *testing.T) {
146146
t.Parallel()
147147

148-
move := NewMove(false)
148+
move := NewMove(false, 0600)
149149
assert.True(t, move.CanModifyDest())
150150

151-
dryRunMove := NewMove(true)
151+
dryRunMove := NewMove(true, 0600)
152152
assert.False(t, dryRunMove.CanModifyDest())
153153
}
154154

155155
func TestCopyCanModifyDest(t *testing.T) {
156156
t.Parallel()
157157

158-
cpy := NewCopy(false)
158+
cpy := NewCopy(false, 0600)
159159
assert.True(t, cpy.CanModifyDest())
160160

161-
dryRunCopy := NewCopy(true)
161+
dryRunCopy := NewCopy(true, 0600)
162162
assert.False(t, dryRunCopy.CanModifyDest())
163163
}
164164

165165
func TestReflinkCanModifyDest(t *testing.T) {
166166
t.Parallel()
167167

168-
reflink := NewReflink(false)
168+
reflink := NewReflink(false, 0600)
169169
assert.True(t, reflink.CanModifyDest())
170170

171-
dryRunReflink := NewReflink(true)
171+
dryRunReflink := NewReflink(true, 0600)
172172
assert.False(t, dryRunReflink.CanModifyDest())
173173
}

0 commit comments

Comments
 (0)