Skip to content

Commit

Permalink
prop: copy initial values and store as pointers internally
Browse files Browse the repository at this point in the history
Without using pointers internally, accepting structs using Store
wouldn't work properly. This requires making a copy of the initial
values (which were documented already as "initial" values, but were
indeed just reused by the existing code). But using them directly was
anyway not possible since access not going through Get or GetMust could
have led to race conditions anyway.

Fixes #298.
  • Loading branch information
guelfey committed Feb 13, 2022
1 parent 041560b commit 43289f1
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 15 deletions.
46 changes: 31 additions & 15 deletions prop/prop.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,9 @@ const IntrospectDataString = `
// Prop represents a single property. It is used for creating a Properties
// value.
type Prop struct {
// Initial value. Must be a DBus-representable type.
// Initial value. Must be a DBus-representable type. This is not modified
// after Properties has been initialized; use Get or GetMust to access the
// value.
Value interface{}

// If true, the value can be modified by calls to Set.
Expand Down Expand Up @@ -154,7 +156,7 @@ func (p *Prop) Introspection(name string) introspect.Property {
}
result.Annotations = []introspect.Annotation{
{
Name: "org.freedesktop.DBus.Property.EmitsChangedSignal",
Name: "org.freedesktop.DBus.Property.EmitsChangedSignal",
Value: p.Emit.String(),
},
}
Expand All @@ -173,7 +175,7 @@ type Change struct {
// using the org.freedesktop.DBus.Properties interface. It is safe for
// concurrent use by multiple goroutines.
type Properties struct {
m map[string]map[string]*Prop
m Map
mut sync.RWMutex
conn *dbus.Conn
path dbus.ObjectPath
Expand All @@ -183,7 +185,7 @@ type Properties struct {
// swallowing the error, shouldn't be used.
//
// Deprecated: use Export instead.
func New(conn *dbus.Conn, path dbus.ObjectPath, props map[string]map[string]*Prop) *Properties {
func New(conn *dbus.Conn, path dbus.ObjectPath, props Map) *Properties {
p, err := Export(conn, path, props)
if err != nil {
return nil
Expand All @@ -196,15 +198,33 @@ func New(conn *dbus.Conn, path dbus.ObjectPath, props map[string]map[string]*Pro
// second-level key is the name of the property. The returned structure will be
// exported as org.freedesktop.DBus.Properties on path.
func Export(
conn *dbus.Conn, path dbus.ObjectPath, props map[string]map[string]*Prop,
conn *dbus.Conn, path dbus.ObjectPath, props Map,
) (*Properties, error) {
p := &Properties{m: props, conn: conn, path: path}
p := &Properties{m: copyProps(props), conn: conn, path: path}
if err := conn.Export(p, path, "org.freedesktop.DBus.Properties"); err != nil {
return nil, err
}
return p, nil
}

// Map is a helper type for supplying the configuration of properties to be handled.
type Map = map[string]map[string]*Prop

func copyProps(in Map) Map {
out := make(Map, len(in))
for intf, props := range in {
out[intf] = make(map[string]*Prop)
for name, prop := range props {
out[intf][name] = new(Prop)
*out[intf][name] = *prop
val := reflect.New(reflect.TypeOf(prop.Value))
val.Elem().Set(reflect.ValueOf(prop.Value))
out[intf][name].Value = val.Interface()
}
}
return out
}

// Get implements org.freedesktop.DBus.Properties.Get.
func (p *Properties) Get(iface, property string) (dbus.Variant, *dbus.Error) {
p.mut.RLock()
Expand All @@ -217,7 +237,7 @@ func (p *Properties) Get(iface, property string) (dbus.Variant, *dbus.Error) {
if !ok {
return dbus.Variant{}, ErrPropNotFound
}
return dbus.MakeVariant(prop.Value), nil
return dbus.MakeVariant(reflect.ValueOf(prop.Value).Elem().Interface()), nil
}

// GetAll implements org.freedesktop.DBus.Properties.GetAll.
Expand All @@ -230,7 +250,7 @@ func (p *Properties) GetAll(iface string) (map[string]dbus.Variant, *dbus.Error)
}
rm := make(map[string]dbus.Variant, len(m))
for k, v := range m {
rm[k] = dbus.MakeVariant(v.Value)
rm[k] = dbus.MakeVariant(reflect.ValueOf(v.Value).Elem().Interface())
}
return rm, nil
}
Expand All @@ -240,7 +260,7 @@ func (p *Properties) GetAll(iface string) (map[string]dbus.Variant, *dbus.Error)
func (p *Properties) GetMust(iface, property string) interface{} {
p.mut.RLock()
defer p.mut.RUnlock()
return p.m[iface][property].Value
return reflect.ValueOf(p.m[iface][property].Value).Elem().Interface()
}

// Introspection returns the introspection data that represents the properties
Expand All @@ -260,9 +280,6 @@ func (p *Properties) Introspection(iface string) []introspect.Property {
// must already be locked.
func (p *Properties) set(iface, property string, v interface{}) error {
prop := p.m[iface][property]
if reflect.ValueOf(prop.Value).Kind() != reflect.Ptr {
prop.Value = reflect.New(reflect.TypeOf(prop.Value)).Interface()
}
err := dbus.Store([]interface{}{v}, prop.Value)
if err != nil {
return err
Expand Down Expand Up @@ -324,9 +341,8 @@ func (p *Properties) Set(iface, property string, newv dbus.Variant) *dbus.Error
func (p *Properties) SetMust(iface, property string, v interface{}) {
p.mut.Lock()
defer p.mut.Unlock() // unlock in case of panic
prop := p.m[iface][property]
prop.Value = v
if err := p.emitChange(iface, property); err != nil {
err := p.set(iface, property, v)
if err != nil {
panic(err)
}
}
44 changes: 44 additions & 0 deletions prop/prop_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,47 @@ func TestValidateStructsAsProp(t *testing.T) {
comparePropValue(obj, "FooStructPtr", *zooPtr, t)
comparePropValue(obj, "SliceOfFoos", zoos, t)
}

func TestInt32(t *testing.T) {
srv, err := dbus.SessionBus()
if err != nil {
t.Fatal(err)
}
defer srv.Close()

cli, err := dbus.SessionBus()
if err != nil {
t.Fatal(err)
}
defer cli.Close()

propsSpec := map[string]map[string]*Prop{
"org.guelfey.DBus.Test": {
"int32": {
int32(100),
true,
EmitTrue,
nil,
},
},
}
props := New(srv, "/org/guelfey/DBus/Test", propsSpec)

obj := cli.Object(srv.Names()[0], "/org/guelfey/DBus/Test")

comparePropValue(obj, "int32", int32(100), t)
r := props.GetMust("org.guelfey.DBus.Test", "int32")
if r != int32(100) {
t.Errorf("expected r to be int32(100), but was %#v", r)
}

if err := props.Set("org.guelfey.DBus.Test", "int32", dbus.MakeVariant(int32(101))); err != nil {
t.Fatalf("failed to set prop int32 to 101")
}

comparePropValue(obj, "int32", int32(101), t)
r = props.GetMust("org.guelfey.DBus.Test", "int32")
if r != int32(101) {
t.Errorf("expected r to be int32(101), but was %#v", r)
}
}

0 comments on commit 43289f1

Please sign in to comment.