mirror of
https://github.com/42wim/matterbridge
synced 2024-11-17 03:26:07 +00:00
509 lines
13 KiB
Go
509 lines
13 KiB
Go
|
// Copyright 2024 The Libc Authors. All rights reserved.
|
||
|
// Use of this source code is governed by a BSD-style
|
||
|
// license that can be found in the LICENSE file.
|
||
|
|
||
|
//go:build linux && (amd64 || loong64)
|
||
|
|
||
|
package libc // import "modernc.org/libc"
|
||
|
|
||
|
import (
|
||
|
"runtime"
|
||
|
"slices"
|
||
|
"sync"
|
||
|
"sync/atomic"
|
||
|
"time"
|
||
|
"unsafe"
|
||
|
)
|
||
|
|
||
|
type pthreadAttr struct {
|
||
|
detachState int32
|
||
|
}
|
||
|
|
||
|
type pthreadCleanupItem struct {
|
||
|
routine, arg uintptr
|
||
|
}
|
||
|
|
||
|
// C version is 40 bytes.
|
||
|
type pthreadMutex struct {
|
||
|
sync.Mutex // 0 8
|
||
|
count int32 // 8 4
|
||
|
mType uint32 // 12 4
|
||
|
outer sync.Mutex // 16 8
|
||
|
owner int32 // 20 4
|
||
|
// 24
|
||
|
}
|
||
|
|
||
|
type pthreadConds struct {
|
||
|
sync.Mutex
|
||
|
conds map[uintptr][]chan struct{}
|
||
|
}
|
||
|
|
||
|
var (
|
||
|
// Ensure there's enough space for unsafe type conversions.
|
||
|
_ [unsafe.Sizeof(sync.Mutex{}) - __CCGO_SIZEOF_GO_MUTEX]byte
|
||
|
_ [unsafe.Sizeof(Tpthread_mutex_t{}) - unsafe.Sizeof(pthreadMutex{})]byte
|
||
|
_ [unsafe.Sizeof(Tpthread_attr_t{}) - unsafe.Sizeof(pthreadAttr{})]byte
|
||
|
|
||
|
pthreadKeysMutex sync.Mutex
|
||
|
pthreadKeyDestructors []uintptr
|
||
|
pthreadKeysFree []Tpthread_key_t
|
||
|
|
||
|
conds = pthreadConds{conds: map[uintptr][]chan struct{}{}}
|
||
|
)
|
||
|
|
||
|
func _pthread_setcancelstate(tls *TLS, new int32, old uintptr) int32 {
|
||
|
//TODO actually respect cancel state
|
||
|
if uint32(new) > 2 {
|
||
|
return EINVAL
|
||
|
}
|
||
|
|
||
|
p := tls.pthread + unsafe.Offsetof(t__pthread{}.Fcanceldisable)
|
||
|
if old != 0 {
|
||
|
r := *(*int32)(unsafe.Pointer(p))
|
||
|
*(*int32)(unsafe.Pointer(old)) = int32(byte(r))
|
||
|
}
|
||
|
*(*int32)(unsafe.Pointer(p)) = new
|
||
|
return 0
|
||
|
}
|
||
|
|
||
|
func Xpthread_getspecific(tls *TLS, k Tpthread_key_t) uintptr {
|
||
|
return tls.pthreadKeyValues[k]
|
||
|
}
|
||
|
|
||
|
func Xpthread_setspecific(tls *TLS, k Tpthread_key_t, x uintptr) int32 {
|
||
|
if tls.pthreadKeyValues == nil {
|
||
|
tls.pthreadKeyValues = map[Tpthread_key_t]uintptr{}
|
||
|
}
|
||
|
tls.pthreadKeyValues[k] = x
|
||
|
return 0
|
||
|
}
|
||
|
|
||
|
func Xpthread_key_create(tls *TLS, k uintptr, dtor uintptr) int32 {
|
||
|
pthreadKeysMutex.Lock()
|
||
|
|
||
|
defer pthreadKeysMutex.Unlock()
|
||
|
|
||
|
var key Tpthread_key_t
|
||
|
switch l := Tpthread_key_t(len(pthreadKeysFree)); {
|
||
|
case l == 0:
|
||
|
key = Tpthread_key_t(len(pthreadKeyDestructors))
|
||
|
pthreadKeyDestructors = append(pthreadKeyDestructors, dtor)
|
||
|
default:
|
||
|
key = pthreadKeysFree[l-1]
|
||
|
pthreadKeysFree = pthreadKeysFree[:l-1]
|
||
|
pthreadKeyDestructors[key] = dtor
|
||
|
}
|
||
|
*(*Tpthread_key_t)(unsafe.Pointer(k)) = key
|
||
|
return 0
|
||
|
}
|
||
|
|
||
|
func Xpthread_key_delete(tls *TLS, k Tpthread_key_t) int32 {
|
||
|
pthreadKeysMutex.Lock()
|
||
|
|
||
|
defer pthreadKeysMutex.Unlock()
|
||
|
|
||
|
pthreadKeysFree = append(pthreadKeysFree, k)
|
||
|
return 0
|
||
|
}
|
||
|
|
||
|
func Xpthread_create(tls *TLS, res, attrp, entry, arg uintptr) int32 {
|
||
|
var attr pthreadAttr
|
||
|
if attrp != 0 {
|
||
|
attr = *(*pthreadAttr)(unsafe.Pointer(attrp))
|
||
|
}
|
||
|
|
||
|
detachState := int32(_DT_JOINABLE)
|
||
|
if attr.detachState != 0 {
|
||
|
detachState = _DT_DETACHED
|
||
|
}
|
||
|
tls2 := NewTLS()
|
||
|
tls2.ownsPthread = false
|
||
|
*(*Tpthread_t)(unsafe.Pointer(res)) = tls2.pthread
|
||
|
(*t__pthread)(unsafe.Pointer(tls2.pthread)).Fdetach_state = detachState
|
||
|
if detachState == _DT_JOINABLE {
|
||
|
(*sync.Mutex)(unsafe.Pointer(tls2.pthread + unsafe.Offsetof(t__pthread{}.F__ccgo_join_mutex))).Lock()
|
||
|
}
|
||
|
|
||
|
go func() {
|
||
|
Xpthread_exit(tls2, (*(*func(*TLS, uintptr) uintptr)(unsafe.Pointer(&struct{ uintptr }{entry})))(tls2, arg))
|
||
|
}()
|
||
|
|
||
|
return 0
|
||
|
}
|
||
|
|
||
|
func Xpthread_exit(tls *TLS, result uintptr) {
|
||
|
state := atomic.LoadInt32((*int32)(unsafe.Pointer(tls.pthread + unsafe.Offsetof(t__pthread{}.Fdetach_state))))
|
||
|
(*t__pthread)(unsafe.Pointer(tls.pthread)).Fresult = result
|
||
|
switch state {
|
||
|
case _DT_JOINABLE, _DT_DETACHED:
|
||
|
// ok
|
||
|
default:
|
||
|
panic(todo("", state))
|
||
|
}
|
||
|
|
||
|
for len(tls.pthreadCleanupItems) != 0 {
|
||
|
Xpthread_cleanup_pop(tls, 1)
|
||
|
}
|
||
|
for {
|
||
|
done := true
|
||
|
for k, v := range tls.pthreadKeyValues {
|
||
|
if v != 0 {
|
||
|
delete(tls.pthreadKeyValues, k)
|
||
|
pthreadKeysMutex.Lock()
|
||
|
d := pthreadKeyDestructors[k]
|
||
|
pthreadKeysMutex.Unlock()
|
||
|
if d != 0 {
|
||
|
done = false
|
||
|
(*(*func(*TLS, uintptr))(unsafe.Pointer(&struct{ uintptr }{d})))(tls, v)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if done {
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
if state == _DT_JOINABLE {
|
||
|
(*sync.Mutex)(unsafe.Pointer(tls.pthread + unsafe.Offsetof(t__pthread{}.F__ccgo_join_mutex))).Unlock()
|
||
|
}
|
||
|
atomic.StoreInt32((*int32)(unsafe.Pointer(tls.pthread+unsafe.Offsetof(t__pthread{}.Fdetach_state))), _DT_EXITED)
|
||
|
tls.Close()
|
||
|
runtime.Goexit()
|
||
|
}
|
||
|
|
||
|
func Xpthread_join(tls *TLS, t Tpthread_t, res uintptr) (r int32) {
|
||
|
if (*t__pthread)(unsafe.Pointer(t)).Fdetach_state > _DT_JOINABLE {
|
||
|
return EINVAL
|
||
|
}
|
||
|
|
||
|
(*sync.Mutex)(unsafe.Pointer(t + unsafe.Offsetof(t__pthread{}.F__ccgo_join_mutex))).Lock()
|
||
|
if res != 0 {
|
||
|
*(*uintptr)(unsafe.Pointer(res)) = (*t__pthread)(unsafe.Pointer(tls.pthread)).Fresult
|
||
|
}
|
||
|
return 0
|
||
|
}
|
||
|
|
||
|
func Xpthread_cleanup_push(tls *TLS, f, x uintptr) {
|
||
|
X_pthread_cleanup_push(tls, 0, f, x)
|
||
|
}
|
||
|
|
||
|
func __pthread_cleanup_push(tls *TLS, _, f, x uintptr) {
|
||
|
tls.pthreadCleanupItems = append(tls.pthreadCleanupItems, pthreadCleanupItem{f, x})
|
||
|
}
|
||
|
|
||
|
func X_pthread_cleanup_push(tls *TLS, _, f, x uintptr) {
|
||
|
tls.pthreadCleanupItems = append(tls.pthreadCleanupItems, pthreadCleanupItem{f, x})
|
||
|
}
|
||
|
|
||
|
func Xpthread_cleanup_pop(tls *TLS, run int32) {
|
||
|
X_pthread_cleanup_pop(tls, 0, run)
|
||
|
}
|
||
|
|
||
|
func __pthread_cleanup_pop(tls *TLS, _ uintptr, run int32) {
|
||
|
X_pthread_cleanup_pop(tls, 0, run)
|
||
|
}
|
||
|
|
||
|
func X_pthread_cleanup_pop(tls *TLS, _ uintptr, run int32) {
|
||
|
l := len(tls.pthreadCleanupItems)
|
||
|
item := tls.pthreadCleanupItems[l-1]
|
||
|
tls.pthreadCleanupItems = tls.pthreadCleanupItems[:l-1]
|
||
|
if run != 0 {
|
||
|
(*(*func(*TLS, uintptr))(unsafe.Pointer(&struct{ uintptr }{item.routine})))(tls, item.arg)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func Xpthread_attr_init(tls *TLS, a uintptr) int32 {
|
||
|
*(*Tpthread_attr_t)(unsafe.Pointer(a)) = Tpthread_attr_t{}
|
||
|
return 0
|
||
|
}
|
||
|
|
||
|
func Xpthread_attr_setscope(tls *TLS, a uintptr, scope int32) int32 {
|
||
|
switch scope {
|
||
|
case PTHREAD_SCOPE_SYSTEM:
|
||
|
return 0
|
||
|
case PTHREAD_SCOPE_PROCESS:
|
||
|
return ENOTSUP
|
||
|
default:
|
||
|
return EINVAL
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func Xpthread_attr_setstacksize(tls *TLS, a uintptr, stacksite Tsize_t) int32 {
|
||
|
return 0
|
||
|
}
|
||
|
|
||
|
func Xpthread_attr_setdetachstate(tls *TLS, a uintptr, state int32) (r int32) {
|
||
|
if uint32(state) > 1 {
|
||
|
return EINVAL
|
||
|
}
|
||
|
|
||
|
(*pthreadAttr)(unsafe.Pointer(a)).detachState = state
|
||
|
return 0
|
||
|
}
|
||
|
|
||
|
func Xpthread_attr_getdetachstate(tls *TLS, a uintptr, state uintptr) int32 {
|
||
|
*(*int32)(unsafe.Pointer(state)) = (*pthreadAttr)(unsafe.Pointer(a)).detachState
|
||
|
return 0
|
||
|
}
|
||
|
|
||
|
func Xpthread_attr_destroy(tls *TLS, a uintptr) int32 {
|
||
|
return 0
|
||
|
}
|
||
|
|
||
|
func Xpthread_self(tls *TLS) uintptr {
|
||
|
return tls.pthread
|
||
|
}
|
||
|
|
||
|
func Xpthread_mutex_init(tls *TLS, m, a uintptr) int32 {
|
||
|
*(*Tpthread_mutex_t)(unsafe.Pointer(m)) = Tpthread_mutex_t{}
|
||
|
if a != 0 {
|
||
|
(*pthreadMutex)(unsafe.Pointer(m)).mType = (*Tpthread_mutexattr_t)(unsafe.Pointer(a)).F__attr
|
||
|
}
|
||
|
return 0
|
||
|
}
|
||
|
|
||
|
func Xpthread_mutex_destroy(tls *TLS, mutex uintptr) int32 {
|
||
|
return 0
|
||
|
}
|
||
|
|
||
|
func Xpthread_mutex_lock(tls *TLS, m uintptr) int32 {
|
||
|
(*pthreadMutex)(unsafe.Pointer(m)).outer.Lock()
|
||
|
owner := (*pthreadMutex)(unsafe.Pointer(m)).owner
|
||
|
typ := (*pthreadMutex)(unsafe.Pointer(m)).mType
|
||
|
switch typ {
|
||
|
case PTHREAD_MUTEX_NORMAL:
|
||
|
(*pthreadMutex)(unsafe.Pointer(m)).owner = tls.ID
|
||
|
(*pthreadMutex)(unsafe.Pointer(m)).outer.Unlock()
|
||
|
(*pthreadMutex)(unsafe.Pointer(m)).Lock()
|
||
|
case PTHREAD_MUTEX_RECURSIVE:
|
||
|
switch owner {
|
||
|
case 0:
|
||
|
(*pthreadMutex)(unsafe.Pointer(m)).count = 1
|
||
|
(*pthreadMutex)(unsafe.Pointer(m)).owner = tls.ID
|
||
|
(*pthreadMutex)(unsafe.Pointer(m)).outer.Unlock()
|
||
|
(*pthreadMutex)(unsafe.Pointer(m)).Lock()
|
||
|
return 0
|
||
|
case tls.ID:
|
||
|
(*pthreadMutex)(unsafe.Pointer(m)).count++
|
||
|
(*pthreadMutex)(unsafe.Pointer(m)).outer.Unlock()
|
||
|
return 0
|
||
|
default:
|
||
|
wait:
|
||
|
(*pthreadMutex)(unsafe.Pointer(m)).outer.Unlock()
|
||
|
(*pthreadMutex)(unsafe.Pointer(m)).Lock()
|
||
|
(*pthreadMutex)(unsafe.Pointer(m)).outer.Lock()
|
||
|
if (*pthreadMutex)(unsafe.Pointer(m)).owner != 0 {
|
||
|
goto wait
|
||
|
}
|
||
|
|
||
|
(*pthreadMutex)(unsafe.Pointer(m)).count = 1
|
||
|
(*pthreadMutex)(unsafe.Pointer(m)).owner = tls.ID
|
||
|
(*pthreadMutex)(unsafe.Pointer(m)).outer.Unlock()
|
||
|
return 0
|
||
|
}
|
||
|
default:
|
||
|
panic(todo("typ=%v", typ))
|
||
|
}
|
||
|
return 0
|
||
|
}
|
||
|
|
||
|
func Xpthread_mutex_trylock(tls *TLS, m uintptr) int32 {
|
||
|
(*pthreadMutex)(unsafe.Pointer(m)).outer.Lock()
|
||
|
owner := (*pthreadMutex)(unsafe.Pointer(m)).owner
|
||
|
typ := (*pthreadMutex)(unsafe.Pointer(m)).mType
|
||
|
switch typ {
|
||
|
case PTHREAD_MUTEX_NORMAL:
|
||
|
if owner != 0 {
|
||
|
(*pthreadMutex)(unsafe.Pointer(m)).outer.Unlock()
|
||
|
return EBUSY
|
||
|
}
|
||
|
|
||
|
(*pthreadMutex)(unsafe.Pointer(m)).owner = tls.ID
|
||
|
(*pthreadMutex)(unsafe.Pointer(m)).outer.Unlock()
|
||
|
(*pthreadMutex)(unsafe.Pointer(m)).Lock()
|
||
|
return 0
|
||
|
default:
|
||
|
panic(todo("typ=%v", typ))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func Xpthread_mutex_unlock(tls *TLS, m uintptr) int32 {
|
||
|
(*pthreadMutex)(unsafe.Pointer(m)).outer.Lock()
|
||
|
count := (*pthreadMutex)(unsafe.Pointer(m)).count
|
||
|
owner := (*pthreadMutex)(unsafe.Pointer(m)).owner
|
||
|
typ := (*pthreadMutex)(unsafe.Pointer(m)).mType
|
||
|
switch typ {
|
||
|
case PTHREAD_MUTEX_NORMAL:
|
||
|
(*pthreadMutex)(unsafe.Pointer(m)).outer.Unlock()
|
||
|
(*pthreadMutex)(unsafe.Pointer(m)).Unlock()
|
||
|
return 0
|
||
|
case PTHREAD_MUTEX_RECURSIVE:
|
||
|
switch owner {
|
||
|
case tls.ID:
|
||
|
switch count {
|
||
|
case 1:
|
||
|
(*pthreadMutex)(unsafe.Pointer(m)).owner = 0
|
||
|
(*pthreadMutex)(unsafe.Pointer(m)).outer.Unlock()
|
||
|
(*pthreadMutex)(unsafe.Pointer(m)).Unlock()
|
||
|
return 0
|
||
|
default:
|
||
|
(*pthreadMutex)(unsafe.Pointer(m)).count--
|
||
|
(*pthreadMutex)(unsafe.Pointer(m)).outer.Unlock()
|
||
|
return 0
|
||
|
}
|
||
|
default:
|
||
|
panic(todo("", owner, tls.ID))
|
||
|
}
|
||
|
default:
|
||
|
panic(todo("", typ))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func Xpthread_cond_init(tls *TLS, c, a uintptr) int32 {
|
||
|
*(*Tpthread_cond_t)(unsafe.Pointer(c)) = Tpthread_cond_t{}
|
||
|
if a != 0 {
|
||
|
panic(todo(""))
|
||
|
}
|
||
|
|
||
|
conds.Lock()
|
||
|
delete(conds.conds, c)
|
||
|
conds.Unlock()
|
||
|
return 0
|
||
|
}
|
||
|
|
||
|
func Xpthread_cond_timedwait(tls *TLS, c, m, ts uintptr) (r int32) {
|
||
|
var to <-chan time.Time
|
||
|
if ts != 0 {
|
||
|
deadlineSecs := (*Ttimespec)(unsafe.Pointer(ts)).Ftv_sec
|
||
|
deadlineNsecs := (*Ttimespec)(unsafe.Pointer(ts)).Ftv_nsec
|
||
|
deadline := time.Unix(deadlineSecs, deadlineNsecs)
|
||
|
d := deadline.Sub(time.Now())
|
||
|
if d <= 0 {
|
||
|
return ETIMEDOUT
|
||
|
}
|
||
|
|
||
|
to = time.After(d)
|
||
|
}
|
||
|
|
||
|
conds.Lock()
|
||
|
waiters := conds.conds[c]
|
||
|
ch := make(chan struct{}, 1)
|
||
|
waiters = append(waiters, ch)
|
||
|
conds.conds[c] = waiters
|
||
|
conds.Unlock()
|
||
|
|
||
|
defer func() {
|
||
|
conds.Lock()
|
||
|
|
||
|
defer conds.Unlock()
|
||
|
|
||
|
waiters = conds.conds[c]
|
||
|
for i, v := range waiters {
|
||
|
if v == ch {
|
||
|
conds.conds[c] = slices.Delete(waiters, i, i+1)
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
}()
|
||
|
|
||
|
switch typ := (*pthreadMutex)(unsafe.Pointer(m)).mType; typ {
|
||
|
case PTHREAD_MUTEX_NORMAL:
|
||
|
(*pthreadMutex)(unsafe.Pointer(m)).owner = 0
|
||
|
(*pthreadMutex)(unsafe.Pointer(m)).Unlock()
|
||
|
select {
|
||
|
case <-ch:
|
||
|
// ok
|
||
|
case <-to:
|
||
|
r = ETIMEDOUT
|
||
|
}
|
||
|
(*pthreadMutex)(unsafe.Pointer(m)).owner = tls.ID
|
||
|
(*pthreadMutex)(unsafe.Pointer(m)).Lock()
|
||
|
return r
|
||
|
default:
|
||
|
panic(todo("", typ))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func Xpthread_cond_wait(tls *TLS, c, m uintptr) int32 {
|
||
|
return Xpthread_cond_timedwait(tls, c, m, 0)
|
||
|
}
|
||
|
|
||
|
func Xpthread_cond_signal(tls *TLS, c uintptr) int32 {
|
||
|
return pthreadSignalN(tls, c, false)
|
||
|
}
|
||
|
|
||
|
func pthreadSignalN(tls *TLS, c uintptr, all bool) int32 {
|
||
|
conds.Lock()
|
||
|
waiters := conds.conds[c]
|
||
|
handle := waiters
|
||
|
if len(waiters) != 0 {
|
||
|
switch {
|
||
|
case all:
|
||
|
delete(conds.conds, c)
|
||
|
default:
|
||
|
handle = handle[:1]
|
||
|
conds.conds[c] = waiters[1:]
|
||
|
}
|
||
|
}
|
||
|
conds.Unlock()
|
||
|
for _, v := range handle {
|
||
|
close(v)
|
||
|
}
|
||
|
return 0
|
||
|
}
|
||
|
|
||
|
func Xpthread_cond_broadcast(tls *TLS, c uintptr) int32 {
|
||
|
return pthreadSignalN(tls, c, true)
|
||
|
}
|
||
|
|
||
|
func Xpthread_cond_destroy(tls *TLS, c uintptr) int32 {
|
||
|
return Xpthread_cond_broadcast(tls, c)
|
||
|
}
|
||
|
|
||
|
func Xpthread_atfork(tls *TLS, prepare, parent, child uintptr) int32 {
|
||
|
// fork(2) not supported.
|
||
|
return 0
|
||
|
}
|
||
|
|
||
|
func Xpthread_mutexattr_init(tls *TLS, a uintptr) int32 {
|
||
|
*(*Tpthread_mutexattr_t)(unsafe.Pointer(a)) = Tpthread_mutexattr_t{}
|
||
|
return 0
|
||
|
}
|
||
|
|
||
|
func Xpthread_mutexattr_destroy(tls *TLS, a uintptr) int32 {
|
||
|
return 0
|
||
|
}
|
||
|
|
||
|
func Xpthread_mutexattr_settype(tls *TLS, a uintptr, typ int32) int32 {
|
||
|
if uint32(typ) > 2 {
|
||
|
return EINVAL
|
||
|
}
|
||
|
|
||
|
(*Tpthread_mutexattr_t)(unsafe.Pointer(a)).F__attr = uint32(typ) & 3
|
||
|
return 0
|
||
|
}
|
||
|
|
||
|
func Xpthread_detach(tls *TLS, t uintptr) int32 {
|
||
|
state := atomic.SwapInt32((*int32)(unsafe.Pointer(tls.pthread+unsafe.Offsetof(t__pthread{}.Fdetach_state))), _DT_DETACHED)
|
||
|
switch state {
|
||
|
case _DT_EXITED, _DT_DETACHED:
|
||
|
return 0
|
||
|
default:
|
||
|
panic(todo("", tls.ID, state))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// int pthread_equal(pthread_t, pthread_t);
|
||
|
func Xpthread_equal(tls *TLS, t, u uintptr) int32 {
|
||
|
return Bool32(t == u)
|
||
|
}
|
||
|
|
||
|
// int pthread_sigmask(int how, const sigset_t *restrict set, sigset_t *restrict old)
|
||
|
func _pthread_sigmask(tls *TLS, now int32, set, old uintptr) int32 {
|
||
|
// ignored
|
||
|
return 0
|
||
|
}
|
||
|
|
||
|
// 202402251838 all_test.go:589: files=36 buildFails=30 execFails=2 pass=4
|
||
|
// 202402262246 all_test.go:589: files=36 buildFails=26 execFails=2 pass=8
|
||
|
// 202403041858 all_musl_test.go:640: files=36 buildFails=22 execFails=4 pass=10
|