一、简介
OpenWrt路由操作系统的框架基础软件有很多,大部分是通用的软件模块,如 dhcp 、dnsmasq、iproute、cmwp、vpn、ipsec等等;OpenWrt还集成部分具有专属特征软件模块,也是OpenWRT系统核心框架软件组件,从此篇开始分析 《OpenWrt系统框架基础软件模块》系列文章。
OpenWrt 核心软件:procd、uci、libubox、ubus、ubox、luci、netifd 软件组件内容,此部分软件模块是构成OpenWrt框架基础软件。
procd 部分源码内容涉及内容较多,笔者前面几篇文章《详解 OpenWRT系统框架基础软件模块之libubox》等,都是为分析 procd、netifd 两部分内容准备铺垫,请同学们回顾一下前面的内容,在看此篇文章。
OpenWRT 系统中各软件组件之间的关系,如下图:
此图仅是把部分软件模块标识出来,其中还有很多软件模块如:uhttpd、rpcd、mwan3、hotplug、coldplug等等模块,都是依托openWRT的系统ubus总线来构建。此框图可以快速建立系统软件组件之间关系。
二、 procd 软件架构
我们先看看procd 文件夹中的源码结构,CmakeList.txt 文件是项目生产依据。关键内容如下:
cmake_minimum_required(VERSION 2.6)
PROJECT(procd C)
INCLUDE(GNUInstallDirs)
ADD_DEFINITIONS(-Os -ggdb -Wall -Werror --std=gnu99 -Wmissing-declarations)
SET(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "")
IF(APPLE)
INCLUDE_DIRECTORIES(/opt/local/include)
LINK_DIRECTORIES(/opt/local/lib)
ENDIF()
ADD_LIBRARY(setlbf SHARED service/setlbf.c)
INSTALL(TARGETS setlbf
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
)
SET(SOURCES procd.c signal.c state.c inittab.c rcS.c ubus.c system.c sysupgrade.c
service/service.c service/instance.c service/validate.c service/trigger.c service/watch.c
utils/utils.c)
IF(NOT DISABLE_INIT)
SET(SOURCES ${SOURCES} watchdog.c plug/coldplug.c plug/hotplug.c)
ENDIF()
IF(ZRAM_TMPFS)
ADD_DEFINITIONS(-DZRAM_TMPFS)
SET(SOURCES_ZRAM initd/zram.c)
ENDIF()
add_subdirectory(upgraded)
// 第一 系统初始化 /sbin/init 线程
// 内核检索 evn arg 环境变量、执行用户态初始化线程 /sbin/init;
IF(DISABLE_INIT)
ADD_DEFINITIONS(-DDISABLE_INIT)
ELSE()
ADD_EXECUTABLE(init initd/init.c initd/early.c initd/preinit.c initd/mkdev.c sysupgrade.c watchdog.c
utils/utils.c ${SOURCES_ZRAM})
TARGET_LINK_LIBRARIES(init ${LIBS})
INSTALL(TARGETS init
RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR}
)
// 第二 procd 线程
ADD_EXECUTABLE(procd ${SOURCES})
TARGET_LINK_LIBRARIES(procd ${LIBS})
INSTALL(TARGETS procd
RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR}
)
// 第三 udevtrigger 设备守护线程(udev)
ADD_EXECUTABLE(udevtrigger plug/udevtrigger.c)
INSTALL(TARGETS udevtrigger
RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR}
)
ENDIF()
//第四 utrace 用户空间 trace 工具
IF(UTRACE_SUPPORT)
ADD_EXECUTABLE(utrace trace/trace.c)
TARGET_LINK_LIBRARIES(utrace ${ubox} ${json} ${blobmsg_json})
INSTALL(TARGETS utrace
RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR}
)
ADD_DEPENDENCIES(utrace syscall-names-h)
此项目中涉及内容较多,请参考文件中注释。此部是OpenWrt系统核心基础功能实现,通过此部分内容可以清晰看到 系统启动流程。由于内容涉及面较宽,把分析源码过程文件贴出来,供大家参考作为源码关系导图使用。
源码分析关系记录
一、系统启动初始化 /sbin/init 线程
内核源码 init/main.c 文件中
static int __ref kernel_init(void *unused)
{
int ret;
kernel_init_freeable();
/* need to finish all async __init code before freeing the memory */
async_synchronize_full();
ftrace_free_init_mem();
free_initmem();
mark_readonly();
system_state = SYSTEM_RUNNING;
numa_default_policy();
rcu_end_inkernel_boot();
if (ramdisk_execute_command) {
ret = run_init_process(ramdisk_execute_command);
if (!ret)
return 0;
pr_err("Failed to execute %s (error %d)\n",
ramdisk_execute_command, ret);
}
/*
* We try each of these until one succeeds.
*
* The Bourne shell can be used instead of init if we are
* trying to recover a really broken machine.
*/
if (execute_command) {
ret = run_init_process(execute_command);
if (!ret)
return 0;
panic("Requested init %s failed (error %d).",
execute_command, ret);
}
if (!try_to_run_init_process("/sbin/init") ||
!try_to_run_init_process("/etc/init") ||
!try_to_run_init_process("/bin/init") ||
!try_to_run_init_process("/bin/sh"))
return 0;
panic("No working init found. Try passing init= option to kernel. "
"See Linux Documentation/admin-guide/init.rst for guidance.");
}
该函数检查 execute_command 环境参数(env arg)为空时、执行用户态初始化线程 /sbin/init,自此进入到用户态空间;
1.1 入口主函数
int main(int argc, char **argv)
(1). 初始化信号量
ulog_open(ULOG_KMSG, LOG_DAEMON, "init");
sigaction(SIGTERM, &sa_shutdown, NULL);
sigaction(SIGUSR1, &sa_shutdown, NULL);
sigaction(SIGUSR2, &sa_shutdown, NULL);
sigaction(SIGPWR, &sa_shutdown, NULL);
(2).
early();
(3).
cmdline();
(4).
watchdog_init(1);
(5).
pid = fork();
if (!pid) {
char *kmod[] = { "/sbin/kmodloader", "/etc/modules-boot.d/", NULL };
if (debug < 3)
patch_stdio("/dev/null");
execvp(kmod[0], kmod); // fork() 子进程执行函数 execvp() 函数
/*
* 函数说明:execvp()会从PATH 环境变量所指的目录中查找符合参数file 的文件名, 找到后便执行该文件, 然后将第二个参数argv 传给该欲执行的文件。
* 返回值:如果执行成功则函数不会返回, 执行失败则直接返回-1, 失败原因存于errno 中.
*
* 执行 /sbin/kmodloader 函数
* 执行 /etc/modules-boot.d/ 路径下的模块初始化程序,
* root@ixeRouter:~# ls /etc/modules-boot.d/
* 02-crypto-hash 30-fs-squashfs 50-usb-ohci
* 04-crypto-crc32c 30-gpio-button-hotplug 50-usb-uhci
* 09-crypto-aead 35-usb-ehci 51-usb-ohci-pci
* 09-crypto-manager 40-scsi-core 54-usb3
* 15-libphy 40-usb2 60-leds-gpio
* 15-mii 41-ata-ahci mmc
* 20-usb-core 42-usb2-pci sdhci-mt7620
*/
ERROR("Failed to start kmodloader: %m\n");
exit(EXIT_FAILURE);
}
if (pid <= 0) {
ERROR("Failed to start kmodloader instance: %m\n");
} else {//主线程执行下面程序
const struct timespec req = {0, 10 * 1000 * 1000};
int i;
for (i = 0; i < 1200; i++) {
if (waitpid(pid, NULL, WNOHANG) > 0)
break;
nanosleep(&req, NULL);
watchdog_ping();
}
}
(6).
uloop_init();
(7).
preinit();
char *init[] = { "/bin/sh", "/etc/preinit", NULL };
char *plug[] = { "/sbin/procd", "-h", "/etc/hotplug-preinit.json", NULL };
plugd_proc.pid = fork(); // fork() 创建子进程
if (!plugd_proc.pid) {
execvp(plug[0], plug); // 子进程 执行/sbin/procd 函数, 1# 线程创建成功
ERROR("Failed to start plugd: %m\n");
exit(EXIT_FAILURE);
}
if (plugd_proc.pid <= 0) {
ERROR("Failed to start new plugd instance: %m\n");
return;
}
uloop_process_add(&plugd_proc); // 把 procd 放到 uloop 链表中
setenv("PREINIT", "1", 1);
fd = creat("/tmp/.preinit", 0600);
if (fd < 0)
ERROR("Failed to create sentinel file: %m\n");
else
close(fd);
preinit_proc.cb = spawn_procd;
preinit_proc.pid = fork(); //fork() 创建子进程
if (!preinit_proc.pid) {
execvp(init[0], init); // 子进程 执行/bin/sh , 线程创建成功
ERROR("Failed to start preinit: %m\n");
exit(EXIT_FAILURE);
}
if (preinit_proc.pid <= 0) {
ERROR("Failed to start new preinit instance: %m\n");
return;
}
uloop_process_add(&preinit_proc); 把 spawn_procd 放到 uloop 链表中
(8).
uloop_run();
1.2 函数 spawn_procd 入口
static void spawn_procd(struct uloop_process *proc, int ret)
{
char *wdt_fd = watchdog_fd();
char *argv[] = { "/sbin/procd", NULL};
if (plugd_proc.pid > 0)
kill(plugd_proc.pid, SIGKILL);
unsetenv("INITRAMFS");
unsetenv("PREINIT");
unlink("/tmp/.preinit");
check_sysupgrade(); //检查系统软件更新
DEBUG(2, "Exec to real procd now\n");
if (wdt_fd)
setenv("WDTFD", wdt_fd, 1);
check_dbglvl();
if (debug > 0) {
snprintf(dbg, 2, "%d", debug);
setenv("DBGLVL", dbg, 1);
}
execvp(argv[0], argv);
}
1.3 系统软件检查更新
static void check_sysupgrade(void)
{
char *prefix = NULL, *path = NULL, *command = NULL;
size_t n;
if (chdir("/"))
return;
FILE *sysupgrade = fopen("/tmp/sysupgrade", "r");
if (!sysupgrade)
return;
n = 0;
if (getdelim(&prefix, &n, 0, sysupgrade) < 0)
goto fail;
n = 0;
if (getdelim(&path, &n, 0, sysupgrade) < 0)
goto fail;
n = 0;
if (getdelim(&command, &n, 0, sysupgrade) < 0)
goto fail;
fclose(sysupgrade);
sysupgrade_exec_upgraded(prefix, path, NULL, command, NULL); // 执行系统升级 /sbin/upgraded
while (true)
sleep(1);
fail:
fclose(sysupgrade);
free(prefix);
free(path);
free(command);
}
二、第1号进程 procd 函数分析
2.0 procd.c 文件中main函数
(1).
uloop_init();
(1.0)
uloop_init_pollfd();
epoll || kqueue 初始化
(1.1).
waker_init();
waker_fd.cb = waker_consume()
uloop_fd_add(&waker_fd, ULOOP_READ)
(1.2).
uloop_setup_signals(true);
uloop_install_handler(SIGINT, uloop_handle_sigint, &old_sigint, add); //初始化信号 SIGINT
uloop_install_handler(SIGTERM, uloop_handle_sigint, &old_sigterm, add); //初始化信号 SIGTERM
uloop_install_handler(SIGCHLD, uloop_sigchld, &old_sigchld, add); //初始化信号 SIGCHLD
uloop_ignore_signal(SIGPIPE, add); //初始化信号 SIGCHLD
(2).
procd_signal()
sigaction(SIGTERM, &sa_shutdown, NULL);
sigaction(SIGINT, &sa_shutdown, NULL);
sigaction(SIGUSR1, &sa_shutdown, NULL);
sigaction(SIGUSR2, &sa_shutdown, NULL);
sigaction(SIGPWR, &sa_shutdown, NULL);
signal_shutdown(); //reboot
sigaction(SIGSEGV, &sa_crash, NULL);
sigaction(SIGBUS, &sa_crash, NULL);
do_reboot(); //reboot
sigaction(SIGHUP, &sa_dummy, NULL);
sigaction(SIGKILL, &sa_dummy, NULL);
sigaction(SIGSTOP, &sa_dummy, NULL);
signal_dummy(); //输出错误提示信息
#ifndef DISABLE_INIT
reboot(RB_DISABLE_CAD); //
#endif
(3).
if (getpid() != 1)
procd_connect_ubus(); // 初始化 ubus 总线
else
procd_state_next(); //循环状态机变量,切换程序运行状态
void procd_state_next(void)
{
DEBUG(4, "Change state %d -> %d\n", state, state + 1);
state++;
state_enter(); //调用状态机处理函数 state_enter()
}
(4).
uloop_run();
uloop_run_timeout(-1);
uloop_handle_processes(); //
uloop_run_events(next_time); // 执行ubus的 RPC 回调函数
(5).
uloop_done();
2.1 函数 state_enter()
// 状态机参数定义
static int state = STATE_NONE;
enum {
STATE_NONE = 0,
STATE_EARLY,
STATE_UBUS,
STATE_INIT,
STATE_RUNNING,
STATE_SHUTDOWN,
STATE_HALT,
__STATE_MAX,
};
// 状态机运行次序: STATE_EARLY -> STATE_UBUS -> STATE_INIT -> STATE_RUNNING;
// STATE_SHUTDOWN、STATE_HALT
static void state_enter(void)
{
char ubus_cmd[] = "/sbin/ubusd";
switch (state) {
case STATE_EARLY:
LOG("- early -\n");
watchdog_init(0); // 初始化 看门狗
hotplug("/etc/hotplug.json"); // 热插拔函数入口参数是:/etc/hotplug.json 见文件内容
procd_coldplug(); // 冷插拔
break;
case STATE_UBUS:
// try to reopen incase the wdt was not available before coldplug
watchdog_init(0); // 初始化 看门狗
set_stdio("console"); // 设置console参数
LOG("- ubus -\n");
procd_connect_ubus(); // 连接ubus总线
service_start_early("ubus", ubus_cmd); // 启动 RPC
break;
case STATE_INIT:
LOG("- init -\n");
procd_inittab(); //执行 /etc/inittab 文件中脚本函数,
::sysinit:/etc/init.d/rcS S boot
::shutdown:/etc/init.d/rcS K shutdown
::askconsole:/usr/libexec/login.sh
procd_inittab_run("respawn"); //
procd_inittab_run("askconsole");
procd_inittab_run("askfirst");
procd_inittab_run("sysinit");
// switch to syslog log channel
ulog_open(ULOG_SYSLOG, LOG_DAEMON, "procd");
break;
case STATE_RUNNING:
LOG("- init complete -\n");
procd_inittab_run("respawnlate");
procd_inittab_run("askconsolelate");
break;
case STATE_SHUTDOWN:
/* Redirect output to the console for the users' benefit */
set_console(); // 依据 uboot传递到 /proc/cmdline文件中的参数,重定向 console 口
LOG("- shutdown -\n");
procd_inittab_run("shutdown"); // 初始化 shutdown 回调函数
sync();
break;
case STATE_HALT:
// To prevent killed processes from interrupting the sleep
signal(SIGCHLD, SIG_IGN);
LOG("- SIGTERM processes -\n");
kill(-1, SIGTERM);
sync();
sleep(1);
LOG("- SIGKILL processes -\n");
kill(-1, SIGKILL);
sync();
sleep(1);
#ifndef DISABLE_INIT
perform_halt();
#else
exit(EXIT_SUCCESS);
#endif
break;
default:
ERROR("Unhandled state %d\n", state);
return;
};
}
2.2 看门狗初始化,入参preinit=0
static struct uloop_timeout wdt_timeout;
static int wdt_fd = -1;
static int wdt_drv_timeout = 30; //看门狗超时时间:30s
static int wdt_frequency = 5; //喂狗频率:5s
static bool wdt_magicclose = false;
void watchdog_init(int preinit)
{
wdt_timeout.cb = watchdog_timeout_cb;
if (watchdog_open(!preinit) < 0)
return;
LOG("- watchdog -\n");
watchdog_set_drv_timeout(); //设置看门狗的时间=wdt_drv_timeout=30s
watchdog_timeout_cb(&wdt_timeout);
DEBUG(4, "Opened watchdog with timeout %ds\n", watchdog_timeout(0));
}
文件 /etc/hotplug.json 内容
[
[ "case", "ACTION", {
"add": [
[ "if",
[ "and",
[ "has", "MAJOR" ],
[ "has", "MINOR" ]
],
[
[ "if",
[ "eq", "DEVNAME",
[ "null", "full", "ptmx", "zero", "tty", "net", "random", "urandom" ]
],
[
[ "makedev", "/dev/%DEVNAME%", "0666" ],
[ "return" ]
]
],
[ "if",
[ "regex", "DEVNAME", "^snd" ],
[ "makedev", "/dev/%DEVNAME%", "0660", "audio" ]
],
[ "if",
[ "regex", "DEVNAME", "^tty" ],
[ "makedev", "/dev/%DEVNAME%", "0660", "dialout" ]
],
[ "if",
[ "has", "DEVNAME" ],
[ "makedev", "/dev/%DEVNAME%", "0600" ]
]
]
],
[ "if",
[ "has", "FIRMWARE" ],
[
[ "exec", "/sbin/hotplug-call", "%SUBSYSTEM%" ],
[ "load-firmware", "/lib/firmware" ],
[ "return" ]
]
]
],
"remove" : [
[ "if",
[ "and",
[ "has", "DEVNAME" ],
[ "has", "MAJOR" ],
[ "has", "MINOR" ]
],
[ "rm", "/dev/%DEVNAME%" ]
]
]
} ],
[ "if",
[ "and", //键盘事件
[ "has", "BUTTON" ],
[ "eq", "SUBSYSTEM", "button" ]
],
[ "button", "/etc/rc.button/%BUTTON%" ] // 键盘事件执行的脚本位置 /etc/rc.button/
],
[ "if",
[ "and", // usb串口事件
[ "eq", "SUBSYSTEM", "usb-serial" ],
[ "regex", "DEVNAME",
[ "^ttyUSB", "^ttyACM" ]
]
],
[ "exec", "/sbin/hotplug-call", "tty" ],
[ "if",
[ "isdir", "/etc/hotplug.d/%SUBSYSTEM%" ],
[ "exec", "/sbin/hotplug-call", "%SUBSYSTEM%" ]
]
]
]
2.3 inittab.c 文件 procd_inittab函数详解
// 数组 handlers[] 初始化参数
static struct init_handler handlers[] = {
{
.name = "sysinit",
.cb = runrc, // 执行 /etc/rc.d 路径下的文件
}, {
.name = "shutdown",
.cb = runrc,
}, {
.name = "askfirst",
.cb = askfirst,
.multi = 1,
}, {
.name = "askconsole",
.cb = askconsole,
.multi = 1,
}, {
.name = "respawn",
.cb = rcrespawn,
.multi = 1,
}, {
.name = "askconsolelate",
.cb = askconsole,
.multi = 1,
}, {
.name = "respawnlate",
.cb = rcrespawn,
.multi = 1,
}
};
struct init_action {
struct list_head list;
char *id;
char *argv[MAX_ARGS];
char *line;
struct init_handler *handler;
struct uloop_process proc;
int respawn;
struct uloop_timeout tout;
};
static const char *tab = "/etc/inittab";
static char *ask = "/sbin/askfirst";
static LIST_HEAD(actions);
入口函数
void procd_inittab(void){
// 读取 /etc/inittab 文件中内容,把此文件中 ::sysinit:/etc/init.d/rcS S boot
// ::shutdown:/etc/init.d/rcS K shutdown
// ::askconsole:/usr/libexec/login.sh
// 内容添加到 handlers[] 数组中,
while (fgets(line, LINE_LEN, fp)) {
add_action(a, tags[TAG_ACTION]) //逐项添加到
}
}
// 回调函数中,都调用此 fork_worker , fork 其他线程。
static void fork_worker(struct init_action *a)
{
pid_t p;
a->proc.pid = fork();
if (!a->proc.pid) {
p = setsid();
if (patch_stdio(a->id))
ERROR("Failed to setup i/o redirection\n");
ioctl(STDIN_FILENO, TIOCSCTTY, 1);
tcsetpgrp(STDIN_FILENO, p);
execvp(a->argv[0], a->argv); //给*a 参数,fork 子线程
ERROR("Failed to execute %s: %m\n", a->argv[0]);
exit(-1);
}
if (a->proc.pid > 0) {
DEBUG(4, "Launched new %s action, pid=%d\n",
a->handler->name,
(int) a->proc.pid);
uloop_process_add(&a->proc); //把 *a 注册到 ubus 总线上
}
}
// uloop 线程的回调参数
struct uloop_process
{
struct list_head list;
bool pending;
uloop_process_handler cb;
pid_t pid;
};
// ubus 总线rpc接口的全局链表
static struct list_head timeouts = LIST_HEAD_INIT(timeouts); //超时回调 rpc 接口函数
static struct list_head processes = LIST_HEAD_INIT(processes); //显性回调 rpc 接口函数
// 注册函数
int uloop_process_add(struct uloop_process *p)
{
struct uloop_process *tmp;
struct list_head *h = &processes;
if (p->pending)
return -1;
list_for_each_entry(tmp, &processes, list) {
if (tmp->pid > p->pid) {
h = &tmp->list;
break;
}
}
list_add_tail(&p->list, h); //把 *p 添加到 processes链表中,注册到 ubus 总线,
p->pending = true;
return 0;
}
三、 udevtrigger 设备扫描线程(udev)
udevtrigger.c 文件、main()入口函数分析
此部分是设备文件系统注册过程
int main(int argc, char *argv[], char *envp[])
{
openlog("udevtrigger", LOG_PID | LOG_CONS, LOG_DAEMON);
scan_subdir("/sys/bus", "/devices", false, 1);
scan_subdir("/sys/class", NULL, false, 1); // 处理 /sys/class 目录下设备
if (stat("/sys/class/block", &statbuf) != 0)
scan_subdir("/sys/block", NULL, true, 1); //处理系统分区信息
exit:
closelog();
return 0;
}
// 递归扫描设备目录
static void scan_subdir(const char *base, const char *subdir,
bool insert, int depth)
{
DIR *dir;
struct dirent *dent;
dir = opendir(base);
if (dir == NULL)
return;
for (dent = readdir(dir); dent != NULL; dent = readdir(dir)) {
char dirname[PATH_SIZE];
if (dent->d_name[0] == '.')
continue;
strlcpy(dirname, base, sizeof(dirname));
strlcat(dirname, "/", sizeof(dirname));
strlcat(dirname, dent->d_name, sizeof(dirname));
if (insert) {
int err;
err = device_list_insert(dirname);
if (err)
continue;
}
if (subdir)
strlcat(dirname, subdir, sizeof(base));
if (depth)
scan_subdir(dirname, NULL, true, depth - 1);
}
closedir(dir);
}
// 把设备添加到 设备链表中
static int device_list_insert(const char *path)
{
char devpath[PATH_SIZE];
struct stat statbuf;
dbg("add '%s'" , path);
/* we only have a device, if we have a dev and an uevent file */
if (!device_has_attribute(path, "/dev", S_IRUSR) ||
!device_has_attribute(path, "/uevent", S_IWUSR))
return -1;
strlcpy(devpath, &path[4], sizeof(devpath));
/* resolve possible link to real target */
if (lstat(path, &statbuf) < 0)
return -1;
if (S_ISLNK(statbuf.st_mode))
if (sysfs_resolve_link(devpath, sizeof(devpath)) != 0)
return -1;
trigger_uevent(devpath);
return 0;
}
// 把设备文件映射到内存,按照指定路径
static int sysfs_resolve_link(char *devpath, size_t size)
{
char link_path[PATH_SIZE];
char link_target[PATH_SIZE];
int len;
int i;
int back;
strlcpy(link_path, "/sys", sizeof(link_path));
strlcat(link_path, devpath, sizeof(link_path));
len = readlink(link_path, link_target, sizeof(link_target) - 1);
/*
* 定义函数:int readlink(const char * path, char * buf, size_t bufsiz);
* 函数说明:readlink()会将参数path 的符号连接内容存到参数buf 所指的内存空间,
* 返回的内容不是以NULL作字符串结尾, 但会将字符串的字符数返回.
* 若参数bufsiz 小于符号连接的内容长度, 过长的内容会被截断.
* 返回值:执行成功则传符号连接所指的文件路径字符串, 失败则返回-1, 错误代码存于errno.
*/
if (len <= 0)
return -1;
link_target[len] = '\0';
dbg("path link '%s' points to '%s'", devpath, link_target);
for (back = 0; strncmp(&link_target[back * 3], "../", 3) == 0; back++)
;
dbg("base '%s', tail '%s', back %i", devpath, &link_target[back * 3], back);
for (i = 0; i <= back; i++) {
char *pos = strrchr(devpath, '/');
if (pos == NULL)
return -1;
pos[0] = '\0';
}
dbg("after moving back '%s'", devpath);
strlcat(devpath, "/", size);
strlcat(devpath, &link_target[back * 3], size);
return 0;
}