Initial import from vendor-sys branch of openzfs
This commit is contained in:
commit
eda14cbc26
308
sys/contrib/openzfs/AUTHORS
Normal file
308
sys/contrib/openzfs/AUTHORS
Normal file
@ -0,0 +1,308 @@
|
||||
MAINTAINERS:
|
||||
|
||||
Brian Behlendorf <behlendorf1@llnl.gov>
|
||||
Tony Hutter <hutter2@llnl.gov>
|
||||
|
||||
PAST MAINTAINERS:
|
||||
|
||||
Ned Bass <bass6@llnl.gov>
|
||||
|
||||
CONTRIBUTORS:
|
||||
|
||||
Aaron Fineman <abyxcos@gmail.com>
|
||||
Adam Leventhal <ahl@delphix.com>
|
||||
Adam Stevko <adam.stevko@gmail.com>
|
||||
Ahmed G <ahmedg@delphix.com>
|
||||
Akash Ayare <aayare@delphix.com>
|
||||
Alan Somers <asomers@gmail.com>
|
||||
Alar Aun <spamtoaun@gmail.com>
|
||||
Albert Lee <trisk@nexenta.com>
|
||||
Alec Salazar <alec.j.salazar@gmail.com>
|
||||
Alejandro R. Sedeño <asedeno@mit.edu>
|
||||
Alek Pinchuk <alek@nexenta.com>
|
||||
Alex Braunegg <alex.braunegg@gmail.com>
|
||||
Alex McWhirter <alexmcwhirter@triadic.us>
|
||||
Alex Reece <alex@delphix.com>
|
||||
Alex Wilson <alex.wilson@joyent.com>
|
||||
Alex Zhuravlev <alexey.zhuravlev@intel.com>
|
||||
Alexander Eremin <a.eremin@nexenta.com>
|
||||
Alexander Motin <mav@freebsd.org>
|
||||
Alexander Pyhalov <apyhalov@gmail.com>
|
||||
Alexander Stetsenko <ams@nexenta.com>
|
||||
Alexey Shvetsov <alexxy@gentoo.org>
|
||||
Alexey Smirnoff <fling@member.fsf.org>
|
||||
Allan Jude <allanjude@freebsd.org>
|
||||
AndCycle <andcycle@andcycle.idv.tw>
|
||||
Andreas Buschmann <andreas.buschmann@tech.net.de>
|
||||
Andreas Dilger <adilger@intel.com>
|
||||
Andrew Barnes <barnes333@gmail.com>
|
||||
Andrew Hamilton <ahamilto@tjhsst.edu>
|
||||
Andrew Reid <ColdCanuck@nailedtotheperch.com>
|
||||
Andrew Stormont <andrew.stormont@nexenta.com>
|
||||
Andrew Tselischev <andrewtselischev@gmail.com>
|
||||
Andrey Vesnovaty <andrey.vesnovaty@gmail.com>
|
||||
Andriy Gapon <avg@freebsd.org>
|
||||
Andy Bakun <github@thwartedefforts.org>
|
||||
Aniruddha Shankar <k@191a.net>
|
||||
Antonio Russo <antonio.e.russo@gmail.com>
|
||||
Arkadiusz Bubała <arkadiusz.bubala@open-e.com>
|
||||
Arne Jansen <arne@die-jansens.de>
|
||||
Aron Xu <happyaron.xu@gmail.com>
|
||||
Bart Coddens <bart.coddens@gmail.com>
|
||||
Basil Crow <basil.crow@delphix.com>
|
||||
Huang Liu <liu.huang@zte.com.cn>
|
||||
Ben Allen <bsallen@alcf.anl.gov>
|
||||
Ben Rubson <ben.rubson@gmail.com>
|
||||
Benjamin Albrecht <git@albrecht.io>
|
||||
Bill McGonigle <bill-github.com-public1@bfccomputing.com>
|
||||
Bill Pijewski <wdp@joyent.com>
|
||||
Boris Protopopov <boris.protopopov@nexenta.com>
|
||||
Brad Lewis <brad.lewis@delphix.com>
|
||||
Brian Behlendorf <behlendorf1@llnl.gov>
|
||||
Brian J. Murrell <brian@sun.com>
|
||||
Caleb James DeLisle <calebdelisle@lavabit.com>
|
||||
Cao Xuewen <cao.xuewen@zte.com.cn>
|
||||
Carlo Landmeter <clandmeter@gmail.com>
|
||||
Carlos Alberto Lopez Perez <clopez@igalia.com>
|
||||
Chaoyu Zhang <zhang.chaoyu@zte.com.cn>
|
||||
Chen Can <chen.can2@zte.com.cn>
|
||||
Chen Haiquan <oc@yunify.com>
|
||||
Chip Parker <aparker@enthought.com>
|
||||
Chris Burroughs <chris.burroughs@gmail.com>
|
||||
Chris Dunlap <cdunlap@llnl.gov>
|
||||
Chris Dunlop <chris@onthe.net.au>
|
||||
Chris Siden <chris.siden@delphix.com>
|
||||
Chris Wedgwood <cw@f00f.org>
|
||||
Chris Williamson <chris.williamson@delphix.com>
|
||||
Chris Zubrzycki <github@mid-earth.net>
|
||||
Christ Schlacta <aarcane@aarcane.info>
|
||||
Christer Ekholm <che@chrekh.se>
|
||||
Christian Kohlschütter <christian@kohlschutter.com>
|
||||
Christian Neukirchen <chneukirchen@gmail.com>
|
||||
Christian Schwarz <me@cschwarz.com>
|
||||
Christopher Voltz <cjunk@voltz.ws>
|
||||
Chunwei Chen <david.chen@nutanix.com>
|
||||
Clemens Fruhwirth <clemens@endorphin.org>
|
||||
Coleman Kane <ckane@colemankane.org>
|
||||
Colin Ian King <colin.king@canonical.com>
|
||||
Craig Loomis <cloomis@astro.princeton.edu>
|
||||
Craig Sanders <github@taz.net.au>
|
||||
Cyril Plisko <cyril.plisko@infinidat.com>
|
||||
DHE <git@dehacked.net>
|
||||
Damian Wojsław <damian@wojslaw.pl>
|
||||
Dan Kimmel <dan.kimmel@delphix.com>
|
||||
Dan McDonald <danmcd@nexenta.com>
|
||||
Dan Swartzendruber <dswartz@druber.com>
|
||||
Dan Vatca <dan.vatca@gmail.com>
|
||||
Daniel Hoffman <dj.hoffman@delphix.com>
|
||||
Daniel Verite <daniel@verite.pro>
|
||||
Daniil Lunev <d.lunev.mail@gmail.com>
|
||||
Darik Horn <dajhorn@vanadac.com>
|
||||
Dave Eddy <dave@daveeddy.com>
|
||||
David Lamparter <equinox@diac24.net>
|
||||
David Qian <david.qian@intel.com>
|
||||
David Quigley <david.quigley@intel.com>
|
||||
Debabrata Banerjee <dbanerje@akamai.com>
|
||||
Denys Rtveliashvili <denys@rtveliashvili.name>
|
||||
Derek Dai <daiderek@gmail.com>
|
||||
Dimitri John Ledkov <xnox@ubuntu.com>
|
||||
Dmitry Khasanov <pik4ez@gmail.com>
|
||||
Dominik Hassler <hadfl@omniosce.org>
|
||||
Dominik Honnef <dominikh@fork-bomb.org>
|
||||
Don Brady <don.brady@delphix.com>
|
||||
Dr. András Korn <korn-github.com@elan.rulez.org>
|
||||
Eli Rosenthal <eli.rosenthal@delphix.com>
|
||||
Eric Desrochers <eric.desrochers@canonical.com>
|
||||
Eric Dillmann <eric@jave.fr>
|
||||
Eric Schrock <Eric.Schrock@delphix.com>
|
||||
Etienne Dechamps <etienne@edechamps.fr>
|
||||
Evan Susarret <evansus@gmail.com>
|
||||
Fabian Grünbichler <f.gruenbichler@proxmox.com>
|
||||
Fajar A. Nugraha <github@fajar.net>
|
||||
Fan Yong <fan.yong@intel.com>
|
||||
Feng Sun <loyou85@gmail.com>
|
||||
Frederik Wessels <wessels147@gmail.com>
|
||||
Frédéric Vanniere <f.vanniere@planet-work.com>
|
||||
Garrett D'Amore <garrett@nexenta.com>
|
||||
Garrison Jensen <garrison.jensen@gmail.com>
|
||||
Gary Mills <gary_mills@fastmail.fm>
|
||||
Gaurav Kumar <gauravk.18@gmail.com>
|
||||
GeLiXin <ge.lixin@zte.com.cn>
|
||||
George Amanakis <g_amanakis@yahoo.com>
|
||||
George Melikov <mail@gmelikov.ru>
|
||||
George Wilson <gwilson@delphix.com>
|
||||
Georgy Yakovlev <ya@sysdump.net>
|
||||
Giuseppe Di Natale <guss80@gmail.com>
|
||||
Gordan Bobic <gordan@redsleeve.org>
|
||||
Gordon Ross <gwr@nexenta.com>
|
||||
Gregor Kopka <gregor@kopka.net>
|
||||
Grischa Zengel <github.zfsonlinux@zengel.info>
|
||||
Gunnar Beutner <gunnar@beutner.name>
|
||||
Gvozden Neskovic <neskovic@gmail.com>
|
||||
Hajo Möller <dasjoe@gmail.com>
|
||||
Hans Rosenfeld <hans.rosenfeld@nexenta.com>
|
||||
Håkan Johansson <f96hajo@chalmers.se>
|
||||
Igor Kozhukhov <ikozhukhov@gmail.com>
|
||||
Igor Lvovsky <ilvovsky@gmail.com>
|
||||
Isaac Huang <he.huang@intel.com>
|
||||
JK Dingwall <james@dingwall.me.uk>
|
||||
Jacek Fefliński <feflik@gmail.com>
|
||||
James Cowgill <james.cowgill@mips.com>
|
||||
James Lee <jlee@thestaticvoid.com>
|
||||
James Pan <jiaming.pan@yahoo.com>
|
||||
Jan Engelhardt <jengelh@inai.de>
|
||||
Jan Kryl <jan.kryl@nexenta.com>
|
||||
Jan Sanislo <oystr@cs.washington.edu>
|
||||
Jason King <jason.brian.king@gmail.com>
|
||||
Jason Zaman <jasonzaman@gmail.com>
|
||||
Javen Wu <wu.javen@gmail.com>
|
||||
Jeremy Gill <jgill@parallax-innovations.com>
|
||||
Jeremy Jones <jeremy@delphix.com>
|
||||
Jerry Jelinek <jerry.jelinek@joyent.com>
|
||||
Jinshan Xiong <jinshan.xiong@intel.com>
|
||||
Joe Stein <joe.stein@delphix.com>
|
||||
John Albietz <inthecloud247@gmail.com>
|
||||
John Eismeier <john.eismeier@gmail.com>
|
||||
John L. Hammond <john.hammond@intel.com>
|
||||
John Layman <jlayman@sagecloud.com>
|
||||
John Paul Adrian Glaubitz <glaubitz@physik.fu-berlin.de>
|
||||
John Wren Kennedy <john.kennedy@delphix.com>
|
||||
Johnny Stenback <github@jstenback.com>
|
||||
Jorgen Lundman <lundman@lundman.net>
|
||||
Josef 'Jeff' Sipek <josef.sipek@nexenta.com>
|
||||
Joshua M. Clulow <josh@sysmgr.org>
|
||||
Justin Bedő <cu@cua0.org>
|
||||
Justin Lecher <jlec@gentoo.org>
|
||||
Justin T. Gibbs <gibbs@FreeBSD.org>
|
||||
Jörg Thalheim <joerg@higgsboson.tk>
|
||||
KORN Andras <korn@elan.rulez.org>
|
||||
Kamil Domański <kamil@domanski.co>
|
||||
Karsten Kretschmer <kkretschmer@gmail.com>
|
||||
Kash Pande <kash@tripleback.net>
|
||||
Keith M Wesolowski <wesolows@foobazco.org>
|
||||
Kevin Tanguy <kevin.tanguy@ovh.net>
|
||||
KireinaHoro <i@jsteward.moe>
|
||||
Kjeld Schouten-Lebbing <kjeld@schouten-lebbing.nl>
|
||||
Kohsuke Kawaguchi <kk@kohsuke.org>
|
||||
Kyle Blatter <kyleblatter@llnl.gov>
|
||||
Kyle Fuller <inbox@kylefuller.co.uk>
|
||||
Loli <ezomori.nozomu@gmail.com>
|
||||
Lars Johannsen <laj@it.dk>
|
||||
Li Dongyang <dongyang.li@anu.edu.au>
|
||||
Li Wei <W.Li@Sun.COM>
|
||||
Lukas Wunner <lukas@wunner.de>
|
||||
Madhav Suresh <madhav.suresh@delphix.com>
|
||||
Manoj Joseph <manoj.joseph@delphix.com>
|
||||
Manuel Amador (Rudd-O) <rudd-o@rudd-o.com>
|
||||
Marcel Huber <marcelhuberfoo@gmail.com>
|
||||
Marcel Telka <marcel.telka@nexenta.com>
|
||||
Marcel Wysocki <maci.stgn@gmail.com>
|
||||
Mark Shellenbaum <Mark.Shellenbaum@Oracle.COM>
|
||||
Mark Wright <markwright@internode.on.net>
|
||||
Martin Matuska <mm@FreeBSD.org>
|
||||
Massimo Maggi <me@massimo-maggi.eu>
|
||||
Matt Johnston <matt@fugro-fsi.com.au>
|
||||
Matt Kemp <matt@mattikus.com>
|
||||
Matthew Ahrens <matt@delphix.com>
|
||||
Matthew Thode <mthode@mthode.org>
|
||||
Matus Kral <matuskral@me.com>
|
||||
Max Grossman <max.grossman@delphix.com>
|
||||
Maximilian Mehnert <maximilian.mehnert@gmx.de>
|
||||
Michael Gebetsroither <michael@mgeb.org>
|
||||
Michael Kjorling <michael@kjorling.se>
|
||||
Michael Martin <mgmartin.mgm@gmail.com>
|
||||
Michael Niewöhner <foss@mniewoehner.de>
|
||||
Mike Gerdts <mike.gerdts@joyent.com>
|
||||
Mike Harsch <mike@harschsystems.com>
|
||||
Mike Leddy <mike.leddy@gmail.com>
|
||||
Mike Swanson <mikeonthecomputer@gmail.com>
|
||||
Milan Jurik <milan.jurik@xylab.cz>
|
||||
Morgan Jones <mjones@rice.edu>
|
||||
Moritz Maxeiner <moritz@ucworks.org>
|
||||
Nathaniel Clark <Nathaniel.Clark@misrule.us>
|
||||
Nathaniel Wesley Filardo <nwf@cs.jhu.edu>
|
||||
Nav Ravindranath <nav@delphix.com>
|
||||
Neal Gompa (ニール・ゴンパ) <ngompa13@gmail.com>
|
||||
Ned Bass <bass6@llnl.gov>
|
||||
Neependra Khare <neependra@kqinfotech.com>
|
||||
Neil Stockbridge <neil@dist.ro>
|
||||
Nick Garvey <garvey.nick@gmail.com>
|
||||
Nikolay Borisov <n.borisov.lkml@gmail.com>
|
||||
Olaf Faaland <faaland1@llnl.gov>
|
||||
Oleg Drokin <green@linuxhacker.ru>
|
||||
Oleg Stepura <oleg@stepura.com>
|
||||
Patrik Greco <sikevux@sikevux.se>
|
||||
Paul B. Henson <henson@acm.org>
|
||||
Paul Dagnelie <pcd@delphix.com>
|
||||
Paul Zuchowski <pzuchowski@datto.com>
|
||||
Pavel Boldin <boldin.pavel@gmail.com>
|
||||
Pavel Zakharov <pavel.zakharov@delphix.com>
|
||||
Pawel Jakub Dawidek <pjd@FreeBSD.org>
|
||||
Pedro Giffuni <pfg@freebsd.org>
|
||||
Peng <peng.hse@xtaotech.com>
|
||||
Peter Ashford <ashford@accs.com>
|
||||
Prakash Surya <prakash.surya@delphix.com>
|
||||
Prasad Joshi <prasadjoshi124@gmail.com>
|
||||
Ralf Ertzinger <ralf@skytale.net>
|
||||
Randall Mason <ClashTheBunny@gmail.com>
|
||||
Remy Blank <remy.blank@pobox.com>
|
||||
Ricardo M. Correia <ricardo.correia@oracle.com>
|
||||
Rich Ercolani <rincebrain@gmail.com>
|
||||
Richard Elling <Richard.Elling@RichardElling.com>
|
||||
Richard Laager <rlaager@wiktel.com>
|
||||
Richard Lowe <richlowe@richlowe.net>
|
||||
Richard Sharpe <rsharpe@samba.org>
|
||||
Richard Yao <ryao@gentoo.org>
|
||||
Rohan Puri <rohan.puri15@gmail.com>
|
||||
Romain Dolbeau <romain.dolbeau@atos.net>
|
||||
Roman Strashkin <roman.strashkin@nexenta.com>
|
||||
Ruben Kerkhof <ruben@rubenkerkhof.com>
|
||||
Saso Kiselkov <saso.kiselkov@nexenta.com>
|
||||
Scot W. Stevenson <scot.stevenson@gmail.com>
|
||||
Sean Eric Fagan <sef@ixsystems.com>
|
||||
Sebastian Gottschall <s.gottschall@dd-wrt.com>
|
||||
Sen Haerens <sen@senhaerens.be>
|
||||
Serapheim Dimitropoulos <serapheim@delphix.com>
|
||||
Seth Forshee <seth.forshee@canonical.com>
|
||||
Shampavman <sham.pavman@nexenta.com>
|
||||
Shen Yan <shenyanxxxy@qq.com>
|
||||
Simon Guest <simon.guest@tesujimath.org>
|
||||
Simon Klinkert <simon.klinkert@gmail.com>
|
||||
Sowrabha Gopal <sowrabha.gopal@delphix.com>
|
||||
Stanislav Seletskiy <s.seletskiy@gmail.com>
|
||||
Steffen Müthing <steffen.muething@iwr.uni-heidelberg.de>
|
||||
Stephen Blinick <stephen.blinick@delphix.com>
|
||||
Steve Dougherty <sdougherty@barracuda.com>
|
||||
Steven Burgess <sburgess@dattobackup.com>
|
||||
Steven Hartland <smh@freebsd.org>
|
||||
Steven Johnson <sjohnson@sakuraindustries.com>
|
||||
Stian Ellingsen <stian@plaimi.net>
|
||||
Suman Chakravartula <schakrava@gmail.com>
|
||||
Sydney Vanda <sydney.m.vanda@intel.com>
|
||||
Sören Tempel <soeren+git@soeren-tempel.net>
|
||||
Thijs Cramer <thijs.cramer@gmail.com>
|
||||
Tim Chase <tim@chase2k.com>
|
||||
Tim Connors <tconnors@rather.puzzling.org>
|
||||
Tim Crawford <tcrawford@datto.com>
|
||||
Tim Haley <Tim.Haley@Sun.COM>
|
||||
Tobin Harding <me@tobin.cc>
|
||||
Tom Caputi <tcaputi@datto.com>
|
||||
Tom Matthews <tom@axiom-partners.com>
|
||||
Tom Prince <tom.prince@ualberta.net>
|
||||
Tomohiro Kusumi <kusumi.tomohiro@gmail.com>
|
||||
Tony Hutter <hutter2@llnl.gov>
|
||||
Toomas Soome <tsoome@me.com>
|
||||
Trey Dockendorf <treydock@gmail.com>
|
||||
Turbo Fredriksson <turbo@bayour.com>
|
||||
Tyler J. Stachecki <stachecki.tyler@gmail.com>
|
||||
Vitaut Bajaryn <vitaut.bayaryn@gmail.com>
|
||||
Weigang Li <weigang.li@intel.com>
|
||||
Will Andrews <will@freebsd.org>
|
||||
Will Rouesnel <w.rouesnel@gmail.com>
|
||||
Wolfgang Bumiller <w.bumiller@proxmox.com>
|
||||
Xin Li <delphij@FreeBSD.org>
|
||||
Ying Zhu <casualfisher@gmail.com>
|
||||
YunQiang Su <syq@debian.org>
|
||||
Yuri Pankov <yuri.pankov@gmail.com>
|
||||
Yuxuan Shui <yshuiv7@gmail.com>
|
||||
Zachary Bedell <zac@thebedells.org>
|
2
sys/contrib/openzfs/CODE_OF_CONDUCT.md
Normal file
2
sys/contrib/openzfs/CODE_OF_CONDUCT.md
Normal file
@ -0,0 +1,2 @@
|
||||
The [OpenZFS Code of Conduct](http://www.open-zfs.org/wiki/Code_of_Conduct)
|
||||
applies to spaces associated with the ZFS on Linux project, including GitHub.
|
31
sys/contrib/openzfs/COPYRIGHT
Normal file
31
sys/contrib/openzfs/COPYRIGHT
Normal file
@ -0,0 +1,31 @@
|
||||
Refer to the git commit log for authoritative copyright attribution.
|
||||
|
||||
The original ZFS source code was obtained from Open Solaris which was
|
||||
released under the terms of the CDDL open source license. Additional
|
||||
changes have been included from OpenZFS and the Illumos project which
|
||||
are similarly licensed. These projects can be found on Github at:
|
||||
|
||||
* https://github.com/illumos/illumos-gate
|
||||
* https://github.com/openzfs/openzfs
|
||||
|
||||
Unless otherwise noted, all files in this distribution are released
|
||||
under the Common Development and Distribution License (CDDL).
|
||||
|
||||
Exceptions are noted within the associated source files headers and
|
||||
by including a THIRDPARTYLICENSE file with the license terms. A few
|
||||
notable exceptions and their respective licenses include:
|
||||
|
||||
* Skein Checksum Implementation: module/icp/algs/skein/THIRDPARTYLICENSE
|
||||
* AES Implementation: module/icp/asm-x86_64/aes/THIRDPARTYLICENSE.gladman
|
||||
* AES Implementation: module/icp/asm-x86_64/aes/THIRDPARTYLICENSE.openssl
|
||||
* PBKDF2 Implementation: lib/libzfs/THIRDPARTYLICENSE.openssl
|
||||
* SPL Implementation: module/os/linux/spl/THIRDPARTYLICENSE.gplv2
|
||||
* GCM Implementation: module/icp/asm-x86_64/modes/THIRDPARTYLICENSE.cryptogams
|
||||
* GCM Implementation: module/icp/asm-x86_64/modes/THIRDPARTYLICENSE.openssl
|
||||
* GHASH Implementation: module/icp/asm-x86_64/modes/THIRDPARTYLICENSE.cryptogams
|
||||
* GHASH Implementation: module/icp/asm-x86_64/modes/THIRDPARTYLICENSE.openssl
|
||||
|
||||
This product includes software developed by the OpenSSL Project for use
|
||||
in the OpenSSL Toolkit (http://www.openssl.org/)
|
||||
|
||||
See the LICENSE and NOTICE for more information.
|
384
sys/contrib/openzfs/LICENSE
Normal file
384
sys/contrib/openzfs/LICENSE
Normal file
@ -0,0 +1,384 @@
|
||||
Unless otherwise noted, all files in this distribution are released
|
||||
under the Common Development and Distribution License (CDDL).
|
||||
Exceptions are noted within the associated source files.
|
||||
|
||||
--------------------------------------------------------------------
|
||||
|
||||
|
||||
COMMON DEVELOPMENT AND DISTRIBUTION LICENSE Version 1.0
|
||||
|
||||
1. Definitions.
|
||||
|
||||
1.1. "Contributor" means each individual or entity that creates
|
||||
or contributes to the creation of Modifications.
|
||||
|
||||
1.2. "Contributor Version" means the combination of the Original
|
||||
Software, prior Modifications used by a Contributor (if any),
|
||||
and the Modifications made by that particular Contributor.
|
||||
|
||||
1.3. "Covered Software" means (a) the Original Software, or (b)
|
||||
Modifications, or (c) the combination of files containing
|
||||
Original Software with files containing Modifications, in
|
||||
each case including portions thereof.
|
||||
|
||||
1.4. "Executable" means the Covered Software in any form other
|
||||
than Source Code.
|
||||
|
||||
1.5. "Initial Developer" means the individual or entity that first
|
||||
makes Original Software available under this License.
|
||||
|
||||
1.6. "Larger Work" means a work which combines Covered Software or
|
||||
portions thereof with code not governed by the terms of this
|
||||
License.
|
||||
|
||||
1.7. "License" means this document.
|
||||
|
||||
1.8. "Licensable" means having the right to grant, to the maximum
|
||||
extent possible, whether at the time of the initial grant or
|
||||
subsequently acquired, any and all of the rights conveyed
|
||||
herein.
|
||||
|
||||
1.9. "Modifications" means the Source Code and Executable form of
|
||||
any of the following:
|
||||
|
||||
A. Any file that results from an addition to, deletion from or
|
||||
modification of the contents of a file containing Original
|
||||
Software or previous Modifications;
|
||||
|
||||
B. Any new file that contains any part of the Original
|
||||
Software or previous Modifications; or
|
||||
|
||||
C. Any new file that is contributed or otherwise made
|
||||
available under the terms of this License.
|
||||
|
||||
1.10. "Original Software" means the Source Code and Executable
|
||||
form of computer software code that is originally released
|
||||
under this License.
|
||||
|
||||
1.11. "Patent Claims" means any patent claim(s), now owned or
|
||||
hereafter acquired, including without limitation, method,
|
||||
process, and apparatus claims, in any patent Licensable by
|
||||
grantor.
|
||||
|
||||
1.12. "Source Code" means (a) the common form of computer software
|
||||
code in which modifications are made and (b) associated
|
||||
documentation included in or with such code.
|
||||
|
||||
1.13. "You" (or "Your") means an individual or a legal entity
|
||||
exercising rights under, and complying with all of the terms
|
||||
of, this License. For legal entities, "You" includes any
|
||||
entity which controls, is controlled by, or is under common
|
||||
control with You. For purposes of this definition,
|
||||
"control" means (a) the power, direct or indirect, to cause
|
||||
the direction or management of such entity, whether by
|
||||
contract or otherwise, or (b) ownership of more than fifty
|
||||
percent (50%) of the outstanding shares or beneficial
|
||||
ownership of such entity.
|
||||
|
||||
2. License Grants.
|
||||
|
||||
2.1. The Initial Developer Grant.
|
||||
|
||||
Conditioned upon Your compliance with Section 3.1 below and
|
||||
subject to third party intellectual property claims, the Initial
|
||||
Developer hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
(a) under intellectual property rights (other than patent or
|
||||
trademark) Licensable by Initial Developer, to use,
|
||||
reproduce, modify, display, perform, sublicense and
|
||||
distribute the Original Software (or portions thereof),
|
||||
with or without Modifications, and/or as part of a Larger
|
||||
Work; and
|
||||
|
||||
(b) under Patent Claims infringed by the making, using or
|
||||
selling of Original Software, to make, have made, use,
|
||||
practice, sell, and offer for sale, and/or otherwise
|
||||
dispose of the Original Software (or portions thereof).
|
||||
|
||||
(c) The licenses granted in Sections 2.1(a) and (b) are
|
||||
effective on the date Initial Developer first distributes
|
||||
or otherwise makes the Original Software available to a
|
||||
third party under the terms of this License.
|
||||
|
||||
(d) Notwithstanding Section 2.1(b) above, no patent license is
|
||||
granted: (1) for code that You delete from the Original
|
||||
Software, or (2) for infringements caused by: (i) the
|
||||
modification of the Original Software, or (ii) the
|
||||
combination of the Original Software with other software
|
||||
or devices.
|
||||
|
||||
2.2. Contributor Grant.
|
||||
|
||||
Conditioned upon Your compliance with Section 3.1 below and
|
||||
subject to third party intellectual property claims, each
|
||||
Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
(a) under intellectual property rights (other than patent or
|
||||
trademark) Licensable by Contributor to use, reproduce,
|
||||
modify, display, perform, sublicense and distribute the
|
||||
Modifications created by such Contributor (or portions
|
||||
thereof), either on an unmodified basis, with other
|
||||
Modifications, as Covered Software and/or as part of a
|
||||
Larger Work; and
|
||||
|
||||
(b) under Patent Claims infringed by the making, using, or
|
||||
selling of Modifications made by that Contributor either
|
||||
alone and/or in combination with its Contributor Version
|
||||
(or portions of such combination), to make, use, sell,
|
||||
offer for sale, have made, and/or otherwise dispose of:
|
||||
(1) Modifications made by that Contributor (or portions
|
||||
thereof); and (2) the combination of Modifications made by
|
||||
that Contributor with its Contributor Version (or portions
|
||||
of such combination).
|
||||
|
||||
(c) The licenses granted in Sections 2.2(a) and 2.2(b) are
|
||||
effective on the date Contributor first distributes or
|
||||
otherwise makes the Modifications available to a third
|
||||
party.
|
||||
|
||||
(d) Notwithstanding Section 2.2(b) above, no patent license is
|
||||
granted: (1) for any code that Contributor has deleted
|
||||
from the Contributor Version; (2) for infringements caused
|
||||
by: (i) third party modifications of Contributor Version,
|
||||
or (ii) the combination of Modifications made by that
|
||||
Contributor with other software (except as part of the
|
||||
Contributor Version) or other devices; or (3) under Patent
|
||||
Claims infringed by Covered Software in the absence of
|
||||
Modifications made by that Contributor.
|
||||
|
||||
3. Distribution Obligations.
|
||||
|
||||
3.1. Availability of Source Code.
|
||||
|
||||
Any Covered Software that You distribute or otherwise make
|
||||
available in Executable form must also be made available in Source
|
||||
Code form and that Source Code form must be distributed only under
|
||||
the terms of this License. You must include a copy of this
|
||||
License with every copy of the Source Code form of the Covered
|
||||
Software You distribute or otherwise make available. You must
|
||||
inform recipients of any such Covered Software in Executable form
|
||||
as to how they can obtain such Covered Software in Source Code
|
||||
form in a reasonable manner on or through a medium customarily
|
||||
used for software exchange.
|
||||
|
||||
3.2. Modifications.
|
||||
|
||||
The Modifications that You create or to which You contribute are
|
||||
governed by the terms of this License. You represent that You
|
||||
believe Your Modifications are Your original creation(s) and/or
|
||||
You have sufficient rights to grant the rights conveyed by this
|
||||
License.
|
||||
|
||||
3.3. Required Notices.
|
||||
|
||||
You must include a notice in each of Your Modifications that
|
||||
identifies You as the Contributor of the Modification. You may
|
||||
not remove or alter any copyright, patent or trademark notices
|
||||
contained within the Covered Software, or any notices of licensing
|
||||
or any descriptive text giving attribution to any Contributor or
|
||||
the Initial Developer.
|
||||
|
||||
3.4. Application of Additional Terms.
|
||||
|
||||
You may not offer or impose any terms on any Covered Software in
|
||||
Source Code form that alters or restricts the applicable version
|
||||
of this License or the recipients' rights hereunder. You may
|
||||
choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of
|
||||
Covered Software. However, you may do so only on Your own behalf,
|
||||
and not on behalf of the Initial Developer or any Contributor.
|
||||
You must make it absolutely clear that any such warranty, support,
|
||||
indemnity or liability obligation is offered by You alone, and You
|
||||
hereby agree to indemnify the Initial Developer and every
|
||||
Contributor for any liability incurred by the Initial Developer or
|
||||
such Contributor as a result of warranty, support, indemnity or
|
||||
liability terms You offer.
|
||||
|
||||
3.5. Distribution of Executable Versions.
|
||||
|
||||
You may distribute the Executable form of the Covered Software
|
||||
under the terms of this License or under the terms of a license of
|
||||
Your choice, which may contain terms different from this License,
|
||||
provided that You are in compliance with the terms of this License
|
||||
and that the license for the Executable form does not attempt to
|
||||
limit or alter the recipient's rights in the Source Code form from
|
||||
the rights set forth in this License. If You distribute the
|
||||
Covered Software in Executable form under a different license, You
|
||||
must make it absolutely clear that any terms which differ from
|
||||
this License are offered by You alone, not by the Initial
|
||||
Developer or Contributor. You hereby agree to indemnify the
|
||||
Initial Developer and every Contributor for any liability incurred
|
||||
by the Initial Developer or such Contributor as a result of any
|
||||
such terms You offer.
|
||||
|
||||
3.6. Larger Works.
|
||||
|
||||
You may create a Larger Work by combining Covered Software with
|
||||
other code not governed by the terms of this License and
|
||||
distribute the Larger Work as a single product. In such a case,
|
||||
You must make sure the requirements of this License are fulfilled
|
||||
for the Covered Software.
|
||||
|
||||
4. Versions of the License.
|
||||
|
||||
4.1. New Versions.
|
||||
|
||||
Sun Microsystems, Inc. is the initial license steward and may
|
||||
publish revised and/or new versions of this License from time to
|
||||
time. Each version will be given a distinguishing version number.
|
||||
Except as provided in Section 4.3, no one other than the license
|
||||
steward has the right to modify this License.
|
||||
|
||||
4.2. Effect of New Versions.
|
||||
|
||||
You may always continue to use, distribute or otherwise make the
|
||||
Covered Software available under the terms of the version of the
|
||||
License under which You originally received the Covered Software.
|
||||
If the Initial Developer includes a notice in the Original
|
||||
Software prohibiting it from being distributed or otherwise made
|
||||
available under any subsequent version of the License, You must
|
||||
distribute and make the Covered Software available under the terms
|
||||
of the version of the License under which You originally received
|
||||
the Covered Software. Otherwise, You may also choose to use,
|
||||
distribute or otherwise make the Covered Software available under
|
||||
the terms of any subsequent version of the License published by
|
||||
the license steward.
|
||||
|
||||
4.3. Modified Versions.
|
||||
|
||||
When You are an Initial Developer and You want to create a new
|
||||
license for Your Original Software, You may create and use a
|
||||
modified version of this License if You: (a) rename the license
|
||||
and remove any references to the name of the license steward
|
||||
(except to note that the license differs from this License); and
|
||||
(b) otherwise make it clear that the license contains terms which
|
||||
differ from this License.
|
||||
|
||||
5. DISCLAIMER OF WARRANTY.
|
||||
|
||||
COVERED SOFTWARE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS"
|
||||
BASIS, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
|
||||
INCLUDING, WITHOUT LIMITATION, WARRANTIES THAT THE COVERED
|
||||
SOFTWARE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR
|
||||
PURPOSE OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND
|
||||
PERFORMANCE OF THE COVERED SOFTWARE IS WITH YOU. SHOULD ANY
|
||||
COVERED SOFTWARE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT THE
|
||||
INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY
|
||||
NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF
|
||||
WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF
|
||||
ANY COVERED SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS
|
||||
DISCLAIMER.
|
||||
|
||||
6. TERMINATION.
|
||||
|
||||
6.1. This License and the rights granted hereunder will terminate
|
||||
automatically if You fail to comply with terms herein and fail to
|
||||
cure such breach within 30 days of becoming aware of the breach.
|
||||
Provisions which, by their nature, must remain in effect beyond
|
||||
the termination of this License shall survive.
|
||||
|
||||
6.2. If You assert a patent infringement claim (excluding
|
||||
declaratory judgment actions) against Initial Developer or a
|
||||
Contributor (the Initial Developer or Contributor against whom You
|
||||
assert such claim is referred to as "Participant") alleging that
|
||||
the Participant Software (meaning the Contributor Version where
|
||||
the Participant is a Contributor or the Original Software where
|
||||
the Participant is the Initial Developer) directly or indirectly
|
||||
infringes any patent, then any and all rights granted directly or
|
||||
indirectly to You by such Participant, the Initial Developer (if
|
||||
the Initial Developer is not the Participant) and all Contributors
|
||||
under Sections 2.1 and/or 2.2 of this License shall, upon 60 days
|
||||
notice from Participant terminate prospectively and automatically
|
||||
at the expiration of such 60 day notice period, unless if within
|
||||
such 60 day period You withdraw Your claim with respect to the
|
||||
Participant Software against such Participant either unilaterally
|
||||
or pursuant to a written agreement with Participant.
|
||||
|
||||
6.3. In the event of termination under Sections 6.1 or 6.2 above,
|
||||
all end user licenses that have been validly granted by You or any
|
||||
distributor hereunder prior to termination (excluding licenses
|
||||
granted to You by any distributor) shall survive termination.
|
||||
|
||||
7. LIMITATION OF LIABILITY.
|
||||
|
||||
UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT
|
||||
(INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE
|
||||
INITIAL DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF
|
||||
COVERED SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE
|
||||
LIABLE TO ANY PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
|
||||
CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT
|
||||
LIMITATION, DAMAGES FOR LOST PROFITS, LOSS OF GOODWILL, WORK
|
||||
STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER
|
||||
COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN
|
||||
INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF
|
||||
LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL
|
||||
INJURY RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT
|
||||
APPLICABLE LAW PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO
|
||||
NOT ALLOW THE EXCLUSION OR LIMITATION OF INCIDENTAL OR
|
||||
CONSEQUENTIAL DAMAGES, SO THIS EXCLUSION AND LIMITATION MAY NOT
|
||||
APPLY TO YOU.
|
||||
|
||||
8. U.S. GOVERNMENT END USERS.
|
||||
|
||||
The Covered Software is a "commercial item," as that term is
|
||||
defined in 48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial
|
||||
computer software" (as that term is defined at 48
|
||||
C.F.R. 252.227-7014(a)(1)) and "commercial computer software
|
||||
documentation" as such terms are used in 48 C.F.R. 12.212
|
||||
(Sept. 1995). Consistent with 48 C.F.R. 12.212 and 48
|
||||
C.F.R. 227.7202-1 through 227.7202-4 (June 1995), all
|
||||
U.S. Government End Users acquire Covered Software with only those
|
||||
rights set forth herein. This U.S. Government Rights clause is in
|
||||
lieu of, and supersedes, any other FAR, DFAR, or other clause or
|
||||
provision that addresses Government rights in computer software
|
||||
under this License.
|
||||
|
||||
9. MISCELLANEOUS.
|
||||
|
||||
This License represents the complete agreement concerning subject
|
||||
matter hereof. If any provision of this License is held to be
|
||||
unenforceable, such provision shall be reformed only to the extent
|
||||
necessary to make it enforceable. This License shall be governed
|
||||
by the law of the jurisdiction specified in a notice contained
|
||||
within the Original Software (except to the extent applicable law,
|
||||
if any, provides otherwise), excluding such jurisdiction's
|
||||
conflict-of-law provisions. Any litigation relating to this
|
||||
License shall be subject to the jurisdiction of the courts located
|
||||
in the jurisdiction and venue specified in a notice contained
|
||||
within the Original Software, with the losing party responsible
|
||||
for costs, including, without limitation, court costs and
|
||||
reasonable attorneys' fees and expenses. The application of the
|
||||
United Nations Convention on Contracts for the International Sale
|
||||
of Goods is expressly excluded. Any law or regulation which
|
||||
provides that the language of a contract shall be construed
|
||||
against the drafter shall not apply to this License. You agree
|
||||
that You alone are responsible for compliance with the United
|
||||
States export administration regulations (and the export control
|
||||
laws and regulation of any other countries) when You use,
|
||||
distribute or otherwise make available any Covered Software.
|
||||
|
||||
10. RESPONSIBILITY FOR CLAIMS.
|
||||
|
||||
As between Initial Developer and the Contributors, each party is
|
||||
responsible for claims and damages arising, directly or
|
||||
indirectly, out of its utilization of rights under this License
|
||||
and You agree to work with Initial Developer and Contributors to
|
||||
distribute such responsibility on an equitable basis. Nothing
|
||||
herein is intended or shall be deemed to constitute any admission
|
||||
of liability.
|
||||
|
||||
--------------------------------------------------------------------
|
||||
|
||||
NOTICE PURSUANT TO SECTION 9 OF THE COMMON DEVELOPMENT AND
|
||||
DISTRIBUTION LICENSE (CDDL)
|
||||
|
||||
For Covered Software in this distribution, this License shall
|
||||
be governed by the laws of the State of California (excluding
|
||||
conflict-of-law provisions).
|
||||
|
||||
Any litigation relating to this License shall be subject to the
|
||||
jurisdiction of the Federal Courts of the Northern District of
|
||||
California and the state courts of the State of California, with
|
||||
venue lying in Santa Clara County, California.
|
10
sys/contrib/openzfs/META
Normal file
10
sys/contrib/openzfs/META
Normal file
@ -0,0 +1,10 @@
|
||||
Meta: 1
|
||||
Name: zfs
|
||||
Branch: 1.0
|
||||
Version: 0.8.0
|
||||
Release: 1
|
||||
Release-Tags: relext
|
||||
License: CDDL
|
||||
Author: OpenZFS on Linux
|
||||
Linux-Maximum: 5.6
|
||||
Linux-Minimum: 3.10
|
256
sys/contrib/openzfs/Makefile.am
Normal file
256
sys/contrib/openzfs/Makefile.am
Normal file
@ -0,0 +1,256 @@
|
||||
ACLOCAL_AMFLAGS = -I config
|
||||
|
||||
SUBDIRS = include
|
||||
if BUILD_LINUX
|
||||
SUBDIRS += rpm
|
||||
endif
|
||||
|
||||
if CONFIG_USER
|
||||
SUBDIRS += etc man scripts lib tests cmd contrib
|
||||
if BUILD_LINUX
|
||||
SUBDIRS += udev
|
||||
endif
|
||||
endif
|
||||
if CONFIG_KERNEL
|
||||
SUBDIRS += module
|
||||
|
||||
extradir = $(prefix)/src/zfs-$(VERSION)
|
||||
extra_HEADERS = zfs.release.in zfs_config.h.in
|
||||
|
||||
if BUILD_LINUX
|
||||
kerneldir = $(prefix)/src/zfs-$(VERSION)/$(LINUX_VERSION)
|
||||
nodist_kernel_HEADERS = zfs.release zfs_config.h module/$(LINUX_SYMBOLS)
|
||||
endif
|
||||
endif
|
||||
|
||||
AUTOMAKE_OPTIONS = foreign
|
||||
EXTRA_DIST = autogen.sh copy-builtin
|
||||
EXTRA_DIST += cppcheck-suppressions.txt
|
||||
EXTRA_DIST += config/config.awk config/rpm.am config/deb.am config/tgz.am
|
||||
EXTRA_DIST += META AUTHORS COPYRIGHT LICENSE NEWS NOTICE README.md
|
||||
EXTRA_DIST += CODE_OF_CONDUCT.md
|
||||
EXTRA_DIST += module/lua/README.zfs module/os/linux/spl/README.md
|
||||
|
||||
# Include all the extra licensing information for modules
|
||||
EXTRA_DIST += module/icp/algs/skein/THIRDPARTYLICENSE
|
||||
EXTRA_DIST += module/icp/algs/skein/THIRDPARTYLICENSE.descrip
|
||||
EXTRA_DIST += module/icp/asm-x86_64/aes/THIRDPARTYLICENSE.gladman
|
||||
EXTRA_DIST += module/icp/asm-x86_64/aes/THIRDPARTYLICENSE.gladman.descrip
|
||||
EXTRA_DIST += module/icp/asm-x86_64/aes/THIRDPARTYLICENSE.openssl
|
||||
EXTRA_DIST += module/icp/asm-x86_64/aes/THIRDPARTYLICENSE.openssl.descrip
|
||||
EXTRA_DIST += module/icp/asm-x86_64/modes/THIRDPARTYLICENSE.cryptogams
|
||||
EXTRA_DIST += module/icp/asm-x86_64/modes/THIRDPARTYLICENSE.cryptogams.descrip
|
||||
EXTRA_DIST += module/icp/asm-x86_64/modes/THIRDPARTYLICENSE.openssl
|
||||
EXTRA_DIST += module/icp/asm-x86_64/modes/THIRDPARTYLICENSE.openssl.descrip
|
||||
EXTRA_DIST += module/os/linux/spl/THIRDPARTYLICENSE.gplv2
|
||||
EXTRA_DIST += module/os/linux/spl/THIRDPARTYLICENSE.gplv2.descrip
|
||||
EXTRA_DIST += module/zfs/THIRDPARTYLICENSE.cityhash
|
||||
EXTRA_DIST += module/zfs/THIRDPARTYLICENSE.cityhash.descrip
|
||||
|
||||
@CODE_COVERAGE_RULES@
|
||||
|
||||
GITREV = include/zfs_gitrev.h
|
||||
|
||||
PHONY = gitrev
|
||||
gitrev:
|
||||
$(AM_V_GEN)$(top_srcdir)/scripts/make_gitrev.sh $(GITREV)
|
||||
|
||||
all: gitrev
|
||||
|
||||
# Double-colon rules are allowed; there are multiple independent definitions.
|
||||
maintainer-clean-local::
|
||||
-$(RM) $(GITREV)
|
||||
|
||||
distclean-local::
|
||||
-$(RM) -R autom4te*.cache build
|
||||
-find . \( -name SCCS -o -name BitKeeper -o -name .svn -o -name CVS \
|
||||
-o -name .pc -o -name .hg -o -name .git \) -prune -o \
|
||||
\( -name '*.orig' -o -name '*.rej' -o -name '*~' \
|
||||
-o -name '*.bak' -o -name '#*#' -o -name '.*.orig' \
|
||||
-o -name '.*.rej' -o -size 0 -o -name '*%' -o -name '.*.cmd' \
|
||||
-o -name 'core' -o -name 'Makefile' -o -name 'Module.symvers' \
|
||||
-o -name '*.order' -o -name '*.markers' -o -name '*.gcda' \
|
||||
-o -name '*.gcno' \) \
|
||||
-type f -print | xargs $(RM)
|
||||
|
||||
all-local:
|
||||
-[ -x ${top_builddir}/scripts/zfs-tests.sh ] && \
|
||||
${top_builddir}/scripts/zfs-tests.sh -c
|
||||
|
||||
dist-hook:
|
||||
$(AM_V_GEN)$(top_srcdir)/scripts/make_gitrev.sh -D $(distdir) $(GITREV)
|
||||
$(SED) ${ac_inplace} -e 's/Release:[[:print:]]*/Release: $(RELEASE)/' \
|
||||
$(distdir)/META
|
||||
|
||||
if BUILD_LINUX
|
||||
# For compatibility, create a matching spl-x.y.z directly which contains
|
||||
# symlinks to the updated header and object file locations. These
|
||||
# compatibility links will be removed in the next major release.
|
||||
if CONFIG_KERNEL
|
||||
install-data-hook:
|
||||
rm -rf $(DESTDIR)$(prefix)/src/spl-$(VERSION) && \
|
||||
mkdir $(DESTDIR)$(prefix)/src/spl-$(VERSION) && \
|
||||
cd $(DESTDIR)$(prefix)/src/spl-$(VERSION) && \
|
||||
ln -s ../zfs-$(VERSION)/include/spl include && \
|
||||
ln -s ../zfs-$(VERSION)/$(LINUX_VERSION) $(LINUX_VERSION) && \
|
||||
ln -s ../zfs-$(VERSION)/zfs_config.h.in spl_config.h.in && \
|
||||
ln -s ../zfs-$(VERSION)/zfs.release.in spl.release.in && \
|
||||
cd $(DESTDIR)$(prefix)/src/zfs-$(VERSION)/$(LINUX_VERSION) && \
|
||||
ln -fs zfs_config.h spl_config.h && \
|
||||
ln -fs zfs.release spl.release
|
||||
endif
|
||||
endif
|
||||
|
||||
PHONY += codecheck
|
||||
codecheck: cstyle shellcheck checkbashisms flake8 mancheck testscheck vcscheck
|
||||
|
||||
PHONY += checkstyle
|
||||
checkstyle: codecheck commitcheck
|
||||
|
||||
PHONY += commitcheck
|
||||
commitcheck:
|
||||
@if git rev-parse --git-dir > /dev/null 2>&1; then \
|
||||
${top_srcdir}/scripts/commitcheck.sh; \
|
||||
fi
|
||||
|
||||
PHONY += cstyle
|
||||
cstyle:
|
||||
@find ${top_srcdir} -name build -prune \
|
||||
-o -type f -name '*.[hc]' \
|
||||
! -name 'zfs_config.*' ! -name '*.mod.c' \
|
||||
! -name 'opt_global.h' ! -name '*_if*.h' \
|
||||
! -path './module/zstd/lib/*' \
|
||||
-exec ${top_srcdir}/scripts/cstyle.pl -cpP {} \+
|
||||
|
||||
filter_executable = -exec test -x '{}' \; -print
|
||||
|
||||
PHONY += shellcheck
|
||||
shellcheck:
|
||||
@if type shellcheck > /dev/null 2>&1; then \
|
||||
shellcheck --exclude=SC1090 --exclude=SC1117 --format=gcc \
|
||||
$$(find ${top_srcdir}/scripts/*.sh -type f) \
|
||||
$$(find ${top_srcdir}/cmd/zed/zed.d/*.sh -type f) \
|
||||
$$(find ${top_srcdir}/cmd/zpool/zpool.d/* \
|
||||
-type f ${filter_executable}); \
|
||||
else \
|
||||
echo "skipping shellcheck because shellcheck is not installed"; \
|
||||
fi
|
||||
|
||||
PHONY += checkbashisms
|
||||
checkbashisms:
|
||||
@if type checkbashisms > /dev/null 2>&1; then \
|
||||
checkbashisms -n -p -x \
|
||||
$$(find ${top_srcdir} \
|
||||
-name '.git' -prune \
|
||||
-o -name 'build' -prune \
|
||||
-o -name 'tests' -prune \
|
||||
-o -name 'config' -prune \
|
||||
-o -name 'zed-functions.sh*' -prune \
|
||||
-o -name 'zfs-import*' -prune \
|
||||
-o -name 'zfs-mount*' -prune \
|
||||
-o -name 'zfs-zed*' -prune \
|
||||
-o -name 'smart' -prune \
|
||||
-o -name 'paxcheck.sh' -prune \
|
||||
-o -name 'make_gitrev.sh' -prune \
|
||||
-o -type f ! -name 'config*' \
|
||||
! -name 'libtool' \
|
||||
-exec bash -c 'awk "NR==1 && /\#\!.*bin\/sh.*/ {print FILENAME;}" "{}"' \;); \
|
||||
else \
|
||||
echo "skipping checkbashisms because checkbashisms is not installed"; \
|
||||
fi
|
||||
|
||||
PHONY += mancheck
|
||||
mancheck:
|
||||
@if type mandoc > /dev/null 2>&1; then \
|
||||
find ${top_srcdir}/man/man8 -type f -name 'zfs.8' \
|
||||
-o -name 'zpool.8' -o -name 'zdb.8' \
|
||||
-o -name 'zgenhostid.8' | \
|
||||
xargs mandoc -Tlint -Werror; \
|
||||
else \
|
||||
echo "skipping mancheck because mandoc is not installed"; \
|
||||
fi
|
||||
|
||||
if BUILD_LINUX
|
||||
stat_fmt = -c '%A %n'
|
||||
else
|
||||
stat_fmt = -f '%Sp %N'
|
||||
endif
|
||||
|
||||
PHONY += testscheck
|
||||
testscheck:
|
||||
@find ${top_srcdir}/tests/zfs-tests -type f \
|
||||
\( -name '*.ksh' -not ${filter_executable} \) -o \
|
||||
\( -name '*.kshlib' ${filter_executable} \) -o \
|
||||
\( -name '*.shlib' ${filter_executable} \) -o \
|
||||
\( -name '*.cfg' ${filter_executable} \) | \
|
||||
xargs -r stat ${stat_fmt} | \
|
||||
awk '{c++; print} END {if(c>0) exit 1}'
|
||||
|
||||
PHONY += vcscheck
|
||||
vcscheck:
|
||||
@if git rev-parse --git-dir > /dev/null 2>&1; then \
|
||||
git ls-files . --exclude-standard --others | \
|
||||
awk '{c++; print} END {if(c>0) exit 1}' ; \
|
||||
fi
|
||||
|
||||
PHONY += lint
|
||||
lint: cppcheck paxcheck
|
||||
|
||||
PHONY += cppcheck
|
||||
cppcheck:
|
||||
@if type cppcheck > /dev/null 2>&1; then \
|
||||
cppcheck --quiet --force --error-exitcode=2 --inline-suppr \
|
||||
--suppressions-list=${top_srcdir}/cppcheck-suppressions.txt \
|
||||
-UHAVE_SSE2 -UHAVE_AVX512F -UHAVE_UIO_ZEROCOPY \
|
||||
${top_srcdir}; \
|
||||
else \
|
||||
echo "skipping cppcheck because cppcheck is not installed"; \
|
||||
fi
|
||||
|
||||
PHONY += paxcheck
|
||||
paxcheck:
|
||||
@if type scanelf > /dev/null 2>&1; then \
|
||||
${top_srcdir}/scripts/paxcheck.sh ${top_builddir}; \
|
||||
else \
|
||||
echo "skipping paxcheck because scanelf is not installed"; \
|
||||
fi
|
||||
|
||||
PHONY += flake8
|
||||
flake8:
|
||||
@if type flake8 > /dev/null 2>&1; then \
|
||||
flake8 ${top_srcdir}; \
|
||||
else \
|
||||
echo "skipping flake8 because flake8 is not installed"; \
|
||||
fi
|
||||
|
||||
PHONY += ctags
|
||||
ctags:
|
||||
$(RM) tags
|
||||
find $(top_srcdir) -name '.?*' -prune \
|
||||
-o -type f -name '*.[hcS]' -print | xargs ctags -a
|
||||
|
||||
PHONY += etags
|
||||
etags:
|
||||
$(RM) TAGS
|
||||
find $(top_srcdir) -name '.?*' -prune \
|
||||
-o -type f -name '*.[hcS]' -print | xargs etags -a
|
||||
|
||||
PHONY += cscopelist
|
||||
cscopelist:
|
||||
find $(top_srcdir) -name '.?*' -prune \
|
||||
-o -type f -name '*.[hc]' -print >cscope.files
|
||||
|
||||
PHONY += tags
|
||||
tags: ctags etags
|
||||
|
||||
PHONY += pkg pkg-dkms pkg-kmod pkg-utils
|
||||
pkg: @DEFAULT_PACKAGE@
|
||||
pkg-dkms: @DEFAULT_PACKAGE@-dkms
|
||||
pkg-kmod: @DEFAULT_PACKAGE@-kmod
|
||||
pkg-utils: @DEFAULT_PACKAGE@-utils
|
||||
|
||||
include config/rpm.am
|
||||
include config/deb.am
|
||||
include config/tgz.am
|
||||
|
||||
.PHONY: $(PHONY)
|
3
sys/contrib/openzfs/NEWS
Normal file
3
sys/contrib/openzfs/NEWS
Normal file
@ -0,0 +1,3 @@
|
||||
Descriptions of all releases can be found on github:
|
||||
|
||||
https://github.com/zfsonlinux/zfs/releases
|
16
sys/contrib/openzfs/NOTICE
Normal file
16
sys/contrib/openzfs/NOTICE
Normal file
@ -0,0 +1,16 @@
|
||||
This work was produced under the auspices of the U.S. Department of Energy by
|
||||
Lawrence Livermore National Laboratory under Contract DE-AC52-07NA27344.
|
||||
|
||||
This work was prepared as an account of work sponsored by an agency of the
|
||||
United States Government. Neither the United States Government nor Lawrence
|
||||
Livermore National Security, LLC, nor any of their employees makes any warranty,
|
||||
expressed or implied, or assumes any legal liability or responsibility for the
|
||||
accuracy, completeness, or usefulness of any information, apparatus, product, or
|
||||
process disclosed, or represents that its use would not infringe privately owned
|
||||
rights. Reference herein to any specific commercial product, process, or service
|
||||
by trade name, trademark, manufacturer, or otherwise does not necessarily
|
||||
constitute or imply its endorsement, recommendation, or favoring by the United
|
||||
States Government or Lawrence Livermore National Security, LLC. The views and
|
||||
opinions of authors expressed herein do not necessarily state or reflect those
|
||||
of the United States Government or Lawrence Livermore National Security, LLC,
|
||||
and shall not be used for advertising or product endorsement purposes.
|
35
sys/contrib/openzfs/README.md
Normal file
35
sys/contrib/openzfs/README.md
Normal file
@ -0,0 +1,35 @@
|
||||
![img](https://openzfs.github.io/openzfs-docs/_static/img/logo/480px-Open-ZFS-Secondary-Logo-Colour-halfsize.png)
|
||||
|
||||
OpenZFS is an advanced file system and volume manager which was originally
|
||||
developed for Solaris and is now maintained by the OpenZFS community.
|
||||
This repository contains the code for running OpenZFS on Linux and FreeBSD.
|
||||
|
||||
[![codecov](https://codecov.io/gh/openzfs/zfs/branch/master/graph/badge.svg)](https://codecov.io/gh/openzfs/zfs)
|
||||
[![coverity](https://scan.coverity.com/projects/1973/badge.svg)](https://scan.coverity.com/projects/openzfs-zfs)
|
||||
|
||||
# Official Resources
|
||||
|
||||
* [Documentation](https://openzfs.github.io/openzfs-docs/) - for using and developing this repo
|
||||
* [ZoL Site](https://zfsonlinux.org) - Linux release info & links
|
||||
* [Mailing lists](https://openzfs.github.io/openzfs-docs/Project%20and%20Community/Mailing%20Lists.html)
|
||||
* [OpenZFS site](http://open-zfs.org/) - for conference videos and info on other platforms (illumos, OSX, Windows, etc)
|
||||
|
||||
# Installation
|
||||
|
||||
Full documentation for installing OpenZFS on your favorite Linux distribution can
|
||||
be found at the [ZoL Site](https://zfsonlinux.org/).
|
||||
|
||||
# Contribute & Develop
|
||||
|
||||
We have a separate document with [contribution guidelines](./.github/CONTRIBUTING.md).
|
||||
|
||||
We have a [Code of Conduct](./CODE_OF_CONDUCT.md).
|
||||
|
||||
# Release
|
||||
|
||||
OpenZFS is released under a CDDL license.
|
||||
For more details see the NOTICE, LICENSE and COPYRIGHT files; `UCRL-CODE-235197`
|
||||
|
||||
# Supported Kernels
|
||||
* The `META` file contains the officially recognized supported Linux kernel versions.
|
||||
* Supported FreeBSD versions are 12-STABLE and 13-CURRENT.
|
50
sys/contrib/openzfs/TEST
Normal file
50
sys/contrib/openzfs/TEST
Normal file
@ -0,0 +1,50 @@
|
||||
#!/bin/sh
|
||||
|
||||
### prepare
|
||||
#TEST_PREPARE_WATCHDOG="yes"
|
||||
#TEST_PREPARE_SHARES="yes"
|
||||
|
||||
### ztest
|
||||
#TEST_ZTEST_SKIP="yes"
|
||||
#TEST_ZTEST_TIMEOUT=1800
|
||||
#TEST_ZTEST_DIR="/var/tmp/"
|
||||
#TEST_ZTEST_OPTIONS="-V"
|
||||
#TEST_ZTEST_CORE_DIR="/mnt/zloop"
|
||||
|
||||
### zimport
|
||||
#TEST_ZIMPORT_SKIP="yes"
|
||||
#TEST_ZIMPORT_DIR="/var/tmp/zimport"
|
||||
#TEST_ZIMPORT_VERSIONS="master installed"
|
||||
#TEST_ZIMPORT_POOLS="zol-0.6.1 zol-0.6.2 master installed"
|
||||
#TEST_ZIMPORT_OPTIONS="-c"
|
||||
|
||||
### xfstests
|
||||
#TEST_XFSTESTS_SKIP="yes"
|
||||
#TEST_XFSTESTS_URL="https://github.com/behlendorf/xfstests/archive/"
|
||||
#TEST_XFSTESTS_VER="zfs.tar.gz"
|
||||
#TEST_XFSTESTS_POOL="tank"
|
||||
#TEST_XFSTESTS_FS="xfstests"
|
||||
#TEST_XFSTESTS_VDEV="/var/tmp/vdev"
|
||||
#TEST_XFSTESTS_OPTIONS=""
|
||||
|
||||
### zfs-tests.sh
|
||||
#TEST_ZFSTESTS_SKIP="yes"
|
||||
#TEST_ZFSTESTS_DIR="/mnt/"
|
||||
#TEST_ZFSTESTS_DISKS="vdb vdc vdd"
|
||||
#TEST_ZFSTESTS_DISKSIZE="8G"
|
||||
#TEST_ZFSTESTS_ITERS="1"
|
||||
#TEST_ZFSTESTS_OPTIONS="-vx"
|
||||
#TEST_ZFSTESTS_RUNFILE="linux.run"
|
||||
#TEST_ZFSTESTS_TAGS="functional"
|
||||
|
||||
### zfsstress
|
||||
#TEST_ZFSSTRESS_SKIP="yes"
|
||||
#TEST_ZFSSTRESS_URL="https://github.com/nedbass/zfsstress/archive/"
|
||||
#TEST_ZFSSTRESS_VER="master.tar.gz"
|
||||
#TEST_ZFSSTRESS_RUNTIME=300
|
||||
#TEST_ZFSSTRESS_POOL="tank"
|
||||
#TEST_ZFSSTRESS_FS="fish"
|
||||
#TEST_ZFSSTRESS_FSOPT="-o overlay=on"
|
||||
#TEST_ZFSSTRESS_VDEV="/var/tmp/vdev"
|
||||
#TEST_ZFSSTRESS_DIR="/$TEST_ZFSSTRESS_POOL/$TEST_ZFSSTRESS_FS"
|
||||
#TEST_ZFSSTRESS_OPTIONS=""
|
4
sys/contrib/openzfs/autogen.sh
Executable file
4
sys/contrib/openzfs/autogen.sh
Executable file
@ -0,0 +1,4 @@
|
||||
#!/bin/sh
|
||||
|
||||
autoreconf -fiv || exit 1
|
||||
rm -Rf autom4te.cache
|
10
sys/contrib/openzfs/cmd/Makefile.am
Normal file
10
sys/contrib/openzfs/cmd/Makefile.am
Normal file
@ -0,0 +1,10 @@
|
||||
SUBDIRS = zfs zpool zdb zhack zinject zstream zstreamdump ztest
|
||||
SUBDIRS += fsck_zfs vdev_id raidz_test zfs_ids_to_path
|
||||
|
||||
if USING_PYTHON
|
||||
SUBDIRS += arcstat arc_summary dbufstat
|
||||
endif
|
||||
|
||||
if BUILD_LINUX
|
||||
SUBDIRS += mount_zfs zed zgenhostid zvol_id zvol_wait
|
||||
endif
|
1
sys/contrib/openzfs/cmd/arc_summary/.gitignore
vendored
Normal file
1
sys/contrib/openzfs/cmd/arc_summary/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
arc_summary
|
13
sys/contrib/openzfs/cmd/arc_summary/Makefile.am
Normal file
13
sys/contrib/openzfs/cmd/arc_summary/Makefile.am
Normal file
@ -0,0 +1,13 @@
|
||||
bin_SCRIPTS = arc_summary
|
||||
|
||||
CLEANFILES = arc_summary
|
||||
EXTRA_DIST = arc_summary2 arc_summary3
|
||||
|
||||
if USING_PYTHON_2
|
||||
SCRIPT = arc_summary2
|
||||
else
|
||||
SCRIPT = arc_summary3
|
||||
endif
|
||||
|
||||
arc_summary: $(SCRIPT)
|
||||
cp $< $@
|
1093
sys/contrib/openzfs/cmd/arc_summary/arc_summary2
Executable file
1093
sys/contrib/openzfs/cmd/arc_summary/arc_summary2
Executable file
File diff suppressed because it is too large
Load Diff
943
sys/contrib/openzfs/cmd/arc_summary/arc_summary3
Executable file
943
sys/contrib/openzfs/cmd/arc_summary/arc_summary3
Executable file
@ -0,0 +1,943 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Copyright (c) 2008 Ben Rockwood <benr@cuddletech.com>,
|
||||
# Copyright (c) 2010 Martin Matuska <mm@FreeBSD.org>,
|
||||
# Copyright (c) 2010-2011 Jason J. Hellenthal <jhell@DataIX.net>,
|
||||
# Copyright (c) 2017 Scot W. Stevenson <scot.stevenson@gmail.com>
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions
|
||||
# are met:
|
||||
#
|
||||
# 1. Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# 2. Redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
# SUCH DAMAGE.
|
||||
"""Print statistics on the ZFS ARC Cache and other information
|
||||
|
||||
Provides basic information on the ARC, its efficiency, the L2ARC (if present),
|
||||
the Data Management Unit (DMU), Virtual Devices (VDEVs), and tunables. See
|
||||
the in-source documentation and code at
|
||||
https://github.com/zfsonlinux/zfs/blob/master/module/zfs/arc.c for details.
|
||||
The original introduction to arc_summary can be found at
|
||||
http://cuddletech.com/?p=454
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
DESCRIPTION = 'Print ARC and other statistics for ZFS on Linux'
|
||||
INDENT = ' '*8
|
||||
LINE_LENGTH = 72
|
||||
DATE_FORMAT = '%a %b %d %H:%M:%S %Y'
|
||||
TITLE = 'ZFS Subsystem Report'
|
||||
|
||||
SECTIONS = 'arc archits dmu l2arc spl tunables vdev zil'.split()
|
||||
SECTION_HELP = 'print info from one section ('+' '.join(SECTIONS)+')'
|
||||
|
||||
# Tunables and SPL are handled separately because they come from
|
||||
# different sources
|
||||
SECTION_PATHS = {'arc': 'arcstats',
|
||||
'dmu': 'dmu_tx',
|
||||
'l2arc': 'arcstats', # L2ARC stuff lives in arcstats
|
||||
'vdev': 'vdev_cache_stats',
|
||||
'xuio': 'xuio_stats',
|
||||
'zfetch': 'zfetchstats',
|
||||
'zil': 'zil'}
|
||||
|
||||
parser = argparse.ArgumentParser(description=DESCRIPTION)
|
||||
parser.add_argument('-a', '--alternate', action='store_true', default=False,
|
||||
help='use alternate formatting for tunables and SPL',
|
||||
dest='alt')
|
||||
parser.add_argument('-d', '--description', action='store_true', default=False,
|
||||
help='print descriptions with tunables and SPL',
|
||||
dest='desc')
|
||||
parser.add_argument('-g', '--graph', action='store_true', default=False,
|
||||
help='print graph on ARC use and exit', dest='graph')
|
||||
parser.add_argument('-p', '--page', type=int, dest='page',
|
||||
help='print page by number (DEPRECATED, use "-s")')
|
||||
parser.add_argument('-r', '--raw', action='store_true', default=False,
|
||||
help='dump all available data with minimal formatting',
|
||||
dest='raw')
|
||||
parser.add_argument('-s', '--section', dest='section', help=SECTION_HELP)
|
||||
ARGS = parser.parse_args()
|
||||
|
||||
|
||||
if sys.platform.startswith('freebsd'):
|
||||
# Requires py36-sysctl on FreeBSD
|
||||
import sysctl
|
||||
|
||||
VDEV_CACHE_SIZE = 'vdev.cache_size'
|
||||
|
||||
def load_kstats(section):
|
||||
base = 'kstat.zfs.misc.{section}.'.format(section=section)
|
||||
# base is removed from the name
|
||||
fmt = lambda kstat: '{name} : {value}'.format(name=kstat.name[len(base):],
|
||||
value=kstat.value)
|
||||
return [fmt(kstat) for kstat in sysctl.filter(base)]
|
||||
|
||||
def get_params(base):
|
||||
cut = 8 # = len('vfs.zfs.')
|
||||
return {ctl.name[cut:]: str(ctl.value) for ctl in sysctl.filter(base)}
|
||||
|
||||
def get_tunable_params():
|
||||
return get_params('vfs.zfs')
|
||||
|
||||
def get_vdev_params():
|
||||
return get_params('vfs.zfs.vdev')
|
||||
|
||||
def get_version_impl(request):
|
||||
# FreeBSD reports versions for zpl and spa instead of zfs and spl.
|
||||
name = {'zfs': 'zpl',
|
||||
'spl': 'spa'}[request]
|
||||
mib = 'vfs.zfs.version.{}'.format(name)
|
||||
version = sysctl.filter(mib)[0].value
|
||||
return '{} version {}'.format(name, version)
|
||||
|
||||
def get_descriptions(_request):
|
||||
# py-sysctl doesn't give descriptions, so we have to shell out.
|
||||
command = ['sysctl', '-d', 'vfs.zfs']
|
||||
|
||||
# The recommended way to do this is with subprocess.run(). However,
|
||||
# some installed versions of Python are < 3.5, so we offer them
|
||||
# the option of doing it the old way (for now)
|
||||
if 'run' in dir(subprocess):
|
||||
info = subprocess.run(command, stdout=subprocess.PIPE,
|
||||
universal_newlines=True)
|
||||
lines = info.stdout.split('\n')
|
||||
else:
|
||||
info = subprocess.check_output(command, universal_newlines=True)
|
||||
lines = info.split('\n')
|
||||
|
||||
def fmt(line):
|
||||
name, desc = line.split(':', 1)
|
||||
return (name.strip(), desc.strip())
|
||||
|
||||
return dict([fmt(line) for line in lines if len(line) > 0])
|
||||
|
||||
|
||||
elif sys.platform.startswith('linux'):
|
||||
KSTAT_PATH = '/proc/spl/kstat/zfs'
|
||||
SPL_PATH = '/sys/module/spl/parameters'
|
||||
TUNABLES_PATH = '/sys/module/zfs/parameters'
|
||||
|
||||
VDEV_CACHE_SIZE = 'zfs_vdev_cache_size'
|
||||
|
||||
def load_kstats(section):
|
||||
path = os.path.join(KSTAT_PATH, section)
|
||||
with open(path) as f:
|
||||
return list(f)[2:] # Get rid of header
|
||||
|
||||
def get_params(basepath):
|
||||
"""Collect information on the Solaris Porting Layer (SPL) or the
|
||||
tunables, depending on the PATH given. Does not check if PATH is
|
||||
legal.
|
||||
"""
|
||||
result = {}
|
||||
for name in os.listdir(basepath):
|
||||
path = os.path.join(basepath, name)
|
||||
with open(path) as f:
|
||||
value = f.read()
|
||||
result[name] = value.strip()
|
||||
return result
|
||||
|
||||
def get_spl_params():
|
||||
return get_params(SPL_PATH)
|
||||
|
||||
def get_tunable_params():
|
||||
return get_params(TUNABLES_PATH)
|
||||
|
||||
def get_vdev_params():
|
||||
return get_params(TUNABLES_PATH)
|
||||
|
||||
def get_version_impl(request):
|
||||
# The original arc_summary called /sbin/modinfo/{spl,zfs} to get
|
||||
# the version information. We switch to /sys/module/{spl,zfs}/version
|
||||
# to make sure we get what is really loaded in the kernel
|
||||
command = ["cat", "/sys/module/{0}/version".format(request)]
|
||||
req = request.upper()
|
||||
|
||||
# The recommended way to do this is with subprocess.run(). However,
|
||||
# some installed versions of Python are < 3.5, so we offer them
|
||||
# the option of doing it the old way (for now)
|
||||
if 'run' in dir(subprocess):
|
||||
info = subprocess.run(command, stdout=subprocess.PIPE,
|
||||
universal_newlines=True)
|
||||
version = info.stdout.strip()
|
||||
else:
|
||||
info = subprocess.check_output(command, universal_newlines=True)
|
||||
version = info.strip()
|
||||
|
||||
return version
|
||||
|
||||
def get_descriptions(request):
|
||||
"""Get the descriptions of the Solaris Porting Layer (SPL) or the
|
||||
tunables, return with minimal formatting.
|
||||
"""
|
||||
|
||||
if request not in ('spl', 'zfs'):
|
||||
print('ERROR: description of "{0}" requested)'.format(request))
|
||||
sys.exit(1)
|
||||
|
||||
descs = {}
|
||||
target_prefix = 'parm:'
|
||||
|
||||
# We would prefer to do this with /sys/modules -- see the discussion at
|
||||
# get_version() -- but there isn't a way to get the descriptions from
|
||||
# there, so we fall back on modinfo
|
||||
command = ["/sbin/modinfo", request, "-0"]
|
||||
|
||||
# The recommended way to do this is with subprocess.run(). However,
|
||||
# some installed versions of Python are < 3.5, so we offer them
|
||||
# the option of doing it the old way (for now)
|
||||
info = ''
|
||||
|
||||
try:
|
||||
|
||||
if 'run' in dir(subprocess):
|
||||
info = subprocess.run(command, stdout=subprocess.PIPE,
|
||||
universal_newlines=True)
|
||||
raw_output = info.stdout.split('\0')
|
||||
else:
|
||||
info = subprocess.check_output(command,
|
||||
universal_newlines=True)
|
||||
raw_output = info.split('\0')
|
||||
|
||||
except subprocess.CalledProcessError:
|
||||
print("Error: Descriptions not available",
|
||||
"(can't access kernel module)")
|
||||
sys.exit(1)
|
||||
|
||||
for line in raw_output:
|
||||
|
||||
if not line.startswith(target_prefix):
|
||||
continue
|
||||
|
||||
line = line[len(target_prefix):].strip()
|
||||
name, raw_desc = line.split(':', 1)
|
||||
desc = raw_desc.rsplit('(', 1)[0]
|
||||
|
||||
if desc == '':
|
||||
desc = '(No description found)'
|
||||
|
||||
descs[name.strip()] = desc.strip()
|
||||
|
||||
return descs
|
||||
|
||||
|
||||
def cleanup_line(single_line):
|
||||
"""Format a raw line of data from /proc and isolate the name value
|
||||
part, returning a tuple with each. Currently, this gets rid of the
|
||||
middle '4'. For example "arc_no_grow 4 0" returns the tuple
|
||||
("arc_no_grow", "0").
|
||||
"""
|
||||
name, _, value = single_line.split()
|
||||
|
||||
return name, value
|
||||
|
||||
|
||||
def draw_graph(kstats_dict):
|
||||
"""Draw a primitive graph representing the basic information on the
|
||||
ARC -- its size and the proportion used by MFU and MRU -- and quit.
|
||||
We use max size of the ARC to calculate how full it is. This is a
|
||||
very rough representation.
|
||||
"""
|
||||
|
||||
arc_stats = isolate_section('arcstats', kstats_dict)
|
||||
|
||||
GRAPH_INDENT = ' '*4
|
||||
GRAPH_WIDTH = 60
|
||||
arc_size = f_bytes(arc_stats['size'])
|
||||
arc_perc = f_perc(arc_stats['size'], arc_stats['c_max'])
|
||||
mfu_size = f_bytes(arc_stats['mfu_size'])
|
||||
mru_size = f_bytes(arc_stats['mru_size'])
|
||||
meta_limit = f_bytes(arc_stats['arc_meta_limit'])
|
||||
meta_size = f_bytes(arc_stats['arc_meta_used'])
|
||||
dnode_limit = f_bytes(arc_stats['arc_dnode_limit'])
|
||||
dnode_size = f_bytes(arc_stats['dnode_size'])
|
||||
|
||||
info_form = ('ARC: {0} ({1}) MFU: {2} MRU: {3} META: {4} ({5}) '
|
||||
'DNODE {6} ({7})')
|
||||
info_line = info_form.format(arc_size, arc_perc, mfu_size, mru_size,
|
||||
meta_size, meta_limit, dnode_size,
|
||||
dnode_limit)
|
||||
info_spc = ' '*int((GRAPH_WIDTH-len(info_line))/2)
|
||||
info_line = GRAPH_INDENT+info_spc+info_line
|
||||
|
||||
graph_line = GRAPH_INDENT+'+'+('-'*(GRAPH_WIDTH-2))+'+'
|
||||
|
||||
mfu_perc = float(int(arc_stats['mfu_size'])/int(arc_stats['c_max']))
|
||||
mru_perc = float(int(arc_stats['mru_size'])/int(arc_stats['c_max']))
|
||||
arc_perc = float(int(arc_stats['size'])/int(arc_stats['c_max']))
|
||||
total_ticks = float(arc_perc)*GRAPH_WIDTH
|
||||
mfu_ticks = mfu_perc*GRAPH_WIDTH
|
||||
mru_ticks = mru_perc*GRAPH_WIDTH
|
||||
other_ticks = total_ticks-(mfu_ticks+mru_ticks)
|
||||
|
||||
core_form = 'F'*int(mfu_ticks)+'R'*int(mru_ticks)+'O'*int(other_ticks)
|
||||
core_spc = ' '*(GRAPH_WIDTH-(2+len(core_form)))
|
||||
core_line = GRAPH_INDENT+'|'+core_form+core_spc+'|'
|
||||
|
||||
for line in ('', info_line, graph_line, core_line, graph_line, ''):
|
||||
print(line)
|
||||
|
||||
|
||||
def f_bytes(byte_string):
|
||||
"""Return human-readable representation of a byte value in
|
||||
powers of 2 (eg "KiB" for "kibibytes", etc) to two decimal
|
||||
points. Values smaller than one KiB are returned without
|
||||
decimal points. Note "bytes" is a reserved keyword.
|
||||
"""
|
||||
|
||||
prefixes = ([2**80, "YiB"], # yobibytes (yotta)
|
||||
[2**70, "ZiB"], # zebibytes (zetta)
|
||||
[2**60, "EiB"], # exbibytes (exa)
|
||||
[2**50, "PiB"], # pebibytes (peta)
|
||||
[2**40, "TiB"], # tebibytes (tera)
|
||||
[2**30, "GiB"], # gibibytes (giga)
|
||||
[2**20, "MiB"], # mebibytes (mega)
|
||||
[2**10, "KiB"]) # kibibytes (kilo)
|
||||
|
||||
bites = int(byte_string)
|
||||
|
||||
if bites >= 2**10:
|
||||
for limit, unit in prefixes:
|
||||
|
||||
if bites >= limit:
|
||||
value = bites / limit
|
||||
break
|
||||
|
||||
result = '{0:.1f} {1}'.format(value, unit)
|
||||
else:
|
||||
result = '{0} Bytes'.format(bites)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def f_hits(hits_string):
|
||||
"""Create a human-readable representation of the number of hits.
|
||||
The single-letter symbols used are SI to avoid the confusion caused
|
||||
by the different "short scale" and "long scale" representations in
|
||||
English, which use the same words for different values. See
|
||||
https://en.wikipedia.org/wiki/Names_of_large_numbers and:
|
||||
https://physics.nist.gov/cuu/Units/prefixes.html
|
||||
"""
|
||||
|
||||
numbers = ([10**24, 'Y'], # yotta (septillion)
|
||||
[10**21, 'Z'], # zetta (sextillion)
|
||||
[10**18, 'E'], # exa (quintrillion)
|
||||
[10**15, 'P'], # peta (quadrillion)
|
||||
[10**12, 'T'], # tera (trillion)
|
||||
[10**9, 'G'], # giga (billion)
|
||||
[10**6, 'M'], # mega (million)
|
||||
[10**3, 'k']) # kilo (thousand)
|
||||
|
||||
hits = int(hits_string)
|
||||
|
||||
if hits >= 1000:
|
||||
for limit, symbol in numbers:
|
||||
|
||||
if hits >= limit:
|
||||
value = hits/limit
|
||||
break
|
||||
|
||||
result = "%0.1f%s" % (value, symbol)
|
||||
else:
|
||||
result = "%d" % hits
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def f_perc(value1, value2):
|
||||
"""Calculate percentage and return in human-readable form. If
|
||||
rounding produces the result '0.0' though the first number is
|
||||
not zero, include a 'less-than' symbol to avoid confusion.
|
||||
Division by zero is handled by returning 'n/a'; no error
|
||||
is called.
|
||||
"""
|
||||
|
||||
v1 = float(value1)
|
||||
v2 = float(value2)
|
||||
|
||||
try:
|
||||
perc = 100 * v1/v2
|
||||
except ZeroDivisionError:
|
||||
result = 'n/a'
|
||||
else:
|
||||
result = '{0:0.1f} %'.format(perc)
|
||||
|
||||
if result == '0.0 %' and v1 > 0:
|
||||
result = '< 0.1 %'
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def format_raw_line(name, value):
|
||||
"""For the --raw option for the tunable and SPL outputs, decide on the
|
||||
correct formatting based on the --alternate flag.
|
||||
"""
|
||||
|
||||
if ARGS.alt:
|
||||
result = '{0}{1}={2}'.format(INDENT, name, value)
|
||||
else:
|
||||
spc = LINE_LENGTH-(len(INDENT)+len(value))
|
||||
result = '{0}{1:<{spc}}{2}'.format(INDENT, name, value, spc=spc)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def get_kstats():
|
||||
"""Collect information on the ZFS subsystem. The step does not perform any
|
||||
further processing, giving us the option to only work on what is actually
|
||||
needed. The name "kstat" is a holdover from the Solaris utility of the same
|
||||
name.
|
||||
"""
|
||||
|
||||
result = {}
|
||||
|
||||
for section in SECTION_PATHS.values():
|
||||
if section not in result:
|
||||
result[section] = load_kstats(section)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def get_version(request):
|
||||
"""Get the version number of ZFS or SPL on this machine for header.
|
||||
Returns an error string, but does not raise an error, if we can't
|
||||
get the ZFS/SPL version.
|
||||
"""
|
||||
|
||||
if request not in ('spl', 'zfs'):
|
||||
error_msg = '(ERROR: "{0}" requested)'.format(request)
|
||||
return error_msg
|
||||
|
||||
return get_version_impl(request)
|
||||
|
||||
|
||||
def print_header():
|
||||
"""Print the initial heading with date and time as well as info on the
|
||||
kernel and ZFS versions. This is not called for the graph.
|
||||
"""
|
||||
|
||||
# datetime is now recommended over time but we keep the exact formatting
|
||||
# from the older version of arc_summary in case there are scripts
|
||||
# that expect it in this way
|
||||
daydate = time.strftime(DATE_FORMAT)
|
||||
spc_date = LINE_LENGTH-len(daydate)
|
||||
sys_version = os.uname()
|
||||
|
||||
sys_msg = sys_version.sysname+' '+sys_version.release
|
||||
zfs = get_version('zfs')
|
||||
spc_zfs = LINE_LENGTH-len(zfs)
|
||||
|
||||
machine_msg = 'Machine: '+sys_version.nodename+' ('+sys_version.machine+')'
|
||||
spl = get_version('spl')
|
||||
spc_spl = LINE_LENGTH-len(spl)
|
||||
|
||||
print('\n'+('-'*LINE_LENGTH))
|
||||
print('{0:<{spc}}{1}'.format(TITLE, daydate, spc=spc_date))
|
||||
print('{0:<{spc}}{1}'.format(sys_msg, zfs, spc=spc_zfs))
|
||||
print('{0:<{spc}}{1}\n'.format(machine_msg, spl, spc=spc_spl))
|
||||
|
||||
|
||||
def print_raw(kstats_dict):
|
||||
"""Print all available data from the system in a minimally sorted format.
|
||||
This can be used as a source to be piped through 'grep'.
|
||||
"""
|
||||
|
||||
sections = sorted(kstats_dict.keys())
|
||||
|
||||
for section in sections:
|
||||
|
||||
print('\n{0}:'.format(section.upper()))
|
||||
lines = sorted(kstats_dict[section])
|
||||
|
||||
for line in lines:
|
||||
name, value = cleanup_line(line)
|
||||
print(format_raw_line(name, value))
|
||||
|
||||
# Tunables and SPL must be handled separately because they come from a
|
||||
# different source and have descriptions the user might request
|
||||
print()
|
||||
section_spl()
|
||||
section_tunables()
|
||||
|
||||
|
||||
def isolate_section(section_name, kstats_dict):
|
||||
"""From the complete information on all sections, retrieve only those
|
||||
for one section.
|
||||
"""
|
||||
|
||||
try:
|
||||
section_data = kstats_dict[section_name]
|
||||
except KeyError:
|
||||
print('ERROR: Data on {0} not available'.format(section_data))
|
||||
sys.exit(1)
|
||||
|
||||
section_dict = dict(cleanup_line(l) for l in section_data)
|
||||
|
||||
return section_dict
|
||||
|
||||
|
||||
# Formatted output helper functions
|
||||
|
||||
|
||||
def prt_1(text, value):
|
||||
"""Print text and one value, no indent"""
|
||||
spc = ' '*(LINE_LENGTH-(len(text)+len(value)))
|
||||
print('{0}{spc}{1}'.format(text, value, spc=spc))
|
||||
|
||||
|
||||
def prt_i1(text, value):
|
||||
"""Print text and one value, with indent"""
|
||||
spc = ' '*(LINE_LENGTH-(len(INDENT)+len(text)+len(value)))
|
||||
print(INDENT+'{0}{spc}{1}'.format(text, value, spc=spc))
|
||||
|
||||
|
||||
def prt_2(text, value1, value2):
|
||||
"""Print text and two values, no indent"""
|
||||
values = '{0:>9} {1:>9}'.format(value1, value2)
|
||||
spc = ' '*(LINE_LENGTH-(len(text)+len(values)+2))
|
||||
print('{0}{spc} {1}'.format(text, values, spc=spc))
|
||||
|
||||
|
||||
def prt_i2(text, value1, value2):
|
||||
"""Print text and two values, with indent"""
|
||||
values = '{0:>9} {1:>9}'.format(value1, value2)
|
||||
spc = ' '*(LINE_LENGTH-(len(INDENT)+len(text)+len(values)+2))
|
||||
print(INDENT+'{0}{spc} {1}'.format(text, values, spc=spc))
|
||||
|
||||
|
||||
# The section output concentrates on important parameters instead of
|
||||
# being exhaustive (that is what the --raw parameter is for)
|
||||
|
||||
|
||||
def section_arc(kstats_dict):
|
||||
"""Give basic information on the ARC, MRU and MFU. This is the first
|
||||
and most used section.
|
||||
"""
|
||||
|
||||
arc_stats = isolate_section('arcstats', kstats_dict)
|
||||
|
||||
throttle = arc_stats['memory_throttle_count']
|
||||
|
||||
if throttle == '0':
|
||||
health = 'HEALTHY'
|
||||
else:
|
||||
health = 'THROTTLED'
|
||||
|
||||
prt_1('ARC status:', health)
|
||||
prt_i1('Memory throttle count:', throttle)
|
||||
print()
|
||||
|
||||
arc_size = arc_stats['size']
|
||||
arc_target_size = arc_stats['c']
|
||||
arc_max = arc_stats['c_max']
|
||||
arc_min = arc_stats['c_min']
|
||||
mfu_size = arc_stats['mfu_size']
|
||||
mru_size = arc_stats['mru_size']
|
||||
meta_limit = arc_stats['arc_meta_limit']
|
||||
meta_size = arc_stats['arc_meta_used']
|
||||
dnode_limit = arc_stats['arc_dnode_limit']
|
||||
dnode_size = arc_stats['dnode_size']
|
||||
target_size_ratio = '{0}:1'.format(int(arc_max) // int(arc_min))
|
||||
|
||||
prt_2('ARC size (current):',
|
||||
f_perc(arc_size, arc_max), f_bytes(arc_size))
|
||||
prt_i2('Target size (adaptive):',
|
||||
f_perc(arc_target_size, arc_max), f_bytes(arc_target_size))
|
||||
prt_i2('Min size (hard limit):',
|
||||
f_perc(arc_min, arc_max), f_bytes(arc_min))
|
||||
prt_i2('Max size (high water):',
|
||||
target_size_ratio, f_bytes(arc_max))
|
||||
caches_size = int(mfu_size)+int(mru_size)
|
||||
prt_i2('Most Frequently Used (MFU) cache size:',
|
||||
f_perc(mfu_size, caches_size), f_bytes(mfu_size))
|
||||
prt_i2('Most Recently Used (MRU) cache size:',
|
||||
f_perc(mru_size, caches_size), f_bytes(mru_size))
|
||||
prt_i2('Metadata cache size (hard limit):',
|
||||
f_perc(meta_limit, arc_max), f_bytes(meta_limit))
|
||||
prt_i2('Metadata cache size (current):',
|
||||
f_perc(meta_size, meta_limit), f_bytes(meta_size))
|
||||
prt_i2('Dnode cache size (hard limit):',
|
||||
f_perc(dnode_limit, meta_limit), f_bytes(dnode_limit))
|
||||
prt_i2('Dnode cache size (current):',
|
||||
f_perc(dnode_size, dnode_limit), f_bytes(dnode_size))
|
||||
print()
|
||||
|
||||
print('ARC hash breakdown:')
|
||||
prt_i1('Elements max:', f_hits(arc_stats['hash_elements_max']))
|
||||
prt_i2('Elements current:',
|
||||
f_perc(arc_stats['hash_elements'], arc_stats['hash_elements_max']),
|
||||
f_hits(arc_stats['hash_elements']))
|
||||
prt_i1('Collisions:', f_hits(arc_stats['hash_collisions']))
|
||||
|
||||
prt_i1('Chain max:', f_hits(arc_stats['hash_chain_max']))
|
||||
prt_i1('Chains:', f_hits(arc_stats['hash_chains']))
|
||||
print()
|
||||
|
||||
print('ARC misc:')
|
||||
prt_i1('Deleted:', f_hits(arc_stats['deleted']))
|
||||
prt_i1('Mutex misses:', f_hits(arc_stats['mutex_miss']))
|
||||
prt_i1('Eviction skips:', f_hits(arc_stats['evict_skip']))
|
||||
print()
|
||||
|
||||
|
||||
def section_archits(kstats_dict):
|
||||
"""Print information on how the caches are accessed ("arc hits").
|
||||
"""
|
||||
|
||||
arc_stats = isolate_section('arcstats', kstats_dict)
|
||||
all_accesses = int(arc_stats['hits'])+int(arc_stats['misses'])
|
||||
actual_hits = int(arc_stats['mfu_hits'])+int(arc_stats['mru_hits'])
|
||||
|
||||
prt_1('ARC total accesses (hits + misses):', f_hits(all_accesses))
|
||||
ta_todo = (('Cache hit ratio:', arc_stats['hits']),
|
||||
('Cache miss ratio:', arc_stats['misses']),
|
||||
('Actual hit ratio (MFU + MRU hits):', actual_hits))
|
||||
|
||||
for title, value in ta_todo:
|
||||
prt_i2(title, f_perc(value, all_accesses), f_hits(value))
|
||||
|
||||
dd_total = int(arc_stats['demand_data_hits']) +\
|
||||
int(arc_stats['demand_data_misses'])
|
||||
prt_i2('Data demand efficiency:',
|
||||
f_perc(arc_stats['demand_data_hits'], dd_total),
|
||||
f_hits(dd_total))
|
||||
|
||||
dp_total = int(arc_stats['prefetch_data_hits']) +\
|
||||
int(arc_stats['prefetch_data_misses'])
|
||||
prt_i2('Data prefetch efficiency:',
|
||||
f_perc(arc_stats['prefetch_data_hits'], dp_total),
|
||||
f_hits(dp_total))
|
||||
|
||||
known_hits = int(arc_stats['mfu_hits']) +\
|
||||
int(arc_stats['mru_hits']) +\
|
||||
int(arc_stats['mfu_ghost_hits']) +\
|
||||
int(arc_stats['mru_ghost_hits'])
|
||||
|
||||
anon_hits = int(arc_stats['hits'])-known_hits
|
||||
|
||||
print()
|
||||
print('Cache hits by cache type:')
|
||||
cl_todo = (('Most frequently used (MFU):', arc_stats['mfu_hits']),
|
||||
('Most recently used (MRU):', arc_stats['mru_hits']),
|
||||
('Most frequently used (MFU) ghost:',
|
||||
arc_stats['mfu_ghost_hits']),
|
||||
('Most recently used (MRU) ghost:',
|
||||
arc_stats['mru_ghost_hits']))
|
||||
|
||||
for title, value in cl_todo:
|
||||
prt_i2(title, f_perc(value, arc_stats['hits']), f_hits(value))
|
||||
|
||||
# For some reason, anon_hits can turn negative, which is weird. Until we
|
||||
# have figured out why this happens, we just hide the problem, following
|
||||
# the behavior of the original arc_summary.
|
||||
if anon_hits >= 0:
|
||||
prt_i2('Anonymously used:',
|
||||
f_perc(anon_hits, arc_stats['hits']), f_hits(anon_hits))
|
||||
|
||||
print()
|
||||
print('Cache hits by data type:')
|
||||
dt_todo = (('Demand data:', arc_stats['demand_data_hits']),
|
||||
('Demand prefetch data:', arc_stats['prefetch_data_hits']),
|
||||
('Demand metadata:', arc_stats['demand_metadata_hits']),
|
||||
('Demand prefetch metadata:',
|
||||
arc_stats['prefetch_metadata_hits']))
|
||||
|
||||
for title, value in dt_todo:
|
||||
prt_i2(title, f_perc(value, arc_stats['hits']), f_hits(value))
|
||||
|
||||
print()
|
||||
print('Cache misses by data type:')
|
||||
dm_todo = (('Demand data:', arc_stats['demand_data_misses']),
|
||||
('Demand prefetch data:',
|
||||
arc_stats['prefetch_data_misses']),
|
||||
('Demand metadata:', arc_stats['demand_metadata_misses']),
|
||||
('Demand prefetch metadata:',
|
||||
arc_stats['prefetch_metadata_misses']))
|
||||
|
||||
for title, value in dm_todo:
|
||||
prt_i2(title, f_perc(value, arc_stats['misses']), f_hits(value))
|
||||
|
||||
print()
|
||||
|
||||
|
||||
def section_dmu(kstats_dict):
|
||||
"""Collect information on the DMU"""
|
||||
|
||||
zfetch_stats = isolate_section('zfetchstats', kstats_dict)
|
||||
|
||||
zfetch_access_total = int(zfetch_stats['hits'])+int(zfetch_stats['misses'])
|
||||
|
||||
prt_1('DMU prefetch efficiency:', f_hits(zfetch_access_total))
|
||||
prt_i2('Hit ratio:', f_perc(zfetch_stats['hits'], zfetch_access_total),
|
||||
f_hits(zfetch_stats['hits']))
|
||||
prt_i2('Miss ratio:', f_perc(zfetch_stats['misses'], zfetch_access_total),
|
||||
f_hits(zfetch_stats['misses']))
|
||||
print()
|
||||
|
||||
|
||||
def section_l2arc(kstats_dict):
|
||||
"""Collect information on L2ARC device if present. If not, tell user
|
||||
that we're skipping the section.
|
||||
"""
|
||||
|
||||
# The L2ARC statistics live in the same section as the normal ARC stuff
|
||||
arc_stats = isolate_section('arcstats', kstats_dict)
|
||||
|
||||
if arc_stats['l2_size'] == '0':
|
||||
print('L2ARC not detected, skipping section\n')
|
||||
return
|
||||
|
||||
l2_errors = int(arc_stats['l2_writes_error']) +\
|
||||
int(arc_stats['l2_cksum_bad']) +\
|
||||
int(arc_stats['l2_io_error'])
|
||||
|
||||
l2_access_total = int(arc_stats['l2_hits'])+int(arc_stats['l2_misses'])
|
||||
health = 'HEALTHY'
|
||||
|
||||
if l2_errors > 0:
|
||||
health = 'DEGRADED'
|
||||
|
||||
prt_1('L2ARC status:', health)
|
||||
|
||||
l2_todo = (('Low memory aborts:', 'l2_abort_lowmem'),
|
||||
('Free on write:', 'l2_free_on_write'),
|
||||
('R/W clashes:', 'l2_rw_clash'),
|
||||
('Bad checksums:', 'l2_cksum_bad'),
|
||||
('I/O errors:', 'l2_io_error'))
|
||||
|
||||
for title, value in l2_todo:
|
||||
prt_i1(title, f_hits(arc_stats[value]))
|
||||
|
||||
print()
|
||||
prt_1('L2ARC size (adaptive):', f_bytes(arc_stats['l2_size']))
|
||||
prt_i2('Compressed:', f_perc(arc_stats['l2_asize'], arc_stats['l2_size']),
|
||||
f_bytes(arc_stats['l2_asize']))
|
||||
prt_i2('Header size:',
|
||||
f_perc(arc_stats['l2_hdr_size'], arc_stats['l2_size']),
|
||||
f_bytes(arc_stats['l2_hdr_size']))
|
||||
|
||||
print()
|
||||
prt_1('L2ARC breakdown:', f_hits(l2_access_total))
|
||||
prt_i2('Hit ratio:',
|
||||
f_perc(arc_stats['l2_hits'], l2_access_total),
|
||||
f_hits(arc_stats['l2_hits']))
|
||||
prt_i2('Miss ratio:',
|
||||
f_perc(arc_stats['l2_misses'], l2_access_total),
|
||||
f_hits(arc_stats['l2_misses']))
|
||||
prt_i1('Feeds:', f_hits(arc_stats['l2_feeds']))
|
||||
|
||||
print()
|
||||
print('L2ARC writes:')
|
||||
|
||||
if arc_stats['l2_writes_done'] != arc_stats['l2_writes_sent']:
|
||||
prt_i2('Writes sent:', 'FAULTED', f_hits(arc_stats['l2_writes_sent']))
|
||||
prt_i2('Done ratio:',
|
||||
f_perc(arc_stats['l2_writes_done'],
|
||||
arc_stats['l2_writes_sent']),
|
||||
f_hits(arc_stats['l2_writes_done']))
|
||||
prt_i2('Error ratio:',
|
||||
f_perc(arc_stats['l2_writes_error'],
|
||||
arc_stats['l2_writes_sent']),
|
||||
f_hits(arc_stats['l2_writes_error']))
|
||||
else:
|
||||
prt_i2('Writes sent:', '100 %', f_hits(arc_stats['l2_writes_sent']))
|
||||
|
||||
print()
|
||||
print('L2ARC evicts:')
|
||||
prt_i1('Lock retries:', f_hits(arc_stats['l2_evict_lock_retry']))
|
||||
prt_i1('Upon reading:', f_hits(arc_stats['l2_evict_reading']))
|
||||
print()
|
||||
|
||||
|
||||
def section_spl(*_):
|
||||
"""Print the SPL parameters, if requested with alternative format
|
||||
and/or descriptions. This does not use kstats.
|
||||
"""
|
||||
|
||||
if sys.platform.startswith('freebsd'):
|
||||
# No SPL support in FreeBSD
|
||||
return
|
||||
|
||||
spls = get_spl_params()
|
||||
keylist = sorted(spls.keys())
|
||||
print('Solaris Porting Layer (SPL):')
|
||||
|
||||
if ARGS.desc:
|
||||
descriptions = get_descriptions('spl')
|
||||
|
||||
for key in keylist:
|
||||
value = spls[key]
|
||||
|
||||
if ARGS.desc:
|
||||
try:
|
||||
print(INDENT+'#', descriptions[key])
|
||||
except KeyError:
|
||||
print(INDENT+'# (No description found)') # paranoid
|
||||
|
||||
print(format_raw_line(key, value))
|
||||
|
||||
print()
|
||||
|
||||
|
||||
def section_tunables(*_):
|
||||
"""Print the tunables, if requested with alternative format and/or
|
||||
descriptions. This does not use kstasts.
|
||||
"""
|
||||
|
||||
tunables = get_tunable_params()
|
||||
keylist = sorted(tunables.keys())
|
||||
print('Tunables:')
|
||||
|
||||
if ARGS.desc:
|
||||
descriptions = get_descriptions('zfs')
|
||||
|
||||
for key in keylist:
|
||||
value = tunables[key]
|
||||
|
||||
if ARGS.desc:
|
||||
try:
|
||||
print(INDENT+'#', descriptions[key])
|
||||
except KeyError:
|
||||
print(INDENT+'# (No description found)') # paranoid
|
||||
|
||||
print(format_raw_line(key, value))
|
||||
|
||||
print()
|
||||
|
||||
|
||||
def section_vdev(kstats_dict):
|
||||
"""Collect information on VDEV caches"""
|
||||
|
||||
# Currently [Nov 2017] the VDEV cache is disabled, because it is actually
|
||||
# harmful. When this is the case, we just skip the whole entry. See
|
||||
# https://github.com/zfsonlinux/zfs/blob/master/module/zfs/vdev_cache.c
|
||||
# for details
|
||||
tunables = get_vdev_params()
|
||||
|
||||
if tunables[VDEV_CACHE_SIZE] == '0':
|
||||
print('VDEV cache disabled, skipping section\n')
|
||||
return
|
||||
|
||||
vdev_stats = isolate_section('vdev_cache_stats', kstats_dict)
|
||||
|
||||
vdev_cache_total = int(vdev_stats['hits']) +\
|
||||
int(vdev_stats['misses']) +\
|
||||
int(vdev_stats['delegations'])
|
||||
|
||||
prt_1('VDEV cache summary:', f_hits(vdev_cache_total))
|
||||
prt_i2('Hit ratio:', f_perc(vdev_stats['hits'], vdev_cache_total),
|
||||
f_hits(vdev_stats['hits']))
|
||||
prt_i2('Miss ratio:', f_perc(vdev_stats['misses'], vdev_cache_total),
|
||||
f_hits(vdev_stats['misses']))
|
||||
prt_i2('Delegations:', f_perc(vdev_stats['delegations'], vdev_cache_total),
|
||||
f_hits(vdev_stats['delegations']))
|
||||
print()
|
||||
|
||||
|
||||
def section_zil(kstats_dict):
|
||||
"""Collect information on the ZFS Intent Log. Some of the information
|
||||
taken from https://github.com/zfsonlinux/zfs/blob/master/include/sys/zil.h
|
||||
"""
|
||||
|
||||
zil_stats = isolate_section('zil', kstats_dict)
|
||||
|
||||
prt_1('ZIL committed transactions:',
|
||||
f_hits(zil_stats['zil_itx_count']))
|
||||
prt_i1('Commit requests:', f_hits(zil_stats['zil_commit_count']))
|
||||
prt_i1('Flushes to stable storage:',
|
||||
f_hits(zil_stats['zil_commit_writer_count']))
|
||||
prt_i2('Transactions to SLOG storage pool:',
|
||||
f_bytes(zil_stats['zil_itx_metaslab_slog_bytes']),
|
||||
f_hits(zil_stats['zil_itx_metaslab_slog_count']))
|
||||
prt_i2('Transactions to non-SLOG storage pool:',
|
||||
f_bytes(zil_stats['zil_itx_metaslab_normal_bytes']),
|
||||
f_hits(zil_stats['zil_itx_metaslab_normal_count']))
|
||||
print()
|
||||
|
||||
|
||||
section_calls = {'arc': section_arc,
|
||||
'archits': section_archits,
|
||||
'dmu': section_dmu,
|
||||
'l2arc': section_l2arc,
|
||||
'spl': section_spl,
|
||||
'tunables': section_tunables,
|
||||
'vdev': section_vdev,
|
||||
'zil': section_zil}
|
||||
|
||||
|
||||
def main():
|
||||
"""Run program. The options to draw a graph and to print all data raw are
|
||||
treated separately because they come with their own call.
|
||||
"""
|
||||
|
||||
kstats = get_kstats()
|
||||
|
||||
if ARGS.graph:
|
||||
draw_graph(kstats)
|
||||
sys.exit(0)
|
||||
|
||||
print_header()
|
||||
|
||||
if ARGS.raw:
|
||||
print_raw(kstats)
|
||||
|
||||
elif ARGS.section:
|
||||
|
||||
try:
|
||||
section_calls[ARGS.section](kstats)
|
||||
except KeyError:
|
||||
print('Error: Section "{0}" unknown'.format(ARGS.section))
|
||||
sys.exit(1)
|
||||
|
||||
elif ARGS.page:
|
||||
print('WARNING: Pages are deprecated, please use "--section"\n')
|
||||
|
||||
pages_to_calls = {1: 'arc',
|
||||
2: 'archits',
|
||||
3: 'l2arc',
|
||||
4: 'dmu',
|
||||
5: 'vdev',
|
||||
6: 'tunables'}
|
||||
|
||||
try:
|
||||
call = pages_to_calls[ARGS.page]
|
||||
except KeyError:
|
||||
print('Error: Page "{0}" not supported'.format(ARGS.page))
|
||||
sys.exit(1)
|
||||
else:
|
||||
section_calls[call](kstats)
|
||||
|
||||
else:
|
||||
# If no parameters were given, we print all sections. We might want to
|
||||
# change the sequence by hand
|
||||
calls = sorted(section_calls.keys())
|
||||
|
||||
for section in calls:
|
||||
section_calls[section](kstats)
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
1
sys/contrib/openzfs/cmd/arcstat/.gitignore
vendored
Normal file
1
sys/contrib/openzfs/cmd/arcstat/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
arcstat
|
5
sys/contrib/openzfs/cmd/arcstat/Makefile.am
Normal file
5
sys/contrib/openzfs/cmd/arcstat/Makefile.am
Normal file
@ -0,0 +1,5 @@
|
||||
include $(top_srcdir)/config/Substfiles.am
|
||||
|
||||
bin_SCRIPTS = arcstat
|
||||
|
||||
SUBSTFILES += $(bin_SCRIPTS)
|
494
sys/contrib/openzfs/cmd/arcstat/arcstat.in
Executable file
494
sys/contrib/openzfs/cmd/arcstat/arcstat.in
Executable file
@ -0,0 +1,494 @@
|
||||
#!/usr/bin/env @PYTHON_SHEBANG@
|
||||
#
|
||||
# Print out ZFS ARC Statistics exported via kstat(1)
|
||||
# For a definition of fields, or usage, use arcstat -v
|
||||
#
|
||||
# This script was originally a fork of the original arcstat.pl (0.1)
|
||||
# by Neelakanth Nadgir, originally published on his Sun blog on
|
||||
# 09/18/2007
|
||||
# http://blogs.sun.com/realneel/entry/zfs_arc_statistics
|
||||
#
|
||||
# A new version aimed to improve upon the original by adding features
|
||||
# and fixing bugs as needed. This version was maintained by Mike
|
||||
# Harsch and was hosted in a public open source repository:
|
||||
# http://github.com/mharsch/arcstat
|
||||
#
|
||||
# but has since moved to the illumos-gate repository.
|
||||
#
|
||||
# This Python port was written by John Hixson for FreeNAS, introduced
|
||||
# in commit e2c29f:
|
||||
# https://github.com/freenas/freenas
|
||||
#
|
||||
# and has been improved by many people since.
|
||||
#
|
||||
# CDDL HEADER START
|
||||
#
|
||||
# The contents of this file are subject to the terms of the
|
||||
# Common Development and Distribution License, Version 1.0 only
|
||||
# (the "License"). You may not use this file except in compliance
|
||||
# with the License.
|
||||
#
|
||||
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||
# or http://www.opensolaris.org/os/licensing.
|
||||
# See the License for the specific language governing permissions
|
||||
# and limitations under the License.
|
||||
#
|
||||
# When distributing Covered Code, include this CDDL HEADER in each
|
||||
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||
# If applicable, add the following below this CDDL HEADER, with the
|
||||
# fields enclosed by brackets "[]" replaced with your own identifying
|
||||
# information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
#
|
||||
# CDDL HEADER END
|
||||
#
|
||||
#
|
||||
# Fields have a fixed width. Every interval, we fill the "v"
|
||||
# hash with its corresponding value (v[field]=value) using calculate().
|
||||
# @hdr is the array of fields that needs to be printed, so we
|
||||
# just iterate over this array and print the values using our pretty printer.
|
||||
#
|
||||
# This script must remain compatible with Python 2.6+ and Python 3.4+.
|
||||
#
|
||||
|
||||
import sys
|
||||
import time
|
||||
import getopt
|
||||
import re
|
||||
import copy
|
||||
|
||||
from signal import signal, SIGINT, SIGWINCH, SIG_DFL
|
||||
|
||||
|
||||
cols = {
|
||||
# HDR: [Size, Scale, Description]
|
||||
"time": [8, -1, "Time"],
|
||||
"hits": [4, 1000, "ARC reads per second"],
|
||||
"miss": [4, 1000, "ARC misses per second"],
|
||||
"read": [4, 1000, "Total ARC accesses per second"],
|
||||
"hit%": [4, 100, "ARC hit percentage"],
|
||||
"miss%": [5, 100, "ARC miss percentage"],
|
||||
"dhit": [4, 1000, "Demand hits per second"],
|
||||
"dmis": [4, 1000, "Demand misses per second"],
|
||||
"dh%": [3, 100, "Demand hit percentage"],
|
||||
"dm%": [3, 100, "Demand miss percentage"],
|
||||
"phit": [4, 1000, "Prefetch hits per second"],
|
||||
"pmis": [4, 1000, "Prefetch misses per second"],
|
||||
"ph%": [3, 100, "Prefetch hits percentage"],
|
||||
"pm%": [3, 100, "Prefetch miss percentage"],
|
||||
"mhit": [4, 1000, "Metadata hits per second"],
|
||||
"mmis": [4, 1000, "Metadata misses per second"],
|
||||
"mread": [5, 1000, "Metadata accesses per second"],
|
||||
"mh%": [3, 100, "Metadata hit percentage"],
|
||||
"mm%": [3, 100, "Metadata miss percentage"],
|
||||
"arcsz": [5, 1024, "ARC size"],
|
||||
"size": [4, 1024, "ARC size"],
|
||||
"c": [4, 1024, "ARC target size"],
|
||||
"mfu": [4, 1000, "MFU list hits per second"],
|
||||
"mru": [4, 1000, "MRU list hits per second"],
|
||||
"mfug": [4, 1000, "MFU ghost list hits per second"],
|
||||
"mrug": [4, 1000, "MRU ghost list hits per second"],
|
||||
"eskip": [5, 1000, "evict_skip per second"],
|
||||
"mtxmis": [6, 1000, "mutex_miss per second"],
|
||||
"dread": [5, 1000, "Demand accesses per second"],
|
||||
"pread": [5, 1000, "Prefetch accesses per second"],
|
||||
"l2hits": [6, 1000, "L2ARC hits per second"],
|
||||
"l2miss": [6, 1000, "L2ARC misses per second"],
|
||||
"l2read": [6, 1000, "Total L2ARC accesses per second"],
|
||||
"l2hit%": [6, 100, "L2ARC access hit percentage"],
|
||||
"l2miss%": [7, 100, "L2ARC access miss percentage"],
|
||||
"l2asize": [7, 1024, "Actual (compressed) size of the L2ARC"],
|
||||
"l2size": [6, 1024, "Size of the L2ARC"],
|
||||
"l2bytes": [7, 1024, "Bytes read per second from the L2ARC"],
|
||||
"grow": [4, 1000, "ARC grow disabled"],
|
||||
"need": [4, 1024, "ARC reclaim need"],
|
||||
"free": [4, 1024, "ARC free memory"],
|
||||
"avail": [5, 1024, "ARC available memory"],
|
||||
"waste": [5, 1024, "Wasted memory due to round up to pagesize"],
|
||||
}
|
||||
|
||||
v = {}
|
||||
hdr = ["time", "read", "miss", "miss%", "dmis", "dm%", "pmis", "pm%", "mmis",
|
||||
"mm%", "size", "c", "avail"]
|
||||
xhdr = ["time", "mfu", "mru", "mfug", "mrug", "eskip", "mtxmis", "dread",
|
||||
"pread", "read"]
|
||||
sint = 1 # Default interval is 1 second
|
||||
count = 1 # Default count is 1
|
||||
hdr_intr = 20 # Print header every 20 lines of output
|
||||
opfile = None
|
||||
sep = " " # Default separator is 2 spaces
|
||||
version = "0.4"
|
||||
l2exist = False
|
||||
cmd = ("Usage: arcstat [-hvx] [-f fields] [-o file] [-s string] [interval "
|
||||
"[count]]\n")
|
||||
cur = {}
|
||||
d = {}
|
||||
out = None
|
||||
kstat = None
|
||||
|
||||
|
||||
if sys.platform.startswith('freebsd'):
|
||||
# Requires py27-sysctl on FreeBSD
|
||||
import sysctl
|
||||
|
||||
def kstat_update():
|
||||
global kstat
|
||||
|
||||
k = sysctl.filter('kstat.zfs.misc.arcstats')
|
||||
|
||||
if not k:
|
||||
sys.exit(1)
|
||||
|
||||
kstat = {}
|
||||
|
||||
for s in k:
|
||||
if not s:
|
||||
continue
|
||||
|
||||
name, value = s.name, s.value
|
||||
# Trims 'kstat.zfs.misc.arcstats' from the name
|
||||
kstat[name[24:]] = int(value)
|
||||
|
||||
elif sys.platform.startswith('linux'):
|
||||
def kstat_update():
|
||||
global kstat
|
||||
|
||||
k = [line.strip() for line in open('/proc/spl/kstat/zfs/arcstats')]
|
||||
|
||||
if not k:
|
||||
sys.exit(1)
|
||||
|
||||
del k[0:2]
|
||||
kstat = {}
|
||||
|
||||
for s in k:
|
||||
if not s:
|
||||
continue
|
||||
|
||||
name, unused, value = s.split()
|
||||
kstat[name] = int(value)
|
||||
|
||||
|
||||
def detailed_usage():
|
||||
sys.stderr.write("%s\n" % cmd)
|
||||
sys.stderr.write("Field definitions are as follows:\n")
|
||||
for key in cols:
|
||||
sys.stderr.write("%11s : %s\n" % (key, cols[key][2]))
|
||||
sys.stderr.write("\n")
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
def usage():
|
||||
sys.stderr.write("%s\n" % cmd)
|
||||
sys.stderr.write("\t -h : Print this help message\n")
|
||||
sys.stderr.write("\t -v : List all possible field headers and definitions"
|
||||
"\n")
|
||||
sys.stderr.write("\t -x : Print extended stats\n")
|
||||
sys.stderr.write("\t -f : Specify specific fields to print (see -v)\n")
|
||||
sys.stderr.write("\t -o : Redirect output to the specified file\n")
|
||||
sys.stderr.write("\t -s : Override default field separator with custom "
|
||||
"character or string\n")
|
||||
sys.stderr.write("\nExamples:\n")
|
||||
sys.stderr.write("\tarcstat -o /tmp/a.log 2 10\n")
|
||||
sys.stderr.write("\tarcstat -s \",\" -o /tmp/a.log 2 10\n")
|
||||
sys.stderr.write("\tarcstat -v\n")
|
||||
sys.stderr.write("\tarcstat -f time,hit%,dh%,ph%,mh% 1\n")
|
||||
sys.stderr.write("\n")
|
||||
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def snap_stats():
|
||||
global cur
|
||||
global kstat
|
||||
|
||||
prev = copy.deepcopy(cur)
|
||||
kstat_update()
|
||||
|
||||
cur = kstat
|
||||
for key in cur:
|
||||
if re.match(key, "class"):
|
||||
continue
|
||||
if key in prev:
|
||||
d[key] = cur[key] - prev[key]
|
||||
else:
|
||||
d[key] = cur[key]
|
||||
|
||||
|
||||
def prettynum(sz, scale, num=0):
|
||||
suffix = [' ', 'K', 'M', 'G', 'T', 'P', 'E', 'Z']
|
||||
index = 0
|
||||
save = 0
|
||||
|
||||
# Special case for date field
|
||||
if scale == -1:
|
||||
return "%s" % num
|
||||
|
||||
# Rounding error, return 0
|
||||
elif 0 < num < 1:
|
||||
num = 0
|
||||
|
||||
while abs(num) > scale and index < 5:
|
||||
save = num
|
||||
num = num / scale
|
||||
index += 1
|
||||
|
||||
if index == 0:
|
||||
return "%*d" % (sz, num)
|
||||
|
||||
if abs(save / scale) < 10:
|
||||
return "%*.1f%s" % (sz - 1, num, suffix[index])
|
||||
else:
|
||||
return "%*d%s" % (sz - 1, num, suffix[index])
|
||||
|
||||
|
||||
def print_values():
|
||||
global hdr
|
||||
global sep
|
||||
global v
|
||||
|
||||
sys.stdout.write(sep.join(
|
||||
prettynum(cols[col][0], cols[col][1], v[col]) for col in hdr))
|
||||
|
||||
sys.stdout.write("\n")
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
def print_header():
|
||||
global hdr
|
||||
global sep
|
||||
|
||||
sys.stdout.write(sep.join("%*s" % (cols[col][0], col) for col in hdr))
|
||||
|
||||
sys.stdout.write("\n")
|
||||
|
||||
|
||||
def get_terminal_lines():
|
||||
try:
|
||||
import fcntl
|
||||
import termios
|
||||
import struct
|
||||
data = fcntl.ioctl(sys.stdout.fileno(), termios.TIOCGWINSZ, '1234')
|
||||
sz = struct.unpack('hh', data)
|
||||
return sz[0]
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def update_hdr_intr():
|
||||
global hdr_intr
|
||||
|
||||
lines = get_terminal_lines()
|
||||
if lines and lines > 3:
|
||||
hdr_intr = lines - 3
|
||||
|
||||
|
||||
def resize_handler(signum, frame):
|
||||
update_hdr_intr()
|
||||
|
||||
|
||||
def init():
|
||||
global sint
|
||||
global count
|
||||
global hdr
|
||||
global xhdr
|
||||
global opfile
|
||||
global sep
|
||||
global out
|
||||
global l2exist
|
||||
|
||||
desired_cols = None
|
||||
xflag = False
|
||||
hflag = False
|
||||
vflag = False
|
||||
i = 1
|
||||
|
||||
try:
|
||||
opts, args = getopt.getopt(
|
||||
sys.argv[1:],
|
||||
"xo:hvs:f:",
|
||||
[
|
||||
"extended",
|
||||
"outfile",
|
||||
"help",
|
||||
"verbose",
|
||||
"separator",
|
||||
"columns"
|
||||
]
|
||||
)
|
||||
except getopt.error as msg:
|
||||
sys.stderr.write("Error: %s\n" % str(msg))
|
||||
usage()
|
||||
opts = None
|
||||
|
||||
for opt, arg in opts:
|
||||
if opt in ('-x', '--extended'):
|
||||
xflag = True
|
||||
if opt in ('-o', '--outfile'):
|
||||
opfile = arg
|
||||
i += 1
|
||||
if opt in ('-h', '--help'):
|
||||
hflag = True
|
||||
if opt in ('-v', '--verbose'):
|
||||
vflag = True
|
||||
if opt in ('-s', '--separator'):
|
||||
sep = arg
|
||||
i += 1
|
||||
if opt in ('-f', '--columns'):
|
||||
desired_cols = arg
|
||||
i += 1
|
||||
i += 1
|
||||
|
||||
argv = sys.argv[i:]
|
||||
sint = int(argv[0]) if argv else sint
|
||||
count = int(argv[1]) if len(argv) > 1 else (0 if len(argv) > 0 else 1)
|
||||
|
||||
if hflag or (xflag and desired_cols):
|
||||
usage()
|
||||
|
||||
if vflag:
|
||||
detailed_usage()
|
||||
|
||||
if xflag:
|
||||
hdr = xhdr
|
||||
|
||||
update_hdr_intr()
|
||||
|
||||
# check if L2ARC exists
|
||||
snap_stats()
|
||||
l2_size = cur.get("l2_size")
|
||||
if l2_size:
|
||||
l2exist = True
|
||||
|
||||
if desired_cols:
|
||||
hdr = desired_cols.split(",")
|
||||
|
||||
invalid = []
|
||||
incompat = []
|
||||
for ele in hdr:
|
||||
if ele not in cols:
|
||||
invalid.append(ele)
|
||||
elif not l2exist and ele.startswith("l2"):
|
||||
sys.stdout.write("No L2ARC Here\n%s\n" % ele)
|
||||
incompat.append(ele)
|
||||
|
||||
if len(invalid) > 0:
|
||||
sys.stderr.write("Invalid column definition! -- %s\n" % invalid)
|
||||
usage()
|
||||
|
||||
if len(incompat) > 0:
|
||||
sys.stderr.write("Incompatible field specified! -- %s\n" %
|
||||
incompat)
|
||||
usage()
|
||||
|
||||
if opfile:
|
||||
try:
|
||||
out = open(opfile, "w")
|
||||
sys.stdout = out
|
||||
|
||||
except IOError:
|
||||
sys.stderr.write("Cannot open %s for writing\n" % opfile)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def calculate():
|
||||
global d
|
||||
global v
|
||||
global l2exist
|
||||
|
||||
v = dict()
|
||||
v["time"] = time.strftime("%H:%M:%S", time.localtime())
|
||||
v["hits"] = d["hits"] / sint
|
||||
v["miss"] = d["misses"] / sint
|
||||
v["read"] = v["hits"] + v["miss"]
|
||||
v["hit%"] = 100 * v["hits"] / v["read"] if v["read"] > 0 else 0
|
||||
v["miss%"] = 100 - v["hit%"] if v["read"] > 0 else 0
|
||||
|
||||
v["dhit"] = (d["demand_data_hits"] + d["demand_metadata_hits"]) / sint
|
||||
v["dmis"] = (d["demand_data_misses"] + d["demand_metadata_misses"]) / sint
|
||||
|
||||
v["dread"] = v["dhit"] + v["dmis"]
|
||||
v["dh%"] = 100 * v["dhit"] / v["dread"] if v["dread"] > 0 else 0
|
||||
v["dm%"] = 100 - v["dh%"] if v["dread"] > 0 else 0
|
||||
|
||||
v["phit"] = (d["prefetch_data_hits"] + d["prefetch_metadata_hits"]) / sint
|
||||
v["pmis"] = (d["prefetch_data_misses"] +
|
||||
d["prefetch_metadata_misses"]) / sint
|
||||
|
||||
v["pread"] = v["phit"] + v["pmis"]
|
||||
v["ph%"] = 100 * v["phit"] / v["pread"] if v["pread"] > 0 else 0
|
||||
v["pm%"] = 100 - v["ph%"] if v["pread"] > 0 else 0
|
||||
|
||||
v["mhit"] = (d["prefetch_metadata_hits"] +
|
||||
d["demand_metadata_hits"]) / sint
|
||||
v["mmis"] = (d["prefetch_metadata_misses"] +
|
||||
d["demand_metadata_misses"]) / sint
|
||||
|
||||
v["mread"] = v["mhit"] + v["mmis"]
|
||||
v["mh%"] = 100 * v["mhit"] / v["mread"] if v["mread"] > 0 else 0
|
||||
v["mm%"] = 100 - v["mh%"] if v["mread"] > 0 else 0
|
||||
|
||||
v["arcsz"] = cur["size"]
|
||||
v["size"] = cur["size"]
|
||||
v["c"] = cur["c"]
|
||||
v["mfu"] = d["mfu_hits"] / sint
|
||||
v["mru"] = d["mru_hits"] / sint
|
||||
v["mrug"] = d["mru_ghost_hits"] / sint
|
||||
v["mfug"] = d["mfu_ghost_hits"] / sint
|
||||
v["eskip"] = d["evict_skip"] / sint
|
||||
v["mtxmis"] = d["mutex_miss"] / sint
|
||||
|
||||
if l2exist:
|
||||
v["l2hits"] = d["l2_hits"] / sint
|
||||
v["l2miss"] = d["l2_misses"] / sint
|
||||
v["l2read"] = v["l2hits"] + v["l2miss"]
|
||||
v["l2hit%"] = 100 * v["l2hits"] / v["l2read"] if v["l2read"] > 0 else 0
|
||||
|
||||
v["l2miss%"] = 100 - v["l2hit%"] if v["l2read"] > 0 else 0
|
||||
v["l2asize"] = cur["l2_asize"]
|
||||
v["l2size"] = cur["l2_size"]
|
||||
v["l2bytes"] = d["l2_read_bytes"] / sint
|
||||
|
||||
v["grow"] = 0 if cur["arc_no_grow"] else 1
|
||||
v["need"] = cur["arc_need_free"]
|
||||
v["free"] = cur["memory_free_bytes"]
|
||||
v["avail"] = cur["memory_available_bytes"]
|
||||
v["waste"] = cur["abd_chunk_waste_size"]
|
||||
|
||||
|
||||
def main():
|
||||
global sint
|
||||
global count
|
||||
global hdr_intr
|
||||
|
||||
i = 0
|
||||
count_flag = 0
|
||||
|
||||
init()
|
||||
if count > 0:
|
||||
count_flag = 1
|
||||
|
||||
signal(SIGINT, SIG_DFL)
|
||||
signal(SIGWINCH, resize_handler)
|
||||
while True:
|
||||
if i == 0:
|
||||
print_header()
|
||||
|
||||
snap_stats()
|
||||
calculate()
|
||||
print_values()
|
||||
|
||||
if count_flag == 1:
|
||||
if count <= 1:
|
||||
break
|
||||
count -= 1
|
||||
|
||||
i = 0 if i >= hdr_intr else i + 1
|
||||
time.sleep(sint)
|
||||
|
||||
if out:
|
||||
out.close()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
1
sys/contrib/openzfs/cmd/dbufstat/.gitignore
vendored
Normal file
1
sys/contrib/openzfs/cmd/dbufstat/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
dbufstat
|
5
sys/contrib/openzfs/cmd/dbufstat/Makefile.am
Normal file
5
sys/contrib/openzfs/cmd/dbufstat/Makefile.am
Normal file
@ -0,0 +1,5 @@
|
||||
include $(top_srcdir)/config/Substfiles.am
|
||||
|
||||
bin_SCRIPTS = dbufstat
|
||||
|
||||
SUBSTFILES += $(bin_SCRIPTS)
|
669
sys/contrib/openzfs/cmd/dbufstat/dbufstat.in
Executable file
669
sys/contrib/openzfs/cmd/dbufstat/dbufstat.in
Executable file
@ -0,0 +1,669 @@
|
||||
#!/usr/bin/env @PYTHON_SHEBANG@
|
||||
#
|
||||
# Print out statistics for all cached dmu buffers. This information
|
||||
# is available through the dbufs kstat and may be post-processed as
|
||||
# needed by the script.
|
||||
#
|
||||
# CDDL HEADER START
|
||||
#
|
||||
# The contents of this file are subject to the terms of the
|
||||
# Common Development and Distribution License, Version 1.0 only
|
||||
# (the "License"). You may not use this file except in compliance
|
||||
# with the License.
|
||||
#
|
||||
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||
# or http://www.opensolaris.org/os/licensing.
|
||||
# See the License for the specific language governing permissions
|
||||
# and limitations under the License.
|
||||
#
|
||||
# When distributing Covered Code, include this CDDL HEADER in each
|
||||
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||
# If applicable, add the following below this CDDL HEADER, with the
|
||||
# fields enclosed by brackets "[]" replaced with your own identifying
|
||||
# information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
#
|
||||
# CDDL HEADER END
|
||||
#
|
||||
# Copyright (C) 2013 Lawrence Livermore National Security, LLC.
|
||||
# Produced at Lawrence Livermore National Laboratory (cf, DISCLAIMER).
|
||||
#
|
||||
# This script must remain compatible with Python 2.6+ and Python 3.4+.
|
||||
#
|
||||
|
||||
import sys
|
||||
import getopt
|
||||
import errno
|
||||
import re
|
||||
|
||||
bhdr = ["pool", "objset", "object", "level", "blkid", "offset", "dbsize"]
|
||||
bxhdr = ["pool", "objset", "object", "level", "blkid", "offset", "dbsize",
|
||||
"meta", "state", "dbholds", "dbc", "list", "atype", "flags",
|
||||
"count", "asize", "access", "mru", "gmru", "mfu", "gmfu", "l2",
|
||||
"l2_dattr", "l2_asize", "l2_comp", "aholds", "dtype", "btype",
|
||||
"data_bs", "meta_bs", "bsize", "lvls", "dholds", "blocks", "dsize"]
|
||||
bincompat = ["cached", "direct", "indirect", "bonus", "spill"]
|
||||
|
||||
dhdr = ["pool", "objset", "object", "dtype", "cached"]
|
||||
dxhdr = ["pool", "objset", "object", "dtype", "btype", "data_bs", "meta_bs",
|
||||
"bsize", "lvls", "dholds", "blocks", "dsize", "cached", "direct",
|
||||
"indirect", "bonus", "spill"]
|
||||
dincompat = ["level", "blkid", "offset", "dbsize", "meta", "state", "dbholds",
|
||||
"dbc", "list", "atype", "flags", "count", "asize", "access",
|
||||
"mru", "gmru", "mfu", "gmfu", "l2", "l2_dattr", "l2_asize",
|
||||
"l2_comp", "aholds"]
|
||||
|
||||
thdr = ["pool", "objset", "dtype", "cached"]
|
||||
txhdr = ["pool", "objset", "dtype", "cached", "direct", "indirect",
|
||||
"bonus", "spill"]
|
||||
tincompat = ["object", "level", "blkid", "offset", "dbsize", "meta", "state",
|
||||
"dbc", "dbholds", "list", "atype", "flags", "count", "asize",
|
||||
"access", "mru", "gmru", "mfu", "gmfu", "l2", "l2_dattr",
|
||||
"l2_asize", "l2_comp", "aholds", "btype", "data_bs", "meta_bs",
|
||||
"bsize", "lvls", "dholds", "blocks", "dsize"]
|
||||
|
||||
cols = {
|
||||
# hdr: [size, scale, description]
|
||||
"pool": [15, -1, "pool name"],
|
||||
"objset": [6, -1, "dataset identification number"],
|
||||
"object": [10, -1, "object number"],
|
||||
"level": [5, -1, "indirection level of buffer"],
|
||||
"blkid": [8, -1, "block number of buffer"],
|
||||
"offset": [12, 1024, "offset in object of buffer"],
|
||||
"dbsize": [7, 1024, "size of buffer"],
|
||||
"meta": [4, -1, "is this buffer metadata?"],
|
||||
"state": [5, -1, "state of buffer (read, cached, etc)"],
|
||||
"dbholds": [7, 1000, "number of holds on buffer"],
|
||||
"dbc": [3, -1, "in dbuf cache"],
|
||||
"list": [4, -1, "which ARC list contains this buffer"],
|
||||
"atype": [7, -1, "ARC header type (data or metadata)"],
|
||||
"flags": [9, -1, "ARC read flags"],
|
||||
"count": [5, -1, "ARC data count"],
|
||||
"asize": [7, 1024, "size of this ARC buffer"],
|
||||
"access": [10, -1, "time this ARC buffer was last accessed"],
|
||||
"mru": [5, 1000, "hits while on the ARC's MRU list"],
|
||||
"gmru": [5, 1000, "hits while on the ARC's MRU ghost list"],
|
||||
"mfu": [5, 1000, "hits while on the ARC's MFU list"],
|
||||
"gmfu": [5, 1000, "hits while on the ARC's MFU ghost list"],
|
||||
"l2": [5, 1000, "hits while on the L2ARC"],
|
||||
"l2_dattr": [8, -1, "L2ARC disk address/offset"],
|
||||
"l2_asize": [8, 1024, "L2ARC alloc'd size (depending on compression)"],
|
||||
"l2_comp": [21, -1, "L2ARC compression algorithm for buffer"],
|
||||
"aholds": [6, 1000, "number of holds on this ARC buffer"],
|
||||
"dtype": [27, -1, "dnode type"],
|
||||
"btype": [27, -1, "bonus buffer type"],
|
||||
"data_bs": [7, 1024, "data block size"],
|
||||
"meta_bs": [7, 1024, "metadata block size"],
|
||||
"bsize": [6, 1024, "bonus buffer size"],
|
||||
"lvls": [6, -1, "number of indirection levels"],
|
||||
"dholds": [6, 1000, "number of holds on dnode"],
|
||||
"blocks": [8, 1000, "number of allocated blocks"],
|
||||
"dsize": [12, 1024, "size of dnode"],
|
||||
"cached": [6, 1024, "bytes cached for all blocks"],
|
||||
"direct": [6, 1024, "bytes cached for direct blocks"],
|
||||
"indirect": [8, 1024, "bytes cached for indirect blocks"],
|
||||
"bonus": [5, 1024, "bytes cached for bonus buffer"],
|
||||
"spill": [5, 1024, "bytes cached for spill block"],
|
||||
}
|
||||
|
||||
hdr = None
|
||||
xhdr = None
|
||||
sep = " " # Default separator is 2 spaces
|
||||
cmd = ("Usage: dbufstat [-bdhnrtvx] [-i file] [-f fields] [-o file] "
|
||||
"[-s string] [-F filter]\n")
|
||||
raw = 0
|
||||
|
||||
|
||||
def print_incompat_helper(incompat):
|
||||
cnt = 0
|
||||
for key in sorted(incompat):
|
||||
if cnt is 0:
|
||||
sys.stderr.write("\t")
|
||||
elif cnt > 8:
|
||||
sys.stderr.write(",\n\t")
|
||||
cnt = 0
|
||||
else:
|
||||
sys.stderr.write(", ")
|
||||
|
||||
sys.stderr.write("%s" % key)
|
||||
cnt += 1
|
||||
|
||||
sys.stderr.write("\n\n")
|
||||
|
||||
|
||||
def detailed_usage():
|
||||
sys.stderr.write("%s\n" % cmd)
|
||||
|
||||
sys.stderr.write("Field definitions incompatible with '-b' option:\n")
|
||||
print_incompat_helper(bincompat)
|
||||
|
||||
sys.stderr.write("Field definitions incompatible with '-d' option:\n")
|
||||
print_incompat_helper(dincompat)
|
||||
|
||||
sys.stderr.write("Field definitions incompatible with '-t' option:\n")
|
||||
print_incompat_helper(tincompat)
|
||||
|
||||
sys.stderr.write("Field definitions are as follows:\n")
|
||||
for key in sorted(cols.keys()):
|
||||
sys.stderr.write("%11s : %s\n" % (key, cols[key][2]))
|
||||
sys.stderr.write("\n")
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
def usage():
|
||||
sys.stderr.write("%s\n" % cmd)
|
||||
sys.stderr.write("\t -b : Print table of information for each dbuf\n")
|
||||
sys.stderr.write("\t -d : Print table of information for each dnode\n")
|
||||
sys.stderr.write("\t -h : Print this help message\n")
|
||||
sys.stderr.write("\t -n : Exclude header from output\n")
|
||||
sys.stderr.write("\t -r : Print raw values\n")
|
||||
sys.stderr.write("\t -t : Print table of information for each dnode type"
|
||||
"\n")
|
||||
sys.stderr.write("\t -v : List all possible field headers and definitions"
|
||||
"\n")
|
||||
sys.stderr.write("\t -x : Print extended stats\n")
|
||||
sys.stderr.write("\t -i : Redirect input from the specified file\n")
|
||||
sys.stderr.write("\t -f : Specify specific fields to print (see -v)\n")
|
||||
sys.stderr.write("\t -o : Redirect output to the specified file\n")
|
||||
sys.stderr.write("\t -s : Override default field separator with custom "
|
||||
"character or string\n")
|
||||
sys.stderr.write("\t -F : Filter output by value or regex\n")
|
||||
sys.stderr.write("\nExamples:\n")
|
||||
sys.stderr.write("\tdbufstat -d -o /tmp/d.log\n")
|
||||
sys.stderr.write("\tdbufstat -t -s \",\" -o /tmp/t.log\n")
|
||||
sys.stderr.write("\tdbufstat -v\n")
|
||||
sys.stderr.write("\tdbufstat -d -f pool,object,objset,dsize,cached\n")
|
||||
sys.stderr.write("\tdbufstat -bx -F dbc=1,objset=54,pool=testpool\n")
|
||||
sys.stderr.write("\n")
|
||||
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def prettynum(sz, scale, num=0):
|
||||
global raw
|
||||
|
||||
suffix = [' ', 'K', 'M', 'G', 'T', 'P', 'E', 'Z']
|
||||
index = 0
|
||||
save = 0
|
||||
|
||||
if raw or scale == -1:
|
||||
return "%*s" % (sz, num)
|
||||
|
||||
# Rounding error, return 0
|
||||
elif 0 < num < 1:
|
||||
num = 0
|
||||
|
||||
while num > scale and index < 5:
|
||||
save = num
|
||||
num = num / scale
|
||||
index += 1
|
||||
|
||||
if index == 0:
|
||||
return "%*d" % (sz, num)
|
||||
|
||||
if (save / scale) < 10:
|
||||
return "%*.1f%s" % (sz - 1, num, suffix[index])
|
||||
else:
|
||||
return "%*d%s" % (sz - 1, num, suffix[index])
|
||||
|
||||
|
||||
def print_values(v):
|
||||
global hdr
|
||||
global sep
|
||||
|
||||
try:
|
||||
for col in hdr:
|
||||
sys.stdout.write("%s%s" % (
|
||||
prettynum(cols[col][0], cols[col][1], v[col]), sep))
|
||||
sys.stdout.write("\n")
|
||||
except IOError as e:
|
||||
if e.errno == errno.EPIPE:
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def print_header():
|
||||
global hdr
|
||||
global sep
|
||||
|
||||
try:
|
||||
for col in hdr:
|
||||
sys.stdout.write("%*s%s" % (cols[col][0], col, sep))
|
||||
sys.stdout.write("\n")
|
||||
except IOError as e:
|
||||
if e.errno == errno.EPIPE:
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def get_typestring(t):
|
||||
ot_strings = [
|
||||
"DMU_OT_NONE",
|
||||
# general:
|
||||
"DMU_OT_OBJECT_DIRECTORY",
|
||||
"DMU_OT_OBJECT_ARRAY",
|
||||
"DMU_OT_PACKED_NVLIST",
|
||||
"DMU_OT_PACKED_NVLIST_SIZE",
|
||||
"DMU_OT_BPOBJ",
|
||||
"DMU_OT_BPOBJ_HDR",
|
||||
# spa:
|
||||
"DMU_OT_SPACE_MAP_HEADER",
|
||||
"DMU_OT_SPACE_MAP",
|
||||
# zil:
|
||||
"DMU_OT_INTENT_LOG",
|
||||
# dmu:
|
||||
"DMU_OT_DNODE",
|
||||
"DMU_OT_OBJSET",
|
||||
# dsl:
|
||||
"DMU_OT_DSL_DIR",
|
||||
"DMU_OT_DSL_DIR_CHILD_MAP",
|
||||
"DMU_OT_DSL_DS_SNAP_MAP",
|
||||
"DMU_OT_DSL_PROPS",
|
||||
"DMU_OT_DSL_DATASET",
|
||||
# zpl:
|
||||
"DMU_OT_ZNODE",
|
||||
"DMU_OT_OLDACL",
|
||||
"DMU_OT_PLAIN_FILE_CONTENTS",
|
||||
"DMU_OT_DIRECTORY_CONTENTS",
|
||||
"DMU_OT_MASTER_NODE",
|
||||
"DMU_OT_UNLINKED_SET",
|
||||
# zvol:
|
||||
"DMU_OT_ZVOL",
|
||||
"DMU_OT_ZVOL_PROP",
|
||||
# other; for testing only!
|
||||
"DMU_OT_PLAIN_OTHER",
|
||||
"DMU_OT_UINT64_OTHER",
|
||||
"DMU_OT_ZAP_OTHER",
|
||||
# new object types:
|
||||
"DMU_OT_ERROR_LOG",
|
||||
"DMU_OT_SPA_HISTORY",
|
||||
"DMU_OT_SPA_HISTORY_OFFSETS",
|
||||
"DMU_OT_POOL_PROPS",
|
||||
"DMU_OT_DSL_PERMS",
|
||||
"DMU_OT_ACL",
|
||||
"DMU_OT_SYSACL",
|
||||
"DMU_OT_FUID",
|
||||
"DMU_OT_FUID_SIZE",
|
||||
"DMU_OT_NEXT_CLONES",
|
||||
"DMU_OT_SCAN_QUEUE",
|
||||
"DMU_OT_USERGROUP_USED",
|
||||
"DMU_OT_USERGROUP_QUOTA",
|
||||
"DMU_OT_USERREFS",
|
||||
"DMU_OT_DDT_ZAP",
|
||||
"DMU_OT_DDT_STATS",
|
||||
"DMU_OT_SA",
|
||||
"DMU_OT_SA_MASTER_NODE",
|
||||
"DMU_OT_SA_ATTR_REGISTRATION",
|
||||
"DMU_OT_SA_ATTR_LAYOUTS",
|
||||
"DMU_OT_SCAN_XLATE",
|
||||
"DMU_OT_DEDUP",
|
||||
"DMU_OT_DEADLIST",
|
||||
"DMU_OT_DEADLIST_HDR",
|
||||
"DMU_OT_DSL_CLONES",
|
||||
"DMU_OT_BPOBJ_SUBOBJ"]
|
||||
otn_strings = {
|
||||
0x80: "DMU_OTN_UINT8_DATA",
|
||||
0xc0: "DMU_OTN_UINT8_METADATA",
|
||||
0x81: "DMU_OTN_UINT16_DATA",
|
||||
0xc1: "DMU_OTN_UINT16_METADATA",
|
||||
0x82: "DMU_OTN_UINT32_DATA",
|
||||
0xc2: "DMU_OTN_UINT32_METADATA",
|
||||
0x83: "DMU_OTN_UINT64_DATA",
|
||||
0xc3: "DMU_OTN_UINT64_METADATA",
|
||||
0x84: "DMU_OTN_ZAP_DATA",
|
||||
0xc4: "DMU_OTN_ZAP_METADATA",
|
||||
0xa0: "DMU_OTN_UINT8_ENC_DATA",
|
||||
0xe0: "DMU_OTN_UINT8_ENC_METADATA",
|
||||
0xa1: "DMU_OTN_UINT16_ENC_DATA",
|
||||
0xe1: "DMU_OTN_UINT16_ENC_METADATA",
|
||||
0xa2: "DMU_OTN_UINT32_ENC_DATA",
|
||||
0xe2: "DMU_OTN_UINT32_ENC_METADATA",
|
||||
0xa3: "DMU_OTN_UINT64_ENC_DATA",
|
||||
0xe3: "DMU_OTN_UINT64_ENC_METADATA",
|
||||
0xa4: "DMU_OTN_ZAP_ENC_DATA",
|
||||
0xe4: "DMU_OTN_ZAP_ENC_METADATA"}
|
||||
|
||||
# If "-rr" option is used, don't convert to string representation
|
||||
if raw > 1:
|
||||
return "%i" % t
|
||||
|
||||
try:
|
||||
if t < len(ot_strings):
|
||||
return ot_strings[t]
|
||||
else:
|
||||
return otn_strings[t]
|
||||
except (IndexError, KeyError):
|
||||
return "(UNKNOWN)"
|
||||
|
||||
|
||||
def get_compstring(c):
|
||||
comp_strings = ["ZIO_COMPRESS_INHERIT", "ZIO_COMPRESS_ON",
|
||||
"ZIO_COMPRESS_OFF", "ZIO_COMPRESS_LZJB",
|
||||
"ZIO_COMPRESS_EMPTY", "ZIO_COMPRESS_GZIP_1",
|
||||
"ZIO_COMPRESS_GZIP_2", "ZIO_COMPRESS_GZIP_3",
|
||||
"ZIO_COMPRESS_GZIP_4", "ZIO_COMPRESS_GZIP_5",
|
||||
"ZIO_COMPRESS_GZIP_6", "ZIO_COMPRESS_GZIP_7",
|
||||
"ZIO_COMPRESS_GZIP_8", "ZIO_COMPRESS_GZIP_9",
|
||||
"ZIO_COMPRESS_ZLE", "ZIO_COMPRESS_LZ4",
|
||||
"ZIO_COMPRESS_ZSTD", "ZIO_COMPRESS_FUNCTION"]
|
||||
|
||||
# If "-rr" option is used, don't convert to string representation
|
||||
if raw > 1:
|
||||
return "%i" % c
|
||||
|
||||
try:
|
||||
return comp_strings[c]
|
||||
except IndexError:
|
||||
return "%i" % c
|
||||
|
||||
|
||||
def parse_line(line, labels):
|
||||
global hdr
|
||||
|
||||
new = dict()
|
||||
val = None
|
||||
for col in hdr:
|
||||
# These are "special" fields computed in the update_dict
|
||||
# function, prevent KeyError exception on labels[col] for these.
|
||||
if col not in ['bonus', 'cached', 'direct', 'indirect', 'spill']:
|
||||
val = line[labels[col]]
|
||||
|
||||
if col in ['pool', 'flags']:
|
||||
new[col] = str(val)
|
||||
elif col in ['dtype', 'btype']:
|
||||
new[col] = get_typestring(int(val))
|
||||
elif col in ['l2_comp']:
|
||||
new[col] = get_compstring(int(val))
|
||||
else:
|
||||
new[col] = int(val)
|
||||
|
||||
return new
|
||||
|
||||
|
||||
def update_dict(d, k, line, labels):
|
||||
pool = line[labels['pool']]
|
||||
objset = line[labels['objset']]
|
||||
key = line[labels[k]]
|
||||
|
||||
dbsize = int(line[labels['dbsize']])
|
||||
blkid = int(line[labels['blkid']])
|
||||
level = int(line[labels['level']])
|
||||
|
||||
if pool not in d:
|
||||
d[pool] = dict()
|
||||
|
||||
if objset not in d[pool]:
|
||||
d[pool][objset] = dict()
|
||||
|
||||
if key not in d[pool][objset]:
|
||||
d[pool][objset][key] = parse_line(line, labels)
|
||||
d[pool][objset][key]['bonus'] = 0
|
||||
d[pool][objset][key]['cached'] = 0
|
||||
d[pool][objset][key]['direct'] = 0
|
||||
d[pool][objset][key]['indirect'] = 0
|
||||
d[pool][objset][key]['spill'] = 0
|
||||
|
||||
d[pool][objset][key]['cached'] += dbsize
|
||||
|
||||
if blkid == -1:
|
||||
d[pool][objset][key]['bonus'] += dbsize
|
||||
elif blkid == -2:
|
||||
d[pool][objset][key]['spill'] += dbsize
|
||||
else:
|
||||
if level == 0:
|
||||
d[pool][objset][key]['direct'] += dbsize
|
||||
else:
|
||||
d[pool][objset][key]['indirect'] += dbsize
|
||||
|
||||
return d
|
||||
|
||||
|
||||
def skip_line(vals, filters):
|
||||
'''
|
||||
Determines if a line should be skipped during printing
|
||||
based on a set of filters
|
||||
'''
|
||||
if len(filters) == 0:
|
||||
return False
|
||||
|
||||
for key in vals:
|
||||
if key in filters:
|
||||
val = prettynum(cols[key][0], cols[key][1], vals[key]).strip()
|
||||
# we want a full match here
|
||||
if re.match("(?:" + filters[key] + r")\Z", val) is None:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def print_dict(d, filters, noheader):
|
||||
if not noheader:
|
||||
print_header()
|
||||
for pool in list(d.keys()):
|
||||
for objset in list(d[pool].keys()):
|
||||
for v in list(d[pool][objset].values()):
|
||||
if not skip_line(v, filters):
|
||||
print_values(v)
|
||||
|
||||
|
||||
def dnodes_build_dict(filehandle):
|
||||
labels = dict()
|
||||
dnodes = dict()
|
||||
|
||||
# First 3 lines are header information, skip the first two
|
||||
for i in range(2):
|
||||
next(filehandle)
|
||||
|
||||
# The third line contains the labels and index locations
|
||||
for i, v in enumerate(next(filehandle).split()):
|
||||
labels[v] = i
|
||||
|
||||
# The rest of the file is buffer information
|
||||
for line in filehandle:
|
||||
update_dict(dnodes, 'object', line.split(), labels)
|
||||
|
||||
return dnodes
|
||||
|
||||
|
||||
def types_build_dict(filehandle):
|
||||
labels = dict()
|
||||
types = dict()
|
||||
|
||||
# First 3 lines are header information, skip the first two
|
||||
for i in range(2):
|
||||
next(filehandle)
|
||||
|
||||
# The third line contains the labels and index locations
|
||||
for i, v in enumerate(next(filehandle).split()):
|
||||
labels[v] = i
|
||||
|
||||
# The rest of the file is buffer information
|
||||
for line in filehandle:
|
||||
update_dict(types, 'dtype', line.split(), labels)
|
||||
|
||||
return types
|
||||
|
||||
|
||||
def buffers_print_all(filehandle, filters, noheader):
|
||||
labels = dict()
|
||||
|
||||
# First 3 lines are header information, skip the first two
|
||||
for i in range(2):
|
||||
next(filehandle)
|
||||
|
||||
# The third line contains the labels and index locations
|
||||
for i, v in enumerate(next(filehandle).split()):
|
||||
labels[v] = i
|
||||
|
||||
if not noheader:
|
||||
print_header()
|
||||
|
||||
# The rest of the file is buffer information
|
||||
for line in filehandle:
|
||||
vals = parse_line(line.split(), labels)
|
||||
if not skip_line(vals, filters):
|
||||
print_values(vals)
|
||||
|
||||
|
||||
def main():
|
||||
global hdr
|
||||
global sep
|
||||
global raw
|
||||
|
||||
desired_cols = None
|
||||
bflag = False
|
||||
dflag = False
|
||||
hflag = False
|
||||
ifile = None
|
||||
ofile = None
|
||||
tflag = False
|
||||
vflag = False
|
||||
xflag = False
|
||||
nflag = False
|
||||
filters = dict()
|
||||
|
||||
try:
|
||||
opts, args = getopt.getopt(
|
||||
sys.argv[1:],
|
||||
"bdf:hi:o:rs:tvxF:n",
|
||||
[
|
||||
"buffers",
|
||||
"dnodes",
|
||||
"columns",
|
||||
"help",
|
||||
"infile",
|
||||
"outfile",
|
||||
"separator",
|
||||
"types",
|
||||
"verbose",
|
||||
"extended",
|
||||
"filter"
|
||||
]
|
||||
)
|
||||
except getopt.error:
|
||||
usage()
|
||||
opts = None
|
||||
|
||||
for opt, arg in opts:
|
||||
if opt in ('-b', '--buffers'):
|
||||
bflag = True
|
||||
if opt in ('-d', '--dnodes'):
|
||||
dflag = True
|
||||
if opt in ('-f', '--columns'):
|
||||
desired_cols = arg
|
||||
if opt in ('-h', '--help'):
|
||||
hflag = True
|
||||
if opt in ('-i', '--infile'):
|
||||
ifile = arg
|
||||
if opt in ('-o', '--outfile'):
|
||||
ofile = arg
|
||||
if opt in ('-r', '--raw'):
|
||||
raw += 1
|
||||
if opt in ('-s', '--separator'):
|
||||
sep = arg
|
||||
if opt in ('-t', '--types'):
|
||||
tflag = True
|
||||
if opt in ('-v', '--verbose'):
|
||||
vflag = True
|
||||
if opt in ('-x', '--extended'):
|
||||
xflag = True
|
||||
if opt in ('-n', '--noheader'):
|
||||
nflag = True
|
||||
if opt in ('-F', '--filter'):
|
||||
fils = [x.strip() for x in arg.split(",")]
|
||||
|
||||
for fil in fils:
|
||||
f = [x.strip() for x in fil.split("=")]
|
||||
|
||||
if len(f) != 2:
|
||||
sys.stderr.write("Invalid filter '%s'.\n" % fil)
|
||||
sys.exit(1)
|
||||
|
||||
if f[0] not in cols:
|
||||
sys.stderr.write("Invalid field '%s' in filter.\n" % f[0])
|
||||
sys.exit(1)
|
||||
|
||||
if f[0] in filters:
|
||||
sys.stderr.write("Field '%s' specified multiple times in "
|
||||
"filter.\n" % f[0])
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
re.compile("(?:" + f[1] + r")\Z")
|
||||
except re.error:
|
||||
sys.stderr.write("Invalid regex for field '%s' in "
|
||||
"filter.\n" % f[0])
|
||||
sys.exit(1)
|
||||
|
||||
filters[f[0]] = f[1]
|
||||
|
||||
if hflag or (xflag and desired_cols):
|
||||
usage()
|
||||
|
||||
if vflag:
|
||||
detailed_usage()
|
||||
|
||||
# Ensure at most only one of b, d, or t flags are set
|
||||
if (bflag and dflag) or (bflag and tflag) or (dflag and tflag):
|
||||
usage()
|
||||
|
||||
if bflag:
|
||||
hdr = bxhdr if xflag else bhdr
|
||||
elif tflag:
|
||||
hdr = txhdr if xflag else thdr
|
||||
else: # Even if dflag is False, it's the default if none set
|
||||
dflag = True
|
||||
hdr = dxhdr if xflag else dhdr
|
||||
|
||||
if desired_cols:
|
||||
hdr = desired_cols.split(",")
|
||||
|
||||
invalid = []
|
||||
incompat = []
|
||||
for ele in hdr:
|
||||
if ele not in cols:
|
||||
invalid.append(ele)
|
||||
elif ((bflag and bincompat and ele in bincompat) or
|
||||
(dflag and dincompat and ele in dincompat) or
|
||||
(tflag and tincompat and ele in tincompat)):
|
||||
incompat.append(ele)
|
||||
|
||||
if len(invalid) > 0:
|
||||
sys.stderr.write("Invalid column definition! -- %s\n" % invalid)
|
||||
usage()
|
||||
|
||||
if len(incompat) > 0:
|
||||
sys.stderr.write("Incompatible field specified! -- %s\n" %
|
||||
incompat)
|
||||
usage()
|
||||
|
||||
if ofile:
|
||||
try:
|
||||
tmp = open(ofile, "w")
|
||||
sys.stdout = tmp
|
||||
|
||||
except IOError:
|
||||
sys.stderr.write("Cannot open %s for writing\n" % ofile)
|
||||
sys.exit(1)
|
||||
|
||||
if not ifile:
|
||||
ifile = '/proc/spl/kstat/zfs/dbufs'
|
||||
|
||||
if ifile is not "-":
|
||||
try:
|
||||
tmp = open(ifile, "r")
|
||||
sys.stdin = tmp
|
||||
except IOError:
|
||||
sys.stderr.write("Cannot open %s for reading\n" % ifile)
|
||||
sys.exit(1)
|
||||
|
||||
if bflag:
|
||||
buffers_print_all(sys.stdin, filters, nflag)
|
||||
|
||||
if dflag:
|
||||
print_dict(dnodes_build_dict(sys.stdin), filters, nflag)
|
||||
|
||||
if tflag:
|
||||
print_dict(types_build_dict(sys.stdin), filters, nflag)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
1
sys/contrib/openzfs/cmd/fsck_zfs/Makefile.am
Normal file
1
sys/contrib/openzfs/cmd/fsck_zfs/Makefile.am
Normal file
@ -0,0 +1 @@
|
||||
dist_sbin_SCRIPTS = fsck.zfs
|
9
sys/contrib/openzfs/cmd/fsck_zfs/fsck.zfs
Executable file
9
sys/contrib/openzfs/cmd/fsck_zfs/fsck.zfs
Executable file
@ -0,0 +1,9 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# fsck.zfs: A fsck helper to accommodate distributions that expect
|
||||
# to be able to execute a fsck on all filesystem types. Currently
|
||||
# this script does nothing but it could be extended to act as a
|
||||
# compatibility wrapper for 'zpool scrub'.
|
||||
#
|
||||
|
||||
exit 0
|
1
sys/contrib/openzfs/cmd/mount_zfs/.gitignore
vendored
Normal file
1
sys/contrib/openzfs/cmd/mount_zfs/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
mount.zfs
|
20
sys/contrib/openzfs/cmd/mount_zfs/Makefile.am
Normal file
20
sys/contrib/openzfs/cmd/mount_zfs/Makefile.am
Normal file
@ -0,0 +1,20 @@
|
||||
include $(top_srcdir)/config/Rules.am
|
||||
|
||||
#
|
||||
# Ignore the prefix for the mount helper. It must be installed in /sbin/
|
||||
# because this path is hardcoded in the mount(8) for security reasons.
|
||||
# However, if needed, the configure option --with-mounthelperdir= can be used
|
||||
# to override the default install location.
|
||||
#
|
||||
sbindir=$(mounthelperdir)
|
||||
sbin_PROGRAMS = mount.zfs
|
||||
|
||||
mount_zfs_SOURCES = \
|
||||
mount_zfs.c
|
||||
|
||||
mount_zfs_LDADD = \
|
||||
$(abs_top_builddir)/lib/libzfs/libzfs.la \
|
||||
$(abs_top_builddir)/lib/libzfs_core/libzfs_core.la \
|
||||
$(abs_top_builddir)/lib/libnvpair/libnvpair.la
|
||||
|
||||
mount_zfs_LDADD += $(LTLIBINTL)
|
408
sys/contrib/openzfs/cmd/mount_zfs/mount_zfs.c
Normal file
408
sys/contrib/openzfs/cmd/mount_zfs/mount_zfs.c
Normal file
@ -0,0 +1,408 @@
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License (the "License").
|
||||
* You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||
* or http://www.opensolaris.org/os/licensing.
|
||||
* See the License for the specific language governing permissions
|
||||
* and limitations under the License.
|
||||
*
|
||||
* When distributing Covered Code, include this CDDL HEADER in each
|
||||
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||
* If applicable, add the following below this CDDL HEADER, with the
|
||||
* fields enclosed by brackets "[]" replaced with your own identifying
|
||||
* information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
*
|
||||
* CDDL HEADER END
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2011 Lawrence Livermore National Security, LLC.
|
||||
*/
|
||||
|
||||
#include <libintl.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/file.h>
|
||||
#include <sys/mount.h>
|
||||
#include <sys/mntent.h>
|
||||
#include <sys/stat.h>
|
||||
#include <libzfs.h>
|
||||
#include <libzutil.h>
|
||||
#include <locale.h>
|
||||
#include <getopt.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
|
||||
#define ZS_COMMENT 0x00000000 /* comment */
|
||||
#define ZS_ZFSUTIL 0x00000001 /* caller is zfs(8) */
|
||||
|
||||
libzfs_handle_t *g_zfs;
|
||||
|
||||
/*
|
||||
* Return the pool/dataset to mount given the name passed to mount. This
|
||||
* is expected to be of the form pool/dataset, however may also refer to
|
||||
* a block device if that device contains a valid zfs label.
|
||||
*/
|
||||
static char *
|
||||
parse_dataset(char *dataset)
|
||||
{
|
||||
char cwd[PATH_MAX];
|
||||
struct stat64 statbuf;
|
||||
int error;
|
||||
int len;
|
||||
|
||||
/*
|
||||
* We expect a pool/dataset to be provided, however if we're
|
||||
* given a device which is a member of a zpool we attempt to
|
||||
* extract the pool name stored in the label. Given the pool
|
||||
* name we can mount the root dataset.
|
||||
*/
|
||||
error = stat64(dataset, &statbuf);
|
||||
if (error == 0) {
|
||||
nvlist_t *config;
|
||||
char *name;
|
||||
int fd;
|
||||
|
||||
fd = open(dataset, O_RDONLY);
|
||||
if (fd < 0)
|
||||
goto out;
|
||||
|
||||
error = zpool_read_label(fd, &config, NULL);
|
||||
(void) close(fd);
|
||||
if (error)
|
||||
goto out;
|
||||
|
||||
error = nvlist_lookup_string(config,
|
||||
ZPOOL_CONFIG_POOL_NAME, &name);
|
||||
if (error) {
|
||||
nvlist_free(config);
|
||||
} else {
|
||||
dataset = strdup(name);
|
||||
nvlist_free(config);
|
||||
return (dataset);
|
||||
}
|
||||
}
|
||||
out:
|
||||
/*
|
||||
* If a file or directory in your current working directory is
|
||||
* named 'dataset' then mount(8) will prepend your current working
|
||||
* directory to the dataset. There is no way to prevent this
|
||||
* behavior so we simply check for it and strip the prepended
|
||||
* patch when it is added.
|
||||
*/
|
||||
if (getcwd(cwd, PATH_MAX) == NULL)
|
||||
return (dataset);
|
||||
|
||||
len = strlen(cwd);
|
||||
|
||||
/* Do not add one when cwd already ends in a trailing '/' */
|
||||
if (strncmp(cwd, dataset, len) == 0)
|
||||
return (dataset + len + (cwd[len-1] != '/'));
|
||||
|
||||
return (dataset);
|
||||
}
|
||||
|
||||
/*
|
||||
* Update the mtab_* code to use the libmount library when it is commonly
|
||||
* available otherwise fallback to legacy mode. The mount(8) utility will
|
||||
* manage the lock file for us to prevent racing updates to /etc/mtab.
|
||||
*/
|
||||
static int
|
||||
mtab_is_writeable(void)
|
||||
{
|
||||
struct stat st;
|
||||
int error, fd;
|
||||
|
||||
error = lstat("/etc/mtab", &st);
|
||||
if (error || S_ISLNK(st.st_mode))
|
||||
return (0);
|
||||
|
||||
fd = open("/etc/mtab", O_RDWR | O_CREAT, 0644);
|
||||
if (fd < 0)
|
||||
return (0);
|
||||
|
||||
close(fd);
|
||||
return (1);
|
||||
}
|
||||
|
||||
static int
|
||||
mtab_update(char *dataset, char *mntpoint, char *type, char *mntopts)
|
||||
{
|
||||
struct mntent mnt;
|
||||
FILE *fp;
|
||||
int error;
|
||||
|
||||
mnt.mnt_fsname = dataset;
|
||||
mnt.mnt_dir = mntpoint;
|
||||
mnt.mnt_type = type;
|
||||
mnt.mnt_opts = mntopts ? mntopts : "";
|
||||
mnt.mnt_freq = 0;
|
||||
mnt.mnt_passno = 0;
|
||||
|
||||
fp = setmntent("/etc/mtab", "a+");
|
||||
if (!fp) {
|
||||
(void) fprintf(stderr, gettext(
|
||||
"filesystem '%s' was mounted, but /etc/mtab "
|
||||
"could not be opened due to error %d\n"),
|
||||
dataset, errno);
|
||||
return (MOUNT_FILEIO);
|
||||
}
|
||||
|
||||
error = addmntent(fp, &mnt);
|
||||
if (error) {
|
||||
(void) fprintf(stderr, gettext(
|
||||
"filesystem '%s' was mounted, but /etc/mtab "
|
||||
"could not be updated due to error %d\n"),
|
||||
dataset, errno);
|
||||
return (MOUNT_FILEIO);
|
||||
}
|
||||
|
||||
(void) endmntent(fp);
|
||||
|
||||
return (MOUNT_SUCCESS);
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
zfs_handle_t *zhp;
|
||||
char prop[ZFS_MAXPROPLEN];
|
||||
uint64_t zfs_version = 0;
|
||||
char mntopts[MNT_LINE_MAX] = { '\0' };
|
||||
char badopt[MNT_LINE_MAX] = { '\0' };
|
||||
char mtabopt[MNT_LINE_MAX] = { '\0' };
|
||||
char mntpoint[PATH_MAX];
|
||||
char *dataset;
|
||||
unsigned long mntflags = 0, zfsflags = 0, remount = 0;
|
||||
int sloppy = 0, fake = 0, verbose = 0, nomtab = 0, zfsutil = 0;
|
||||
int error, c;
|
||||
|
||||
(void) setlocale(LC_ALL, "");
|
||||
(void) textdomain(TEXT_DOMAIN);
|
||||
|
||||
opterr = 0;
|
||||
|
||||
/* check options */
|
||||
while ((c = getopt_long(argc, argv, "sfnvo:h?", 0, 0)) != -1) {
|
||||
switch (c) {
|
||||
case 's':
|
||||
sloppy = 1;
|
||||
break;
|
||||
case 'f':
|
||||
fake = 1;
|
||||
break;
|
||||
case 'n':
|
||||
nomtab = 1;
|
||||
break;
|
||||
case 'v':
|
||||
verbose++;
|
||||
break;
|
||||
case 'o':
|
||||
(void) strlcpy(mntopts, optarg, sizeof (mntopts));
|
||||
break;
|
||||
case 'h':
|
||||
case '?':
|
||||
(void) fprintf(stderr, gettext("Invalid option '%c'\n"),
|
||||
optopt);
|
||||
(void) fprintf(stderr, gettext("Usage: mount.zfs "
|
||||
"[-sfnv] [-o options] <dataset> <mountpoint>\n"));
|
||||
return (MOUNT_USAGE);
|
||||
}
|
||||
}
|
||||
|
||||
argc -= optind;
|
||||
argv += optind;
|
||||
|
||||
/* check that we only have two arguments */
|
||||
if (argc != 2) {
|
||||
if (argc == 0)
|
||||
(void) fprintf(stderr, gettext("missing dataset "
|
||||
"argument\n"));
|
||||
else if (argc == 1)
|
||||
(void) fprintf(stderr,
|
||||
gettext("missing mountpoint argument\n"));
|
||||
else
|
||||
(void) fprintf(stderr, gettext("too many arguments\n"));
|
||||
(void) fprintf(stderr, "usage: mount <dataset> <mountpoint>\n");
|
||||
return (MOUNT_USAGE);
|
||||
}
|
||||
|
||||
dataset = parse_dataset(argv[0]);
|
||||
|
||||
/* canonicalize the mount point */
|
||||
if (realpath(argv[1], mntpoint) == NULL) {
|
||||
(void) fprintf(stderr, gettext("filesystem '%s' cannot be "
|
||||
"mounted at '%s' due to canonicalization error %d.\n"),
|
||||
dataset, argv[1], errno);
|
||||
return (MOUNT_SYSERR);
|
||||
}
|
||||
|
||||
/* validate mount options and set mntflags */
|
||||
error = zfs_parse_mount_options(mntopts, &mntflags, &zfsflags, sloppy,
|
||||
badopt, mtabopt);
|
||||
if (error) {
|
||||
switch (error) {
|
||||
case ENOMEM:
|
||||
(void) fprintf(stderr, gettext("filesystem '%s' "
|
||||
"cannot be mounted due to a memory allocation "
|
||||
"failure.\n"), dataset);
|
||||
return (MOUNT_SYSERR);
|
||||
case ENOENT:
|
||||
(void) fprintf(stderr, gettext("filesystem '%s' "
|
||||
"cannot be mounted due to invalid option "
|
||||
"'%s'.\n"), dataset, badopt);
|
||||
(void) fprintf(stderr, gettext("Use the '-s' option "
|
||||
"to ignore the bad mount option.\n"));
|
||||
return (MOUNT_USAGE);
|
||||
default:
|
||||
(void) fprintf(stderr, gettext("filesystem '%s' "
|
||||
"cannot be mounted due to internal error %d.\n"),
|
||||
dataset, error);
|
||||
return (MOUNT_SOFTWARE);
|
||||
}
|
||||
}
|
||||
|
||||
if (verbose)
|
||||
(void) fprintf(stdout, gettext("mount.zfs:\n"
|
||||
" dataset: \"%s\"\n mountpoint: \"%s\"\n"
|
||||
" mountflags: 0x%lx\n zfsflags: 0x%lx\n"
|
||||
" mountopts: \"%s\"\n mtabopts: \"%s\"\n"),
|
||||
dataset, mntpoint, mntflags, zfsflags, mntopts, mtabopt);
|
||||
|
||||
if (mntflags & MS_REMOUNT) {
|
||||
nomtab = 1;
|
||||
remount = 1;
|
||||
}
|
||||
|
||||
if (zfsflags & ZS_ZFSUTIL)
|
||||
zfsutil = 1;
|
||||
|
||||
if ((g_zfs = libzfs_init()) == NULL) {
|
||||
(void) fprintf(stderr, "%s\n", libzfs_error_init(errno));
|
||||
return (MOUNT_SYSERR);
|
||||
}
|
||||
|
||||
/* try to open the dataset to access the mount point */
|
||||
if ((zhp = zfs_open(g_zfs, dataset,
|
||||
ZFS_TYPE_FILESYSTEM | ZFS_TYPE_SNAPSHOT)) == NULL) {
|
||||
(void) fprintf(stderr, gettext("filesystem '%s' cannot be "
|
||||
"mounted, unable to open the dataset\n"), dataset);
|
||||
libzfs_fini(g_zfs);
|
||||
return (MOUNT_USAGE);
|
||||
}
|
||||
|
||||
zfs_adjust_mount_options(zhp, mntpoint, mntopts, mtabopt);
|
||||
|
||||
/* treat all snapshots as legacy mount points */
|
||||
if (zfs_get_type(zhp) == ZFS_TYPE_SNAPSHOT)
|
||||
(void) strlcpy(prop, ZFS_MOUNTPOINT_LEGACY, ZFS_MAXPROPLEN);
|
||||
else
|
||||
(void) zfs_prop_get(zhp, ZFS_PROP_MOUNTPOINT, prop,
|
||||
sizeof (prop), NULL, NULL, 0, B_FALSE);
|
||||
|
||||
/*
|
||||
* Fetch the max supported zfs version in case we get ENOTSUP
|
||||
* back from the mount command, since we need the zfs handle
|
||||
* to do so.
|
||||
*/
|
||||
zfs_version = zfs_prop_get_int(zhp, ZFS_PROP_VERSION);
|
||||
if (zfs_version == 0) {
|
||||
fprintf(stderr, gettext("unable to fetch "
|
||||
"ZFS version for filesystem '%s'\n"), dataset);
|
||||
return (MOUNT_SYSERR);
|
||||
}
|
||||
|
||||
zfs_close(zhp);
|
||||
libzfs_fini(g_zfs);
|
||||
|
||||
/*
|
||||
* Legacy mount points may only be mounted using 'mount', never using
|
||||
* 'zfs mount'. However, since 'zfs mount' actually invokes 'mount'
|
||||
* we differentiate the two cases using the 'zfsutil' mount option.
|
||||
* This mount option should only be supplied by the 'zfs mount' util.
|
||||
*
|
||||
* The only exception to the above rule is '-o remount' which is
|
||||
* always allowed for non-legacy datasets. This is done because when
|
||||
* using zfs as your root file system both rc.sysinit/umountroot and
|
||||
* systemd depend on 'mount -o remount <mountpoint>' to work.
|
||||
*/
|
||||
if (zfsutil && (strcmp(prop, ZFS_MOUNTPOINT_LEGACY) == 0)) {
|
||||
(void) fprintf(stderr, gettext(
|
||||
"filesystem '%s' cannot be mounted using 'zfs mount'.\n"
|
||||
"Use 'zfs set mountpoint=%s' or 'mount -t zfs %s %s'.\n"
|
||||
"See zfs(8) for more information.\n"),
|
||||
dataset, mntpoint, dataset, mntpoint);
|
||||
return (MOUNT_USAGE);
|
||||
}
|
||||
|
||||
if (!zfsutil && !(remount || fake) &&
|
||||
strcmp(prop, ZFS_MOUNTPOINT_LEGACY)) {
|
||||
(void) fprintf(stderr, gettext(
|
||||
"filesystem '%s' cannot be mounted using 'mount'.\n"
|
||||
"Use 'zfs set mountpoint=%s' or 'zfs mount %s'.\n"
|
||||
"See zfs(8) for more information.\n"),
|
||||
dataset, "legacy", dataset);
|
||||
return (MOUNT_USAGE);
|
||||
}
|
||||
|
||||
if (!fake) {
|
||||
error = mount(dataset, mntpoint, MNTTYPE_ZFS,
|
||||
mntflags, mntopts);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
switch (errno) {
|
||||
case ENOENT:
|
||||
(void) fprintf(stderr, gettext("mount point "
|
||||
"'%s' does not exist\n"), mntpoint);
|
||||
return (MOUNT_SYSERR);
|
||||
case EBUSY:
|
||||
(void) fprintf(stderr, gettext("filesystem "
|
||||
"'%s' is already mounted\n"), dataset);
|
||||
return (MOUNT_BUSY);
|
||||
case ENOTSUP:
|
||||
if (zfs_version > ZPL_VERSION) {
|
||||
(void) fprintf(stderr,
|
||||
gettext("filesystem '%s' (v%d) is not "
|
||||
"supported by this implementation of "
|
||||
"ZFS (max v%d).\n"), dataset,
|
||||
(int)zfs_version, (int)ZPL_VERSION);
|
||||
} else {
|
||||
(void) fprintf(stderr,
|
||||
gettext("filesystem '%s' mount "
|
||||
"failed for unknown reason.\n"), dataset);
|
||||
}
|
||||
return (MOUNT_SYSERR);
|
||||
#ifdef MS_MANDLOCK
|
||||
case EPERM:
|
||||
if (mntflags & MS_MANDLOCK) {
|
||||
(void) fprintf(stderr, gettext("filesystem "
|
||||
"'%s' has the 'nbmand=on' property set, "
|
||||
"this mount\noption may be disabled in "
|
||||
"your kernel. Use 'zfs set nbmand=off'\n"
|
||||
"to disable this option and try to "
|
||||
"mount the filesystem again.\n"), dataset);
|
||||
return (MOUNT_SYSERR);
|
||||
}
|
||||
/* fallthru */
|
||||
#endif
|
||||
default:
|
||||
(void) fprintf(stderr, gettext("filesystem "
|
||||
"'%s' can not be mounted: %s\n"), dataset,
|
||||
strerror(errno));
|
||||
return (MOUNT_USAGE);
|
||||
}
|
||||
}
|
||||
|
||||
if (!nomtab && mtab_is_writeable()) {
|
||||
error = mtab_update(dataset, mntpoint, MNTTYPE_ZFS, mtabopt);
|
||||
if (error)
|
||||
return (error);
|
||||
}
|
||||
|
||||
return (MOUNT_SUCCESS);
|
||||
}
|
1
sys/contrib/openzfs/cmd/raidz_test/.gitignore
vendored
Normal file
1
sys/contrib/openzfs/cmd/raidz_test/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/raidz_test
|
20
sys/contrib/openzfs/cmd/raidz_test/Makefile.am
Normal file
20
sys/contrib/openzfs/cmd/raidz_test/Makefile.am
Normal file
@ -0,0 +1,20 @@
|
||||
include $(top_srcdir)/config/Rules.am
|
||||
|
||||
# Includes kernel code, generate warnings for large stack frames
|
||||
AM_CFLAGS += $(FRAME_LARGER_THAN)
|
||||
|
||||
# Unconditionally enable ASSERTs
|
||||
AM_CPPFLAGS += -DDEBUG -UNDEBUG -DZFS_DEBUG
|
||||
|
||||
bin_PROGRAMS = raidz_test
|
||||
|
||||
raidz_test_SOURCES = \
|
||||
raidz_test.h \
|
||||
raidz_test.c \
|
||||
raidz_bench.c
|
||||
|
||||
raidz_test_LDADD = \
|
||||
$(abs_top_builddir)/lib/libzpool/libzpool.la \
|
||||
$(abs_top_builddir)/lib/libzfs_core/libzfs_core.la
|
||||
|
||||
raidz_test_LDADD += -lm
|
227
sys/contrib/openzfs/cmd/raidz_test/raidz_bench.c
Normal file
227
sys/contrib/openzfs/cmd/raidz_test/raidz_bench.c
Normal file
@ -0,0 +1,227 @@
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License (the "License").
|
||||
* You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||
* or http://www.opensolaris.org/os/licensing.
|
||||
* See the License for the specific language governing permissions
|
||||
* and limitations under the License.
|
||||
*
|
||||
* When distributing Covered Code, include this CDDL HEADER in each
|
||||
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||
* If applicable, add the following below this CDDL HEADER, with the
|
||||
* fields enclosed by brackets "[]" replaced with your own identifying
|
||||
* information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
*
|
||||
* CDDL HEADER END
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2016 Gvozden Nešković. All rights reserved.
|
||||
*/
|
||||
|
||||
#include <sys/zfs_context.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/zio.h>
|
||||
#include <sys/vdev_raidz.h>
|
||||
#include <sys/vdev_raidz_impl.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <sys/time.h>
|
||||
|
||||
#include "raidz_test.h"
|
||||
|
||||
#define GEN_BENCH_MEMORY (((uint64_t)1ULL)<<32)
|
||||
#define REC_BENCH_MEMORY (((uint64_t)1ULL)<<29)
|
||||
#define BENCH_ASHIFT 12
|
||||
#define MIN_CS_SHIFT BENCH_ASHIFT
|
||||
#define MAX_CS_SHIFT SPA_MAXBLOCKSHIFT
|
||||
|
||||
static zio_t zio_bench;
|
||||
static raidz_map_t *rm_bench;
|
||||
static size_t max_data_size = SPA_MAXBLOCKSIZE;
|
||||
|
||||
static void
|
||||
bench_init_raidz_map(void)
|
||||
{
|
||||
zio_bench.io_offset = 0;
|
||||
zio_bench.io_size = max_data_size;
|
||||
|
||||
/*
|
||||
* To permit larger column sizes these have to be done
|
||||
* allocated using aligned alloc instead of zio_abd_buf_alloc
|
||||
*/
|
||||
zio_bench.io_abd = raidz_alloc(max_data_size);
|
||||
|
||||
init_zio_abd(&zio_bench);
|
||||
}
|
||||
|
||||
static void
|
||||
bench_fini_raidz_maps(void)
|
||||
{
|
||||
/* tear down golden zio */
|
||||
raidz_free(zio_bench.io_abd, max_data_size);
|
||||
bzero(&zio_bench, sizeof (zio_t));
|
||||
}
|
||||
|
||||
static inline void
|
||||
run_gen_bench_impl(const char *impl)
|
||||
{
|
||||
int fn, ncols;
|
||||
uint64_t ds, iter_cnt, iter, disksize;
|
||||
hrtime_t start;
|
||||
double elapsed, d_bw;
|
||||
|
||||
/* Benchmark generate functions */
|
||||
for (fn = 0; fn < RAIDZ_GEN_NUM; fn++) {
|
||||
|
||||
for (ds = MIN_CS_SHIFT; ds <= MAX_CS_SHIFT; ds++) {
|
||||
/* create suitable raidz_map */
|
||||
ncols = rto_opts.rto_dcols + fn + 1;
|
||||
zio_bench.io_size = 1ULL << ds;
|
||||
rm_bench = vdev_raidz_map_alloc(&zio_bench,
|
||||
BENCH_ASHIFT, ncols, fn+1);
|
||||
|
||||
/* estimate iteration count */
|
||||
iter_cnt = GEN_BENCH_MEMORY;
|
||||
iter_cnt /= zio_bench.io_size;
|
||||
|
||||
start = gethrtime();
|
||||
for (iter = 0; iter < iter_cnt; iter++)
|
||||
vdev_raidz_generate_parity(rm_bench);
|
||||
elapsed = NSEC2SEC((double)(gethrtime() - start));
|
||||
|
||||
disksize = (1ULL << ds) / rto_opts.rto_dcols;
|
||||
d_bw = (double)iter_cnt * (double)disksize;
|
||||
d_bw /= (1024.0 * 1024.0 * elapsed);
|
||||
|
||||
LOG(D_ALL, "%10s, %8s, %zu, %10llu, %lf, %lf, %u\n",
|
||||
impl,
|
||||
raidz_gen_name[fn],
|
||||
rto_opts.rto_dcols,
|
||||
(1ULL<<ds),
|
||||
d_bw,
|
||||
d_bw * (double)(ncols),
|
||||
(unsigned)iter_cnt);
|
||||
|
||||
vdev_raidz_map_free(rm_bench);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
run_gen_bench(void)
|
||||
{
|
||||
char **impl_name;
|
||||
|
||||
LOG(D_INFO, DBLSEP "\nBenchmarking parity generation...\n\n");
|
||||
LOG(D_ALL, "impl, math, dcols, iosize, disk_bw, total_bw, iter\n");
|
||||
|
||||
for (impl_name = (char **)raidz_impl_names; *impl_name != NULL;
|
||||
impl_name++) {
|
||||
|
||||
if (vdev_raidz_impl_set(*impl_name) != 0)
|
||||
continue;
|
||||
|
||||
run_gen_bench_impl(*impl_name);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
run_rec_bench_impl(const char *impl)
|
||||
{
|
||||
int fn, ncols, nbad;
|
||||
uint64_t ds, iter_cnt, iter, disksize;
|
||||
hrtime_t start;
|
||||
double elapsed, d_bw;
|
||||
static const int tgt[7][3] = {
|
||||
{1, 2, 3}, /* rec_p: bad QR & D[0] */
|
||||
{0, 2, 3}, /* rec_q: bad PR & D[0] */
|
||||
{0, 1, 3}, /* rec_r: bad PQ & D[0] */
|
||||
{2, 3, 4}, /* rec_pq: bad R & D[0][1] */
|
||||
{1, 3, 4}, /* rec_pr: bad Q & D[0][1] */
|
||||
{0, 3, 4}, /* rec_qr: bad P & D[0][1] */
|
||||
{3, 4, 5} /* rec_pqr: bad & D[0][1][2] */
|
||||
};
|
||||
|
||||
for (fn = 0; fn < RAIDZ_REC_NUM; fn++) {
|
||||
for (ds = MIN_CS_SHIFT; ds <= MAX_CS_SHIFT; ds++) {
|
||||
|
||||
/* create suitable raidz_map */
|
||||
ncols = rto_opts.rto_dcols + PARITY_PQR;
|
||||
zio_bench.io_size = 1ULL << ds;
|
||||
|
||||
/*
|
||||
* raidz block is too short to test
|
||||
* the requested method
|
||||
*/
|
||||
if (zio_bench.io_size / rto_opts.rto_dcols <
|
||||
(1ULL << BENCH_ASHIFT))
|
||||
continue;
|
||||
|
||||
rm_bench = vdev_raidz_map_alloc(&zio_bench,
|
||||
BENCH_ASHIFT, ncols, PARITY_PQR);
|
||||
|
||||
/* estimate iteration count */
|
||||
iter_cnt = (REC_BENCH_MEMORY);
|
||||
iter_cnt /= zio_bench.io_size;
|
||||
|
||||
/* calculate how many bad columns there are */
|
||||
nbad = MIN(3, raidz_ncols(rm_bench) -
|
||||
raidz_parity(rm_bench));
|
||||
|
||||
start = gethrtime();
|
||||
for (iter = 0; iter < iter_cnt; iter++)
|
||||
vdev_raidz_reconstruct(rm_bench, tgt[fn], nbad);
|
||||
elapsed = NSEC2SEC((double)(gethrtime() - start));
|
||||
|
||||
disksize = (1ULL << ds) / rto_opts.rto_dcols;
|
||||
d_bw = (double)iter_cnt * (double)(disksize);
|
||||
d_bw /= (1024.0 * 1024.0 * elapsed);
|
||||
|
||||
LOG(D_ALL, "%10s, %8s, %zu, %10llu, %lf, %lf, %u\n",
|
||||
impl,
|
||||
raidz_rec_name[fn],
|
||||
rto_opts.rto_dcols,
|
||||
(1ULL<<ds),
|
||||
d_bw,
|
||||
d_bw * (double)ncols,
|
||||
(unsigned)iter_cnt);
|
||||
|
||||
vdev_raidz_map_free(rm_bench);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
run_rec_bench(void)
|
||||
{
|
||||
char **impl_name;
|
||||
|
||||
LOG(D_INFO, DBLSEP "\nBenchmarking data reconstruction...\n\n");
|
||||
LOG(D_ALL, "impl, math, dcols, iosize, disk_bw, total_bw, iter\n");
|
||||
|
||||
for (impl_name = (char **)raidz_impl_names; *impl_name != NULL;
|
||||
impl_name++) {
|
||||
|
||||
if (vdev_raidz_impl_set(*impl_name) != 0)
|
||||
continue;
|
||||
|
||||
run_rec_bench_impl(*impl_name);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
run_raidz_benchmark(void)
|
||||
{
|
||||
bench_init_raidz_map();
|
||||
|
||||
run_gen_bench();
|
||||
run_rec_bench();
|
||||
|
||||
bench_fini_raidz_maps();
|
||||
}
|
782
sys/contrib/openzfs/cmd/raidz_test/raidz_test.c
Normal file
782
sys/contrib/openzfs/cmd/raidz_test/raidz_test.c
Normal file
@ -0,0 +1,782 @@
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License (the "License").
|
||||
* You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||
* or http://www.opensolaris.org/os/licensing.
|
||||
* See the License for the specific language governing permissions
|
||||
* and limitations under the License.
|
||||
*
|
||||
* When distributing Covered Code, include this CDDL HEADER in each
|
||||
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||
* If applicable, add the following below this CDDL HEADER, with the
|
||||
* fields enclosed by brackets "[]" replaced with your own identifying
|
||||
* information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
*
|
||||
* CDDL HEADER END
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2016 Gvozden Nešković. All rights reserved.
|
||||
*/
|
||||
|
||||
#include <sys/zfs_context.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/zio.h>
|
||||
#include <umem.h>
|
||||
#include <sys/vdev_raidz.h>
|
||||
#include <sys/vdev_raidz_impl.h>
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include "raidz_test.h"
|
||||
|
||||
static int *rand_data;
|
||||
raidz_test_opts_t rto_opts;
|
||||
|
||||
static char gdb[256];
|
||||
static const char gdb_tmpl[] = "gdb -ex \"set pagination 0\" -p %d";
|
||||
|
||||
static void sig_handler(int signo)
|
||||
{
|
||||
struct sigaction action;
|
||||
/*
|
||||
* Restore default action and re-raise signal so SIGSEGV and
|
||||
* SIGABRT can trigger a core dump.
|
||||
*/
|
||||
action.sa_handler = SIG_DFL;
|
||||
sigemptyset(&action.sa_mask);
|
||||
action.sa_flags = 0;
|
||||
(void) sigaction(signo, &action, NULL);
|
||||
|
||||
if (rto_opts.rto_gdb)
|
||||
if (system(gdb)) { }
|
||||
|
||||
raise(signo);
|
||||
}
|
||||
|
||||
static void print_opts(raidz_test_opts_t *opts, boolean_t force)
|
||||
{
|
||||
char *verbose;
|
||||
switch (opts->rto_v) {
|
||||
case 0:
|
||||
verbose = "no";
|
||||
break;
|
||||
case 1:
|
||||
verbose = "info";
|
||||
break;
|
||||
default:
|
||||
verbose = "debug";
|
||||
break;
|
||||
}
|
||||
|
||||
if (force || opts->rto_v >= D_INFO) {
|
||||
(void) fprintf(stdout, DBLSEP "Running with options:\n"
|
||||
" (-a) zio ashift : %zu\n"
|
||||
" (-o) zio offset : 1 << %zu\n"
|
||||
" (-d) number of raidz data columns : %zu\n"
|
||||
" (-s) size of DATA : 1 << %zu\n"
|
||||
" (-S) sweep parameters : %s \n"
|
||||
" (-v) verbose : %s \n\n",
|
||||
opts->rto_ashift, /* -a */
|
||||
ilog2(opts->rto_offset), /* -o */
|
||||
opts->rto_dcols, /* -d */
|
||||
ilog2(opts->rto_dsize), /* -s */
|
||||
opts->rto_sweep ? "yes" : "no", /* -S */
|
||||
verbose); /* -v */
|
||||
}
|
||||
}
|
||||
|
||||
static void usage(boolean_t requested)
|
||||
{
|
||||
const raidz_test_opts_t *o = &rto_opts_defaults;
|
||||
|
||||
FILE *fp = requested ? stdout : stderr;
|
||||
|
||||
(void) fprintf(fp, "Usage:\n"
|
||||
"\t[-a zio ashift (default: %zu)]\n"
|
||||
"\t[-o zio offset, exponent radix 2 (default: %zu)]\n"
|
||||
"\t[-d number of raidz data columns (default: %zu)]\n"
|
||||
"\t[-s zio size, exponent radix 2 (default: %zu)]\n"
|
||||
"\t[-S parameter sweep (default: %s)]\n"
|
||||
"\t[-t timeout for parameter sweep test]\n"
|
||||
"\t[-B benchmark all raidz implementations]\n"
|
||||
"\t[-v increase verbosity (default: %zu)]\n"
|
||||
"\t[-h (print help)]\n"
|
||||
"\t[-T test the test, see if failure would be detected]\n"
|
||||
"\t[-D debug (attach gdb on SIGSEGV)]\n"
|
||||
"",
|
||||
o->rto_ashift, /* -a */
|
||||
ilog2(o->rto_offset), /* -o */
|
||||
o->rto_dcols, /* -d */
|
||||
ilog2(o->rto_dsize), /* -s */
|
||||
rto_opts.rto_sweep ? "yes" : "no", /* -S */
|
||||
o->rto_v); /* -d */
|
||||
|
||||
exit(requested ? 0 : 1);
|
||||
}
|
||||
|
||||
static void process_options(int argc, char **argv)
|
||||
{
|
||||
size_t value;
|
||||
int opt;
|
||||
|
||||
raidz_test_opts_t *o = &rto_opts;
|
||||
|
||||
bcopy(&rto_opts_defaults, o, sizeof (*o));
|
||||
|
||||
while ((opt = getopt(argc, argv, "TDBSvha:o:d:s:t:")) != -1) {
|
||||
value = 0;
|
||||
|
||||
switch (opt) {
|
||||
case 'a':
|
||||
value = strtoull(optarg, NULL, 0);
|
||||
o->rto_ashift = MIN(13, MAX(9, value));
|
||||
break;
|
||||
case 'o':
|
||||
value = strtoull(optarg, NULL, 0);
|
||||
o->rto_offset = ((1ULL << MIN(12, value)) >> 9) << 9;
|
||||
break;
|
||||
case 'd':
|
||||
value = strtoull(optarg, NULL, 0);
|
||||
o->rto_dcols = MIN(255, MAX(1, value));
|
||||
break;
|
||||
case 's':
|
||||
value = strtoull(optarg, NULL, 0);
|
||||
o->rto_dsize = 1ULL << MIN(SPA_MAXBLOCKSHIFT,
|
||||
MAX(SPA_MINBLOCKSHIFT, value));
|
||||
break;
|
||||
case 't':
|
||||
value = strtoull(optarg, NULL, 0);
|
||||
o->rto_sweep_timeout = value;
|
||||
break;
|
||||
case 'v':
|
||||
o->rto_v++;
|
||||
break;
|
||||
case 'S':
|
||||
o->rto_sweep = 1;
|
||||
break;
|
||||
case 'B':
|
||||
o->rto_benchmark = 1;
|
||||
break;
|
||||
case 'D':
|
||||
o->rto_gdb = 1;
|
||||
break;
|
||||
case 'T':
|
||||
o->rto_sanity = 1;
|
||||
break;
|
||||
case 'h':
|
||||
usage(B_TRUE);
|
||||
break;
|
||||
case '?':
|
||||
default:
|
||||
usage(B_FALSE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#define DATA_COL(rm, i) ((rm)->rm_col[raidz_parity(rm) + (i)].rc_abd)
|
||||
#define DATA_COL_SIZE(rm, i) ((rm)->rm_col[raidz_parity(rm) + (i)].rc_size)
|
||||
|
||||
#define CODE_COL(rm, i) ((rm)->rm_col[(i)].rc_abd)
|
||||
#define CODE_COL_SIZE(rm, i) ((rm)->rm_col[(i)].rc_size)
|
||||
|
||||
static int
|
||||
cmp_code(raidz_test_opts_t *opts, const raidz_map_t *rm, const int parity)
|
||||
{
|
||||
int i, ret = 0;
|
||||
|
||||
VERIFY(parity >= 1 && parity <= 3);
|
||||
|
||||
for (i = 0; i < parity; i++) {
|
||||
if (abd_cmp(CODE_COL(rm, i), CODE_COL(opts->rm_golden, i))
|
||||
!= 0) {
|
||||
ret++;
|
||||
LOG_OPT(D_DEBUG, opts,
|
||||
"\nParity block [%d] different!\n", i);
|
||||
}
|
||||
}
|
||||
return (ret);
|
||||
}
|
||||
|
||||
static int
|
||||
cmp_data(raidz_test_opts_t *opts, raidz_map_t *rm)
|
||||
{
|
||||
int i, ret = 0;
|
||||
int dcols = opts->rm_golden->rm_cols - raidz_parity(opts->rm_golden);
|
||||
|
||||
for (i = 0; i < dcols; i++) {
|
||||
if (abd_cmp(DATA_COL(opts->rm_golden, i), DATA_COL(rm, i))
|
||||
!= 0) {
|
||||
ret++;
|
||||
|
||||
LOG_OPT(D_DEBUG, opts,
|
||||
"\nData block [%d] different!\n", i);
|
||||
}
|
||||
}
|
||||
return (ret);
|
||||
}
|
||||
|
||||
static int
|
||||
init_rand(void *data, size_t size, void *private)
|
||||
{
|
||||
int i;
|
||||
int *dst = (int *)data;
|
||||
|
||||
for (i = 0; i < size / sizeof (int); i++)
|
||||
dst[i] = rand_data[i];
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
static void
|
||||
corrupt_colums(raidz_map_t *rm, const int *tgts, const int cnt)
|
||||
{
|
||||
int i;
|
||||
raidz_col_t *col;
|
||||
|
||||
for (i = 0; i < cnt; i++) {
|
||||
col = &rm->rm_col[tgts[i]];
|
||||
abd_iterate_func(col->rc_abd, 0, col->rc_size, init_rand, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
init_zio_abd(zio_t *zio)
|
||||
{
|
||||
abd_iterate_func(zio->io_abd, 0, zio->io_size, init_rand, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
fini_raidz_map(zio_t **zio, raidz_map_t **rm)
|
||||
{
|
||||
vdev_raidz_map_free(*rm);
|
||||
raidz_free((*zio)->io_abd, (*zio)->io_size);
|
||||
umem_free(*zio, sizeof (zio_t));
|
||||
|
||||
*zio = NULL;
|
||||
*rm = NULL;
|
||||
}
|
||||
|
||||
static int
|
||||
init_raidz_golden_map(raidz_test_opts_t *opts, const int parity)
|
||||
{
|
||||
int err = 0;
|
||||
zio_t *zio_test;
|
||||
raidz_map_t *rm_test;
|
||||
const size_t total_ncols = opts->rto_dcols + parity;
|
||||
|
||||
if (opts->rm_golden) {
|
||||
fini_raidz_map(&opts->zio_golden, &opts->rm_golden);
|
||||
}
|
||||
|
||||
opts->zio_golden = umem_zalloc(sizeof (zio_t), UMEM_NOFAIL);
|
||||
zio_test = umem_zalloc(sizeof (zio_t), UMEM_NOFAIL);
|
||||
|
||||
opts->zio_golden->io_offset = zio_test->io_offset = opts->rto_offset;
|
||||
opts->zio_golden->io_size = zio_test->io_size = opts->rto_dsize;
|
||||
|
||||
opts->zio_golden->io_abd = raidz_alloc(opts->rto_dsize);
|
||||
zio_test->io_abd = raidz_alloc(opts->rto_dsize);
|
||||
|
||||
init_zio_abd(opts->zio_golden);
|
||||
init_zio_abd(zio_test);
|
||||
|
||||
VERIFY0(vdev_raidz_impl_set("original"));
|
||||
|
||||
opts->rm_golden = vdev_raidz_map_alloc(opts->zio_golden,
|
||||
opts->rto_ashift, total_ncols, parity);
|
||||
rm_test = vdev_raidz_map_alloc(zio_test,
|
||||
opts->rto_ashift, total_ncols, parity);
|
||||
|
||||
VERIFY(opts->zio_golden);
|
||||
VERIFY(opts->rm_golden);
|
||||
|
||||
vdev_raidz_generate_parity(opts->rm_golden);
|
||||
vdev_raidz_generate_parity(rm_test);
|
||||
|
||||
/* sanity check */
|
||||
err |= cmp_data(opts, rm_test);
|
||||
err |= cmp_code(opts, rm_test, parity);
|
||||
|
||||
if (err)
|
||||
ERR("initializing the golden copy ... [FAIL]!\n");
|
||||
|
||||
/* tear down raidz_map of test zio */
|
||||
fini_raidz_map(&zio_test, &rm_test);
|
||||
|
||||
return (err);
|
||||
}
|
||||
|
||||
static raidz_map_t *
|
||||
init_raidz_map(raidz_test_opts_t *opts, zio_t **zio, const int parity)
|
||||
{
|
||||
raidz_map_t *rm = NULL;
|
||||
const size_t alloc_dsize = opts->rto_dsize;
|
||||
const size_t total_ncols = opts->rto_dcols + parity;
|
||||
const int ccols[] = { 0, 1, 2 };
|
||||
|
||||
VERIFY(zio);
|
||||
VERIFY(parity <= 3 && parity >= 1);
|
||||
|
||||
*zio = umem_zalloc(sizeof (zio_t), UMEM_NOFAIL);
|
||||
|
||||
(*zio)->io_offset = 0;
|
||||
(*zio)->io_size = alloc_dsize;
|
||||
(*zio)->io_abd = raidz_alloc(alloc_dsize);
|
||||
init_zio_abd(*zio);
|
||||
|
||||
rm = vdev_raidz_map_alloc(*zio, opts->rto_ashift,
|
||||
total_ncols, parity);
|
||||
VERIFY(rm);
|
||||
|
||||
/* Make sure code columns are destroyed */
|
||||
corrupt_colums(rm, ccols, parity);
|
||||
|
||||
return (rm);
|
||||
}
|
||||
|
||||
static int
|
||||
run_gen_check(raidz_test_opts_t *opts)
|
||||
{
|
||||
char **impl_name;
|
||||
int fn, err = 0;
|
||||
zio_t *zio_test;
|
||||
raidz_map_t *rm_test;
|
||||
|
||||
err = init_raidz_golden_map(opts, PARITY_PQR);
|
||||
if (0 != err)
|
||||
return (err);
|
||||
|
||||
LOG(D_INFO, DBLSEP);
|
||||
LOG(D_INFO, "Testing parity generation...\n");
|
||||
|
||||
for (impl_name = (char **)raidz_impl_names+1; *impl_name != NULL;
|
||||
impl_name++) {
|
||||
|
||||
LOG(D_INFO, SEP);
|
||||
LOG(D_INFO, "\tTesting [%s] implementation...", *impl_name);
|
||||
|
||||
if (0 != vdev_raidz_impl_set(*impl_name)) {
|
||||
LOG(D_INFO, "[SKIP]\n");
|
||||
continue;
|
||||
} else {
|
||||
LOG(D_INFO, "[SUPPORTED]\n");
|
||||
}
|
||||
|
||||
for (fn = 0; fn < RAIDZ_GEN_NUM; fn++) {
|
||||
|
||||
/* Check if should stop */
|
||||
if (rto_opts.rto_should_stop)
|
||||
return (err);
|
||||
|
||||
/* create suitable raidz_map */
|
||||
rm_test = init_raidz_map(opts, &zio_test, fn+1);
|
||||
VERIFY(rm_test);
|
||||
|
||||
LOG(D_INFO, "\t\tTesting method [%s] ...",
|
||||
raidz_gen_name[fn]);
|
||||
|
||||
if (!opts->rto_sanity)
|
||||
vdev_raidz_generate_parity(rm_test);
|
||||
|
||||
if (cmp_code(opts, rm_test, fn+1) != 0) {
|
||||
LOG(D_INFO, "[FAIL]\n");
|
||||
err++;
|
||||
} else
|
||||
LOG(D_INFO, "[PASS]\n");
|
||||
|
||||
fini_raidz_map(&zio_test, &rm_test);
|
||||
}
|
||||
}
|
||||
|
||||
fini_raidz_map(&opts->zio_golden, &opts->rm_golden);
|
||||
|
||||
return (err);
|
||||
}
|
||||
|
||||
static int
|
||||
run_rec_check_impl(raidz_test_opts_t *opts, raidz_map_t *rm, const int fn)
|
||||
{
|
||||
int x0, x1, x2;
|
||||
int tgtidx[3];
|
||||
int err = 0;
|
||||
static const int rec_tgts[7][3] = {
|
||||
{1, 2, 3}, /* rec_p: bad QR & D[0] */
|
||||
{0, 2, 3}, /* rec_q: bad PR & D[0] */
|
||||
{0, 1, 3}, /* rec_r: bad PQ & D[0] */
|
||||
{2, 3, 4}, /* rec_pq: bad R & D[0][1] */
|
||||
{1, 3, 4}, /* rec_pr: bad Q & D[0][1] */
|
||||
{0, 3, 4}, /* rec_qr: bad P & D[0][1] */
|
||||
{3, 4, 5} /* rec_pqr: bad & D[0][1][2] */
|
||||
};
|
||||
|
||||
memcpy(tgtidx, rec_tgts[fn], sizeof (tgtidx));
|
||||
|
||||
if (fn < RAIDZ_REC_PQ) {
|
||||
/* can reconstruct 1 failed data disk */
|
||||
for (x0 = 0; x0 < opts->rto_dcols; x0++) {
|
||||
if (x0 >= rm->rm_cols - raidz_parity(rm))
|
||||
continue;
|
||||
|
||||
/* Check if should stop */
|
||||
if (rto_opts.rto_should_stop)
|
||||
return (err);
|
||||
|
||||
LOG(D_DEBUG, "[%d] ", x0);
|
||||
|
||||
tgtidx[2] = x0 + raidz_parity(rm);
|
||||
|
||||
corrupt_colums(rm, tgtidx+2, 1);
|
||||
|
||||
if (!opts->rto_sanity)
|
||||
vdev_raidz_reconstruct(rm, tgtidx, 3);
|
||||
|
||||
if (cmp_data(opts, rm) != 0) {
|
||||
err++;
|
||||
LOG(D_DEBUG, "\nREC D[%d]... [FAIL]\n", x0);
|
||||
}
|
||||
}
|
||||
|
||||
} else if (fn < RAIDZ_REC_PQR) {
|
||||
/* can reconstruct 2 failed data disk */
|
||||
for (x0 = 0; x0 < opts->rto_dcols; x0++) {
|
||||
if (x0 >= rm->rm_cols - raidz_parity(rm))
|
||||
continue;
|
||||
for (x1 = x0 + 1; x1 < opts->rto_dcols; x1++) {
|
||||
if (x1 >= rm->rm_cols - raidz_parity(rm))
|
||||
continue;
|
||||
|
||||
/* Check if should stop */
|
||||
if (rto_opts.rto_should_stop)
|
||||
return (err);
|
||||
|
||||
LOG(D_DEBUG, "[%d %d] ", x0, x1);
|
||||
|
||||
tgtidx[1] = x0 + raidz_parity(rm);
|
||||
tgtidx[2] = x1 + raidz_parity(rm);
|
||||
|
||||
corrupt_colums(rm, tgtidx+1, 2);
|
||||
|
||||
if (!opts->rto_sanity)
|
||||
vdev_raidz_reconstruct(rm, tgtidx, 3);
|
||||
|
||||
if (cmp_data(opts, rm) != 0) {
|
||||
err++;
|
||||
LOG(D_DEBUG, "\nREC D[%d %d]... "
|
||||
"[FAIL]\n", x0, x1);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* can reconstruct 3 failed data disk */
|
||||
for (x0 = 0; x0 < opts->rto_dcols; x0++) {
|
||||
if (x0 >= rm->rm_cols - raidz_parity(rm))
|
||||
continue;
|
||||
for (x1 = x0 + 1; x1 < opts->rto_dcols; x1++) {
|
||||
if (x1 >= rm->rm_cols - raidz_parity(rm))
|
||||
continue;
|
||||
for (x2 = x1 + 1; x2 < opts->rto_dcols; x2++) {
|
||||
if (x2 >=
|
||||
rm->rm_cols - raidz_parity(rm))
|
||||
continue;
|
||||
|
||||
/* Check if should stop */
|
||||
if (rto_opts.rto_should_stop)
|
||||
return (err);
|
||||
|
||||
LOG(D_DEBUG, "[%d %d %d]", x0, x1, x2);
|
||||
|
||||
tgtidx[0] = x0 + raidz_parity(rm);
|
||||
tgtidx[1] = x1 + raidz_parity(rm);
|
||||
tgtidx[2] = x2 + raidz_parity(rm);
|
||||
|
||||
corrupt_colums(rm, tgtidx, 3);
|
||||
|
||||
if (!opts->rto_sanity)
|
||||
vdev_raidz_reconstruct(rm,
|
||||
tgtidx, 3);
|
||||
|
||||
if (cmp_data(opts, rm) != 0) {
|
||||
err++;
|
||||
LOG(D_DEBUG,
|
||||
"\nREC D[%d %d %d]... "
|
||||
"[FAIL]\n", x0, x1, x2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return (err);
|
||||
}
|
||||
|
||||
static int
|
||||
run_rec_check(raidz_test_opts_t *opts)
|
||||
{
|
||||
char **impl_name;
|
||||
unsigned fn, err = 0;
|
||||
zio_t *zio_test;
|
||||
raidz_map_t *rm_test;
|
||||
|
||||
err = init_raidz_golden_map(opts, PARITY_PQR);
|
||||
if (0 != err)
|
||||
return (err);
|
||||
|
||||
LOG(D_INFO, DBLSEP);
|
||||
LOG(D_INFO, "Testing data reconstruction...\n");
|
||||
|
||||
for (impl_name = (char **)raidz_impl_names+1; *impl_name != NULL;
|
||||
impl_name++) {
|
||||
|
||||
LOG(D_INFO, SEP);
|
||||
LOG(D_INFO, "\tTesting [%s] implementation...", *impl_name);
|
||||
|
||||
if (vdev_raidz_impl_set(*impl_name) != 0) {
|
||||
LOG(D_INFO, "[SKIP]\n");
|
||||
continue;
|
||||
} else
|
||||
LOG(D_INFO, "[SUPPORTED]\n");
|
||||
|
||||
|
||||
/* create suitable raidz_map */
|
||||
rm_test = init_raidz_map(opts, &zio_test, PARITY_PQR);
|
||||
/* generate parity */
|
||||
vdev_raidz_generate_parity(rm_test);
|
||||
|
||||
for (fn = 0; fn < RAIDZ_REC_NUM; fn++) {
|
||||
|
||||
LOG(D_INFO, "\t\tTesting method [%s] ...",
|
||||
raidz_rec_name[fn]);
|
||||
|
||||
if (run_rec_check_impl(opts, rm_test, fn) != 0) {
|
||||
LOG(D_INFO, "[FAIL]\n");
|
||||
err++;
|
||||
|
||||
} else
|
||||
LOG(D_INFO, "[PASS]\n");
|
||||
|
||||
}
|
||||
/* tear down test raidz_map */
|
||||
fini_raidz_map(&zio_test, &rm_test);
|
||||
}
|
||||
|
||||
fini_raidz_map(&opts->zio_golden, &opts->rm_golden);
|
||||
|
||||
return (err);
|
||||
}
|
||||
|
||||
static int
|
||||
run_test(raidz_test_opts_t *opts)
|
||||
{
|
||||
int err = 0;
|
||||
|
||||
if (opts == NULL)
|
||||
opts = &rto_opts;
|
||||
|
||||
print_opts(opts, B_FALSE);
|
||||
|
||||
err |= run_gen_check(opts);
|
||||
err |= run_rec_check(opts);
|
||||
|
||||
return (err);
|
||||
}
|
||||
|
||||
#define SWEEP_RUNNING 0
|
||||
#define SWEEP_FINISHED 1
|
||||
#define SWEEP_ERROR 2
|
||||
#define SWEEP_TIMEOUT 3
|
||||
|
||||
static int sweep_state = 0;
|
||||
static raidz_test_opts_t failed_opts;
|
||||
|
||||
static kmutex_t sem_mtx;
|
||||
static kcondvar_t sem_cv;
|
||||
static int max_free_slots;
|
||||
static int free_slots;
|
||||
|
||||
static void
|
||||
sweep_thread(void *arg)
|
||||
{
|
||||
int err = 0;
|
||||
raidz_test_opts_t *opts = (raidz_test_opts_t *)arg;
|
||||
VERIFY(opts != NULL);
|
||||
|
||||
err = run_test(opts);
|
||||
|
||||
if (rto_opts.rto_sanity) {
|
||||
/* 25% chance that a sweep test fails */
|
||||
if (rand() < (RAND_MAX/4))
|
||||
err = 1;
|
||||
}
|
||||
|
||||
if (0 != err) {
|
||||
mutex_enter(&sem_mtx);
|
||||
memcpy(&failed_opts, opts, sizeof (raidz_test_opts_t));
|
||||
sweep_state = SWEEP_ERROR;
|
||||
mutex_exit(&sem_mtx);
|
||||
}
|
||||
|
||||
umem_free(opts, sizeof (raidz_test_opts_t));
|
||||
|
||||
/* signal the next thread */
|
||||
mutex_enter(&sem_mtx);
|
||||
free_slots++;
|
||||
cv_signal(&sem_cv);
|
||||
mutex_exit(&sem_mtx);
|
||||
|
||||
thread_exit();
|
||||
}
|
||||
|
||||
static int
|
||||
run_sweep(void)
|
||||
{
|
||||
static const size_t dcols_v[] = { 1, 2, 3, 4, 5, 6, 7, 8, 12, 15, 16 };
|
||||
static const size_t ashift_v[] = { 9, 12, 14 };
|
||||
static const size_t size_v[] = { 1 << 9, 21 * (1 << 9), 13 * (1 << 12),
|
||||
1 << 17, (1 << 20) - (1 << 12), SPA_MAXBLOCKSIZE };
|
||||
|
||||
(void) setvbuf(stdout, NULL, _IONBF, 0);
|
||||
|
||||
ulong_t total_comb = ARRAY_SIZE(size_v) * ARRAY_SIZE(ashift_v) *
|
||||
ARRAY_SIZE(dcols_v);
|
||||
ulong_t tried_comb = 0;
|
||||
hrtime_t time_diff, start_time = gethrtime();
|
||||
raidz_test_opts_t *opts;
|
||||
int a, d, s;
|
||||
|
||||
max_free_slots = free_slots = MAX(2, boot_ncpus);
|
||||
|
||||
mutex_init(&sem_mtx, NULL, MUTEX_DEFAULT, NULL);
|
||||
cv_init(&sem_cv, NULL, CV_DEFAULT, NULL);
|
||||
|
||||
for (s = 0; s < ARRAY_SIZE(size_v); s++)
|
||||
for (a = 0; a < ARRAY_SIZE(ashift_v); a++)
|
||||
for (d = 0; d < ARRAY_SIZE(dcols_v); d++) {
|
||||
|
||||
if (size_v[s] < (1 << ashift_v[a])) {
|
||||
total_comb--;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (++tried_comb % 20 == 0)
|
||||
LOG(D_ALL, "%lu/%lu... ", tried_comb, total_comb);
|
||||
|
||||
/* wait for signal to start new thread */
|
||||
mutex_enter(&sem_mtx);
|
||||
while (cv_timedwait_sig(&sem_cv, &sem_mtx,
|
||||
ddi_get_lbolt() + hz)) {
|
||||
|
||||
/* check if should stop the test (timeout) */
|
||||
time_diff = (gethrtime() - start_time) / NANOSEC;
|
||||
if (rto_opts.rto_sweep_timeout > 0 &&
|
||||
time_diff >= rto_opts.rto_sweep_timeout) {
|
||||
sweep_state = SWEEP_TIMEOUT;
|
||||
rto_opts.rto_should_stop = B_TRUE;
|
||||
mutex_exit(&sem_mtx);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* check if should stop the test (error) */
|
||||
if (sweep_state != SWEEP_RUNNING) {
|
||||
mutex_exit(&sem_mtx);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* exit loop if a slot is available */
|
||||
if (free_slots > 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
free_slots--;
|
||||
mutex_exit(&sem_mtx);
|
||||
|
||||
opts = umem_zalloc(sizeof (raidz_test_opts_t), UMEM_NOFAIL);
|
||||
opts->rto_ashift = ashift_v[a];
|
||||
opts->rto_dcols = dcols_v[d];
|
||||
opts->rto_offset = (1 << ashift_v[a]) * rand();
|
||||
opts->rto_dsize = size_v[s];
|
||||
opts->rto_v = 0; /* be quiet */
|
||||
|
||||
VERIFY3P(thread_create(NULL, 0, sweep_thread, (void *) opts,
|
||||
0, NULL, TS_RUN, defclsyspri), !=, NULL);
|
||||
}
|
||||
|
||||
exit:
|
||||
LOG(D_ALL, "\nWaiting for test threads to finish...\n");
|
||||
mutex_enter(&sem_mtx);
|
||||
VERIFY(free_slots <= max_free_slots);
|
||||
while (free_slots < max_free_slots) {
|
||||
(void) cv_wait(&sem_cv, &sem_mtx);
|
||||
}
|
||||
mutex_exit(&sem_mtx);
|
||||
|
||||
if (sweep_state == SWEEP_ERROR) {
|
||||
ERR("Sweep test failed! Failed option: \n");
|
||||
print_opts(&failed_opts, B_TRUE);
|
||||
} else {
|
||||
if (sweep_state == SWEEP_TIMEOUT)
|
||||
LOG(D_ALL, "Test timeout (%lus). Stopping...\n",
|
||||
(ulong_t)rto_opts.rto_sweep_timeout);
|
||||
|
||||
LOG(D_ALL, "Sweep test succeeded on %lu raidz maps!\n",
|
||||
(ulong_t)tried_comb);
|
||||
}
|
||||
|
||||
mutex_destroy(&sem_mtx);
|
||||
|
||||
return (sweep_state == SWEEP_ERROR ? SWEEP_ERROR : 0);
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
size_t i;
|
||||
struct sigaction action;
|
||||
int err = 0;
|
||||
|
||||
/* init gdb string early */
|
||||
(void) sprintf(gdb, gdb_tmpl, getpid());
|
||||
|
||||
action.sa_handler = sig_handler;
|
||||
sigemptyset(&action.sa_mask);
|
||||
action.sa_flags = 0;
|
||||
|
||||
if (sigaction(SIGSEGV, &action, NULL) < 0) {
|
||||
ERR("raidz_test: cannot catch SIGSEGV: %s.\n", strerror(errno));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
(void) setvbuf(stdout, NULL, _IOLBF, 0);
|
||||
|
||||
dprintf_setup(&argc, argv);
|
||||
|
||||
process_options(argc, argv);
|
||||
|
||||
kernel_init(SPA_MODE_READ);
|
||||
|
||||
/* setup random data because rand() is not reentrant */
|
||||
rand_data = (int *)umem_alloc(SPA_MAXBLOCKSIZE, UMEM_NOFAIL);
|
||||
srand((unsigned)time(NULL) * getpid());
|
||||
for (i = 0; i < SPA_MAXBLOCKSIZE / sizeof (int); i++)
|
||||
rand_data[i] = rand();
|
||||
|
||||
mprotect(rand_data, SPA_MAXBLOCKSIZE, PROT_READ);
|
||||
|
||||
if (rto_opts.rto_benchmark) {
|
||||
run_raidz_benchmark();
|
||||
} else if (rto_opts.rto_sweep) {
|
||||
err = run_sweep();
|
||||
} else {
|
||||
err = run_test(NULL);
|
||||
}
|
||||
|
||||
umem_free(rand_data, SPA_MAXBLOCKSIZE);
|
||||
kernel_fini();
|
||||
|
||||
return (err);
|
||||
}
|
116
sys/contrib/openzfs/cmd/raidz_test/raidz_test.h
Normal file
116
sys/contrib/openzfs/cmd/raidz_test/raidz_test.h
Normal file
@ -0,0 +1,116 @@
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License (the "License").
|
||||
* You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||
* or http://www.opensolaris.org/os/licensing.
|
||||
* See the License for the specific language governing permissions
|
||||
* and limitations under the License.
|
||||
*
|
||||
* When distributing Covered Code, include this CDDL HEADER in each
|
||||
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||
* If applicable, add the following below this CDDL HEADER, with the
|
||||
* fields enclosed by brackets "[]" replaced with your own identifying
|
||||
* information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
*
|
||||
* CDDL HEADER END
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2016 Gvozden Nešković. All rights reserved.
|
||||
*/
|
||||
|
||||
#ifndef RAIDZ_TEST_H
|
||||
#define RAIDZ_TEST_H
|
||||
|
||||
#include <sys/spa.h>
|
||||
|
||||
static const char *raidz_impl_names[] = {
|
||||
"original",
|
||||
"scalar",
|
||||
"sse2",
|
||||
"ssse3",
|
||||
"avx2",
|
||||
"avx512f",
|
||||
"avx512bw",
|
||||
"aarch64_neon",
|
||||
"aarch64_neonx2",
|
||||
"powerpc_altivec",
|
||||
NULL
|
||||
};
|
||||
|
||||
typedef struct raidz_test_opts {
|
||||
size_t rto_ashift;
|
||||
size_t rto_offset;
|
||||
size_t rto_dcols;
|
||||
size_t rto_dsize;
|
||||
size_t rto_v;
|
||||
size_t rto_sweep;
|
||||
size_t rto_sweep_timeout;
|
||||
size_t rto_benchmark;
|
||||
size_t rto_sanity;
|
||||
size_t rto_gdb;
|
||||
|
||||
/* non-user options */
|
||||
boolean_t rto_should_stop;
|
||||
|
||||
zio_t *zio_golden;
|
||||
raidz_map_t *rm_golden;
|
||||
} raidz_test_opts_t;
|
||||
|
||||
static const raidz_test_opts_t rto_opts_defaults = {
|
||||
.rto_ashift = 9,
|
||||
.rto_offset = 1ULL << 0,
|
||||
.rto_dcols = 8,
|
||||
.rto_dsize = 1<<19,
|
||||
.rto_v = 0,
|
||||
.rto_sweep = 0,
|
||||
.rto_benchmark = 0,
|
||||
.rto_sanity = 0,
|
||||
.rto_gdb = 0,
|
||||
.rto_should_stop = B_FALSE
|
||||
};
|
||||
|
||||
extern raidz_test_opts_t rto_opts;
|
||||
|
||||
static inline size_t ilog2(size_t a)
|
||||
{
|
||||
return (a > 1 ? 1 + ilog2(a >> 1) : 0);
|
||||
}
|
||||
|
||||
|
||||
#define D_ALL 0
|
||||
#define D_INFO 1
|
||||
#define D_DEBUG 2
|
||||
|
||||
#define LOG(lvl, a...) \
|
||||
{ \
|
||||
if (rto_opts.rto_v >= lvl) \
|
||||
(void) fprintf(stdout, a); \
|
||||
} \
|
||||
|
||||
#define LOG_OPT(lvl, opt, a...) \
|
||||
{ \
|
||||
if (opt->rto_v >= lvl) \
|
||||
(void) fprintf(stdout, a); \
|
||||
} \
|
||||
|
||||
#define ERR(a...) (void) fprintf(stderr, a)
|
||||
|
||||
|
||||
#define DBLSEP "================\n"
|
||||
#define SEP "----------------\n"
|
||||
|
||||
|
||||
#define raidz_alloc(size) abd_alloc(size, B_FALSE)
|
||||
#define raidz_free(p, size) abd_free(p)
|
||||
|
||||
|
||||
void init_zio_abd(zio_t *zio);
|
||||
|
||||
void run_raidz_benchmark(void);
|
||||
|
||||
#endif /* RAIDZ_TEST_H */
|
1
sys/contrib/openzfs/cmd/vdev_id/Makefile.am
Normal file
1
sys/contrib/openzfs/cmd/vdev_id/Makefile.am
Normal file
@ -0,0 +1 @@
|
||||
dist_udev_SCRIPTS = vdev_id
|
605
sys/contrib/openzfs/cmd/vdev_id/vdev_id
Executable file
605
sys/contrib/openzfs/cmd/vdev_id/vdev_id
Executable file
@ -0,0 +1,605 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# vdev_id: udev helper to generate user-friendly names for JBOD disks
|
||||
#
|
||||
# This script parses the file /etc/zfs/vdev_id.conf to map a
|
||||
# physical path in a storage topology to a channel name. The
|
||||
# channel name is combined with a disk enclosure slot number to
|
||||
# create an alias that reflects the physical location of the drive.
|
||||
# This is particularly helpful when it comes to tasks like replacing
|
||||
# failed drives. Slot numbers may also be re-mapped in case the
|
||||
# default numbering is unsatisfactory. The drive aliases will be
|
||||
# created as symbolic links in /dev/disk/by-vdev.
|
||||
#
|
||||
# The currently supported topologies are sas_direct and sas_switch.
|
||||
# A multipath mode is supported in which dm-mpath devices are
|
||||
# handled by examining the first-listed running component disk. In
|
||||
# multipath mode the configuration file should contain a channel
|
||||
# definition with the same name for each path to a given enclosure.
|
||||
#
|
||||
# The alias keyword provides a simple way to map already-existing
|
||||
# device symlinks to more convenient names. It is suitable for
|
||||
# small, static configurations or for sites that have some automated
|
||||
# way to generate the mapping file.
|
||||
#
|
||||
#
|
||||
# Some example configuration files are given below.
|
||||
|
||||
# #
|
||||
# # Example vdev_id.conf - sas_direct.
|
||||
# #
|
||||
#
|
||||
# multipath no
|
||||
# topology sas_direct
|
||||
# phys_per_port 4
|
||||
# slot bay
|
||||
#
|
||||
# # PCI_ID HBA PORT CHANNEL NAME
|
||||
# channel 85:00.0 1 A
|
||||
# channel 85:00.0 0 B
|
||||
# channel 86:00.0 1 C
|
||||
# channel 86:00.0 0 D
|
||||
#
|
||||
# # Custom mapping for Channel A
|
||||
#
|
||||
# # Linux Mapped
|
||||
# # Slot Slot Channel
|
||||
# slot 1 7 A
|
||||
# slot 2 10 A
|
||||
# slot 3 3 A
|
||||
# slot 4 6 A
|
||||
#
|
||||
# # Default mapping for B, C, and D
|
||||
# slot 1 4
|
||||
# slot 2 2
|
||||
# slot 3 1
|
||||
# slot 4 3
|
||||
|
||||
# #
|
||||
# # Example vdev_id.conf - sas_switch
|
||||
# #
|
||||
#
|
||||
# topology sas_switch
|
||||
#
|
||||
# # SWITCH PORT CHANNEL NAME
|
||||
# channel 1 A
|
||||
# channel 2 B
|
||||
# channel 3 C
|
||||
# channel 4 D
|
||||
|
||||
# #
|
||||
# # Example vdev_id.conf - multipath
|
||||
# #
|
||||
#
|
||||
# multipath yes
|
||||
#
|
||||
# # PCI_ID HBA PORT CHANNEL NAME
|
||||
# channel 85:00.0 1 A
|
||||
# channel 85:00.0 0 B
|
||||
# channel 86:00.0 1 A
|
||||
# channel 86:00.0 0 B
|
||||
|
||||
# #
|
||||
# # Example vdev_id.conf - alias
|
||||
# #
|
||||
#
|
||||
# # by-vdev
|
||||
# # name fully qualified or base name of device link
|
||||
# alias d1 /dev/disk/by-id/wwn-0x5000c5002de3b9ca
|
||||
# alias d2 wwn-0x5000c5002def789e
|
||||
|
||||
PATH=/bin:/sbin:/usr/bin:/usr/sbin
|
||||
CONFIG=/etc/zfs/vdev_id.conf
|
||||
PHYS_PER_PORT=
|
||||
DEV=
|
||||
MULTIPATH=
|
||||
TOPOLOGY=
|
||||
BAY=
|
||||
|
||||
usage() {
|
||||
cat << EOF
|
||||
Usage: vdev_id [-h]
|
||||
vdev_id <-d device> [-c config_file] [-p phys_per_port]
|
||||
[-g sas_direct|sas_switch|scsi] [-m]
|
||||
|
||||
-c specify name of an alternative config file [default=$CONFIG]
|
||||
-d specify basename of device (i.e. sda)
|
||||
-e Create enclose device symlinks only (/dev/by-enclosure)
|
||||
-g Storage network topology [default="$TOPOLOGY"]
|
||||
-m Run in multipath mode
|
||||
-p number of phy's per switch port [default=$PHYS_PER_PORT]
|
||||
-h show this summary
|
||||
EOF
|
||||
exit 0
|
||||
}
|
||||
|
||||
map_slot() {
|
||||
LINUX_SLOT=$1
|
||||
CHANNEL=$2
|
||||
|
||||
MAPPED_SLOT=`awk "\\$1 == \"slot\" && \\$2 == ${LINUX_SLOT} && \
|
||||
\\$4 ~ /^${CHANNEL}$|^$/ { print \\$3; exit }" $CONFIG`
|
||||
if [ -z "$MAPPED_SLOT" ] ; then
|
||||
MAPPED_SLOT=$LINUX_SLOT
|
||||
fi
|
||||
printf "%d" ${MAPPED_SLOT}
|
||||
}
|
||||
|
||||
map_channel() {
|
||||
MAPPED_CHAN=
|
||||
PCI_ID=$1
|
||||
PORT=$2
|
||||
|
||||
case $TOPOLOGY in
|
||||
"sas_switch")
|
||||
MAPPED_CHAN=`awk "\\$1 == \"channel\" && \\$2 == ${PORT} \
|
||||
{ print \\$3; exit }" $CONFIG`
|
||||
;;
|
||||
"sas_direct"|"scsi")
|
||||
MAPPED_CHAN=`awk "\\$1 == \"channel\" && \
|
||||
\\$2 == \"${PCI_ID}\" && \\$3 == ${PORT} \
|
||||
{ print \\$4; exit }" $CONFIG`
|
||||
;;
|
||||
esac
|
||||
printf "%s" ${MAPPED_CHAN}
|
||||
}
|
||||
|
||||
sas_handler() {
|
||||
if [ -z "$PHYS_PER_PORT" ] ; then
|
||||
PHYS_PER_PORT=`awk "\\$1 == \"phys_per_port\" \
|
||||
{print \\$2; exit}" $CONFIG`
|
||||
fi
|
||||
PHYS_PER_PORT=${PHYS_PER_PORT:-4}
|
||||
if ! echo $PHYS_PER_PORT | grep -q -E '^[0-9]+$' ; then
|
||||
echo "Error: phys_per_port value $PHYS_PER_PORT is non-numeric"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$MULTIPATH_MODE" ] ; then
|
||||
MULTIPATH_MODE=`awk "\\$1 == \"multipath\" \
|
||||
{print \\$2; exit}" $CONFIG`
|
||||
fi
|
||||
|
||||
# Use first running component device if we're handling a dm-mpath device
|
||||
if [ "$MULTIPATH_MODE" = "yes" ] ; then
|
||||
# If udev didn't tell us the UUID via DM_NAME, check /dev/mapper
|
||||
if [ -z "$DM_NAME" ] ; then
|
||||
DM_NAME=`ls -l --full-time /dev/mapper |
|
||||
awk "/\/$DEV$/{print \\$9}"`
|
||||
fi
|
||||
|
||||
# For raw disks udev exports DEVTYPE=partition when
|
||||
# handling partitions, and the rules can be written to
|
||||
# take advantage of this to append a -part suffix. For
|
||||
# dm devices we get DEVTYPE=disk even for partitions so
|
||||
# we have to append the -part suffix directly in the
|
||||
# helper.
|
||||
if [ "$DEVTYPE" != "partition" ] ; then
|
||||
PART=`echo $DM_NAME | awk -Fp '/p/{print "-part"$2}'`
|
||||
fi
|
||||
|
||||
# Strip off partition information.
|
||||
DM_NAME=`echo $DM_NAME | sed 's/p[0-9][0-9]*$//'`
|
||||
if [ -z "$DM_NAME" ] ; then
|
||||
return
|
||||
fi
|
||||
|
||||
# Get the raw scsi device name from multipath -ll. Strip off
|
||||
# leading pipe symbols to make field numbering consistent.
|
||||
DEV=`multipath -ll $DM_NAME |
|
||||
awk '/running/{gsub("^[|]"," "); print $3 ; exit}'`
|
||||
if [ -z "$DEV" ] ; then
|
||||
return
|
||||
fi
|
||||
fi
|
||||
|
||||
if echo $DEV | grep -q ^/devices/ ; then
|
||||
sys_path=$DEV
|
||||
else
|
||||
sys_path=`udevadm info -q path -p /sys/block/$DEV 2>/dev/null`
|
||||
fi
|
||||
|
||||
# Use positional parameters as an ad-hoc array
|
||||
set -- $(echo "$sys_path" | tr / ' ')
|
||||
num_dirs=$#
|
||||
scsi_host_dir="/sys"
|
||||
|
||||
# Get path up to /sys/.../hostX
|
||||
i=1
|
||||
while [ $i -le $num_dirs ] ; do
|
||||
d=$(eval echo \${$i})
|
||||
scsi_host_dir="$scsi_host_dir/$d"
|
||||
echo $d | grep -q -E '^host[0-9]+$' && break
|
||||
i=$(($i + 1))
|
||||
done
|
||||
|
||||
if [ $i = $num_dirs ] ; then
|
||||
return
|
||||
fi
|
||||
|
||||
PCI_ID=$(eval echo \${$(($i -1))} | awk -F: '{print $2":"$3}')
|
||||
|
||||
# In sas_switch mode, the directory four levels beneath
|
||||
# /sys/.../hostX contains symlinks to phy devices that reveal
|
||||
# the switch port number. In sas_direct mode, the phy links one
|
||||
# directory down reveal the HBA port.
|
||||
port_dir=$scsi_host_dir
|
||||
case $TOPOLOGY in
|
||||
"sas_switch") j=$(($i + 4)) ;;
|
||||
"sas_direct") j=$(($i + 1)) ;;
|
||||
esac
|
||||
|
||||
i=$(($i + 1))
|
||||
while [ $i -le $j ] ; do
|
||||
port_dir="$port_dir/$(eval echo \${$i})"
|
||||
i=$(($i + 1))
|
||||
done
|
||||
|
||||
PHY=`ls -d $port_dir/phy* 2>/dev/null | head -1 | awk -F: '{print $NF}'`
|
||||
if [ -z "$PHY" ] ; then
|
||||
PHY=0
|
||||
fi
|
||||
PORT=$(( $PHY / $PHYS_PER_PORT ))
|
||||
|
||||
# Look in /sys/.../sas_device/end_device-X for the bay_identifier
|
||||
# attribute.
|
||||
end_device_dir=$port_dir
|
||||
while [ $i -lt $num_dirs ] ; do
|
||||
d=$(eval echo \${$i})
|
||||
end_device_dir="$end_device_dir/$d"
|
||||
if echo $d | grep -q '^end_device' ; then
|
||||
end_device_dir="$end_device_dir/sas_device/$d"
|
||||
break
|
||||
fi
|
||||
i=$(($i + 1))
|
||||
done
|
||||
|
||||
SLOT=
|
||||
case $BAY in
|
||||
"bay")
|
||||
SLOT=`cat $end_device_dir/bay_identifier 2>/dev/null`
|
||||
;;
|
||||
"phy")
|
||||
SLOT=`cat $end_device_dir/phy_identifier 2>/dev/null`
|
||||
;;
|
||||
"port")
|
||||
d=$(eval echo \${$i})
|
||||
SLOT=`echo $d | sed -e 's/^.*://'`
|
||||
;;
|
||||
"id")
|
||||
i=$(($i + 1))
|
||||
d=$(eval echo \${$i})
|
||||
SLOT=`echo $d | sed -e 's/^.*://'`
|
||||
;;
|
||||
"lun")
|
||||
i=$(($i + 2))
|
||||
d=$(eval echo \${$i})
|
||||
SLOT=`echo $d | sed -e 's/^.*://'`
|
||||
;;
|
||||
"ses")
|
||||
# look for this SAS path in all SCSI Enclosure Services
|
||||
# (SES) enclosures
|
||||
sas_address=`cat $end_device_dir/sas_address 2>/dev/null`
|
||||
enclosures=`lsscsi -g | \
|
||||
sed -n -e '/enclosu/s/^.* \([^ ][^ ]*\) *$/\1/p'`
|
||||
for enclosure in $enclosures; do
|
||||
set -- $(sg_ses -p aes $enclosure | \
|
||||
awk "/device slot number:/{slot=\$12} \
|
||||
/SAS address: $sas_address/\
|
||||
{print slot}")
|
||||
SLOT=$1
|
||||
if [ -n "$SLOT" ] ; then
|
||||
break
|
||||
fi
|
||||
done
|
||||
;;
|
||||
esac
|
||||
if [ -z "$SLOT" ] ; then
|
||||
return
|
||||
fi
|
||||
|
||||
CHAN=`map_channel $PCI_ID $PORT`
|
||||
SLOT=`map_slot $SLOT $CHAN`
|
||||
if [ -z "$CHAN" ] ; then
|
||||
return
|
||||
fi
|
||||
echo ${CHAN}${SLOT}${PART}
|
||||
}
|
||||
|
||||
scsi_handler() {
|
||||
if [ -z "$FIRST_BAY_NUMBER" ] ; then
|
||||
FIRST_BAY_NUMBER=`awk "\\$1 == \"first_bay_number\" \
|
||||
{print \\$2; exit}" $CONFIG`
|
||||
fi
|
||||
FIRST_BAY_NUMBER=${FIRST_BAY_NUMBER:-0}
|
||||
|
||||
if [ -z "$PHYS_PER_PORT" ] ; then
|
||||
PHYS_PER_PORT=`awk "\\$1 == \"phys_per_port\" \
|
||||
{print \\$2; exit}" $CONFIG`
|
||||
fi
|
||||
PHYS_PER_PORT=${PHYS_PER_PORT:-4}
|
||||
if ! echo $PHYS_PER_PORT | grep -q -E '^[0-9]+$' ; then
|
||||
echo "Error: phys_per_port value $PHYS_PER_PORT is non-numeric"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$MULTIPATH_MODE" ] ; then
|
||||
MULTIPATH_MODE=`awk "\\$1 == \"multipath\" \
|
||||
{print \\$2; exit}" $CONFIG`
|
||||
fi
|
||||
|
||||
# Use first running component device if we're handling a dm-mpath device
|
||||
if [ "$MULTIPATH_MODE" = "yes" ] ; then
|
||||
# If udev didn't tell us the UUID via DM_NAME, check /dev/mapper
|
||||
if [ -z "$DM_NAME" ] ; then
|
||||
DM_NAME=`ls -l --full-time /dev/mapper |
|
||||
awk "/\/$DEV$/{print \\$9}"`
|
||||
fi
|
||||
|
||||
# For raw disks udev exports DEVTYPE=partition when
|
||||
# handling partitions, and the rules can be written to
|
||||
# take advantage of this to append a -part suffix. For
|
||||
# dm devices we get DEVTYPE=disk even for partitions so
|
||||
# we have to append the -part suffix directly in the
|
||||
# helper.
|
||||
if [ "$DEVTYPE" != "partition" ] ; then
|
||||
PART=`echo $DM_NAME | awk -Fp '/p/{print "-part"$2}'`
|
||||
fi
|
||||
|
||||
# Strip off partition information.
|
||||
DM_NAME=`echo $DM_NAME | sed 's/p[0-9][0-9]*$//'`
|
||||
if [ -z "$DM_NAME" ] ; then
|
||||
return
|
||||
fi
|
||||
|
||||
# Get the raw scsi device name from multipath -ll. Strip off
|
||||
# leading pipe symbols to make field numbering consistent.
|
||||
DEV=`multipath -ll $DM_NAME |
|
||||
awk '/running/{gsub("^[|]"," "); print $3 ; exit}'`
|
||||
if [ -z "$DEV" ] ; then
|
||||
return
|
||||
fi
|
||||
fi
|
||||
|
||||
if echo $DEV | grep -q ^/devices/ ; then
|
||||
sys_path=$DEV
|
||||
else
|
||||
sys_path=`udevadm info -q path -p /sys/block/$DEV 2>/dev/null`
|
||||
fi
|
||||
|
||||
# expect sys_path like this, for example:
|
||||
# /devices/pci0000:00/0000:00:0b.0/0000:09:00.0/0000:0a:05.0/0000:0c:00.0/host3/target3:1:0/3:1:0:21/block/sdv
|
||||
|
||||
# Use positional parameters as an ad-hoc array
|
||||
set -- $(echo "$sys_path" | tr / ' ')
|
||||
num_dirs=$#
|
||||
scsi_host_dir="/sys"
|
||||
|
||||
# Get path up to /sys/.../hostX
|
||||
i=1
|
||||
while [ $i -le $num_dirs ] ; do
|
||||
d=$(eval echo \${$i})
|
||||
scsi_host_dir="$scsi_host_dir/$d"
|
||||
echo $d | grep -q -E '^host[0-9]+$' && break
|
||||
i=$(($i + 1))
|
||||
done
|
||||
|
||||
if [ $i = $num_dirs ] ; then
|
||||
return
|
||||
fi
|
||||
|
||||
PCI_ID=$(eval echo \${$(($i -1))} | awk -F: '{print $2":"$3}')
|
||||
|
||||
# In scsi mode, the directory two levels beneath
|
||||
# /sys/.../hostX reveals the port and slot.
|
||||
port_dir=$scsi_host_dir
|
||||
j=$(($i + 2))
|
||||
|
||||
i=$(($i + 1))
|
||||
while [ $i -le $j ] ; do
|
||||
port_dir="$port_dir/$(eval echo \${$i})"
|
||||
i=$(($i + 1))
|
||||
done
|
||||
|
||||
set -- $(echo $port_dir | sed -e 's/^.*:\([^:]*\):\([^:]*\)$/\1 \2/')
|
||||
PORT=$1
|
||||
SLOT=$(($2 + $FIRST_BAY_NUMBER))
|
||||
|
||||
if [ -z "$SLOT" ] ; then
|
||||
return
|
||||
fi
|
||||
|
||||
CHAN=`map_channel $PCI_ID $PORT`
|
||||
SLOT=`map_slot $SLOT $CHAN`
|
||||
if [ -z "$CHAN" ] ; then
|
||||
return
|
||||
fi
|
||||
echo ${CHAN}${SLOT}${PART}
|
||||
}
|
||||
|
||||
# Figure out the name for the enclosure symlink
|
||||
enclosure_handler () {
|
||||
# We get all the info we need from udev's DEVPATH variable:
|
||||
#
|
||||
# DEVPATH=/sys/devices/pci0000:00/0000:00:03.0/0000:05:00.0/host0/subsystem/devices/0:0:0:0/scsi_generic/sg0
|
||||
|
||||
# Get the enclosure ID ("0:0:0:0")
|
||||
ENC=$(basename $(readlink -m "/sys/$DEVPATH/../.."))
|
||||
if [ ! -d /sys/class/enclosure/$ENC ] ; then
|
||||
# Not an enclosure, bail out
|
||||
return
|
||||
fi
|
||||
|
||||
# Get the long sysfs device path to our enclosure. Looks like:
|
||||
# /devices/pci0000:00/0000:00:03.0/0000:05:00.0/host0/port-0:0/ ... /enclosure/0:0:0:0
|
||||
|
||||
ENC_DEVICE=$(readlink /sys/class/enclosure/$ENC)
|
||||
|
||||
# Grab the full path to the hosts port dir:
|
||||
# /devices/pci0000:00/0000:00:03.0/0000:05:00.0/host0/port-0:0
|
||||
PORT_DIR=$(echo $ENC_DEVICE | grep -Eo '.+host[0-9]+/port-[0-9]+:[0-9]+')
|
||||
|
||||
# Get the port number
|
||||
PORT_ID=$(echo $PORT_DIR | grep -Eo "[0-9]+$")
|
||||
|
||||
# The PCI directory is two directories up from the port directory
|
||||
# /sys/devices/pci0000:00/0000:00:03.0/0000:05:00.0
|
||||
PCI_ID_LONG=$(basename $(readlink -m "/sys/$PORT_DIR/../.."))
|
||||
|
||||
# Strip down the PCI address from 0000:05:00.0 to 05:00.0
|
||||
PCI_ID=$(echo "$PCI_ID_LONG" | sed -r 's/^[0-9]+://g')
|
||||
|
||||
# Name our device according to vdev_id.conf (like "L0" or "U1").
|
||||
NAME=$(awk "/channel/{if (\$1 == \"channel\" && \$2 == \"$PCI_ID\" && \
|
||||
\$3 == \"$PORT_ID\") {print \$4int(count[\$4])}; count[\$4]++}" $CONFIG)
|
||||
|
||||
echo "${NAME}"
|
||||
}
|
||||
|
||||
alias_handler () {
|
||||
# Special handling is needed to correctly append a -part suffix
|
||||
# to partitions of device mapper devices. The DEVTYPE attribute
|
||||
# is normally set to "disk" instead of "partition" in this case,
|
||||
# so the udev rules won't handle that for us as they do for
|
||||
# "plain" block devices.
|
||||
#
|
||||
# For example, we may have the following links for a device and its
|
||||
# partitions,
|
||||
#
|
||||
# /dev/disk/by-id/dm-name-isw_dibgbfcije_ARRAY0 -> ../../dm-0
|
||||
# /dev/disk/by-id/dm-name-isw_dibgbfcije_ARRAY0p1 -> ../../dm-1
|
||||
# /dev/disk/by-id/dm-name-isw_dibgbfcije_ARRAY0p2 -> ../../dm-3
|
||||
#
|
||||
# and the following alias in vdev_id.conf.
|
||||
#
|
||||
# alias A0 dm-name-isw_dibgbfcije_ARRAY0
|
||||
#
|
||||
# The desired outcome is for the following links to be created
|
||||
# without having explicitly defined aliases for the partitions.
|
||||
#
|
||||
# /dev/disk/by-vdev/A0 -> ../../dm-0
|
||||
# /dev/disk/by-vdev/A0-part1 -> ../../dm-1
|
||||
# /dev/disk/by-vdev/A0-part2 -> ../../dm-3
|
||||
#
|
||||
# Warning: The following grep pattern will misidentify whole-disk
|
||||
# devices whose names end with 'p' followed by a string of
|
||||
# digits as partitions, causing alias creation to fail. This
|
||||
# ambiguity seems unavoidable, so devices using this facility
|
||||
# must not use such names.
|
||||
DM_PART=
|
||||
if echo $DM_NAME | grep -q -E 'p[0-9][0-9]*$' ; then
|
||||
if [ "$DEVTYPE" != "partition" ] ; then
|
||||
DM_PART=`echo $DM_NAME | awk -Fp '/p/{print "-part"$2}'`
|
||||
fi
|
||||
fi
|
||||
|
||||
# DEVLINKS attribute must have been populated by already-run udev rules.
|
||||
for link in $DEVLINKS ; do
|
||||
# Remove partition information to match key of top-level device.
|
||||
if [ -n "$DM_PART" ] ; then
|
||||
link=`echo $link | sed 's/p[0-9][0-9]*$//'`
|
||||
fi
|
||||
# Check both the fully qualified and the base name of link.
|
||||
for l in $link `basename $link` ; do
|
||||
alias=`awk "\\$1 == \"alias\" && \\$3 == \"${l}\" \
|
||||
{ print \\$2; exit }" $CONFIG`
|
||||
if [ -n "$alias" ] ; then
|
||||
echo ${alias}${DM_PART}
|
||||
return
|
||||
fi
|
||||
done
|
||||
done
|
||||
}
|
||||
|
||||
while getopts 'c:d:eg:mp:h' OPTION; do
|
||||
case ${OPTION} in
|
||||
c)
|
||||
CONFIG=${OPTARG}
|
||||
;;
|
||||
d)
|
||||
DEV=${OPTARG}
|
||||
;;
|
||||
e)
|
||||
# When udev sees a scsi_generic device, it calls this script with -e to
|
||||
# create the enclosure device symlinks only. We also need
|
||||
# "enclosure_symlinks yes" set in vdev_id.config to actually create the
|
||||
# symlink.
|
||||
ENCLOSURE_MODE=$(awk '{if ($1 == "enclosure_symlinks") print $2}' $CONFIG)
|
||||
if [ "$ENCLOSURE_MODE" != "yes" ] ; then
|
||||
exit 0
|
||||
fi
|
||||
;;
|
||||
g)
|
||||
TOPOLOGY=$OPTARG
|
||||
;;
|
||||
p)
|
||||
PHYS_PER_PORT=${OPTARG}
|
||||
;;
|
||||
m)
|
||||
MULTIPATH_MODE=yes
|
||||
;;
|
||||
h)
|
||||
usage
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ ! -r $CONFIG ] ; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ -z "$DEV" ] && [ -z "$ENCLOSURE_MODE" ] ; then
|
||||
echo "Error: missing required option -d"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$TOPOLOGY" ] ; then
|
||||
TOPOLOGY=`awk "\\$1 == \"topology\" {print \\$2; exit}" $CONFIG`
|
||||
fi
|
||||
|
||||
if [ -z "$BAY" ] ; then
|
||||
BAY=`awk "\\$1 == \"slot\" {print \\$2; exit}" $CONFIG`
|
||||
fi
|
||||
|
||||
TOPOLOGY=${TOPOLOGY:-sas_direct}
|
||||
|
||||
# Should we create /dev/by-enclosure symlinks?
|
||||
if [ "$ENCLOSURE_MODE" = "yes" ] && [ "$TOPOLOGY" = "sas_direct" ] ; then
|
||||
ID_ENCLOSURE=$(enclosure_handler)
|
||||
if [ -z "$ID_ENCLOSURE" ] ; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Just create the symlinks to the enclosure devices and then exit.
|
||||
ENCLOSURE_PREFIX=$(awk '/enclosure_symlinks_prefix/{print $2}' $CONFIG)
|
||||
if [ -z "$ENCLOSURE_PREFIX" ] ; then
|
||||
ENCLOSURE_PREFIX="enc"
|
||||
fi
|
||||
echo "ID_ENCLOSURE=$ID_ENCLOSURE"
|
||||
echo "ID_ENCLOSURE_PATH=by-enclosure/$ENCLOSURE_PREFIX-$ID_ENCLOSURE"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# First check if an alias was defined for this device.
|
||||
ID_VDEV=`alias_handler`
|
||||
|
||||
if [ -z "$ID_VDEV" ] ; then
|
||||
BAY=${BAY:-bay}
|
||||
case $TOPOLOGY in
|
||||
sas_direct|sas_switch)
|
||||
ID_VDEV=`sas_handler`
|
||||
;;
|
||||
scsi)
|
||||
ID_VDEV=`scsi_handler`
|
||||
;;
|
||||
*)
|
||||
echo "Error: unknown topology $TOPOLOGY"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
if [ -n "$ID_VDEV" ] ; then
|
||||
echo "ID_VDEV=${ID_VDEV}"
|
||||
echo "ID_VDEV_PATH=disk/by-vdev/${ID_VDEV}"
|
||||
fi
|
1
sys/contrib/openzfs/cmd/zdb/.gitignore
vendored
Normal file
1
sys/contrib/openzfs/cmd/zdb/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/zdb
|
16
sys/contrib/openzfs/cmd/zdb/Makefile.am
Normal file
16
sys/contrib/openzfs/cmd/zdb/Makefile.am
Normal file
@ -0,0 +1,16 @@
|
||||
include $(top_srcdir)/config/Rules.am
|
||||
|
||||
# Unconditionally enable debugging for zdb
|
||||
AM_CPPFLAGS += -DDEBUG -UNDEBUG -DZFS_DEBUG
|
||||
|
||||
sbin_PROGRAMS = zdb
|
||||
|
||||
zdb_SOURCES = \
|
||||
zdb.c \
|
||||
zdb_il.c \
|
||||
zdb.h
|
||||
|
||||
zdb_LDADD = \
|
||||
$(abs_top_builddir)/lib/libzpool/libzpool.la \
|
||||
$(abs_top_builddir)/lib/libzfs_core/libzfs_core.la \
|
||||
$(abs_top_builddir)/lib/libnvpair/libnvpair.la
|
8606
sys/contrib/openzfs/cmd/zdb/zdb.c
Normal file
8606
sys/contrib/openzfs/cmd/zdb/zdb.c
Normal file
File diff suppressed because it is too large
Load Diff
33
sys/contrib/openzfs/cmd/zdb/zdb.h
Normal file
33
sys/contrib/openzfs/cmd/zdb/zdb.h
Normal file
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License (the "License").
|
||||
* You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||
* or http://www.opensolaris.org/os/licensing.
|
||||
* See the License for the specific language governing permissions
|
||||
* and limitations under the License.
|
||||
*
|
||||
* When distributing Covered Code, include this CDDL HEADER in each
|
||||
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||
* If applicable, add the following below this CDDL HEADER, with the
|
||||
* fields enclosed by brackets "[]" replaced with your own identifying
|
||||
* information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
*
|
||||
* CDDL HEADER END
|
||||
*/
|
||||
/*
|
||||
* Copyright 2017 Spectra Logic Corp Inc. All rights reserved.
|
||||
* Use is subject to license terms.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef _ZDB_H
|
||||
#define _ZDB_H
|
||||
|
||||
void dump_intent_log(zilog_t *);
|
||||
extern uint8_t dump_opt[256];
|
||||
|
||||
#endif /* _ZDB_H */
|
431
sys/contrib/openzfs/cmd/zdb/zdb_il.c
Normal file
431
sys/contrib/openzfs/cmd/zdb/zdb_il.c
Normal file
@ -0,0 +1,431 @@
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License (the "License").
|
||||
* You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||
* or http://www.opensolaris.org/os/licensing.
|
||||
* See the License for the specific language governing permissions
|
||||
* and limitations under the License.
|
||||
*
|
||||
* When distributing Covered Code, include this CDDL HEADER in each
|
||||
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||
* If applicable, add the following below this CDDL HEADER, with the
|
||||
* fields enclosed by brackets "[]" replaced with your own identifying
|
||||
* information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
*
|
||||
* CDDL HEADER END
|
||||
*/
|
||||
/*
|
||||
* Copyright 2009 Sun Microsystems, Inc. All rights reserved.
|
||||
* Copyright (c) 2012 Cyril Plisko. All rights reserved.
|
||||
* Use is subject to license terms.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (c) 2013, 2017 by Delphix. All rights reserved.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Print intent log header and statistics.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
#include <sys/zfs_context.h>
|
||||
#include <sys/spa.h>
|
||||
#include <sys/dmu.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/resource.h>
|
||||
#include <sys/zil.h>
|
||||
#include <sys/zil_impl.h>
|
||||
#include <sys/spa_impl.h>
|
||||
#include <sys/abd.h>
|
||||
|
||||
#include "zdb.h"
|
||||
|
||||
extern uint8_t dump_opt[256];
|
||||
|
||||
static char tab_prefix[4] = "\t\t\t";
|
||||
|
||||
static void
|
||||
print_log_bp(const blkptr_t *bp, const char *prefix)
|
||||
{
|
||||
char blkbuf[BP_SPRINTF_LEN];
|
||||
|
||||
snprintf_blkptr(blkbuf, sizeof (blkbuf), bp);
|
||||
(void) printf("%s%s\n", prefix, blkbuf);
|
||||
}
|
||||
|
||||
/* ARGSUSED */
|
||||
static void
|
||||
zil_prt_rec_create(zilog_t *zilog, int txtype, void *arg)
|
||||
{
|
||||
lr_create_t *lr = arg;
|
||||
time_t crtime = lr->lr_crtime[0];
|
||||
char *name, *link;
|
||||
lr_attr_t *lrattr;
|
||||
|
||||
name = (char *)(lr + 1);
|
||||
|
||||
if (lr->lr_common.lrc_txtype == TX_CREATE_ATTR ||
|
||||
lr->lr_common.lrc_txtype == TX_MKDIR_ATTR) {
|
||||
lrattr = (lr_attr_t *)(lr + 1);
|
||||
name += ZIL_XVAT_SIZE(lrattr->lr_attr_masksize);
|
||||
}
|
||||
|
||||
if (txtype == TX_SYMLINK) {
|
||||
link = name + strlen(name) + 1;
|
||||
(void) printf("%s%s -> %s\n", tab_prefix, name, link);
|
||||
} else if (txtype != TX_MKXATTR) {
|
||||
(void) printf("%s%s\n", tab_prefix, name);
|
||||
}
|
||||
|
||||
(void) printf("%s%s", tab_prefix, ctime(&crtime));
|
||||
(void) printf("%sdoid %llu, foid %llu, slots %llu, mode %llo\n",
|
||||
tab_prefix, (u_longlong_t)lr->lr_doid,
|
||||
(u_longlong_t)LR_FOID_GET_OBJ(lr->lr_foid),
|
||||
(u_longlong_t)LR_FOID_GET_SLOTS(lr->lr_foid),
|
||||
(longlong_t)lr->lr_mode);
|
||||
(void) printf("%suid %llu, gid %llu, gen %llu, rdev 0x%llx\n",
|
||||
tab_prefix,
|
||||
(u_longlong_t)lr->lr_uid, (u_longlong_t)lr->lr_gid,
|
||||
(u_longlong_t)lr->lr_gen, (u_longlong_t)lr->lr_rdev);
|
||||
}
|
||||
|
||||
/* ARGSUSED */
|
||||
static void
|
||||
zil_prt_rec_remove(zilog_t *zilog, int txtype, void *arg)
|
||||
{
|
||||
lr_remove_t *lr = arg;
|
||||
|
||||
(void) printf("%sdoid %llu, name %s\n", tab_prefix,
|
||||
(u_longlong_t)lr->lr_doid, (char *)(lr + 1));
|
||||
}
|
||||
|
||||
/* ARGSUSED */
|
||||
static void
|
||||
zil_prt_rec_link(zilog_t *zilog, int txtype, void *arg)
|
||||
{
|
||||
lr_link_t *lr = arg;
|
||||
|
||||
(void) printf("%sdoid %llu, link_obj %llu, name %s\n", tab_prefix,
|
||||
(u_longlong_t)lr->lr_doid, (u_longlong_t)lr->lr_link_obj,
|
||||
(char *)(lr + 1));
|
||||
}
|
||||
|
||||
/* ARGSUSED */
|
||||
static void
|
||||
zil_prt_rec_rename(zilog_t *zilog, int txtype, void *arg)
|
||||
{
|
||||
lr_rename_t *lr = arg;
|
||||
char *snm = (char *)(lr + 1);
|
||||
char *tnm = snm + strlen(snm) + 1;
|
||||
|
||||
(void) printf("%ssdoid %llu, tdoid %llu\n", tab_prefix,
|
||||
(u_longlong_t)lr->lr_sdoid, (u_longlong_t)lr->lr_tdoid);
|
||||
(void) printf("%ssrc %s tgt %s\n", tab_prefix, snm, tnm);
|
||||
}
|
||||
|
||||
/* ARGSUSED */
|
||||
static int
|
||||
zil_prt_rec_write_cb(void *data, size_t len, void *unused)
|
||||
{
|
||||
char *cdata = data;
|
||||
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
if (isprint(*cdata))
|
||||
(void) printf("%c ", *cdata);
|
||||
else
|
||||
(void) printf("%2X", *cdata);
|
||||
cdata++;
|
||||
}
|
||||
return (0);
|
||||
}
|
||||
|
||||
/* ARGSUSED */
|
||||
static void
|
||||
zil_prt_rec_write(zilog_t *zilog, int txtype, void *arg)
|
||||
{
|
||||
lr_write_t *lr = arg;
|
||||
abd_t *data;
|
||||
blkptr_t *bp = &lr->lr_blkptr;
|
||||
zbookmark_phys_t zb;
|
||||
int verbose = MAX(dump_opt['d'], dump_opt['i']);
|
||||
int error;
|
||||
|
||||
(void) printf("%sfoid %llu, offset %llx, length %llx\n", tab_prefix,
|
||||
(u_longlong_t)lr->lr_foid, (u_longlong_t)lr->lr_offset,
|
||||
(u_longlong_t)lr->lr_length);
|
||||
|
||||
if (txtype == TX_WRITE2 || verbose < 5)
|
||||
return;
|
||||
|
||||
if (lr->lr_common.lrc_reclen == sizeof (lr_write_t)) {
|
||||
(void) printf("%shas blkptr, %s\n", tab_prefix,
|
||||
!BP_IS_HOLE(bp) &&
|
||||
bp->blk_birth >= spa_min_claim_txg(zilog->zl_spa) ?
|
||||
"will claim" : "won't claim");
|
||||
print_log_bp(bp, tab_prefix);
|
||||
|
||||
if (BP_IS_HOLE(bp)) {
|
||||
(void) printf("\t\t\tLSIZE 0x%llx\n",
|
||||
(u_longlong_t)BP_GET_LSIZE(bp));
|
||||
(void) printf("%s<hole>\n", tab_prefix);
|
||||
return;
|
||||
}
|
||||
if (bp->blk_birth < zilog->zl_header->zh_claim_txg) {
|
||||
(void) printf("%s<block already committed>\n",
|
||||
tab_prefix);
|
||||
return;
|
||||
}
|
||||
|
||||
SET_BOOKMARK(&zb, dmu_objset_id(zilog->zl_os),
|
||||
lr->lr_foid, ZB_ZIL_LEVEL,
|
||||
lr->lr_offset / BP_GET_LSIZE(bp));
|
||||
|
||||
data = abd_alloc(BP_GET_LSIZE(bp), B_FALSE);
|
||||
error = zio_wait(zio_read(NULL, zilog->zl_spa,
|
||||
bp, data, BP_GET_LSIZE(bp), NULL, NULL,
|
||||
ZIO_PRIORITY_SYNC_READ, ZIO_FLAG_CANFAIL, &zb));
|
||||
if (error)
|
||||
goto out;
|
||||
} else {
|
||||
/* data is stored after the end of the lr_write record */
|
||||
data = abd_alloc(lr->lr_length, B_FALSE);
|
||||
abd_copy_from_buf(data, lr + 1, lr->lr_length);
|
||||
}
|
||||
|
||||
(void) printf("%s", tab_prefix);
|
||||
(void) abd_iterate_func(data,
|
||||
0, MIN(lr->lr_length, (verbose < 6 ? 20 : SPA_MAXBLOCKSIZE)),
|
||||
zil_prt_rec_write_cb, NULL);
|
||||
(void) printf("\n");
|
||||
|
||||
out:
|
||||
abd_free(data);
|
||||
}
|
||||
|
||||
/* ARGSUSED */
|
||||
static void
|
||||
zil_prt_rec_truncate(zilog_t *zilog, int txtype, void *arg)
|
||||
{
|
||||
lr_truncate_t *lr = arg;
|
||||
|
||||
(void) printf("%sfoid %llu, offset 0x%llx, length 0x%llx\n", tab_prefix,
|
||||
(u_longlong_t)lr->lr_foid, (longlong_t)lr->lr_offset,
|
||||
(u_longlong_t)lr->lr_length);
|
||||
}
|
||||
|
||||
/* ARGSUSED */
|
||||
static void
|
||||
zil_prt_rec_setattr(zilog_t *zilog, int txtype, void *arg)
|
||||
{
|
||||
lr_setattr_t *lr = arg;
|
||||
time_t atime = (time_t)lr->lr_atime[0];
|
||||
time_t mtime = (time_t)lr->lr_mtime[0];
|
||||
|
||||
(void) printf("%sfoid %llu, mask 0x%llx\n", tab_prefix,
|
||||
(u_longlong_t)lr->lr_foid, (u_longlong_t)lr->lr_mask);
|
||||
|
||||
if (lr->lr_mask & AT_MODE) {
|
||||
(void) printf("%sAT_MODE %llo\n", tab_prefix,
|
||||
(longlong_t)lr->lr_mode);
|
||||
}
|
||||
|
||||
if (lr->lr_mask & AT_UID) {
|
||||
(void) printf("%sAT_UID %llu\n", tab_prefix,
|
||||
(u_longlong_t)lr->lr_uid);
|
||||
}
|
||||
|
||||
if (lr->lr_mask & AT_GID) {
|
||||
(void) printf("%sAT_GID %llu\n", tab_prefix,
|
||||
(u_longlong_t)lr->lr_gid);
|
||||
}
|
||||
|
||||
if (lr->lr_mask & AT_SIZE) {
|
||||
(void) printf("%sAT_SIZE %llu\n", tab_prefix,
|
||||
(u_longlong_t)lr->lr_size);
|
||||
}
|
||||
|
||||
if (lr->lr_mask & AT_ATIME) {
|
||||
(void) printf("%sAT_ATIME %llu.%09llu %s", tab_prefix,
|
||||
(u_longlong_t)lr->lr_atime[0],
|
||||
(u_longlong_t)lr->lr_atime[1],
|
||||
ctime(&atime));
|
||||
}
|
||||
|
||||
if (lr->lr_mask & AT_MTIME) {
|
||||
(void) printf("%sAT_MTIME %llu.%09llu %s", tab_prefix,
|
||||
(u_longlong_t)lr->lr_mtime[0],
|
||||
(u_longlong_t)lr->lr_mtime[1],
|
||||
ctime(&mtime));
|
||||
}
|
||||
}
|
||||
|
||||
/* ARGSUSED */
|
||||
static void
|
||||
zil_prt_rec_acl(zilog_t *zilog, int txtype, void *arg)
|
||||
{
|
||||
lr_acl_t *lr = arg;
|
||||
|
||||
(void) printf("%sfoid %llu, aclcnt %llu\n", tab_prefix,
|
||||
(u_longlong_t)lr->lr_foid, (u_longlong_t)lr->lr_aclcnt);
|
||||
}
|
||||
|
||||
typedef void (*zil_prt_rec_func_t)(zilog_t *, int, void *);
|
||||
typedef struct zil_rec_info {
|
||||
zil_prt_rec_func_t zri_print;
|
||||
const char *zri_name;
|
||||
uint64_t zri_count;
|
||||
} zil_rec_info_t;
|
||||
|
||||
static zil_rec_info_t zil_rec_info[TX_MAX_TYPE] = {
|
||||
{.zri_print = NULL, .zri_name = "Total "},
|
||||
{.zri_print = zil_prt_rec_create, .zri_name = "TX_CREATE "},
|
||||
{.zri_print = zil_prt_rec_create, .zri_name = "TX_MKDIR "},
|
||||
{.zri_print = zil_prt_rec_create, .zri_name = "TX_MKXATTR "},
|
||||
{.zri_print = zil_prt_rec_create, .zri_name = "TX_SYMLINK "},
|
||||
{.zri_print = zil_prt_rec_remove, .zri_name = "TX_REMOVE "},
|
||||
{.zri_print = zil_prt_rec_remove, .zri_name = "TX_RMDIR "},
|
||||
{.zri_print = zil_prt_rec_link, .zri_name = "TX_LINK "},
|
||||
{.zri_print = zil_prt_rec_rename, .zri_name = "TX_RENAME "},
|
||||
{.zri_print = zil_prt_rec_write, .zri_name = "TX_WRITE "},
|
||||
{.zri_print = zil_prt_rec_truncate, .zri_name = "TX_TRUNCATE "},
|
||||
{.zri_print = zil_prt_rec_setattr, .zri_name = "TX_SETATTR "},
|
||||
{.zri_print = zil_prt_rec_acl, .zri_name = "TX_ACL_V0 "},
|
||||
{.zri_print = zil_prt_rec_acl, .zri_name = "TX_ACL_ACL "},
|
||||
{.zri_print = zil_prt_rec_create, .zri_name = "TX_CREATE_ACL "},
|
||||
{.zri_print = zil_prt_rec_create, .zri_name = "TX_CREATE_ATTR "},
|
||||
{.zri_print = zil_prt_rec_create, .zri_name = "TX_CREATE_ACL_ATTR "},
|
||||
{.zri_print = zil_prt_rec_create, .zri_name = "TX_MKDIR_ACL "},
|
||||
{.zri_print = zil_prt_rec_create, .zri_name = "TX_MKDIR_ATTR "},
|
||||
{.zri_print = zil_prt_rec_create, .zri_name = "TX_MKDIR_ACL_ATTR "},
|
||||
{.zri_print = zil_prt_rec_write, .zri_name = "TX_WRITE2 "},
|
||||
};
|
||||
|
||||
/* ARGSUSED */
|
||||
static int
|
||||
print_log_record(zilog_t *zilog, lr_t *lr, void *arg, uint64_t claim_txg)
|
||||
{
|
||||
int txtype;
|
||||
int verbose = MAX(dump_opt['d'], dump_opt['i']);
|
||||
|
||||
/* reduce size of txtype to strip off TX_CI bit */
|
||||
txtype = lr->lrc_txtype;
|
||||
|
||||
ASSERT(txtype != 0 && (uint_t)txtype < TX_MAX_TYPE);
|
||||
ASSERT(lr->lrc_txg);
|
||||
|
||||
(void) printf("\t\t%s%s len %6llu, txg %llu, seq %llu\n",
|
||||
(lr->lrc_txtype & TX_CI) ? "CI-" : "",
|
||||
zil_rec_info[txtype].zri_name,
|
||||
(u_longlong_t)lr->lrc_reclen,
|
||||
(u_longlong_t)lr->lrc_txg,
|
||||
(u_longlong_t)lr->lrc_seq);
|
||||
|
||||
if (txtype && verbose >= 3) {
|
||||
if (!zilog->zl_os->os_encrypted) {
|
||||
zil_rec_info[txtype].zri_print(zilog, txtype, lr);
|
||||
} else {
|
||||
(void) printf("%s(encrypted)\n", tab_prefix);
|
||||
}
|
||||
}
|
||||
|
||||
zil_rec_info[txtype].zri_count++;
|
||||
zil_rec_info[0].zri_count++;
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
/* ARGSUSED */
|
||||
static int
|
||||
print_log_block(zilog_t *zilog, blkptr_t *bp, void *arg, uint64_t claim_txg)
|
||||
{
|
||||
char blkbuf[BP_SPRINTF_LEN + 10];
|
||||
int verbose = MAX(dump_opt['d'], dump_opt['i']);
|
||||
const char *claim;
|
||||
|
||||
if (verbose <= 3)
|
||||
return (0);
|
||||
|
||||
if (verbose >= 5) {
|
||||
(void) strcpy(blkbuf, ", ");
|
||||
snprintf_blkptr(blkbuf + strlen(blkbuf),
|
||||
sizeof (blkbuf) - strlen(blkbuf), bp);
|
||||
} else {
|
||||
blkbuf[0] = '\0';
|
||||
}
|
||||
|
||||
if (claim_txg != 0)
|
||||
claim = "already claimed";
|
||||
else if (bp->blk_birth >= spa_min_claim_txg(zilog->zl_spa))
|
||||
claim = "will claim";
|
||||
else
|
||||
claim = "won't claim";
|
||||
|
||||
(void) printf("\tBlock seqno %llu, %s%s\n",
|
||||
(u_longlong_t)bp->blk_cksum.zc_word[ZIL_ZC_SEQ], claim, blkbuf);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
static void
|
||||
print_log_stats(int verbose)
|
||||
{
|
||||
unsigned i, w, p10;
|
||||
|
||||
if (verbose > 3)
|
||||
(void) printf("\n");
|
||||
|
||||
if (zil_rec_info[0].zri_count == 0)
|
||||
return;
|
||||
|
||||
for (w = 1, p10 = 10; zil_rec_info[0].zri_count >= p10; p10 *= 10)
|
||||
w++;
|
||||
|
||||
for (i = 0; i < TX_MAX_TYPE; i++)
|
||||
if (zil_rec_info[i].zri_count || verbose >= 3)
|
||||
(void) printf("\t\t%s %*llu\n",
|
||||
zil_rec_info[i].zri_name, w,
|
||||
(u_longlong_t)zil_rec_info[i].zri_count);
|
||||
(void) printf("\n");
|
||||
}
|
||||
|
||||
/* ARGSUSED */
|
||||
void
|
||||
dump_intent_log(zilog_t *zilog)
|
||||
{
|
||||
const zil_header_t *zh = zilog->zl_header;
|
||||
int verbose = MAX(dump_opt['d'], dump_opt['i']);
|
||||
int i;
|
||||
|
||||
if (BP_IS_HOLE(&zh->zh_log) || verbose < 1)
|
||||
return;
|
||||
|
||||
(void) printf("\n ZIL header: claim_txg %llu, "
|
||||
"claim_blk_seq %llu, claim_lr_seq %llu",
|
||||
(u_longlong_t)zh->zh_claim_txg,
|
||||
(u_longlong_t)zh->zh_claim_blk_seq,
|
||||
(u_longlong_t)zh->zh_claim_lr_seq);
|
||||
(void) printf(" replay_seq %llu, flags 0x%llx\n",
|
||||
(u_longlong_t)zh->zh_replay_seq, (u_longlong_t)zh->zh_flags);
|
||||
|
||||
for (i = 0; i < TX_MAX_TYPE; i++)
|
||||
zil_rec_info[i].zri_count = 0;
|
||||
|
||||
/* see comment in zil_claim() or zil_check_log_chain() */
|
||||
if (zilog->zl_spa->spa_uberblock.ub_checkpoint_txg != 0 &&
|
||||
zh->zh_claim_txg == 0)
|
||||
return;
|
||||
|
||||
if (verbose >= 2) {
|
||||
(void) printf("\n");
|
||||
(void) zil_parse(zilog, print_log_block, print_log_record, NULL,
|
||||
zh->zh_claim_txg, B_FALSE);
|
||||
print_log_stats(verbose);
|
||||
}
|
||||
}
|
1
sys/contrib/openzfs/cmd/zed/.gitignore
vendored
Normal file
1
sys/contrib/openzfs/cmd/zed/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/zed
|
49
sys/contrib/openzfs/cmd/zed/Makefile.am
Normal file
49
sys/contrib/openzfs/cmd/zed/Makefile.am
Normal file
@ -0,0 +1,49 @@
|
||||
include $(top_srcdir)/config/Rules.am
|
||||
|
||||
AM_CFLAGS += $(LIBUDEV_CFLAGS) $(LIBUUID_CFLAGS)
|
||||
|
||||
SUBDIRS = zed.d
|
||||
|
||||
sbin_PROGRAMS = zed
|
||||
|
||||
ZED_SRC = \
|
||||
zed.c \
|
||||
zed.h \
|
||||
zed_conf.c \
|
||||
zed_conf.h \
|
||||
zed_disk_event.c \
|
||||
zed_disk_event.h \
|
||||
zed_event.c \
|
||||
zed_event.h \
|
||||
zed_exec.c \
|
||||
zed_exec.h \
|
||||
zed_file.c \
|
||||
zed_file.h \
|
||||
zed_log.c \
|
||||
zed_log.h \
|
||||
zed_strings.c \
|
||||
zed_strings.h
|
||||
|
||||
FMA_SRC = \
|
||||
agents/zfs_agents.c \
|
||||
agents/zfs_agents.h \
|
||||
agents/zfs_diagnosis.c \
|
||||
agents/zfs_mod.c \
|
||||
agents/zfs_retire.c \
|
||||
agents/fmd_api.c \
|
||||
agents/fmd_api.h \
|
||||
agents/fmd_serd.c \
|
||||
agents/fmd_serd.h
|
||||
|
||||
zed_SOURCES = $(ZED_SRC) $(FMA_SRC)
|
||||
|
||||
zed_LDADD = \
|
||||
$(abs_top_builddir)/lib/libzfs/libzfs.la \
|
||||
$(abs_top_builddir)/lib/libzfs_core/libzfs_core.la \
|
||||
$(abs_top_builddir)/lib/libnvpair/libnvpair.la \
|
||||
$(abs_top_builddir)/lib/libuutil/libuutil.la
|
||||
|
||||
zed_LDADD += -lrt $(LIBUDEV_LIBS) $(LIBUUID_LIBS)
|
||||
zed_LDFLAGS = -pthread
|
||||
|
||||
EXTRA_DIST = agents/README.md
|
112
sys/contrib/openzfs/cmd/zed/agents/README.md
Normal file
112
sys/contrib/openzfs/cmd/zed/agents/README.md
Normal file
@ -0,0 +1,112 @@
|
||||
## Fault Management Logic for ZED ##
|
||||
|
||||
The integration of Fault Management Daemon (FMD) logic from illumos
|
||||
is being deployed in three phases. This logic is encapsulated in
|
||||
several software modules inside ZED.
|
||||
|
||||
### ZED+FM Phase 1 ###
|
||||
|
||||
All the phase 1 work is in current Master branch. Phase I work includes:
|
||||
|
||||
* Add new paths to the persistent VDEV label for device matching.
|
||||
* Add a disk monitor for generating _disk-add_ and _disk-change_ events.
|
||||
* Add support for automated VDEV auto-online, auto-replace and auto-expand.
|
||||
* Expand the statechange event to include all VDEV state transitions.
|
||||
|
||||
### ZED+FM Phase 2 (WIP) ###
|
||||
|
||||
The phase 2 work primarily entails the _Diagnosis Engine_ and the
|
||||
_Retire Agent_ modules. It also includes infrastructure to support a
|
||||
crude FMD environment to host these modules. For additional
|
||||
information see the **FMD Components in ZED** and **Implementation
|
||||
Notes** sections below.
|
||||
|
||||
### ZED+FM Phase 3 ###
|
||||
|
||||
Future work will add additional functionality and will likely include:
|
||||
|
||||
* Add FMD module garbage collection (periodically call `fmd_module_gc()`).
|
||||
* Add real module property retrieval (currently hard-coded in accessors).
|
||||
* Additional diagnosis telemetry (like latency outliers and SMART data).
|
||||
* Export FMD module statistics.
|
||||
* Zedlet parallel execution and resiliency (add watchdog).
|
||||
|
||||
### ZFS Fault Management Overview ###
|
||||
|
||||
The primary purpose with ZFS fault management is automated diagnosis
|
||||
and isolation of VDEV faults. A fault is something we can associate
|
||||
with an impact (e.g. loss of data redundancy) and a corrective action
|
||||
(e.g. offline or replace a disk). A typical ZFS fault management stack
|
||||
is comprised of _error detectors_ (e.g. `zfs_ereport_post()`), a _disk
|
||||
monitor_, a _diagnosis engine_ and _response agents_.
|
||||
|
||||
After detecting a software error, the ZFS kernel module sends error
|
||||
events to the ZED user daemon which in turn routes the events to its
|
||||
internal FMA modules based on their event subscriptions. Likewise, if
|
||||
a disk is added or changed in the system, the disk monitor sends disk
|
||||
events which are consumed by a response agent.
|
||||
|
||||
### FMD Components in ZED ###
|
||||
|
||||
There are three FMD modules (aka agents) that are now built into ZED.
|
||||
|
||||
1. A _Diagnosis Engine_ module (`agents/zfs_diagnosis.c`)
|
||||
2. A _Retire Agent_ module (`agents/zfs_retire.c`)
|
||||
3. A _Disk Add Agent_ module (`agents/zfs_mod.c`)
|
||||
|
||||
To begin with, a **Diagnosis Engine** consumes per-vdev I/O and checksum
|
||||
ereports and feeds them into a Soft Error Rate Discrimination (SERD)
|
||||
algorithm which will generate a corresponding fault diagnosis when the
|
||||
tracked VDEV encounters **N** events in a given **T** time window. The
|
||||
initial N and T values for the SERD algorithm are estimates inherited
|
||||
from illumos (10 errors in 10 minutes).
|
||||
|
||||
In turn, a **Retire Agent** responds to diagnosed faults by isolating
|
||||
the faulty VDEV. It will notify the ZFS kernel module of the new VDEV
|
||||
state (degraded or faulted). The retire agent is also responsible for
|
||||
managing hot spares across all pools. When it encounters a device fault
|
||||
or a device removal it will replace the device with an appropriate
|
||||
spare if available.
|
||||
|
||||
Finally, a **Disk Add Agent** responds to events from a libudev disk
|
||||
monitor (`EC_DEV_ADD` or `EC_DEV_STATUS`) and will online, replace or
|
||||
expand the associated VDEV. This agent is also known as the `zfs_mod`
|
||||
or Sysevent Loadable Module (SLM) on the illumos platform. The added
|
||||
disk is matched to a specific VDEV using its device id, physical path
|
||||
or VDEV GUID.
|
||||
|
||||
Note that the _auto-replace_ feature (aka hot plug) is opt-in and you
|
||||
must set the pool's `autoreplace` property to enable it. The new disk
|
||||
will be matched to the corresponding leaf VDEV by physical location
|
||||
and labeled with a GPT partition before replacing the original VDEV
|
||||
in the pool.
|
||||
|
||||
### Implementation Notes ###
|
||||
|
||||
* The FMD module API required for logic modules is emulated and implemented
|
||||
in the `fmd_api.c` and `fmd_serd.c` source files. This support includes
|
||||
module registration, memory allocation, module property accessors, basic
|
||||
case management, one-shot timers and SERD engines.
|
||||
For detailed information on the FMD module API, see the document --
|
||||
_"Fault Management Daemon Programmer's Reference Manual"_.
|
||||
|
||||
* The event subscriptions for the modules (located in a module specific
|
||||
configuration file on illumos) are currently hard-coded into the ZED
|
||||
`zfs_agent_dispatch()` function.
|
||||
|
||||
* The FMD modules are called one at a time from a single thread that
|
||||
consumes events queued to the modules. These events are sourced from
|
||||
the normal ZED events and also include events posted from the diagnosis
|
||||
engine and the libudev disk event monitor.
|
||||
|
||||
* The FMD code modules have minimal changes and were intentionally left
|
||||
as similar as possible to their upstream source files.
|
||||
|
||||
* The sysevent namespace in ZED differs from illumos. For example:
|
||||
* illumos uses `"resource.sysevent.EC_zfs.ESC_ZFS_vdev_remove"`
|
||||
* Linux uses `"sysevent.fs.zfs.vdev_remove"`
|
||||
|
||||
* The FMD Modules port was produced by Intel Federal, LLC under award
|
||||
number B609815 between the U.S. Department of Energy (DOE) and Intel
|
||||
Federal, LLC.
|
||||
|
760
sys/contrib/openzfs/cmd/zed/agents/fmd_api.c
Normal file
760
sys/contrib/openzfs/cmd/zed/agents/fmd_api.c
Normal file
@ -0,0 +1,760 @@
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License (the "License").
|
||||
* You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||
* or http://www.opensolaris.org/os/licensing.
|
||||
* See the License for the specific language governing permissions
|
||||
* and limitations under the License.
|
||||
*
|
||||
* When distributing Covered Code, include this CDDL HEADER in each
|
||||
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||
* If applicable, add the following below this CDDL HEADER, with the
|
||||
* fields enclosed by brackets "[]" replaced with your own identifying
|
||||
* information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
*
|
||||
* CDDL HEADER END
|
||||
*/
|
||||
/*
|
||||
* Copyright (c) 2004, 2010, Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Copyright (c) 2016, Intel Corporation.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file implements the minimal FMD module API required to support the
|
||||
* fault logic modules in ZED. This support includes module registration,
|
||||
* memory allocation, module property accessors, basic case management,
|
||||
* one-shot timers and SERD engines.
|
||||
*
|
||||
* In the ZED runtime, the modules are called from a single thread so no
|
||||
* locking is required in this emulated FMD environment.
|
||||
*/
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/fm/protocol.h>
|
||||
#include <uuid/uuid.h>
|
||||
#include <signal.h>
|
||||
#include <strings.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "fmd_api.h"
|
||||
#include "fmd_serd.h"
|
||||
|
||||
#include "zfs_agents.h"
|
||||
#include "../zed_log.h"
|
||||
|
||||
typedef struct fmd_modstat {
|
||||
fmd_stat_t ms_accepted; /* total events accepted by module */
|
||||
fmd_stat_t ms_caseopen; /* cases currently open */
|
||||
fmd_stat_t ms_casesolved; /* total cases solved by module */
|
||||
fmd_stat_t ms_caseclosed; /* total cases closed by module */
|
||||
} fmd_modstat_t;
|
||||
|
||||
typedef struct fmd_module {
|
||||
const char *mod_name; /* basename of module (ro) */
|
||||
const fmd_hdl_info_t *mod_info; /* module info registered with handle */
|
||||
void *mod_spec; /* fmd_hdl_get/setspecific data value */
|
||||
fmd_stat_t *mod_ustat; /* module specific custom stats */
|
||||
uint_t mod_ustat_cnt; /* count of ustat stats */
|
||||
fmd_modstat_t mod_stats; /* fmd built-in per-module statistics */
|
||||
fmd_serd_hash_t mod_serds; /* hash of serd engs owned by module */
|
||||
char *mod_vers; /* a copy of module version string */
|
||||
} fmd_module_t;
|
||||
|
||||
/*
|
||||
* ZED has two FMD hardwired module instances
|
||||
*/
|
||||
fmd_module_t zfs_retire_module;
|
||||
fmd_module_t zfs_diagnosis_module;
|
||||
|
||||
/*
|
||||
* Enable a reasonable set of defaults for libumem debugging on DEBUG builds.
|
||||
*/
|
||||
|
||||
#ifdef DEBUG
|
||||
const char *
|
||||
_umem_debug_init(void)
|
||||
{
|
||||
return ("default,verbose"); /* $UMEM_DEBUG setting */
|
||||
}
|
||||
|
||||
const char *
|
||||
_umem_logging_init(void)
|
||||
{
|
||||
return ("fail,contents"); /* $UMEM_LOGGING setting */
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Register a module with fmd and finish module initialization.
|
||||
* Returns an integer indicating whether it succeeded (zero) or
|
||||
* failed (non-zero).
|
||||
*/
|
||||
int
|
||||
fmd_hdl_register(fmd_hdl_t *hdl, int version, const fmd_hdl_info_t *mip)
|
||||
{
|
||||
fmd_module_t *mp = (fmd_module_t *)hdl;
|
||||
|
||||
mp->mod_info = mip;
|
||||
mp->mod_name = mip->fmdi_desc + 4; /* drop 'ZFS ' prefix */
|
||||
mp->mod_spec = NULL;
|
||||
|
||||
/* bare minimum module stats */
|
||||
(void) strcpy(mp->mod_stats.ms_accepted.fmds_name, "fmd.accepted");
|
||||
(void) strcpy(mp->mod_stats.ms_caseopen.fmds_name, "fmd.caseopen");
|
||||
(void) strcpy(mp->mod_stats.ms_casesolved.fmds_name, "fmd.casesolved");
|
||||
(void) strcpy(mp->mod_stats.ms_caseclosed.fmds_name, "fmd.caseclosed");
|
||||
|
||||
fmd_serd_hash_create(&mp->mod_serds);
|
||||
|
||||
fmd_hdl_debug(hdl, "register module");
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
void
|
||||
fmd_hdl_unregister(fmd_hdl_t *hdl)
|
||||
{
|
||||
fmd_module_t *mp = (fmd_module_t *)hdl;
|
||||
fmd_modstat_t *msp = &mp->mod_stats;
|
||||
const fmd_hdl_ops_t *ops = mp->mod_info->fmdi_ops;
|
||||
|
||||
/* dump generic module stats */
|
||||
fmd_hdl_debug(hdl, "%s: %llu", msp->ms_accepted.fmds_name,
|
||||
msp->ms_accepted.fmds_value.ui64);
|
||||
if (ops->fmdo_close != NULL) {
|
||||
fmd_hdl_debug(hdl, "%s: %llu", msp->ms_caseopen.fmds_name,
|
||||
msp->ms_caseopen.fmds_value.ui64);
|
||||
fmd_hdl_debug(hdl, "%s: %llu", msp->ms_casesolved.fmds_name,
|
||||
msp->ms_casesolved.fmds_value.ui64);
|
||||
fmd_hdl_debug(hdl, "%s: %llu", msp->ms_caseclosed.fmds_name,
|
||||
msp->ms_caseclosed.fmds_value.ui64);
|
||||
}
|
||||
|
||||
/* dump module specific stats */
|
||||
if (mp->mod_ustat != NULL) {
|
||||
int i;
|
||||
|
||||
for (i = 0; i < mp->mod_ustat_cnt; i++) {
|
||||
fmd_hdl_debug(hdl, "%s: %llu",
|
||||
mp->mod_ustat[i].fmds_name,
|
||||
mp->mod_ustat[i].fmds_value.ui64);
|
||||
}
|
||||
}
|
||||
|
||||
fmd_serd_hash_destroy(&mp->mod_serds);
|
||||
|
||||
fmd_hdl_debug(hdl, "unregister module");
|
||||
}
|
||||
|
||||
/*
|
||||
* fmd_hdl_setspecific() is used to associate a data pointer with
|
||||
* the specified handle for the duration of the module's lifetime.
|
||||
* This pointer can be retrieved using fmd_hdl_getspecific().
|
||||
*/
|
||||
void
|
||||
fmd_hdl_setspecific(fmd_hdl_t *hdl, void *spec)
|
||||
{
|
||||
fmd_module_t *mp = (fmd_module_t *)hdl;
|
||||
|
||||
mp->mod_spec = spec;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the module-specific data pointer previously associated
|
||||
* with the handle using fmd_hdl_setspecific().
|
||||
*/
|
||||
void *
|
||||
fmd_hdl_getspecific(fmd_hdl_t *hdl)
|
||||
{
|
||||
fmd_module_t *mp = (fmd_module_t *)hdl;
|
||||
|
||||
return (mp->mod_spec);
|
||||
}
|
||||
|
||||
void *
|
||||
fmd_hdl_alloc(fmd_hdl_t *hdl, size_t size, int flags)
|
||||
{
|
||||
return (umem_alloc(size, flags));
|
||||
}
|
||||
|
||||
void *
|
||||
fmd_hdl_zalloc(fmd_hdl_t *hdl, size_t size, int flags)
|
||||
{
|
||||
return (umem_zalloc(size, flags));
|
||||
}
|
||||
|
||||
void
|
||||
fmd_hdl_free(fmd_hdl_t *hdl, void *data, size_t size)
|
||||
{
|
||||
umem_free(data, size);
|
||||
}
|
||||
|
||||
/*
|
||||
* Record a module debug message using the specified format.
|
||||
*/
|
||||
void
|
||||
fmd_hdl_debug(fmd_hdl_t *hdl, const char *format, ...)
|
||||
{
|
||||
char message[256];
|
||||
va_list vargs;
|
||||
fmd_module_t *mp = (fmd_module_t *)hdl;
|
||||
|
||||
va_start(vargs, format);
|
||||
(void) vsnprintf(message, sizeof (message), format, vargs);
|
||||
va_end(vargs);
|
||||
|
||||
/* prefix message with module name */
|
||||
zed_log_msg(LOG_INFO, "%s: %s", mp->mod_name, message);
|
||||
}
|
||||
|
||||
/* Property Retrieval */
|
||||
|
||||
int32_t
|
||||
fmd_prop_get_int32(fmd_hdl_t *hdl, const char *name)
|
||||
{
|
||||
/*
|
||||
* These can be looked up in mp->modinfo->fmdi_props
|
||||
* For now we just hard code for phase 2. In the
|
||||
* future, there can be a ZED based override.
|
||||
*/
|
||||
if (strcmp(name, "spare_on_remove") == 0)
|
||||
return (1);
|
||||
|
||||
if (strcmp(name, "io_N") == 0 || strcmp(name, "checksum_N") == 0)
|
||||
return (10); /* N = 10 events */
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
int64_t
|
||||
fmd_prop_get_int64(fmd_hdl_t *hdl, const char *name)
|
||||
{
|
||||
/*
|
||||
* These can be looked up in mp->modinfo->fmdi_props
|
||||
* For now we just hard code for phase 2. In the
|
||||
* future, there can be a ZED based override.
|
||||
*/
|
||||
if (strcmp(name, "remove_timeout") == 0)
|
||||
return (15ULL * 1000ULL * 1000ULL * 1000ULL); /* 15 sec */
|
||||
|
||||
if (strcmp(name, "io_T") == 0 || strcmp(name, "checksum_T") == 0)
|
||||
return (1000ULL * 1000ULL * 1000ULL * 600ULL); /* 10 min */
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
/* FMD Statistics */
|
||||
|
||||
fmd_stat_t *
|
||||
fmd_stat_create(fmd_hdl_t *hdl, uint_t flags, uint_t nstats, fmd_stat_t *statv)
|
||||
{
|
||||
fmd_module_t *mp = (fmd_module_t *)hdl;
|
||||
|
||||
if (flags == FMD_STAT_NOALLOC) {
|
||||
mp->mod_ustat = statv;
|
||||
mp->mod_ustat_cnt = nstats;
|
||||
}
|
||||
|
||||
return (statv);
|
||||
}
|
||||
|
||||
/* Case Management */
|
||||
|
||||
fmd_case_t *
|
||||
fmd_case_open(fmd_hdl_t *hdl, void *data)
|
||||
{
|
||||
fmd_module_t *mp = (fmd_module_t *)hdl;
|
||||
uuid_t uuid;
|
||||
|
||||
fmd_case_t *cp;
|
||||
|
||||
cp = fmd_hdl_zalloc(hdl, sizeof (fmd_case_t), FMD_SLEEP);
|
||||
cp->ci_mod = hdl;
|
||||
cp->ci_state = FMD_CASE_UNSOLVED;
|
||||
cp->ci_flags = FMD_CF_DIRTY;
|
||||
cp->ci_data = data;
|
||||
cp->ci_bufptr = NULL;
|
||||
cp->ci_bufsiz = 0;
|
||||
|
||||
uuid_generate(uuid);
|
||||
uuid_unparse(uuid, cp->ci_uuid);
|
||||
|
||||
fmd_hdl_debug(hdl, "case opened (%s)", cp->ci_uuid);
|
||||
mp->mod_stats.ms_caseopen.fmds_value.ui64++;
|
||||
|
||||
return (cp);
|
||||
}
|
||||
|
||||
void
|
||||
fmd_case_solve(fmd_hdl_t *hdl, fmd_case_t *cp)
|
||||
{
|
||||
fmd_module_t *mp = (fmd_module_t *)hdl;
|
||||
|
||||
/*
|
||||
* For ZED, the event was already sent from fmd_case_add_suspect()
|
||||
*/
|
||||
|
||||
if (cp->ci_state >= FMD_CASE_SOLVED)
|
||||
fmd_hdl_debug(hdl, "case is already solved or closed");
|
||||
|
||||
cp->ci_state = FMD_CASE_SOLVED;
|
||||
|
||||
fmd_hdl_debug(hdl, "case solved (%s)", cp->ci_uuid);
|
||||
mp->mod_stats.ms_casesolved.fmds_value.ui64++;
|
||||
}
|
||||
|
||||
void
|
||||
fmd_case_close(fmd_hdl_t *hdl, fmd_case_t *cp)
|
||||
{
|
||||
fmd_module_t *mp = (fmd_module_t *)hdl;
|
||||
const fmd_hdl_ops_t *ops = mp->mod_info->fmdi_ops;
|
||||
|
||||
fmd_hdl_debug(hdl, "case closed (%s)", cp->ci_uuid);
|
||||
|
||||
if (ops->fmdo_close != NULL)
|
||||
ops->fmdo_close(hdl, cp);
|
||||
|
||||
mp->mod_stats.ms_caseopen.fmds_value.ui64--;
|
||||
mp->mod_stats.ms_caseclosed.fmds_value.ui64++;
|
||||
|
||||
if (cp->ci_bufptr != NULL && cp->ci_bufsiz > 0)
|
||||
fmd_hdl_free(hdl, cp->ci_bufptr, cp->ci_bufsiz);
|
||||
|
||||
fmd_hdl_free(hdl, cp, sizeof (fmd_case_t));
|
||||
}
|
||||
|
||||
void
|
||||
fmd_case_uuresolved(fmd_hdl_t *hdl, const char *uuid)
|
||||
{
|
||||
fmd_hdl_debug(hdl, "case resolved by uuid (%s)", uuid);
|
||||
}
|
||||
|
||||
int
|
||||
fmd_case_solved(fmd_hdl_t *hdl, fmd_case_t *cp)
|
||||
{
|
||||
return ((cp->ci_state >= FMD_CASE_SOLVED) ? FMD_B_TRUE : FMD_B_FALSE);
|
||||
}
|
||||
|
||||
void
|
||||
fmd_case_add_ereport(fmd_hdl_t *hdl, fmd_case_t *cp, fmd_event_t *ep)
|
||||
{
|
||||
}
|
||||
|
||||
static void
|
||||
zed_log_fault(nvlist_t *nvl, const char *uuid, const char *code)
|
||||
{
|
||||
nvlist_t *rsrc;
|
||||
char *strval;
|
||||
uint64_t guid;
|
||||
uint8_t byte;
|
||||
|
||||
zed_log_msg(LOG_INFO, "\nzed_fault_event:");
|
||||
|
||||
if (uuid != NULL)
|
||||
zed_log_msg(LOG_INFO, "\t%s: %s", FM_SUSPECT_UUID, uuid);
|
||||
if (nvlist_lookup_string(nvl, FM_CLASS, &strval) == 0)
|
||||
zed_log_msg(LOG_INFO, "\t%s: %s", FM_CLASS, strval);
|
||||
if (code != NULL)
|
||||
zed_log_msg(LOG_INFO, "\t%s: %s", FM_SUSPECT_DIAG_CODE, code);
|
||||
if (nvlist_lookup_uint8(nvl, FM_FAULT_CERTAINTY, &byte) == 0)
|
||||
zed_log_msg(LOG_INFO, "\t%s: %llu", FM_FAULT_CERTAINTY, byte);
|
||||
if (nvlist_lookup_nvlist(nvl, FM_FAULT_RESOURCE, &rsrc) == 0) {
|
||||
if (nvlist_lookup_string(rsrc, FM_FMRI_SCHEME, &strval) == 0)
|
||||
zed_log_msg(LOG_INFO, "\t%s: %s", FM_FMRI_SCHEME,
|
||||
strval);
|
||||
if (nvlist_lookup_uint64(rsrc, FM_FMRI_ZFS_POOL, &guid) == 0)
|
||||
zed_log_msg(LOG_INFO, "\t%s: %llu", FM_FMRI_ZFS_POOL,
|
||||
guid);
|
||||
if (nvlist_lookup_uint64(rsrc, FM_FMRI_ZFS_VDEV, &guid) == 0)
|
||||
zed_log_msg(LOG_INFO, "\t%s: %llu \n", FM_FMRI_ZFS_VDEV,
|
||||
guid);
|
||||
}
|
||||
}
|
||||
|
||||
static const char *
|
||||
fmd_fault_mkcode(nvlist_t *fault)
|
||||
{
|
||||
char *class, *code = "-";
|
||||
|
||||
/*
|
||||
* Note: message codes come from: openzfs/usr/src/cmd/fm/dicts/ZFS.po
|
||||
*/
|
||||
if (nvlist_lookup_string(fault, FM_CLASS, &class) == 0) {
|
||||
if (strcmp(class, "fault.fs.zfs.vdev.io") == 0)
|
||||
code = "ZFS-8000-FD";
|
||||
else if (strcmp(class, "fault.fs.zfs.vdev.checksum") == 0)
|
||||
code = "ZFS-8000-GH";
|
||||
else if (strcmp(class, "fault.fs.zfs.io_failure_wait") == 0)
|
||||
code = "ZFS-8000-HC";
|
||||
else if (strcmp(class, "fault.fs.zfs.io_failure_continue") == 0)
|
||||
code = "ZFS-8000-JQ";
|
||||
else if (strcmp(class, "fault.fs.zfs.log_replay") == 0)
|
||||
code = "ZFS-8000-K4";
|
||||
else if (strcmp(class, "fault.fs.zfs.pool") == 0)
|
||||
code = "ZFS-8000-CS";
|
||||
else if (strcmp(class, "fault.fs.zfs.device") == 0)
|
||||
code = "ZFS-8000-D3";
|
||||
|
||||
}
|
||||
return (code);
|
||||
}
|
||||
|
||||
void
|
||||
fmd_case_add_suspect(fmd_hdl_t *hdl, fmd_case_t *cp, nvlist_t *fault)
|
||||
{
|
||||
nvlist_t *nvl;
|
||||
const char *code = fmd_fault_mkcode(fault);
|
||||
int64_t tod[2];
|
||||
int err = 0;
|
||||
|
||||
/*
|
||||
* payload derived from fmd_protocol_list()
|
||||
*/
|
||||
|
||||
(void) gettimeofday(&cp->ci_tv, NULL);
|
||||
tod[0] = cp->ci_tv.tv_sec;
|
||||
tod[1] = cp->ci_tv.tv_usec;
|
||||
|
||||
nvl = fmd_nvl_alloc(hdl, FMD_SLEEP);
|
||||
|
||||
err |= nvlist_add_uint8(nvl, FM_VERSION, FM_SUSPECT_VERSION);
|
||||
err |= nvlist_add_string(nvl, FM_CLASS, FM_LIST_SUSPECT_CLASS);
|
||||
err |= nvlist_add_string(nvl, FM_SUSPECT_UUID, cp->ci_uuid);
|
||||
err |= nvlist_add_string(nvl, FM_SUSPECT_DIAG_CODE, code);
|
||||
err |= nvlist_add_int64_array(nvl, FM_SUSPECT_DIAG_TIME, tod, 2);
|
||||
err |= nvlist_add_uint32(nvl, FM_SUSPECT_FAULT_SZ, 1);
|
||||
err |= nvlist_add_nvlist_array(nvl, FM_SUSPECT_FAULT_LIST, &fault, 1);
|
||||
|
||||
if (err)
|
||||
zed_log_die("failed to populate nvlist");
|
||||
|
||||
zed_log_fault(fault, cp->ci_uuid, code);
|
||||
zfs_agent_post_event(FM_LIST_SUSPECT_CLASS, NULL, nvl);
|
||||
|
||||
nvlist_free(nvl);
|
||||
nvlist_free(fault);
|
||||
}
|
||||
|
||||
void
|
||||
fmd_case_setspecific(fmd_hdl_t *hdl, fmd_case_t *cp, void *data)
|
||||
{
|
||||
cp->ci_data = data;
|
||||
}
|
||||
|
||||
void *
|
||||
fmd_case_getspecific(fmd_hdl_t *hdl, fmd_case_t *cp)
|
||||
{
|
||||
return (cp->ci_data);
|
||||
}
|
||||
|
||||
void
|
||||
fmd_buf_create(fmd_hdl_t *hdl, fmd_case_t *cp, const char *name, size_t size)
|
||||
{
|
||||
assert(strcmp(name, "data") == 0);
|
||||
assert(cp->ci_bufptr == NULL);
|
||||
assert(size < (1024 * 1024));
|
||||
|
||||
cp->ci_bufptr = fmd_hdl_alloc(hdl, size, FMD_SLEEP);
|
||||
cp->ci_bufsiz = size;
|
||||
}
|
||||
|
||||
void
|
||||
fmd_buf_read(fmd_hdl_t *hdl, fmd_case_t *cp,
|
||||
const char *name, void *buf, size_t size)
|
||||
{
|
||||
assert(strcmp(name, "data") == 0);
|
||||
assert(cp->ci_bufptr != NULL);
|
||||
assert(size <= cp->ci_bufsiz);
|
||||
|
||||
bcopy(cp->ci_bufptr, buf, size);
|
||||
}
|
||||
|
||||
void
|
||||
fmd_buf_write(fmd_hdl_t *hdl, fmd_case_t *cp,
|
||||
const char *name, const void *buf, size_t size)
|
||||
{
|
||||
assert(strcmp(name, "data") == 0);
|
||||
assert(cp->ci_bufptr != NULL);
|
||||
assert(cp->ci_bufsiz >= size);
|
||||
|
||||
bcopy(buf, cp->ci_bufptr, size);
|
||||
}
|
||||
|
||||
/* SERD Engines */
|
||||
|
||||
void
|
||||
fmd_serd_create(fmd_hdl_t *hdl, const char *name, uint_t n, hrtime_t t)
|
||||
{
|
||||
fmd_module_t *mp = (fmd_module_t *)hdl;
|
||||
|
||||
if (fmd_serd_eng_lookup(&mp->mod_serds, name) != NULL) {
|
||||
zed_log_msg(LOG_ERR, "failed to create SERD engine '%s': "
|
||||
" name already exists", name);
|
||||
return;
|
||||
}
|
||||
|
||||
(void) fmd_serd_eng_insert(&mp->mod_serds, name, n, t);
|
||||
}
|
||||
|
||||
void
|
||||
fmd_serd_destroy(fmd_hdl_t *hdl, const char *name)
|
||||
{
|
||||
fmd_module_t *mp = (fmd_module_t *)hdl;
|
||||
|
||||
fmd_serd_eng_delete(&mp->mod_serds, name);
|
||||
|
||||
fmd_hdl_debug(hdl, "serd_destroy %s", name);
|
||||
}
|
||||
|
||||
int
|
||||
fmd_serd_exists(fmd_hdl_t *hdl, const char *name)
|
||||
{
|
||||
fmd_module_t *mp = (fmd_module_t *)hdl;
|
||||
|
||||
return (fmd_serd_eng_lookup(&mp->mod_serds, name) != NULL);
|
||||
}
|
||||
|
||||
void
|
||||
fmd_serd_reset(fmd_hdl_t *hdl, const char *name)
|
||||
{
|
||||
fmd_module_t *mp = (fmd_module_t *)hdl;
|
||||
fmd_serd_eng_t *sgp;
|
||||
|
||||
if ((sgp = fmd_serd_eng_lookup(&mp->mod_serds, name)) == NULL) {
|
||||
zed_log_msg(LOG_ERR, "serd engine '%s' does not exist", name);
|
||||
return;
|
||||
}
|
||||
|
||||
fmd_serd_eng_reset(sgp);
|
||||
|
||||
fmd_hdl_debug(hdl, "serd_reset %s", name);
|
||||
}
|
||||
|
||||
int
|
||||
fmd_serd_record(fmd_hdl_t *hdl, const char *name, fmd_event_t *ep)
|
||||
{
|
||||
fmd_module_t *mp = (fmd_module_t *)hdl;
|
||||
fmd_serd_eng_t *sgp;
|
||||
int err;
|
||||
|
||||
if ((sgp = fmd_serd_eng_lookup(&mp->mod_serds, name)) == NULL) {
|
||||
zed_log_msg(LOG_ERR, "failed to add record to SERD engine '%s'",
|
||||
name);
|
||||
return (FMD_B_FALSE);
|
||||
}
|
||||
err = fmd_serd_eng_record(sgp, ep->ev_hrt);
|
||||
|
||||
return (err);
|
||||
}
|
||||
|
||||
/* FMD Timers */
|
||||
|
||||
static void
|
||||
_timer_notify(union sigval sv)
|
||||
{
|
||||
fmd_timer_t *ftp = sv.sival_ptr;
|
||||
fmd_hdl_t *hdl = ftp->ft_hdl;
|
||||
fmd_module_t *mp = (fmd_module_t *)hdl;
|
||||
const fmd_hdl_ops_t *ops = mp->mod_info->fmdi_ops;
|
||||
struct itimerspec its;
|
||||
|
||||
fmd_hdl_debug(hdl, "timer fired (%p)", ftp->ft_tid);
|
||||
|
||||
/* disarm the timer */
|
||||
bzero(&its, sizeof (struct itimerspec));
|
||||
timer_settime(ftp->ft_tid, 0, &its, NULL);
|
||||
|
||||
/* Note that the fmdo_timeout can remove this timer */
|
||||
if (ops->fmdo_timeout != NULL)
|
||||
ops->fmdo_timeout(hdl, ftp, ftp->ft_arg);
|
||||
}
|
||||
|
||||
/*
|
||||
* Install a new timer which will fire at least delta nanoseconds after the
|
||||
* current time. After the timeout has expired, the module's fmdo_timeout
|
||||
* entry point is called.
|
||||
*/
|
||||
fmd_timer_t *
|
||||
fmd_timer_install(fmd_hdl_t *hdl, void *arg, fmd_event_t *ep, hrtime_t delta)
|
||||
{
|
||||
struct sigevent sev;
|
||||
struct itimerspec its;
|
||||
fmd_timer_t *ftp;
|
||||
|
||||
ftp = fmd_hdl_alloc(hdl, sizeof (fmd_timer_t), FMD_SLEEP);
|
||||
ftp->ft_arg = arg;
|
||||
ftp->ft_hdl = hdl;
|
||||
|
||||
its.it_value.tv_sec = delta / 1000000000;
|
||||
its.it_value.tv_nsec = delta % 1000000000;
|
||||
its.it_interval.tv_sec = its.it_value.tv_sec;
|
||||
its.it_interval.tv_nsec = its.it_value.tv_nsec;
|
||||
|
||||
sev.sigev_notify = SIGEV_THREAD;
|
||||
sev.sigev_notify_function = _timer_notify;
|
||||
sev.sigev_notify_attributes = NULL;
|
||||
sev.sigev_value.sival_ptr = ftp;
|
||||
|
||||
timer_create(CLOCK_REALTIME, &sev, &ftp->ft_tid);
|
||||
timer_settime(ftp->ft_tid, 0, &its, NULL);
|
||||
|
||||
fmd_hdl_debug(hdl, "installing timer for %d secs (%p)",
|
||||
(int)its.it_value.tv_sec, ftp->ft_tid);
|
||||
|
||||
return (ftp);
|
||||
}
|
||||
|
||||
void
|
||||
fmd_timer_remove(fmd_hdl_t *hdl, fmd_timer_t *ftp)
|
||||
{
|
||||
fmd_hdl_debug(hdl, "removing timer (%p)", ftp->ft_tid);
|
||||
|
||||
timer_delete(ftp->ft_tid);
|
||||
|
||||
fmd_hdl_free(hdl, ftp, sizeof (fmd_timer_t));
|
||||
}
|
||||
|
||||
/* Name-Value Pair Lists */
|
||||
|
||||
nvlist_t *
|
||||
fmd_nvl_create_fault(fmd_hdl_t *hdl, const char *class, uint8_t certainty,
|
||||
nvlist_t *asru, nvlist_t *fru, nvlist_t *resource)
|
||||
{
|
||||
nvlist_t *nvl;
|
||||
int err = 0;
|
||||
|
||||
if (nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0) != 0)
|
||||
zed_log_die("failed to xalloc fault nvlist");
|
||||
|
||||
err |= nvlist_add_uint8(nvl, FM_VERSION, FM_FAULT_VERSION);
|
||||
err |= nvlist_add_string(nvl, FM_CLASS, class);
|
||||
err |= nvlist_add_uint8(nvl, FM_FAULT_CERTAINTY, certainty);
|
||||
|
||||
if (asru != NULL)
|
||||
err |= nvlist_add_nvlist(nvl, FM_FAULT_ASRU, asru);
|
||||
if (fru != NULL)
|
||||
err |= nvlist_add_nvlist(nvl, FM_FAULT_FRU, fru);
|
||||
if (resource != NULL)
|
||||
err |= nvlist_add_nvlist(nvl, FM_FAULT_RESOURCE, resource);
|
||||
|
||||
if (err)
|
||||
zed_log_die("failed to populate nvlist: %s\n", strerror(err));
|
||||
|
||||
return (nvl);
|
||||
}
|
||||
|
||||
/*
|
||||
* sourced from fmd_string.c
|
||||
*/
|
||||
static int
|
||||
fmd_strmatch(const char *s, const char *p)
|
||||
{
|
||||
char c;
|
||||
|
||||
if (p == NULL)
|
||||
return (0);
|
||||
|
||||
if (s == NULL)
|
||||
s = ""; /* treat NULL string as the empty string */
|
||||
|
||||
do {
|
||||
if ((c = *p++) == '\0')
|
||||
return (*s == '\0');
|
||||
|
||||
if (c == '*') {
|
||||
while (*p == '*')
|
||||
p++; /* consecutive *'s can be collapsed */
|
||||
|
||||
if (*p == '\0')
|
||||
return (1);
|
||||
|
||||
while (*s != '\0') {
|
||||
if (fmd_strmatch(s++, p) != 0)
|
||||
return (1);
|
||||
}
|
||||
|
||||
return (0);
|
||||
}
|
||||
} while (c == *s++);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
int
|
||||
fmd_nvl_class_match(fmd_hdl_t *hdl, nvlist_t *nvl, const char *pattern)
|
||||
{
|
||||
char *class;
|
||||
|
||||
return (nvl != NULL &&
|
||||
nvlist_lookup_string(nvl, FM_CLASS, &class) == 0 &&
|
||||
fmd_strmatch(class, pattern));
|
||||
}
|
||||
|
||||
nvlist_t *
|
||||
fmd_nvl_alloc(fmd_hdl_t *hdl, int flags)
|
||||
{
|
||||
nvlist_t *nvl = NULL;
|
||||
|
||||
if (nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0) != 0)
|
||||
return (NULL);
|
||||
|
||||
return (nvl);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ZED Agent specific APIs
|
||||
*/
|
||||
|
||||
fmd_hdl_t *
|
||||
fmd_module_hdl(const char *name)
|
||||
{
|
||||
if (strcmp(name, "zfs-retire") == 0)
|
||||
return ((fmd_hdl_t *)&zfs_retire_module);
|
||||
if (strcmp(name, "zfs-diagnosis") == 0)
|
||||
return ((fmd_hdl_t *)&zfs_diagnosis_module);
|
||||
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
boolean_t
|
||||
fmd_module_initialized(fmd_hdl_t *hdl)
|
||||
{
|
||||
fmd_module_t *mp = (fmd_module_t *)hdl;
|
||||
|
||||
return (mp->mod_info != NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* fmd_module_recv is called for each event that is received by
|
||||
* the fault manager that has a class that matches one of the
|
||||
* module's subscriptions.
|
||||
*/
|
||||
void
|
||||
fmd_module_recv(fmd_hdl_t *hdl, nvlist_t *nvl, const char *class)
|
||||
{
|
||||
fmd_module_t *mp = (fmd_module_t *)hdl;
|
||||
const fmd_hdl_ops_t *ops = mp->mod_info->fmdi_ops;
|
||||
fmd_event_t faux_event = {0};
|
||||
int64_t *tv;
|
||||
uint_t n;
|
||||
|
||||
/*
|
||||
* Will need to normalized this if we persistently store the case data
|
||||
*/
|
||||
if (nvlist_lookup_int64_array(nvl, FM_EREPORT_TIME, &tv, &n) == 0)
|
||||
faux_event.ev_hrt = tv[0] * NANOSEC + tv[1];
|
||||
else
|
||||
faux_event.ev_hrt = 0;
|
||||
|
||||
ops->fmdo_recv(hdl, &faux_event, nvl, class);
|
||||
|
||||
mp->mod_stats.ms_accepted.fmds_value.ui64++;
|
||||
|
||||
/* TBD - should we initiate fm_module_gc() periodically? */
|
||||
}
|
246
sys/contrib/openzfs/cmd/zed/agents/fmd_api.h
Normal file
246
sys/contrib/openzfs/cmd/zed/agents/fmd_api.h
Normal file
@ -0,0 +1,246 @@
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License (the "License").
|
||||
* You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||
* or http://www.opensolaris.org/os/licensing.
|
||||
* See the License for the specific language governing permissions
|
||||
* and limitations under the License.
|
||||
*
|
||||
* When distributing Covered Code, include this CDDL HEADER in each
|
||||
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||
* If applicable, add the following below this CDDL HEADER, with the
|
||||
* fields enclosed by brackets "[]" replaced with your own identifying
|
||||
* information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
*
|
||||
* CDDL HEADER END
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (c) 2004, 2010, Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Copyright (c) 2016, Intel Corporation.
|
||||
*/
|
||||
|
||||
#ifndef _FMD_API_H
|
||||
#define _FMD_API_H
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/time.h>
|
||||
#include <time.h>
|
||||
#include <libnvpair.h>
|
||||
#include <stdarg.h>
|
||||
#include <umem.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Fault Management Daemon Client Interfaces
|
||||
*/
|
||||
|
||||
#define FMD_API_VERSION 5
|
||||
|
||||
typedef struct fmd_hdl fmd_hdl_t;
|
||||
|
||||
typedef struct fmd_timer {
|
||||
timer_t ft_tid;
|
||||
void *ft_arg;
|
||||
fmd_hdl_t *ft_hdl;
|
||||
} fmd_timer_t;
|
||||
|
||||
#define id_t fmd_timer_t *
|
||||
|
||||
|
||||
typedef struct fmd_event {
|
||||
hrtime_t ev_hrt; /* event time used by SERD engines */
|
||||
} fmd_event_t;
|
||||
|
||||
typedef struct fmd_case {
|
||||
char ci_uuid[48]; /* uuid string for this case */
|
||||
fmd_hdl_t *ci_mod; /* module that owns this case */
|
||||
void *ci_data; /* data from fmd_case_setspecific() */
|
||||
ushort_t ci_state; /* case state (see below) */
|
||||
ushort_t ci_flags; /* case flags (see below) */
|
||||
struct timeval ci_tv; /* time of original diagnosis */
|
||||
void *ci_bufptr; /* case data serialization buffer */
|
||||
size_t ci_bufsiz;
|
||||
} fmd_case_t;
|
||||
|
||||
|
||||
#define FMD_B_FALSE 0 /* false value for booleans as int */
|
||||
#define FMD_B_TRUE 1 /* true value for booleans as int */
|
||||
|
||||
|
||||
#define FMD_CASE_UNSOLVED 0 /* case is not yet solved (waiting) */
|
||||
#define FMD_CASE_SOLVED 1 /* case is solved (suspects added) */
|
||||
#define FMD_CASE_CLOSE_WAIT 2 /* case is executing fmdo_close() */
|
||||
#define FMD_CASE_CLOSED 3 /* case is closed (reconfig done) */
|
||||
#define FMD_CASE_REPAIRED 4 /* case is repaired */
|
||||
#define FMD_CASE_RESOLVED 5 /* case is resolved (can be freed) */
|
||||
|
||||
#define FMD_CF_DIRTY 0x01 /* case is in need of checkpoint */
|
||||
#define FMD_CF_SOLVED 0x02 /* case has been solved */
|
||||
#define FMD_CF_ISOLATED 0x04 /* case has been isolated */
|
||||
#define FMD_CF_REPAIRED 0x08 /* case has been repaired */
|
||||
#define FMD_CF_RESOLVED 0x10 /* case has been resolved */
|
||||
|
||||
|
||||
#define FMD_TYPE_BOOL 0 /* int */
|
||||
#define FMD_TYPE_INT32 1 /* int32_t */
|
||||
#define FMD_TYPE_UINT32 2 /* uint32_t */
|
||||
#define FMD_TYPE_INT64 3 /* int64_t */
|
||||
#define FMD_TYPE_UINT64 4 /* uint64_t */
|
||||
#define FMD_TYPE_TIME 5 /* uint64_t */
|
||||
#define FMD_TYPE_SIZE 6 /* uint64_t */
|
||||
|
||||
typedef struct fmd_prop {
|
||||
const char *fmdp_name; /* property name */
|
||||
uint_t fmdp_type; /* property type (see above) */
|
||||
const char *fmdp_defv; /* default value */
|
||||
} fmd_prop_t;
|
||||
|
||||
typedef struct fmd_stat {
|
||||
char fmds_name[32]; /* statistic name */
|
||||
uint_t fmds_type; /* statistic type (see above) */
|
||||
char fmds_desc[64]; /* statistic description */
|
||||
union {
|
||||
int bool; /* FMD_TYPE_BOOL */
|
||||
int32_t i32; /* FMD_TYPE_INT32 */
|
||||
uint32_t ui32; /* FMD_TYPE_UINT32 */
|
||||
int64_t i64; /* FMD_TYPE_INT64 */
|
||||
uint64_t ui64; /* FMD_TYPE_UINT64 */
|
||||
} fmds_value;
|
||||
} fmd_stat_t;
|
||||
|
||||
typedef struct fmd_hdl_ops {
|
||||
void (*fmdo_recv)(fmd_hdl_t *, fmd_event_t *, nvlist_t *, const char *);
|
||||
void (*fmdo_timeout)(fmd_hdl_t *, id_t, void *);
|
||||
void (*fmdo_close)(fmd_hdl_t *, fmd_case_t *);
|
||||
void (*fmdo_stats)(fmd_hdl_t *);
|
||||
void (*fmdo_gc)(fmd_hdl_t *);
|
||||
} fmd_hdl_ops_t;
|
||||
|
||||
#define FMD_SEND_SUCCESS 0 /* fmdo_send queued event */
|
||||
#define FMD_SEND_FAILED 1 /* fmdo_send unrecoverable error */
|
||||
#define FMD_SEND_RETRY 2 /* fmdo_send requests retry */
|
||||
|
||||
typedef struct fmd_hdl_info {
|
||||
const char *fmdi_desc; /* fmd client description string */
|
||||
const char *fmdi_vers; /* fmd client version string */
|
||||
const fmd_hdl_ops_t *fmdi_ops; /* ops vector for client */
|
||||
const fmd_prop_t *fmdi_props; /* array of configuration props */
|
||||
} fmd_hdl_info_t;
|
||||
|
||||
extern int fmd_hdl_register(fmd_hdl_t *, int, const fmd_hdl_info_t *);
|
||||
extern void fmd_hdl_unregister(fmd_hdl_t *);
|
||||
|
||||
extern void fmd_hdl_setspecific(fmd_hdl_t *, void *);
|
||||
extern void *fmd_hdl_getspecific(fmd_hdl_t *);
|
||||
|
||||
#define FMD_SLEEP UMEM_NOFAIL
|
||||
|
||||
extern void *fmd_hdl_alloc(fmd_hdl_t *, size_t, int);
|
||||
extern void *fmd_hdl_zalloc(fmd_hdl_t *, size_t, int);
|
||||
extern void fmd_hdl_free(fmd_hdl_t *, void *, size_t);
|
||||
|
||||
extern char *fmd_hdl_strdup(fmd_hdl_t *, const char *, int);
|
||||
extern void fmd_hdl_strfree(fmd_hdl_t *, char *);
|
||||
|
||||
extern void fmd_hdl_vdebug(fmd_hdl_t *, const char *, va_list);
|
||||
extern void fmd_hdl_debug(fmd_hdl_t *, const char *, ...);
|
||||
|
||||
extern int32_t fmd_prop_get_int32(fmd_hdl_t *, const char *);
|
||||
extern int64_t fmd_prop_get_int64(fmd_hdl_t *, const char *);
|
||||
|
||||
#define FMD_STAT_NOALLOC 0x0 /* fmd should use caller's memory */
|
||||
#define FMD_STAT_ALLOC 0x1 /* fmd should allocate stats memory */
|
||||
|
||||
extern fmd_stat_t *fmd_stat_create(fmd_hdl_t *, uint_t, uint_t, fmd_stat_t *);
|
||||
extern void fmd_stat_destroy(fmd_hdl_t *, uint_t, fmd_stat_t *);
|
||||
extern void fmd_stat_setstr(fmd_hdl_t *, fmd_stat_t *, const char *);
|
||||
|
||||
extern fmd_case_t *fmd_case_open(fmd_hdl_t *, void *);
|
||||
extern void fmd_case_reset(fmd_hdl_t *, fmd_case_t *);
|
||||
extern void fmd_case_solve(fmd_hdl_t *, fmd_case_t *);
|
||||
extern void fmd_case_close(fmd_hdl_t *, fmd_case_t *);
|
||||
|
||||
extern const char *fmd_case_uuid(fmd_hdl_t *, fmd_case_t *);
|
||||
extern fmd_case_t *fmd_case_uulookup(fmd_hdl_t *, const char *);
|
||||
extern void fmd_case_uuclose(fmd_hdl_t *, const char *);
|
||||
extern int fmd_case_uuclosed(fmd_hdl_t *, const char *);
|
||||
extern int fmd_case_uuisresolved(fmd_hdl_t *, const char *);
|
||||
extern void fmd_case_uuresolved(fmd_hdl_t *, const char *);
|
||||
|
||||
extern int fmd_case_solved(fmd_hdl_t *, fmd_case_t *);
|
||||
extern int fmd_case_closed(fmd_hdl_t *, fmd_case_t *);
|
||||
|
||||
extern void fmd_case_add_ereport(fmd_hdl_t *, fmd_case_t *, fmd_event_t *);
|
||||
extern void fmd_case_add_serd(fmd_hdl_t *, fmd_case_t *, const char *);
|
||||
extern void fmd_case_add_suspect(fmd_hdl_t *, fmd_case_t *, nvlist_t *);
|
||||
|
||||
extern void fmd_case_setspecific(fmd_hdl_t *, fmd_case_t *, void *);
|
||||
extern void *fmd_case_getspecific(fmd_hdl_t *, fmd_case_t *);
|
||||
|
||||
extern fmd_case_t *fmd_case_next(fmd_hdl_t *, fmd_case_t *);
|
||||
extern fmd_case_t *fmd_case_prev(fmd_hdl_t *, fmd_case_t *);
|
||||
|
||||
extern void fmd_buf_create(fmd_hdl_t *, fmd_case_t *, const char *, size_t);
|
||||
extern void fmd_buf_destroy(fmd_hdl_t *, fmd_case_t *, const char *);
|
||||
extern void fmd_buf_read(fmd_hdl_t *, fmd_case_t *,
|
||||
const char *, void *, size_t);
|
||||
extern void fmd_buf_write(fmd_hdl_t *, fmd_case_t *,
|
||||
const char *, const void *, size_t);
|
||||
extern size_t fmd_buf_size(fmd_hdl_t *, fmd_case_t *, const char *);
|
||||
|
||||
extern void fmd_serd_create(fmd_hdl_t *, const char *, uint_t, hrtime_t);
|
||||
extern void fmd_serd_destroy(fmd_hdl_t *, const char *);
|
||||
extern int fmd_serd_exists(fmd_hdl_t *, const char *);
|
||||
extern void fmd_serd_reset(fmd_hdl_t *, const char *);
|
||||
extern int fmd_serd_record(fmd_hdl_t *, const char *, fmd_event_t *);
|
||||
extern int fmd_serd_fired(fmd_hdl_t *, const char *);
|
||||
extern int fmd_serd_empty(fmd_hdl_t *, const char *);
|
||||
|
||||
extern id_t fmd_timer_install(fmd_hdl_t *, void *, fmd_event_t *, hrtime_t);
|
||||
extern void fmd_timer_remove(fmd_hdl_t *, id_t);
|
||||
|
||||
extern nvlist_t *fmd_nvl_create_fault(fmd_hdl_t *,
|
||||
const char *, uint8_t, nvlist_t *, nvlist_t *, nvlist_t *);
|
||||
|
||||
extern int fmd_nvl_class_match(fmd_hdl_t *, nvlist_t *, const char *);
|
||||
|
||||
#define FMD_HAS_FAULT_FRU 0
|
||||
#define FMD_HAS_FAULT_ASRU 1
|
||||
#define FMD_HAS_FAULT_RESOURCE 2
|
||||
|
||||
extern void fmd_repair_fru(fmd_hdl_t *, const char *);
|
||||
extern int fmd_repair_asru(fmd_hdl_t *, const char *);
|
||||
|
||||
extern nvlist_t *fmd_nvl_alloc(fmd_hdl_t *, int);
|
||||
extern nvlist_t *fmd_nvl_dup(fmd_hdl_t *, nvlist_t *, int);
|
||||
|
||||
/*
|
||||
* ZED Specific Interfaces
|
||||
*/
|
||||
|
||||
extern fmd_hdl_t *fmd_module_hdl(const char *);
|
||||
extern boolean_t fmd_module_initialized(fmd_hdl_t *);
|
||||
extern void fmd_module_recv(fmd_hdl_t *, nvlist_t *, const char *);
|
||||
|
||||
/* ZFS FMA Retire Agent */
|
||||
extern void _zfs_retire_init(fmd_hdl_t *);
|
||||
extern void _zfs_retire_fini(fmd_hdl_t *);
|
||||
|
||||
/* ZFS FMA Diagnosis Engine */
|
||||
extern void _zfs_diagnosis_init(fmd_hdl_t *);
|
||||
extern void _zfs_diagnosis_fini(fmd_hdl_t *);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* _FMD_API_H */
|
316
sys/contrib/openzfs/cmd/zed/agents/fmd_serd.c
Normal file
316
sys/contrib/openzfs/cmd/zed/agents/fmd_serd.c
Normal file
@ -0,0 +1,316 @@
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License, Version 1.0 only
|
||||
* (the "License"). You may not use this file except in compliance
|
||||
* with the License.
|
||||
*
|
||||
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||
* or http://www.opensolaris.org/os/licensing.
|
||||
* See the License for the specific language governing permissions
|
||||
* and limitations under the License.
|
||||
*
|
||||
* When distributing Covered Code, include this CDDL HEADER in each
|
||||
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||
* If applicable, add the following below this CDDL HEADER, with the
|
||||
* fields enclosed by brackets "[]" replaced with your own identifying
|
||||
* information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
*
|
||||
* CDDL HEADER END
|
||||
*/
|
||||
/*
|
||||
* Copyright 2004 Sun Microsystems, Inc. All rights reserved.
|
||||
* Use is subject to license terms.
|
||||
*
|
||||
* Copyright (c) 2016, Intel Corporation.
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <strings.h>
|
||||
#include <sys/list.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
#include "fmd_api.h"
|
||||
#include "fmd_serd.h"
|
||||
#include "../zed_log.h"
|
||||
|
||||
|
||||
#define FMD_STR_BUCKETS 211
|
||||
|
||||
|
||||
#ifdef SERD_ENG_DEBUG
|
||||
#define serd_log_msg(fmt, ...) \
|
||||
zed_log_msg(LOG_INFO, fmt, __VA_ARGS__)
|
||||
#else
|
||||
#define serd_log_msg(fmt, ...)
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
* SERD Engine Backend
|
||||
*/
|
||||
|
||||
/*
|
||||
* Compute the delta between events in nanoseconds. To account for very old
|
||||
* events which are replayed, we must handle the case where time is negative.
|
||||
* We convert the hrtime_t's to unsigned 64-bit integers and then handle the
|
||||
* case where 'old' is greater than 'new' (i.e. high-res time has wrapped).
|
||||
*/
|
||||
static hrtime_t
|
||||
fmd_event_delta(hrtime_t t1, hrtime_t t2)
|
||||
{
|
||||
uint64_t old = t1;
|
||||
uint64_t new = t2;
|
||||
|
||||
return (new >= old ? new - old : (UINT64_MAX - old) + new + 1);
|
||||
}
|
||||
|
||||
static fmd_serd_eng_t *
|
||||
fmd_serd_eng_alloc(const char *name, uint64_t n, hrtime_t t)
|
||||
{
|
||||
fmd_serd_eng_t *sgp;
|
||||
|
||||
sgp = malloc(sizeof (fmd_serd_eng_t));
|
||||
bzero(sgp, sizeof (fmd_serd_eng_t));
|
||||
|
||||
sgp->sg_name = strdup(name);
|
||||
sgp->sg_flags = FMD_SERD_DIRTY;
|
||||
sgp->sg_n = n;
|
||||
sgp->sg_t = t;
|
||||
|
||||
list_create(&sgp->sg_list, sizeof (fmd_serd_elem_t),
|
||||
offsetof(fmd_serd_elem_t, se_list));
|
||||
|
||||
return (sgp);
|
||||
}
|
||||
|
||||
static void
|
||||
fmd_serd_eng_free(fmd_serd_eng_t *sgp)
|
||||
{
|
||||
fmd_serd_eng_reset(sgp);
|
||||
free(sgp->sg_name);
|
||||
list_destroy(&sgp->sg_list);
|
||||
free(sgp);
|
||||
}
|
||||
|
||||
/*
|
||||
* sourced from fmd_string.c
|
||||
*/
|
||||
static ulong_t
|
||||
fmd_strhash(const char *key)
|
||||
{
|
||||
ulong_t g, h = 0;
|
||||
const char *p;
|
||||
|
||||
for (p = key; *p != '\0'; p++) {
|
||||
h = (h << 4) + *p;
|
||||
|
||||
if ((g = (h & 0xf0000000)) != 0) {
|
||||
h ^= (g >> 24);
|
||||
h ^= g;
|
||||
}
|
||||
}
|
||||
|
||||
return (h);
|
||||
}
|
||||
|
||||
void
|
||||
fmd_serd_hash_create(fmd_serd_hash_t *shp)
|
||||
{
|
||||
shp->sh_hashlen = FMD_STR_BUCKETS;
|
||||
shp->sh_hash = calloc(shp->sh_hashlen, sizeof (void *));
|
||||
shp->sh_count = 0;
|
||||
}
|
||||
|
||||
void
|
||||
fmd_serd_hash_destroy(fmd_serd_hash_t *shp)
|
||||
{
|
||||
fmd_serd_eng_t *sgp, *ngp;
|
||||
uint_t i;
|
||||
|
||||
for (i = 0; i < shp->sh_hashlen; i++) {
|
||||
for (sgp = shp->sh_hash[i]; sgp != NULL; sgp = ngp) {
|
||||
ngp = sgp->sg_next;
|
||||
fmd_serd_eng_free(sgp);
|
||||
}
|
||||
}
|
||||
|
||||
free(shp->sh_hash);
|
||||
bzero(shp, sizeof (fmd_serd_hash_t));
|
||||
}
|
||||
|
||||
void
|
||||
fmd_serd_hash_apply(fmd_serd_hash_t *shp, fmd_serd_eng_f *func, void *arg)
|
||||
{
|
||||
fmd_serd_eng_t *sgp;
|
||||
uint_t i;
|
||||
|
||||
for (i = 0; i < shp->sh_hashlen; i++) {
|
||||
for (sgp = shp->sh_hash[i]; sgp != NULL; sgp = sgp->sg_next)
|
||||
func(sgp, arg);
|
||||
}
|
||||
}
|
||||
|
||||
fmd_serd_eng_t *
|
||||
fmd_serd_eng_insert(fmd_serd_hash_t *shp, const char *name,
|
||||
uint_t n, hrtime_t t)
|
||||
{
|
||||
uint_t h = fmd_strhash(name) % shp->sh_hashlen;
|
||||
fmd_serd_eng_t *sgp = fmd_serd_eng_alloc(name, n, t);
|
||||
|
||||
serd_log_msg(" SERD Engine: inserting %s N %d T %llu",
|
||||
name, (int)n, (long long unsigned)t);
|
||||
|
||||
sgp->sg_next = shp->sh_hash[h];
|
||||
shp->sh_hash[h] = sgp;
|
||||
shp->sh_count++;
|
||||
|
||||
return (sgp);
|
||||
}
|
||||
|
||||
fmd_serd_eng_t *
|
||||
fmd_serd_eng_lookup(fmd_serd_hash_t *shp, const char *name)
|
||||
{
|
||||
uint_t h = fmd_strhash(name) % shp->sh_hashlen;
|
||||
fmd_serd_eng_t *sgp;
|
||||
|
||||
for (sgp = shp->sh_hash[h]; sgp != NULL; sgp = sgp->sg_next) {
|
||||
if (strcmp(name, sgp->sg_name) == 0)
|
||||
return (sgp);
|
||||
}
|
||||
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
void
|
||||
fmd_serd_eng_delete(fmd_serd_hash_t *shp, const char *name)
|
||||
{
|
||||
uint_t h = fmd_strhash(name) % shp->sh_hashlen;
|
||||
fmd_serd_eng_t *sgp, **pp = &shp->sh_hash[h];
|
||||
|
||||
serd_log_msg(" SERD Engine: deleting %s", name);
|
||||
|
||||
for (sgp = *pp; sgp != NULL; sgp = sgp->sg_next) {
|
||||
if (strcmp(sgp->sg_name, name) != 0)
|
||||
pp = &sgp->sg_next;
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
if (sgp != NULL) {
|
||||
*pp = sgp->sg_next;
|
||||
fmd_serd_eng_free(sgp);
|
||||
assert(shp->sh_count != 0);
|
||||
shp->sh_count--;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
fmd_serd_eng_discard(fmd_serd_eng_t *sgp, fmd_serd_elem_t *sep)
|
||||
{
|
||||
list_remove(&sgp->sg_list, sep);
|
||||
sgp->sg_count--;
|
||||
|
||||
serd_log_msg(" SERD Engine: discarding %s, %d remaining",
|
||||
sgp->sg_name, (int)sgp->sg_count);
|
||||
|
||||
free(sep);
|
||||
}
|
||||
|
||||
int
|
||||
fmd_serd_eng_record(fmd_serd_eng_t *sgp, hrtime_t hrt)
|
||||
{
|
||||
fmd_serd_elem_t *sep, *oep;
|
||||
|
||||
/*
|
||||
* If the fired flag is already set, return false and discard the
|
||||
* event. This means that the caller will only see the engine "fire"
|
||||
* once until fmd_serd_eng_reset() is called. The fmd_serd_eng_fired()
|
||||
* function can also be used in combination with fmd_serd_eng_record().
|
||||
*/
|
||||
if (sgp->sg_flags & FMD_SERD_FIRED) {
|
||||
serd_log_msg(" SERD Engine: record %s already fired!",
|
||||
sgp->sg_name);
|
||||
return (FMD_B_FALSE);
|
||||
}
|
||||
|
||||
while (sgp->sg_count >= sgp->sg_n)
|
||||
fmd_serd_eng_discard(sgp, list_tail(&sgp->sg_list));
|
||||
|
||||
sep = malloc(sizeof (fmd_serd_elem_t));
|
||||
sep->se_hrt = hrt;
|
||||
|
||||
list_insert_head(&sgp->sg_list, sep);
|
||||
sgp->sg_count++;
|
||||
|
||||
serd_log_msg(" SERD Engine: recording %s of %d (%llu)",
|
||||
sgp->sg_name, (int)sgp->sg_count, (long long unsigned)hrt);
|
||||
|
||||
/*
|
||||
* Pick up the oldest element pointer for comparison to 'sep'. We must
|
||||
* do this after adding 'sep' because 'oep' and 'sep' can be the same.
|
||||
*/
|
||||
oep = list_tail(&sgp->sg_list);
|
||||
|
||||
if (sgp->sg_count >= sgp->sg_n &&
|
||||
fmd_event_delta(oep->se_hrt, sep->se_hrt) <= sgp->sg_t) {
|
||||
sgp->sg_flags |= FMD_SERD_FIRED | FMD_SERD_DIRTY;
|
||||
serd_log_msg(" SERD Engine: fired %s", sgp->sg_name);
|
||||
return (FMD_B_TRUE);
|
||||
}
|
||||
|
||||
sgp->sg_flags |= FMD_SERD_DIRTY;
|
||||
return (FMD_B_FALSE);
|
||||
}
|
||||
|
||||
int
|
||||
fmd_serd_eng_fired(fmd_serd_eng_t *sgp)
|
||||
{
|
||||
return (sgp->sg_flags & FMD_SERD_FIRED);
|
||||
}
|
||||
|
||||
int
|
||||
fmd_serd_eng_empty(fmd_serd_eng_t *sgp)
|
||||
{
|
||||
return (sgp->sg_count == 0);
|
||||
}
|
||||
|
||||
void
|
||||
fmd_serd_eng_reset(fmd_serd_eng_t *sgp)
|
||||
{
|
||||
serd_log_msg(" SERD Engine: resetting %s", sgp->sg_name);
|
||||
|
||||
while (sgp->sg_count != 0)
|
||||
fmd_serd_eng_discard(sgp, list_head(&sgp->sg_list));
|
||||
|
||||
sgp->sg_flags &= ~FMD_SERD_FIRED;
|
||||
sgp->sg_flags |= FMD_SERD_DIRTY;
|
||||
}
|
||||
|
||||
void
|
||||
fmd_serd_eng_gc(fmd_serd_eng_t *sgp)
|
||||
{
|
||||
fmd_serd_elem_t *sep, *nep;
|
||||
hrtime_t hrt;
|
||||
|
||||
if (sgp->sg_count == 0 || (sgp->sg_flags & FMD_SERD_FIRED))
|
||||
return; /* no garbage collection needed if empty or fired */
|
||||
|
||||
sep = list_head(&sgp->sg_list);
|
||||
if (sep == NULL)
|
||||
return;
|
||||
|
||||
hrt = sep->se_hrt - sgp->sg_t;
|
||||
|
||||
for (sep = list_head(&sgp->sg_list); sep != NULL; sep = nep) {
|
||||
if (sep->se_hrt >= hrt)
|
||||
break; /* sep and subsequent events are all within T */
|
||||
|
||||
nep = list_next(&sgp->sg_list, sep);
|
||||
fmd_serd_eng_discard(sgp, sep);
|
||||
sgp->sg_flags |= FMD_SERD_DIRTY;
|
||||
}
|
||||
}
|
86
sys/contrib/openzfs/cmd/zed/agents/fmd_serd.h
Normal file
86
sys/contrib/openzfs/cmd/zed/agents/fmd_serd.h
Normal file
@ -0,0 +1,86 @@
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License, Version 1.0 only
|
||||
* (the "License"). You may not use this file except in compliance
|
||||
* with the License.
|
||||
*
|
||||
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||
* or http://www.opensolaris.org/os/licensing.
|
||||
* See the License for the specific language governing permissions
|
||||
* and limitations under the License.
|
||||
*
|
||||
* When distributing Covered Code, include this CDDL HEADER in each
|
||||
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||
* If applicable, add the following below this CDDL HEADER, with the
|
||||
* fields enclosed by brackets "[]" replaced with your own identifying
|
||||
* information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
*
|
||||
* CDDL HEADER END
|
||||
*/
|
||||
/*
|
||||
* Copyright 2004 Sun Microsystems, Inc. All rights reserved.
|
||||
* Use is subject to license terms.
|
||||
*
|
||||
* Copyright (c) 2016, Intel Corporation.
|
||||
*/
|
||||
|
||||
#ifndef _FMD_SERD_H
|
||||
#define _FMD_SERD_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <sys/list.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
typedef struct fmd_serd_elem {
|
||||
list_node_t se_list; /* linked list forward/back pointers */
|
||||
hrtime_t se_hrt; /* upper bound on event hrtime */
|
||||
} fmd_serd_elem_t;
|
||||
|
||||
typedef struct fmd_serd_eng {
|
||||
char *sg_name; /* string name for this engine */
|
||||
struct fmd_serd_eng *sg_next; /* next engine on hash chain */
|
||||
list_t sg_list; /* list of fmd_serd_elem_t's */
|
||||
uint_t sg_count; /* count of events in sg_list */
|
||||
uint_t sg_flags; /* engine flags (see below) */
|
||||
uint_t sg_n; /* engine N parameter (event count) */
|
||||
hrtime_t sg_t; /* engine T parameter (nanoseconds) */
|
||||
} fmd_serd_eng_t;
|
||||
|
||||
#define FMD_SERD_FIRED 0x1 /* error rate has exceeded threshold */
|
||||
#define FMD_SERD_DIRTY 0x2 /* engine needs to be checkpointed */
|
||||
|
||||
typedef void fmd_serd_eng_f(fmd_serd_eng_t *, void *);
|
||||
|
||||
typedef struct fmd_serd_hash {
|
||||
fmd_serd_eng_t **sh_hash; /* hash bucket array for buffers */
|
||||
uint_t sh_hashlen; /* length of hash bucket array */
|
||||
uint_t sh_count; /* count of engines in hash */
|
||||
} fmd_serd_hash_t;
|
||||
|
||||
extern void fmd_serd_hash_create(fmd_serd_hash_t *);
|
||||
extern void fmd_serd_hash_destroy(fmd_serd_hash_t *);
|
||||
extern void fmd_serd_hash_apply(fmd_serd_hash_t *, fmd_serd_eng_f *, void *);
|
||||
|
||||
extern fmd_serd_eng_t *fmd_serd_eng_insert(fmd_serd_hash_t *,
|
||||
const char *, uint32_t, hrtime_t);
|
||||
|
||||
extern fmd_serd_eng_t *fmd_serd_eng_lookup(fmd_serd_hash_t *, const char *);
|
||||
extern void fmd_serd_eng_delete(fmd_serd_hash_t *, const char *);
|
||||
|
||||
extern int fmd_serd_eng_record(fmd_serd_eng_t *, hrtime_t);
|
||||
extern int fmd_serd_eng_fired(fmd_serd_eng_t *);
|
||||
extern int fmd_serd_eng_empty(fmd_serd_eng_t *);
|
||||
|
||||
extern void fmd_serd_eng_reset(fmd_serd_eng_t *);
|
||||
extern void fmd_serd_eng_gc(fmd_serd_eng_t *);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* _FMD_SERD_H */
|
422
sys/contrib/openzfs/cmd/zed/agents/zfs_agents.c
Normal file
422
sys/contrib/openzfs/cmd/zed/agents/zfs_agents.c
Normal file
@ -0,0 +1,422 @@
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License Version 1.0 (CDDL-1.0).
|
||||
* You can obtain a copy of the license from the top-level file
|
||||
* "OPENSOLARIS.LICENSE" or at <http://opensource.org/licenses/CDDL-1.0>.
|
||||
* You may not use this file except in compliance with the license.
|
||||
*
|
||||
* CDDL HEADER END
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (c) 2016, Intel Corporation.
|
||||
* Copyright (c) 2018, loli10K <ezomori.nozomu@gmail.com>
|
||||
*/
|
||||
|
||||
#include <libnvpair.h>
|
||||
#include <libzfs.h>
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/list.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/sysevent/eventdefs.h>
|
||||
#include <sys/sysevent/dev.h>
|
||||
#include <sys/fm/protocol.h>
|
||||
#include <sys/fm/fs/zfs.h>
|
||||
#include <pthread.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "zfs_agents.h"
|
||||
#include "fmd_api.h"
|
||||
#include "../zed_log.h"
|
||||
|
||||
/*
|
||||
* agent dispatch code
|
||||
*/
|
||||
|
||||
static pthread_mutex_t agent_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
static pthread_cond_t agent_cond = PTHREAD_COND_INITIALIZER;
|
||||
static list_t agent_events; /* list of pending events */
|
||||
static int agent_exiting;
|
||||
|
||||
typedef struct agent_event {
|
||||
char ae_class[64];
|
||||
char ae_subclass[32];
|
||||
nvlist_t *ae_nvl;
|
||||
list_node_t ae_node;
|
||||
} agent_event_t;
|
||||
|
||||
pthread_t g_agents_tid;
|
||||
|
||||
libzfs_handle_t *g_zfs_hdl;
|
||||
|
||||
/* guid search data */
|
||||
typedef enum device_type {
|
||||
DEVICE_TYPE_L2ARC, /* l2arc device */
|
||||
DEVICE_TYPE_SPARE, /* spare device */
|
||||
DEVICE_TYPE_PRIMARY /* any primary pool storage device */
|
||||
} device_type_t;
|
||||
|
||||
typedef struct guid_search {
|
||||
uint64_t gs_pool_guid;
|
||||
uint64_t gs_vdev_guid;
|
||||
char *gs_devid;
|
||||
device_type_t gs_vdev_type;
|
||||
uint64_t gs_vdev_expandtime; /* vdev expansion time */
|
||||
} guid_search_t;
|
||||
|
||||
/*
|
||||
* Walks the vdev tree recursively looking for a matching devid.
|
||||
* Returns B_TRUE as soon as a matching device is found, B_FALSE otherwise.
|
||||
*/
|
||||
static boolean_t
|
||||
zfs_agent_iter_vdev(zpool_handle_t *zhp, nvlist_t *nvl, void *arg)
|
||||
{
|
||||
guid_search_t *gsp = arg;
|
||||
char *path = NULL;
|
||||
uint_t c, children;
|
||||
nvlist_t **child;
|
||||
|
||||
/*
|
||||
* First iterate over any children.
|
||||
*/
|
||||
if (nvlist_lookup_nvlist_array(nvl, ZPOOL_CONFIG_CHILDREN,
|
||||
&child, &children) == 0) {
|
||||
for (c = 0; c < children; c++) {
|
||||
if (zfs_agent_iter_vdev(zhp, child[c], gsp)) {
|
||||
gsp->gs_vdev_type = DEVICE_TYPE_PRIMARY;
|
||||
return (B_TRUE);
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Iterate over any spares and cache devices
|
||||
*/
|
||||
if (nvlist_lookup_nvlist_array(nvl, ZPOOL_CONFIG_SPARES,
|
||||
&child, &children) == 0) {
|
||||
for (c = 0; c < children; c++) {
|
||||
if (zfs_agent_iter_vdev(zhp, child[c], gsp)) {
|
||||
gsp->gs_vdev_type = DEVICE_TYPE_L2ARC;
|
||||
return (B_TRUE);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (nvlist_lookup_nvlist_array(nvl, ZPOOL_CONFIG_L2CACHE,
|
||||
&child, &children) == 0) {
|
||||
for (c = 0; c < children; c++) {
|
||||
if (zfs_agent_iter_vdev(zhp, child[c], gsp)) {
|
||||
gsp->gs_vdev_type = DEVICE_TYPE_SPARE;
|
||||
return (B_TRUE);
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
* On a devid match, grab the vdev guid and expansion time, if any.
|
||||
*/
|
||||
if (gsp->gs_devid != NULL &&
|
||||
(nvlist_lookup_string(nvl, ZPOOL_CONFIG_DEVID, &path) == 0) &&
|
||||
(strcmp(gsp->gs_devid, path) == 0)) {
|
||||
(void) nvlist_lookup_uint64(nvl, ZPOOL_CONFIG_GUID,
|
||||
&gsp->gs_vdev_guid);
|
||||
(void) nvlist_lookup_uint64(nvl, ZPOOL_CONFIG_EXPANSION_TIME,
|
||||
&gsp->gs_vdev_expandtime);
|
||||
return (B_TRUE);
|
||||
}
|
||||
|
||||
return (B_FALSE);
|
||||
}
|
||||
|
||||
static int
|
||||
zfs_agent_iter_pool(zpool_handle_t *zhp, void *arg)
|
||||
{
|
||||
guid_search_t *gsp = arg;
|
||||
nvlist_t *config, *nvl;
|
||||
|
||||
/*
|
||||
* For each vdev in this pool, look for a match by devid
|
||||
*/
|
||||
if ((config = zpool_get_config(zhp, NULL)) != NULL) {
|
||||
if (nvlist_lookup_nvlist(config, ZPOOL_CONFIG_VDEV_TREE,
|
||||
&nvl) == 0) {
|
||||
(void) zfs_agent_iter_vdev(zhp, nvl, gsp);
|
||||
}
|
||||
}
|
||||
/*
|
||||
* if a match was found then grab the pool guid
|
||||
*/
|
||||
if (gsp->gs_vdev_guid) {
|
||||
(void) nvlist_lookup_uint64(config, ZPOOL_CONFIG_POOL_GUID,
|
||||
&gsp->gs_pool_guid);
|
||||
}
|
||||
|
||||
zpool_close(zhp);
|
||||
return (gsp->gs_vdev_guid != 0);
|
||||
}
|
||||
|
||||
void
|
||||
zfs_agent_post_event(const char *class, const char *subclass, nvlist_t *nvl)
|
||||
{
|
||||
agent_event_t *event;
|
||||
|
||||
if (subclass == NULL)
|
||||
subclass = "";
|
||||
|
||||
event = malloc(sizeof (agent_event_t));
|
||||
if (event == NULL || nvlist_dup(nvl, &event->ae_nvl, 0) != 0) {
|
||||
if (event)
|
||||
free(event);
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp(class, "sysevent.fs.zfs.vdev_check") == 0) {
|
||||
class = EC_ZFS;
|
||||
subclass = ESC_ZFS_VDEV_CHECK;
|
||||
}
|
||||
|
||||
/*
|
||||
* On ZFS on Linux, we don't get the expected FM_RESOURCE_REMOVED
|
||||
* ereport from vdev_disk layer after a hot unplug. Fortunately we
|
||||
* get a EC_DEV_REMOVE from our disk monitor and it is a suitable
|
||||
* proxy so we remap it here for the benefit of the diagnosis engine.
|
||||
*/
|
||||
if ((strcmp(class, EC_DEV_REMOVE) == 0) &&
|
||||
(strcmp(subclass, ESC_DISK) == 0) &&
|
||||
(nvlist_exists(nvl, ZFS_EV_VDEV_GUID) ||
|
||||
nvlist_exists(nvl, DEV_IDENTIFIER))) {
|
||||
nvlist_t *payload = event->ae_nvl;
|
||||
struct timeval tv;
|
||||
int64_t tod[2];
|
||||
uint64_t pool_guid = 0, vdev_guid = 0;
|
||||
guid_search_t search = { 0 };
|
||||
device_type_t devtype = DEVICE_TYPE_PRIMARY;
|
||||
|
||||
class = "resource.fs.zfs.removed";
|
||||
subclass = "";
|
||||
|
||||
(void) nvlist_add_string(payload, FM_CLASS, class);
|
||||
(void) nvlist_lookup_uint64(nvl, ZFS_EV_POOL_GUID, &pool_guid);
|
||||
(void) nvlist_lookup_uint64(nvl, ZFS_EV_VDEV_GUID, &vdev_guid);
|
||||
|
||||
(void) gettimeofday(&tv, NULL);
|
||||
tod[0] = tv.tv_sec;
|
||||
tod[1] = tv.tv_usec;
|
||||
(void) nvlist_add_int64_array(payload, FM_EREPORT_TIME, tod, 2);
|
||||
|
||||
/*
|
||||
* For multipath, spare and l2arc devices ZFS_EV_VDEV_GUID or
|
||||
* ZFS_EV_POOL_GUID may be missing so find them.
|
||||
*/
|
||||
(void) nvlist_lookup_string(nvl, DEV_IDENTIFIER,
|
||||
&search.gs_devid);
|
||||
(void) zpool_iter(g_zfs_hdl, zfs_agent_iter_pool, &search);
|
||||
pool_guid = search.gs_pool_guid;
|
||||
vdev_guid = search.gs_vdev_guid;
|
||||
devtype = search.gs_vdev_type;
|
||||
|
||||
/*
|
||||
* We want to avoid reporting "remove" events coming from
|
||||
* libudev for VDEVs which were expanded recently (10s) and
|
||||
* avoid activating spares in response to partitions being
|
||||
* deleted and created in rapid succession.
|
||||
*/
|
||||
if (search.gs_vdev_expandtime != 0 &&
|
||||
search.gs_vdev_expandtime + 10 > tv.tv_sec) {
|
||||
zed_log_msg(LOG_INFO, "agent post event: ignoring '%s' "
|
||||
"for recently expanded device '%s'", EC_DEV_REMOVE,
|
||||
search.gs_devid);
|
||||
goto out;
|
||||
}
|
||||
|
||||
(void) nvlist_add_uint64(payload,
|
||||
FM_EREPORT_PAYLOAD_ZFS_POOL_GUID, pool_guid);
|
||||
(void) nvlist_add_uint64(payload,
|
||||
FM_EREPORT_PAYLOAD_ZFS_VDEV_GUID, vdev_guid);
|
||||
switch (devtype) {
|
||||
case DEVICE_TYPE_L2ARC:
|
||||
(void) nvlist_add_string(payload,
|
||||
FM_EREPORT_PAYLOAD_ZFS_VDEV_TYPE,
|
||||
VDEV_TYPE_L2CACHE);
|
||||
break;
|
||||
case DEVICE_TYPE_SPARE:
|
||||
(void) nvlist_add_string(payload,
|
||||
FM_EREPORT_PAYLOAD_ZFS_VDEV_TYPE, VDEV_TYPE_SPARE);
|
||||
break;
|
||||
case DEVICE_TYPE_PRIMARY:
|
||||
(void) nvlist_add_string(payload,
|
||||
FM_EREPORT_PAYLOAD_ZFS_VDEV_TYPE, VDEV_TYPE_DISK);
|
||||
break;
|
||||
}
|
||||
|
||||
zed_log_msg(LOG_INFO, "agent post event: mapping '%s' to '%s'",
|
||||
EC_DEV_REMOVE, class);
|
||||
}
|
||||
|
||||
(void) strlcpy(event->ae_class, class, sizeof (event->ae_class));
|
||||
(void) strlcpy(event->ae_subclass, subclass,
|
||||
sizeof (event->ae_subclass));
|
||||
|
||||
(void) pthread_mutex_lock(&agent_lock);
|
||||
list_insert_tail(&agent_events, event);
|
||||
(void) pthread_mutex_unlock(&agent_lock);
|
||||
|
||||
out:
|
||||
(void) pthread_cond_signal(&agent_cond);
|
||||
}
|
||||
|
||||
static void
|
||||
zfs_agent_dispatch(const char *class, const char *subclass, nvlist_t *nvl)
|
||||
{
|
||||
/*
|
||||
* The diagnosis engine subscribes to the following events.
|
||||
* On illumos these subscriptions reside in:
|
||||
* /usr/lib/fm/fmd/plugins/zfs-diagnosis.conf
|
||||
*/
|
||||
if (strstr(class, "ereport.fs.zfs.") != NULL ||
|
||||
strstr(class, "resource.fs.zfs.") != NULL ||
|
||||
strcmp(class, "sysevent.fs.zfs.vdev_remove") == 0 ||
|
||||
strcmp(class, "sysevent.fs.zfs.vdev_remove_dev") == 0 ||
|
||||
strcmp(class, "sysevent.fs.zfs.pool_destroy") == 0) {
|
||||
fmd_module_recv(fmd_module_hdl("zfs-diagnosis"), nvl, class);
|
||||
}
|
||||
|
||||
/*
|
||||
* The retire agent subscribes to the following events.
|
||||
* On illumos these subscriptions reside in:
|
||||
* /usr/lib/fm/fmd/plugins/zfs-retire.conf
|
||||
*
|
||||
* NOTE: faults events come directly from our diagnosis engine
|
||||
* and will not pass through the zfs kernel module.
|
||||
*/
|
||||
if (strcmp(class, FM_LIST_SUSPECT_CLASS) == 0 ||
|
||||
strcmp(class, "resource.fs.zfs.removed") == 0 ||
|
||||
strcmp(class, "resource.fs.zfs.statechange") == 0 ||
|
||||
strcmp(class, "sysevent.fs.zfs.vdev_remove") == 0) {
|
||||
fmd_module_recv(fmd_module_hdl("zfs-retire"), nvl, class);
|
||||
}
|
||||
|
||||
/*
|
||||
* The SLM module only consumes disk events and vdev check events
|
||||
*
|
||||
* NOTE: disk events come directly from disk monitor and will
|
||||
* not pass through the zfs kernel module.
|
||||
*/
|
||||
if (strstr(class, "EC_dev_") != NULL ||
|
||||
strcmp(class, EC_ZFS) == 0) {
|
||||
(void) zfs_slm_event(class, subclass, nvl);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Events are consumed and dispatched from this thread
|
||||
* An agent can also post an event so event list lock
|
||||
* is not held when calling an agent.
|
||||
* One event is consumed at a time.
|
||||
*/
|
||||
static void *
|
||||
zfs_agent_consumer_thread(void *arg)
|
||||
{
|
||||
for (;;) {
|
||||
agent_event_t *event;
|
||||
|
||||
(void) pthread_mutex_lock(&agent_lock);
|
||||
|
||||
/* wait for an event to show up */
|
||||
while (!agent_exiting && list_is_empty(&agent_events))
|
||||
(void) pthread_cond_wait(&agent_cond, &agent_lock);
|
||||
|
||||
if (agent_exiting) {
|
||||
(void) pthread_mutex_unlock(&agent_lock);
|
||||
zed_log_msg(LOG_INFO, "zfs_agent_consumer_thread: "
|
||||
"exiting");
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
if ((event = (list_head(&agent_events))) != NULL) {
|
||||
list_remove(&agent_events, event);
|
||||
|
||||
(void) pthread_mutex_unlock(&agent_lock);
|
||||
|
||||
/* dispatch to all event subscribers */
|
||||
zfs_agent_dispatch(event->ae_class, event->ae_subclass,
|
||||
event->ae_nvl);
|
||||
|
||||
nvlist_free(event->ae_nvl);
|
||||
free(event);
|
||||
continue;
|
||||
}
|
||||
|
||||
(void) pthread_mutex_unlock(&agent_lock);
|
||||
}
|
||||
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
void
|
||||
zfs_agent_init(libzfs_handle_t *zfs_hdl)
|
||||
{
|
||||
fmd_hdl_t *hdl;
|
||||
|
||||
g_zfs_hdl = zfs_hdl;
|
||||
|
||||
if (zfs_slm_init() != 0)
|
||||
zed_log_die("Failed to initialize zfs slm");
|
||||
zed_log_msg(LOG_INFO, "Add Agent: init");
|
||||
|
||||
hdl = fmd_module_hdl("zfs-diagnosis");
|
||||
_zfs_diagnosis_init(hdl);
|
||||
if (!fmd_module_initialized(hdl))
|
||||
zed_log_die("Failed to initialize zfs diagnosis");
|
||||
|
||||
hdl = fmd_module_hdl("zfs-retire");
|
||||
_zfs_retire_init(hdl);
|
||||
if (!fmd_module_initialized(hdl))
|
||||
zed_log_die("Failed to initialize zfs retire");
|
||||
|
||||
list_create(&agent_events, sizeof (agent_event_t),
|
||||
offsetof(struct agent_event, ae_node));
|
||||
|
||||
if (pthread_create(&g_agents_tid, NULL, zfs_agent_consumer_thread,
|
||||
NULL) != 0) {
|
||||
list_destroy(&agent_events);
|
||||
zed_log_die("Failed to initialize agents");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
zfs_agent_fini(void)
|
||||
{
|
||||
fmd_hdl_t *hdl;
|
||||
agent_event_t *event;
|
||||
|
||||
agent_exiting = 1;
|
||||
(void) pthread_cond_signal(&agent_cond);
|
||||
|
||||
/* wait for zfs_enum_pools thread to complete */
|
||||
(void) pthread_join(g_agents_tid, NULL);
|
||||
|
||||
/* drain any pending events */
|
||||
while ((event = (list_head(&agent_events))) != NULL) {
|
||||
list_remove(&agent_events, event);
|
||||
nvlist_free(event->ae_nvl);
|
||||
free(event);
|
||||
}
|
||||
|
||||
list_destroy(&agent_events);
|
||||
|
||||
if ((hdl = fmd_module_hdl("zfs-retire")) != NULL) {
|
||||
_zfs_retire_fini(hdl);
|
||||
fmd_hdl_unregister(hdl);
|
||||
}
|
||||
if ((hdl = fmd_module_hdl("zfs-diagnosis")) != NULL) {
|
||||
_zfs_diagnosis_fini(hdl);
|
||||
fmd_hdl_unregister(hdl);
|
||||
}
|
||||
|
||||
zed_log_msg(LOG_INFO, "Add Agent: fini");
|
||||
zfs_slm_fini();
|
||||
|
||||
g_zfs_hdl = NULL;
|
||||
}
|
46
sys/contrib/openzfs/cmd/zed/agents/zfs_agents.h
Normal file
46
sys/contrib/openzfs/cmd/zed/agents/zfs_agents.h
Normal file
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License Version 1.0 (CDDL-1.0).
|
||||
* You can obtain a copy of the license from the top-level file
|
||||
* "OPENSOLARIS.LICENSE" or at <http://opensource.org/licenses/CDDL-1.0>.
|
||||
* You may not use this file except in compliance with the license.
|
||||
*
|
||||
* CDDL HEADER END
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (c) 2016, Intel Corporation.
|
||||
*/
|
||||
|
||||
#ifndef ZFS_AGENTS_H
|
||||
#define ZFS_AGENTS_H
|
||||
|
||||
#include <libzfs.h>
|
||||
#include <libnvpair.h>
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Agent abstraction presented to ZED
|
||||
*/
|
||||
extern void zfs_agent_init(libzfs_handle_t *);
|
||||
extern void zfs_agent_fini(void);
|
||||
extern void zfs_agent_post_event(const char *, const char *, nvlist_t *);
|
||||
|
||||
/*
|
||||
* ZFS Sysevent Linkable Module (SLM)
|
||||
*/
|
||||
extern int zfs_slm_init(void);
|
||||
extern void zfs_slm_fini(void);
|
||||
extern void zfs_slm_event(const char *, const char *, nvlist_t *);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* !ZFS_AGENTS_H */
|
981
sys/contrib/openzfs/cmd/zed/agents/zfs_diagnosis.c
Normal file
981
sys/contrib/openzfs/cmd/zed/agents/zfs_diagnosis.c
Normal file
@ -0,0 +1,981 @@
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License (the "License").
|
||||
* You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||
* or http://www.opensolaris.org/os/licensing.
|
||||
* See the License for the specific language governing permissions
|
||||
* and limitations under the License.
|
||||
*
|
||||
* When distributing Covered Code, include this CDDL HEADER in each
|
||||
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||
* If applicable, add the following below this CDDL HEADER, with the
|
||||
* fields enclosed by brackets "[]" replaced with your own identifying
|
||||
* information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
*
|
||||
* CDDL HEADER END
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright 2015 Nexenta Systems, Inc. All rights reserved.
|
||||
* Copyright (c) 2016, Intel Corporation.
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include <libuutil.h>
|
||||
#include <libzfs.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/fs/zfs.h>
|
||||
#include <sys/fm/protocol.h>
|
||||
#include <sys/fm/fs/zfs.h>
|
||||
|
||||
#include "zfs_agents.h"
|
||||
#include "fmd_api.h"
|
||||
|
||||
/*
|
||||
* Our serd engines are named 'zfs_<pool_guid>_<vdev_guid>_{checksum,io}'. This
|
||||
* #define reserves enough space for two 64-bit hex values plus the length of
|
||||
* the longest string.
|
||||
*/
|
||||
#define MAX_SERDLEN (16 * 2 + sizeof ("zfs___checksum"))
|
||||
|
||||
/*
|
||||
* On-disk case structure. This must maintain backwards compatibility with
|
||||
* previous versions of the DE. By default, any members appended to the end
|
||||
* will be filled with zeros if they don't exist in a previous version.
|
||||
*/
|
||||
typedef struct zfs_case_data {
|
||||
uint64_t zc_version;
|
||||
uint64_t zc_ena;
|
||||
uint64_t zc_pool_guid;
|
||||
uint64_t zc_vdev_guid;
|
||||
int zc_pool_state;
|
||||
char zc_serd_checksum[MAX_SERDLEN];
|
||||
char zc_serd_io[MAX_SERDLEN];
|
||||
int zc_has_remove_timer;
|
||||
} zfs_case_data_t;
|
||||
|
||||
/*
|
||||
* Time-of-day
|
||||
*/
|
||||
typedef struct er_timeval {
|
||||
uint64_t ertv_sec;
|
||||
uint64_t ertv_nsec;
|
||||
} er_timeval_t;
|
||||
|
||||
/*
|
||||
* In-core case structure.
|
||||
*/
|
||||
typedef struct zfs_case {
|
||||
boolean_t zc_present;
|
||||
uint32_t zc_version;
|
||||
zfs_case_data_t zc_data;
|
||||
fmd_case_t *zc_case;
|
||||
uu_list_node_t zc_node;
|
||||
id_t zc_remove_timer;
|
||||
char *zc_fru;
|
||||
er_timeval_t zc_when;
|
||||
} zfs_case_t;
|
||||
|
||||
#define CASE_DATA "data"
|
||||
#define CASE_FRU "fru"
|
||||
#define CASE_DATA_VERSION_INITIAL 1
|
||||
#define CASE_DATA_VERSION_SERD 2
|
||||
|
||||
typedef struct zfs_de_stats {
|
||||
fmd_stat_t old_drops;
|
||||
fmd_stat_t dev_drops;
|
||||
fmd_stat_t vdev_drops;
|
||||
fmd_stat_t import_drops;
|
||||
fmd_stat_t resource_drops;
|
||||
} zfs_de_stats_t;
|
||||
|
||||
zfs_de_stats_t zfs_stats = {
|
||||
{ "old_drops", FMD_TYPE_UINT64, "ereports dropped (from before load)" },
|
||||
{ "dev_drops", FMD_TYPE_UINT64, "ereports dropped (dev during open)"},
|
||||
{ "vdev_drops", FMD_TYPE_UINT64, "ereports dropped (weird vdev types)"},
|
||||
{ "import_drops", FMD_TYPE_UINT64, "ereports dropped (during import)" },
|
||||
{ "resource_drops", FMD_TYPE_UINT64, "resource related ereports" }
|
||||
};
|
||||
|
||||
static hrtime_t zfs_remove_timeout;
|
||||
|
||||
uu_list_pool_t *zfs_case_pool;
|
||||
uu_list_t *zfs_cases;
|
||||
|
||||
#define ZFS_MAKE_RSRC(type) \
|
||||
FM_RSRC_CLASS "." ZFS_ERROR_CLASS "." type
|
||||
#define ZFS_MAKE_EREPORT(type) \
|
||||
FM_EREPORT_CLASS "." ZFS_ERROR_CLASS "." type
|
||||
|
||||
/*
|
||||
* Write out the persistent representation of an active case.
|
||||
*/
|
||||
static void
|
||||
zfs_case_serialize(fmd_hdl_t *hdl, zfs_case_t *zcp)
|
||||
{
|
||||
zcp->zc_data.zc_version = CASE_DATA_VERSION_SERD;
|
||||
}
|
||||
|
||||
/*
|
||||
* Read back the persistent representation of an active case.
|
||||
*/
|
||||
static zfs_case_t *
|
||||
zfs_case_unserialize(fmd_hdl_t *hdl, fmd_case_t *cp)
|
||||
{
|
||||
zfs_case_t *zcp;
|
||||
|
||||
zcp = fmd_hdl_zalloc(hdl, sizeof (zfs_case_t), FMD_SLEEP);
|
||||
zcp->zc_case = cp;
|
||||
|
||||
fmd_buf_read(hdl, cp, CASE_DATA, &zcp->zc_data,
|
||||
sizeof (zcp->zc_data));
|
||||
|
||||
if (zcp->zc_data.zc_version > CASE_DATA_VERSION_SERD) {
|
||||
fmd_hdl_free(hdl, zcp, sizeof (zfs_case_t));
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* fmd_buf_read() will have already zeroed out the remainder of the
|
||||
* buffer, so we don't have to do anything special if the version
|
||||
* doesn't include the SERD engine name.
|
||||
*/
|
||||
|
||||
if (zcp->zc_data.zc_has_remove_timer)
|
||||
zcp->zc_remove_timer = fmd_timer_install(hdl, zcp,
|
||||
NULL, zfs_remove_timeout);
|
||||
|
||||
uu_list_node_init(zcp, &zcp->zc_node, zfs_case_pool);
|
||||
(void) uu_list_insert_before(zfs_cases, NULL, zcp);
|
||||
|
||||
fmd_case_setspecific(hdl, cp, zcp);
|
||||
|
||||
return (zcp);
|
||||
}
|
||||
|
||||
/*
|
||||
* Iterate over any active cases. If any cases are associated with a pool or
|
||||
* vdev which is no longer present on the system, close the associated case.
|
||||
*/
|
||||
static void
|
||||
zfs_mark_vdev(uint64_t pool_guid, nvlist_t *vd, er_timeval_t *loaded)
|
||||
{
|
||||
uint64_t vdev_guid = 0;
|
||||
uint_t c, children;
|
||||
nvlist_t **child;
|
||||
zfs_case_t *zcp;
|
||||
|
||||
(void) nvlist_lookup_uint64(vd, ZPOOL_CONFIG_GUID, &vdev_guid);
|
||||
|
||||
/*
|
||||
* Mark any cases associated with this (pool, vdev) pair.
|
||||
*/
|
||||
for (zcp = uu_list_first(zfs_cases); zcp != NULL;
|
||||
zcp = uu_list_next(zfs_cases, zcp)) {
|
||||
if (zcp->zc_data.zc_pool_guid == pool_guid &&
|
||||
zcp->zc_data.zc_vdev_guid == vdev_guid) {
|
||||
zcp->zc_present = B_TRUE;
|
||||
zcp->zc_when = *loaded;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Iterate over all children.
|
||||
*/
|
||||
if (nvlist_lookup_nvlist_array(vd, ZPOOL_CONFIG_CHILDREN, &child,
|
||||
&children) == 0) {
|
||||
for (c = 0; c < children; c++)
|
||||
zfs_mark_vdev(pool_guid, child[c], loaded);
|
||||
}
|
||||
|
||||
if (nvlist_lookup_nvlist_array(vd, ZPOOL_CONFIG_L2CACHE, &child,
|
||||
&children) == 0) {
|
||||
for (c = 0; c < children; c++)
|
||||
zfs_mark_vdev(pool_guid, child[c], loaded);
|
||||
}
|
||||
|
||||
if (nvlist_lookup_nvlist_array(vd, ZPOOL_CONFIG_SPARES, &child,
|
||||
&children) == 0) {
|
||||
for (c = 0; c < children; c++)
|
||||
zfs_mark_vdev(pool_guid, child[c], loaded);
|
||||
}
|
||||
}
|
||||
|
||||
/*ARGSUSED*/
|
||||
static int
|
||||
zfs_mark_pool(zpool_handle_t *zhp, void *unused)
|
||||
{
|
||||
zfs_case_t *zcp;
|
||||
uint64_t pool_guid;
|
||||
uint64_t *tod;
|
||||
er_timeval_t loaded = { 0 };
|
||||
nvlist_t *config, *vd;
|
||||
uint_t nelem = 0;
|
||||
int ret;
|
||||
|
||||
pool_guid = zpool_get_prop_int(zhp, ZPOOL_PROP_GUID, NULL);
|
||||
/*
|
||||
* Mark any cases associated with just this pool.
|
||||
*/
|
||||
for (zcp = uu_list_first(zfs_cases); zcp != NULL;
|
||||
zcp = uu_list_next(zfs_cases, zcp)) {
|
||||
if (zcp->zc_data.zc_pool_guid == pool_guid &&
|
||||
zcp->zc_data.zc_vdev_guid == 0)
|
||||
zcp->zc_present = B_TRUE;
|
||||
}
|
||||
|
||||
if ((config = zpool_get_config(zhp, NULL)) == NULL) {
|
||||
zpool_close(zhp);
|
||||
return (-1);
|
||||
}
|
||||
|
||||
(void) nvlist_lookup_uint64_array(config, ZPOOL_CONFIG_LOADED_TIME,
|
||||
&tod, &nelem);
|
||||
if (nelem == 2) {
|
||||
loaded.ertv_sec = tod[0];
|
||||
loaded.ertv_nsec = tod[1];
|
||||
for (zcp = uu_list_first(zfs_cases); zcp != NULL;
|
||||
zcp = uu_list_next(zfs_cases, zcp)) {
|
||||
if (zcp->zc_data.zc_pool_guid == pool_guid &&
|
||||
zcp->zc_data.zc_vdev_guid == 0) {
|
||||
zcp->zc_when = loaded;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ret = nvlist_lookup_nvlist(config, ZPOOL_CONFIG_VDEV_TREE, &vd);
|
||||
if (ret) {
|
||||
zpool_close(zhp);
|
||||
return (-1);
|
||||
}
|
||||
|
||||
zfs_mark_vdev(pool_guid, vd, &loaded);
|
||||
|
||||
zpool_close(zhp);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
struct load_time_arg {
|
||||
uint64_t lt_guid;
|
||||
er_timeval_t *lt_time;
|
||||
boolean_t lt_found;
|
||||
};
|
||||
|
||||
static int
|
||||
zpool_find_load_time(zpool_handle_t *zhp, void *arg)
|
||||
{
|
||||
struct load_time_arg *lta = arg;
|
||||
uint64_t pool_guid;
|
||||
uint64_t *tod;
|
||||
nvlist_t *config;
|
||||
uint_t nelem;
|
||||
|
||||
if (lta->lt_found) {
|
||||
zpool_close(zhp);
|
||||
return (0);
|
||||
}
|
||||
|
||||
pool_guid = zpool_get_prop_int(zhp, ZPOOL_PROP_GUID, NULL);
|
||||
if (pool_guid != lta->lt_guid) {
|
||||
zpool_close(zhp);
|
||||
return (0);
|
||||
}
|
||||
|
||||
if ((config = zpool_get_config(zhp, NULL)) == NULL) {
|
||||
zpool_close(zhp);
|
||||
return (-1);
|
||||
}
|
||||
|
||||
if (nvlist_lookup_uint64_array(config, ZPOOL_CONFIG_LOADED_TIME,
|
||||
&tod, &nelem) == 0 && nelem == 2) {
|
||||
lta->lt_found = B_TRUE;
|
||||
lta->lt_time->ertv_sec = tod[0];
|
||||
lta->lt_time->ertv_nsec = tod[1];
|
||||
}
|
||||
|
||||
zpool_close(zhp);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
static void
|
||||
zfs_purge_cases(fmd_hdl_t *hdl)
|
||||
{
|
||||
zfs_case_t *zcp;
|
||||
uu_list_walk_t *walk;
|
||||
libzfs_handle_t *zhdl = fmd_hdl_getspecific(hdl);
|
||||
|
||||
/*
|
||||
* There is no way to open a pool by GUID, or lookup a vdev by GUID. No
|
||||
* matter what we do, we're going to have to stomach an O(vdevs * cases)
|
||||
* algorithm. In reality, both quantities are likely so small that
|
||||
* neither will matter. Given that iterating over pools is more
|
||||
* expensive than iterating over the in-memory case list, we opt for a
|
||||
* 'present' flag in each case that starts off cleared. We then iterate
|
||||
* over all pools, marking those that are still present, and removing
|
||||
* those that aren't found.
|
||||
*
|
||||
* Note that we could also construct an FMRI and rely on
|
||||
* fmd_nvl_fmri_present(), but this would end up doing the same search.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Mark the cases as not present.
|
||||
*/
|
||||
for (zcp = uu_list_first(zfs_cases); zcp != NULL;
|
||||
zcp = uu_list_next(zfs_cases, zcp))
|
||||
zcp->zc_present = B_FALSE;
|
||||
|
||||
/*
|
||||
* Iterate over all pools and mark the pools and vdevs found. If this
|
||||
* fails (most probably because we're out of memory), then don't close
|
||||
* any of the cases and we cannot be sure they are accurate.
|
||||
*/
|
||||
if (zpool_iter(zhdl, zfs_mark_pool, NULL) != 0)
|
||||
return;
|
||||
|
||||
/*
|
||||
* Remove those cases which were not found.
|
||||
*/
|
||||
walk = uu_list_walk_start(zfs_cases, UU_WALK_ROBUST);
|
||||
while ((zcp = uu_list_walk_next(walk)) != NULL) {
|
||||
if (!zcp->zc_present)
|
||||
fmd_case_close(hdl, zcp->zc_case);
|
||||
}
|
||||
uu_list_walk_end(walk);
|
||||
}
|
||||
|
||||
/*
|
||||
* Construct the name of a serd engine given the pool/vdev GUID and type (io or
|
||||
* checksum).
|
||||
*/
|
||||
static void
|
||||
zfs_serd_name(char *buf, uint64_t pool_guid, uint64_t vdev_guid,
|
||||
const char *type)
|
||||
{
|
||||
(void) snprintf(buf, MAX_SERDLEN, "zfs_%llx_%llx_%s",
|
||||
(long long unsigned int)pool_guid,
|
||||
(long long unsigned int)vdev_guid, type);
|
||||
}
|
||||
|
||||
/*
|
||||
* Solve a given ZFS case. This first checks to make sure the diagnosis is
|
||||
* still valid, as well as cleaning up any pending timer associated with the
|
||||
* case.
|
||||
*/
|
||||
static void
|
||||
zfs_case_solve(fmd_hdl_t *hdl, zfs_case_t *zcp, const char *faultname,
|
||||
boolean_t checkunusable)
|
||||
{
|
||||
nvlist_t *detector, *fault;
|
||||
boolean_t serialize;
|
||||
nvlist_t *fru = NULL;
|
||||
fmd_hdl_debug(hdl, "solving fault '%s'", faultname);
|
||||
|
||||
/*
|
||||
* Construct the detector from the case data. The detector is in the
|
||||
* ZFS scheme, and is either the pool or the vdev, depending on whether
|
||||
* this is a vdev or pool fault.
|
||||
*/
|
||||
detector = fmd_nvl_alloc(hdl, FMD_SLEEP);
|
||||
|
||||
(void) nvlist_add_uint8(detector, FM_VERSION, ZFS_SCHEME_VERSION0);
|
||||
(void) nvlist_add_string(detector, FM_FMRI_SCHEME, FM_FMRI_SCHEME_ZFS);
|
||||
(void) nvlist_add_uint64(detector, FM_FMRI_ZFS_POOL,
|
||||
zcp->zc_data.zc_pool_guid);
|
||||
if (zcp->zc_data.zc_vdev_guid != 0) {
|
||||
(void) nvlist_add_uint64(detector, FM_FMRI_ZFS_VDEV,
|
||||
zcp->zc_data.zc_vdev_guid);
|
||||
}
|
||||
|
||||
fault = fmd_nvl_create_fault(hdl, faultname, 100, detector,
|
||||
fru, detector);
|
||||
fmd_case_add_suspect(hdl, zcp->zc_case, fault);
|
||||
|
||||
nvlist_free(fru);
|
||||
|
||||
fmd_case_solve(hdl, zcp->zc_case);
|
||||
|
||||
serialize = B_FALSE;
|
||||
if (zcp->zc_data.zc_has_remove_timer) {
|
||||
fmd_timer_remove(hdl, zcp->zc_remove_timer);
|
||||
zcp->zc_data.zc_has_remove_timer = 0;
|
||||
serialize = B_TRUE;
|
||||
}
|
||||
if (serialize)
|
||||
zfs_case_serialize(hdl, zcp);
|
||||
|
||||
nvlist_free(detector);
|
||||
}
|
||||
|
||||
static boolean_t
|
||||
timeval_earlier(er_timeval_t *a, er_timeval_t *b)
|
||||
{
|
||||
return (a->ertv_sec < b->ertv_sec ||
|
||||
(a->ertv_sec == b->ertv_sec && a->ertv_nsec < b->ertv_nsec));
|
||||
}
|
||||
|
||||
/*ARGSUSED*/
|
||||
static void
|
||||
zfs_ereport_when(fmd_hdl_t *hdl, nvlist_t *nvl, er_timeval_t *when)
|
||||
{
|
||||
int64_t *tod;
|
||||
uint_t nelem;
|
||||
|
||||
if (nvlist_lookup_int64_array(nvl, FM_EREPORT_TIME, &tod,
|
||||
&nelem) == 0 && nelem == 2) {
|
||||
when->ertv_sec = tod[0];
|
||||
when->ertv_nsec = tod[1];
|
||||
} else {
|
||||
when->ertv_sec = when->ertv_nsec = UINT64_MAX;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Main fmd entry point.
|
||||
*/
|
||||
/*ARGSUSED*/
|
||||
static void
|
||||
zfs_fm_recv(fmd_hdl_t *hdl, fmd_event_t *ep, nvlist_t *nvl, const char *class)
|
||||
{
|
||||
zfs_case_t *zcp, *dcp;
|
||||
int32_t pool_state;
|
||||
uint64_t ena, pool_guid, vdev_guid;
|
||||
er_timeval_t pool_load;
|
||||
er_timeval_t er_when;
|
||||
nvlist_t *detector;
|
||||
boolean_t pool_found = B_FALSE;
|
||||
boolean_t isresource;
|
||||
char *type;
|
||||
|
||||
/*
|
||||
* We subscribe to notifications for vdev or pool removal. In these
|
||||
* cases, there may be cases that no longer apply. Purge any cases
|
||||
* that no longer apply.
|
||||
*/
|
||||
if (fmd_nvl_class_match(hdl, nvl, "sysevent.fs.zfs.*")) {
|
||||
fmd_hdl_debug(hdl, "purging orphaned cases from %s",
|
||||
strrchr(class, '.') + 1);
|
||||
zfs_purge_cases(hdl);
|
||||
zfs_stats.resource_drops.fmds_value.ui64++;
|
||||
return;
|
||||
}
|
||||
|
||||
isresource = fmd_nvl_class_match(hdl, nvl, "resource.fs.zfs.*");
|
||||
|
||||
if (isresource) {
|
||||
/*
|
||||
* For resources, we don't have a normal payload.
|
||||
*/
|
||||
if (nvlist_lookup_uint64(nvl, FM_EREPORT_PAYLOAD_ZFS_VDEV_GUID,
|
||||
&vdev_guid) != 0)
|
||||
pool_state = SPA_LOAD_OPEN;
|
||||
else
|
||||
pool_state = SPA_LOAD_NONE;
|
||||
detector = NULL;
|
||||
} else {
|
||||
(void) nvlist_lookup_nvlist(nvl,
|
||||
FM_EREPORT_DETECTOR, &detector);
|
||||
(void) nvlist_lookup_int32(nvl,
|
||||
FM_EREPORT_PAYLOAD_ZFS_POOL_CONTEXT, &pool_state);
|
||||
}
|
||||
|
||||
/*
|
||||
* We also ignore all ereports generated during an import of a pool,
|
||||
* since the only possible fault (.pool) would result in import failure,
|
||||
* and hence no persistent fault. Some day we may want to do something
|
||||
* with these ereports, so we continue generating them internally.
|
||||
*/
|
||||
if (pool_state == SPA_LOAD_IMPORT) {
|
||||
zfs_stats.import_drops.fmds_value.ui64++;
|
||||
fmd_hdl_debug(hdl, "ignoring '%s' during import", class);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Device I/O errors are ignored during pool open.
|
||||
*/
|
||||
if (pool_state == SPA_LOAD_OPEN &&
|
||||
(fmd_nvl_class_match(hdl, nvl,
|
||||
ZFS_MAKE_EREPORT(FM_EREPORT_ZFS_CHECKSUM)) ||
|
||||
fmd_nvl_class_match(hdl, nvl,
|
||||
ZFS_MAKE_EREPORT(FM_EREPORT_ZFS_IO)) ||
|
||||
fmd_nvl_class_match(hdl, nvl,
|
||||
ZFS_MAKE_EREPORT(FM_EREPORT_ZFS_PROBE_FAILURE)))) {
|
||||
fmd_hdl_debug(hdl, "ignoring '%s' during pool open", class);
|
||||
zfs_stats.dev_drops.fmds_value.ui64++;
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* We ignore ereports for anything except disks and files.
|
||||
*/
|
||||
if (nvlist_lookup_string(nvl, FM_EREPORT_PAYLOAD_ZFS_VDEV_TYPE,
|
||||
&type) == 0) {
|
||||
if (strcmp(type, VDEV_TYPE_DISK) != 0 &&
|
||||
strcmp(type, VDEV_TYPE_FILE) != 0) {
|
||||
zfs_stats.vdev_drops.fmds_value.ui64++;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Determine if this ereport corresponds to an open case.
|
||||
* Each vdev or pool can have a single case.
|
||||
*/
|
||||
(void) nvlist_lookup_uint64(nvl,
|
||||
FM_EREPORT_PAYLOAD_ZFS_POOL_GUID, &pool_guid);
|
||||
if (nvlist_lookup_uint64(nvl,
|
||||
FM_EREPORT_PAYLOAD_ZFS_VDEV_GUID, &vdev_guid) != 0)
|
||||
vdev_guid = 0;
|
||||
if (nvlist_lookup_uint64(nvl, FM_EREPORT_ENA, &ena) != 0)
|
||||
ena = 0;
|
||||
|
||||
zfs_ereport_when(hdl, nvl, &er_when);
|
||||
|
||||
for (zcp = uu_list_first(zfs_cases); zcp != NULL;
|
||||
zcp = uu_list_next(zfs_cases, zcp)) {
|
||||
if (zcp->zc_data.zc_pool_guid == pool_guid) {
|
||||
pool_found = B_TRUE;
|
||||
pool_load = zcp->zc_when;
|
||||
}
|
||||
if (zcp->zc_data.zc_vdev_guid == vdev_guid)
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* Avoid falsely accusing a pool of being faulty. Do so by
|
||||
* not replaying ereports that were generated prior to the
|
||||
* current import. If the failure that generated them was
|
||||
* transient because the device was actually removed but we
|
||||
* didn't receive the normal asynchronous notification, we
|
||||
* don't want to mark it as faulted and potentially panic. If
|
||||
* there is still a problem we'd expect not to be able to
|
||||
* import the pool, or that new ereports will be generated
|
||||
* once the pool is used.
|
||||
*/
|
||||
if (pool_found && timeval_earlier(&er_when, &pool_load)) {
|
||||
fmd_hdl_debug(hdl, "ignoring pool %llx, "
|
||||
"ereport time %lld.%lld, pool load time = %lld.%lld",
|
||||
pool_guid, er_when.ertv_sec, er_when.ertv_nsec,
|
||||
pool_load.ertv_sec, pool_load.ertv_nsec);
|
||||
zfs_stats.old_drops.fmds_value.ui64++;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!pool_found) {
|
||||
/*
|
||||
* Haven't yet seen this pool, but same situation
|
||||
* may apply.
|
||||
*/
|
||||
libzfs_handle_t *zhdl = fmd_hdl_getspecific(hdl);
|
||||
struct load_time_arg la;
|
||||
|
||||
la.lt_guid = pool_guid;
|
||||
la.lt_time = &pool_load;
|
||||
la.lt_found = B_FALSE;
|
||||
|
||||
if (zhdl != NULL &&
|
||||
zpool_iter(zhdl, zpool_find_load_time, &la) == 0 &&
|
||||
la.lt_found == B_TRUE) {
|
||||
pool_found = B_TRUE;
|
||||
|
||||
if (timeval_earlier(&er_when, &pool_load)) {
|
||||
fmd_hdl_debug(hdl, "ignoring pool %llx, "
|
||||
"ereport time %lld.%lld, "
|
||||
"pool load time = %lld.%lld",
|
||||
pool_guid, er_when.ertv_sec,
|
||||
er_when.ertv_nsec, pool_load.ertv_sec,
|
||||
pool_load.ertv_nsec);
|
||||
zfs_stats.old_drops.fmds_value.ui64++;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (zcp == NULL) {
|
||||
fmd_case_t *cs;
|
||||
zfs_case_data_t data = { 0 };
|
||||
|
||||
/*
|
||||
* If this is one of our 'fake' resource ereports, and there is
|
||||
* no case open, simply discard it.
|
||||
*/
|
||||
if (isresource) {
|
||||
zfs_stats.resource_drops.fmds_value.ui64++;
|
||||
fmd_hdl_debug(hdl, "discarding '%s for vdev %llu",
|
||||
class, vdev_guid);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Skip tracking some ereports
|
||||
*/
|
||||
if (strcmp(class,
|
||||
ZFS_MAKE_EREPORT(FM_EREPORT_ZFS_DATA)) == 0 ||
|
||||
strcmp(class,
|
||||
ZFS_MAKE_EREPORT(FM_EREPORT_ZFS_CONFIG_CACHE_WRITE)) == 0 ||
|
||||
strcmp(class,
|
||||
ZFS_MAKE_EREPORT(FM_EREPORT_ZFS_DELAY)) == 0) {
|
||||
zfs_stats.resource_drops.fmds_value.ui64++;
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Open a new case.
|
||||
*/
|
||||
cs = fmd_case_open(hdl, NULL);
|
||||
|
||||
fmd_hdl_debug(hdl, "opening case for vdev %llu due to '%s'",
|
||||
vdev_guid, class);
|
||||
|
||||
/*
|
||||
* Initialize the case buffer. To commonize code, we actually
|
||||
* create the buffer with existing data, and then call
|
||||
* zfs_case_unserialize() to instantiate the in-core structure.
|
||||
*/
|
||||
fmd_buf_create(hdl, cs, CASE_DATA, sizeof (zfs_case_data_t));
|
||||
|
||||
data.zc_version = CASE_DATA_VERSION_SERD;
|
||||
data.zc_ena = ena;
|
||||
data.zc_pool_guid = pool_guid;
|
||||
data.zc_vdev_guid = vdev_guid;
|
||||
data.zc_pool_state = (int)pool_state;
|
||||
|
||||
fmd_buf_write(hdl, cs, CASE_DATA, &data, sizeof (data));
|
||||
|
||||
zcp = zfs_case_unserialize(hdl, cs);
|
||||
assert(zcp != NULL);
|
||||
if (pool_found)
|
||||
zcp->zc_when = pool_load;
|
||||
}
|
||||
|
||||
if (isresource) {
|
||||
fmd_hdl_debug(hdl, "resource event '%s'", class);
|
||||
|
||||
if (fmd_nvl_class_match(hdl, nvl,
|
||||
ZFS_MAKE_RSRC(FM_RESOURCE_AUTOREPLACE))) {
|
||||
/*
|
||||
* The 'resource.fs.zfs.autoreplace' event indicates
|
||||
* that the pool was loaded with the 'autoreplace'
|
||||
* property set. In this case, any pending device
|
||||
* failures should be ignored, as the asynchronous
|
||||
* autoreplace handling will take care of them.
|
||||
*/
|
||||
fmd_case_close(hdl, zcp->zc_case);
|
||||
} else if (fmd_nvl_class_match(hdl, nvl,
|
||||
ZFS_MAKE_RSRC(FM_RESOURCE_REMOVED))) {
|
||||
/*
|
||||
* The 'resource.fs.zfs.removed' event indicates that
|
||||
* device removal was detected, and the device was
|
||||
* closed asynchronously. If this is the case, we
|
||||
* assume that any recent I/O errors were due to the
|
||||
* device removal, not any fault of the device itself.
|
||||
* We reset the SERD engine, and cancel any pending
|
||||
* timers.
|
||||
*/
|
||||
if (zcp->zc_data.zc_has_remove_timer) {
|
||||
fmd_timer_remove(hdl, zcp->zc_remove_timer);
|
||||
zcp->zc_data.zc_has_remove_timer = 0;
|
||||
zfs_case_serialize(hdl, zcp);
|
||||
}
|
||||
if (zcp->zc_data.zc_serd_io[0] != '\0')
|
||||
fmd_serd_reset(hdl, zcp->zc_data.zc_serd_io);
|
||||
if (zcp->zc_data.zc_serd_checksum[0] != '\0')
|
||||
fmd_serd_reset(hdl,
|
||||
zcp->zc_data.zc_serd_checksum);
|
||||
} else if (fmd_nvl_class_match(hdl, nvl,
|
||||
ZFS_MAKE_RSRC(FM_RESOURCE_STATECHANGE))) {
|
||||
uint64_t state = 0;
|
||||
|
||||
if (zcp != NULL &&
|
||||
nvlist_lookup_uint64(nvl,
|
||||
FM_EREPORT_PAYLOAD_ZFS_VDEV_STATE, &state) == 0 &&
|
||||
state == VDEV_STATE_HEALTHY) {
|
||||
fmd_hdl_debug(hdl, "closing case after a "
|
||||
"device statechange to healthy");
|
||||
fmd_case_close(hdl, zcp->zc_case);
|
||||
}
|
||||
}
|
||||
zfs_stats.resource_drops.fmds_value.ui64++;
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Associate the ereport with this case.
|
||||
*/
|
||||
fmd_case_add_ereport(hdl, zcp->zc_case, ep);
|
||||
|
||||
/*
|
||||
* Don't do anything else if this case is already solved.
|
||||
*/
|
||||
if (fmd_case_solved(hdl, zcp->zc_case))
|
||||
return;
|
||||
|
||||
fmd_hdl_debug(hdl, "error event '%s'", class);
|
||||
|
||||
/*
|
||||
* Determine if we should solve the case and generate a fault. We solve
|
||||
* a case if:
|
||||
*
|
||||
* a. A pool failed to open (ereport.fs.zfs.pool)
|
||||
* b. A device failed to open (ereport.fs.zfs.pool) while a pool
|
||||
* was up and running.
|
||||
*
|
||||
* We may see a series of ereports associated with a pool open, all
|
||||
* chained together by the same ENA. If the pool open succeeds, then
|
||||
* we'll see no further ereports. To detect when a pool open has
|
||||
* succeeded, we associate a timer with the event. When it expires, we
|
||||
* close the case.
|
||||
*/
|
||||
if (fmd_nvl_class_match(hdl, nvl,
|
||||
ZFS_MAKE_EREPORT(FM_EREPORT_ZFS_POOL))) {
|
||||
/*
|
||||
* Pool level fault. Before solving the case, go through and
|
||||
* close any open device cases that may be pending.
|
||||
*/
|
||||
for (dcp = uu_list_first(zfs_cases); dcp != NULL;
|
||||
dcp = uu_list_next(zfs_cases, dcp)) {
|
||||
if (dcp->zc_data.zc_pool_guid ==
|
||||
zcp->zc_data.zc_pool_guid &&
|
||||
dcp->zc_data.zc_vdev_guid != 0)
|
||||
fmd_case_close(hdl, dcp->zc_case);
|
||||
}
|
||||
|
||||
zfs_case_solve(hdl, zcp, "fault.fs.zfs.pool", B_TRUE);
|
||||
} else if (fmd_nvl_class_match(hdl, nvl,
|
||||
ZFS_MAKE_EREPORT(FM_EREPORT_ZFS_LOG_REPLAY))) {
|
||||
/*
|
||||
* Pool level fault for reading the intent logs.
|
||||
*/
|
||||
zfs_case_solve(hdl, zcp, "fault.fs.zfs.log_replay", B_TRUE);
|
||||
} else if (fmd_nvl_class_match(hdl, nvl, "ereport.fs.zfs.vdev.*")) {
|
||||
/*
|
||||
* Device fault.
|
||||
*/
|
||||
zfs_case_solve(hdl, zcp, "fault.fs.zfs.device", B_TRUE);
|
||||
} else if (fmd_nvl_class_match(hdl, nvl,
|
||||
ZFS_MAKE_EREPORT(FM_EREPORT_ZFS_IO)) ||
|
||||
fmd_nvl_class_match(hdl, nvl,
|
||||
ZFS_MAKE_EREPORT(FM_EREPORT_ZFS_CHECKSUM)) ||
|
||||
fmd_nvl_class_match(hdl, nvl,
|
||||
ZFS_MAKE_EREPORT(FM_EREPORT_ZFS_IO_FAILURE)) ||
|
||||
fmd_nvl_class_match(hdl, nvl,
|
||||
ZFS_MAKE_EREPORT(FM_EREPORT_ZFS_PROBE_FAILURE))) {
|
||||
char *failmode = NULL;
|
||||
boolean_t checkremove = B_FALSE;
|
||||
|
||||
/*
|
||||
* If this is a checksum or I/O error, then toss it into the
|
||||
* appropriate SERD engine and check to see if it has fired.
|
||||
* Ideally, we want to do something more sophisticated,
|
||||
* (persistent errors for a single data block, etc). For now,
|
||||
* a single SERD engine is sufficient.
|
||||
*/
|
||||
if (fmd_nvl_class_match(hdl, nvl,
|
||||
ZFS_MAKE_EREPORT(FM_EREPORT_ZFS_IO))) {
|
||||
if (zcp->zc_data.zc_serd_io[0] == '\0') {
|
||||
zfs_serd_name(zcp->zc_data.zc_serd_io,
|
||||
pool_guid, vdev_guid, "io");
|
||||
fmd_serd_create(hdl, zcp->zc_data.zc_serd_io,
|
||||
fmd_prop_get_int32(hdl, "io_N"),
|
||||
fmd_prop_get_int64(hdl, "io_T"));
|
||||
zfs_case_serialize(hdl, zcp);
|
||||
}
|
||||
if (fmd_serd_record(hdl, zcp->zc_data.zc_serd_io, ep))
|
||||
checkremove = B_TRUE;
|
||||
} else if (fmd_nvl_class_match(hdl, nvl,
|
||||
ZFS_MAKE_EREPORT(FM_EREPORT_ZFS_CHECKSUM))) {
|
||||
if (zcp->zc_data.zc_serd_checksum[0] == '\0') {
|
||||
zfs_serd_name(zcp->zc_data.zc_serd_checksum,
|
||||
pool_guid, vdev_guid, "checksum");
|
||||
fmd_serd_create(hdl,
|
||||
zcp->zc_data.zc_serd_checksum,
|
||||
fmd_prop_get_int32(hdl, "checksum_N"),
|
||||
fmd_prop_get_int64(hdl, "checksum_T"));
|
||||
zfs_case_serialize(hdl, zcp);
|
||||
}
|
||||
if (fmd_serd_record(hdl,
|
||||
zcp->zc_data.zc_serd_checksum, ep)) {
|
||||
zfs_case_solve(hdl, zcp,
|
||||
"fault.fs.zfs.vdev.checksum", B_FALSE);
|
||||
}
|
||||
} else if (fmd_nvl_class_match(hdl, nvl,
|
||||
ZFS_MAKE_EREPORT(FM_EREPORT_ZFS_IO_FAILURE)) &&
|
||||
(nvlist_lookup_string(nvl,
|
||||
FM_EREPORT_PAYLOAD_ZFS_POOL_FAILMODE, &failmode) == 0) &&
|
||||
failmode != NULL) {
|
||||
if (strncmp(failmode, FM_EREPORT_FAILMODE_CONTINUE,
|
||||
strlen(FM_EREPORT_FAILMODE_CONTINUE)) == 0) {
|
||||
zfs_case_solve(hdl, zcp,
|
||||
"fault.fs.zfs.io_failure_continue",
|
||||
B_FALSE);
|
||||
} else if (strncmp(failmode, FM_EREPORT_FAILMODE_WAIT,
|
||||
strlen(FM_EREPORT_FAILMODE_WAIT)) == 0) {
|
||||
zfs_case_solve(hdl, zcp,
|
||||
"fault.fs.zfs.io_failure_wait", B_FALSE);
|
||||
}
|
||||
} else if (fmd_nvl_class_match(hdl, nvl,
|
||||
ZFS_MAKE_EREPORT(FM_EREPORT_ZFS_PROBE_FAILURE))) {
|
||||
#ifndef __linux__
|
||||
/* This causes an unexpected fault diagnosis on linux */
|
||||
checkremove = B_TRUE;
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
* Because I/O errors may be due to device removal, we postpone
|
||||
* any diagnosis until we're sure that we aren't about to
|
||||
* receive a 'resource.fs.zfs.removed' event.
|
||||
*/
|
||||
if (checkremove) {
|
||||
if (zcp->zc_data.zc_has_remove_timer)
|
||||
fmd_timer_remove(hdl, zcp->zc_remove_timer);
|
||||
zcp->zc_remove_timer = fmd_timer_install(hdl, zcp, NULL,
|
||||
zfs_remove_timeout);
|
||||
if (!zcp->zc_data.zc_has_remove_timer) {
|
||||
zcp->zc_data.zc_has_remove_timer = 1;
|
||||
zfs_case_serialize(hdl, zcp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* The timeout is fired when we diagnosed an I/O error, and it was not due to
|
||||
* device removal (which would cause the timeout to be cancelled).
|
||||
*/
|
||||
/* ARGSUSED */
|
||||
static void
|
||||
zfs_fm_timeout(fmd_hdl_t *hdl, id_t id, void *data)
|
||||
{
|
||||
zfs_case_t *zcp = data;
|
||||
|
||||
if (id == zcp->zc_remove_timer)
|
||||
zfs_case_solve(hdl, zcp, "fault.fs.zfs.vdev.io", B_FALSE);
|
||||
}
|
||||
|
||||
/*
|
||||
* The specified case has been closed and any case-specific
|
||||
* data structures should be deallocated.
|
||||
*/
|
||||
static void
|
||||
zfs_fm_close(fmd_hdl_t *hdl, fmd_case_t *cs)
|
||||
{
|
||||
zfs_case_t *zcp = fmd_case_getspecific(hdl, cs);
|
||||
|
||||
if (zcp->zc_data.zc_serd_checksum[0] != '\0')
|
||||
fmd_serd_destroy(hdl, zcp->zc_data.zc_serd_checksum);
|
||||
if (zcp->zc_data.zc_serd_io[0] != '\0')
|
||||
fmd_serd_destroy(hdl, zcp->zc_data.zc_serd_io);
|
||||
if (zcp->zc_data.zc_has_remove_timer)
|
||||
fmd_timer_remove(hdl, zcp->zc_remove_timer);
|
||||
|
||||
uu_list_remove(zfs_cases, zcp);
|
||||
uu_list_node_fini(zcp, &zcp->zc_node, zfs_case_pool);
|
||||
fmd_hdl_free(hdl, zcp, sizeof (zfs_case_t));
|
||||
}
|
||||
|
||||
/*
|
||||
* We use the fmd gc entry point to look for old cases that no longer apply.
|
||||
* This allows us to keep our set of case data small in a long running system.
|
||||
*/
|
||||
static void
|
||||
zfs_fm_gc(fmd_hdl_t *hdl)
|
||||
{
|
||||
zfs_purge_cases(hdl);
|
||||
}
|
||||
|
||||
static const fmd_hdl_ops_t fmd_ops = {
|
||||
zfs_fm_recv, /* fmdo_recv */
|
||||
zfs_fm_timeout, /* fmdo_timeout */
|
||||
zfs_fm_close, /* fmdo_close */
|
||||
NULL, /* fmdo_stats */
|
||||
zfs_fm_gc, /* fmdo_gc */
|
||||
};
|
||||
|
||||
static const fmd_prop_t fmd_props[] = {
|
||||
{ "checksum_N", FMD_TYPE_UINT32, "10" },
|
||||
{ "checksum_T", FMD_TYPE_TIME, "10min" },
|
||||
{ "io_N", FMD_TYPE_UINT32, "10" },
|
||||
{ "io_T", FMD_TYPE_TIME, "10min" },
|
||||
{ "remove_timeout", FMD_TYPE_TIME, "15sec" },
|
||||
{ NULL, 0, NULL }
|
||||
};
|
||||
|
||||
static const fmd_hdl_info_t fmd_info = {
|
||||
"ZFS Diagnosis Engine", "1.0", &fmd_ops, fmd_props
|
||||
};
|
||||
|
||||
void
|
||||
_zfs_diagnosis_init(fmd_hdl_t *hdl)
|
||||
{
|
||||
libzfs_handle_t *zhdl;
|
||||
|
||||
if ((zhdl = libzfs_init()) == NULL)
|
||||
return;
|
||||
|
||||
if ((zfs_case_pool = uu_list_pool_create("zfs_case_pool",
|
||||
sizeof (zfs_case_t), offsetof(zfs_case_t, zc_node),
|
||||
NULL, UU_LIST_POOL_DEBUG)) == NULL) {
|
||||
libzfs_fini(zhdl);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((zfs_cases = uu_list_create(zfs_case_pool, NULL,
|
||||
UU_LIST_DEBUG)) == NULL) {
|
||||
uu_list_pool_destroy(zfs_case_pool);
|
||||
libzfs_fini(zhdl);
|
||||
return;
|
||||
}
|
||||
|
||||
if (fmd_hdl_register(hdl, FMD_API_VERSION, &fmd_info) != 0) {
|
||||
uu_list_destroy(zfs_cases);
|
||||
uu_list_pool_destroy(zfs_case_pool);
|
||||
libzfs_fini(zhdl);
|
||||
return;
|
||||
}
|
||||
|
||||
fmd_hdl_setspecific(hdl, zhdl);
|
||||
|
||||
(void) fmd_stat_create(hdl, FMD_STAT_NOALLOC, sizeof (zfs_stats) /
|
||||
sizeof (fmd_stat_t), (fmd_stat_t *)&zfs_stats);
|
||||
|
||||
zfs_remove_timeout = fmd_prop_get_int64(hdl, "remove_timeout");
|
||||
}
|
||||
|
||||
void
|
||||
_zfs_diagnosis_fini(fmd_hdl_t *hdl)
|
||||
{
|
||||
zfs_case_t *zcp;
|
||||
uu_list_walk_t *walk;
|
||||
libzfs_handle_t *zhdl;
|
||||
|
||||
/*
|
||||
* Remove all active cases.
|
||||
*/
|
||||
walk = uu_list_walk_start(zfs_cases, UU_WALK_ROBUST);
|
||||
while ((zcp = uu_list_walk_next(walk)) != NULL) {
|
||||
fmd_hdl_debug(hdl, "removing case ena %llu",
|
||||
(long long unsigned)zcp->zc_data.zc_ena);
|
||||
uu_list_remove(zfs_cases, zcp);
|
||||
uu_list_node_fini(zcp, &zcp->zc_node, zfs_case_pool);
|
||||
fmd_hdl_free(hdl, zcp, sizeof (zfs_case_t));
|
||||
}
|
||||
uu_list_walk_end(walk);
|
||||
|
||||
uu_list_destroy(zfs_cases);
|
||||
uu_list_pool_destroy(zfs_case_pool);
|
||||
|
||||
zhdl = fmd_hdl_getspecific(hdl);
|
||||
libzfs_fini(zhdl);
|
||||
}
|
956
sys/contrib/openzfs/cmd/zed/agents/zfs_mod.c
Normal file
956
sys/contrib/openzfs/cmd/zed/agents/zfs_mod.c
Normal file
@ -0,0 +1,956 @@
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License (the "License").
|
||||
* You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||
* or http://www.opensolaris.org/os/licensing.
|
||||
* See the License for the specific language governing permissions
|
||||
* and limitations under the License.
|
||||
*
|
||||
* When distributing Covered Code, include this CDDL HEADER in each
|
||||
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||
* If applicable, add the following below this CDDL HEADER, with the
|
||||
* fields enclosed by brackets "[]" replaced with your own identifying
|
||||
* information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
*
|
||||
* CDDL HEADER END
|
||||
*/
|
||||
/*
|
||||
* Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2012 by Delphix. All rights reserved.
|
||||
* Copyright 2014 Nexenta Systems, Inc. All rights reserved.
|
||||
* Copyright (c) 2016, 2017, Intel Corporation.
|
||||
* Copyright (c) 2017 Open-E, Inc. All Rights Reserved.
|
||||
*/
|
||||
|
||||
/*
|
||||
* ZFS syseventd module.
|
||||
*
|
||||
* file origin: openzfs/usr/src/cmd/syseventd/modules/zfs_mod/zfs_mod.c
|
||||
*
|
||||
* The purpose of this module is to identify when devices are added to the
|
||||
* system, and appropriately online or replace the affected vdevs.
|
||||
*
|
||||
* When a device is added to the system:
|
||||
*
|
||||
* 1. Search for any vdevs whose devid matches that of the newly added
|
||||
* device.
|
||||
*
|
||||
* 2. If no vdevs are found, then search for any vdevs whose udev path
|
||||
* matches that of the new device.
|
||||
*
|
||||
* 3. If no vdevs match by either method, then ignore the event.
|
||||
*
|
||||
* 4. Attempt to online the device with a flag to indicate that it should
|
||||
* be unspared when resilvering completes. If this succeeds, then the
|
||||
* same device was inserted and we should continue normally.
|
||||
*
|
||||
* 5. If the pool does not have the 'autoreplace' property set, attempt to
|
||||
* online the device again without the unspare flag, which will
|
||||
* generate a FMA fault.
|
||||
*
|
||||
* 6. If the pool has the 'autoreplace' property set, and the matching vdev
|
||||
* is a whole disk, then label the new disk and attempt a 'zpool
|
||||
* replace'.
|
||||
*
|
||||
* The module responds to EC_DEV_ADD events. The special ESC_ZFS_VDEV_CHECK
|
||||
* event indicates that a device failed to open during pool load, but the
|
||||
* autoreplace property was set. In this case, we deferred the associated
|
||||
* FMA fault until our module had a chance to process the autoreplace logic.
|
||||
* If the device could not be replaced, then the second online attempt will
|
||||
* trigger the FMA fault that we skipped earlier.
|
||||
*
|
||||
* ZFS on Linux porting notes:
|
||||
* Linux udev provides a disk insert for both the disk and the partition
|
||||
*
|
||||
*/
|
||||
|
||||
#include <ctype.h>
|
||||
#include <fcntl.h>
|
||||
#include <libnvpair.h>
|
||||
#include <libzfs.h>
|
||||
#include <libzutil.h>
|
||||
#include <limits.h>
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <syslog.h>
|
||||
#include <sys/list.h>
|
||||
#include <sys/sunddi.h>
|
||||
#include <sys/sysevent/eventdefs.h>
|
||||
#include <sys/sysevent/dev.h>
|
||||
#include <thread_pool.h>
|
||||
#include <pthread.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include "zfs_agents.h"
|
||||
#include "../zed_log.h"
|
||||
|
||||
#define DEV_BYID_PATH "/dev/disk/by-id/"
|
||||
#define DEV_BYPATH_PATH "/dev/disk/by-path/"
|
||||
#define DEV_BYVDEV_PATH "/dev/disk/by-vdev/"
|
||||
|
||||
typedef void (*zfs_process_func_t)(zpool_handle_t *, nvlist_t *, boolean_t);
|
||||
|
||||
libzfs_handle_t *g_zfshdl;
|
||||
list_t g_pool_list; /* list of unavailable pools at initialization */
|
||||
list_t g_device_list; /* list of disks with asynchronous label request */
|
||||
tpool_t *g_tpool;
|
||||
boolean_t g_enumeration_done;
|
||||
pthread_t g_zfs_tid; /* zfs_enum_pools() thread */
|
||||
|
||||
typedef struct unavailpool {
|
||||
zpool_handle_t *uap_zhp;
|
||||
list_node_t uap_node;
|
||||
} unavailpool_t;
|
||||
|
||||
typedef struct pendingdev {
|
||||
char pd_physpath[128];
|
||||
list_node_t pd_node;
|
||||
} pendingdev_t;
|
||||
|
||||
static int
|
||||
zfs_toplevel_state(zpool_handle_t *zhp)
|
||||
{
|
||||
nvlist_t *nvroot;
|
||||
vdev_stat_t *vs;
|
||||
unsigned int c;
|
||||
|
||||
verify(nvlist_lookup_nvlist(zpool_get_config(zhp, NULL),
|
||||
ZPOOL_CONFIG_VDEV_TREE, &nvroot) == 0);
|
||||
verify(nvlist_lookup_uint64_array(nvroot, ZPOOL_CONFIG_VDEV_STATS,
|
||||
(uint64_t **)&vs, &c) == 0);
|
||||
return (vs->vs_state);
|
||||
}
|
||||
|
||||
static int
|
||||
zfs_unavail_pool(zpool_handle_t *zhp, void *data)
|
||||
{
|
||||
zed_log_msg(LOG_INFO, "zfs_unavail_pool: examining '%s' (state %d)",
|
||||
zpool_get_name(zhp), (int)zfs_toplevel_state(zhp));
|
||||
|
||||
if (zfs_toplevel_state(zhp) < VDEV_STATE_DEGRADED) {
|
||||
unavailpool_t *uap;
|
||||
uap = malloc(sizeof (unavailpool_t));
|
||||
uap->uap_zhp = zhp;
|
||||
list_insert_tail((list_t *)data, uap);
|
||||
} else {
|
||||
zpool_close(zhp);
|
||||
}
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Two stage replace on Linux
|
||||
* since we get disk notifications
|
||||
* we can wait for partitioned disk slice to show up!
|
||||
*
|
||||
* First stage tags the disk, initiates async partitioning, and returns
|
||||
* Second stage finds the tag and proceeds to ZFS labeling/replace
|
||||
*
|
||||
* disk-add --> label-disk + tag-disk --> partition-add --> zpool_vdev_attach
|
||||
*
|
||||
* 1. physical match with no fs, no partition
|
||||
* tag it top, partition disk
|
||||
*
|
||||
* 2. physical match again, see partition and tag
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* The device associated with the given vdev (either by devid or physical path)
|
||||
* has been added to the system. If 'isdisk' is set, then we only attempt a
|
||||
* replacement if it's a whole disk. This also implies that we should label the
|
||||
* disk first.
|
||||
*
|
||||
* First, we attempt to online the device (making sure to undo any spare
|
||||
* operation when finished). If this succeeds, then we're done. If it fails,
|
||||
* and the new state is VDEV_CANT_OPEN, it indicates that the device was opened,
|
||||
* but that the label was not what we expected. If the 'autoreplace' property
|
||||
* is enabled, then we relabel the disk (if specified), and attempt a 'zpool
|
||||
* replace'. If the online is successful, but the new state is something else
|
||||
* (REMOVED or FAULTED), it indicates that we're out of sync or in some sort of
|
||||
* race, and we should avoid attempting to relabel the disk.
|
||||
*
|
||||
* Also can arrive here from a ESC_ZFS_VDEV_CHECK event
|
||||
*/
|
||||
static void
|
||||
zfs_process_add(zpool_handle_t *zhp, nvlist_t *vdev, boolean_t labeled)
|
||||
{
|
||||
char *path;
|
||||
vdev_state_t newstate;
|
||||
nvlist_t *nvroot, *newvd;
|
||||
pendingdev_t *device;
|
||||
uint64_t wholedisk = 0ULL;
|
||||
uint64_t offline = 0ULL;
|
||||
uint64_t guid = 0ULL;
|
||||
char *physpath = NULL, *new_devid = NULL, *enc_sysfs_path = NULL;
|
||||
char rawpath[PATH_MAX], fullpath[PATH_MAX];
|
||||
char devpath[PATH_MAX];
|
||||
int ret;
|
||||
boolean_t is_dm = B_FALSE;
|
||||
boolean_t is_sd = B_FALSE;
|
||||
uint_t c;
|
||||
vdev_stat_t *vs;
|
||||
|
||||
if (nvlist_lookup_string(vdev, ZPOOL_CONFIG_PATH, &path) != 0)
|
||||
return;
|
||||
|
||||
/* Skip healthy disks */
|
||||
verify(nvlist_lookup_uint64_array(vdev, ZPOOL_CONFIG_VDEV_STATS,
|
||||
(uint64_t **)&vs, &c) == 0);
|
||||
if (vs->vs_state == VDEV_STATE_HEALTHY) {
|
||||
zed_log_msg(LOG_INFO, "%s: %s is already healthy, skip it.",
|
||||
__func__, path);
|
||||
return;
|
||||
}
|
||||
|
||||
(void) nvlist_lookup_string(vdev, ZPOOL_CONFIG_PHYS_PATH, &physpath);
|
||||
(void) nvlist_lookup_string(vdev, ZPOOL_CONFIG_VDEV_ENC_SYSFS_PATH,
|
||||
&enc_sysfs_path);
|
||||
(void) nvlist_lookup_uint64(vdev, ZPOOL_CONFIG_WHOLE_DISK, &wholedisk);
|
||||
(void) nvlist_lookup_uint64(vdev, ZPOOL_CONFIG_OFFLINE, &offline);
|
||||
(void) nvlist_lookup_uint64(vdev, ZPOOL_CONFIG_GUID, &guid);
|
||||
|
||||
if (offline)
|
||||
return; /* don't intervene if it was taken offline */
|
||||
|
||||
is_dm = zfs_dev_is_dm(path);
|
||||
zed_log_msg(LOG_INFO, "zfs_process_add: pool '%s' vdev '%s', phys '%s'"
|
||||
" wholedisk %d, %s dm (guid %llu)", zpool_get_name(zhp), path,
|
||||
physpath ? physpath : "NULL", wholedisk, is_dm ? "is" : "not",
|
||||
(long long unsigned int)guid);
|
||||
|
||||
/*
|
||||
* The VDEV guid is preferred for identification (gets passed in path)
|
||||
*/
|
||||
if (guid != 0) {
|
||||
(void) snprintf(fullpath, sizeof (fullpath), "%llu",
|
||||
(long long unsigned int)guid);
|
||||
} else {
|
||||
/*
|
||||
* otherwise use path sans partition suffix for whole disks
|
||||
*/
|
||||
(void) strlcpy(fullpath, path, sizeof (fullpath));
|
||||
if (wholedisk) {
|
||||
char *spath = zfs_strip_partition(fullpath);
|
||||
if (!spath) {
|
||||
zed_log_msg(LOG_INFO, "%s: Can't alloc",
|
||||
__func__);
|
||||
return;
|
||||
}
|
||||
|
||||
(void) strlcpy(fullpath, spath, sizeof (fullpath));
|
||||
free(spath);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Attempt to online the device.
|
||||
*/
|
||||
if (zpool_vdev_online(zhp, fullpath,
|
||||
ZFS_ONLINE_CHECKREMOVE | ZFS_ONLINE_UNSPARE, &newstate) == 0 &&
|
||||
(newstate == VDEV_STATE_HEALTHY ||
|
||||
newstate == VDEV_STATE_DEGRADED)) {
|
||||
zed_log_msg(LOG_INFO, " zpool_vdev_online: vdev %s is %s",
|
||||
fullpath, (newstate == VDEV_STATE_HEALTHY) ?
|
||||
"HEALTHY" : "DEGRADED");
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* vdev_id alias rule for using scsi_debug devices (FMA automated
|
||||
* testing)
|
||||
*/
|
||||
if (physpath != NULL && strcmp("scsidebug", physpath) == 0)
|
||||
is_sd = B_TRUE;
|
||||
|
||||
/*
|
||||
* If the pool doesn't have the autoreplace property set, then use
|
||||
* vdev online to trigger a FMA fault by posting an ereport.
|
||||
*/
|
||||
if (!zpool_get_prop_int(zhp, ZPOOL_PROP_AUTOREPLACE, NULL) ||
|
||||
!(wholedisk || is_dm) || (physpath == NULL)) {
|
||||
(void) zpool_vdev_online(zhp, fullpath, ZFS_ONLINE_FORCEFAULT,
|
||||
&newstate);
|
||||
zed_log_msg(LOG_INFO, "Pool's autoreplace is not enabled or "
|
||||
"not a whole disk for '%s'", fullpath);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert physical path into its current device node. Rawpath
|
||||
* needs to be /dev/disk/by-vdev for a scsi_debug device since
|
||||
* /dev/disk/by-path will not be present.
|
||||
*/
|
||||
(void) snprintf(rawpath, sizeof (rawpath), "%s%s",
|
||||
is_sd ? DEV_BYVDEV_PATH : DEV_BYPATH_PATH, physpath);
|
||||
|
||||
if (realpath(rawpath, devpath) == NULL && !is_dm) {
|
||||
zed_log_msg(LOG_INFO, " realpath: %s failed (%s)",
|
||||
rawpath, strerror(errno));
|
||||
|
||||
(void) zpool_vdev_online(zhp, fullpath, ZFS_ONLINE_FORCEFAULT,
|
||||
&newstate);
|
||||
|
||||
zed_log_msg(LOG_INFO, " zpool_vdev_online: %s FORCEFAULT (%s)",
|
||||
fullpath, libzfs_error_description(g_zfshdl));
|
||||
return;
|
||||
}
|
||||
|
||||
/* Only autoreplace bad disks */
|
||||
if ((vs->vs_state != VDEV_STATE_DEGRADED) &&
|
||||
(vs->vs_state != VDEV_STATE_FAULTED) &&
|
||||
(vs->vs_state != VDEV_STATE_CANT_OPEN)) {
|
||||
return;
|
||||
}
|
||||
|
||||
nvlist_lookup_string(vdev, "new_devid", &new_devid);
|
||||
|
||||
if (is_dm) {
|
||||
/* Don't label device mapper or multipath disks. */
|
||||
} else if (!labeled) {
|
||||
/*
|
||||
* we're auto-replacing a raw disk, so label it first
|
||||
*/
|
||||
char *leafname;
|
||||
|
||||
/*
|
||||
* If this is a request to label a whole disk, then attempt to
|
||||
* write out the label. Before we can label the disk, we need
|
||||
* to map the physical string that was matched on to the under
|
||||
* lying device node.
|
||||
*
|
||||
* If any part of this process fails, then do a force online
|
||||
* to trigger a ZFS fault for the device (and any hot spare
|
||||
* replacement).
|
||||
*/
|
||||
leafname = strrchr(devpath, '/') + 1;
|
||||
|
||||
/*
|
||||
* If this is a request to label a whole disk, then attempt to
|
||||
* write out the label.
|
||||
*/
|
||||
if (zpool_label_disk(g_zfshdl, zhp, leafname) != 0) {
|
||||
zed_log_msg(LOG_INFO, " zpool_label_disk: could not "
|
||||
"label '%s' (%s)", leafname,
|
||||
libzfs_error_description(g_zfshdl));
|
||||
|
||||
(void) zpool_vdev_online(zhp, fullpath,
|
||||
ZFS_ONLINE_FORCEFAULT, &newstate);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* The disk labeling is asynchronous on Linux. Just record
|
||||
* this label request and return as there will be another
|
||||
* disk add event for the partition after the labeling is
|
||||
* completed.
|
||||
*/
|
||||
device = malloc(sizeof (pendingdev_t));
|
||||
(void) strlcpy(device->pd_physpath, physpath,
|
||||
sizeof (device->pd_physpath));
|
||||
list_insert_tail(&g_device_list, device);
|
||||
|
||||
zed_log_msg(LOG_INFO, " zpool_label_disk: async '%s' (%llu)",
|
||||
leafname, (u_longlong_t)guid);
|
||||
|
||||
return; /* resumes at EC_DEV_ADD.ESC_DISK for partition */
|
||||
|
||||
} else /* labeled */ {
|
||||
boolean_t found = B_FALSE;
|
||||
/*
|
||||
* match up with request above to label the disk
|
||||
*/
|
||||
for (device = list_head(&g_device_list); device != NULL;
|
||||
device = list_next(&g_device_list, device)) {
|
||||
if (strcmp(physpath, device->pd_physpath) == 0) {
|
||||
list_remove(&g_device_list, device);
|
||||
free(device);
|
||||
found = B_TRUE;
|
||||
break;
|
||||
}
|
||||
zed_log_msg(LOG_INFO, "zpool_label_disk: %s != %s",
|
||||
physpath, device->pd_physpath);
|
||||
}
|
||||
if (!found) {
|
||||
/* unexpected partition slice encountered */
|
||||
zed_log_msg(LOG_INFO, "labeled disk %s unexpected here",
|
||||
fullpath);
|
||||
(void) zpool_vdev_online(zhp, fullpath,
|
||||
ZFS_ONLINE_FORCEFAULT, &newstate);
|
||||
return;
|
||||
}
|
||||
|
||||
zed_log_msg(LOG_INFO, " zpool_label_disk: resume '%s' (%llu)",
|
||||
physpath, (u_longlong_t)guid);
|
||||
|
||||
(void) snprintf(devpath, sizeof (devpath), "%s%s",
|
||||
DEV_BYID_PATH, new_devid);
|
||||
}
|
||||
|
||||
/*
|
||||
* Construct the root vdev to pass to zpool_vdev_attach(). While adding
|
||||
* the entire vdev structure is harmless, we construct a reduced set of
|
||||
* path/physpath/wholedisk to keep it simple.
|
||||
*/
|
||||
if (nvlist_alloc(&nvroot, NV_UNIQUE_NAME, 0) != 0) {
|
||||
zed_log_msg(LOG_WARNING, "zfs_mod: nvlist_alloc out of memory");
|
||||
return;
|
||||
}
|
||||
if (nvlist_alloc(&newvd, NV_UNIQUE_NAME, 0) != 0) {
|
||||
zed_log_msg(LOG_WARNING, "zfs_mod: nvlist_alloc out of memory");
|
||||
nvlist_free(nvroot);
|
||||
return;
|
||||
}
|
||||
|
||||
if (nvlist_add_string(newvd, ZPOOL_CONFIG_TYPE, VDEV_TYPE_DISK) != 0 ||
|
||||
nvlist_add_string(newvd, ZPOOL_CONFIG_PATH, path) != 0 ||
|
||||
nvlist_add_string(newvd, ZPOOL_CONFIG_DEVID, new_devid) != 0 ||
|
||||
(physpath != NULL && nvlist_add_string(newvd,
|
||||
ZPOOL_CONFIG_PHYS_PATH, physpath) != 0) ||
|
||||
(enc_sysfs_path != NULL && nvlist_add_string(newvd,
|
||||
ZPOOL_CONFIG_VDEV_ENC_SYSFS_PATH, enc_sysfs_path) != 0) ||
|
||||
nvlist_add_uint64(newvd, ZPOOL_CONFIG_WHOLE_DISK, wholedisk) != 0 ||
|
||||
nvlist_add_string(nvroot, ZPOOL_CONFIG_TYPE, VDEV_TYPE_ROOT) != 0 ||
|
||||
nvlist_add_nvlist_array(nvroot, ZPOOL_CONFIG_CHILDREN, &newvd,
|
||||
1) != 0) {
|
||||
zed_log_msg(LOG_WARNING, "zfs_mod: unable to add nvlist pairs");
|
||||
nvlist_free(newvd);
|
||||
nvlist_free(nvroot);
|
||||
return;
|
||||
}
|
||||
|
||||
nvlist_free(newvd);
|
||||
|
||||
/*
|
||||
* Wait for udev to verify the links exist, then auto-replace
|
||||
* the leaf disk at same physical location.
|
||||
*/
|
||||
if (zpool_label_disk_wait(path, 3000) != 0) {
|
||||
zed_log_msg(LOG_WARNING, "zfs_mod: expected replacement "
|
||||
"disk %s is missing", path);
|
||||
nvlist_free(nvroot);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = zpool_vdev_attach(zhp, fullpath, path, nvroot, B_TRUE, B_FALSE);
|
||||
|
||||
zed_log_msg(LOG_INFO, " zpool_vdev_replace: %s with %s (%s)",
|
||||
fullpath, path, (ret == 0) ? "no errors" :
|
||||
libzfs_error_description(g_zfshdl));
|
||||
|
||||
nvlist_free(nvroot);
|
||||
}
|
||||
|
||||
/*
|
||||
* Utility functions to find a vdev matching given criteria.
|
||||
*/
|
||||
typedef struct dev_data {
|
||||
const char *dd_compare;
|
||||
const char *dd_prop;
|
||||
zfs_process_func_t dd_func;
|
||||
boolean_t dd_found;
|
||||
boolean_t dd_islabeled;
|
||||
uint64_t dd_pool_guid;
|
||||
uint64_t dd_vdev_guid;
|
||||
const char *dd_new_devid;
|
||||
} dev_data_t;
|
||||
|
||||
static void
|
||||
zfs_iter_vdev(zpool_handle_t *zhp, nvlist_t *nvl, void *data)
|
||||
{
|
||||
dev_data_t *dp = data;
|
||||
char *path = NULL;
|
||||
uint_t c, children;
|
||||
nvlist_t **child;
|
||||
|
||||
/*
|
||||
* First iterate over any children.
|
||||
*/
|
||||
if (nvlist_lookup_nvlist_array(nvl, ZPOOL_CONFIG_CHILDREN,
|
||||
&child, &children) == 0) {
|
||||
for (c = 0; c < children; c++)
|
||||
zfs_iter_vdev(zhp, child[c], data);
|
||||
}
|
||||
|
||||
/*
|
||||
* Iterate over any spares and cache devices
|
||||
*/
|
||||
if (nvlist_lookup_nvlist_array(nvl, ZPOOL_CONFIG_SPARES,
|
||||
&child, &children) == 0) {
|
||||
for (c = 0; c < children; c++)
|
||||
zfs_iter_vdev(zhp, child[c], data);
|
||||
}
|
||||
if (nvlist_lookup_nvlist_array(nvl, ZPOOL_CONFIG_L2CACHE,
|
||||
&child, &children) == 0) {
|
||||
for (c = 0; c < children; c++)
|
||||
zfs_iter_vdev(zhp, child[c], data);
|
||||
}
|
||||
|
||||
/* once a vdev was matched and processed there is nothing left to do */
|
||||
if (dp->dd_found)
|
||||
return;
|
||||
|
||||
/*
|
||||
* Match by GUID if available otherwise fallback to devid or physical
|
||||
*/
|
||||
if (dp->dd_vdev_guid != 0) {
|
||||
uint64_t guid;
|
||||
|
||||
if (nvlist_lookup_uint64(nvl, ZPOOL_CONFIG_GUID,
|
||||
&guid) != 0 || guid != dp->dd_vdev_guid) {
|
||||
return;
|
||||
}
|
||||
zed_log_msg(LOG_INFO, " zfs_iter_vdev: matched on %llu", guid);
|
||||
dp->dd_found = B_TRUE;
|
||||
|
||||
} else if (dp->dd_compare != NULL) {
|
||||
/*
|
||||
* NOTE: On Linux there is an event for partition, so unlike
|
||||
* illumos, substring matching is not required to accommodate
|
||||
* the partition suffix. An exact match will be present in
|
||||
* the dp->dd_compare value.
|
||||
*/
|
||||
if (nvlist_lookup_string(nvl, dp->dd_prop, &path) != 0 ||
|
||||
strcmp(dp->dd_compare, path) != 0)
|
||||
return;
|
||||
|
||||
zed_log_msg(LOG_INFO, " zfs_iter_vdev: matched %s on %s",
|
||||
dp->dd_prop, path);
|
||||
dp->dd_found = B_TRUE;
|
||||
|
||||
/* pass the new devid for use by replacing code */
|
||||
if (dp->dd_new_devid != NULL) {
|
||||
(void) nvlist_add_string(nvl, "new_devid",
|
||||
dp->dd_new_devid);
|
||||
}
|
||||
}
|
||||
|
||||
(dp->dd_func)(zhp, nvl, dp->dd_islabeled);
|
||||
}
|
||||
|
||||
static void
|
||||
zfs_enable_ds(void *arg)
|
||||
{
|
||||
unavailpool_t *pool = (unavailpool_t *)arg;
|
||||
|
||||
(void) zpool_enable_datasets(pool->uap_zhp, NULL, 0);
|
||||
zpool_close(pool->uap_zhp);
|
||||
free(pool);
|
||||
}
|
||||
|
||||
static int
|
||||
zfs_iter_pool(zpool_handle_t *zhp, void *data)
|
||||
{
|
||||
nvlist_t *config, *nvl;
|
||||
dev_data_t *dp = data;
|
||||
uint64_t pool_guid;
|
||||
unavailpool_t *pool;
|
||||
|
||||
zed_log_msg(LOG_INFO, "zfs_iter_pool: evaluating vdevs on %s (by %s)",
|
||||
zpool_get_name(zhp), dp->dd_vdev_guid ? "GUID" : dp->dd_prop);
|
||||
|
||||
/*
|
||||
* For each vdev in this pool, look for a match to apply dd_func
|
||||
*/
|
||||
if ((config = zpool_get_config(zhp, NULL)) != NULL) {
|
||||
if (dp->dd_pool_guid == 0 ||
|
||||
(nvlist_lookup_uint64(config, ZPOOL_CONFIG_POOL_GUID,
|
||||
&pool_guid) == 0 && pool_guid == dp->dd_pool_guid)) {
|
||||
(void) nvlist_lookup_nvlist(config,
|
||||
ZPOOL_CONFIG_VDEV_TREE, &nvl);
|
||||
zfs_iter_vdev(zhp, nvl, data);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* if this pool was originally unavailable,
|
||||
* then enable its datasets asynchronously
|
||||
*/
|
||||
if (g_enumeration_done) {
|
||||
for (pool = list_head(&g_pool_list); pool != NULL;
|
||||
pool = list_next(&g_pool_list, pool)) {
|
||||
|
||||
if (strcmp(zpool_get_name(zhp),
|
||||
zpool_get_name(pool->uap_zhp)))
|
||||
continue;
|
||||
if (zfs_toplevel_state(zhp) >= VDEV_STATE_DEGRADED) {
|
||||
list_remove(&g_pool_list, pool);
|
||||
(void) tpool_dispatch(g_tpool, zfs_enable_ds,
|
||||
pool);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
zpool_close(zhp);
|
||||
return (dp->dd_found); /* cease iteration after a match */
|
||||
}
|
||||
|
||||
/*
|
||||
* Given a physical device location, iterate over all
|
||||
* (pool, vdev) pairs which correspond to that location.
|
||||
*/
|
||||
static boolean_t
|
||||
devphys_iter(const char *physical, const char *devid, zfs_process_func_t func,
|
||||
boolean_t is_slice)
|
||||
{
|
||||
dev_data_t data = { 0 };
|
||||
|
||||
data.dd_compare = physical;
|
||||
data.dd_func = func;
|
||||
data.dd_prop = ZPOOL_CONFIG_PHYS_PATH;
|
||||
data.dd_found = B_FALSE;
|
||||
data.dd_islabeled = is_slice;
|
||||
data.dd_new_devid = devid; /* used by auto replace code */
|
||||
|
||||
(void) zpool_iter(g_zfshdl, zfs_iter_pool, &data);
|
||||
|
||||
return (data.dd_found);
|
||||
}
|
||||
|
||||
/*
|
||||
* Given a device identifier, find any vdevs with a matching devid.
|
||||
* On Linux we can match devid directly which is always a whole disk.
|
||||
*/
|
||||
static boolean_t
|
||||
devid_iter(const char *devid, zfs_process_func_t func, boolean_t is_slice)
|
||||
{
|
||||
dev_data_t data = { 0 };
|
||||
|
||||
data.dd_compare = devid;
|
||||
data.dd_func = func;
|
||||
data.dd_prop = ZPOOL_CONFIG_DEVID;
|
||||
data.dd_found = B_FALSE;
|
||||
data.dd_islabeled = is_slice;
|
||||
data.dd_new_devid = devid;
|
||||
|
||||
(void) zpool_iter(g_zfshdl, zfs_iter_pool, &data);
|
||||
|
||||
return (data.dd_found);
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle a EC_DEV_ADD.ESC_DISK event.
|
||||
*
|
||||
* illumos
|
||||
* Expects: DEV_PHYS_PATH string in schema
|
||||
* Matches: vdev's ZPOOL_CONFIG_PHYS_PATH or ZPOOL_CONFIG_DEVID
|
||||
*
|
||||
* path: '/dev/dsk/c0t1d0s0' (persistent)
|
||||
* devid: 'id1,sd@SATA_____Hitachi_HDS72101______JP2940HZ3H74MC/a'
|
||||
* phys_path: '/pci@0,0/pci103c,1609@11/disk@1,0:a'
|
||||
*
|
||||
* linux
|
||||
* provides: DEV_PHYS_PATH and DEV_IDENTIFIER strings in schema
|
||||
* Matches: vdev's ZPOOL_CONFIG_PHYS_PATH or ZPOOL_CONFIG_DEVID
|
||||
*
|
||||
* path: '/dev/sdc1' (not persistent)
|
||||
* devid: 'ata-SAMSUNG_HD204UI_S2HGJD2Z805891-part1'
|
||||
* phys_path: 'pci-0000:04:00.0-sas-0x4433221106000000-lun-0'
|
||||
*/
|
||||
static int
|
||||
zfs_deliver_add(nvlist_t *nvl, boolean_t is_lofi)
|
||||
{
|
||||
char *devpath = NULL, *devid;
|
||||
boolean_t is_slice;
|
||||
|
||||
/*
|
||||
* Expecting a devid string and an optional physical location
|
||||
*/
|
||||
if (nvlist_lookup_string(nvl, DEV_IDENTIFIER, &devid) != 0)
|
||||
return (-1);
|
||||
|
||||
(void) nvlist_lookup_string(nvl, DEV_PHYS_PATH, &devpath);
|
||||
|
||||
is_slice = (nvlist_lookup_boolean(nvl, DEV_IS_PART) == 0);
|
||||
|
||||
zed_log_msg(LOG_INFO, "zfs_deliver_add: adding %s (%s) (is_slice %d)",
|
||||
devid, devpath ? devpath : "NULL", is_slice);
|
||||
|
||||
/*
|
||||
* Iterate over all vdevs looking for a match in the following order:
|
||||
* 1. ZPOOL_CONFIG_DEVID (identifies the unique disk)
|
||||
* 2. ZPOOL_CONFIG_PHYS_PATH (identifies disk physical location).
|
||||
*
|
||||
* For disks, we only want to pay attention to vdevs marked as whole
|
||||
* disks or are a multipath device.
|
||||
*/
|
||||
if (!devid_iter(devid, zfs_process_add, is_slice) && devpath != NULL)
|
||||
(void) devphys_iter(devpath, devid, zfs_process_add, is_slice);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Called when we receive a VDEV_CHECK event, which indicates a device could not
|
||||
* be opened during initial pool open, but the autoreplace property was set on
|
||||
* the pool. In this case, we treat it as if it were an add event.
|
||||
*/
|
||||
static int
|
||||
zfs_deliver_check(nvlist_t *nvl)
|
||||
{
|
||||
dev_data_t data = { 0 };
|
||||
|
||||
if (nvlist_lookup_uint64(nvl, ZFS_EV_POOL_GUID,
|
||||
&data.dd_pool_guid) != 0 ||
|
||||
nvlist_lookup_uint64(nvl, ZFS_EV_VDEV_GUID,
|
||||
&data.dd_vdev_guid) != 0 ||
|
||||
data.dd_vdev_guid == 0)
|
||||
return (0);
|
||||
|
||||
zed_log_msg(LOG_INFO, "zfs_deliver_check: pool '%llu', vdev %llu",
|
||||
data.dd_pool_guid, data.dd_vdev_guid);
|
||||
|
||||
data.dd_func = zfs_process_add;
|
||||
|
||||
(void) zpool_iter(g_zfshdl, zfs_iter_pool, &data);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
static int
|
||||
zfsdle_vdev_online(zpool_handle_t *zhp, void *data)
|
||||
{
|
||||
char *devname = data;
|
||||
boolean_t avail_spare, l2cache;
|
||||
nvlist_t *tgt;
|
||||
int error;
|
||||
|
||||
zed_log_msg(LOG_INFO, "zfsdle_vdev_online: searching for '%s' in '%s'",
|
||||
devname, zpool_get_name(zhp));
|
||||
|
||||
if ((tgt = zpool_find_vdev_by_physpath(zhp, devname,
|
||||
&avail_spare, &l2cache, NULL)) != NULL) {
|
||||
char *path, fullpath[MAXPATHLEN];
|
||||
uint64_t wholedisk;
|
||||
|
||||
error = nvlist_lookup_string(tgt, ZPOOL_CONFIG_PATH, &path);
|
||||
if (error) {
|
||||
zpool_close(zhp);
|
||||
return (0);
|
||||
}
|
||||
|
||||
error = nvlist_lookup_uint64(tgt, ZPOOL_CONFIG_WHOLE_DISK,
|
||||
&wholedisk);
|
||||
if (error)
|
||||
wholedisk = 0;
|
||||
|
||||
if (wholedisk) {
|
||||
path = strrchr(path, '/');
|
||||
if (path != NULL) {
|
||||
path = zfs_strip_partition(path + 1);
|
||||
if (path == NULL) {
|
||||
zpool_close(zhp);
|
||||
return (0);
|
||||
}
|
||||
} else {
|
||||
zpool_close(zhp);
|
||||
return (0);
|
||||
}
|
||||
|
||||
(void) strlcpy(fullpath, path, sizeof (fullpath));
|
||||
free(path);
|
||||
|
||||
/*
|
||||
* We need to reopen the pool associated with this
|
||||
* device so that the kernel can update the size of
|
||||
* the expanded device. When expanding there is no
|
||||
* need to restart the scrub from the beginning.
|
||||
*/
|
||||
boolean_t scrub_restart = B_FALSE;
|
||||
(void) zpool_reopen_one(zhp, &scrub_restart);
|
||||
} else {
|
||||
(void) strlcpy(fullpath, path, sizeof (fullpath));
|
||||
}
|
||||
|
||||
if (zpool_get_prop_int(zhp, ZPOOL_PROP_AUTOEXPAND, NULL)) {
|
||||
vdev_state_t newstate;
|
||||
|
||||
if (zpool_get_state(zhp) != POOL_STATE_UNAVAIL) {
|
||||
error = zpool_vdev_online(zhp, fullpath, 0,
|
||||
&newstate);
|
||||
zed_log_msg(LOG_INFO, "zfsdle_vdev_online: "
|
||||
"setting device '%s' to ONLINE state "
|
||||
"in pool '%s': %d", fullpath,
|
||||
zpool_get_name(zhp), error);
|
||||
}
|
||||
}
|
||||
zpool_close(zhp);
|
||||
return (1);
|
||||
}
|
||||
zpool_close(zhp);
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* This function handles the ESC_DEV_DLE device change event. Use the
|
||||
* provided vdev guid when looking up a disk or partition, when the guid
|
||||
* is not present assume the entire disk is owned by ZFS and append the
|
||||
* expected -part1 partition information then lookup by physical path.
|
||||
*/
|
||||
static int
|
||||
zfs_deliver_dle(nvlist_t *nvl)
|
||||
{
|
||||
char *devname, name[MAXPATHLEN];
|
||||
uint64_t guid;
|
||||
|
||||
if (nvlist_lookup_uint64(nvl, ZFS_EV_VDEV_GUID, &guid) == 0) {
|
||||
sprintf(name, "%llu", (u_longlong_t)guid);
|
||||
} else if (nvlist_lookup_string(nvl, DEV_PHYS_PATH, &devname) == 0) {
|
||||
strlcpy(name, devname, MAXPATHLEN);
|
||||
zfs_append_partition(name, MAXPATHLEN);
|
||||
} else {
|
||||
zed_log_msg(LOG_INFO, "zfs_deliver_dle: no guid or physpath");
|
||||
}
|
||||
|
||||
if (zpool_iter(g_zfshdl, zfsdle_vdev_online, name) != 1) {
|
||||
zed_log_msg(LOG_INFO, "zfs_deliver_dle: device '%s' not "
|
||||
"found", name);
|
||||
return (1);
|
||||
}
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* syseventd daemon module event handler
|
||||
*
|
||||
* Handles syseventd daemon zfs device related events:
|
||||
*
|
||||
* EC_DEV_ADD.ESC_DISK
|
||||
* EC_DEV_STATUS.ESC_DEV_DLE
|
||||
* EC_ZFS.ESC_ZFS_VDEV_CHECK
|
||||
*
|
||||
* Note: assumes only one thread active at a time (not thread safe)
|
||||
*/
|
||||
static int
|
||||
zfs_slm_deliver_event(const char *class, const char *subclass, nvlist_t *nvl)
|
||||
{
|
||||
int ret;
|
||||
boolean_t is_lofi = B_FALSE, is_check = B_FALSE, is_dle = B_FALSE;
|
||||
|
||||
if (strcmp(class, EC_DEV_ADD) == 0) {
|
||||
/*
|
||||
* We're mainly interested in disk additions, but we also listen
|
||||
* for new loop devices, to allow for simplified testing.
|
||||
*/
|
||||
if (strcmp(subclass, ESC_DISK) == 0)
|
||||
is_lofi = B_FALSE;
|
||||
else if (strcmp(subclass, ESC_LOFI) == 0)
|
||||
is_lofi = B_TRUE;
|
||||
else
|
||||
return (0);
|
||||
|
||||
is_check = B_FALSE;
|
||||
} else if (strcmp(class, EC_ZFS) == 0 &&
|
||||
strcmp(subclass, ESC_ZFS_VDEV_CHECK) == 0) {
|
||||
/*
|
||||
* This event signifies that a device failed to open
|
||||
* during pool load, but the 'autoreplace' property was
|
||||
* set, so we should pretend it's just been added.
|
||||
*/
|
||||
is_check = B_TRUE;
|
||||
} else if (strcmp(class, EC_DEV_STATUS) == 0 &&
|
||||
strcmp(subclass, ESC_DEV_DLE) == 0) {
|
||||
is_dle = B_TRUE;
|
||||
} else {
|
||||
return (0);
|
||||
}
|
||||
|
||||
if (is_dle)
|
||||
ret = zfs_deliver_dle(nvl);
|
||||
else if (is_check)
|
||||
ret = zfs_deliver_check(nvl);
|
||||
else
|
||||
ret = zfs_deliver_add(nvl, is_lofi);
|
||||
|
||||
return (ret);
|
||||
}
|
||||
|
||||
/*ARGSUSED*/
|
||||
static void *
|
||||
zfs_enum_pools(void *arg)
|
||||
{
|
||||
(void) zpool_iter(g_zfshdl, zfs_unavail_pool, (void *)&g_pool_list);
|
||||
/*
|
||||
* Linux - instead of using a thread pool, each list entry
|
||||
* will spawn a thread when an unavailable pool transitions
|
||||
* to available. zfs_slm_fini will wait for these threads.
|
||||
*/
|
||||
g_enumeration_done = B_TRUE;
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* called from zed daemon at startup
|
||||
*
|
||||
* sent messages from zevents or udev monitor
|
||||
*
|
||||
* For now, each agent has its own libzfs instance
|
||||
*/
|
||||
int
|
||||
zfs_slm_init()
|
||||
{
|
||||
if ((g_zfshdl = libzfs_init()) == NULL)
|
||||
return (-1);
|
||||
|
||||
/*
|
||||
* collect a list of unavailable pools (asynchronously,
|
||||
* since this can take a while)
|
||||
*/
|
||||
list_create(&g_pool_list, sizeof (struct unavailpool),
|
||||
offsetof(struct unavailpool, uap_node));
|
||||
|
||||
if (pthread_create(&g_zfs_tid, NULL, zfs_enum_pools, NULL) != 0) {
|
||||
list_destroy(&g_pool_list);
|
||||
libzfs_fini(g_zfshdl);
|
||||
return (-1);
|
||||
}
|
||||
|
||||
list_create(&g_device_list, sizeof (struct pendingdev),
|
||||
offsetof(struct pendingdev, pd_node));
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
void
|
||||
zfs_slm_fini()
|
||||
{
|
||||
unavailpool_t *pool;
|
||||
pendingdev_t *device;
|
||||
|
||||
/* wait for zfs_enum_pools thread to complete */
|
||||
(void) pthread_join(g_zfs_tid, NULL);
|
||||
/* destroy the thread pool */
|
||||
if (g_tpool != NULL) {
|
||||
tpool_wait(g_tpool);
|
||||
tpool_destroy(g_tpool);
|
||||
}
|
||||
|
||||
while ((pool = (list_head(&g_pool_list))) != NULL) {
|
||||
list_remove(&g_pool_list, pool);
|
||||
zpool_close(pool->uap_zhp);
|
||||
free(pool);
|
||||
}
|
||||
list_destroy(&g_pool_list);
|
||||
|
||||
while ((device = (list_head(&g_device_list))) != NULL) {
|
||||
list_remove(&g_device_list, device);
|
||||
free(device);
|
||||
}
|
||||
list_destroy(&g_device_list);
|
||||
|
||||
libzfs_fini(g_zfshdl);
|
||||
}
|
||||
|
||||
void
|
||||
zfs_slm_event(const char *class, const char *subclass, nvlist_t *nvl)
|
||||
{
|
||||
zed_log_msg(LOG_INFO, "zfs_slm_event: %s.%s", class, subclass);
|
||||
(void) zfs_slm_deliver_event(class, subclass, nvl);
|
||||
}
|
557
sys/contrib/openzfs/cmd/zed/agents/zfs_retire.c
Normal file
557
sys/contrib/openzfs/cmd/zed/agents/zfs_retire.c
Normal file
@ -0,0 +1,557 @@
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License (the "License").
|
||||
* You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||
* or http://www.opensolaris.org/os/licensing.
|
||||
* See the License for the specific language governing permissions
|
||||
* and limitations under the License.
|
||||
*
|
||||
* When distributing Covered Code, include this CDDL HEADER in each
|
||||
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||
* If applicable, add the following below this CDDL HEADER, with the
|
||||
* fields enclosed by brackets "[]" replaced with your own identifying
|
||||
* information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
*
|
||||
* CDDL HEADER END
|
||||
*/
|
||||
/*
|
||||
* Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Copyright (c) 2016, Intel Corporation.
|
||||
* Copyright (c) 2018, loli10K <ezomori.nozomu@gmail.com>
|
||||
*/
|
||||
|
||||
/*
|
||||
* The ZFS retire agent is responsible for managing hot spares across all pools.
|
||||
* When we see a device fault or a device removal, we try to open the associated
|
||||
* pool and look for any hot spares. We iterate over any available hot spares
|
||||
* and attempt a 'zpool replace' for each one.
|
||||
*
|
||||
* For vdevs diagnosed as faulty, the agent is also responsible for proactively
|
||||
* marking the vdev FAULTY (for I/O errors) or DEGRADED (for checksum errors).
|
||||
*/
|
||||
|
||||
#include <sys/fs/zfs.h>
|
||||
#include <sys/fm/protocol.h>
|
||||
#include <sys/fm/fs/zfs.h>
|
||||
#include <libzfs.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "zfs_agents.h"
|
||||
#include "fmd_api.h"
|
||||
|
||||
|
||||
typedef struct zfs_retire_repaired {
|
||||
struct zfs_retire_repaired *zrr_next;
|
||||
uint64_t zrr_pool;
|
||||
uint64_t zrr_vdev;
|
||||
} zfs_retire_repaired_t;
|
||||
|
||||
typedef struct zfs_retire_data {
|
||||
libzfs_handle_t *zrd_hdl;
|
||||
zfs_retire_repaired_t *zrd_repaired;
|
||||
} zfs_retire_data_t;
|
||||
|
||||
static void
|
||||
zfs_retire_clear_data(fmd_hdl_t *hdl, zfs_retire_data_t *zdp)
|
||||
{
|
||||
zfs_retire_repaired_t *zrp;
|
||||
|
||||
while ((zrp = zdp->zrd_repaired) != NULL) {
|
||||
zdp->zrd_repaired = zrp->zrr_next;
|
||||
fmd_hdl_free(hdl, zrp, sizeof (zfs_retire_repaired_t));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Find a pool with a matching GUID.
|
||||
*/
|
||||
typedef struct find_cbdata {
|
||||
uint64_t cb_guid;
|
||||
zpool_handle_t *cb_zhp;
|
||||
nvlist_t *cb_vdev;
|
||||
} find_cbdata_t;
|
||||
|
||||
static int
|
||||
find_pool(zpool_handle_t *zhp, void *data)
|
||||
{
|
||||
find_cbdata_t *cbp = data;
|
||||
|
||||
if (cbp->cb_guid ==
|
||||
zpool_get_prop_int(zhp, ZPOOL_PROP_GUID, NULL)) {
|
||||
cbp->cb_zhp = zhp;
|
||||
return (1);
|
||||
}
|
||||
|
||||
zpool_close(zhp);
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Find a vdev within a tree with a matching GUID.
|
||||
*/
|
||||
static nvlist_t *
|
||||
find_vdev(libzfs_handle_t *zhdl, nvlist_t *nv, uint64_t search_guid)
|
||||
{
|
||||
uint64_t guid;
|
||||
nvlist_t **child;
|
||||
uint_t c, children;
|
||||
nvlist_t *ret;
|
||||
|
||||
if (nvlist_lookup_uint64(nv, ZPOOL_CONFIG_GUID, &guid) == 0 &&
|
||||
guid == search_guid) {
|
||||
fmd_hdl_debug(fmd_module_hdl("zfs-retire"),
|
||||
"matched vdev %llu", guid);
|
||||
return (nv);
|
||||
}
|
||||
|
||||
if (nvlist_lookup_nvlist_array(nv, ZPOOL_CONFIG_CHILDREN,
|
||||
&child, &children) != 0)
|
||||
return (NULL);
|
||||
|
||||
for (c = 0; c < children; c++) {
|
||||
if ((ret = find_vdev(zhdl, child[c], search_guid)) != NULL)
|
||||
return (ret);
|
||||
}
|
||||
|
||||
if (nvlist_lookup_nvlist_array(nv, ZPOOL_CONFIG_L2CACHE,
|
||||
&child, &children) != 0)
|
||||
return (NULL);
|
||||
|
||||
for (c = 0; c < children; c++) {
|
||||
if ((ret = find_vdev(zhdl, child[c], search_guid)) != NULL)
|
||||
return (ret);
|
||||
}
|
||||
|
||||
if (nvlist_lookup_nvlist_array(nv, ZPOOL_CONFIG_SPARES,
|
||||
&child, &children) != 0)
|
||||
return (NULL);
|
||||
|
||||
for (c = 0; c < children; c++) {
|
||||
if ((ret = find_vdev(zhdl, child[c], search_guid)) != NULL)
|
||||
return (ret);
|
||||
}
|
||||
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* Given a (pool, vdev) GUID pair, find the matching pool and vdev.
|
||||
*/
|
||||
static zpool_handle_t *
|
||||
find_by_guid(libzfs_handle_t *zhdl, uint64_t pool_guid, uint64_t vdev_guid,
|
||||
nvlist_t **vdevp)
|
||||
{
|
||||
find_cbdata_t cb;
|
||||
zpool_handle_t *zhp;
|
||||
nvlist_t *config, *nvroot;
|
||||
|
||||
/*
|
||||
* Find the corresponding pool and make sure the vdev still exists.
|
||||
*/
|
||||
cb.cb_guid = pool_guid;
|
||||
if (zpool_iter(zhdl, find_pool, &cb) != 1)
|
||||
return (NULL);
|
||||
|
||||
zhp = cb.cb_zhp;
|
||||
config = zpool_get_config(zhp, NULL);
|
||||
if (nvlist_lookup_nvlist(config, ZPOOL_CONFIG_VDEV_TREE,
|
||||
&nvroot) != 0) {
|
||||
zpool_close(zhp);
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
if (vdev_guid != 0) {
|
||||
if ((*vdevp = find_vdev(zhdl, nvroot, vdev_guid)) == NULL) {
|
||||
zpool_close(zhp);
|
||||
return (NULL);
|
||||
}
|
||||
}
|
||||
|
||||
return (zhp);
|
||||
}
|
||||
|
||||
/*
|
||||
* Given a vdev, attempt to replace it with every known spare until one
|
||||
* succeeds or we run out of devices to try.
|
||||
* Return whether we were successful or not in replacing the device.
|
||||
*/
|
||||
static boolean_t
|
||||
replace_with_spare(fmd_hdl_t *hdl, zpool_handle_t *zhp, nvlist_t *vdev)
|
||||
{
|
||||
nvlist_t *config, *nvroot, *replacement;
|
||||
nvlist_t **spares;
|
||||
uint_t s, nspares;
|
||||
char *dev_name;
|
||||
zprop_source_t source;
|
||||
int ashift;
|
||||
|
||||
config = zpool_get_config(zhp, NULL);
|
||||
if (nvlist_lookup_nvlist(config, ZPOOL_CONFIG_VDEV_TREE,
|
||||
&nvroot) != 0)
|
||||
return (B_FALSE);
|
||||
|
||||
/*
|
||||
* Find out if there are any hot spares available in the pool.
|
||||
*/
|
||||
if (nvlist_lookup_nvlist_array(nvroot, ZPOOL_CONFIG_SPARES,
|
||||
&spares, &nspares) != 0)
|
||||
return (B_FALSE);
|
||||
|
||||
/*
|
||||
* lookup "ashift" pool property, we may need it for the replacement
|
||||
*/
|
||||
ashift = zpool_get_prop_int(zhp, ZPOOL_PROP_ASHIFT, &source);
|
||||
|
||||
replacement = fmd_nvl_alloc(hdl, FMD_SLEEP);
|
||||
|
||||
(void) nvlist_add_string(replacement, ZPOOL_CONFIG_TYPE,
|
||||
VDEV_TYPE_ROOT);
|
||||
|
||||
dev_name = zpool_vdev_name(NULL, zhp, vdev, B_FALSE);
|
||||
|
||||
/*
|
||||
* Try to replace each spare, ending when we successfully
|
||||
* replace it.
|
||||
*/
|
||||
for (s = 0; s < nspares; s++) {
|
||||
char *spare_name;
|
||||
|
||||
if (nvlist_lookup_string(spares[s], ZPOOL_CONFIG_PATH,
|
||||
&spare_name) != 0)
|
||||
continue;
|
||||
|
||||
/* if set, add the "ashift" pool property to the spare nvlist */
|
||||
if (source != ZPROP_SRC_DEFAULT)
|
||||
(void) nvlist_add_uint64(spares[s],
|
||||
ZPOOL_CONFIG_ASHIFT, ashift);
|
||||
|
||||
(void) nvlist_add_nvlist_array(replacement,
|
||||
ZPOOL_CONFIG_CHILDREN, &spares[s], 1);
|
||||
|
||||
fmd_hdl_debug(hdl, "zpool_vdev_replace '%s' with spare '%s'",
|
||||
dev_name, basename(spare_name));
|
||||
|
||||
if (zpool_vdev_attach(zhp, dev_name, spare_name,
|
||||
replacement, B_TRUE, B_FALSE) == 0) {
|
||||
free(dev_name);
|
||||
nvlist_free(replacement);
|
||||
return (B_TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
free(dev_name);
|
||||
nvlist_free(replacement);
|
||||
|
||||
return (B_FALSE);
|
||||
}
|
||||
|
||||
/*
|
||||
* Repair this vdev if we had diagnosed a 'fault.fs.zfs.device' and
|
||||
* ASRU is now usable. ZFS has found the device to be present and
|
||||
* functioning.
|
||||
*/
|
||||
/*ARGSUSED*/
|
||||
static void
|
||||
zfs_vdev_repair(fmd_hdl_t *hdl, nvlist_t *nvl)
|
||||
{
|
||||
zfs_retire_data_t *zdp = fmd_hdl_getspecific(hdl);
|
||||
zfs_retire_repaired_t *zrp;
|
||||
uint64_t pool_guid, vdev_guid;
|
||||
if (nvlist_lookup_uint64(nvl, FM_EREPORT_PAYLOAD_ZFS_POOL_GUID,
|
||||
&pool_guid) != 0 || nvlist_lookup_uint64(nvl,
|
||||
FM_EREPORT_PAYLOAD_ZFS_VDEV_GUID, &vdev_guid) != 0)
|
||||
return;
|
||||
|
||||
/*
|
||||
* Before checking the state of the ASRU, go through and see if we've
|
||||
* already made an attempt to repair this ASRU. This list is cleared
|
||||
* whenever we receive any kind of list event, and is designed to
|
||||
* prevent us from generating a feedback loop when we attempt repairs
|
||||
* against a faulted pool. The problem is that checking the unusable
|
||||
* state of the ASRU can involve opening the pool, which can post
|
||||
* statechange events but otherwise leave the pool in the faulted
|
||||
* state. This list allows us to detect when a statechange event is
|
||||
* due to our own request.
|
||||
*/
|
||||
for (zrp = zdp->zrd_repaired; zrp != NULL; zrp = zrp->zrr_next) {
|
||||
if (zrp->zrr_pool == pool_guid &&
|
||||
zrp->zrr_vdev == vdev_guid)
|
||||
return;
|
||||
}
|
||||
|
||||
zrp = fmd_hdl_alloc(hdl, sizeof (zfs_retire_repaired_t), FMD_SLEEP);
|
||||
zrp->zrr_next = zdp->zrd_repaired;
|
||||
zrp->zrr_pool = pool_guid;
|
||||
zrp->zrr_vdev = vdev_guid;
|
||||
zdp->zrd_repaired = zrp;
|
||||
|
||||
fmd_hdl_debug(hdl, "marking repaired vdev %llu on pool %llu",
|
||||
vdev_guid, pool_guid);
|
||||
}
|
||||
|
||||
/*ARGSUSED*/
|
||||
static void
|
||||
zfs_retire_recv(fmd_hdl_t *hdl, fmd_event_t *ep, nvlist_t *nvl,
|
||||
const char *class)
|
||||
{
|
||||
uint64_t pool_guid, vdev_guid;
|
||||
zpool_handle_t *zhp;
|
||||
nvlist_t *resource, *fault;
|
||||
nvlist_t **faults;
|
||||
uint_t f, nfaults;
|
||||
zfs_retire_data_t *zdp = fmd_hdl_getspecific(hdl);
|
||||
libzfs_handle_t *zhdl = zdp->zrd_hdl;
|
||||
boolean_t fault_device, degrade_device;
|
||||
boolean_t is_repair;
|
||||
char *scheme;
|
||||
nvlist_t *vdev = NULL;
|
||||
char *uuid;
|
||||
int repair_done = 0;
|
||||
boolean_t retire;
|
||||
boolean_t is_disk;
|
||||
vdev_aux_t aux;
|
||||
uint64_t state = 0;
|
||||
|
||||
fmd_hdl_debug(hdl, "zfs_retire_recv: '%s'", class);
|
||||
|
||||
nvlist_lookup_uint64(nvl, FM_EREPORT_PAYLOAD_ZFS_VDEV_STATE, &state);
|
||||
|
||||
/*
|
||||
* If this is a resource notifying us of device removal then simply
|
||||
* check for an available spare and continue unless the device is a
|
||||
* l2arc vdev, in which case we just offline it.
|
||||
*/
|
||||
if (strcmp(class, "resource.fs.zfs.removed") == 0 ||
|
||||
(strcmp(class, "resource.fs.zfs.statechange") == 0 &&
|
||||
state == VDEV_STATE_REMOVED)) {
|
||||
char *devtype;
|
||||
char *devname;
|
||||
|
||||
if (nvlist_lookup_uint64(nvl, FM_EREPORT_PAYLOAD_ZFS_POOL_GUID,
|
||||
&pool_guid) != 0 ||
|
||||
nvlist_lookup_uint64(nvl, FM_EREPORT_PAYLOAD_ZFS_VDEV_GUID,
|
||||
&vdev_guid) != 0)
|
||||
return;
|
||||
|
||||
if ((zhp = find_by_guid(zhdl, pool_guid, vdev_guid,
|
||||
&vdev)) == NULL)
|
||||
return;
|
||||
|
||||
devname = zpool_vdev_name(NULL, zhp, vdev, B_FALSE);
|
||||
|
||||
/* Can't replace l2arc with a spare: offline the device */
|
||||
if (nvlist_lookup_string(nvl, FM_EREPORT_PAYLOAD_ZFS_VDEV_TYPE,
|
||||
&devtype) == 0 && strcmp(devtype, VDEV_TYPE_L2CACHE) == 0) {
|
||||
fmd_hdl_debug(hdl, "zpool_vdev_offline '%s'", devname);
|
||||
zpool_vdev_offline(zhp, devname, B_TRUE);
|
||||
} else if (!fmd_prop_get_int32(hdl, "spare_on_remove") ||
|
||||
replace_with_spare(hdl, zhp, vdev) == B_FALSE) {
|
||||
/* Could not handle with spare */
|
||||
fmd_hdl_debug(hdl, "no spare for '%s'", devname);
|
||||
}
|
||||
|
||||
free(devname);
|
||||
zpool_close(zhp);
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp(class, FM_LIST_RESOLVED_CLASS) == 0)
|
||||
return;
|
||||
|
||||
/*
|
||||
* Note: on zfsonlinux statechange events are more than just
|
||||
* healthy ones so we need to confirm the actual state value.
|
||||
*/
|
||||
if (strcmp(class, "resource.fs.zfs.statechange") == 0 &&
|
||||
state == VDEV_STATE_HEALTHY) {
|
||||
zfs_vdev_repair(hdl, nvl);
|
||||
return;
|
||||
}
|
||||
if (strcmp(class, "sysevent.fs.zfs.vdev_remove") == 0) {
|
||||
zfs_vdev_repair(hdl, nvl);
|
||||
return;
|
||||
}
|
||||
|
||||
zfs_retire_clear_data(hdl, zdp);
|
||||
|
||||
if (strcmp(class, FM_LIST_REPAIRED_CLASS) == 0)
|
||||
is_repair = B_TRUE;
|
||||
else
|
||||
is_repair = B_FALSE;
|
||||
|
||||
/*
|
||||
* We subscribe to zfs faults as well as all repair events.
|
||||
*/
|
||||
if (nvlist_lookup_nvlist_array(nvl, FM_SUSPECT_FAULT_LIST,
|
||||
&faults, &nfaults) != 0)
|
||||
return;
|
||||
|
||||
for (f = 0; f < nfaults; f++) {
|
||||
fault = faults[f];
|
||||
|
||||
fault_device = B_FALSE;
|
||||
degrade_device = B_FALSE;
|
||||
is_disk = B_FALSE;
|
||||
|
||||
if (nvlist_lookup_boolean_value(fault, FM_SUSPECT_RETIRE,
|
||||
&retire) == 0 && retire == 0)
|
||||
continue;
|
||||
|
||||
/*
|
||||
* While we subscribe to fault.fs.zfs.*, we only take action
|
||||
* for faults targeting a specific vdev (open failure or SERD
|
||||
* failure). We also subscribe to fault.io.* events, so that
|
||||
* faulty disks will be faulted in the ZFS configuration.
|
||||
*/
|
||||
if (fmd_nvl_class_match(hdl, fault, "fault.fs.zfs.vdev.io")) {
|
||||
fault_device = B_TRUE;
|
||||
} else if (fmd_nvl_class_match(hdl, fault,
|
||||
"fault.fs.zfs.vdev.checksum")) {
|
||||
degrade_device = B_TRUE;
|
||||
} else if (fmd_nvl_class_match(hdl, fault,
|
||||
"fault.fs.zfs.device")) {
|
||||
fault_device = B_FALSE;
|
||||
} else if (fmd_nvl_class_match(hdl, fault, "fault.io.*")) {
|
||||
is_disk = B_TRUE;
|
||||
fault_device = B_TRUE;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_disk) {
|
||||
continue;
|
||||
} else {
|
||||
/*
|
||||
* This is a ZFS fault. Lookup the resource, and
|
||||
* attempt to find the matching vdev.
|
||||
*/
|
||||
if (nvlist_lookup_nvlist(fault, FM_FAULT_RESOURCE,
|
||||
&resource) != 0 ||
|
||||
nvlist_lookup_string(resource, FM_FMRI_SCHEME,
|
||||
&scheme) != 0)
|
||||
continue;
|
||||
|
||||
if (strcmp(scheme, FM_FMRI_SCHEME_ZFS) != 0)
|
||||
continue;
|
||||
|
||||
if (nvlist_lookup_uint64(resource, FM_FMRI_ZFS_POOL,
|
||||
&pool_guid) != 0)
|
||||
continue;
|
||||
|
||||
if (nvlist_lookup_uint64(resource, FM_FMRI_ZFS_VDEV,
|
||||
&vdev_guid) != 0) {
|
||||
if (is_repair)
|
||||
vdev_guid = 0;
|
||||
else
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((zhp = find_by_guid(zhdl, pool_guid, vdev_guid,
|
||||
&vdev)) == NULL)
|
||||
continue;
|
||||
|
||||
aux = VDEV_AUX_ERR_EXCEEDED;
|
||||
}
|
||||
|
||||
if (vdev_guid == 0) {
|
||||
/*
|
||||
* For pool-level repair events, clear the entire pool.
|
||||
*/
|
||||
fmd_hdl_debug(hdl, "zpool_clear of pool '%s'",
|
||||
zpool_get_name(zhp));
|
||||
(void) zpool_clear(zhp, NULL, NULL);
|
||||
zpool_close(zhp);
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* If this is a repair event, then mark the vdev as repaired and
|
||||
* continue.
|
||||
*/
|
||||
if (is_repair) {
|
||||
repair_done = 1;
|
||||
fmd_hdl_debug(hdl, "zpool_clear of pool '%s' vdev %llu",
|
||||
zpool_get_name(zhp), vdev_guid);
|
||||
(void) zpool_vdev_clear(zhp, vdev_guid);
|
||||
zpool_close(zhp);
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* Actively fault the device if needed.
|
||||
*/
|
||||
if (fault_device)
|
||||
(void) zpool_vdev_fault(zhp, vdev_guid, aux);
|
||||
if (degrade_device)
|
||||
(void) zpool_vdev_degrade(zhp, vdev_guid, aux);
|
||||
|
||||
if (fault_device || degrade_device)
|
||||
fmd_hdl_debug(hdl, "zpool_vdev_%s: vdev %llu on '%s'",
|
||||
fault_device ? "fault" : "degrade", vdev_guid,
|
||||
zpool_get_name(zhp));
|
||||
|
||||
/*
|
||||
* Attempt to substitute a hot spare.
|
||||
*/
|
||||
(void) replace_with_spare(hdl, zhp, vdev);
|
||||
zpool_close(zhp);
|
||||
}
|
||||
|
||||
if (strcmp(class, FM_LIST_REPAIRED_CLASS) == 0 && repair_done &&
|
||||
nvlist_lookup_string(nvl, FM_SUSPECT_UUID, &uuid) == 0)
|
||||
fmd_case_uuresolved(hdl, uuid);
|
||||
}
|
||||
|
||||
static const fmd_hdl_ops_t fmd_ops = {
|
||||
zfs_retire_recv, /* fmdo_recv */
|
||||
NULL, /* fmdo_timeout */
|
||||
NULL, /* fmdo_close */
|
||||
NULL, /* fmdo_stats */
|
||||
NULL, /* fmdo_gc */
|
||||
};
|
||||
|
||||
static const fmd_prop_t fmd_props[] = {
|
||||
{ "spare_on_remove", FMD_TYPE_BOOL, "true" },
|
||||
{ NULL, 0, NULL }
|
||||
};
|
||||
|
||||
static const fmd_hdl_info_t fmd_info = {
|
||||
"ZFS Retire Agent", "1.0", &fmd_ops, fmd_props
|
||||
};
|
||||
|
||||
void
|
||||
_zfs_retire_init(fmd_hdl_t *hdl)
|
||||
{
|
||||
zfs_retire_data_t *zdp;
|
||||
libzfs_handle_t *zhdl;
|
||||
|
||||
if ((zhdl = libzfs_init()) == NULL)
|
||||
return;
|
||||
|
||||
if (fmd_hdl_register(hdl, FMD_API_VERSION, &fmd_info) != 0) {
|
||||
libzfs_fini(zhdl);
|
||||
return;
|
||||
}
|
||||
|
||||
zdp = fmd_hdl_zalloc(hdl, sizeof (zfs_retire_data_t), FMD_SLEEP);
|
||||
zdp->zrd_hdl = zhdl;
|
||||
|
||||
fmd_hdl_setspecific(hdl, zdp);
|
||||
}
|
||||
|
||||
void
|
||||
_zfs_retire_fini(fmd_hdl_t *hdl)
|
||||
{
|
||||
zfs_retire_data_t *zdp = fmd_hdl_getspecific(hdl);
|
||||
|
||||
if (zdp != NULL) {
|
||||
zfs_retire_clear_data(hdl, zdp);
|
||||
libzfs_fini(zdp->zrd_hdl);
|
||||
fmd_hdl_free(hdl, zdp, sizeof (zfs_retire_data_t));
|
||||
}
|
||||
}
|
306
sys/contrib/openzfs/cmd/zed/zed.c
Normal file
306
sys/contrib/openzfs/cmd/zed/zed.c
Normal file
@ -0,0 +1,306 @@
|
||||
/*
|
||||
* This file is part of the ZFS Event Daemon (ZED)
|
||||
* for ZFS on Linux (ZoL) <http://zfsonlinux.org/>.
|
||||
* Developed at Lawrence Livermore National Laboratory (LLNL-CODE-403049).
|
||||
* Copyright (C) 2013-2014 Lawrence Livermore National Security, LLC.
|
||||
* Refer to the ZoL git commit log for authoritative copyright attribution.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License Version 1.0 (CDDL-1.0).
|
||||
* You can obtain a copy of the license from the top-level file
|
||||
* "OPENSOLARIS.LICENSE" or at <http://opensource.org/licenses/CDDL-1.0>.
|
||||
* You may not use this file except in compliance with the license.
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include "zed.h"
|
||||
#include "zed_conf.h"
|
||||
#include "zed_event.h"
|
||||
#include "zed_file.h"
|
||||
#include "zed_log.h"
|
||||
|
||||
static volatile sig_atomic_t _got_exit = 0;
|
||||
static volatile sig_atomic_t _got_hup = 0;
|
||||
|
||||
/*
|
||||
* Signal handler for SIGINT & SIGTERM.
|
||||
*/
|
||||
static void
|
||||
_exit_handler(int signum)
|
||||
{
|
||||
_got_exit = 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Signal handler for SIGHUP.
|
||||
*/
|
||||
static void
|
||||
_hup_handler(int signum)
|
||||
{
|
||||
_got_hup = 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Register signal handlers.
|
||||
*/
|
||||
static void
|
||||
_setup_sig_handlers(void)
|
||||
{
|
||||
struct sigaction sa;
|
||||
|
||||
if (sigemptyset(&sa.sa_mask) < 0)
|
||||
zed_log_die("Failed to initialize sigset");
|
||||
|
||||
sa.sa_flags = SA_RESTART;
|
||||
sa.sa_handler = SIG_IGN;
|
||||
|
||||
if (sigaction(SIGPIPE, &sa, NULL) < 0)
|
||||
zed_log_die("Failed to ignore SIGPIPE");
|
||||
|
||||
sa.sa_handler = _exit_handler;
|
||||
if (sigaction(SIGINT, &sa, NULL) < 0)
|
||||
zed_log_die("Failed to register SIGINT handler");
|
||||
|
||||
if (sigaction(SIGTERM, &sa, NULL) < 0)
|
||||
zed_log_die("Failed to register SIGTERM handler");
|
||||
|
||||
sa.sa_handler = _hup_handler;
|
||||
if (sigaction(SIGHUP, &sa, NULL) < 0)
|
||||
zed_log_die("Failed to register SIGHUP handler");
|
||||
}
|
||||
|
||||
/*
|
||||
* Lock all current and future pages in the virtual memory address space.
|
||||
* Access to locked pages will never be delayed by a page fault.
|
||||
*
|
||||
* EAGAIN is tested up to max_tries in case this is a transient error.
|
||||
*
|
||||
* Note that memory locks are not inherited by a child created via fork()
|
||||
* and are automatically removed during an execve(). As such, this must
|
||||
* be called after the daemon fork()s (when running in the background).
|
||||
*/
|
||||
static void
|
||||
_lock_memory(void)
|
||||
{
|
||||
#if HAVE_MLOCKALL
|
||||
int i = 0;
|
||||
const int max_tries = 10;
|
||||
|
||||
for (i = 0; i < max_tries; i++) {
|
||||
if (mlockall(MCL_CURRENT | MCL_FUTURE) == 0) {
|
||||
zed_log_msg(LOG_INFO, "Locked all pages in memory");
|
||||
return;
|
||||
}
|
||||
if (errno != EAGAIN)
|
||||
break;
|
||||
}
|
||||
zed_log_die("Failed to lock memory pages: %s", strerror(errno));
|
||||
|
||||
#else /* HAVE_MLOCKALL */
|
||||
zed_log_die("Failed to lock memory pages: mlockall() not supported");
|
||||
#endif /* HAVE_MLOCKALL */
|
||||
}
|
||||
|
||||
/*
|
||||
* Start daemonization of the process including the double fork().
|
||||
*
|
||||
* The parent process will block here until _finish_daemonize() is called
|
||||
* (in the grandchild process), at which point the parent process will exit.
|
||||
* This prevents the parent process from exiting until initialization is
|
||||
* complete.
|
||||
*/
|
||||
static void
|
||||
_start_daemonize(void)
|
||||
{
|
||||
pid_t pid;
|
||||
struct sigaction sa;
|
||||
|
||||
/* Create pipe for communicating with child during daemonization. */
|
||||
zed_log_pipe_open();
|
||||
|
||||
/* Background process and ensure child is not process group leader. */
|
||||
pid = fork();
|
||||
if (pid < 0) {
|
||||
zed_log_die("Failed to create child process: %s",
|
||||
strerror(errno));
|
||||
} else if (pid > 0) {
|
||||
|
||||
/* Close writes since parent will only read from pipe. */
|
||||
zed_log_pipe_close_writes();
|
||||
|
||||
/* Wait for notification that daemonization is complete. */
|
||||
zed_log_pipe_wait();
|
||||
|
||||
zed_log_pipe_close_reads();
|
||||
_exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
/* Close reads since child will only write to pipe. */
|
||||
zed_log_pipe_close_reads();
|
||||
|
||||
/* Create independent session and detach from terminal. */
|
||||
if (setsid() < 0)
|
||||
zed_log_die("Failed to create new session: %s",
|
||||
strerror(errno));
|
||||
|
||||
/* Prevent child from terminating on HUP when session leader exits. */
|
||||
if (sigemptyset(&sa.sa_mask) < 0)
|
||||
zed_log_die("Failed to initialize sigset");
|
||||
|
||||
sa.sa_flags = 0;
|
||||
sa.sa_handler = SIG_IGN;
|
||||
|
||||
if (sigaction(SIGHUP, &sa, NULL) < 0)
|
||||
zed_log_die("Failed to ignore SIGHUP");
|
||||
|
||||
/* Ensure process cannot re-acquire terminal. */
|
||||
pid = fork();
|
||||
if (pid < 0) {
|
||||
zed_log_die("Failed to create grandchild process: %s",
|
||||
strerror(errno));
|
||||
} else if (pid > 0) {
|
||||
_exit(EXIT_SUCCESS);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Finish daemonization of the process by closing stdin/stdout/stderr.
|
||||
*
|
||||
* This must be called at the end of initialization after all external
|
||||
* communication channels are established and accessible.
|
||||
*/
|
||||
static void
|
||||
_finish_daemonize(void)
|
||||
{
|
||||
int devnull;
|
||||
|
||||
/* Preserve fd 0/1/2, but discard data to/from stdin/stdout/stderr. */
|
||||
devnull = open("/dev/null", O_RDWR);
|
||||
if (devnull < 0)
|
||||
zed_log_die("Failed to open /dev/null: %s", strerror(errno));
|
||||
|
||||
if (dup2(devnull, STDIN_FILENO) < 0)
|
||||
zed_log_die("Failed to dup /dev/null onto stdin: %s",
|
||||
strerror(errno));
|
||||
|
||||
if (dup2(devnull, STDOUT_FILENO) < 0)
|
||||
zed_log_die("Failed to dup /dev/null onto stdout: %s",
|
||||
strerror(errno));
|
||||
|
||||
if (dup2(devnull, STDERR_FILENO) < 0)
|
||||
zed_log_die("Failed to dup /dev/null onto stderr: %s",
|
||||
strerror(errno));
|
||||
|
||||
if ((devnull > STDERR_FILENO) && (close(devnull) < 0))
|
||||
zed_log_die("Failed to close /dev/null: %s", strerror(errno));
|
||||
|
||||
/* Notify parent that daemonization is complete. */
|
||||
zed_log_pipe_close_writes();
|
||||
}
|
||||
|
||||
/*
|
||||
* ZFS Event Daemon (ZED).
|
||||
*/
|
||||
int
|
||||
main(int argc, char *argv[])
|
||||
{
|
||||
struct zed_conf *zcp;
|
||||
uint64_t saved_eid;
|
||||
int64_t saved_etime[2];
|
||||
|
||||
zed_log_init(argv[0]);
|
||||
zed_log_stderr_open(LOG_NOTICE);
|
||||
zcp = zed_conf_create();
|
||||
zed_conf_parse_opts(zcp, argc, argv);
|
||||
if (zcp->do_verbose)
|
||||
zed_log_stderr_open(LOG_INFO);
|
||||
|
||||
if (geteuid() != 0)
|
||||
zed_log_die("Must be run as root");
|
||||
|
||||
zed_conf_parse_file(zcp);
|
||||
|
||||
zed_file_close_from(STDERR_FILENO + 1);
|
||||
|
||||
(void) umask(0);
|
||||
|
||||
if (chdir("/") < 0)
|
||||
zed_log_die("Failed to change to root directory");
|
||||
|
||||
if (zed_conf_scan_dir(zcp) < 0)
|
||||
exit(EXIT_FAILURE);
|
||||
|
||||
if (!zcp->do_foreground) {
|
||||
_start_daemonize();
|
||||
zed_log_syslog_open(LOG_DAEMON);
|
||||
}
|
||||
_setup_sig_handlers();
|
||||
|
||||
if (zcp->do_memlock)
|
||||
_lock_memory();
|
||||
|
||||
if ((zed_conf_write_pid(zcp) < 0) && (!zcp->do_force))
|
||||
exit(EXIT_FAILURE);
|
||||
|
||||
if (!zcp->do_foreground)
|
||||
_finish_daemonize();
|
||||
|
||||
zed_log_msg(LOG_NOTICE,
|
||||
"ZFS Event Daemon %s-%s (PID %d)",
|
||||
ZFS_META_VERSION, ZFS_META_RELEASE, (int)getpid());
|
||||
|
||||
if (zed_conf_open_state(zcp) < 0)
|
||||
exit(EXIT_FAILURE);
|
||||
|
||||
if (zed_conf_read_state(zcp, &saved_eid, saved_etime) < 0)
|
||||
exit(EXIT_FAILURE);
|
||||
|
||||
idle:
|
||||
/*
|
||||
* If -I is specified, attempt to open /dev/zfs repeatedly until
|
||||
* successful.
|
||||
*/
|
||||
do {
|
||||
if (!zed_event_init(zcp))
|
||||
break;
|
||||
/* Wait for some time and try again. tunable? */
|
||||
sleep(30);
|
||||
} while (!_got_exit && zcp->do_idle);
|
||||
|
||||
if (_got_exit)
|
||||
goto out;
|
||||
|
||||
zed_event_seek(zcp, saved_eid, saved_etime);
|
||||
|
||||
while (!_got_exit) {
|
||||
int rv;
|
||||
if (_got_hup) {
|
||||
_got_hup = 0;
|
||||
(void) zed_conf_scan_dir(zcp);
|
||||
}
|
||||
rv = zed_event_service(zcp);
|
||||
|
||||
/* ENODEV: When kernel module is unloaded (osx) */
|
||||
if (rv == ENODEV)
|
||||
break;
|
||||
}
|
||||
|
||||
zed_log_msg(LOG_NOTICE, "Exiting");
|
||||
zed_event_fini(zcp);
|
||||
|
||||
if (zcp->do_idle && !_got_exit)
|
||||
goto idle;
|
||||
|
||||
out:
|
||||
zed_conf_destroy(zcp);
|
||||
zed_log_fini();
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
1
sys/contrib/openzfs/cmd/zed/zed.d/.gitignore
vendored
Normal file
1
sys/contrib/openzfs/cmd/zed/zed.d/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
history_event-zfs-list-cacher.sh
|
53
sys/contrib/openzfs/cmd/zed/zed.d/Makefile.am
Normal file
53
sys/contrib/openzfs/cmd/zed/zed.d/Makefile.am
Normal file
@ -0,0 +1,53 @@
|
||||
include $(top_srcdir)/config/Rules.am
|
||||
include $(top_srcdir)/config/Substfiles.am
|
||||
|
||||
EXTRA_DIST += README
|
||||
|
||||
zedconfdir = $(sysconfdir)/zfs/zed.d
|
||||
|
||||
dist_zedconf_DATA = \
|
||||
zed-functions.sh \
|
||||
zed.rc
|
||||
|
||||
zedexecdir = $(zfsexecdir)/zed.d
|
||||
|
||||
dist_zedexec_SCRIPTS = \
|
||||
all-debug.sh \
|
||||
all-syslog.sh \
|
||||
data-notify.sh \
|
||||
generic-notify.sh \
|
||||
resilver_finish-notify.sh \
|
||||
scrub_finish-notify.sh \
|
||||
statechange-led.sh \
|
||||
statechange-notify.sh \
|
||||
vdev_clear-led.sh \
|
||||
vdev_attach-led.sh \
|
||||
pool_import-led.sh \
|
||||
resilver_finish-start-scrub.sh \
|
||||
trim_finish-notify.sh
|
||||
|
||||
nodist_zedexec_SCRIPTS = history_event-zfs-list-cacher.sh
|
||||
|
||||
SUBSTFILES += $(nodist_zedexec_SCRIPTS)
|
||||
|
||||
zedconfdefaults = \
|
||||
all-syslog.sh \
|
||||
data-notify.sh \
|
||||
history_event-zfs-list-cacher.sh \
|
||||
resilver_finish-notify.sh \
|
||||
scrub_finish-notify.sh \
|
||||
statechange-led.sh \
|
||||
statechange-notify.sh \
|
||||
vdev_clear-led.sh \
|
||||
vdev_attach-led.sh \
|
||||
pool_import-led.sh \
|
||||
resilver_finish-start-scrub.sh
|
||||
|
||||
install-data-hook:
|
||||
$(MKDIR_P) "$(DESTDIR)$(zedconfdir)"
|
||||
for f in $(zedconfdefaults); do \
|
||||
test -f "$(DESTDIR)$(zedconfdir)/$${f}" -o \
|
||||
-L "$(DESTDIR)$(zedconfdir)/$${f}" || \
|
||||
ln -s "$(zedexecdir)/$${f}" "$(DESTDIR)$(zedconfdir)"; \
|
||||
done
|
||||
chmod 0600 "$(DESTDIR)$(zedconfdir)/zed.rc"
|
30
sys/contrib/openzfs/cmd/zed/zed.d/README
Normal file
30
sys/contrib/openzfs/cmd/zed/zed.d/README
Normal file
@ -0,0 +1,30 @@
|
||||
Shell scripts are the recommended choice for ZEDLETs that mostly call
|
||||
other utilities and do relatively little data manipulation.
|
||||
|
||||
Shell scripts MUST work on both bash and dash.
|
||||
|
||||
Shell scripts MUST run cleanly through ShellCheck:
|
||||
http://www.shellcheck.net/
|
||||
|
||||
General functions reside in "zed-functions.sh". Use them where applicable.
|
||||
|
||||
Additional references that may be of use:
|
||||
|
||||
Google Shell Style Guide
|
||||
https://github.com/google/styleguide/blob/gh-pages/shell.xml
|
||||
|
||||
Dash as /bin/sh
|
||||
https://wiki.ubuntu.com/DashAsBinSh
|
||||
|
||||
Common shell script mistakes
|
||||
http://www.pixelbeat.org/programming/shell_script_mistakes.html
|
||||
|
||||
Filenames and Pathnames in Shell: How to do it Correctly
|
||||
http://www.dwheeler.com/essays/filenames-in-shell.html
|
||||
|
||||
Autoconf: Portable Shell Programming
|
||||
https://www.gnu.org/software/autoconf/manual/autoconf.html#Portable-Shell
|
||||
|
||||
Please BE CONSISTENT with the existing style, check for errors,
|
||||
minimize dependencies where possible, try to be portable,
|
||||
and comment anything non-obvious. Festina lente.
|
26
sys/contrib/openzfs/cmd/zed/zed.d/all-debug.sh
Executable file
26
sys/contrib/openzfs/cmd/zed/zed.d/all-debug.sh
Executable file
@ -0,0 +1,26 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Log all environment variables to ZED_DEBUG_LOG.
|
||||
#
|
||||
# This can be a useful aid when developing/debugging ZEDLETs since it shows the
|
||||
# environment variables defined for each zevent.
|
||||
|
||||
[ -f "${ZED_ZEDLET_DIR}/zed.rc" ] && . "${ZED_ZEDLET_DIR}/zed.rc"
|
||||
. "${ZED_ZEDLET_DIR}/zed-functions.sh"
|
||||
|
||||
: "${ZED_DEBUG_LOG:="${TMPDIR:="/tmp"}/zed.debug.log"}"
|
||||
|
||||
zed_exit_if_ignoring_this_event
|
||||
|
||||
lockfile="$(basename -- "${ZED_DEBUG_LOG}").lock"
|
||||
|
||||
umask 077
|
||||
zed_lock "${lockfile}"
|
||||
exec >> "${ZED_DEBUG_LOG}"
|
||||
|
||||
printenv | sort
|
||||
echo
|
||||
|
||||
exec >&-
|
||||
zed_unlock "${lockfile}"
|
||||
exit 0
|
14
sys/contrib/openzfs/cmd/zed/zed.d/all-syslog.sh
Executable file
14
sys/contrib/openzfs/cmd/zed/zed.d/all-syslog.sh
Executable file
@ -0,0 +1,14 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Log the zevent via syslog.
|
||||
|
||||
[ -f "${ZED_ZEDLET_DIR}/zed.rc" ] && . "${ZED_ZEDLET_DIR}/zed.rc"
|
||||
. "${ZED_ZEDLET_DIR}/zed-functions.sh"
|
||||
|
||||
zed_exit_if_ignoring_this_event
|
||||
|
||||
zed_log_msg "eid=${ZEVENT_EID}" "class=${ZEVENT_SUBCLASS}" \
|
||||
"${ZEVENT_POOL_GUID:+"pool_guid=${ZEVENT_POOL_GUID}"}" \
|
||||
"${ZEVENT_VDEV_PATH:+"vdev_path=${ZEVENT_VDEV_PATH}"}" \
|
||||
"${ZEVENT_VDEV_STATE_STR:+"vdev_state=${ZEVENT_VDEV_STATE_STR}"}"
|
||||
exit 0
|
43
sys/contrib/openzfs/cmd/zed/zed.d/data-notify.sh
Executable file
43
sys/contrib/openzfs/cmd/zed/zed.d/data-notify.sh
Executable file
@ -0,0 +1,43 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Send notification in response to a DATA error.
|
||||
#
|
||||
# Only one notification per ZED_NOTIFY_INTERVAL_SECS will be sent for a given
|
||||
# class/pool/[vdev] combination. This protects against spamming the recipient
|
||||
# should multiple events occur together in time for the same pool/[vdev].
|
||||
#
|
||||
# Exit codes:
|
||||
# 0: notification sent
|
||||
# 1: notification failed
|
||||
# 2: notification not configured
|
||||
# 3: notification suppressed
|
||||
# 9: internal error
|
||||
|
||||
[ -f "${ZED_ZEDLET_DIR}/zed.rc" ] && . "${ZED_ZEDLET_DIR}/zed.rc"
|
||||
. "${ZED_ZEDLET_DIR}/zed-functions.sh"
|
||||
|
||||
[ -n "${ZEVENT_POOL}" ] || exit 9
|
||||
[ -n "${ZEVENT_SUBCLASS}" ] || exit 9
|
||||
[ -n "${ZED_NOTIFY_DATA}" ] || exit 3
|
||||
|
||||
rate_limit_tag="${ZEVENT_POOL};${ZEVENT_VDEV_GUID:-0};${ZEVENT_SUBCLASS};notify"
|
||||
zed_rate_limit "${rate_limit_tag}" || exit 3
|
||||
|
||||
umask 077
|
||||
note_subject="ZFS ${ZEVENT_SUBCLASS} error for ${ZEVENT_POOL} on $(hostname)"
|
||||
note_pathname="${TMPDIR:="/tmp"}/$(basename -- "$0").${ZEVENT_EID}.$$"
|
||||
{
|
||||
echo "ZFS has detected a data error:"
|
||||
echo
|
||||
echo " eid: ${ZEVENT_EID}"
|
||||
echo " class: ${ZEVENT_SUBCLASS}"
|
||||
echo " host: $(hostname)"
|
||||
echo " time: ${ZEVENT_TIME_STRING}"
|
||||
echo " error: ${ZEVENT_ZIO_ERR}"
|
||||
echo " objid: ${ZEVENT_ZIO_OBJSET}:${ZEVENT_ZIO_OBJECT}"
|
||||
echo " pool: ${ZEVENT_POOL}"
|
||||
} > "${note_pathname}"
|
||||
|
||||
zed_notify "${note_subject}" "${note_pathname}"; rv=$?
|
||||
rm -f "${note_pathname}"
|
||||
exit "${rv}"
|
54
sys/contrib/openzfs/cmd/zed/zed.d/generic-notify.sh
Executable file
54
sys/contrib/openzfs/cmd/zed/zed.d/generic-notify.sh
Executable file
@ -0,0 +1,54 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Send notification in response to a given zevent.
|
||||
#
|
||||
# This is a generic script than can be symlinked to a file in the
|
||||
# enabled-zedlets directory to have a notification sent when a particular
|
||||
# class of zevents occurs. The symlink filename must begin with the zevent
|
||||
# (sub)class string (e.g., "probe_failure-notify.sh" for the "probe_failure"
|
||||
# subclass). Refer to the zed(8) manpage for details.
|
||||
#
|
||||
# Only one notification per ZED_NOTIFY_INTERVAL_SECS will be sent for a given
|
||||
# class/pool combination. This protects against spamming the recipient
|
||||
# should multiple events occur together in time for the same pool.
|
||||
#
|
||||
# Exit codes:
|
||||
# 0: notification sent
|
||||
# 1: notification failed
|
||||
# 2: notification not configured
|
||||
# 3: notification suppressed
|
||||
|
||||
[ -f "${ZED_ZEDLET_DIR}/zed.rc" ] && . "${ZED_ZEDLET_DIR}/zed.rc"
|
||||
. "${ZED_ZEDLET_DIR}/zed-functions.sh"
|
||||
|
||||
# Rate-limit the notification based in part on the filename.
|
||||
#
|
||||
rate_limit_tag="${ZEVENT_POOL};${ZEVENT_SUBCLASS};$(basename -- "$0")"
|
||||
rate_limit_interval="${ZED_NOTIFY_INTERVAL_SECS}"
|
||||
zed_rate_limit "${rate_limit_tag}" "${rate_limit_interval}" || exit 3
|
||||
|
||||
umask 077
|
||||
pool_str="${ZEVENT_POOL:+" for ${ZEVENT_POOL}"}"
|
||||
host_str=" on $(hostname)"
|
||||
note_subject="ZFS ${ZEVENT_SUBCLASS} event${pool_str}${host_str}"
|
||||
note_pathname="${TMPDIR:="/tmp"}/$(basename -- "$0").${ZEVENT_EID}.$$"
|
||||
{
|
||||
echo "ZFS has posted the following event:"
|
||||
echo
|
||||
echo " eid: ${ZEVENT_EID}"
|
||||
echo " class: ${ZEVENT_SUBCLASS}"
|
||||
echo " host: $(hostname)"
|
||||
echo " time: ${ZEVENT_TIME_STRING}"
|
||||
|
||||
[ -n "${ZEVENT_VDEV_TYPE}" ] && echo " vtype: ${ZEVENT_VDEV_TYPE}"
|
||||
[ -n "${ZEVENT_VDEV_PATH}" ] && echo " vpath: ${ZEVENT_VDEV_PATH}"
|
||||
[ -n "${ZEVENT_VDEV_GUID}" ] && echo " vguid: ${ZEVENT_VDEV_GUID}"
|
||||
|
||||
[ -n "${ZEVENT_POOL}" ] && [ -x "${ZPOOL}" ] \
|
||||
&& "${ZPOOL}" status "${ZEVENT_POOL}"
|
||||
|
||||
} > "${note_pathname}"
|
||||
|
||||
zed_notify "${note_subject}" "${note_pathname}"; rv=$?
|
||||
rm -f "${note_pathname}"
|
||||
exit "${rv}"
|
85
sys/contrib/openzfs/cmd/zed/zed.d/history_event-zfs-list-cacher.sh.in
Executable file
85
sys/contrib/openzfs/cmd/zed/zed.d/history_event-zfs-list-cacher.sh.in
Executable file
@ -0,0 +1,85 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Track changes to enumerated pools for use in early-boot
|
||||
set -ef
|
||||
|
||||
FSLIST_DIR="@sysconfdir@/zfs/zfs-list.cache"
|
||||
FSLIST_TMP="@runstatedir@/zfs-list.cache.new"
|
||||
FSLIST="${FSLIST_DIR}/${ZEVENT_POOL}"
|
||||
|
||||
# If the pool specific cache file is not writeable, abort
|
||||
[ -w "${FSLIST}" ] || exit 0
|
||||
|
||||
[ -f "${ZED_ZEDLET_DIR}/zed.rc" ] && . "${ZED_ZEDLET_DIR}/zed.rc"
|
||||
. "${ZED_ZEDLET_DIR}/zed-functions.sh"
|
||||
|
||||
zed_exit_if_ignoring_this_event
|
||||
zed_check_cmd "${ZFS}" sort diff grep
|
||||
|
||||
# If we are acting on a snapshot, we have nothing to do
|
||||
printf '%s' "${ZEVENT_HISTORY_DSNAME}" | grep '@' && exit 0
|
||||
|
||||
# We obtain a lock on zfs-list to avoid any simultaneous writes.
|
||||
# If we run into trouble, log and drop the lock
|
||||
abort_alter() {
|
||||
zed_log_msg "Error updating zfs-list.cache!"
|
||||
zed_unlock zfs-list
|
||||
}
|
||||
|
||||
finished() {
|
||||
zed_unlock zfs-list
|
||||
trap - EXIT
|
||||
exit 0
|
||||
}
|
||||
|
||||
case "${ZEVENT_HISTORY_INTERNAL_NAME}" in
|
||||
create|"finish receiving"|import|destroy|rename)
|
||||
;;
|
||||
|
||||
export)
|
||||
zed_lock zfs-list
|
||||
trap abort_alter EXIT
|
||||
echo > "${FSLIST}"
|
||||
finished
|
||||
;;
|
||||
|
||||
set|inherit)
|
||||
# Only act if one of the tracked properties is altered.
|
||||
case "${ZEVENT_HISTORY_INTERNAL_STR%%=*}" in
|
||||
canmount|mountpoint|atime|relatime|devices|exec|readonly| \
|
||||
setuid|nbmand|encroot|keylocation|org.openzfs.systemd:requires| \
|
||||
org.openzfs.systemd:requires-mounts-for| \
|
||||
org.openzfs.systemd:before|org.openzfs.systemd:after| \
|
||||
org.openzfs.systemd:wanted-by|org.openzfs.systemd:required-by| \
|
||||
org.openzfs.systemd:nofail|org.openzfs.systemd:ignore \
|
||||
) ;;
|
||||
*) exit 0 ;;
|
||||
esac
|
||||
;;
|
||||
|
||||
*)
|
||||
# Ignore all other events.
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
|
||||
zed_lock zfs-list
|
||||
trap abort_alter EXIT
|
||||
|
||||
PROPS="name,mountpoint,canmount,atime,relatime,devices,exec\
|
||||
,readonly,setuid,nbmand,encroot,keylocation\
|
||||
,org.openzfs.systemd:requires,org.openzfs.systemd:requires-mounts-for\
|
||||
,org.openzfs.systemd:before,org.openzfs.systemd:after\
|
||||
,org.openzfs.systemd:wanted-by,org.openzfs.systemd:required-by\
|
||||
,org.openzfs.systemd:nofail,org.openzfs.systemd:ignore"
|
||||
|
||||
"${ZFS}" list -H -t filesystem -o $PROPS -r "${ZEVENT_POOL}" > "${FSLIST_TMP}"
|
||||
|
||||
# Sort the output so that it is stable
|
||||
sort "${FSLIST_TMP}" -o "${FSLIST_TMP}"
|
||||
|
||||
# Don't modify the file if it hasn't changed
|
||||
diff -q "${FSLIST_TMP}" "${FSLIST}" || mv "${FSLIST_TMP}" "${FSLIST}"
|
||||
rm -f "${FSLIST_TMP}"
|
||||
|
||||
finished
|
1
sys/contrib/openzfs/cmd/zed/zed.d/pool_import-led.sh
Symbolic link
1
sys/contrib/openzfs/cmd/zed/zed.d/pool_import-led.sh
Symbolic link
@ -0,0 +1 @@
|
||||
statechange-led.sh
|
1
sys/contrib/openzfs/cmd/zed/zed.d/resilver_finish-notify.sh
Symbolic link
1
sys/contrib/openzfs/cmd/zed/zed.d/resilver_finish-notify.sh
Symbolic link
@ -0,0 +1 @@
|
||||
scrub_finish-notify.sh
|
19
sys/contrib/openzfs/cmd/zed/zed.d/resilver_finish-start-scrub.sh
Executable file
19
sys/contrib/openzfs/cmd/zed/zed.d/resilver_finish-start-scrub.sh
Executable file
@ -0,0 +1,19 @@
|
||||
#!/bin/sh
|
||||
# resilver_finish-start-scrub.sh
|
||||
# Run a scrub after a resilver
|
||||
#
|
||||
# Exit codes:
|
||||
# 1: Internal error
|
||||
# 2: Script wasn't enabled in zed.rc
|
||||
# 3: Scrubs are automatically started for sequential resilvers
|
||||
[ -f "${ZED_ZEDLET_DIR}/zed.rc" ] && . "${ZED_ZEDLET_DIR}/zed.rc"
|
||||
. "${ZED_ZEDLET_DIR}/zed-functions.sh"
|
||||
|
||||
[ "${ZED_SCRUB_AFTER_RESILVER}" = "1" ] || exit 2
|
||||
[ "${ZEVENT_RESILVER_TYPE}" != "sequential" ] || exit 3
|
||||
[ -n "${ZEVENT_POOL}" ] || exit 1
|
||||
[ -n "${ZEVENT_SUBCLASS}" ] || exit 1
|
||||
zed_check_cmd "${ZPOOL}" || exit 1
|
||||
|
||||
zed_log_msg "Starting scrub after resilver on ${ZEVENT_POOL}"
|
||||
"${ZPOOL}" scrub "${ZEVENT_POOL}"
|
59
sys/contrib/openzfs/cmd/zed/zed.d/scrub_finish-notify.sh
Executable file
59
sys/contrib/openzfs/cmd/zed/zed.d/scrub_finish-notify.sh
Executable file
@ -0,0 +1,59 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Send notification in response to a RESILVER_FINISH or SCRUB_FINISH.
|
||||
#
|
||||
# By default, "zpool status" output will only be included for a scrub_finish
|
||||
# zevent if the pool is not healthy; to always include its output, set
|
||||
# ZED_NOTIFY_VERBOSE=1.
|
||||
#
|
||||
# Exit codes:
|
||||
# 0: notification sent
|
||||
# 1: notification failed
|
||||
# 2: notification not configured
|
||||
# 3: notification suppressed
|
||||
# 9: internal error
|
||||
|
||||
[ -f "${ZED_ZEDLET_DIR}/zed.rc" ] && . "${ZED_ZEDLET_DIR}/zed.rc"
|
||||
. "${ZED_ZEDLET_DIR}/zed-functions.sh"
|
||||
|
||||
[ -n "${ZEVENT_POOL}" ] || exit 9
|
||||
[ -n "${ZEVENT_SUBCLASS}" ] || exit 9
|
||||
|
||||
if [ "${ZEVENT_SUBCLASS}" = "resilver_finish" ]; then
|
||||
action="resilver"
|
||||
elif [ "${ZEVENT_SUBCLASS}" = "scrub_finish" ]; then
|
||||
action="scrub"
|
||||
else
|
||||
zed_log_err "unsupported event class \"${ZEVENT_SUBCLASS}\""
|
||||
exit 9
|
||||
fi
|
||||
|
||||
zed_check_cmd "${ZPOOL}" || exit 9
|
||||
|
||||
# For scrub, suppress notification if the pool is healthy
|
||||
# and verbosity is not enabled.
|
||||
#
|
||||
if [ "${ZEVENT_SUBCLASS}" = "scrub_finish" ]; then
|
||||
healthy="$("${ZPOOL}" status -x "${ZEVENT_POOL}" \
|
||||
| grep "'${ZEVENT_POOL}' is healthy")"
|
||||
[ -n "${healthy}" ] && [ "${ZED_NOTIFY_VERBOSE}" -eq 0 ] && exit 3
|
||||
fi
|
||||
|
||||
umask 077
|
||||
note_subject="ZFS ${ZEVENT_SUBCLASS} event for ${ZEVENT_POOL} on $(hostname)"
|
||||
note_pathname="${TMPDIR:="/tmp"}/$(basename -- "$0").${ZEVENT_EID}.$$"
|
||||
{
|
||||
echo "ZFS has finished a ${action}:"
|
||||
echo
|
||||
echo " eid: ${ZEVENT_EID}"
|
||||
echo " class: ${ZEVENT_SUBCLASS}"
|
||||
echo " host: $(hostname)"
|
||||
echo " time: ${ZEVENT_TIME_STRING}"
|
||||
|
||||
"${ZPOOL}" status "${ZEVENT_POOL}"
|
||||
|
||||
} > "${note_pathname}"
|
||||
|
||||
zed_notify "${note_subject}" "${note_pathname}"; rv=$?
|
||||
rm -f "${note_pathname}"
|
||||
exit "${rv}"
|
177
sys/contrib/openzfs/cmd/zed/zed.d/statechange-led.sh
Executable file
177
sys/contrib/openzfs/cmd/zed/zed.d/statechange-led.sh
Executable file
@ -0,0 +1,177 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Turn off/on the VDEV's enclosure fault LEDs when the pool's state changes.
|
||||
#
|
||||
# Turn the VDEV's fault LED on if it becomes FAULTED, DEGRADED or UNAVAIL.
|
||||
# Turn the LED off when it's back ONLINE again.
|
||||
#
|
||||
# This script run in two basic modes:
|
||||
#
|
||||
# 1. If $ZEVENT_VDEV_ENC_SYSFS_PATH and $ZEVENT_VDEV_STATE_STR are set, then
|
||||
# only set the LED for that particular VDEV. This is the case for statechange
|
||||
# events and some vdev_* events.
|
||||
#
|
||||
# 2. If those vars are not set, then check the state of all VDEVs in the pool
|
||||
# and set the LEDs accordingly. This is the case for pool_import events.
|
||||
#
|
||||
# Note that this script requires that your enclosure be supported by the
|
||||
# Linux SCSI enclosure services (ses) driver. The script will do nothing
|
||||
# if you have no enclosure, or if your enclosure isn't supported.
|
||||
#
|
||||
# Exit codes:
|
||||
# 0: enclosure led successfully set
|
||||
# 1: enclosure leds not available
|
||||
# 2: enclosure leds administratively disabled
|
||||
# 3: The led sysfs path passed from ZFS does not exist
|
||||
# 4: $ZPOOL not set
|
||||
# 5: awk is not installed
|
||||
|
||||
[ -f "${ZED_ZEDLET_DIR}/zed.rc" ] && . "${ZED_ZEDLET_DIR}/zed.rc"
|
||||
. "${ZED_ZEDLET_DIR}/zed-functions.sh"
|
||||
|
||||
if [ ! -d /sys/class/enclosure ] ; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "${ZED_USE_ENCLOSURE_LEDS}" != "1" ] ; then
|
||||
exit 2
|
||||
fi
|
||||
|
||||
zed_check_cmd "$ZPOOL" || exit 4
|
||||
zed_check_cmd awk || exit 5
|
||||
|
||||
# Global used in set_led debug print
|
||||
vdev=""
|
||||
|
||||
# check_and_set_led (file, val)
|
||||
#
|
||||
# Read an enclosure sysfs file, and write it if it's not already set to 'val'
|
||||
#
|
||||
# Arguments
|
||||
# file: sysfs file to set (like /sys/class/enclosure/0:0:1:0/SLOT 10/fault)
|
||||
# val: value to set it to
|
||||
#
|
||||
# Return
|
||||
# 0 on success, 3 on missing sysfs path
|
||||
#
|
||||
check_and_set_led()
|
||||
{
|
||||
file="$1"
|
||||
val="$2"
|
||||
|
||||
if [ ! -e "$file" ] ; then
|
||||
return 3
|
||||
fi
|
||||
|
||||
# If another process is accessing the LED when we attempt to update it,
|
||||
# the update will be lost so retry until the LED actually changes or we
|
||||
# timeout.
|
||||
for _ in $(seq 1 5); do
|
||||
# We want to check the current state first, since writing to the
|
||||
# 'fault' entry always causes a SES command, even if the
|
||||
# current state is already what you want.
|
||||
current=$(cat "${file}")
|
||||
|
||||
# On some enclosures if you write 1 to fault, and read it back,
|
||||
# it will return 2. Treat all non-zero values as 1 for
|
||||
# simplicity.
|
||||
if [ "$current" != "0" ] ; then
|
||||
current=1
|
||||
fi
|
||||
|
||||
if [ "$current" != "$val" ] ; then
|
||||
echo "$val" > "$file"
|
||||
zed_log_msg "vdev $vdev set '$file' LED to $val"
|
||||
else
|
||||
break
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
state_to_val()
|
||||
{
|
||||
state="$1"
|
||||
if [ "$state" = "FAULTED" ] || [ "$state" = "DEGRADED" ] || \
|
||||
[ "$state" = "UNAVAIL" ] ; then
|
||||
echo 1
|
||||
elif [ "$state" = "ONLINE" ] ; then
|
||||
echo 0
|
||||
fi
|
||||
}
|
||||
|
||||
# process_pool ([pool])
|
||||
#
|
||||
# Iterate through a pool (or pools) and set the VDEV's enclosure slot LEDs to
|
||||
# the VDEV's state.
|
||||
#
|
||||
# Arguments
|
||||
# pool: Optional pool name. If not specified, iterate though all pools.
|
||||
#
|
||||
# Return
|
||||
# 0 on success, 3 on missing sysfs path
|
||||
#
|
||||
process_pool()
|
||||
{
|
||||
pool="$1"
|
||||
rc=0
|
||||
|
||||
# Lookup all the current LED values and paths in parallel
|
||||
#shellcheck disable=SC2016
|
||||
cmd='echo led_token=$(cat "$VDEV_ENC_SYSFS_PATH/fault"),"$VDEV_ENC_SYSFS_PATH",'
|
||||
out=$($ZPOOL status -vc "$cmd" "$pool" | grep 'led_token=')
|
||||
|
||||
#shellcheck disable=SC2034
|
||||
echo "$out" | while read -r vdev state read write chksum therest; do
|
||||
# Read out current LED value and path
|
||||
tmp=$(echo "$therest" | sed 's/^.*led_token=//g')
|
||||
vdev_enc_sysfs_path=$(echo "$tmp" | awk -F ',' '{print $2}')
|
||||
current_val=$(echo "$tmp" | awk -F ',' '{print $1}')
|
||||
|
||||
if [ "$current_val" != "0" ] ; then
|
||||
current_val=1
|
||||
fi
|
||||
|
||||
if [ -z "$vdev_enc_sysfs_path" ] ; then
|
||||
# Skip anything with no sysfs LED entries
|
||||
continue
|
||||
fi
|
||||
|
||||
if [ ! -e "$vdev_enc_sysfs_path/fault" ] ; then
|
||||
#shellcheck disable=SC2030
|
||||
rc=1
|
||||
zed_log_msg "vdev $vdev '$file/fault' doesn't exist"
|
||||
continue;
|
||||
fi
|
||||
|
||||
val=$(state_to_val "$state")
|
||||
|
||||
if [ "$current_val" = "$val" ] ; then
|
||||
# LED is already set correctly
|
||||
continue;
|
||||
fi
|
||||
|
||||
if ! check_and_set_led "$vdev_enc_sysfs_path/fault" "$val"; then
|
||||
rc=1
|
||||
fi
|
||||
|
||||
done
|
||||
|
||||
#shellcheck disable=SC2031
|
||||
if [ "$rc" = "0" ] ; then
|
||||
return 0
|
||||
else
|
||||
# We didn't see a sysfs entry that we wanted to set
|
||||
return 3
|
||||
fi
|
||||
}
|
||||
|
||||
if [ -n "$ZEVENT_VDEV_ENC_SYSFS_PATH" ] && [ -n "$ZEVENT_VDEV_STATE_STR" ] ; then
|
||||
# Got a statechange for an individual VDEV
|
||||
val=$(state_to_val "$ZEVENT_VDEV_STATE_STR")
|
||||
vdev=$(basename "$ZEVENT_VDEV_PATH")
|
||||
check_and_set_led "$ZEVENT_VDEV_ENC_SYSFS_PATH/fault" "$val"
|
||||
else
|
||||
# Process the entire pool
|
||||
poolname=$(zed_guid_to_pool "$ZEVENT_POOL_GUID")
|
||||
process_pool "$poolname"
|
||||
fi
|
74
sys/contrib/openzfs/cmd/zed/zed.d/statechange-notify.sh
Executable file
74
sys/contrib/openzfs/cmd/zed/zed.d/statechange-notify.sh
Executable file
@ -0,0 +1,74 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# CDDL HEADER START
|
||||
#
|
||||
# The contents of this file are subject to the terms of the
|
||||
# Common Development and Distribution License Version 1.0 (CDDL-1.0).
|
||||
# You can obtain a copy of the license from the top-level file
|
||||
# "OPENSOLARIS.LICENSE" or at <http://opensource.org/licenses/CDDL-1.0>.
|
||||
# You may not use this file except in compliance with the license.
|
||||
#
|
||||
# CDDL HEADER END
|
||||
#
|
||||
|
||||
#
|
||||
# Send notification in response to a fault induced statechange
|
||||
#
|
||||
# ZEVENT_SUBCLASS: 'statechange'
|
||||
# ZEVENT_VDEV_STATE_STR: 'DEGRADED', 'FAULTED' or 'REMOVED'
|
||||
#
|
||||
# Exit codes:
|
||||
# 0: notification sent
|
||||
# 1: notification failed
|
||||
# 2: notification not configured
|
||||
# 3: statechange not relevant
|
||||
# 4: statechange string missing (unexpected)
|
||||
|
||||
[ -f "${ZED_ZEDLET_DIR}/zed.rc" ] && . "${ZED_ZEDLET_DIR}/zed.rc"
|
||||
. "${ZED_ZEDLET_DIR}/zed-functions.sh"
|
||||
|
||||
[ -n "${ZEVENT_VDEV_STATE_STR}" ] || exit 4
|
||||
|
||||
if [ "${ZEVENT_VDEV_STATE_STR}" != "FAULTED" ] \
|
||||
&& [ "${ZEVENT_VDEV_STATE_STR}" != "DEGRADED" ] \
|
||||
&& [ "${ZEVENT_VDEV_STATE_STR}" != "REMOVED" ]; then
|
||||
exit 3
|
||||
fi
|
||||
|
||||
umask 077
|
||||
note_subject="ZFS device fault for pool ${ZEVENT_POOL_GUID} on $(hostname)"
|
||||
note_pathname="${TMPDIR:="/tmp"}/$(basename -- "$0").${ZEVENT_EID}.$$"
|
||||
{
|
||||
if [ "${ZEVENT_VDEV_STATE_STR}" = "FAULTED" ] ; then
|
||||
echo "The number of I/O errors associated with a ZFS device exceeded"
|
||||
echo "acceptable levels. ZFS has marked the device as faulted."
|
||||
elif [ "${ZEVENT_VDEV_STATE_STR}" = "DEGRADED" ] ; then
|
||||
echo "The number of checksum errors associated with a ZFS device"
|
||||
echo "exceeded acceptable levels. ZFS has marked the device as"
|
||||
echo "degraded."
|
||||
else
|
||||
echo "ZFS has detected that a device was removed."
|
||||
fi
|
||||
|
||||
echo
|
||||
echo " impact: Fault tolerance of the pool may be compromised."
|
||||
echo " eid: ${ZEVENT_EID}"
|
||||
echo " class: ${ZEVENT_SUBCLASS}"
|
||||
echo " state: ${ZEVENT_VDEV_STATE_STR}"
|
||||
echo " host: $(hostname)"
|
||||
echo " time: ${ZEVENT_TIME_STRING}"
|
||||
|
||||
[ -n "${ZEVENT_VDEV_TYPE}" ] && echo " vtype: ${ZEVENT_VDEV_TYPE}"
|
||||
[ -n "${ZEVENT_VDEV_PATH}" ] && echo " vpath: ${ZEVENT_VDEV_PATH}"
|
||||
[ -n "${ZEVENT_VDEV_PHYSPATH}" ] && echo " vphys: ${ZEVENT_VDEV_PHYSPATH}"
|
||||
[ -n "${ZEVENT_VDEV_GUID}" ] && echo " vguid: ${ZEVENT_VDEV_GUID}"
|
||||
[ -n "${ZEVENT_VDEV_DEVID}" ] && echo " devid: ${ZEVENT_VDEV_DEVID}"
|
||||
|
||||
echo " pool: ${ZEVENT_POOL_GUID}"
|
||||
|
||||
} > "${note_pathname}"
|
||||
|
||||
zed_notify "${note_subject}" "${note_pathname}"; rv=$?
|
||||
|
||||
rm -f "${note_pathname}"
|
||||
exit "${rv}"
|
37
sys/contrib/openzfs/cmd/zed/zed.d/trim_finish-notify.sh
Executable file
37
sys/contrib/openzfs/cmd/zed/zed.d/trim_finish-notify.sh
Executable file
@ -0,0 +1,37 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Send notification in response to a TRIM_FINISH. The event
|
||||
# will be received for each vdev in the pool which was trimmed.
|
||||
#
|
||||
# Exit codes:
|
||||
# 0: notification sent
|
||||
# 1: notification failed
|
||||
# 2: notification not configured
|
||||
# 9: internal error
|
||||
|
||||
[ -f "${ZED_ZEDLET_DIR}/zed.rc" ] && . "${ZED_ZEDLET_DIR}/zed.rc"
|
||||
. "${ZED_ZEDLET_DIR}/zed-functions.sh"
|
||||
|
||||
[ -n "${ZEVENT_POOL}" ] || exit 9
|
||||
[ -n "${ZEVENT_SUBCLASS}" ] || exit 9
|
||||
|
||||
zed_check_cmd "${ZPOOL}" || exit 9
|
||||
|
||||
umask 077
|
||||
note_subject="ZFS ${ZEVENT_SUBCLASS} event for ${ZEVENT_POOL} on $(hostname)"
|
||||
note_pathname="${TMPDIR:="/tmp"}/$(basename -- "$0").${ZEVENT_EID}.$$"
|
||||
{
|
||||
echo "ZFS has finished a trim:"
|
||||
echo
|
||||
echo " eid: ${ZEVENT_EID}"
|
||||
echo " class: ${ZEVENT_SUBCLASS}"
|
||||
echo " host: $(hostname)"
|
||||
echo " time: ${ZEVENT_TIME_STRING}"
|
||||
|
||||
"${ZPOOL}" status -t "${ZEVENT_POOL}"
|
||||
|
||||
} > "${note_pathname}"
|
||||
|
||||
zed_notify "${note_subject}" "${note_pathname}"; rv=$?
|
||||
rm -f "${note_pathname}"
|
||||
exit "${rv}"
|
1
sys/contrib/openzfs/cmd/zed/zed.d/vdev_attach-led.sh
Symbolic link
1
sys/contrib/openzfs/cmd/zed/zed.d/vdev_attach-led.sh
Symbolic link
@ -0,0 +1 @@
|
||||
statechange-led.sh
|
1
sys/contrib/openzfs/cmd/zed/zed.d/vdev_clear-led.sh
Symbolic link
1
sys/contrib/openzfs/cmd/zed/zed.d/vdev_clear-led.sh
Symbolic link
@ -0,0 +1 @@
|
||||
statechange-led.sh
|
538
sys/contrib/openzfs/cmd/zed/zed.d/zed-functions.sh
Executable file
538
sys/contrib/openzfs/cmd/zed/zed.d/zed-functions.sh
Executable file
@ -0,0 +1,538 @@
|
||||
#!/bin/sh
|
||||
# shellcheck disable=SC2039
|
||||
# zed-functions.sh
|
||||
#
|
||||
# ZED helper functions for use in ZEDLETs
|
||||
|
||||
|
||||
# Variable Defaults
|
||||
#
|
||||
: "${ZED_LOCKDIR:="/var/lock"}"
|
||||
: "${ZED_NOTIFY_INTERVAL_SECS:=3600}"
|
||||
: "${ZED_NOTIFY_VERBOSE:=0}"
|
||||
: "${ZED_RUNDIR:="/var/run"}"
|
||||
: "${ZED_SYSLOG_PRIORITY:="daemon.notice"}"
|
||||
: "${ZED_SYSLOG_TAG:="zed"}"
|
||||
|
||||
ZED_FLOCK_FD=8
|
||||
|
||||
|
||||
# zed_check_cmd (cmd, ...)
|
||||
#
|
||||
# For each argument given, search PATH for the executable command [cmd].
|
||||
# Log a message if [cmd] is not found.
|
||||
#
|
||||
# Arguments
|
||||
# cmd: name of executable command for which to search
|
||||
#
|
||||
# Return
|
||||
# 0 if all commands are found in PATH and are executable
|
||||
# n for a count of the command executables that are not found
|
||||
#
|
||||
zed_check_cmd()
|
||||
{
|
||||
local cmd
|
||||
local rv=0
|
||||
|
||||
for cmd; do
|
||||
if ! command -v "${cmd}" >/dev/null 2>&1; then
|
||||
zed_log_err "\"${cmd}\" not installed"
|
||||
rv=$((rv + 1))
|
||||
fi
|
||||
done
|
||||
return "${rv}"
|
||||
}
|
||||
|
||||
|
||||
# zed_log_msg (msg, ...)
|
||||
#
|
||||
# Write all argument strings to the system log.
|
||||
#
|
||||
# Globals
|
||||
# ZED_SYSLOG_PRIORITY
|
||||
# ZED_SYSLOG_TAG
|
||||
#
|
||||
# Return
|
||||
# nothing
|
||||
#
|
||||
zed_log_msg()
|
||||
{
|
||||
logger -p "${ZED_SYSLOG_PRIORITY}" -t "${ZED_SYSLOG_TAG}" -- "$@"
|
||||
}
|
||||
|
||||
|
||||
# zed_log_err (msg, ...)
|
||||
#
|
||||
# Write an error message to the system log. This message will contain the
|
||||
# script name, EID, and all argument strings.
|
||||
#
|
||||
# Globals
|
||||
# ZED_SYSLOG_PRIORITY
|
||||
# ZED_SYSLOG_TAG
|
||||
# ZEVENT_EID
|
||||
#
|
||||
# Return
|
||||
# nothing
|
||||
#
|
||||
zed_log_err()
|
||||
{
|
||||
logger -p "${ZED_SYSLOG_PRIORITY}" -t "${ZED_SYSLOG_TAG}" -- "error:" \
|
||||
"$(basename -- "$0"):""${ZEVENT_EID:+" eid=${ZEVENT_EID}:"}" "$@"
|
||||
}
|
||||
|
||||
|
||||
# zed_lock (lockfile, [fd])
|
||||
#
|
||||
# Obtain an exclusive (write) lock on [lockfile]. If the lock cannot be
|
||||
# immediately acquired, wait until it becomes available.
|
||||
#
|
||||
# Every zed_lock() must be paired with a corresponding zed_unlock().
|
||||
#
|
||||
# By default, flock-style locks associate the lockfile with file descriptor 8.
|
||||
# The bash manpage warns that file descriptors >9 should be used with care as
|
||||
# they may conflict with file descriptors used internally by the shell. File
|
||||
# descriptor 9 is reserved for zed_rate_limit(). If concurrent locks are held
|
||||
# within the same process, they must use different file descriptors (preferably
|
||||
# decrementing from 8); otherwise, obtaining a new lock with a given file
|
||||
# descriptor will release the previous lock associated with that descriptor.
|
||||
#
|
||||
# Arguments
|
||||
# lockfile: pathname of the lock file; the lock will be stored in
|
||||
# ZED_LOCKDIR unless the pathname contains a "/".
|
||||
# fd: integer for the file descriptor used by flock (OPTIONAL unless holding
|
||||
# concurrent locks)
|
||||
#
|
||||
# Globals
|
||||
# ZED_FLOCK_FD
|
||||
# ZED_LOCKDIR
|
||||
#
|
||||
# Return
|
||||
# nothing
|
||||
#
|
||||
zed_lock()
|
||||
{
|
||||
local lockfile="$1"
|
||||
local fd="${2:-${ZED_FLOCK_FD}}"
|
||||
local umask_bak
|
||||
local err
|
||||
|
||||
[ -n "${lockfile}" ] || return
|
||||
if ! expr "${lockfile}" : '.*/' >/dev/null 2>&1; then
|
||||
lockfile="${ZED_LOCKDIR}/${lockfile}"
|
||||
fi
|
||||
|
||||
umask_bak="$(umask)"
|
||||
umask 077
|
||||
|
||||
# Obtain a lock on the file bound to the given file descriptor.
|
||||
#
|
||||
eval "exec ${fd}> '${lockfile}'"
|
||||
err="$(flock --exclusive "${fd}" 2>&1)"
|
||||
# shellcheck disable=SC2181
|
||||
if [ $? -ne 0 ]; then
|
||||
zed_log_err "failed to lock \"${lockfile}\": ${err}"
|
||||
fi
|
||||
|
||||
umask "${umask_bak}"
|
||||
}
|
||||
|
||||
|
||||
# zed_unlock (lockfile, [fd])
|
||||
#
|
||||
# Release the lock on [lockfile].
|
||||
#
|
||||
# Arguments
|
||||
# lockfile: pathname of the lock file
|
||||
# fd: integer for the file descriptor used by flock (must match the file
|
||||
# descriptor passed to the zed_lock function call)
|
||||
#
|
||||
# Globals
|
||||
# ZED_FLOCK_FD
|
||||
# ZED_LOCKDIR
|
||||
#
|
||||
# Return
|
||||
# nothing
|
||||
#
|
||||
zed_unlock()
|
||||
{
|
||||
local lockfile="$1"
|
||||
local fd="${2:-${ZED_FLOCK_FD}}"
|
||||
local err
|
||||
|
||||
[ -n "${lockfile}" ] || return
|
||||
if ! expr "${lockfile}" : '.*/' >/dev/null 2>&1; then
|
||||
lockfile="${ZED_LOCKDIR}/${lockfile}"
|
||||
fi
|
||||
|
||||
# Release the lock and close the file descriptor.
|
||||
err="$(flock --unlock "${fd}" 2>&1)"
|
||||
# shellcheck disable=SC2181
|
||||
if [ $? -ne 0 ]; then
|
||||
zed_log_err "failed to unlock \"${lockfile}\": ${err}"
|
||||
fi
|
||||
eval "exec ${fd}>&-"
|
||||
}
|
||||
|
||||
|
||||
# zed_notify (subject, pathname)
|
||||
#
|
||||
# Send a notification via all available methods.
|
||||
#
|
||||
# Arguments
|
||||
# subject: notification subject
|
||||
# pathname: pathname containing the notification message (OPTIONAL)
|
||||
#
|
||||
# Return
|
||||
# 0: notification succeeded via at least one method
|
||||
# 1: notification failed
|
||||
# 2: no notification methods configured
|
||||
#
|
||||
zed_notify()
|
||||
{
|
||||
local subject="$1"
|
||||
local pathname="$2"
|
||||
local num_success=0
|
||||
local num_failure=0
|
||||
|
||||
zed_notify_email "${subject}" "${pathname}"; rv=$?
|
||||
[ "${rv}" -eq 0 ] && num_success=$((num_success + 1))
|
||||
[ "${rv}" -eq 1 ] && num_failure=$((num_failure + 1))
|
||||
|
||||
zed_notify_pushbullet "${subject}" "${pathname}"; rv=$?
|
||||
[ "${rv}" -eq 0 ] && num_success=$((num_success + 1))
|
||||
[ "${rv}" -eq 1 ] && num_failure=$((num_failure + 1))
|
||||
|
||||
zed_notify_slack_webhook "${subject}" "${pathname}"; rv=$?
|
||||
[ "${rv}" -eq 0 ] && num_success=$((num_success + 1))
|
||||
[ "${rv}" -eq 1 ] && num_failure=$((num_failure + 1))
|
||||
|
||||
[ "${num_success}" -gt 0 ] && return 0
|
||||
[ "${num_failure}" -gt 0 ] && return 1
|
||||
return 2
|
||||
}
|
||||
|
||||
|
||||
# zed_notify_email (subject, pathname)
|
||||
#
|
||||
# Send a notification via email to the address specified by ZED_EMAIL_ADDR.
|
||||
#
|
||||
# Requires the mail executable to be installed in the standard PATH, or
|
||||
# ZED_EMAIL_PROG to be defined with the pathname of an executable capable of
|
||||
# reading a message body from stdin.
|
||||
#
|
||||
# Command-line options to the mail executable can be specified in
|
||||
# ZED_EMAIL_OPTS. This undergoes the following keyword substitutions:
|
||||
# - @ADDRESS@ is replaced with the space-delimited recipient email address(es)
|
||||
# - @SUBJECT@ is replaced with the notification subject
|
||||
#
|
||||
# Arguments
|
||||
# subject: notification subject
|
||||
# pathname: pathname containing the notification message (OPTIONAL)
|
||||
#
|
||||
# Globals
|
||||
# ZED_EMAIL_PROG
|
||||
# ZED_EMAIL_OPTS
|
||||
# ZED_EMAIL_ADDR
|
||||
#
|
||||
# Return
|
||||
# 0: notification sent
|
||||
# 1: notification failed
|
||||
# 2: not configured
|
||||
#
|
||||
zed_notify_email()
|
||||
{
|
||||
local subject="$1"
|
||||
local pathname="${2:-"/dev/null"}"
|
||||
|
||||
: "${ZED_EMAIL_PROG:="mail"}"
|
||||
: "${ZED_EMAIL_OPTS:="-s '@SUBJECT@' @ADDRESS@"}"
|
||||
|
||||
# For backward compatibility with ZED_EMAIL.
|
||||
if [ -n "${ZED_EMAIL}" ] && [ -z "${ZED_EMAIL_ADDR}" ]; then
|
||||
ZED_EMAIL_ADDR="${ZED_EMAIL}"
|
||||
fi
|
||||
[ -n "${ZED_EMAIL_ADDR}" ] || return 2
|
||||
|
||||
zed_check_cmd "${ZED_EMAIL_PROG}" || return 1
|
||||
|
||||
[ -n "${subject}" ] || return 1
|
||||
if [ ! -r "${pathname}" ]; then
|
||||
zed_log_err \
|
||||
"$(basename "${ZED_EMAIL_PROG}") cannot read \"${pathname}\""
|
||||
return 1
|
||||
fi
|
||||
|
||||
ZED_EMAIL_OPTS="$(echo "${ZED_EMAIL_OPTS}" \
|
||||
| sed -e "s/@ADDRESS@/${ZED_EMAIL_ADDR}/g" \
|
||||
-e "s/@SUBJECT@/${subject}/g")"
|
||||
|
||||
# shellcheck disable=SC2086
|
||||
eval "${ZED_EMAIL_PROG}" ${ZED_EMAIL_OPTS} < "${pathname}" >/dev/null 2>&1
|
||||
rv=$?
|
||||
if [ "${rv}" -ne 0 ]; then
|
||||
zed_log_err "$(basename "${ZED_EMAIL_PROG}") exit=${rv}"
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
# zed_notify_pushbullet (subject, pathname)
|
||||
#
|
||||
# Send a notification via Pushbullet <https://www.pushbullet.com/>.
|
||||
# The access token (ZED_PUSHBULLET_ACCESS_TOKEN) identifies this client to the
|
||||
# Pushbullet server. The optional channel tag (ZED_PUSHBULLET_CHANNEL_TAG) is
|
||||
# for pushing to notification feeds that can be subscribed to; if a channel is
|
||||
# not defined, push notifications will instead be sent to all devices
|
||||
# associated with the account specified by the access token.
|
||||
#
|
||||
# Requires awk, curl, and sed executables to be installed in the standard PATH.
|
||||
#
|
||||
# References
|
||||
# https://docs.pushbullet.com/
|
||||
# https://www.pushbullet.com/security
|
||||
#
|
||||
# Arguments
|
||||
# subject: notification subject
|
||||
# pathname: pathname containing the notification message (OPTIONAL)
|
||||
#
|
||||
# Globals
|
||||
# ZED_PUSHBULLET_ACCESS_TOKEN
|
||||
# ZED_PUSHBULLET_CHANNEL_TAG
|
||||
#
|
||||
# Return
|
||||
# 0: notification sent
|
||||
# 1: notification failed
|
||||
# 2: not configured
|
||||
#
|
||||
zed_notify_pushbullet()
|
||||
{
|
||||
local subject="$1"
|
||||
local pathname="${2:-"/dev/null"}"
|
||||
local msg_body
|
||||
local msg_tag
|
||||
local msg_json
|
||||
local msg_out
|
||||
local msg_err
|
||||
local url="https://api.pushbullet.com/v2/pushes"
|
||||
|
||||
[ -n "${ZED_PUSHBULLET_ACCESS_TOKEN}" ] || return 2
|
||||
|
||||
[ -n "${subject}" ] || return 1
|
||||
if [ ! -r "${pathname}" ]; then
|
||||
zed_log_err "pushbullet cannot read \"${pathname}\""
|
||||
return 1
|
||||
fi
|
||||
|
||||
zed_check_cmd "awk" "curl" "sed" || return 1
|
||||
|
||||
# Escape the following characters in the message body for JSON:
|
||||
# newline, backslash, double quote, horizontal tab, vertical tab,
|
||||
# and carriage return.
|
||||
#
|
||||
msg_body="$(awk '{ ORS="\\n" } { gsub(/\\/, "\\\\"); gsub(/"/, "\\\"");
|
||||
gsub(/\t/, "\\t"); gsub(/\f/, "\\f"); gsub(/\r/, "\\r"); print }' \
|
||||
"${pathname}")"
|
||||
|
||||
# Push to a channel if one is configured.
|
||||
#
|
||||
[ -n "${ZED_PUSHBULLET_CHANNEL_TAG}" ] && msg_tag="$(printf \
|
||||
'"channel_tag": "%s", ' "${ZED_PUSHBULLET_CHANNEL_TAG}")"
|
||||
|
||||
# Construct the JSON message for pushing a note.
|
||||
#
|
||||
msg_json="$(printf '{%s"type": "note", "title": "%s", "body": "%s"}' \
|
||||
"${msg_tag}" "${subject}" "${msg_body}")"
|
||||
|
||||
# Send the POST request and check for errors.
|
||||
#
|
||||
msg_out="$(curl -u "${ZED_PUSHBULLET_ACCESS_TOKEN}:" -X POST "${url}" \
|
||||
--header "Content-Type: application/json" --data-binary "${msg_json}" \
|
||||
2>/dev/null)"; rv=$?
|
||||
if [ "${rv}" -ne 0 ]; then
|
||||
zed_log_err "curl exit=${rv}"
|
||||
return 1
|
||||
fi
|
||||
msg_err="$(echo "${msg_out}" \
|
||||
| sed -n -e 's/.*"error" *:.*"message" *: *"\([^"]*\)".*/\1/p')"
|
||||
if [ -n "${msg_err}" ]; then
|
||||
zed_log_err "pushbullet \"${msg_err}"\"
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
# zed_notify_slack_webhook (subject, pathname)
|
||||
#
|
||||
# Notification via Slack Webhook <https://api.slack.com/incoming-webhooks>.
|
||||
# The Webhook URL (ZED_SLACK_WEBHOOK_URL) identifies this client to the
|
||||
# Slack channel.
|
||||
#
|
||||
# Requires awk, curl, and sed executables to be installed in the standard PATH.
|
||||
#
|
||||
# References
|
||||
# https://api.slack.com/incoming-webhooks
|
||||
#
|
||||
# Arguments
|
||||
# subject: notification subject
|
||||
# pathname: pathname containing the notification message (OPTIONAL)
|
||||
#
|
||||
# Globals
|
||||
# ZED_SLACK_WEBHOOK_URL
|
||||
#
|
||||
# Return
|
||||
# 0: notification sent
|
||||
# 1: notification failed
|
||||
# 2: not configured
|
||||
#
|
||||
zed_notify_slack_webhook()
|
||||
{
|
||||
[ -n "${ZED_SLACK_WEBHOOK_URL}" ] || return 2
|
||||
|
||||
local subject="$1"
|
||||
local pathname="${2:-"/dev/null"}"
|
||||
local msg_body
|
||||
local msg_tag
|
||||
local msg_json
|
||||
local msg_out
|
||||
local msg_err
|
||||
local url="${ZED_SLACK_WEBHOOK_URL}"
|
||||
|
||||
[ -n "${subject}" ] || return 1
|
||||
if [ ! -r "${pathname}" ]; then
|
||||
zed_log_err "slack webhook cannot read \"${pathname}\""
|
||||
return 1
|
||||
fi
|
||||
|
||||
zed_check_cmd "awk" "curl" "sed" || return 1
|
||||
|
||||
# Escape the following characters in the message body for JSON:
|
||||
# newline, backslash, double quote, horizontal tab, vertical tab,
|
||||
# and carriage return.
|
||||
#
|
||||
msg_body="$(awk '{ ORS="\\n" } { gsub(/\\/, "\\\\"); gsub(/"/, "\\\"");
|
||||
gsub(/\t/, "\\t"); gsub(/\f/, "\\f"); gsub(/\r/, "\\r"); print }' \
|
||||
"${pathname}")"
|
||||
|
||||
# Construct the JSON message for posting.
|
||||
#
|
||||
msg_json="$(printf '{"text": "*%s*\n%s"}' "${subject}" "${msg_body}" )"
|
||||
|
||||
# Send the POST request and check for errors.
|
||||
#
|
||||
msg_out="$(curl -X POST "${url}" \
|
||||
--header "Content-Type: application/json" --data-binary "${msg_json}" \
|
||||
2>/dev/null)"; rv=$?
|
||||
if [ "${rv}" -ne 0 ]; then
|
||||
zed_log_err "curl exit=${rv}"
|
||||
return 1
|
||||
fi
|
||||
msg_err="$(echo "${msg_out}" \
|
||||
| sed -n -e 's/.*"error" *:.*"message" *: *"\([^"]*\)".*/\1/p')"
|
||||
if [ -n "${msg_err}" ]; then
|
||||
zed_log_err "slack webhook \"${msg_err}"\"
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
# zed_rate_limit (tag, [interval])
|
||||
#
|
||||
# Check whether an event of a given type [tag] has already occurred within the
|
||||
# last [interval] seconds.
|
||||
#
|
||||
# This function obtains a lock on the statefile using file descriptor 9.
|
||||
#
|
||||
# Arguments
|
||||
# tag: arbitrary string for grouping related events to rate-limit
|
||||
# interval: time interval in seconds (OPTIONAL)
|
||||
#
|
||||
# Globals
|
||||
# ZED_NOTIFY_INTERVAL_SECS
|
||||
# ZED_RUNDIR
|
||||
#
|
||||
# Return
|
||||
# 0 if the event should be processed
|
||||
# 1 if the event should be dropped
|
||||
#
|
||||
# State File Format
|
||||
# time;tag
|
||||
#
|
||||
zed_rate_limit()
|
||||
{
|
||||
local tag="$1"
|
||||
local interval="${2:-${ZED_NOTIFY_INTERVAL_SECS}}"
|
||||
local lockfile="zed.zedlet.state.lock"
|
||||
local lockfile_fd=9
|
||||
local statefile="${ZED_RUNDIR}/zed.zedlet.state"
|
||||
local time_now
|
||||
local time_prev
|
||||
local umask_bak
|
||||
local rv=0
|
||||
|
||||
[ -n "${tag}" ] || return 0
|
||||
|
||||
zed_lock "${lockfile}" "${lockfile_fd}"
|
||||
time_now="$(date +%s)"
|
||||
time_prev="$(grep -E "^[0-9]+;${tag}\$" "${statefile}" 2>/dev/null \
|
||||
| tail -1 | cut -d\; -f1)"
|
||||
|
||||
if [ -n "${time_prev}" ] \
|
||||
&& [ "$((time_now - time_prev))" -lt "${interval}" ]; then
|
||||
rv=1
|
||||
else
|
||||
umask_bak="$(umask)"
|
||||
umask 077
|
||||
grep -E -v "^[0-9]+;${tag}\$" "${statefile}" 2>/dev/null \
|
||||
> "${statefile}.$$"
|
||||
echo "${time_now};${tag}" >> "${statefile}.$$"
|
||||
mv -f "${statefile}.$$" "${statefile}"
|
||||
umask "${umask_bak}"
|
||||
fi
|
||||
|
||||
zed_unlock "${lockfile}" "${lockfile_fd}"
|
||||
return "${rv}"
|
||||
}
|
||||
|
||||
|
||||
# zed_guid_to_pool (guid)
|
||||
#
|
||||
# Convert a pool GUID into its pool name (like "tank")
|
||||
# Arguments
|
||||
# guid: pool GUID (decimal or hex)
|
||||
#
|
||||
# Return
|
||||
# Pool name
|
||||
#
|
||||
zed_guid_to_pool()
|
||||
{
|
||||
if [ -z "$1" ] ; then
|
||||
return
|
||||
fi
|
||||
|
||||
guid=$(printf "%llu" "$1")
|
||||
if [ -n "$guid" ] ; then
|
||||
$ZPOOL get -H -ovalue,name guid | awk '$1=='"$guid"' {print $2}'
|
||||
fi
|
||||
}
|
||||
|
||||
# zed_exit_if_ignoring_this_event
|
||||
#
|
||||
# Exit the script if we should ignore this event, as determined by
|
||||
# $ZED_SYSLOG_SUBCLASS_INCLUDE and $ZED_SYSLOG_SUBCLASS_EXCLUDE in zed.rc.
|
||||
# This function assumes you've imported the normal zed variables.
|
||||
zed_exit_if_ignoring_this_event()
|
||||
{
|
||||
if [ -n "${ZED_SYSLOG_SUBCLASS_INCLUDE}" ]; then
|
||||
eval "case ${ZEVENT_SUBCLASS} in
|
||||
${ZED_SYSLOG_SUBCLASS_INCLUDE});;
|
||||
*) exit 0;;
|
||||
esac"
|
||||
elif [ -n "${ZED_SYSLOG_SUBCLASS_EXCLUDE}" ]; then
|
||||
eval "case ${ZEVENT_SUBCLASS} in
|
||||
${ZED_SYSLOG_SUBCLASS_EXCLUDE}) exit 0;;
|
||||
*);;
|
||||
esac"
|
||||
fi
|
||||
}
|
122
sys/contrib/openzfs/cmd/zed/zed.d/zed.rc
Normal file
122
sys/contrib/openzfs/cmd/zed/zed.d/zed.rc
Normal file
@ -0,0 +1,122 @@
|
||||
##
|
||||
# zed.rc
|
||||
#
|
||||
# This file should be owned by root and permissioned 0600.
|
||||
##
|
||||
|
||||
##
|
||||
# Absolute path to the debug output file.
|
||||
#
|
||||
#ZED_DEBUG_LOG="/tmp/zed.debug.log"
|
||||
|
||||
##
|
||||
# Email address of the zpool administrator for receipt of notifications;
|
||||
# multiple addresses can be specified if they are delimited by whitespace.
|
||||
# Email will only be sent if ZED_EMAIL_ADDR is defined.
|
||||
# Disabled by default; uncomment to enable.
|
||||
#
|
||||
#ZED_EMAIL_ADDR="root"
|
||||
|
||||
##
|
||||
# Name or path of executable responsible for sending notifications via email;
|
||||
# the mail program must be capable of reading a message body from stdin.
|
||||
# Email will only be sent if ZED_EMAIL_ADDR is defined.
|
||||
#
|
||||
#ZED_EMAIL_PROG="mail"
|
||||
|
||||
##
|
||||
# Command-line options for ZED_EMAIL_PROG.
|
||||
# The string @ADDRESS@ will be replaced with the recipient email address(es).
|
||||
# The string @SUBJECT@ will be replaced with the notification subject;
|
||||
# this should be protected with quotes to prevent word-splitting.
|
||||
# Email will only be sent if ZED_EMAIL_ADDR is defined.
|
||||
#
|
||||
#ZED_EMAIL_OPTS="-s '@SUBJECT@' @ADDRESS@"
|
||||
|
||||
##
|
||||
# Default directory for zed lock files.
|
||||
#
|
||||
#ZED_LOCKDIR="/var/lock"
|
||||
|
||||
##
|
||||
# Minimum number of seconds between notifications for a similar event.
|
||||
#
|
||||
#ZED_NOTIFY_INTERVAL_SECS=3600
|
||||
|
||||
##
|
||||
# Notification verbosity.
|
||||
# If set to 0, suppress notification if the pool is healthy.
|
||||
# If set to 1, send notification regardless of pool health.
|
||||
#
|
||||
#ZED_NOTIFY_VERBOSE=0
|
||||
|
||||
##
|
||||
# Send notifications for 'ereport.fs.zfs.data' events.
|
||||
# Disabled by default, any non-empty value will enable the feature.
|
||||
#
|
||||
#ZED_NOTIFY_DATA=
|
||||
|
||||
##
|
||||
# Pushbullet access token.
|
||||
# This grants full access to your account -- protect it accordingly!
|
||||
# <https://www.pushbullet.com/get-started>
|
||||
# <https://www.pushbullet.com/account>
|
||||
# Disabled by default; uncomment to enable.
|
||||
#
|
||||
#ZED_PUSHBULLET_ACCESS_TOKEN=""
|
||||
|
||||
##
|
||||
# Pushbullet channel tag for push notification feeds that can be subscribed to.
|
||||
# <https://www.pushbullet.com/my-channel>
|
||||
# If not defined, push notifications will instead be sent to all devices
|
||||
# associated with the account specified by the access token.
|
||||
# Disabled by default; uncomment to enable.
|
||||
#
|
||||
#ZED_PUSHBULLET_CHANNEL_TAG=""
|
||||
|
||||
##
|
||||
# Slack Webhook URL.
|
||||
# This allows posting to the given channel and includes an access token.
|
||||
# <https://api.slack.com/incoming-webhooks>
|
||||
# Disabled by default; uncomment to enable.
|
||||
#
|
||||
#ZED_SLACK_WEBHOOK_URL=""
|
||||
|
||||
##
|
||||
# Default directory for zed state files.
|
||||
#
|
||||
#ZED_RUNDIR="/var/run"
|
||||
|
||||
##
|
||||
# Turn on/off enclosure LEDs when drives get DEGRADED/FAULTED. This works for
|
||||
# device mapper and multipath devices as well. Your enclosure must be
|
||||
# supported by the Linux SES driver for this to work.
|
||||
#
|
||||
ZED_USE_ENCLOSURE_LEDS=1
|
||||
|
||||
##
|
||||
# Run a scrub after every resilver
|
||||
# Disabled by default, 1 to enable and 0 to disable.
|
||||
#ZED_SCRUB_AFTER_RESILVER=0
|
||||
|
||||
##
|
||||
# The syslog priority (e.g., specified as a "facility.level" pair).
|
||||
#
|
||||
#ZED_SYSLOG_PRIORITY="daemon.notice"
|
||||
|
||||
##
|
||||
# The syslog tag for marking zed events.
|
||||
#
|
||||
#ZED_SYSLOG_TAG="zed"
|
||||
|
||||
##
|
||||
# Which set of event subclasses to log
|
||||
# By default, events from all subclasses are logged.
|
||||
# If ZED_SYSLOG_SUBCLASS_INCLUDE is set, only subclasses
|
||||
# matching the pattern are logged. Use the pipe symbol (|)
|
||||
# or shell wildcards (*, ?) to match multiple subclasses.
|
||||
# Otherwise, if ZED_SYSLOG_SUBCLASS_EXCLUDE is set, the
|
||||
# matching subclasses are excluded from logging.
|
||||
#ZED_SYSLOG_SUBCLASS_INCLUDE="checksum|scrub_*|vdev.*"
|
||||
#ZED_SYSLOG_SUBCLASS_EXCLUDE="statechange|config_*|history_event"
|
||||
|
58
sys/contrib/openzfs/cmd/zed/zed.h
Normal file
58
sys/contrib/openzfs/cmd/zed/zed.h
Normal file
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* This file is part of the ZFS Event Daemon (ZED)
|
||||
* for ZFS on Linux (ZoL) <http://zfsonlinux.org/>.
|
||||
* Developed at Lawrence Livermore National Laboratory (LLNL-CODE-403049).
|
||||
* Copyright (C) 2013-2014 Lawrence Livermore National Security, LLC.
|
||||
* Refer to the ZoL git commit log for authoritative copyright attribution.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License Version 1.0 (CDDL-1.0).
|
||||
* You can obtain a copy of the license from the top-level file
|
||||
* "OPENSOLARIS.LICENSE" or at <http://opensource.org/licenses/CDDL-1.0>.
|
||||
* You may not use this file except in compliance with the license.
|
||||
*/
|
||||
|
||||
#ifndef ZED_H
|
||||
#define ZED_H
|
||||
|
||||
/*
|
||||
* Absolute path for the default zed configuration file.
|
||||
*/
|
||||
#define ZED_CONF_FILE SYSCONFDIR "/zfs/zed.conf"
|
||||
|
||||
/*
|
||||
* Absolute path for the default zed pid file.
|
||||
*/
|
||||
#define ZED_PID_FILE RUNSTATEDIR "/zed.pid"
|
||||
|
||||
/*
|
||||
* Absolute path for the default zed state file.
|
||||
*/
|
||||
#define ZED_STATE_FILE RUNSTATEDIR "/zed.state"
|
||||
|
||||
/*
|
||||
* Absolute path for the default zed zedlet directory.
|
||||
*/
|
||||
#define ZED_ZEDLET_DIR SYSCONFDIR "/zfs/zed.d"
|
||||
|
||||
/*
|
||||
* Reserved for future use.
|
||||
*/
|
||||
#define ZED_MAX_EVENTS 0
|
||||
|
||||
/*
|
||||
* Reserved for future use.
|
||||
*/
|
||||
#define ZED_MIN_EVENTS 0
|
||||
|
||||
/*
|
||||
* String prefix for ZED variables passed via environment variables.
|
||||
*/
|
||||
#define ZED_VAR_PREFIX "ZED_"
|
||||
|
||||
/*
|
||||
* String prefix for ZFS event names passed via environment variables.
|
||||
*/
|
||||
#define ZEVENT_VAR_PREFIX "ZEVENT_"
|
||||
|
||||
#endif /* !ZED_H */
|
735
sys/contrib/openzfs/cmd/zed/zed_conf.c
Normal file
735
sys/contrib/openzfs/cmd/zed/zed_conf.c
Normal file
@ -0,0 +1,735 @@
|
||||
/*
|
||||
* This file is part of the ZFS Event Daemon (ZED)
|
||||
* for ZFS on Linux (ZoL) <http://zfsonlinux.org/>.
|
||||
* Developed at Lawrence Livermore National Laboratory (LLNL-CODE-403049).
|
||||
* Copyright (C) 2013-2014 Lawrence Livermore National Security, LLC.
|
||||
* Refer to the ZoL git commit log for authoritative copyright attribution.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License Version 1.0 (CDDL-1.0).
|
||||
* You can obtain a copy of the license from the top-level file
|
||||
* "OPENSOLARIS.LICENSE" or at <http://opensource.org/licenses/CDDL-1.0>.
|
||||
* You may not use this file except in compliance with the license.
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <ctype.h>
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <libgen.h>
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/uio.h>
|
||||
#include <unistd.h>
|
||||
#include "zed.h"
|
||||
#include "zed_conf.h"
|
||||
#include "zed_file.h"
|
||||
#include "zed_log.h"
|
||||
#include "zed_strings.h"
|
||||
|
||||
/*
|
||||
* Return a new configuration with default values.
|
||||
*/
|
||||
struct zed_conf *
|
||||
zed_conf_create(void)
|
||||
{
|
||||
struct zed_conf *zcp;
|
||||
|
||||
zcp = calloc(1, sizeof (*zcp));
|
||||
if (!zcp)
|
||||
goto nomem;
|
||||
|
||||
zcp->syslog_facility = LOG_DAEMON;
|
||||
zcp->min_events = ZED_MIN_EVENTS;
|
||||
zcp->max_events = ZED_MAX_EVENTS;
|
||||
zcp->pid_fd = -1;
|
||||
zcp->zedlets = NULL; /* created via zed_conf_scan_dir() */
|
||||
zcp->state_fd = -1; /* opened via zed_conf_open_state() */
|
||||
zcp->zfs_hdl = NULL; /* opened via zed_event_init() */
|
||||
zcp->zevent_fd = -1; /* opened via zed_event_init() */
|
||||
|
||||
if (!(zcp->conf_file = strdup(ZED_CONF_FILE)))
|
||||
goto nomem;
|
||||
|
||||
if (!(zcp->pid_file = strdup(ZED_PID_FILE)))
|
||||
goto nomem;
|
||||
|
||||
if (!(zcp->zedlet_dir = strdup(ZED_ZEDLET_DIR)))
|
||||
goto nomem;
|
||||
|
||||
if (!(zcp->state_file = strdup(ZED_STATE_FILE)))
|
||||
goto nomem;
|
||||
|
||||
return (zcp);
|
||||
|
||||
nomem:
|
||||
zed_log_die("Failed to create conf: %s", strerror(errno));
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* Destroy the configuration [zcp].
|
||||
*
|
||||
* Note: zfs_hdl & zevent_fd are destroyed via zed_event_fini().
|
||||
*/
|
||||
void
|
||||
zed_conf_destroy(struct zed_conf *zcp)
|
||||
{
|
||||
if (!zcp)
|
||||
return;
|
||||
|
||||
if (zcp->state_fd >= 0) {
|
||||
if (close(zcp->state_fd) < 0)
|
||||
zed_log_msg(LOG_WARNING,
|
||||
"Failed to close state file \"%s\": %s",
|
||||
zcp->state_file, strerror(errno));
|
||||
zcp->state_fd = -1;
|
||||
}
|
||||
if (zcp->pid_file) {
|
||||
if ((unlink(zcp->pid_file) < 0) && (errno != ENOENT))
|
||||
zed_log_msg(LOG_WARNING,
|
||||
"Failed to remove PID file \"%s\": %s",
|
||||
zcp->pid_file, strerror(errno));
|
||||
}
|
||||
if (zcp->pid_fd >= 0) {
|
||||
if (close(zcp->pid_fd) < 0)
|
||||
zed_log_msg(LOG_WARNING,
|
||||
"Failed to close PID file \"%s\": %s",
|
||||
zcp->pid_file, strerror(errno));
|
||||
zcp->pid_fd = -1;
|
||||
}
|
||||
if (zcp->conf_file) {
|
||||
free(zcp->conf_file);
|
||||
zcp->conf_file = NULL;
|
||||
}
|
||||
if (zcp->pid_file) {
|
||||
free(zcp->pid_file);
|
||||
zcp->pid_file = NULL;
|
||||
}
|
||||
if (zcp->zedlet_dir) {
|
||||
free(zcp->zedlet_dir);
|
||||
zcp->zedlet_dir = NULL;
|
||||
}
|
||||
if (zcp->state_file) {
|
||||
free(zcp->state_file);
|
||||
zcp->state_file = NULL;
|
||||
}
|
||||
if (zcp->zedlets) {
|
||||
zed_strings_destroy(zcp->zedlets);
|
||||
zcp->zedlets = NULL;
|
||||
}
|
||||
free(zcp);
|
||||
}
|
||||
|
||||
/*
|
||||
* Display command-line help and exit.
|
||||
*
|
||||
* If [got_err] is 0, output to stdout and exit normally;
|
||||
* otherwise, output to stderr and exit with a failure status.
|
||||
*/
|
||||
static void
|
||||
_zed_conf_display_help(const char *prog, int got_err)
|
||||
{
|
||||
FILE *fp = got_err ? stderr : stdout;
|
||||
int w1 = 4; /* width of leading whitespace */
|
||||
int w2 = 8; /* width of L-justified option field */
|
||||
|
||||
fprintf(fp, "Usage: %s [OPTION]...\n", (prog ? prog : "zed"));
|
||||
fprintf(fp, "\n");
|
||||
fprintf(fp, "%*c%*s %s\n", w1, 0x20, -w2, "-h",
|
||||
"Display help.");
|
||||
fprintf(fp, "%*c%*s %s\n", w1, 0x20, -w2, "-L",
|
||||
"Display license information.");
|
||||
fprintf(fp, "%*c%*s %s\n", w1, 0x20, -w2, "-V",
|
||||
"Display version information.");
|
||||
fprintf(fp, "\n");
|
||||
fprintf(fp, "%*c%*s %s\n", w1, 0x20, -w2, "-v",
|
||||
"Be verbose.");
|
||||
fprintf(fp, "%*c%*s %s\n", w1, 0x20, -w2, "-f",
|
||||
"Force daemon to run.");
|
||||
fprintf(fp, "%*c%*s %s\n", w1, 0x20, -w2, "-F",
|
||||
"Run daemon in the foreground.");
|
||||
fprintf(fp, "%*c%*s %s\n", w1, 0x20, -w2, "-I",
|
||||
"Idle daemon until kernel module is (re)loaded.");
|
||||
fprintf(fp, "%*c%*s %s\n", w1, 0x20, -w2, "-M",
|
||||
"Lock all pages in memory.");
|
||||
fprintf(fp, "%*c%*s %s\n", w1, 0x20, -w2, "-P",
|
||||
"$PATH for ZED to use (only used by ZTS).");
|
||||
fprintf(fp, "%*c%*s %s\n", w1, 0x20, -w2, "-Z",
|
||||
"Zero state file.");
|
||||
fprintf(fp, "\n");
|
||||
#if 0
|
||||
fprintf(fp, "%*c%*s %s [%s]\n", w1, 0x20, -w2, "-c FILE",
|
||||
"Read configuration from FILE.", ZED_CONF_FILE);
|
||||
#endif
|
||||
fprintf(fp, "%*c%*s %s [%s]\n", w1, 0x20, -w2, "-d DIR",
|
||||
"Read enabled ZEDLETs from DIR.", ZED_ZEDLET_DIR);
|
||||
fprintf(fp, "%*c%*s %s [%s]\n", w1, 0x20, -w2, "-p FILE",
|
||||
"Write daemon's PID to FILE.", ZED_PID_FILE);
|
||||
fprintf(fp, "%*c%*s %s [%s]\n", w1, 0x20, -w2, "-s FILE",
|
||||
"Write daemon's state to FILE.", ZED_STATE_FILE);
|
||||
fprintf(fp, "\n");
|
||||
|
||||
exit(got_err ? EXIT_FAILURE : EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
/*
|
||||
* Display license information to stdout and exit.
|
||||
*/
|
||||
static void
|
||||
_zed_conf_display_license(void)
|
||||
{
|
||||
const char **pp;
|
||||
const char *text[] = {
|
||||
"The ZFS Event Daemon (ZED) is distributed under the terms of the",
|
||||
" Common Development and Distribution License (CDDL-1.0)",
|
||||
" <http://opensource.org/licenses/CDDL-1.0>.",
|
||||
"",
|
||||
"Developed at Lawrence Livermore National Laboratory"
|
||||
" (LLNL-CODE-403049).",
|
||||
"",
|
||||
NULL
|
||||
};
|
||||
|
||||
for (pp = text; *pp; pp++)
|
||||
printf("%s\n", *pp);
|
||||
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
/*
|
||||
* Display version information to stdout and exit.
|
||||
*/
|
||||
static void
|
||||
_zed_conf_display_version(void)
|
||||
{
|
||||
printf("%s-%s-%s\n",
|
||||
ZFS_META_NAME, ZFS_META_VERSION, ZFS_META_RELEASE);
|
||||
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
/*
|
||||
* Copy the [path] string to the [resultp] ptr.
|
||||
* If [path] is not an absolute path, prefix it with the current working dir.
|
||||
* If [resultp] is non-null, free its existing string before assignment.
|
||||
*/
|
||||
static void
|
||||
_zed_conf_parse_path(char **resultp, const char *path)
|
||||
{
|
||||
char buf[PATH_MAX];
|
||||
|
||||
assert(resultp != NULL);
|
||||
assert(path != NULL);
|
||||
|
||||
if (*resultp)
|
||||
free(*resultp);
|
||||
|
||||
if (path[0] == '/') {
|
||||
*resultp = strdup(path);
|
||||
} else if (!getcwd(buf, sizeof (buf))) {
|
||||
zed_log_die("Failed to get current working dir: %s",
|
||||
strerror(errno));
|
||||
} else if (strlcat(buf, "/", sizeof (buf)) >= sizeof (buf)) {
|
||||
zed_log_die("Failed to copy path: %s", strerror(ENAMETOOLONG));
|
||||
} else if (strlcat(buf, path, sizeof (buf)) >= sizeof (buf)) {
|
||||
zed_log_die("Failed to copy path: %s", strerror(ENAMETOOLONG));
|
||||
} else {
|
||||
*resultp = strdup(buf);
|
||||
}
|
||||
if (!*resultp)
|
||||
zed_log_die("Failed to copy path: %s", strerror(ENOMEM));
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse the command-line options into the configuration [zcp].
|
||||
*/
|
||||
void
|
||||
zed_conf_parse_opts(struct zed_conf *zcp, int argc, char **argv)
|
||||
{
|
||||
const char * const opts = ":hLVc:d:p:P:s:vfFMZI";
|
||||
int opt;
|
||||
|
||||
if (!zcp || !argv || !argv[0])
|
||||
zed_log_die("Failed to parse options: Internal error");
|
||||
|
||||
opterr = 0; /* suppress default getopt err msgs */
|
||||
|
||||
while ((opt = getopt(argc, argv, opts)) != -1) {
|
||||
switch (opt) {
|
||||
case 'h':
|
||||
_zed_conf_display_help(argv[0], EXIT_SUCCESS);
|
||||
break;
|
||||
case 'L':
|
||||
_zed_conf_display_license();
|
||||
break;
|
||||
case 'V':
|
||||
_zed_conf_display_version();
|
||||
break;
|
||||
case 'c':
|
||||
_zed_conf_parse_path(&zcp->conf_file, optarg);
|
||||
break;
|
||||
case 'd':
|
||||
_zed_conf_parse_path(&zcp->zedlet_dir, optarg);
|
||||
break;
|
||||
case 'I':
|
||||
zcp->do_idle = 1;
|
||||
break;
|
||||
case 'p':
|
||||
_zed_conf_parse_path(&zcp->pid_file, optarg);
|
||||
break;
|
||||
case 'P':
|
||||
_zed_conf_parse_path(&zcp->path, optarg);
|
||||
break;
|
||||
case 's':
|
||||
_zed_conf_parse_path(&zcp->state_file, optarg);
|
||||
break;
|
||||
case 'v':
|
||||
zcp->do_verbose = 1;
|
||||
break;
|
||||
case 'f':
|
||||
zcp->do_force = 1;
|
||||
break;
|
||||
case 'F':
|
||||
zcp->do_foreground = 1;
|
||||
break;
|
||||
case 'M':
|
||||
zcp->do_memlock = 1;
|
||||
break;
|
||||
case 'Z':
|
||||
zcp->do_zero = 1;
|
||||
break;
|
||||
case '?':
|
||||
default:
|
||||
if (optopt == '?')
|
||||
_zed_conf_display_help(argv[0], EXIT_SUCCESS);
|
||||
|
||||
fprintf(stderr, "%s: %s '-%c'\n\n", argv[0],
|
||||
"Invalid option", optopt);
|
||||
_zed_conf_display_help(argv[0], EXIT_FAILURE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse the configuration file into the configuration [zcp].
|
||||
*
|
||||
* FIXME: Not yet implemented.
|
||||
*/
|
||||
void
|
||||
zed_conf_parse_file(struct zed_conf *zcp)
|
||||
{
|
||||
if (!zcp)
|
||||
zed_log_die("Failed to parse config: %s", strerror(EINVAL));
|
||||
}
|
||||
|
||||
/*
|
||||
* Scan the [zcp] zedlet_dir for files to exec based on the event class.
|
||||
* Files must be executable by user, but not writable by group or other.
|
||||
* Dotfiles are ignored.
|
||||
*
|
||||
* Return 0 on success with an updated set of zedlets,
|
||||
* or -1 on error with errno set.
|
||||
*
|
||||
* FIXME: Check if zedlet_dir and all parent dirs are secure.
|
||||
*/
|
||||
int
|
||||
zed_conf_scan_dir(struct zed_conf *zcp)
|
||||
{
|
||||
zed_strings_t *zedlets;
|
||||
DIR *dirp;
|
||||
struct dirent *direntp;
|
||||
char pathname[PATH_MAX];
|
||||
struct stat st;
|
||||
int n;
|
||||
|
||||
if (!zcp) {
|
||||
errno = EINVAL;
|
||||
zed_log_msg(LOG_ERR, "Failed to scan zedlet dir: %s",
|
||||
strerror(errno));
|
||||
return (-1);
|
||||
}
|
||||
zedlets = zed_strings_create();
|
||||
if (!zedlets) {
|
||||
errno = ENOMEM;
|
||||
zed_log_msg(LOG_WARNING, "Failed to scan dir \"%s\": %s",
|
||||
zcp->zedlet_dir, strerror(errno));
|
||||
return (-1);
|
||||
}
|
||||
dirp = opendir(zcp->zedlet_dir);
|
||||
if (!dirp) {
|
||||
int errno_bak = errno;
|
||||
zed_log_msg(LOG_WARNING, "Failed to open dir \"%s\": %s",
|
||||
zcp->zedlet_dir, strerror(errno));
|
||||
zed_strings_destroy(zedlets);
|
||||
errno = errno_bak;
|
||||
return (-1);
|
||||
}
|
||||
while ((direntp = readdir(dirp))) {
|
||||
if (direntp->d_name[0] == '.')
|
||||
continue;
|
||||
|
||||
n = snprintf(pathname, sizeof (pathname),
|
||||
"%s/%s", zcp->zedlet_dir, direntp->d_name);
|
||||
if ((n < 0) || (n >= sizeof (pathname))) {
|
||||
zed_log_msg(LOG_WARNING, "Failed to stat \"%s\": %s",
|
||||
direntp->d_name, strerror(ENAMETOOLONG));
|
||||
continue;
|
||||
}
|
||||
if (stat(pathname, &st) < 0) {
|
||||
zed_log_msg(LOG_WARNING, "Failed to stat \"%s\": %s",
|
||||
pathname, strerror(errno));
|
||||
continue;
|
||||
}
|
||||
if (!S_ISREG(st.st_mode)) {
|
||||
zed_log_msg(LOG_INFO,
|
||||
"Ignoring \"%s\": not a regular file",
|
||||
direntp->d_name);
|
||||
continue;
|
||||
}
|
||||
if ((st.st_uid != 0) && !zcp->do_force) {
|
||||
zed_log_msg(LOG_NOTICE,
|
||||
"Ignoring \"%s\": not owned by root",
|
||||
direntp->d_name);
|
||||
continue;
|
||||
}
|
||||
if (!(st.st_mode & S_IXUSR)) {
|
||||
zed_log_msg(LOG_INFO,
|
||||
"Ignoring \"%s\": not executable by user",
|
||||
direntp->d_name);
|
||||
continue;
|
||||
}
|
||||
if ((st.st_mode & S_IWGRP) && !zcp->do_force) {
|
||||
zed_log_msg(LOG_NOTICE,
|
||||
"Ignoring \"%s\": writable by group",
|
||||
direntp->d_name);
|
||||
continue;
|
||||
}
|
||||
if ((st.st_mode & S_IWOTH) && !zcp->do_force) {
|
||||
zed_log_msg(LOG_NOTICE,
|
||||
"Ignoring \"%s\": writable by other",
|
||||
direntp->d_name);
|
||||
continue;
|
||||
}
|
||||
if (zed_strings_add(zedlets, NULL, direntp->d_name) < 0) {
|
||||
zed_log_msg(LOG_WARNING,
|
||||
"Failed to register \"%s\": %s",
|
||||
direntp->d_name, strerror(errno));
|
||||
continue;
|
||||
}
|
||||
if (zcp->do_verbose)
|
||||
zed_log_msg(LOG_INFO,
|
||||
"Registered zedlet \"%s\"", direntp->d_name);
|
||||
}
|
||||
if (closedir(dirp) < 0) {
|
||||
int errno_bak = errno;
|
||||
zed_log_msg(LOG_WARNING, "Failed to close dir \"%s\": %s",
|
||||
zcp->zedlet_dir, strerror(errno));
|
||||
zed_strings_destroy(zedlets);
|
||||
errno = errno_bak;
|
||||
return (-1);
|
||||
}
|
||||
if (zcp->zedlets)
|
||||
zed_strings_destroy(zcp->zedlets);
|
||||
|
||||
zcp->zedlets = zedlets;
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Write the PID file specified in [zcp].
|
||||
* Return 0 on success, -1 on error.
|
||||
*
|
||||
* This must be called after fork()ing to become a daemon (so the correct PID
|
||||
* is recorded), but before daemonization is complete and the parent process
|
||||
* exits (for synchronization with systemd).
|
||||
*/
|
||||
int
|
||||
zed_conf_write_pid(struct zed_conf *zcp)
|
||||
{
|
||||
const mode_t dirmode = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH;
|
||||
const mode_t filemode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
|
||||
char buf[PATH_MAX];
|
||||
int n;
|
||||
char *p;
|
||||
mode_t mask;
|
||||
int rv;
|
||||
|
||||
if (!zcp || !zcp->pid_file) {
|
||||
errno = EINVAL;
|
||||
zed_log_msg(LOG_ERR, "Failed to create PID file: %s",
|
||||
strerror(errno));
|
||||
return (-1);
|
||||
}
|
||||
assert(zcp->pid_fd == -1);
|
||||
/*
|
||||
* Create PID file directory if needed.
|
||||
*/
|
||||
n = strlcpy(buf, zcp->pid_file, sizeof (buf));
|
||||
if (n >= sizeof (buf)) {
|
||||
errno = ENAMETOOLONG;
|
||||
zed_log_msg(LOG_ERR, "Failed to create PID file: %s",
|
||||
strerror(errno));
|
||||
goto err;
|
||||
}
|
||||
p = strrchr(buf, '/');
|
||||
if (p)
|
||||
*p = '\0';
|
||||
|
||||
if ((mkdirp(buf, dirmode) < 0) && (errno != EEXIST)) {
|
||||
zed_log_msg(LOG_ERR, "Failed to create directory \"%s\": %s",
|
||||
buf, strerror(errno));
|
||||
goto err;
|
||||
}
|
||||
/*
|
||||
* Obtain PID file lock.
|
||||
*/
|
||||
mask = umask(0);
|
||||
umask(mask | 022);
|
||||
zcp->pid_fd = open(zcp->pid_file, (O_RDWR | O_CREAT), filemode);
|
||||
umask(mask);
|
||||
if (zcp->pid_fd < 0) {
|
||||
zed_log_msg(LOG_ERR, "Failed to open PID file \"%s\": %s",
|
||||
zcp->pid_file, strerror(errno));
|
||||
goto err;
|
||||
}
|
||||
rv = zed_file_lock(zcp->pid_fd);
|
||||
if (rv < 0) {
|
||||
zed_log_msg(LOG_ERR, "Failed to lock PID file \"%s\": %s",
|
||||
zcp->pid_file, strerror(errno));
|
||||
goto err;
|
||||
} else if (rv > 0) {
|
||||
pid_t pid = zed_file_is_locked(zcp->pid_fd);
|
||||
if (pid < 0) {
|
||||
zed_log_msg(LOG_ERR,
|
||||
"Failed to test lock on PID file \"%s\"",
|
||||
zcp->pid_file);
|
||||
} else if (pid > 0) {
|
||||
zed_log_msg(LOG_ERR,
|
||||
"Found PID %d bound to PID file \"%s\"",
|
||||
pid, zcp->pid_file);
|
||||
} else {
|
||||
zed_log_msg(LOG_ERR,
|
||||
"Inconsistent lock state on PID file \"%s\"",
|
||||
zcp->pid_file);
|
||||
}
|
||||
goto err;
|
||||
}
|
||||
/*
|
||||
* Write PID file.
|
||||
*/
|
||||
n = snprintf(buf, sizeof (buf), "%d\n", (int)getpid());
|
||||
if ((n < 0) || (n >= sizeof (buf))) {
|
||||
errno = ERANGE;
|
||||
zed_log_msg(LOG_ERR, "Failed to write PID file \"%s\": %s",
|
||||
zcp->pid_file, strerror(errno));
|
||||
} else if (zed_file_write_n(zcp->pid_fd, buf, n) != n) {
|
||||
zed_log_msg(LOG_ERR, "Failed to write PID file \"%s\": %s",
|
||||
zcp->pid_file, strerror(errno));
|
||||
} else if (fdatasync(zcp->pid_fd) < 0) {
|
||||
zed_log_msg(LOG_ERR, "Failed to sync PID file \"%s\": %s",
|
||||
zcp->pid_file, strerror(errno));
|
||||
} else {
|
||||
return (0);
|
||||
}
|
||||
|
||||
err:
|
||||
if (zcp->pid_fd >= 0) {
|
||||
(void) close(zcp->pid_fd);
|
||||
zcp->pid_fd = -1;
|
||||
}
|
||||
return (-1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Open and lock the [zcp] state_file.
|
||||
* Return 0 on success, -1 on error.
|
||||
*
|
||||
* FIXME: Move state information into kernel.
|
||||
*/
|
||||
int
|
||||
zed_conf_open_state(struct zed_conf *zcp)
|
||||
{
|
||||
char dirbuf[PATH_MAX];
|
||||
mode_t dirmode = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH;
|
||||
int n;
|
||||
char *p;
|
||||
int rv;
|
||||
|
||||
if (!zcp || !zcp->state_file) {
|
||||
errno = EINVAL;
|
||||
zed_log_msg(LOG_ERR, "Failed to open state file: %s",
|
||||
strerror(errno));
|
||||
return (-1);
|
||||
}
|
||||
n = strlcpy(dirbuf, zcp->state_file, sizeof (dirbuf));
|
||||
if (n >= sizeof (dirbuf)) {
|
||||
errno = ENAMETOOLONG;
|
||||
zed_log_msg(LOG_WARNING, "Failed to open state file: %s",
|
||||
strerror(errno));
|
||||
return (-1);
|
||||
}
|
||||
p = strrchr(dirbuf, '/');
|
||||
if (p)
|
||||
*p = '\0';
|
||||
|
||||
if ((mkdirp(dirbuf, dirmode) < 0) && (errno != EEXIST)) {
|
||||
zed_log_msg(LOG_WARNING,
|
||||
"Failed to create directory \"%s\": %s",
|
||||
dirbuf, strerror(errno));
|
||||
return (-1);
|
||||
}
|
||||
if (zcp->state_fd >= 0) {
|
||||
if (close(zcp->state_fd) < 0) {
|
||||
zed_log_msg(LOG_WARNING,
|
||||
"Failed to close state file \"%s\": %s",
|
||||
zcp->state_file, strerror(errno));
|
||||
return (-1);
|
||||
}
|
||||
}
|
||||
if (zcp->do_zero)
|
||||
(void) unlink(zcp->state_file);
|
||||
|
||||
zcp->state_fd = open(zcp->state_file,
|
||||
(O_RDWR | O_CREAT), (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH));
|
||||
if (zcp->state_fd < 0) {
|
||||
zed_log_msg(LOG_WARNING, "Failed to open state file \"%s\": %s",
|
||||
zcp->state_file, strerror(errno));
|
||||
return (-1);
|
||||
}
|
||||
rv = zed_file_lock(zcp->state_fd);
|
||||
if (rv < 0) {
|
||||
zed_log_msg(LOG_WARNING, "Failed to lock state file \"%s\": %s",
|
||||
zcp->state_file, strerror(errno));
|
||||
return (-1);
|
||||
}
|
||||
if (rv > 0) {
|
||||
pid_t pid = zed_file_is_locked(zcp->state_fd);
|
||||
if (pid < 0) {
|
||||
zed_log_msg(LOG_WARNING,
|
||||
"Failed to test lock on state file \"%s\"",
|
||||
zcp->state_file);
|
||||
} else if (pid > 0) {
|
||||
zed_log_msg(LOG_WARNING,
|
||||
"Found PID %d bound to state file \"%s\"",
|
||||
pid, zcp->state_file);
|
||||
} else {
|
||||
zed_log_msg(LOG_WARNING,
|
||||
"Inconsistent lock state on state file \"%s\"",
|
||||
zcp->state_file);
|
||||
}
|
||||
return (-1);
|
||||
}
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Read the opened [zcp] state_file to obtain the eid & etime of the last event
|
||||
* processed. Write the state from the last event to the [eidp] & [etime] args
|
||||
* passed by reference. Note that etime[] is an array of size 2.
|
||||
* Return 0 on success, -1 on error.
|
||||
*/
|
||||
int
|
||||
zed_conf_read_state(struct zed_conf *zcp, uint64_t *eidp, int64_t etime[])
|
||||
{
|
||||
ssize_t len;
|
||||
struct iovec iov[3];
|
||||
ssize_t n;
|
||||
|
||||
if (!zcp || !eidp || !etime) {
|
||||
errno = EINVAL;
|
||||
zed_log_msg(LOG_ERR,
|
||||
"Failed to read state file: %s", strerror(errno));
|
||||
return (-1);
|
||||
}
|
||||
if (lseek(zcp->state_fd, 0, SEEK_SET) == (off_t)-1) {
|
||||
zed_log_msg(LOG_WARNING,
|
||||
"Failed to reposition state file offset: %s",
|
||||
strerror(errno));
|
||||
return (-1);
|
||||
}
|
||||
len = 0;
|
||||
iov[0].iov_base = eidp;
|
||||
len += iov[0].iov_len = sizeof (*eidp);
|
||||
iov[1].iov_base = &etime[0];
|
||||
len += iov[1].iov_len = sizeof (etime[0]);
|
||||
iov[2].iov_base = &etime[1];
|
||||
len += iov[2].iov_len = sizeof (etime[1]);
|
||||
|
||||
n = readv(zcp->state_fd, iov, 3);
|
||||
if (n == 0) {
|
||||
*eidp = 0;
|
||||
} else if (n < 0) {
|
||||
zed_log_msg(LOG_WARNING,
|
||||
"Failed to read state file \"%s\": %s",
|
||||
zcp->state_file, strerror(errno));
|
||||
return (-1);
|
||||
} else if (n != len) {
|
||||
errno = EIO;
|
||||
zed_log_msg(LOG_WARNING,
|
||||
"Failed to read state file \"%s\": Read %d of %d bytes",
|
||||
zcp->state_file, n, len);
|
||||
return (-1);
|
||||
}
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Write the [eid] & [etime] of the last processed event to the opened
|
||||
* [zcp] state_file. Note that etime[] is an array of size 2.
|
||||
* Return 0 on success, -1 on error.
|
||||
*/
|
||||
int
|
||||
zed_conf_write_state(struct zed_conf *zcp, uint64_t eid, int64_t etime[])
|
||||
{
|
||||
ssize_t len;
|
||||
struct iovec iov[3];
|
||||
ssize_t n;
|
||||
|
||||
if (!zcp) {
|
||||
errno = EINVAL;
|
||||
zed_log_msg(LOG_ERR,
|
||||
"Failed to write state file: %s", strerror(errno));
|
||||
return (-1);
|
||||
}
|
||||
if (lseek(zcp->state_fd, 0, SEEK_SET) == (off_t)-1) {
|
||||
zed_log_msg(LOG_WARNING,
|
||||
"Failed to reposition state file offset: %s",
|
||||
strerror(errno));
|
||||
return (-1);
|
||||
}
|
||||
len = 0;
|
||||
iov[0].iov_base = &eid;
|
||||
len += iov[0].iov_len = sizeof (eid);
|
||||
iov[1].iov_base = &etime[0];
|
||||
len += iov[1].iov_len = sizeof (etime[0]);
|
||||
iov[2].iov_base = &etime[1];
|
||||
len += iov[2].iov_len = sizeof (etime[1]);
|
||||
|
||||
n = writev(zcp->state_fd, iov, 3);
|
||||
if (n < 0) {
|
||||
zed_log_msg(LOG_WARNING,
|
||||
"Failed to write state file \"%s\": %s",
|
||||
zcp->state_file, strerror(errno));
|
||||
return (-1);
|
||||
}
|
||||
if (n != len) {
|
||||
errno = EIO;
|
||||
zed_log_msg(LOG_WARNING,
|
||||
"Failed to write state file \"%s\": Wrote %d of %d bytes",
|
||||
zcp->state_file, n, len);
|
||||
return (-1);
|
||||
}
|
||||
if (fdatasync(zcp->state_fd) < 0) {
|
||||
zed_log_msg(LOG_WARNING,
|
||||
"Failed to sync state file \"%s\": %s",
|
||||
zcp->state_file, strerror(errno));
|
||||
return (-1);
|
||||
}
|
||||
return (0);
|
||||
}
|
62
sys/contrib/openzfs/cmd/zed/zed_conf.h
Normal file
62
sys/contrib/openzfs/cmd/zed/zed_conf.h
Normal file
@ -0,0 +1,62 @@
|
||||
/*
|
||||
* This file is part of the ZFS Event Daemon (ZED)
|
||||
* for ZFS on Linux (ZoL) <http://zfsonlinux.org/>.
|
||||
* Developed at Lawrence Livermore National Laboratory (LLNL-CODE-403049).
|
||||
* Copyright (C) 2013-2014 Lawrence Livermore National Security, LLC.
|
||||
* Refer to the ZoL git commit log for authoritative copyright attribution.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License Version 1.0 (CDDL-1.0).
|
||||
* You can obtain a copy of the license from the top-level file
|
||||
* "OPENSOLARIS.LICENSE" or at <http://opensource.org/licenses/CDDL-1.0>.
|
||||
* You may not use this file except in compliance with the license.
|
||||
*/
|
||||
|
||||
#ifndef ZED_CONF_H
|
||||
#define ZED_CONF_H
|
||||
|
||||
#include <libzfs.h>
|
||||
#include <stdint.h>
|
||||
#include "zed_strings.h"
|
||||
|
||||
struct zed_conf {
|
||||
unsigned do_force:1; /* true if force enabled */
|
||||
unsigned do_foreground:1; /* true if run in foreground */
|
||||
unsigned do_memlock:1; /* true if locking memory */
|
||||
unsigned do_verbose:1; /* true if verbosity enabled */
|
||||
unsigned do_zero:1; /* true if zeroing state */
|
||||
unsigned do_idle:1; /* true if idle enabled */
|
||||
int syslog_facility; /* syslog facility value */
|
||||
int min_events; /* RESERVED FOR FUTURE USE */
|
||||
int max_events; /* RESERVED FOR FUTURE USE */
|
||||
char *conf_file; /* abs path to config file */
|
||||
char *pid_file; /* abs path to pid file */
|
||||
int pid_fd; /* fd to pid file for lock */
|
||||
char *zedlet_dir; /* abs path to zedlet dir */
|
||||
zed_strings_t *zedlets; /* names of enabled zedlets */
|
||||
char *state_file; /* abs path to state file */
|
||||
int state_fd; /* fd to state file */
|
||||
libzfs_handle_t *zfs_hdl; /* handle to libzfs */
|
||||
int zevent_fd; /* fd for access to zevents */
|
||||
char *path; /* custom $PATH for zedlets to use */
|
||||
};
|
||||
|
||||
struct zed_conf *zed_conf_create(void);
|
||||
|
||||
void zed_conf_destroy(struct zed_conf *zcp);
|
||||
|
||||
void zed_conf_parse_opts(struct zed_conf *zcp, int argc, char **argv);
|
||||
|
||||
void zed_conf_parse_file(struct zed_conf *zcp);
|
||||
|
||||
int zed_conf_scan_dir(struct zed_conf *zcp);
|
||||
|
||||
int zed_conf_write_pid(struct zed_conf *zcp);
|
||||
|
||||
int zed_conf_open_state(struct zed_conf *zcp);
|
||||
|
||||
int zed_conf_read_state(struct zed_conf *zcp, uint64_t *eidp, int64_t etime[]);
|
||||
|
||||
int zed_conf_write_state(struct zed_conf *zcp, uint64_t eid, int64_t etime[]);
|
||||
|
||||
#endif /* !ZED_CONF_H */
|
416
sys/contrib/openzfs/cmd/zed/zed_disk_event.c
Normal file
416
sys/contrib/openzfs/cmd/zed/zed_disk_event.c
Normal file
@ -0,0 +1,416 @@
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License Version 1.0 (CDDL-1.0).
|
||||
* You can obtain a copy of the license from the top-level file
|
||||
* "OPENSOLARIS.LICENSE" or at <http://opensource.org/licenses/CDDL-1.0>.
|
||||
* You may not use this file except in compliance with the license.
|
||||
*
|
||||
* CDDL HEADER END
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (c) 2016, 2017, Intel Corporation.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_LIBUDEV
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <libnvpair.h>
|
||||
#include <libudev.h>
|
||||
#include <libzfs.h>
|
||||
#include <libzutil.h>
|
||||
#include <pthread.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <sys/sysevent/eventdefs.h>
|
||||
#include <sys/sysevent/dev.h>
|
||||
|
||||
#include "zed_log.h"
|
||||
#include "zed_disk_event.h"
|
||||
#include "agents/zfs_agents.h"
|
||||
|
||||
/*
|
||||
* Portions of ZED need to see disk events for disks belonging to ZFS pools.
|
||||
* A libudev monitor is established to monitor block device actions and pass
|
||||
* them on to internal ZED logic modules. Initially, zfs_mod.c is the only
|
||||
* consumer and is the Linux equivalent for the illumos syseventd ZFS SLM
|
||||
* module responsible for handling disk events for ZFS.
|
||||
*/
|
||||
|
||||
pthread_t g_mon_tid;
|
||||
struct udev *g_udev;
|
||||
struct udev_monitor *g_mon;
|
||||
|
||||
|
||||
#define DEV_BYID_PATH "/dev/disk/by-id/"
|
||||
|
||||
/* 64MB is minimum usable disk for ZFS */
|
||||
#define MINIMUM_SECTORS 131072
|
||||
|
||||
|
||||
/*
|
||||
* Post disk event to SLM module
|
||||
*
|
||||
* occurs in the context of monitor thread
|
||||
*/
|
||||
static void
|
||||
zed_udev_event(const char *class, const char *subclass, nvlist_t *nvl)
|
||||
{
|
||||
char *strval;
|
||||
uint64_t numval;
|
||||
|
||||
zed_log_msg(LOG_INFO, "zed_disk_event:");
|
||||
zed_log_msg(LOG_INFO, "\tclass: %s", class);
|
||||
zed_log_msg(LOG_INFO, "\tsubclass: %s", subclass);
|
||||
if (nvlist_lookup_string(nvl, DEV_NAME, &strval) == 0)
|
||||
zed_log_msg(LOG_INFO, "\t%s: %s", DEV_NAME, strval);
|
||||
if (nvlist_lookup_string(nvl, DEV_PATH, &strval) == 0)
|
||||
zed_log_msg(LOG_INFO, "\t%s: %s", DEV_PATH, strval);
|
||||
if (nvlist_lookup_string(nvl, DEV_IDENTIFIER, &strval) == 0)
|
||||
zed_log_msg(LOG_INFO, "\t%s: %s", DEV_IDENTIFIER, strval);
|
||||
if (nvlist_lookup_string(nvl, DEV_PHYS_PATH, &strval) == 0)
|
||||
zed_log_msg(LOG_INFO, "\t%s: %s", DEV_PHYS_PATH, strval);
|
||||
if (nvlist_lookup_uint64(nvl, DEV_SIZE, &numval) == 0)
|
||||
zed_log_msg(LOG_INFO, "\t%s: %llu", DEV_SIZE, numval);
|
||||
if (nvlist_lookup_uint64(nvl, ZFS_EV_POOL_GUID, &numval) == 0)
|
||||
zed_log_msg(LOG_INFO, "\t%s: %llu", ZFS_EV_POOL_GUID, numval);
|
||||
if (nvlist_lookup_uint64(nvl, ZFS_EV_VDEV_GUID, &numval) == 0)
|
||||
zed_log_msg(LOG_INFO, "\t%s: %llu", ZFS_EV_VDEV_GUID, numval);
|
||||
|
||||
(void) zfs_agent_post_event(class, subclass, nvl);
|
||||
}
|
||||
|
||||
/*
|
||||
* dev_event_nvlist: place event schema into an nv pair list
|
||||
*
|
||||
* NAME VALUE (example)
|
||||
* -------------- --------------------------------------------------------
|
||||
* DEV_NAME /dev/sdl
|
||||
* DEV_PATH /devices/pci0000:00/0000:00:03.0/0000:04:00.0/host0/...
|
||||
* DEV_IDENTIFIER ata-Hitachi_HTS725050A9A362_100601PCG420VLJ37DMC
|
||||
* DEV_PHYS_PATH pci-0000:04:00.0-sas-0x4433221101000000-lun-0
|
||||
* DEV_IS_PART ---
|
||||
* DEV_SIZE 500107862016
|
||||
* ZFS_EV_POOL_GUID 17523635698032189180
|
||||
* ZFS_EV_VDEV_GUID 14663607734290803088
|
||||
*/
|
||||
static nvlist_t *
|
||||
dev_event_nvlist(struct udev_device *dev)
|
||||
{
|
||||
nvlist_t *nvl;
|
||||
char strval[128];
|
||||
const char *value, *path;
|
||||
uint64_t guid;
|
||||
|
||||
if (nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0) != 0)
|
||||
return (NULL);
|
||||
|
||||
if (zfs_device_get_devid(dev, strval, sizeof (strval)) == 0)
|
||||
(void) nvlist_add_string(nvl, DEV_IDENTIFIER, strval);
|
||||
if (zfs_device_get_physical(dev, strval, sizeof (strval)) == 0)
|
||||
(void) nvlist_add_string(nvl, DEV_PHYS_PATH, strval);
|
||||
if ((path = udev_device_get_devnode(dev)) != NULL)
|
||||
(void) nvlist_add_string(nvl, DEV_NAME, path);
|
||||
if ((value = udev_device_get_devpath(dev)) != NULL)
|
||||
(void) nvlist_add_string(nvl, DEV_PATH, value);
|
||||
value = udev_device_get_devtype(dev);
|
||||
if ((value != NULL && strcmp("partition", value) == 0) ||
|
||||
(udev_device_get_property_value(dev, "ID_PART_ENTRY_NUMBER")
|
||||
!= NULL)) {
|
||||
(void) nvlist_add_boolean(nvl, DEV_IS_PART);
|
||||
}
|
||||
if ((value = udev_device_get_sysattr_value(dev, "size")) != NULL) {
|
||||
uint64_t numval = DEV_BSIZE;
|
||||
|
||||
numval *= strtoull(value, NULL, 10);
|
||||
(void) nvlist_add_uint64(nvl, DEV_SIZE, numval);
|
||||
}
|
||||
|
||||
/*
|
||||
* Grab the pool and vdev guids from blkid cache
|
||||
*/
|
||||
value = udev_device_get_property_value(dev, "ID_FS_UUID");
|
||||
if (value != NULL && (guid = strtoull(value, NULL, 10)) != 0)
|
||||
(void) nvlist_add_uint64(nvl, ZFS_EV_POOL_GUID, guid);
|
||||
|
||||
value = udev_device_get_property_value(dev, "ID_FS_UUID_SUB");
|
||||
if (value != NULL && (guid = strtoull(value, NULL, 10)) != 0)
|
||||
(void) nvlist_add_uint64(nvl, ZFS_EV_VDEV_GUID, guid);
|
||||
|
||||
/*
|
||||
* Either a vdev guid or a devid must be present for matching
|
||||
*/
|
||||
if (!nvlist_exists(nvl, DEV_IDENTIFIER) &&
|
||||
!nvlist_exists(nvl, ZFS_EV_VDEV_GUID)) {
|
||||
nvlist_free(nvl);
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
return (nvl);
|
||||
}
|
||||
|
||||
/*
|
||||
* Listen for block device uevents
|
||||
*/
|
||||
static void *
|
||||
zed_udev_monitor(void *arg)
|
||||
{
|
||||
struct udev_monitor *mon = arg;
|
||||
char *tmp, *tmp2;
|
||||
|
||||
zed_log_msg(LOG_INFO, "Waiting for new udev disk events...");
|
||||
|
||||
while (1) {
|
||||
struct udev_device *dev;
|
||||
const char *action, *type, *part, *sectors;
|
||||
const char *bus, *uuid;
|
||||
const char *class, *subclass;
|
||||
nvlist_t *nvl;
|
||||
boolean_t is_zfs = B_FALSE;
|
||||
|
||||
/* allow a cancellation while blocked (recvmsg) */
|
||||
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
|
||||
|
||||
/* blocks at recvmsg until an event occurs */
|
||||
if ((dev = udev_monitor_receive_device(mon)) == NULL) {
|
||||
zed_log_msg(LOG_WARNING, "zed_udev_monitor: receive "
|
||||
"device error %d", errno);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* allow all steps to complete before a cancellation */
|
||||
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
|
||||
|
||||
/*
|
||||
* Strongly typed device is the preferred filter
|
||||
*/
|
||||
type = udev_device_get_property_value(dev, "ID_FS_TYPE");
|
||||
if (type != NULL && type[0] != '\0') {
|
||||
if (strcmp(type, "zfs_member") == 0) {
|
||||
is_zfs = B_TRUE;
|
||||
} else {
|
||||
/* not ours, so skip */
|
||||
zed_log_msg(LOG_INFO, "zed_udev_monitor: skip "
|
||||
"%s (in use by %s)",
|
||||
udev_device_get_devnode(dev), type);
|
||||
udev_device_unref(dev);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* if this is a disk and it is partitioned, then the
|
||||
* zfs label will reside in a DEVTYPE=partition and
|
||||
* we can skip passing this event
|
||||
*/
|
||||
type = udev_device_get_property_value(dev, "DEVTYPE");
|
||||
part = udev_device_get_property_value(dev,
|
||||
"ID_PART_TABLE_TYPE");
|
||||
if (type != NULL && type[0] != '\0' &&
|
||||
strcmp(type, "disk") == 0 &&
|
||||
part != NULL && part[0] != '\0') {
|
||||
/* skip and wait for partition event */
|
||||
udev_device_unref(dev);
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* ignore small partitions
|
||||
*/
|
||||
sectors = udev_device_get_property_value(dev,
|
||||
"ID_PART_ENTRY_SIZE");
|
||||
if (sectors == NULL)
|
||||
sectors = udev_device_get_sysattr_value(dev, "size");
|
||||
if (sectors != NULL &&
|
||||
strtoull(sectors, NULL, 10) < MINIMUM_SECTORS) {
|
||||
udev_device_unref(dev);
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* If the blkid probe didn't find ZFS, then a persistent
|
||||
* device id string is required in the message schema
|
||||
* for matching with vdevs. Preflight here for expected
|
||||
* udev information.
|
||||
*/
|
||||
bus = udev_device_get_property_value(dev, "ID_BUS");
|
||||
uuid = udev_device_get_property_value(dev, "DM_UUID");
|
||||
if (!is_zfs && (bus == NULL && uuid == NULL)) {
|
||||
zed_log_msg(LOG_INFO, "zed_udev_monitor: %s no devid "
|
||||
"source", udev_device_get_devnode(dev));
|
||||
udev_device_unref(dev);
|
||||
continue;
|
||||
}
|
||||
|
||||
action = udev_device_get_action(dev);
|
||||
if (strcmp(action, "add") == 0) {
|
||||
class = EC_DEV_ADD;
|
||||
subclass = ESC_DISK;
|
||||
} else if (strcmp(action, "remove") == 0) {
|
||||
class = EC_DEV_REMOVE;
|
||||
subclass = ESC_DISK;
|
||||
} else if (strcmp(action, "change") == 0) {
|
||||
class = EC_DEV_STATUS;
|
||||
subclass = ESC_DEV_DLE;
|
||||
} else {
|
||||
zed_log_msg(LOG_WARNING, "zed_udev_monitor: %s unknown",
|
||||
action);
|
||||
udev_device_unref(dev);
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* Special case an EC_DEV_ADD for multipath devices
|
||||
*
|
||||
* When a multipath device is created, udev reports the
|
||||
* following:
|
||||
*
|
||||
* 1. "add" event of the dm device for the multipath device
|
||||
* (like /dev/dm-3).
|
||||
* 2. "change" event to create the actual multipath device
|
||||
* symlink (like /dev/mapper/mpatha). The event also
|
||||
* passes back the relevant DM vars we care about, like
|
||||
* DM_UUID.
|
||||
* 3. Another "change" event identical to #2 (that we ignore).
|
||||
*
|
||||
* To get the behavior we want, we treat the "change" event
|
||||
* in #2 as a "add" event; as if "/dev/mapper/mpatha" was
|
||||
* a new disk being added.
|
||||
*/
|
||||
if (strcmp(class, EC_DEV_STATUS) == 0 &&
|
||||
udev_device_get_property_value(dev, "DM_UUID") &&
|
||||
udev_device_get_property_value(dev, "MPATH_SBIN_PATH")) {
|
||||
tmp = (char *)udev_device_get_devnode(dev);
|
||||
tmp2 = zfs_get_underlying_path(tmp);
|
||||
if (tmp && tmp2 && (strcmp(tmp, tmp2) != 0)) {
|
||||
/*
|
||||
* We have a real underlying device, which
|
||||
* means that this multipath "change" event is
|
||||
* an "add" event.
|
||||
*
|
||||
* If the multipath device and the underlying
|
||||
* dev are the same name (i.e. /dev/dm-5), then
|
||||
* there is no real underlying disk for this
|
||||
* multipath device, and so this "change" event
|
||||
* really is a multipath removal.
|
||||
*/
|
||||
class = EC_DEV_ADD;
|
||||
subclass = ESC_DISK;
|
||||
} else {
|
||||
tmp = (char *)
|
||||
udev_device_get_property_value(dev,
|
||||
"DM_NR_VALID_PATHS");
|
||||
/* treat as a multipath remove */
|
||||
if (tmp != NULL && strcmp(tmp, "0") == 0) {
|
||||
class = EC_DEV_REMOVE;
|
||||
subclass = ESC_DISK;
|
||||
}
|
||||
}
|
||||
free(tmp2);
|
||||
}
|
||||
|
||||
/*
|
||||
* Special case an EC_DEV_ADD for scsi_debug devices
|
||||
*
|
||||
* These devices require a udevadm trigger command after
|
||||
* creation in order to register the vdev_id scsidebug alias
|
||||
* rule (adds a persistent path (phys_path) used for fault
|
||||
* management automated tests in the ZFS test suite.
|
||||
*
|
||||
* After udevadm trigger command, event registers as a "change"
|
||||
* event but needs to instead be handled as another "add" event
|
||||
* to allow for disk labeling and partitioning to occur.
|
||||
*/
|
||||
if (strcmp(class, EC_DEV_STATUS) == 0 &&
|
||||
udev_device_get_property_value(dev, "ID_VDEV") &&
|
||||
udev_device_get_property_value(dev, "ID_MODEL")) {
|
||||
const char *id_model, *id_model_sd = "scsi_debug";
|
||||
|
||||
id_model = udev_device_get_property_value(dev,
|
||||
"ID_MODEL");
|
||||
if (strcmp(id_model, id_model_sd) == 0) {
|
||||
class = EC_DEV_ADD;
|
||||
subclass = ESC_DISK;
|
||||
}
|
||||
}
|
||||
|
||||
if ((nvl = dev_event_nvlist(dev)) != NULL) {
|
||||
zed_udev_event(class, subclass, nvl);
|
||||
nvlist_free(nvl);
|
||||
}
|
||||
|
||||
udev_device_unref(dev);
|
||||
}
|
||||
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
int
|
||||
zed_disk_event_init()
|
||||
{
|
||||
int fd, fflags;
|
||||
|
||||
if ((g_udev = udev_new()) == NULL) {
|
||||
zed_log_msg(LOG_WARNING, "udev_new failed (%d)", errno);
|
||||
return (-1);
|
||||
}
|
||||
|
||||
/* Set up a udev monitor for block devices */
|
||||
g_mon = udev_monitor_new_from_netlink(g_udev, "udev");
|
||||
udev_monitor_filter_add_match_subsystem_devtype(g_mon, "block", "disk");
|
||||
udev_monitor_filter_add_match_subsystem_devtype(g_mon, "block",
|
||||
"partition");
|
||||
udev_monitor_enable_receiving(g_mon);
|
||||
|
||||
/* Make sure monitoring socket is blocking */
|
||||
fd = udev_monitor_get_fd(g_mon);
|
||||
if ((fflags = fcntl(fd, F_GETFL)) & O_NONBLOCK)
|
||||
(void) fcntl(fd, F_SETFL, fflags & ~O_NONBLOCK);
|
||||
|
||||
/* spawn a thread to monitor events */
|
||||
if (pthread_create(&g_mon_tid, NULL, zed_udev_monitor, g_mon) != 0) {
|
||||
udev_monitor_unref(g_mon);
|
||||
udev_unref(g_udev);
|
||||
zed_log_msg(LOG_WARNING, "pthread_create failed");
|
||||
return (-1);
|
||||
}
|
||||
|
||||
zed_log_msg(LOG_INFO, "zed_disk_event_init");
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
void
|
||||
zed_disk_event_fini()
|
||||
{
|
||||
/* cancel monitor thread at recvmsg() */
|
||||
(void) pthread_cancel(g_mon_tid);
|
||||
(void) pthread_join(g_mon_tid, NULL);
|
||||
|
||||
/* cleanup udev resources */
|
||||
udev_monitor_unref(g_mon);
|
||||
udev_unref(g_udev);
|
||||
|
||||
zed_log_msg(LOG_INFO, "zed_disk_event_fini");
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#include "zed_disk_event.h"
|
||||
|
||||
int
|
||||
zed_disk_event_init()
|
||||
{
|
||||
return (0);
|
||||
}
|
||||
|
||||
void
|
||||
zed_disk_event_fini()
|
||||
{
|
||||
}
|
||||
|
||||
#endif /* HAVE_LIBUDEV */
|
31
sys/contrib/openzfs/cmd/zed/zed_disk_event.h
Normal file
31
sys/contrib/openzfs/cmd/zed/zed_disk_event.h
Normal file
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License Version 1.0 (CDDL-1.0).
|
||||
* You can obtain a copy of the license from the top-level file
|
||||
* "OPENSOLARIS.LICENSE" or at <http://opensource.org/licenses/CDDL-1.0>.
|
||||
* You may not use this file except in compliance with the license.
|
||||
*
|
||||
* CDDL HEADER END
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (c) 2016, Intel Corporation.
|
||||
*/
|
||||
|
||||
#ifndef ZED_DISK_EVENT_H
|
||||
#define ZED_DISK_EVENT_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
extern int zed_disk_event_init(void);
|
||||
extern void zed_disk_event_fini(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* !ZED_DISK_EVENT_H */
|
965
sys/contrib/openzfs/cmd/zed/zed_event.c
Normal file
965
sys/contrib/openzfs/cmd/zed/zed_event.c
Normal file
@ -0,0 +1,965 @@
|
||||
/*
|
||||
* This file is part of the ZFS Event Daemon (ZED)
|
||||
* for ZFS on Linux (ZoL) <http://zfsonlinux.org/>.
|
||||
* Developed at Lawrence Livermore National Laboratory (LLNL-CODE-403049).
|
||||
* Copyright (C) 2013-2014 Lawrence Livermore National Security, LLC.
|
||||
* Refer to the ZoL git commit log for authoritative copyright attribution.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License Version 1.0 (CDDL-1.0).
|
||||
* You can obtain a copy of the license from the top-level file
|
||||
* "OPENSOLARIS.LICENSE" or at <http://opensource.org/licenses/CDDL-1.0>.
|
||||
* You may not use this file except in compliance with the license.
|
||||
*/
|
||||
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <libzfs.h> /* FIXME: Replace with libzfs_core. */
|
||||
#include <paths.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/zfs_ioctl.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/fm/fs/zfs.h>
|
||||
#include "zed.h"
|
||||
#include "zed_conf.h"
|
||||
#include "zed_disk_event.h"
|
||||
#include "zed_event.h"
|
||||
#include "zed_exec.h"
|
||||
#include "zed_file.h"
|
||||
#include "zed_log.h"
|
||||
#include "zed_strings.h"
|
||||
|
||||
#include "agents/zfs_agents.h"
|
||||
|
||||
#define MAXBUF 4096
|
||||
|
||||
/*
|
||||
* Open the libzfs interface.
|
||||
*/
|
||||
int
|
||||
zed_event_init(struct zed_conf *zcp)
|
||||
{
|
||||
if (!zcp)
|
||||
zed_log_die("Failed zed_event_init: %s", strerror(EINVAL));
|
||||
|
||||
zcp->zfs_hdl = libzfs_init();
|
||||
if (!zcp->zfs_hdl) {
|
||||
if (zcp->do_idle)
|
||||
return (-1);
|
||||
zed_log_die("Failed to initialize libzfs");
|
||||
}
|
||||
|
||||
zcp->zevent_fd = open(ZFS_DEV, O_RDWR);
|
||||
if (zcp->zevent_fd < 0) {
|
||||
if (zcp->do_idle)
|
||||
return (-1);
|
||||
zed_log_die("Failed to open \"%s\": %s",
|
||||
ZFS_DEV, strerror(errno));
|
||||
}
|
||||
|
||||
zfs_agent_init(zcp->zfs_hdl);
|
||||
|
||||
if (zed_disk_event_init() != 0) {
|
||||
if (zcp->do_idle)
|
||||
return (-1);
|
||||
zed_log_die("Failed to initialize disk events");
|
||||
}
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Close the libzfs interface.
|
||||
*/
|
||||
void
|
||||
zed_event_fini(struct zed_conf *zcp)
|
||||
{
|
||||
if (!zcp)
|
||||
zed_log_die("Failed zed_event_fini: %s", strerror(EINVAL));
|
||||
|
||||
zed_disk_event_fini();
|
||||
zfs_agent_fini();
|
||||
|
||||
if (zcp->zevent_fd >= 0) {
|
||||
if (close(zcp->zevent_fd) < 0)
|
||||
zed_log_msg(LOG_WARNING, "Failed to close \"%s\": %s",
|
||||
ZFS_DEV, strerror(errno));
|
||||
|
||||
zcp->zevent_fd = -1;
|
||||
}
|
||||
if (zcp->zfs_hdl) {
|
||||
libzfs_fini(zcp->zfs_hdl);
|
||||
zcp->zfs_hdl = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Seek to the event specified by [saved_eid] and [saved_etime].
|
||||
* This protects against processing a given event more than once.
|
||||
* Return 0 upon a successful seek to the specified event, or -1 otherwise.
|
||||
*
|
||||
* A zevent is considered to be uniquely specified by its (eid,time) tuple.
|
||||
* The unsigned 64b eid is set to 1 when the kernel module is loaded, and
|
||||
* incremented by 1 for each new event. Since the state file can persist
|
||||
* across a kernel module reload, the time must be checked to ensure a match.
|
||||
*/
|
||||
int
|
||||
zed_event_seek(struct zed_conf *zcp, uint64_t saved_eid, int64_t saved_etime[])
|
||||
{
|
||||
uint64_t eid;
|
||||
int found;
|
||||
nvlist_t *nvl;
|
||||
int n_dropped;
|
||||
int64_t *etime;
|
||||
uint_t nelem;
|
||||
int rv;
|
||||
|
||||
if (!zcp) {
|
||||
errno = EINVAL;
|
||||
zed_log_msg(LOG_ERR, "Failed to seek zevent: %s",
|
||||
strerror(errno));
|
||||
return (-1);
|
||||
}
|
||||
eid = 0;
|
||||
found = 0;
|
||||
while ((eid < saved_eid) && !found) {
|
||||
rv = zpool_events_next(zcp->zfs_hdl, &nvl, &n_dropped,
|
||||
ZEVENT_NONBLOCK, zcp->zevent_fd);
|
||||
|
||||
if ((rv != 0) || !nvl)
|
||||
break;
|
||||
|
||||
if (n_dropped > 0) {
|
||||
zed_log_msg(LOG_WARNING, "Missed %d events", n_dropped);
|
||||
/*
|
||||
* FIXME: Increase max size of event nvlist in
|
||||
* /sys/module/zfs/parameters/zfs_zevent_len_max ?
|
||||
*/
|
||||
}
|
||||
if (nvlist_lookup_uint64(nvl, "eid", &eid) != 0) {
|
||||
zed_log_msg(LOG_WARNING, "Failed to lookup zevent eid");
|
||||
} else if (nvlist_lookup_int64_array(nvl, "time",
|
||||
&etime, &nelem) != 0) {
|
||||
zed_log_msg(LOG_WARNING,
|
||||
"Failed to lookup zevent time (eid=%llu)", eid);
|
||||
} else if (nelem != 2) {
|
||||
zed_log_msg(LOG_WARNING,
|
||||
"Failed to lookup zevent time (eid=%llu, nelem=%u)",
|
||||
eid, nelem);
|
||||
} else if ((eid != saved_eid) ||
|
||||
(etime[0] != saved_etime[0]) ||
|
||||
(etime[1] != saved_etime[1])) {
|
||||
/* no-op */
|
||||
} else {
|
||||
found = 1;
|
||||
}
|
||||
free(nvl);
|
||||
}
|
||||
if (!found && (saved_eid > 0)) {
|
||||
if (zpool_events_seek(zcp->zfs_hdl, ZEVENT_SEEK_START,
|
||||
zcp->zevent_fd) < 0)
|
||||
zed_log_msg(LOG_WARNING, "Failed to seek to eid=0");
|
||||
else
|
||||
eid = 0;
|
||||
}
|
||||
zed_log_msg(LOG_NOTICE, "Processing events since eid=%llu", eid);
|
||||
return (found ? 0 : -1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Return non-zero if nvpair [name] should be formatted in hex; o/w, return 0.
|
||||
*/
|
||||
static int
|
||||
_zed_event_value_is_hex(const char *name)
|
||||
{
|
||||
const char *hex_suffix[] = {
|
||||
"_guid",
|
||||
"_guids",
|
||||
NULL
|
||||
};
|
||||
const char **pp;
|
||||
char *p;
|
||||
|
||||
if (!name)
|
||||
return (0);
|
||||
|
||||
for (pp = hex_suffix; *pp; pp++) {
|
||||
p = strstr(name, *pp);
|
||||
if (p && strlen(p) == strlen(*pp))
|
||||
return (1);
|
||||
}
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Add an environment variable for [eid] to the container [zsp].
|
||||
*
|
||||
* The variable name is the concatenation of [prefix] and [name] converted to
|
||||
* uppercase with non-alphanumeric characters converted to underscores;
|
||||
* [prefix] is optional, and [name] must begin with an alphabetic character.
|
||||
* If the converted variable name already exists within the container [zsp],
|
||||
* its existing value will be replaced with the new value.
|
||||
*
|
||||
* The variable value is specified by the format string [fmt].
|
||||
*
|
||||
* Returns 0 on success, and -1 on error (with errno set).
|
||||
*
|
||||
* All environment variables in [zsp] should be added through this function.
|
||||
*/
|
||||
static int
|
||||
_zed_event_add_var(uint64_t eid, zed_strings_t *zsp,
|
||||
const char *prefix, const char *name, const char *fmt, ...)
|
||||
{
|
||||
char keybuf[MAXBUF];
|
||||
char valbuf[MAXBUF];
|
||||
char *dstp;
|
||||
const char *srcp;
|
||||
const char *lastp;
|
||||
int n;
|
||||
int buflen;
|
||||
va_list vargs;
|
||||
|
||||
assert(zsp != NULL);
|
||||
assert(fmt != NULL);
|
||||
|
||||
if (!name) {
|
||||
errno = EINVAL;
|
||||
zed_log_msg(LOG_WARNING,
|
||||
"Failed to add variable for eid=%llu: Name is empty", eid);
|
||||
return (-1);
|
||||
} else if (!isalpha(name[0])) {
|
||||
errno = EINVAL;
|
||||
zed_log_msg(LOG_WARNING,
|
||||
"Failed to add variable for eid=%llu: "
|
||||
"Name \"%s\" is invalid", eid, name);
|
||||
return (-1);
|
||||
}
|
||||
/*
|
||||
* Construct the string key by converting PREFIX (if present) and NAME.
|
||||
*/
|
||||
dstp = keybuf;
|
||||
lastp = keybuf + sizeof (keybuf);
|
||||
if (prefix) {
|
||||
for (srcp = prefix; *srcp && (dstp < lastp); srcp++)
|
||||
*dstp++ = isalnum(*srcp) ? toupper(*srcp) : '_';
|
||||
}
|
||||
for (srcp = name; *srcp && (dstp < lastp); srcp++)
|
||||
*dstp++ = isalnum(*srcp) ? toupper(*srcp) : '_';
|
||||
|
||||
if (dstp == lastp) {
|
||||
errno = ENAMETOOLONG;
|
||||
zed_log_msg(LOG_WARNING,
|
||||
"Failed to add variable for eid=%llu: Name too long", eid);
|
||||
return (-1);
|
||||
}
|
||||
*dstp = '\0';
|
||||
/*
|
||||
* Construct the string specified by "[PREFIX][NAME]=[FMT]".
|
||||
*/
|
||||
dstp = valbuf;
|
||||
buflen = sizeof (valbuf);
|
||||
n = strlcpy(dstp, keybuf, buflen);
|
||||
if (n >= sizeof (valbuf)) {
|
||||
errno = EMSGSIZE;
|
||||
zed_log_msg(LOG_WARNING, "Failed to add %s for eid=%llu: %s",
|
||||
keybuf, eid, "Exceeded buffer size");
|
||||
return (-1);
|
||||
}
|
||||
dstp += n;
|
||||
buflen -= n;
|
||||
|
||||
*dstp++ = '=';
|
||||
buflen--;
|
||||
|
||||
if (buflen <= 0) {
|
||||
errno = EMSGSIZE;
|
||||
zed_log_msg(LOG_WARNING, "Failed to add %s for eid=%llu: %s",
|
||||
keybuf, eid, "Exceeded buffer size");
|
||||
return (-1);
|
||||
}
|
||||
|
||||
va_start(vargs, fmt);
|
||||
n = vsnprintf(dstp, buflen, fmt, vargs);
|
||||
va_end(vargs);
|
||||
|
||||
if ((n < 0) || (n >= buflen)) {
|
||||
errno = EMSGSIZE;
|
||||
zed_log_msg(LOG_WARNING, "Failed to add %s for eid=%llu: %s",
|
||||
keybuf, eid, "Exceeded buffer size");
|
||||
return (-1);
|
||||
} else if (zed_strings_add(zsp, keybuf, valbuf) < 0) {
|
||||
zed_log_msg(LOG_WARNING, "Failed to add %s for eid=%llu: %s",
|
||||
keybuf, eid, strerror(errno));
|
||||
return (-1);
|
||||
}
|
||||
return (0);
|
||||
}
|
||||
|
||||
static int
|
||||
_zed_event_add_array_err(uint64_t eid, const char *name)
|
||||
{
|
||||
errno = EMSGSIZE;
|
||||
zed_log_msg(LOG_WARNING,
|
||||
"Failed to convert nvpair \"%s\" for eid=%llu: "
|
||||
"Exceeded buffer size", name, eid);
|
||||
return (-1);
|
||||
}
|
||||
|
||||
static int
|
||||
_zed_event_add_int8_array(uint64_t eid, zed_strings_t *zsp,
|
||||
const char *prefix, nvpair_t *nvp)
|
||||
{
|
||||
char buf[MAXBUF];
|
||||
int buflen = sizeof (buf);
|
||||
const char *name;
|
||||
int8_t *i8p;
|
||||
uint_t nelem;
|
||||
uint_t i;
|
||||
char *p;
|
||||
int n;
|
||||
|
||||
assert((nvp != NULL) && (nvpair_type(nvp) == DATA_TYPE_INT8_ARRAY));
|
||||
|
||||
name = nvpair_name(nvp);
|
||||
(void) nvpair_value_int8_array(nvp, &i8p, &nelem);
|
||||
for (i = 0, p = buf; (i < nelem) && (buflen > 0); i++) {
|
||||
n = snprintf(p, buflen, "%d ", i8p[i]);
|
||||
if ((n < 0) || (n >= buflen))
|
||||
return (_zed_event_add_array_err(eid, name));
|
||||
p += n;
|
||||
buflen -= n;
|
||||
}
|
||||
if (nelem > 0)
|
||||
*--p = '\0';
|
||||
|
||||
return (_zed_event_add_var(eid, zsp, prefix, name, "%s", buf));
|
||||
}
|
||||
|
||||
static int
|
||||
_zed_event_add_uint8_array(uint64_t eid, zed_strings_t *zsp,
|
||||
const char *prefix, nvpair_t *nvp)
|
||||
{
|
||||
char buf[MAXBUF];
|
||||
int buflen = sizeof (buf);
|
||||
const char *name;
|
||||
uint8_t *u8p;
|
||||
uint_t nelem;
|
||||
uint_t i;
|
||||
char *p;
|
||||
int n;
|
||||
|
||||
assert((nvp != NULL) && (nvpair_type(nvp) == DATA_TYPE_UINT8_ARRAY));
|
||||
|
||||
name = nvpair_name(nvp);
|
||||
(void) nvpair_value_uint8_array(nvp, &u8p, &nelem);
|
||||
for (i = 0, p = buf; (i < nelem) && (buflen > 0); i++) {
|
||||
n = snprintf(p, buflen, "%u ", u8p[i]);
|
||||
if ((n < 0) || (n >= buflen))
|
||||
return (_zed_event_add_array_err(eid, name));
|
||||
p += n;
|
||||
buflen -= n;
|
||||
}
|
||||
if (nelem > 0)
|
||||
*--p = '\0';
|
||||
|
||||
return (_zed_event_add_var(eid, zsp, prefix, name, "%s", buf));
|
||||
}
|
||||
|
||||
static int
|
||||
_zed_event_add_int16_array(uint64_t eid, zed_strings_t *zsp,
|
||||
const char *prefix, nvpair_t *nvp)
|
||||
{
|
||||
char buf[MAXBUF];
|
||||
int buflen = sizeof (buf);
|
||||
const char *name;
|
||||
int16_t *i16p;
|
||||
uint_t nelem;
|
||||
uint_t i;
|
||||
char *p;
|
||||
int n;
|
||||
|
||||
assert((nvp != NULL) && (nvpair_type(nvp) == DATA_TYPE_INT16_ARRAY));
|
||||
|
||||
name = nvpair_name(nvp);
|
||||
(void) nvpair_value_int16_array(nvp, &i16p, &nelem);
|
||||
for (i = 0, p = buf; (i < nelem) && (buflen > 0); i++) {
|
||||
n = snprintf(p, buflen, "%d ", i16p[i]);
|
||||
if ((n < 0) || (n >= buflen))
|
||||
return (_zed_event_add_array_err(eid, name));
|
||||
p += n;
|
||||
buflen -= n;
|
||||
}
|
||||
if (nelem > 0)
|
||||
*--p = '\0';
|
||||
|
||||
return (_zed_event_add_var(eid, zsp, prefix, name, "%s", buf));
|
||||
}
|
||||
|
||||
static int
|
||||
_zed_event_add_uint16_array(uint64_t eid, zed_strings_t *zsp,
|
||||
const char *prefix, nvpair_t *nvp)
|
||||
{
|
||||
char buf[MAXBUF];
|
||||
int buflen = sizeof (buf);
|
||||
const char *name;
|
||||
uint16_t *u16p;
|
||||
uint_t nelem;
|
||||
uint_t i;
|
||||
char *p;
|
||||
int n;
|
||||
|
||||
assert((nvp != NULL) && (nvpair_type(nvp) == DATA_TYPE_UINT16_ARRAY));
|
||||
|
||||
name = nvpair_name(nvp);
|
||||
(void) nvpair_value_uint16_array(nvp, &u16p, &nelem);
|
||||
for (i = 0, p = buf; (i < nelem) && (buflen > 0); i++) {
|
||||
n = snprintf(p, buflen, "%u ", u16p[i]);
|
||||
if ((n < 0) || (n >= buflen))
|
||||
return (_zed_event_add_array_err(eid, name));
|
||||
p += n;
|
||||
buflen -= n;
|
||||
}
|
||||
if (nelem > 0)
|
||||
*--p = '\0';
|
||||
|
||||
return (_zed_event_add_var(eid, zsp, prefix, name, "%s", buf));
|
||||
}
|
||||
|
||||
static int
|
||||
_zed_event_add_int32_array(uint64_t eid, zed_strings_t *zsp,
|
||||
const char *prefix, nvpair_t *nvp)
|
||||
{
|
||||
char buf[MAXBUF];
|
||||
int buflen = sizeof (buf);
|
||||
const char *name;
|
||||
int32_t *i32p;
|
||||
uint_t nelem;
|
||||
uint_t i;
|
||||
char *p;
|
||||
int n;
|
||||
|
||||
assert((nvp != NULL) && (nvpair_type(nvp) == DATA_TYPE_INT32_ARRAY));
|
||||
|
||||
name = nvpair_name(nvp);
|
||||
(void) nvpair_value_int32_array(nvp, &i32p, &nelem);
|
||||
for (i = 0, p = buf; (i < nelem) && (buflen > 0); i++) {
|
||||
n = snprintf(p, buflen, "%d ", i32p[i]);
|
||||
if ((n < 0) || (n >= buflen))
|
||||
return (_zed_event_add_array_err(eid, name));
|
||||
p += n;
|
||||
buflen -= n;
|
||||
}
|
||||
if (nelem > 0)
|
||||
*--p = '\0';
|
||||
|
||||
return (_zed_event_add_var(eid, zsp, prefix, name, "%s", buf));
|
||||
}
|
||||
|
||||
static int
|
||||
_zed_event_add_uint32_array(uint64_t eid, zed_strings_t *zsp,
|
||||
const char *prefix, nvpair_t *nvp)
|
||||
{
|
||||
char buf[MAXBUF];
|
||||
int buflen = sizeof (buf);
|
||||
const char *name;
|
||||
uint32_t *u32p;
|
||||
uint_t nelem;
|
||||
uint_t i;
|
||||
char *p;
|
||||
int n;
|
||||
|
||||
assert((nvp != NULL) && (nvpair_type(nvp) == DATA_TYPE_UINT32_ARRAY));
|
||||
|
||||
name = nvpair_name(nvp);
|
||||
(void) nvpair_value_uint32_array(nvp, &u32p, &nelem);
|
||||
for (i = 0, p = buf; (i < nelem) && (buflen > 0); i++) {
|
||||
n = snprintf(p, buflen, "%u ", u32p[i]);
|
||||
if ((n < 0) || (n >= buflen))
|
||||
return (_zed_event_add_array_err(eid, name));
|
||||
p += n;
|
||||
buflen -= n;
|
||||
}
|
||||
if (nelem > 0)
|
||||
*--p = '\0';
|
||||
|
||||
return (_zed_event_add_var(eid, zsp, prefix, name, "%s", buf));
|
||||
}
|
||||
|
||||
static int
|
||||
_zed_event_add_int64_array(uint64_t eid, zed_strings_t *zsp,
|
||||
const char *prefix, nvpair_t *nvp)
|
||||
{
|
||||
char buf[MAXBUF];
|
||||
int buflen = sizeof (buf);
|
||||
const char *name;
|
||||
int64_t *i64p;
|
||||
uint_t nelem;
|
||||
uint_t i;
|
||||
char *p;
|
||||
int n;
|
||||
|
||||
assert((nvp != NULL) && (nvpair_type(nvp) == DATA_TYPE_INT64_ARRAY));
|
||||
|
||||
name = nvpair_name(nvp);
|
||||
(void) nvpair_value_int64_array(nvp, &i64p, &nelem);
|
||||
for (i = 0, p = buf; (i < nelem) && (buflen > 0); i++) {
|
||||
n = snprintf(p, buflen, "%lld ", (u_longlong_t)i64p[i]);
|
||||
if ((n < 0) || (n >= buflen))
|
||||
return (_zed_event_add_array_err(eid, name));
|
||||
p += n;
|
||||
buflen -= n;
|
||||
}
|
||||
if (nelem > 0)
|
||||
*--p = '\0';
|
||||
|
||||
return (_zed_event_add_var(eid, zsp, prefix, name, "%s", buf));
|
||||
}
|
||||
|
||||
static int
|
||||
_zed_event_add_uint64_array(uint64_t eid, zed_strings_t *zsp,
|
||||
const char *prefix, nvpair_t *nvp)
|
||||
{
|
||||
char buf[MAXBUF];
|
||||
int buflen = sizeof (buf);
|
||||
const char *name;
|
||||
const char *fmt;
|
||||
uint64_t *u64p;
|
||||
uint_t nelem;
|
||||
uint_t i;
|
||||
char *p;
|
||||
int n;
|
||||
|
||||
assert((nvp != NULL) && (nvpair_type(nvp) == DATA_TYPE_UINT64_ARRAY));
|
||||
|
||||
name = nvpair_name(nvp);
|
||||
fmt = _zed_event_value_is_hex(name) ? "0x%.16llX " : "%llu ";
|
||||
(void) nvpair_value_uint64_array(nvp, &u64p, &nelem);
|
||||
for (i = 0, p = buf; (i < nelem) && (buflen > 0); i++) {
|
||||
n = snprintf(p, buflen, fmt, (u_longlong_t)u64p[i]);
|
||||
if ((n < 0) || (n >= buflen))
|
||||
return (_zed_event_add_array_err(eid, name));
|
||||
p += n;
|
||||
buflen -= n;
|
||||
}
|
||||
if (nelem > 0)
|
||||
*--p = '\0';
|
||||
|
||||
return (_zed_event_add_var(eid, zsp, prefix, name, "%s", buf));
|
||||
}
|
||||
|
||||
static int
|
||||
_zed_event_add_string_array(uint64_t eid, zed_strings_t *zsp,
|
||||
const char *prefix, nvpair_t *nvp)
|
||||
{
|
||||
char buf[MAXBUF];
|
||||
int buflen = sizeof (buf);
|
||||
const char *name;
|
||||
char **strp;
|
||||
uint_t nelem;
|
||||
uint_t i;
|
||||
char *p;
|
||||
int n;
|
||||
|
||||
assert((nvp != NULL) && (nvpair_type(nvp) == DATA_TYPE_STRING_ARRAY));
|
||||
|
||||
name = nvpair_name(nvp);
|
||||
(void) nvpair_value_string_array(nvp, &strp, &nelem);
|
||||
for (i = 0, p = buf; (i < nelem) && (buflen > 0); i++) {
|
||||
n = snprintf(p, buflen, "%s ", strp[i] ? strp[i] : "<NULL>");
|
||||
if ((n < 0) || (n >= buflen))
|
||||
return (_zed_event_add_array_err(eid, name));
|
||||
p += n;
|
||||
buflen -= n;
|
||||
}
|
||||
if (nelem > 0)
|
||||
*--p = '\0';
|
||||
|
||||
return (_zed_event_add_var(eid, zsp, prefix, name, "%s", buf));
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert the nvpair [nvp] to a string which is added to the environment
|
||||
* of the child process.
|
||||
* Return 0 on success, -1 on error.
|
||||
*
|
||||
* FIXME: Refactor with cmd/zpool/zpool_main.c:zpool_do_events_nvprint()?
|
||||
*/
|
||||
static void
|
||||
_zed_event_add_nvpair(uint64_t eid, zed_strings_t *zsp, nvpair_t *nvp)
|
||||
{
|
||||
const char *name;
|
||||
data_type_t type;
|
||||
const char *prefix = ZEVENT_VAR_PREFIX;
|
||||
boolean_t b;
|
||||
double d;
|
||||
uint8_t i8;
|
||||
uint16_t i16;
|
||||
uint32_t i32;
|
||||
uint64_t i64;
|
||||
char *str;
|
||||
|
||||
assert(zsp != NULL);
|
||||
assert(nvp != NULL);
|
||||
|
||||
name = nvpair_name(nvp);
|
||||
type = nvpair_type(nvp);
|
||||
|
||||
switch (type) {
|
||||
case DATA_TYPE_BOOLEAN:
|
||||
_zed_event_add_var(eid, zsp, prefix, name, "%s", "1");
|
||||
break;
|
||||
case DATA_TYPE_BOOLEAN_VALUE:
|
||||
(void) nvpair_value_boolean_value(nvp, &b);
|
||||
_zed_event_add_var(eid, zsp, prefix, name, "%s", b ? "1" : "0");
|
||||
break;
|
||||
case DATA_TYPE_BYTE:
|
||||
(void) nvpair_value_byte(nvp, &i8);
|
||||
_zed_event_add_var(eid, zsp, prefix, name, "%d", i8);
|
||||
break;
|
||||
case DATA_TYPE_INT8:
|
||||
(void) nvpair_value_int8(nvp, (int8_t *)&i8);
|
||||
_zed_event_add_var(eid, zsp, prefix, name, "%d", i8);
|
||||
break;
|
||||
case DATA_TYPE_UINT8:
|
||||
(void) nvpair_value_uint8(nvp, &i8);
|
||||
_zed_event_add_var(eid, zsp, prefix, name, "%u", i8);
|
||||
break;
|
||||
case DATA_TYPE_INT16:
|
||||
(void) nvpair_value_int16(nvp, (int16_t *)&i16);
|
||||
_zed_event_add_var(eid, zsp, prefix, name, "%d", i16);
|
||||
break;
|
||||
case DATA_TYPE_UINT16:
|
||||
(void) nvpair_value_uint16(nvp, &i16);
|
||||
_zed_event_add_var(eid, zsp, prefix, name, "%u", i16);
|
||||
break;
|
||||
case DATA_TYPE_INT32:
|
||||
(void) nvpair_value_int32(nvp, (int32_t *)&i32);
|
||||
_zed_event_add_var(eid, zsp, prefix, name, "%d", i32);
|
||||
break;
|
||||
case DATA_TYPE_UINT32:
|
||||
(void) nvpair_value_uint32(nvp, &i32);
|
||||
_zed_event_add_var(eid, zsp, prefix, name, "%u", i32);
|
||||
break;
|
||||
case DATA_TYPE_INT64:
|
||||
(void) nvpair_value_int64(nvp, (int64_t *)&i64);
|
||||
_zed_event_add_var(eid, zsp, prefix, name,
|
||||
"%lld", (longlong_t)i64);
|
||||
break;
|
||||
case DATA_TYPE_UINT64:
|
||||
(void) nvpair_value_uint64(nvp, &i64);
|
||||
_zed_event_add_var(eid, zsp, prefix, name,
|
||||
(_zed_event_value_is_hex(name) ? "0x%.16llX" : "%llu"),
|
||||
(u_longlong_t)i64);
|
||||
/*
|
||||
* shadow readable strings for vdev state pairs
|
||||
*/
|
||||
if (strcmp(name, FM_EREPORT_PAYLOAD_ZFS_VDEV_STATE) == 0 ||
|
||||
strcmp(name, FM_EREPORT_PAYLOAD_ZFS_VDEV_LASTSTATE) == 0) {
|
||||
char alt[32];
|
||||
|
||||
(void) snprintf(alt, sizeof (alt), "%s_str", name);
|
||||
_zed_event_add_var(eid, zsp, prefix, alt, "%s",
|
||||
zpool_state_to_name(i64, VDEV_AUX_NONE));
|
||||
} else
|
||||
/*
|
||||
* shadow readable strings for pool state
|
||||
*/
|
||||
if (strcmp(name, FM_EREPORT_PAYLOAD_ZFS_POOL_STATE) == 0) {
|
||||
char alt[32];
|
||||
|
||||
(void) snprintf(alt, sizeof (alt), "%s_str", name);
|
||||
_zed_event_add_var(eid, zsp, prefix, alt, "%s",
|
||||
zpool_pool_state_to_name(i64));
|
||||
}
|
||||
break;
|
||||
case DATA_TYPE_DOUBLE:
|
||||
(void) nvpair_value_double(nvp, &d);
|
||||
_zed_event_add_var(eid, zsp, prefix, name, "%g", d);
|
||||
break;
|
||||
case DATA_TYPE_HRTIME:
|
||||
(void) nvpair_value_hrtime(nvp, (hrtime_t *)&i64);
|
||||
_zed_event_add_var(eid, zsp, prefix, name,
|
||||
"%llu", (u_longlong_t)i64);
|
||||
break;
|
||||
case DATA_TYPE_NVLIST:
|
||||
_zed_event_add_var(eid, zsp, prefix, name,
|
||||
"%s", "_NOT_IMPLEMENTED_"); /* FIXME */
|
||||
break;
|
||||
case DATA_TYPE_STRING:
|
||||
(void) nvpair_value_string(nvp, &str);
|
||||
_zed_event_add_var(eid, zsp, prefix, name,
|
||||
"%s", (str ? str : "<NULL>"));
|
||||
break;
|
||||
case DATA_TYPE_BOOLEAN_ARRAY:
|
||||
_zed_event_add_var(eid, zsp, prefix, name,
|
||||
"%s", "_NOT_IMPLEMENTED_"); /* FIXME */
|
||||
break;
|
||||
case DATA_TYPE_BYTE_ARRAY:
|
||||
_zed_event_add_var(eid, zsp, prefix, name,
|
||||
"%s", "_NOT_IMPLEMENTED_"); /* FIXME */
|
||||
break;
|
||||
case DATA_TYPE_INT8_ARRAY:
|
||||
_zed_event_add_int8_array(eid, zsp, prefix, nvp);
|
||||
break;
|
||||
case DATA_TYPE_UINT8_ARRAY:
|
||||
_zed_event_add_uint8_array(eid, zsp, prefix, nvp);
|
||||
break;
|
||||
case DATA_TYPE_INT16_ARRAY:
|
||||
_zed_event_add_int16_array(eid, zsp, prefix, nvp);
|
||||
break;
|
||||
case DATA_TYPE_UINT16_ARRAY:
|
||||
_zed_event_add_uint16_array(eid, zsp, prefix, nvp);
|
||||
break;
|
||||
case DATA_TYPE_INT32_ARRAY:
|
||||
_zed_event_add_int32_array(eid, zsp, prefix, nvp);
|
||||
break;
|
||||
case DATA_TYPE_UINT32_ARRAY:
|
||||
_zed_event_add_uint32_array(eid, zsp, prefix, nvp);
|
||||
break;
|
||||
case DATA_TYPE_INT64_ARRAY:
|
||||
_zed_event_add_int64_array(eid, zsp, prefix, nvp);
|
||||
break;
|
||||
case DATA_TYPE_UINT64_ARRAY:
|
||||
_zed_event_add_uint64_array(eid, zsp, prefix, nvp);
|
||||
break;
|
||||
case DATA_TYPE_STRING_ARRAY:
|
||||
_zed_event_add_string_array(eid, zsp, prefix, nvp);
|
||||
break;
|
||||
case DATA_TYPE_NVLIST_ARRAY:
|
||||
_zed_event_add_var(eid, zsp, prefix, name,
|
||||
"%s", "_NOT_IMPLEMENTED_"); /* FIXME */
|
||||
break;
|
||||
default:
|
||||
errno = EINVAL;
|
||||
zed_log_msg(LOG_WARNING,
|
||||
"Failed to convert nvpair \"%s\" for eid=%llu: "
|
||||
"Unrecognized type=%u", name, eid, (unsigned int) type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Restrict various environment variables to safe and sane values
|
||||
* when constructing the environment for the child process, unless
|
||||
* we're running with a custom $PATH (like under the ZFS test suite).
|
||||
*
|
||||
* Reference: Secure Programming Cookbook by Viega & Messier, Section 1.1.
|
||||
*/
|
||||
static void
|
||||
_zed_event_add_env_restrict(uint64_t eid, zed_strings_t *zsp,
|
||||
const char *path)
|
||||
{
|
||||
const char *env_restrict[][2] = {
|
||||
{ "IFS", " \t\n" },
|
||||
{ "PATH", _PATH_STDPATH },
|
||||
{ "ZDB", SBINDIR "/zdb" },
|
||||
{ "ZED", SBINDIR "/zed" },
|
||||
{ "ZFS", SBINDIR "/zfs" },
|
||||
{ "ZINJECT", SBINDIR "/zinject" },
|
||||
{ "ZPOOL", SBINDIR "/zpool" },
|
||||
{ "ZFS_ALIAS", ZFS_META_ALIAS },
|
||||
{ "ZFS_VERSION", ZFS_META_VERSION },
|
||||
{ "ZFS_RELEASE", ZFS_META_RELEASE },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
/*
|
||||
* If we have a custom $PATH, use the default ZFS binary locations
|
||||
* instead of the hard-coded ones.
|
||||
*/
|
||||
const char *env_path[][2] = {
|
||||
{ "IFS", " \t\n" },
|
||||
{ "PATH", NULL }, /* $PATH copied in later on */
|
||||
{ "ZDB", "zdb" },
|
||||
{ "ZED", "zed" },
|
||||
{ "ZFS", "zfs" },
|
||||
{ "ZINJECT", "zinject" },
|
||||
{ "ZPOOL", "zpool" },
|
||||
{ "ZFS_ALIAS", ZFS_META_ALIAS },
|
||||
{ "ZFS_VERSION", ZFS_META_VERSION },
|
||||
{ "ZFS_RELEASE", ZFS_META_RELEASE },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
const char *(*pa)[2];
|
||||
|
||||
assert(zsp != NULL);
|
||||
|
||||
pa = path != NULL ? env_path : env_restrict;
|
||||
|
||||
for (; *(*pa); pa++) {
|
||||
/* Use our custom $PATH if we have one */
|
||||
if (path != NULL && strcmp((*pa)[0], "PATH") == 0)
|
||||
(*pa)[1] = path;
|
||||
|
||||
_zed_event_add_var(eid, zsp, NULL, (*pa)[0], "%s", (*pa)[1]);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Preserve specified variables from the parent environment
|
||||
* when constructing the environment for the child process.
|
||||
*
|
||||
* Reference: Secure Programming Cookbook by Viega & Messier, Section 1.1.
|
||||
*/
|
||||
static void
|
||||
_zed_event_add_env_preserve(uint64_t eid, zed_strings_t *zsp)
|
||||
{
|
||||
const char *env_preserve[] = {
|
||||
"TZ",
|
||||
NULL
|
||||
};
|
||||
const char **keyp;
|
||||
const char *val;
|
||||
|
||||
assert(zsp != NULL);
|
||||
|
||||
for (keyp = env_preserve; *keyp; keyp++) {
|
||||
if ((val = getenv(*keyp)))
|
||||
_zed_event_add_var(eid, zsp, NULL, *keyp, "%s", val);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Compute the "subclass" by removing the first 3 components of [class]
|
||||
* (which will always be of the form "*.fs.zfs"). Return a pointer inside
|
||||
* the string [class], or NULL if insufficient components exist.
|
||||
*/
|
||||
static const char *
|
||||
_zed_event_get_subclass(const char *class)
|
||||
{
|
||||
const char *p;
|
||||
int i;
|
||||
|
||||
if (!class)
|
||||
return (NULL);
|
||||
|
||||
p = class;
|
||||
for (i = 0; i < 3; i++) {
|
||||
p = strchr(p, '.');
|
||||
if (!p)
|
||||
break;
|
||||
p++;
|
||||
}
|
||||
return (p);
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert the zevent time from a 2-element array of 64b integers
|
||||
* into a more convenient form:
|
||||
* - TIME_SECS is the second component of the time.
|
||||
* - TIME_NSECS is the nanosecond component of the time.
|
||||
* - TIME_STRING is an almost-RFC3339-compliant string representation.
|
||||
*/
|
||||
static void
|
||||
_zed_event_add_time_strings(uint64_t eid, zed_strings_t *zsp, int64_t etime[])
|
||||
{
|
||||
struct tm *stp;
|
||||
char buf[32];
|
||||
|
||||
assert(zsp != NULL);
|
||||
assert(etime != NULL);
|
||||
|
||||
_zed_event_add_var(eid, zsp, ZEVENT_VAR_PREFIX, "TIME_SECS",
|
||||
"%lld", (long long int) etime[0]);
|
||||
_zed_event_add_var(eid, zsp, ZEVENT_VAR_PREFIX, "TIME_NSECS",
|
||||
"%lld", (long long int) etime[1]);
|
||||
|
||||
if (!(stp = localtime((const time_t *) &etime[0]))) {
|
||||
zed_log_msg(LOG_WARNING, "Failed to add %s%s for eid=%llu: %s",
|
||||
ZEVENT_VAR_PREFIX, "TIME_STRING", eid, "localtime error");
|
||||
} else if (!strftime(buf, sizeof (buf), "%Y-%m-%d %H:%M:%S%z", stp)) {
|
||||
zed_log_msg(LOG_WARNING, "Failed to add %s%s for eid=%llu: %s",
|
||||
ZEVENT_VAR_PREFIX, "TIME_STRING", eid, "strftime error");
|
||||
} else {
|
||||
_zed_event_add_var(eid, zsp, ZEVENT_VAR_PREFIX, "TIME_STRING",
|
||||
"%s", buf);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Service the next zevent, blocking until one is available.
|
||||
*/
|
||||
int
|
||||
zed_event_service(struct zed_conf *zcp)
|
||||
{
|
||||
nvlist_t *nvl;
|
||||
nvpair_t *nvp;
|
||||
int n_dropped;
|
||||
zed_strings_t *zsp;
|
||||
uint64_t eid;
|
||||
int64_t *etime;
|
||||
uint_t nelem;
|
||||
char *class;
|
||||
const char *subclass;
|
||||
int rv;
|
||||
|
||||
if (!zcp) {
|
||||
errno = EINVAL;
|
||||
zed_log_msg(LOG_ERR, "Failed to service zevent: %s",
|
||||
strerror(errno));
|
||||
return (EINVAL);
|
||||
}
|
||||
rv = zpool_events_next(zcp->zfs_hdl, &nvl, &n_dropped, ZEVENT_NONE,
|
||||
zcp->zevent_fd);
|
||||
|
||||
if ((rv != 0) || !nvl)
|
||||
return (errno);
|
||||
|
||||
if (n_dropped > 0) {
|
||||
zed_log_msg(LOG_WARNING, "Missed %d events", n_dropped);
|
||||
/*
|
||||
* FIXME: Increase max size of event nvlist in
|
||||
* /sys/module/zfs/parameters/zfs_zevent_len_max ?
|
||||
*/
|
||||
}
|
||||
if (nvlist_lookup_uint64(nvl, "eid", &eid) != 0) {
|
||||
zed_log_msg(LOG_WARNING, "Failed to lookup zevent eid");
|
||||
} else if (nvlist_lookup_int64_array(
|
||||
nvl, "time", &etime, &nelem) != 0) {
|
||||
zed_log_msg(LOG_WARNING,
|
||||
"Failed to lookup zevent time (eid=%llu)", eid);
|
||||
} else if (nelem != 2) {
|
||||
zed_log_msg(LOG_WARNING,
|
||||
"Failed to lookup zevent time (eid=%llu, nelem=%u)",
|
||||
eid, nelem);
|
||||
} else if (nvlist_lookup_string(nvl, "class", &class) != 0) {
|
||||
zed_log_msg(LOG_WARNING,
|
||||
"Failed to lookup zevent class (eid=%llu)", eid);
|
||||
} else {
|
||||
/* let internal modules see this event first */
|
||||
zfs_agent_post_event(class, NULL, nvl);
|
||||
|
||||
zsp = zed_strings_create();
|
||||
|
||||
nvp = NULL;
|
||||
while ((nvp = nvlist_next_nvpair(nvl, nvp)))
|
||||
_zed_event_add_nvpair(eid, zsp, nvp);
|
||||
|
||||
_zed_event_add_env_restrict(eid, zsp, zcp->path);
|
||||
_zed_event_add_env_preserve(eid, zsp);
|
||||
|
||||
_zed_event_add_var(eid, zsp, ZED_VAR_PREFIX, "PID",
|
||||
"%d", (int)getpid());
|
||||
_zed_event_add_var(eid, zsp, ZED_VAR_PREFIX, "ZEDLET_DIR",
|
||||
"%s", zcp->zedlet_dir);
|
||||
subclass = _zed_event_get_subclass(class);
|
||||
_zed_event_add_var(eid, zsp, ZEVENT_VAR_PREFIX, "SUBCLASS",
|
||||
"%s", (subclass ? subclass : class));
|
||||
|
||||
_zed_event_add_time_strings(eid, zsp, etime);
|
||||
|
||||
zed_exec_process(eid, class, subclass,
|
||||
zcp->zedlet_dir, zcp->zedlets, zsp, zcp->zevent_fd);
|
||||
|
||||
zed_conf_write_state(zcp, eid, etime);
|
||||
|
||||
zed_strings_destroy(zsp);
|
||||
}
|
||||
nvlist_free(nvl);
|
||||
return (0);
|
||||
}
|
29
sys/contrib/openzfs/cmd/zed/zed_event.h
Normal file
29
sys/contrib/openzfs/cmd/zed/zed_event.h
Normal file
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* This file is part of the ZFS Event Daemon (ZED)
|
||||
* for ZFS on Linux (ZoL) <http://zfsonlinux.org/>.
|
||||
* Developed at Lawrence Livermore National Laboratory (LLNL-CODE-403049).
|
||||
* Copyright (C) 2013-2014 Lawrence Livermore National Security, LLC.
|
||||
* Refer to the ZoL git commit log for authoritative copyright attribution.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License Version 1.0 (CDDL-1.0).
|
||||
* You can obtain a copy of the license from the top-level file
|
||||
* "OPENSOLARIS.LICENSE" or at <http://opensource.org/licenses/CDDL-1.0>.
|
||||
* You may not use this file except in compliance with the license.
|
||||
*/
|
||||
|
||||
#ifndef ZED_EVENT_H
|
||||
#define ZED_EVENT_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
int zed_event_init(struct zed_conf *zcp);
|
||||
|
||||
void zed_event_fini(struct zed_conf *zcp);
|
||||
|
||||
int zed_event_seek(struct zed_conf *zcp, uint64_t saved_eid,
|
||||
int64_t saved_etime[]);
|
||||
|
||||
int zed_event_service(struct zed_conf *zcp);
|
||||
|
||||
#endif /* !ZED_EVENT_H */
|
232
sys/contrib/openzfs/cmd/zed/zed_exec.c
Normal file
232
sys/contrib/openzfs/cmd/zed/zed_exec.c
Normal file
@ -0,0 +1,232 @@
|
||||
/*
|
||||
* This file is part of the ZFS Event Daemon (ZED)
|
||||
* for ZFS on Linux (ZoL) <http://zfsonlinux.org/>.
|
||||
* Developed at Lawrence Livermore National Laboratory (LLNL-CODE-403049).
|
||||
* Copyright (C) 2013-2014 Lawrence Livermore National Security, LLC.
|
||||
* Refer to the ZoL git commit log for authoritative copyright attribution.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License Version 1.0 (CDDL-1.0).
|
||||
* You can obtain a copy of the license from the top-level file
|
||||
* "OPENSOLARIS.LICENSE" or at <http://opensource.org/licenses/CDDL-1.0>.
|
||||
* You may not use this file except in compliance with the license.
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include "zed_exec.h"
|
||||
#include "zed_file.h"
|
||||
#include "zed_log.h"
|
||||
#include "zed_strings.h"
|
||||
|
||||
#define ZEVENT_FILENO 3
|
||||
|
||||
/*
|
||||
* Create an environment string array for passing to execve() using the
|
||||
* NAME=VALUE strings in container [zsp].
|
||||
* Return a newly-allocated environment, or NULL on error.
|
||||
*/
|
||||
static char **
|
||||
_zed_exec_create_env(zed_strings_t *zsp)
|
||||
{
|
||||
int num_ptrs;
|
||||
int buflen;
|
||||
char *buf;
|
||||
char **pp;
|
||||
char *p;
|
||||
const char *q;
|
||||
int i;
|
||||
int len;
|
||||
|
||||
num_ptrs = zed_strings_count(zsp) + 1;
|
||||
buflen = num_ptrs * sizeof (char *);
|
||||
for (q = zed_strings_first(zsp); q; q = zed_strings_next(zsp))
|
||||
buflen += strlen(q) + 1;
|
||||
|
||||
buf = calloc(1, buflen);
|
||||
if (!buf)
|
||||
return (NULL);
|
||||
|
||||
pp = (char **)buf;
|
||||
p = buf + (num_ptrs * sizeof (char *));
|
||||
i = 0;
|
||||
for (q = zed_strings_first(zsp); q; q = zed_strings_next(zsp)) {
|
||||
pp[i] = p;
|
||||
len = strlen(q) + 1;
|
||||
memcpy(p, q, len);
|
||||
p += len;
|
||||
i++;
|
||||
}
|
||||
pp[i] = NULL;
|
||||
assert(buf + buflen == p);
|
||||
return ((char **)buf);
|
||||
}
|
||||
|
||||
/*
|
||||
* Fork a child process to handle event [eid]. The program [prog]
|
||||
* in directory [dir] is executed with the environment [env].
|
||||
*
|
||||
* The file descriptor [zfd] is the zevent_fd used to track the
|
||||
* current cursor location within the zevent nvlist.
|
||||
*/
|
||||
static void
|
||||
_zed_exec_fork_child(uint64_t eid, const char *dir, const char *prog,
|
||||
char *env[], int zfd)
|
||||
{
|
||||
char path[PATH_MAX];
|
||||
int n;
|
||||
pid_t pid;
|
||||
int fd;
|
||||
pid_t wpid;
|
||||
int status;
|
||||
|
||||
assert(dir != NULL);
|
||||
assert(prog != NULL);
|
||||
assert(env != NULL);
|
||||
assert(zfd >= 0);
|
||||
|
||||
n = snprintf(path, sizeof (path), "%s/%s", dir, prog);
|
||||
if ((n < 0) || (n >= sizeof (path))) {
|
||||
zed_log_msg(LOG_WARNING,
|
||||
"Failed to fork \"%s\" for eid=%llu: %s",
|
||||
prog, eid, strerror(ENAMETOOLONG));
|
||||
return;
|
||||
}
|
||||
pid = fork();
|
||||
if (pid < 0) {
|
||||
zed_log_msg(LOG_WARNING,
|
||||
"Failed to fork \"%s\" for eid=%llu: %s",
|
||||
prog, eid, strerror(errno));
|
||||
return;
|
||||
} else if (pid == 0) {
|
||||
(void) umask(022);
|
||||
if ((fd = open("/dev/null", O_RDWR)) != -1) {
|
||||
(void) dup2(fd, STDIN_FILENO);
|
||||
(void) dup2(fd, STDOUT_FILENO);
|
||||
(void) dup2(fd, STDERR_FILENO);
|
||||
}
|
||||
(void) dup2(zfd, ZEVENT_FILENO);
|
||||
zed_file_close_from(ZEVENT_FILENO + 1);
|
||||
execle(path, prog, NULL, env);
|
||||
_exit(127);
|
||||
}
|
||||
|
||||
/* parent process */
|
||||
|
||||
zed_log_msg(LOG_INFO, "Invoking \"%s\" eid=%llu pid=%d",
|
||||
prog, eid, pid);
|
||||
|
||||
/* FIXME: Timeout rogue child processes with sigalarm? */
|
||||
|
||||
/*
|
||||
* Wait for child process using WNOHANG to limit
|
||||
* the time spent waiting to 10 seconds (10,000ms).
|
||||
*/
|
||||
for (n = 0; n < 1000; n++) {
|
||||
wpid = waitpid(pid, &status, WNOHANG);
|
||||
if (wpid == (pid_t)-1) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
zed_log_msg(LOG_WARNING,
|
||||
"Failed to wait for \"%s\" eid=%llu pid=%d",
|
||||
prog, eid, pid);
|
||||
break;
|
||||
} else if (wpid == 0) {
|
||||
struct timespec t;
|
||||
|
||||
/* child still running */
|
||||
t.tv_sec = 0;
|
||||
t.tv_nsec = 10000000; /* 10ms */
|
||||
(void) nanosleep(&t, NULL);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (WIFEXITED(status)) {
|
||||
zed_log_msg(LOG_INFO,
|
||||
"Finished \"%s\" eid=%llu pid=%d exit=%d",
|
||||
prog, eid, pid, WEXITSTATUS(status));
|
||||
} else if (WIFSIGNALED(status)) {
|
||||
zed_log_msg(LOG_INFO,
|
||||
"Finished \"%s\" eid=%llu pid=%d sig=%d/%s",
|
||||
prog, eid, pid, WTERMSIG(status),
|
||||
strsignal(WTERMSIG(status)));
|
||||
} else {
|
||||
zed_log_msg(LOG_INFO,
|
||||
"Finished \"%s\" eid=%llu pid=%d status=0x%X",
|
||||
prog, eid, (unsigned int) status);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* kill child process after 10 seconds
|
||||
*/
|
||||
if (wpid == 0) {
|
||||
zed_log_msg(LOG_WARNING, "Killing hung \"%s\" pid=%d",
|
||||
prog, pid);
|
||||
(void) kill(pid, SIGKILL);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Process the event [eid] by synchronously invoking all zedlets with a
|
||||
* matching class prefix.
|
||||
*
|
||||
* Each executable in [zedlets] from the directory [dir] is matched against
|
||||
* the event's [class], [subclass], and the "all" class (which matches
|
||||
* all events). Every zedlet with a matching class prefix is invoked.
|
||||
* The NAME=VALUE strings in [envs] will be passed to the zedlet as
|
||||
* environment variables.
|
||||
*
|
||||
* The file descriptor [zfd] is the zevent_fd used to track the
|
||||
* current cursor location within the zevent nvlist.
|
||||
*
|
||||
* Return 0 on success, -1 on error.
|
||||
*/
|
||||
int
|
||||
zed_exec_process(uint64_t eid, const char *class, const char *subclass,
|
||||
const char *dir, zed_strings_t *zedlets, zed_strings_t *envs, int zfd)
|
||||
{
|
||||
const char *class_strings[4];
|
||||
const char *allclass = "all";
|
||||
const char **csp;
|
||||
const char *z;
|
||||
char **e;
|
||||
int n;
|
||||
|
||||
if (!dir || !zedlets || !envs || zfd < 0)
|
||||
return (-1);
|
||||
|
||||
csp = class_strings;
|
||||
|
||||
if (class)
|
||||
*csp++ = class;
|
||||
|
||||
if (subclass)
|
||||
*csp++ = subclass;
|
||||
|
||||
if (allclass)
|
||||
*csp++ = allclass;
|
||||
|
||||
*csp = NULL;
|
||||
|
||||
e = _zed_exec_create_env(envs);
|
||||
|
||||
for (z = zed_strings_first(zedlets); z; z = zed_strings_next(zedlets)) {
|
||||
for (csp = class_strings; *csp; csp++) {
|
||||
n = strlen(*csp);
|
||||
if ((strncmp(z, *csp, n) == 0) && !isalpha(z[n]))
|
||||
_zed_exec_fork_child(eid, dir, z, e, zfd);
|
||||
}
|
||||
}
|
||||
free(e);
|
||||
return (0);
|
||||
}
|
25
sys/contrib/openzfs/cmd/zed/zed_exec.h
Normal file
25
sys/contrib/openzfs/cmd/zed/zed_exec.h
Normal file
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* This file is part of the ZFS Event Daemon (ZED)
|
||||
* for ZFS on Linux (ZoL) <http://zfsonlinux.org/>.
|
||||
* Developed at Lawrence Livermore National Laboratory (LLNL-CODE-403049).
|
||||
* Copyright (C) 2013-2014 Lawrence Livermore National Security, LLC.
|
||||
* Refer to the ZoL git commit log for authoritative copyright attribution.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License Version 1.0 (CDDL-1.0).
|
||||
* You can obtain a copy of the license from the top-level file
|
||||
* "OPENSOLARIS.LICENSE" or at <http://opensource.org/licenses/CDDL-1.0>.
|
||||
* You may not use this file except in compliance with the license.
|
||||
*/
|
||||
|
||||
#ifndef ZED_EXEC_H
|
||||
#define ZED_EXEC_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include "zed_strings.h"
|
||||
|
||||
int zed_exec_process(uint64_t eid, const char *class, const char *subclass,
|
||||
const char *dir, zed_strings_t *zedlets, zed_strings_t *envs,
|
||||
int zevent_fd);
|
||||
|
||||
#endif /* !ZED_EXEC_H */
|
217
sys/contrib/openzfs/cmd/zed/zed_file.c
Normal file
217
sys/contrib/openzfs/cmd/zed/zed_file.c
Normal file
@ -0,0 +1,217 @@
|
||||
/*
|
||||
* This file is part of the ZFS Event Daemon (ZED)
|
||||
* for ZFS on Linux (ZoL) <http://zfsonlinux.org/>.
|
||||
* Developed at Lawrence Livermore National Laboratory (LLNL-CODE-403049).
|
||||
* Copyright (C) 2013-2014 Lawrence Livermore National Security, LLC.
|
||||
* Refer to the ZoL git commit log for authoritative copyright attribution.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License Version 1.0 (CDDL-1.0).
|
||||
* You can obtain a copy of the license from the top-level file
|
||||
* "OPENSOLARIS.LICENSE" or at <http://opensource.org/licenses/CDDL-1.0>.
|
||||
* You may not use this file except in compliance with the license.
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <limits.h>
|
||||
#include <string.h>
|
||||
#include <sys/resource.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include "zed_file.h"
|
||||
#include "zed_log.h"
|
||||
|
||||
/*
|
||||
* Read up to [n] bytes from [fd] into [buf].
|
||||
* Return the number of bytes read, 0 on EOF, or -1 on error.
|
||||
*/
|
||||
ssize_t
|
||||
zed_file_read_n(int fd, void *buf, size_t n)
|
||||
{
|
||||
unsigned char *p;
|
||||
size_t n_left;
|
||||
ssize_t n_read;
|
||||
|
||||
p = buf;
|
||||
n_left = n;
|
||||
while (n_left > 0) {
|
||||
if ((n_read = read(fd, p, n_left)) < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
else
|
||||
return (-1);
|
||||
|
||||
} else if (n_read == 0) {
|
||||
break;
|
||||
}
|
||||
n_left -= n_read;
|
||||
p += n_read;
|
||||
}
|
||||
return (n - n_left);
|
||||
}
|
||||
|
||||
/*
|
||||
* Write [n] bytes from [buf] out to [fd].
|
||||
* Return the number of bytes written, or -1 on error.
|
||||
*/
|
||||
ssize_t
|
||||
zed_file_write_n(int fd, void *buf, size_t n)
|
||||
{
|
||||
const unsigned char *p;
|
||||
size_t n_left;
|
||||
ssize_t n_written;
|
||||
|
||||
p = buf;
|
||||
n_left = n;
|
||||
while (n_left > 0) {
|
||||
if ((n_written = write(fd, p, n_left)) < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
else
|
||||
return (-1);
|
||||
|
||||
}
|
||||
n_left -= n_written;
|
||||
p += n_written;
|
||||
}
|
||||
return (n);
|
||||
}
|
||||
|
||||
/*
|
||||
* Set an exclusive advisory lock on the open file descriptor [fd].
|
||||
* Return 0 on success, 1 if a conflicting lock is held by another process,
|
||||
* or -1 on error (with errno set).
|
||||
*/
|
||||
int
|
||||
zed_file_lock(int fd)
|
||||
{
|
||||
struct flock lock;
|
||||
|
||||
if (fd < 0) {
|
||||
errno = EBADF;
|
||||
return (-1);
|
||||
}
|
||||
lock.l_type = F_WRLCK;
|
||||
lock.l_whence = SEEK_SET;
|
||||
lock.l_start = 0;
|
||||
lock.l_len = 0;
|
||||
|
||||
if (fcntl(fd, F_SETLK, &lock) < 0) {
|
||||
if ((errno == EACCES) || (errno == EAGAIN))
|
||||
return (1);
|
||||
|
||||
return (-1);
|
||||
}
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Release an advisory lock held on the open file descriptor [fd].
|
||||
* Return 0 on success, or -1 on error (with errno set).
|
||||
*/
|
||||
int
|
||||
zed_file_unlock(int fd)
|
||||
{
|
||||
struct flock lock;
|
||||
|
||||
if (fd < 0) {
|
||||
errno = EBADF;
|
||||
return (-1);
|
||||
}
|
||||
lock.l_type = F_UNLCK;
|
||||
lock.l_whence = SEEK_SET;
|
||||
lock.l_start = 0;
|
||||
lock.l_len = 0;
|
||||
|
||||
if (fcntl(fd, F_SETLK, &lock) < 0)
|
||||
return (-1);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test whether an exclusive advisory lock could be obtained for the open
|
||||
* file descriptor [fd].
|
||||
* Return 0 if the file is not locked, >0 for the PID of another process
|
||||
* holding a conflicting lock, or -1 on error (with errno set).
|
||||
*/
|
||||
pid_t
|
||||
zed_file_is_locked(int fd)
|
||||
{
|
||||
struct flock lock;
|
||||
|
||||
if (fd < 0) {
|
||||
errno = EBADF;
|
||||
return (-1);
|
||||
}
|
||||
lock.l_type = F_WRLCK;
|
||||
lock.l_whence = SEEK_SET;
|
||||
lock.l_start = 0;
|
||||
lock.l_len = 0;
|
||||
|
||||
if (fcntl(fd, F_GETLK, &lock) < 0)
|
||||
return (-1);
|
||||
|
||||
if (lock.l_type == F_UNLCK)
|
||||
return (0);
|
||||
|
||||
return (lock.l_pid);
|
||||
}
|
||||
|
||||
/*
|
||||
* Close all open file descriptors greater than or equal to [lowfd].
|
||||
* Any errors encountered while closing file descriptors are ignored.
|
||||
*/
|
||||
void
|
||||
zed_file_close_from(int lowfd)
|
||||
{
|
||||
const int maxfd_def = 256;
|
||||
int errno_bak;
|
||||
struct rlimit rl;
|
||||
int maxfd;
|
||||
int fd;
|
||||
|
||||
errno_bak = errno;
|
||||
|
||||
if (getrlimit(RLIMIT_NOFILE, &rl) < 0) {
|
||||
maxfd = maxfd_def;
|
||||
} else if (rl.rlim_max == RLIM_INFINITY) {
|
||||
maxfd = maxfd_def;
|
||||
} else {
|
||||
maxfd = rl.rlim_max;
|
||||
}
|
||||
for (fd = lowfd; fd < maxfd; fd++)
|
||||
(void) close(fd);
|
||||
|
||||
errno = errno_bak;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set the CLOEXEC flag on file descriptor [fd] so it will be automatically
|
||||
* closed upon successful execution of one of the exec functions.
|
||||
* Return 0 on success, or -1 on error.
|
||||
*
|
||||
* FIXME: No longer needed?
|
||||
*/
|
||||
int
|
||||
zed_file_close_on_exec(int fd)
|
||||
{
|
||||
int flags;
|
||||
|
||||
if (fd < 0) {
|
||||
errno = EBADF;
|
||||
return (-1);
|
||||
}
|
||||
flags = fcntl(fd, F_GETFD);
|
||||
if (flags == -1)
|
||||
return (-1);
|
||||
|
||||
flags |= FD_CLOEXEC;
|
||||
|
||||
if (fcntl(fd, F_SETFD, flags) == -1)
|
||||
return (-1);
|
||||
|
||||
return (0);
|
||||
}
|
35
sys/contrib/openzfs/cmd/zed/zed_file.h
Normal file
35
sys/contrib/openzfs/cmd/zed/zed_file.h
Normal file
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* This file is part of the ZFS Event Daemon (ZED)
|
||||
* for ZFS on Linux (ZoL) <http://zfsonlinux.org/>.
|
||||
* Developed at Lawrence Livermore National Laboratory (LLNL-CODE-403049).
|
||||
* Copyright (C) 2013-2014 Lawrence Livermore National Security, LLC.
|
||||
* Refer to the ZoL git commit log for authoritative copyright attribution.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License Version 1.0 (CDDL-1.0).
|
||||
* You can obtain a copy of the license from the top-level file
|
||||
* "OPENSOLARIS.LICENSE" or at <http://opensource.org/licenses/CDDL-1.0>.
|
||||
* You may not use this file except in compliance with the license.
|
||||
*/
|
||||
|
||||
#ifndef ZED_FILE_H
|
||||
#define ZED_FILE_H
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
ssize_t zed_file_read_n(int fd, void *buf, size_t n);
|
||||
|
||||
ssize_t zed_file_write_n(int fd, void *buf, size_t n);
|
||||
|
||||
int zed_file_lock(int fd);
|
||||
|
||||
int zed_file_unlock(int fd);
|
||||
|
||||
pid_t zed_file_is_locked(int fd);
|
||||
|
||||
void zed_file_close_from(int fd);
|
||||
|
||||
int zed_file_close_on_exec(int fd);
|
||||
|
||||
#endif /* !ZED_FILE_H */
|
256
sys/contrib/openzfs/cmd/zed/zed_log.c
Normal file
256
sys/contrib/openzfs/cmd/zed/zed_log.c
Normal file
@ -0,0 +1,256 @@
|
||||
/*
|
||||
* This file is part of the ZFS Event Daemon (ZED)
|
||||
* for ZFS on Linux (ZoL) <http://zfsonlinux.org/>.
|
||||
* Developed at Lawrence Livermore National Laboratory (LLNL-CODE-403049).
|
||||
* Copyright (C) 2013-2014 Lawrence Livermore National Security, LLC.
|
||||
* Refer to the ZoL git commit log for authoritative copyright attribution.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License Version 1.0 (CDDL-1.0).
|
||||
* You can obtain a copy of the license from the top-level file
|
||||
* "OPENSOLARIS.LICENSE" or at <http://opensource.org/licenses/CDDL-1.0>.
|
||||
* You may not use this file except in compliance with the license.
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include <syslog.h>
|
||||
#include <unistd.h>
|
||||
#include "zed_log.h"
|
||||
|
||||
#define ZED_LOG_MAX_LOG_LEN 1024
|
||||
|
||||
static struct {
|
||||
unsigned do_stderr:1;
|
||||
unsigned do_syslog:1;
|
||||
const char *identity;
|
||||
int priority;
|
||||
int pipe_fd[2];
|
||||
} _ctx;
|
||||
|
||||
/*
|
||||
* Initialize the logging subsystem.
|
||||
*/
|
||||
void
|
||||
zed_log_init(const char *identity)
|
||||
{
|
||||
if (identity) {
|
||||
const char *p = strrchr(identity, '/');
|
||||
_ctx.identity = (p != NULL) ? p + 1 : identity;
|
||||
} else {
|
||||
_ctx.identity = NULL;
|
||||
}
|
||||
_ctx.pipe_fd[0] = -1;
|
||||
_ctx.pipe_fd[1] = -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Shutdown the logging subsystem.
|
||||
*/
|
||||
void
|
||||
zed_log_fini(void)
|
||||
{
|
||||
zed_log_stderr_close();
|
||||
zed_log_syslog_close();
|
||||
}
|
||||
|
||||
/*
|
||||
* Create pipe for communicating daemonization status between the parent and
|
||||
* child processes across the double-fork().
|
||||
*/
|
||||
void
|
||||
zed_log_pipe_open(void)
|
||||
{
|
||||
if ((_ctx.pipe_fd[0] != -1) || (_ctx.pipe_fd[1] != -1))
|
||||
zed_log_die("Invalid use of zed_log_pipe_open in PID %d",
|
||||
(int)getpid());
|
||||
|
||||
if (pipe(_ctx.pipe_fd) < 0)
|
||||
zed_log_die("Failed to create daemonize pipe in PID %d: %s",
|
||||
(int)getpid(), strerror(errno));
|
||||
}
|
||||
|
||||
/*
|
||||
* Close the read-half of the daemonize pipe.
|
||||
*
|
||||
* This should be called by the child after fork()ing from the parent since
|
||||
* the child will never read from this pipe.
|
||||
*/
|
||||
void
|
||||
zed_log_pipe_close_reads(void)
|
||||
{
|
||||
if (_ctx.pipe_fd[0] < 0)
|
||||
zed_log_die(
|
||||
"Invalid use of zed_log_pipe_close_reads in PID %d",
|
||||
(int)getpid());
|
||||
|
||||
if (close(_ctx.pipe_fd[0]) < 0)
|
||||
zed_log_die(
|
||||
"Failed to close reads on daemonize pipe in PID %d: %s",
|
||||
(int)getpid(), strerror(errno));
|
||||
|
||||
_ctx.pipe_fd[0] = -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Close the write-half of the daemonize pipe.
|
||||
*
|
||||
* This should be called by the parent after fork()ing its child since the
|
||||
* parent will never write to this pipe.
|
||||
*
|
||||
* This should also be called by the child once initialization is complete
|
||||
* in order to signal the parent that it can safely exit.
|
||||
*/
|
||||
void
|
||||
zed_log_pipe_close_writes(void)
|
||||
{
|
||||
if (_ctx.pipe_fd[1] < 0)
|
||||
zed_log_die(
|
||||
"Invalid use of zed_log_pipe_close_writes in PID %d",
|
||||
(int)getpid());
|
||||
|
||||
if (close(_ctx.pipe_fd[1]) < 0)
|
||||
zed_log_die(
|
||||
"Failed to close writes on daemonize pipe in PID %d: %s",
|
||||
(int)getpid(), strerror(errno));
|
||||
|
||||
_ctx.pipe_fd[1] = -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Block on reading from the daemonize pipe until signaled by the child
|
||||
* (via zed_log_pipe_close_writes()) that initialization is complete.
|
||||
*
|
||||
* This should only be called by the parent while waiting to exit after
|
||||
* fork()ing the child.
|
||||
*/
|
||||
void
|
||||
zed_log_pipe_wait(void)
|
||||
{
|
||||
ssize_t n;
|
||||
char c;
|
||||
|
||||
if (_ctx.pipe_fd[0] < 0)
|
||||
zed_log_die("Invalid use of zed_log_pipe_wait in PID %d",
|
||||
(int)getpid());
|
||||
|
||||
for (;;) {
|
||||
n = read(_ctx.pipe_fd[0], &c, sizeof (c));
|
||||
if (n < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
zed_log_die(
|
||||
"Failed to read from daemonize pipe in PID %d: %s",
|
||||
(int)getpid(), strerror(errno));
|
||||
}
|
||||
if (n == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Start logging messages at the syslog [priority] level or higher to stderr.
|
||||
* Refer to syslog(3) for valid priority values.
|
||||
*/
|
||||
void
|
||||
zed_log_stderr_open(int priority)
|
||||
{
|
||||
_ctx.do_stderr = 1;
|
||||
_ctx.priority = priority;
|
||||
}
|
||||
|
||||
/*
|
||||
* Stop logging messages to stderr.
|
||||
*/
|
||||
void
|
||||
zed_log_stderr_close(void)
|
||||
{
|
||||
if (_ctx.do_stderr)
|
||||
_ctx.do_stderr = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Start logging messages to syslog.
|
||||
* Refer to syslog(3) for valid option/facility values.
|
||||
*/
|
||||
void
|
||||
zed_log_syslog_open(int facility)
|
||||
{
|
||||
_ctx.do_syslog = 1;
|
||||
openlog(_ctx.identity, LOG_NDELAY | LOG_PID, facility);
|
||||
}
|
||||
|
||||
/*
|
||||
* Stop logging messages to syslog.
|
||||
*/
|
||||
void
|
||||
zed_log_syslog_close(void)
|
||||
{
|
||||
if (_ctx.do_syslog) {
|
||||
_ctx.do_syslog = 0;
|
||||
closelog();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Auxiliary function to log a message to syslog and/or stderr.
|
||||
*/
|
||||
static void
|
||||
_zed_log_aux(int priority, const char *fmt, va_list vargs)
|
||||
{
|
||||
char buf[ZED_LOG_MAX_LOG_LEN];
|
||||
int n;
|
||||
|
||||
if (!fmt)
|
||||
return;
|
||||
|
||||
n = vsnprintf(buf, sizeof (buf), fmt, vargs);
|
||||
if ((n < 0) || (n >= sizeof (buf))) {
|
||||
buf[sizeof (buf) - 2] = '+';
|
||||
buf[sizeof (buf) - 1] = '\0';
|
||||
}
|
||||
|
||||
if (_ctx.do_syslog)
|
||||
syslog(priority, "%s", buf);
|
||||
|
||||
if (_ctx.do_stderr && (priority <= _ctx.priority))
|
||||
fprintf(stderr, "%s\n", buf);
|
||||
}
|
||||
|
||||
/*
|
||||
* Log a message at the given [priority] level specified by the printf-style
|
||||
* format string [fmt].
|
||||
*/
|
||||
void
|
||||
zed_log_msg(int priority, const char *fmt, ...)
|
||||
{
|
||||
va_list vargs;
|
||||
|
||||
if (fmt) {
|
||||
va_start(vargs, fmt);
|
||||
_zed_log_aux(priority, fmt, vargs);
|
||||
va_end(vargs);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Log a fatal error message specified by the printf-style format string [fmt].
|
||||
*/
|
||||
void
|
||||
zed_log_die(const char *fmt, ...)
|
||||
{
|
||||
va_list vargs;
|
||||
|
||||
if (fmt) {
|
||||
va_start(vargs, fmt);
|
||||
_zed_log_aux(LOG_ERR, fmt, vargs);
|
||||
va_end(vargs);
|
||||
}
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
44
sys/contrib/openzfs/cmd/zed/zed_log.h
Normal file
44
sys/contrib/openzfs/cmd/zed/zed_log.h
Normal file
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* This file is part of the ZFS Event Daemon (ZED)
|
||||
* for ZFS on Linux (ZoL) <http://zfsonlinux.org/>.
|
||||
* Developed at Lawrence Livermore National Laboratory (LLNL-CODE-403049).
|
||||
* Copyright (C) 2013-2014 Lawrence Livermore National Security, LLC.
|
||||
* Refer to the ZoL git commit log for authoritative copyright attribution.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License Version 1.0 (CDDL-1.0).
|
||||
* You can obtain a copy of the license from the top-level file
|
||||
* "OPENSOLARIS.LICENSE" or at <http://opensource.org/licenses/CDDL-1.0>.
|
||||
* You may not use this file except in compliance with the license.
|
||||
*/
|
||||
|
||||
#ifndef ZED_LOG_H
|
||||
#define ZED_LOG_H
|
||||
|
||||
#include <syslog.h>
|
||||
|
||||
void zed_log_init(const char *identity);
|
||||
|
||||
void zed_log_fini(void);
|
||||
|
||||
void zed_log_pipe_open(void);
|
||||
|
||||
void zed_log_pipe_close_reads(void);
|
||||
|
||||
void zed_log_pipe_close_writes(void);
|
||||
|
||||
void zed_log_pipe_wait(void);
|
||||
|
||||
void zed_log_stderr_open(int priority);
|
||||
|
||||
void zed_log_stderr_close(void);
|
||||
|
||||
void zed_log_syslog_open(int facility);
|
||||
|
||||
void zed_log_syslog_close(void);
|
||||
|
||||
void zed_log_msg(int priority, const char *fmt, ...);
|
||||
|
||||
void zed_log_die(const char *fmt, ...);
|
||||
|
||||
#endif /* !ZED_LOG_H */
|
247
sys/contrib/openzfs/cmd/zed/zed_strings.c
Normal file
247
sys/contrib/openzfs/cmd/zed/zed_strings.c
Normal file
@ -0,0 +1,247 @@
|
||||
/*
|
||||
* This file is part of the ZFS Event Daemon (ZED)
|
||||
* for ZFS on Linux (ZoL) <http://zfsonlinux.org/>.
|
||||
* Developed at Lawrence Livermore National Laboratory (LLNL-CODE-403049).
|
||||
* Copyright (C) 2013-2014 Lawrence Livermore National Security, LLC.
|
||||
* Refer to the ZoL git commit log for authoritative copyright attribution.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License Version 1.0 (CDDL-1.0).
|
||||
* You can obtain a copy of the license from the top-level file
|
||||
* "OPENSOLARIS.LICENSE" or at <http://opensource.org/licenses/CDDL-1.0>.
|
||||
* You may not use this file except in compliance with the license.
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/avl.h>
|
||||
#include <sys/sysmacros.h>
|
||||
#include "zed_strings.h"
|
||||
|
||||
struct zed_strings {
|
||||
avl_tree_t tree;
|
||||
avl_node_t *iteratorp;
|
||||
};
|
||||
|
||||
struct zed_strings_node {
|
||||
avl_node_t node;
|
||||
char *key;
|
||||
char *val;
|
||||
};
|
||||
|
||||
typedef struct zed_strings_node zed_strings_node_t;
|
||||
|
||||
/*
|
||||
* Compare zed_strings_node_t nodes [x1] and [x2].
|
||||
* As required for the AVL tree, return -1 for <, 0 for ==, and +1 for >.
|
||||
*/
|
||||
static int
|
||||
_zed_strings_node_compare(const void *x1, const void *x2)
|
||||
{
|
||||
const char *s1;
|
||||
const char *s2;
|
||||
int rv;
|
||||
|
||||
assert(x1 != NULL);
|
||||
assert(x2 != NULL);
|
||||
|
||||
s1 = ((const zed_strings_node_t *) x1)->key;
|
||||
assert(s1 != NULL);
|
||||
s2 = ((const zed_strings_node_t *) x2)->key;
|
||||
assert(s2 != NULL);
|
||||
rv = strcmp(s1, s2);
|
||||
|
||||
if (rv < 0)
|
||||
return (-1);
|
||||
|
||||
if (rv > 0)
|
||||
return (1);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Return a new string container, or NULL on error.
|
||||
*/
|
||||
zed_strings_t *
|
||||
zed_strings_create(void)
|
||||
{
|
||||
zed_strings_t *zsp;
|
||||
|
||||
zsp = calloc(1, sizeof (*zsp));
|
||||
if (!zsp)
|
||||
return (NULL);
|
||||
|
||||
avl_create(&zsp->tree, _zed_strings_node_compare,
|
||||
sizeof (zed_strings_node_t), offsetof(zed_strings_node_t, node));
|
||||
|
||||
zsp->iteratorp = NULL;
|
||||
return (zsp);
|
||||
}
|
||||
|
||||
/*
|
||||
* Destroy the string node [np].
|
||||
*/
|
||||
static void
|
||||
_zed_strings_node_destroy(zed_strings_node_t *np)
|
||||
{
|
||||
if (!np)
|
||||
return;
|
||||
|
||||
if (np->key) {
|
||||
if (np->key != np->val)
|
||||
free(np->key);
|
||||
np->key = NULL;
|
||||
}
|
||||
if (np->val) {
|
||||
free(np->val);
|
||||
np->val = NULL;
|
||||
}
|
||||
free(np);
|
||||
}
|
||||
|
||||
/*
|
||||
* Return a new string node for storing the string [val], or NULL on error.
|
||||
* If [key] is specified, it will be used to index the node; otherwise,
|
||||
* the string [val] will be used.
|
||||
*/
|
||||
static zed_strings_node_t *
|
||||
_zed_strings_node_create(const char *key, const char *val)
|
||||
{
|
||||
zed_strings_node_t *np;
|
||||
|
||||
assert(val != NULL);
|
||||
|
||||
np = calloc(1, sizeof (*np));
|
||||
if (!np)
|
||||
return (NULL);
|
||||
|
||||
np->val = strdup(val);
|
||||
if (!np->val)
|
||||
goto nomem;
|
||||
|
||||
if (key) {
|
||||
np->key = strdup(key);
|
||||
if (!np->key)
|
||||
goto nomem;
|
||||
} else {
|
||||
np->key = np->val;
|
||||
}
|
||||
return (np);
|
||||
|
||||
nomem:
|
||||
_zed_strings_node_destroy(np);
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* Destroy the string container [zsp] and all nodes within.
|
||||
*/
|
||||
void
|
||||
zed_strings_destroy(zed_strings_t *zsp)
|
||||
{
|
||||
void *cookie;
|
||||
zed_strings_node_t *np;
|
||||
|
||||
if (!zsp)
|
||||
return;
|
||||
|
||||
cookie = NULL;
|
||||
while ((np = avl_destroy_nodes(&zsp->tree, &cookie)))
|
||||
_zed_strings_node_destroy(np);
|
||||
|
||||
avl_destroy(&zsp->tree);
|
||||
free(zsp);
|
||||
}
|
||||
|
||||
/*
|
||||
* Add a copy of the string [s] indexed by [key] to the container [zsp].
|
||||
* If [key] already exists within the container [zsp], it will be replaced
|
||||
* with the new string [s].
|
||||
* If [key] is NULL, the string [s] will be used as the key.
|
||||
* Return 0 on success, or -1 on error.
|
||||
*/
|
||||
int
|
||||
zed_strings_add(zed_strings_t *zsp, const char *key, const char *s)
|
||||
{
|
||||
zed_strings_node_t *newp, *oldp;
|
||||
|
||||
if (!zsp || !s) {
|
||||
errno = EINVAL;
|
||||
return (-1);
|
||||
}
|
||||
if (key == s)
|
||||
key = NULL;
|
||||
|
||||
newp = _zed_strings_node_create(key, s);
|
||||
if (!newp)
|
||||
return (-1);
|
||||
|
||||
oldp = avl_find(&zsp->tree, newp, NULL);
|
||||
if (oldp) {
|
||||
avl_remove(&zsp->tree, oldp);
|
||||
_zed_strings_node_destroy(oldp);
|
||||
}
|
||||
avl_add(&zsp->tree, newp);
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the first string in container [zsp].
|
||||
* Return NULL if there are no strings, or on error.
|
||||
* This can be called multiple times to re-traverse [zsp].
|
||||
* XXX: Not thread-safe.
|
||||
*/
|
||||
const char *
|
||||
zed_strings_first(zed_strings_t *zsp)
|
||||
{
|
||||
if (!zsp) {
|
||||
errno = EINVAL;
|
||||
return (NULL);
|
||||
}
|
||||
zsp->iteratorp = avl_first(&zsp->tree);
|
||||
if (!zsp->iteratorp)
|
||||
return (NULL);
|
||||
|
||||
return (((zed_strings_node_t *)zsp->iteratorp)->val);
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the next string in container [zsp].
|
||||
* Return NULL after the last string, or on error.
|
||||
* This must be called after zed_strings_first().
|
||||
* XXX: Not thread-safe.
|
||||
*/
|
||||
const char *
|
||||
zed_strings_next(zed_strings_t *zsp)
|
||||
{
|
||||
if (!zsp) {
|
||||
errno = EINVAL;
|
||||
return (NULL);
|
||||
}
|
||||
if (!zsp->iteratorp)
|
||||
return (NULL);
|
||||
|
||||
zsp->iteratorp = AVL_NEXT(&zsp->tree, zsp->iteratorp);
|
||||
if (!zsp->iteratorp)
|
||||
return (NULL);
|
||||
|
||||
return (((zed_strings_node_t *)zsp->iteratorp)->val);
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the number of strings in container [zsp], or -1 on error.
|
||||
*/
|
||||
int
|
||||
zed_strings_count(zed_strings_t *zsp)
|
||||
{
|
||||
if (!zsp) {
|
||||
errno = EINVAL;
|
||||
return (-1);
|
||||
}
|
||||
return (avl_numnodes(&zsp->tree));
|
||||
}
|
27
sys/contrib/openzfs/cmd/zed/zed_strings.h
Normal file
27
sys/contrib/openzfs/cmd/zed/zed_strings.h
Normal file
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* This file is part of the ZFS Event Daemon (ZED)
|
||||
* for ZFS on Linux (ZoL) <http://zfsonlinux.org/>.
|
||||
* Developed at Lawrence Livermore National Laboratory (LLNL-CODE-403049).
|
||||
* Copyright (C) 2013-2014 Lawrence Livermore National Security, LLC.
|
||||
* Refer to the ZoL git commit log for authoritative copyright attribution.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License Version 1.0 (CDDL-1.0).
|
||||
* You can obtain a copy of the license from the top-level file
|
||||
* "OPENSOLARIS.LICENSE" or at <http://opensource.org/licenses/CDDL-1.0>.
|
||||
* You may not use this file except in compliance with the license.
|
||||
*/
|
||||
|
||||
#ifndef ZED_STRINGS_H
|
||||
#define ZED_STRINGS_H
|
||||
|
||||
typedef struct zed_strings zed_strings_t;
|
||||
|
||||
zed_strings_t *zed_strings_create(void);
|
||||
void zed_strings_destroy(zed_strings_t *zsp);
|
||||
int zed_strings_add(zed_strings_t *zsp, const char *key, const char *s);
|
||||
const char *zed_strings_first(zed_strings_t *zsp);
|
||||
const char *zed_strings_next(zed_strings_t *zsp);
|
||||
int zed_strings_count(zed_strings_t *zsp);
|
||||
|
||||
#endif /* !ZED_STRINGS_H */
|
1
sys/contrib/openzfs/cmd/zfs/.gitignore
vendored
Normal file
1
sys/contrib/openzfs/cmd/zfs/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/zfs
|
23
sys/contrib/openzfs/cmd/zfs/Makefile.am
Normal file
23
sys/contrib/openzfs/cmd/zfs/Makefile.am
Normal file
@ -0,0 +1,23 @@
|
||||
include $(top_srcdir)/config/Rules.am
|
||||
|
||||
sbin_PROGRAMS = zfs
|
||||
|
||||
zfs_SOURCES = \
|
||||
zfs_iter.c \
|
||||
zfs_iter.h \
|
||||
zfs_main.c \
|
||||
zfs_util.h \
|
||||
zfs_project.c \
|
||||
zfs_projectutil.h
|
||||
|
||||
zfs_LDADD = \
|
||||
$(abs_top_builddir)/lib/libzfs/libzfs.la \
|
||||
$(abs_top_builddir)/lib/libzfs_core/libzfs_core.la \
|
||||
$(abs_top_builddir)/lib/libnvpair/libnvpair.la \
|
||||
$(abs_top_builddir)/lib/libuutil/libuutil.la
|
||||
|
||||
zfs_LDADD += $(LTLIBINTL)
|
||||
|
||||
if BUILD_FREEBSD
|
||||
zfs_LDADD += -lgeom -ljail
|
||||
endif
|
512
sys/contrib/openzfs/cmd/zfs/zfs_iter.c
Normal file
512
sys/contrib/openzfs/cmd/zfs/zfs_iter.c
Normal file
@ -0,0 +1,512 @@
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License (the "License").
|
||||
* You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||
* or http://www.opensolaris.org/os/licensing.
|
||||
* See the License for the specific language governing permissions
|
||||
* and limitations under the License.
|
||||
*
|
||||
* When distributing Covered Code, include this CDDL HEADER in each
|
||||
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||
* If applicable, add the following below this CDDL HEADER, with the
|
||||
* fields enclosed by brackets "[]" replaced with your own identifying
|
||||
* information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
*
|
||||
* CDDL HEADER END
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2012 Pawel Jakub Dawidek <pawel@dawidek.net>.
|
||||
* Copyright 2013 Nexenta Systems, Inc. All rights reserved.
|
||||
* Copyright (c) 2013 by Delphix. All rights reserved.
|
||||
*/
|
||||
|
||||
#include <libintl.h>
|
||||
#include <libuutil.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
|
||||
#include <libzfs.h>
|
||||
|
||||
#include "zfs_util.h"
|
||||
#include "zfs_iter.h"
|
||||
|
||||
/*
|
||||
* This is a private interface used to gather up all the datasets specified on
|
||||
* the command line so that we can iterate over them in order.
|
||||
*
|
||||
* First, we iterate over all filesystems, gathering them together into an
|
||||
* AVL tree. We report errors for any explicitly specified datasets
|
||||
* that we couldn't open.
|
||||
*
|
||||
* When finished, we have an AVL tree of ZFS handles. We go through and execute
|
||||
* the provided callback for each one, passing whatever data the user supplied.
|
||||
*/
|
||||
|
||||
typedef struct zfs_node {
|
||||
zfs_handle_t *zn_handle;
|
||||
uu_avl_node_t zn_avlnode;
|
||||
} zfs_node_t;
|
||||
|
||||
typedef struct callback_data {
|
||||
uu_avl_t *cb_avl;
|
||||
int cb_flags;
|
||||
zfs_type_t cb_types;
|
||||
zfs_sort_column_t *cb_sortcol;
|
||||
zprop_list_t **cb_proplist;
|
||||
int cb_depth_limit;
|
||||
int cb_depth;
|
||||
uint8_t cb_props_table[ZFS_NUM_PROPS];
|
||||
} callback_data_t;
|
||||
|
||||
uu_avl_pool_t *avl_pool;
|
||||
|
||||
/*
|
||||
* Include snaps if they were requested or if this a zfs list where types
|
||||
* were not specified and the "listsnapshots" property is set on this pool.
|
||||
*/
|
||||
static boolean_t
|
||||
zfs_include_snapshots(zfs_handle_t *zhp, callback_data_t *cb)
|
||||
{
|
||||
zpool_handle_t *zph;
|
||||
|
||||
if ((cb->cb_flags & ZFS_ITER_PROP_LISTSNAPS) == 0)
|
||||
return (cb->cb_types & ZFS_TYPE_SNAPSHOT);
|
||||
|
||||
zph = zfs_get_pool_handle(zhp);
|
||||
return (zpool_get_prop_int(zph, ZPOOL_PROP_LISTSNAPS, NULL));
|
||||
}
|
||||
|
||||
/*
|
||||
* Called for each dataset. If the object is of an appropriate type,
|
||||
* add it to the avl tree and recurse over any children as necessary.
|
||||
*/
|
||||
static int
|
||||
zfs_callback(zfs_handle_t *zhp, void *data)
|
||||
{
|
||||
callback_data_t *cb = data;
|
||||
boolean_t should_close = B_TRUE;
|
||||
boolean_t include_snaps = zfs_include_snapshots(zhp, cb);
|
||||
boolean_t include_bmarks = (cb->cb_types & ZFS_TYPE_BOOKMARK);
|
||||
|
||||
if ((zfs_get_type(zhp) & cb->cb_types) ||
|
||||
((zfs_get_type(zhp) == ZFS_TYPE_SNAPSHOT) && include_snaps)) {
|
||||
uu_avl_index_t idx;
|
||||
zfs_node_t *node = safe_malloc(sizeof (zfs_node_t));
|
||||
|
||||
node->zn_handle = zhp;
|
||||
uu_avl_node_init(node, &node->zn_avlnode, avl_pool);
|
||||
if (uu_avl_find(cb->cb_avl, node, cb->cb_sortcol,
|
||||
&idx) == NULL) {
|
||||
if (cb->cb_proplist) {
|
||||
if ((*cb->cb_proplist) &&
|
||||
!(*cb->cb_proplist)->pl_all)
|
||||
zfs_prune_proplist(zhp,
|
||||
cb->cb_props_table);
|
||||
|
||||
if (zfs_expand_proplist(zhp, cb->cb_proplist,
|
||||
(cb->cb_flags & ZFS_ITER_RECVD_PROPS),
|
||||
(cb->cb_flags & ZFS_ITER_LITERAL_PROPS))
|
||||
!= 0) {
|
||||
free(node);
|
||||
return (-1);
|
||||
}
|
||||
}
|
||||
uu_avl_insert(cb->cb_avl, node, idx);
|
||||
should_close = B_FALSE;
|
||||
} else {
|
||||
free(node);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Recurse if necessary.
|
||||
*/
|
||||
if (cb->cb_flags & ZFS_ITER_RECURSE &&
|
||||
((cb->cb_flags & ZFS_ITER_DEPTH_LIMIT) == 0 ||
|
||||
cb->cb_depth < cb->cb_depth_limit)) {
|
||||
cb->cb_depth++;
|
||||
|
||||
/*
|
||||
* If we are not looking for filesystems, we don't need to
|
||||
* recurse into filesystems when we are at our depth limit.
|
||||
*/
|
||||
if ((cb->cb_depth < cb->cb_depth_limit ||
|
||||
(cb->cb_flags & ZFS_ITER_DEPTH_LIMIT) == 0 ||
|
||||
(cb->cb_types &
|
||||
(ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME))) &&
|
||||
zfs_get_type(zhp) == ZFS_TYPE_FILESYSTEM) {
|
||||
(void) zfs_iter_filesystems(zhp, zfs_callback, data);
|
||||
}
|
||||
|
||||
if (((zfs_get_type(zhp) & (ZFS_TYPE_SNAPSHOT |
|
||||
ZFS_TYPE_BOOKMARK)) == 0) && include_snaps) {
|
||||
(void) zfs_iter_snapshots(zhp,
|
||||
(cb->cb_flags & ZFS_ITER_SIMPLE) != 0,
|
||||
zfs_callback, data, 0, 0);
|
||||
}
|
||||
|
||||
if (((zfs_get_type(zhp) & (ZFS_TYPE_SNAPSHOT |
|
||||
ZFS_TYPE_BOOKMARK)) == 0) && include_bmarks) {
|
||||
(void) zfs_iter_bookmarks(zhp, zfs_callback, data);
|
||||
}
|
||||
|
||||
cb->cb_depth--;
|
||||
}
|
||||
|
||||
if (should_close)
|
||||
zfs_close(zhp);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
int
|
||||
zfs_add_sort_column(zfs_sort_column_t **sc, const char *name,
|
||||
boolean_t reverse)
|
||||
{
|
||||
zfs_sort_column_t *col;
|
||||
zfs_prop_t prop;
|
||||
|
||||
if ((prop = zfs_name_to_prop(name)) == ZPROP_INVAL &&
|
||||
!zfs_prop_user(name))
|
||||
return (-1);
|
||||
|
||||
col = safe_malloc(sizeof (zfs_sort_column_t));
|
||||
|
||||
col->sc_prop = prop;
|
||||
col->sc_reverse = reverse;
|
||||
if (prop == ZPROP_INVAL) {
|
||||
col->sc_user_prop = safe_malloc(strlen(name) + 1);
|
||||
(void) strcpy(col->sc_user_prop, name);
|
||||
}
|
||||
|
||||
if (*sc == NULL) {
|
||||
col->sc_last = col;
|
||||
*sc = col;
|
||||
} else {
|
||||
(*sc)->sc_last->sc_next = col;
|
||||
(*sc)->sc_last = col;
|
||||
}
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
void
|
||||
zfs_free_sort_columns(zfs_sort_column_t *sc)
|
||||
{
|
||||
zfs_sort_column_t *col;
|
||||
|
||||
while (sc != NULL) {
|
||||
col = sc->sc_next;
|
||||
free(sc->sc_user_prop);
|
||||
free(sc);
|
||||
sc = col;
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
zfs_sort_only_by_name(const zfs_sort_column_t *sc)
|
||||
{
|
||||
return (sc != NULL && sc->sc_next == NULL &&
|
||||
sc->sc_prop == ZFS_PROP_NAME);
|
||||
}
|
||||
|
||||
/* ARGSUSED */
|
||||
static int
|
||||
zfs_compare(const void *larg, const void *rarg, void *unused)
|
||||
{
|
||||
zfs_handle_t *l = ((zfs_node_t *)larg)->zn_handle;
|
||||
zfs_handle_t *r = ((zfs_node_t *)rarg)->zn_handle;
|
||||
const char *lname = zfs_get_name(l);
|
||||
const char *rname = zfs_get_name(r);
|
||||
char *lat, *rat;
|
||||
uint64_t lcreate, rcreate;
|
||||
int ret;
|
||||
|
||||
lat = (char *)strchr(lname, '@');
|
||||
rat = (char *)strchr(rname, '@');
|
||||
|
||||
if (lat != NULL)
|
||||
*lat = '\0';
|
||||
if (rat != NULL)
|
||||
*rat = '\0';
|
||||
|
||||
ret = strcmp(lname, rname);
|
||||
if (ret == 0 && (lat != NULL || rat != NULL)) {
|
||||
/*
|
||||
* If we're comparing a dataset to one of its snapshots, we
|
||||
* always make the full dataset first.
|
||||
*/
|
||||
if (lat == NULL) {
|
||||
ret = -1;
|
||||
} else if (rat == NULL) {
|
||||
ret = 1;
|
||||
} else {
|
||||
/*
|
||||
* If we have two snapshots from the same dataset, then
|
||||
* we want to sort them according to creation time. We
|
||||
* use the hidden CREATETXG property to get an absolute
|
||||
* ordering of snapshots.
|
||||
*/
|
||||
lcreate = zfs_prop_get_int(l, ZFS_PROP_CREATETXG);
|
||||
rcreate = zfs_prop_get_int(r, ZFS_PROP_CREATETXG);
|
||||
|
||||
/*
|
||||
* Both lcreate and rcreate being 0 means we don't have
|
||||
* properties and we should compare full name.
|
||||
*/
|
||||
if (lcreate == 0 && rcreate == 0)
|
||||
ret = strcmp(lat + 1, rat + 1);
|
||||
else if (lcreate < rcreate)
|
||||
ret = -1;
|
||||
else if (lcreate > rcreate)
|
||||
ret = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (lat != NULL)
|
||||
*lat = '@';
|
||||
if (rat != NULL)
|
||||
*rat = '@';
|
||||
|
||||
return (ret);
|
||||
}
|
||||
|
||||
/*
|
||||
* Sort datasets by specified columns.
|
||||
*
|
||||
* o Numeric types sort in ascending order.
|
||||
* o String types sort in alphabetical order.
|
||||
* o Types inappropriate for a row sort that row to the literal
|
||||
* bottom, regardless of the specified ordering.
|
||||
*
|
||||
* If no sort columns are specified, or two datasets compare equally
|
||||
* across all specified columns, they are sorted alphabetically by name
|
||||
* with snapshots grouped under their parents.
|
||||
*/
|
||||
static int
|
||||
zfs_sort(const void *larg, const void *rarg, void *data)
|
||||
{
|
||||
zfs_handle_t *l = ((zfs_node_t *)larg)->zn_handle;
|
||||
zfs_handle_t *r = ((zfs_node_t *)rarg)->zn_handle;
|
||||
zfs_sort_column_t *sc = (zfs_sort_column_t *)data;
|
||||
zfs_sort_column_t *psc;
|
||||
|
||||
for (psc = sc; psc != NULL; psc = psc->sc_next) {
|
||||
char lbuf[ZFS_MAXPROPLEN], rbuf[ZFS_MAXPROPLEN];
|
||||
char *lstr, *rstr;
|
||||
uint64_t lnum, rnum;
|
||||
boolean_t lvalid, rvalid;
|
||||
int ret = 0;
|
||||
|
||||
/*
|
||||
* We group the checks below the generic code. If 'lstr' and
|
||||
* 'rstr' are non-NULL, then we do a string based comparison.
|
||||
* Otherwise, we compare 'lnum' and 'rnum'.
|
||||
*/
|
||||
lstr = rstr = NULL;
|
||||
if (psc->sc_prop == ZPROP_INVAL) {
|
||||
nvlist_t *luser, *ruser;
|
||||
nvlist_t *lval, *rval;
|
||||
|
||||
luser = zfs_get_user_props(l);
|
||||
ruser = zfs_get_user_props(r);
|
||||
|
||||
lvalid = (nvlist_lookup_nvlist(luser,
|
||||
psc->sc_user_prop, &lval) == 0);
|
||||
rvalid = (nvlist_lookup_nvlist(ruser,
|
||||
psc->sc_user_prop, &rval) == 0);
|
||||
|
||||
if (lvalid)
|
||||
verify(nvlist_lookup_string(lval,
|
||||
ZPROP_VALUE, &lstr) == 0);
|
||||
if (rvalid)
|
||||
verify(nvlist_lookup_string(rval,
|
||||
ZPROP_VALUE, &rstr) == 0);
|
||||
} else if (psc->sc_prop == ZFS_PROP_NAME) {
|
||||
lvalid = rvalid = B_TRUE;
|
||||
|
||||
(void) strlcpy(lbuf, zfs_get_name(l), sizeof (lbuf));
|
||||
(void) strlcpy(rbuf, zfs_get_name(r), sizeof (rbuf));
|
||||
|
||||
lstr = lbuf;
|
||||
rstr = rbuf;
|
||||
} else if (zfs_prop_is_string(psc->sc_prop)) {
|
||||
lvalid = (zfs_prop_get(l, psc->sc_prop, lbuf,
|
||||
sizeof (lbuf), NULL, NULL, 0, B_TRUE) == 0);
|
||||
rvalid = (zfs_prop_get(r, psc->sc_prop, rbuf,
|
||||
sizeof (rbuf), NULL, NULL, 0, B_TRUE) == 0);
|
||||
|
||||
lstr = lbuf;
|
||||
rstr = rbuf;
|
||||
} else {
|
||||
lvalid = zfs_prop_valid_for_type(psc->sc_prop,
|
||||
zfs_get_type(l), B_FALSE);
|
||||
rvalid = zfs_prop_valid_for_type(psc->sc_prop,
|
||||
zfs_get_type(r), B_FALSE);
|
||||
|
||||
if (lvalid)
|
||||
(void) zfs_prop_get_numeric(l, psc->sc_prop,
|
||||
&lnum, NULL, NULL, 0);
|
||||
if (rvalid)
|
||||
(void) zfs_prop_get_numeric(r, psc->sc_prop,
|
||||
&rnum, NULL, NULL, 0);
|
||||
}
|
||||
|
||||
if (!lvalid && !rvalid)
|
||||
continue;
|
||||
else if (!lvalid)
|
||||
return (1);
|
||||
else if (!rvalid)
|
||||
return (-1);
|
||||
|
||||
if (lstr)
|
||||
ret = strcmp(lstr, rstr);
|
||||
else if (lnum < rnum)
|
||||
ret = -1;
|
||||
else if (lnum > rnum)
|
||||
ret = 1;
|
||||
|
||||
if (ret != 0) {
|
||||
if (psc->sc_reverse == B_TRUE)
|
||||
ret = (ret < 0) ? 1 : -1;
|
||||
return (ret);
|
||||
}
|
||||
}
|
||||
|
||||
return (zfs_compare(larg, rarg, NULL));
|
||||
}
|
||||
|
||||
int
|
||||
zfs_for_each(int argc, char **argv, int flags, zfs_type_t types,
|
||||
zfs_sort_column_t *sortcol, zprop_list_t **proplist, int limit,
|
||||
zfs_iter_f callback, void *data)
|
||||
{
|
||||
callback_data_t cb = {0};
|
||||
int ret = 0;
|
||||
zfs_node_t *node;
|
||||
uu_avl_walk_t *walk;
|
||||
|
||||
avl_pool = uu_avl_pool_create("zfs_pool", sizeof (zfs_node_t),
|
||||
offsetof(zfs_node_t, zn_avlnode), zfs_sort, UU_DEFAULT);
|
||||
|
||||
if (avl_pool == NULL)
|
||||
nomem();
|
||||
|
||||
cb.cb_sortcol = sortcol;
|
||||
cb.cb_flags = flags;
|
||||
cb.cb_proplist = proplist;
|
||||
cb.cb_types = types;
|
||||
cb.cb_depth_limit = limit;
|
||||
/*
|
||||
* If cb_proplist is provided then in the zfs_handles created we
|
||||
* retain only those properties listed in cb_proplist and sortcol.
|
||||
* The rest are pruned. So, the caller should make sure that no other
|
||||
* properties other than those listed in cb_proplist/sortcol are
|
||||
* accessed.
|
||||
*
|
||||
* If cb_proplist is NULL then we retain all the properties. We
|
||||
* always retain the zoned property, which some other properties
|
||||
* need (userquota & friends), and the createtxg property, which
|
||||
* we need to sort snapshots.
|
||||
*/
|
||||
if (cb.cb_proplist && *cb.cb_proplist) {
|
||||
zprop_list_t *p = *cb.cb_proplist;
|
||||
|
||||
while (p) {
|
||||
if (p->pl_prop >= ZFS_PROP_TYPE &&
|
||||
p->pl_prop < ZFS_NUM_PROPS) {
|
||||
cb.cb_props_table[p->pl_prop] = B_TRUE;
|
||||
}
|
||||
p = p->pl_next;
|
||||
}
|
||||
|
||||
while (sortcol) {
|
||||
if (sortcol->sc_prop >= ZFS_PROP_TYPE &&
|
||||
sortcol->sc_prop < ZFS_NUM_PROPS) {
|
||||
cb.cb_props_table[sortcol->sc_prop] = B_TRUE;
|
||||
}
|
||||
sortcol = sortcol->sc_next;
|
||||
}
|
||||
|
||||
cb.cb_props_table[ZFS_PROP_ZONED] = B_TRUE;
|
||||
cb.cb_props_table[ZFS_PROP_CREATETXG] = B_TRUE;
|
||||
} else {
|
||||
(void) memset(cb.cb_props_table, B_TRUE,
|
||||
sizeof (cb.cb_props_table));
|
||||
}
|
||||
|
||||
if ((cb.cb_avl = uu_avl_create(avl_pool, NULL, UU_DEFAULT)) == NULL)
|
||||
nomem();
|
||||
|
||||
if (argc == 0) {
|
||||
/*
|
||||
* If given no arguments, iterate over all datasets.
|
||||
*/
|
||||
cb.cb_flags |= ZFS_ITER_RECURSE;
|
||||
ret = zfs_iter_root(g_zfs, zfs_callback, &cb);
|
||||
} else {
|
||||
int i;
|
||||
zfs_handle_t *zhp;
|
||||
zfs_type_t argtype;
|
||||
|
||||
/*
|
||||
* If we're recursive, then we always allow filesystems as
|
||||
* arguments. If we also are interested in snapshots or
|
||||
* bookmarks, then we can take volumes as well.
|
||||
*/
|
||||
argtype = types;
|
||||
if (flags & ZFS_ITER_RECURSE) {
|
||||
argtype |= ZFS_TYPE_FILESYSTEM;
|
||||
if (types & (ZFS_TYPE_SNAPSHOT | ZFS_TYPE_BOOKMARK))
|
||||
argtype |= ZFS_TYPE_VOLUME;
|
||||
}
|
||||
|
||||
for (i = 0; i < argc; i++) {
|
||||
if (flags & ZFS_ITER_ARGS_CAN_BE_PATHS) {
|
||||
zhp = zfs_path_to_zhandle(g_zfs, argv[i],
|
||||
argtype);
|
||||
} else {
|
||||
zhp = zfs_open(g_zfs, argv[i], argtype);
|
||||
}
|
||||
if (zhp != NULL)
|
||||
ret |= zfs_callback(zhp, &cb);
|
||||
else
|
||||
ret = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* At this point we've got our AVL tree full of zfs handles, so iterate
|
||||
* over each one and execute the real user callback.
|
||||
*/
|
||||
for (node = uu_avl_first(cb.cb_avl); node != NULL;
|
||||
node = uu_avl_next(cb.cb_avl, node))
|
||||
ret |= callback(node->zn_handle, data);
|
||||
|
||||
/*
|
||||
* Finally, clean up the AVL tree.
|
||||
*/
|
||||
if ((walk = uu_avl_walk_start(cb.cb_avl, UU_WALK_ROBUST)) == NULL)
|
||||
nomem();
|
||||
|
||||
while ((node = uu_avl_walk_next(walk)) != NULL) {
|
||||
uu_avl_remove(cb.cb_avl, node);
|
||||
zfs_close(node->zn_handle);
|
||||
free(node);
|
||||
}
|
||||
|
||||
uu_avl_walk_end(walk);
|
||||
uu_avl_destroy(cb.cb_avl);
|
||||
uu_avl_pool_destroy(avl_pool);
|
||||
|
||||
return (ret);
|
||||
}
|
61
sys/contrib/openzfs/cmd/zfs/zfs_iter.h
Normal file
61
sys/contrib/openzfs/cmd/zfs/zfs_iter.h
Normal file
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License (the "License").
|
||||
* You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||
* or http://www.opensolaris.org/os/licensing.
|
||||
* See the License for the specific language governing permissions
|
||||
* and limitations under the License.
|
||||
*
|
||||
* When distributing Covered Code, include this CDDL HEADER in each
|
||||
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||
* If applicable, add the following below this CDDL HEADER, with the
|
||||
* fields enclosed by brackets "[]" replaced with your own identifying
|
||||
* information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
*
|
||||
* CDDL HEADER END
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright 2009 Sun Microsystems, Inc. All rights reserved.
|
||||
* Use is subject to license terms.
|
||||
* Copyright 2013 Nexenta Systems, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
#ifndef ZFS_ITER_H
|
||||
#define ZFS_ITER_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct zfs_sort_column {
|
||||
struct zfs_sort_column *sc_next;
|
||||
struct zfs_sort_column *sc_last;
|
||||
zfs_prop_t sc_prop;
|
||||
char *sc_user_prop;
|
||||
boolean_t sc_reverse;
|
||||
} zfs_sort_column_t;
|
||||
|
||||
#define ZFS_ITER_RECURSE (1 << 0)
|
||||
#define ZFS_ITER_ARGS_CAN_BE_PATHS (1 << 1)
|
||||
#define ZFS_ITER_PROP_LISTSNAPS (1 << 2)
|
||||
#define ZFS_ITER_DEPTH_LIMIT (1 << 3)
|
||||
#define ZFS_ITER_RECVD_PROPS (1 << 4)
|
||||
#define ZFS_ITER_LITERAL_PROPS (1 << 5)
|
||||
#define ZFS_ITER_SIMPLE (1 << 6)
|
||||
|
||||
int zfs_for_each(int, char **, int options, zfs_type_t,
|
||||
zfs_sort_column_t *, zprop_list_t **, int, zfs_iter_f, void *);
|
||||
int zfs_add_sort_column(zfs_sort_column_t **, const char *, boolean_t);
|
||||
void zfs_free_sort_columns(zfs_sort_column_t *);
|
||||
int zfs_sort_only_by_name(const zfs_sort_column_t *);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* ZFS_ITER_H */
|
8620
sys/contrib/openzfs/cmd/zfs/zfs_main.c
Normal file
8620
sys/contrib/openzfs/cmd/zfs/zfs_main.c
Normal file
File diff suppressed because it is too large
Load Diff
295
sys/contrib/openzfs/cmd/zfs/zfs_project.c
Normal file
295
sys/contrib/openzfs/cmd/zfs/zfs_project.c
Normal file
@ -0,0 +1,295 @@
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License (the "License").
|
||||
* You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||
* or http://www.opensolaris.org/os/licensing.
|
||||
* See the License for the specific language governing permissions
|
||||
* and limitations under the License.
|
||||
*
|
||||
* When distributing Covered Code, include this CDDL HEADER in each
|
||||
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||
* If applicable, add the following below this CDDL HEADER, with the
|
||||
* fields enclosed by brackets "[]" replaced with your own identifying
|
||||
* information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
*
|
||||
* CDDL HEADER END
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (c) 2017, Intle Corporation. All rights reserved.
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <getopt.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <strings.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <dirent.h>
|
||||
#include <stddef.h>
|
||||
#include <libintl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/list.h>
|
||||
#include <sys/zfs_project.h>
|
||||
|
||||
#include "zfs_util.h"
|
||||
#include "zfs_projectutil.h"
|
||||
|
||||
typedef struct zfs_project_item {
|
||||
list_node_t zpi_list;
|
||||
char zpi_name[0];
|
||||
} zfs_project_item_t;
|
||||
|
||||
static void
|
||||
zfs_project_item_alloc(list_t *head, const char *name)
|
||||
{
|
||||
zfs_project_item_t *zpi;
|
||||
|
||||
zpi = safe_malloc(sizeof (zfs_project_item_t) + strlen(name) + 1);
|
||||
strcpy(zpi->zpi_name, name);
|
||||
list_insert_tail(head, zpi);
|
||||
}
|
||||
|
||||
static int
|
||||
zfs_project_sanity_check(const char *name, zfs_project_control_t *zpc,
|
||||
struct stat *st)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = stat(name, st);
|
||||
if (ret) {
|
||||
(void) fprintf(stderr, gettext("failed to stat %s: %s\n"),
|
||||
name, strerror(errno));
|
||||
return (ret);
|
||||
}
|
||||
|
||||
if (!S_ISREG(st->st_mode) && !S_ISDIR(st->st_mode)) {
|
||||
(void) fprintf(stderr, gettext("only support project quota on "
|
||||
"regular file or directory\n"));
|
||||
return (-1);
|
||||
}
|
||||
|
||||
if (!S_ISDIR(st->st_mode)) {
|
||||
if (zpc->zpc_dironly) {
|
||||
(void) fprintf(stderr, gettext(
|
||||
"'-d' option on non-dir target %s\n"), name);
|
||||
return (-1);
|
||||
}
|
||||
|
||||
if (zpc->zpc_recursive) {
|
||||
(void) fprintf(stderr, gettext(
|
||||
"'-r' option on non-dir target %s\n"), name);
|
||||
return (-1);
|
||||
}
|
||||
}
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
static int
|
||||
zfs_project_load_projid(const char *name, zfs_project_control_t *zpc)
|
||||
{
|
||||
zfsxattr_t fsx;
|
||||
int ret, fd;
|
||||
|
||||
fd = open(name, O_RDONLY | O_NOCTTY);
|
||||
if (fd < 0) {
|
||||
(void) fprintf(stderr, gettext("failed to open %s: %s\n"),
|
||||
name, strerror(errno));
|
||||
return (fd);
|
||||
}
|
||||
|
||||
ret = ioctl(fd, ZFS_IOC_FSGETXATTR, &fsx);
|
||||
if (ret)
|
||||
(void) fprintf(stderr,
|
||||
gettext("failed to get xattr for %s: %s\n"),
|
||||
name, strerror(errno));
|
||||
else
|
||||
zpc->zpc_expected_projid = fsx.fsx_projid;
|
||||
|
||||
close(fd);
|
||||
return (ret);
|
||||
}
|
||||
|
||||
static int
|
||||
zfs_project_handle_one(const char *name, zfs_project_control_t *zpc)
|
||||
{
|
||||
zfsxattr_t fsx;
|
||||
int ret, fd;
|
||||
|
||||
fd = open(name, O_RDONLY | O_NOCTTY);
|
||||
if (fd < 0) {
|
||||
if (errno == ENOENT && zpc->zpc_ignore_noent)
|
||||
return (0);
|
||||
|
||||
(void) fprintf(stderr, gettext("failed to open %s: %s\n"),
|
||||
name, strerror(errno));
|
||||
return (fd);
|
||||
}
|
||||
|
||||
ret = ioctl(fd, ZFS_IOC_FSGETXATTR, &fsx);
|
||||
if (ret) {
|
||||
(void) fprintf(stderr,
|
||||
gettext("failed to get xattr for %s: %s\n"),
|
||||
name, strerror(errno));
|
||||
goto out;
|
||||
}
|
||||
|
||||
switch (zpc->zpc_op) {
|
||||
case ZFS_PROJECT_OP_LIST:
|
||||
(void) printf("%5u %c %s\n", fsx.fsx_projid,
|
||||
(fsx.fsx_xflags & ZFS_PROJINHERIT_FL) ? 'P' : '-', name);
|
||||
goto out;
|
||||
case ZFS_PROJECT_OP_CHECK:
|
||||
if (fsx.fsx_projid == zpc->zpc_expected_projid &&
|
||||
fsx.fsx_xflags & ZFS_PROJINHERIT_FL)
|
||||
goto out;
|
||||
|
||||
if (!zpc->zpc_newline) {
|
||||
char c = '\0';
|
||||
|
||||
(void) printf("%s%c", name, c);
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (fsx.fsx_projid != zpc->zpc_expected_projid)
|
||||
(void) printf("%s - project ID is not set properly "
|
||||
"(%u/%u)\n", name, fsx.fsx_projid,
|
||||
(uint32_t)zpc->zpc_expected_projid);
|
||||
|
||||
if (!(fsx.fsx_xflags & ZFS_PROJINHERIT_FL))
|
||||
(void) printf("%s - project inherit flag is not set\n",
|
||||
name);
|
||||
|
||||
goto out;
|
||||
case ZFS_PROJECT_OP_CLEAR:
|
||||
if (!(fsx.fsx_xflags & ZFS_PROJINHERIT_FL) &&
|
||||
(zpc->zpc_keep_projid ||
|
||||
fsx.fsx_projid == ZFS_DEFAULT_PROJID))
|
||||
goto out;
|
||||
|
||||
fsx.fsx_xflags &= ~ZFS_PROJINHERIT_FL;
|
||||
if (!zpc->zpc_keep_projid)
|
||||
fsx.fsx_projid = ZFS_DEFAULT_PROJID;
|
||||
break;
|
||||
case ZFS_PROJECT_OP_SET:
|
||||
if (fsx.fsx_projid == zpc->zpc_expected_projid &&
|
||||
(!zpc->zpc_set_flag || fsx.fsx_xflags & ZFS_PROJINHERIT_FL))
|
||||
goto out;
|
||||
|
||||
fsx.fsx_projid = zpc->zpc_expected_projid;
|
||||
if (zpc->zpc_set_flag)
|
||||
fsx.fsx_xflags |= ZFS_PROJINHERIT_FL;
|
||||
break;
|
||||
default:
|
||||
ASSERT(0);
|
||||
break;
|
||||
}
|
||||
|
||||
ret = ioctl(fd, ZFS_IOC_FSSETXATTR, &fsx);
|
||||
if (ret)
|
||||
(void) fprintf(stderr,
|
||||
gettext("failed to set xattr for %s: %s\n"),
|
||||
name, strerror(errno));
|
||||
|
||||
out:
|
||||
close(fd);
|
||||
return (ret);
|
||||
}
|
||||
|
||||
static int
|
||||
zfs_project_handle_dir(const char *name, zfs_project_control_t *zpc,
|
||||
list_t *head)
|
||||
{
|
||||
char fullname[PATH_MAX];
|
||||
struct dirent *ent;
|
||||
DIR *dir;
|
||||
int ret = 0;
|
||||
|
||||
dir = opendir(name);
|
||||
if (dir == NULL) {
|
||||
if (errno == ENOENT && zpc->zpc_ignore_noent)
|
||||
return (0);
|
||||
|
||||
ret = -errno;
|
||||
(void) fprintf(stderr, gettext("failed to opendir %s: %s\n"),
|
||||
name, strerror(errno));
|
||||
return (ret);
|
||||
}
|
||||
|
||||
/* Non-top item, ignore the case of being removed or renamed by race. */
|
||||
zpc->zpc_ignore_noent = B_TRUE;
|
||||
errno = 0;
|
||||
while (!ret && (ent = readdir(dir)) != NULL) {
|
||||
/* skip "." and ".." */
|
||||
if (strcmp(ent->d_name, ".") == 0 ||
|
||||
strcmp(ent->d_name, "..") == 0)
|
||||
continue;
|
||||
|
||||
if (strlen(ent->d_name) + strlen(name) >=
|
||||
sizeof (fullname) + 1) {
|
||||
errno = ENAMETOOLONG;
|
||||
break;
|
||||
}
|
||||
|
||||
sprintf(fullname, "%s/%s", name, ent->d_name);
|
||||
ret = zfs_project_handle_one(fullname, zpc);
|
||||
if (!ret && zpc->zpc_recursive && ent->d_type == DT_DIR)
|
||||
zfs_project_item_alloc(head, fullname);
|
||||
}
|
||||
|
||||
if (errno && !ret) {
|
||||
ret = -errno;
|
||||
(void) fprintf(stderr, gettext("failed to readdir %s: %s\n"),
|
||||
name, strerror(errno));
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
return (ret);
|
||||
}
|
||||
|
||||
int
|
||||
zfs_project_handle(const char *name, zfs_project_control_t *zpc)
|
||||
{
|
||||
zfs_project_item_t *zpi;
|
||||
struct stat st;
|
||||
list_t head;
|
||||
int ret;
|
||||
|
||||
ret = zfs_project_sanity_check(name, zpc, &st);
|
||||
if (ret)
|
||||
return (ret);
|
||||
|
||||
if ((zpc->zpc_op == ZFS_PROJECT_OP_SET ||
|
||||
zpc->zpc_op == ZFS_PROJECT_OP_CHECK) &&
|
||||
zpc->zpc_expected_projid == ZFS_INVALID_PROJID) {
|
||||
ret = zfs_project_load_projid(name, zpc);
|
||||
if (ret)
|
||||
return (ret);
|
||||
}
|
||||
|
||||
zpc->zpc_ignore_noent = B_FALSE;
|
||||
ret = zfs_project_handle_one(name, zpc);
|
||||
if (ret || !S_ISDIR(st.st_mode) || zpc->zpc_dironly ||
|
||||
(!zpc->zpc_recursive &&
|
||||
zpc->zpc_op != ZFS_PROJECT_OP_LIST &&
|
||||
zpc->zpc_op != ZFS_PROJECT_OP_CHECK))
|
||||
return (ret);
|
||||
|
||||
list_create(&head, sizeof (zfs_project_item_t),
|
||||
offsetof(zfs_project_item_t, zpi_list));
|
||||
zfs_project_item_alloc(&head, name);
|
||||
while ((zpi = list_remove_head(&head)) != NULL) {
|
||||
if (!ret)
|
||||
ret = zfs_project_handle_dir(zpi->zpi_name, zpc, &head);
|
||||
free(zpi);
|
||||
}
|
||||
|
||||
return (ret);
|
||||
}
|
49
sys/contrib/openzfs/cmd/zfs/zfs_projectutil.h
Normal file
49
sys/contrib/openzfs/cmd/zfs/zfs_projectutil.h
Normal file
@ -0,0 +1,49 @@
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License (the "License").
|
||||
* You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||
* or http://www.opensolaris.org/os/licensing.
|
||||
* See the License for the specific language governing permissions
|
||||
* and limitations under the License.
|
||||
*
|
||||
* When distributing Covered Code, include this CDDL HEADER in each
|
||||
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||
* If applicable, add the following below this CDDL HEADER, with the
|
||||
* fields enclosed by brackets "[]" replaced with your own identifying
|
||||
* information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
*
|
||||
* CDDL HEADER END
|
||||
*/
|
||||
/*
|
||||
* Copyright (c) 2017, Intel Corporation. All rights reserved.
|
||||
*/
|
||||
|
||||
#ifndef _ZFS_PROJECTUTIL_H
|
||||
#define _ZFS_PROJECTUTIL_H
|
||||
|
||||
typedef enum {
|
||||
ZFS_PROJECT_OP_DEFAULT = 0,
|
||||
ZFS_PROJECT_OP_LIST = 1,
|
||||
ZFS_PROJECT_OP_CHECK = 2,
|
||||
ZFS_PROJECT_OP_CLEAR = 3,
|
||||
ZFS_PROJECT_OP_SET = 4,
|
||||
} zfs_project_ops_t;
|
||||
|
||||
typedef struct zfs_project_control {
|
||||
uint64_t zpc_expected_projid;
|
||||
zfs_project_ops_t zpc_op;
|
||||
boolean_t zpc_dironly;
|
||||
boolean_t zpc_ignore_noent;
|
||||
boolean_t zpc_keep_projid;
|
||||
boolean_t zpc_newline;
|
||||
boolean_t zpc_recursive;
|
||||
boolean_t zpc_set_flag;
|
||||
} zfs_project_control_t;
|
||||
|
||||
int zfs_project_handle(const char *name, zfs_project_control_t *zpc);
|
||||
|
||||
#endif /* _ZFS_PROJECTUTIL_H */
|
42
sys/contrib/openzfs/cmd/zfs/zfs_util.h
Normal file
42
sys/contrib/openzfs/cmd/zfs/zfs_util.h
Normal file
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License (the "License").
|
||||
* You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||
* or http://www.opensolaris.org/os/licensing.
|
||||
* See the License for the specific language governing permissions
|
||||
* and limitations under the License.
|
||||
*
|
||||
* When distributing Covered Code, include this CDDL HEADER in each
|
||||
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||
* If applicable, add the following below this CDDL HEADER, with the
|
||||
* fields enclosed by brackets "[]" replaced with your own identifying
|
||||
* information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
*
|
||||
* CDDL HEADER END
|
||||
*/
|
||||
/*
|
||||
* Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
|
||||
*/
|
||||
|
||||
#ifndef _ZFS_UTIL_H
|
||||
#define _ZFS_UTIL_H
|
||||
|
||||
#include <libzfs.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void * safe_malloc(size_t size);
|
||||
void nomem(void);
|
||||
extern libzfs_handle_t *g_zfs;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* _ZFS_UTIL_H */
|
1
sys/contrib/openzfs/cmd/zfs_ids_to_path/.gitignore
vendored
Normal file
1
sys/contrib/openzfs/cmd/zfs_ids_to_path/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
zfs_ids_to_path
|
9
sys/contrib/openzfs/cmd/zfs_ids_to_path/Makefile.am
Normal file
9
sys/contrib/openzfs/cmd/zfs_ids_to_path/Makefile.am
Normal file
@ -0,0 +1,9 @@
|
||||
include $(top_srcdir)/config/Rules.am
|
||||
|
||||
sbin_PROGRAMS = zfs_ids_to_path
|
||||
|
||||
zfs_ids_to_path_SOURCES = \
|
||||
zfs_ids_to_path.c
|
||||
|
||||
zfs_ids_to_path_LDADD = \
|
||||
$(abs_top_builddir)/lib/libzfs/libzfs.la
|
96
sys/contrib/openzfs/cmd/zfs_ids_to_path/zfs_ids_to_path.c
Normal file
96
sys/contrib/openzfs/cmd/zfs_ids_to_path/zfs_ids_to_path.c
Normal file
@ -0,0 +1,96 @@
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License (the "License").
|
||||
* You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||
* or http://www.opensolaris.org/os/licensing.
|
||||
* See the License for the specific language governing permissions
|
||||
* and limitations under the License.
|
||||
*
|
||||
* When distributing Covered Code, include this CDDL HEADER in each
|
||||
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||
* If applicable, add the following below this CDDL HEADER, with the
|
||||
* fields enclosed by brackets "[]" replaced with your own identifying
|
||||
* information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
*
|
||||
* CDDL HEADER END
|
||||
*/
|
||||
/*
|
||||
* Copyright (c) 2019 by Delphix. All rights reserved.
|
||||
*/
|
||||
#include <libintl.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <stdint.h>
|
||||
#include <libzfs.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
|
||||
libzfs_handle_t *g_zfs;
|
||||
|
||||
static void
|
||||
usage(int err)
|
||||
{
|
||||
fprintf(stderr, "Usage: [-v] zfs_ids_to_path <pool> <objset id> "
|
||||
"<object id>\n");
|
||||
exit(err);
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
boolean_t verbose = B_FALSE;
|
||||
char c;
|
||||
while ((c = getopt(argc, argv, "v")) != -1) {
|
||||
switch (c) {
|
||||
case 'v':
|
||||
verbose = B_TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
argc -= optind;
|
||||
argv += optind;
|
||||
|
||||
if (argc != 3) {
|
||||
(void) fprintf(stderr, "Incorrect number of arguments: %d\n",
|
||||
argc);
|
||||
usage(1);
|
||||
}
|
||||
|
||||
uint64_t objset, object;
|
||||
if (sscanf(argv[1], "%llu", (u_longlong_t *)&objset) != 1) {
|
||||
(void) fprintf(stderr, "Invalid objset id: %s\n", argv[2]);
|
||||
usage(2);
|
||||
}
|
||||
if (sscanf(argv[2], "%llu", (u_longlong_t *)&object) != 1) {
|
||||
(void) fprintf(stderr, "Invalid object id: %s\n", argv[3]);
|
||||
usage(3);
|
||||
}
|
||||
if ((g_zfs = libzfs_init()) == NULL) {
|
||||
(void) fprintf(stderr, "%s\n", libzfs_error_init(errno));
|
||||
return (4);
|
||||
}
|
||||
zpool_handle_t *pool = zpool_open(g_zfs, argv[0]);
|
||||
if (pool == NULL) {
|
||||
fprintf(stderr, "Could not open pool %s\n", argv[1]);
|
||||
libzfs_fini(g_zfs);
|
||||
return (5);
|
||||
}
|
||||
|
||||
char pathname[PATH_MAX * 2];
|
||||
if (verbose) {
|
||||
zpool_obj_to_path_ds(pool, objset, object, pathname,
|
||||
sizeof (pathname));
|
||||
} else {
|
||||
zpool_obj_to_path(pool, objset, object, pathname,
|
||||
sizeof (pathname));
|
||||
}
|
||||
printf("%s\n", pathname);
|
||||
zpool_close(pool);
|
||||
libzfs_fini(g_zfs);
|
||||
return (0);
|
||||
}
|
1
sys/contrib/openzfs/cmd/zgenhostid/Makefile.am
Normal file
1
sys/contrib/openzfs/cmd/zgenhostid/Makefile.am
Normal file
@ -0,0 +1 @@
|
||||
dist_bin_SCRIPTS = zgenhostid
|
61
sys/contrib/openzfs/cmd/zgenhostid/zgenhostid
Executable file
61
sys/contrib/openzfs/cmd/zgenhostid/zgenhostid
Executable file
@ -0,0 +1,61 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Emulate genhostid(1) available on RHEL/CENTOS, for use on distros
|
||||
# which do not provide that utility.
|
||||
#
|
||||
# Usage:
|
||||
# zgenhostid
|
||||
# zgenhostid <value>
|
||||
#
|
||||
# If /etc/hostid already exists and is size > 0, the script exits immediately
|
||||
# and changes nothing. Unlike genhostid, this generates an error message.
|
||||
#
|
||||
# The first form generates a random hostid and stores it in /etc/hostid.
|
||||
# The second form checks that the provided value is between 0x1 and 0xFFFFFFFF
|
||||
# and if so, stores it in /etc/hostid. This form is not supported by
|
||||
# genhostid(1).
|
||||
|
||||
hostid_file=/etc/hostid
|
||||
|
||||
function usage {
|
||||
echo "$0 [value]"
|
||||
echo "If $hostid_file is not present, store a hostid in it." >&2
|
||||
echo "The optional value must be an 8-digit hex number between" >&2
|
||||
echo "1 and 2^32-1. If no value is provided, a random one will" >&2
|
||||
echo "be generated. The value must be unique among your systems." >&2
|
||||
}
|
||||
|
||||
# hostid(1) ignores contents of /etc/hostid if size < 4 bytes. It would
|
||||
# be better if this checked size >= 4 bytes but it the method must be
|
||||
# widely portable.
|
||||
if [ -s $hostid_file ]; then
|
||||
echo "$hostid_file already exists. No change made." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -n "$1" ]; then
|
||||
host_id=$1
|
||||
else
|
||||
# $RANDOM goes from 0..32k-1
|
||||
number=$((((RANDOM % 4) * 32768 + RANDOM) * 32768 + RANDOM))
|
||||
host_id=$(printf "%08x" $number)
|
||||
fi
|
||||
|
||||
if egrep -o '^0{8}$' <<< $host_id >/dev/null 2>&1; then
|
||||
usage
|
||||
exit 2
|
||||
fi
|
||||
|
||||
if ! egrep -o '^[a-fA-F0-9]{8}$' <<< $host_id >/dev/null 2>&1; then
|
||||
usage
|
||||
exit 3
|
||||
fi
|
||||
|
||||
a=${host_id:6:2}
|
||||
b=${host_id:4:2}
|
||||
c=${host_id:2:2}
|
||||
d=${host_id:0:2}
|
||||
|
||||
echo -ne \\x$a\\x$b\\x$c\\x$d > $hostid_file
|
||||
|
||||
exit 0
|
1
sys/contrib/openzfs/cmd/zhack/.gitignore
vendored
Normal file
1
sys/contrib/openzfs/cmd/zhack/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/zhack
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user