• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2023 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 <libudev.h>
30 #include <linux/input.h>
31 #include <unistd.h>
32 
33 #include "mmi_log.h"
34 
35 using namespace std::literals;
36 namespace {
37 constexpr OHOS::HiviewDFX::HiLogLabel LABEL = { LOG_CORE, OHOS::MMI::MMI_LOG_DOMAIN, "MmiLibudev" };
38 constexpr int UTIL_PATH_SIZE = 1024;
39 constexpr int UTIL_LINE_SIZE = 16384;
40 
StartsWith(std::string_view str,std::string_view prefix)41 bool StartsWith(std::string_view str, std::string_view prefix)
42 {
43     return str.size() >= prefix.size() && str.substr(0, prefix.size()) == prefix;
44 }
45 
ChopTail(std::string_view & str,char sep)46 bool ChopTail(std::string_view &str, char sep)
47 {
48     auto pos = str.rfind(sep);
49     if (pos == std::string_view::npos) {
50         return false;
51     }
52     str.remove_suffix(str.size() - pos);
53     return true;
54 }
55 
ResolveSymLink(const std::string & syspath)56 std::string ResolveSymLink(const std::string &syspath)
57 {
58     constexpr auto backStr = "../"sv;
59     char linkTarget[UTIL_PATH_SIZE];
60 
61     ssize_t len = readlink(syspath.c_str(), linkTarget, sizeof(linkTarget));
62     if (len <= 0 || len == static_cast<ssize_t>(sizeof(linkTarget))) {
63         return syspath;
64     }
65 
66     std::string_view tail{ linkTarget, len };
67     int back = 0;
68     for (; StartsWith(tail, backStr); back++) {
69         tail.remove_prefix(backStr.size());
70     }
71 
72     std::string_view base = syspath;
73     for (int i = 0; i <= back; i++) {
74         if (!ChopTail(base, '/')) {
75             return syspath;
76         }
77     }
78 
79     return std::string{ base }.append("/").append(tail);
80 }
81 
GetLinkValue(const std::string & slink,const std::string & syspath)82 std::optional<std::string> GetLinkValue(const std::string &slink, const std::string &syspath)
83 {
84     auto path = syspath + "/" + slink;
85 
86     char target[UTIL_PATH_SIZE];
87     ssize_t len = readlink(path.c_str(), target, sizeof(target));
88     if (len <= 0 || len == static_cast<ssize_t>(sizeof(target))) {
89         return std::nullopt;
90     }
91 
92     std::string_view result{ target, len };
93     auto pos = result.rfind('/');
94     if (pos == std::string_view::npos) {
95         return std::nullopt;
96     }
97     return std::string{ result.substr(pos + 1) };
98 }
99 
100 class BitVector {
101 public:
102     // This type depends on kernel definition
103     using val_t = unsigned long;
104 
105     // Input string is hexadecimal 64-bit numbers separated by spaces with high bit number first
BitVector(const std::string & str)106     explicit BitVector(const std::string &str)
107     {
108         std::istringstream ss{ str };
109         ss >> std::hex;
110         std::copy(std::istream_iterator<val_t>(ss), std::istream_iterator<val_t>(), std::back_inserter(bits_));
111         // Since numbers in string starts with high number we need to reverse vector to count numbers from low to high
112         std::reverse(bits_.begin(), bits_.end());
113     }
114 
CheckBit(size_t idx) const115     [[nodiscard]] bool CheckBit(size_t idx) const
116     {
117         auto vidx = idx / (sizeof(val_t) * CHAR_BIT);
118         auto bidx = idx % (sizeof(val_t) * CHAR_BIT);
119         if (vidx >= bits_.size()) {
120             return false;
121         }
122         return (bits_[vidx] & (1ULL << bidx)) != 0;
123     }
124 
125 private:
126     std::vector<val_t> bits_;
127 };
128 } // namespace
129 
130 struct udev {};
131 
132 struct udev_device {
133 public:
134     // Not copyable and not movable
135     udev_device(udev_device &) = delete;
136     udev_device(udev_device &&) = delete;
137     udev_device &operator = (udev_device &) = delete;
138     udev_device &operator = (udev_device &&) = delete;
139 
NewFromSyspathudev_device140     static udev_device *NewFromSyspath(const std::string &syspathParam)
141     {
142         CALL_DEBUG_ENTER;
143         // path starts in sys
144         if (!StartsWith(syspathParam, "/sys/") || syspathParam.back() == '/') {
145             errno = EINVAL;
146             return nullptr;
147         }
148 
149         // resolve possible symlink to real path
150         std::string path = ResolveSymLink(syspathParam);
151         if (StartsWith(path, "/sys/devices/")) {
152             // all "devices" require a "uevent" file
153             struct stat statbuf;
154             std::string filename = path + "/uevent";
155             if (stat(filename.c_str(), &statbuf) != 0) {
156                 return nullptr;
157             }
158         } else {
159             return nullptr;
160         }
161 
162         auto *inst = new udev_device;
163         inst->SetSyspath(std::move(path));
164 
165         return inst;
166     }
167 
NewFromDevnumudev_device168     static udev_device *NewFromDevnum(char type, dev_t devnum)
169     {
170         CALL_DEBUG_ENTER;
171         const char *typeStr = nullptr;
172 
173         if (type == 'b') {
174             typeStr = "block";
175         } else if (type == 'c') {
176             typeStr = "char";
177         } else {
178             errno = EINVAL;
179             return nullptr;
180         }
181 
182         // use /sys/dev/{block,char}/<maj>:<min> link
183         auto majStr = std::to_string(major(devnum));
184         auto minStr = std::to_string(minor(devnum));
185         return NewFromSyspath("/sys/dev/"s + typeStr + "/" + majStr + ":" + minStr);
186     }
187 
Refudev_device188     void Ref()
189     {
190         refcount++;
191     }
192 
Unrefudev_device193     void Unref()
194     {
195         if (--refcount <= 0) {
196             delete this;
197         }
198     }
199 
GetParentudev_device200     udev_device *GetParent()
201     {
202         if (!parentDevice_.has_value()) {
203             parentDevice_ = NewFromChild(this);
204         }
205         return *parentDevice_;
206     }
207 
GetSyspathudev_device208     const std::string &GetSyspath() const
209     {
210         return syspath;
211     }
212 
GetSysnameudev_device213     const std::string &GetSysname() const
214     {
215         return sysname;
216     }
217 
GetDevnodeudev_device218     const std::string &GetDevnode()
219     {
220         return GetProperty("DEVNAME");
221     }
222 
IsInitializedudev_device223     bool IsInitialized()
224     {
225         if (!ueventLoaded) {
226             ReadUeventFile();
227         }
228         return ueventLoaded;
229     }
230 
GetParentWithSubsystemudev_device231     udev_device *GetParentWithSubsystem(const std::string &subsystem)
232     {
233         CALL_DEBUG_ENTER;
234         udev_device *parent = GetParent();
235         while (parent != nullptr) {
236             auto parentSubsystem = parent->GetSubsystem();
237             if (parentSubsystem.has_value() && parentSubsystem.value() == subsystem) {
238                 break;
239             }
240             parent = parent->GetParent();
241         }
242 
243         if (parent == nullptr) {
244             errno = ENOENT;
245         }
246         return parent;
247     }
248 
HasPropertyudev_device249     bool HasProperty(const std::string &key)
250     {
251         if (!ueventLoaded) {
252             ReadUeventFile();
253         }
254         return property_.find(key) != property_.end();
255     }
256 
GetPropertyudev_device257     const std::string &GetProperty(const std::string &key)
258     {
259         if (!ueventLoaded) {
260             ReadUeventFile();
261         }
262         return property_[key];
263     }
264 
265 private:
266     udev_device() = default;
267 
~udev_deviceudev_device268     ~udev_device()
269     {
270         if (parentDevice_.has_value() && parentDevice_.value() != nullptr) {
271             parentDevice_.value()->Unref();
272         }
273     }
274 
NewFromChildudev_device275     static udev_device *NewFromChild(udev_device *child)
276     {
277         CALL_DEBUG_ENTER;
278         std::string_view path = child->GetSyspath();
279 
280         while (true) {
281             if (!ChopTail(path, '/')) {
282                 break;
283             }
284             udev_device *parent = NewFromSyspath(std::string{ path });
285             if (parent != nullptr) {
286                 return parent;
287             }
288         }
289 
290         return nullptr;
291     }
292 
SetSyspathudev_device293     void SetSyspath(std::string newSyspath)
294     {
295         CALL_DEBUG_ENTER;
296         syspath = std::move(newSyspath);
297 
298         AddProperty("DEVPATH", syspath.substr(0, "/sys"sv.size()));
299 
300         auto pos = syspath.rfind('/');
301         if (pos == std::string::npos) {
302             return;
303         }
304         sysname = syspath.substr(pos + 1);
305 
306         // some devices have '!' in their name, change that to '/'
307         for (char &c : sysname) {
308             if (c == '!') {
309                 c = '/';
310             }
311         }
312     }
313 
AddPropertyFromStringudev_device314     void AddPropertyFromString(const std::string &line)
315     {
316         auto pos = line.find('=');
317         if (pos == std::string::npos) {
318             return;
319         }
320         std::string key = line.substr(0, pos);
321         if (key == "DEVNAME") {
322             SetDevnode(line.substr(pos + 1));
323             return;
324         }
325         AddProperty(std::move(key), line.substr(pos + 1));
326     }
327 
ReadUeventFileudev_device328     void ReadUeventFile()
329     {
330         CALL_DEBUG_ENTER;
331         if (ueventLoaded) {
332             return;
333         }
334 
335         auto filename = syspath + "/uevent";
336         std::ifstream f(filename, std::ios_base::in);
337         if (!f.is_open()) {
338             MMI_HILOGE("ReadUeventFile(): path: %{public}s, error: %{public}s", filename.c_str(), std::strerror(errno));
339             return;
340         }
341         ueventLoaded = true;
342 
343         char line[UTIL_LINE_SIZE];
344         while (f.getline(line, sizeof(line))) {
345             AddPropertyFromString(line);
346         }
347 
348         CheckInputProperties();
349     }
350 
CheckAcceludev_device351     bool CheckAccel(const BitVector &ev, const BitVector &abs, const BitVector &prop)
352     {
353         bool hasKeys = ev.CheckBit(EV_KEY);
354         bool has3dCoordinates = abs.CheckBit(ABS_X) && abs.CheckBit(ABS_Y) && abs.CheckBit(ABS_Z);
355         bool isAccelerometer = prop.CheckBit(INPUT_PROP_ACCELEROMETER);
356 
357         if (!hasKeys && has3dCoordinates) {
358             isAccelerometer = true;
359         }
360 
361         if (isAccelerometer) {
362             SetInputProperty("ID_INPUT_ACCELEROMETER");
363         }
364         return isAccelerometer;
365     }
366 
HasJoystickAxesOrButtonsudev_device367     bool HasJoystickAxesOrButtons(const BitVector &abs, const BitVector &key)
368     {
369         bool hasJoystickAxesOrButtons = false;
370         // Some mouses have so much buttons that they overflow in joystick range, ignore them
371         if (!key.CheckBit(BTN_JOYSTICK - 1)) {
372             for (int button = BTN_JOYSTICK; button < BTN_DIGI && !hasJoystickAxesOrButtons; button++) {
373                 hasJoystickAxesOrButtons = key.CheckBit(button);
374             }
375             for (int button = BTN_TRIGGER_HAPPY1; button <= BTN_TRIGGER_HAPPY40 && !hasJoystickAxesOrButtons;
376                 button++) {
377                 hasJoystickAxesOrButtons = key.CheckBit(button);
378             }
379             for (int button = BTN_DPAD_UP; button <= BTN_DPAD_RIGHT && !hasJoystickAxesOrButtons; button++) {
380                 hasJoystickAxesOrButtons = key.CheckBit(button);
381             }
382         }
383         for (int axis = ABS_RX; axis < ABS_PRESSURE && !hasJoystickAxesOrButtons; axis++) {
384             hasJoystickAxesOrButtons = abs.CheckBit(axis);
385         }
386         return hasJoystickAxesOrButtons;
387     }
388 
CheckPointingStickudev_device389     bool CheckPointingStick(const BitVector &prop)
390     {
391         if (prop.CheckBit(INPUT_PROP_POINTING_STICK)) {
392             SetInputProperty("ID_INPUT_POINTINGSTICK");
393             return true;
394         }
395         return false;
396     }
397 
CheckAndSetPropudev_device398     void CheckAndSetProp(std::string prop, const bool &flag)
399     {
400         if (flag) {
401             SetInputProperty(prop);
402             MMI_HILOGD("device has prop with %{public}s", prop.c_str());
403         }
404     }
405 
CheckMouseButtonudev_device406     void CheckMouseButton(const BitVector &key, bool &flag)
407     {
408         for (int button = BTN_MOUSE; button < BTN_JOYSTICK && !flag; button++) {
409             flag = key.CheckBit(button);
410         }
411     }
412 
UpdateProByKeyudev_device413     void UpdateProByKey(const BitVector &key, const bool &isDirect, bool &probablyTablet, bool &probablyTouchpad,
414         bool &probablyTouchscreen)
415     {
416         probablyTablet = key.CheckBit(BTN_STYLUS) || key.CheckBit(BTN_TOOL_PEN);
417         probablyTouchpad = key.CheckBit(BTN_TOOL_FINGER) && !key.CheckBit(BTN_TOOL_PEN) && !isDirect;
418         probablyTouchscreen = key.CheckBit(BTN_TOUCH) && isDirect;
419     }
420 
CheckMtCoordinatesudev_device421     bool CheckMtCoordinates(const BitVector &abs)
422     {
423         bool hasMtCoordinates = abs.CheckBit(ABS_MT_POSITION_X) && abs.CheckBit(ABS_MT_POSITION_Y);
424         /* unset hasMtCoordinates if devices claims to have all abs axis */
425         if (hasMtCoordinates && abs.CheckBit(ABS_MT_SLOT) && abs.CheckBit(ABS_MT_SLOT - 1)) {
426             hasMtCoordinates = false;
427         }
428         return hasMtCoordinates;
429     }
430 
UpdateProByStatusudev_device431     void UpdateProByStatus(const bool &isMouse, const bool &isTouchpad, const bool &isTouchscreen,
432         const bool &isJoystick, const bool &isTablet)
433     {
434         CheckAndSetProp("ID_INPUT_MOUSE", isMouse);
435         CheckAndSetProp("ID_INPUT_TOUCHPAD", isTouchpad);
436         CheckAndSetProp("ID_INPUT_TOUCHSCREEN", isTouchscreen);
437         CheckAndSetProp("ID_INPUT_JOYSTICK", isJoystick);
438         CheckAndSetProp("ID_INPUT_TABLET", isTablet);
439     }
440 
CheckPointersudev_device441     bool CheckPointers(const BitVector &ev, const BitVector &abs, const BitVector &key, const BitVector &rel,
442         const BitVector &prop)
443     {
444         bool isDirect = prop.CheckBit(INPUT_PROP_DIRECT);
445         bool hasAbsCoordinates = abs.CheckBit(ABS_X) && abs.CheckBit(ABS_Y);
446         bool hasRelCoordinates = ev.CheckBit(EV_REL) && rel.CheckBit(REL_X) && rel.CheckBit(REL_Y);
447         bool hasMtCoordinates = CheckMtCoordinates(abs);
448 
449         bool hasMouseButton = false;
450         CheckMouseButton(key, hasMouseButton);
451 
452         bool probablyTablet;
453         bool probablyTouchpad;
454         bool probablyTouchscreen;
455         UpdateProByKey(key, isDirect, probablyTablet, probablyTouchpad, probablyTouchscreen);
456         bool probablyJoystick = HasJoystickAxesOrButtons(abs, key);
457 
458         bool isTablet = false;
459         bool isMouse = false;
460         bool isTouchpad = false;
461         bool isTouchscreen = false;
462         bool isJoystick = false;
463         if (hasAbsCoordinates) {
464             if (probablyTablet) {
465                 isTablet = true;
466             } else if (probablyTouchpad) {
467                 isTouchpad = true;
468             } else if (hasMouseButton) {
469                 /* This path is taken by VMware's USB mouse, which has
470                  * absolute axes, but no touch/pressure button. */
471                 isMouse = true;
472             } else if (probablyTouchscreen) {
473                 isTouchscreen = true;
474             } else {
475                 isJoystick = probablyJoystick;
476             }
477         } else {
478             isJoystick = probablyJoystick;
479         }
480 
481         if (hasMtCoordinates) {
482             if (probablyTablet) {
483                 isTablet = true;
484             } else if (probablyTouchpad) {
485                 isTouchpad = true;
486             } else if (probablyTouchscreen) {
487                 isTouchscreen = true;
488             }
489         }
490 
491         /* mouse buttons and no axis */
492         if (!isTablet && !isTouchpad && !isJoystick && hasMouseButton && (hasRelCoordinates || !hasAbsCoordinates)) {
493             isMouse = true;
494         }
495 
496         UpdateProByStatus(isMouse, isTouchpad, isTouchscreen, isJoystick, isTablet);
497 
498         return isTablet || isMouse || isTouchpad || isTouchscreen || isJoystick || CheckPointingStick(prop);
499     }
500 
CheckKeysudev_device501     bool CheckKeys(const BitVector &ev, const BitVector &key)
502     {
503         if (!ev.CheckBit(EV_KEY)) {
504             return false;
505         }
506 
507         /* only consider KEY_* here, not BTN_* */
508         bool found = false;
509         for (int i = 0; i < BTN_MISC && !found; ++i) {
510             found = key.CheckBit(i);
511         }
512         /* If there are no keys in the lower block, check the higher blocks */
513         for (int i = KEY_OK; i < BTN_DPAD_UP && !found; ++i) {
514             found = key.CheckBit(i);
515         }
516         for (int i = KEY_ALS_TOGGLE; i < BTN_TRIGGER_HAPPY && !found; ++i) {
517             found = key.CheckBit(i);
518         }
519 
520         if (found) {
521             SetInputProperty("ID_INPUT_KEY");
522         }
523 
524         /* the first 32 bits are ESC, numbers, and Q to D; if we have all of
525          * those, consider it a full keyboard; do not test KEY_RESERVED, though */
526         bool isKeyboard = true;
527         for (int i = KEY_ESC; i < KEY_D && isKeyboard; i++) {
528             isKeyboard = key.CheckBit(i);
529         }
530         if (isKeyboard) {
531             SetInputProperty("ID_INPUT_KEYBOARD");
532         }
533 
534         return found || isKeyboard;
535     }
536 
SetInputPropertyudev_device537     void SetInputProperty(std::string prop)
538     {
539         AddProperty("ID_INPUT", "1");
540         AddProperty(std::move(prop), "1");
541     }
542 
CheckInputPropertiesudev_device543     void CheckInputProperties()
544     {
545         CALL_DEBUG_ENTER;
546         BitVector ev{ GetProperty("EV") };
547         BitVector abs{ GetProperty("ABS") };
548         BitVector key{ GetProperty("KEY") };
549         BitVector rel{ GetProperty("REL") };
550         BitVector prop{ GetProperty("PROP") };
551 
552         bool isPointer = CheckAccel(ev, abs, prop) || CheckPointers(ev, abs, key, rel, prop);
553         bool isKey = CheckKeys(ev, key);
554         /* Some evdev nodes have only a scrollwheel */
555         if (!isPointer && !isKey && ev.CheckBit(EV_REL) && (rel.CheckBit(REL_WHEEL) || rel.CheckBit(REL_HWHEEL))) {
556             SetInputProperty("ID_INPUT_KEY");
557         }
558         if (ev.CheckBit(EV_SW)) {
559             SetInputProperty("ID_INPUT_SWITCH");
560         }
561     }
562 
SetDevnodeudev_device563     void SetDevnode(std::string newDevnode)
564     {
565         if (newDevnode[0] != '/') {
566             newDevnode = "/dev/" + newDevnode;
567         }
568         AddProperty("DEVNAME", std::move(newDevnode));
569     }
570 
AddPropertyudev_device571     void AddProperty(std::string key, std::string value)
572     {
573         property_[std::move(key)] = std::move(value);
574     }
575 
GetSubsystemudev_device576     std::optional<std::string> GetSubsystem()
577     {
578         CALL_DEBUG_ENTER;
579         if (!subsystem_.has_value()) {
580             auto res = GetLinkValue("subsystem", syspath);
581             // read "subsystem" link
582             if (res.has_value()) {
583                 SetSubsystem(std::move(*res));
584                 return subsystem_;
585             }
586             subsystem_ = "";
587         }
588         return subsystem_;
589     }
590 
SetSubsystemudev_device591     void SetSubsystem(std::string newSubsystem)
592     {
593         subsystem_ = newSubsystem;
594         AddProperty("SUBSYSTEM", std::move(newSubsystem));
595     }
596 
597 private:
598     int refcount = 1;
599     std::string syspath;
600     std::string sysname;
601 
602     std::optional<udev_device *> parentDevice_;
603     std::optional<std::string> subsystem_;
604 
605     bool ueventLoaded = false;
606     std::unordered_map<std::string, std::string> property_;
607 };
608 
609 // C-style interface
610 
udev_new(void)611 udev *udev_new(void)
612 {
613     static udev instance{};
614     return &instance;
615 }
616 
udev_unref(udev * udev)617 udev *udev_unref([[maybe_unused]] udev *udev)
618 {
619     return nullptr;
620 }
621 
udev_device_ref(udev_device * device)622 udev_device *udev_device_ref(udev_device *device)
623 {
624     if (device == nullptr) {
625         return nullptr;
626     }
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     if (device == nullptr) {
634         return nullptr;
635     }
636     device->Unref();
637     return nullptr;
638 }
639 
udev_device_get_udev(udev_device * device)640 udev *udev_device_get_udev(udev_device *device)
641 {
642     if (device == nullptr) {
643         return nullptr;
644     }
645     return udev_new();
646 }
647 
udev_device_new_from_syspath(udev * udev,const char * syspath)648 udev_device *udev_device_new_from_syspath(udev *udev, const char *syspath)
649 {
650     if (udev == nullptr || syspath == nullptr) {
651         errno = EINVAL;
652         return nullptr;
653     }
654     return udev_device::NewFromSyspath(syspath);
655 }
656 
udev_device_new_from_devnum(udev * udev,char type,dev_t devnum)657 udev_device *udev_device_new_from_devnum(udev *udev, char type, dev_t devnum)
658 {
659     if (udev == nullptr) {
660         errno = EINVAL;
661         return nullptr;
662     }
663     return udev_device::NewFromDevnum(type, devnum);
664 }
665 
udev_device_get_parent(udev_device * device)666 udev_device *udev_device_get_parent(udev_device *device)
667 {
668     if (device == nullptr) {
669         errno = EINVAL;
670         return nullptr;
671     }
672     return device->GetParent();
673 }
674 
udev_device_get_parent_with_subsystem_devtype(udev_device * device,const char * subsystem,const char * devtype)675 udev_device *udev_device_get_parent_with_subsystem_devtype(udev_device *device, const char *subsystem,
676     const char *devtype)
677 {
678     if (device == nullptr) {
679         return nullptr;
680     }
681     if (subsystem == nullptr) {
682         errno = EINVAL;
683         return nullptr;
684     }
685     // Searching with specific devtype is not supported, since not used by libinput
686     if (devtype != nullptr) {
687         return nullptr;
688     }
689     return device->GetParentWithSubsystem(subsystem);
690 }
691 
udev_device_get_syspath(udev_device * device)692 const char *udev_device_get_syspath(udev_device *device)
693 {
694     if (device == nullptr) {
695         return nullptr;
696     }
697     return device->GetSyspath().c_str();
698 }
699 
udev_device_get_sysname(udev_device * device)700 const char *udev_device_get_sysname(udev_device *device)
701 {
702     if (device == nullptr) {
703         return nullptr;
704     }
705     return device->GetSysname().c_str();
706 }
707 
udev_device_get_devnode(udev_device * device)708 const char *udev_device_get_devnode(udev_device *device)
709 {
710     if (device == nullptr) {
711         return nullptr;
712     }
713     return device->GetDevnode().c_str();
714 }
715 
udev_device_get_is_initialized(udev_device * device)716 int udev_device_get_is_initialized(udev_device *device)
717 {
718     return (device != nullptr) ? static_cast<int>(device->IsInitialized()) : -1;
719 }
720 
udev_device_get_property_value(udev_device * device,const char * key)721 const char *udev_device_get_property_value(udev_device *device, const char *key)
722 {
723     if (device == nullptr || key == nullptr) {
724         return nullptr;
725     }
726     std::string skey{ key };
727     if (!device->HasProperty(key)) {
728         return nullptr;
729     }
730     return device->GetProperty(key).c_str();
731 }
732