diff --git a/thirdparty/minidlna/HPKBUILD b/thirdparty/minidlna/HPKBUILD new file mode 100644 index 0000000000000000000000000000000000000000..58f1ad0eca708f8d792cfde26cb762525f1e459c --- /dev/null +++ b/thirdparty/minidlna/HPKBUILD @@ -0,0 +1,92 @@ +# Contributor: TangShaoteng +# Maintainer: TangShaoteng + +pkgname=minidlna +pkgver=1.3.3 +pkgrel=0 +pkgdesc="MiniDLNA is a simple media server software, with the aim of being fully compliant with DLNA/UPnP-AV clients." +url="https://sourceforge.net/projects/minidlna" +archs=("armeabi-v7a" "arm64-v8a") +license=("GPLv2") +depends=("FFmpeg" "jpeg" "sqlite" "libexif" "libid3tag" "libogg" "libvorbis" "flac") +makedepends=() + +source="https://sourceforge.net/projects/$pkgname/files/$pkgname/$pkgver/$pkgname-$pkgver.tar.gz" + +autounpack=true +downloadpackage=true +buildtools="configure" +builddir=$pkgname-$pkgver +packagename=$builddir.tar.gz +patchflag=true + +source envset.sh +host= + +prepare() { + if $patchflag + then + cd $builddir + # SDK 不支持sys/queue.h + patch -p1 < `pwd`/../minidlna_oh_pkg.patch + # patch只需要打一次,关闭打patch + patchflag=false + cd $OLDPWD + fi + cp -arf $builddir $builddir-$ARCH-build + if [ $ARCH == "armeabi-v7a" ] + then + setarm32ENV + host=arm-linux + elif [ $ARCH == "arm64-v8a" ] + then + setarm64ENV + host=aarch64-linux + else + echo "${ARCH} not support" + return -1 + fi + # pkgconfig找不到头文件和库,使用CFLAGS方式 + export CFLAGS="-I${LYCIUM_ROOT}/usr/FFmpeg/$ARCH/include -I${LYCIUM_ROOT}/usr/jpeg/$ARCH/include -I${LYCIUM_ROOT}/usr/sqlite/$ARCH/include -I${LYCIUM_ROOT}/usr/libexif/$ARCH/include -I${LYCIUM_ROOT}/usr/libid3tag/$ARCH/include -I${LYCIUM_ROOT}/usr/libogg/$ARCH/include -I${LYCIUM_ROOT}/usr/libvorbis/$ARCH/include -I${LYCIUM_ROOT}/usr/flac/$ARCH/include -L${LYCIUM_ROOT}/usr/jpeg/$ARCH/lib -L${LYCIUM_ROOT}/usr/libexif/$ARCH/lib -L${LYCIUM_ROOT}/usr/libid3tag/$ARCH/lib -L${LYCIUM_ROOT}/usr/libogg/$ARCH/lib -L${LYCIUM_ROOT}/usr/sqlite/$ARCH/lib -L${LYCIUM_ROOT}/usr/FFmpeg/$ARCH/lib -L${LYCIUM_ROOT}/usr/libvorbis/$ARCH/lib -L${LYCIUM_ROOT}/usr/flac/$ARCH/lib $CFLAGS" +} + +build() { + cd $builddir-$ARCH-build + ./configure "$@" --host=$host > `pwd`/build.log 2>&1 + make VERBOSE=1 -j4 >> `pwd`/build.log 2>&1 + ret=$? + cd $OLDPWD + return $ret +} + +package() { + cd $builddir-$ARCH-build + make install >> `pwd`/build.log 2>&1 + cd $OLDPWD + if [ $ARCH == "armeabi-v7a" ] + then + unsetarm32ENV + elif [ $ARCH == "arm64-v8a" ] + then + unsetarm64ENV + else + echo "${ARCH} not support" + return -1 + fi + unset host +} + +check() { + echo "The test must be on an OpenHarmony device!" + # real test + # mkdir -p ${LYCIUM_ROOT}/usr/${pkgname}/$ARCH/Music ${LYCIUM_ROOT}/usr/${pkgname}/$ARCH/Pictures ${LYCIUM_ROOT}/usr/${pkgname}/$ARCH/Videos + # cp /system/etc/graphic/bootsound.wav ${LYCIUM_ROOT}/usr/${pkgname}/$ARCH/Music/ + # cp /system/etc/wallpaperdefault.jpeg ${LYCIUM_ROOT}/usr/${pkgname}/$ARCH/Pictures/ + # cp /system/etc/graphic/bootvideo.mp4 ${LYCIUM_ROOT}/usr/${pkgname}/$ARCH/Videos/ + # echo -e "media_dir=A,${LYCIUM_ROOT}/usr/${pkgname}/$ARCH/Music\nmedia_dir=P,${LYCIUM_ROOT}/usr/${pkgname}/$ARCH/Pictures\nmedia_dir=V,${LYCIUM_ROOT}/usr/${pkgname}/$ARCH/Videos\nfriendly_name=OHOSMedia\ndb_dir=${LYCIUM_ROOT}/usr/${pkgname}/$ARCH/cache\nlog_dir=${LYCIUM_ROOT}/usr/${pkgname}/$ARCH/log\ninotify=yes" > ${LYCIUM_ROOT}/usr/${pkgname}/$ARCH/minidlna.conf + # minidlnad -v -f ${LYCIUM_ROOT}/usr/${pkgname}/$ARCH/minidlna.conf -p 8080 -P ${LYCIUM_ROOT}/usr/${pkgname}/$ARCH/minidlna.pid +} + +cleanbuild() { + rm -rf ${PWD}/$builddir ${PWD}/$builddir-${archs[0]}-build ${PWD}/$builddir-${archs[1]}-build +} diff --git a/thirdparty/minidlna/HPKCHECK b/thirdparty/minidlna/HPKCHECK new file mode 100644 index 0000000000000000000000000000000000000000..b81fcb234d5b0d88176b1a765af5af2470069d65 --- /dev/null +++ b/thirdparty/minidlna/HPKCHECK @@ -0,0 +1,81 @@ +# Contributor: TangShaoteng +# Maintainer: TangShaoteng + +source HPKBUILD > /dev/null 2>&1 +logfile=${LYCIUM_THIRDPARTY_ROOT}/${pkgname}/${pkgname}_${ARCH}_${OHOS_SDK_VER}_test.log + +checkprepare() { + # 将minidlnad可执行程序添加到环境变量 + export PATH=${LYCIUM_ROOT}/usr/${pkgname}/$ARCH/sbin:$PATH +} + +openharmonycheck() { + res=0 + # 获取IP地址 + ipv4_addr_str=`ifconfig | grep "inet addr" | grep Bcast | awk '{print $2}'` + ipv4_addr_result=$(echo $ipv4_addr_str | grep "addr") + if [ -z "$ipv4_addr_result" ] + then + echo "error: not found ipv4 addr" > ${logfile} 2>&1 + return -1; + fi + ipv4_addr=${ipv4_addr_result:5} + # 创建配置文件 + echo -e "media_dir=A,${LYCIUM_ROOT}/usr/${pkgname}/$ARCH/Music\nmedia_dir=P,${LYCIUM_ROOT}/usr/${pkgname}/$ARCH/Pictures\nmedia_dir=V,${LYCIUM_ROOT}/usr/${pkgname}/$ARCH/Videos\nfriendly_name=OHOSMedia\ndb_dir=${LYCIUM_ROOT}/usr/${pkgname}/$ARCH/cache\nlog_dir=${LYCIUM_ROOT}/usr/${pkgname}/$ARCH/log\ninotify=yes" > ${LYCIUM_ROOT}/usr/${pkgname}/$ARCH/minidlna.conf + res=$? + if [ $res -ne 0 ] + then + echo "create minidlna config file failed" > ${logfile} 2>&1 + return res; + fi + # 拷贝音乐资源到minidlna共享目录 + mkdir -p ${LYCIUM_ROOT}/usr/${pkgname}/$ARCH/Music ${LYCIUM_ROOT}/usr/${pkgname}/$ARCH/Pictures ${LYCIUM_ROOT}/usr/${pkgname}/$ARCH/Videos + cp /system/etc/graphic/bootsound.wav ${LYCIUM_ROOT}/usr/${pkgname}/$ARCH/Music/ + res=$? + if [ $res -ne 0 ] + then + echo "copy music file failed" > ${logfile} 2>&1 + return res; + fi + # 拷贝图片资源到minidlna共享目录 + cp /system/etc/wallpaperdefault.jpeg ${LYCIUM_ROOT}/usr/${pkgname}/$ARCH/Pictures/ + res=$? + if [ $res -ne 0 ] + then + echo "copy picture file failed" > ${logfile} 2>&1 + return res; + fi + # 拷贝视频资源到minidlna共享目录 + cp /system/etc/graphic/bootvideo.mp4 ${LYCIUM_ROOT}/usr/${pkgname}/$ARCH/Videos/ + res=$? + if [ $res -ne 0 ] + then + echo "copy video file failed" > ${logfile} 2>&1 + return res; + fi + # 启动minidlna服务器程序 + minidlnad -v -f ${LYCIUM_ROOT}/usr/${pkgname}/$ARCH/minidlna.conf -p 8080 -P ${LYCIUM_ROOT}/usr/${pkgname}/$ARCH/minidlna.pid > ${logfile} 2>&1 + res=$? + if [ $res -ne 0 ] + then + echo "error: the minidlna service failed to run" >> ${logfile} 2>&1 + mkdir ${LYCIUM_FAULT_PATH}/${pkgname} + cp ${LYCIUM_ROOT}/usr/${pkgname}/$ARCH/log/* ${LYCIUM_FAULT_PATH}/${pkgname}/ + return res; + fi + # 查询minidlna服务器程序运行状态 + minidlna_pid=`ps -ef | grep minidlnad | grep -v grep | awk '{print $2}'` + if [ -z "$minidlna_pid" ] + then + echo "error: the minidlna service exited" >> ${logfile} 2>&1 + mkdir ${LYCIUM_FAULT_PATH}/${pkgname} + cp ${LYCIUM_ROOT}/usr/${pkgname}/$ARCH/log/* ${LYCIUM_FAULT_PATH}/${pkgname}/ + return res; + fi + # 5分钟后主动结束minidlna服务器程序,测试结束 + sleep 300 + kill -9 ${minidlna_pid} + res=$? + + return $res +} diff --git a/thirdparty/minidlna/OAT.xml b/thirdparty/minidlna/OAT.xml new file mode 100644 index 0000000000000000000000000000000000000000..00f4c47405081491b05af1251a48f342c410298a --- /dev/null +++ b/thirdparty/minidlna/OAT.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/thirdparty/minidlna/README.OpenSource b/thirdparty/minidlna/README.OpenSource new file mode 100644 index 0000000000000000000000000000000000000000..317a3901f033304f8a9648fa4e1e5d8a75d13786 --- /dev/null +++ b/thirdparty/minidlna/README.OpenSource @@ -0,0 +1,11 @@ +[ + { + "Name": "minidlna", + "License": "GPLv2", + "License File": "https://sourceforge.net/projects/minidlna/", + "Version Number": "1.3.3", + "Owner": "sttangc@isoftstone.com", + "Upstream URL": "https://sourceforge.net/projects/minidlna/files/minidlna/1.3.3/minidlna-1.3.3.tar.gz", + "Description": "MiniDLNA (aka ReadyDLNA) is server software with the aim of being fully compliant with DLNA/UPnP-AV clients." + } +] diff --git a/thirdparty/minidlna/README_zh.md b/thirdparty/minidlna/README_zh.md new file mode 100644 index 0000000000000000000000000000000000000000..54578a65fbc68541346b4a438407d34982b97334 --- /dev/null +++ b/thirdparty/minidlna/README_zh.md @@ -0,0 +1,11 @@ +# minidlna三方库说明 +## 功能简介 +minidlna(又名ReadyDLNA)是服务器软件,旨在完全兼容DLNA / UPnP-AV客户端。 +## 使用约束 +- IDE版本:DevEco Studio 3.1 Release +- SDK版本:ohos_sdk_public 4.0.8.1 (API Version 10 Release) +- 三方库版本:1.3.3 +- 当前适配的功能:向网络上的客户端提供媒体文件(音乐、图片、视频)。 + +## 集成方式 ++ [应用hap包集成](docs/hap_integrate.md) diff --git a/thirdparty/minidlna/SHA512SUM b/thirdparty/minidlna/SHA512SUM new file mode 100644 index 0000000000000000000000000000000000000000..eda3e5e8c1965deee2f7fa16b8929adc2f765b37 --- /dev/null +++ b/thirdparty/minidlna/SHA512SUM @@ -0,0 +1 @@ +3571af71b49d46aacc273a9b35e2c78aeccb966c1d6122f8186074c34f9a177ac60622ccf29f307d8d73f502c7a99f023f96f0c43bbd398c7ef82bb57d77cc1d minidlna-1.3.3.tar.gz diff --git a/thirdparty/minidlna/docs/hap_integrate.md b/thirdparty/minidlna/docs/hap_integrate.md new file mode 100644 index 0000000000000000000000000000000000000000..d2b708b5e5c519da4efeda71d3dcea92321641bc --- /dev/null +++ b/thirdparty/minidlna/docs/hap_integrate.md @@ -0,0 +1,139 @@ +# minidlna集成到应用hap + +本库是在RK3568开发板上基于OpenHarmony3.2 Release版本的镜像验证的,如果是从未使用过RK3568,可以先查看[润和RK3568开发板标准系统快速上手](https://gitee.com/openharmony-sig/knowledge_demo_temp/tree/master/docs/rk3568_helloworld)。 + +## 开发环境 + +- ubuntu20.04 +- [OpenHarmony3.2Release镜像](https://gitee.com/link?target=https%3A%2F%2Frepo.huaweicloud.com%2Fopenharmony%2Fos%2F3.2-Release%2Fdayu200_standard_arm32.tar.gz) +- [ohos_sdk_public 4.0.8.1 (API Version 10 Release)](https://gitee.com/link?target=http%3A%2F%2Fdownload.ci.openharmony.cn%2Fversion%2FMaster_Version%2FOpenHarmony_4.0.8.1%2F20230608_091058%2Fversion-Master_Version-OpenHarmony_4.0.8.1-20230608_091058-ohos-sdk-public.tar.gz) +- [DevEco Studio 3.1 Release](https://gitee.com/link?target=https%3A%2F%2Fcontentcenter-vali-drcn.dbankcdn.cn%2Fpvt_2%2FDeveloperAlliance_package_901_9%2F81%2Fv3%2FtgRUB84wR72nTfE8Ir_xMw%2Fdevecostudio-windows-3.1.0.501.zip%3FHW-CC-KV%3DV1%26HW-CC-Date%3D20230621T074329Z%26HW-CC-Expire%3D315360000%26HW-CC-Sign%3D22F6787DF6093ECB4D4E08F9379B114280E1F65DA710599E48EA38CB24F3DBF2) +- [准备三方库构建环境](../../../lycium/README.md#1编译环境准备) +- [准备三方库测试环境](../../../lycium/README.md#3ci环境准备) + +## 编译三方库 + +- 下载本仓库 + + ```shell + git clone https://gitee.com/openharmony-sig/tpc_c_cplusplus.git --depth=1 + ``` + +- 三方库目录结构 + + ```shell + tpc_c_cplusplus/thirdparty/minidlna #三方库minidlna的目录结构如下 + ├── minidlna_oh_pkg.patch #构建patch文件 + ├── docs #三方库相关文档的文件夹 + ├── HPKBUILD #构建脚本 + ├── HPKCHECK #自动化测试脚本 + ├── SHA512SUM #三方库校验文件 + ├── README.OpenSource #说明三方库源码的下载地址,版本,license等信息 + ├── README_zh.md #三方库说明文档 + ├── OAT.xml #开源扫描相关文件 + ``` + +- 在tpc_c_cplusplus/lycium目录下编译三方库 + + 编译环境的搭建参考[准备三方库构建环境](../../../lycium/README.md#1编译环境准备) + + ```shell + cd tpc_c_cplusplus/lycium + ./build.sh minidlna + ``` + +- 三方库头文件及生成的库 + + 在lycium目录下会生成usr目录,该目录下存在已编译完成的32位和64位三方库和头文件 + + ```shell + minidlna/arm64-v8a minidlna/armeabi-v7a + ``` +- [测试三方库](#测试三方库) + +## 应用中使用三方库 + +- 该库是服务器程序,按照该库的官方编译指导编译出来的就是可执行程序及服务器相关配置文件,没有动态库和静态库可供应用直接使用。 + +## 测试三方库 + +三方库的测试使用原库自带的测试用例来做测试,[准备三方库测试环境](../../../lycium/README.md#3ci环境准备) + +- 测试环境搭建 + + 1. 需要组建测试网络:测试设备与windows电脑处于同一局域网络,双向能ping通对方。 + 2. 确保windows电脑的**启用网络发现**和**启用文件和打印机共享**两个配置项是打开的(检查方法:控制面板->网络和Internet->网络共享中心->更改高级共享设置)。 + +- 配置环境变量 + 执行如下命令: + + ```shell + export LD_LIBRARY_PATH=${LYCIUM_ROOT}/usr/jpeg/${ARCH}/lib:${LYCIUM_ROOT}/usr/FFmpeg/${ARCH}/lib:${LYCIUM_ROOT}/usr/libexif/${ARCH}/lib:${LYCIUM_ROOT}/usr/libvorbis/${ARCH}/lib:${LYCIUM_ROOT}/usr/sqlite/${ARCH}/lib:${LYCIUM_ROOT}/usr/flac/${ARCH}/lib:${LYCIUM_ROOT}/usr/libid3tag/${ARCH}/lib:${LYCIUM_ROOT}/usr/libogg/${ARCH}/lib:$LD_LIBRARY_PATH + export PATH=${LYCIUM_ROOT}/usr/minidlna/${ARCH}/sbin:$PATH + ``` + > 注意:LYCIUM_ROOT代表lycium所在目录的绝对路径;ARCH代表构建架构,64位为arm64-v8a,32位为armeabi-v7a。 + +- 创建配置文件/data/minidlna.conf,内容如下: + ```shell + media_dir=A,/data/Music + media_dir=P,/data/Pictures + media_dir=V,/data/Videos + friendly_name=OHOSMedia + db_dir=/data/minidlna/cache + log_dir=/data/minidlna/log + inotify=yes + ``` + +- 拷贝媒体资源到指定目录 + ``` + mkdir -p /data/Music /data/Pictures /data/Video + cp /system/etc/graphic/bootsound.wav /data/Music + cp /system/etc/wallpaperdefault.jpeg /data/Pictures + cp /system/etc/graphic/bootvideo.mp4 /data/Videos + ``` + +- 启动minidlna服务器程序 + ``` + minidlnad -v -f /data/minidlna.conf -p 8080 -P /data/minidlna/minidlna.pid + ``` + +- 查看minidlna运行状态 + minidlnad是守护进程,在后台运行,用以下命令查看进程是否存在: + ``` + ps -ef | grep minidlnad | grep -v grep + ``` + +Windows电脑端测试 + +- 使用Windows Media Player播放器测试 + 1. 在播放器的《其他媒体库》下方有OHOSMedia设备存在 + +![minidlna_media_player_device](pic/minidlna_media_player_device.png) + + 2. 打开视频分类,可以看到视频文件,可以打开一个视频进行播放 + +![minidlna_video](pic/minidlna_video.png) + + 3. 打开图片分类,可以看到图片文件,可以打开一张图片进行预览 + +![minidlna_picture](pic/minidlna_picture.png) + + 4. 打开音乐分类,可以看到音乐文件,可以打开一个音乐进行播放 + +![minidlna_music](pic/minidlna_music.png) + +- 使用文件资源管理器进行测试 + 打开文件资源管理器中的《网络位置》,我们也可以找到OHOSMedia + +![minidlna_network_device](pic/minidlna_network_device.png) + + 打开OHOSMedia后可以看到Music、Pictures、Video等文件夹,同样可以进入对应的目录打开文件进行播放或预览 + +![minidlna_network_filemanager](pic/minidlna_network_filemanager.png) + +## 参考资料 + +- [润和RK3568开发板标准系统快速上手](https://gitee.com/openharmony-sig/knowledge_demo_temp/tree/master/docs/rk3568_helloworld) +- [OpenHarmony三方库地址](https://gitee.com/openharmony-tpc) +- [OpenHarmony知识体系](https://gitee.com/openharmony-sig/knowledge) +- [通过DevEco Studio开发一个NAPI工程](https://gitee.com/openharmony-sig/knowledge_demo_temp/blob/master/docs/napi_study/docs/hello_napi.md) diff --git a/thirdparty/minidlna/docs/pic/minidlna_media_player_device.png b/thirdparty/minidlna/docs/pic/minidlna_media_player_device.png new file mode 100644 index 0000000000000000000000000000000000000000..a81f7c5339ef2365646332a2de41c310b4d2d31f Binary files /dev/null and b/thirdparty/minidlna/docs/pic/minidlna_media_player_device.png differ diff --git a/thirdparty/minidlna/docs/pic/minidlna_music.png b/thirdparty/minidlna/docs/pic/minidlna_music.png new file mode 100644 index 0000000000000000000000000000000000000000..8c8c1440d8ef060906cf053ea5077587e4497b4a Binary files /dev/null and b/thirdparty/minidlna/docs/pic/minidlna_music.png differ diff --git a/thirdparty/minidlna/docs/pic/minidlna_network_device.png b/thirdparty/minidlna/docs/pic/minidlna_network_device.png new file mode 100644 index 0000000000000000000000000000000000000000..35f91e1bb2b00472c072aa1cb3fb702d3a123dfe Binary files /dev/null and b/thirdparty/minidlna/docs/pic/minidlna_network_device.png differ diff --git a/thirdparty/minidlna/docs/pic/minidlna_network_filemanager.png b/thirdparty/minidlna/docs/pic/minidlna_network_filemanager.png new file mode 100644 index 0000000000000000000000000000000000000000..7b0b32bc0eaf151d1daa2bf26d0a9da75186f4f6 Binary files /dev/null and b/thirdparty/minidlna/docs/pic/minidlna_network_filemanager.png differ diff --git a/thirdparty/minidlna/docs/pic/minidlna_picture.png b/thirdparty/minidlna/docs/pic/minidlna_picture.png new file mode 100644 index 0000000000000000000000000000000000000000..62027012b2cf655a5a52b2c5e34a51532898cc86 Binary files /dev/null and b/thirdparty/minidlna/docs/pic/minidlna_picture.png differ diff --git a/thirdparty/minidlna/docs/pic/minidlna_video.png b/thirdparty/minidlna/docs/pic/minidlna_video.png new file mode 100644 index 0000000000000000000000000000000000000000..2e87e549e6b14c8cb69e50e3599bf6adf8560473 Binary files /dev/null and b/thirdparty/minidlna/docs/pic/minidlna_video.png differ diff --git a/thirdparty/minidlna/minidlna_oh_pkg.patch b/thirdparty/minidlna/minidlna_oh_pkg.patch new file mode 100644 index 0000000000000000000000000000000000000000..6efc93dec57ebd77bcca42c2d5079e1a1b44b742 --- /dev/null +++ b/thirdparty/minidlna/minidlna_oh_pkg.patch @@ -0,0 +1,594 @@ +diff --git a/configure b/configure +index 6392334..aaf52d9 100755 +--- a/configure ++++ b/configure +@@ -12219,7 +12219,8 @@ then : + printf "%s\n" "#define HAVE_SYS_PARAM_H 1" >>confdefs.h + + fi +-ac_fn_c_check_header_compile "$LINENO" "sys/queue.h" "ac_cv_header_sys_queue_h" "$ac_includes_default" ++#ac_fn_c_check_header_compile "$LINENO" "sys/queue.h" "ac_cv_header_sys_queue_h" "$ac_includes_default" ++ac_cv_header_sys_queue_h=yes + if test "x$ac_cv_header_sys_queue_h" = xyes + then : + printf "%s\n" "#define HAVE_SYS_QUEUE_H 1" >>confdefs.h +diff --git a/sys/queue.h b/sys/queue.h +new file mode 100644 +index 0000000..daf4553 +--- /dev/null ++++ b/sys/queue.h +@@ -0,0 +1,574 @@ ++/* ++ * Copyright (c) 1991, 1993 ++ * The Regents of the University of California. All rights reserved. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions ++ * are met: ++ * 1. Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * 2. Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * 3. Neither the name of the University nor the names of its contributors ++ * may be used to endorse or promote products derived from this software ++ * without specific prior written permission. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ++ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE ++ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL ++ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS ++ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ++ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT ++ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY ++ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF ++ * SUCH DAMAGE. ++ * ++ * @(#)queue.h 8.5 (Berkeley) 8/20/94 ++ */ ++ ++#ifndef _SYS_QUEUE_H_ ++#define _SYS_QUEUE_H_ ++ ++/* ++ * This file defines five types of data structures: singly-linked lists, ++ * lists, simple queues, tail queues, and circular queues. ++ * ++ * A singly-linked list is headed by a single forward pointer. The ++ * elements are singly linked for minimum space and pointer manipulation ++ * overhead at the expense of O(n) removal for arbitrary elements. New ++ * elements can be added to the list after an existing element or at the ++ * head of the list. Elements being removed from the head of the list ++ * should use the explicit macro for this purpose for optimum ++ * efficiency. A singly-linked list may only be traversed in the forward ++ * direction. Singly-linked lists are ideal for applications with large ++ * datasets and few or no removals or for implementing a LIFO queue. ++ * ++ * A list is headed by a single forward pointer (or an array of forward ++ * pointers for a hash table header). The elements are doubly linked ++ * so that an arbitrary element can be removed without a need to ++ * traverse the list. New elements can be added to the list before ++ * or after an existing element or at the head of the list. A list ++ * may only be traversed in the forward direction. ++ * ++ * A simple queue is headed by a pair of pointers, one the head of the ++ * list and the other to the tail of the list. The elements are singly ++ * linked to save space, so elements can only be removed from the ++ * head of the list. New elements can be added to the list after ++ * an existing element, at the head of the list, or at the end of the ++ * list. A simple queue may only be traversed in the forward direction. ++ * ++ * A tail queue is headed by a pair of pointers, one to the head of the ++ * list and the other to the tail of the list. The elements are doubly ++ * linked so that an arbitrary element can be removed without a need to ++ * traverse the list. New elements can be added to the list before or ++ * after an existing element, at the head of the list, or at the end of ++ * the list. A tail queue may be traversed in either direction. ++ * ++ * A circle queue is headed by a pair of pointers, one to the head of the ++ * list and the other to the tail of the list. The elements are doubly ++ * linked so that an arbitrary element can be removed without a need to ++ * traverse the list. New elements can be added to the list before or after ++ * an existing element, at the head of the list, or at the end of the list. ++ * A circle queue may be traversed in either direction, but has a more ++ * complex end of list detection. ++ * ++ * For details on the use of these macros, see the queue(3) manual page. ++ */ ++ ++/* ++ * List definitions. ++ */ ++#define LIST_HEAD(name, type) \ ++struct name { \ ++ struct type *lh_first; /* first element */ \ ++} ++ ++#define LIST_HEAD_INITIALIZER(head) \ ++ { NULL } ++ ++#define LIST_ENTRY(type) \ ++struct { \ ++ struct type *le_next; /* next element */ \ ++ struct type **le_prev; /* address of previous next element */ \ ++} ++ ++/* ++ * List functions. ++ */ ++#define LIST_INIT(head) do { \ ++ (head)->lh_first = NULL; \ ++} while (/*CONSTCOND*/0) ++ ++#define LIST_INSERT_AFTER(listelm, elm, field) do { \ ++ if (((elm)->field.le_next = (listelm)->field.le_next) != NULL) \ ++ (listelm)->field.le_next->field.le_prev = \ ++ &(elm)->field.le_next; \ ++ (listelm)->field.le_next = (elm); \ ++ (elm)->field.le_prev = &(listelm)->field.le_next; \ ++} while (/*CONSTCOND*/0) ++ ++#define LIST_INSERT_BEFORE(listelm, elm, field) do { \ ++ (elm)->field.le_prev = (listelm)->field.le_prev; \ ++ (elm)->field.le_next = (listelm); \ ++ *(listelm)->field.le_prev = (elm); \ ++ (listelm)->field.le_prev = &(elm)->field.le_next; \ ++} while (/*CONSTCOND*/0) ++ ++#define LIST_INSERT_HEAD(head, elm, field) do { \ ++ if (((elm)->field.le_next = (head)->lh_first) != NULL) \ ++ (head)->lh_first->field.le_prev = &(elm)->field.le_next;\ ++ (head)->lh_first = (elm); \ ++ (elm)->field.le_prev = &(head)->lh_first; \ ++} while (/*CONSTCOND*/0) ++ ++#define LIST_REMOVE(elm, field) do { \ ++ if ((elm)->field.le_next != NULL) \ ++ (elm)->field.le_next->field.le_prev = \ ++ (elm)->field.le_prev; \ ++ *(elm)->field.le_prev = (elm)->field.le_next; \ ++} while (/*CONSTCOND*/0) ++ ++#define LIST_FOREACH(var, head, field) \ ++ for ((var) = ((head)->lh_first); \ ++ (var); \ ++ (var) = ((var)->field.le_next)) ++ ++/* ++ * List access methods. ++ */ ++#define LIST_EMPTY(head) ((head)->lh_first == NULL) ++#define LIST_FIRST(head) ((head)->lh_first) ++#define LIST_NEXT(elm, field) ((elm)->field.le_next) ++ ++ ++/* ++ * Singly-linked List definitions. ++ */ ++#define SLIST_HEAD(name, type) \ ++struct name { \ ++ struct type *slh_first; /* first element */ \ ++} ++ ++#define SLIST_HEAD_INITIALIZER(head) \ ++ { NULL } ++ ++#define SLIST_ENTRY(type) \ ++struct { \ ++ struct type *sle_next; /* next element */ \ ++} ++ ++/* ++ * Singly-linked List functions. ++ */ ++#define SLIST_INIT(head) do { \ ++ (head)->slh_first = NULL; \ ++} while (/*CONSTCOND*/0) ++ ++#define SLIST_INSERT_AFTER(slistelm, elm, field) do { \ ++ (elm)->field.sle_next = (slistelm)->field.sle_next; \ ++ (slistelm)->field.sle_next = (elm); \ ++} while (/*CONSTCOND*/0) ++ ++#define SLIST_INSERT_HEAD(head, elm, field) do { \ ++ (elm)->field.sle_next = (head)->slh_first; \ ++ (head)->slh_first = (elm); \ ++} while (/*CONSTCOND*/0) ++ ++#define SLIST_REMOVE_HEAD(head, field) do { \ ++ (head)->slh_first = (head)->slh_first->field.sle_next; \ ++} while (/*CONSTCOND*/0) ++ ++#define SLIST_REMOVE(head, elm, type, field) do { \ ++ if ((head)->slh_first == (elm)) { \ ++ SLIST_REMOVE_HEAD((head), field); \ ++ } \ ++ else { \ ++ struct type *curelm = (head)->slh_first; \ ++ while(curelm->field.sle_next != (elm)) \ ++ curelm = curelm->field.sle_next; \ ++ curelm->field.sle_next = \ ++ curelm->field.sle_next->field.sle_next; \ ++ } \ ++} while (/*CONSTCOND*/0) ++ ++#define SLIST_FOREACH(var, head, field) \ ++ for((var) = (head)->slh_first; (var); (var) = (var)->field.sle_next) ++ ++/* ++ * Singly-linked List access methods. ++ */ ++#define SLIST_EMPTY(head) ((head)->slh_first == NULL) ++#define SLIST_FIRST(head) ((head)->slh_first) ++#define SLIST_NEXT(elm, field) ((elm)->field.sle_next) ++ ++ ++/* ++ * Singly-linked Tail queue declarations. ++ */ ++#define STAILQ_HEAD(name, type) \ ++struct name { \ ++ struct type *stqh_first; /* first element */ \ ++ struct type **stqh_last; /* addr of last next element */ \ ++} ++ ++#define STAILQ_HEAD_INITIALIZER(head) \ ++ { NULL, &(head).stqh_first } ++ ++#define STAILQ_ENTRY(type) \ ++struct { \ ++ struct type *stqe_next; /* next element */ \ ++} ++ ++/* ++ * Singly-linked Tail queue functions. ++ */ ++#define STAILQ_INIT(head) do { \ ++ (head)->stqh_first = NULL; \ ++ (head)->stqh_last = &(head)->stqh_first; \ ++} while (/*CONSTCOND*/0) ++ ++#define STAILQ_INSERT_HEAD(head, elm, field) do { \ ++ if (((elm)->field.stqe_next = (head)->stqh_first) == NULL) \ ++ (head)->stqh_last = &(elm)->field.stqe_next; \ ++ (head)->stqh_first = (elm); \ ++} while (/*CONSTCOND*/0) ++ ++#define STAILQ_INSERT_TAIL(head, elm, field) do { \ ++ (elm)->field.stqe_next = NULL; \ ++ *(head)->stqh_last = (elm); \ ++ (head)->stqh_last = &(elm)->field.stqe_next; \ ++} while (/*CONSTCOND*/0) ++ ++#define STAILQ_INSERT_AFTER(head, listelm, elm, field) do { \ ++ if (((elm)->field.stqe_next = (listelm)->field.stqe_next) == NULL)\ ++ (head)->stqh_last = &(elm)->field.stqe_next; \ ++ (listelm)->field.stqe_next = (elm); \ ++} while (/*CONSTCOND*/0) ++ ++#define STAILQ_REMOVE_HEAD(head, field) do { \ ++ if (((head)->stqh_first = (head)->stqh_first->field.stqe_next) == NULL) \ ++ (head)->stqh_last = &(head)->stqh_first; \ ++} while (/*CONSTCOND*/0) ++ ++#define STAILQ_REMOVE(head, elm, type, field) do { \ ++ if ((head)->stqh_first == (elm)) { \ ++ STAILQ_REMOVE_HEAD((head), field); \ ++ } else { \ ++ struct type *curelm = (head)->stqh_first; \ ++ while (curelm->field.stqe_next != (elm)) \ ++ curelm = curelm->field.stqe_next; \ ++ if ((curelm->field.stqe_next = \ ++ curelm->field.stqe_next->field.stqe_next) == NULL) \ ++ (head)->stqh_last = &(curelm)->field.stqe_next; \ ++ } \ ++} while (/*CONSTCOND*/0) ++ ++#define STAILQ_FOREACH(var, head, field) \ ++ for ((var) = ((head)->stqh_first); \ ++ (var); \ ++ (var) = ((var)->field.stqe_next)) ++ ++#define STAILQ_CONCAT(head1, head2) do { \ ++ if (!STAILQ_EMPTY((head2))) { \ ++ *(head1)->stqh_last = (head2)->stqh_first; \ ++ (head1)->stqh_last = (head2)->stqh_last; \ ++ STAILQ_INIT((head2)); \ ++ } \ ++} while (/*CONSTCOND*/0) ++ ++/* ++ * Singly-linked Tail queue access methods. ++ */ ++#define STAILQ_EMPTY(head) ((head)->stqh_first == NULL) ++#define STAILQ_FIRST(head) ((head)->stqh_first) ++#define STAILQ_NEXT(elm, field) ((elm)->field.stqe_next) ++ ++ ++/* ++ * Simple queue definitions. ++ */ ++#define SIMPLEQ_HEAD(name, type) \ ++struct name { \ ++ struct type *sqh_first; /* first element */ \ ++ struct type **sqh_last; /* addr of last next element */ \ ++} ++ ++#define SIMPLEQ_HEAD_INITIALIZER(head) \ ++ { NULL, &(head).sqh_first } ++ ++#define SIMPLEQ_ENTRY(type) \ ++struct { \ ++ struct type *sqe_next; /* next element */ \ ++} ++ ++/* ++ * Simple queue functions. ++ */ ++#define SIMPLEQ_INIT(head) do { \ ++ (head)->sqh_first = NULL; \ ++ (head)->sqh_last = &(head)->sqh_first; \ ++} while (/*CONSTCOND*/0) ++ ++#define SIMPLEQ_INSERT_HEAD(head, elm, field) do { \ ++ if (((elm)->field.sqe_next = (head)->sqh_first) == NULL) \ ++ (head)->sqh_last = &(elm)->field.sqe_next; \ ++ (head)->sqh_first = (elm); \ ++} while (/*CONSTCOND*/0) ++ ++#define SIMPLEQ_INSERT_TAIL(head, elm, field) do { \ ++ (elm)->field.sqe_next = NULL; \ ++ *(head)->sqh_last = (elm); \ ++ (head)->sqh_last = &(elm)->field.sqe_next; \ ++} while (/*CONSTCOND*/0) ++ ++#define SIMPLEQ_INSERT_AFTER(head, listelm, elm, field) do { \ ++ if (((elm)->field.sqe_next = (listelm)->field.sqe_next) == NULL)\ ++ (head)->sqh_last = &(elm)->field.sqe_next; \ ++ (listelm)->field.sqe_next = (elm); \ ++} while (/*CONSTCOND*/0) ++ ++#define SIMPLEQ_REMOVE_HEAD(head, field) do { \ ++ if (((head)->sqh_first = (head)->sqh_first->field.sqe_next) == NULL) \ ++ (head)->sqh_last = &(head)->sqh_first; \ ++} while (/*CONSTCOND*/0) ++ ++#define SIMPLEQ_REMOVE(head, elm, type, field) do { \ ++ if ((head)->sqh_first == (elm)) { \ ++ SIMPLEQ_REMOVE_HEAD((head), field); \ ++ } else { \ ++ struct type *curelm = (head)->sqh_first; \ ++ while (curelm->field.sqe_next != (elm)) \ ++ curelm = curelm->field.sqe_next; \ ++ if ((curelm->field.sqe_next = \ ++ curelm->field.sqe_next->field.sqe_next) == NULL) \ ++ (head)->sqh_last = &(curelm)->field.sqe_next; \ ++ } \ ++} while (/*CONSTCOND*/0) ++ ++#define SIMPLEQ_FOREACH(var, head, field) \ ++ for ((var) = ((head)->sqh_first); \ ++ (var); \ ++ (var) = ((var)->field.sqe_next)) ++ ++/* ++ * Simple queue access methods. ++ */ ++#define SIMPLEQ_EMPTY(head) ((head)->sqh_first == NULL) ++#define SIMPLEQ_FIRST(head) ((head)->sqh_first) ++#define SIMPLEQ_NEXT(elm, field) ((elm)->field.sqe_next) ++ ++ ++/* ++ * Tail queue definitions. ++ */ ++#define _TAILQ_HEAD(name, type, qual) \ ++struct name { \ ++ qual type *tqh_first; /* first element */ \ ++ qual type *qual *tqh_last; /* addr of last next element */ \ ++} ++#define TAILQ_HEAD(name, type) _TAILQ_HEAD(name, struct type,) ++ ++#define TAILQ_HEAD_INITIALIZER(head) \ ++ { NULL, &(head).tqh_first } ++ ++#define _TAILQ_ENTRY(type, qual) \ ++struct { \ ++ qual type *tqe_next; /* next element */ \ ++ qual type *qual *tqe_prev; /* address of previous next element */\ ++} ++#define TAILQ_ENTRY(type) _TAILQ_ENTRY(struct type,) ++ ++/* ++ * Tail queue functions. ++ */ ++#define TAILQ_INIT(head) do { \ ++ (head)->tqh_first = NULL; \ ++ (head)->tqh_last = &(head)->tqh_first; \ ++} while (/*CONSTCOND*/0) ++ ++#define TAILQ_INSERT_HEAD(head, elm, field) do { \ ++ if (((elm)->field.tqe_next = (head)->tqh_first) != NULL) \ ++ (head)->tqh_first->field.tqe_prev = \ ++ &(elm)->field.tqe_next; \ ++ else \ ++ (head)->tqh_last = &(elm)->field.tqe_next; \ ++ (head)->tqh_first = (elm); \ ++ (elm)->field.tqe_prev = &(head)->tqh_first; \ ++} while (/*CONSTCOND*/0) ++ ++#define TAILQ_INSERT_TAIL(head, elm, field) do { \ ++ (elm)->field.tqe_next = NULL; \ ++ (elm)->field.tqe_prev = (head)->tqh_last; \ ++ *(head)->tqh_last = (elm); \ ++ (head)->tqh_last = &(elm)->field.tqe_next; \ ++} while (/*CONSTCOND*/0) ++ ++#define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \ ++ if (((elm)->field.tqe_next = (listelm)->field.tqe_next) != NULL)\ ++ (elm)->field.tqe_next->field.tqe_prev = \ ++ &(elm)->field.tqe_next; \ ++ else \ ++ (head)->tqh_last = &(elm)->field.tqe_next; \ ++ (listelm)->field.tqe_next = (elm); \ ++ (elm)->field.tqe_prev = &(listelm)->field.tqe_next; \ ++} while (/*CONSTCOND*/0) ++ ++#define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \ ++ (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \ ++ (elm)->field.tqe_next = (listelm); \ ++ *(listelm)->field.tqe_prev = (elm); \ ++ (listelm)->field.tqe_prev = &(elm)->field.tqe_next; \ ++} while (/*CONSTCOND*/0) ++ ++#define TAILQ_REMOVE(head, elm, field) do { \ ++ if (((elm)->field.tqe_next) != NULL) \ ++ (elm)->field.tqe_next->field.tqe_prev = \ ++ (elm)->field.tqe_prev; \ ++ else \ ++ (head)->tqh_last = (elm)->field.tqe_prev; \ ++ *(elm)->field.tqe_prev = (elm)->field.tqe_next; \ ++} while (/*CONSTCOND*/0) ++ ++#define TAILQ_FOREACH(var, head, field) \ ++ for ((var) = ((head)->tqh_first); \ ++ (var); \ ++ (var) = ((var)->field.tqe_next)) ++ ++#define TAILQ_FOREACH_REVERSE(var, head, headname, field) \ ++ for ((var) = (*(((struct headname *)((head)->tqh_last))->tqh_last)); \ ++ (var); \ ++ (var) = (*(((struct headname *)((var)->field.tqe_prev))->tqh_last))) ++ ++#define TAILQ_CONCAT(head1, head2, field) do { \ ++ if (!TAILQ_EMPTY(head2)) { \ ++ *(head1)->tqh_last = (head2)->tqh_first; \ ++ (head2)->tqh_first->field.tqe_prev = (head1)->tqh_last; \ ++ (head1)->tqh_last = (head2)->tqh_last; \ ++ TAILQ_INIT((head2)); \ ++ } \ ++} while (/*CONSTCOND*/0) ++ ++/* ++ * Tail queue access methods. ++ */ ++#define TAILQ_EMPTY(head) ((head)->tqh_first == NULL) ++#define TAILQ_FIRST(head) ((head)->tqh_first) ++#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next) ++ ++#define TAILQ_LAST(head, headname) \ ++ (*(((struct headname *)((head)->tqh_last))->tqh_last)) ++#define TAILQ_PREV(elm, headname, field) \ ++ (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last)) ++ ++ ++/* ++ * Circular queue definitions. ++ */ ++#define CIRCLEQ_HEAD(name, type) \ ++struct name { \ ++ struct type *cqh_first; /* first element */ \ ++ struct type *cqh_last; /* last element */ \ ++} ++ ++#define CIRCLEQ_HEAD_INITIALIZER(head) \ ++ { (void *)&head, (void *)&head } ++ ++#define CIRCLEQ_ENTRY(type) \ ++struct { \ ++ struct type *cqe_next; /* next element */ \ ++ struct type *cqe_prev; /* previous element */ \ ++} ++ ++/* ++ * Circular queue functions. ++ */ ++#define CIRCLEQ_INIT(head) do { \ ++ (head)->cqh_first = (void *)(head); \ ++ (head)->cqh_last = (void *)(head); \ ++} while (/*CONSTCOND*/0) ++ ++#define CIRCLEQ_INSERT_AFTER(head, listelm, elm, field) do { \ ++ (elm)->field.cqe_next = (listelm)->field.cqe_next; \ ++ (elm)->field.cqe_prev = (listelm); \ ++ if ((listelm)->field.cqe_next == (void *)(head)) \ ++ (head)->cqh_last = (elm); \ ++ else \ ++ (listelm)->field.cqe_next->field.cqe_prev = (elm); \ ++ (listelm)->field.cqe_next = (elm); \ ++} while (/*CONSTCOND*/0) ++ ++#define CIRCLEQ_INSERT_BEFORE(head, listelm, elm, field) do { \ ++ (elm)->field.cqe_next = (listelm); \ ++ (elm)->field.cqe_prev = (listelm)->field.cqe_prev; \ ++ if ((listelm)->field.cqe_prev == (void *)(head)) \ ++ (head)->cqh_first = (elm); \ ++ else \ ++ (listelm)->field.cqe_prev->field.cqe_next = (elm); \ ++ (listelm)->field.cqe_prev = (elm); \ ++} while (/*CONSTCOND*/0) ++ ++#define CIRCLEQ_INSERT_HEAD(head, elm, field) do { \ ++ (elm)->field.cqe_next = (head)->cqh_first; \ ++ (elm)->field.cqe_prev = (void *)(head); \ ++ if ((head)->cqh_last == (void *)(head)) \ ++ (head)->cqh_last = (elm); \ ++ else \ ++ (head)->cqh_first->field.cqe_prev = (elm); \ ++ (head)->cqh_first = (elm); \ ++} while (/*CONSTCOND*/0) ++ ++#define CIRCLEQ_INSERT_TAIL(head, elm, field) do { \ ++ (elm)->field.cqe_next = (void *)(head); \ ++ (elm)->field.cqe_prev = (head)->cqh_last; \ ++ if ((head)->cqh_first == (void *)(head)) \ ++ (head)->cqh_first = (elm); \ ++ else \ ++ (head)->cqh_last->field.cqe_next = (elm); \ ++ (head)->cqh_last = (elm); \ ++} while (/*CONSTCOND*/0) ++ ++#define CIRCLEQ_REMOVE(head, elm, field) do { \ ++ if ((elm)->field.cqe_next == (void *)(head)) \ ++ (head)->cqh_last = (elm)->field.cqe_prev; \ ++ else \ ++ (elm)->field.cqe_next->field.cqe_prev = \ ++ (elm)->field.cqe_prev; \ ++ if ((elm)->field.cqe_prev == (void *)(head)) \ ++ (head)->cqh_first = (elm)->field.cqe_next; \ ++ else \ ++ (elm)->field.cqe_prev->field.cqe_next = \ ++ (elm)->field.cqe_next; \ ++} while (/*CONSTCOND*/0) ++ ++#define CIRCLEQ_FOREACH(var, head, field) \ ++ for ((var) = ((head)->cqh_first); \ ++ (var) != (const void *)(head); \ ++ (var) = ((var)->field.cqe_next)) ++ ++#define CIRCLEQ_FOREACH_REVERSE(var, head, field) \ ++ for ((var) = ((head)->cqh_last); \ ++ (var) != (const void *)(head); \ ++ (var) = ((var)->field.cqe_prev)) ++ ++/* ++ * Circular queue access methods. ++ */ ++#define CIRCLEQ_EMPTY(head) ((head)->cqh_first == (void *)(head)) ++#define CIRCLEQ_FIRST(head) ((head)->cqh_first) ++#define CIRCLEQ_LAST(head) ((head)->cqh_last) ++#define CIRCLEQ_NEXT(elm, field) ((elm)->field.cqe_next) ++#define CIRCLEQ_PREV(elm, field) ((elm)->field.cqe_prev) ++ ++#define CIRCLEQ_LOOP_NEXT(head, elm, field) \ ++ (((elm)->field.cqe_next == (void *)(head)) \ ++ ? ((head)->cqh_first) \ ++ : (elm->field.cqe_next)) ++#define CIRCLEQ_LOOP_PREV(head, elm, field) \ ++ (((elm)->field.cqe_prev == (void *)(head)) \ ++ ? ((head)->cqh_last) \ ++ : (elm->field.cqe_prev)) ++ ++#endif /* sys/queue.h */