Skip to content

Commit d2c72f9

Browse files
committed
Limit number of simultaneous file extractions to prevent hardlock
Added helper extraction functions chmod & chown is now set after a folder's extraction to prevent permission issues
1 parent 2ba4551 commit d2c72f9

File tree

5 files changed

+118
-21
lines changed

5 files changed

+118
-21
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
testing
1+
testing
2+
/go-unsquashfs

cmd/go-unsquashfs/main.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package main
2+
3+
import (
4+
"flag"
5+
"fmt"
6+
"os"
7+
"time"
8+
9+
"github.com/CalebQ42/squashfs"
10+
)
11+
12+
func main() {
13+
verbose := flag.Bool("v", false, "Verbose")
14+
ignore := flag.Bool("ip", false, "Ignore Permissions and extract all files/folders with 0755")
15+
flag.Parse()
16+
if len(flag.Args()) < 2 {
17+
fmt.Println("Please provide a file name and extraction path")
18+
os.Exit(0)
19+
}
20+
f, err := os.Open(flag.Arg(0))
21+
if err != nil {
22+
panic(err)
23+
}
24+
r, err := squashfs.NewReader(f)
25+
if err != nil {
26+
panic(err)
27+
}
28+
op := squashfs.DefaultOptions()
29+
op.Verbose = *verbose
30+
op.IgnorePerm = *ignore
31+
n := time.Now()
32+
err = r.ExtractWithOptions(flag.Arg(1), op)
33+
if err != nil {
34+
panic(err)
35+
}
36+
fmt.Println("Took:", time.Since(n))
37+
}

internal/threadmanager/manager.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package threadmanager
2+
3+
type Manager struct {
4+
c chan int
5+
}
6+
7+
func NewManager(maxRoutines int) *Manager {
8+
m := &Manager{
9+
c: make(chan int, maxRoutines),
10+
}
11+
for i := 0; i < maxRoutines; i++ {
12+
m.c <- i
13+
}
14+
return m
15+
}
16+
17+
func (m *Manager) Lock() int {
18+
return <-m.c
19+
}
20+
21+
func (m *Manager) Unlock(n int) {
22+
m.c <- n
23+
}

reader_file.go

Lines changed: 48 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"github.com/CalebQ42/squashfs/internal/data"
1616
"github.com/CalebQ42/squashfs/internal/directory"
1717
"github.com/CalebQ42/squashfs/internal/inode"
18+
"github.com/CalebQ42/squashfs/internal/threadmanager"
1819
)
1920

