Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/dom/webserial/   (Firefox Browser Version 153.0.1©)  Datei vom 27.6.2026 mit Größe 51 kB image not shown  

Quelle  PosixSerialPlatformService.cpp

  Sprache: C
 

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


#include "PosixSerialPlatformService.h"

#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>

#include "SerialLogging.h"
#include "mozilla/AsyncPlatformPipes.h"
#include "mozilla/Maybe.h"
#include "mozilla/Result.h"
#include "mozilla/ResultVariant.h"
#include "mozilla/SyncRunnable.h"
#include "nsDirectoryServiceDefs.h"
#include "nsIFile.h"
#include "nsString.h"
#include "nsThreadUtils.h"

#ifdef XP_MACOSX
#  include <CoreFoundation/CFNumber.h>
#  include <IOKit/IOKitLib.h>
#  include <IOKit/serial/IOSerialKeys.h>
#  include <IOKit/serial/ioss.h>
#  include <IOKit/usb/IOUSBLib.h>
#  ifndef kIOMainPortDefault
#    define kIOMainPortDefault kIOMasterPortDefault
#  endif
#endif

#ifdef TCGETS2
// Support non-standard baud rates through TCGETS2/TCSETS2
#  define LINUX_NSTD_BAUD 1
#endif

#ifdef LINUX_NSTD_BAUD
#  include <asm/termbits.h>
#else
#  include <termios.h>
#endif

#ifdef XP_LINUX
#  include <linux/serial.h>
#endif

