Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ab98f839 authored by Oleg Nitz's avatar Oleg Nitz
Browse files

Use Native Serial Service for file operations.

Replace serial file operations previouly implemented as a part of
Serial system service by invocations of Native Serial Service,
so that we don't have to give SELinux file permissions to all
system services.

Bug: 415125368
Bug: 428744191
Flag: android.hardware.serial.flags.enable_wired_serial_api
Test: atest SerialTests
Change-Id: Id3ebf587879c77da4d1184417e08e7eec59ad3f7
parent 90f9298b
Loading
Loading
Loading
Loading
+5 −8
Original line number Diff line number Diff line
@@ -28,14 +28,12 @@ oneway interface ISerialPortResponseCallback {
    /** Error codes for {@link #onError}. */
    @Backing(type="int")
    enum ErrorCode {
        // IOException while reading the list of derial drivers
        ERROR_READING_DRIVERS = 0,
        // Serial port with the given name does not exist.
        ERROR_PORT_NOT_FOUND = 1,
        ERROR_PORT_NOT_FOUND = 0,
        // SecurityException due to access denied.
        ERROR_ACCESS_DENIED = 2,
        // ErrnoException while opening the serial port.
        ERROR_OPENING_PORT = 3,
        ERROR_ACCESS_DENIED = 1,
        // Error while opening the serial port.
        ERROR_OPENING_PORT = 2,
    }

    /**
@@ -50,8 +48,7 @@ oneway interface ISerialPortResponseCallback {
     * Called when the serial port opening failed.
     *
     * @param errorCode The error code indicating the type of error that occurred.
     * @param errno The errno from ErrnoException in case of ERROR_OPENING_PORT.
     * @param message Additional text information about the error.
     */
    void onError(in ErrorCode errorCode, in int errno, in String message);
    void onError(in ErrorCode errorCode, in String message);
}
+12 −15
Original line number Diff line number Diff line
@@ -16,8 +16,6 @@

package android.hardware.serial;

import static android.system.OsConstants.ENOENT;

import static java.lang.annotation.RetentionPolicy.SOURCE;

import android.annotation.FlaggedApi;
@@ -27,7 +25,6 @@ import android.content.Context;
import android.os.OutcomeReceiver;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.system.ErrnoException;

