AF_UNIX SEQPACKET/DGRAM, writable /dev overlay, uevent netlink socket; eudev packages
udevd now starts, reads its rules and listens. Still to come: the sysfs device tree and writable uevent files so a trigger has something to replay. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EVRhxi9zpdW2uDUN4FVcao
This commit is contained in:
parent
ed81f9a07a
commit
a9f8cb7e0f
7 changed files with 457 additions and 40 deletions
|
|
@ -539,6 +539,10 @@ int socknl_copy(struct socket*, uint, char*, int);
|
||||||
void socknl_take(struct socket*, uint);
|
void socknl_take(struct socket*, uint);
|
||||||
int sock_shutdown(struct socket*, int how);
|
int sock_shutdown(struct socket*, int how);
|
||||||
int sockpair(struct socket **a, struct socket **b);
|
int sockpair(struct socket **a, struct socket **b);
|
||||||
|
int sockpair_type(struct socket **a, struct socket **b, int type);
|
||||||
|
int sock_is_packet(struct socket *s);
|
||||||
|
int sock_netlink_bind(struct socket *s, uint groups);
|
||||||
|
void uevent_send(uint groups, const char *msg, int len, struct socket *from);
|
||||||
const char* sockname(struct socket*);
|
const char* sockname(struct socket*);
|
||||||
// AF_INET addressing. Separate from sockbind/sockconnect because the families
|
// AF_INET addressing. Separate from sockbind/sockconnect because the families
|
||||||
// do not name the same kind of thing; `addr` and `port` are network order.
|
// do not name the same kind of thing; `addr` and `port` are network order.
|
||||||
|
|
|
||||||
|
|
@ -24,10 +24,14 @@
|
||||||
#define AF_NETLINK 16
|
#define AF_NETLINK 16
|
||||||
#define PF_NETLINK AF_NETLINK
|
#define PF_NETLINK AF_NETLINK
|
||||||
#define NETLINK_ROUTE 0
|
#define NETLINK_ROUTE 0
|
||||||
|
#define NETLINK_KOBJECT_UEVENT 15
|
||||||
|
#define UEVENT_GROUP_KERNEL 1 // what the kernel announces
|
||||||
|
#define UEVENT_GROUP_UDEV 2 // what udevd re-announces after its rules
|
||||||
|
|
||||||
#define SOCK_STREAM 1
|
#define SOCK_STREAM 1
|
||||||
#define SOCK_DGRAM 2
|
#define SOCK_DGRAM 2
|
||||||
#define SOCK_RAW 3
|
#define SOCK_RAW 3
|
||||||
|
#define SOCK_SEQPACKET 5
|
||||||
|
|
||||||
// Flags ORed into socket()'s and accept4()'s `type`/`flags` argument. Linux's
|
// Flags ORed into socket()'s and accept4()'s `type`/`flags` argument. Linux's
|
||||||
// values, because a musl program passes Linux's constants.
|
// values, because a musl program passes Linux's constants.
|
||||||
|
|
@ -337,6 +341,10 @@ struct socket {
|
||||||
char data[SOCKBUF];
|
char data[SOCKBUF];
|
||||||
uint nread, nwrite;
|
uint nread, nwrite;
|
||||||
|
|
||||||
|
// AF_NETLINK only: which protocol, and the multicast groups bound.
|
||||||
|
int nlproto;
|
||||||
|
uint nlgroups;
|
||||||
|
|
||||||
// Connections waiting to be accepted, on a listener.
|
// Connections waiting to be accepted, on a listener.
|
||||||
struct socket *queue[NBACKLOG];
|
struct socket *queue[NBACKLOG];
|
||||||
int nqueue;
|
int nqueue;
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@
|
||||||
#include "uapi/errno.h"
|
#include "uapi/errno.h"
|
||||||
#include "fs/blkdev.h"
|
#include "fs/blkdev.h"
|
||||||
#include "dev/pty.h"
|
#include "dev/pty.h"
|
||||||
|
#include "core/uaccess.h"
|
||||||
|
|
||||||
// Inode numbering. Two directories and a flat table of nodes, so the number is
|
// Inode numbering. Two directories and a flat table of nodes, so the number is
|
||||||
// either a directory id or an index into devnodes[] offset past them.
|
// either a directory id or an index into devnodes[] offset past them.
|
||||||
|
|
@ -57,6 +58,52 @@
|
||||||
#define DEVFS_PTS0 8192ULL
|
#define DEVFS_PTS0 8192ULL
|
||||||
#define DEVFS_PTM0 12288ULL
|
#define DEVFS_PTM0 12288ULL
|
||||||
|
|
||||||
|
// What udev adds to /dev: directories (/dev/input/by-path, /dev/disk/...) and
|
||||||
|
// symlinks (/dev/fd, /dev/stdin, /dev/char/13:64, /dev/disk/by-id/...). The
|
||||||
|
// device nodes themselves stay the kernel's; this is a small table of names
|
||||||
|
// that live only in memory, as they would in a tmpfs, listed after the real
|
||||||
|
// entries of whichever directory they are in. Nothing here can hold data: a
|
||||||
|
// regular file in /dev is refused.
|
||||||
|
#define DEVFS_EXTRA0 32768ULL
|
||||||
|
#define NEXTRA 192
|
||||||
|
#define EXTRA_NAME 48
|
||||||
|
#define EXTRA_TARGET 112
|
||||||
|
|
||||||
|
struct extra {
|
||||||
|
uchar used; // allocated (an inode exists or is about to)
|
||||||
|
uchar linked; // has a name in a directory
|
||||||
|
short type; // T_DIR or T_SYMLINK
|
||||||
|
uint64 parent; // inum of the directory it is in
|
||||||
|
ushort tlen;
|
||||||
|
char name[EXTRA_NAME];
|
||||||
|
char target[EXTRA_TARGET];
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct {
|
||||||
|
struct spinlock lock;
|
||||||
|
struct extra e[NEXTRA];
|
||||||
|
} extras;
|
||||||
|
|
||||||
|
static int extras_ready;
|
||||||
|
|
||||||
|
static void
|
||||||
|
extras_init(void)
|
||||||
|
{
|
||||||
|
if (!extras_ready) {
|
||||||
|
initlock(&extras.lock, "devfsx");
|
||||||
|
extras_ready = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
extra_of(uint64 inum)
|
||||||
|
{
|
||||||
|
if (inum < DEVFS_EXTRA0)
|
||||||
|
return -1;
|
||||||
|
uint64 i = inum - DEVFS_EXTRA0;
|
||||||
|
return i < NEXTRA ? (int)i : -1;
|
||||||
|
}
|
||||||
|
|
||||||
// Which directory a node lives in. Only two exist and a third would want a real
|
// Which directory a node lives in. Only two exist and a third would want a real
|
||||||
// tree rather than a flag, which is the point at which this file should grow
|
// tree rather than a flag, which is the point at which this file should grow
|
||||||
// one instead of adding DEV_DIR_THIRD.
|
// one instead of adding DEV_DIR_THIRD.
|
||||||
|
|
@ -158,7 +205,21 @@ devfs_iload(struct inode *ip)
|
||||||
ip->major = ip->minor = 0;
|
ip->major = ip->minor = 0;
|
||||||
ip->size = 0;
|
ip->size = 0;
|
||||||
|
|
||||||
if (ip->inum == DEVFS_ROOT || ip->inum == DEVFS_INPUT ||
|
int x = extra_of(ip->inum);
|
||||||
|
if (x >= 0) {
|
||||||
|
extras_init();
|
||||||
|
acquire(&extras.lock);
|
||||||
|
if (extras.e[x].used) {
|
||||||
|
ip->type = extras.e[x].type;
|
||||||
|
ip->nlink = extras.e[x].type == T_DIR ? 2 : 1;
|
||||||
|
ip->size = extras.e[x].tlen;
|
||||||
|
ip->mode = extras.e[x].type == T_DIR ? 0755 : 0777;
|
||||||
|
} else {
|
||||||
|
ip->type = 0;
|
||||||
|
ip->nlink = 0;
|
||||||
|
}
|
||||||
|
release(&extras.lock);
|
||||||
|
} else if (ip->inum == DEVFS_ROOT || ip->inum == DEVFS_INPUT ||
|
||||||
ip->inum == DEVFS_PTS) {
|
ip->inum == DEVFS_PTS) {
|
||||||
ip->type = T_DIR;
|
ip->type = T_DIR;
|
||||||
ip->nlink = 2;
|
ip->nlink = 2;
|
||||||
|
|
@ -193,16 +254,69 @@ devfs_iload(struct inode *ip)
|
||||||
}
|
}
|
||||||
|
|
||||||
static void devfs_istore(struct inode *ip) { (void)ip; }
|
static void devfs_istore(struct inode *ip) { (void)ip; }
|
||||||
static void devfs_ifree(struct inode *ip) { (void)ip; }
|
static void
|
||||||
|
devfs_ifree(struct inode *ip)
|
||||||
|
{
|
||||||
|
int x = extra_of(ip->inum);
|
||||||
|
if (x < 0)
|
||||||
|
return;
|
||||||
|
extras_init();
|
||||||
|
acquire(&extras.lock);
|
||||||
|
extras.e[x].used = 0;
|
||||||
|
release(&extras.lock);
|
||||||
|
}
|
||||||
static void devfs_truncate(struct inode *ip, uint64 n) { (void)ip; (void)n; }
|
static void devfs_truncate(struct inode *ip, uint64 n) { (void)ip; (void)n; }
|
||||||
|
|
||||||
// Never reached for a device node: fileread and filewrite see T_DEV and go
|
// Never reached for a device node: fileread and filewrite see T_DEV and go
|
||||||
// through devsw. Present because fs_ops has the slots, and answering -EIO is
|
// through devsw. Present because fs_ops has the slots, and answering -EIO is
|
||||||
// better than a null pointer if something ever does arrive here.
|
// better than a null pointer if something ever does arrive here.
|
||||||
static int devfs_read(struct inode *ip, int u, uint64 d, uint64 o, uint n)
|
static int
|
||||||
{ (void)ip; (void)u; (void)d; (void)o; (void)n; return -1; }
|
devfs_read(struct inode *ip, int u, uint64 d, uint64 o, uint n)
|
||||||
static int devfs_write(struct inode *ip, int u, uint64 s, uint64 o, uint n)
|
{
|
||||||
{ (void)ip; (void)u; (void)s; (void)o; (void)n; return -1; }
|
int x = extra_of(ip->inum);
|
||||||
|
if (x < 0 || ip->type != T_SYMLINK)
|
||||||
|
return -1;
|
||||||
|
extras_init();
|
||||||
|
char buf[EXTRA_TARGET];
|
||||||
|
acquire(&extras.lock);
|
||||||
|
uint len = extras.e[x].tlen;
|
||||||
|
memmove(buf, extras.e[x].target, len);
|
||||||
|
release(&extras.lock);
|
||||||
|
if (o >= len)
|
||||||
|
return 0;
|
||||||
|
if (o + n > len)
|
||||||
|
n = (uint)(len - o);
|
||||||
|
if (u) {
|
||||||
|
if (copy_to_user(d, buf + o, n) < 0)
|
||||||
|
return -EFAULT;
|
||||||
|
} else {
|
||||||
|
memmove((char *)d, buf + o, n);
|
||||||
|
}
|
||||||
|
return (int)n;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only a symlink's target can be written, once, from offset 0.
|
||||||
|
static int
|
||||||
|
devfs_write(struct inode *ip, int u, uint64 s, uint64 o, uint n)
|
||||||
|
{
|
||||||
|
int x = extra_of(ip->inum);
|
||||||
|
if (x < 0 || ip->type != T_SYMLINK || o != 0 || n >= EXTRA_TARGET)
|
||||||
|
return -1;
|
||||||
|
extras_init();
|
||||||
|
char buf[EXTRA_TARGET];
|
||||||
|
if (u) {
|
||||||
|
if (copy_from_user(buf, s, n) < 0)
|
||||||
|
return -EFAULT;
|
||||||
|
} else {
|
||||||
|
memmove(buf, (char *)s, n);
|
||||||
|
}
|
||||||
|
acquire(&extras.lock);
|
||||||
|
memmove(extras.e[x].target, buf, n);
|
||||||
|
extras.e[x].tlen = (ushort)n;
|
||||||
|
release(&extras.lock);
|
||||||
|
ip->size = n;
|
||||||
|
return (int)n;
|
||||||
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
streq(const char *a, const char *b)
|
streq(const char *a, const char *b)
|
||||||
|
|
@ -211,12 +325,41 @@ streq(const char *a, const char *b)
|
||||||
return *a == *b;
|
return *a == *b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static struct inode *
|
||||||
|
extra_find(uint64 parent, const char *name)
|
||||||
|
{
|
||||||
|
extras_init();
|
||||||
|
acquire(&extras.lock);
|
||||||
|
for (int i = 0; i < NEXTRA; i++)
|
||||||
|
if (extras.e[i].used && extras.e[i].linked && extras.e[i].parent == parent &&
|
||||||
|
streq(name, extras.e[i].name)) {
|
||||||
|
release(&extras.lock);
|
||||||
|
return iget(DEVFS_DEV, DEVFS_EXTRA0 + (uint64)i);
|
||||||
|
}
|
||||||
|
release(&extras.lock);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static struct inode *
|
static struct inode *
|
||||||
devfs_dirlookup(struct inode *dp, char *name, uint *poff)
|
devfs_dirlookup(struct inode *dp, char *name, uint *poff)
|
||||||
{
|
{
|
||||||
if (poff)
|
if (poff)
|
||||||
*poff = 0;
|
*poff = 0;
|
||||||
|
|
||||||
|
int dx = extra_of(dp->inum);
|
||||||
|
if (dx >= 0) {
|
||||||
|
extras_init();
|
||||||
|
if (streq(name, "."))
|
||||||
|
return iget(DEVFS_DEV, dp->inum);
|
||||||
|
if (streq(name, "..")) {
|
||||||
|
acquire(&extras.lock);
|
||||||
|
uint64 up = extras.e[dx].parent;
|
||||||
|
release(&extras.lock);
|
||||||
|
return iget(DEVFS_DEV, up ? up : DEVFS_ROOT);
|
||||||
|
}
|
||||||
|
return extra_find(dp->inum, name);
|
||||||
|
}
|
||||||
|
|
||||||
if (streq(name, ".") || (streq(name, "..") && dp->inum == DEVFS_ROOT))
|
if (streq(name, ".") || (streq(name, "..") && dp->inum == DEVFS_ROOT))
|
||||||
return iget(DEVFS_DEV, dp->inum);
|
return iget(DEVFS_DEV, dp->inum);
|
||||||
if (streq(name, ".."))
|
if (streq(name, ".."))
|
||||||
|
|
@ -261,19 +404,85 @@ devfs_dirlookup(struct inode *dp, char *name, uint *poff)
|
||||||
if (streq(name, blkdev_name((uint)b)))
|
if (streq(name, blkdev_name((uint)b)))
|
||||||
return iget(DEVFS_DEV, DEVFS_BLK0 + (uint64)b);
|
return iget(DEVFS_DEV, DEVFS_BLK0 + (uint64)b);
|
||||||
|
|
||||||
|
return extra_find(dp->inum, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// What can be created here: a directory or a symlink, by ialloc and then
|
||||||
|
// dirlink -- the order every filesystem is driven in. The device nodes
|
||||||
|
// themselves are the kernel's: mknod answers EROFS by never getting an inode.
|
||||||
|
static struct inode *
|
||||||
|
devfs_ialloc(uint dev, short type)
|
||||||
|
{
|
||||||
|
(void)dev;
|
||||||
|
if (type != T_DIR && type != T_SYMLINK)
|
||||||
|
return 0;
|
||||||
|
extras_init();
|
||||||
|
acquire(&extras.lock);
|
||||||
|
for (int i = 0; i < NEXTRA; i++)
|
||||||
|
if (!extras.e[i].used) {
|
||||||
|
memset(&extras.e[i], 0, sizeof(extras.e[i]));
|
||||||
|
extras.e[i].used = 1;
|
||||||
|
extras.e[i].type = type;
|
||||||
|
release(&extras.lock);
|
||||||
|
return iget(DEVFS_DEV, DEVFS_EXTRA0 + (uint64)i);
|
||||||
|
}
|
||||||
|
release(&extras.lock);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// A read-only filesystem, and these say so rather than pretending. mknod on
|
static int
|
||||||
// /dev returning EROFS is the correct answer: the node either exists because
|
devfs_dirlink(struct inode *dp, char *name, uint64 inum)
|
||||||
// the kernel has the device, or it does not exist because it has not.
|
{
|
||||||
static int devfs_dirlink(struct inode *dp, char *name, uint64 inum)
|
int x = extra_of(inum);
|
||||||
{ (void)dp; (void)name; (void)inum; return -EROFS; }
|
if (streq(name, ".") || streq(name, ".."))
|
||||||
static int devfs_dirunlink(struct inode *dp, char *name, uint off)
|
return 0; /* a new directory's own dots */
|
||||||
{ (void)dp; (void)name; (void)off; return -EROFS; }
|
if (x < 0 || strlen(name) >= EXTRA_NAME)
|
||||||
static int devfs_dirempty(struct inode *dp) { (void)dp; return 0; }
|
return -EINVAL;
|
||||||
static struct inode *devfs_ialloc(uint dev, short type)
|
extras_init();
|
||||||
{ (void)dev; (void)type; return 0; }
|
acquire(&extras.lock);
|
||||||
|
if (!extras.e[x].used || extras.e[x].linked) {
|
||||||
|
release(&extras.lock);
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
extras.e[x].parent = dp->inum;
|
||||||
|
extras.e[x].linked = 1;
|
||||||
|
memmove(extras.e[x].name, name, strlen(name) + 1);
|
||||||
|
release(&extras.lock);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
devfs_dirempty(struct inode *dp)
|
||||||
|
{
|
||||||
|
if (extra_of(dp->inum) < 0)
|
||||||
|
return 0;
|
||||||
|
extras_init();
|
||||||
|
acquire(&extras.lock);
|
||||||
|
for (int i = 0; i < NEXTRA; i++)
|
||||||
|
if (extras.e[i].used && extras.e[i].linked && extras.e[i].parent == dp->inum) {
|
||||||
|
release(&extras.lock);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
release(&extras.lock);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
devfs_dirunlink(struct inode *dp, char *name, uint off)
|
||||||
|
{
|
||||||
|
(void)off;
|
||||||
|
extras_init();
|
||||||
|
acquire(&extras.lock);
|
||||||
|
for (int i = 0; i < NEXTRA; i++)
|
||||||
|
if (extras.e[i].used && extras.e[i].linked && extras.e[i].parent == dp->inum &&
|
||||||
|
streq(name, extras.e[i].name)) {
|
||||||
|
extras.e[i].linked = 0; /* the inode goes when nobody holds it */
|
||||||
|
release(&extras.lock);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
release(&extras.lock);
|
||||||
|
return -EROFS; /* a device node is the kernel's */
|
||||||
|
}
|
||||||
|
|
||||||
struct linux_dirent64 {
|
struct linux_dirent64 {
|
||||||
uint64 d_ino;
|
uint64 d_ino;
|
||||||
|
|
@ -312,8 +521,9 @@ devfs_getdents(struct inode *dp, char *out, int cap, uint64 *poff)
|
||||||
int used = 0;
|
int used = 0;
|
||||||
uint64 n = *poff;
|
uint64 n = *poff;
|
||||||
|
|
||||||
|
int isx = extra_of(dp->inum) >= 0;
|
||||||
if (dp->inum != DEVFS_ROOT && dp->inum != DEVFS_INPUT &&
|
if (dp->inum != DEVFS_ROOT && dp->inum != DEVFS_INPUT &&
|
||||||
dp->inum != DEVFS_PTS)
|
dp->inum != DEVFS_PTS && !isx)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
if (n == 0) {
|
if (n == 0) {
|
||||||
|
|
@ -326,6 +536,25 @@ devfs_getdents(struct inode *dp, char *out, int cap, uint64 *poff)
|
||||||
n++;
|
n++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A directory udev made: the dots, then what is in it.
|
||||||
|
if (isx) {
|
||||||
|
uint64 base = 2;
|
||||||
|
extras_init();
|
||||||
|
acquire(&extras.lock);
|
||||||
|
for (int i = 0; i < NEXTRA; i++) {
|
||||||
|
if (!extras.e[i].used || !extras.e[i].linked || extras.e[i].parent != dp->inum)
|
||||||
|
continue;
|
||||||
|
if (n > base) { base++; continue; }
|
||||||
|
if (emit(out, cap, &used, DEVFS_EXTRA0 + (uint64)i,
|
||||||
|
extras.e[i].type == T_DIR ? 4 : 10 /* DT_LNK */, extras.e[i].name) < 0)
|
||||||
|
break;
|
||||||
|
n++;
|
||||||
|
base++;
|
||||||
|
}
|
||||||
|
release(&extras.lock);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
// /dev/pts: the dots, then the pairs that exist. Ordinal 2 + N is pair N,
|
// /dev/pts: the dots, then the pairs that exist. Ordinal 2 + N is pair N,
|
||||||
// whether or not the ones before it exist, so a pair opened between two
|
// whether or not the ones before it exist, so a pair opened between two
|
||||||
// calls does not shift the rest.
|
// calls does not shift the rest.
|
||||||
|
|
@ -378,6 +607,21 @@ devfs_getdents(struct inode *dp, char *out, int cap, uint64 *poff)
|
||||||
base++;
|
base++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// What udev added to this directory, after everything the kernel put there.
|
||||||
|
extras_init();
|
||||||
|
acquire(&extras.lock);
|
||||||
|
for (int i = 0; i < NEXTRA; i++) {
|
||||||
|
if (!extras.e[i].used || !extras.e[i].linked || extras.e[i].parent != dp->inum)
|
||||||
|
continue;
|
||||||
|
if (n > base) { base++; continue; }
|
||||||
|
if (emit(out, cap, &used, DEVFS_EXTRA0 + (uint64)i,
|
||||||
|
extras.e[i].type == T_DIR ? 4 : 10, extras.e[i].name) < 0)
|
||||||
|
break;
|
||||||
|
n++;
|
||||||
|
base++;
|
||||||
|
}
|
||||||
|
release(&extras.lock);
|
||||||
|
|
||||||
out:
|
out:
|
||||||
*poff = n;
|
*poff = n;
|
||||||
return used;
|
return used;
|
||||||
|
|
|
||||||
|
|
@ -113,6 +113,62 @@ sockinit(void)
|
||||||
"the socket table, with buffered bytes and descriptors in flight");
|
"the socket table, with buffered bytes and descriptors in flight");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SOCK_SEQPACKET and SOCK_DGRAM keep message boundaries. They ride in the same
|
||||||
|
// ring as a stream, each message behind a four-byte length, so a write is one
|
||||||
|
// message and a read takes exactly one -- the rest of a message that does not
|
||||||
|
// fit the buffer is dropped, as Linux drops it. A message is never split, so
|
||||||
|
// one has to fit the ring whole.
|
||||||
|
static int
|
||||||
|
is_packet(struct socket *s)
|
||||||
|
{
|
||||||
|
if(s->domain == AF_NETLINK)
|
||||||
|
return s->nlproto == NETLINK_KOBJECT_UEVENT;
|
||||||
|
return s->domain == AF_UNIX && (s->type == SOCK_DGRAM || s->type == SOCK_SEQPACKET);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
sock_is_packet(struct socket *s)
|
||||||
|
{
|
||||||
|
return is_packet(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
// bind(2) on a netlink socket: only the multicast groups mean anything here.
|
||||||
|
int
|
||||||
|
sock_netlink_bind(struct socket *s, uint groups)
|
||||||
|
{
|
||||||
|
acquire(&stab.lock);
|
||||||
|
s->nlgroups = groups;
|
||||||
|
release(&stab.lock);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Queue one uevent datagram on every uevent socket listening to any of
|
||||||
|
// `groups`, except `from`. A full queue drops the message, as Linux does:
|
||||||
|
// udev recovers with a trigger, and the sender must never wait on a reader.
|
||||||
|
void
|
||||||
|
uevent_send(uint groups, const char *msg, int len, struct socket *from)
|
||||||
|
{
|
||||||
|
struct socket *s;
|
||||||
|
|
||||||
|
if(len <= 0 || len + 4 > SOCKBUF)
|
||||||
|
return;
|
||||||
|
acquire(&stab.lock);
|
||||||
|
for(s = stab.sock; s < stab.sock + NSOCK; s++){
|
||||||
|
if(s->state == SS_FREE || s == from || s->domain != AF_NETLINK ||
|
||||||
|
s->nlproto != NETLINK_KOBJECT_UEVENT || !(s->nlgroups & groups))
|
||||||
|
continue;
|
||||||
|
if(s->nwrite - s->nread + (uint)len + 4 > SOCKBUF)
|
||||||
|
continue;
|
||||||
|
for(int i = 0; i < 4; i++)
|
||||||
|
s->data[s->nwrite++ % SOCKBUF] = (char)((uint)len >> (8 * i));
|
||||||
|
for(int i = 0; i < len; i++)
|
||||||
|
s->data[s->nwrite++ % SOCKBUF] = msg[i];
|
||||||
|
wakeup(&s->nread);
|
||||||
|
}
|
||||||
|
release(&stab.lock);
|
||||||
|
poll_kick();
|
||||||
|
}
|
||||||
|
|
||||||
// Caller holds stab.lock.
|
// Caller holds stab.lock.
|
||||||
static struct socket *
|
static struct socket *
|
||||||
alloc_locked(int type)
|
alloc_locked(int type)
|
||||||
|
|
@ -415,7 +471,7 @@ sockread_fds(struct socket *s, char *addr, int n, struct file **fout, int *nfout
|
||||||
// so an empty buffer means the caller has already drained this dump rather
|
// so an empty buffer means the caller has already drained this dump rather
|
||||||
// than that more is coming. Blocking here would hang a getifaddrs() that
|
// than that more is coming. Blocking here would hang a getifaddrs() that
|
||||||
// asked one question too many.
|
// asked one question too many.
|
||||||
if(s->domain == AF_NETLINK){
|
if(s->domain == AF_NETLINK && !is_packet(s)){
|
||||||
acquire(&stab.lock);
|
acquire(&stab.lock);
|
||||||
uint have = s->nwrite - s->nread;
|
uint have = s->nwrite - s->nread;
|
||||||
if(have == 0){
|
if(have == 0){
|
||||||
|
|
@ -439,6 +495,39 @@ sockread_fds(struct socket *s, char *addr, int n, struct file **fout, int *nfout
|
||||||
sleep(&s->nread, &stab.lock);
|
sleep(&s->nread, &stab.lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// One message, whole or truncated; descriptors ride with the header.
|
||||||
|
if(is_packet(s) && s->nread != s->nwrite){
|
||||||
|
uint len = 0;
|
||||||
|
for(i = 0; i < 4; i++)
|
||||||
|
len |= (uint)(uchar)s->data[(s->nread + i) % SOCKBUF] << (8 * i);
|
||||||
|
if(s->nscm > 0 && s->scm[0].pos == s->nread){
|
||||||
|
got = s->scm[0].n;
|
||||||
|
for(i = 0; i < got; i++)
|
||||||
|
taken[i] = s->scm[0].f[i];
|
||||||
|
for(i = 1; i < s->nscm; i++)
|
||||||
|
s->scm[i - 1] = s->scm[i];
|
||||||
|
s->nscm--;
|
||||||
|
}
|
||||||
|
s->nread += 4;
|
||||||
|
uint take = len < (uint)n ? len : (uint)n;
|
||||||
|
for(i = 0; i < (int)take; i++)
|
||||||
|
addr[i] = s->data[(s->nread + i) % SOCKBUF];
|
||||||
|
s->nread += len;
|
||||||
|
wakeup(&s->nwrite);
|
||||||
|
release(&stab.lock);
|
||||||
|
poll_kick();
|
||||||
|
if(fout){
|
||||||
|
for(int j = 0; j < got; j++)
|
||||||
|
fout[j] = taken[j];
|
||||||
|
if(nfout)
|
||||||
|
*nfout = got;
|
||||||
|
} else {
|
||||||
|
for(int j = 0; j < got; j++)
|
||||||
|
fileclose(taken[j]);
|
||||||
|
}
|
||||||
|
return (int)take;
|
||||||
|
}
|
||||||
|
|
||||||
// A message carrying descriptors is a boundary. This read either starts
|
// A message carrying descriptors is a boundary. This read either starts
|
||||||
// exactly on one -- and takes them -- or is cut short so that it stops
|
// exactly on one -- and takes them -- or is cut short so that it stops
|
||||||
// before it, leaving the next read to start there.
|
// before it, leaving the next read to start there.
|
||||||
|
|
@ -519,6 +608,11 @@ sockwrite_fds(struct socket *s, char *addr, int n, struct file **f, int nf)
|
||||||
// rather than a peer's -- there is no peer. Doing the work here is what lets
|
// rather than a peer's -- there is no peer. Doing the work here is what lets
|
||||||
// the read that follows never block, which matters because musl reads with
|
// the read that follows never block, which matters because musl reads with
|
||||||
// MSG_DONTWAIT and treats a short read as the end of the dump.
|
// MSG_DONTWAIT and treats a short read as the end of the dump.
|
||||||
|
if(s->domain == AF_NETLINK && is_packet(s)){
|
||||||
|
// udevd announces what it did on group 2; the sender does not hear itself.
|
||||||
|
uevent_send(UEVENT_GROUP_UDEV, addr, n, s);
|
||||||
|
return n;
|
||||||
|
}
|
||||||
if(s->domain == AF_NETLINK){
|
if(s->domain == AF_NETLINK){
|
||||||
acquire(&stab.lock);
|
acquire(&stab.lock);
|
||||||
s->nread = s->nwrite = 0;
|
s->nread = s->nwrite = 0;
|
||||||
|
|
@ -548,6 +642,48 @@ sockwrite_fds(struct socket *s, char *addr, int n, struct file **f, int nf)
|
||||||
return -ENOBUFS;
|
return -ENOBUFS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(is_packet(s)){
|
||||||
|
if(n + 4 > SOCKBUF){
|
||||||
|
release(&stab.lock);
|
||||||
|
return -EMSGSIZE;
|
||||||
|
}
|
||||||
|
for(;;){
|
||||||
|
dst = s->peer;
|
||||||
|
if(dst == 0){
|
||||||
|
release(&stab.lock);
|
||||||
|
sigsend(myproc()->pid, SIGPIPE);
|
||||||
|
return -EPIPE;
|
||||||
|
}
|
||||||
|
if(dst->nwrite - dst->nread + (uint)n + 4 <= SOCKBUF)
|
||||||
|
break;
|
||||||
|
if(myproc()->killed){
|
||||||
|
release(&stab.lock);
|
||||||
|
return -EINTR;
|
||||||
|
}
|
||||||
|
wakeup(&dst->nread);
|
||||||
|
sleep(&dst->nwrite, &stab.lock);
|
||||||
|
}
|
||||||
|
if(nf > 0){
|
||||||
|
if(dst->nscm >= NSCM){
|
||||||
|
release(&stab.lock);
|
||||||
|
return -ENOBUFS;
|
||||||
|
}
|
||||||
|
struct scm_fds *b = &dst->scm[dst->nscm++];
|
||||||
|
b->pos = dst->nwrite; // the header, where a packet read starts
|
||||||
|
b->n = nf;
|
||||||
|
for(int j = 0; j < nf; j++)
|
||||||
|
b->f[j] = f[j];
|
||||||
|
}
|
||||||
|
for(i = 0; i < 4; i++)
|
||||||
|
dst->data[dst->nwrite++ % SOCKBUF] = (char)((uint)n >> (8 * i));
|
||||||
|
for(i = 0; i < n; i++)
|
||||||
|
dst->data[dst->nwrite++ % SOCKBUF] = addr[i];
|
||||||
|
wakeup(&dst->nread);
|
||||||
|
release(&stab.lock);
|
||||||
|
poll_kick();
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
for(i = 0; i < n; i++){
|
for(i = 0; i < n; i++){
|
||||||
// Re-read every iteration: the peer can go away while we sleep below.
|
// Re-read every iteration: the peer can go away while we sleep below.
|
||||||
dst = s->peer;
|
dst = s->peer;
|
||||||
|
|
@ -607,9 +743,11 @@ socknew(int domain, int type, int proto)
|
||||||
|
|
||||||
if(domain != AF_UNIX && domain != AF_INET && domain != AF_NETLINK)
|
if(domain != AF_UNIX && domain != AF_INET && domain != AF_NETLINK)
|
||||||
return 0;
|
return 0;
|
||||||
// AF_UNIX is stream-only here; AF_INET has datagrams too, and inet_new is
|
// AF_UNIX: streams, and message-preserving SEQPACKET and DGRAM (connected
|
||||||
|
// pairs and listeners; an unconnected DGRAM has no address to send to and
|
||||||
|
// will be refused when it tries). AF_INET has datagrams too, and inet_new is
|
||||||
// what decides whether it can serve the type asked for.
|
// what decides whether it can serve the type asked for.
|
||||||
if(domain == AF_UNIX && type != SOCK_STREAM)
|
if(domain == AF_UNIX && type != SOCK_STREAM && type != SOCK_DGRAM && type != SOCK_SEQPACKET)
|
||||||
return 0;
|
return 0;
|
||||||
// AF_NETLINK carries whole messages and musl asks for SOCK_RAW; SOCK_DGRAM is
|
// AF_NETLINK carries whole messages and musl asks for SOCK_RAW; SOCK_DGRAM is
|
||||||
// accepted too because Linux does. NETLINK_ROUTE is the only protocol served
|
// accepted too because Linux does. NETLINK_ROUTE is the only protocol served
|
||||||
|
|
@ -618,14 +756,16 @@ socknew(int domain, int type, int proto)
|
||||||
if(domain == AF_NETLINK){
|
if(domain == AF_NETLINK){
|
||||||
if(type != SOCK_RAW && type != SOCK_DGRAM)
|
if(type != SOCK_RAW && type != SOCK_DGRAM)
|
||||||
return 0;
|
return 0;
|
||||||
if(proto != NETLINK_ROUTE)
|
if(proto != NETLINK_ROUTE && proto != NETLINK_KOBJECT_UEVENT)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
acquire(&stab.lock);
|
acquire(&stab.lock);
|
||||||
s = alloc_locked(type);
|
s = alloc_locked(type);
|
||||||
if(s)
|
if(s){
|
||||||
s->domain = domain;
|
s->domain = domain;
|
||||||
|
s->nlproto = domain == AF_NETLINK ? proto : 0;
|
||||||
|
}
|
||||||
release(&stab.lock);
|
release(&stab.lock);
|
||||||
if(s == 0)
|
if(s == 0)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
@ -644,16 +784,16 @@ socknew(int domain, int type, int proto)
|
||||||
|
|
||||||
// A connected pair with no name and no listener, for socketpair(2).
|
// A connected pair with no name and no listener, for socketpair(2).
|
||||||
int
|
int
|
||||||
sockpair(struct socket **pa, struct socket **pb)
|
sockpair_type(struct socket **pa, struct socket **pb, int type)
|
||||||
{
|
{
|
||||||
struct socket *a, *b;
|
struct socket *a, *b;
|
||||||
|
|
||||||
acquire(&stab.lock);
|
acquire(&stab.lock);
|
||||||
if((a = alloc_locked(SOCK_STREAM)) == 0){
|
if((a = alloc_locked(type)) == 0){
|
||||||
release(&stab.lock);
|
release(&stab.lock);
|
||||||
return -ENFILE;
|
return -ENFILE;
|
||||||
}
|
}
|
||||||
if((b = alloc_locked(SOCK_STREAM)) == 0){
|
if((b = alloc_locked(type)) == 0){
|
||||||
a->state = SS_FREE;
|
a->state = SS_FREE;
|
||||||
a->ref = 0;
|
a->ref = 0;
|
||||||
release(&stab.lock);
|
release(&stab.lock);
|
||||||
|
|
@ -674,6 +814,12 @@ sockpair(struct socket **pa, struct socket **pb)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
sockpair(struct socket **pa, struct socket **pb)
|
||||||
|
{
|
||||||
|
return sockpair_type(pa, pb, SOCK_STREAM);
|
||||||
|
}
|
||||||
|
|
||||||
// bind(2) for AF_UNIX. The caller has already created the filesystem node and
|
// bind(2) for AF_UNIX. The caller has already created the filesystem node and
|
||||||
// hands over its identity; see sys_bind() in kernel/sys/sys_net.c for why the
|
// hands over its identity; see sys_bind() in kernel/sys/sys_net.c for why the
|
||||||
// creation lives there and not here.
|
// creation lives there and not here.
|
||||||
|
|
|
||||||
|
|
@ -164,7 +164,7 @@ SYSCALL_DEFINE(socket)
|
||||||
if (type & ~(0xFF | SOCK_CLOEXEC | SOCK_NONBLOCK))
|
if (type & ~(0xFF | SOCK_CLOEXEC | SOCK_NONBLOCK))
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
type &= 0xFF;
|
type &= 0xFF;
|
||||||
if (domain == AF_UNIX && type != SOCK_STREAM)
|
if (domain == AF_UNIX && type != SOCK_STREAM && type != SOCK_DGRAM && type != SOCK_SEQPACKET)
|
||||||
return -EPROTOTYPE;
|
return -EPROTOTYPE;
|
||||||
if (domain == AF_INET && type != SOCK_STREAM && type != SOCK_DGRAM &&
|
if (domain == AF_INET && type != SOCK_STREAM && type != SOCK_DGRAM &&
|
||||||
type != SOCK_RAW)
|
type != SOCK_RAW)
|
||||||
|
|
@ -185,7 +185,7 @@ SYSCALL_DEFINE(socket)
|
||||||
// default rather than departing from it, and programs do write them out:
|
// default rather than departing from it, and programs do write them out:
|
||||||
// toybox's `nc` passes IPPROTO_TCP, and refusing it meant "Protocol not
|
// toybox's `nc` passes IPPROTO_TCP, and refusing it meant "Protocol not
|
||||||
// supported" for TCP on a system whose whole network is TCP.
|
// supported" for TCP on a system whose whole network is TCP.
|
||||||
if (protocol != 0) {
|
if (protocol != 0 && domain != AF_NETLINK) {
|
||||||
int redundant =
|
int redundant =
|
||||||
(domain == AF_INET && type == SOCK_STREAM && protocol == IPPROTO_TCP) ||
|
(domain == AF_INET && type == SOCK_STREAM && protocol == IPPROTO_TCP) ||
|
||||||
(domain == AF_INET && type == SOCK_DGRAM && protocol == IPPROTO_UDP);
|
(domain == AF_INET && type == SOCK_DGRAM && protocol == IPPROTO_UDP);
|
||||||
|
|
@ -270,6 +270,16 @@ SYSCALL_DEFINE(bind)
|
||||||
return sockbind_in(s, ip, port_from_net(port));
|
return sockbind_in(s, ip, port_from_net(port));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sockaddr_nl: family, pad, port id, groups. Only the groups matter here.
|
||||||
|
if (sock_domain(s) == AF_NETLINK) {
|
||||||
|
uint groups = 0;
|
||||||
|
if (ulen < 12)
|
||||||
|
return -EINVAL;
|
||||||
|
if (copy_from_user(&groups, uaddr + 8, 4) < 0)
|
||||||
|
return -EFAULT;
|
||||||
|
return sock_netlink_bind(s, groups);
|
||||||
|
}
|
||||||
|
|
||||||
uint64 key;
|
uint64 key;
|
||||||
if (sun_abstract(uaddr, ulen, &key))
|
if (sun_abstract(uaddr, ulen, &key))
|
||||||
return sockbind_abstract(s, key);
|
return sockbind_abstract(s, key);
|
||||||
|
|
@ -459,15 +469,11 @@ SYSCALL_DEFINE(socketpair)
|
||||||
return -EAFNOSUPPORT;
|
return -EAFNOSUPPORT;
|
||||||
int sflags = type & (SOCK_CLOEXEC | SOCK_NONBLOCK);
|
int sflags = type & (SOCK_CLOEXEC | SOCK_NONBLOCK);
|
||||||
type &= 0xFF;
|
type &= 0xFF;
|
||||||
// Datagram and sequenced-packet pairs are carried as a stream. What they
|
// Datagram and sequenced-packet pairs keep message boundaries (kernel/net/unix.c).
|
||||||
// are used for between two processes is passing a descriptor or a status
|
if (type != SOCK_STREAM && type != SOCK_DGRAM && type != SOCK_SEQPACKET)
|
||||||
// word -- Rust's process spawning asks for SOCK_SEQPACKET to hear back
|
|
||||||
// from its child -- and one message at a time is all of that. A reader
|
|
||||||
// that sends two and wants them kept apart would see them joined.
|
|
||||||
if (type != SOCK_STREAM && type != 2 /* DGRAM */ && type != 5 /* SEQPACKET */)
|
|
||||||
return -EPROTOTYPE;
|
return -EPROTOTYPE;
|
||||||
|
|
||||||
if ((r = sockpair(&a, &b)) < 0)
|
if ((r = sockpair_type(&a, &b, type)) < 0)
|
||||||
return r;
|
return r;
|
||||||
|
|
||||||
if ((fd0 = sock_to_fd(a, sflags)) < 0) {
|
if ((fd0 = sock_to_fd(a, sflags)) < 0) {
|
||||||
|
|
@ -1076,7 +1082,7 @@ SYSCALL_DEFINE(recvmsg)
|
||||||
// one rather than discarding it as Linux would: musl's getifaddrs reads
|
// one rather than discarding it as Linux would: musl's getifaddrs reads
|
||||||
// in chunks, and this whole queue is one "datagram" only by convention.
|
// in chunks, and this whole queue is one "datagram" only by convention.
|
||||||
int nlret = -1;
|
int nlret = -1;
|
||||||
if (sock_domain(s) == AF_NETLINK) {
|
if (sock_domain(s) == AF_NETLINK && !sock_is_packet(s)) {
|
||||||
uint have = socknl_pending(s);
|
uint have = socknl_pending(s);
|
||||||
if (have == 0)
|
if (have == 0)
|
||||||
return -EAGAIN; // never blocks; see sockread
|
return -EAGAIN; // never blocks; see sockread
|
||||||
|
|
@ -1127,8 +1133,8 @@ SYSCALL_DEFINE(recvmsg)
|
||||||
return rd;
|
return rd;
|
||||||
}
|
}
|
||||||
got += rd;
|
got += rd;
|
||||||
if (rd < len)
|
if (rd < len || sock_is_packet(s))
|
||||||
break;
|
break; // one message per call
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nf > 0) {
|
if (nf > 0) {
|
||||||
|
|
|
||||||
|
|
@ -56,3 +56,9 @@ curl
|
||||||
# not ALSA's kernel side, and SDL 3 has no OSS backend of its own.
|
# not ALSA's kernel side, and SDL 3 has no OSS backend of its own.
|
||||||
alsa-lib
|
alsa-lib
|
||||||
alsa-plugins
|
alsa-plugins
|
||||||
|
# udev: the daemon that names devices and tells Xorg (and libinput, and every
|
||||||
|
# Wayland compositor) what is plugged in. eudev is udev without systemd.
|
||||||
|
# `udevadm`, the rules, and libudev come with it.
|
||||||
|
eudev
|
||||||
|
eudev-libs
|
||||||
|
udev-init-scripts
|
||||||
|
|
|
||||||
|
|
@ -19,11 +19,12 @@ cairo-gobject-1.18.4-r1.apk ec302c75a24edaa4a2f867cfb5eec85cf30efe184044bab6e2b9
|
||||||
c-ares-1.34.9-r0.apk 3539a3e11c1ef1516117d8179a546ac882086e94e0d78ad51d687b3fa2b3b5f2 main
|
c-ares-1.34.9-r0.apk 3539a3e11c1ef1516117d8179a546ac882086e94e0d78ad51d687b3fa2b3b5f2 main
|
||||||
cmatrix-2.0-r2.apk d9f6bea3851492a6db2a9cd3f7e2df0f0756c6651a702b36ed2b887328ad1f00 community
|
cmatrix-2.0-r2.apk d9f6bea3851492a6db2a9cd3f7e2df0f0756c6651a702b36ed2b887328ad1f00 community
|
||||||
cups-libs-2.4.19-r1.apk 5739c568d90f23006d74ff82619e1a18447a6f698ae2af61970debe6658a7bea main
|
cups-libs-2.4.19-r1.apk 5739c568d90f23006d74ff82619e1a18447a6f698ae2af61970debe6658a7bea main
|
||||||
curl-8.22.0-r0.apk 9262352e6341bb87e3047d1ca0b4051b2a9f4011409df25e4190e6be0334b860 main
|
curl-8.23.0-r0.apk 531ae298451a4c1c44f563fc3b2eedc2c1d7e0a26a33bdfc0efe6faaff6a5942 main
|
||||||
curl-zsh-completion-8.22.0-r0.apk 182a5f0afb5fd8786137f4610c065c305aa593fd18994df908c146335fb6011c main
|
curl-zsh-completion-8.23.0-r0.apk 123ff531444fe6c3095c49553f8798bcf6f6ca8c7668b4140cc33daf24c351ec main
|
||||||
dbus-libs-1.16.2-r2.apk 105f6ba17454c587ca9345f9deb348012d1950588a22da42120c954d396c99b6 main
|
dbus-libs-1.16.2-r2.apk 105f6ba17454c587ca9345f9deb348012d1950588a22da42120c954d396c99b6 main
|
||||||
e2fsprogs-libs-1.47.4-r0.apk 2e067395a6981b06cfd2cd3abf0e6c6574f7ef64d02e01f75fc5323d4a337fb9 main
|
e2fsprogs-libs-1.47.4-r0.apk 2e067395a6981b06cfd2cd3abf0e6c6574f7ef64d02e01f75fc5323d4a337fb9 main
|
||||||
encodings-1.1.0-r0.apk f7c0eb762638d0249b4fbbe07a8e4ed6aab8e86b74c239c40e47a36219777d52 main
|
encodings-1.1.0-r0.apk f7c0eb762638d0249b4fbbe07a8e4ed6aab8e86b74c239c40e47a36219777d52 main
|
||||||
|
eudev-3.2.14-r6.apk 540c78af1eed3bc411cae4e9171e911afc0704210c0b97315e978e05c3b39dce main
|
||||||
eudev-libs-3.2.14-r6.apk 45fdbcf37c5c0e82067e98a818d3c1fd16d0261c62128dcb6b935b86b4ec6c86 main
|
eudev-libs-3.2.14-r6.apk 45fdbcf37c5c0e82067e98a818d3c1fd16d0261c62128dcb6b935b86b4ec6c86 main
|
||||||
fastfetch-2.64.2-r0.apk 9c593d689f1c04625d3b433066ad92e430a384fa8e99585fee71310a20a965cb community
|
fastfetch-2.64.2-r0.apk 9c593d689f1c04625d3b433066ad92e430a384fa8e99585fee71310a20a965cb community
|
||||||
fastfetch-zsh-completion-2.64.2-r0.apk ded7a5c7911c4db60ab71a320104f9af3f629976429c897db67ea87c5ac53ec8 community
|
fastfetch-zsh-completion-2.64.2-r0.apk ded7a5c7911c4db60ab71a320104f9af3f629976429c897db67ea87c5ac53ec8 community
|
||||||
|
|
@ -48,6 +49,7 @@ harfbuzz-13.2.1-r0.apk 01f7862265ef2256bbadd0fc998df3d1243dee606df3bd8284471cf39
|
||||||
htop-3.5.3-r0.apk 6f6d444ca9a4133c2c13323ded04128783d59f924bcff0276d150a982904a2f3 main
|
htop-3.5.3-r0.apk 6f6d444ca9a4133c2c13323ded04128783d59f924bcff0276d150a982904a2f3 main
|
||||||
hwdata-pci-0.408-r0.apk fe61404e6d2bd1e04c021d53641c0ebce97911084d77994e43a288a1278686c1 main
|
hwdata-pci-0.408-r0.apk fe61404e6d2bd1e04c021d53641c0ebce97911084d77994e43a288a1278686c1 main
|
||||||
hwdata-usb-0.408-r0.apk 272d365cb05dd085dd8022154cf71e3f3b0aaddd41c1269eeaf939c9d38675c8 main
|
hwdata-usb-0.408-r0.apk 272d365cb05dd085dd8022154cf71e3f3b0aaddd41c1269eeaf939c9d38675c8 main
|
||||||
|
kmod-libs-34.2-r1.apk d6504eb204a47a35e1dd5eaa1018301b2dc0b1809f84d88fab2fdbadfc9c3c73 main
|
||||||
lbreakouthd-1.2.2-r0.apk 4d7f78d866a7c544132e5f159d18f978515ac948517f2b8a6c625756bb81a3e0 community
|
lbreakouthd-1.2.2-r0.apk 4d7f78d866a7c544132e5f159d18f978515ac948517f2b8a6c625756bb81a3e0 community
|
||||||
lbreakouthd-data-1.2.2-r0.apk 28ce98df27bfbb442fa397fbb9eb3eb5464e5b7b040ea034f207be52f4f4f07f community
|
lbreakouthd-data-1.2.2-r0.apk 28ce98df27bfbb442fa397fbb9eb3eb5464e5b7b040ea034f207be52f4f4f07f community
|
||||||
lcms2-2.19-r0.apk b4cb038af3c2f02bedd8456ae876c97ac0a9f29789add77871bfd6ce084ff136 main
|
lcms2-2.19-r0.apk b4cb038af3c2f02bedd8456ae876c97ac0a9f29789add77871bfd6ce084ff136 main
|
||||||
|
|
@ -59,7 +61,7 @@ libbz2-1.0.8-r6.apk 2737e2b23ed08d4911ee62cd0163b5f4bf0379cacf4dc35b23a00209584c
|
||||||
libcap2-2.78-r0.apk 42886a7b4bcde26347c712925c8dc7db202b6029ef9b8c7ee81577ab7bdd8fad main
|
libcap2-2.78-r0.apk 42886a7b4bcde26347c712925c8dc7db202b6029ef9b8c7ee81577ab7bdd8fad main
|
||||||
libcom_err-1.47.4-r0.apk 4723d0fb8e286dc6adb54dd3cdaedcd8db54260f75ee778a5acb4c16922c0f52 main
|
libcom_err-1.47.4-r0.apk 4723d0fb8e286dc6adb54dd3cdaedcd8db54260f75ee778a5acb4c16922c0f52 main
|
||||||
libcrypto3-3.5.9-r0.apk 6632d758d8f5e9ea3b650fe966f23bbf9a202f8b8dceecac93da135dec5e3689 main
|
libcrypto3-3.5.9-r0.apk 6632d758d8f5e9ea3b650fe966f23bbf9a202f8b8dceecac93da135dec5e3689 main
|
||||||
libcurl-8.22.0-r0.apk 40b2a8e15a8d24bc95a5f42aaeb018313a1ff457cee71d8e15aaf1328da765e8 main
|
libcurl-8.23.0-r0.apk 1f70ca404818805a6465b615bbfe84ef861488a963d7e630108ebd7fbaa51665 main
|
||||||
libdav1d-1.5.3-r0.apk 40a2b1b92cdcc6fd295934f799ee80ef67e2e384097624d565c6222d827f8480 main
|
libdav1d-1.5.3-r0.apk 40a2b1b92cdcc6fd295934f799ee80ef67e2e384097624d565c6222d827f8480 main
|
||||||
libdrm-2.4.134-r0.apk 584fd84ad5030040e613fa1816097cd1706ddd542100070852b0eab6e09e12ca main
|
libdrm-2.4.134-r0.apk 584fd84ad5030040e613fa1816097cd1706ddd542100070852b0eab6e09e12ca main
|
||||||
libeconf-0.8.3-r0.apk 2a791c7957961f830cf31b04f35f814a63afcf633badc9052425ff1cf2f64380 main
|
libeconf-0.8.3-r0.apk 2a791c7957961f830cf31b04f35f814a63afcf633badc9052425ff1cf2f64380 main
|
||||||
|
|
@ -157,6 +159,7 @@ ssl_client-1.37.0-r31.apk e578a140670e4cf9816117b0c2063632bf3754ecbe8fa593a7fd2b
|
||||||
tiff-4.7.2-r0.apk 757ce87ebe4923a6be9869d026e250958ca09696529c464781e8a91d2b81670b main
|
tiff-4.7.2-r0.apk 757ce87ebe4923a6be9869d026e250958ca09696529c464781e8a91d2b81670b main
|
||||||
tmux-3.7c-r0.apk 16f3438f5a24124dd500f70bf6f2a232c9b3dfe5e7f7e615969ac5c00c2c8d0b main
|
tmux-3.7c-r0.apk 16f3438f5a24124dd500f70bf6f2a232c9b3dfe5e7f7e615969ac5c00c2c8d0b main
|
||||||
tmux-zsh-completion-5.9-r7.apk e505b2a8c0d56beb65b6f47fd732f6ee6d8d8a72a1a1ea27a6922dad48d9ce89 main
|
tmux-zsh-completion-5.9-r7.apk e505b2a8c0d56beb65b6f47fd732f6ee6d8d8a72a1a1ea27a6922dad48d9ce89 main
|
||||||
|
udev-init-scripts-35-r1.apk b707bc50e5c8d2faec08f90e7603b76c2c42dfb0e90e713f82be0f98411d8161 main
|
||||||
util-macros-1.20.2-r0.apk 7c129d085b646b13ccef3738e93038221aec840c1a6764279c5adc9950ba1bdc main
|
util-macros-1.20.2-r0.apk 7c129d085b646b13ccef3738e93038221aec840c1a6764279c5adc9950ba1bdc main
|
||||||
wayland-libs-client-1.25.0-r0.apk a728f7103217cafdc83ff6ad0d679c197dbbe280bd5788c8336259f7a39cb303 main
|
wayland-libs-client-1.25.0-r0.apk a728f7103217cafdc83ff6ad0d679c197dbbe280bd5788c8336259f7a39cb303 main
|
||||||
wayland-libs-cursor-1.25.0-r0.apk 16ec369226507abaeb84b496ce2bb1a4ef29987ea91ba6bedc40bb0e0af274e7 main
|
wayland-libs-cursor-1.25.0-r0.apk 16ec369226507abaeb84b496ce2bb1a4ef29987ea91ba6bedc40bb0e0af274e7 main
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue