Skip to content
项目
群组
代码片段
帮助
正在加载...
登录
切换导航
F
freeswitch
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
分枝图
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
分枝图
统计图
创建新议题
作业
提交
议题看板
打开侧边栏
张华
freeswitch
Commits
599a2005
提交
599a2005
authored
11月 26, 2010
作者:
Giovanni Maruzzelli
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
skypopen: adding osscuse directory, lot of news to come in some days ;)
上级
c7aefe93
显示空白字符变更
内嵌
并排
正在显示
12 个修改的文件
包含
4977 行增加
和
0 行删除
+4977
-0
98-osscuse.rules
src/mod/endpoints/mod_skypopen/osscuse/98-osscuse.rules
+7
-0
LICENSE
src/mod/endpoints/mod_skypopen/osscuse/LICENSE
+339
-0
Makefile
src/mod/endpoints/mod_skypopen/osscuse/Makefile
+69
-0
README
src/mod/endpoints/mod_skypopen/osscuse/README
+119
-0
ossp-alsap.c
src/mod/endpoints/mod_skypopen/osscuse/ossp-alsap.c
+613
-0
ossp-slave.c
src/mod/endpoints/mod_skypopen/osscuse/ossp-slave.c
+250
-0
ossp-slave.h
src/mod/endpoints/mod_skypopen/osscuse/ossp-slave.h
+28
-0
ossp-util.c
src/mod/endpoints/mod_skypopen/osscuse/ossp-util.c
+369
-0
ossp-util.h
src/mod/endpoints/mod_skypopen/osscuse/ossp-util.h
+609
-0
ossp.c
src/mod/endpoints/mod_skypopen/osscuse/ossp.c
+83
-0
ossp.h
src/mod/endpoints/mod_skypopen/osscuse/ossp.h
+117
-0
osspd.c
src/mod/endpoints/mod_skypopen/osscuse/osspd.c
+2374
-0
没有找到文件。
src/mod/endpoints/mod_skypopen/osscuse/98-osscuse.rules
0 → 100644
浏览文件 @
599a2005
# Since these devices are not part of 'sound' subsystem the group is forced
# to audio by name
# /dev/cuse can stay mode 0660 root:root since osspd is run as root
# and drops privileges to user level when opened by user
KERNEL=="dsp", GROUP="audio"
KERNEL=="mixer", GROUP="audio"
KERNEL=="adsp", GROUP="audio"
src/mod/endpoints/mod_skypopen/osscuse/LICENSE
0 → 100644
浏览文件 @
599a2005
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
src/mod/endpoints/mod_skypopen/osscuse/Makefile
0 → 100644
浏览文件 @
599a2005
# These can be overridden if needed
# DESTDIR is completely respected
CC
:=
gcc
AR
:=
ar
CFLAGS
:=
-Wall
$(CFLAGS)
XLDFLAGS
:=
$(LDFLAGS)
LDFLAGS
:=
-L
.
-lossp
$(LDFLAGS)
prefix
:=
/usr/local
DESTDIR
:=
UDEVDIR
:=
/etc/udev/rules.d
ifeq
"$(origin OSSPD_CFLAGS)"
"undefined"
OSSPD_CFLAGS
:=
$(
shell
pkg-config
--cflags
fuse
)
endif
ifeq
"$(origin OSSPD_LDFLAGS)"
"undefined"
OSSPD_LDFLAGS
:=
$(
shell
pkg-config
--libs
fuse
)
endif
ifeq
"$(origin OSSP_PADSP_CFLAGS)"
"undefined"
OSSP_PADSP_CFLAGS
:=
$(
shell
pkg-config
--cflags
libpulse
)
endif
ifeq
"$(origin OSSP_PADSP_LDFLAGS)"
"undefined"
OSSP_PADSP_LDFLAGS
:=
$(
shell
pkg-config
--libs
libpulse
)
endif
ifeq
"$(origin OSSP_ALSAP_CFLAGS)"
"undefined"
OSSP_ALSAP_CFLAGS
:=
$(
shell
pkg-config
--libs
alsa
)
endif
ifeq
"$(origin OSSP_ALSAP_LDFLAGS)"
"undefined"
OSSP_ALSAP_LDFLAGS
:=
$(
shell
pkg-config
--libs
alsa
)
endif
headers
:=
ossp.h ossp-util.h ossp-slave.h
#all: osspd ossp-padsp ossp-alsap
all
:
osspd ossp-alsap
install
:
mkdir
-p
$(DESTDIR)$(prefix)
/sbin
install
-m755
osspd ossp-padsp ossp-alsap
$(DESTDIR)$(prefix)
/sbin
mkdir
-p
$(DESTDIR)$(UDEVDIR)
install
-m644
98-osscuse.rules
$(DESTDIR)$(UDEVDIR)
libossp.a
:
ossp.c ossp.h ossp-util.c ossp-util.h ossp-slave.c ossp-slave.h
$(CC)
$(CFLAGS)
-c
-o
ossp.o ossp.c
$(CC)
$(CFLAGS)
-c
-o
ossp-util.o ossp-util.c
$(CC)
$(CFLAGS)
-c
-o
ossp-slave.o ossp-slave.c
$(AR)
rc
$@
ossp.o ossp-util.o ossp-slave.o
osspd
:
osspd.c libossp.a $(headers)
$(CC)
$(CFLAGS)
$(OSSPD_CFLAGS)
-o
$@
$<
$(OSSPD_LDFLAGS)
$(LDFLAGS)
ossp-padsp
:
ossp-padsp.c libossp.a $(headers)
$(CC)
$(CFLAGS)
$(OSSP_PADSP_CFLAGS)
-o
$@
$<
$(OSSP_PADSP_LDFLAGS)
$(LDFLAGS)
ossp-alsap
:
ossp-alsap.c libossp.a $(headers)
$(CC)
$(CFLAGS)
$(OSSP_ALSAP_CFLAGS)
-o
$@
$<
$(OSSP_ALSAP_LDFLAGS)
$(LDFLAGS)
osstest
:
osstest.c
$(CC)
$(CFLAGS)
-o
$@
$<
$(XLDFLAGS)
test
:
osstest
@
./osstest
clean
:
rm
-f
*
.o
*
.a osspd ossp-padsp ossp-alsap osstest
src/mod/endpoints/mod_skypopen/osscuse/README
0 → 100644
浏览文件 @
599a2005
OSS Proxy - emulate OSS device using CUSE
Copyright (C) 2008-2009 SUSE Linux Products GmbH
Copyright (C) 2008-2009 Tejun Heo <tj@kernel.org>
1. What is it?
--------------
Well, first, OSS refers to Open Sound System. If it still doesn't
ring a bell, think /dev/dsp, /dev/adsp and /dev/mixer.
Currently, Linux supports two audio programming interface - ALSA and
OSS. The latter one is deprecated and has been that way for a long
time but there still are applications which still use them including
UML (usermode Linux) host sound support.
ALSA contains OSS emulation but sadly the emulation is behind
multiplexing layer (which is in userland) which means that if your
sound card doesn't support multiple audio streams, only either one of
ALSA or OSS interface would be usable at any given moment.
There have been also attempts to emulate OSS in userland using dynamic
library preloading - aoss and more recently padsp. This works for
many applications but it's just not easy to emulate everything using
the technique. Things like polling, signals, forking, privilege
changes make it very difficult to emulate things reliably.
OSS Proxy uses CUSE (extension of FUSE allowing character devices to
be implemented in userspace) to implement OSS interface - /dev/dsp,
/dev/adsp and /dev/mixer. From the POV of the applications, these
devices are proper character devices and behave exactly the same way
so it can be made quite versatile.
2. Hmmm... So, how does the whole thing work?
---------------------------------------------
The OSS Proxy daemon - osspd - should be started first. Note that
osspd will fail to start if sound device number regions are already
occupied. You'll need to turn off OSS or its emulation[1].
On startup, osspd creates /dev/dsp, /dev/adsp and /dev/mixer using
CUSE. When an application access one of the devices, all IOs are
redirected to osspd via CUSE. Upon receiving a new DSP open request,
osspd creates a slave process which drops the root privilege and
assumes the opening process's credentials. After handshaking, osspd
forwards all relevant IOs to the slave which is responsible for
actually playing the sound.
Currently there's only one slave implemented - ossp-padsp, which as
the name suggests forwards (again) the sound to pulseaudio. To sum
up, the whole pipe looks like the following.
App <-> /dev/dsp <-> CUSE <-> osspd <-> ossp-padsp <-> pulseaudio
Which is a lot of forwarding, but on modern machines, it won't be too
noticeable.
3. What works?
--------------
Well, MIDI part isn't implemented and I doubt it will be in any near
future but except that everything should work. Playing, recording,
5.1ch, A-V syncing, all should work. If not, it's a bug, so please
report.
The mixer behaves a bit differently tho. In the original OSS,
/dev/mixer is the hardware mixer, so adjusting volumes there affects
all audio streams. When using ossp, each process group gets its own
mixer and the mixer always contains only two knobs - PCM and IGAIN.
Combined with per-stream volume control of pulseaudio, this scheme
works quite well for applications with embedded volume control
although it makes standalone OSS mixer programs virtually useless[2].
4. How do I use it?
-------------------
First you need CUSE support in kernel which might land on 2.6.28 with
sufficient luck[3] and then you also need libfuse which supports
CUSE[4]. Once you have both, it should be easy. First build it by
running `make'. You can set OSSPD_CFLAGS, OSSPD_LDFLAGS,
OSSP_PADSP_CFLAGS and OSSP_PADSP_LDFLAGS if you have stuff at
non-default locations.
After build completes, there will be two executables - `osspd' and
`ossp-padsp'. Just copy them to where other system executables live.
Specific location doesn't matter as long as both files end up in the
same directory.
Execute `osspd'. It will create the device files and you're all set.
`osspd' uses syslog with LOG_DAEMON facility, so if something doesn't
work take a look at what osspd complains about.
[1] As of this writing, turning on any sound support makes the
soundcore module claim OSS device regions. Patch to make it claim
OSS device regions only when OSS support or emulation is enabled
is scheduled for 2.6.28. Even with the patch, soundcore will
claim OSS device regions if OSS support or ALSA OSS emulation is
enabled. Make sure they're turned off.
[2] If you have a strong reason to use standalone OSS mixer program,
you can play some shell tricks to put it into the same process
group as the target audio application. e.g. To use aumix with
mpg123 - `(mpg123 asdf.mp3 > /dev/null 2>&1 & aumix)', but
seriously, just use PA or ALSA one.
[3] For the time being, here's the git tree with all the necessary
changes. This tree is base on top of 2.6.27-rc3.
http://git.kernel.org/?p=linux/kernel/git/tj/misc.git;a=shortlog;h=cuse
git://git.kernel.org/pub/scm/linux/kernel/git/tj/misc.git cuse
[4] And libfuse with the modifications can be found at...
http://userweb.kernel.org/~tj/ossp/fuse-cuse.tar.gz
src/mod/endpoints/mod_skypopen/osscuse/ossp-alsap.c
0 → 100644
浏览文件 @
599a2005
/*
* ossp-alsap - ossp DSP slave which forwards to alsa
*
* Copyright (C) 2009 Maarten Lankhorst <m.b.lankhorst@gmail.com>
*
* This file is released under the GPLv2.
*
* Why an alsa plugin as well? Just to show how much
* the alsa userspace api sucks ;-)
*/
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <libgen.h>
#include <limits.h>
#include <poll.h>
#include <pthread.h>
#include <pwd.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <alsa/asoundlib.h>
#include <sys/soundcard.h>
#include "ossp-slave.h"
enum
{
AFMT_FLOAT
=
0x00004000
,
AFMT_S32_LE
=
0x00001000
,
AFMT_S32_BE
=
0x00002000
,
};
static
size_t
page_size
;
/* alsa structures */
static
snd_pcm_t
*
pcm
[
2
];
static
snd_pcm_hw_params_t
*
hw_params
;
static
snd_pcm_sw_params_t
*
sw_params
;
static
int
block
;
static
unsigned
int
byte_counter
[
2
];
static
snd_pcm_uframes_t
mmap_pos
[
2
];
static
int
stream_corked
[
2
];
static
int
stream_notify
;
static
struct
format
{
snd_pcm_format_t
format
;
snd_pcm_sframes_t
rate
;
int
channels
;
}
hw_format
=
{
SND_PCM_FORMAT_U8
,
8000
,
1
};
#if 0
/* future mmap stuff */
static size_t mmap_raw_size, mmap_size;
static int mmap_fd[2] = { -1, -1 };
static void *mmap_map[2];
static uint64_t mmap_idx[2]; /* mmap pointer */
static uint64_t mmap_last_idx[2]; /* last idx for get_ptr */
static struct ring_buf mmap_stg[2]; /* staging ring buffer */
static size_t mmap_lead[2]; /* lead bytes */
static int mmap_sync[2]; /* sync with backend stream */
#endif
static
snd_pcm_format_t
fmt_oss_to_alsa
(
int
fmt
)
{
switch
(
fmt
)
{
case
AFMT_U8
:
return
SND_PCM_FORMAT_U8
;
case
AFMT_A_LAW
:
return
SND_PCM_FORMAT_A_LAW
;
case
AFMT_MU_LAW
:
return
SND_PCM_FORMAT_MU_LAW
;
case
AFMT_S16_LE
:
return
SND_PCM_FORMAT_S16_LE
;
case
AFMT_S16_BE
:
return
SND_PCM_FORMAT_S16_BE
;
case
AFMT_FLOAT
:
return
SND_PCM_FORMAT_FLOAT
;
case
AFMT_S32_LE
:
return
SND_PCM_FORMAT_S32_LE
;
case
AFMT_S32_BE
:
return
SND_PCM_FORMAT_S32_BE
;
default:
return
SND_PCM_FORMAT_U8
;
}
}
static
int
fmt_alsa_to_oss
(
snd_pcm_format_t
fmt
)
{
switch
(
fmt
)
{
case
SND_PCM_FORMAT_U8
:
return
AFMT_U8
;
case
SND_PCM_FORMAT_A_LAW
:
return
AFMT_A_LAW
;
case
SND_PCM_FORMAT_MU_LAW
:
return
AFMT_MU_LAW
;
case
SND_PCM_FORMAT_S16_LE
:
return
AFMT_S16_LE
;
case
SND_PCM_FORMAT_S16_BE
:
return
AFMT_S16_BE
;
case
SND_PCM_FORMAT_FLOAT
:
return
AFMT_FLOAT
;
case
SND_PCM_FORMAT_S32_LE
:
return
AFMT_S32_LE
;
case
SND_PCM_FORMAT_S32_BE
:
return
AFMT_S32_BE
;
default:
return
AFMT_U8
;
}
}
static
void
flush_streams
(
int
drain
)
{
/* FIXME: snd_pcm_drain appears to be able to deadlock,
* always drop or check state? */
if
(
drain
)
{
if
(
pcm
[
PLAY
])
snd_pcm_drain
(
pcm
[
PLAY
]);
if
(
pcm
[
REC
])
snd_pcm_drain
(
pcm
[
REC
]);
}
else
{
if
(
pcm
[
PLAY
])
snd_pcm_drop
(
pcm
[
PLAY
]);
if
(
pcm
[
REC
])
snd_pcm_drop
(
pcm
[
REC
]);
}
/* XXX: Really needed? */
#if 0
if (pcm[PLAY]) {
snd_pcm_close(pcm[PLAY]);
snd_pcm_open(&pcm[PLAY], "default",
SND_PCM_STREAM_PLAYBACK, block);
}
if (pcm[REC]) {
snd_pcm_close(pcm[REC]);
snd_pcm_open(&pcm[REC], "default",
SND_PCM_STREAM_CAPTURE, block);
}
#endif
}
static
void
kill_streams
(
void
)
{
flush_streams
(
0
);
}
static
int
trigger_streams
(
int
play
,
int
rec
)
{
int
ret
=
0
;
if
(
pcm
[
PLAY
]
&&
play
>=
0
)
{
ret
=
snd_pcm_sw_params_set_start_threshold
(
pcm
[
PLAY
],
sw_params
,
play
?
1
:
-
1
);
if
(
ret
>=
0
)
snd_pcm_sw_params
(
pcm
[
PLAY
],
sw_params
);
}
if
(
ret
>=
0
&&
pcm
[
REC
]
&&
rec
>=
0
)
{
ret
=
snd_pcm_sw_params_set_start_threshold
(
pcm
[
REC
],
sw_params
,
rec
?
1
:
-
1
);
if
(
ret
>=
0
)
snd_pcm_sw_params
(
pcm
[
REC
],
sw_params
);
}
return
ret
;
}
static
ssize_t
alsap_mixer
(
enum
ossp_opcode
opcode
,
void
*
carg
,
void
*
din
,
size_t
din_sz
,
void
*
rarg
,
void
*
dout
,
size_t
*
dout_szp
,
int
tfd
)
{
return
-
EBUSY
;
}
static
int
set_hw_params
(
snd_pcm_t
*
pcm
)
{
int
ret
;
unsigned
rate
;
ret
=
snd_pcm_hw_params_any
(
pcm
,
hw_params
);
if
(
ret
>=
0
)
ret
=
snd_pcm_hw_params_set_access
(
pcm
,
hw_params
,
SND_PCM_ACCESS_RW_INTERLEAVED
);
rate
=
hw_format
.
rate
;
if
(
ret
>=
0
)
ret
=
snd_pcm_hw_params_set_rate_minmax
(
pcm
,
hw_params
,
&
rate
,
NULL
,
&
rate
,
NULL
);
if
(
ret
>=
0
)
ret
=
snd_pcm_hw_params_set_format
(
pcm
,
hw_params
,
hw_format
.
format
);
if
(
ret
>=
0
)
ret
=
snd_pcm_hw_params_set_channels
(
pcm
,
hw_params
,
hw_format
.
channels
);
if
(
ret
>=
0
)
ret
=
snd_pcm_hw_params
(
pcm
,
hw_params
);
if
(
ret
>=
0
)
ret
=
snd_pcm_sw_params_current
(
pcm
,
sw_params
);
if
(
ret
>=
0
)
ret
=
snd_pcm_sw_params
(
pcm
,
sw_params
);
return
ret
;
}
static
ssize_t
alsap_open
(
enum
ossp_opcode
opcode
,
void
*
carg
,
void
*
din
,
size_t
din_sz
,
void
*
rarg
,
void
*
dout
,
size_t
*
dout_szp
,
int
tfd
)
{
struct
ossp_dsp_open_arg
*
arg
=
carg
;
int
ret
;
block
=
arg
->
flags
&
O_NONBLOCK
?
SND_PCM_NONBLOCK
:
0
;
int
access
;
// block |= SND_PCM_ASYNC;
/* Woop dee dooo.. I love handling things in SIGIO (PAIN!!)
* Probably needed for MMAP
*/
if
(
!
hw_params
)
ret
=
snd_pcm_hw_params_malloc
(
&
hw_params
);
if
(
ret
<
0
)
return
ret
;
if
(
!
sw_params
)
ret
=
snd_pcm_sw_params_malloc
(
&
sw_params
);
if
(
ret
<
0
)
return
ret
;
if
(
pcm
[
PLAY
])
snd_pcm_close
(
pcm
[
PLAY
]);
if
(
pcm
[
REC
])
snd_pcm_close
(
pcm
[
REC
]);
pcm
[
REC
]
=
pcm
[
PLAY
]
=
NULL
;
access
=
arg
->
flags
&
O_ACCMODE
;
if
(
access
==
O_WRONLY
||
access
==
O_RDWR
)
{
ret
=
snd_pcm_open
(
&
pcm
[
PLAY
],
"default"
,
SND_PCM_STREAM_PLAYBACK
,
block
);
if
(
ret
>=
0
)
ret
=
set_hw_params
(
pcm
[
PLAY
]);
}
if
(
ret
>=
0
&&
(
access
==
O_RDONLY
||
access
==
O_RDWR
))
{
ret
=
snd_pcm_open
(
&
pcm
[
REC
],
"default"
,
SND_PCM_STREAM_CAPTURE
,
block
);
if
(
ret
>=
0
)
ret
=
set_hw_params
(
pcm
[
REC
]);
}
if
(
ret
<
0
)
{
if
(
pcm
[
PLAY
])
snd_pcm_close
(
pcm
[
PLAY
]);
if
(
pcm
[
REC
])
snd_pcm_close
(
pcm
[
REC
]);
pcm
[
REC
]
=
pcm
[
PLAY
]
=
NULL
;
return
ret
;
}
return
0
;
}
#define GIOVANNI
#ifdef GIOVANNI
#define GIOVA_SLEEP 40000
#define GIOVA_BLK 3840
static
ssize_t
alsap_write
(
enum
ossp_opcode
opcode
,
void
*
carg
,
void
*
din
,
size_t
din_sz
,
void
*
rarg
,
void
*
dout
,
size_t
*
dout_szp
,
int
tfd
)
{
usleep
((
GIOVA_SLEEP
/
GIOVA_BLK
)
*
din_sz
);
return
din_sz
;
}
static
ssize_t
alsap_read
(
enum
ossp_opcode
opcode
,
void
*
carg
,
void
*
din
,
size_t
din_sz
,
void
*
rarg
,
void
*
dout
,
size_t
*
dout_szp
,
int
tfd
)
{
usleep
((
GIOVA_SLEEP
/
GIOVA_BLK
)
*
*
dout_szp
);
return
*
dout_szp
;
}
#else// GIOVANNI
static
ssize_t
alsap_write
(
enum
ossp_opcode
opcode
,
void
*
carg
,
void
*
din
,
size_t
din_sz
,
void
*
rarg
,
void
*
dout
,
size_t
*
dout_szp
,
int
tfd
)
{
// struct ossp_dsp_rw_arg *arg = carg;
int
ret
,
insize
;
insize
=
snd_pcm_bytes_to_frames
(
pcm
[
PLAY
],
din_sz
);
if
(
snd_pcm_state
(
pcm
[
PLAY
])
==
SND_PCM_STATE_SETUP
)
snd_pcm_prepare
(
pcm
[
PLAY
]);
// snd_pcm_start(pcm[PLAY]);
ret
=
snd_pcm_writei
(
pcm
[
PLAY
],
din
,
insize
);
if
(
ret
<
0
)
ret
=
snd_pcm_recover
(
pcm
[
PLAY
],
ret
,
1
);
if
(
ret
>=
0
)
return
snd_pcm_frames_to_bytes
(
pcm
[
PLAY
],
ret
);
else
return
ret
;
}
static
ssize_t
alsap_read
(
enum
ossp_opcode
opcode
,
void
*
carg
,
void
*
din
,
size_t
din_sz
,
void
*
rarg
,
void
*
dout
,
size_t
*
dout_szp
,
int
tfd
)
{
// struct ossp_dsp_rw_arg *arg = carg;
int
ret
,
outsize
;
outsize
=
snd_pcm_bytes_to_frames
(
pcm
[
REC
],
*
dout_szp
);
if
(
snd_pcm_state
(
pcm
[
REC
])
==
SND_PCM_STATE_SETUP
)
snd_pcm_prepare
(
pcm
[
REC
]);
ret
=
snd_pcm_readi
(
pcm
[
REC
],
dout
,
outsize
);
if
(
ret
<
0
)
ret
=
snd_pcm_recover
(
pcm
[
REC
],
ret
,
1
);
if
(
ret
>=
0
)
*
dout_szp
=
ret
=
snd_pcm_frames_to_bytes
(
pcm
[
REC
],
ret
);
else
*
dout_szp
=
0
;
return
ret
;
}
#endif// GIOVANNI
static
ssize_t
alsap_poll
(
enum
ossp_opcode
opcode
,
void
*
carg
,
void
*
din
,
size_t
din_sz
,
void
*
rarg
,
void
*
dout
,
size_t
*
dout_szp
,
int
tfd
)
{
unsigned
revents
=
0
;
stream_notify
|=
*
(
int
*
)
carg
;
if
(
pcm
[
PLAY
])
revents
|=
POLLOUT
;
if
(
pcm
[
REC
])
revents
|=
POLLIN
;
*
(
unsigned
*
)
rarg
=
revents
;
return
0
;
}
static
ssize_t
alsap_flush
(
enum
ossp_opcode
opcode
,
void
*
carg
,
void
*
din
,
size_t
din_sz
,
void
*
rarg
,
void
*
dout
,
size_t
*
dout_szp
,
int
tfd
)
{
flush_streams
(
opcode
==
OSSP_DSP_SYNC
);
return
0
;
}
static
ssize_t
alsap_post
(
enum
ossp_opcode
opcode
,
void
*
carg
,
void
*
din
,
size_t
din_sz
,
void
*
rarg
,
void
*
dout
,
size_t
*
dout_szp
,
int
tfd
)
{
int
ret
;
ret
=
trigger_streams
(
1
,
1
);
if
(
ret
>=
0
&&
pcm
[
PLAY
])
ret
=
snd_pcm_start
(
pcm
[
PLAY
]);
if
(
pcm
[
REC
])
ret
=
snd_pcm_start
(
pcm
[
REC
]);
return
ret
;
}
static
ssize_t
alsap_get_param
(
enum
ossp_opcode
opcode
,
void
*
carg
,
void
*
din
,
size_t
din_sz
,
void
*
rarg
,
void
*
dout
,
size_t
*
dout_szp
,
int
tfd
)
{
int
v
=
0
;
switch
(
opcode
)
{
case
OSSP_DSP_GET_RATE
:
return
hw_format
.
rate
;
case
OSSP_DSP_GET_CHANNELS
:
return
hw_format
.
channels
;
case
OSSP_DSP_GET_FORMAT
:
{
v
=
fmt_alsa_to_oss
(
hw_format
.
format
);
break
;
}
case
OSSP_DSP_GET_BLKSIZE
:
{
snd_pcm_uframes_t
psize
;
snd_pcm_hw_params_get_period_size
(
hw_params
,
&
psize
,
NULL
);
v
=
psize
;
break
;
}
case
OSSP_DSP_GET_FORMATS
:
v
=
AFMT_U8
|
AFMT_A_LAW
|
AFMT_MU_LAW
|
AFMT_S16_LE
|
AFMT_S16_BE
|
AFMT_FLOAT
|
AFMT_S32_LE
|
AFMT_S32_BE
;
break
;
case
OSSP_DSP_GET_TRIGGER
:
if
(
!
stream_corked
[
PLAY
])
v
|=
PCM_ENABLE_OUTPUT
;
if
(
!
stream_corked
[
REC
])
v
|=
PCM_ENABLE_INPUT
;
break
;
default:
assert
(
0
);
}
*
(
int
*
)
rarg
=
v
;
return
0
;
}
static
ssize_t
alsap_set_param
(
enum
ossp_opcode
opcode
,
void
*
carg
,
void
*
din
,
size_t
din_sz
,
void
*
rarg
,
void
*
dout
,
size_t
*
dout_szp
,
int
tfd
)
{
int
v
=
*
(
int
*
)
carg
;
int
ret
=
0
;
/* kill the streams before changing parameters */
kill_streams
();
switch
(
opcode
)
{
case
OSSP_DSP_SET_RATE
:
{
hw_format
.
rate
=
v
;
break
;
}
case
OSSP_DSP_SET_CHANNELS
:
{
hw_format
.
channels
=
v
;
break
;
}
case
OSSP_DSP_SET_FORMAT
:
{
snd_pcm_format_t
format
=
fmt_oss_to_alsa
(
v
);
hw_format
.
format
=
format
;
break
;
}
case
OSSP_DSP_SET_SUBDIVISION
:
if
(
!
v
)
v
=
1
;
#if 0
if (!v) {
v = user_subdivision ?: 1;
break;
}
user_frag_size = 0;
user_subdivision = v;
break;
case OSSP_DSP_SET_FRAGMENT:
user_subdivision = 0;
user_frag_size = 1 << (v & 0xffff);
user_max_frags = (v >> 16) & 0xffff;
if (user_frag_size < 4)
user_frag_size = 4;
if (user_max_frags < 2)
user_max_frags = 2;
#else
case
OSSP_DSP_SET_FRAGMENT
:
#endif
break
;
default:
assert
(
0
);
}
if
(
pcm
[
PLAY
])
ret
=
set_hw_params
(
pcm
[
PLAY
]);
if
(
ret
>=
0
&&
pcm
[
REC
])
ret
=
set_hw_params
(
pcm
[
REC
]);
if
(
rarg
)
*
(
int
*
)
rarg
=
v
;
return
0
;
}
static
ssize_t
alsap_set_trigger
(
enum
ossp_opcode
opcode
,
void
*
carg
,
void
*
din
,
size_t
din_sz
,
void
*
rarg
,
void
*
dout
,
size_t
*
dout_szp
,
int
fd
)
{
int
enable
=
*
(
int
*
)
carg
;
stream_corked
[
PLAY
]
=
!!
(
enable
&
PCM_ENABLE_OUTPUT
);
stream_corked
[
REC
]
=
!!
(
enable
&
PCM_ENABLE_INPUT
);
return
trigger_streams
(
enable
&
PCM_ENABLE_OUTPUT
,
enable
&
PCM_ENABLE_INPUT
);
}
static
ssize_t
alsap_get_space
(
enum
ossp_opcode
opcode
,
void
*
carg
,
void
*
din
,
size_t
din_sz
,
void
*
rarg
,
void
*
dout
,
size_t
*
dout_szp
,
int
tfd
)
{
int
dir
=
(
opcode
==
OSSP_DSP_GET_OSPACE
)
?
PLAY
:
REC
;
int
underrun
=
0
;
struct
audio_buf_info
info
=
{
};
unsigned
long
bufsize
;
snd_pcm_uframes_t
avail
,
fragsize
;
snd_pcm_state_t
state
;
if
(
!
pcm
[
dir
])
return
-
EINVAL
;
state
=
snd_pcm_state
(
pcm
[
dir
]);
if
(
state
==
SND_PCM_STATE_XRUN
)
{
snd_pcm_recover
(
pcm
[
dir
],
-
EPIPE
,
0
);
underrun
=
1
;
}
else
if
(
state
==
SND_PCM_STATE_SUSPENDED
)
{
snd_pcm_recover
(
pcm
[
dir
],
-
ESTRPIPE
,
0
);
underrun
=
1
;
}
snd_pcm_hw_params_current
(
pcm
[
dir
],
hw_params
);
snd_pcm_hw_params_get_period_size
(
hw_params
,
&
fragsize
,
NULL
);
snd_pcm_hw_params_get_buffer_size
(
hw_params
,
&
bufsize
);
info
.
fragsize
=
snd_pcm_frames_to_bytes
(
pcm
[
dir
],
fragsize
);
info
.
fragstotal
=
bufsize
/
fragsize
;
if
(
!
underrun
)
{
avail
=
snd_pcm_avail_update
(
pcm
[
dir
]);
info
.
fragments
=
avail
/
fragsize
;
}
else
info
.
fragments
=
info
.
fragstotal
;
info
.
bytes
=
info
.
fragsize
*
info
.
fragments
;
*
(
struct
audio_buf_info
*
)
rarg
=
info
;
return
0
;
}
static
ssize_t
alsap_get_ptr
(
enum
ossp_opcode
opcode
,
void
*
carg
,
void
*
din
,
size_t
din_sz
,
void
*
rarg
,
void
*
dout
,
size_t
*
dout_szp
,
int
tfd
)
{
int
dir
=
(
opcode
==
OSSP_DSP_GET_OPTR
)
?
PLAY
:
REC
;
struct
count_info
info
=
{
};
if
(
!
pcm
[
dir
])
return
-
EIO
;
snd_pcm_hw_params_current
(
pcm
[
dir
],
hw_params
);
info
.
bytes
=
byte_counter
[
dir
];
snd_pcm_hw_params_get_periods
(
hw_params
,
(
unsigned
int
*
)
&
info
.
blocks
,
NULL
);
info
.
ptr
=
mmap_pos
[
dir
];
*
(
struct
count_info
*
)
rarg
=
info
;
return
0
;
}
static
ssize_t
alsap_get_odelay
(
enum
ossp_opcode
opcode
,
void
*
carg
,
void
*
din
,
size_t
din_sz
,
void
*
rarg
,
void
*
dout
,
size_t
*
dout_szp
,
int
fd
)
{
snd_pcm_sframes_t
delay
;
if
(
!
pcm
[
PLAY
])
return
-
EIO
;
if
(
snd_pcm_delay
(
pcm
[
PLAY
],
&
delay
)
<
0
)
return
-
EIO
;
*
(
int
*
)
rarg
=
snd_pcm_frames_to_bytes
(
pcm
[
PLAY
],
delay
);
return
0
;
}
static
ossp_action_fn_t
action_fn_tbl
[
OSSP_NR_OPCODES
]
=
{
[
OSSP_MIXER
]
=
alsap_mixer
,
[
OSSP_DSP_OPEN
]
=
alsap_open
,
[
OSSP_DSP_READ
]
=
alsap_read
,
[
OSSP_DSP_WRITE
]
=
alsap_write
,
[
OSSP_DSP_POLL
]
=
alsap_poll
,
#if 0
[OSSP_DSP_MMAP] = alsap_mmap,
[OSSP_DSP_MUNMAP] = alsap_munmap,
#endif
[
OSSP_DSP_RESET
]
=
alsap_flush
,
[
OSSP_DSP_SYNC
]
=
alsap_flush
,
[
OSSP_DSP_POST
]
=
alsap_post
,
[
OSSP_DSP_GET_RATE
]
=
alsap_get_param
,
[
OSSP_DSP_GET_CHANNELS
]
=
alsap_get_param
,
[
OSSP_DSP_GET_FORMAT
]
=
alsap_get_param
,
[
OSSP_DSP_GET_BLKSIZE
]
=
alsap_get_param
,
[
OSSP_DSP_GET_FORMATS
]
=
alsap_get_param
,
[
OSSP_DSP_SET_RATE
]
=
alsap_set_param
,
[
OSSP_DSP_SET_CHANNELS
]
=
alsap_set_param
,
[
OSSP_DSP_SET_FORMAT
]
=
alsap_set_param
,
[
OSSP_DSP_SET_SUBDIVISION
]
=
alsap_set_param
,
[
OSSP_DSP_SET_FRAGMENT
]
=
alsap_set_param
,
[
OSSP_DSP_GET_TRIGGER
]
=
alsap_get_param
,
[
OSSP_DSP_SET_TRIGGER
]
=
alsap_set_trigger
,
[
OSSP_DSP_GET_OSPACE
]
=
alsap_get_space
,
[
OSSP_DSP_GET_ISPACE
]
=
alsap_get_space
,
[
OSSP_DSP_GET_OPTR
]
=
alsap_get_ptr
,
[
OSSP_DSP_GET_IPTR
]
=
alsap_get_ptr
,
[
OSSP_DSP_GET_ODELAY
]
=
alsap_get_odelay
,
};
static
int
action_pre
(
void
)
{
return
0
;
}
static
void
action_post
(
void
)
{
}
int
main
(
int
argc
,
char
**
argv
)
{
int
rc
;
ossp_slave_init
(
argc
,
argv
);
page_size
=
sysconf
(
_SC_PAGE_SIZE
);
/* Okay, now we're open for business */
rc
=
0
;
do
{
rc
=
ossp_slave_process_command
(
ossp_cmd_fd
,
action_fn_tbl
,
action_pre
,
action_post
);
}
while
(
rc
>
0
);
return
rc
?
1
:
0
;
}
src/mod/endpoints/mod_skypopen/osscuse/ossp-slave.c
0 → 100644
浏览文件 @
599a2005
/*
* ossp-slave - OSS Proxy: Common codes for slaves
*
* Copyright (C) 2008-2010 SUSE Linux Products GmbH
* Copyright (C) 2008-2010 Tejun Heo <tj@kernel.org>
*
* This file is released under the GPLv2.
*/
#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pwd.h>
#include <signal.h>
#include "ossp-slave.h"
static
const
char
*
usage
=
"usage: ossp-SLAVE [options]
\n
"
"
\n
"
"proxies commands from osspd to pulseaudio
\n
"
"
\n
"
"options:
\n
"
" -u UID uid to use
\n
"
" -g GID gid to use
\n
"
" -c CMD_FD fd to receive commands from osspd
\n
"
" -n NOTIFY_FD fd to send async notifications to osspd
\n
"
" -m MMAP_FD fd to use for mmap
\n
"
" -o MMAP_OFFSET mmap offset
\n
"
" -s MMAP_SIZE mmap size
\n
"
" -l LOG_LEVEL set log level
\n
"
" -t enable log timestamps
\n
"
;
char
ossp_user_name
[
OSSP_USER_NAME_LEN
];
int
ossp_cmd_fd
=
-
1
,
ossp_notify_fd
=
-
1
;
void
*
ossp_mmap_addr
[
2
];
void
ossp_slave_init
(
int
argc
,
char
**
argv
)
{
int
have_uid
=
0
,
have_gid
=
0
;
uid_t
uid
;
gid_t
gid
;
int
mmap_fd
=
-
1
;
off_t
mmap_off
=
0
;
size_t
mmap_size
=
0
;
int
opt
;
struct
passwd
*
pw
,
pw_buf
;
struct
sigaction
sa
;
char
pw_sbuf
[
sysconf
(
_SC_GETPW_R_SIZE_MAX
)];
while
((
opt
=
getopt
(
argc
,
argv
,
"u:g:c:n:m:o:s:l:t"
))
!=
-
1
)
{
switch
(
opt
)
{
case
'u'
:
have_uid
=
1
;
uid
=
strtol
(
optarg
,
NULL
,
0
);
break
;
case
'g'
:
have_gid
=
1
;
gid
=
strtol
(
optarg
,
NULL
,
0
);
break
;
case
'c'
:
ossp_cmd_fd
=
strtol
(
optarg
,
NULL
,
0
);
break
;
case
'n'
:
ossp_notify_fd
=
strtol
(
optarg
,
NULL
,
0
);
break
;
case
'm'
:
mmap_fd
=
strtol
(
optarg
,
NULL
,
0
);
break
;
case
'o'
:
mmap_off
=
strtoull
(
optarg
,
NULL
,
0
);
break
;
case
's'
:
mmap_size
=
strtoul
(
optarg
,
NULL
,
0
);
break
;
case
'l'
:
ossp_log_level
=
strtol
(
optarg
,
NULL
,
0
);
break
;
case
't'
:
ossp_log_timestamp
=
1
;
break
;
}
}
if
(
!
have_uid
||
!
have_gid
||
ossp_cmd_fd
<
0
||
ossp_notify_fd
<
0
)
{
fprintf
(
stderr
,
usage
);
_exit
(
1
);
}
snprintf
(
ossp_user_name
,
sizeof
(
ossp_user_name
),
"uid%d"
,
uid
);
if
(
getpwuid_r
(
uid
,
&
pw_buf
,
pw_sbuf
,
sizeof
(
pw_sbuf
),
&
pw
)
==
0
)
snprintf
(
ossp_user_name
,
sizeof
(
ossp_user_name
),
"%s"
,
pw
->
pw_name
);
snprintf
(
ossp_log_name
,
sizeof
(
ossp_log_name
),
"ossp-padsp[%s:%d]"
,
ossp_user_name
,
getpid
());
if
(
mmap_fd
>=
0
)
{
void
*
p
;
if
(
!
mmap_off
||
!
mmap_size
)
{
fprintf
(
stderr
,
usage
);
_exit
(
1
);
}
p
=
mmap
(
NULL
,
mmap_size
,
PROT_READ
|
PROT_WRITE
,
MAP_SHARED
,
mmap_fd
,
mmap_off
);
if
(
p
==
MAP_FAILED
)
fatal_e
(
-
errno
,
"mmap failed"
);
ossp_mmap_addr
[
PLAY
]
=
p
;
ossp_mmap_addr
[
REC
]
=
p
+
mmap_size
/
2
;
close
(
mmap_fd
);
}
/* mmap done, drop privileges */
if
(
setresgid
(
gid
,
gid
,
gid
)
||
setresuid
(
uid
,
uid
,
uid
))
fatal_e
(
-
errno
,
"failed to drop privileges"
);
/* block SIGPIPE */
memset
(
&
sa
,
0
,
sizeof
(
sa
));
sa
.
sa_handler
=
SIG_IGN
;
if
(
sigaction
(
SIGPIPE
,
&
sa
,
NULL
))
fatal_e
(
-
errno
,
"failed to ignore SIGPIPE"
);
}
int
ossp_slave_process_command
(
int
cmd_fd
,
ossp_action_fn_t
const
*
action_fn_tbl
,
int
(
*
action_pre_fn
)(
void
),
void
(
*
action_post_fn
)(
void
))
{
static
struct
sized_buf
carg_sbuf
=
{
},
rarg_sbuf
=
{
};
static
struct
sized_buf
din_sbuf
=
{
},
dout_sbuf
=
{
};
struct
ossp_cmd
cmd
;
int
fd
=
-
1
;
char
cmsg_buf
[
CMSG_SPACE
(
sizeof
(
fd
))];
struct
iovec
iov
=
{
&
cmd
,
sizeof
(
cmd
)
};
struct
msghdr
msg
=
{
.
msg_iov
=
&
iov
,
.
msg_iovlen
=
1
,
.
msg_control
=
cmsg_buf
,
.
msg_controllen
=
sizeof
(
cmsg_buf
)
};
struct
cmsghdr
*
cmsg
;
size_t
carg_size
,
din_size
,
rarg_size
,
dout_size
;
char
*
carg
=
NULL
,
*
din
=
NULL
,
*
rarg
=
NULL
,
*
dout
=
NULL
;
struct
ossp_reply
reply
=
{
.
magic
=
OSSP_REPLY_MAGIC
};
ssize_t
ret
;
ret
=
recvmsg
(
cmd_fd
,
&
msg
,
0
);
if
(
ret
==
0
)
return
0
;
if
(
ret
<
0
)
{
ret
=
-
errno
;
err_e
(
ret
,
"failed to read command channel"
);
return
ret
;
}
if
(
ret
!=
sizeof
(
cmd
))
{
err
(
"command struct size mismatch (%zu, should be %zu)"
,
ret
,
sizeof
(
cmd
));
return
-
EINVAL
;
}
if
(
cmd
.
magic
!=
OSSP_CMD_MAGIC
)
{
err
(
"illegal command magic 0x%x"
,
cmd
.
magic
);
return
-
EINVAL
;
}
for
(
cmsg
=
CMSG_FIRSTHDR
(
&
msg
);
cmsg
;
cmsg
=
CMSG_NXTHDR
(
&
msg
,
cmsg
))
{
if
(
cmsg
->
cmsg_level
==
SOL_SOCKET
&&
cmsg
->
cmsg_type
==
SCM_RIGHTS
)
fd
=
*
(
int
*
)
CMSG_DATA
(
cmsg
);
else
{
err
(
"unknown cmsg %d:%d received (opcode %d)"
,
cmsg
->
cmsg_level
,
cmsg
->
cmsg_type
,
cmd
.
opcode
);
return
-
EINVAL
;
}
}
if
(
cmd
.
opcode
>=
OSSP_NR_OPCODES
)
{
err
(
"unknown opcode %d"
,
cmd
.
opcode
);
return
-
EINVAL
;
}
carg_size
=
ossp_arg_sizes
[
cmd
.
opcode
].
carg_size
;
din_size
=
cmd
.
din_size
;
rarg_size
=
ossp_arg_sizes
[
cmd
.
opcode
].
rarg_size
;
dout_size
=
cmd
.
dout_size
;
if
((
fd
>=
0
)
!=
ossp_arg_sizes
[
cmd
.
opcode
].
has_fd
)
{
err
(
"fd=%d unexpected for opcode %d"
,
fd
,
cmd
.
opcode
);
return
-
EINVAL
;
}
if
(
ensure_sbuf_size
(
&
carg_sbuf
,
carg_size
)
||
ensure_sbuf_size
(
&
din_sbuf
,
din_size
)
||
ensure_sbuf_size
(
&
rarg_sbuf
,
rarg_size
)
||
ensure_sbuf_size
(
&
dout_sbuf
,
dout_size
))
{
err
(
"failed to allocate command buffers"
);
return
-
ENOMEM
;
}
if
(
carg_size
)
{
carg
=
carg_sbuf
.
buf
;
ret
=
read_fill
(
cmd_fd
,
carg
,
carg_size
);
if
(
ret
<
0
)
return
ret
;
}
if
(
din_size
)
{
din
=
din_sbuf
.
buf
;
ret
=
read_fill
(
cmd_fd
,
din
,
din_size
);
if
(
ret
<
0
)
return
ret
;
}
if
(
rarg_size
)
rarg
=
rarg_sbuf
.
buf
;
if
(
dout_size
)
dout
=
dout_sbuf
.
buf
;
ret
=
-
EINVAL
;
if
(
action_fn_tbl
[
cmd
.
opcode
])
{
ret
=
action_pre_fn
();
if
(
ret
==
0
)
{
ret
=
action_fn_tbl
[
cmd
.
opcode
](
cmd
.
opcode
,
carg
,
din
,
din_size
,
rarg
,
dout
,
&
dout_size
,
fd
);
action_post_fn
();
}
}
reply
.
result
=
ret
;
if
(
ret
>=
0
)
reply
.
dout_size
=
dout_size
;
else
{
rarg_size
=
0
;
dout_size
=
0
;
}
if
(
write_fill
(
cmd_fd
,
&
reply
,
sizeof
(
reply
))
<
0
||
write_fill
(
cmd_fd
,
rarg
,
rarg_size
)
<
0
||
write_fill
(
cmd_fd
,
dout
,
dout_size
)
<
0
)
return
-
EIO
;
return
1
;
}
src/mod/endpoints/mod_skypopen/osscuse/ossp-slave.h
0 → 100644
浏览文件 @
599a2005
/*
* ossp-slave - OSS Proxy: Common codes for slaves
*
* Copyright (C) 2008-2010 SUSE Linux Products GmbH
* Copyright (C) 2008-2010 Tejun Heo <tj@kernel.org>
*
* This file is released under the GPLv2.
*/
#ifndef _OSSP_SLAVE_H
#define _OSSP_SLAVE_H
#include "ossp.h"
#include "ossp-util.h"
#define OSSP_USER_NAME_LEN 128
extern
char
ossp_user_name
[
OSSP_USER_NAME_LEN
];
extern
int
ossp_cmd_fd
,
ossp_notify_fd
;
extern
void
*
ossp_mmap_addr
[
2
];
void
ossp_slave_init
(
int
argc
,
char
**
argv
);
int
ossp_slave_process_command
(
int
cmd_fd
,
ossp_action_fn_t
const
*
action_fn_tbl
,
int
(
*
action_pre_fn
)(
void
),
void
(
*
action_post_fn
)(
void
));
#endif
/* _OSSP_SLAVE_H */
src/mod/endpoints/mod_skypopen/osscuse/ossp-util.c
0 → 100644
浏览文件 @
599a2005
/*
* ossp-util - OSS Proxy: Common utilities
*
* Copyright (C) 2008-2010 SUSE Linux Products GmbH
* Copyright (C) 2008-2010 Tejun Heo <tj@kernel.org>
*
* This file is released under the GPLv2.
*/
#include <ctype.h>
#include <fcntl.h>
#include <inttypes.h>
#include <limits.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <syslog.h>
#include <unistd.h>
#include "ossp-util.h"
#define BIT(nr) (1UL << (nr))
#define BIT_MASK(nr) (1UL << ((nr) % BITS_PER_LONG))
#define BIT_WORD(nr) ((nr) / BITS_PER_LONG)
#define BITS_TO_LONGS(nr) DIV_ROUND_UP(nr, BITS_PER_BYTE * sizeof(long))
#define BITOP_WORD(nr) ((nr) / BITS_PER_LONG)
char
ossp_log_name
[
OSSP_LOG_NAME_LEN
];
int
ossp_log_level
=
OSSP_LOG_DFL
;
int
ossp_log_timestamp
;
static
const
char
*
severity_strs
[]
=
{
[
OSSP_LOG_CRIT
]
=
"CRIT"
,
[
OSSP_LOG_ERR
]
=
" ERR"
,
[
OSSP_LOG_WARN
]
=
"WARN"
,
[
OSSP_LOG_INFO
]
=
NULL
,
[
OSSP_LOG_DBG0
]
=
"DBG0"
,
[
OSSP_LOG_DBG1
]
=
"DBG1"
,
};
static
int
severity_map
[]
=
{
[
OSSP_LOG_CRIT
]
=
LOG_ERR
,
[
OSSP_LOG_ERR
]
=
LOG_ERR
,
[
OSSP_LOG_WARN
]
=
LOG_WARNING
,
[
OSSP_LOG_INFO
]
=
LOG_INFO
,
[
OSSP_LOG_DBG0
]
=
LOG_DEBUG
,
[
OSSP_LOG_DBG1
]
=
LOG_DEBUG
,
};
void
log_msg
(
int
severity
,
const
char
*
fmt
,
...)
{
static
int
syslog_opened
=
0
;
char
buf
[
1024
];
size_t
len
=
sizeof
(
buf
),
off
=
0
;
va_list
ap
;
if
(
severity
>
abs
(
ossp_log_level
))
return
;
if
(
ossp_log_level
<
0
&&
!
syslog_opened
)
openlog
(
ossp_log_name
,
0
,
LOG_DAEMON
);
assert
(
severity
>=
0
&&
severity
<
ARRAY_SIZE
(
severity_strs
));
if
(
ossp_log_timestamp
)
{
static
uint64_t
start
;
uint64_t
now
;
struct
timeval
tv
;
gettimeofday
(
&
tv
,
NULL
);
now
=
tv
.
tv_sec
*
1000
+
tv
.
tv_usec
/
1000
;
if
(
!
start
)
start
=
now
;
off
+=
snprintf
(
buf
+
off
,
len
-
off
,
"<%08"
PRIu64
"> "
,
now
-
start
);
}
if
(
ossp_log_level
>
0
)
{
char
sev_buf
[
16
]
=
""
;
if
(
severity_strs
[
severity
])
snprintf
(
sev_buf
,
sizeof
(
sev_buf
),
" %s"
,
severity_strs
[
severity
]);
off
+=
snprintf
(
buf
+
off
,
len
-
off
,
"%s%s: "
,
ossp_log_name
,
sev_buf
);
}
else
if
(
severity_strs
[
severity
])
off
+=
snprintf
(
buf
+
off
,
len
-
off
,
"%s "
,
severity_strs
[
severity
]);
va_start
(
ap
,
fmt
);
off
+=
vsnprintf
(
buf
+
off
,
len
-
off
,
fmt
,
ap
);
va_end
(
ap
);
off
+=
snprintf
(
buf
+
off
,
len
-
off
,
"
\n
"
);
if
(
ossp_log_level
>
0
)
fputs
(
buf
,
stderr
);
else
syslog
(
severity_map
[
severity
],
"%s"
,
buf
);
}
int
read_fill
(
int
fd
,
void
*
buf
,
size_t
size
)
{
while
(
size
)
{
ssize_t
ret
;
int
rc
;
ret
=
read
(
fd
,
buf
,
size
);
if
(
ret
<=
0
)
{
if
(
ret
==
0
)
rc
=
-
EIO
;
else
rc
=
-
errno
;
err_e
(
rc
,
"failed to read_fill %zu bytes from fd %d"
,
size
,
fd
);
return
rc
;
}
buf
+=
ret
;
size
-=
ret
;
}
return
0
;
}
int
write_fill
(
int
fd
,
const
void
*
buf
,
size_t
size
)
{
while
(
size
)
{
ssize_t
ret
;
int
rc
;
ret
=
write
(
fd
,
buf
,
size
);
if
(
ret
<=
0
)
{
if
(
ret
==
0
)
rc
=
-
EIO
;
else
rc
=
-
errno
;
err_e
(
rc
,
"failed to write_fill %zu bytes to fd %d"
,
size
,
fd
);
return
rc
;
}
buf
+=
ret
;
size
-=
ret
;
}
return
0
;
}
void
ring_fill
(
struct
ring_buf
*
ring
,
const
void
*
buf
,
size_t
size
)
{
size_t
tail
;
assert
(
ring_space
(
ring
)
>=
size
);
tail
=
(
ring
->
head
+
ring
->
size
-
ring
->
bytes
)
%
ring
->
size
;
if
(
ring
->
head
>=
tail
)
{
size_t
todo
=
min
(
size
,
ring
->
size
-
ring
->
head
);
memcpy
(
ring
->
buf
+
ring
->
head
,
buf
,
todo
);
ring
->
head
=
(
ring
->
head
+
todo
)
%
ring
->
size
;
ring
->
bytes
+=
todo
;
buf
+=
todo
;
size
-=
todo
;
}
assert
(
ring
->
size
-
ring
->
head
>=
size
);
memcpy
(
ring
->
buf
+
ring
->
head
,
buf
,
size
);
ring
->
head
+=
size
;
ring
->
bytes
+=
size
;
}
void
*
ring_data
(
struct
ring_buf
*
ring
,
size_t
*
sizep
)
{
size_t
tail
;
if
(
!
ring
->
bytes
)
return
NULL
;
tail
=
(
ring
->
head
+
ring
->
size
-
ring
->
bytes
)
%
ring
->
size
;
*
sizep
=
min
(
ring
->
bytes
,
ring
->
size
-
tail
);
return
ring
->
buf
+
tail
;
}
int
ring_resize
(
struct
ring_buf
*
ring
,
size_t
new_size
)
{
struct
ring_buf
new_ring
=
{
.
size
=
new_size
};
void
*
p
;
size_t
size
;
if
(
ring_bytes
(
ring
)
>
new_size
)
return
-
ENOSPC
;
new_ring
.
buf
=
calloc
(
1
,
new_size
);
if
(
new_size
&&
!
new_ring
.
buf
)
return
-
ENOMEM
;
while
((
p
=
ring_data
(
ring
,
&
size
)))
{
ring_fill
(
&
new_ring
,
p
,
size
);
ring_consume
(
ring
,
size
);
}
free
(
ring
->
buf
);
*
ring
=
new_ring
;
return
0
;
}
int
ensure_sbuf_size
(
struct
sized_buf
*
sbuf
,
size_t
size
)
{
char
*
new_buf
;
if
(
sbuf
->
size
>=
size
)
return
0
;
new_buf
=
realloc
(
sbuf
->
buf
,
size
);
if
(
size
&&
!
new_buf
)
return
-
ENOMEM
;
sbuf
->
buf
=
new_buf
;
sbuf
->
size
=
size
;
return
0
;
}
static
unsigned
long
__ffs
(
unsigned
long
word
)
{
int
num
=
0
;
if
(
BITS_PER_LONG
==
64
)
{
if
((
word
&
0xffffffff
)
==
0
)
{
num
+=
32
;
word
>>=
32
;
}
}
if
((
word
&
0xffff
)
==
0
)
{
num
+=
16
;
word
>>=
16
;
}
if
((
word
&
0xff
)
==
0
)
{
num
+=
8
;
word
>>=
8
;
}
if
((
word
&
0xf
)
==
0
)
{
num
+=
4
;
word
>>=
4
;
}
if
((
word
&
0x3
)
==
0
)
{
num
+=
2
;
word
>>=
2
;
}
if
((
word
&
0x1
)
==
0
)
num
+=
1
;
return
num
;
}
#define ffz(x) __ffs(~(x))
unsigned
long
find_next_zero_bit
(
const
unsigned
long
*
addr
,
unsigned
long
size
,
unsigned
long
offset
)
{
const
unsigned
long
*
p
=
addr
+
BITOP_WORD
(
offset
);
unsigned
long
result
=
offset
&
~
(
BITS_PER_LONG
-
1
);
unsigned
long
tmp
;
if
(
offset
>=
size
)
return
size
;
size
-=
result
;
offset
%=
BITS_PER_LONG
;
if
(
offset
)
{
tmp
=
*
(
p
++
);
tmp
|=
~
0UL
>>
(
BITS_PER_LONG
-
offset
);
if
(
size
<
BITS_PER_LONG
)
goto
found_first
;
if
(
~
tmp
)
goto
found_middle
;
size
-=
BITS_PER_LONG
;
result
+=
BITS_PER_LONG
;
}
while
(
size
&
~
(
BITS_PER_LONG
-
1
))
{
if
(
~
(
tmp
=
*
(
p
++
)))
goto
found_middle
;
result
+=
BITS_PER_LONG
;
size
-=
BITS_PER_LONG
;
}
if
(
!
size
)
return
result
;
tmp
=
*
p
;
found_first:
tmp
|=
~
0UL
<<
size
;
if
(
tmp
==
~
0UL
)
/* Are any bits zero? */
return
result
+
size
;
/* Nope. */
found_middle:
return
result
+
ffz
(
tmp
);
}
void
__set_bit
(
int
nr
,
volatile
unsigned
long
*
addr
)
{
unsigned
long
mask
=
BIT_MASK
(
nr
);
unsigned
long
*
p
=
((
unsigned
long
*
)
addr
)
+
BIT_WORD
(
nr
);
*
p
|=
mask
;
}
void
__clear_bit
(
int
nr
,
volatile
unsigned
long
*
addr
)
{
unsigned
long
mask
=
BIT_MASK
(
nr
);
unsigned
long
*
p
=
((
unsigned
long
*
)
addr
)
+
BIT_WORD
(
nr
);
*
p
&=
~
mask
;
}
int
get_proc_self_info
(
pid_t
pid
,
pid_t
*
ppid_r
,
char
*
cmd_buf
,
size_t
cmd_buf_sz
)
{
char
path
[
64
],
buf
[
4096
];
int
fd
=
-
1
;
char
*
cmd_start
,
*
cmd_end
,
*
ppid_start
,
*
end
;
ssize_t
ret
;
pid_t
ppid
;
int
i
,
rc
;
snprintf
(
path
,
sizeof
(
path
),
"/proc/%ld/stat"
,
(
long
)
pid
);
fd
=
open
(
path
,
O_RDONLY
);
if
(
fd
<
0
)
{
rc
=
-
errno
;
goto
out
;
}
ret
=
read
(
fd
,
buf
,
sizeof
(
buf
));
if
(
ret
<
0
)
goto
out
;
if
(
ret
==
sizeof
(
buf
))
{
rc
=
-
EOVERFLOW
;
goto
out
;
}
buf
[
ret
]
=
'\0'
;
rc
=
-
EINVAL
;
cmd_start
=
strchr
(
buf
,
'('
);
cmd_end
=
strrchr
(
buf
,
')'
);
if
(
!
cmd_start
||
!
cmd_end
)
goto
out
;
cmd_start
++
;
ppid_start
=
cmd_end
;
for
(
i
=
0
;
i
<
3
;
i
++
)
{
ppid_start
=
strchr
(
ppid_start
,
' '
);
if
(
!
ppid_start
)
goto
out
;
ppid_start
++
;
}
ppid
=
strtoul
(
ppid_start
,
&
end
,
10
);
if
(
end
==
ppid_start
||
*
end
!=
' '
)
goto
out
;
if
(
ppid_r
)
*
ppid_r
=
ppid
;
if
(
cmd_buf
)
{
size_t
len
=
min_t
(
size_t
,
cmd_end
-
cmd_start
,
cmd_buf_sz
-
1
);
memcpy
(
cmd_buf
,
cmd_start
,
len
);
cmd_buf
[
len
]
=
'\0'
;
}
rc
=
0
;
out:
close
(
fd
);
return
rc
;
}
src/mod/endpoints/mod_skypopen/osscuse/ossp-util.h
0 → 100644
浏览文件 @
599a2005
/*
* ossp-util - OSS Proxy: Common utilities
*
* Copyright (C) 2008-2010 SUSE Linux Products GmbH
* Copyright (C) 2008-2010 Tejun Heo <tj@kernel.org>
*
* This file is released under the GPLv2.
*/
#ifndef _OSSP_UTIL_H
#define _OSSP_UTIL_H
#include <assert.h>
#include <stddef.h>
#include <string.h>
#include <sys/types.h>
#include <errno.h>
#include <unistd.h>
#include "ossp.h"
#define OSSP_LOG_NAME_LEN 128
enum
{
OSSP_LOG_CRIT
=
1
,
OSSP_LOG_ERR
,
OSSP_LOG_WARN
,
OSSP_LOG_INFO
,
OSSP_LOG_DFL
=
OSSP_LOG_INFO
,
/* default log level */
OSSP_LOG_DBG0
,
OSSP_LOG_DBG1
,
OSSP_LOG_MAX
=
OSSP_LOG_DBG1
,
};
extern
char
ossp_log_name
[
OSSP_LOG_NAME_LEN
];
extern
int
ossp_log_level
;
extern
int
ossp_log_timestamp
;
#define BITS_PER_BYTE 8
#define BITS_PER_LONG (BITS_PER_BYTE * sizeof(long))
#define DIV_ROUND_UP(n,d) (((n) + (d) - 1) / (d))
#define BITS_TO_LONGS(nr) DIV_ROUND_UP(nr, BITS_PER_BYTE * sizeof(long))
/* ARRAY_SIZE and min/max macros stolen from linux/kernel.h */
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
#define min(x, y) ({ \
typeof(x) _min1 = (x); \
typeof(y) _min2 = (y); \
(void) (&_min1 == &_min2); \
_min1 < _min2 ? _min1 : _min2; })
#define max(x, y) ({ \
typeof(x) _max1 = (x); \
typeof(y) _max2 = (y); \
(void) (&_max1 == &_max2); \
_max1 > _max2 ? _max1 : _max2; })
#define min_t(type, x, y) ({ \
type __min1 = (x); \
type __min2 = (y); \
__min1 < __min2 ? __min1: __min2; })
#define max_t(type, x, y) ({ \
type __max1 = (x); \
type __max2 = (y); \
__max1 > __max2 ? __max1: __max2; })
void
log_msg
(
int
severity
,
const
char
*
fmt
,
...)
__attribute__
((
format
(
printf
,
2
,
3
)));
#define fatal(fmt, args...) do { \
log_msg(OSSP_LOG_CRIT, fmt , ##args); \
_exit(1); \
} while (0)
#define err(fmt, args...) log_msg(OSSP_LOG_ERR, fmt , ##args)
#define warn(fmt, args...) log_msg(OSSP_LOG_WARN, fmt , ##args)
#define info(fmt, args...) log_msg(OSSP_LOG_INFO, fmt , ##args)
#define dbg0(fmt, args...) log_msg(OSSP_LOG_DBG0, fmt , ##args)
#define dbg1(fmt, args...) log_msg(OSSP_LOG_DBG1, fmt , ##args)
#define fatal_e(e, fmt, args...) \
fatal(fmt" (%s)" , ##args, strerror(-(e)))
#define err_e(e, fmt, args...) \
err(fmt" (%s)" , ##args, strerror(-(e)))
#define warn_e(e, fmt, args...) \
warn(fmt" (%s)" , ##args, strerror(-(e)))
#define info_e(e, fmt, args...) \
info(fmt" (%s)" , ##args, strerror(-(e)))
#define dbg0_e(e, fmt, args...) \
dbg0(fmt" (%s)" , ##args, strerror(-(e)))
#define dbg1_e(e, fmt, args...) \
dbg1(fmt" (%s)" , ##args, strerror(-(e)))
struct
ring_buf
{
char
*
buf
;
size_t
size
;
size_t
head
;
size_t
bytes
;
};
static
inline
size_t
ring_size
(
struct
ring_buf
*
ring
)
{
return
ring
->
size
;
}
static
inline
size_t
ring_bytes
(
struct
ring_buf
*
ring
)
{
return
ring
->
bytes
;
}
static
inline
size_t
ring_space
(
struct
ring_buf
*
ring
)
{
return
ring
->
size
-
ring
->
bytes
;
}
static
inline
void
ring_consume
(
struct
ring_buf
*
ring
,
size_t
size
)
{
assert
(
ring
->
bytes
>=
size
);
ring
->
bytes
-=
size
;
}
static
inline
void
ring_manual_init
(
struct
ring_buf
*
ring
,
void
*
buf
,
size_t
size
,
size_t
head
,
size_t
bytes
)
{
ring
->
buf
=
buf
;
ring
->
size
=
size
;
ring
->
head
=
head
;
ring
->
bytes
=
bytes
;
}
void
ring_fill
(
struct
ring_buf
*
ring
,
const
void
*
buf
,
size_t
size
);
void
*
ring_data
(
struct
ring_buf
*
ring
,
size_t
*
sizep
);
int
ring_resize
(
struct
ring_buf
*
ring
,
size_t
new_size
);
struct
sized_buf
{
char
*
buf
;
size_t
size
;
};
int
ensure_sbuf_size
(
struct
sized_buf
*
sbuf
,
size_t
size
);
int
read_fill
(
int
fd
,
void
*
buf
,
size_t
size
);
int
write_fill
(
int
fd
,
const
void
*
buf
,
size_t
size
);
/*
* Bitops lifted from linux asm-generic implementation.
*/
unsigned
long
find_next_zero_bit
(
const
unsigned
long
*
addr
,
unsigned
long
size
,
unsigned
long
offset
);
#define find_first_zero_bit(addr, size) find_next_zero_bit((addr), (size), 0)
extern
void
__set_bit
(
int
nr
,
volatile
unsigned
long
*
addr
);
extern
void
__clear_bit
(
int
nr
,
volatile
unsigned
long
*
addr
);
typedef
ssize_t
(
*
ossp_action_fn_t
)(
enum
ossp_opcode
opcode
,
void
*
carg
,
void
*
din
,
size_t
din_sz
,
void
*
rarg
,
void
*
dout
,
size_t
*
dout_szp
,
int
fd
);
int
get_proc_self_info
(
pid_t
tid
,
pid_t
*
pgrp
,
char
*
cmd_buf
,
size_t
cmd_buf_sz
);
/*
* Doubly linked list handling code shamelessly stolen from the Linux
* kernel 2.6.26 include/linux/list.h.
*/
/**
* container_of - cast a member of a structure out to the containing structure
* @ptr: the pointer to the member.
* @type: the type of the container struct this is embedded in.
* @member: the name of the member within the struct.
*
*/
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
#define LIST_POISON1 ((void *) 0x00100100)
#define LIST_POISON2 ((void *) 0x00200200)
/*
* Simple doubly linked list implementation.
*
* Some of the internal functions ("__xxx") are useful when
* manipulating whole lists rather than single entries, as
* sometimes we already know the next/prev entries and we can
* generate better code by using them directly rather than
* using the generic single-entry routines.
*/
struct
list_head
{
struct
list_head
*
next
,
*
prev
;
};
#define LIST_HEAD_INIT(name) { &(name), &(name) }
#define LIST_HEAD(name) \
struct list_head name = LIST_HEAD_INIT(name)
static
inline
void
INIT_LIST_HEAD
(
struct
list_head
*
list
)
{
list
->
next
=
list
;
list
->
prev
=
list
;
}
/*
* Insert a new entry between two known consecutive entries.
*
* This is only for internal list manipulation where we know
* the prev/next entries already!
*/
static
inline
void
__list_add
(
struct
list_head
*
new
,
struct
list_head
*
prev
,
struct
list_head
*
next
)
{
next
->
prev
=
new
;
new
->
next
=
next
;
new
->
prev
=
prev
;
prev
->
next
=
new
;
}
/**
* list_add - add a new entry
* @new: new entry to be added
* @head: list head to add it after
*
* Insert a new entry after the specified head.
* This is good for implementing stacks.
*/
static
inline
void
list_add
(
struct
list_head
*
new
,
struct
list_head
*
head
)
{
__list_add
(
new
,
head
,
head
->
next
);
}
/**
* list_add_tail - add a new entry
* @new: new entry to be added
* @head: list head to add it before
*
* Insert a new entry before the specified head.
* This is useful for implementing queues.
*/
static
inline
void
list_add_tail
(
struct
list_head
*
new
,
struct
list_head
*
head
)
{
__list_add
(
new
,
head
->
prev
,
head
);
}
/*
* Delete a list entry by making the prev/next entries
* point to each other.
*
* This is only for internal list manipulation where we know
* the prev/next entries already!
*/
static
inline
void
__list_del
(
struct
list_head
*
prev
,
struct
list_head
*
next
)
{
next
->
prev
=
prev
;
prev
->
next
=
next
;
}
/**
* list_del - deletes entry from list.
* @entry: the element to delete from the list.
* Note: list_empty() on entry does not return true after this, the entry is
* in an undefined state.
*/
static
inline
void
list_del
(
struct
list_head
*
entry
)
{
__list_del
(
entry
->
prev
,
entry
->
next
);
entry
->
next
=
LIST_POISON1
;
entry
->
prev
=
LIST_POISON2
;
}
/**
* list_replace - replace old entry by new one
* @old : the element to be replaced
* @new : the new element to insert
*
* If @old was empty, it will be overwritten.
*/
static
inline
void
list_replace
(
struct
list_head
*
old
,
struct
list_head
*
new
)
{
new
->
next
=
old
->
next
;
new
->
next
->
prev
=
new
;
new
->
prev
=
old
->
prev
;
new
->
prev
->
next
=
new
;
}
static
inline
void
list_replace_init
(
struct
list_head
*
old
,
struct
list_head
*
new
)
{
list_replace
(
old
,
new
);
INIT_LIST_HEAD
(
old
);
}
/**
* list_del_init - deletes entry from list and reinitialize it.
* @entry: the element to delete from the list.
*/
static
inline
void
list_del_init
(
struct
list_head
*
entry
)
{
__list_del
(
entry
->
prev
,
entry
->
next
);
INIT_LIST_HEAD
(
entry
);
}
/**
* list_move - delete from one list and add as another's head
* @list: the entry to move
* @head: the head that will precede our entry
*/
static
inline
void
list_move
(
struct
list_head
*
list
,
struct
list_head
*
head
)
{
__list_del
(
list
->
prev
,
list
->
next
);
list_add
(
list
,
head
);
}
/**
* list_move_tail - delete from one list and add as another's tail
* @list: the entry to move
* @head: the head that will follow our entry
*/
static
inline
void
list_move_tail
(
struct
list_head
*
list
,
struct
list_head
*
head
)
{
__list_del
(
list
->
prev
,
list
->
next
);
list_add_tail
(
list
,
head
);
}
/**
* list_is_last - tests whether @list is the last entry in list @head
* @list: the entry to test
* @head: the head of the list
*/
static
inline
int
list_is_last
(
const
struct
list_head
*
list
,
const
struct
list_head
*
head
)
{
return
list
->
next
==
head
;
}
/**
* list_empty - tests whether a list is empty
* @head: the list to test.
*/
static
inline
int
list_empty
(
const
struct
list_head
*
head
)
{
return
head
->
next
==
head
;
}
/**
* list_empty_careful - tests whether a list is empty and not being modified
* @head: the list to test
*
* Description:
* tests whether a list is empty _and_ checks that no other CPU might be
* in the process of modifying either member (next or prev)
*
* NOTE: using list_empty_careful() without synchronization
* can only be safe if the only activity that can happen
* to the list entry is list_del_init(). Eg. it cannot be used
* if another CPU could re-list_add() it.
*/
static
inline
int
list_empty_careful
(
const
struct
list_head
*
head
)
{
struct
list_head
*
next
=
head
->
next
;
return
(
next
==
head
)
&&
(
next
==
head
->
prev
);
}
/**
* list_is_singular - tests whether a list has just one entry.
* @head: the list to test.
*/
static
inline
int
list_is_singular
(
const
struct
list_head
*
head
)
{
return
!
list_empty
(
head
)
&&
(
head
->
next
==
head
->
prev
);
}
static
inline
void
__list_splice
(
const
struct
list_head
*
list
,
struct
list_head
*
head
)
{
struct
list_head
*
first
=
list
->
next
;
struct
list_head
*
last
=
list
->
prev
;
struct
list_head
*
at
=
head
->
next
;
first
->
prev
=
head
;
head
->
next
=
first
;
last
->
next
=
at
;
at
->
prev
=
last
;
}
/**
* list_splice - join two lists
* @list: the new list to add.
* @head: the place to add it in the first list.
*/
static
inline
void
list_splice
(
const
struct
list_head
*
list
,
struct
list_head
*
head
)
{
if
(
!
list_empty
(
list
))
__list_splice
(
list
,
head
);
}
/**
* list_splice_init - join two lists and reinitialise the emptied list.
* @list: the new list to add.
* @head: the place to add it in the first list.
*
* The list at @list is reinitialised
*/
static
inline
void
list_splice_init
(
struct
list_head
*
list
,
struct
list_head
*
head
)
{
if
(
!
list_empty
(
list
))
{
__list_splice
(
list
,
head
);
INIT_LIST_HEAD
(
list
);
}
}
/**
* list_entry - get the struct for this entry
* @ptr: the &struct list_head pointer.
* @type: the type of the struct this is embedded in.
* @member: the name of the list_struct within the struct.
*/
#define list_entry(ptr, type, member) \
container_of(ptr, type, member)
/**
* list_first_entry - get the first element from a list
* @ptr: the list head to take the element from.
* @type: the type of the struct this is embedded in.
* @member: the name of the list_struct within the struct.
*
* Note, that list is expected to be not empty.
*/
#define list_first_entry(ptr, type, member) \
list_entry((ptr)->next, type, member)
/**
* list_for_each - iterate over a list
* @pos: the &struct list_head to use as a loop cursor.
* @head: the head for your list.
*/
#define list_for_each(pos, head) \
for (pos = (head)->next; pos != (head); pos = pos->next)
/**
* list_for_each_prev - iterate over a list backwards
* @pos: the &struct list_head to use as a loop cursor.
* @head: the head for your list.
*/
#define list_for_each_prev(pos, head) \
for (pos = (head)->prev; pos != (head); pos = pos->prev)
/**
* list_for_each_safe - iterate over a list safe against removal of list entry
* @pos: the &struct list_head to use as a loop cursor.
* @n: another &struct list_head to use as temporary storage
* @head: the head for your list.
*/
#define list_for_each_safe(pos, n, head) \
for (pos = (head)->next, n = pos->next; pos != (head); \
pos = n, n = pos->next)
/**
* list_for_each_prev_safe - iterate over a list backwards safe against removal of list entry
* @pos: the &struct list_head to use as a loop cursor.
* @n: another &struct list_head to use as temporary storage
* @head: the head for your list.
*/
#define list_for_each_prev_safe(pos, n, head) \
for (pos = (head)->prev, n = pos->prev; \
pos != (head); pos = n, n = pos->prev)
/**
* list_for_each_entry - iterate over list of given type
* @pos: the type * to use as a loop cursor.
* @head: the head for your list.
* @member: the name of the list_struct within the struct.
*/
#define list_for_each_entry(pos, head, member) \
for (pos = list_entry((head)->next, typeof(*pos), member); \
&pos->member != (head); \
pos = list_entry(pos->member.next, typeof(*pos), member))
/**
* list_for_each_entry_reverse - iterate backwards over list of given type.
* @pos: the type * to use as a loop cursor.
* @head: the head for your list.
* @member: the name of the list_struct within the struct.
*/
#define list_for_each_entry_reverse(pos, head, member) \
for (pos = list_entry((head)->prev, typeof(*pos), member); \
&pos->member != (head); \
pos = list_entry(pos->member.prev, typeof(*pos), member))
/**
* list_prepare_entry - prepare a pos entry for use in list_for_each_entry_continue()
* @pos: the type * to use as a start point
* @head: the head of the list
* @member: the name of the list_struct within the struct.
*
* Prepares a pos entry for use as a start point in list_for_each_entry_continue().
*/
#define list_prepare_entry(pos, head, member) \
((pos) ? : list_entry(head, typeof(*pos), member))
/**
* list_for_each_entry_continue - continue iteration over list of given type
* @pos: the type * to use as a loop cursor.
* @head: the head for your list.
* @member: the name of the list_struct within the struct.
*
* Continue to iterate over list of given type, continuing after
* the current position.
*/
#define list_for_each_entry_continue(pos, head, member) \
for (pos = list_entry(pos->member.next, typeof(*pos), member); \
&pos->member != (head); \
pos = list_entry(pos->member.next, typeof(*pos), member))
/**
* list_for_each_entry_continue_reverse - iterate backwards from the given point
* @pos: the type * to use as a loop cursor.
* @head: the head for your list.
* @member: the name of the list_struct within the struct.
*
* Start to iterate over list of given type backwards, continuing after
* the current position.
*/
#define list_for_each_entry_continue_reverse(pos, head, member) \
for (pos = list_entry(pos->member.prev, typeof(*pos), member); \
&pos->member != (head); \
pos = list_entry(pos->member.prev, typeof(*pos), member))
/**
* list_for_each_entry_from - iterate over list of given type from the current point
* @pos: the type * to use as a loop cursor.
* @head: the head for your list.
* @member: the name of the list_struct within the struct.
*
* Iterate over list of given type, continuing from current position.
*/
#define list_for_each_entry_from(pos, head, member) \
for (; &pos->member != (head); \
pos = list_entry(pos->member.next, typeof(*pos), member))
/**
* list_for_each_entry_safe - iterate over list of given type safe against removal of list entry
* @pos: the type * to use as a loop cursor.
* @n: another type * to use as temporary storage
* @head: the head for your list.
* @member: the name of the list_struct within the struct.
*/
#define list_for_each_entry_safe(pos, n, head, member) \
for (pos = list_entry((head)->next, typeof(*pos), member), \
n = list_entry(pos->member.next, typeof(*pos), member); \
&pos->member != (head); \
pos = n, n = list_entry(n->member.next, typeof(*n), member))
/**
* list_for_each_entry_safe_continue
* @pos: the type * to use as a loop cursor.
* @n: another type * to use as temporary storage
* @head: the head for your list.
* @member: the name of the list_struct within the struct.
*
* Iterate over list of given type, continuing after current point,
* safe against removal of list entry.
*/
#define list_for_each_entry_safe_continue(pos, n, head, member) \
for (pos = list_entry(pos->member.next, typeof(*pos), member), \
n = list_entry(pos->member.next, typeof(*pos), member); \
&pos->member != (head); \
pos = n, n = list_entry(n->member.next, typeof(*n), member))
/**
* list_for_each_entry_safe_from
* @pos: the type * to use as a loop cursor.
* @n: another type * to use as temporary storage
* @head: the head for your list.
* @member: the name of the list_struct within the struct.
*
* Iterate over list of given type from current point, safe against
* removal of list entry.
*/
#define list_for_each_entry_safe_from(pos, n, head, member) \
for (n = list_entry(pos->member.next, typeof(*pos), member); \
&pos->member != (head); \
pos = n, n = list_entry(n->member.next, typeof(*n), member))
/**
* list_for_each_entry_safe_reverse
* @pos: the type * to use as a loop cursor.
* @n: another type * to use as temporary storage
* @head: the head for your list.
* @member: the name of the list_struct within the struct.
*
* Iterate backwards over list of given type, safe against removal
* of list entry.
*/
#define list_for_each_entry_safe_reverse(pos, n, head, member) \
for (pos = list_entry((head)->prev, typeof(*pos), member), \
n = list_entry(pos->member.prev, typeof(*pos), member); \
&pos->member != (head); \
pos = n, n = list_entry(n->member.prev, typeof(*n), member))
#endif
/*_OSSP_UTIL_H*/
src/mod/endpoints/mod_skypopen/osscuse/ossp.c
0 → 100644
浏览文件 @
599a2005
/*
* ossp - OSS Proxy: emulate OSS device using CUSE
*
* Copyright (C) 2008-2010 SUSE Linux Products GmbH
* Copyright (C) 2008-2010 Tejun Heo <tj@kernel.org>
*
* This file is released under the GPLv2.
*/
#include "ossp.h"
const
struct
ossp_arg_size
ossp_arg_sizes
[
OSSP_NR_OPCODES
]
=
{
[
OSSP_MIXER
]
=
{
sizeof
(
struct
ossp_mixer_arg
),
sizeof
(
struct
ossp_mixer_arg
),
0
},
[
OSSP_DSP_OPEN
]
=
{
sizeof
(
struct
ossp_dsp_open_arg
),
0
,
0
},
[
OSSP_DSP_READ
]
=
{
sizeof
(
struct
ossp_dsp_rw_arg
),
0
,
0
},
[
OSSP_DSP_WRITE
]
=
{
sizeof
(
struct
ossp_dsp_rw_arg
),
0
,
0
},
[
OSSP_DSP_POLL
]
=
{
sizeof
(
int
),
sizeof
(
unsigned
),
0
},
[
OSSP_DSP_MMAP
]
=
{
sizeof
(
struct
ossp_dsp_mmap_arg
),
0
,
0
},
[
OSSP_DSP_MUNMAP
]
=
{
sizeof
(
int
),
0
,
0
},
[
OSSP_DSP_RESET
]
=
{
0
,
0
,
0
},
[
OSSP_DSP_SYNC
]
=
{
0
,
0
,
0
},
[
OSSP_DSP_POST
]
=
{
0
,
0
,
0
},
[
OSSP_DSP_GET_RATE
]
=
{
0
,
sizeof
(
int
),
0
},
[
OSSP_DSP_GET_CHANNELS
]
=
{
0
,
sizeof
(
int
),
0
},
[
OSSP_DSP_GET_FORMAT
]
=
{
0
,
sizeof
(
int
),
0
},
[
OSSP_DSP_GET_BLKSIZE
]
=
{
0
,
sizeof
(
int
),
0
},
[
OSSP_DSP_GET_FORMATS
]
=
{
0
,
sizeof
(
int
),
0
},
[
OSSP_DSP_SET_RATE
]
=
{
sizeof
(
int
),
sizeof
(
int
),
0
},
[
OSSP_DSP_SET_CHANNELS
]
=
{
sizeof
(
int
),
sizeof
(
int
),
0
},
[
OSSP_DSP_SET_FORMAT
]
=
{
sizeof
(
int
),
sizeof
(
int
),
0
},
[
OSSP_DSP_SET_SUBDIVISION
]
=
{
sizeof
(
int
),
sizeof
(
int
),
0
},
[
OSSP_DSP_SET_FRAGMENT
]
=
{
sizeof
(
int
),
0
,
0
},
[
OSSP_DSP_GET_TRIGGER
]
=
{
0
,
sizeof
(
int
),
0
},
[
OSSP_DSP_SET_TRIGGER
]
=
{
sizeof
(
int
),
0
,
0
},
[
OSSP_DSP_GET_OSPACE
]
=
{
0
,
sizeof
(
struct
audio_buf_info
),
0
},
[
OSSP_DSP_GET_ISPACE
]
=
{
0
,
sizeof
(
struct
audio_buf_info
),
0
},
[
OSSP_DSP_GET_OPTR
]
=
{
0
,
sizeof
(
struct
count_info
),
0
},
[
OSSP_DSP_GET_IPTR
]
=
{
0
,
sizeof
(
struct
count_info
),
0
},
[
OSSP_DSP_GET_ODELAY
]
=
{
0
,
sizeof
(
int
),
0
},
};
const
char
*
ossp_cmd_str
[
OSSP_NR_OPCODES
]
=
{
[
OSSP_MIXER
]
=
"MIXER"
,
[
OSSP_DSP_OPEN
]
=
"OPEN"
,
[
OSSP_DSP_READ
]
=
"READ"
,
[
OSSP_DSP_WRITE
]
=
"WRITE"
,
[
OSSP_DSP_POLL
]
=
"POLL"
,
[
OSSP_DSP_MMAP
]
=
"MMAP"
,
[
OSSP_DSP_MUNMAP
]
=
"MUNMAP"
,
[
OSSP_DSP_RESET
]
=
"RESET"
,
[
OSSP_DSP_SYNC
]
=
"SYNC"
,
[
OSSP_DSP_POST
]
=
"POST"
,
[
OSSP_DSP_GET_RATE
]
=
"GET_RATE"
,
[
OSSP_DSP_GET_CHANNELS
]
=
"GET_CHANNELS"
,
[
OSSP_DSP_GET_FORMAT
]
=
"GET_FORMAT"
,
[
OSSP_DSP_GET_BLKSIZE
]
=
"GET_BLKSIZE"
,
[
OSSP_DSP_GET_FORMATS
]
=
"GET_FORMATS"
,
[
OSSP_DSP_SET_RATE
]
=
"SET_RATE"
,
[
OSSP_DSP_SET_CHANNELS
]
=
"SET_CHANNELS"
,
[
OSSP_DSP_SET_FORMAT
]
=
"SET_FORMAT"
,
[
OSSP_DSP_SET_SUBDIVISION
]
=
"SET_BUSDIVISION"
,
[
OSSP_DSP_SET_FRAGMENT
]
=
"SET_FRAGMENT"
,
[
OSSP_DSP_GET_TRIGGER
]
=
"GET_TRIGGER"
,
[
OSSP_DSP_SET_TRIGGER
]
=
"SET_TRIGGER"
,
[
OSSP_DSP_GET_OSPACE
]
=
"GET_OSPACE"
,
[
OSSP_DSP_GET_ISPACE
]
=
"GET_ISPACE"
,
[
OSSP_DSP_GET_OPTR
]
=
"GET_OPTR"
,
[
OSSP_DSP_GET_IPTR
]
=
"GET_IPTR"
,
[
OSSP_DSP_GET_ODELAY
]
=
"GET_ODELAY"
,
};
const
char
*
ossp_notify_str
[
OSSP_NR_NOTIFY_OPCODES
]
=
{
[
OSSP_NOTIFY_POLL
]
=
"POLL"
,
[
OSSP_NOTIFY_OBITUARY
]
=
"OBITUARY"
,
[
OSSP_NOTIFY_VOLCHG
]
=
"VOLCHG"
,
};
src/mod/endpoints/mod_skypopen/osscuse/ossp.h
0 → 100644
浏览文件 @
599a2005
/*
* ossp - OSS Proxy: emulate OSS device using CUSE
*
* Copyright (C) 2008-2010 SUSE Linux Products GmbH
* Copyright (C) 2008-2010 Tejun Heo <tj@kernel.org>
*
* This file is released under the GPLv2.
*/
#ifndef _OSSP_H
#define _OSSP_H
#include <sys/types.h>
#include <inttypes.h>
#include <sys/soundcard.h>
#define OSSP_VERSION "1.3.2"
#define OSSP_CMD_MAGIC 0xdeadbeef
#define OSSP_REPLY_MAGIC 0xbeefdead
#define OSSP_NOTIFY_MAGIC 0xbebebebe
#define PLAY 0
#define REC 1
#define LEFT 0
#define RIGHT 1
enum
ossp_opcode
{
OSSP_MIXER
,
OSSP_DSP_OPEN
,
OSSP_DSP_READ
,
OSSP_DSP_WRITE
,
OSSP_DSP_POLL
,
OSSP_DSP_MMAP
,
OSSP_DSP_MUNMAP
,
OSSP_DSP_RESET
,
OSSP_DSP_SYNC
,
OSSP_DSP_POST
,
OSSP_DSP_GET_RATE
,
OSSP_DSP_GET_CHANNELS
,
OSSP_DSP_GET_FORMAT
,
OSSP_DSP_GET_BLKSIZE
,
OSSP_DSP_GET_FORMATS
,
OSSP_DSP_SET_RATE
,
OSSP_DSP_SET_CHANNELS
,
OSSP_DSP_SET_FORMAT
,
OSSP_DSP_SET_SUBDIVISION
,
OSSP_DSP_SET_FRAGMENT
,
OSSP_DSP_GET_TRIGGER
,
OSSP_DSP_SET_TRIGGER
,
OSSP_DSP_GET_OSPACE
,
OSSP_DSP_GET_ISPACE
,
OSSP_DSP_GET_OPTR
,
OSSP_DSP_GET_IPTR
,
OSSP_DSP_GET_ODELAY
,
OSSP_NR_OPCODES
,
};
enum
ossp_notify_opcode
{
OSSP_NOTIFY_POLL
,
OSSP_NOTIFY_OBITUARY
,
OSSP_NOTIFY_VOLCHG
,
OSSP_NR_NOTIFY_OPCODES
,
};
struct
ossp_mixer_arg
{
int
vol
[
2
][
2
];
};
struct
ossp_dsp_open_arg
{
int
flags
;
pid_t
opener_pid
;
};
struct
ossp_dsp_rw_arg
{
unsigned
nonblock
:
1
;
};
struct
ossp_dsp_mmap_arg
{
int
dir
;
size_t
size
;
};
struct
ossp_cmd
{
unsigned
magic
;
enum
ossp_opcode
opcode
;
size_t
din_size
;
size_t
dout_size
;
};
struct
ossp_reply
{
unsigned
magic
;
int
result
;
size_t
dout_size
;
/* <= cmd.data_in_size */
};
struct
ossp_notify
{
unsigned
magic
;
enum
ossp_notify_opcode
opcode
;
};
struct
ossp_arg_size
{
ssize_t
carg_size
;
ssize_t
rarg_size
;
unsigned
has_fd
:
1
;
};
extern
const
struct
ossp_arg_size
ossp_arg_sizes
[
OSSP_NR_OPCODES
];
extern
const
char
*
ossp_cmd_str
[
OSSP_NR_OPCODES
];
extern
const
char
*
ossp_notify_str
[
OSSP_NR_NOTIFY_OPCODES
];
#endif
/* _OSSP_H */
src/mod/endpoints/mod_skypopen/osscuse/osspd.c
0 → 100644
浏览文件 @
599a2005
/*
* osspd - OSS Proxy Daemon: emulate OSS device using CUSE
*
* Copyright (C) 2008-2010 SUSE Linux Products GmbH
* Copyright (C) 2008-2010 Tejun Heo <tj@kernel.org>
*
* This file is released under the GPLv2.
*/
#undef GIOVANNI
#define FUSE_USE_VERSION 28
#define _GNU_SOURCE
#include <assert.h>
#include <cuse_lowlevel.h>
#include <fcntl.h>
#include <fuse_opt.h>
#include <libgen.h>
#include <limits.h>
#include <pthread.h>
#include <pwd.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <sys/soundcard.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <unistd.h>
#include "ossp.h"
#include "ossp-util.h"
/*
* MMAP support needs to be updated to the new fuse MMAP API. Disable
* it for the time being.
*/
#warning mmap support disabled for now
/* #define OSSP_MMAP */
#define DFL_MIXER_NAME "mixer"
#define DFL_DSP_NAME "dsp"
#define DFL_ADSP_NAME "adsp"
#define STRFMT "S[%u/%d]"
#define STRID(os) os->id, os->pid
#define dbg1_os(os, fmt, args...) dbg1(STRFMT" "fmt, STRID(os) , ##args)
#define dbg0_os(os, fmt, args...) dbg0(STRFMT" "fmt, STRID(os) , ##args)
#define warn_os(os, fmt, args...) warn(STRFMT" "fmt, STRID(os) , ##args)
#define err_os(os, fmt, args...) err(STRFMT" "fmt, STRID(os) , ##args)
#define warn_ose(os, err, fmt, args...) \
warn_e(err, STRFMT" "fmt, STRID(os) , ##args)
#define err_ose(os, err, fmt, args...) \
err_e(err, STRFMT" "fmt, STRID(os) , ##args)
enum
{
SNDRV_OSS_VERSION
=
((
3
<<
16
)
|
(
8
<<
8
)
|
(
1
<<
4
)
|
(
0
)),
/* 3.8.1a */
DFL_MIXER_MAJOR
=
14
,
DFL_MIXER_MINOR
=
0
,
DFL_DSP_MAJOR
=
14
,
DFL_DSP_MINOR
=
3
,
DFL_ADSP_MAJOR
=
14
,
DFL_ADSP_MINOR
=
12
,
DFL_MAX_STREAMS
=
128
,
MIXER_PUT_DELAY
=
600
,
/* 10 mins */
/* DSPS_MMAP_SIZE / 2 must be multiple of SHMLBA */
DSPS_MMAP_SIZE
=
2
*
(
512
<<
10
),
/* 512k for each dir */
};
struct
ossp_uid_cnt
{
struct
list_head
link
;
uid_t
uid
;
unsigned
nr_os
;
};
struct
ossp_mixer
{
pid_t
pgrp
;
struct
list_head
link
;
struct
list_head
delayed_put_link
;
unsigned
refcnt
;
/* the following two fields are protected by mixer_mutex */
int
vol
[
2
][
2
];
int
modify_counter
;
time_t
put_expires
;
};
struct
ossp_mixer_cmd
{
struct
ossp_mixer
*
mixer
;
struct
ossp_mixer_arg
set
;
int
out_dir
;
int
rvol
;
};
#define for_each_vol(i, j) \
for (i = 0, j = 0; i < 2; j += i << 1, j++, i = j >> 1, j &= 1)
struct
ossp_stream
{
unsigned
id
;
/* stream ID */
struct
list_head
link
;
struct
list_head
pgrp_link
;
struct
list_head
notify_link
;
unsigned
refcnt
;
pthread_mutex_t
cmd_mutex
;
pthread_mutex_t
mmap_mutex
;
struct
fuse_pollhandle
*
ph
;
/* stream owner info */
pid_t
pid
;
pid_t
pgrp
;
uid_t
uid
;
gid_t
gid
;
/* slave info */
pid_t
slave_pid
;
int
cmd_fd
;
int
notify_tx
;
int
notify_rx
;
/* the following dead flag is set asynchronously, keep it separate. */
int
dead
;
/* stream mixer state, protected by mixer_mutex */
int
mixer_pending
;
int
vol
[
2
][
2
];
int
vol_set
[
2
][
2
];
off_t
mmap_off
;
size_t
mmap_size
;
struct
ossp_uid_cnt
*
ucnt
;
struct
fuse_session
*
se
;
/* associated fuse session */
struct
ossp_mixer
*
mixer
;
};
struct
ossp_dsp_stream
{
struct
ossp_stream
os
;
unsigned
rw
;
unsigned
mmapped
;
int
nonblock
;
};
#define os_to_dsps(_os) container_of(_os, struct ossp_dsp_stream, os)
static
unsigned
max_streams
;
static
unsigned
umax_streams
;
static
unsigned
hashtbl_size
;
static
char
dsp_slave_path
[
PATH_MAX
];
static
pthread_mutex_t
mutex
=
PTHREAD_MUTEX_INITIALIZER
;
static
pthread_mutex_t
mixer_mutex
=
PTHREAD_MUTEX_INITIALIZER
;
static
unsigned
long
*
os_id_bitmap
;
static
unsigned
nr_mixers
;
static
struct
list_head
*
mixer_tbl
;
/* indexed by PGRP */
static
struct
list_head
*
os_tbl
;
/* indexed by ID */
static
struct
list_head
*
os_pgrp_tbl
;
/* indexed by PGRP */
static
struct
list_head
*
os_notify_tbl
;
/* indexed by notify fd */
static
LIST_HEAD
(
uid_cnt_list
);
static
int
notify_epfd
;
/* epoll used to monitor notify fds */
static
pthread_t
notify_poller_thread
;
static
pthread_t
slave_reaper_thread
;
static
pthread_t
mixer_delayed_put_thread
;
static
pthread_t
cuse_mixer_thread
;
static
pthread_t
cuse_adsp_thread
;
static
pthread_cond_t
notify_poller_kill_wait
=
PTHREAD_COND_INITIALIZER
;
static
pthread_cond_t
slave_reaper_wait
=
PTHREAD_COND_INITIALIZER
;
static
LIST_HEAD
(
slave_corpse_list
);
static
LIST_HEAD
(
mixer_delayed_put_head
);
/* delayed reference */
static
pthread_cond_t
mixer_delayed_put_cond
=
PTHREAD_COND_INITIALIZER
;
static
int
init_wait_fd
=
-
1
;
static
int
exit_on_idle
;
static
struct
fuse_session
*
mixer_se
;
static
struct
fuse_session
*
dsp_se
;
static
struct
fuse_session
*
adsp_se
;
static
void
put_os
(
struct
ossp_stream
*
os
);
/***************************************************************************
* Accessors
*/
static
struct
list_head
*
mixer_tbl_head
(
pid_t
pid
)
{
return
&
mixer_tbl
[
pid
%
hashtbl_size
];
}
static
struct
list_head
*
os_tbl_head
(
uint64_t
id
)
{
return
&
os_tbl
[
id
%
hashtbl_size
];
}
static
struct
list_head
*
os_pgrp_tbl_head
(
pid_t
pgrp
)
{
return
&
os_pgrp_tbl
[
pgrp
%
hashtbl_size
];
}
static
struct
list_head
*
os_notify_tbl_head
(
int
notify_rx
)
{
return
&
os_notify_tbl
[
notify_rx
%
hashtbl_size
];
}
static
struct
ossp_mixer
*
find_mixer_locked
(
pid_t
pgrp
)
{
struct
ossp_mixer
*
mixer
;
list_for_each_entry
(
mixer
,
mixer_tbl_head
(
pgrp
),
link
)
if
(
mixer
->
pgrp
==
pgrp
)
return
mixer
;
return
NULL
;
}
static
struct
ossp_mixer
*
find_mixer
(
pid_t
pgrp
)
{
struct
ossp_mixer
*
mixer
;
pthread_mutex_lock
(
&
mutex
);
mixer
=
find_mixer_locked
(
pgrp
);
pthread_mutex_unlock
(
&
mutex
);
return
mixer
;
}
static
struct
ossp_stream
*
find_os
(
unsigned
id
)
{
struct
ossp_stream
*
os
,
*
found
=
NULL
;
pthread_mutex_lock
(
&
mutex
);
list_for_each_entry
(
os
,
os_tbl_head
(
id
),
link
)
if
(
os
->
id
==
id
)
{
found
=
os
;
break
;
}
pthread_mutex_unlock
(
&
mutex
);
return
found
;
}
static
struct
ossp_stream
*
find_os_by_notify_rx
(
int
notify_rx
)
{
struct
ossp_stream
*
os
,
*
found
=
NULL
;
pthread_mutex_lock
(
&
mutex
);
list_for_each_entry
(
os
,
os_notify_tbl_head
(
notify_rx
),
notify_link
)
if
(
os
->
notify_rx
==
notify_rx
)
{
found
=
os
;
break
;
}
pthread_mutex_unlock
(
&
mutex
);
return
found
;
}
/***************************************************************************
* Command and ioctl helpers
*/
static
ssize_t
exec_cmd_intern
(
struct
ossp_stream
*
os
,
enum
ossp_opcode
opcode
,
const
void
*
carg
,
size_t
carg_size
,
const
void
*
din
,
size_t
din_size
,
void
*
rarg
,
size_t
rarg_size
,
void
*
dout
,
size_t
*
dout_sizep
,
int
fd
)
{
size_t
dout_size
=
dout_sizep
?
*
dout_sizep
:
0
;
struct
ossp_cmd
cmd
=
{
.
magic
=
OSSP_CMD_MAGIC
,
.
opcode
=
opcode
,
.
din_size
=
din_size
,
.
dout_size
=
dout_size
};
struct
iovec
iov
=
{
&
cmd
,
sizeof
(
cmd
)
};
struct
msghdr
msg
=
{
.
msg_iov
=
&
iov
,
.
msg_iovlen
=
1
};
struct
ossp_reply
reply
=
{
};
char
cmsg_buf
[
CMSG_SPACE
(
sizeof
(
fd
))];
char
reason
[
512
];
int
rc
;
if
(
os
->
dead
)
return
-
EIO
;
//dbg1_os(os, "opcode %s=%d carg=%zu din=%zu rarg=%zu dout=%zu",
//ossp_cmd_str[opcode], opcode, carg_size, din_size, rarg_size,
//dout_size);
#ifndef GIOVANNI
memset
(
dout
,
255
,
dout_size
);
memset
(
din
,
255
,
din_size
);
#define GIOVA_BLK 3840
#define GIOVA_SLEEP 40000
switch
(
opcode
){
case
1
:
//OPEN
reply
.
result
=
0
;
break
;
case
2
:
//READ
usleep
((
GIOVA_SLEEP
/
GIOVA_BLK
)
*
*
dout_sizep
);
reply
.
result
=
*
dout_sizep
;
break
;
case
3
:
//WRITE
usleep
((
GIOVA_SLEEP
/
GIOVA_BLK
)
*
din_size
);
reply
.
result
=
din_size
;
break
;
case
9
:
//POST
reply
.
result
=
-
32
;
break
;
case
13
:
//GET_BLKSIZE
reply
.
result
=
0
;
*
(
int
*
)
rarg
=
GIOVA_BLK
;
break
;
case
14
:
//GET_FORMATS
reply
.
result
=
0
;
*
(
int
*
)
rarg
=
28731
;
break
;
case
15
:
//SET_RATE
reply
.
result
=
0
;
*
(
int
*
)
rarg
=
*
(
int
*
)
carg
;
break
;
case
16
:
//SET_CHANNELS
reply
.
result
=
0
;
*
(
int
*
)
rarg
=
*
(
int
*
)
carg
;
break
;
case
17
:
//SET_FORMAT
reply
.
result
=
0
;
*
(
int
*
)
rarg
=
*
(
int
*
)
carg
;
break
;
case
19
:
//SET_FRAGMENT
reply
.
result
=
0
;
break
;
default:
reply
.
result
=
0
;
break
;
}
#endif // GIOVANNI
#ifdef GIOVANNI
if
(
fd
>=
0
)
{
struct
cmsghdr
*
cmsg
;
msg
.
msg_control
=
cmsg_buf
;
msg
.
msg_controllen
=
sizeof
(
cmsg_buf
);
cmsg
=
CMSG_FIRSTHDR
(
&
msg
);
cmsg
->
cmsg_level
=
SOL_SOCKET
;
cmsg
->
cmsg_type
=
SCM_RIGHTS
;
cmsg
->
cmsg_len
=
CMSG_LEN
(
sizeof
(
fd
));
*
(
int
*
)
CMSG_DATA
(
cmsg
)
=
fd
;
msg
.
msg_controllen
=
cmsg
->
cmsg_len
;
}
if
(
sendmsg
(
os
->
cmd_fd
,
&
msg
,
0
)
<=
0
)
{
rc
=
-
errno
;
snprintf
(
reason
,
sizeof
(
reason
),
"command sendmsg failed: %s"
,
strerror
(
-
rc
));
goto
fail
;
}
if
((
rc
=
write_fill
(
os
->
cmd_fd
,
carg
,
carg_size
))
<
0
||
(
rc
=
write_fill
(
os
->
cmd_fd
,
din
,
din_size
))
<
0
)
{
snprintf
(
reason
,
sizeof
(
reason
),
"can't tranfer command argument and/or data: %s"
,
strerror
(
-
rc
));
goto
fail
;
}
if
((
rc
=
read_fill
(
os
->
cmd_fd
,
&
reply
,
sizeof
(
reply
)))
<
0
)
{
snprintf
(
reason
,
sizeof
(
reason
),
"can't read reply: %s"
,
strerror
(
-
rc
));
goto
fail
;
}
if
(
reply
.
magic
!=
OSSP_REPLY_MAGIC
)
{
snprintf
(
reason
,
sizeof
(
reason
),
"reply magic mismatch %x != %x"
,
reply
.
magic
,
OSSP_REPLY_MAGIC
);
rc
=
-
EINVAL
;
goto
fail
;
}
if
(
reply
.
result
<
0
)
goto
out_unlock
;
if
(
reply
.
dout_size
>
dout_size
)
{
snprintf
(
reason
,
sizeof
(
reason
),
"data out size overflow %zu > %zu"
,
reply
.
dout_size
,
dout_size
);
rc
=
-
EINVAL
;
goto
fail
;
}
dout_size
=
reply
.
dout_size
;
if
(
dout_sizep
)
*
dout_sizep
=
dout_size
;
if
((
rc
=
read_fill
(
os
->
cmd_fd
,
rarg
,
rarg_size
))
<
0
||
(
rc
=
read_fill
(
os
->
cmd_fd
,
dout
,
dout_size
))
<
0
)
{
snprintf
(
reason
,
sizeof
(
reason
),
"can't read data out: %s"
,
strerror
(
-
rc
));
goto
fail
;
}
#endif // GIOVANNI
out_unlock:
//dbg1_os(os, " completed, result=%d dout=%zu",
//reply.result, dout_size);
//if(rarg)
//dbg1_os(os, " 2 %s=%d completed, result=%d dout=%zu carg=%d rarg=%d", ossp_cmd_str[opcode], opcode,
//reply.result, dout_size, carg ? *(int *) carg : 666, *(int *)rarg);
return
reply
.
result
;
fail:
warn_os
(
os
,
"communication with slave failed (%s)"
,
reason
);
os
->
dead
=
1
;
return
rc
;
}
static
ssize_t
exec_cmd
(
struct
ossp_stream
*
os
,
enum
ossp_opcode
opcode
,
const
void
*
carg
,
size_t
carg_size
,
const
void
*
din
,
size_t
din_size
,
void
*
rarg
,
size_t
rarg_size
,
void
*
dout
,
size_t
*
dout_sizep
,
int
fd
)
{
int
is_mixer
;
int
i
,
j
;
ssize_t
ret
,
mret
;
/* mixer command is handled exlicitly below */
is_mixer
=
opcode
==
OSSP_MIXER
;
if
(
is_mixer
)
{
ret
=
-
pthread_mutex_trylock
(
&
os
->
cmd_mutex
);
if
(
ret
)
return
ret
;
}
else
{
pthread_mutex_lock
(
&
os
->
cmd_mutex
);
ret
=
exec_cmd_intern
(
os
,
opcode
,
carg
,
carg_size
,
din
,
din_size
,
rarg
,
rarg_size
,
dout
,
dout_sizep
,
fd
);
}
/* lazy mixer handling */
pthread_mutex_lock
(
&
mixer_mutex
);
if
(
os
->
mixer_pending
)
{
struct
ossp_mixer_arg
marg
;
repeat_mixer:
/* we have mixer command pending */
memcpy
(
marg
.
vol
,
os
->
vol_set
,
sizeof
(
os
->
vol_set
));
memset
(
os
->
vol_set
,
-
1
,
sizeof
(
os
->
vol_set
));
pthread_mutex_unlock
(
&
mixer_mutex
);
mret
=
exec_cmd_intern
(
os
,
OSSP_MIXER
,
&
marg
,
sizeof
(
marg
),
NULL
,
0
,
&
marg
,
sizeof
(
marg
),
NULL
,
NULL
,
-
1
);
pthread_mutex_lock
(
&
mixer_mutex
);
/* was there mixer set request while executing mixer command? */
for_each_vol
(
i
,
j
)
if
(
os
->
vol_set
[
i
][
j
]
>=
0
)
goto
repeat_mixer
;
/* update internal mixer state */
if
(
mret
==
0
)
{
for_each_vol
(
i
,
j
)
{
if
(
marg
.
vol
[
i
][
j
]
>=
0
)
{
if
(
os
->
vol
[
i
][
j
]
!=
marg
.
vol
[
i
][
j
])
os
->
mixer
->
modify_counter
++
;
os
->
vol
[
i
][
j
]
=
marg
.
vol
[
i
][
j
];
}
}
}
os
->
mixer_pending
=
0
;
}
pthread_mutex_unlock
(
&
os
->
cmd_mutex
);
/*
* mixer mutex must be released after cmd_mutex so that
* exec_mixer_cmd() can guarantee that mixer_pending flags
* will be handled immediately or when the currently
* in-progress command completes.
*/
pthread_mutex_unlock
(
&
mixer_mutex
);
return
is_mixer
?
mret
:
ret
;
}
static
ssize_t
exec_simple_cmd
(
struct
ossp_stream
*
os
,
enum
ossp_opcode
opcode
,
void
*
carg
,
void
*
rarg
)
{
return
exec_cmd
(
os
,
opcode
,
carg
,
ossp_arg_sizes
[
opcode
].
carg_size
,
NULL
,
0
,
rarg
,
ossp_arg_sizes
[
opcode
].
rarg_size
,
NULL
,
NULL
,
-
1
);
}
static
int
ioctl_prep_uarg
(
fuse_req_t
req
,
void
*
in
,
size_t
in_sz
,
void
*
out
,
size_t
out_sz
,
void
*
uarg
,
const
void
*
in_buf
,
size_t
in_bufsz
,
size_t
out_bufsz
)
{
struct
iovec
in_iov
=
{
},
out_iov
=
{
};
int
retry
=
0
;
if
(
in
)
{
if
(
!
in_bufsz
)
{
in_iov
.
iov_base
=
uarg
;
in_iov
.
iov_len
=
in_sz
;
retry
=
1
;
}
else
{
assert
(
in_bufsz
==
in_sz
);
memcpy
(
in
,
in_buf
,
in_sz
);
}
}
if
(
out
)
{
if
(
!
out_bufsz
)
{
out_iov
.
iov_base
=
uarg
;
out_iov
.
iov_len
=
out_sz
;
retry
=
1
;
}
else
assert
(
out_bufsz
==
out_sz
);
}
if
(
retry
)
fuse_reply_ioctl_retry
(
req
,
&
in_iov
,
1
,
&
out_iov
,
1
);
return
retry
;
}
#define PREP_UARG(inp, outp) do { \
if (ioctl_prep_uarg(req, (inp), sizeof(*(inp)), \
(outp), sizeof(*(outp)), uarg, \
in_buf, in_bufsz, out_bufsz)) \
return; \
} while (0)
#define IOCTL_RETURN(result, outp) do { \
if ((outp) != NULL) \
fuse_reply_ioctl(req, result, (outp), sizeof(*(outp))); \
else \
fuse_reply_ioctl(req, result, NULL, 0); \
return; \
} while (0)
/***************************************************************************
* Mixer implementation
*/
static
void
put_mixer_real
(
struct
ossp_mixer
*
mixer
)
{
if
(
!--
mixer
->
refcnt
)
{
dbg0
(
"DESTROY mixer(%d)"
,
mixer
->
pgrp
);
list_del_init
(
&
mixer
->
link
);
list_del_init
(
&
mixer
->
delayed_put_link
);
free
(
mixer
);
nr_mixers
--
;
/*
* If exit_on_idle, mixer for pgrp0 is touched during
* init and each stream has mixer attached. As mixers
* are destroyed after they have been idle for
* MIXER_PUT_DELAY seconds, we can use it for idle
* detection. Note that this might race with
* concurrent open. The race is inherent.
*/
if
(
exit_on_idle
&&
!
nr_mixers
)
{
info
(
"idle, exiting"
);
exit
(
0
);
}
}
}
static
struct
ossp_mixer
*
get_mixer
(
pid_t
pgrp
)
{
struct
ossp_mixer
*
mixer
;
pthread_mutex_lock
(
&
mutex
);
/* is there a matching one? */
mixer
=
find_mixer_locked
(
pgrp
);
if
(
mixer
)
{
if
(
list_empty
(
&
mixer
->
delayed_put_link
))
mixer
->
refcnt
++
;
else
list_del_init
(
&
mixer
->
delayed_put_link
);
goto
out_unlock
;
}
/* reap delayed put list if there are too many mixers */
while
(
nr_mixers
>
2
*
max_streams
&&
!
list_empty
(
&
mixer_delayed_put_head
))
{
struct
ossp_mixer
*
mixer
=
list_first_entry
(
&
mixer_delayed_put_head
,
struct
ossp_mixer
,
delayed_put_link
);
assert
(
mixer
->
refcnt
==
1
);
put_mixer_real
(
mixer
);
}
/* create a new one */
mixer
=
calloc
(
1
,
sizeof
(
*
mixer
));
if
(
!
mixer
)
{
warn
(
"failed to allocate mixer for %d"
,
pgrp
);
mixer
=
NULL
;
goto
out_unlock
;
}
mixer
->
pgrp
=
pgrp
;
INIT_LIST_HEAD
(
&
mixer
->
link
);
INIT_LIST_HEAD
(
&
mixer
->
delayed_put_link
);
mixer
->
refcnt
=
1
;
memset
(
mixer
->
vol
,
-
1
,
sizeof
(
mixer
->
vol
));
list_add
(
&
mixer
->
link
,
mixer_tbl_head
(
pgrp
));
nr_mixers
++
;
dbg0
(
"CREATE mixer(%d)"
,
pgrp
);
out_unlock:
pthread_mutex_unlock
(
&
mutex
);
return
mixer
;
}
static
void
put_mixer
(
struct
ossp_mixer
*
mixer
)
{
pthread_mutex_lock
(
&
mutex
);
if
(
mixer
)
{
if
(
mixer
->
refcnt
==
1
)
{
struct
timespec
ts
;
clock_gettime
(
CLOCK_REALTIME
,
&
ts
);
mixer
->
put_expires
=
ts
.
tv_sec
+
MIXER_PUT_DELAY
;
list_add_tail
(
&
mixer
->
delayed_put_link
,
&
mixer_delayed_put_head
);
pthread_cond_signal
(
&
mixer_delayed_put_cond
);
}
else
put_mixer_real
(
mixer
);
}
pthread_mutex_unlock
(
&
mutex
);
}
static
void
*
mixer_delayed_put_worker
(
void
*
arg
)
{
struct
ossp_mixer
*
mixer
;
struct
timespec
ts
;
time_t
now
;
pthread_mutex_lock
(
&
mutex
);
again:
clock_gettime
(
CLOCK_REALTIME
,
&
ts
);
now
=
ts
.
tv_sec
;
mixer
=
NULL
;
while
(
!
list_empty
(
&
mixer_delayed_put_head
))
{
mixer
=
list_first_entry
(
&
mixer_delayed_put_head
,
struct
ossp_mixer
,
delayed_put_link
);
if
(
now
<=
mixer
->
put_expires
)
break
;
assert
(
mixer
->
refcnt
==
1
);
put_mixer_real
(
mixer
);
mixer
=
NULL
;
}
if
(
mixer
)
{
ts
.
tv_sec
=
mixer
->
put_expires
+
1
;
pthread_cond_timedwait
(
&
mixer_delayed_put_cond
,
&
mutex
,
&
ts
);
}
else
pthread_cond_wait
(
&
mixer_delayed_put_cond
,
&
mutex
);
goto
again
;
}
static
void
init_mixer_cmd
(
struct
ossp_mixer_cmd
*
mxcmd
,
struct
ossp_mixer
*
mixer
)
{
memset
(
mxcmd
,
0
,
sizeof
(
*
mxcmd
));
memset
(
&
mxcmd
->
set
.
vol
,
-
1
,
sizeof
(
mxcmd
->
set
.
vol
));
mxcmd
->
mixer
=
mixer
;
mxcmd
->
out_dir
=
-
1
;
}
static
int
exec_mixer_cmd
(
struct
ossp_mixer_cmd
*
mxcmd
,
struct
ossp_stream
*
os
)
{
int
i
,
j
,
rc
;
/*
* Set pending flags before trying to execute mixer command.
* Combined with lock release order in exec_cmd(), this
* guarantees that the mixer command will be executed
* immediately or when the current command completes.
*/
pthread_mutex_lock
(
&
mixer_mutex
);
os
->
mixer_pending
=
1
;
for_each_vol
(
i
,
j
)
if
(
mxcmd
->
set
.
vol
[
i
][
j
]
>=
0
)
os
->
vol_set
[
i
][
j
]
=
mxcmd
->
set
.
vol
[
i
][
j
];
pthread_mutex_unlock
(
&
mixer_mutex
);
rc
=
exec_simple_cmd
(
os
,
OSSP_MIXER
,
NULL
,
NULL
);
if
(
rc
>=
0
)
{
dbg0_os
(
os
,
"volume set=%d/%d:%d/%d get=%d/%d:%d/%d"
,
mxcmd
->
set
.
vol
[
PLAY
][
LEFT
],
mxcmd
->
set
.
vol
[
PLAY
][
RIGHT
],
mxcmd
->
set
.
vol
[
REC
][
LEFT
],
mxcmd
->
set
.
vol
[
REC
][
RIGHT
],
os
->
vol
[
PLAY
][
LEFT
],
os
->
vol
[
PLAY
][
RIGHT
],
os
->
vol
[
REC
][
LEFT
],
os
->
vol
[
REC
][
RIGHT
]);
}
else
if
(
rc
!=
-
EBUSY
)
warn_ose
(
os
,
rc
,
"mixer command failed"
);
return
rc
;
}
static
void
finish_mixer_cmd
(
struct
ossp_mixer_cmd
*
mxcmd
)
{
struct
ossp_mixer
*
mixer
=
mxcmd
->
mixer
;
struct
ossp_stream
*
os
;
int
dir
=
mxcmd
->
out_dir
;
int
vol
[
2
][
2
]
=
{
};
int
cnt
[
2
][
2
]
=
{
};
int
i
,
j
;
pthread_mutex_lock
(
&
mixer_mutex
);
/* get volume of all streams attached to this mixer */
pthread_mutex_lock
(
&
mutex
);
list_for_each_entry
(
os
,
os_pgrp_tbl_head
(
mixer
->
pgrp
),
pgrp_link
)
{
if
(
os
->
pgrp
!=
mixer
->
pgrp
)
continue
;
for_each_vol
(
i
,
j
)
{
if
(
os
->
vol
[
i
][
j
]
<
0
)
continue
;
vol
[
i
][
j
]
+=
os
->
vol
[
i
][
j
];
cnt
[
i
][
j
]
++
;
}
}
pthread_mutex_unlock
(
&
mutex
);
/* calculate the summary volume values */
for_each_vol
(
i
,
j
)
{
if
(
mxcmd
->
set
.
vol
[
i
][
j
]
>=
0
)
vol
[
i
][
j
]
=
mxcmd
->
set
.
vol
[
i
][
j
];
else
if
(
cnt
[
i
][
j
])
vol
[
i
][
j
]
=
vol
[
i
][
j
]
/
cnt
[
i
][
j
];
else
if
(
mixer
->
vol
[
i
][
j
]
>=
0
)
vol
[
i
][
j
]
=
mixer
->
vol
[
i
][
j
];
else
vol
[
i
][
j
]
=
100
;
vol
[
i
][
j
]
=
min
(
max
(
0
,
vol
[
i
][
j
]),
100
);
}
if
(
dir
>=
0
)
mxcmd
->
rvol
=
vol
[
dir
][
LEFT
]
|
(
vol
[
dir
][
RIGHT
]
<<
8
);
pthread_mutex_unlock
(
&
mixer_mutex
);
}
static
void
mixer_simple_ioctl
(
fuse_req_t
req
,
struct
ossp_mixer
*
mixer
,
unsigned
cmd
,
void
*
uarg
,
const
void
*
in_buf
,
size_t
in_bufsz
,
size_t
out_bufsz
,
int
*
not_minep
)
{
const
char
*
id
=
"OSS Proxy"
,
*
name
=
"Mixer"
;
int
i
;
switch
(
cmd
)
{
case
SOUND_MIXER_INFO
:
{
struct
mixer_info
info
=
{
};
PREP_UARG
(
NULL
,
&
info
);
strncpy
(
info
.
id
,
id
,
sizeof
(
info
.
id
)
-
1
);
strncpy
(
info
.
name
,
name
,
sizeof
(
info
.
name
)
-
1
);
info
.
modify_counter
=
mixer
->
modify_counter
;
IOCTL_RETURN
(
0
,
&
info
);
}
case
SOUND_OLD_MIXER_INFO
:
{
struct
_old_mixer_info
info
=
{
};
PREP_UARG
(
NULL
,
&
info
);
strncpy
(
info
.
id
,
id
,
sizeof
(
info
.
id
)
-
1
);
strncpy
(
info
.
name
,
name
,
sizeof
(
info
.
name
)
-
1
);
IOCTL_RETURN
(
0
,
&
info
);
}
case
OSS_GETVERSION
:
i
=
SNDRV_OSS_VERSION
;
goto
puti
;
case
SOUND_MIXER_READ_DEVMASK
:
case
SOUND_MIXER_READ_STEREODEVS
:
i
=
SOUND_MASK_PCM
|
SOUND_MASK_IGAIN
;
goto
puti
;
case
SOUND_MIXER_READ_CAPS
:
i
=
SOUND_CAP_EXCL_INPUT
;
goto
puti
;
case
SOUND_MIXER_READ_RECMASK
:
case
SOUND_MIXER_READ_RECSRC
:
i
=
SOUND_MASK_IGAIN
;
goto
puti
;
puti:
PREP_UARG
(
NULL
,
&
i
);
IOCTL_RETURN
(
0
,
&
i
);
case
SOUND_MIXER_WRITE_RECSRC
:
IOCTL_RETURN
(
0
,
NULL
);
default:
*
not_minep
=
1
;
return
;
}
assert
(
0
);
}
static
void
mixer_do_ioctl
(
fuse_req_t
req
,
struct
ossp_mixer
*
mixer
,
unsigned
cmd
,
void
*
uarg
,
const
void
*
in_buf
,
size_t
in_bufsz
,
size_t
out_bufsz
)
{
struct
ossp_mixer_cmd
mxcmd
;
struct
ossp_stream
*
os
,
**
osa
;
int
not_mine
=
0
;
int
slot
=
cmd
&
0xff
,
dir
;
int
nr_os
;
int
i
,
rc
;
mixer_simple_ioctl
(
req
,
mixer
,
cmd
,
uarg
,
in_buf
,
in_bufsz
,
out_bufsz
,
&
not_mine
);
if
(
!
not_mine
)
return
;
rc
=
-
ENXIO
;
if
(
!
(
cmd
&
(
SIOC_IN
|
SIOC_OUT
)))
goto
err
;
/*
* Okay, it's not one of the easy ones. Build mxcmd for
* actual volume control.
*/
if
(
cmd
&
SIOC_IN
)
PREP_UARG
(
&
i
,
&
i
);
else
PREP_UARG
(
NULL
,
&
i
);
switch
(
slot
)
{
case
SOUND_MIXER_PCM
:
dir
=
PLAY
;
break
;
case
SOUND_MIXER_IGAIN
:
dir
=
REC
;
break
;
default:
i
=
0
;
IOCTL_RETURN
(
0
,
&
i
);
}
init_mixer_cmd
(
&
mxcmd
,
mixer
);
if
(
cmd
&
SIOC_IN
)
{
unsigned
l
,
r
;
rc
=
-
EINVAL
;
l
=
i
&
0xff
;
r
=
(
i
>>
8
)
&
0xff
;
if
(
l
>
100
||
r
>
100
)
goto
err
;
mixer
->
vol
[
dir
][
LEFT
]
=
mxcmd
.
set
.
vol
[
dir
][
LEFT
]
=
l
;
mixer
->
vol
[
dir
][
RIGHT
]
=
mxcmd
.
set
.
vol
[
dir
][
RIGHT
]
=
r
;
}
mxcmd
.
out_dir
=
dir
;
/*
* Apply volume conrol
*/
/* acquire target streams */
pthread_mutex_lock
(
&
mutex
);
osa
=
calloc
(
max_streams
,
sizeof
(
osa
[
0
]));
if
(
!
osa
)
{
pthread_mutex_unlock
(
&
mutex
);
rc
=
-
ENOMEM
;
goto
err
;
}
nr_os
=
0
;
list_for_each_entry
(
os
,
os_pgrp_tbl_head
(
mixer
->
pgrp
),
pgrp_link
)
{
if
(
os
->
pgrp
==
mixer
->
pgrp
)
{
osa
[
nr_os
++
]
=
os
;
os
->
refcnt
++
;
}
}
pthread_mutex_unlock
(
&
mutex
);
/* execute mxcmd for each stream and put it */
for
(
i
=
0
;
i
<
nr_os
;
i
++
)
{
exec_mixer_cmd
(
&
mxcmd
,
osa
[
i
]);
put_os
(
osa
[
i
]);
}
finish_mixer_cmd
(
&
mxcmd
);
free
(
osa
);
IOCTL_RETURN
(
0
,
out_bufsz
?
&
mxcmd
.
rvol
:
NULL
);
err:
fuse_reply_err
(
req
,
-
rc
);
}
static
void
mixer_open
(
fuse_req_t
req
,
struct
fuse_file_info
*
fi
)
{
pid_t
pid
=
fuse_req_ctx
(
req
)
->
pid
,
pgrp
;
struct
ossp_mixer
*
mixer
;
int
rc
;
rc
=
get_proc_self_info
(
pid
,
&
pgrp
,
NULL
,
0
);
if
(
rc
)
{
err_e
(
rc
,
"get_proc_self_info(%d) failed"
,
pid
);
fuse_reply_err
(
req
,
-
rc
);
return
;
}
mixer
=
get_mixer
(
pgrp
);
fi
->
fh
=
pgrp
;
if
(
mixer
)
fuse_reply_open
(
req
,
fi
);
else
fuse_reply_err
(
req
,
ENOMEM
);
}
static
void
mixer_ioctl
(
fuse_req_t
req
,
int
signed_cmd
,
void
*
uarg
,
struct
fuse_file_info
*
fi
,
unsigned
int
flags
,
const
void
*
in_buf
,
size_t
in_bufsz
,
size_t
out_bufsz
)
{
struct
ossp_mixer
*
mixer
;
mixer
=
find_mixer
(
fi
->
fh
);
if
(
!
mixer
)
{
fuse_reply_err
(
req
,
EBADF
);
return
;
}
mixer_do_ioctl
(
req
,
mixer
,
signed_cmd
,
uarg
,
in_buf
,
in_bufsz
,
out_bufsz
);
}
static
void
mixer_release
(
fuse_req_t
req
,
struct
fuse_file_info
*
fi
)
{
struct
ossp_mixer
*
mixer
;
mixer
=
find_mixer
(
fi
->
fh
);
if
(
mixer
)
{
put_mixer
(
mixer
);
fuse_reply_err
(
req
,
0
);
}
else
fuse_reply_err
(
req
,
EBADF
);
}
/***************************************************************************
* Stream implementation
*/
static
int
alloc_os
(
size_t
stream_size
,
size_t
mmap_size
,
pid_t
pid
,
uid_t
pgrp
,
uid_t
uid
,
gid_t
gid
,
int
cmd_sock
,
const
int
*
notify
,
struct
fuse_session
*
se
,
struct
ossp_stream
**
osp
)
{
struct
ossp_uid_cnt
*
tmp_ucnt
,
*
ucnt
=
NULL
;
struct
ossp_stream
*
os
;
int
rc
;
assert
(
stream_size
>=
sizeof
(
struct
ossp_stream
));
os
=
calloc
(
1
,
stream_size
);
if
(
!
os
)
return
-
ENOMEM
;
INIT_LIST_HEAD
(
&
os
->
link
);
INIT_LIST_HEAD
(
&
os
->
pgrp_link
);
INIT_LIST_HEAD
(
&
os
->
notify_link
);
os
->
refcnt
=
1
;
rc
=
-
pthread_mutex_init
(
&
os
->
cmd_mutex
,
NULL
);
if
(
rc
)
goto
err_free
;
rc
=
-
pthread_mutex_init
(
&
os
->
mmap_mutex
,
NULL
);
if
(
rc
)
goto
err_destroy_cmd_mutex
;
pthread_mutex_lock
(
&
mutex
);
list_for_each_entry
(
tmp_ucnt
,
&
uid_cnt_list
,
link
)
if
(
tmp_ucnt
->
uid
==
uid
)
{
ucnt
=
tmp_ucnt
;
break
;
}
if
(
!
ucnt
)
{
rc
=
-
ENOMEM
;
ucnt
=
calloc
(
1
,
sizeof
(
*
ucnt
));
if
(
!
ucnt
)
goto
err_unlock
;
ucnt
->
uid
=
uid
;
list_add
(
&
ucnt
->
link
,
&
uid_cnt_list
);
}
rc
=
-
EBUSY
;
if
(
ucnt
->
nr_os
+
1
>
umax_streams
)
goto
err_unlock
;
/* everything looks fine, allocate id and init stream */
rc
=
-
EBUSY
;
os
->
id
=
find_next_zero_bit
(
os_id_bitmap
,
max_streams
,
0
);
if
(
os
->
id
>=
max_streams
)
goto
err_unlock
;
__set_bit
(
os
->
id
,
os_id_bitmap
);
os
->
cmd_fd
=
cmd_sock
;
os
->
notify_tx
=
notify
[
1
];
os
->
notify_rx
=
notify
[
0
];
os
->
pid
=
pid
;
os
->
pgrp
=
pgrp
;
os
->
uid
=
uid
;
os
->
gid
=
gid
;
if
(
mmap_size
)
{
os
->
mmap_off
=
os
->
id
*
mmap_size
;
os
->
mmap_size
=
mmap_size
;
}
os
->
ucnt
=
ucnt
;
os
->
se
=
se
;
memset
(
os
->
vol
,
-
1
,
sizeof
(
os
->
vol
));
memset
(
os
->
vol_set
,
-
1
,
sizeof
(
os
->
vol
));
list_add
(
&
os
->
link
,
os_tbl_head
(
os
->
id
));
list_add
(
&
os
->
pgrp_link
,
os_pgrp_tbl_head
(
os
->
pgrp
));
ucnt
->
nr_os
++
;
*
osp
=
os
;
pthread_mutex_unlock
(
&
mutex
);
return
0
;
err_unlock:
pthread_mutex_unlock
(
&
mutex
);
pthread_mutex_destroy
(
&
os
->
mmap_mutex
);
err_destroy_cmd_mutex:
pthread_mutex_destroy
(
&
os
->
cmd_mutex
);
err_free:
free
(
os
);
return
rc
;
}
static
void
shutdown_notification
(
struct
ossp_stream
*
os
)
{
struct
ossp_notify
obituary
=
{
.
magic
=
OSSP_NOTIFY_MAGIC
,
.
opcode
=
OSSP_NOTIFY_OBITUARY
};
ssize_t
ret
;
/*
* Shutdown notification for this stream. We politely ask
* notify_poller to shut the receive side down to avoid racing
* with it.
*/
while
(
os
->
notify_rx
>=
0
)
{
ret
=
write
(
os
->
notify_tx
,
&
obituary
,
sizeof
(
obituary
));
if
(
ret
<=
0
)
{
if
(
ret
==
0
)
warn_os
(
os
,
"unexpected EOF on notify_tx"
);
else
if
(
errno
!=
EPIPE
)
warn_ose
(
os
,
-
errno
,
"unexpected error on notify_tx"
);
close
(
os
->
notify_rx
);
os
->
notify_rx
=
-
1
;
break
;
}
if
(
ret
!=
sizeof
(
obituary
))
warn_os
(
os
,
"short transfer on notify_tx"
);
pthread_cond_wait
(
&
notify_poller_kill_wait
,
&
mutex
);
}
}
static
void
put_os
(
struct
ossp_stream
*
os
)
{
if
(
!
os
)
return
;
pthread_mutex_lock
(
&
mutex
);
assert
(
os
->
refcnt
);
if
(
--
os
->
refcnt
)
{
pthread_mutex_unlock
(
&
mutex
);
return
;
}
os
->
dead
=
1
;
shutdown_notification
(
os
);
dbg0_os
(
os
,
"DESTROY"
);
list_del_init
(
&
os
->
link
);
list_del_init
(
&
os
->
pgrp_link
);
list_del_init
(
&
os
->
notify_link
);
os
->
ucnt
->
nr_os
--
;
pthread_mutex_unlock
(
&
mutex
);
close
(
os
->
cmd_fd
);
close
(
os
->
notify_tx
);
put_mixer
(
os
->
mixer
);
pthread_mutex_destroy
(
&
os
->
cmd_mutex
);
pthread_mutex_destroy
(
&
os
->
mmap_mutex
);
pthread_mutex_lock
(
&
mutex
);
dbg1_os
(
os
,
"stream dead, requesting reaping"
);
list_add_tail
(
&
os
->
link
,
&
slave_corpse_list
);
pthread_cond_signal
(
&
slave_reaper_wait
);
pthread_mutex_unlock
(
&
mutex
);
}
static
void
set_extra_env
(
pid_t
pid
)
{
char
procenviron
[
32
];
const
int
step
=
1024
;
char
*
data
=
malloc
(
step
+
1
);
int
ofs
=
0
;
int
fd
;
int
ret
;
if
(
!
data
)
return
;
sprintf
(
procenviron
,
"/proc/%d/environ"
,
pid
);
fd
=
open
(
procenviron
,
O_RDONLY
);
if
(
fd
<
0
)
return
;
/*
* There should really be a 'read whole file to a newly allocated
* buffer' function.
*/
while
((
ret
=
read
(
fd
,
data
+
ofs
,
step
))
>
0
)
{
char
*
newdata
;
ofs
+=
ret
;
newdata
=
realloc
(
data
,
ofs
+
step
+
1
);
if
(
!
newdata
)
{
ret
=
-
1
;
break
;
}
data
=
newdata
;
}
if
(
ret
==
0
)
{
char
*
ptr
=
data
;
/* Append the extra 0 for end condition */
data
[
ofs
]
=
0
;
while
((
ret
=
strlen
(
ptr
))
>
0
)
{
/*
* Copy all PULSE variables and DISPLAY so that
* ssh -X remotehost 'mplayer -ao oss' will work
*/
if
(
!
strncmp
(
ptr
,
"DISPLAY="
,
8
)
||
!
strncmp
(
ptr
,
"PULSE_"
,
6
))
putenv
(
ptr
);
ptr
+=
ret
+
1
;
}
}
free
(
data
);
close
(
fd
);
}
#ifndef GIOVANNI
int
contapid
=
13000
;
#endif// GIOVANNI
static
int
create_os
(
const
char
*
slave_path
,
size_t
stream_size
,
size_t
mmap_size
,
pid_t
pid
,
pid_t
pgrp
,
uid_t
uid
,
gid_t
gid
,
struct
fuse_session
*
se
,
struct
ossp_stream
**
osp
)
{
static
pthread_mutex_t
create_mutex
=
PTHREAD_MUTEX_INITIALIZER
;
int
cmd_sock
[
2
]
=
{
-
1
,
-
1
};
int
notify_sock
[
2
]
=
{
-
1
,
-
1
};
struct
ossp_stream
*
os
=
NULL
;
struct
epoll_event
ev
=
{
};
int
i
,
rc
;
/*
* Only one thread can be creating a stream. This is to avoid
* leaking unwanted fds into slaves.
*/
pthread_mutex_lock
(
&
create_mutex
);
/* prepare communication channels */
if
(
socketpair
(
AF_UNIX
,
SOCK_STREAM
,
0
,
cmd_sock
)
||
socketpair
(
AF_UNIX
,
SOCK_STREAM
,
0
,
notify_sock
))
{
rc
=
-
errno
;
warn_e
(
rc
,
"failed to create slave command channel"
);
goto
close_all
;
}
if
(
fcntl
(
notify_sock
[
0
],
F_SETFL
,
O_NONBLOCK
)
<
0
)
{
rc
=
-
errno
;
warn_e
(
rc
,
"failed to set NONBLOCK on notify sock"
);
goto
close_all
;
}
/*
* Alloc stream which will be responsible for all server side
* resources from now on.
*/
rc
=
alloc_os
(
stream_size
,
mmap_size
,
pid
,
pgrp
,
uid
,
gid
,
cmd_sock
[
0
],
notify_sock
,
se
,
&
os
);
if
(
rc
)
{
warn_e
(
rc
,
"failed to allocate stream for %d"
,
pid
);
goto
close_all
;
}
rc
=
-
ENOMEM
;
os
->
mixer
=
get_mixer
(
pgrp
);
if
(
!
os
->
mixer
)
goto
put_os
;
/*
* Register notification. If successful, notify_poller has
* custody of notify_rx fd.
*/
pthread_mutex_lock
(
&
mutex
);
list_add
(
&
os
->
notify_link
,
os_notify_tbl_head
(
os
->
notify_rx
));
pthread_mutex_unlock
(
&
mutex
);
#ifndef GIOVANNI
os
->
slave_pid
=
contapid
;
contapid
++
;
if
(
contapid
>
30000
)
contapid
=
13000
;
#endif //GIOVANNI
//#ifdef GIOVANNI
ev
.
events
=
EPOLLIN
;
ev
.
data
.
fd
=
notify_sock
[
0
];
if
(
epoll_ctl
(
notify_epfd
,
EPOLL_CTL_ADD
,
notify_sock
[
0
],
&
ev
))
{
/*
* Without poller watching this notify sock, poller
* shutdown sequence in shutdown_notification() can't
* be used. Kill notification rx manually.
*/
rc
=
-
errno
;
warn_ose
(
os
,
rc
,
"failed to add notify epoll"
);
close
(
os
->
notify_rx
);
os
->
notify_rx
=
-
1
;
goto
put_os
;
}
/* start slave */
os
->
slave_pid
=
fork
();
if
(
os
->
slave_pid
<
0
)
{
rc
=
-
errno
;
warn_ose
(
os
,
rc
,
"failed to fork slave"
);
goto
put_os
;
}
if
(
os
->
slave_pid
==
0
)
{
/* child */
char
id_str
[
2
][
16
],
fd_str
[
3
][
16
];
char
mmap_off_str
[
32
],
mmap_size_str
[
32
];
char
log_str
[
16
],
slave_path_copy
[
PATH_MAX
];
char
*
argv
[]
=
{
slave_path_copy
,
"-u"
,
id_str
[
0
],
"-g"
,
id_str
[
1
],
"-c"
,
fd_str
[
0
],
"-n"
,
fd_str
[
1
],
"-m"
,
fd_str
[
2
],
"-o"
,
mmap_off_str
,
"-s"
,
mmap_size_str
,
"-l"
,
log_str
,
NULL
,
NULL
};
struct
passwd
*
pwd
;
/* drop stuff we don't need */
if
(
close
(
cmd_sock
[
0
])
||
close
(
notify_sock
[
0
]))
fatal_e
(
-
errno
,
"failed to close server pipe fds"
);
#ifdef OSSP_MMAP
if
(
!
mmap_size
)
close
(
fuse_mmap_fd
(
se
));
#endif
clearenv
();
pwd
=
getpwuid
(
os
->
uid
);
if
(
pwd
)
{
setenv
(
"LOGNAME"
,
pwd
->
pw_name
,
1
);
setenv
(
"USER"
,
pwd
->
pw_name
,
1
);
setenv
(
"HOME"
,
pwd
->
pw_dir
,
1
);
}
/* Set extra environment variables from the caller */
set_extra_env
(
pid
);
/* prep and exec */
slave_path_copy
[
sizeof
(
slave_path_copy
)
-
1
]
=
'\0'
;
strncpy
(
slave_path_copy
,
slave_path
,
sizeof
(
slave_path_copy
)
-
1
);
if
(
slave_path_copy
[
sizeof
(
slave_path_copy
)
-
1
]
!=
'\0'
)
{
rc
=
-
errno
;
err_ose
(
os
,
rc
,
"slave path too long"
);
goto
child_fail
;
}
snprintf
(
id_str
[
0
],
sizeof
(
id_str
[
0
]),
"%d"
,
os
->
uid
);
snprintf
(
id_str
[
1
],
sizeof
(
id_str
[
0
]),
"%d"
,
os
->
gid
);
snprintf
(
fd_str
[
0
],
sizeof
(
fd_str
[
0
]),
"%d"
,
cmd_sock
[
1
]);
snprintf
(
fd_str
[
1
],
sizeof
(
fd_str
[
1
]),
"%d"
,
notify_sock
[
1
]);
snprintf
(
fd_str
[
2
],
sizeof
(
fd_str
[
2
]),
"%d"
,
#ifdef OSSP_MMAP
mmap_size
?
fuse_mmap_fd
(
se
)
:
#endif
-
1
);
snprintf
(
mmap_off_str
,
sizeof
(
mmap_off_str
),
"0x%llx"
,
(
unsigned
long
long
)
os
->
mmap_off
);
snprintf
(
mmap_size_str
,
sizeof
(
mmap_size_str
),
"0x%zx"
,
mmap_size
);
snprintf
(
log_str
,
sizeof
(
log_str
),
"%d"
,
ossp_log_level
);
if
(
ossp_log_timestamp
)
argv
[
ARRAY_SIZE
(
argv
)
-
2
]
=
"-t"
;
execv
(
slave_path
,
argv
);
rc
=
-
errno
;
err_ose
(
os
,
rc
,
"execv failed for <%d>"
,
pid
);
child_fail:
_exit
(
1
);
}
//#endif //GIOVANNI
/* turn on CLOEXEC on all server side fds */
if
(
fcntl
(
os
->
cmd_fd
,
F_SETFD
,
FD_CLOEXEC
)
<
0
||
fcntl
(
os
->
notify_tx
,
F_SETFD
,
FD_CLOEXEC
)
<
0
||
fcntl
(
os
->
notify_rx
,
F_SETFD
,
FD_CLOEXEC
)
<
0
)
{
rc
=
-
errno
;
err_ose
(
os
,
rc
,
"failed to set CLOEXEC on server side fds"
);
goto
put_os
;
}
dbg0_os
(
os
,
"CREATE slave=%d %s"
,
os
->
slave_pid
,
slave_path
);
dbg0_os
(
os
,
" client=%d cmd=%d:%d notify=%d:%d mmap=%d:0x%llx:%zu"
,
pid
,
cmd_sock
[
0
],
cmd_sock
[
1
],
notify_sock
[
0
],
notify_sock
[
1
],
#ifdef OSSP_MMAP
os
->
mmap_size
?
fuse_mmap_fd
(
se
)
:
#endif
-
1
,
(
unsigned
long
long
)
os
->
mmap_off
,
os
->
mmap_size
);
*
osp
=
os
;
rc
=
0
;
goto
close_client_fds
;
put_os:
put_os
(
os
);
close_client_fds:
close
(
cmd_sock
[
1
]);
pthread_mutex_unlock
(
&
create_mutex
);
return
rc
;
close_all:
for
(
i
=
0
;
i
<
2
;
i
++
)
{
close
(
cmd_sock
[
i
]);
close
(
notify_sock
[
i
]);
}
pthread_mutex_unlock
(
&
create_mutex
);
return
rc
;
}
static
void
dsp_open_common
(
fuse_req_t
req
,
struct
fuse_file_info
*
fi
,
struct
fuse_session
*
se
)
{
const
struct
fuse_ctx
*
fuse_ctx
=
fuse_req_ctx
(
req
);
struct
ossp_dsp_open_arg
arg
=
{
};
struct
ossp_stream
*
os
=
NULL
;
struct
ossp_mixer
*
mixer
;
struct
ossp_dsp_stream
*
dsps
;
struct
ossp_mixer_cmd
mxcmd
;
pid_t
pgrp
;
ssize_t
ret
;
ret
=
get_proc_self_info
(
fuse_ctx
->
pid
,
&
pgrp
,
NULL
,
0
);
if
(
ret
)
{
err_e
(
ret
,
"get_proc_self_info(%d) failed"
,
fuse_ctx
->
pid
);
goto
err
;
}
ret
=
create_os
(
dsp_slave_path
,
sizeof
(
*
dsps
),
DSPS_MMAP_SIZE
,
fuse_ctx
->
pid
,
pgrp
,
fuse_ctx
->
uid
,
fuse_ctx
->
gid
,
se
,
&
os
);
if
(
ret
)
goto
err
;
dsps
=
os_to_dsps
(
os
);
mixer
=
os
->
mixer
;
switch
(
fi
->
flags
&
O_ACCMODE
)
{
case
O_WRONLY
:
dsps
->
rw
|=
1
<<
PLAY
;
break
;
case
O_RDONLY
:
dsps
->
rw
|=
1
<<
REC
;
break
;
case
O_RDWR
:
dsps
->
rw
|=
(
1
<<
PLAY
)
|
(
1
<<
REC
);
break
;
default:
assert
(
0
);
}
arg
.
flags
=
fi
->
flags
;
arg
.
opener_pid
=
os
->
pid
;
ret
=
exec_simple_cmd
(
&
dsps
->
os
,
OSSP_DSP_OPEN
,
&
arg
,
NULL
);
if
(
ret
<
0
)
{
put_os
(
os
);
goto
err
;
}
memcpy
(
os
->
vol
,
mixer
->
vol
,
sizeof
(
os
->
vol
));
if
(
os
->
vol
[
PLAY
][
0
]
>=
0
||
os
->
vol
[
REC
][
0
]
>=
0
)
{
init_mixer_cmd
(
&
mxcmd
,
mixer
);
memcpy
(
mxcmd
.
set
.
vol
,
os
->
vol
,
sizeof
(
os
->
vol
));
exec_mixer_cmd
(
&
mxcmd
,
os
);
finish_mixer_cmd
(
&
mxcmd
);
}
fi
->
direct_io
=
1
;
fi
->
nonseekable
=
1
;
fi
->
fh
=
os
->
id
;
fuse_reply_open
(
req
,
fi
);
return
;
err:
fuse_reply_err
(
req
,
-
ret
);
}
static
void
dsp_open
(
fuse_req_t
req
,
struct
fuse_file_info
*
fi
)
{
dsp_open_common
(
req
,
fi
,
dsp_se
);
}
static
void
adsp_open
(
fuse_req_t
req
,
struct
fuse_file_info
*
fi
)
{
dsp_open_common
(
req
,
fi
,
adsp_se
);
}
static
void
dsp_release
(
fuse_req_t
req
,
struct
fuse_file_info
*
fi
)
{
struct
ossp_stream
*
os
;
os
=
find_os
(
fi
->
fh
);
if
(
os
)
{
put_os
(
os
);
fuse_reply_err
(
req
,
0
);
}
else
fuse_reply_err
(
req
,
EBADF
);
}
static
void
dsp_read
(
fuse_req_t
req
,
size_t
size
,
off_t
off
,
struct
fuse_file_info
*
fi
)
{
struct
ossp_dsp_rw_arg
arg
=
{
};
struct
ossp_stream
*
os
;
struct
ossp_dsp_stream
*
dsps
;
void
*
buf
=
NULL
;
ssize_t
ret
;
ret
=
-
EBADF
;
os
=
find_os
(
fi
->
fh
);
if
(
!
os
)
goto
out
;
dsps
=
os_to_dsps
(
os
);
ret
=
-
EINVAL
;
if
(
!
(
dsps
->
rw
&
(
1
<<
REC
)))
goto
out
;
ret
=
-
ENXIO
;
if
(
dsps
->
mmapped
)
goto
out
;
ret
=
-
ENOMEM
;
buf
=
malloc
(
size
);
if
(
!
buf
)
goto
out
;
arg
.
nonblock
=
(
fi
->
flags
&
O_NONBLOCK
)
||
dsps
->
nonblock
;
ret
=
exec_cmd
(
os
,
OSSP_DSP_READ
,
&
arg
,
sizeof
(
arg
),
NULL
,
0
,
NULL
,
0
,
buf
,
&
size
,
-
1
);
out:
if
(
ret
>=
0
)
fuse_reply_buf
(
req
,
buf
,
size
);
else
fuse_reply_err
(
req
,
-
ret
);
free
(
buf
);
}
static
void
dsp_write
(
fuse_req_t
req
,
const
char
*
buf
,
size_t
size
,
off_t
off
,
struct
fuse_file_info
*
fi
)
{
struct
ossp_dsp_rw_arg
arg
=
{
};
struct
ossp_stream
*
os
;
struct
ossp_dsp_stream
*
dsps
;
ssize_t
ret
;
ret
=
-
EBADF
;
os
=
find_os
(
fi
->
fh
);
if
(
!
os
)
goto
out
;
dsps
=
os_to_dsps
(
os
);
ret
=
-
EINVAL
;
if
(
!
(
dsps
->
rw
&
(
1
<<
PLAY
)))
goto
out
;
ret
=
-
ENXIO
;
if
(
dsps
->
mmapped
)
goto
out
;
arg
.
nonblock
=
(
fi
->
flags
&
O_NONBLOCK
)
||
dsps
->
nonblock
;
ret
=
exec_cmd
(
os
,
OSSP_DSP_WRITE
,
&
arg
,
sizeof
(
arg
),
buf
,
size
,
NULL
,
0
,
NULL
,
NULL
,
-
1
);
out:
if
(
ret
>=
0
)
fuse_reply_write
(
req
,
ret
);
else
fuse_reply_err
(
req
,
-
ret
);
}
static
void
dsp_poll
(
fuse_req_t
req
,
struct
fuse_file_info
*
fi
,
struct
fuse_pollhandle
*
ph
)
{
int
notify
=
ph
!=
NULL
;
unsigned
revents
=
0
;
struct
ossp_stream
*
os
;
ssize_t
ret
;
ret
=
-
EBADF
;
os
=
find_os
(
fi
->
fh
);
if
(
!
os
)
goto
out
;
if
(
ph
)
{
pthread_mutex_lock
(
&
mutex
);
if
(
os
->
ph
)
fuse_pollhandle_destroy
(
os
->
ph
);
os
->
ph
=
ph
;
pthread_mutex_unlock
(
&
mutex
);
}
ret
=
exec_simple_cmd
(
os
,
OSSP_DSP_POLL
,
&
notify
,
&
revents
);
out:
if
(
ret
>=
0
)
fuse_reply_poll
(
req
,
revents
);
else
fuse_reply_err
(
req
,
-
ret
);
}
static
void
dsp_ioctl
(
fuse_req_t
req
,
int
signed_cmd
,
void
*
uarg
,
struct
fuse_file_info
*
fi
,
unsigned
int
flags
,
const
void
*
in_buf
,
size_t
in_bufsz
,
size_t
out_bufsz
)
{
/* some ioctl constants are long and has the highest bit set */
unsigned
cmd
=
signed_cmd
;
struct
ossp_stream
*
os
;
struct
ossp_dsp_stream
*
dsps
;
enum
ossp_opcode
op
;
ssize_t
ret
;
int
i
;
ret
=
-
EBADF
;
os
=
find_os
(
fi
->
fh
);
if
(
!
os
)
goto
err
;
dsps
=
os_to_dsps
(
os
);
/* mixer commands are allowed on DSP devices */
if
(((
cmd
>>
8
)
&
0xff
)
==
'M'
)
{
mixer_do_ioctl
(
req
,
os
->
mixer
,
cmd
,
uarg
,
in_buf
,
in_bufsz
,
out_bufsz
);
return
;
}
/* and the rest */
switch
(
cmd
)
{
case
OSS_GETVERSION
:
i
=
SNDRV_OSS_VERSION
;
PREP_UARG
(
NULL
,
&
i
);
IOCTL_RETURN
(
0
,
&
i
);
case
SNDCTL_DSP_GETCAPS
:
i
=
DSP_CAP_DUPLEX
|
DSP_CAP_REALTIME
|
DSP_CAP_TRIGGER
|
#ifdef OSSP_MMAP
DSP_CAP_MMAP
|
#endif
DSP_CAP_MULTI
;
PREP_UARG
(
NULL
,
&
i
);
IOCTL_RETURN
(
0
,
&
i
);
case
SNDCTL_DSP_NONBLOCK
:
dsps
->
nonblock
=
1
;
ret
=
0
;
IOCTL_RETURN
(
0
,
NULL
);
case
SNDCTL_DSP_RESET
:
op
=
OSSP_DSP_RESET
;
goto
nd
;
case
SNDCTL_DSP_SYNC
:
op
=
OSSP_DSP_SYNC
;
goto
nd
;
case
SNDCTL_DSP_POST
:
op
=
OSSP_DSP_POST
;
goto
nd
;
nd:
ret
=
exec_simple_cmd
(
&
dsps
->
os
,
op
,
NULL
,
NULL
);
if
(
ret
)
goto
err
;
IOCTL_RETURN
(
0
,
NULL
);
case
SOUND_PCM_READ_RATE
:
op
=
OSSP_DSP_GET_RATE
;
goto
ri
;
case
SOUND_PCM_READ_BITS
:
op
=
OSSP_DSP_GET_FORMAT
;
goto
ri
;
case
SOUND_PCM_READ_CHANNELS
:
op
=
OSSP_DSP_GET_CHANNELS
;
goto
ri
;
case
SNDCTL_DSP_GETBLKSIZE
:
op
=
OSSP_DSP_GET_BLKSIZE
;
goto
ri
;
case
SNDCTL_DSP_GETFMTS
:
op
=
OSSP_DSP_GET_FORMATS
;
goto
ri
;
case
SNDCTL_DSP_GETTRIGGER
:
op
=
OSSP_DSP_GET_TRIGGER
;
goto
ri
;
ri:
PREP_UARG
(
NULL
,
&
i
);
ret
=
exec_simple_cmd
(
&
dsps
->
os
,
op
,
NULL
,
&
i
);
if
(
ret
)
goto
err
;
IOCTL_RETURN
(
0
,
&
i
);
case
SNDCTL_DSP_SPEED
:
op
=
OSSP_DSP_SET_RATE
;
goto
wi
;
case
SNDCTL_DSP_SETFMT
:
op
=
OSSP_DSP_SET_FORMAT
;
goto
wi
;
case
SNDCTL_DSP_CHANNELS
:
op
=
OSSP_DSP_SET_CHANNELS
;
goto
wi
;
case
SNDCTL_DSP_SUBDIVIDE
:
op
=
OSSP_DSP_SET_SUBDIVISION
;
goto
wi
;
wi:
PREP_UARG
(
&
i
,
&
i
);
ret
=
exec_simple_cmd
(
&
dsps
->
os
,
op
,
&
i
,
&
i
);
if
(
ret
)
goto
err
;
IOCTL_RETURN
(
0
,
&
i
);
case
SNDCTL_DSP_STEREO
:
PREP_UARG
(
NULL
,
&
i
);
i
=
2
;
ret
=
exec_simple_cmd
(
&
dsps
->
os
,
OSSP_DSP_SET_CHANNELS
,
&
i
,
&
i
);
i
--
;
if
(
ret
)
goto
err
;
IOCTL_RETURN
(
0
,
&
i
);
case
SNDCTL_DSP_SETFRAGMENT
:
PREP_UARG
(
&
i
,
NULL
);
ret
=
exec_simple_cmd
(
&
dsps
->
os
,
OSSP_DSP_SET_FRAGMENT
,
&
i
,
NULL
);
if
(
ret
)
goto
err
;
IOCTL_RETURN
(
0
,
NULL
);
case
SNDCTL_DSP_SETTRIGGER
:
PREP_UARG
(
&
i
,
NULL
);
ret
=
exec_simple_cmd
(
&
dsps
->
os
,
OSSP_DSP_SET_TRIGGER
,
&
i
,
NULL
);
if
(
ret
)
goto
err
;
IOCTL_RETURN
(
0
,
NULL
);
case
SNDCTL_DSP_GETOSPACE
:
case
SNDCTL_DSP_GETISPACE
:
{
struct
audio_buf_info
info
;
ret
=
-
EINVAL
;
if
(
cmd
==
SNDCTL_DSP_GETOSPACE
)
{
if
(
!
(
dsps
->
rw
&
(
1
<<
PLAY
)))
goto
err
;
op
=
OSSP_DSP_GET_OSPACE
;
}
else
{
if
(
!
(
dsps
->
rw
&
(
1
<<
REC
)))
goto
err
;
op
=
OSSP_DSP_GET_ISPACE
;
}
PREP_UARG
(
NULL
,
&
info
);
ret
=
exec_simple_cmd
(
&
dsps
->
os
,
op
,
NULL
,
&
info
);
if
(
ret
)
goto
err
;
IOCTL_RETURN
(
0
,
&
info
);
}
case
SNDCTL_DSP_GETOPTR
:
case
SNDCTL_DSP_GETIPTR
:
{
struct
count_info
info
;
op
=
cmd
==
SNDCTL_DSP_GETOPTR
?
OSSP_DSP_GET_OPTR
:
OSSP_DSP_GET_IPTR
;
PREP_UARG
(
NULL
,
&
info
);
ret
=
exec_simple_cmd
(
&
dsps
->
os
,
op
,
NULL
,
&
info
);
if
(
ret
)
goto
err
;
IOCTL_RETURN
(
0
,
&
info
);
}
case
SNDCTL_DSP_GETODELAY
:
PREP_UARG
(
NULL
,
&
i
);
i
=
0
;
ret
=
exec_simple_cmd
(
&
dsps
->
os
,
OSSP_DSP_GET_ODELAY
,
NULL
,
&
i
);
IOCTL_RETURN
(
ret
,
&
i
);
/* always copy out result, 0 on err */
case
SOUND_PCM_WRITE_FILTER
:
case
SOUND_PCM_READ_FILTER
:
ret
=
-
EIO
;
goto
err
;
case
SNDCTL_DSP_MAPINBUF
:
case
SNDCTL_DSP_MAPOUTBUF
:
ret
=
-
EINVAL
;
goto
err
;
case
SNDCTL_DSP_SETSYNCRO
:
case
SNDCTL_DSP_SETDUPLEX
:
case
SNDCTL_DSP_PROFILE
:
IOCTL_RETURN
(
0
,
NULL
);
default:
warn_os
(
os
,
"unknown ioctl 0x%x"
,
cmd
);
ret
=
-
EINVAL
;
goto
err
;
}
assert
(
0
);
/* control shouldn't reach here */
err:
fuse_reply_err
(
req
,
-
ret
);
}
#ifdef OSSP_MMAP
static
int
dsp_mmap_dir
(
int
prot
)
{
if
(
!
(
prot
&
PROT_WRITE
))
return
REC
;
return
PLAY
;
}
static
void
dsp_mmap
(
fuse_req_t
req
,
void
*
addr
,
size_t
len
,
int
prot
,
int
flags
,
off_t
offset
,
struct
fuse_file_info
*
fi
,
uint64_t
mh
)
{
int
dir
=
dsp_mmap_dir
(
prot
);
struct
ossp_dsp_mmap_arg
arg
=
{
};
struct
ossp_stream
*
os
;
struct
ossp_dsp_stream
*
dsps
;
ssize_t
ret
;
os
=
find_os
(
fi
->
fh
);
if
(
!
os
)
{
fuse_reply_err
(
req
,
EBADF
);
return
;
}
dsps
=
os_to_dsps
(
os
);
if
(
!
os
->
mmap_off
||
len
>
os
->
mmap_size
/
2
)
{
fuse_reply_err
(
req
,
EINVAL
);
return
;
}
pthread_mutex_lock
(
&
os
->
mmap_mutex
);
ret
=
-
EBUSY
;
if
(
dsps
->
mmapped
&
(
1
<<
dir
))
goto
out_unlock
;
arg
.
dir
=
dir
;
arg
.
size
=
len
;
ret
=
exec_simple_cmd
(
os
,
OSSP_DSP_MMAP
,
&
arg
,
NULL
);
if
(
ret
==
0
)
dsps
->
mmapped
|=
1
<<
dir
;
out_unlock:
pthread_mutex_unlock
(
&
os
->
mmap_mutex
);
if
(
ret
==
0
)
fuse_reply_mmap
(
req
,
os
->
mmap_off
+
dir
*
os
->
mmap_size
/
2
,
0
);
else
fuse_reply_err
(
req
,
-
ret
);
}
static
void
dsp_munmap
(
fuse_req_t
req
,
size_t
len
,
struct
fuse_file_info
*
fi
,
off_t
offset
,
uint64_t
mh
)
{
struct
ossp_stream
*
os
;
struct
ossp_dsp_stream
*
dsps
;
int
dir
,
rc
;
os
=
find_os
(
fi
->
fh
);
if
(
!
os
)
goto
out
;
dsps
=
os_to_dsps
(
os
);
pthread_mutex_lock
(
&
os
->
mmap_mutex
);
for
(
dir
=
0
;
dir
<
2
;
dir
++
)
if
(
offset
==
os
->
mmap_off
+
dir
*
os
->
mmap_size
/
2
)
break
;
if
(
dir
==
2
||
len
>
os
->
mmap_size
/
2
)
{
warn_os
(
os
,
"invalid munmap request "
"offset=%llu len=%zu mmapped=0x%x"
,
(
unsigned
long
long
)
offset
,
len
,
dsps
->
mmapped
);
goto
out_unlock
;
}
rc
=
exec_simple_cmd
(
os
,
OSSP_DSP_MUNMAP
,
&
dir
,
NULL
);
if
(
rc
)
warn_ose
(
os
,
rc
,
"MUNMAP failed for dir=%d"
,
dir
);
dsps
->
mmapped
&=
~
(
1
<<
dir
);
out_unlock:
pthread_mutex_unlock
(
&
os
->
mmap_mutex
);
out:
fuse_reply_none
(
req
);
}
#endif
/***************************************************************************
* Notify poller
*/
static
void
*
notify_poller
(
void
*
arg
)
{
struct
epoll_event
events
[
1024
];
int
i
,
nfds
;
repeat:
nfds
=
epoll_wait
(
notify_epfd
,
events
,
ARRAY_SIZE
(
events
),
-
1
);
for
(
i
=
0
;
i
<
nfds
;
i
++
)
{
int
do_notify
=
0
;
struct
ossp_stream
*
os
;
struct
ossp_notify
notify
;
ssize_t
ret
;
os
=
find_os_by_notify_rx
(
events
[
i
].
data
.
fd
);
if
(
!
os
)
{
err
(
"can't find stream for notify_rx fd %d"
,
events
[
i
].
data
.
fd
);
epoll_ctl
(
notify_epfd
,
EPOLL_CTL_DEL
,
events
[
i
].
data
.
fd
,
NULL
);
/* we don't know what's going on, don't close the fd */
continue
;
}
while
((
ret
=
read
(
os
->
notify_rx
,
&
notify
,
sizeof
(
notify
)))
>
0
)
{
if
(
os
->
dead
)
continue
;
if
(
ret
!=
sizeof
(
notify
))
{
warn_os
(
os
,
"short read on notify_rx (%zu, "
"expected %zu), killing the stream"
,
ret
,
sizeof
(
notify
));
os
->
dead
=
1
;
break
;
}
if
(
notify
.
magic
!=
OSSP_NOTIFY_MAGIC
)
{
warn_os
(
os
,
"invalid magic on notification, "
"killing the stream"
);
os
->
dead
=
1
;
break
;
}
if
(
notify
.
opcode
>=
OSSP_NR_NOTIFY_OPCODES
)
goto
unknown
;
dbg1_os
(
os
,
"NOTIFY %s"
,
ossp_notify_str
[
notify
.
opcode
]);
switch
(
notify
.
opcode
)
{
case
OSSP_NOTIFY_POLL
:
do_notify
=
1
;
break
;
case
OSSP_NOTIFY_OBITUARY
:
os
->
dead
=
1
;
break
;
case
OSSP_NOTIFY_VOLCHG
:
pthread_mutex_lock
(
&
mixer_mutex
);
os
->
mixer
->
modify_counter
++
;
pthread_mutex_unlock
(
&
mixer_mutex
);
break
;
default:
unknown:
warn_os
(
os
,
"unknown notification %d"
,
notify
.
opcode
);
}
}
if
(
ret
==
0
)
os
->
dead
=
1
;
else
if
(
ret
<
0
&&
errno
!=
EAGAIN
)
{
warn_ose
(
os
,
-
errno
,
"read fail on notify fd"
);
os
->
dead
=
1
;
}
if
(
!
do_notify
&&
!
os
->
dead
)
continue
;
pthread_mutex_lock
(
&
mutex
);
if
(
os
->
ph
)
{
fuse_lowlevel_notify_poll
(
os
->
ph
);
fuse_pollhandle_destroy
(
os
->
ph
);
os
->
ph
=
NULL
;
}
if
(
os
->
dead
)
{
dbg0_os
(
os
,
"removing %d from notify poll list"
,
os
->
notify_rx
);
epoll_ctl
(
notify_epfd
,
EPOLL_CTL_DEL
,
os
->
notify_rx
,
NULL
);
close
(
os
->
notify_rx
);
os
->
notify_rx
=
-
1
;
pthread_cond_broadcast
(
&
notify_poller_kill_wait
);
}
pthread_mutex_unlock
(
&
mutex
);
}
goto
repeat
;
}
/***************************************************************************
* Slave corpse reaper
*/
static
void
*
slave_reaper
(
void
*
arg
)
{
struct
ossp_stream
*
os
;
int
status
;
pid_t
pid
;
pthread_mutex_lock
(
&
mutex
);
repeat:
while
(
list_empty
(
&
slave_corpse_list
))
pthread_cond_wait
(
&
slave_reaper_wait
,
&
mutex
);
os
=
list_first_entry
(
&
slave_corpse_list
,
struct
ossp_stream
,
link
);
list_del_init
(
&
os
->
link
);
pthread_mutex_unlock
(
&
mutex
);
do
{
pid
=
waitpid
(
os
->
slave_pid
,
&
status
,
0
);
}
while
(
pid
<
0
&&
errno
==
EINTR
);
if
(
pid
<
0
)
{
if
(
errno
==
ECHILD
)
warn_ose
(
os
,
-
errno
,
"slave %d already gone?"
,
os
->
slave_pid
);
else
fatal_e
(
-
errno
,
"waitpid(%d) failed"
,
os
->
slave_pid
);
}
pthread_mutex_lock
(
&
mutex
);
dbg1_os
(
os
,
"slave %d reaped"
,
os
->
slave_pid
);
__clear_bit
(
os
->
id
,
os_id_bitmap
);
free
(
os
);
goto
repeat
;
}
/***************************************************************************
* Stuff to bind and start everything
*/
static
void
ossp_daemonize
(
void
)
{
int
fd
,
pfd
[
2
];
pid_t
pid
;
ssize_t
ret
;
int
err
;
fd
=
open
(
"/dev/null"
,
O_RDWR
);
if
(
fd
>=
0
)
{
dup2
(
fd
,
0
);
dup2
(
fd
,
1
);
dup2
(
fd
,
2
);
if
(
fd
>
2
)
close
(
fd
);
}
if
(
pipe
(
pfd
))
fatal_e
(
-
errno
,
"failed to create pipe for init wait"
);
if
(
fcntl
(
pfd
[
0
],
F_SETFD
,
FD_CLOEXEC
)
<
0
||
fcntl
(
pfd
[
1
],
F_SETFD
,
FD_CLOEXEC
)
<
0
)
fatal_e
(
-
errno
,
"failed to set CLOEXEC on init wait pipe"
);
pid
=
fork
();
if
(
pid
<
0
)
fatal_e
(
-
errno
,
"failed to fork for daemon"
);
if
(
pid
==
0
)
{
close
(
pfd
[
0
]);
init_wait_fd
=
pfd
[
1
];
/* be evil, my child */
chdir
(
"/"
);
setsid
();
return
;
}
/* wait for init completion and pass over success indication */
close
(
pfd
[
1
]);
do
{
ret
=
read
(
pfd
[
0
],
&
err
,
sizeof
(
err
));
}
while
(
ret
<
0
&&
errno
==
EINTR
);
if
(
ret
==
sizeof
(
err
)
&&
err
==
0
)
exit
(
0
);
fatal
(
"daemon init failed ret=%zd err=%d"
,
ret
,
err
);
exit
(
1
);
}
static
void
ossp_init_done
(
void
*
userdata
)
{
/* init complete, notify parent if it's waiting */
if
(
init_wait_fd
>=
0
)
{
ssize_t
ret
;
int
err
=
0
;
ret
=
write
(
init_wait_fd
,
&
err
,
sizeof
(
err
));
if
(
ret
!=
sizeof
(
err
))
fatal_e
(
-
errno
,
"failed to notify init completion, "
"ret=%zd"
,
ret
);
close
(
init_wait_fd
);
init_wait_fd
=
-
1
;
}
}
static
const
struct
cuse_lowlevel_ops
mixer_ops
=
{
.
open
=
mixer_open
,
.
release
=
mixer_release
,
.
ioctl
=
mixer_ioctl
,
};
static
const
struct
cuse_lowlevel_ops
dsp_ops
=
{
.
init_done
=
ossp_init_done
,
.
open
=
dsp_open
,
.
release
=
dsp_release
,
.
read
=
dsp_read
,
.
write
=
dsp_write
,
.
poll
=
dsp_poll
,
.
ioctl
=
dsp_ioctl
,
#ifdef OSSP_MMAP
.
mmap
=
dsp_mmap
,
.
munmap
=
dsp_munmap
,
#endif
};
static
const
struct
cuse_lowlevel_ops
adsp_ops
=
{
.
open
=
adsp_open
,
.
release
=
dsp_release
,
.
read
=
dsp_read
,
.
write
=
dsp_write
,
.
poll
=
dsp_poll
,
.
ioctl
=
dsp_ioctl
,
#ifdef OSSP_MMAP
.
mmap
=
dsp_mmap
,
.
munmap
=
dsp_munmap
,
#endif
};
static
const
char
*
usage
=
"usage: osspd [options]
\n
"
"
\n
"
"options:
\n
"
" --help print this help message
\n
"
" --dsp=NAME DSP device name (default dsp)
\n
"
" --dsp-maj=MAJ DSP device major number (default 14)
\n
"
" --dsp-min=MIN DSP device minor number (default 3)
\n
"
" --adsp=NAME Aux DSP device name (default adsp, blank to disable)
\n
"
" --adsp-maj=MAJ Aux DSP device major number (default 14)
\n
"
" --adsp-min=MIN Aux DSP device minor number (default 12)
\n
"
" --mixer=NAME mixer device name (default mixer, blank to disable)
\n
"
" --mixer-maj=MAJ mixer device major number (default 14)
\n
"
" --mixer-min=MIN mixer device minor number (default 0)
\n
"
" --max=MAX maximum number of open streams (default 256)
\n
"
" --umax=MAX maximum number of open streams per UID (default --max)
\n
"
" --exit-on-idle exit if idle
\n
"
" --dsp-slave=PATH DSP slave (default ossp-padsp in the same dir)
\n
"
" --log=LEVEL log level (0..6)
\n
"
" --timestamp timestamp log messages
\n
"
" -v increase verbosity, can be specified multiple times
\n
"
" -f Run in foreground (don't daemonize)
\n
"
"
\n
"
;
struct
ossp_param
{
char
*
dsp_name
;
unsigned
dsp_major
;
unsigned
dsp_minor
;
char
*
adsp_name
;
unsigned
adsp_major
;
unsigned
adsp_minor
;
char
*
mixer_name
;
unsigned
mixer_major
;
unsigned
mixer_minor
;
unsigned
max_streams
;
unsigned
umax_streams
;
char
*
dsp_slave_path
;
unsigned
log_level
;
int
exit_on_idle
;
int
timestamp
;
int
fg
;
int
help
;
};
#define OSSP_OPT(t, p) { t, offsetof(struct ossp_param, p), 1 }
static
const
struct
fuse_opt
ossp_opts
[]
=
{
OSSP_OPT
(
"--dsp=%s"
,
dsp_name
),
OSSP_OPT
(
"--dsp-maj=%u"
,
dsp_major
),
OSSP_OPT
(
"--dsp-min=%u"
,
dsp_minor
),
OSSP_OPT
(
"--adsp=%s"
,
adsp_name
),
OSSP_OPT
(
"--adsp-maj=%u"
,
adsp_major
),
OSSP_OPT
(
"--adsp-min=%u"
,
adsp_minor
),
OSSP_OPT
(
"--mixer=%s"
,
mixer_name
),
OSSP_OPT
(
"--mixer-maj=%u"
,
mixer_major
),
OSSP_OPT
(
"--mixer-min=%u"
,
mixer_minor
),
OSSP_OPT
(
"--max=%u"
,
max_streams
),
OSSP_OPT
(
"--umax=%u"
,
umax_streams
),
OSSP_OPT
(
"--exit-on-idle"
,
exit_on_idle
),
OSSP_OPT
(
"--dsp-slave=%s"
,
dsp_slave_path
),
OSSP_OPT
(
"--timestamp"
,
timestamp
),
OSSP_OPT
(
"--log=%u"
,
log_level
),
OSSP_OPT
(
"-f"
,
fg
),
FUSE_OPT_KEY
(
"-h"
,
0
),
FUSE_OPT_KEY
(
"--help"
,
0
),
FUSE_OPT_KEY
(
"-v"
,
1
),
FUSE_OPT_END
};
static
struct
fuse_session
*
setup_ossp_cuse
(
const
struct
cuse_lowlevel_ops
*
ops
,
const
char
*
name
,
int
major
,
int
minor
,
int
argc
,
char
**
argv
)
{
char
name_buf
[
128
];
const
char
*
bufp
=
name_buf
;
struct
cuse_info
ci
=
{
.
dev_major
=
major
,
.
dev_minor
=
minor
,
.
dev_info_argc
=
1
,
.
dev_info_argv
=
&
bufp
,
.
flags
=
CUSE_UNRESTRICTED_IOCTL
};
struct
fuse_session
*
se
;
int
fd
;
snprintf
(
name_buf
,
sizeof
(
name_buf
),
"DEVNAME=%s"
,
name
);
se
=
cuse_lowlevel_setup
(
argc
,
argv
,
&
ci
,
ops
,
NULL
,
NULL
);
if
(
!
se
)
{
err
(
"failed to setup %s CUSE"
,
name
);
return
NULL
;
}
fd
=
fuse_chan_fd
(
fuse_session_next_chan
(
se
,
NULL
));
if
(
#ifdef OSSP_MMAP
fd
!=
fuse_mmap_fd
(
se
)
&&
#endif
fcntl
(
fd
,
F_SETFD
,
FD_CLOEXEC
)
<
0
)
{
err_e
(
-
errno
,
"failed to set CLOEXEC on %s CUSE fd"
,
name
);
cuse_lowlevel_teardown
(
se
);
return
NULL
;
}
return
se
;
}
static
void
*
cuse_worker
(
void
*
arg
)
{
struct
fuse_session
*
se
=
arg
;
int
rc
;
rc
=
fuse_session_loop_mt
(
se
);
cuse_lowlevel_teardown
(
se
);
return
(
void
*
)(
unsigned
long
)
rc
;
}
static
int
process_arg
(
void
*
data
,
const
char
*
arg
,
int
key
,
struct
fuse_args
*
outargs
)
{
struct
ossp_param
*
param
=
data
;
switch
(
key
)
{
case
0
:
fprintf
(
stderr
,
usage
);
param
->
help
=
1
;
return
0
;
case
1
:
param
->
log_level
++
;
return
0
;
}
return
1
;
}
int
main
(
int
argc
,
char
**
argv
)
{
static
struct
ossp_param
param
=
{
.
dsp_name
=
DFL_DSP_NAME
,
.
dsp_major
=
DFL_DSP_MAJOR
,
.
dsp_minor
=
DFL_DSP_MINOR
,
.
adsp_name
=
DFL_ADSP_NAME
,
.
adsp_major
=
DFL_ADSP_MAJOR
,
.
adsp_minor
=
DFL_ADSP_MINOR
,
.
mixer_name
=
DFL_MIXER_NAME
,
.
mixer_major
=
DFL_MIXER_MAJOR
,
.
mixer_minor
=
DFL_MIXER_MINOR
,
.
max_streams
=
DFL_MAX_STREAMS
,
};
struct
fuse_args
args
=
FUSE_ARGS_INIT
(
argc
,
argv
);
char
path_buf
[
PATH_MAX
],
*
dir
;
char
adsp_buf
[
64
]
=
""
,
mixer_buf
[
64
]
=
""
;
struct
sigaction
sa
;
struct
stat
stat_buf
;
ssize_t
ret
;
unsigned
u
;
snprintf
(
ossp_log_name
,
sizeof
(
ossp_log_name
),
"osspd"
);
param
.
log_level
=
ossp_log_level
;
if
(
fuse_opt_parse
(
&
args
,
&
param
,
ossp_opts
,
process_arg
))
fatal
(
"failed to parse arguments"
);
if
(
param
.
help
)
return
0
;
max_streams
=
param
.
max_streams
;
hashtbl_size
=
max_streams
/
2
+
13
;
umax_streams
=
max_streams
;
if
(
param
.
umax_streams
)
umax_streams
=
param
.
umax_streams
;
if
(
param
.
log_level
>
OSSP_LOG_MAX
)
param
.
log_level
=
OSSP_LOG_MAX
;
if
(
!
param
.
fg
)
param
.
log_level
=
-
param
.
log_level
;
ossp_log_level
=
param
.
log_level
;
ossp_log_timestamp
=
param
.
timestamp
;
if
(
!
param
.
fg
)
ossp_daemonize
();
/* daemonization already handled, prevent forking inside FUSE */
fuse_opt_add_arg
(
&
args
,
"-f"
);
info
(
"OSS Proxy v%s (C) 2008-2010 by Tejun Heo <teheo@suse.de>"
,
OSSP_VERSION
);
/* ignore stupid SIGPIPEs */
memset
(
&
sa
,
0
,
sizeof
(
sa
));
sa
.
sa_handler
=
SIG_IGN
;
if
(
sigaction
(
SIGPIPE
,
&
sa
,
NULL
))
fatal_e
(
-
errno
,
"failed to ignore SIGPIPE"
);
//#ifdef GIOVANNI
/* determine slave path and check for availability */
ret
=
readlink
(
"/proc/self/exe"
,
path_buf
,
PATH_MAX
-
1
);
if
(
ret
<
0
)
fatal_e
(
-
errno
,
"failed to determine executable path"
);
path_buf
[
ret
]
=
'\0'
;
dir
=
dirname
(
path_buf
);
if
(
param
.
dsp_slave_path
)
{
strncpy
(
dsp_slave_path
,
param
.
dsp_slave_path
,
PATH_MAX
-
1
);
dsp_slave_path
[
PATH_MAX
-
1
]
=
'\0'
;
}
else
{
ret
=
snprintf
(
dsp_slave_path
,
PATH_MAX
,
"%s/%s"
,
dir
,
"ossp-padsp"
);
if
(
ret
>=
PATH_MAX
)
fatal
(
"dsp slave pathname too long"
);
}
if
(
stat
(
dsp_slave_path
,
&
stat_buf
))
fatal_e
(
-
errno
,
"failed to stat %s"
,
dsp_slave_path
);
if
(
!
S_ISREG
(
stat_buf
.
st_mode
)
||
!
(
stat_buf
.
st_mode
&
0444
))
fatal
(
"%s is not executable"
,
dsp_slave_path
);
//#endif// GIOVANNI
/* allocate tables */
os_id_bitmap
=
calloc
(
BITS_TO_LONGS
(
max_streams
),
sizeof
(
long
));
mixer_tbl
=
calloc
(
hashtbl_size
,
sizeof
(
mixer_tbl
[
0
]));
os_tbl
=
calloc
(
hashtbl_size
,
sizeof
(
os_tbl
[
0
]));
os_pgrp_tbl
=
calloc
(
hashtbl_size
,
sizeof
(
os_pgrp_tbl
[
0
]));
os_notify_tbl
=
calloc
(
hashtbl_size
,
sizeof
(
os_notify_tbl
[
0
]));
if
(
!
os_id_bitmap
||
!
mixer_tbl
||
!
os_tbl
||
!
os_pgrp_tbl
||
!
os_notify_tbl
)
fatal
(
"failed to allocate stream hash tables"
);
for
(
u
=
0
;
u
<
hashtbl_size
;
u
++
)
{
INIT_LIST_HEAD
(
&
mixer_tbl
[
u
]);
INIT_LIST_HEAD
(
&
os_tbl
[
u
]);
INIT_LIST_HEAD
(
&
os_pgrp_tbl
[
u
]);
INIT_LIST_HEAD
(
&
os_notify_tbl
[
u
]);
}
__set_bit
(
0
,
os_id_bitmap
);
/* don't use id 0 */
/* create mixer delayed reference worker */
ret
=
-
pthread_create
(
&
mixer_delayed_put_thread
,
NULL
,
mixer_delayed_put_worker
,
NULL
);
if
(
ret
)
fatal_e
(
ret
,
"failed to create mixer delayed put worker"
);
/* if exit_on_idle, touch mixer for pgrp0 */
exit_on_idle
=
param
.
exit_on_idle
;
if
(
exit_on_idle
)
{
struct
ossp_mixer
*
mixer
;
mixer
=
get_mixer
(
0
);
if
(
!
mixer
)
fatal
(
"failed to touch idle mixer"
);
put_mixer
(
mixer
);
}
/* create notify epoll and kick off watcher thread */
notify_epfd
=
epoll_create
(
max_streams
);
if
(
notify_epfd
<
0
)
fatal_e
(
-
errno
,
"failed to create notify epoll"
);
if
(
fcntl
(
notify_epfd
,
F_SETFD
,
FD_CLOEXEC
)
<
0
)
fatal_e
(
-
errno
,
"failed to set CLOEXEC on notify epfd"
);
ret
=
-
pthread_create
(
&
notify_poller_thread
,
NULL
,
notify_poller
,
NULL
);
if
(
ret
)
fatal_e
(
ret
,
"failed to create notify poller thread"
);
/* create reaper for slave corpses */
ret
=
-
pthread_create
(
&
slave_reaper_thread
,
NULL
,
slave_reaper
,
NULL
);
if
(
ret
)
fatal_e
(
ret
,
"failed to create slave reaper thread"
);
#ifdef GIOVANNI
/* we're set, let's setup fuse structures */
if
(
strlen
(
param
.
mixer_name
))
mixer_se
=
setup_ossp_cuse
(
&
mixer_ops
,
param
.
mixer_name
,
param
.
mixer_major
,
param
.
mixer_minor
,
args
.
argc
,
args
.
argv
);
if
(
strlen
(
param
.
adsp_name
))
adsp_se
=
setup_ossp_cuse
(
&
dsp_ops
,
param
.
adsp_name
,
param
.
adsp_major
,
param
.
adsp_minor
,
args
.
argc
,
args
.
argv
);
#endif// GIOVANNI
dsp_se
=
setup_ossp_cuse
(
&
dsp_ops
,
param
.
dsp_name
,
param
.
dsp_major
,
param
.
dsp_minor
,
args
.
argc
,
args
.
argv
);
if
(
!
dsp_se
)
fatal
(
"can't create dsp, giving up"
);
#ifdef GIOVANNI
if
(
mixer_se
)
snprintf
(
mixer_buf
,
sizeof
(
mixer_buf
),
", %s (%d:%d)"
,
param
.
mixer_name
,
param
.
mixer_major
,
param
.
mixer_minor
);
if
(
adsp_se
)
snprintf
(
adsp_buf
,
sizeof
(
adsp_buf
),
", %s (%d:%d)"
,
param
.
adsp_name
,
param
.
adsp_major
,
param
.
adsp_minor
);
#endif// GIOVANNI
info
(
"Creating %s (%d:%d)%s%s"
,
param
.
dsp_name
,
param
.
dsp_major
,
param
.
dsp_minor
,
adsp_buf
,
mixer_buf
);
#ifdef GIOVANNI
/* start threads for mixer and adsp */
if
(
mixer_se
)
{
ret
=
-
pthread_create
(
&
cuse_mixer_thread
,
NULL
,
cuse_worker
,
mixer_se
);
if
(
ret
)
err_e
(
ret
,
"failed to create mixer worker"
);
}
if
(
adsp_se
)
{
ret
=
-
pthread_create
(
&
cuse_adsp_thread
,
NULL
,
cuse_worker
,
adsp_se
);
if
(
ret
)
err_e
(
ret
,
"failed to create adsp worker"
);
}
#endif// GIOVANNI
/* run CUSE for /dev/dsp in the main thread */
ret
=
(
ssize_t
)
cuse_worker
(
dsp_se
);
if
(
ret
<
0
)
fatal
(
"dsp worker failed"
);
return
0
;
}
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论