2021
// File represents a file inside a squashfs archive.
@@ -219,25 +220,48 @@ func (f File) GetSymlinkFile() *File {
219220

220221
// ExtractionOptions are available options on how to extract.
221222
type ExtractionOptions struct {
223+
manager *threadmanager.Manager
222224
LogOutput io.Writer //Where error log should write.
223225
DereferenceSymlink bool //Replace symlinks with the target file.
224226
UnbreakSymlink bool //Try to make sure symlinks remain unbroken when extracted, without changing the symlink.
225227
Verbose bool //Prints extra info to log on an error.
226-
IgnorePerm bool //Ignore file's permissions and instead use FolderPerm.
227-
FolderPerm fs.FileMode //Permission to use when making the root folder if it doesn't exist.
228+
IgnorePerm bool //Ignore file's permissions and instead use Perm.
229+
Perm fs.FileMode //Permission to use when IgnorePerm. Defaults to 0755.
228230
notFirst bool
229231
}
230232

233+
func DefaultOptions() *ExtractionOptions {
234+
return &ExtractionOptions{
235+
Perm: 0755,
236+
}
237+
}
238+
231239
// ExtractTo extracts the File to the given folder with the default options.
232240
// If the File is a directory, it instead extracts the directory's contents to the folder.
233241
func (f File) ExtractTo(folder string) error {
234-
return f.realExtract(folder, &ExtractionOptions{})
242+
return f.realExtract(folder, DefaultOptions())
243+
}
244+
245+
// ExtractVerbose extracts the File to the folder with the Verbose option.
246+
func (f File) ExtractVerbose(folder string) error {
247+
op := DefaultOptions()
248+
op.Verbose = true
249+
return f.realExtract(folder, op)
250+
}
251+
252+
// ExtractIgnorePermissions extracts the File to the folder with the IgnorePerm option.
253+
func (f File) ExtractIgnorePermissions(folder string) error {
254+
op := DefaultOptions()
255+
op.IgnorePerm = true
256+
return f.realExtract(folder, op)
235257
}
236258

237259
// ExtractSymlink extracts the File to the folder with the DereferenceSymlink option.
238260
// If the File is a directory, it instead extracts the directory's contents to the folder.
239261
func (f File) ExtractSymlink(folder string) error {
240-
return f.realExtract(folder, &ExtractionOptions{})
262+
op := DefaultOptions()
263+
op.DereferenceSymlink = true
264+
return f.realExtract(folder, op)
241265
}
242266

243267
// ExtractWithOptions extracts the File to the given folder with the given ExtrationOptions.
@@ -250,18 +274,17 @@ func (f File) ExtractWithOptions(folder string, op *ExtractionOptions) error {
250274
}
251275

252276
func (f File) realExtract(folder string, op *ExtractionOptions) (err error) {
277+
if op.manager == nil {
278+
op.manager = threadmanager.NewManager(runtime.NumCPU())
279+
}
253280
extDir := folder + "/" + f.e.Name
254281
if !op.notFirst {
255282
op.notFirst = true
256283
if f.IsDir() {
257284
extDir = folder
258285
_, err = os.Open(folder)
259286
if err != nil && os.IsNotExist(err) {
260-
if op.IgnorePerm {
261-
err = os.Mkdir(extDir, op.FolderPerm|(f.Mode()&fs.ModeType))
262-
} else {
263-
err = os.Mkdir(extDir, f.Mode())
264-
}
287+
err = os.Mkdir(extDir, op.Perm)
265288
}
266289
if err != nil {
267290
if op.Verbose {
@@ -274,17 +297,19 @@ func (f File) realExtract(folder string, op *ExtractionOptions) (err error) {
274297
switch {
275298
case f.IsDir():
276299
if folder != extDir && f.e.Name != "" {
277-
if op.IgnorePerm {
278-
err = os.Mkdir(extDir, op.FolderPerm|(f.Mode()&fs.ModeType))
279-
} else {
280-
err = os.Mkdir(extDir, f.Mode())
281-
}
300+
//First extract it with a permisive permission.
301+
err = os.Mkdir(extDir, op.Perm)
282302
if err != nil {
283303
if op.Verbose {
284304
log.Println("Error while making directory", extDir)
285305
}
286306
return
287307
}
308+
//Then set it to it's actual permissions once we're done with it
309+
if !op.IgnorePerm {
310+
defer os.Chmod(extDir, f.Mode())
311+
defer os.Chown(extDir, int(f.r.ids[f.i.UidInd]), int(f.r.ids[f.i.GidInd]))
312+
}
288313
}
289314
var filFS *FS
290315
filFS, err = f.FS()
@@ -324,6 +349,8 @@ func (f File) realExtract(folder string, op *ExtractionOptions) (err error) {
324349
//Then we extract the files.
325350
for i = 0; i < len(files); i++ {
326351
go func(index int) {
352+
n := op.manager.Lock()
353+
defer op.manager.Unlock(n)
327354
subF, goErr := f.r.newFile(files[index], filFS)
328355
if goErr != nil {
329356
if op.Verbose {
@@ -368,9 +395,10 @@ func (f File) realExtract(folder string, op *ExtractionOptions) (err error) {
368395
return err
369396
}
370397
if op.IgnorePerm {
371-
os.Chmod(fil.Name(), op.FolderPerm|(f.Mode()&fs.ModeType))
398+
os.Chmod(extDir, op.Perm|(f.Mode()&fs.ModeType))
372399
} else {
373-
os.Chmod(fil.Name(), f.Mode())
400+
os.Chmod(extDir, f.Mode())
401+
os.Chown(extDir, int(f.r.ids[f.i.UidInd]), int(f.r.ids[f.i.GidInd]))
374402
}
375403
case f.IsSymlink():
376404
symPath := f.SymlinkPath()
@@ -420,9 +448,10 @@ func (f File) realExtract(folder string, op *ExtractionOptions) (err error) {
420448
return err
421449
}
422450
if op.IgnorePerm {
423-
os.Chmod(extDir, op.FolderPerm|(f.Mode()&fs.ModeType))
451+
os.Chmod(extDir, op.Perm|(f.Mode()&fs.ModeType))
424452
} else {
425453
os.Chmod(extDir, f.Mode())
454+
os.Chown(extDir, int(f.r.ids[f.i.UidInd]), int(f.r.ids[f.i.GidInd]))
426455
}
427456
case f.isDeviceOrFifo():
428457
if runtime.GOOS == "windows" {
@@ -469,9 +498,10 @@ func (f File) realExtract(folder string, op *ExtractionOptions) (err error) {
469498
return err
470499
}
471500
if op.IgnorePerm {
472-
os.Chmod(extDir, op.FolderPerm|(f.Mode()&fs.ModeType))
501+
os.Chmod(extDir, op.Perm|(f.Mode()&fs.ModeType))
473502
} else {
474503
os.Chmod(extDir, f.Mode())
504+
os.Chown(extDir, int(f.r.ids[f.i.UidInd]), int(f.r.ids[f.i.GidInd]))
475505
}
476506
case f.e.Type == inode.Sock:
477507
if op.Verbose {

squashfs_test.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import (
1818
)
1919

2020
const (
21-
squashfsURL = "https://darkstorm.tech/LinuxPATest.sfs"
21+
squashfsURL = "https://darkstorm.tech/files/LinuxPATest.sfs"
2222
squashfsName = "LinuxPATest.sfs"
2323

2424
filePath = "PortableApps/Notepad++Portable/App/DefaultData/Config/contextMenu.xml"
@@ -122,7 +122,13 @@ func TestExtractQuick(t *testing.T) {
122122
if err != nil {
123123
t.Fatal(err)
124124
}
125-
err = rdr.ExtractWithOptions(libPath, &squashfs.ExtractionOptions{Verbose: true})
125+
os.RemoveAll(filepath.Join(tmpDir, "testLog.txt"))
126+
logFil, _ := os.Create(filepath.Join(tmpDir, "testLog.txt"))
127+
op := squashfs.DefaultOptions()
128+
op.Verbose = true
129+
op.IgnorePerm = true
130+
op.LogOutput = logFil
131+
err = rdr.ExtractWithOptions(libPath, op)
126132
if err != nil {
127133
t.Fatal(err)
128134
}

0 commit comments

Comments
 (0)