/** * @file usb_moded-configfs.c * * Copyright (C) 2018 Jolla. All rights reserved. * * @author: Simo Piiroinen * * This program is free software; you can redistribute it and/or * modify it under the terms of the Lesser GNU General Public License * version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the Lesser GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "usb_moded-configfs.h" #include "usb_moded.h" #include "usb_moded-android.h" #include "usb_moded-common.h" #include "usb_moded-config-private.h" #include "usb_moded-log.h" #include "usb_moded-mac.h" #include #include #include #include #include #include /* ========================================================================= * * Constants * ========================================================================= */ #define FUNCTION_MASS_STORAGE "mass_storage.usb0" #define FUNCTION_RNDIS "rndis_bam.rndis" #define FUNCTION_MTP "ffs.mtp" #define CONFIGFS_GADGET "/config/usb_gadget/g1" #define CONFIGFS_CONFIG CONFIGFS_GADGET"/configs/b.1" #define CONFIGFS_FUNCTIONS CONFIGFS_GADGET"/functions" #define CONFIGFS_UDC CONFIGFS_GADGET"/UDC" #define CONFIGFS_ID_VENDOR CONFIGFS_GADGET"/idVendor" #define CONFIGFS_ID_PRODUCT CONFIGFS_GADGET"/idProduct" #define CONFIGFS_MANUFACTURER CONFIGFS_GADGET"/strings/0x409/manufacturer" #define CONFIGFS_PRODUCT CONFIGFS_GADGET"/strings/0x409/product" #define CONFIGFS_SERIAL CONFIGFS_GADGET"/strings/0x409/serialnumber" #define CONFIGFS_RNDIS_WCEIS CONFIGFS_FUNCTIONS"/"FUNCTION_RNDIS"/wceis" #define CONFIGFS_RNDIS_ETHADDR CONFIGFS_FUNCTIONS"/"FUNCTION_RNDIS"/ethaddr" /* ========================================================================= * * Prototypes * ========================================================================= */ /* -- configfs -- */ static int configfs_file_type (const char *path); static const char *configfs_function_path (char *buff, size_t size, const char *func, ...); static const char *configfs_unit_path (char *buff, size_t size, const char *func, const char *unit); static const char *configfs_config_path (char *buff, size_t size, const char *func); static bool configfs_mkdir (const char *path); static bool configfs_rmdir (const char *path); static const char *configfs_register_function (const char *function); #ifdef DEAD_CODE static bool configfs_unregister_function (const char *function); #endif static const char *configfs_add_unit (const char *function, const char *unit); static bool configfs_remove_unit (const char *function, const char *unit); static bool configfs_enable_function (const char *function); static bool configfs_disable_function (const char *function); static bool configfs_disable_all_functions (void); static char *configfs_strip (char *str); bool configfs_in_use (void); static bool configfs_probe (void); static const char *configfs_udc_enable_value (void); static bool configfs_write_file (const char *path, const char *text); static bool configfs_read_file (const char *path, char *buff, size_t size); static bool configfs_write_udc (const char *text); #ifdef DEAD_CODE static bool configfs_read_udc (char *buff, size_t size); #endif bool configfs_set_udc (bool enable); bool configfs_init_values (void); bool configfs_set_charging_mode (void); bool configfs_set_productid (const char *id); bool configfs_set_vendorid (const char *id); static const char *configfs_map_function (const char *func); bool configfs_set_function (const char *func); bool configfs_add_mass_storage_lun (int lun); bool configfs_remove_mass_storage_lun(int lun); bool configfs_set_mass_storage_attr (int lun, const char *attr, const char *value); /* ========================================================================= * * Data * ========================================================================= */ static int configfs_probed = -1; /* ========================================================================= * * Functions * ========================================================================= */ static int configfs_file_type(const char *path) { LOG_REGISTER_CONTEXT; int type = -1; if( !path ) goto EXIT; struct stat st; if( lstat(path, &st) == -1 ) goto EXIT; type = st.st_mode & S_IFMT; EXIT: return type; } static const char * configfs_function_path(char *buff, size_t size, const char *func, ...) { LOG_REGISTER_CONTEXT; char *pos = buff; char *end = buff + size; snprintf(pos, end-pos, "%s", CONFIGFS_FUNCTIONS); va_list va; va_start(va, func); while( func ) { pos = strchr(pos, 0); snprintf(pos, end-pos, "/%s", func); func = va_arg(va, char *); } va_end(va); return buff; } static const char * configfs_unit_path(char *buff, size_t size, const char *func, const char *unit) { LOG_REGISTER_CONTEXT; return configfs_function_path(buff, size, func, unit, NULL); } static const char * configfs_config_path(char *buff, size_t size, const char *func) { LOG_REGISTER_CONTEXT; snprintf(buff, size, "%s/%s", CONFIGFS_CONFIG, func); return buff; } static bool configfs_mkdir(const char *path) { LOG_REGISTER_CONTEXT; bool ack = false; if( mkdir(path, 0775) == -1 && errno != EEXIST ) { log_err("%s: mkdir failed: %m", path); goto EXIT; } if( configfs_file_type(path) != S_IFDIR ) { log_err("%s: is not a directory", path); goto EXIT; } ack = true; EXIT: return ack; } static bool configfs_rmdir(const char *path) { LOG_REGISTER_CONTEXT; bool ack = false; if( rmdir(path) == -1 && errno != ENOENT ) { log_err("%s: rmdir failed: %m", path); goto EXIT; } ack = true; EXIT: return ack; } static const char * configfs_register_function(const char *function) { LOG_REGISTER_CONTEXT; const char *res = 0; static char fpath[256]; configfs_function_path(fpath, sizeof fpath, function, NULL); if( !configfs_mkdir(fpath) ) goto EXIT; log_debug("function %s is registered", function); res = fpath; EXIT: return res; } #ifdef DEAD_CODE static bool configfs_unregister_function(const char *function) { LOG_REGISTER_CONTEXT; bool ack = false; char fpath[256]; configfs_function_path(fpath, sizeof fpath, function, NULL); if( !configfs_rmdir(fpath) ) goto EXIT; log_debug("function %s is unregistered", function); ack = true; EXIT: return ack; } #endif static const char * configfs_add_unit(const char *function, const char *unit) { LOG_REGISTER_CONTEXT; const char *res = 0; static char upath[256]; configfs_unit_path(upath, sizeof upath, function, unit); if( !configfs_mkdir(upath) ) goto EXIT; log_debug("function %s unit %s added", function, unit); res = upath; EXIT: return res; } static bool configfs_remove_unit(const char *function, const char *unit) { LOG_REGISTER_CONTEXT; bool ack = false; static char upath[256]; configfs_unit_path(upath, sizeof upath, function, unit); if( !configfs_rmdir(upath) ) goto EXIT; log_debug("function %s unit %s removed", function, unit); ack = true; EXIT: return ack; } static bool configfs_enable_function(const char *function) { LOG_REGISTER_CONTEXT; bool ack = false; const char *fpath = configfs_register_function(function); if( !fpath ) { log_err("function %s is not registered", function); goto EXIT; } char cpath[256]; configfs_config_path(cpath, sizeof cpath, function); switch( configfs_file_type(cpath) ) { case S_IFLNK: if( unlink(cpath) == -1 ) { log_err("%s: unlink failed: %m", cpath); goto EXIT; } /* fall through */ case -1: if( symlink(fpath, cpath) == -1 ) { log_err("%s: failed to symlink to %s: %m", cpath, fpath); goto EXIT; } break; default: log_err("%s: is not a symlink", cpath); goto EXIT; } log_debug("function %s is enabled", function); ack = true; EXIT: return ack; } static bool configfs_disable_function(const char *function) { LOG_REGISTER_CONTEXT; bool ack = false; char cpath[256]; configfs_config_path(cpath, sizeof cpath, function); if( configfs_file_type(cpath) != S_IFLNK ) { log_err("%s: is not a symlink", cpath); goto EXIT; } if( unlink(cpath) == -1 ) { log_err("%s: unlink failed: %m", cpath); goto EXIT; } log_debug("function %s is disabled", function); ack = true; EXIT: return ack; } static bool configfs_disable_all_functions(void) { LOG_REGISTER_CONTEXT; bool ack = false; DIR *dir = 0; if( !(dir = opendir(CONFIGFS_CONFIG)) ) { log_err("%s: opendir failed: %m", CONFIGFS_CONFIG); goto EXIT; } ack = true; struct dirent *de; while( (de = readdir(dir)) ) { if( de->d_type != DT_LNK ) continue; if( !configfs_disable_function(de->d_name) ) ack = false; } if( ack ) log_debug("all functions are disabled"); EXIT: if( dir ) closedir(dir); return ack; } static char *configfs_strip(char *str) { LOG_REGISTER_CONTEXT; unsigned char *src = (unsigned char *)str; unsigned char *dst = (unsigned char *)str; while( *src > 0 && *src <= 32 ) ++src; for( ;; ) { while( *src > 32 ) *dst++ = *src++; while( *src > 0 && *src <= 32 ) ++src; if( *src == 0 ) break; *dst++ = ' '; } *dst = 0; return str; } bool configfs_in_use(void) { LOG_REGISTER_CONTEXT; if( configfs_probed < 0 ) log_debug("configfs_in_use() called before configfs_probe()"); return configfs_probed > 0; } static bool configfs_probe(void) { LOG_REGISTER_CONTEXT; if( configfs_probed <= 0 ) { configfs_probed = access(CONFIGFS_GADGET, F_OK) == 0; log_warning("CONFIGFS %sdetected", configfs_probed ? "" : "not "); } return configfs_in_use(); } static const char * configfs_udc_enable_value(void) { LOG_REGISTER_CONTEXT; static bool probed = false; static char *value = 0; if( !probed ) { probed = true; /* Find first symlink in /sys/class/udc directory */ struct dirent *de; DIR *dir = opendir("/sys/class/udc"); if( dir ) { while( (de = readdir(dir)) ) { if( de->d_type != DT_LNK ) continue; if( de->d_name[0] == '.' ) continue; value = strdup(de->d_name); break; } closedir(dir); } } return value ?: ""; } static bool configfs_write_file(const char *path, const char *text) { LOG_REGISTER_CONTEXT; bool ack = false; int fd = -1; if( !path || !text ) goto EXIT; log_debug("WRITE %s '%s'", path, text); char buff[64]; snprintf(buff, sizeof buff, "%s\n", text); size_t size = strlen(buff); if( (fd = open(path, O_WRONLY)) == -1 ) { log_err("%s: can't open for writing: %m", path); goto EXIT; } int rc = write(fd, text, size); if( rc == -1 ) { log_err("%s: write failure: %m", path); goto EXIT; } if( (size_t)rc != size ) { log_err("%s: write failure: partial success", path); goto EXIT; } ack = true; EXIT: if( fd != -1 ) close(fd); return ack; } static bool configfs_read_file(const char *path, char *buff, size_t size) { LOG_REGISTER_CONTEXT; bool ack = false; int fd = -1; if( !path || !buff ) goto EXIT; if( size < 2 ) goto EXIT; if( (fd = open(path, O_RDONLY)) == -1 ) { log_err("%s: can't open for reading: %m", path); goto EXIT; } int rc = read(fd, buff, size - 1); if( rc == -1 ) { log_err("%s: read failure: %m", path); goto EXIT; } buff[rc] = 0; configfs_strip(buff); ack = true; log_debug("READ %s '%s'", path, buff); EXIT: if( fd != -1 ) close(fd); return ack; } #ifdef DEAD_CODE static bool configfs_read_udc(char *buff, size_t size) { LOG_REGISTER_CONTEXT; return configfs_read_file(CONFIGFS_UDC, buff, size); } #endif static bool configfs_write_udc(const char *text) { LOG_REGISTER_CONTEXT; bool ack = false; char prev[64]; if( !configfs_read_file(CONFIGFS_UDC, prev, sizeof prev) ) goto EXIT; if( strcmp(prev, text) ) { if( !configfs_write_file(CONFIGFS_UDC, text) ) goto EXIT; } ack = true; EXIT: return ack; } bool configfs_set_udc(bool enable) { LOG_REGISTER_CONTEXT; log_debug("UDC - %s", enable ? "ENABLE" : "DISABLE"); const char *value = ""; if( enable ) value = configfs_udc_enable_value(); return configfs_write_udc(value); } /** initialize the basic configfs values */ bool configfs_init_values(void) { LOG_REGISTER_CONTEXT; if( !configfs_probe() ) goto EXIT; /* Disable */ configfs_set_udc(false); /* Configure */ gchar *text; if( (text = config_get_android_vendor_id()) ) { configfs_write_file(CONFIGFS_ID_VENDOR, text); g_free(text); } if( (text = config_get_android_product_id()) ) { configfs_write_file(CONFIGFS_ID_PRODUCT, text); g_free(text); } if( (text = config_get_android_manufacturer()) ) { configfs_write_file(CONFIGFS_MANUFACTURER, text); g_free(text); } if( (text = config_get_android_product()) ) { configfs_write_file(CONFIGFS_PRODUCT, text); g_free(text); } if( (text = android_get_serial()) ) { configfs_write_file(CONFIGFS_SERIAL, text); g_free(text); } /* Prep: charging_only */ configfs_register_function(FUNCTION_MASS_STORAGE); /* Prep: mtp_mode */ configfs_register_function(FUNCTION_MTP); if( access("/dev/mtp/ep0", F_OK) == -1 ) { common_system("/bin/mount -o uid=100000,gid=100000 -t functionfs mtp /dev/mtp"); } /* Prep: developer_mode */ configfs_register_function(FUNCTION_RNDIS); if( (text = mac_read_mac()) ) { configfs_write_file(CONFIGFS_RNDIS_ETHADDR, text); g_free(text); } /* For rndis to be discovered correctly in M$ Windows (vista and later) */ configfs_write_file(CONFIGFS_RNDIS_WCEIS, "1"); /* Leave disabled, will enable on cable connect detected */ EXIT: return configfs_in_use(); } /* Set a charging mode for the configfs gadget * * @return true if successful, false on failure */ bool configfs_set_charging_mode(void) { LOG_REGISTER_CONTEXT; bool ack = false; if( !configfs_set_function("mass_storage") ) goto EXIT; /* TODO: make this configurable */ configfs_set_productid("0AFE"); if( !configfs_set_udc(true) ) goto EXIT; ack = true; EXIT: log_debug("CONFIGFS %s() -> %d", __func__, ack); return ack; } /* Set a product id for the configfs gadget * * @return true if successful, false on failure */ bool configfs_set_productid(const char *id) { LOG_REGISTER_CONTEXT; bool ack = false; if( id && configfs_in_use() ) { /* Config files have things like "0A02". * Kernel wants to see "0x0a02" ... */ char *end = 0; unsigned num = strtol(id, &end, 16); char str[16]; if( end > id && *end == 0 ) { snprintf(str, sizeof str, "0x%04x", num); id = str; } ack = configfs_write_file(CONFIGFS_ID_PRODUCT, id); } log_debug("CONFIGFS %s(%s) -> %d", __func__, id, ack); return ack; } /* Set a vendor id for the configfs gadget * * @return true if successful, false on failure */ bool configfs_set_vendorid(const char *id) { LOG_REGISTER_CONTEXT; bool ack = false; if( id && configfs_in_use() ) { log_debug("%s(%s) was called", __func__, id); /* Config files have things like "0A02". * Kernel wants to see "0x0a02" ... */ char *end = 0; unsigned num = strtol(id, &end, 16); char str[16]; if( end > id && *end == 0 ) { snprintf(str, sizeof str, "0x%04x", num); id = str; } ack = configfs_write_file(CONFIGFS_ID_VENDOR, id); } log_debug("CONFIGFS %s(%s) -> %d", __func__, id, ack); return ack; } static const char * configfs_map_function(const char *func) { LOG_REGISTER_CONTEXT; if( func == 0 ) ; else if( !strcmp(func, "mass_storage") ) func = FUNCTION_MASS_STORAGE; else if( !strcmp(func, "rndis") ) func = FUNCTION_RNDIS; else if( !strcmp(func, "mtp") ) func = FUNCTION_MTP; else if( !strcmp(func, "ffs") ) // existing config files ... func = FUNCTION_MTP; return func; } /* Set a function * * @param func Mame of function to enable, or NULL to disable all * * @return true if successful, false on failure */ bool configfs_set_function(const char *func) { LOG_REGISTER_CONTEXT; bool ack = false; if( !configfs_in_use() ) goto EXIT; /* Normalize names used by usb-moded itself and already * existing configuration files etc. */ func = configfs_map_function(func); if( !configfs_set_udc(false) ) goto EXIT; if( !configfs_disable_all_functions() ) goto EXIT; if( func && !configfs_enable_function(func) ) goto EXIT; /* Leave disabled, so that caller can adjust attributes * etc before enabling */ ack = true; EXIT: log_debug("CONFIGFS %s(%s) -> %d", __func__, func, ack); return ack; } bool configfs_add_mass_storage_lun(int lun) { LOG_REGISTER_CONTEXT; bool ack = false; if( !configfs_in_use() ) goto EXIT; char unit[32]; snprintf(unit, sizeof unit, "lun.%d", lun); ack = configfs_add_unit(FUNCTION_MASS_STORAGE, unit) != 0; EXIT: return ack; } bool configfs_remove_mass_storage_lun(int lun) { LOG_REGISTER_CONTEXT; bool ack = false; if( !configfs_in_use() ) goto EXIT; char unit[32]; snprintf(unit, sizeof unit, "lun.%d", lun); ack = configfs_remove_unit(FUNCTION_MASS_STORAGE, unit); EXIT: return ack; } bool configfs_set_mass_storage_attr(int lun, const char *attr, const char *value) { LOG_REGISTER_CONTEXT; bool ack = false; if( !configfs_in_use() ) goto EXIT; char unit[32]; snprintf(unit, sizeof unit, "lun.%d", lun); char path[256]; configfs_function_path(path, sizeof path, FUNCTION_MASS_STORAGE, unit, attr, NULL); ack = configfs_write_file(path, value); EXIT: return ack; }