namespace mozilla::dom {

constexpr int kPollTimeoutMs = 100;
// Maximum total time to spend in a single Write() call before giving up.
// Prevents indefinite blocking when a serial port's buffer is full (e.g.
// due to flow control), which would block all other serial ports.
constexpr int kWriteTimeoutMs = 5000;
#ifdef XP_MACOSX
constexpr size_t kDeviceNameBufferSize = 256;
#endif

// Returns Ok if aDevpath refers to a real serial device by attempting
// to open it and issuing a TIOCMGET ioctl, and errno otherwise.
// This filters out phantom ttyS* entries and non-serial tty devices.
static Result<Ok, int> IsRealSerialPort(const char* aDevpath) {
  int fd = open(aDevpath, O_RDWR | O_NONBLOCK | O_NOCTTY);
  if (fd < 0) {
    int openErrno = errno;
    MOZ_LOG(gWebSerialLog, LogLevel::Debug,
            ("IsRealSerialPort: open(%s, O_RDWR|O_NONBLOCK|O_NOCTTY) failed: "
             "errno=%d (%s)",
             aDevpath, openErrno, strerror(openErrno)));
    return Err(openErrno);
  }
  int status;
  bool isReal = ioctl(fd, TIOCMGET, &status) == 0;
  // Capture errno before close(), which may overwrite it.
  int ioctlErrno = errno;
  close(fd);
  if (isReal) {
    MOZ_LOG(gWebSerialLog, LogLevel::Debug,
            ("IsRealSerialPort: %s accepted (TIOCMGET status=0x%x)", aDevpath,
             status));
    return Ok();
  }
  MOZ_LOG(gWebSerialLog, LogLevel::Debug,
          ("IsRealSerialPort: TIOCMGET on %s failed: errno=%d (%s)", aDevpath,
           ioctlErrno, strerror(ioctlErrno)));
  return Err(ioctlErrno);
}

#ifdef XP_MACOSX
// macOS exposes built-in serial ports used for WiFi debugging and kernel
// debugging via IOKit. They are not useful targets for WebSerial, so
// hide them from enumeration. Bluetooth ports are intentionally not filtered.
static bool IsMacOSSystemSerialPort(const char* aDevicePath) {
  static constexpr const char* kSystemPortPaths[] = {
      "/dev/tty.wlan-debug",
      "/dev/tty.debug-console",
  };
  for (const char* p : kSystemPortPaths) {
    if (strcmp(aDevicePath, p) == 0) {
      return true;
    }
  }
  return false;
}
#endif

PosixSerialPlatformService::PosixSerialPlatformService()
#ifdef XP_LINUX
    : mMonitor(nullptr),
      mMonitorSourceID(0)
#elif defined(XP_MACOSX)
    : mNotificationPort(nullptr),
      mAddedIterator(0),
      mRemovedIterator(0)
#endif
{
  MOZ_LOG(gWebSerialLog, LogLevel::Debug,
          ("PosixSerialPlatformService[%p] created", this));
}

PosixSerialPlatformService::~PosixSerialPlatformService() {
  MOZ_LOG(gWebSerialLog, LogLevel::Debug,
          ("PosixSerialPlatformService[%p] destroyed", this));
}

nsresult PosixSerialPlatformService::Init() { return StartMonitoring(); }

void PosixSerialPlatformService::Shutdown() {
  if (IsShutdown()) {
    return;
  }
  MOZ_LOG(gWebSerialLog, LogLevel::Info,
          ("PosixSerialPlatformService[%p]::Shutdown (closing %u open ports)",
           this, mOpenPorts.Count()));

  SerialPlatformService::Shutdown();

#ifdef XP_LINUX
  if (mMonitorSourceID) {
    g_source_remove(mMonitorSourceID);
    mMonitorSourceID = 0;
  }
#elif defined(XP_MACOSX)
  if (mAddedIterator) {
    IOObjectRelease(mAddedIterator);
    mAddedIterator = 0;
  }

  if (mRemovedIterator) {
    IOObjectRelease(mRemovedIterator);
    mRemovedIterator = 0;
  }

  if (mNotificationPort) {
    CFRunLoopSourceRef runLoopSource =
        IONotificationPortGetRunLoopSource(mNotificationPort);
    if (runLoopSource) {
      CFRunLoopRemoveSource(CFRunLoopGetMain(), runLoopSource,
                            kCFRunLoopDefaultMode);
      MOZ_LOG(gWebSerialLog, LogLevel::Debug,
              ("PosixSerialPlatformService[%p]::Shutdown removed run "
               "loop source from main run loop",
               this));
    }
    IONotificationPortDestroy(mNotificationPort);
    mNotificationPort = nullptr;
  }
#endif

  RefPtr<PosixSerialPlatformService> self = this;
  SyncRunnable::DispatchToThread(
      IOThread(), NS_NewRunnableFunction(
                      "PosixSerialPlatformService::Shutdown:IOCleanup", [self] {
                        self->mOpenPorts.Clear();
#ifdef XP_LINUX
                        if (self->mMonitor && self->mUdevLib) {
                          self->mUdevLib->udev_monitor_unref(self->mMonitor);
                          self->mMonitor = nullptr;
                        }
                        self->mUdevLib = nullptr;
#endif
                      }));

  MOZ_LOG(gWebSerialLog, LogLevel::Info,
          ("PosixSerialPlatformService[%p]::Shutdown complete", this));
}

nsresult PosixSerialPlatformService::EnumeratePortsImpl(
    SerialPortList& aPorts, bool* aLikelyAccessDenied) {
  aPorts.Clear();

  MOZ_LOG(
      gWebSerialLog, LogLevel::Debug,
      ("PosixSerialPlatformService[%p]::EnumeratePorts starting enumeration",
       this));

#ifdef XP_MACOSX
  io_iterator_t serialPortIterator;
  CFMutableDictionaryRef classesToMatch =
      IOServiceMatching(kIOSerialBSDServiceValue);

  if (classesToMatch == nullptr) {
    MOZ_LOG(gWebSerialLog, LogLevel::Error,
            ("PosixSerialPlatformService[%p]::EnumeratePorts IOServiceMatching "
             "failed",
             this));
    return NS_ERROR_FAILURE;
  }

  // The call to IOServiceGetMatchingServices consumes a ref to classesToMatch.
  if (IOServiceGetMatchingServices(kIOMainPortDefault, classesToMatch,
                                   &serialPortIterator) != KERN_SUCCESS) {
    MOZ_LOG(gWebSerialLog, LogLevel::Error,
            ("PosixSerialPlatformService[%p]::EnumeratePorts "
             "IOServiceGetMatchingServices failed",
             this));
    return NS_ERROR_FAILURE;
  }

  io_object_t serialService;
  while ((serialService = IOIteratorNext(serialPortIterator))) {
    IPCSerialPortInfo info;
    if (ExtractDeviceInfo(serialService, info)) {
      MOZ_LOG(gWebSerialLog, LogLevel::Debug,
              ("PosixSerialPlatformService[%p]::EnumeratePorts found port: "
               "path=%s, friendlyName=%s, VID=0x%04x, PID=0x%04x",
               this, NS_ConvertUTF16toUTF8(info.path()).get(),
               NS_ConvertUTF16toUTF8(info.friendlyName()).get(),
               info.usbVendorId().valueOr(0), info.usbProductId().valueOr(0)));

      aPorts.AppendElement(info);
    }

    IOObjectRelease(serialService);
  }

  IOObjectRelease(serialPortIterator);

#elif defined(XP_LINUX)
  // Tracks whether every device we failed to access during udev enumeration
  // failed with EACCES. Combined with an empty port list below, this signals
  // the user likely lacks permission to access serial ports (e.g. is not in
  // the 'dialout' group, or a Snap/Flatpak sandbox blocks access).
  enum ErrorKind : uint8_t {
    eNone = 0,
    eAccessDenied = 1 << 0,
    eOther = 1 << 1
  };
  ErrorKind errors = ErrorKind::eNone;

  // Use an IIFE to avoid excessive nesting
  [&]() {
    if (!mUdevLib) {
      MOZ_LOG(gWebSerialLog, LogLevel::Warning,
              ("PosixSerialPlatformService[%p]::EnumeratePorts udev not "
               "available, falling back to /dev scan",
               this));
      return;
    }

    udev_enumerate* enumerate = mUdevLib->udev_enumerate_new(mUdevLib->udev);
    if (!enumerate) {
      MOZ_LOG(gWebSerialLog, LogLevel::Error,
              ("PosixSerialPlatformService[%p]::EnumeratePorts "
               "udev_enumerate_new failed",
               this));
      return;
    }
    auto cleanupEnum =
        MakeScopeExit([&]() { mUdevLib->udev_enumerate_unref(enumerate); });

    if (mUdevLib->udev_enumerate_add_match_subsystem(enumerate, "tty") < 0 ||
        mUdevLib->udev_enumerate_scan_devices(enumerate) < 0) {
      MOZ_LOG(gWebSerialLog, LogLevel::Error,
              ("PosixSerialPlatformService[%p]::EnumeratePorts "
               "udev enumerate setup failed",
               this));
      return;
    }

    for (udev_list_entry* entry =
             mUdevLib->udev_enumerate_get_list_entry(enumerate);
         entry; entry = mUdevLib->udev_list_entry_get_next(entry)) {
      const char* syspath = mUdevLib->udev_list_entry_get_name(entry);
      if (!syspath) {
        MOZ_LOG(gWebSerialLog, LogLevel::Warning,
                ("PosixSerialPlatformService[%p]::EnumeratePorts "
                 "udev_list_entry_get_name returned null",
                 this));
        continue;
      }

      MOZ_LOG(gWebSerialLog, LogLevel::Debug,
              ("PosixSerialPlatformService[%p]::EnumeratePorts examining "
               "syspath=%s",
               this, syspath));

      udev_device* dev =
          mUdevLib->udev_device_new_from_syspath(mUdevLib->udev, syspath);
      if (!dev) {
        MOZ_LOG(gWebSerialLog, LogLevel::Warning,
                ("PosixSerialPlatformService[%p]::EnumeratePorts "
                 "udev_device_new_from_syspath failed for %s",
                 this, syspath));
        continue;
      }
      auto cleanupDev =
          MakeScopeExit([&]() { mUdevLib->udev_device_unref(dev); });

      const char* devnode = mUdevLib->udev_device_get_devnode(dev);
      if (!devnode) {
        MOZ_LOG(gWebSerialLog, LogLevel::Debug,
                ("PosixSerialPlatformService[%p]::EnumeratePorts "
                 "no devnode for syspath=%s",
                 this, syspath));
        continue;
      }

      auto isReal = IsRealSerialPort(devnode);
      if (isReal.isErr()) {
        int err = isReal.unwrapErr();
        // ENOTTY means this device is a kind of tty device
        // that doesn't support serial operations at all
        // (like /dev/ptmx), so skip over it for this calculation.
        if (err != ENOTTY) {
          errors = static_cast<ErrorKind>(
              errors |
              ((err == EACCES) ? ErrorKind::eAccessDenied : ErrorKind::eOther));
        }
        MOZ_LOG(gWebSerialLog, LogLevel::Verbose,
                ("PosixSerialPlatformService[%p]::EnumeratePorts "
                 "rejecting device devnode=%s, errors=%d",
                 this, devnode, static_cast<int>(errors)));
        MOZ_LOG(gWebSerialLog, LogLevel::Debug,
                ("PosixSerialPlatformService[%p]::EnumeratePorts "
                 "rejecting device devnode=%s (not a real serial port)",
                 this, devnode));
        continue;
      }

      IPCSerialPortInfo info;
      PopulatePortInfoFromUdev(dev, devnode, info);

      MOZ_LOG(gWebSerialLog, LogLevel::Debug,
              ("PosixSerialPlatformService[%p]::EnumeratePorts found port: "
               "path=%s, friendlyName=%s, VID=0x%04x, PID=0x%04x",
               this, NS_ConvertUTF16toUTF8(info.path()).get(),
               NS_ConvertUTF16toUTF8(info.friendlyName()).get(),
               info.usbVendorId().valueOr(0), info.usbProductId().valueOr(0)));

      aPorts.AppendElement(info);
    }
  }();

  // Also scan /dev/ directly for onboard serial ports that may not appear
  // in the udev enumeration (e.g. on minimal or embedded Linux systems).
  DIR* devDir = opendir("/dev");
  if (devDir) {
    auto cleanupDir = MakeScopeExit([&]() { closedir(devDir); });

    while (struct dirent* ent = readdir(devDir)) {
      nsAutoCString devpath("/dev/");
      devpath.Append(ent->d_name);

      // Skip if already found by udev enumeration.
      NS_ConvertUTF8toUTF16 devpathUtf16(devpath);
      bool alreadyFound = false;
      for (const auto& existing : aPorts) {
        if (existing.path() == devpathUtf16) {
          alreadyFound = true;
          break;
        }
      }
      if (alreadyFound) {
        continue;
      }

      if (IsRealSerialPort(devpath.get()).isErr()) {
        continue;
      }

      MOZ_LOG(gWebSerialLog, LogLevel::Debug,
              ("PosixSerialPlatformService[%p]::EnumeratePorts found onboard "
               "port from /dev scan: %s",
               this, devpath.get()));

      IPCSerialPortInfo info;
      bool haveSetInfo = false;
      // Try udev lookup for richer metadata (product name, vendor/product IDs).
      nsAutoCString syspath("/sys/class/tty/");
      syspath.Append(ent->d_name);
      udev_device* dev = mUdevLib ? mUdevLib->udev_device_new_from_syspath(
                                        mUdevLib->udev, syspath.get())
                                  : nullptr;
      if (dev) {
        PopulatePortInfoFromUdev(dev, devpath.get(), info);
        mUdevLib->udev_device_unref(dev);
        haveSetInfo = true;
      }
      if (!haveSetInfo) {
        info.id() = devpathUtf16;
        info.path() = devpathUtf16;
        info.friendlyName() =
            NS_ConvertUTF8toUTF16(nsDependentCString(ent->d_name));
      }
      aPorts.AppendElement(info);
    }
  }

  // Only flag a likely permission problem when we found no ports at all and
  // every udev access failure was EACCES. If any port enumerated, the user
  // clearly has access, so unrelated EACCES errors (e.g. on /dev/tty0) are
  // ignored.
  if (aLikelyAccessDenied) {
    *aLikelyAccessDenied =
        aPorts.IsEmpty() && (errors == ErrorKind::eAccessDenied);
  }
#endif

  MOZ_LOG(gWebSerialLog, LogLevel::Debug,
          ("PosixSerialPlatformService[%p]::EnumeratePorts found %zu ports",
           this, aPorts.Length()));

  return NS_OK;
}

int PosixSerialPlatformService::FindPortFd(const nsString& aPortId) {
  AssertIsOnIOThread();
  if (auto entry = mOpenPorts.Lookup(aPortId)) {
    return entry.Data().get();
  }
  return -1;
}

// Returns whether the conversion succeeded
static bool ConvertBaudRate(uint32_t aBaudRate, speed_t& aSpeed) {
#define BAUDRATE_TO_SPEED_CASE(x) \
  case x:                         \
    aSpeed = B##x;                \
    return true;

  switch (aBaudRate) {
    BAUDRATE_TO_SPEED_CASE(0)
    BAUDRATE_TO_SPEED_CASE(50)
    BAUDRATE_TO_SPEED_CASE(75)
    BAUDRATE_TO_SPEED_CASE(110)
    BAUDRATE_TO_SPEED_CASE(134)
    BAUDRATE_TO_SPEED_CASE(150)
    BAUDRATE_TO_SPEED_CASE(200)
    BAUDRATE_TO_SPEED_CASE(300)
    BAUDRATE_TO_SPEED_CASE(600)
    BAUDRATE_TO_SPEED_CASE(1200)
    BAUDRATE_TO_SPEED_CASE(1800)
    BAUDRATE_TO_SPEED_CASE(2400)
    BAUDRATE_TO_SPEED_CASE(4800)
    BAUDRATE_TO_SPEED_CASE(9600)
    BAUDRATE_TO_SPEED_CASE(19200)
    BAUDRATE_TO_SPEED_CASE(38400)
    BAUDRATE_TO_SPEED_CASE(57600)
    BAUDRATE_TO_SPEED_CASE(115200)
#ifdef B230400
    BAUDRATE_TO_SPEED_CASE(230400)
#endif
#ifdef B460800
    BAUDRATE_TO_SPEED_CASE(460800)
#endif
#ifdef B576000
    BAUDRATE_TO_SPEED_CASE(576000)
#endif
#ifdef B921600
    BAUDRATE_TO_SPEED_CASE(921600)
#endif
  }
#undef BAUDRATE_TO_SPEED_CASE

  return false;
}

nsresult PosixSerialPlatformService::ConfigurePort(
    int aFd, const IPCSerialOptions& aOptions) {
  MOZ_LOG(gWebSerialLog, LogLevel::Debug,
          ("PosixSerialPlatformService[%p]::ConfigurePort fd=%d, baudRate=%u, "
           "dataBits=%u, stopBits=%u, parity=%u, flowControl=%u",
           this, aFd, aOptions.baudRate(), aOptions.dataBits(),
           aOptions.stopBits(), static_cast<uint8_t>(aOptions.parity()),
           static_cast<uint8_t>(aOptions.flowControl())));

#ifdef LINUX_NSTD_BAUD
  struct termios2 tty;
  if (ioctl(aFd, TCGETS2, &tty) < 0) {
#else
  struct termios tty;
  if (tcgetattr(aFd, &tty) != 0) {
#endif
    MOZ_LOG(gWebSerialLog, LogLevel::Error,
            ("PosixSerialPlatformService[%p]::ConfigurePort tcgetattr/ioctl "
             "failed: "
             "errno=%d",
             this, errno));
    return NS_ERROR_FAILURE;
  }

  tty.c_cflag |= (CLOCAL | CREAD);
  tty.c_cflag &= ~HUPCL;

  tty.c_cflag &= ~CSIZE;
  switch (aOptions.dataBits()) {
    case 7:
      tty.c_cflag |= CS7;
      break;
    case 8:
      tty.c_cflag |= CS8;
      break;
    default:
      MOZ_LOG(
          gWebSerialLog, LogLevel::Error,
          ("PosixSerialPlatformService[%p]::ConfigurePort invalid dataBits: %u",
           this, aOptions.dataBits()));
      return NS_ERROR_INVALID_ARG;
  }

  switch (aOptions.stopBits()) {
    case 1:
      tty.c_cflag &= ~CSTOPB;
      break;
    case 2:
      tty.c_cflag |= CSTOPB;
      break;
    default:
      MOZ_LOG(
          gWebSerialLog, LogLevel::Error,
          ("PosixSerialPlatformService[%p]::ConfigurePort invalid stopBits: %u",
           this, aOptions.stopBits()));
      return NS_ERROR_INVALID_ARG;
  }

  switch (aOptions.parity()) {
    case ParityType::None:
      tty.c_cflag &= ~PARENB;
      tty.c_iflag |= IGNPAR;
      tty.c_iflag &= ~INPCK;
      break;
    case ParityType::Even:
      tty.c_cflag |= PARENB;
      tty.c_cflag &= ~PARODD;
      tty.c_iflag &= ~IGNPAR;
      tty.c_iflag |= INPCK;
      break;
    case ParityType::Odd:
      tty.c_cflag |= (PARENB | PARODD);
      tty.c_iflag &= ~IGNPAR;
      tty.c_iflag |= INPCK;
      break;
  }

  switch (aOptions.flowControl()) {
    case FlowControlType::None:
      tty.c_cflag &= ~CRTSCTS;
      break;
    case FlowControlType::Hardware:
      tty.c_cflag |= CRTSCTS;
      break;
  }

  // Set flags for raw operation
  tty.c_lflag &= ~(ICANON | ECHO | ECHOE | ECHONL | ISIG | IEXTEN);
  tty.c_oflag &= ~OPOST;
  tty.c_iflag &= ~(IGNBRK | BRKINT | ISTRIP | INLCR | IGNCR | ICRNL | IXON |
                   IXOFF | IXANY);
  tty.c_iflag &= ~PARMRK;

  // VMIN=1: the terminal driver requires at least 1 byte before completing
  // a read.  VMIN=0 also works on Linux, because O_NONBLOCK takes precedence
  // and reading with no bytes available still fails with EAGAIN, but not on
  // macOS where that case returns 0 which PlatformPipeReader treats as EOF.
  // POSIX allows either behavior, so we use VMIN=1 instead.
  tty.c_cc[VMIN] = 1;
  tty.c_cc[VTIME] = 0;

  speed_t speed;
#if defined(XP_MACOSX)
  // The IOSSIOSPEED ioctl() is only available on Mac.
  bool need_speedioctl = false;
#endif
  if (ConvertBaudRate(aOptions.baudRate(), speed)) {
#ifdef XP_LINUX
    tty.c_cflag &= ~CBAUD;
    tty.c_cflag |= speed;
#else
    if (cfsetispeed(&tty, speed) != 0) {
      MOZ_LOG(
          gWebSerialLog, LogLevel::Error,
          ("PosixSerialPlatformService[%p]::ConfigurePort cfsetispeed failed: "
           "errno=%d",
           this, errno));
      return NS_ERROR_FAILURE;
    }
    if (cfsetospeed(&tty, speed) != 0) {
      MOZ_LOG(
          gWebSerialLog, LogLevel::Error,
          ("PosixSerialPlatformService[%p]::ConfigurePort cfsetospeed failed: "
           "errno=%d",
           this, errno));
      return NS_ERROR_FAILURE;
    }
#endif
  } else {
    // Attempt to use a custom baud rate
    MOZ_LOG(gWebSerialLog, LogLevel::Verbose,
            ("PosixSerialPlatformService[%p]::ConfigurePort attempting to use "
             "custom baudRate: %u",
             this, aOptions.baudRate()));
#ifdef LINUX_NSTD_BAUD
    tty.c_cflag &= ~CBAUD;
    tty.c_cflag |= CBAUDEX;
    tty.c_ispeed = aOptions.baudRate();
    tty.c_ospeed = aOptions.baudRate();
#elif defined(XP_MACOSX)
    // cfsetispeed() and cfsetospeed() sometimes work for custom baud rates
    // on Mac, but the ioctl is more reliable. But we have to set it after
    // setting everything else or it will get overwritten.
    need_speedioctl = true;
#else
  // User requested a custom baud rate but we don't have TCSETS2 or
  // IOSSIOSPEED ioctl's, so there's nothing we can do.
  MOZ_LOG(gWebSerialLog, LogLevel::Error,
          ("PosixSerialPlatformService[%p]::ConfigurePort could not set "
           "custom baudRate: %u",
           this, aOptions.baudRate()));
  return NS_ERROR_FAILURE;
#endif
  }
#ifdef LINUX_NSTD_BAUD
  if (ioctl(aFd, TCSETS2, &tty) < 0) {
#else
  if (tcsetattr(aFd, TCSANOW, &tty) != 0) {
#endif
    MOZ_LOG(gWebSerialLog, LogLevel::Error,
            ("PosixSerialPlatformService[%p]::ConfigurePort tcsetattr/ioctl "
             "failed: "
             "errno=%d",
             this, errno));
    return NS_ERROR_FAILURE;
  }

#if defined(XP_MACOSX)
  if (need_speedioctl) {
    speed = aOptions.baudRate();
    if (ioctl(aFd, IOSSIOSPEED, &speed) == -1) {
      MOZ_LOG(gWebSerialLog, LogLevel::Error,
              ("PosixSerialPlatformService[%p]::ConfigurePort IOSSIOSPEED "
               "ioctl failed to set baudRate %u: "
               "errno=%d",
               this, aOptions.baudRate(), errno));
      return NS_ERROR_FAILURE;
    }
  }
#endif

  MOZ_LOG(
      gWebSerialLog, LogLevel::Debug,
      ("PosixSerialPlatformService[%p]::ConfigurePort configuration successful",
       this));
  return NS_OK;
}

nsresult PosixSerialPlatformService::OpenImpl(
    const nsString& aPortId, const IPCSerialOptions& aOptions) {
  MOZ_LOG(gWebSerialLog, LogLevel::Debug,
          ("PosixSerialPlatformService[%p]::Open portId=%s, baudRate=%u", this,
           NS_ConvertUTF16toUTF8(aPortId).get(), aOptions.baudRate()));
  AssertIsOnIOThread();

  // Reject path traversal attempts (e.g. "/dev/tty/../../../etc/passwd").
  if (aPortId.Find(u"/.."_ns) != kNotFound) {
    MOZ_LOG(gWebSerialLog, LogLevel::Error,
            ("PosixSerialPlatformService[%p]::Open rejected portId '%s': "
             "path traversal detected",
             this, NS_ConvertUTF16toUTF8(aPortId).get()));
    return NS_ERROR_INVALID_ARG;
  }

  // Validate portId is a serial device path. This prevents a compromised
  // content process from using a crafted portId to open arbitrary files.
  if (IsRealSerialPort(NS_ConvertUTF16toUTF8(aPortId).get()).isErr()) {
    MOZ_LOG(gWebSerialLog, LogLevel::Error,
            ("PosixSerialPlatformService[%p]::Open rejected invalid portId "
             "'%s': not a serial device path",
             this, NS_ConvertUTF16toUTF8(aPortId).get()));
    return NS_ERROR_INVALID_ARG;
  }

  if (mOpenPorts.Contains(aPortId)) {
    MOZ_LOG(gWebSerialLog, LogLevel::Error,
            ("PosixSerialPlatformService[%p]::Open port already open: %s", this,
             NS_ConvertUTF16toUTF8(aPortId).get()));
    return NS_ERROR_FILE_IS_LOCKED;
  }

  NS_ConvertUTF16toUTF8 path(aPortId);
  int fd = open(path.get(), O_RDWR | O_NOCTTY | O_NONBLOCK);

  if (fd < 0) {
    MOZ_LOG(
        gWebSerialLog, LogLevel::Error,
        ("PosixSerialPlatformService[%p]::Open open() failed for %s: errno=%d",
         this, path.get(), errno));
    if (errno == EACCES) {
      return NS_ERROR_FILE_ACCESS_DENIED;
    } else if (errno == ENOENT) {
      return NS_ERROR_FILE_NOT_FOUND;
    } else if (errno == EBUSY) {
      return NS_ERROR_FILE_IS_LOCKED;
    } else {
      return NS_ERROR_NOT_AVAILABLE;
    }
  }

  mozilla::UniqueFileHandle handle(fd);
  MOZ_LOG(gWebSerialLog, LogLevel::Debug,
          ("PosixSerialPlatformService[%p]::Open opened fd=%d", this, fd));

  if (ioctl(fd, TIOCEXCL) < 0) {
    MOZ_LOG(gWebSerialLog, LogLevel::Warning,
            ("PosixSerialPlatformService[%p]::Open TIOCEXCL failed: errno=%d",
             this, errno));
  }

  nsresult rv = ConfigurePort(fd, aOptions);
  if (NS_FAILED(rv)) {
    MOZ_LOG(
        gWebSerialLog, LogLevel::Error,
        ("PosixSerialPlatformService[%p]::Open ConfigurePort failed: 0x%08x",
         this, static_cast<uint32_t>(rv)));
    return rv;
  }

#ifdef LINUX_NSTD_BAUD
  ioctl(fd, TCFLSH, TCIOFLUSH);
#else
  tcflush(fd, TCIOFLUSH);
#endif

  mOpenPorts.InsertOrUpdate(aPortId, std::move(handle));
  MOZ_LOG(gWebSerialLog, LogLevel::Debug,
          ("PosixSerialPlatformService[%p]::Open successfully opened port %s",
           this, NS_ConvertUTF16toUTF8(aPortId).get()));
  return NS_OK;
}

nsresult PosixSerialPlatformService::CloseImpl(const nsString& aPortId) {
  MOZ_LOG(gWebSerialLog, LogLevel::Debug,
          ("PosixSerialPlatformService[%p]::Close portId=%s", this,
           NS_ConvertUTF16toUTF8(aPortId).get()));
  AssertIsOnIOThread();

  if (!mOpenPorts.Remove(aPortId)) {
    MOZ_LOG(gWebSerialLog, LogLevel::Error,
            ("PosixSerialPlatformService[%p]::Close port not found: %s", this,
             NS_ConvertUTF16toUTF8(aPortId).get()));
    return NS_ERROR_NOT_AVAILABLE;
  }

  MOZ_LOG(gWebSerialLog, LogLevel::Debug,
          ("PosixSerialPlatformService[%p]::Close successfully closed port %s",
           this, NS_ConvertUTF16toUTF8(aPortId).get()));
  return NS_OK;
}

nsresult PosixSerialPlatformService::WriteImpl(const nsString& aPortId,
                                               Span<const uint8_t> aData) {
  int fd = FindPortFd(aPortId);
  if (fd < 0) {
    MOZ_LOG(gWebSerialLog, LogLevel::Error,
            ("PosixSerialPlatformService[%p]::Write port not found: %s", this,
             NS_ConvertUTF16toUTF8(aPortId).get()));
    return NS_ERROR_NOT_AVAILABLE;
  }

  if (aData.IsEmpty()) {
    MOZ_LOG(gWebSerialLog, LogLevel::Verbose,
            ("PosixSerialPlatformService[%p]::Write empty data", this));
    return NS_OK;
  }

  MOZ_LOG(gWebSerialLog, LogLevel::Verbose,
          ("PosixSerialPlatformService[%p]::Write writing %zu bytes", this,
           aData.Length()));

  size_t totalWritten = 0;
  const uint8_t* buffer = aData.Elements();
  size_t remaining = aData.Length();
  int totalPollTimeMs = 0;

  while (remaining > 0) {
    ssize_t bytesWritten = write(fd, buffer + totalWritten, remaining);

    if (bytesWritten < 0) {
      if (errno == EINTR) {
        continue;
      }
      if (errno == EAGAIN) {
        if (totalPollTimeMs >= kWriteTimeoutMs) {
          MOZ_LOG(gWebSerialLog, LogLevel::Error,
                  ("PosixSerialPlatformService[%p]::Write timed out after %dms "
                   "with %zu bytes remaining",
                   this, totalPollTimeMs, remaining));
          return NS_ERROR_NET_TIMEOUT;
        }

        struct pollfd pfd;
        pfd.fd = fd;
        pfd.events = POLLOUT;
        pfd.revents = 0;

        int pollResult = poll(&pfd, 1, kPollTimeoutMs);
        if (pollResult < 0) {
          if (errno == EINTR) {
            continue;
          }
          MOZ_LOG(
              gWebSerialLog, LogLevel::Error,
              ("PosixSerialPlatformService[%p]::Write poll failed: errno=%d",
               this, errno));
          return NS_ERROR_FAILURE;
        }
        if (pollResult == 0) {
          totalPollTimeMs += kPollTimeoutMs;
          continue;
        }
        if (pfd.revents & (POLLERR | POLLHUP | POLLNVAL)) {
          MOZ_LOG(gWebSerialLog, LogLevel::Error,
                  ("PosixSerialPlatformService[%p]::Write poll error: "
                   "revents=0x%x",
                   this, pfd.revents));
          return NS_ERROR_FAILURE;
        }
        continue;
      }
      MOZ_LOG(gWebSerialLog, LogLevel::Error,
              ("PosixSerialPlatformService[%p]::Write write() failed: errno=%d",
               this, errno));
      return NS_ERROR_FAILURE;
    }

    if (bytesWritten == 0) {
      MOZ_LOG(
          gWebSerialLog, LogLevel::Error,
          ("PosixSerialPlatformService[%p]::Write write() returned 0", this));
      return NS_ERROR_FAILURE;
    }

    totalWritten += bytesWritten;
    remaining -= bytesWritten;
    totalPollTimeMs = 0;

    if (remaining > 0) {
      MOZ_LOG(gWebSerialLog, LogLevel::Verbose,
              ("PosixSerialPlatformService[%p]::Write partial write: %zd "
               "bytes, %zu "
               "remaining",
               this, bytesWritten, remaining));
    }
  }

  MOZ_LOG(gWebSerialLog, LogLevel::Verbose,
          ("PosixSerialPlatformService[%p]::Write successfully wrote %zu bytes",
           this, totalWritten));
  return NS_OK;
}

nsresult PosixSerialPlatformService::DrainImpl(const nsString& aPortId) {
  int fd = FindPortFd(aPortId);
  if (fd < 0) {
    MOZ_LOG(gWebSerialLog, LogLevel::Error,
            ("PosixSerialPlatformService[%p]::Drain port not found: %s", this,
             NS_ConvertUTF16toUTF8(aPortId).get()));
    return NS_ERROR_NOT_AVAILABLE;
  }

  MOZ_LOG(gWebSerialLog, LogLevel::Debug,
          ("PosixSerialPlatformService[%p]::Drain draining transmit buffers",
           this));

#ifdef LINUX_NSTD_BAUD
  if (ioctl(fd, TCSBRK, 1) < 0) {
#else
  if (tcdrain(fd) < 0) {
#endif
    MOZ_LOG(gWebSerialLog, LogLevel::Error,
            ("PosixSerialPlatformService[%p]::Drain tcdrain() failed: errno=%d",
             this, errno));
    return NS_ERROR_FAILURE;
  }

  MOZ_LOG(gWebSerialLog, LogLevel::Debug,
          ("PosixSerialPlatformService[%p]::Drain successfully drained buffers",
           this));
  return NS_OK;
}

nsresult PosixSerialPlatformService::FlushImpl(const nsString& aPortId,
                                               bool aReceive) {
  int fd = FindPortFd(aPortId);
  if (fd < 0) {
    MOZ_LOG(gWebSerialLog, LogLevel::Error,
            ("PosixSerialPlatformService[%p]::Flush port not found: %s", this,
             NS_ConvertUTF16toUTF8(aPortId).get()));
    return NS_ERROR_NOT_AVAILABLE;
  }

  int queue = aReceive ? TCIFLUSH : TCOFLUSH;
  MOZ_LOG(gWebSerialLog, LogLevel::Debug,
          ("PosixSerialPlatformService[%p]::Flush discarding %s buffers", this,
           aReceive ? "receive" : "transmit"));

#ifdef LINUX_NSTD_BAUD
  if (ioctl(fd, TCFLSH, queue) < 0) {
#else
  if (tcflush(fd, queue) < 0) {
#endif
    MOZ_LOG(gWebSerialLog, LogLevel::Error,
            ("PosixSerialPlatformService[%p]::Flush tcflush() failed: errno=%d",
             this, errno));
    return NS_ERROR_FAILURE;
  }

  MOZ_LOG(gWebSerialLog, LogLevel::Debug,
          ("PosixSerialPlatformService[%p]::Flush successfully flushed %s "
           "buffers",
           this, aReceive ? "receive" : "transmit"));
  return NS_OK;
}

nsresult PosixSerialPlatformService::SetSignalsImpl(
    const nsString& aPortId, const IPCSerialOutputSignals& aSignals) {
  MOZ_LOG(
      gWebSerialLog, LogLevel::Debug,
      ("PosixSerialPlatformService[%p]::SetSignals portId=%s, DTR=%s, RTS=%s, "
       "Break=%s",
       this, NS_ConvertUTF16toUTF8(aPortId).get(),
       aSignals.dataTerminalReady().isSome()
           ? (aSignals.dataTerminalReady().value() ? "true" : "false")
           : "unchanged",
       aSignals.requestToSend().isSome()
           ? (aSignals.requestToSend().value() ? "true" : "false")
           : "unchanged",
       aSignals.breakSignal().isSome()
           ? (aSignals.breakSignal().value() ? "true" : "false")
           : "unchanged"));

  int fd = FindPortFd(aPortId);
  if (fd < 0) {
    MOZ_LOG(gWebSerialLog, LogLevel::Error,
            ("PosixSerialPlatformService[%p]::SetSignals port not found: %s",
             this, NS_ConvertUTF16toUTF8(aPortId).get()));
    return NS_ERROR_NOT_AVAILABLE;
  }

  if (aSignals.dataTerminalReady().isSome() ||
      aSignals.requestToSend().isSome()) {
    int status;
    if (ioctl(fd, TIOCMGET, &status) < 0) {
      MOZ_LOG(gWebSerialLog, LogLevel::Error,
              ("PosixSerialPlatformService[%p]::SetSignals failure to get "
               "status on %d: errno=%d",
               this, fd, errno));
      return NS_ERROR_FAILURE;
    }
    MOZ_LOG(gWebSerialLog, LogLevel::Verbose,
            ("PosixSerialPlatformService[%p]::SetSignals got status on %d: "
             "status=0x%x",
             this, fd, uint32_t(status)));

    // The spec requires that we set DTR first, then RTS, then break.
    // We could do this with TIOCMBIS or TIOCMBIC to set or clear
    // individual flags, but that seemed to return ENOTTY on Mac.
    // So instead we somewhat clunkily get the current flags, toggle
    // the bit, then set the flags for each thing we want to change.
    if (aSignals.dataTerminalReady().isSome()) {
      if (aSignals.dataTerminalReady().value()) {
        status |= TIOCM_DTR;
      } else {
        status &= ~TIOCM_DTR;
      }
      if (ioctl(fd, TIOCMSET, &status) < 0) {
        MOZ_LOG(gWebSerialLog, LogLevel::Error,
                ("PosixSerialPlatformService[%p]::SetSignals set DTR status on "
                 "%d failed: "
                 "newStatus=%x errno=%d",
                 this, fd, uint32_t(status), errno));
        return NS_ERROR_FAILURE;
      }
    }

    if (aSignals.requestToSend().isSome()) {
      if (aSignals.requestToSend().value()) {
        status |= TIOCM_RTS;
      } else {
        status &= ~TIOCM_RTS;
      }
      if (ioctl(fd, TIOCMSET, &status) < 0) {
        MOZ_LOG(gWebSerialLog, LogLevel::Error,
                ("PosixSerialPlatformService[%p]::SetSignals set RTS status on "
                 "%d failed: "
                 "newStatus=%x errno=%d",
                 this, fd, uint32_t(status), errno));
        return NS_ERROR_FAILURE;
      }
    }
  }

  if (aSignals.breakSignal().isSome()) {
    int cmd = aSignals.breakSignal().value() ? TIOCSBRK : TIOCCBRK;
    if (ioctl(fd, cmd) < 0) {
      MOZ_LOG(
          gWebSerialLog, LogLevel::Error,
          ("PosixSerialPlatformService[%p]::SetSignals ioctl %s on %d failed: "
           "errno=%d",
           this, aSignals.breakSignal().value() ? "TIOCSBRK" : "TIOCCBRK", fd,
           errno));
      return NS_ERROR_FAILURE;
    }
  }

  MOZ_LOG(
      gWebSerialLog, LogLevel::Debug,
      ("PosixSerialPlatformService[%p]::SetSignals signals set successfully",
       this));
  return NS_OK;
}

nsresult PosixSerialPlatformService::GetSignalsImpl(
    const nsString& aPortId, IPCSerialInputSignals& aSignals) {
  MOZ_LOG(gWebSerialLog, LogLevel::Debug,
          ("PosixSerialPlatformService[%p]::GetSignals portId=%s", this,
           NS_ConvertUTF16toUTF8(aPortId).get()));

  int fd = FindPortFd(aPortId);
  if (fd < 0) {
    MOZ_LOG(gWebSerialLog, LogLevel::Error,
            ("PosixSerialPlatformService[%p]::GetSignals port not found: %s",
             this, NS_ConvertUTF16toUTF8(aPortId).get()));
    return NS_ERROR_NOT_AVAILABLE;
  }

  int status;
  if (ioctl(fd, TIOCMGET, &status) < 0) {
    MOZ_LOG(
        gWebSerialLog, LogLevel::Error,
        ("PosixSerialPlatformService[%p]::GetSignals ioctl TIOCMGET failed: "
         "errno=%d",
         this, errno));
    return NS_ERROR_FAILURE;
  }

  aSignals = IPCSerialInputSignals{
      (status & TIOCM_CAR) != 0,  // dataCarrierDetect
      (status & TIOCM_CTS) != 0,  // clearToSend
      (status & TIOCM_RNG) != 0,  // ringIndicator
      (status & TIOCM_DSR) != 0   // dataSetReady
  };

  MOZ_LOG(gWebSerialLog, LogLevel::Debug,
          ("PosixSerialPlatformService[%p]::GetSignals DCD=%d, CTS=%d, RI=%d, "
           "DSR=%d",
           this, aSignals.dataCarrierDetect(), aSignals.clearToSend(),
           aSignals.ringIndicator(), aSignals.dataSetReady()));

  return NS_OK;
}

nsresult PosixSerialPlatformService::GetReadStreamImpl(
    const nsString& aPortId, uint32_t aBufferSize,
    nsIAsyncInputStream** aStream) {
  AssertIsOnIOThread();
  int fd = FindPortFd(aPortId);
  if (fd < 0) {
    MOZ_LOG(gWebSerialLog, LogLevel::Error,
            ("PosixSerialPlatformService[%p]::GetReadStream port not found: %s",
             this, NS_ConvertUTF16toUTF8(aPortId).get()));
    return NS_ERROR_NOT_AVAILABLE;
  }
  UniqueFileHandle readHandle = DuplicateFileHandle(fd);
  if (!readHandle) {
    MOZ_LOG(gWebSerialLog, LogLevel::Error,
            ("PosixSerialPlatformService[%p]::GetReadStream dup failed for "
             "port '%s': errno=%d",
             this, NS_ConvertUTF16toUTF8(aPortId).get(), errno));
    return NS_ERROR_FAILURE;
  }
  RefPtr<PlatformPipeReader> reader =
      MakeRefPtr<PlatformPipeReader>(std::move(readHandle), aBufferSize);
  reader.forget(aStream);
  return NS_OK;
}

nsresult PosixSerialPlatformService::StartMonitoring() {
#ifdef XP_LINUX
  return InitializeUdev();
#elif defined(XP_MACOSX)
  return InitializeMacOS();
#else
  return NS_OK;
#endif
}

#ifdef XP_MACOSX
nsresult PosixSerialPlatformService::InitializeMacOS() {
  MOZ_LOG(gWebSerialLog, LogLevel::Debug,
          ("PosixSerialPlatformService[%p]::InitializeMacOS setting up IOKit "
           "notifications",
           this));

  mNotificationPort = IONotificationPortCreate(kIOMainPortDefault);
  if (!mNotificationPort) {
    MOZ_LOG(gWebSerialLog, LogLevel::Error,
            ("PosixSerialPlatformService[%p]::InitializeMacOS "
             "IONotificationPortCreate failed",
             this));
    return NS_ERROR_FAILURE;
  }

  CFRunLoopAddSource(CFRunLoopGetMain(),
                     IONotificationPortGetRunLoopSource(mNotificationPort),
                     kCFRunLoopDefaultMode);
  MOZ_LOG(gWebSerialLog, LogLevel::Debug,
          ("PosixSerialPlatformService[%p]::InitializeMacOS added run loop "
           "source to main run loop",
           this));

  CFMutableDictionaryRef matchingDict =
      IOServiceMatching(kIOSerialBSDServiceValue);
  if (!matchingDict) {
    MOZ_LOG(
        gWebSerialLog, LogLevel::Error,
        ("PosixSerialPlatformService[%p]::InitializeMacOS IOServiceMatching "
         "failed",
         this));
    return NS_ERROR_FAILURE;
  }

  // The call to IOServiceAddMatchingNotification consumes a ref to
  // matchingDict.
  kern_return_t kr = IOServiceAddMatchingNotification(
      mNotificationPort, kIOMatchedNotification, matchingDict,
      DeviceAddedCallback, this, &mAddedIterator);
  if (kr != KERN_SUCCESS) {
    MOZ_LOG(gWebSerialLog, LogLevel::Error,
            ("PosixSerialPlatformService[%p]::InitializeMacOS "
             "IOServiceAddMatchingNotification for added devices failed: %d",
             this, kr));
    return NS_ERROR_FAILURE;
  }

  MOZ_LOG(gWebSerialLog, LogLevel::Debug,
          ("PosixSerialPlatformService[%p]::InitializeMacOS draining initial "
           "added device iterator",
           this));
  OnDeviceAdded(mAddedIterator, true);

  // The call to IOServiceAddMatchingNotification consumes a ref to
  // matchingDict2.
  CFMutableDictionaryRef matchingDict2 =
      IOServiceMatching(kIOSerialBSDServiceValue);
  if (!matchingDict2) {
    MOZ_LOG(
        gWebSerialLog, LogLevel::Error,
        ("PosixSerialPlatformService[%p]::InitializeMacOS IOServiceMatching "
         "failed for removed devices",
         this));
    return NS_ERROR_FAILURE;
  }

  kr = IOServiceAddMatchingNotification(
      mNotificationPort, kIOTerminatedNotification, matchingDict2,
      DeviceRemovedCallback, this, &mRemovedIterator);
  if (kr != KERN_SUCCESS) {
    MOZ_LOG(gWebSerialLog, LogLevel::Error,
            ("PosixSerialPlatformService[%p]::InitializeMacOS "
             "IOServiceAddMatchingNotification for removed devices failed: %d",
             this, kr));
    return NS_ERROR_FAILURE;
  }

  MOZ_LOG(gWebSerialLog, LogLevel::Debug,
          ("PosixSerialPlatformService[%p]::InitializeMacOS draining initial "
           "removed device iterator",
           this));
  OnDeviceRemoved(mRemovedIterator, true);

  MOZ_LOG(gWebSerialLog, LogLevel::Info,
          ("PosixSerialPlatformService[%p]::InitializeMacOS monitoring started",
           this));
  return NS_OK;
}
#endif

#ifdef XP_LINUX
nsresult PosixSerialPlatformService::InitializeUdev() {
  mUdevLib = MakeUnique<udev_lib>();
  if (!*mUdevLib) {
    MOZ_LOG(gWebSerialLog, LogLevel::Error,
            ("PosixSerialPlatformService[%p]::InitializeUdev udev_lib "
             "initialization failed",
             this));
    mUdevLib = nullptr;
    return NS_ERROR_FAILURE;
  }

  mMonitor = mUdevLib->udev_monitor_new_from_netlink(mUdevLib->udev, "udev");
  if (!mMonitor) {
    MOZ_LOG(gWebSerialLog, LogLevel::Error,
            ("PosixSerialPlatformService[%p]::InitializeUdev "
             "udev_monitor_new_from_netlink failed",
             this));
    mUdevLib = nullptr;
    return NS_ERROR_FAILURE;
  }

  auto cleanupUdevAndMonitor = MakeScopeExit([&]() {
    mUdevLib->udev_monitor_unref(mMonitor);
    mMonitor = nullptr;
    mUdevLib = nullptr;
  });

  if (mUdevLib->udev_monitor_filter_add_match_subsystem_devtype(mMonitor, "tty",
                                                                nullptr) < 0) {
    MOZ_LOG(gWebSerialLog, LogLevel::Error,
            ("PosixSerialPlatformService[%p]::InitializeUdev filter add failed",
             this));
    return NS_ERROR_FAILURE;
  }

  if (mUdevLib->udev_monitor_enable_receiving(mMonitor) < 0) {
    MOZ_LOG(gWebSerialLog, LogLevel::Error,
            ("PosixSerialPlatformService[%p]::InitializeUdev enable receiving "
             "failed",
             this));
    return NS_ERROR_FAILURE;
  }

  int fd = mUdevLib->udev_monitor_get_fd(mMonitor);
  if (fd < 0) {
    MOZ_LOG(
        gWebSerialLog, LogLevel::Error,
        ("PosixSerialPlatformService[%p]::InitializeUdev get fd failed", this));
    return NS_ERROR_FAILURE;
  }

  GIOChannel* channel = g_io_channel_unix_new(fd);
  if (!channel) {
    MOZ_LOG(
        gWebSerialLog, LogLevel::Error,
        ("PosixSerialPlatformService[%p]::InitializeUdev g_io_channel_unix_new "
         "failed",
         this));
    return NS_ERROR_FAILURE;
  }

  // udev operations did not error, so don't clean it up here
  cleanupUdevAndMonitor.release();

  mMonitorSourceID =
      g_io_add_watch(channel, GIOCondition(G_IO_IN | G_IO_ERR | G_IO_HUP),
                     OnUdevMonitor, this);
  g_io_channel_unref(channel);

  MOZ_LOG(gWebSerialLog, LogLevel::Info,
          ("PosixSerialPlatformService[%p]::InitializeUdev udev monitoring "
           "initialized",
           this));
  return NS_OK;
}

gboolean PosixSerialPlatformService::OnUdevMonitor(GIOChannel* source,
                                                   GIOCondition condition,
                                                   gpointer data) {
  PosixSerialPlatformService* service =
      static_cast<PosixSerialPlatformService*>(data);

  if (condition & (G_IO_ERR | G_IO_HUP)) {
    MOZ_LOG(
        gWebSerialLog, LogLevel::Error,
        ("PosixSerialPlatformService[%p]::OnUdevMonitor error condition: %d",
         service, condition));
    return FALSE;
  }

  if (condition & G_IO_IN) {
    service->ReadUdevChange();
  }

  return TRUE;
}

void PosixSerialPlatformService::PopulatePortInfoFromUdev(
    udev_device* aDev, const char* aDevnode, IPCSerialPortInfo& aPortInfo) {
  MOZ_ASSERT(mUdevLib);

  NS_ConvertUTF8toUTF16 path(aDevnode);
  aPortInfo.id() = path;
  aPortInfo.path() = path;

  const char* productName =
      mUdevLib->udev_device_get_property_value(aDev, "ID_MODEL_FROM_DATABASE");
  if (!productName) {
    productName = mUdevLib->udev_device_get_property_value(aDev, "ID_MODEL");
  }
  if (productName) {
    aPortInfo.friendlyName() = NS_ConvertUTF8toUTF16(productName);
  } else {
    const char* basename = strrchr(aDevnode, '/');
    aPortInfo.friendlyName() =
        NS_ConvertUTF8toUTF16(basename ? basename + 1 : aDevnode);
  }

  const char* vendorIdStr =
      mUdevLib->udev_device_get_property_value(aDev, "ID_VENDOR_ID");
  const char* productIdStr =
      mUdevLib->udev_device_get_property_value(aDev, "ID_MODEL_ID");
  if (vendorIdStr && productIdStr) {
    unsigned int vendorId, productId;
    if (sscanf(vendorIdStr, "%x", &vendorId) == 1 &&
        sscanf(productIdStr, "%x", &productId) == 1) {
      aPortInfo.usbVendorId() = Some(static_cast<uint16_t>(vendorId));
      aPortInfo.usbProductId() = Some(static_cast<uint16_t>(productId));
    }
  }
}

void PosixSerialPlatformService::ReadUdevChange() {
  if (IsShutdown() || !mUdevLib) {
    return;
  }

  udev_device* dev = mUdevLib->udev_monitor_receive_device(mMonitor);
  if (!dev) {
    return;
  }
  auto cleanupDev = MakeScopeExit([&]() { mUdevLib->udev_device_unref(dev); });

  const char* action = mUdevLib->udev_device_get_action(dev);
  const char* devnode = mUdevLib->udev_device_get_devnode(dev);

  if (!action || !devnode) {
    return;
  }

  MOZ_LOG(
      gWebSerialLog, LogLevel::Debug,
      ("PosixSerialPlatformService[%p]::ReadUdevChange action=%s, devnode=%s",
       this, action, devnode));

  if (strcmp(action, "add") == 0) {
    IPCSerialPortInfo portInfo;
    PopulatePortInfoFromUdev(dev, devnode, portInfo);

    MOZ_LOG(
        gWebSerialLog, LogLevel::Info,
        ("PosixSerialPlatformService[%p]::ReadUdevChange device connected: "
         "path=%s, friendlyName=%s, VID=0x%04x, PID=0x%04x",
         this, devnode, NS_ConvertUTF16toUTF8(portInfo.friendlyName()).get(),
         portInfo.usbVendorId().valueOr(0),
         portInfo.usbProductId().valueOr(0)));

    NotifyPortConnected(portInfo);
  } else if (strcmp(action, "remove") == 0) {
    MOZ_LOG(
        gWebSerialLog, LogLevel::Info,
        ("PosixSerialPlatformService[%p]::ReadUdevChange device disconnected: "
         "path=%s",
         this, devnode));

    NotifyPortDisconnected(NS_ConvertUTF8toUTF16(devnode));
  }
}
#endif

#ifdef XP_MACOSX
bool PosixSerialPlatformService::ExtractDeviceInfo(
    io_service_t device, IPCSerialPortInfo& portInfo) {
  if (!device) {
    MOZ_LOG(
        gWebSerialLog, LogLevel::Debug,
        ("PosixSerialPlatformService[%p]::ExtractDeviceInfo device is null, "
         "device may be terminated",
         this));
    return false;
  }

  CFTypeRef pathRef = IORegistryEntryCreateCFProperty(
      device, CFSTR(kIODialinDeviceKey), kCFAllocatorDefault, 0);

  auto cleanupPath = MakeScopeExit([&]() {
    if (pathRef) CFRelease(pathRef);
  });

  if (!pathRef) {
    MOZ_LOG(gWebSerialLog, LogLevel::Debug,
            ("PosixSerialPlatformService[%p]::ExtractDeviceInfo failed to get "
             "device path, device may be terminated",
             this));
    return false;
  }

  if (CFGetTypeID(pathRef) != CFStringGetTypeID()) {
    MOZ_LOG(gWebSerialLog, LogLevel::Warning,
            ("PosixSerialPlatformService[%p]::ExtractDeviceInfo device path is "
             "not a string",
             this));
    return false;
  }

  char devicePath[PATH_MAX];
  if (!CFStringGetCString((CFStringRef)pathRef, devicePath, sizeof(devicePath),
                          kCFStringEncodingUTF8)) {
    return false;
  }

  if (IsMacOSSystemSerialPort(devicePath)) {
    MOZ_LOG(gWebSerialLog, LogLevel::Debug,
            ("PosixSerialPlatformService[%p]::ExtractDeviceInfo filtering "
             "macOS system port: %s",
             this, devicePath));
    return false;
  }

  NS_ConvertUTF8toUTF16 path(devicePath);
  portInfo.id() = path;
  portInfo.path() = path;

  CFTypeRef baseNameRef = IORegistryEntryCreateCFProperty(
      device, CFSTR(kIOTTYBaseNameKey), kCFAllocatorDefault, 0);
  auto cleanupBaseName = MakeScopeExit([&]() {
    if (baseNameRef) CFRelease(baseNameRef);
  });

  if (baseNameRef) {
    if (CFGetTypeID(baseNameRef) == CFStringGetTypeID()) {
      char baseName[kDeviceNameBufferSize];
      if (CFStringGetCString((CFStringRef)baseNameRef, baseName,
                             sizeof(baseName), kCFStringEncodingUTF8)) {
        NS_ConvertUTF8toUTF16 friendlyName(baseName);
        portInfo.friendlyName() = friendlyName;
      }
    }
  }

  // We search for these properties on all ancestors, because the registry tree
  // can have a serial service with a USB service as its ancestor.
  CFTypeRef vendorIdRef = IORegistryEntrySearchCFProperty(
      device, kIOServicePlane, CFSTR("idVendor"), kCFAllocatorDefault,
      kIORegistryIterateRecursively | kIORegistryIterateParents);
  CFTypeRef productIdRef = IORegistryEntrySearchCFProperty(
      device, kIOServicePlane, CFSTR("idProduct"), kCFAllocatorDefault,
      kIORegistryIterateRecursively | kIORegistryIterateParents);

  auto cleanupVendorIdAndProductId = MakeScopeExit([&]() {
    if (vendorIdRef) CFRelease(vendorIdRef);
    if (productIdRef) CFRelease(productIdRef);
  });

  if (vendorIdRef && productIdRef &&
      CFGetTypeID(vendorIdRef) == CFNumberGetTypeID() &&
      CFGetTypeID(productIdRef) == CFNumberGetTypeID()) {
    SInt32 vendorId, productId;
    if (CFNumberGetValue((CFNumberRef)vendorIdRef, kCFNumberSInt32Type,
                         &vendorId) &&
        CFNumberGetValue((CFNumberRef)productIdRef, kCFNumberSInt32Type,
                         &productId)) {
      portInfo.usbVendorId() = Some(static_cast<uint16_t>(vendorId));
      portInfo.usbProductId() = Some(static_cast<uint16_t>(productId));
    }
  }

  CFTypeRef productNameRef = IORegistryEntrySearchCFProperty(
      device, kIOServicePlane, CFSTR(kUSBProductString), kCFAllocatorDefault,
      kIORegistryIterateRecursively | kIORegistryIterateParents);
  auto cleanupProductName = MakeScopeExit([&]() {
    if (productNameRef) CFRelease(productNameRef);
  });

  if (productNameRef) {
    if (CFGetTypeID(productNameRef) == CFStringGetTypeID()) {
      char productName[kDeviceNameBufferSize];
      if (CFStringGetCString((CFStringRef)productNameRef, productName,
                             sizeof(productName), kCFStringEncodingUTF8)) {
        NS_ConvertUTF8toUTF16 friendlyName(productName);
        portInfo.friendlyName() = friendlyName;
      }
    }
  }

  if (portInfo.friendlyName().IsEmpty()) {
    portInfo.friendlyName() = path;
  }

  return true;
}

void PosixSerialPlatformService::DeviceAddedCallback(void* context,
                                                     io_iterator_t iterator) {
  PosixSerialPlatformService* service =
      static_cast<PosixSerialPlatformService*>(context);
  MOZ_LOG(
      gWebSerialLog, LogLevel::Debug,
      ("PosixSerialPlatformService[%p]::DeviceAddedCallback invoked", service));
  service->OnDeviceAdded(iterator, false);
}

void PosixSerialPlatformService::DeviceRemovedCallback(void* context,
                                                       io_iterator_t iterator) {
  PosixSerialPlatformService* service =
      static_cast<PosixSerialPlatformService*>(context);
  MOZ_LOG(gWebSerialLog, LogLevel::Debug,
          ("PosixSerialPlatformService[%p]::DeviceRemovedCallback invoked",
           service));
  service->OnDeviceRemoved(iterator, false);
}

void PosixSerialPlatformService::OnDeviceAdded(io_iterator_t iterator,
                                               bool aSkipNotify) {
  io_service_t device;
  int deviceCount = 0;
  while ((device = IOIteratorNext(iterator))) {
    deviceCount++;
    IPCSerialPortInfo portInfo;
    if (ExtractDeviceInfo(device, portInfo)) {
      MOZ_LOG(gWebSerialLog, LogLevel::Debug,
              ("PosixSerialPlatformService[%p]::OnDeviceAdded skip=%d device "
               ": path=%s, friendlyName=%s, VID=0x%04x, PID=0x%04x",
               this, aSkipNotify, NS_ConvertUTF16toUTF8(portInfo.path()).get(),
               NS_ConvertUTF16toUTF8(portInfo.friendlyName()).get(),
               portInfo.usbVendorId().valueOr(0),
               portInfo.usbProductId().valueOr(0)));
      if (!aSkipNotify) {
        NotifyPortConnected(portInfo);
      }
    }

    IOObjectRelease(device);
  }

  MOZ_LOG(gWebSerialLog, LogLevel::Debug,
          ("PosixSerialPlatformService[%p]::OnDeviceAdded processed %d devices "
           "(skipNotify=%d)",
           this, deviceCount, aSkipNotify));
}

void PosixSerialPlatformService::OnDeviceRemoved(io_iterator_t iterator,
                                                 bool aSkipNotify) {
  io_service_t device;
  int deviceCount = 0;
  while ((device = IOIteratorNext(iterator))) {
    deviceCount++;
    IPCSerialPortInfo portInfo;
    if (ExtractDeviceInfo(device, portInfo)) {
      MOZ_LOG(
          gWebSerialLog, LogLevel::Debug,
          ("PosixSerialPlatformService[%p]::OnDeviceRemoved skip=%d "
           "device: path=%s",
           this, aSkipNotify, NS_ConvertUTF16toUTF8(portInfo.path()).get()));
      if (!aSkipNotify) {
        NotifyPortDisconnected(portInfo.path());
      }
    } else {
      MOZ_LOG(gWebSerialLog, LogLevel::Warning,
              ("PosixSerialPlatformService[%p]::OnDeviceRemoved failed to "
               "extract device info, device may have been terminated",
               this));
    }

    IOObjectRelease(device);
  }

  MOZ_LOG(gWebSerialLog, LogLevel::Debug,
          ("PosixSerialPlatformService[%p]::OnDeviceRemoved processed %d "
           "devices (skipNotify=%d)",
           this, deviceCount, aSkipNotify));
}
#endif

already_AddRefed<SerialPlatformService>
SerialPlatformService::GetInstanceImpl() {
  return MakeAndAddRef<PosixSerialPlatformService>();
}

}  // namespace mozilla::dom

Messung V0.5 in Prozent
C=91 H=95 G=92

¤ Dauer der Verarbeitung: 0.34 Sekunden  (vorverarbeitet am  2026-08-25) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.