@@ -12,6 +12,7 @@ import (
1212 "path/filepath"
1313 "strings"
1414
15+ "github.com/hashicorp/go-multierror"
1516 "github.com/pkg/errors"
1617 "github.com/sourcegraph/src-cli/internal/api"
1718 "github.com/sourcegraph/src-cli/internal/campaigns/graphql"
@@ -67,6 +68,11 @@ func unzipToTempDir(ctx context.Context, zipFile, tempDir, tempFilePrefix string
6768 if err != nil {
6869 return "" , err
6970 }
71+
72+ if err := os .Chmod (volumeDir , 0777 ); err != nil {
73+ return "" , err
74+ }
75+
7076 return volumeDir , unzip (zipFile , volumeDir )
7177}
7278
@@ -86,6 +92,10 @@ func fetchRepositoryArchive(ctx context.Context, client api.Client, repo *graphq
8692 return fmt .Errorf ("unable to fetch archive (HTTP %d from %s)" , resp .StatusCode , req .URL .String ())
8793 }
8894
95+ // Unlike the mkdirAll() calls elsewhere in this file, this is only giving
96+ // us a temporary place on the filesystem to keep the archive. Since it's
97+ // never mounted into the containers being run, we can keep these
98+ // directories 0700 without issue.
8999 if err := os .MkdirAll (filepath .Dir (dest ), 0700 ); err != nil {
90100 return err
91101 }
@@ -130,13 +140,13 @@ func unzip(zipFile, dest string) error {
130140 }
131141
132142 if f .FileInfo ().IsDir () {
133- if err := os . MkdirAll ( fpath , os . ModePerm ); err != nil {
143+ if err := mkdirAll ( dest , f . Name , 0777 ); err != nil {
134144 return err
135145 }
136146 continue
137147 }
138148
139- if err := os . MkdirAll ( filepath .Dir (fpath ), os . ModePerm ); err != nil {
149+ if err := mkdirAll ( dest , filepath .Dir (f . Name ), 0777 ); err != nil {
140150 return err
141151 }
142152
@@ -145,6 +155,16 @@ func unzip(zipFile, dest string) error {
145155 return err
146156 }
147157
158+ // Since the container might not run as the same user, we need to ensure
159+ // that the file is globally writable. If the execute bit is normally
160+ // set on the zipped up file, let's ensure we propagate that to the
161+ // group and other permission bits too.
162+ if f .Mode ()& 0111 != 0 {
163+ outFile .Chmod (0777 )
164+ } else {
165+ outFile .Chmod (0666 )
166+ }
167+
148168 rc , err := f .Open ()
149169 if err != nil {
150170 outFile .Close ()
@@ -166,3 +186,61 @@ func unzip(zipFile, dest string) error {
166186
167187 return nil
168188}
189+
190+ // Technically, this is a misnomer, since it might be a socket or block special,
191+ // but errPathExistsAsNonDir is just ugly for an internal type.
192+ type errPathExistsAsFile string
193+
194+ var _ error = errPathExistsAsFile ("" )
195+
196+ func (e errPathExistsAsFile ) Error () string {
197+ return fmt .Sprintf ("path already exists, but not as a directory: %s" , string (e ))
198+ }
199+
200+ // mkdirAll is essentially os.MkdirAll(filepath.Join(base, path), perm), but
201+ // applies the given permission regardless of the user's umask.
202+ func mkdirAll (base , path string , perm os.FileMode ) error {
203+ abs := filepath .Join (base , path )
204+
205+ // Create the directory if it doesn't exist.
206+ st , err := os .Stat (abs )
207+ if err != nil {
208+ // It's expected that we'll get an error if the directory doesn't exist,
209+ // so let's check that it's of the type we expect.
210+ if ! os .IsNotExist (err ) {
211+ return err
212+ }
213+
214+ // Now we're clear to create the directory.
215+ if err := os .MkdirAll (abs , perm ); err != nil {
216+ return err
217+ }
218+ } else if ! st .IsDir () {
219+ // The file/socket/whatever exists, but it's not a directory. That's
220+ // definitely going to be an issue.
221+ return errPathExistsAsFile (abs )
222+ }
223+
224+ // If os.MkdirAll() was invoked earlier, then the permissions it set were
225+ // subject to the umask. Let's walk the directories we may or may not have
226+ // created and ensure their permissions look how we want.
227+ return ensureAll (base , path , perm )
228+ }
229+
230+ // ensureAll ensures that all directories under path have the expected
231+ // permissions.
232+ func ensureAll (base , path string , perm os.FileMode ) error {
233+ var errs * multierror.Error
234+
235+ // In plain English: for each directory in the path parameter, we should
236+ // chmod that path to the permissions that are expected.
237+ acc := []string {base }
238+ for _ , element := range strings .Split (path , string (os .PathSeparator )) {
239+ acc = append (acc , element )
240+ if err := os .Chmod (filepath .Join (acc ... ), perm ); err != nil {
241+ errs = multierror .Append (errs , err )
242+ }
243+ }
244+
245+ return errs .ErrorOrNil ()
246+ }
0 commit comments