1 /*
2  * Copyright (c) 2023-2024 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #include <algorithm>
17 #include <cerrno>
18 #include <climits>
19 #include <cstring>
20 #include <fstream>
21 #include <iostream>
22 #include <iterator>
23 #include <optional>
24 #include <sstream>
25 #include <string>
26 #include <unordered_map>
27 #include <vector>
28 
29 #include <unistd.h>
30 
31 #include <libudev.h>
32 #include <linux/input.h>
33 
34 #include "mmi_log.h"
35 
36 #undef MMI_LOG_DOMAIN
37 #define MMI_LOG_DOMAIN MMI_LOG_SERVER
38 #undef MMI_LOG_TAG
39 #define MMI_LOG_TAG "MmiLibudev"
40 
41 using namespace std::literals;
42 namespace {
43 constexpr int UTIL_PATH_SIZE { 1024 };
44 constexpr int UTIL_LINE_SIZE { 16384 };
45 
StartsWith(std::string_view str,std::string_view prefix)46 bool StartsWith(std::string_view str, std::string_view prefix)
47 {
48     return str.size() >= prefix.size() && str.substr(0, prefix.size()) == prefix;
49 }
50 
ChopTail(std::string_view & str,char sep)51 bool ChopTail(std::string_view &str, char sep)
52 {
53     auto pos = str.rfind(sep);
54     if (pos == std::string_view::npos) {
55         return false;
56     }
57     str.remove_suffix(str.size() - pos);
58     return true;
59 }
60 
ResolveSymLink(const std::string & syspath)61 std::string ResolveSymLink(const std::string &syspath)
62 {
63     constexpr auto backStr = "../"sv;
64     char linkTarget[UTIL_PATH_SIZE];
65 
66     ssize_t len = readlink(syspath.c_str(), linkTarget, sizeof(linkTarget));
67     if (len <= 0 || len == static_cast<ssize_t>(sizeof(linkTarget))) {
68         return syspath;
69     }
70 
71     std::string_view tail{ linkTarget, len };
72     int back = 0;
73     for (; StartsWith(tail, backStr); back++) {
74         tail.remove_prefix(backStr.size());
75     }
76 
77     std::string_view base = syspath;
78     for (int i = 0; i <= back; i++) {
79         if (!ChopTail(base, '/')) {
80             return syspath;
81         }
82     }
83 
84     return std::string{ base }.append("/").append(tail);
85 }
86 
GetLinkValue(const std::string & slink,const std::string & syspath)87 std::optional<std::string> GetLinkValue(const std::string &slink, const std::string &syspath)
88 {
89     auto path = syspath + "/" + slink;
90 
91     char target[UTIL_PATH_SIZE];
92     ssize_t len = readlink(path.c_str(), target, sizeof(target));
93     if (len <= 0 || len == static_cast<ssize_t>(sizeof(target))) {
94         MMI_HILOGE("Failed to read link");
95         return std::nullopt;
96     }
97 
98     std::string_view result{ target, len };
99     auto pos = result.rfind('/');
100     if (pos == std::string_view::npos) {
101         MMI_HILOGE("Failed to get link value");
102         return std::nullopt;
103     }
104     return std::string{ result.substr(pos + 1) };
105 }
106 
107 class BitVector {
108 public:
109     // This type depends on kernel definition
110     using val_t = unsigned long;
111 
112     // Input string is hexadecimal 64-bit numbers separated by spaces with high bit number first
BitVector(const std::string & str)113     explicit BitVector(const std::string &str)
114     {
115         std::istringstream ss{ str };
116         ss >> std::hex;
117         std::copy(std::istream_iterator<val_t>(ss), std::istream_iterator<val_t>(), std::back_inserter(bits_));
118         // Since numbers in string starts with high number we need to reverse vector to count numbers from low to high
119         std::reverse(bits_.begin(), bits_.end());
120     }
121 
CheckBit(size_t idx) const122     [[nodiscard]] bool CheckBit(size_t idx) const
123     {
124         auto vidx = idx / (sizeof(val_t) * CHAR_BIT);
125         auto bidx = idx % (sizeof(val_t) * CHAR_BIT);
126         if (vidx >= bits_.size()) {
127             return false;
128         }
129         return (bits_[vidx] & (1ULL << bidx)) != 0;
130     }
131 
132 private:
133     std::vector<val_t> bits_;
134 };
135 } // namespace
136 
137 struct udev {};
138 
139 struct udev_device {
140 public:
141     // Not copyable and not movable
142     udev_device(udev_device &) = delete;
143     udev_device(udev_device &&) = delete;
144     udev_device &operator = (udev_device &) = delete;
145     udev_device &operator = (udev_device &&) = delete;
146 
NewFromSyspathudev_device147     static udev_device *NewFromSyspath(const std::string &syspathParam)
148     {
149         // path starts in sys
150         if (!StartsWith(syspathParam, "/sys/") || syspathParam.back() == '/') {
151             errno = EINVAL;
152             return nullptr;
153         }
154 
155         // resolve possible symlink to real path
156         std::string path = ResolveSymLink(syspathParam);
157         if (StartsWith(path, "/sys/devices/")) {
158             // all "devices" require a "uevent" file
159             struct stat statbuf;
160             std::string filename = path + "/uevent";
161             if (stat(filename.c_str(), &statbuf) != 0) {
162                 return nullptr;
163             }
164         } else {
165             return nullptr;
166         }
167 
168         auto *inst = new udev_device;
169         inst->SetSyspath(std::move(path));
170 
171         return inst;
172     }
173 
NewFromDevnumudev_device174     static udev_device *NewFromDevnum(char type, dev_t devnum)
175     {
176         const char *typeStr = nullptr;
177 
178         if (type == 'b') {
179             typeStr = "block";
180         } else if (type == 'c') {
181             typeStr = "char";
182         } else {
183             MMI_HILOGE("Param invalid");
184             errno = EINVAL;
185             return nullptr;
186         }
187 
188         // use /sys/dev/{block,char}/<maj>:<min> link
189         auto majStr = std::to_string(major(devnum));
190         auto minStr = std::to_string(minor(devnum));
191         return NewFromSyspath("/sys/dev/"s + typeStr + "/" + majStr + ":" + minStr);
192     }
193 
Refudev_device194     void Ref()
195     {
196         refcount++;
197     }
198 
Unrefudev_device199     void Unref()
200     {
201         if (--refcount <= 0) {
202             delete this;
203         }
204     }
205 
GetParentudev_device206     udev_device *GetParent()
207     {
208         if (!parentDevice_.has_value()) {
209             parentDevice_ = NewFromChild(this);
210         }
211         return *parentDevice_;
212     }
213 
GetSyspathudev_device214     const std::string &GetSyspath() const
215     {
216         return syspath;
217     }
218 
GetSysnameudev_device219     const std::string &GetSysname() const
220     {
221         return sysname;
222     }
223 
GetDevnodeudev_device224     const std::string &GetDevnode()
225     {
226         return GetProperty("DEVNAME");
227     }
228 
IsInitializedudev_device229     bool IsInitialized()
230     {
231         if (!ueventLoaded) {
232             ReadUeventFile();
233         }
234         return ueventLoaded;
235     }
236 
GetParentWithSubsystemudev_device237     udev_device *GetParentWithSubsystem(const std::string &subsystem)
238     {
239         udev_device *parent = GetParent();
240         while (parent != nullptr) {
241             auto parentSubsystem = parent->GetSubsystem();
242             if (parentSubsystem.has_value() && parentSubsystem.value() == subsystem) {
243                 break;
244             }
245             parent = parent->GetParent();
246         }
247 
248         if (parent == nullptr) {
249             errno = ENOENT;
250         }
251         return parent;
252     }
253 
HasPropertyudev_device254     bool HasProperty(const std::string &key)
255     {
256         if (!ueventLoaded) {
257             ReadUeventFile();
258         }
259         return property_.find(key) != property_.end();
260     }
261 
GetPropertyudev_device262     const std::string &GetProperty(const std::string &key)
263     {
264         if (!ueventLoaded) {
265             ReadUeventFile();
266         }
267         return property_[key];
268     }
269 
270 private:
271     udev_device() = default;
272 
~udev_deviceudev_device273     ~udev_device()
274     {
275         if (parentDevice_.has_value() && parentDevice_.value() != nullptr) {
276             parentDevice_.value()->Unref();
277         }
278     }
279 
NewFromChildudev_device280     static udev_device *NewFromChild(udev_device *child)
281     {
282         std::string_view path = child->GetSyspath();
283 
284         while (true) {
285             if (!ChopTail(path, '/')) {
286                 break;
287             }
288             udev_device *parent = NewFromSyspath(std::string{ path });
289             if (parent != nullptr) {
290                 return parent;
291             }
292         }
293 
294         return nullptr;
295     }
296 
SetSyspathudev_device297     void SetSyspath(std::string newSyspath)
298     {
299         syspath = std::move(newSyspath);
300 
301         AddProperty("DEVPATH", syspath.substr(0, "/sys"sv.size()));
302 
303         auto pos = syspath.rfind('/');
304         if (pos == std::string::npos) {
305             return;
306         }
307         sysname = syspath.substr(pos + 1);
308 
309         // some devices have '!' in their name, change that to '/'
310         for (char &c : sysname) {
311             if (c == '!') {
312                 c = '/';
313             }
314         }
315     }
316 
AddPropertyFromStringudev_device317     void AddPropertyFromString(const std::string &line)
318     {
319         auto pos = line.find('=');
320         if (pos == std::string::npos) {
321             return;
322         }
323         std::string key = line.substr(0, pos);
324         if (key == "DEVNAME") {
325             SetDevnode(line.substr(pos + 1));
326             return;
327         }
328         AddProperty(std::move(key), line.substr(pos + 1));
329     }
330 
ReadUeventFileudev_device331     void ReadUeventFile()
332     {
333         if (ueventLoaded) {
334             return;
335         }
336 
337         auto filename = syspath + "/uevent";
338         char realPath[PATH_MAX] = {};
339         CHKPV(realpath(filename.c_str(), realPath));
340         std::ifstream f(realPath, std::ios_base::in);
341         if (!f.is_open()) {
342             MMI_HILOGE("ReadUeventFile(): path:%{private}s, error:%{public}s", realPath, std::strerror(errno));
343             return;
344         }
345         ueventLoaded = true;
346 
347         char line[UTIL_LINE_SIZE];
348         while (f.getline(line, sizeof(line))) {
349             AddPropertyFromString(line);
350         }
351 
352         CheckInputProperties();
353     }
354 
CheckAcceludev_device355     bool CheckAccel(const BitVector &ev, const BitVector &abs, const BitVector &prop)
356     {
357         bool hasKeys = ev.CheckBit(EV_KEY);
358         bool has3dCoordinates = abs.CheckBit(ABS_X) && abs.CheckBit(ABS_Y) && abs.CheckBit(ABS_Z);
359         bool isAccelerometer = prop.CheckBit(INPUT_PROP_ACCELEROMETER);
360 
361         if (!hasKeys && has3dCoordinates) {
362             isAccelerometer = true;
363         }
364 
365         if (isAccelerometer) {
366             SetInputProperty("ID_INPUT_ACCELEROMETER");
367         }
368         return isAccelerometer;
369     }
370 
HasJoystickAxesOrButtonsudev_device371     bool HasJoystickAxesOrButtons(const BitVector &abs, const BitVector &key)
372     {
373         bool hasJoystickAxesOrButtons = false;
374         // Some mouses have so much buttons that they overflow in joystick range, ignore them
375         if (!key.CheckBit(BTN_JOYSTICK - 1)) {
376             for (int button = BTN_JOYSTICK; button < BTN_DIGI && !hasJoystickAxesOrButtons; button++) {
377                 hasJoystickAxesOrButtons = key.CheckBit(button);
378             }
379             for (int button = BTN_TRIGGER_HAPPY1; button <= BTN_TRIGGER_HAPPY40 && !hasJoystickAxesOrButtons;
380                 button++) {
381                 hasJoystickAxesOrButtons = key.CheckBit(button);
382             }
383             for (int button = BTN_DPAD_UP; button <= BTN_DPAD_RIGHT && !hasJoystickAxesOrButtons; button++) {
384                 hasJoystickAxesOrButtons = key.CheckBit(button);
385             }
386         }
387         for (int axis = ABS_RX; axis < ABS_PRESSURE && !hasJoystickAxesOrButtons; axis++) {
388             hasJoystickAxesOrButtons = abs.CheckBit(axis);
389         }
390         return hasJoystickAxesOrButtons;
391     }
392 
CheckPointingStickudev_device393     bool CheckPointingStick(const BitVector &prop)
394     {
395         if (prop.CheckBit(INPUT_PROP_POINTING_STICK)) {
396             SetInputProperty("ID_INPUT_POINTINGSTICK");
397             return true;
398         }
399         return false;
400     }
401 
CheckAndSetPropudev_device402     void CheckAndSetProp(std::string prop, const bool &flag)
403     {
404         if (flag) {
405             SetInputProperty(prop);
406             MMI_HILOGD("device has prop with %{public}s", prop.c_str());
407         }
408     }
409 
CheckMouseButtonudev_device410     void CheckMouseButton(const BitVector &key, bool &flag)
411     {
412         for (int button = BTN_MOUSE; button < BTN_JOYSTICK && !flag; button++) {
413             flag = key.CheckBit(button);
414         }
415     }
416 
UpdateProByKeyudev_device417     void UpdateProByKey(const BitVector &key, const bool &isDirect, bool &probablyTablet, bool &probablyTouchpad,
418         bool &probablyTouchscreen)
419     {
420         probablyTablet = key.CheckBit(BTN_STYLUS) || key.CheckBit(BTN_TOOL_PEN);
421         probablyTouchpad = key.CheckBit(BTN_TOOL_FINGER) && !key.CheckBit(BTN_TOOL_PEN) && !isDirect;
422         probablyTouchscreen = key.CheckBit(BTN_TOUCH) && isDirect;
423     }
424 
CheckMtCoordinatesudev_device425     bool CheckMtCoordinates(const BitVector &abs)
426     {
427         bool hasMtCoordinates = abs.CheckBit(ABS_MT_POSITION_X) && abs.CheckBit(ABS_MT_POSITION_Y);
428         /* unset hasMtCoordinates if devices claims to have all abs axis */
429         if (hasMtCoordinates && abs.CheckBit(ABS_MT_SLOT) && abs.CheckBit(ABS_MT_SLOT - 1)) {
430             hasMtCoordinates = false;
431         }
432         return hasMtCoordinates;
433     }
434 
UpdateProByStatusudev_device435     void UpdateProByStatus(const bool &isMouse, const bool &isTouchpad, const bool &isTouchscreen,
436         const bool &isJoystick, const bool &isTablet)
437     {
438         CheckAndSetProp("ID_INPUT_MOUSE", isMouse);
439         CheckAndSetProp("ID_INPUT_TOUCHPAD", isTouchpad);
440         CheckAndSetProp("ID_INPUT_TOUCHSCREEN", isTouchscreen);
441         CheckAndSetProp("ID_INPUT_JOYSTICK", isJoystick);
442         CheckAndSetProp("ID_INPUT_TABLET", isTablet);
443     }
444 
CheckPointersudev_device445     bool CheckPointers(const BitVector &ev, const BitVector &abs, const BitVector &key, const BitVector &rel,
446         const BitVector &prop)
447     {
448         bool isDirect = prop.CheckBit(INPUT_PROP_DIRECT);
449         bool hasAbsCoordinates = abs.CheckBit(ABS_X) && abs.CheckBit(ABS_Y);
450         bool hasRelCoordinates = ev.CheckBit(EV_REL) && rel.CheckBit(REL_X) && rel.CheckBit(REL_Y);
451         bool hasMtCoordinates = CheckMtCoordinates(abs);
452 
453         bool hasMouseButton = false;
454         CheckMouseButton(key, hasMouseButton);
455 
456         bool probablyTablet;
457         bool probablyTouchpad;
458         bool probablyTouchscreen;
459         UpdateProByKey(key, isDirect, probablyTablet, probablyTouchpad, probablyTouchscreen);
460         bool probablyJoystick = HasJoystickAxesOrButtons(abs, key);
461 
462         bool isTablet = false;
463         bool isMouse = false;
464         bool isTouchpad = false;
465         bool isTouchscreen = false;
466         bool isJoystick = false;
467         if (hasAbsCoordinates) {
468             if (probablyTablet) {
469                 isTablet = true;
470             } else if (probablyTouchpad) {
471                 isTouchpad = true;
472             } else if (hasMouseButton) {
473                 /* This path is taken by VMware's USB mouse, which has
474                  * absolute axes, but no touch/pressure button. */
475                 isMouse = true;
476             } else if (probablyTouchscreen) {
477                 isTouchscreen = true;
478             } else {
479                 isJoystick = probablyJoystick;
480             }
481         } else {
482             isJoystick = probablyJoystick;
483         }
484 
485         if (hasMtCoordinates) {
486             if (probablyTablet) {
487                 isTablet = true;
488             } else if (probablyTouchpad) {
489                 isTouchpad = true;
490             } else if (probablyTouchscreen) {
491                 isTouchscreen = true;
492             }
493         }
494 
495         /* mouse buttons and no axis */
496         if (!isTablet && !isTouchpad && !isJoystick && hasMouseButton && (hasRelCoordinates || !hasAbsCoordinates)) {
497             isMouse = true;
498         }
499 
500         UpdateProByStatus(isMouse, isTouchpad, isTouchscreen, isJoystick, isTablet);
501 
502         return isTablet || isMouse || isTouchpad || isTouchscreen || isJoystick || CheckPointingStick(prop);
503     }
504 
CheckKeysudev_device505     bool CheckKeys(const BitVector &ev, const BitVector &key)
506     {
507         if (!ev.CheckBit(EV_KEY)) {
508             return false;
509         }
510 
511         /* only consider KEY_* here, not BTN_* */
512         bool found = false;
513         for (int i = 0; i < BTN_MISC && !found; ++i) {
514             found = key.CheckBit(i);
515         }
516         /* If there are no keys in the lower block, check the higher blocks */
517         for (int i = KEY_OK; i < BTN_DPAD_UP && !found; ++i) {
518             found = key.CheckBit(i);
519         }
520         for (int i = KEY_ALS_TOGGLE; i < BTN_TRIGGER_HAPPY && !found; ++i) {
521             found = key.CheckBit(i);
522         }
523 
524         if (found) {
525             SetInputProperty("ID_INPUT_KEY");
526         }
527 
528         /* the first 32 bits are ESC, numbers, and Q to D; if we have all of
529          * those, consider it a full keyboard; do not test KEY_RESERVED, though */
530         bool isKeyboard = true;
531         for (int i = KEY_ESC; i < KEY_D && isKeyboard; i++) {
532             isKeyboard = key.CheckBit(i);
533         }
534         if (isKeyboard) {
535             SetInputProperty("ID_INPUT_KEYBOARD");
536         }
537 
538         return found || isKeyboard;
539     }
540 
SetInputPropertyudev_device541     void SetInputProperty(std::string prop)
542     {
543         AddProperty("ID_INPUT", "1");
544         AddProperty(std::move(prop), "1");
545     }
546 
CheckInputPropertiesudev_device547     void CheckInputProperties()
548     {
549         BitVector ev{ GetProperty("EV") };
550         BitVector abs{ GetProperty("ABS") };
551         BitVector key{ GetProperty("KEY") };
552         BitVector rel{ GetProperty("REL") };
553         BitVector prop{ GetProperty("PROP") };
554 
555         bool isPointer = CheckAccel(ev, abs, prop) || CheckPointers(ev, abs, key, rel, prop);
556         bool isKey = CheckKeys(ev, key);
557         /* Some evdev nodes have only a scrollwheel */
558         if (!isPointer && !isKey && ev.CheckBit(EV_REL) && (rel.CheckBit(REL_WHEEL) || rel.CheckBit(REL_HWHEEL))) {
559             SetInputProperty("ID_INPUT_KEY");
560         }
561         if (ev.CheckBit(EV_SW)) {
562             SetInputProperty("ID_INPUT_SWITCH");
563         }
564     }
565 
SetDevnodeudev_device566     void SetDevnode(std::string newDevnode)
567     {
568         if (newDevnode[0] != '/') {
569             newDevnode = "/dev/" + newDevnode;
570         }
571         AddProperty("DEVNAME", std::move(newDevnode));
572     }
573 
AddPropertyudev_device574     void AddProperty(std::string key, std::string value)
575     {
576         property_[std::move(key)] = std::move(value);
577     }
578 
GetSubsystemudev_device579     std::optional<std::string> GetSubsystem()
580     {
581         if (!subsystem_.has_value()) {
582             auto res = GetLinkValue("subsystem", syspath);
583             // read "subsystem" link
584             if (res.has_value()) {
585                 SetSubsystem(std::move(*res));
586                 return subsystem_;
587             }
588             subsystem_ = "";
589         }
590         return subsystem_;
591     }
592 
SetSubsystemudev_device593     void SetSubsystem(std::string newSubsystem)
594     {
595         subsystem_ = newSubsystem;
596         AddProperty("SUBSYSTEM", std::move(newSubsystem));
597     }
598 
599 private:
600     int refcount = 1;
601     std::string syspath;
602     std::string sysname;
603 
604     std::optional<udev_device *> parentDevice_;
605     std::optional<std::string> subsystem_;
606 
607     bool ueventLoaded = false;
608     std::unordered_map<std::string, std::string> property_;
609 };
610 
611 // C-style interface
612 
udev_new(void)613 udev *udev_new(void)
614 {
615     static udev instance{};
616     return &instance;
617 }
618 
619 udev *udev_unref([[maybe_unused]] udev *udev)
620 {
621     return nullptr;
622 }
623 
udev_device_ref(udev_device * device)624 udev_device *udev_device_ref(udev_device *device)
625 {
626     CHKPP(device);
627     device->Ref();
628     return device;
629 }
630 
udev_device_unref(udev_device * device)631 udev_device *udev_device_unref(udev_device *device)
632 {
633     CHKPP(device);
634     device->Unref();
635     return nullptr;
636 }
637 
udev_device_get_udev(udev_device * device)638 udev *udev_device_get_udev(udev_device *device)
639 {
640     CHKPP(device);
641     return udev_new();
642 }
643 
udev_device_new_from_syspath(udev * udev,const char * syspath)644 udev_device *udev_device_new_from_syspath(udev *udev, const char *syspath)
645 {
646     if (udev == nullptr || syspath == nullptr) {
647         errno = EINVAL;
648         return nullptr;
649     }
650     return udev_device::NewFromSyspath(syspath);
651 }
652 
udev_device_new_from_devnum(udev * udev,char type,dev_t devnum)653 udev_device *udev_device_new_from_devnum(udev *udev, char type, dev_t devnum)
654 {
655     if (udev == nullptr) {
656         errno = EINVAL;
657         return nullptr;
658     }
659     return udev_device::NewFromDevnum(type, devnum);
660 }
661 
udev_device_get_parent(udev_device * device)662 udev_device *udev_device_get_parent(udev_device *device)
663 {
664     if (device == nullptr) {
665         errno = EINVAL;
666         return nullptr;
667     }
668     return device->GetParent();
669 }
670 
udev_device_get_parent_with_subsystem_devtype(udev_device * device,const char * subsystem,const char * devtype)671 udev_device *udev_device_get_parent_with_subsystem_devtype(udev_device *device, const char *subsystem,
672     const char *devtype)
673 {
674     CHKPP(device);
675     if (subsystem == nullptr) {
676         errno = EINVAL;
677         return nullptr;
678     }
679     // Searching with specific devtype is not supported, since not used by libinput
680     CHKPP(devtype);
681     return device->GetParentWithSubsystem(subsystem);
682 }
683 
udev_device_get_syspath(udev_device * device)684 const char *udev_device_get_syspath(udev_device *device)
685 {
686     CHKPP(device);
687     return device->GetSyspath().c_str();
688 }
689 
udev_device_get_sysname(udev_device * device)690 const char *udev_device_get_sysname(udev_device *device)
691 {
692     CHKPP(device);
693     return device->GetSysname().c_str();
694 }
695 
udev_device_get_devnode(udev_device * device)696 const char *udev_device_get_devnode(udev_device *device)
697 {
698     CHKPP(device);
699     return device->GetDevnode().c_str();
700 }
701 
udev_device_get_is_initialized(udev_device * device)702 int udev_device_get_is_initialized(udev_device *device)
703 {
704     return (device != nullptr) ? static_cast<int>(device->IsInitialized()) : -1;
705 }
706 
udev_device_get_property_value(udev_device * device,const char * key)707 const char *udev_device_get_property_value(udev_device *device, const char *key)
708 {
709     CHKPP(device);
710     CHKPP(key);
711     std::string skey{ key };
712     if (!device->HasProperty(key)) {
713         return nullptr;
714     }
715     return device->GetProperty(key).c_str();
716 }
717