Skip to content

Commit 271cb4e

Browse files
committed
- Prevent double-free failure when the same dataset is closed multiple times
In some cases, copying dataset object, either by passing by value or in some other pattern can lead to code that will close the same dataset multiple times (e.g. defer). Or otherwise, code with complicated handling of dataset cleanup. Fixes: #30, Closes: #31
1 parent cfa2e4a commit 271cb4e

File tree

5 files changed

+62
-13
lines changed

5 files changed

+62
-13
lines changed

a_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ func Test(t *testing.T) {
3434
zfsTestDatasetSetProperty(t)
3535
zfsTestDatasetHoldRelease(t)
3636

37+
zfsTestDoubleFreeOnDestroy(t)
3738
zfsTestDatasetDestroy(t)
3839

3940
zpoolTestPoolDestroy(t)

destroy_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,5 @@ func TestDataset_DestroyPromote(t *testing.T) {
6060
t.Log("Destroy promote completed with success")
6161
d.Close()
6262
zpoolTestPoolDestroy(t)
63+
cleanupVDisks()
6364
}

zfs.go

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"path"
1414
"sort"
1515
"strings"
16+
"sync"
1617
"time"
1718
"unsafe"
1819
)
@@ -49,6 +50,7 @@ type HoldTag struct {
4950
// Dataset - ZFS dataset object
5051
type Dataset struct {
5152
list C.dataset_list_ptr
53+
closeOnce *sync.Once
5254
Type DatasetType
5355
Properties map[Prop]Property
5456
Children []Dataset
@@ -58,7 +60,7 @@ func (d *Dataset) openChildren() (err error) {
5860
d.Children = make([]Dataset, 0, 5)
5961
list := C.dataset_list_children(d.list)
6062
for list != nil {
61-
dataset := Dataset{list: list}
63+
dataset := Dataset{list: list, closeOnce: new(sync.Once)}
6264
dataset.Type = DatasetType(C.dataset_type(list))
6365
dataset.Properties = make(map[Prop]Property)
6466
err = dataset.ReloadProperties()
@@ -79,16 +81,20 @@ func (d *Dataset) openChildren() (err error) {
7981
// DatasetOpenAll recursive get handles to all available datasets on system
8082
// (file-systems, volumes or snapshots).
8183
func DatasetOpenAll() (datasets []Dataset, err error) {
82-
var dataset Dataset
83-
dataset.list = C.dataset_list_root()
84-
for dataset.list != nil {
85-
dataset.Type = DatasetType(C.dataset_type(dataset.list))
84+
list := C.dataset_list_root()
85+
for list != nil {
86+
dataset := Dataset{
87+
list: list,
88+
closeOnce: new(sync.Once),
89+
Type: DatasetType(C.dataset_type(list)),
90+
}
91+
dataset.Type = DatasetType(C.dataset_type(list))
8692
err = dataset.ReloadProperties()
8793
if err != nil {
8894
return
8995
}
9096
datasets = append(datasets, dataset)
91-
dataset.list = C.dataset_next(dataset.list)
97+
list = C.dataset_next(list)
9298
}
9399
for ci := range datasets {
94100
if err = datasets[ci].openChildren(); err != nil {
@@ -130,6 +136,7 @@ func DatasetOpenSingle(path string) (d Dataset, err error) {
130136
err = fmt.Errorf("%s - %s", err.Error(), path)
131137
return
132138
}
139+
d.closeOnce = new(sync.Once)
133140
d.Type = DatasetType(C.dataset_type(d.list))
134141
err = d.ReloadProperties()
135142
if err != nil {
@@ -182,11 +189,13 @@ func DatasetCreate(path string, dtype DatasetType,
182189
// Close close dataset and all its recursive children datasets (close handle
183190
// and cleanup dataset object/s from memory)
184191
func (d *Dataset) Close() {
185-
// path, _ := d.Path()
186-
Global.Mtx.Lock()
187-
C.dataset_list_close(d.list)
192+
// if dataset was ever open
193+
if d.closeOnce != nil {
194+
d.closeOnce.Do(func() {
195+
C.dataset_list_close(d.list)
196+
})
197+
}
188198
d.list = nil
189-
Global.Mtx.Unlock()
190199
for _, cd := range d.Children {
191200
cd.Close()
192201
}

zfs_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,3 +338,35 @@ func ExampleDatasetOpenAll() {
338338
}
339339

340340
}
341+
342+
func CopyAndDestroy(d *zfs.Dataset) (err error) {
343+
if err = d.Destroy(false); err != nil {
344+
return
345+
}
346+
d.Close()
347+
return
348+
}
349+
350+
func zfsTestDoubleFreeOnDestroy(t *testing.T) {
351+
TSTDestroyPath := TSTPoolName + "/DESTROY"
352+
println("TEST Doble Free On Destroy( ", TSTVolumePath, " ) ... ")
353+
props := make(map[zfs.Prop]zfs.Property)
354+
d, err := zfs.DatasetCreate(TSTDestroyPath, zfs.DatasetTypeFilesystem, props)
355+
if err != nil {
356+
t.Error(err)
357+
return
358+
}
359+
d.Close()
360+
361+
d, err = zfs.DatasetOpen(TSTDestroyPath)
362+
if err != nil {
363+
t.Error(err)
364+
return
365+
}
366+
defer d.Close()
367+
if err = CopyAndDestroy(&d); err != nil {
368+
t.Error(err)
369+
return
370+
}
371+
print("PASS\n\n")
372+
}

zpool_test.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,18 @@ func createTestpoolVdisks() (err error) {
5151
return
5252
}
5353

54+
func removeVDisk(path string) {
55+
if err := os.Remove(path); err != nil {
56+
println("Error: ", err.Error())
57+
}
58+
}
59+
5460
// Cleanup sparse files used for tests
5561
func cleanupVDisks() {
5662
// try cleanup
57-
os.Remove(s1path)
58-
os.Remove(s2path)
59-
os.Remove(s3path)
63+
removeVDisk(s1path)
64+
removeVDisk(s2path)
65+
removeVDisk(s3path)
6066
}
6167

6268
/* ------------------------------------------------------------------------- */

0 commit comments

Comments
 (0)