|
3 | 3 | package runner |
4 | 4 |
|
5 | 5 | import ( |
| 6 | + "errors" |
6 | 7 | "fmt" |
| 8 | + "os" |
7 | 9 | "path/filepath" |
8 | 10 | "strings" |
| 11 | + "time" |
9 | 12 |
|
10 | 13 | "github.com/itchio/headway/state" |
11 | 14 | "github.com/itchio/ox/syscallex" |
@@ -103,33 +106,59 @@ func (wr *fujiRunner) Run() error { |
103 | 106 |
|
104 | 107 | defer sp.Revoke(consumer) |
105 | 108 |
|
106 | | - cmd := execas.Command(params.FullTargetPath, params.Args...) |
107 | | - cmd.Username = creds.Username |
108 | | - cmd.Domain = "." |
109 | | - cmd.Password = creds.Password |
110 | | - cmd.Dir = params.Dir |
111 | | - cmd.Env = env |
112 | | - cmd.Stdout = params.Stdout |
113 | | - cmd.Stderr = params.Stderr |
114 | | - |
115 | | - var creationFlags uint32 = syscallex.CREATE_SUSPENDED |
116 | | - if params.Console { |
117 | | - // note: this will disable std{in,out,err} redirection |
118 | | - creationFlags |= syscallex.CREATE_NEW_CONSOLE |
119 | | - } |
120 | | - cmd.SysProcAttr = &syscallex.SysProcAttr{ |
121 | | - CreationFlags: creationFlags, |
122 | | - LogonFlags: syscallex.LOGON_WITH_PROFILE, |
123 | | - } |
| 109 | + const maxStartAttempts = 7 |
| 110 | + const startRetryDelay = 1 * time.Second |
| 111 | + |
| 112 | + var cmd *execas.Cmd |
| 113 | + var pg *processGroup |
| 114 | + |
| 115 | + for attempt := 1; attempt <= maxStartAttempts; attempt++ { |
| 116 | + cmd = execas.Command(params.FullTargetPath, params.Args...) |
| 117 | + cmd.Username = creds.Username |
| 118 | + cmd.Domain = "." |
| 119 | + cmd.Password = creds.Password |
| 120 | + cmd.Dir = params.Dir |
| 121 | + cmd.Env = env |
| 122 | + cmd.Stdout = params.Stdout |
| 123 | + cmd.Stderr = params.Stderr |
| 124 | + |
| 125 | + var creationFlags uint32 = syscallex.CREATE_SUSPENDED |
| 126 | + if params.Console { |
| 127 | + // note: this will disable std{in,out,err} redirection |
| 128 | + creationFlags |= syscallex.CREATE_NEW_CONSOLE |
| 129 | + } |
| 130 | + cmd.SysProcAttr = &syscallex.SysProcAttr{ |
| 131 | + CreationFlags: creationFlags, |
| 132 | + LogonFlags: syscallex.LOGON_WITH_PROFILE, |
| 133 | + } |
124 | 134 |
|
125 | | - pg, err := NewProcessGroup(consumer, cmd, params.Ctx) |
126 | | - if err != nil { |
127 | | - return fmt.Errorf("%w", err) |
128 | | - } |
| 135 | + pg, err = NewProcessGroup(consumer, cmd, params.Ctx) |
| 136 | + if err != nil { |
| 137 | + return fmt.Errorf("%w", err) |
| 138 | + } |
129 | 139 |
|
130 | | - err = cmd.Start() |
131 | | - if err != nil { |
132 | | - return fmt.Errorf("%w", err) |
| 140 | + err = cmd.Start() |
| 141 | + if err != nil { |
| 142 | + // After granting ACL permissions, Windows may not have fully |
| 143 | + // propagated them yet. Retry on access denied errors since the |
| 144 | + // permissions will become effective shortly. |
| 145 | + if errors.Is(err, os.ErrPermission) && attempt < maxStartAttempts { |
| 146 | + consumer.Warnf("Access denied on attempt %d/%d, retrying in %v...", attempt, maxStartAttempts, startRetryDelay) |
| 147 | + if params.Ctx != nil { |
| 148 | + select { |
| 149 | + case <-time.After(startRetryDelay): |
| 150 | + case <-params.Ctx.Done(): |
| 151 | + return fmt.Errorf("%w", params.Ctx.Err()) |
| 152 | + } |
| 153 | + } else { |
| 154 | + time.Sleep(startRetryDelay) |
| 155 | + } |
| 156 | + continue |
| 157 | + } |
| 158 | + return fmt.Errorf("%w", err) |
| 159 | + } |
| 160 | + |
| 161 | + break |
133 | 162 | } |
134 | 163 |
|
135 | 164 | err = pg.AfterStart() |
|
0 commit comments