import java.io.IOException;
import java.lang.annotation.Retention;
@@ -131,14 +128,14 @@ public final class SerialPort {
     *
     * <p>Exceptions passed to {@code receiver} may be
     * <ul>
     * <li> {@link ErrnoException} with ENOENT if the port is detached or any syscall to open the
     * port fails that come with an errno</li>
     * <li> {@link IOException} if other required operations fail that don't come with errno</li>
     * <li> {@link SecurityException} if the user rejects the open request</li>
     * <li> {@link IllegalStateException} if the port is not found.</li>
     * <li> {@link IOException} if the port cannot be opened.</li>
     * <li> {@link SecurityException} if the user rejects the open request.</li>
     * </ul>
     *
     * @param flags     open flags that define read/write mode and other options.
     * @param exclusive whether the app needs exclusive access with TIOCEXCL(2const)
     * @param exclusive whether to request exclusive access to the port, preventing other processes
     *                  from opening it.
     * @param executor  the executor used to run receiver
     * @param receiver  the outcome receiver
     * @throws IllegalArgumentException if the set of flags is not correct.
@@ -174,18 +171,18 @@ public final class SerialPort {
        }

        @Override
        public void onError(@ErrorCode int errorCode, int errno, String message) {
            mExecutor.execute(() -> mReceiver.onError(getException(errorCode, errno, message)));
        public void onError(@ErrorCode int errorCode, String message) {
            mExecutor.execute(() -> mReceiver.onError(getException(errorCode, message)));
        }

        @NonNull
        private static Exception getException(int errorCode, int errno, String message) {
        private static Exception getException(int errorCode, String message) {
            return switch (errorCode) {
                case ErrorCode.ERROR_READING_DRIVERS -> new IOException(message);
                case ErrorCode.ERROR_PORT_NOT_FOUND -> new ErrnoException(message, ENOENT);
                case ErrorCode.ERROR_PORT_NOT_FOUND -> new IllegalStateException(message);
                case ErrorCode.ERROR_ACCESS_DENIED -> new SecurityException(message);
                case ErrorCode.ERROR_OPENING_PORT -> new ErrnoException(message, errno);
                default -> new IllegalStateException("Unexpected errorCode " + errorCode);
                case ErrorCode.ERROR_OPENING_PORT -> new IOException(message);
                default -> new IllegalStateException(
                        "errorCode=" + errorCode + ", message=" + message);
            };
        }
    }
+3 −0
Original line number Diff line number Diff line
@@ -18,6 +18,9 @@ java_library_static {
    name: "services.serial",
    defaults: ["platform_service_defaults"],
    srcs: [":services.serial-sources"],
    static_libs: [
        "android.hardware.serialservice-java",
    ],
    libs: [
        "services.core",
    ],
+0 −129
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.serial;

import static com.android.server.serial.SerialConstants.DEV_DIR;
import static com.android.server.serial.SerialConstants.SYSFS_DIR;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.system.ErrnoException;
import android.system.Os;
import android.system.StructStat;

import com.android.internal.annotations.VisibleForTesting;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

/** Represents device related files and links under /dev and /sys. */
class DeviceFileInfo {

    // Device name
    final String mName;

    // Device Number (`st_rdev` field of `struct stat`)
    final long mNumber;

    // Device info link under /sys
    private Path mSysfsLink;

    // Device subsystem
    private String mSubsystem;

    @VisibleForTesting
    DeviceFileInfo(String name, long number) {
        mName = name;
        mNumber = number;
        mSysfsLink = Paths.get(SYSFS_DIR).resolve(name);
    }

    /** Create from /dev/name (check if /sys/class/tty/name exists). */
    @Nullable
    static DeviceFileInfo fromNameInDev(String name) {
        if (!new File(SYSFS_DIR, name).exists()) {
            return null;
        }
        return fromName(name);
    }

    /** Create from /sys/class/tty/name (check if /dev/name exists). */
    @Nullable
    static DeviceFileInfo fromNameInSysfs(String name) {
        if (!new File(DEV_DIR, name).exists()) {
            return null;
        }
        return fromName(name);
    }

    @Nullable
    private static DeviceFileInfo fromName(String name) {
        try {
            StructStat stat = Os.stat(DEV_DIR + "/" + name);
            DeviceFileInfo device = new DeviceFileInfo(name, stat.st_rdev);
            if (device.getSubsystem().equals("platform")) {
                // Keep built-in UARTs hidden (for security reasons).
                return null;
            }
            return device;
        } catch (ErrnoException | IOException e) {
            return null;
        }
    }

    boolean isUsb() {
        try {
            return switch (getSubsystem()) {
                case "usb", "usb-serial" -> true;
                default -> false;
            };
        } catch (IOException e) {
            return false;
        }
    }

    @NonNull
    private String getSubsystem() throws IOException {
        if (mSubsystem == null) {
            // E.g. /sys/class/tty/ttyACM0/device/subsystem -> .../bus/usb
            Path subsystemLink = getSysfsLink().resolve("device/subsystem");
            if (!Files.exists(subsystemLink)) {
                mSubsystem = "virtual";
            }
            mSubsystem = subsystemLink.toRealPath().getFileName().toString();
        }
        return mSubsystem;
    }

    /** Device info link under /sys. */
    Path getSysfsLink() {
        return mSysfsLink;
    }

    @VisibleForTesting
    void setSysfsLink(Path sysfsLink) {
        mSysfsLink = sysfsLink;
    }

    @VisibleForTesting
    void setSubsystem(String subsystem) {
        mSubsystem = subsystem;
    }
}
+13 −6
Original line number Diff line number Diff line
@@ -16,14 +16,21 @@

package com.android.server.serial;

class SerialConstants {
import android.hardware.serialservice.SerialPortInfo;

    /** Directory with device pseudo-files, including serial devices. */
    static final String DEV_DIR = "/dev";
import java.util.function.Predicate;

    /** Sysfs directory containing information about serial devices. */
    static final String SYSFS_DIR = "/sys/class/tty";
/** Filters serial devices that are exposed to the user. */
class SerialDeviceFilter implements Predicate<SerialPortInfo> {

    private SerialConstants() {
    private static final String SERIAL_DRIVER_TYPE = "serial";

    private static final String BUILT_IN_SERIAL_SUBSYSTEM = "serial-base";

    public boolean test(SerialPortInfo info) {
        // Expose only devices having "serial" driver type.
        return info.driverType.equals(SERIAL_DRIVER_TYPE)
                // Keep built-in UARTs hidden (for security reasons).
                && !info.subsystem.equals(BUILT_IN_SERIAL_SUBSYSTEM);
    }
}
Loading