├── .gitignore ├── CODE_OF_CONDUCT.md ├── LICENSES ├── CC0-1.0.txt ├── GPL-3.0-or-later.txt └── LGPL-3.0-or-later.txt ├── Makefile.am ├── README.md ├── autogen.sh ├── configure.ac ├── debian ├── changelog ├── control ├── copyright ├── libzbd-dev.install ├── libzbd2.install ├── libzbd2.symbols ├── not-installed ├── rules ├── salsa-ci.yml ├── source │ └── format ├── tests │ └── control ├── upstream │ └── metadata ├── watch └── zbd-utils.install ├── include └── libzbd │ └── zbd.h ├── lib ├── Makefile.am ├── exports ├── libzbd.pc.in ├── zbd.c ├── zbd.h └── zbd_utils.c ├── libzbd.spec ├── m4 └── dontremove └── tools ├── Makefile.am ├── cli ├── Makefile.am ├── zbd.8 ├── zbd.c ├── zbd.h └── zbd_dump.c ├── gui ├── Makefile.am ├── gzbd.8 ├── gzbd.c ├── gzbd.desktop.in ├── gzbd.h ├── gzbd.png ├── gzbd_if.c ├── gzbd_if_dev.c └── org.gnome.gzbd.policy.in └── viewer ├── Makefile.am ├── gzbd-viewer.8 ├── gzbd-viewer.desktop.in ├── gzbd-viewer.png ├── gzbd_viewer.c ├── gzbd_viewer.h ├── gzbd_viewer_if.c └── org.gnome.gzbd-viewer.policy.in /.gitignore: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: CC0-1.0 2 | # 3 | # SPDX-FileCopyrightText: 2020 Western Digital Corporation or its affiliates. 4 | 5 | # Object files 6 | *.o 7 | *.lo 8 | *.obj 9 | *.elf 10 | *.la 11 | *.so 12 | *.so.* 13 | *.out 14 | *.dump 15 | 16 | tools/zbd 17 | tools/gzbd 18 | tools/gzbd-viewer 19 | 20 | # Generated files 21 | *.desktop 22 | *.policy 23 | 24 | # Dependency files 25 | .depfile 26 | .depend 27 | 28 | # RPM packages build files and directories 29 | rpmbuild 30 | *.tar.gz 31 | *.rpm 32 | 33 | # Debian packages build files and directories 34 | debbuild 35 | *.deb 36 | 37 | # Other 38 | Thumbs.db 39 | desktop.ini 40 | .DS_Store 41 | *~ 42 | 43 | # Autotools stuff 44 | m4/*.m4 45 | Makefile.in 46 | aclocal.m4 47 | autom4te.cache/ 48 | build-aux/ 49 | configure 50 | include/config.h.in 51 | Makefile 52 | config.log 53 | config.status 54 | include/config.h 55 | include/stamp-h1 56 | .deps 57 | libtool 58 | lib/libzbd.pc 59 | .libs 60 | .dirstamp 61 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | 2 | # Contributor Covenant Code of Conduct 3 | 4 | ## Our Pledge 5 | 6 | We as members, contributors, and leaders pledge to make participation in our 7 | community a harassment-free experience for everyone, regardless of age, body 8 | size, visible or invisible disability, ethnicity, sex characteristics, gender 9 | identity and expression, level of experience, education, socio-economic status, 10 | nationality, personal appearance, race, caste, color, religion, or sexual 11 | identity and orientation. 12 | 13 | We pledge to act and interact in ways that contribute to an open, welcoming, 14 | diverse, inclusive, and healthy community. 15 | 16 | ## Our Standards 17 | 18 | Examples of behavior that contributes to a positive environment for our 19 | community include: 20 | 21 | * Demonstrating empathy and kindness toward other people 22 | * Being respectful of differing opinions, viewpoints, and experiences 23 | * Giving and gracefully accepting constructive feedback 24 | * Accepting responsibility and apologizing to those affected by our mistakes, 25 | and learning from the experience 26 | * Focusing on what is best not just for us as individuals, but for the 27 | overall community 28 | 29 | Examples of unacceptable behavior include: 30 | 31 | * The use of sexualized language or imagery, and sexual attention or 32 | advances of any kind 33 | * Trolling, insulting or derogatory comments, and personal or political attacks 34 | * Public or private harassment 35 | * Publishing others' private information, such as a physical or email 36 | address, without their explicit permission 37 | * Other conduct which could reasonably be considered inappropriate in a 38 | professional setting 39 | 40 | ## Enforcement Responsibilities 41 | 42 | Community leaders are responsible for clarifying and enforcing our standards of 43 | acceptable behavior and will take appropriate and fair corrective action in 44 | response to any behavior that they deem inappropriate, threatening, offensive, 45 | or harmful. 46 | 47 | Community leaders have the right and responsibility to remove, edit, or reject 48 | comments, commits, code, wiki edits, issues, and other contributions that are 49 | not aligned to this Code of Conduct, and will communicate reasons for moderation 50 | decisions when appropriate. 51 | 52 | ## Scope 53 | 54 | This Code of Conduct applies within all community spaces, and also applies when 55 | an individual is officially representing the community in public spaces. 56 | Examples of representing our community include using an official e-mail address, 57 | posting via an official social media account, or acting as an appointed 58 | representative at an online or offline event. 59 | 60 | ## Enforcement 61 | 62 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 63 | reported to the community leaders responsible for enforcement at 64 | [Open Source Inquiries][contact]. 65 | All complaints will be reviewed and investigated promptly and fairly. 66 | 67 | All community leaders are obligated to respect the privacy and security of the 68 | reporter of any incident. 69 | 70 | ## Enforcement Guidelines 71 | 72 | Community leaders will follow these Community Impact Guidelines in determining 73 | the consequences for any action they deem in violation of this Code of Conduct: 74 | 75 | ### 1. Correction 76 | 77 | **Community Impact**: Use of inappropriate language or other behavior deemed 78 | unprofessional or unwelcome in the community. 79 | 80 | **Consequence**: A private, written warning from community leaders, providing 81 | clarity around the nature of the violation and an explanation of why the 82 | behavior was inappropriate. A public apology may be requested. 83 | 84 | ### 2. Warning 85 | 86 | **Community Impact**: A violation through a single incident or series 87 | of actions. 88 | 89 | **Consequence**: A warning with consequences for continued behavior. No 90 | interaction with the people involved, including unsolicited interaction with 91 | those enforcing the Code of Conduct, for a specified period of time. This 92 | includes avoiding interactions in community spaces as well as external channels 93 | like social media. Violating these terms may lead to a temporary or 94 | permanent ban. 95 | 96 | ### 3. Temporary Ban 97 | 98 | **Community Impact**: A serious violation of community standards, including 99 | sustained inappropriate behavior. 100 | 101 | **Consequence**: A temporary ban from any sort of interaction or public 102 | communication with the community for a specified period of time. No public or 103 | private interaction with the people involved, including unsolicited interaction 104 | with those enforcing the Code of Conduct, is allowed during this period. 105 | Violating these terms may lead to a permanent ban. 106 | 107 | ### 4. Permanent Ban 108 | 109 | **Community Impact**: Demonstrating a pattern of violation of community 110 | standards, including sustained inappropriate behavior, harassment of an 111 | individual, or aggression toward or disparagement of classes of individuals. 112 | 113 | **Consequence**: A permanent ban from any sort of public interaction within 114 | the community. 115 | 116 | ## Attribution 117 | 118 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 119 | version 2.0, available at 120 | [https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0]. 121 | 122 | Community Impact Guidelines were inspired by 123 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. 124 | 125 | For answers to common questions about this code of conduct, see the FAQ at 126 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available 127 | at [https://www.contributor-covenant.org/translations][translations]. 128 | 129 | [homepage]: https://www.contributor-covenant.org 130 | [v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html 131 | [Mozilla CoC]: https://github.com/mozilla/diversity 132 | [FAQ]: https://www.contributor-covenant.org/faq 133 | [translations]: https://www.contributor-covenant.org/translations 134 | [contact]: https://www.westerndigital.com/contact/contact-open-source 135 | -------------------------------------------------------------------------------- /LICENSES/CC0-1.0.txt: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. 122 | -------------------------------------------------------------------------------- /LICENSES/LGPL-3.0-or-later.txt: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /Makefile.am: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: LGPL-3.0-or-later 2 | # 3 | # SPDX-FileCopyrightText: 2020 Western Digital Corporation or its affiliates. 4 | 5 | ACLOCAL_AMFLAGS = -I m4 6 | 7 | SUBDIRS = lib tools 8 | 9 | EXTRA_DIST = autogen.sh \ 10 | README.md \ 11 | LICENSES/GPL-3.0-or-later.txt \ 12 | LICENSES/LGPL-3.0-or-later.txt 13 | 14 | if BUILDING_RPM 15 | rpmdir = $(abs_top_builddir)/rpmbuild 16 | 17 | EXTRA_DIST += libzbd.spec 18 | RPMARCH=`$(RPM) --eval %_target_cpu` 19 | 20 | rpm: dist 21 | @echo "Building RPM packages..." 22 | @mkdir -p $(rpmdir) 23 | $(RPMBUILD) -ta --clean \ 24 | -D "_topdir $(rpmdir)" \ 25 | -D "pkg_name $(PACKAGE_NAME)" \ 26 | -D "pkg_version $(PACKAGE_VERSION)" \ 27 | libzbd-$(PACKAGE_VERSION).tar.gz 28 | @mv -f $(rpmdir)/RPMS/$(RPMARCH)/*.rpm $(abs_top_builddir) 29 | @mv -f $(rpmdir)/SRPMS/*.rpm $(abs_top_builddir) 30 | @rm -rf $(rpmdir) 31 | @rm -f libzbd-$(PACKAGE_VERSION).tar.gz 32 | else 33 | rpm: 34 | @echo "Building RPM packages requires rpmbuild and rpm utilities" 35 | exit 1 36 | endif 37 | 38 | CLEANFILES = *.rpm *.tar.gz 39 | DISTCLEANFILES = *.rpm *.tar.gz configure 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Copyright (C) 2020 Western Digital Corporation or its affiliates. 2 | 3 | # libzbd 4 | 5 | *libzbd* is a library providing functions simplifying access to zoned block 6 | device information and the execution of zone management operations. 7 | 8 | An example command line application using *libzbd* is available under the 9 | tools directory. Additionally, graphical user interface applications are also 10 | implemented to visually represent the state and usage of zones of a zoned 11 | block device. 12 | 13 | ## License 14 | 15 | *libzbd* is distributed under the terms of the of the GNU Lesser General Public 16 | License Version 3 or any later verison ((SPDX: *LGPL-3.0-or-later*). A copy of 17 | this license with *libzbd* copyright can be found in the file 18 | [LICENSES/LGPL-3.0-or-later.txt](LICENSES/LGPL-3.0-or-later.txt). 19 | 20 | All example applications under the tools directory are distributed under 21 | the terms of the GNU General Public License version 3, or any later version. 22 | A copy of this license can be found in the file 23 | [LICENSES/GPL-3.0-or-later.txt](LICENSES/GPL-3.0-or-later.txt). 24 | 25 | If *libzbd* license files are missing, please see the LGPL version 3 text 26 | [here](https://www.gnu.org/licenses/lgpl-3.0.html) and the GPL version 3 text 27 | [here](https://www.gnu.org/licenses/gpl-3.0.html). 28 | 29 | All source files in *libzbd* contain the LGPL v3 or GPL v3 license SPDX short 30 | identifiers in place of the full license text. 31 | 32 | ``` 33 | SPDX-License-Identifier: LGPL-3.0-or-later 34 | SPDX-License-Identifier: GPL-3.0-or-later 35 | ``` 36 | 37 | Some files such as the `.gitignore` file are public domain specified by the 38 | CC0 1.0 Universal (CC0 1.0) Public Domain Dedication. These files are 39 | identified with the following SPDX header. 40 | 41 | ``` 42 | SPDX-License-Identifier: CC0-1.0 43 | ``` 44 | 45 | See the file [LICENSES/CC0-1.0.txt] for the full text of this license. 46 | 47 | *libzbd* and all its example applications are distributed "as is," without 48 | technical support, and WITHOUT ANY WARRANTY, without even the implied warranty 49 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 50 | 51 | ## Contributions and Bug Reports 52 | 53 | Contributions are accepted as github pull requests or via email (`git 54 | send-email` patches). Any problem may also be reported through github issues 55 | page or by contacting: 56 | 57 | * Damien Le Moal (damien.lemoal@wdc.com) 58 | * Niklas Cassel (niklas.cassel@wdc.com) 59 | 60 | PLEASE DO NOT SUBMIT CONFIDENTIAL INFORMATION OR INFORMATION SPECIFIC TO DRIVES 61 | THAT ARE VENDOR SAMPLES OR NOT PUBLICLY AVAILABLE. 62 | 63 | ## Compilation and installation 64 | 65 | ### Requirements 66 | 67 | *libzbd* requires the following packages for compilation: 68 | 69 | * m4 70 | * autoconf 71 | * automake 72 | * libtool 73 | * GTK3 and GTK3 development headers (when building the *gzbd* and *gzbd-viewer* 74 | graphical applications) 75 | 76 | Since *libzbd* uses Linux(tm) kernel zoned block device interface, compilation 77 | must be done on a system where the kernel header file *blkzoned.h* for zoned 78 | block devices interface is installed under /usr/include/linux/. This implies 79 | that the kernel version must be higher than version 4.10.0. 80 | 81 | If the GTK3 and GTK3 development packages are not installed, compilation of the 82 | *gzbd* and *gzbd-viewer* graphical applications is automatically disabled. 83 | 84 | ### Compilation 85 | 86 | To compile the library and all example applications under the tools directory, 87 | execute the following commands. 88 | 89 | ``` 90 | $ sh ./autogen.sh 91 | $ ./configure 92 | $ make 93 | ``` 94 | 95 | ### Installation 96 | 97 | To install the library and all example applications compiled under the tools 98 | directory, execute the following command. 99 | 100 | ``` 101 | $ sudo make install 102 | ``` 103 | 104 | The library files are by default installed under `/usr/lib` (or `/usr/lib64`). 105 | The library header file is installed under `/usr/include/libzbd`. 106 | 107 | The executable files for the example applications are installed under 108 | `/usr/bin`. 109 | 110 | These default installation paths can be changed. Executing the following 111 | command displays the options used to control the installation paths. 112 | 113 | ``` 114 | $ ./configure --help 115 | ``` 116 | 117 | ### Building RPM Packages 118 | 119 | The *rpm* and *rpmbuild* utilities are necessary to build *libzbd* RPM 120 | packages. Once these utilities are installed, the RPM packages can be built 121 | using the following command. 122 | 123 | ``` 124 | $ sh ./autogen.sh 125 | $ ./configure 126 | $ make rpm 127 | ``` 128 | 129 | Five RPM packages are built: 130 | * A binary package providing *libzbd* library file, the tools executables, 131 | the tools man pages, documentation and license files. 132 | * A source RPM package 133 | * A *debuginfo* RPM package and a *debugsource* RPM package 134 | * A development package providing the *libzbd* header files 135 | 136 | The source RPM package can be used to build the binary and debug RPM packages 137 | outside of *libzbd* source tree using the following command. 138 | 139 | ``` 140 | $ rpmbuild --rebuild libzbd-.src.rpm 141 | ``` 142 | 143 | ## Library 144 | 145 | *libzbd* defines a set of functions and data structures simplifying the 146 | management and use of zoned block devices. 147 | 148 | ### Overview 149 | 150 | *libzbd* functions and data structures are defined in the header file 151 | [include/libzbd/zbd.h]. Applications using *libzbd* must include this header 152 | file and compile against *libzbd* using dynamic linking (`libzbd.so` library 153 | file) or statically linking using the `libzbd.a` archive file. 154 | 155 | *libzbd* internal implementation is simple. No internal library state is 156 | maintained at run-time, with the exception of a list of open zoned block device. 157 | Data types and units used by regular file access system calls are reused. 158 | 159 | * Open zoned block device files are identified using a file descriptor similar 160 | to one that the *open()* system call returns. 161 | * Zone information such as zone start position, zone size and zone write pointer 162 | position use Byte unit, the same unit as used by system calls such as 163 | *fseek()*, *read()* or *write()* for file offset position and I/O buffer 164 | sizes. 165 | 166 | *libzbd* provides data structures for describing zones of a zoned block device 167 | (*struct zbd_zone*) and providing information about the device itself 168 | (*struct zbd_info*). 169 | 170 | ### Library Functions 171 | 172 | *libzbd* implements the following functions. 173 | 174 | Function | Description 175 | ----------------------- | ----------------------------------------------- 176 | *zbd_device_is_zoned()* | Test if a block device is a zoned block device 177 | *zbd_open()* | Open a zoned block device 178 | *zbd_close()* | Close a zoned block device 179 | *zbd_get_info()* | Get an open zoned block device information 180 | *zbd_report_zones()*
*zbd_list_zones()* | Get zone information of an open device 181 | *zbd_report_nr_zones()* | Get the number of zones of an open device 182 | *zbd_zone_operation()* | Execute a zone management operation 183 | *zbd_reset_zones()* | Reset the write pointer position of a range of zones 184 | *zbd_open_zones()* | Explicitly open a range of zones 185 | *zbd_close_zones()* | Explicitly close a range of open zones 186 | *zbd_finish_zones()* | Finish a range of zones 187 | 188 | The following macro definitions are defined to facilitate manipulation of a 189 | zone descriptor information (*struct zbd_zone*). 190 | 191 | Function | Description 192 | ----------------------- | -------------------------------------------------- 193 | *zbd_zone_type()* | Get a zone type (*enum zbd_zone_type*) 194 | *zbd_zone_cnv()* | Test if a zone type is conventional 195 | *zbd_zone_swr()* | Test if a zone type is sequential write required 196 | *zbd_zone_swp()* | Test if a zone type is sequential write preferred 197 | *zbd_zone_seq()* | Test if zone type is sequential (not conventional) 198 | *zbd_zone_cond()* | Get a zone condition (*enum zbd_zone_cond*) 199 | *zbd_zone_not_wp()* | Test if a zone is "not write pointer" (conventional zones) 200 | *zbd_zone_empty()* | Test if a zone is empty 201 | *zbd_zone_imp_open()* | Test if a zone is implicitly open 202 | *zbd_zone_exp_open()* | Test if a zone is explicitly open 203 | *zbd_zone_is_open()* | Test if a zone is open (implicitly or explicitly) 204 | *zbd_zone_closed()* | Test if a zone is closed 205 | *zbd_zone_full()* | Test if a zone is full 206 | *zbd_zone_rdonly()* | Test if a zone is read-only 207 | *zbd_zone_offline()* | Test if a zone is offline 208 | *zbd_zone_start()* | Get a zone start position (bytes) 209 | *zbd_zone_len()* | Get a zone size (bytes) 210 | *zbd_zone_capacity()* | Get a zone capacity (bytes) 211 | *zbd_zone_wp()* | Get a zone write pointer position (bytes) 212 | *zbd_zone_flags()* | Get a zone state flags 213 | *zbd_zone_rwp_recommended()* | Test if a zone indicates reset write pointer recommended 214 | *zbd_zone_non_seq_resources()* | Test if a zone indicates using non-sequential write resources 215 | 216 | The followimg utility functions are also defined. 217 | 218 | Function | Description 219 | ------------------------ | -------------------------------------------------- 220 | *zbd_set_log_level()* | Set the logging level of the library functions 221 | *zbd_device_model_str()* | Get a string describing a device zoned model 222 | *zbd_zone_type_str()* | Get a string describing a zone type 223 | *zbd_zone_cond_str()* | Get a string describing a zone condition 224 | 225 | ### Thread Safety 226 | 227 | Since *libzbd* does not maintain any internal state for open zoned block 228 | devices, that is, it does not dynamically maintain the zone state of open zoned 229 | block devices, no synchronization mechanism for multiple threads applications is 230 | implemented. It is the responsibility of the application to ensure that zones of 231 | a device are manipulated correctly with mutual exclusion when needed. This is 232 | in particular necessary for operations like concurrent write to the same zone 233 | by multiple threads or the execution of zone management operations while reading 234 | or writing the target zones. 235 | 236 | ### Functions Documentation 237 | 238 | *libzbd* functions and data types are documented in more details using code 239 | comments in the file [include/libzbd/zbd.h]. 240 | 241 | ## Tools 242 | 243 | Under the tools directory, several simple applications are available as 244 | examples. These appliations are as follows. 245 | 246 | * **zbd** This application execute zone commands on a device. 247 | 248 | * **gzbd** provides a graphical user interface showing zone information of a 249 | zoned block device. It also displays the write status (write pointer 250 | position) of zones graphically using color coding (red for written space and 251 | green for unwritten space). Operations on zones can also be executed directly 252 | from the interface (reset zone write pointer, open zone, close zone, etc). 253 | 254 | * **gzbd-viewer** provides a simple graphical user interface showing the write 255 | pointer position and zone state of zones of a zoned block device. Similar 256 | color coding as *gzbd* is used. This application automatically refresh the 257 | device zone information after a configurable timeout elapses. The default 258 | refresh cycle is 2 times per seconds (500 ms). 259 | -------------------------------------------------------------------------------- /autogen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # SPDX-License-Identifier: LGPL-3.0-or-later 4 | # 5 | # SPDX-FileCopyrightText: 2020 Western Digital Corporation or its affiliates. 6 | 7 | exec autoreconf -f -i 8 | -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: LGPL-3.0-or-later 2 | # 3 | # SPDX-FileCopyrightText: 2020 Western Digital Corporation or its affiliates. 4 | 5 | AC_INIT([libzbd], [2.0.4], 6 | [damien.lemoal@wdc.com, niklas.cassel@wdc.com], 7 | [libzbd], [https://github.com/westerndigitalcorporation/libzbd]) 8 | 9 | AC_CONFIG_AUX_DIR([build-aux]) 10 | AC_CANONICAL_TARGET 11 | AC_CONFIG_MACRO_DIR([m4]) 12 | AC_CONFIG_HEADERS([include/config.h]) 13 | AC_PREFIX_DEFAULT(/usr) 14 | AM_INIT_AUTOMAKE([-Wall foreign subdir-objects]) 15 | AM_SILENT_RULES([yes]) 16 | 17 | # Change default CFLAGS from "-g -O2" to "-O2" for regular builds. 18 | AC_ARG_ENABLE(debug, 19 | [ --enable-debug Compile with "-g" option], 20 | [DBGCFLAGS="-g"], 21 | [DBGCFLAGS="-O2"]) 22 | CFLAGS="$CFLAGS $DBGCFLAGS" 23 | 24 | AC_PROG_CC 25 | AM_PROG_CC_C_O 26 | AC_PROG_INSTALL 27 | 28 | AC_USE_SYSTEM_EXTENSIONS 29 | AC_SYS_LARGEFILE 30 | 31 | m4_ifdef([AM_PROG_AR], [AM_PROG_AR]) 32 | m4_pattern_allow([AM_PROG_AR]) 33 | LT_INIT 34 | 35 | # Package version details: .. 36 | PACKAGE_VERSION_MAJOR=$(echo $PACKAGE_VERSION | awk -F. '{print $1}') 37 | PACKAGE_VERSION_MINOR=$(echo $PACKAGE_VERSION | awk -F. '{print $2}') 38 | PACKAGE_VERSION_RELEASE=$(echo $PACKAGE_VERSION | awk -F. '{print $3}') 39 | 40 | # libtool friendly library version format 41 | LIBZBD_VERSION_LT=$PACKAGE_VERSION_MAJOR:$PACKAGE_VERSION_MINOR:$PACKAGE_VERSION_RELEASE 42 | AC_SUBST([LIBZBD_VERSION_LT]) 43 | 44 | # Checks for rpm package builds 45 | AC_PATH_PROG([RPMBUILD], [rpmbuild], [notfound]) 46 | AC_PATH_PROG([RPM], [rpm], [notfound]) 47 | AM_CONDITIONAL([BUILDING_RPM], 48 | [test "x$RPMBUILD" != xnotfound && test "x$RPM" != xnotfound]) 49 | 50 | # Checks for header files 51 | AC_CHECK_HEADER(libgen.h, [], 52 | [AC_MSG_ERROR([Couldn't find libgen.h])], 53 | [[#include ]]) 54 | AC_CHECK_HEADER(linux/blkzoned.h, [], 55 | [AC_MSG_ERROR([Couldn't find linux/blkzoned.h. Kernel too old ?])], 56 | [[#include ]]) 57 | 58 | AC_CHECK_MEMBER([struct blk_zone.capacity], 59 | [AC_DEFINE(HAVE_BLK_ZONE_REP_V2, [1], [report zones includes zone capacity])], 60 | [], [[#include ]]) 61 | 62 | # Conditionals 63 | 64 | # Build GUI tools only if GTK3 is installed and can be detected with pkg-config. 65 | AC_ARG_ENABLE([gui], 66 | AS_HELP_STRING([--disable-gui], 67 | [Disable build of GUI tools (gzbd and gzbd-viewer) [default=no]])) 68 | AS_IF([test "x$enable_gui" != "xno"], 69 | [m4_ifdef([PKG_CHECK_MODULES], 70 | [PKG_CHECK_MODULES([GTK], [gtk+-3.0], [HAVE_GTK3=1], [HAVE_GTK3=0])], 71 | [HAVE_GTK3=0]) 72 | AM_CONDITIONAL([BUILD_GUI], [test "$HAVE_GTK3" -eq 1])], 73 | [AM_CONDITIONAL([BUILD_GUI], false)]) 74 | 75 | AC_CONFIG_FILES([ 76 | lib/libzbd.pc 77 | lib/Makefile 78 | tools/Makefile 79 | Makefile 80 | ]) 81 | 82 | AC_OUTPUT 83 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | libzbd (2.0.2-2) unstable; urgency=medium 2 | 3 | * Source only upload. 4 | 5 | -- Sudip Mukherjee Tue, 15 Feb 2022 22:02:14 +0000 6 | 7 | libzbd (2.0.2-1) unstable; urgency=medium 8 | 9 | [ Debian Janitor ] 10 | * Set upstream metadata fields: Bug-Database, Bug-Submit, Repository, 11 | Repository-Browse. 12 | * Update standards version to 4.6.0, no changes needed. 13 | 14 | [ Sudip Mukherjee ] 15 | * New upstream version 2.0.2 16 | - Update version for SONAME change. 17 | - Update symbols. 18 | * Update Standards-Version to 4.6.0.1 19 | 20 | -- Sudip Mukherjee Mon, 14 Feb 2022 14:23:27 +0000 21 | 22 | libzbd (1.5.0-1) unstable; urgency=medium 23 | 24 | * Upload to unstable. 25 | * New upstream version 1.5.0 26 | - Update installed library name. 27 | - Update library name in symbols. 28 | 29 | -- Sudip Mukherjee Wed, 18 Aug 2021 23:00:07 +0100 30 | 31 | libzbd (1.4.0-1) experimental; urgency=medium 32 | 33 | * New upstream version 1.4.0 34 | - Update symbols. 35 | - Update copyright. 36 | 37 | -- Sudip Mukherjee Fri, 11 Jun 2021 23:41:24 +0100 38 | 39 | libzbd (1.3.0-1) experimental; urgency=medium 40 | 41 | * New upstream version 1.3.0 42 | - Update copyright. 43 | - Update symbols. 44 | 45 | -- Sudip Mukherjee Fri, 16 Apr 2021 19:50:56 +0100 46 | 47 | libzbd (1.2.0-1) unstable; urgency=medium 48 | 49 | * New upstream version 1.2.0 50 | - Update symbols. 51 | - Update copyright. 52 | 53 | -- Sudip Mukherjee Sat, 16 Jan 2021 22:53:00 +0000 54 | 55 | libzbd (1.1.0-2) unstable; urgency=medium 56 | 57 | * Source only upload. 58 | 59 | -- Sudip Mukherjee Sun, 10 Jan 2021 18:23:39 +0000 60 | 61 | libzbd (1.1.0-1) unstable; urgency=medium 62 | 63 | * Initial release (Closes: #977914) 64 | 65 | -- Sudip Mukherjee Wed, 23 Dec 2020 23:39:53 +0000 66 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: libzbd 2 | Priority: optional 3 | Maintainer: Sudip Mukherjee 4 | Build-Depends: debhelper-compat (= 13), autoconf-archive, libgtk-3-dev 5 | Standards-Version: 4.6.0.1 6 | Section: libs 7 | Homepage: https://zonedstorage.io/projects/libzbd/ 8 | Vcs-Browser: https://salsa.debian.org/sudip/libzbd 9 | Vcs-Git: https://salsa.debian.org/sudip/libzbd.git 10 | 11 | Package: libzbd-dev 12 | Section: libdevel 13 | Architecture: linux-any 14 | Multi-Arch: same 15 | Depends: libzbd2 (= ${binary:Version}), ${misc:Depends} 16 | Description: Library to manipulate zoned block devices (development files) 17 | libzbd uses the kernel provided zoned block device interface based on the 18 | ioctl() system call. It provides functions for discovering and managing the 19 | state of zones of zoned block devices. Read and write accesses to the devices 20 | can be done using standard standard I/O system calls. 21 | . 22 | This package is needed to compile programs against libzbd. 23 | 24 | Package: libzbd2 25 | Architecture: linux-any 26 | Multi-Arch: same 27 | Depends: ${shlibs:Depends}, ${misc:Depends} 28 | Description: Library to manipulate zoned block devices (shared library) 29 | libzbd uses the kernel provided zoned block device interface based on the 30 | ioctl() system call. It provides functions for discovering and managing the 31 | state of zones of zoned block devices. Read and write accesses to the devices 32 | can be done using standard standard I/O system calls. 33 | . 34 | This package contains the shared library. 35 | 36 | Package: zbd-utils 37 | Section: utils 38 | Architecture: linux-any 39 | Depends: ${shlibs:Depends}, ${misc:Depends} 40 | Description: Utilities to manipulate zoned block devices 41 | zbd is a command line utility to report, open, close, reset and finish zones 42 | of a device. 43 | . 44 | gzbd is similar to zbd but using a graphical user interface. 45 | . 46 | gzbd-viewer is a graphical user interface showing the condition and state of 47 | zones of a zoned block device. 48 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: libzbd 3 | Upstream-Contact: Damien Le Moal 5 | Source: https://github.com/westerndigitalcorporation/libzbd 6 | 7 | Files: * 8 | Copyright: 2020, 2021, Western Digital Corporation or its affiliates 9 | License: GPL-3.0-or-later 10 | 11 | Files: Makefile.am 12 | autogen.sh 13 | configure.ac 14 | lib/exports 15 | include/libzbd/zbd.h 16 | lib/Makefile.am 17 | lib/zbd.c 18 | lib/zbd.h 19 | lib/zbd_utils.c 20 | lib/libzbd.pc.in 21 | libzbd.spec 22 | Copyright: 2020, Western Digital Corporation or its affiliates 23 | License: LGPL-3.0-or-later 24 | 25 | Files: m4/dontremove 26 | .gitignore 27 | Copyright: 2020, Western Digital Corporation or its affiliates 28 | License: CC0-1.0 29 | 30 | Files: debian/* 31 | Copyright: 2020, Sudip Mukherjee 32 | License: GPL-3.0-or-later 33 | 34 | License: GPL-3.0-or-later 35 | This package is free software; you can redistribute it and/or modify 36 | it under the terms of the GNU General Public License as published by 37 | the Free Software Foundation; either version 3 of the License, or 38 | (at your option) any later version. 39 | . 40 | This package is distributed in the hope that it will be useful, 41 | but WITHOUT ANY WARRANTY; without even the implied warranty of 42 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 43 | GNU General Public License for more details. 44 | . 45 | You should have received a copy of the GNU General Public License 46 | along with this program. If not, see 47 | . 48 | On Debian systems, the complete text of the GNU General 49 | Public License version 3 can be found in "/usr/share/common-licenses/GPL-3". 50 | 51 | License: LGPL-3.0-or-later 52 | This package is free software; you can redistribute it and/or 53 | modify it under the terms of the GNU Lesser General Public 54 | License as published by the Free Software Foundation; either 55 | version 3 of the License, or (at your option) any later version. 56 | . 57 | This package is distributed in the hope that it will be useful, 58 | but WITHOUT ANY WARRANTY; without even the implied warranty of 59 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 60 | Lesser General Public License for more details. 61 | . 62 | You should have received a copy of the GNU General Public License 63 | along with this program. If not, see . 64 | . 65 | On Debian systems, the complete text of the GNU Lesser General 66 | Public License can be found in "/usr/share/common-licenses/LGPL-3" 67 | 68 | License: CC0-1.0 69 | On Debian systems, the full text of the CC0 1.0 Universal 70 | License can be found in the file 71 | "/usr/share/common-licenses/CC0-1.0" 72 | -------------------------------------------------------------------------------- /debian/libzbd-dev.install: -------------------------------------------------------------------------------- 1 | usr/include/libzbd 2 | usr/lib/*/libzbd.a 3 | usr/lib/*/libzbd.so 4 | usr/lib/*/pkgconfig 5 | -------------------------------------------------------------------------------- /debian/libzbd2.install: -------------------------------------------------------------------------------- 1 | usr/lib/*/libzbd.so.* 2 | -------------------------------------------------------------------------------- /debian/libzbd2.symbols: -------------------------------------------------------------------------------- 1 | libzbd.so.2 libzbd2 #MINVER# 2 | ZBD_GLOBAL@ZBD_GLOBAL 1.1.0 3 | zbd_close@ZBD_GLOBAL 1.1.0 4 | zbd_device_is_zoned@ZBD_GLOBAL 1.1.0 5 | zbd_device_model_str@ZBD_GLOBAL 1.1.0 6 | zbd_get_info@ZBD_GLOBAL 2.0.2 7 | zbd_list_zones@ZBD_GLOBAL 1.1.0 8 | zbd_open@ZBD_GLOBAL 1.1.0 9 | zbd_report_zones@ZBD_GLOBAL 1.1.0 10 | zbd_set_log_level@ZBD_GLOBAL 1.1.0 11 | zbd_zone_cond_str@ZBD_GLOBAL 1.1.0 12 | zbd_zone_type_str@ZBD_GLOBAL 1.1.0 13 | zbd_zones_operation@ZBD_GLOBAL 1.1.0 14 | -------------------------------------------------------------------------------- /debian/not-installed: -------------------------------------------------------------------------------- 1 | usr/lib/*/libzbd.la 2 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | %: 4 | dh $@ 5 | -------------------------------------------------------------------------------- /debian/salsa-ci.yml: -------------------------------------------------------------------------------- 1 | # For more information on what jobs are run see: 2 | # https://salsa.debian.org/salsa-ci-team/pipeline 3 | # 4 | # To enable the jobs, go to your repository (at salsa.debian.org) 5 | # and click over Settings > CI/CD > Expand (in General pipelines). 6 | # In "Custom CI config path" write debian/salsa-ci.yml and click 7 | # in "Save Changes". The CI tests will run after the next commit. 8 | --- 9 | include: 10 | - https://salsa.debian.org/salsa-ci-team/pipeline/raw/master/salsa-ci.yml 11 | - https://salsa.debian.org/salsa-ci-team/pipeline/raw/master/pipeline-jobs.yml 12 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (quilt) 2 | -------------------------------------------------------------------------------- /debian/tests/control: -------------------------------------------------------------------------------- 1 | Test-Command: gzbd --help; gzbd-viewer --help 2 | Depends: @ 3 | Restrictions: superficial 4 | -------------------------------------------------------------------------------- /debian/upstream/metadata: -------------------------------------------------------------------------------- 1 | --- 2 | Bug-Database: https://github.com/westerndigitalcorporation/libzbd/issues 3 | Bug-Submit: https://github.com/westerndigitalcorporation/libzbd/issues/new 4 | Repository: https://github.com/westerndigitalcorporation/libzbd.git 5 | Repository-Browse: https://github.com/westerndigitalcorporation/libzbd 6 | -------------------------------------------------------------------------------- /debian/watch: -------------------------------------------------------------------------------- 1 | version=4 2 | 3 | opts="filenamemangle=s%(?:.*?)?v?(\d[\d.]*)\.tar\.gz%libzbd-$1.tar.gz%" \ 4 | https://github.com/westerndigitalcorporation/libzbd/tags \ 5 | (?:.*?/)?v?(\d[\d.]*)\.tar\.gz debian uupdate 6 | -------------------------------------------------------------------------------- /debian/zbd-utils.install: -------------------------------------------------------------------------------- 1 | usr/bin/* 2 | usr/share/* 3 | -------------------------------------------------------------------------------- /include/libzbd/zbd.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: LGPL-3.0-or-later 3 | * 4 | * Copyright (c) 2020 Western Digital Corporation or its affiliates. 5 | * 6 | * Authors: Damien Le Moal (damien.lemoal@wdc.com) 7 | * Ting Yao 8 | */ 9 | 10 | #ifndef _LIBZBD_H_ 11 | #define _LIBZBD_H_ 12 | 13 | #ifdef __cplusplus 14 | extern "C" { 15 | #endif 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | /** 25 | * @brief Zone types 26 | * 27 | * @ZBD_ZONE_TYPE_CNV: The zone has no write pointer and can be writen 28 | * randomly. Zone reset has no effect on the zone. 29 | * @ZBD_ZONE_TYPE_SWR: The zone must be written sequentially 30 | * @ZBD_ZONE_TYPE_SWP: The zone can be written randomly 31 | */ 32 | enum zbd_zone_type { 33 | ZBD_ZONE_TYPE_CNV = BLK_ZONE_TYPE_CONVENTIONAL, 34 | ZBD_ZONE_TYPE_SWR = BLK_ZONE_TYPE_SEQWRITE_REQ, 35 | ZBD_ZONE_TYPE_SWP = BLK_ZONE_TYPE_SEQWRITE_PREF, 36 | }; 37 | 38 | /** 39 | * @brief Zone conditions (state) 40 | * 41 | * @ZBD_ZONE_COND_NOT_WP: The zone has no write pointer, it is conventional. 42 | * @ZBD_ZONE_COND_EMPTY: The zone is empty. 43 | * @ZBD_ZONE_COND_IMP_OPEN: The zone is open, but not explicitly opened. 44 | * @ZBD_ZONE_COND_EXP_OPEN: The zones was explicitly opened by an 45 | * OPEN ZONE command. 46 | * @ZBD_ZONE_COND_CLOSED: The zone was [explicitly] closed after writing. 47 | * @ZBD_ZONE_COND_FULL: The zone is marked as full, possibly by a zone 48 | * FINISH ZONE command. 49 | * @ZBD_ZONE_COND_READONLY: The zone is read-only. 50 | * @ZBD_ZONE_COND_OFFLINE: The zone is offline (dead). 51 | */ 52 | enum zbd_zone_cond { 53 | ZBD_ZONE_COND_NOT_WP = BLK_ZONE_COND_NOT_WP, 54 | ZBD_ZONE_COND_EMPTY = BLK_ZONE_COND_EMPTY, 55 | ZBD_ZONE_COND_IMP_OPEN = BLK_ZONE_COND_IMP_OPEN, 56 | ZBD_ZONE_COND_EXP_OPEN = BLK_ZONE_COND_EXP_OPEN, 57 | ZBD_ZONE_COND_CLOSED = BLK_ZONE_COND_CLOSED, 58 | ZBD_ZONE_COND_FULL = BLK_ZONE_COND_FULL, 59 | ZBD_ZONE_COND_READONLY = BLK_ZONE_COND_READONLY, 60 | ZBD_ZONE_COND_OFFLINE = BLK_ZONE_COND_OFFLINE, 61 | }; 62 | 63 | /** 64 | * @brief Zone flags 65 | * 66 | * @ZBD_ZONE_RWP_RECOMMENDED: The zone should be reset. 67 | * @ZBD_ZONE_NON_SEQ: The zone is using non-sequential write resources. 68 | */ 69 | enum zbd_zone_flags { 70 | ZBD_ZONE_RWP_RECOMMENDED = (1U << 0), 71 | ZBD_ZONE_NON_SEQ_RESOURCES = (1U << 1), 72 | }; 73 | 74 | /** 75 | * @brief Zone descriptor data structure 76 | * 77 | * Provide information on a zone with all position and size values in bytes. 78 | */ 79 | struct zbd_zone { 80 | unsigned long long start; /* Zone start */ 81 | unsigned long long len; /* Zone length */ 82 | unsigned long long capacity; /* Zone capacity */ 83 | unsigned long long wp; /* Zone write pointer */ 84 | unsigned int flags; /* Zone state flags */ 85 | unsigned int type; /* Zone type */ 86 | unsigned int cond; /* Zone condition */ 87 | uint8_t reserved[20]; /* Padding to 64B */ 88 | } __attribute__((packed)); 89 | 90 | /** 91 | * @brief Library log levels 92 | */ 93 | enum zbd_log_level { 94 | ZBD_LOG_NONE = 0, /* Disable all messages */ 95 | ZBD_LOG_ERROR, /* Output details about errors */ 96 | ZBD_LOG_DEBUG, /* Debug-level messages */ 97 | }; 98 | 99 | /** 100 | * @brief Set the library log level 101 | * @param[in] log_level Library log level 102 | * 103 | * Set the library log level using the level name specified by \a log_level. 104 | * Log level are incremental: each level includes the levels preceding it. 105 | * Valid log level names are: 106 | * "none" : Silent operation (no messages) 107 | * "warning" : Print device level standard compliance problems 108 | * "error" : Print messages related to unexpected errors 109 | * "info" : Print normal information messages 110 | * "debug" : Verbose output decribing internally executed commands 111 | * The default level is "warning". 112 | */ 113 | extern void zbd_set_log_level(enum zbd_log_level); 114 | 115 | /** 116 | * @brief Block device zone models. 117 | */ 118 | enum zbd_dev_model { 119 | ZBD_DM_HOST_MANAGED = 1, 120 | ZBD_DM_HOST_AWARE, 121 | ZBD_DM_NOT_ZONED, 122 | }; 123 | 124 | /** 125 | * @brief Device information vendor id string maximum length. 126 | */ 127 | #define ZBD_VENDOR_ID_LENGTH 32 128 | 129 | /** 130 | * @brief Device information data structure 131 | * 132 | * Provide information on a device open using the \a zbd_open function. 133 | */ 134 | struct zbd_info { 135 | 136 | /** 137 | * Device vendor, model and firmware revision string. 138 | */ 139 | char vendor_id[ZBD_VENDOR_ID_LENGTH]; 140 | 141 | /** 142 | * Total number of 512B sectors of the device. 143 | */ 144 | unsigned long long nr_sectors; 145 | 146 | /** 147 | * Total number of logical blocks of the device. 148 | */ 149 | unsigned long long nr_lblocks; 150 | 151 | /** 152 | * Total number of physical blocks of the device. 153 | */ 154 | unsigned long long nr_pblocks; 155 | 156 | /** 157 | * Size in bytes of a zone. 158 | */ 159 | unsigned long long zone_size; 160 | 161 | /** 162 | * Size in 512B sectors of a zone. 163 | */ 164 | unsigned int zone_sectors; 165 | 166 | /** 167 | * Size in bytes of the device logical blocks. 168 | */ 169 | unsigned int lblock_size; 170 | 171 | /** 172 | * Size in bytes of the device physical blocks. 173 | */ 174 | unsigned int pblock_size; 175 | 176 | /** 177 | * Number of zones. 178 | */ 179 | unsigned int nr_zones; 180 | 181 | /** 182 | * Maximum number of explicitely open zones. A value of 0 means that 183 | * the device has no limit. A value of -1 means that the value is 184 | * unknown. 185 | */ 186 | unsigned int max_nr_open_zones; 187 | 188 | /** 189 | * Maximum number of active zones. A value of 0 means that the device 190 | * has no limit. A value of -1 means that the value is unknown. 191 | */ 192 | unsigned int max_nr_active_zones; 193 | 194 | /** 195 | * Device zone model. 196 | */ 197 | unsigned int model; 198 | 199 | /** 200 | * Padding to 128B. 201 | */ 202 | uint8_t reserved[36]; 203 | } __attribute__((packed)); 204 | 205 | /** 206 | * @brief Test if a device is a zoned block device 207 | * @param[in] filename Path to the device file 208 | * @param[in] info Address where to store the device information 209 | * 210 | * Test if a device supports the ZBD/ZAC command set. If \a fake is false, 211 | * only test physical devices. Otherwise, also test regular files and 212 | * regular block devices that may be in use with the fake backend driver 213 | * to create an emulated host-managed zoned block device. 214 | * If \a info is not NULL and the device is identified as a zoned 215 | * block device, the device information is returned at the address 216 | * specified by \a info. 217 | * 218 | * @return Returns a negative error code if the device test failed. 219 | * 1 is returned if the device is identified as a zoned zoned block device. 220 | * Otherwise, 0 is returned. 221 | */ 222 | extern int zbd_device_is_zoned(const char *filename); 223 | 224 | 225 | /** 226 | * @brief Open a ZBD device 227 | * @param[in] filename Path to a device file 228 | * @param[in] flags Device file open flags 229 | * @param[out] info Device information 230 | * 231 | * Opens the device specified by \a filename, and returns a file descriptor 232 | * number similarly to the regular open() system call. If @info is non-null, 233 | * information on the device is returned at the specified address. 234 | * 235 | * @return If the device is not a zoned block device, -ENXIO is returned. 236 | * Any other error code returned by open(2) can be returned as well. 237 | */ 238 | extern int zbd_open(const char *filename, int flags, struct zbd_info *info); 239 | 240 | /** 241 | * @brief Close a ZBD device 242 | * @param[in] fd File descriptor obtained with \a zbd_open 243 | * 244 | * Performs the equivalent to close(2) for a zoned block device open 245 | * using \a zbd_open. 246 | */ 247 | extern void zbd_close(int fd); 248 | 249 | /** 250 | * @brief Get a device information 251 | * @param[in] fd File descriptor obtained with \a zbd_open 252 | * @param[in] info Address of the information structure to fill 253 | * 254 | * Get information about an open device. The \a info parameter is used to 255 | * return a device information. \a info must be allocated by the caller. 256 | */ 257 | extern int zbd_get_info(int fd, struct zbd_info *info); 258 | 259 | /** 260 | * @brief Reporting options definitions 261 | * 262 | * Used to filter the zone information returned by the execution of a 263 | * REPORT ZONES command. Filtering is based on the value of the reporting 264 | * option and on the condition of the zones at the time of the execution of 265 | * the REPORT ZONES command. 266 | * 267 | * ZBD_RO_PARTIAL is not a filter: this reporting option can be combined 268 | * (or'ed) with any other filter option to limit the number of reported 269 | * zone information to the size of the REPORT ZONES command buffer. 270 | */ 271 | enum zbd_report_option { 272 | /* Report all zones */ 273 | ZBD_RO_ALL = 0x00, 274 | 275 | /* Report only empty zones */ 276 | ZBD_RO_EMPTY = 0x01, 277 | 278 | /* Report only implicitly open zones */ 279 | ZBD_RO_IMP_OPEN = 0x02, 280 | 281 | /* Report only explicitly open zones */ 282 | ZBD_RO_EXP_OPEN = 0x03, 283 | 284 | /* Report only closed zones */ 285 | ZBD_RO_CLOSED = 0x04, 286 | 287 | /* Report only full zones */ 288 | ZBD_RO_FULL = 0x05, 289 | 290 | /* Report only read-only zones */ 291 | ZBD_RO_RDONLY = 0x06, 292 | 293 | /* Report only offline zones */ 294 | ZBD_RO_OFFLINE = 0x07, 295 | 296 | /* Report only zones with reset recommended flag set */ 297 | ZBD_RO_RWP_RECOMMENDED = 0x10, 298 | 299 | /* Report only zones with the non-sequential resource used flag set */ 300 | ZBD_RO_NON_SEQ = 0x11, 301 | 302 | /* Report only conventional zones (non-write-pointer zones) */ 303 | ZBD_RO_NOT_WP = 0x3f, 304 | }; 305 | 306 | /** 307 | * @brief Get zone information 308 | * @param[in] fd File descriptor obtained with \a zbd_open 309 | * @param[in] ofst Byte offset from which to report zones 310 | * @param[in] len Maximum length in bytes from \a ofst of the device 311 | * capacity range to inspect for the report 312 | * @param[in] ro Reporting options 313 | * @param[in] zones Pointer to the array of zone information to fill 314 | * @param[out] nr_zones Number of zones in the array \a zones 315 | * 316 | * Get zone information of at most \a nr_zones zones in the range 317 | * [ofst..ofst+len] and matching the \a ro option. If \a len is 0, 318 | * at most \a nr_zones zones starting from \a ofst up to the end on the device 319 | * capacity will be reported. 320 | * Return the zone information obtained in the array \a zones and the number 321 | * of zones reported at the address specified by \a nr_zones. 322 | * The array \a zones must be allocated by the caller and \a nr_zones 323 | * must point to the size of the allocated array (number of zone information 324 | * structures in the array). The first zone reported will be the zone 325 | * containing or after \a ofst. The last zone reported will be the zone 326 | * containing or before \a ofst + \a len. 327 | * 328 | * @return Returns 0 on success and -1 otherwise. 329 | */ 330 | extern int zbd_report_zones(int fd, off_t ofst, off_t len, 331 | enum zbd_report_option ro, 332 | struct zbd_zone *zones, unsigned int *nr_zones); 333 | 334 | /** 335 | * @brief Get number of zones 336 | * @param[in] fd File descriptor obtained with \a zbd_open 337 | * @param[in] ofst Byte offset from which to report zones 338 | * @param[in] len Maximum length in bytes from \a ofst of the device 339 | * capacity range to inspect for the report 340 | * @param[in] ro Reporting options 341 | * @param[out] nr_zones The number of matching zones 342 | * 343 | * Similar to \a zbd_report_zones, but returns only the number of zones that 344 | * \a zbd_report_zones would have returned. This is useful to determine the 345 | * number of zones of a device to allocate an array of zone information 346 | * structures for use with \a zbd_report_zones. 347 | * 348 | * @return Returns 0 on success and -1 otherwise. 349 | */ 350 | static inline int zbd_report_nr_zones(int fd, off_t ofst, off_t len, 351 | enum zbd_report_option ro, 352 | unsigned int *nr_zones) 353 | { 354 | return zbd_report_zones(fd, ofst, len, ro, NULL, nr_zones); 355 | } 356 | 357 | /** 358 | * @brief Get zone information 359 | * @param[in] fd File descriptor obtained with \a zbd_open 360 | * @param[in] ofst Byte offset from which to report zones 361 | * @param[in] len Maximum length in bytes from \a ofst of the device 362 | * capacity range to inspect for the report 363 | * @param[in] ro Reporting options 364 | * @param[out] zones The array of zone information filled 365 | * @param[out] nr_zones Number of zones in the array \a zones 366 | * 367 | * Similar to \a zbd_report_zones, but also allocates an appropriatly sized 368 | * array of zone information structures and return the address of the array 369 | * at the address specified by \a zones. The size of the array allocated and 370 | * filled is returned at the address specified by \a nr_zones. Freeing of the 371 | * memory used by the array of zone information structures allocated by this 372 | * function is the responsability of the user. 373 | * 374 | * @return Returns 0 on success and -1 otherwise. 375 | * Returns -ENOMEM if memory could not be allocated for \a zones. 376 | */ 377 | extern int zbd_list_zones(int fd, off_t ofst, off_t len, 378 | enum zbd_report_option ro, 379 | struct zbd_zone **zones, unsigned int *nr_zones); 380 | 381 | /** 382 | * @brief Zone management operations. 383 | */ 384 | enum zbd_zone_op { 385 | /** 386 | * Reset zones write pointer. 387 | */ 388 | ZBD_OP_RESET = 0x01, 389 | /** 390 | * Explicitly open zones. 391 | */ 392 | ZBD_OP_OPEN = 0x02, 393 | /** 394 | * Close opened zones. 395 | */ 396 | ZBD_OP_CLOSE = 0x03, 397 | /** 398 | * Transition zones to full state. 399 | */ 400 | ZBD_OP_FINISH = 0x04, 401 | }; 402 | 403 | /** 404 | * @brief Execute an operation on a range of zones 405 | * @param[in] fd File descriptor obtained with \a zbd_open 406 | * @param[in] ofst Byte offset identifying the first zone to operate on 407 | * @param[in] len Maximum length in bytes from \a ofst of the set of zones 408 | * to operate on 409 | * @param[in] op The operation to perform 410 | * 411 | * Exexcute an operation on the range of zones defined by [ofst..ofst+len] 412 | * If \a len is 0, all zones from \a ofst will be processed. 413 | * The validity of the operation (reset, open, close or finish) depends on the 414 | * type and condition of the target zones. 415 | * 416 | * @return Returns 0 on success and -1 otherwise. 417 | */ 418 | extern int zbd_zones_operation(int fd, enum zbd_zone_op op, 419 | off_t ofst, off_t len); 420 | 421 | /** 422 | * @brief Reset the write pointer of a range of zones 423 | * @param[in] fd File descriptor obtained with \a zbd_open 424 | * @param[in] ofst Byte offset identifying the first zone to reset 425 | * @param[in] len Maximum length in bytes from \a ofst of the set of zones 426 | * to reset 427 | * 428 | * Resets the write pointer of the zones in the range [ofst..ofst+len]. 429 | * If \a len is 0, all zones from \a ofst will be processed. 430 | * 431 | * @return Returns 0 on success and -1 otherwise. 432 | */ 433 | static inline int zbd_reset_zones(int fd, off_t ofst, off_t len) 434 | { 435 | return zbd_zones_operation(fd, ZBD_OP_RESET, ofst, len); 436 | } 437 | 438 | /** 439 | * @brief Explicitly open a range of zones 440 | * @param[in] fd File descriptor obtained with \a zbd_open 441 | * @param[in] ofst Byte offset identifying the first zone to open 442 | * @param[in] len Maximum length in bytes from \a ofst of the set of zones 443 | * to open 444 | * 445 | * Explicitly open the zones in the range [ofst..ofst+len]. If \a len is 0, 446 | * all zones from \a ofst will be processed. 447 | * 448 | * @return Returns 0 on success and -1 otherwise. 449 | */ 450 | static inline int zbd_open_zones(int fd, off_t ofst, off_t len) 451 | { 452 | return zbd_zones_operation(fd, ZBD_OP_OPEN, ofst, len); 453 | } 454 | 455 | /** 456 | * @brief Close a range of zones 457 | * @param[in] fd File descriptor obtained with \a zbd_open 458 | * @param[in] ofst Byte offset identifying the first zone to close 459 | * @param[in] len Maximum length in bytes from \a ofst of the set of zones 460 | * to close 461 | * 462 | * Close the zones in the range [ofst..ofst+len]. 463 | * If \a len is 0, all zones from \a ofst will be processed. 464 | * 465 | * @return Returns 0 on success and -1 otherwise. 466 | */ 467 | static inline int zbd_close_zones(int fd, off_t ofst, off_t len) 468 | { 469 | return zbd_zones_operation(fd, ZBD_OP_CLOSE, ofst, len); 470 | } 471 | 472 | /** 473 | * @brief Finish a range of zones 474 | * @param[in] fd File descriptor obtained with \a zbd_open 475 | * @param[in] ofst Byte offset identifying the first zone to finish 476 | * @param[in] len Maximum length in bytes from \a ofst of the set of zones 477 | * to finish 478 | * 479 | * Finish the zones in the range [ofst..ofst+len]. 480 | * If \a len is 0, all zones from \a ofst will be processed. 481 | * 482 | * @return Returns 0 on success and -1 otherwise. 483 | */ 484 | static inline int zbd_finish_zones(int fd, off_t ofst, off_t len) 485 | { 486 | return zbd_zones_operation(fd, ZBD_OP_FINISH, ofst, len); 487 | } 488 | 489 | /** 490 | * Accessors 491 | */ 492 | #define zbd_zone_type(z) ((z)->type) 493 | #define zbd_zone_cnv(z) ((z)->type == ZBD_ZONE_TYPE_CNV) 494 | #define zbd_zone_swr(z) ((z)->type == ZBD_ZONE_TYPE_SWR) 495 | #define zbd_zone_swp(z) ((z)->type == ZBD_ZONE_TYPE_SWP) 496 | #define zbd_zone_seq(z) (zbd_zone_swr(z) || zbd_zone_swp(z)) 497 | 498 | #define zbd_zone_cond(z) ((z)->cond) 499 | #define zbd_zone_not_wp(z) ((z)->cond == ZBD_ZONE_COND_NOT_WP) 500 | #define zbd_zone_empty(z) ((z)->cond == ZBD_ZONE_COND_EMPTY) 501 | #define zbd_zone_imp_open(z) ((z)->cond == ZBD_ZONE_COND_IMP_OPEN) 502 | #define zbd_zone_exp_open(z) ((z)->cond == ZBD_ZONE_COND_EXP_OPEN) 503 | #define zbd_zone_is_open(z) (zbd_zone_imp_open(z) || zbd_zone_exp_open(z)) 504 | #define zbd_zone_closed(z) ((z)->cond == ZBD_ZONE_COND_CLOSED) 505 | #define zbd_zone_is_active(z) (zbd_zone_is_open(z) || zbd_zone_closed(z)) 506 | #define zbd_zone_full(z) ((z)->cond == ZBD_ZONE_COND_FULL) 507 | #define zbd_zone_rdonly(z) ((z)->cond == ZBD_ZONE_COND_READONLY) 508 | #define zbd_zone_offline(z) ((z)->cond == ZBD_ZONE_COND_OFFLINE) 509 | 510 | #define zbd_zone_start(z) ((z)->start) 511 | #define zbd_zone_len(z) ((z)->len) 512 | #define zbd_zone_capacity(z) ((z)->capacity) 513 | #define zbd_zone_wp(z) ((z)->wp) 514 | 515 | #define zbd_zone_flags(z) ((z)->flags) 516 | #define zbd_zone_rwp_recommended(z) ((z)->flags & ZBD_ZONE_RWP_RECOMMENDED) 517 | #define zbd_zone_non_seq_resources(z) ((z)->flags & ZBD_ZONE_NON_SEQ_RESOURCES) 518 | 519 | /** 520 | * @brief Returns a string describing a device zone model 521 | * @param[in] model Device model 522 | * @param[in] s Get abbreviated name 523 | * 524 | * Return a string (long or abbreviated) describing a device zone model. 525 | * 526 | * @return Device model string or NULL for an invalid model. 527 | */ 528 | extern const char *zbd_device_model_str(enum zbd_dev_model model, bool s); 529 | 530 | /** 531 | * @brief Returns a string describing a zone type 532 | * @param[in] z Zone descriptor 533 | * @param[in] s Get abbreviated zone type name 534 | * 535 | * Return a string (long or abbreviated) describing a zone type. 536 | * 537 | * @return Zone type string or NULL for an invalid zone type. 538 | */ 539 | extern const char *zbd_zone_type_str(struct zbd_zone *z, bool s); 540 | 541 | /** 542 | * @brief Returns a string describing a zone condition 543 | * @param[in] z Zone descriptor 544 | * @param[in] s Get abbreviated zone condition name 545 | * 546 | * Return a string (long or abbreviated) describing a zone condition. 547 | * 548 | * @return Zone type string or NULL for an invalid zone condition. 549 | */ 550 | extern const char *zbd_zone_cond_str(struct zbd_zone *z, bool s); 551 | 552 | #ifdef __cplusplus 553 | } 554 | #endif 555 | 556 | #endif /* _LIBZBD_H_ */ 557 | -------------------------------------------------------------------------------- /lib/Makefile.am: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: LGPL-3.0-or-later 2 | # 3 | # SPDX-FileCopyrightText: 2020 Western Digital Corporation or its affiliates. 4 | 5 | AM_CFLAGS = \ 6 | $(CFLAGS) \ 7 | -Wall -Wextra -Wno-unused-parameter \ 8 | -I$(top_srcdir)/include 9 | 10 | pkgconfdir = $(libdir)/pkgconfig 11 | pkgconf_DATA = libzbd.pc 12 | pkginclude_HEADERS = ../include/libzbd/zbd.h 13 | 14 | lib_LTLIBRARIES = libzbd.la 15 | 16 | EXTRA_DIST = exports 17 | 18 | CFILES = \ 19 | zbd.c \ 20 | zbd_utils.c 21 | 22 | HFILES = \ 23 | zbd.h 24 | 25 | libzbd_la_DEPENDENCIES = exports 26 | libzbd_la_SOURCES = $(CFILES) $(HFILES) 27 | libzbd_la_CFLAGS = $(AM_CFLAGS) -fPIC 28 | libzbd_la_LDFLAGS = \ 29 | -lpthread \ 30 | -Wl,--version-script,exports \ 31 | -version-number @LIBZBD_VERSION_LT@ 32 | 33 | 34 | -------------------------------------------------------------------------------- /lib/exports: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: LGPL-3.0-or-later 2 | # 3 | # SPDX-FileCopyrightText: 2020 Western Digital Corporation or its affiliates. 4 | 5 | ZBD_GLOBAL { 6 | global: 7 | zbd_set_log_level; 8 | zbd_device_is_zoned; 9 | zbd_open; 10 | zbd_close; 11 | zbd_get_info; 12 | zbd_report_zones; 13 | zbd_list_zones; 14 | zbd_zones_operation; 15 | zbd_device_model_str; 16 | zbd_zone_type_str; 17 | zbd_zone_cond_str; 18 | local: 19 | *; 20 | }; 21 | -------------------------------------------------------------------------------- /lib/libzbd.pc.in: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: LGPL-3.0-or-later 2 | # 3 | # SPDX-FileCopyrightText: 2020 Western Digital Corporation or its affiliates. 4 | 5 | prefix=@prefix@ 6 | exec_prefix=@exec_prefix@ 7 | libdir=@libdir@ 8 | includedir=@includedir@ 9 | 10 | Name: libzbd 11 | Description: A library simplifying management of zoned block devices 12 | Version: @PACKAGE_VERSION@ 13 | Cflags: -I${includedir} 14 | Libs: -L${libdir} -lzbd 15 | 16 | -------------------------------------------------------------------------------- /lib/zbd.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: LGPL-3.0-or-later 2 | /* 3 | * SPDX-FileCopyrightText: 2020 Western Digital Corporation or its affiliates. 4 | * 5 | * Authors: Damien Le Moal (damien.lemoal@wdc.com) 6 | * Ting Yao 7 | */ 8 | #include "zbd.h" 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | /* 21 | * Per fd device information. 22 | */ 23 | #define ZBD_FD_MAX 1024 24 | 25 | static struct zbd_info *zbd_fdi[ZBD_FD_MAX]; 26 | 27 | static inline struct zbd_info *zbd_get_fd(int fd) 28 | { 29 | if (fd < 0 || fd >= ZBD_FD_MAX) 30 | return NULL; 31 | 32 | return zbd_fdi[fd]; 33 | } 34 | 35 | static inline void zbd_put_fd(int fd) 36 | { 37 | free(zbd_fdi[fd]); 38 | zbd_fdi[fd] = NULL; 39 | } 40 | 41 | static int zbd_dev_path(const char *filename, char **path, char **devname) 42 | { 43 | char *p; 44 | 45 | /* Follow symlinks (required for device mapped devices) */ 46 | p = realpath(filename, NULL); 47 | if (!p) { 48 | zbd_error("%s: Failed to get real path %d (%s)\n", 49 | filename, errno, strerror(errno)); 50 | return -1; 51 | } 52 | 53 | *path = p; 54 | *devname = basename(p); 55 | 56 | return 0; 57 | } 58 | 59 | /* 60 | * Get a block device zone model. 61 | */ 62 | static enum zbd_dev_model zbd_get_dev_model(char *devname) 63 | { 64 | char str[128]; 65 | int ret; 66 | 67 | /* Check that this is a zoned block device */ 68 | ret = zbd_get_sysfs_attr_str(devname, "queue/zoned", 69 | str, sizeof(str)); 70 | if (ret) { 71 | long long val; 72 | 73 | /* 74 | * Assume old kernel or kernel without ZBD support enabled: try 75 | * a sysfs file that must exist for all block devices. If it is 76 | * found, then this is a regular non-zoned device. 77 | */ 78 | ret = zbd_get_sysfs_attr_int64(devname, 79 | "queue/logical_block_size", 80 | &val); 81 | if (ret == 0) 82 | return ZBD_DM_NOT_ZONED; 83 | return -1; 84 | } 85 | 86 | if (strcmp(str, "host-aware") == 0) 87 | return ZBD_DM_HOST_AWARE; 88 | if (strcmp(str, "host-managed") == 0) 89 | return ZBD_DM_HOST_MANAGED; 90 | if (strcmp(str, "none") == 0) 91 | return ZBD_DM_NOT_ZONED; 92 | 93 | return -1; 94 | } 95 | 96 | 97 | 98 | /* 99 | * Get max number of open/active zones. 100 | */ 101 | static void zbd_get_max_resources(char *devname, struct zbd_info *zbdi) 102 | { 103 | long long val; 104 | int ret; 105 | 106 | /* 107 | * According to max_open_zones/max_active_zones sysfs documentation, 108 | * a sysfs value of 0 means no limit. 109 | * 110 | * While the ZAC/ZBC standard has special treatment for unknown, 111 | * unknown is exported to sysfs as 0. 112 | * 113 | * Default both to unlimited, and set a limit if we managed to read 114 | * a limit from sysfs successfully. 115 | */ 116 | ret = zbd_get_sysfs_attr_int64(devname, "queue/max_open_zones", &val); 117 | if (ret) 118 | val = 0; 119 | zbdi->max_nr_open_zones = val; 120 | 121 | ret = zbd_get_sysfs_attr_int64(devname, "queue/max_active_zones", &val); 122 | if (ret) 123 | val = 0; 124 | zbdi->max_nr_active_zones = val; 125 | } 126 | 127 | /* 128 | * Get vendor ID. 129 | */ 130 | static int zbd_get_vendor_id(char *devname, struct zbd_info *zbdi) 131 | { 132 | char str[128]; 133 | int ret, n = 0; 134 | 135 | ret = zbd_get_sysfs_attr_str(devname, "device/vendor", 136 | str, sizeof(str)); 137 | if (!ret) 138 | n = snprintf(zbdi->vendor_id, ZBD_VENDOR_ID_LENGTH, 139 | "%s ", str); 140 | 141 | ret = zbd_get_sysfs_attr_str(devname, "device/model", 142 | str, sizeof(str)); 143 | if (!ret) 144 | n += snprintf(&zbdi->vendor_id[n], ZBD_VENDOR_ID_LENGTH - n, 145 | "%s ", str); 146 | 147 | ret = zbd_get_sysfs_attr_str(devname, "device/rev", 148 | str, sizeof(str)); 149 | if (!ret) 150 | n += snprintf(&zbdi->vendor_id[n], ZBD_VENDOR_ID_LENGTH - n, 151 | "%s", str); 152 | 153 | return n > 0; 154 | } 155 | 156 | /* 157 | * Get zone size in 512B sector unit. 158 | */ 159 | #ifdef BLKGETZONESZ 160 | static int zbd_get_zone_sectors(int fd, char *devname, __u32 *zone_sectors) 161 | { 162 | int ret; 163 | 164 | ret = ioctl(fd, BLKGETZONESZ, zone_sectors); 165 | if (ret) { 166 | zbd_error("ioctl BLKGETZONESZ failed %d (%s)\n", 167 | errno, strerror(errno)); 168 | return -1; 169 | } 170 | 171 | return 0; 172 | } 173 | #else 174 | static int zbd_get_zone_sectors(int fd, char *devname, __u32 *zone_sectors) 175 | { 176 | long long zs; 177 | int ret; 178 | 179 | ret = zbd_get_sysfs_attr_int64(devname, "queue/chunk_sectors", &zs); 180 | if (ret) { 181 | zbd_error("Get zone size from sysfs failed\n"); 182 | return -1; 183 | } 184 | 185 | if (zs >= UINT_MAX) { 186 | zbd_error("Invalid zone sectors %lld\n", zs); 187 | return -1; 188 | } 189 | 190 | *zone_sectors = zs; 191 | 192 | return 0; 193 | } 194 | #endif 195 | 196 | static int zbd_get_zone_size(int fd, char *devname, struct zbd_info *zbdi) 197 | { 198 | __u32 zone_sectors; 199 | int ret; 200 | 201 | ret = zbd_get_zone_sectors(fd, devname, &zone_sectors); 202 | if (ret) 203 | return ret; 204 | 205 | if (!zone_sectors) { 206 | zbd_error("Invalid 0 zone size\n"); 207 | return -1; 208 | } 209 | 210 | zbdi->zone_sectors = zone_sectors; 211 | zbdi->zone_size = (unsigned long long)zone_sectors << SECTOR_SHIFT; 212 | 213 | return 0; 214 | } 215 | 216 | /* 217 | * Get total number of zones. 218 | */ 219 | static int zbd_get_nr_zones(int fd, char *devname, struct zbd_info *zbdi) 220 | { 221 | __u32 nr_zones; 222 | 223 | #ifdef BLKGETNRZONES 224 | int ret = ioctl(fd, BLKGETNRZONES, &nr_zones); 225 | if (ret != 0) { 226 | zbd_error("ioctl BLKGETNRZONES failed %d (%s)\n", 227 | errno, strerror(errno)); 228 | return -1; 229 | } 230 | #else 231 | nr_zones = (zbdi->nr_sectors + zbdi->zone_sectors - 1) 232 | / zbdi->zone_sectors; 233 | #endif 234 | 235 | if (!nr_zones) { 236 | zbd_error("Invalid 0 number of zones\n"); 237 | return -1; 238 | } 239 | 240 | zbdi->nr_zones = nr_zones; 241 | 242 | return 0; 243 | } 244 | 245 | static struct zbd_info *zbd_do_get_info(int fd, char *devname) 246 | { 247 | unsigned long long size64; 248 | struct zbd_info *zbdi; 249 | int ret, size32; 250 | 251 | zbdi = malloc(sizeof(struct zbd_info)); 252 | if (!zbdi) 253 | return NULL; 254 | 255 | /* Get zone model */ 256 | zbdi->model = zbd_get_dev_model(devname); 257 | if (zbdi->model != ZBD_DM_HOST_AWARE && 258 | zbdi->model != ZBD_DM_HOST_MANAGED) { 259 | zbd_error("Invalid device zone model\n"); 260 | goto err; 261 | } 262 | 263 | /* Get logical block size */ 264 | ret = ioctl(fd, BLKSSZGET, &size32); 265 | if (ret != 0) { 266 | zbd_error("ioctl BLKSSZGET failed %d (%s)\n", 267 | errno, strerror(errno)); 268 | goto err; 269 | } 270 | zbdi->lblock_size = size32; 271 | if (zbdi->lblock_size <= 0) { 272 | zbd_error("invalid logical sector size %d\n", 273 | size32); 274 | goto err; 275 | } 276 | 277 | /* Get physical block size */ 278 | ret = ioctl(fd, BLKPBSZGET, &size32); 279 | if (ret != 0) { 280 | zbd_error("ioctl BLKPBSZGET failed %d (%s)\n", 281 | errno, strerror(errno)); 282 | goto err; 283 | } 284 | zbdi->pblock_size = size32; 285 | if (zbdi->pblock_size <= 0) { 286 | zbd_error("Invalid physical sector size %d\n", 287 | size32); 288 | goto err; 289 | } 290 | 291 | /* Get capacity (Bytes) */ 292 | ret = ioctl(fd, BLKGETSIZE64, &size64); 293 | if (ret != 0) { 294 | zbd_error("ioctl BLKGETSIZE64 failed %d (%s)\n", 295 | errno, strerror(errno)); 296 | goto err; 297 | } 298 | zbdi->nr_sectors = size64 >> SECTOR_SHIFT; 299 | 300 | zbdi->nr_lblocks = size64 / zbdi->lblock_size; 301 | if (!zbdi->nr_lblocks) { 302 | zbd_error("Invalid capacity (logical blocks)\n"); 303 | goto err; 304 | } 305 | 306 | zbdi->nr_pblocks = size64 / zbdi->pblock_size; 307 | if (!zbdi->nr_pblocks) { 308 | zbd_error("Invalid capacity (physical blocks)\n"); 309 | goto err; 310 | } 311 | 312 | /* Get zone size */ 313 | ret = zbd_get_zone_size(fd, devname, zbdi); 314 | if (ret) 315 | goto err; 316 | 317 | /* Get number of zones */ 318 | ret = zbd_get_nr_zones(fd, devname, zbdi); 319 | if (ret) 320 | goto err; 321 | 322 | /* Get max number of open/active zones */ 323 | zbd_get_max_resources(devname, zbdi); 324 | 325 | /* Finish setting */ 326 | if (!zbd_get_vendor_id(devname, zbdi)) 327 | strncpy(zbdi->vendor_id, 328 | "Unknown", ZBD_VENDOR_ID_LENGTH - 1); 329 | 330 | return zbdi; 331 | err: 332 | free(zbdi); 333 | return NULL; 334 | } 335 | 336 | /** 337 | * zbd_device_is_zoned - Test if a physical device is zoned. 338 | */ 339 | int zbd_device_is_zoned(const char *filename) 340 | { 341 | char *path = NULL, *devname = NULL; 342 | enum zbd_dev_model model; 343 | struct stat st; 344 | int ret; 345 | 346 | ret = zbd_dev_path(filename, &path, &devname); 347 | if (ret) 348 | return ret; 349 | 350 | /* Check device */ 351 | if (stat(path, &st) != 0) { 352 | zbd_error("Stat device file failed %d (%s)\n", 353 | errno, strerror(errno)); 354 | free(path); 355 | return 0; 356 | } 357 | 358 | if (!S_ISBLK(st.st_mode)) { 359 | free(path); 360 | return 0; 361 | } 362 | 363 | model = zbd_get_dev_model(devname); 364 | 365 | free(path); 366 | 367 | return model == ZBD_DM_HOST_AWARE || 368 | model == ZBD_DM_HOST_MANAGED; 369 | } 370 | 371 | /** 372 | * zbd_open - open a ZBD device 373 | */ 374 | int zbd_open(const char *filename, int flags, struct zbd_info *info) 375 | { 376 | char *path = NULL, *devname = NULL; 377 | struct zbd_info *zbdi; 378 | int ret, fd; 379 | 380 | if (!zbd_device_is_zoned(filename)) { 381 | zbd_error("Device %s is not a zoned block device\n", 382 | filename); 383 | return -1; 384 | } 385 | 386 | ret = zbd_dev_path(filename, &path, &devname); 387 | if (ret) 388 | return ret; 389 | 390 | /* Open block device */ 391 | fd = open(path, flags | O_LARGEFILE); //direct 392 | if (fd < 0) { 393 | zbd_error("open %s failed %d (%s)\n", 394 | filename, errno, strerror(errno)); 395 | goto err; 396 | } 397 | 398 | /* Get device information */ 399 | zbdi = zbd_do_get_info(fd, devname); 400 | if (!zbdi) 401 | goto err; 402 | 403 | zbd_fdi[fd] = zbdi; 404 | if (info) 405 | memcpy(info, zbdi, sizeof(struct zbd_info)); 406 | 407 | free(path); 408 | 409 | return fd; 410 | 411 | err: 412 | if (fd >= 0) { 413 | close(fd); 414 | fd = -1; 415 | } 416 | 417 | free(path); 418 | 419 | return fd; 420 | } 421 | 422 | /** 423 | * zbd_close - close a ZBD Device 424 | */ 425 | void zbd_close(int fd) 426 | { 427 | struct zbd_info *zbdi = zbd_get_fd(fd); 428 | 429 | if (!zbdi) { 430 | zbd_error("Invalid file descriptor %d\n\n", fd); 431 | return; 432 | } 433 | 434 | close(fd); 435 | zbd_put_fd(fd); 436 | } 437 | 438 | /** 439 | * zbd_get_info - Get a ZBD device information 440 | */ 441 | int zbd_get_info(int fd, struct zbd_info *info) 442 | { 443 | struct zbd_info *zbdi = zbd_get_fd(fd); 444 | 445 | if (!zbdi) { 446 | zbd_error("Invalid file descriptor %d\n\n", fd); 447 | return -1; 448 | } 449 | 450 | if (!info) 451 | return -1; 452 | 453 | memcpy(info, zbdi, sizeof(struct zbd_info)); 454 | 455 | return 0; 456 | } 457 | 458 | /* 459 | * zbd_should_report_zone - Test if a zone must be reported. 460 | */ 461 | static bool zbd_should_report_zone(struct zbd_zone *zone, 462 | enum zbd_report_option ro) 463 | { 464 | switch (ro) { 465 | case ZBD_RO_ALL: 466 | return true; 467 | case ZBD_RO_NOT_WP: 468 | return zbd_zone_not_wp(zone); 469 | case ZBD_RO_EMPTY: 470 | return zbd_zone_empty(zone); 471 | case ZBD_RO_IMP_OPEN: 472 | return zbd_zone_imp_open(zone); 473 | case ZBD_RO_EXP_OPEN: 474 | return zbd_zone_exp_open(zone); 475 | case ZBD_RO_CLOSED: 476 | return zbd_zone_closed(zone); 477 | case ZBD_RO_FULL: 478 | return zbd_zone_full(zone); 479 | case ZBD_RO_RDONLY: 480 | return zbd_zone_rdonly(zone); 481 | case ZBD_RO_OFFLINE: 482 | return zbd_zone_offline(zone); 483 | case ZBD_RO_RWP_RECOMMENDED: 484 | return zbd_zone_rwp_recommended(zone); 485 | case ZBD_RO_NON_SEQ: 486 | return zbd_zone_non_seq_resources(zone); 487 | default: 488 | return false; 489 | } 490 | } 491 | 492 | /* 493 | * zbd_parse_zone - Fill a zone descriptor 494 | */ 495 | static inline void zbd_parse_zone(struct zbd_zone *zone, struct blk_zone *blkz, 496 | struct blk_zone_report *rep) 497 | { 498 | zone->start = blkz->start << SECTOR_SHIFT; 499 | zone->len = blkz->len << SECTOR_SHIFT; 500 | if (rep->flags & BLK_ZONE_REP_CAPACITY) 501 | zone->capacity = blkz->capacity << SECTOR_SHIFT; 502 | else 503 | zone->capacity = zone->len; 504 | zone->wp = blkz->wp << SECTOR_SHIFT; 505 | 506 | zone->type = blkz->type; 507 | zone->cond = blkz->cond; 508 | zone->flags = 0; 509 | if (blkz->reset) 510 | zone->flags |= ZBD_ZONE_RWP_RECOMMENDED; 511 | if (blkz->non_seq) 512 | zone->flags |= ZBD_ZONE_NON_SEQ_RESOURCES; 513 | } 514 | 515 | #define ZBD_REPORT_MAX_NR_ZONE 8192 516 | 517 | /** 518 | * zbd_report_zones - Get zone information 519 | */ 520 | int zbd_report_zones(int fd, off_t ofst, off_t len, enum zbd_report_option ro, 521 | struct zbd_zone *zones, unsigned int *nr_zones) 522 | { 523 | struct zbd_info *zbdi = zbd_get_fd(fd); 524 | unsigned long long zone_size_mask, end; 525 | struct blk_zone_report *rep; 526 | size_t rep_size; 527 | unsigned int rep_nr_zones; 528 | unsigned int nrz, n = 0, i = 0; 529 | struct blk_zone *blkz; 530 | struct zbd_zone z; 531 | int ret = 0; 532 | 533 | if (!zbdi) { 534 | zbd_error("Invalid file descriptor %d\n\n", fd); 535 | return -1; 536 | } 537 | 538 | /* 539 | * To get zone reports, we need zones and nr_zones. 540 | * To get only the number of zones, we need only nr_zones. 541 | */ 542 | if ((!zones && !nr_zones) || (zones && !nr_zones)) 543 | return -1; 544 | 545 | /* 546 | * When reporting only the number of zones (zones == NULL case), 547 | * ignore the value pointed by nr_zones. 548 | */ 549 | if (zones) { 550 | nrz = *nr_zones; 551 | if (!nrz) 552 | return 0; 553 | } else { 554 | nrz = 0; 555 | } 556 | 557 | zone_size_mask = zbdi->zone_size - 1; 558 | if (len == 0) 559 | len = zbdi->nr_sectors << SECTOR_SHIFT; 560 | 561 | end = ((ofst + len + zone_size_mask) & (~zone_size_mask)) 562 | >> SECTOR_SHIFT; 563 | if (end > zbdi->nr_sectors) 564 | end = zbdi->nr_sectors; 565 | 566 | ofst = (ofst & (~zone_size_mask)) >> SECTOR_SHIFT; 567 | if ((unsigned long long)ofst >= zbdi->nr_sectors) { 568 | *nr_zones = 0; 569 | return 0; 570 | } 571 | 572 | /* Get all zones information */ 573 | rep_nr_zones = ZBD_REPORT_MAX_NR_ZONE; 574 | if (nrz && nrz < rep_nr_zones) 575 | rep_nr_zones = nrz; 576 | rep_size = sizeof(struct blk_zone_report) + 577 | sizeof(struct blk_zone) * rep_nr_zones; 578 | rep = (struct blk_zone_report *)malloc(rep_size); 579 | if (!rep) { 580 | zbd_error("%d: No memory for array of zones\n\n", fd); 581 | return -ENOMEM; 582 | } 583 | 584 | blkz = (struct blk_zone *)(rep + 1); 585 | while ((!nrz || n < nrz) && (unsigned long long)ofst < end) { 586 | 587 | memset(rep, 0, rep_size); 588 | rep->sector = ofst; 589 | rep->nr_zones = rep_nr_zones; 590 | 591 | ret = ioctl(fd, BLKREPORTZONE, rep); 592 | if (ret != 0) { 593 | ret = -errno; 594 | zbd_error("%d: ioctl BLKREPORTZONE at %llu failed %d (%s)\n", 595 | fd, (unsigned long long)ofst, 596 | errno, strerror(errno)); 597 | goto out; 598 | } 599 | 600 | if (!rep->nr_zones) 601 | break; 602 | 603 | for (i = 0; i < rep->nr_zones; i++) { 604 | if ((nrz && (n >= nrz)) || 605 | ((unsigned long long)ofst >= end)) 606 | break; 607 | 608 | zbd_parse_zone(&z, &blkz[i], rep); 609 | if (zbd_should_report_zone(&z, ro)) { 610 | if (zones) 611 | memcpy(&zones[n], &z, sizeof(z)); 612 | n++; 613 | } 614 | 615 | ofst = blkz[i].start + blkz[i].len; 616 | } 617 | } 618 | 619 | /* Return number of zones */ 620 | *nr_zones = n; 621 | 622 | out: 623 | free(rep); 624 | 625 | return ret; 626 | } 627 | 628 | /** 629 | * zbd_list_zones - Get zone information 630 | */ 631 | int zbd_list_zones(int fd, off_t ofst, off_t len, 632 | enum zbd_report_option ro, 633 | struct zbd_zone **pzones, unsigned int *pnr_zones) 634 | { 635 | struct zbd_info *zbdi = zbd_get_fd(fd); 636 | struct zbd_zone *zones = NULL; 637 | unsigned int nr_zones = 0; 638 | int ret; 639 | 640 | if (!zbdi) { 641 | zbd_error("Invalid file descriptor %d\n\n", fd); 642 | return -1; 643 | } 644 | 645 | /* Get number of zones */ 646 | ret = zbd_report_nr_zones(fd, ofst, len, ro, &nr_zones); 647 | if (ret < 0) 648 | return ret; 649 | 650 | if (!nr_zones) 651 | goto out; 652 | 653 | /* Allocate zone array */ 654 | zones = (struct zbd_zone *) calloc(nr_zones, sizeof(struct zbd_zone)); 655 | if (!zones) 656 | return -ENOMEM; 657 | 658 | /* Get zones information */ 659 | ret = zbd_report_zones(fd, ofst, len, ro, zones, &nr_zones); 660 | if (ret != 0) { 661 | zbd_error("%d: zbd_report_zones failed %d\n", 662 | fd, ret); 663 | free(zones); 664 | return ret; 665 | } 666 | 667 | out: 668 | *pzones = zones; 669 | *pnr_zones = nr_zones; 670 | return 0; 671 | } 672 | 673 | /* 674 | * BLKOPENZONE, BLKCLOSEZONE and BLKFINISHZONE ioctl commands 675 | * were introduced with kernel 5.5. If they are not defined on the 676 | * current system, manually define these operations here to generate 677 | * code portable to newer kernels. 678 | */ 679 | #ifndef BLKOPENZONE 680 | #define BLKOPENZONE _IOW(0x12, 134, struct blk_zone_range) 681 | #endif 682 | #ifndef BLKCLOSEZONE 683 | #define BLKCLOSEZONE _IOW(0x12, 135, struct blk_zone_range) 684 | #endif 685 | #ifndef BLKFINISHZONE 686 | #define BLKFINISHZONE _IOW(0x12, 136, struct blk_zone_range) 687 | #endif 688 | 689 | #ifndef ENOIOCTLCMD 690 | #define ENOIOCTLCMD 515 691 | #endif 692 | 693 | /** 694 | * zbd_zone_operation - Execute an operation on a zone 695 | */ 696 | int zbd_zones_operation(int fd, enum zbd_zone_op op, off_t ofst, off_t len) 697 | { 698 | struct zbd_info *zbdi = zbd_get_fd(fd); 699 | unsigned long long zone_size_mask, end; 700 | struct blk_zone_range range; 701 | const char *ioctl_name; 702 | unsigned long ioctl_op; 703 | int ret; 704 | 705 | if (!zbdi) { 706 | zbd_error("Invalid file descriptor %d\n\n", fd); 707 | return -1; 708 | } 709 | 710 | zone_size_mask = zbdi->zone_size - 1; 711 | if (len == 0) 712 | len = zbdi->nr_sectors << SECTOR_SHIFT; 713 | 714 | end = ((ofst + len + zone_size_mask) & (~zone_size_mask)) 715 | >> SECTOR_SHIFT; 716 | if (end > zbdi->nr_sectors) 717 | end = zbdi->nr_sectors; 718 | 719 | /* Check the operation */ 720 | switch (op) { 721 | case ZBD_OP_RESET: 722 | ioctl_name = "BLKRESETZONE"; 723 | ioctl_op = BLKRESETZONE; 724 | break; 725 | case ZBD_OP_OPEN: 726 | ioctl_name = "BLKOPENZONE"; 727 | ioctl_op = BLKOPENZONE; 728 | break; 729 | case ZBD_OP_CLOSE: 730 | ioctl_name = "BLKCLOSEZONE"; 731 | ioctl_op = BLKCLOSEZONE; 732 | break; 733 | case ZBD_OP_FINISH: 734 | ioctl_name = "BLKFINISHZONE"; 735 | ioctl_op = BLKFINISHZONE; 736 | break; 737 | default: 738 | zbd_error("Invalid zone operation 0x%x\n", op); 739 | errno = EINVAL; 740 | return -1; 741 | } 742 | 743 | ofst = (ofst & (~zone_size_mask)) >> SECTOR_SHIFT; 744 | if ((unsigned long long)ofst >= zbdi->nr_sectors || 745 | end == (unsigned long long)ofst) 746 | return 0; 747 | 748 | /* Execute the operation */ 749 | range.sector = ofst; 750 | range.nr_sectors = end - ofst; 751 | ret = ioctl(fd, ioctl_op, &range); 752 | if (ret != 0) { 753 | if (errno == ENOIOCTLCMD || errno == ENOTTY) { 754 | zbd_error("ioctl %s is not supported\n", 755 | ioctl_name); 756 | errno = ENOTSUP; 757 | } else { 758 | zbd_error("ioctl %s failed %d (%s)\n", 759 | ioctl_name, errno, strerror(errno)); 760 | } 761 | return -1; 762 | } 763 | 764 | return 0; 765 | } 766 | -------------------------------------------------------------------------------- /lib/zbd.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: LGPL-3.0-or-later */ 2 | /* 3 | * SPDX-FileCopyrightText: 2020 Western Digital Corporation or its affiliates. 4 | * 5 | * Authors: Damien Le Moal (damien.lemoal@wdc.com) 6 | * Ting Yao 7 | */ 8 | #ifndef __LIBZBD_INTERNAL_H__ 9 | #define __LIBZBD_INTERNAL_H__ 10 | 11 | #define _GNU_SOURCE 12 | 13 | #include "config.h" 14 | #include "libzbd/zbd.h" 15 | 16 | #include 17 | #include 18 | 19 | /* 20 | * 512B sector size shift. 21 | */ 22 | #define SECTOR_SHIFT 9 23 | 24 | /* 25 | * Handle kernel zone capacity support 26 | */ 27 | #ifndef HAVE_BLK_ZONE_REP_V2 28 | #define BLK_ZONE_REP_CAPACITY (1 << 0) 29 | 30 | struct blk_zone_v2 { 31 | __u64 start; /* Zone start sector */ 32 | __u64 len; /* Zone length in number of sectors */ 33 | __u64 wp; /* Zone write pointer position */ 34 | __u8 type; /* Zone type */ 35 | __u8 cond; /* Zone condition */ 36 | __u8 non_seq; /* Non-sequential write resources active */ 37 | __u8 reset; /* Reset write pointer recommended */ 38 | __u8 resv[4]; 39 | __u64 capacity; /* Zone capacity in number of sectors */ 40 | __u8 reserved[24]; 41 | }; 42 | #define blk_zone blk_zone_v2 43 | 44 | struct blk_zone_report_v2 { 45 | __u64 sector; 46 | __u32 nr_zones; 47 | __u32 flags; 48 | struct blk_zone zones[0]; 49 | }; 50 | #define blk_zone_report blk_zone_report_v2 51 | #endif /* HAVE_BLK_ZONE_REP_V2 */ 52 | 53 | extern int zbd_get_sysfs_attr_int64(char *devname, const char *attr, 54 | long long *val); 55 | extern int zbd_get_sysfs_attr_str(char *devname, const char *attr, 56 | char *val, int val_len); 57 | 58 | /* 59 | * Library log level (per thread). 60 | */ 61 | extern __thread int zbd_log_level; 62 | 63 | #define zbd_print(stream,format,args...) \ 64 | do { \ 65 | fprintf((stream), format, ## args); \ 66 | fflush(stream); \ 67 | } while (0) 68 | 69 | #define zbd_print_level(l,stream,format,args...) \ 70 | do { \ 71 | if ((l) <= zbd_log_level) \ 72 | zbd_print((stream), "(libzbd) " format, \ 73 | ## args); \ 74 | } while (0) 75 | 76 | #define zbd_error(format,args...) \ 77 | zbd_print_level(ZBD_LOG_ERROR, stderr, "[ERROR] " format, ##args) 78 | 79 | #define zbd_debug(format,args...) \ 80 | zbd_print_level(ZBD_LOG_DEBUG, stdout, format, ##args) 81 | 82 | #define zbd_panic(format,args...) \ 83 | do { \ 84 | zbd_print_level(ZBD_LOG_ERROR, \ 85 | stderr, \ 86 | "[PANIC] " format, \ 87 | ##args); \ 88 | assert(0); \ 89 | } while (0) 90 | 91 | #define zbd_assert(cond) \ 92 | do { \ 93 | if (!(cond)) \ 94 | zbd_panic("Condition %s failed\n", \ 95 | # cond); \ 96 | } while (0) 97 | 98 | #endif 99 | 100 | /* __LIBZBD_INTERNAL_H__ */ 101 | -------------------------------------------------------------------------------- /lib/zbd_utils.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: LGPL-3.0-or-later 2 | /* 3 | * SPDX-FileCopyrightText: 2020 Western Digital Corporation or its affiliates. 4 | * 5 | * Authors: Damien Le Moal (damien.lemoal@wdc.com) 6 | * Ting Yao 7 | */ 8 | #include "zbd.h" 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | /* 17 | * Log level. 18 | */ 19 | __thread int zbd_log_level = ZBD_LOG_NONE; 20 | 21 | void zbd_set_log_level(enum zbd_log_level level) 22 | { 23 | switch (level) { 24 | case ZBD_LOG_NONE: 25 | case ZBD_LOG_ERROR: 26 | case ZBD_LOG_DEBUG: 27 | zbd_log_level = level; 28 | break; 29 | default: 30 | fprintf(stderr, "libzbd: Invalid log level %d\n", 31 | level); 32 | break; 33 | } 34 | } 35 | 36 | /* 37 | * To handle string conversions. 38 | */ 39 | struct zbd_str { 40 | unsigned int val; 41 | const char *str; 42 | const char *str_short; 43 | }; 44 | 45 | static const char *zbd_get_str(struct zbd_str *str, unsigned int val, bool s) 46 | { 47 | unsigned int i = 0; 48 | 49 | while (str[i].val != UINT_MAX) { 50 | if (str[i].val == val) 51 | break; 52 | i++; 53 | } 54 | 55 | if (s) 56 | return str[i].str_short; 57 | 58 | return str[i].str; 59 | } 60 | 61 | static struct zbd_str zbd_dm_str[] = { 62 | { ZBD_DM_HOST_MANAGED, "host-managed", "HM" }, 63 | { ZBD_DM_HOST_AWARE, "host-aware", "HA" }, 64 | { ZBD_DM_NOT_ZONED, "not-zoned", "NZ" }, 65 | { UINT_MAX, "unknown", "??" }, 66 | }; 67 | 68 | /** 69 | * zbd_device_model_str - Returns a device zone model name 70 | */ 71 | const char *zbd_device_model_str(enum zbd_dev_model model, bool s) 72 | { 73 | return zbd_get_str(zbd_dm_str, model, s); 74 | } 75 | 76 | static struct zbd_str zbd_ztype_str[] = { 77 | { ZBD_ZONE_TYPE_CNV, "conventional", "cnv" }, 78 | { ZBD_ZONE_TYPE_SWR, "seq-write-required", "swr" }, 79 | { ZBD_ZONE_TYPE_SWP, "seq-write-preferred", "swp" }, 80 | { UINT_MAX, "unknown", "???" } 81 | }; 82 | 83 | /** 84 | * zbd_zone_type_str - returns a string describing a zone type 85 | */ 86 | const char *zbd_zone_type_str(struct zbd_zone *z, bool s) 87 | { 88 | return zbd_get_str(zbd_ztype_str, z->type, s); 89 | } 90 | 91 | static struct zbd_str zbd_zcond_str[] = { 92 | { ZBD_ZONE_COND_NOT_WP, "not-write-pointer", "nw" }, 93 | { ZBD_ZONE_COND_EMPTY, "empty", "em" }, 94 | { ZBD_ZONE_COND_FULL, "full", "fu" }, 95 | { ZBD_ZONE_COND_IMP_OPEN, "open-implicit", "oi" }, 96 | { ZBD_ZONE_COND_EXP_OPEN, "open-explicit", "oe" }, 97 | { ZBD_ZONE_COND_CLOSED, "closed", "cl" }, 98 | { ZBD_ZONE_COND_READONLY, "read-only", "ro" }, 99 | { ZBD_ZONE_COND_OFFLINE, "offline", "ol" }, 100 | { UINT_MAX, "unknown", "??" } 101 | }; 102 | 103 | /** 104 | * zbd_zone_cond_str - Returns a string describing a zone condition 105 | */ 106 | const char *zbd_zone_cond_str(struct zbd_zone *z, bool s) 107 | { 108 | return zbd_get_str(zbd_zcond_str, z->cond, s); 109 | } 110 | 111 | /* 112 | * Strip a string of trailing spaces and carriage return. 113 | */ 114 | static int zbd_str_strip(char *str) 115 | { 116 | int i = strlen(str) - 1; 117 | 118 | while (i >= 0) { 119 | if (str[i] == ' ' || 120 | str[i] == '\t' || 121 | str[i] == '\r' || 122 | str[i] == '\n') { 123 | str[i] = '\0'; 124 | i--; 125 | } else { 126 | break; 127 | } 128 | } 129 | 130 | return i + 1; 131 | } 132 | 133 | static int zbd_get_sysfs_attr(char *devname, const char *attr, 134 | char *str, int str_len) 135 | { 136 | char attr_path[128]; 137 | FILE *file; 138 | int ret = 0; 139 | 140 | snprintf(attr_path, sizeof(attr_path), "/sys/block/%s/%s", 141 | devname, attr); 142 | file = fopen(attr_path, "r"); 143 | if (!file) 144 | return -ENOENT; 145 | 146 | if (!fgets(str, str_len, file)) { 147 | ret = -EINVAL; 148 | goto close; 149 | } 150 | 151 | if (!zbd_str_strip(str)) 152 | ret = -EINVAL; 153 | 154 | close: 155 | fclose(file); 156 | 157 | return ret; 158 | } 159 | 160 | int zbd_get_sysfs_attr_int64(char *devname, const char *attr, long long *val) 161 | { 162 | char str[128]; 163 | int ret; 164 | 165 | ret = zbd_get_sysfs_attr(devname, attr, str, 128); 166 | if (ret) 167 | return ret; 168 | 169 | *val = atoll(str); 170 | 171 | return 0; 172 | } 173 | 174 | int zbd_get_sysfs_attr_str(char *devname, const char *attr, 175 | char *val, int val_len) 176 | { 177 | return zbd_get_sysfs_attr(devname, attr, val, val_len); 178 | } 179 | -------------------------------------------------------------------------------- /libzbd.spec: -------------------------------------------------------------------------------- 1 | Name: %{pkg_name} 2 | Version: %{pkg_version} 3 | Release: 1%{?dist} 4 | Summary: A library to control zoned block devices 5 | 6 | License: LGPLv3+ and GPLv3+ 7 | URL: https://github.com/westerndigitalcorporation/%{name} 8 | Source0: %{url}/releases/download/v%{version}/%{name}-%{version}.tar.gz 9 | 10 | BuildRequires: desktop-file-utils 11 | BuildRequires: gtk3-devel 12 | BuildRequires: autoconf 13 | BuildRequires: automake 14 | BuildRequires: libtool 15 | BuildRequires: make 16 | BuildRequires: gcc 17 | 18 | %description 19 | libzbd is a library providing functions simplifying the management and 20 | use of zoned block devices using the kernel ioctl interface. 21 | 22 | # Development headers package 23 | %package devel 24 | Summary: Development header files for libzbd 25 | Requires: %{name}%{?_isa} = %{version}-%{release} 26 | 27 | %description devel 28 | This package provides development header files for libzbd. 29 | 30 | # Command line tools package 31 | %package cli-tools 32 | Summary: Command line tools using libzbd 33 | Requires: %{name}%{?_isa} = %{version}-%{release} 34 | 35 | %description cli-tools 36 | This package provides command line tools using libzbd. 37 | 38 | # Graphic tools package 39 | %package gtk-tools 40 | Summary: GTK tools using libzbd 41 | Requires: %{name}%{?_isa} = %{version}-%{release} 42 | 43 | %description gtk-tools 44 | This package provides GTK-based graphical tools using libzbd. 45 | 46 | %prep 47 | %autosetup 48 | 49 | %build 50 | sh autogen.sh 51 | %configure --libdir="%{_libdir}" --includedir="%{_includedir}" 52 | %make_build 53 | 54 | %install 55 | %make_install PREFIX=%{_prefix} 56 | chmod -x ${RPM_BUILD_ROOT}%{_mandir}/man8/*.8* 57 | 58 | find ${RPM_BUILD_ROOT} -name '*.la' -delete 59 | 60 | desktop-file-validate %{buildroot}/%{_datadir}/applications/gzbd.desktop 61 | desktop-file-validate %{buildroot}/%{_datadir}/applications/gzbd-viewer.desktop 62 | 63 | %ldconfig_scriptlets 64 | 65 | %files 66 | %{_libdir}/*.so.* 67 | %exclude %{_libdir}/*.a 68 | %exclude %{_libdir}/pkgconfig/*.pc 69 | %license LICENSES/LGPL-3.0-or-later.txt 70 | %doc README.md 71 | 72 | %files devel 73 | %{_includedir}/* 74 | %{_libdir}/*.so 75 | %{_libdir}/pkgconfig/*.pc 76 | %license LICENSES/LGPL-3.0-or-later.txt 77 | 78 | %files cli-tools 79 | %{_bindir}/zbd 80 | %{_mandir}/man8/zbd.8* 81 | %license LICENSES/GPL-3.0-or-later.txt 82 | 83 | %files gtk-tools 84 | %{_bindir}/gzbd 85 | %{_datadir}/polkit-1/actions/org.gnome.gzbd.policy 86 | %{_datadir}/applications/gzbd.desktop 87 | %{_datadir}/pixmaps/gzbd.png 88 | %{_bindir}/gzbd-viewer 89 | %{_datadir}/polkit-1/actions/org.gnome.gzbd-viewer.policy 90 | %{_datadir}/applications/gzbd-viewer.desktop 91 | %{_datadir}/pixmaps/gzbd-viewer.png 92 | %{_mandir}/man8/gzbd.8* 93 | %{_mandir}/man8/gzbd-viewer.8* 94 | %license LICENSES/GPL-3.0-or-later.txt 95 | 96 | %changelog 97 | * Tue Jan 31 2023 Damien Le Moal 2.0.4 98 | - Update to version 2.0.4 99 | -------------------------------------------------------------------------------- /m4/dontremove: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: CC0-1.0 2 | # 3 | # SPDX-FileCopyrightText: 2020 Western Digital Corporation or its affiliates. 4 | 5 | the only purpose of this file is to keep the m4 directory in git 6 | -------------------------------------------------------------------------------- /tools/Makefile.am: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: LGPL-3.0-or-later 2 | # 3 | # SPDX-FileCopyrightText: 2021 Western Digital Corporation or its affiliates. 4 | 5 | SUBDIRS = . $(subdirs) 6 | 7 | AM_CFLAGS = \ 8 | $(CFLAGS) \ 9 | -Wall -Wextra -Wno-unused-parameter \ 10 | -I$(top_srcdir)/include 11 | 12 | libzbd_ldadd = $(top_builddir)/lib/libzbd.la 13 | 14 | bin_PROGRAMS = 15 | dist_man8_MANS = 16 | 17 | include cli/Makefile.am 18 | 19 | if BUILD_GUI 20 | 21 | polkitdir = $(datadir)/polkit-1/actions 22 | polkit_action_in_FILES = 23 | 24 | desktopdir = $(datadir)/applications 25 | desktop_in_FILES = 26 | 27 | pixmapdir = $(datadir)/pixmaps 28 | pixmap_FILES = 29 | 30 | bin_subst = sed -e 's,[@]bindir[@],$(bindir),g' 31 | 32 | include gui/Makefile.am 33 | include viewer/Makefile.am 34 | 35 | polkit_action_FILES = $(polkit_action_in_FILES:.policy.in=.policy) 36 | polkit_DATA = $(polkit_action_FILES) 37 | 38 | desktop_FILES = $(desktop_in_FILES:.desktop.in=.desktop) 39 | desktop_DATA = $(desktop_FILES) 40 | 41 | pixmap_DATA = $(pixmap_FILES) 42 | 43 | CLEANFILES = $(desktop_FILES) $(polkit_action_FILES) 44 | 45 | EXTRA_DIST = \ 46 | $(polkit_action_in_FILES) \ 47 | $(desktop_in_FILES) \ 48 | $(pixmap_FILES) 49 | 50 | endif 51 | -------------------------------------------------------------------------------- /tools/cli/Makefile.am: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-3.0-or-later 2 | # 3 | # SPDX-FileCopyrightText: 2020 Western Digital Corporation or its affiliates. 4 | 5 | bin_PROGRAMS += zbd 6 | zbd_SOURCES = \ 7 | cli/zbd.c \ 8 | cli/zbd_dump.c \ 9 | cli/zbd.h 10 | zbd_LDADD = $(libzbd_ldadd) 11 | 12 | dist_man8_MANS += cli/zbd.8 13 | -------------------------------------------------------------------------------- /tools/cli/zbd.8: -------------------------------------------------------------------------------- 1 | .\" SPDX-License-Identifier: GPL-3.0-or-later 2 | .\" SPDX-FileCopyrightText: 2020, Western Digital Corporation or its affiliates. 3 | .\" Written by Damien Le Moal 4 | .\" 5 | .TH ZBD 8 6 | .SH NAME 7 | zbd \- manage zoned block devices 8 | 9 | .SH SYNOPSIS 10 | .B zbd 11 | .I command 12 | [options] 13 | .I device 14 | 15 | .SH DESCRIPTION 16 | .B zbd 17 | is used to manipulate zones of a zoned block device. Zoned block devies are 18 | block devices that support the SCSI Zoned Block Commands (ZBC), 19 | ATA Zoned-device ATA Commands (ZAC) or NVMe Zoned NameSpace commands (ZNS). 20 | The zones to operate on can be specified using the offset and length options. 21 | .PP 22 | The 23 | .I device 24 | argument must be the pathname of the target zoned block device. 25 | 26 | .SH COMMANDS 27 | 28 | By default, all commands will operate from the zone at device offset 0 and 29 | operate on all zones. The common options \fB-ofst\fP and \fB-len\fP can be 30 | used to modify the operation range of commands. 31 | 32 | .SS report 33 | The command \fBzbd report\fP is used to obtain and display the device zone 34 | information. 35 | .PP 36 | By default, the command will report all zones from the start of the device 37 | up to the last zone of the device. Options may be used to modify this behavior, 38 | changing the starting zone or the size of the report. 39 | 40 | .B Report output 41 | .TS 42 | tab(:); 43 | l l. 44 | Zone:Zone number 45 | type:Type of the zone 46 | ofst:Zone start offset in Bytes 47 | len:Zone length in Bytes 48 | cap:Zone usable capacity in Bytes 49 | wp:Zone write pointer position in Bytes 50 | cond:Zone condition 51 | non_seq:Non-sequential write resources active 52 | reset:Reset write pointer recommended 53 | .TE 54 | 55 | .B Zone types 56 | .TS 57 | tab(:); 58 | l l. 59 | cnv:Conventional 60 | swr:Sequential write required 61 | swp:Sequential write preferred 62 | ???:Unknown (should not be reported) 63 | .TE 64 | 65 | .B Zone conditions 66 | .TS 67 | tab(:); 68 | l l. 69 | nw:Not write pointer 70 | em:Empty 71 | fu:Full 72 | oe:Explicitly opened 73 | oi:Implicitly opened 74 | cl:Closed 75 | ol:Offline 76 | ro:Read only 77 | ??:Reserved conditions (should not be reported) 78 | .TE 79 | 80 | .SS reset 81 | The command \fBzbd reset\fP is used to reset the write pointer of one or 82 | more zones. 83 | 84 | .SS open 85 | The command \fBzbd open\fP is used to explicitly open one or more zones. 86 | 87 | .SS close 88 | The command \fBzbd close\fP is used to close one or more zones. 89 | 90 | .SS finish 91 | The command \fBzbd finish\fP is used to finish (transition to full) one 92 | or more zones. 93 | 94 | .SS dump 95 | Save the zone information and zone data of a zoned device to files. The 96 | files are by default saved in the current working directory. A different 97 | output path can be specified using the option \fB-d\fP. The file names are 98 | prefixed by default using the device base name. The device zone information 99 | is saved in the file \fI_zone_info.dump\fP and the device zone data 100 | is saved in the file \fI_zone_data.dump\fP. The file name prefix 101 | can be changed using the option \fB-f\fP. 102 | 103 | .PP 104 | The \fBdump\fP command will save the data of all conventional zones and the 105 | written data of all sequential zones in the operation range. The options 106 | \fB-ofst\fP and \fB-len\fP can be used to limit in size the operation range. 107 | 108 | .PP 109 | The zone information file always saves zone information for all zones of the 110 | device, regardless of the specified operation range. This file can be specified 111 | in place of a device name with the \fBreport\fP command to inspect its content. 112 | 113 | .SS restore 114 | Set a zoned block device zone status and zone data according to the zone 115 | information and zoned data saved in files generated using the \fBdump\fP 116 | command. The resulting state of the target device will be identical to that 117 | of the device used to generate the dump files. The \fBrestore\fP command 118 | will only operate on the zone range that was used with the \fBdump\fP 119 | command. The target zoned block device must be compatible with the zoned 120 | block device used as the source for the dump operation. That is, the target 121 | device must have the same capacity and zone configuration (zone type, zone 122 | size and zone capacity) as the source device. 123 | 124 | .PP 125 | The path and name prefix of the dump files to use for restoring a device 126 | can be changed using the options \fB-d\fP and \fB-f\fP. 127 | 128 | .SH OPTIONS 129 | Options applicable to all commands are as follows. 130 | .TP 131 | .BR \-v 132 | Verbose mode (for debug). 133 | .TP 134 | .BR \-i 135 | Display device information before executing the command. 136 | .TP 137 | .BR "\-ofst " \fIoffset\fP 138 | Start offset in Bytes of the first zone to operate on. 139 | .TP 140 | .BR "\-len " \fIlength\fP 141 | Length in Bytes of the range of zones to operate on. 142 | .TP 143 | .BR \-h , " \-\-help" 144 | Display help text and exit. 145 | .TP 146 | Options applicable only to the \fBzbd report\fP command are as follows. 147 | .TP 148 | .BR \-csv 149 | Output zone information using the comma-separated (csv) format. 150 | .TP 151 | .BR \-n 152 | Display the number of zones that are to be reported. 153 | .TP 154 | .BR \-c 155 | Display the total capacity of all reported zones. When used with the 156 | \fB-n\fP and \fB-csv\fP options, the number of zones is output first. 157 | .TP 158 | .BR "\-ro " \fIfilter\fP 159 | Sepcify reporting option to filter the zone report. \fIfilter\fP 160 | can be one of the following. 161 | .TS 162 | tab(:); 163 | l l. 164 | em:Empty zones 165 | oi:Implicitly opened zones 166 | oe:Explicitly opened zones 167 | cl:Closed zones 168 | fu:Full zones 169 | ro:Read only zones 170 | ol:Offline zones 171 | nw:Conventional zones 172 | ns:Non_seq write resource zones 173 | rw:Reset write pointer recommended zones 174 | .TE 175 | 176 | .SH AUTHOR 177 | .nf 178 | Damien Le Moal 179 | .fi 180 | 181 | .SH SEE ALSO 182 | .BR blkzone (8) 183 | 184 | .SH AVAILABILITY 185 | The zbd command is part of the libzbd library available from 186 | .UR https://\:github.com\:/westerndigitalcorporation\:/libzbd 187 | .UE . 188 | -------------------------------------------------------------------------------- /tools/cli/zbd.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | /* 3 | * SPDX-FileCopyrightText: 2020 Western Digital Corporation or its affiliates. 4 | * 5 | * Authors: Damien Le Moal (damien.lemoal@wdc.com) 6 | * Ting Yao 7 | */ 8 | #include "zbd.h" 9 | 10 | static int zbd_mgmt(int fd, struct zbd_opts *opts) 11 | { 12 | int ret; 13 | 14 | /* Check zone range */ 15 | if (opts->ofst % opts->dev_info.zone_size || 16 | opts->len % opts->dev_info.zone_size) { 17 | fprintf(stderr, "Invalid unaligned offset/length\n"); 18 | return 1; 19 | } 20 | 21 | switch (opts->cmd) { 22 | case ZBD_RESET: 23 | ret = zbd_reset_zones(fd, opts->ofst, opts->len); 24 | break; 25 | case ZBD_OPEN: 26 | ret = zbd_open_zones(fd, opts->ofst, opts->len); 27 | break; 28 | case ZBD_CLOSE: 29 | ret = zbd_close_zones(fd, opts->ofst, opts->len); 30 | break; 31 | case ZBD_FINISH: 32 | ret = zbd_finish_zones(fd, opts->ofst, opts->len); 33 | break; 34 | default: 35 | fprintf(stderr, "Invalid operation\n"); 36 | return 1; 37 | } 38 | 39 | if (ret) 40 | fprintf(stderr, "Zone operation failed %d (%s)\n", 41 | errno, strerror(errno)); 42 | 43 | return ret; 44 | } 45 | 46 | static void zbd_print_zone(struct zbd_opts *opts, struct zbd_zone *z) 47 | { 48 | unsigned int zno = zbd_zone_start(z) / opts->dev_info.zone_size; 49 | 50 | if (opts->rep_csv) { 51 | printf("%05u, %u, %014llu, %014llu, %014llu, %014llu, 0x%01x, %01d, %01d\n", 52 | zno, 53 | (unsigned int)zbd_zone_type(z), 54 | zbd_zone_start(z) / opts->unit, 55 | zbd_zone_len(z) / opts->unit, 56 | zbd_zone_capacity(z) / opts->unit, 57 | zbd_zone_wp(z) / opts->unit, 58 | zbd_zone_cond(z), 59 | zbd_zone_non_seq_resources(z) ? 1 : 0, 60 | zbd_zone_rwp_recommended(z) ? 1 : 0); 61 | return; 62 | } 63 | 64 | if (zbd_zone_cnv(z)) { 65 | printf("Zone %05u: %s, ofst %014llu, len %014llu, cap %014llu\n", 66 | zno, 67 | zbd_zone_type_str(z, true), 68 | zbd_zone_start(z) / opts->unit, 69 | zbd_zone_len(z) / opts->unit, 70 | zbd_zone_capacity(z) / opts->unit); 71 | return; 72 | } 73 | 74 | if (zbd_zone_seq(z)) { 75 | printf("Zone %05u: %s, ofst %014llu, len %014llu, cap %014llu, " 76 | "wp %014llu, %s, non_seq %01d, reset %01d\n", 77 | zno, 78 | zbd_zone_type_str(z, true), 79 | zbd_zone_start(z) / opts->unit, 80 | zbd_zone_len(z) / opts->unit, 81 | zbd_zone_capacity(z) / opts->unit, 82 | zbd_zone_wp(z) / opts->unit, 83 | zbd_zone_cond_str(z, true), 84 | zbd_zone_non_seq_resources(z) ? 1 : 0, 85 | zbd_zone_rwp_recommended(z) ? 1 : 0); 86 | return; 87 | } 88 | 89 | printf("Zone %05u: unknown type 0x%01x, ofst %014llu, len %014llu\n", 90 | zno, zbd_zone_type(z), 91 | zbd_zone_start(z) / opts->unit, 92 | zbd_zone_len(z) / opts->unit); 93 | } 94 | 95 | static int zbd_report(int fd, struct zbd_opts *opts) 96 | { 97 | struct zbd_zone *zones = NULL; 98 | unsigned int i, nz; 99 | int ret; 100 | 101 | /* Allocate zone array */ 102 | nz = (opts->len + opts->dev_info.zone_size - 1) / 103 | opts->dev_info.zone_size; 104 | if (!nz) 105 | return 0; 106 | 107 | zones = (struct zbd_zone *) calloc(nz, sizeof(struct zbd_zone)); 108 | if (!zones) { 109 | fprintf(stderr, "No memory\n"); 110 | return 1; 111 | } 112 | 113 | if (!opts->rep_dump) { 114 | /* Get zone information from the device */ 115 | ret = zbd_report_zones(fd, opts->ofst, opts->len, opts->rep_opt, 116 | zones, &nz); 117 | if (ret != 0) { 118 | fprintf(stderr, "zbd_report_zones() failed %d\n", ret); 119 | ret = 1; 120 | goto out; 121 | } 122 | } else { 123 | /* Get zone information from the dump file */ 124 | ret = zbd_dump_report_zones(fd, opts, zones, &nz); 125 | if (ret != 0) { 126 | ret = 1; 127 | goto out; 128 | } 129 | } 130 | 131 | if (opts->rep_num_zones) { 132 | if (opts->rep_csv) { 133 | if (!opts->rep_capacity) 134 | printf("%u\n", nz); 135 | else 136 | printf("%u, ", nz); 137 | } else { 138 | printf("%u zones\n", nz); 139 | } 140 | if (!opts->rep_capacity) 141 | goto out; 142 | } 143 | 144 | if (opts->rep_capacity) { 145 | unsigned long long capacity = 0; 146 | 147 | for (i = 0; i < nz; i++) 148 | capacity += zbd_zone_capacity(&zones[i]) / opts->unit; 149 | 150 | if (opts->rep_csv) { 151 | printf("%llu\n", capacity); 152 | } else { 153 | if (opts->unit != 1) 154 | printf("%llu x %zu B total zone capacity\n", 155 | capacity, opts->unit); 156 | else 157 | printf("%llu B total zone capacity\n", 158 | capacity); 159 | } 160 | goto out; 161 | } 162 | 163 | if (opts->rep_csv) 164 | printf("zone num, type, ofst, len, cap, wp, cond, non_seq, reset\n"); 165 | for (i = 0; i < nz; i++) 166 | zbd_print_zone(opts, &zones[i]); 167 | 168 | out: 169 | free(zones); 170 | return ret; 171 | } 172 | 173 | struct zbd_action { 174 | int (*action)(int, struct zbd_opts *); 175 | int flags; 176 | }; 177 | 178 | static struct zbd_action zact[] = { 179 | { zbd_report, O_RDONLY }, /* ZBD_REPORT */ 180 | { zbd_mgmt, O_WRONLY }, /* ZBD_RESET */ 181 | { zbd_mgmt, O_WRONLY }, /* ZBD_OPEN */ 182 | { zbd_mgmt, O_WRONLY }, /* ZBD_CLOSE */ 183 | { zbd_mgmt, O_WRONLY }, /* ZBD_FINISH */ 184 | { zbd_dump, O_RDONLY }, /* ZBD_DUMP */ 185 | { zbd_restore, O_RDWR | O_DIRECT }, /* ZBD_RESTORE */ 186 | }; 187 | 188 | static void zbd_print_dev_info(struct zbd_opts *opts) 189 | { 190 | if (opts->cmd == ZBD_REPORT && opts->rep_csv) 191 | return; 192 | 193 | printf("Device %s:\n", opts->dev_path); 194 | 195 | printf(" Vendor ID: %s\n", 196 | opts->dev_info.vendor_id); 197 | printf(" Zone model: %s\n", 198 | zbd_device_model_str(opts->dev_info.model, false)); 199 | printf(" Capacity: %.03F GB (%llu 512-bytes sectors)\n", 200 | (double)(opts->dev_info.nr_sectors << 9) / 1000000000, 201 | opts->dev_info.nr_sectors); 202 | printf(" Logical blocks: %llu blocks of %u B\n", 203 | opts->dev_info.nr_lblocks, opts->dev_info.lblock_size); 204 | printf(" Physical blocks: %llu blocks of %u B\n", 205 | opts->dev_info.nr_pblocks, opts->dev_info.pblock_size); 206 | printf(" Zones: %u zones of %.1F MB\n", 207 | opts->dev_info.nr_zones, 208 | (double)opts->dev_info.zone_size / 1048576.0); 209 | 210 | printf(" Maximum number of open zones: "); 211 | if (opts->dev_info.max_nr_open_zones == 0) 212 | printf("no limit\n"); 213 | else 214 | printf("%u\n", opts->dev_info.max_nr_open_zones); 215 | 216 | printf(" Maximum number of active zones: "); 217 | if (opts->dev_info.max_nr_active_zones == 0) 218 | printf("no limit\n"); 219 | else 220 | printf("%u\n", opts->dev_info.max_nr_active_zones); 221 | } 222 | 223 | static int zbd_usage(char *cmd) 224 | { 225 | printf("Usage: %s [options] \n" 226 | "Commands:\n" 227 | " report : Get zone information from a device or from\n" 228 | " a zone information dump file\n" 229 | " reset : Reset zone(s) of a device\n" 230 | " open : Explicitly open zone(s) of a device\n" 231 | " close : Close zone(s) of a device\n" 232 | " finish : Finish zone(s) of a device\n" 233 | " dump : Dump a device zone information and zone data to\n" 234 | " files (see -d and -f options).\n" 235 | " restore : Restore a device zones status and data from dump\n" 236 | " files (see -d and -f options).\n" 237 | "Common options:\n" 238 | " -v : Verbose mode (for debug)\n" 239 | " -i : Display device information\n" 240 | " -ofst : Start offset of the first zone of the\n" 241 | " target range (default: 0)\n" 242 | " -len : Size of the zone range to operate on\n" 243 | " (default: device capacity)\n" 244 | " -u : Size unit to use for ofst and len options,\n" 245 | " and for displaying zone report results.\n" 246 | " (default: 1)\n" 247 | "Report command options:\n" 248 | " -csv : Use csv output format\n" 249 | " -n : Only output the number of zones reported\n" 250 | " -c : Only output the total capacity of zones reported\n" 251 | " -ro : Specify a zone report option. Possible values are:\n" 252 | " * \"em\": empty zones\n" 253 | " * \"oi\": implicitly open zones\n" 254 | " * \"oe\": explicitly open zones\n" 255 | " * \"cl\": closed zones\n" 256 | " * \"fu\": full zones\n" 257 | " * \"ro\": read-only zones\n" 258 | " * \"ol\": offline zones\n" 259 | " * \"nw\": conventional zones\n" 260 | " * \"ns\": non-seq write resource zones\n" 261 | " * \"rw\": reset-wp recommended zones\n" 262 | "dump and restore commands options:\n" 263 | " -d : Path where to save dump files.\n" 264 | " -f : Name prefix for the dump files. If not\n" 265 | " specified, the device base name is used\n" 266 | " as a dump file name prefix\n", 267 | cmd); 268 | return 1; 269 | } 270 | 271 | int main(int argc, char **argv) 272 | { 273 | struct zbd_opts opts; 274 | bool dev_info = false; 275 | int dev_fd = 0, i, ret = 1; 276 | long long capacity; 277 | char dev_path[PATH_MAX]; 278 | 279 | memset(&opts, 0, sizeof(struct zbd_opts)); 280 | opts.rep_opt = ZBD_RO_ALL; 281 | opts.rep_dump = false; 282 | opts.unit = 1; 283 | 284 | /* Parse options */ 285 | if (argc < 3) 286 | return zbd_usage(argv[0]); 287 | 288 | if (strcmp(argv[1], "report") == 0) { 289 | opts.cmd = ZBD_REPORT; 290 | } else if (strcmp(argv[1], "reset") == 0) { 291 | opts.cmd = ZBD_RESET; 292 | } else if (strcmp(argv[1], "open") == 0) { 293 | opts.cmd = ZBD_OPEN; 294 | } else if (strcmp(argv[1], "close") == 0) { 295 | opts.cmd = ZBD_CLOSE; 296 | } else if (strcmp(argv[1], "finish") == 0) { 297 | opts.cmd = ZBD_FINISH; 298 | } else if (strcmp(argv[1], "dump") == 0) { 299 | opts.cmd = ZBD_DUMP; 300 | } else if (strcmp(argv[1], "restore") == 0) { 301 | opts.cmd = ZBD_RESTORE; 302 | } else { 303 | fprintf(stderr, "Invalid command \"%s\"\n", argv[1]); 304 | return 1; 305 | } 306 | 307 | for (i = 2; i < (argc - 1); i++) { 308 | 309 | /* 310 | * Common options. 311 | */ 312 | if (strcmp(argv[i], "-v") == 0) { 313 | 314 | zbd_set_log_level(ZBD_LOG_DEBUG); 315 | 316 | } else if (strcmp(argv[i], "-i") == 0) { 317 | 318 | dev_info = true; 319 | 320 | } else if (strcmp(argv[i], "-ofst") == 0) { 321 | 322 | if (i >= (argc - 1)) { 323 | fprintf(stderr, "Invalid command line\n"); 324 | return 1; 325 | } 326 | i++; 327 | 328 | opts.ofst = strtoll(argv[i], NULL, 10); 329 | 330 | } else if (strcmp(argv[i], "-len") == 0) { 331 | 332 | if (i >= (argc - 1)) { 333 | fprintf(stderr, "Invalid command line\n"); 334 | return 1; 335 | } 336 | i++; 337 | 338 | opts.len = strtoll(argv[i], NULL, 10); 339 | 340 | } else if (strcmp(argv[i], "-u") == 0) { 341 | 342 | if (i >= (argc - 1)) { 343 | fprintf(stderr, "Invalid command line\n"); 344 | return 1; 345 | } 346 | i++; 347 | 348 | opts.unit = strtoll(argv[i], NULL, 10); 349 | 350 | /* 351 | * Report zones options. 352 | */ 353 | } else if (strcmp(argv[i], "-csv") == 0) { 354 | 355 | opts.rep_csv = true; 356 | 357 | } else if (strcmp(argv[i], "-n") == 0) { 358 | 359 | opts.rep_num_zones = true; 360 | 361 | } else if (strcmp(argv[i], "-c") == 0) { 362 | 363 | opts.rep_capacity = true; 364 | 365 | } else if (strcmp(argv[i], "-ro") == 0) { 366 | 367 | if (i >= (argc - 1)) { 368 | fprintf(stderr, "Invalid command line\n"); 369 | return 1; 370 | } 371 | i++; 372 | 373 | if (strcmp(argv[i], "em") == 0) { 374 | opts.rep_opt = ZBD_RO_EMPTY; 375 | } else if (strcmp(argv[i], "oi") == 0) { 376 | opts.rep_opt = ZBD_RO_IMP_OPEN; 377 | } else if (strcmp(argv[i], "oe") == 0) { 378 | opts.rep_opt = ZBD_RO_EXP_OPEN; 379 | } else if (strcmp(argv[i], "cl") == 0) { 380 | opts.rep_opt = ZBD_RO_CLOSED; 381 | } else if (strcmp(argv[i], "fu") == 0) { 382 | opts.rep_opt = ZBD_RO_FULL; 383 | } else if (strcmp(argv[i], "ro") == 0) { 384 | opts.rep_opt = ZBD_RO_RDONLY; 385 | } else if (strcmp(argv[i], "ol") == 0) { 386 | opts.rep_opt = ZBD_RO_OFFLINE; 387 | } else if (strcmp(argv[i], "rw") == 0) { 388 | opts.rep_opt = ZBD_RO_RWP_RECOMMENDED; 389 | } else if (strcmp(argv[i], "ns") == 0) { 390 | opts.rep_opt = ZBD_RO_NON_SEQ; 391 | } else if (strcmp(argv[i], "nw") == 0) { 392 | opts.rep_opt = ZBD_RO_NOT_WP; 393 | } else { 394 | fprintf(stderr, 395 | "Unknown report option \"%s\"\n", 396 | argv[i]); 397 | return 1; 398 | } 399 | 400 | /* 401 | * Dump and restore command options. 402 | */ 403 | } else if (strcmp(argv[i], "-d") == 0) { 404 | 405 | if (i >= (argc - 1)) { 406 | fprintf(stderr, "Invalid command line\n"); 407 | return 1; 408 | } 409 | i++; 410 | 411 | opts.dump_path = argv[i]; 412 | 413 | } else if (strcmp(argv[i], "-f") == 0) { 414 | 415 | if (i >= (argc - 1)) { 416 | fprintf(stderr, "Invalid command line\n"); 417 | return 1; 418 | } 419 | i++; 420 | 421 | opts.dump_prefix = argv[i]; 422 | 423 | } else if (argv[i][0] == '-') { 424 | 425 | fprintf(stderr, "Unknown option \"%s\"\n", argv[i]); 426 | return 1; 427 | 428 | } else { 429 | break; 430 | } 431 | 432 | } 433 | 434 | if (i != (argc - 1)) { 435 | fprintf(stderr, "No device specified\n"); 436 | return 1; 437 | } 438 | 439 | if (!realpath(argv[i], dev_path)) { 440 | fprintf(stderr, "Invalid device path %s\n", argv[i]); 441 | return 1; 442 | } 443 | opts.dev_path = dev_path; 444 | 445 | /* 446 | * Special case for zone report using zone info dump file. 447 | */ 448 | if (opts.cmd == ZBD_REPORT) { 449 | dev_fd = zbd_open_dump(&opts); 450 | if (dev_fd < 0) 451 | return 1; 452 | } 453 | 454 | if (!dev_fd) { 455 | /* Open device */ 456 | dev_fd = zbd_open(opts.dev_path, 457 | zact[opts.cmd].flags | O_LARGEFILE, 458 | &opts.dev_info); 459 | if (dev_fd < 0) { 460 | fprintf(stderr, "Open %s failed (%s)\n", 461 | opts.dev_path, strerror(errno)); 462 | return 1; 463 | } 464 | } 465 | 466 | /* Check unit, offset and length */ 467 | capacity = (long long)opts.dev_info.nr_sectors << 9; 468 | if (opts.unit > 1 && 469 | (opts.unit > opts.dev_info.zone_size || opts.unit % 512)) { 470 | fprintf(stderr, "Invalid unit\n"); 471 | ret = 1; 472 | goto out; 473 | } 474 | 475 | if (opts.ofst % 512 || opts.len % 512) { 476 | fprintf(stderr, "Invalid unaligned offset/length\n"); 477 | ret = 1; 478 | goto out; 479 | } 480 | 481 | if (opts.ofst >= capacity) { 482 | ret = 0; 483 | goto out; 484 | } 485 | 486 | if (!opts.len) 487 | opts.len = capacity; 488 | if (opts.ofst + opts.len > capacity) 489 | opts.len = capacity - opts.ofst; 490 | 491 | if (dev_info) 492 | zbd_print_dev_info(&opts); 493 | 494 | ret = zact[opts.cmd].action(dev_fd, &opts); 495 | if (ret) 496 | ret = 1; 497 | 498 | out: 499 | if (!opts.rep_dump) 500 | zbd_close(dev_fd); 501 | else 502 | close(dev_fd); 503 | 504 | return ret; 505 | } 506 | 507 | -------------------------------------------------------------------------------- /tools/cli/zbd.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | /* 3 | * SPDX-FileCopyrightText: 2021 Western Digital Corporation or its affiliates. 4 | * 5 | * Authors: Damien Le Moal (damien.lemoal@wdc.com) 6 | */ 7 | #ifndef _ZBD_TOOL_H_ 8 | #define _ZBD_TOOL_H_ 9 | 10 | #define _LARGEFILE64_SOURCE 11 | #define _GNU_SOURCE 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include "libzbd/zbd.h" 26 | 27 | enum zbd_cmd { 28 | ZBD_REPORT, 29 | ZBD_RESET, 30 | ZBD_OPEN, 31 | ZBD_CLOSE, 32 | ZBD_FINISH, 33 | ZBD_DUMP, 34 | ZBD_RESTORE, 35 | }; 36 | 37 | /* 38 | * Command line options and device information. 39 | */ 40 | struct zbd_opts { 41 | /* Common options */ 42 | char *dev_path; 43 | char *dump_path; 44 | char *dump_prefix; 45 | struct zbd_info dev_info; 46 | enum zbd_cmd cmd; 47 | long long ofst; 48 | long long len; 49 | size_t unit; 50 | 51 | /* Report zones options */ 52 | bool rep_csv; 53 | bool rep_num_zones; 54 | bool rep_capacity; 55 | bool rep_dump; 56 | enum zbd_report_option rep_opt; 57 | }; 58 | 59 | /* 60 | * Zone information dump file header. 61 | */ 62 | struct zbd_dump { 63 | struct zbd_info dev_info; /* 128 */ 64 | 65 | unsigned int zstart; /* 132 */ 66 | unsigned int zend; /* 136 */ 67 | 68 | uint8_t reserved[56]; /* 192 */ 69 | } __attribute__((packed)); 70 | 71 | int zbd_open_dump(struct zbd_opts *opts); 72 | int zbd_dump_report_zones(int fd, struct zbd_opts *opts, 73 | struct zbd_zone *zones, unsigned int *nr_zones); 74 | int zbd_dump(int fd, struct zbd_opts *opts); 75 | int zbd_restore(int fd, struct zbd_opts *opts); 76 | 77 | #endif /* _ZBD_TOOL_H_ */ 78 | -------------------------------------------------------------------------------- /tools/cli/zbd_dump.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | /* 3 | * SPDX-FileCopyrightText: 2020 Western Digital Corporation or its affiliates. 4 | * 5 | * Authors: Damien Le Moal (damien.lemoal@wdc.com) 6 | */ 7 | #include "./zbd.h" 8 | 9 | static ssize_t zbd_rw(int fd, bool read, void *buf, size_t count, off_t offset) 10 | { 11 | size_t remaining = count; 12 | off_t ofst = offset; 13 | ssize_t ret; 14 | 15 | while (remaining) { 16 | if (read) 17 | ret = pread(fd, buf, remaining, ofst); 18 | else 19 | ret = pwrite(fd, buf, remaining, ofst); 20 | if (ret < 0) { 21 | fprintf(stderr, "%s failed %d (%s)\n", 22 | read ? "read" : "write", 23 | errno, strerror(errno)); 24 | return -1; 25 | } 26 | if (!ret) 27 | break; 28 | 29 | remaining -= ret; 30 | ofst += ret; 31 | } 32 | 33 | return count - remaining; 34 | } 35 | 36 | static inline ssize_t zbd_read(int fd, void *buf, size_t count, off_t offset) 37 | { 38 | return zbd_rw(fd, true, buf, count, offset); 39 | } 40 | 41 | static inline ssize_t zbd_write(int fd, void *buf, size_t count, off_t offset) 42 | { 43 | return zbd_rw(fd, false, buf, count, offset); 44 | } 45 | 46 | int zbd_open_dump(struct zbd_opts *opts) 47 | { 48 | struct zbd_dump dump; 49 | struct stat st; 50 | int dev_fd = 0; 51 | ssize_t ret; 52 | 53 | ret = stat(opts->dev_path, &st); 54 | if (ret) { 55 | fprintf(stderr, "stat %s failed\n", opts->dev_path); 56 | return -1; 57 | } 58 | 59 | if (!S_ISREG(st.st_mode)) 60 | return 0; 61 | 62 | printf("Regular file specified: assuming dump file\n"); 63 | 64 | dev_fd = open(opts->dev_path, O_RDONLY | O_LARGEFILE); 65 | if (dev_fd < 0) { 66 | fprintf(stderr, "Open %s failed (%s)\n", 67 | opts->dev_path, strerror(errno)); 68 | return -1; 69 | } 70 | 71 | ret = zbd_read(dev_fd, &dump, sizeof(struct zbd_dump), 0); 72 | if (ret != sizeof(struct zbd_dump)) { 73 | fprintf(stderr, "Read dump header failed\n"); 74 | close(dev_fd); 75 | return -1; 76 | } 77 | 78 | memcpy(&opts->dev_info, &dump.dev_info, sizeof(struct zbd_info)); 79 | opts->rep_dump = true; 80 | 81 | return dev_fd; 82 | } 83 | 84 | static bool zbd_dump_should_report_zone(struct zbd_zone *zone, 85 | enum zbd_report_option ro) 86 | { 87 | switch (ro) { 88 | case ZBD_RO_ALL: 89 | return true; 90 | case ZBD_RO_NOT_WP: 91 | return zbd_zone_not_wp(zone); 92 | case ZBD_RO_EMPTY: 93 | return zbd_zone_empty(zone); 94 | case ZBD_RO_IMP_OPEN: 95 | return zbd_zone_imp_open(zone); 96 | case ZBD_RO_EXP_OPEN: 97 | return zbd_zone_exp_open(zone); 98 | case ZBD_RO_CLOSED: 99 | return zbd_zone_closed(zone); 100 | case ZBD_RO_FULL: 101 | return zbd_zone_full(zone); 102 | case ZBD_RO_RDONLY: 103 | return zbd_zone_rdonly(zone); 104 | case ZBD_RO_OFFLINE: 105 | return zbd_zone_offline(zone); 106 | case ZBD_RO_RWP_RECOMMENDED: 107 | return zbd_zone_rwp_recommended(zone); 108 | case ZBD_RO_NON_SEQ: 109 | return zbd_zone_non_seq_resources(zone); 110 | default: 111 | return false; 112 | } 113 | } 114 | 115 | int zbd_dump_report_zones(int fd, struct zbd_opts *opts, 116 | struct zbd_zone *zones, unsigned int *nr_zones) 117 | { 118 | unsigned int i, nz = 0, zstart, zend; 119 | struct zbd_zone zone; 120 | loff_t ofst; 121 | ssize_t ret; 122 | 123 | zstart = opts->ofst / opts->dev_info.zone_size; 124 | zend = (opts->ofst + opts->len + opts->dev_info.zone_size - 1) 125 | / opts->dev_info.zone_size; 126 | 127 | ofst = sizeof(struct zbd_dump) + zstart * sizeof(struct zbd_zone); 128 | for (i = zstart; i < zend; i++) { 129 | ret = zbd_read(fd, &zone, sizeof(struct zbd_zone), ofst); 130 | if (ret != sizeof(struct zbd_zone)) { 131 | fprintf(stderr, "Read zone information failed\n"); 132 | return -1; 133 | } 134 | 135 | if (zbd_dump_should_report_zone(&zone, opts->rep_opt)) { 136 | memcpy(&zones[nz], &zone, sizeof(struct zbd_zone)); 137 | nz++; 138 | } 139 | 140 | ofst += sizeof(struct zbd_zone); 141 | } 142 | 143 | *nr_zones = nz; 144 | 145 | return 0; 146 | } 147 | 148 | #define ZBD_DUMP_IO_SIZE (1024 * 1024) 149 | 150 | static ssize_t zbd_dump_one_zone(int fd, struct zbd_opts *opts, 151 | struct zbd_zone *zone, int dump_fd, void *buf) 152 | { 153 | long long ofst, end; 154 | ssize_t ret, iosize; 155 | 156 | /* Ignore offline zones */ 157 | if (zbd_zone_offline(zone)) 158 | return 0; 159 | 160 | /* Copy zone data */ 161 | ofst = zbd_zone_start(zone); 162 | if (zbd_zone_seq(zone) && !zbd_zone_full(zone)) 163 | end = zbd_zone_wp(zone); 164 | else 165 | end = ofst + zbd_zone_capacity(zone); 166 | 167 | while (ofst < end) { 168 | 169 | if (ofst + ZBD_DUMP_IO_SIZE > end) 170 | iosize = end - ofst; 171 | else 172 | iosize = ZBD_DUMP_IO_SIZE; 173 | 174 | ret = zbd_read(fd, buf, iosize, ofst); 175 | if (ret != iosize) { 176 | fprintf(stderr, "Read zone data failed\n"); 177 | return -1; 178 | } 179 | 180 | ret = zbd_write(dump_fd, buf, iosize, ofst); 181 | if (ret != iosize) { 182 | fprintf(stderr, "Write zone data failed\n"); 183 | return -1; 184 | } 185 | 186 | ofst += iosize; 187 | } 188 | 189 | return end - zbd_zone_start(zone); 190 | } 191 | 192 | static int zbd_dump_zone_data(int fd, struct zbd_opts *opts, 193 | struct zbd_zone *zones, struct zbd_dump *dump) 194 | { 195 | long long dumped_bytes = 0; 196 | unsigned int dumped_zones = 0; 197 | char *data_path = NULL; 198 | int data_fd = 0; 199 | unsigned int i; 200 | ssize_t ret; 201 | void *buf; 202 | 203 | /* Get an IO buffer */ 204 | ret = posix_memalign(&buf, sysconf(_SC_PAGESIZE), ZBD_DUMP_IO_SIZE); 205 | if (ret) { 206 | fprintf(stderr, "No memory\n"); 207 | return -1; 208 | } 209 | 210 | /* Dump zone data */ 211 | ret = asprintf(&data_path, "%s/%s_zone_data.dump", 212 | opts->dump_path, opts->dump_prefix); 213 | 214 | if (ret < 0) { 215 | fprintf(stderr, "No memory\n"); 216 | goto out; 217 | } 218 | 219 | printf(" Dumping zones [%u..%u] data to %s (this may take a while)...\n", 220 | dump->zstart, dump->zend - 1, data_path); 221 | 222 | data_fd = open(data_path, O_WRONLY | O_LARGEFILE | O_TRUNC | O_CREAT, 223 | 0644); 224 | if (data_fd < 0) { 225 | fprintf(stderr, "Create data file %s failed %d (%s)\n", 226 | data_path, errno, strerror(errno)); 227 | ret = -1; 228 | goto out; 229 | } 230 | 231 | /* 232 | * Make sure that the zone data dump file size is always equal 233 | * to the device capacity, even for partial dumps. 234 | */ 235 | ret = ftruncate(data_fd, opts->dev_info.nr_sectors << 9); 236 | if (ret) { 237 | fprintf(stderr, "Truncate data file %s failed %d (%s)\n", 238 | data_path, errno, strerror(errno)); 239 | goto out; 240 | } 241 | 242 | for (i = dump->zstart; i < dump->zend; i++) { 243 | ret = zbd_dump_one_zone(fd, opts, &zones[i], data_fd, buf); 244 | if (ret < 0) 245 | goto out; 246 | if (ret) { 247 | dumped_bytes += ret; 248 | dumped_zones++; 249 | } 250 | } 251 | 252 | printf(" Dumped %lld B from %u zones\n", 253 | dumped_bytes, dumped_zones); 254 | 255 | ret = fsync(data_fd); 256 | if (ret) 257 | fprintf(stderr, "fsync data file %s failed %d (%s)\n", 258 | data_path, errno, strerror(errno)); 259 | 260 | out: 261 | if (data_fd > 0) 262 | close(data_fd); 263 | free(data_path); 264 | free(buf); 265 | 266 | return ret; 267 | } 268 | 269 | static int zbd_dump_zone_info(int fd, struct zbd_opts *opts, 270 | struct zbd_zone *zones, struct zbd_dump *dump) 271 | { 272 | char *info_path = NULL; 273 | int info_fd = 0; 274 | ssize_t ret, sz; 275 | 276 | /* Dump zone information */ 277 | ret = asprintf(&info_path, "%s/%s_zone_info.dump", 278 | opts->dump_path, opts->dump_prefix); 279 | if (ret < 0) { 280 | fprintf(stderr, "No memory\n"); 281 | return -1; 282 | } 283 | 284 | printf(" Dumping zone information to %s\n", info_path); 285 | 286 | info_fd = open(info_path, O_WRONLY | O_LARGEFILE | O_TRUNC | O_CREAT, 287 | 0644); 288 | if (info_fd < 0) { 289 | fprintf(stderr, "Create file %s failed %d (%s)\n", 290 | info_path, errno, strerror(errno)); 291 | ret = -1; 292 | goto out; 293 | } 294 | 295 | ret = zbd_write(info_fd, dump, sizeof(struct zbd_dump), 0); 296 | if (ret != (ssize_t)sizeof(struct zbd_dump)) { 297 | fprintf(stderr, "Write dump header failed\n"); 298 | ret = -1; 299 | goto out; 300 | } 301 | 302 | sz = sizeof(struct zbd_zone) * opts->dev_info.nr_zones; 303 | ret = zbd_write(info_fd, zones, sz, sizeof(struct zbd_dump)); 304 | if (ret != sz) { 305 | fprintf(stderr, "Write zone information failed\n"); 306 | ret = -1; 307 | goto out; 308 | } 309 | 310 | ret = fsync(info_fd); 311 | if (ret) 312 | fprintf(stderr, "fsync zone information file %s failed %d (%s)\n", 313 | info_path, errno, strerror(errno)); 314 | 315 | out: 316 | if (info_fd > 0) 317 | close(info_fd); 318 | free(info_path); 319 | 320 | return ret; 321 | } 322 | 323 | static void zbd_dump_prep_path(struct zbd_opts *opts) 324 | { 325 | if (!opts->dump_path) { 326 | opts->dump_path = get_current_dir_name(); 327 | if (!opts->dump_path) 328 | opts->dump_path = "."; 329 | } 330 | 331 | if (!opts->dump_prefix) 332 | opts->dump_prefix = basename(opts->dev_path); 333 | } 334 | 335 | int zbd_dump(int fd, struct zbd_opts *opts) 336 | { 337 | struct zbd_zone *zones = NULL; 338 | struct zbd_dump dump; 339 | unsigned int nz; 340 | int ret; 341 | 342 | zbd_dump_prep_path(opts); 343 | 344 | /* Setup dump header */ 345 | memset(&dump, 0, sizeof(struct zbd_dump)); 346 | memcpy(&dump.dev_info, &opts->dev_info, sizeof(struct zbd_info)); 347 | dump.zstart = opts->ofst / opts->dev_info.zone_size; 348 | dump.zend = (opts->ofst + opts->len + opts->dev_info.zone_size - 1) 349 | / opts->dev_info.zone_size; 350 | 351 | /* Get zone information */ 352 | ret = zbd_list_zones(fd, 0, 0, ZBD_RO_ALL, &zones, &nz); 353 | if (ret != 0) { 354 | fprintf(stderr, "zbd_list_zones() failed %d\n", ret); 355 | return 1; 356 | } 357 | if (nz != opts->dev_info.nr_zones) { 358 | fprintf(stderr, 359 | "Invalid number of zones: expected %u, got %u\n", 360 | opts->dev_info.nr_zones, nz); 361 | ret = 1; 362 | goto out; 363 | } 364 | 365 | printf("%s: %u zones\n", opts->dev_path, opts->dev_info.nr_zones); 366 | 367 | /* Dump zone information and zone data */ 368 | ret = zbd_dump_zone_data(fd, opts, zones, &dump); 369 | if (ret) 370 | goto out; 371 | 372 | ret = zbd_dump_zone_info(fd, opts, zones, &dump); 373 | 374 | out: 375 | free(zones); 376 | return ret; 377 | } 378 | 379 | struct zbd_restore { 380 | int data_fd; 381 | struct zbd_info dev_info; 382 | struct zbd_zone *dump_zones; 383 | struct zbd_zone *dev_zones; 384 | unsigned int zstart; 385 | unsigned int zend; 386 | void *buf; 387 | long long restored_bytes; 388 | unsigned int restored_zones; 389 | }; 390 | 391 | static int zbd_load_zone_info(struct zbd_restore *ropts, 392 | struct zbd_opts *opts) 393 | { 394 | struct zbd_dump dump; 395 | struct zbd_zone *devz, *dumpz; 396 | unsigned int nr_open_zones = 0; 397 | unsigned int nr_active_zones = 0; 398 | char *info_path = NULL; 399 | ssize_t ret, sz; 400 | unsigned int i; 401 | int info_fd; 402 | 403 | /* Dump zone information */ 404 | ret = asprintf(&info_path, "%s/%s_zone_info.dump", 405 | opts->dump_path, opts->dump_prefix); 406 | if (ret < 0) { 407 | fprintf(stderr, "No memory\n"); 408 | return -1; 409 | } 410 | 411 | printf(" Getting zone information from %s\n", info_path); 412 | 413 | info_fd = open(info_path, O_RDONLY | O_LARGEFILE); 414 | if (info_fd < 0) { 415 | fprintf(stderr, 416 | "Open zone information dump file %s failed %d (%s)\n", 417 | info_path, errno, strerror(errno)); 418 | ret = -1; 419 | goto out; 420 | } 421 | 422 | /* Read dump header */ 423 | ret = zbd_read(info_fd, &dump, sizeof(struct zbd_dump), 0); 424 | if (ret != (ssize_t)sizeof(struct zbd_dump)) { 425 | fprintf(stderr, "Read dump header failed\n"); 426 | ret = -1; 427 | goto out; 428 | } 429 | memcpy(&ropts->dev_info, &dump.dev_info, sizeof(struct zbd_info)); 430 | ropts->zstart = dump.zstart; 431 | ropts->zend = dump.zend; 432 | 433 | /* Check device information against target device */ 434 | ret = -1; 435 | if (ropts->dev_info.nr_sectors != opts->dev_info.nr_sectors) { 436 | fprintf(stderr, "Incompatible capacity\n"); 437 | goto out; 438 | } 439 | if (ropts->dev_info.lblock_size != opts->dev_info.lblock_size) { 440 | fprintf(stderr, "Incompatible logical block size\n"); 441 | goto out; 442 | } 443 | if (ropts->dev_info.pblock_size != opts->dev_info.pblock_size) { 444 | fprintf(stderr, "Incompatible physical block size\n"); 445 | goto out; 446 | } 447 | if (ropts->dev_info.nr_zones != opts->dev_info.nr_zones) { 448 | fprintf(stderr, "Incompatible number of zones\n"); 449 | goto out; 450 | } 451 | if (ropts->dev_info.zone_size != opts->dev_info.zone_size) { 452 | fprintf(stderr, "Incompatible zone size\n"); 453 | goto out; 454 | } 455 | 456 | /* Read dumped zone information */ 457 | ropts->dump_zones = calloc(ropts->dev_info.nr_zones, 458 | sizeof(struct zbd_zone)); 459 | if (!ropts->dump_zones) { 460 | fprintf(stderr, "No memory\n"); 461 | goto out; 462 | } 463 | 464 | sz = sizeof(struct zbd_zone) * ropts->dev_info.nr_zones; 465 | ret = zbd_read(info_fd, ropts->dump_zones, sz, sizeof(struct zbd_dump)); 466 | if (ret != sz) { 467 | fprintf(stderr, "Read zone information failed %zd %zd\n", 468 | ret, sz); 469 | ret = -1; 470 | goto out; 471 | } 472 | 473 | /* Check zones against target device zones */ 474 | for (i = 0; i < ropts->dev_info.nr_zones; i++) { 475 | dumpz = &ropts->dump_zones[i]; 476 | devz = &ropts->dev_zones[i]; 477 | 478 | ret = -1; 479 | if (zbd_zone_type(dumpz) != zbd_zone_type(devz)) { 480 | fprintf(stderr, "Incompatible zone %u type\n", i); 481 | goto out; 482 | } 483 | if (zbd_zone_start(dumpz) != zbd_zone_start(devz)) { 484 | fprintf(stderr, "Incompatible zone %u start\n", i); 485 | goto out; 486 | } 487 | if (zbd_zone_len(dumpz) != zbd_zone_len(devz)) { 488 | fprintf(stderr, "Incompatible zone %u start\n", i); 489 | goto out; 490 | } 491 | if (zbd_zone_capacity(dumpz) != zbd_zone_capacity(devz)) { 492 | fprintf(stderr, "Incompatible zone %u start\n", i); 493 | goto out; 494 | } 495 | if (zbd_zone_offline(devz) && !zbd_zone_offline(dumpz)) { 496 | fprintf(stderr, "Incompatible offline zone %u\n", i); 497 | goto out; 498 | } 499 | if (zbd_zone_rdonly(devz)) { 500 | fprintf(stderr, "Incompatible read-only zone %u\n", i); 501 | goto out; 502 | } 503 | 504 | /* Count open and active zones */ 505 | if (zbd_zone_is_open(dumpz)) 506 | nr_open_zones++; 507 | if (zbd_zone_is_active(dumpz)) 508 | nr_active_zones++; 509 | } 510 | 511 | /* 512 | * Check that the target drive has enough open and 513 | * active zones resource. 514 | */ 515 | if (opts->dev_info.max_nr_open_zones && 516 | nr_open_zones > opts->dev_info.max_nr_open_zones) { 517 | fprintf(stderr, 518 | "Incompatible maximum number of open zones\n"); 519 | goto out; 520 | } 521 | if (opts->dev_info.max_nr_active_zones && 522 | nr_active_zones > opts->dev_info.max_nr_active_zones) { 523 | fprintf(stderr, 524 | "Incompatible maximum number of active zones\n"); 525 | goto out; 526 | } 527 | 528 | ret = 0; 529 | out: 530 | 531 | free(info_path); 532 | close(info_fd); 533 | 534 | return ret; 535 | } 536 | 537 | static int zbd_open_zone_data(struct zbd_restore *ropts, 538 | struct zbd_opts *opts) 539 | { 540 | char *data_path = NULL; 541 | struct stat st; 542 | int ret; 543 | 544 | /* Dump zone information */ 545 | ret = asprintf(&data_path, "%s/%s_zone_data.dump", 546 | opts->dump_path, opts->dump_prefix); 547 | if (ret < 0) { 548 | fprintf(stderr, "No memory\n"); 549 | return -1; 550 | } 551 | 552 | printf(" Restoring zones [%u..%u] data from %s (this may take a while)...\n", 553 | ropts->zstart, ropts->zend - 1, data_path); 554 | 555 | ropts->data_fd = open(data_path, O_RDONLY | O_LARGEFILE); 556 | if (ropts->data_fd < 0) { 557 | fprintf(stderr, 558 | "Open zone data dump file %s failed %d (%s)\n", 559 | data_path, errno, strerror(errno)); 560 | ret = -1; 561 | goto out; 562 | } 563 | 564 | /* Check zone data file size */ 565 | ret = fstat(ropts->data_fd, &st); 566 | if (ret) { 567 | fprintf(stderr, 568 | "stat zone data dump file %s failed %d (%s)\n", 569 | data_path, errno, strerror(errno)); 570 | goto out; 571 | } 572 | 573 | if ((unsigned long long)st.st_size != opts->dev_info.nr_sectors << 9) { 574 | fprintf(stderr, "Invalid zone data dump file size\n"); 575 | ret = -1; 576 | } 577 | 578 | out: 579 | free(data_path); 580 | 581 | return ret; 582 | } 583 | 584 | static long long zbd_restore_zone_data(int fd, struct zbd_restore *ropts, 585 | struct zbd_zone *dumpz) 586 | { 587 | long long ofst, end; 588 | ssize_t ret, iosize; 589 | 590 | /* Copy zone dump data */ 591 | ofst = zbd_zone_start(dumpz); 592 | if (zbd_zone_seq(dumpz) && !zbd_zone_full(dumpz)) 593 | end = zbd_zone_wp(dumpz); 594 | else 595 | end = ofst + zbd_zone_capacity(dumpz); 596 | 597 | while (ofst < end) { 598 | 599 | if (ofst + ZBD_DUMP_IO_SIZE > end) 600 | iosize = end - ofst; 601 | else 602 | iosize = ZBD_DUMP_IO_SIZE; 603 | 604 | ret = zbd_read(ropts->data_fd, ropts->buf, iosize, ofst); 605 | if (ret != iosize) { 606 | fprintf(stderr, "Read zone dump data failed\n"); 607 | return -1; 608 | } 609 | 610 | ret = zbd_write(fd, ropts->buf, iosize, ofst); 611 | if (ret != iosize) { 612 | fprintf(stderr, "Write zone data failed\n"); 613 | return -1; 614 | } 615 | 616 | ofst += iosize; 617 | } 618 | 619 | return end - zbd_zone_start(dumpz); 620 | } 621 | 622 | static int zbd_restore_one_zone(int fd, struct zbd_restore *ropts, 623 | struct zbd_zone *dumpz, struct zbd_zone *devz) 624 | { 625 | long long restored_bytes; 626 | int ret; 627 | 628 | /* Copy zone data */ 629 | restored_bytes = zbd_restore_zone_data(fd, ropts, dumpz); 630 | if (restored_bytes < 0) 631 | return restored_bytes; 632 | 633 | if (restored_bytes) { 634 | ropts->restored_bytes += restored_bytes; 635 | ropts->restored_zones++; 636 | } 637 | 638 | /* Restore zone condition */ 639 | if (zbd_zone_closed(dumpz)) { 640 | ret = zbd_close_zones(fd, zbd_zone_start(devz), 641 | zbd_zone_len(devz)); 642 | if (ret) { 643 | fprintf(stderr, 644 | "Close target zone at %llu failed %d (%s)\n", 645 | zbd_zone_start(devz), 646 | errno, strerror(errno)); 647 | return ret; 648 | } 649 | } else if (zbd_zone_exp_open(dumpz)) { 650 | ret = zbd_open_zones(fd, zbd_zone_start(devz), 651 | zbd_zone_len(devz)); 652 | if (ret) { 653 | fprintf(stderr, 654 | "Open target zone at %llu failed %d (%s)\n", 655 | zbd_zone_start(devz), 656 | errno, strerror(errno)); 657 | return ret; 658 | } 659 | } 660 | 661 | return 0; 662 | } 663 | 664 | int zbd_restore(int fd, struct zbd_opts *opts) 665 | { 666 | struct zbd_restore ropts; 667 | struct zbd_zone *dumpz, *devz; 668 | unsigned int i, nz = 0; 669 | int ret; 670 | 671 | memset(&ropts, 0, sizeof(struct zbd_restore)); 672 | 673 | zbd_dump_prep_path(opts); 674 | 675 | /* Get zone information from the target device */ 676 | ret = zbd_list_zones(fd, 0, 0, ZBD_RO_ALL, &ropts.dev_zones, &nz); 677 | if (ret != 0) { 678 | fprintf(stderr, "zbd_list_zones() failed %d\n", ret); 679 | return ret; 680 | } 681 | if (nz != opts->dev_info.nr_zones) { 682 | fprintf(stderr, 683 | "Invalid number of zones: expected %u, got %u\n", 684 | opts->dev_info.nr_zones, nz); 685 | ret = -1; 686 | goto out; 687 | } 688 | 689 | /* Get and check zone information from the dump file */ 690 | ret = zbd_load_zone_info(&ropts, opts); 691 | if (ret) 692 | goto out; 693 | 694 | /* Open and check the zone data dump file */ 695 | ret = zbd_open_zone_data(&ropts, opts); 696 | if (ret) 697 | goto out; 698 | 699 | /* Get an IO buffer */ 700 | ret = posix_memalign(&ropts.buf, sysconf(_SC_PAGESIZE), 701 | ZBD_DUMP_IO_SIZE); 702 | if (ret) { 703 | fprintf(stderr, "No memory\n"); 704 | goto out; 705 | } 706 | 707 | /* 708 | * Restore the target device. To avoid hitting the max active or max 709 | * open zone limits of the target drive, process all zones in several 710 | * passes with each pass handling one condition. 711 | */ 712 | 713 | /* Pass 1: Reset all zones in the dump range */ 714 | for (i = ropts.zstart; i < ropts.zend; i++) { 715 | dumpz = &ropts.dump_zones[i]; 716 | devz = &ropts.dev_zones[i]; 717 | 718 | if (zbd_zone_offline(dumpz)) 719 | continue; 720 | if (!zbd_zone_seq(dumpz) || zbd_zone_empty(dumpz)) 721 | continue; 722 | 723 | ret = zbd_reset_zones(fd, zbd_zone_start(devz), 724 | zbd_zone_len(devz)); 725 | if (ret) { 726 | fprintf(stderr, "Reset target zone %u failed %d (%s)\n", 727 | i, errno, strerror(errno)); 728 | goto out; 729 | } 730 | } 731 | 732 | /* Pass 2: copy data of conventional and full sequential zones */ 733 | for (i = ropts.zstart; i < ropts.zend; i++) { 734 | dumpz = &ropts.dump_zones[i]; 735 | devz = &ropts.dev_zones[i]; 736 | 737 | if (!zbd_zone_cnv(dumpz) && !zbd_zone_full(dumpz)) 738 | continue; 739 | 740 | ret = zbd_restore_one_zone(fd, &ropts, dumpz, devz); 741 | if (ret < 0) 742 | goto out; 743 | } 744 | 745 | /* Pass 3: handle closed zones */ 746 | for (i = ropts.zstart; i < ropts.zend; i++) { 747 | dumpz = &ropts.dump_zones[i]; 748 | devz = &ropts.dev_zones[i]; 749 | 750 | if (!zbd_zone_closed(dumpz)) 751 | continue; 752 | 753 | ret = zbd_restore_one_zone(fd, &ropts, dumpz, devz); 754 | if (ret < 0) 755 | goto out; 756 | } 757 | 758 | /* Pass 4: handle explicitly open zones */ 759 | for (i = ropts.zstart; i < ropts.zend; i++) { 760 | dumpz = &ropts.dump_zones[i]; 761 | devz = &ropts.dev_zones[i]; 762 | 763 | if (!zbd_zone_exp_open(dumpz)) 764 | continue; 765 | 766 | ret = zbd_restore_one_zone(fd, &ropts, dumpz, devz); 767 | if (ret < 0) 768 | goto out; 769 | } 770 | 771 | /* Pass 5: handle implicitly open zones */ 772 | for (i = ropts.zstart; i < ropts.zend; i++) { 773 | dumpz = &ropts.dump_zones[i]; 774 | devz = &ropts.dev_zones[i]; 775 | 776 | if (!zbd_zone_imp_open(dumpz)) 777 | continue; 778 | 779 | ret = zbd_restore_one_zone(fd, &ropts, dumpz, devz); 780 | if (ret < 0) 781 | goto out; 782 | } 783 | 784 | printf(" Restored %lld B in %u zones\n", 785 | ropts.restored_bytes, ropts.restored_zones); 786 | 787 | ret = fsync(fd); 788 | if (ret) 789 | fprintf(stderr, "fsync target device failed %d (%s)\n", 790 | errno, strerror(errno)); 791 | 792 | fsync(fd); 793 | 794 | out: 795 | if (ropts.data_fd > 0) 796 | close(ropts.data_fd); 797 | free(ropts.dev_zones); 798 | free(ropts.dump_zones); 799 | free(ropts.buf); 800 | 801 | return ret; 802 | } 803 | -------------------------------------------------------------------------------- /tools/gui/Makefile.am: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-3.0-or-later 2 | # 3 | # SPDX-FileCopyrightText: 2020 Western Digital Corporation or its affiliates. 4 | 5 | polkit_action_in_FILES += gui/org.gnome.gzbd.policy.in 6 | desktop_in_FILES += gui/gzbd.desktop.in 7 | pixmap_FILES += gui/gzbd.png 8 | 9 | bin_PROGRAMS += gzbd 10 | gzbd_SOURCES = \ 11 | gui/gzbd.c \ 12 | gui/gzbd_if.c \ 13 | gui/gzbd_if_dev.c \ 14 | gui/gzbd.h 15 | 16 | gzbd_CFLAGS = $(CFLAGS) $(GTK_CFLAGS) 17 | gzbd_LDADD = $(libzbd_ldadd) $(GTK_LIBS) -lpthread 18 | 19 | dist_man8_MANS += gui/gzbd.8 20 | 21 | gui/org.gnome.gzbd.policy: gui/org.gnome.gzbd.policy.in Makefile 22 | $(bin_subst) < gui/org.gnome.gzbd.policy.in > gui/org.gnome.gzbd.policy 23 | 24 | gui/gzbd.desktop: gui/gzbd.desktop.in Makefile 25 | $(bin_subst) < gui/gzbd.desktop.in > gui/gzbd.desktop 26 | -------------------------------------------------------------------------------- /tools/gui/gzbd.8: -------------------------------------------------------------------------------- 1 | .\" SPDX-License-Identifier: GPL-3.0-or-later 2 | .\" SPDX-FileCopyrightText: 2020, Western Digital Corporation or its affiliates. 3 | .\" Written by Damien Le Moal 4 | .\" 5 | .TH GZBD 8 6 | .SH NAME 7 | gzbd \- zoned block device management graphical interface 8 | 9 | .SH SYNOPSIS 10 | .B gzbd 11 | [options] 12 | .I device 13 | 14 | .SH DESCRIPTION 15 | .B gzbd 16 | is a graphical user interface allowing to view a zoned block device zones 17 | and to execute zone commands. The zone reset, open, close and finish commands 18 | can be applied to zones selected from the list of zones displayed. 19 | The list of zones displayed can be filtered based on zone type and condition. 20 | Zones are also represented graphically using colored boxes using a similar 21 | color coding as gzbd-viewer. 22 | 23 | .PP 24 | The 25 | .I device 26 | argument must be the pathname of the target zoned block device. 27 | 28 | .SH OPTIONS 29 | .TP 30 | .BR \-? , " \-\-help" 31 | Show help options and exit. 32 | .TP 33 | .BR \-\-help\-all 34 | Show all help options and exit. 35 | .TP 36 | .BR \-\-help\-gtk 37 | Show GTK+ options and exit. 38 | .TP 39 | .BR \-v , " \-\-verbose" 40 | Use libzbd verbose mode (for debug). 41 | .TP 42 | .BR \-b , " \-\-block " \fIsize\fP 43 | Use \fIsize\fP as the unit for displaying a zone position, 44 | length and write pointer position instead of the default byte 45 | value. 46 | .TP 47 | .BR \-\-display=DISPLAY 48 | Specify the X display to use. 49 | 50 | .SH AUTHOR 51 | .nf 52 | Damien Le Moal 53 | .fi 54 | 55 | .SH SEE ALSO 56 | .BR zbd (8) ", " gzbd-viewer (8) 57 | 58 | .SH AVAILABILITY 59 | The gzbd utility is part of the libzbd library available from 60 | .UR https://\:github.com\:/westerndigitalcorporation\:/libzbd 61 | .UE . 62 | -------------------------------------------------------------------------------- /tools/gui/gzbd.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | /* 3 | * SPDX-FileCopyrightText: 2020 Western Digital Corporation or its affiliates. 4 | * 5 | * Authors: Damien Le Moal (damien.lemoal@wdc.com) 6 | */ 7 | #define _LARGEFILE64_SOURCE 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #include "gzbd.h" 21 | 22 | /** 23 | * Device control. 24 | */ 25 | dz_t dz; 26 | 27 | /** 28 | * Signal handling. 29 | */ 30 | static gboolean dz_process_signal(GIOChannel *source, 31 | GIOCondition condition, 32 | gpointer user_data) 33 | { 34 | char buf[32]; 35 | ssize_t size; 36 | 37 | if (condition & G_IO_IN) { 38 | size = read(g_io_channel_unix_get_fd(source), buf, sizeof(buf)); 39 | if (size > 0) { 40 | /* Got signal */ 41 | gtk_main_quit(); 42 | return TRUE; 43 | } 44 | } 45 | 46 | return FALSE; 47 | } 48 | 49 | static void dz_sig_handler(int sig) 50 | { 51 | /* Propagate signal through the pipe */ 52 | write(dz.sig_pipe[1], &sig, sizeof(int)); 53 | } 54 | 55 | static void dz_set_signal_handlers(void) 56 | { 57 | GIOChannel *sig_channel; 58 | long fd_flags; 59 | int ret; 60 | 61 | ret = pipe(dz.sig_pipe); 62 | if (ret < 0) { 63 | perror("pipe"); 64 | exit(1); 65 | } 66 | 67 | fd_flags = fcntl(dz.sig_pipe[1], F_GETFL); 68 | if (fd_flags < 0) { 69 | perror("Read descriptor flags"); 70 | exit(1); 71 | } 72 | ret = fcntl(dz.sig_pipe[1], F_SETFL, fd_flags | O_NONBLOCK); 73 | if (ret < 0) { 74 | perror("Write descriptor flags"); 75 | exit(1); 76 | } 77 | 78 | /* Install the unix signal handler */ 79 | signal(SIGINT, dz_sig_handler); 80 | signal(SIGQUIT, dz_sig_handler); 81 | signal(SIGTERM, dz_sig_handler); 82 | 83 | /* Convert the reading end of the pipe into a GIOChannel */ 84 | sig_channel = g_io_channel_unix_new(dz.sig_pipe[0]); 85 | g_io_channel_set_encoding(sig_channel, NULL, NULL); 86 | g_io_channel_set_flags(sig_channel, 87 | g_io_channel_get_flags(sig_channel) | 88 | G_IO_FLAG_NONBLOCK, 89 | NULL); 90 | g_io_add_watch(sig_channel, 91 | G_IO_IN | G_IO_PRI, 92 | dz_process_signal, NULL); 93 | } 94 | 95 | int main(int argc, char **argv) 96 | { 97 | gboolean init_ret; 98 | gboolean verbose = FALSE; 99 | GError *error = NULL; 100 | int i, ret = 0; 101 | GOptionEntry options[] = { 102 | { 103 | "verbose", 'v', 0, 104 | G_OPTION_ARG_NONE, &verbose, 105 | "Set libzbd verbose mode", 106 | NULL 107 | }, 108 | { 109 | "block", 'b', 0, 110 | G_OPTION_ARG_INT, &dz.block_size, 111 | "Use block bytes as the unit for displaying zone " 112 | "position, length and write pointer position instead " 113 | "of the default byte value", 114 | NULL 115 | }, 116 | { NULL } 117 | }; 118 | 119 | /* Init */ 120 | memset(&dz, 0, sizeof(dz)); 121 | dz.block_size = 1; 122 | for (i = 0; i < DZ_MAX_DEV; i++) 123 | dz.dev[i].dev_fd = -1; 124 | 125 | init_ret = gtk_init_with_args(&argc, &argv, 126 | "", 127 | options, NULL, &error); 128 | if (init_ret == FALSE || 129 | error != NULL) { 130 | printf("Failed to parse command line arguments: %s\n", 131 | error->message); 132 | g_error_free(error); 133 | return 1; 134 | } 135 | 136 | if (dz.block_size <= 0) { 137 | fprintf(stderr, "Invalid block size\n"); 138 | return 1; 139 | } 140 | 141 | if (verbose) 142 | zbd_set_log_level(ZBD_LOG_DEBUG); 143 | 144 | dz_set_signal_handlers(); 145 | 146 | /* Create GUI */ 147 | dz_if_create(); 148 | 149 | /* Check user credentials */ 150 | if (getuid() != 0) { 151 | dz_if_err("Root privileges are required for running gzbd", 152 | "Since gzbd is capable of erasing vast amounts of" 153 | " data, only root may run it."); 154 | ret = 1; 155 | goto out; 156 | } 157 | 158 | /* Add devices listed on command line */ 159 | for (i = 1; i < argc; i++) 160 | dz_if_add_device(argv[i]); 161 | 162 | /* Main event loop */ 163 | gtk_main(); 164 | 165 | out: 166 | /* Cleanup GUI */ 167 | dz_if_destroy(); 168 | 169 | return ret; 170 | } 171 | 172 | /* 173 | * Report zones. 174 | */ 175 | static int dz_report_zones(dz_dev_t *dzd) 176 | { 177 | unsigned int i, j = 0; 178 | int ret; 179 | 180 | if (!dzd->zones || !dzd->max_nr_zones) { 181 | 182 | /* Get list of all zones */ 183 | dzd->zone_ro = ZBD_RO_ALL; 184 | ret = zbd_list_zones(dzd->dev_fd, 185 | 0, 0, dzd->zone_ro, 186 | &dzd->zbdz, &dzd->nr_zones); 187 | if (ret != 0) 188 | return ret; 189 | 190 | if (!dzd->nr_zones) { 191 | /* That should not happen */ 192 | return -EIO; 193 | } 194 | 195 | /* Allocate zone array */ 196 | dzd->max_nr_zones = dzd->nr_zones; 197 | dzd->zones = (dz_dev_zone_t *) 198 | calloc(dzd->max_nr_zones, sizeof(dz_dev_zone_t)); 199 | if (!dzd->zones) 200 | return -ENOMEM; 201 | 202 | for (i = 0; i < dzd->max_nr_zones; i++) { 203 | dzd->zones[i].no = i; 204 | dzd->zones[i].visible = 1; 205 | memcpy(&dzd->zones[i].info, &dzd->zbdz[i], 206 | sizeof(struct zbd_zone)); 207 | } 208 | 209 | return 0; 210 | 211 | } 212 | 213 | /* Refresh zone list */ 214 | dzd->nr_zones = dzd->max_nr_zones; 215 | ret = zbd_report_zones(dzd->dev_fd, 216 | 0, 0, dzd->zone_ro, 217 | dzd->zbdz, &dzd->nr_zones); 218 | if (ret != 0) { 219 | fprintf(stderr, "Get zone information failed %d (%s)\n", 220 | errno, strerror(errno)); 221 | dzd->nr_zones = 0; 222 | } 223 | 224 | /* Apply filter */ 225 | for (i = 0; i < dzd->max_nr_zones; i++) { 226 | if (j < dzd->nr_zones && 227 | zbd_zone_start(&dzd->zones[i].info) == 228 | zbd_zone_start(&dzd->zbdz[j])) { 229 | memcpy(&dzd->zones[i].info, &dzd->zbdz[j], 230 | sizeof(struct zbd_zone)); 231 | dzd->zones[i].visible = 1; 232 | j++; 233 | } else { 234 | dzd->zones[i].visible = 0; 235 | } 236 | } 237 | 238 | return ret; 239 | } 240 | 241 | /* 242 | * Zone operation. 243 | */ 244 | static int dz_zone_operation(dz_dev_t *dzd) 245 | { 246 | int zno = dzd->zone_no; 247 | off_t ofst, len; 248 | int ret; 249 | 250 | if (zno >= (int)dzd->nr_zones) { 251 | fprintf(stderr, "Invalid zone number %d / %u\n", 252 | zno, 253 | dzd->nr_zones); 254 | return -1; 255 | } 256 | 257 | if (zno < 0) { 258 | ofst = 0; 259 | len = dzd->capacity; 260 | } else { 261 | ofst = zbd_zone_start(&dzd->zones[zno].info); 262 | len = zbd_zone_len(&dzd->zones[zno].info); 263 | } 264 | 265 | ret = zbd_zones_operation(dzd->dev_fd, dzd->zone_op, ofst, len); 266 | if (ret != 0) 267 | fprintf(stderr, "zbd_zone_operation failed %d\n", ret); 268 | 269 | return ret; 270 | } 271 | 272 | /* 273 | * Command thread routine. 274 | */ 275 | void *dz_cmd_run(void *data) 276 | { 277 | dz_dev_t *dzd = data; 278 | int do_report_zones = 1; 279 | int ret; 280 | 281 | switch (dzd->cmd_id) { 282 | case DZ_CMD_REPORT_ZONES: 283 | do_report_zones = 0; 284 | ret = dz_report_zones(dzd); 285 | break; 286 | case DZ_CMD_ZONE_OP: 287 | ret = dz_zone_operation(dzd); 288 | break; 289 | default: 290 | fprintf(stderr, "Invalid command ID %d\n", dzd->cmd_id); 291 | ret = -1; 292 | break; 293 | } 294 | 295 | if (do_report_zones) 296 | ret = dz_report_zones(dzd); 297 | 298 | if (dzd->cmd_dialog) { 299 | int response_id; 300 | if (ret == 0) 301 | response_id = GTK_RESPONSE_OK; 302 | else 303 | response_id = GTK_RESPONSE_REJECT; 304 | gtk_dialog_response(GTK_DIALOG(dzd->cmd_dialog), response_id); 305 | } 306 | 307 | return (void *)((unsigned long) ret); 308 | } 309 | 310 | static GtkWidget *dz_cmd_dialog(char *msg) 311 | { 312 | GtkWidget *dialog, *content_area; 313 | GtkWidget *spinner; 314 | 315 | dialog = gtk_message_dialog_new(GTK_WINDOW(dz.window), 316 | GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, 317 | GTK_MESSAGE_OTHER, 318 | GTK_BUTTONS_NONE, 319 | "%s", msg); 320 | content_area = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); 321 | 322 | spinner = gtk_spinner_new(); 323 | gtk_widget_show(spinner); 324 | gtk_container_add(GTK_CONTAINER(content_area), spinner); 325 | gtk_spinner_start(GTK_SPINNER(spinner)); 326 | 327 | gtk_widget_show_all(dialog); 328 | 329 | return dialog; 330 | } 331 | 332 | 333 | /* 334 | * Open a device. 335 | */ 336 | dz_dev_t *dz_open(char *path) 337 | { 338 | dz_dev_t *dzd = NULL; 339 | int i, ret; 340 | 341 | /* Get an unused device */ 342 | for (i = 0; i < DZ_MAX_DEV; i++) { 343 | if (dz.dev[i].dev_fd < 0) { 344 | dzd = &dz.dev[i]; 345 | break; 346 | } 347 | } 348 | 349 | if (!dzd) { 350 | dz_if_err("Too many open devices", 351 | "At most %d devices can be open", 352 | (int)DZ_MAX_DEV); 353 | fprintf(stderr, "Too many open devices\n"); 354 | return NULL; 355 | } 356 | 357 | /* Open device file */ 358 | strncpy(dzd->path, path, sizeof(dzd->path) - 1); 359 | dzd->dev_fd = zbd_open(dzd->path, O_RDWR | O_LARGEFILE, &dzd->info); 360 | if (dzd->dev_fd < 0) { 361 | ret = errno; 362 | dz_if_err("Open device failed", 363 | "Open %s failed %d (%s)", 364 | dzd->path, ret, strerror(ret)); 365 | fprintf(stderr, "Open device %s failed %d (%s)\n", 366 | dzd->path, ret, strerror(ret)); 367 | return NULL; 368 | } 369 | 370 | dzd->capacity = dzd->info.nr_sectors << 9; 371 | 372 | dzd->block_size = dz.block_size; 373 | if (!dzd->block_size) { 374 | dzd->block_size = 1; 375 | } else if (dzd->info.zone_size % dzd->block_size) { 376 | dz_if_err("Invalid block size", 377 | "The device zone size is not a multiple of the block size"); 378 | fprintf(stderr, "Invalid block size\n"); 379 | ret = 1; 380 | goto out; 381 | } 382 | 383 | /* Get zone information */ 384 | ret = dz_report_zones(dzd); 385 | if (ret != 0) 386 | goto out; 387 | 388 | dz.nr_devs++; 389 | 390 | out: 391 | if (ret != 0) { 392 | dz_close(dzd); 393 | dzd = NULL; 394 | } 395 | 396 | return dzd; 397 | } 398 | 399 | /* 400 | * Close a device. 401 | */ 402 | void dz_close(dz_dev_t *dzd) 403 | { 404 | 405 | if (dzd->dev_fd < 0) 406 | return; 407 | 408 | free(dzd->zbdz); 409 | dzd->zbdz = NULL; 410 | 411 | free(dzd->zones); 412 | dzd->zones = NULL; 413 | 414 | zbd_close(dzd->dev_fd); 415 | dzd->dev_fd = -1; 416 | 417 | memset(dzd, 0, sizeof(dz_dev_t)); 418 | dz.nr_devs--; 419 | } 420 | 421 | /* 422 | * Execute a command. 423 | */ 424 | int dz_cmd_exec(dz_dev_t *dzd, int cmd_id, char *msg) 425 | { 426 | int ret; 427 | 428 | /* Set command */ 429 | dzd->cmd_id = cmd_id; 430 | if (msg) 431 | /* Create a dialog */ 432 | dzd->cmd_dialog = dz_cmd_dialog(msg); 433 | else 434 | dzd->cmd_dialog = NULL; 435 | 436 | /* Create command thread */ 437 | ret = pthread_create(&dzd->cmd_thread, NULL, dz_cmd_run, dzd); 438 | if (ret != 0) 439 | goto out; 440 | 441 | if (dzd->cmd_dialog) { 442 | if (gtk_dialog_run(GTK_DIALOG(dzd->cmd_dialog)) 443 | == GTK_RESPONSE_OK) 444 | ret = 0; 445 | else 446 | ret = -1; 447 | } else { 448 | void *cmd_ret; 449 | pthread_join(dzd->cmd_thread, &cmd_ret); 450 | ret = (long)cmd_ret; 451 | } 452 | 453 | pthread_join(dzd->cmd_thread, NULL); 454 | 455 | out: 456 | if (dzd->cmd_dialog) { 457 | gtk_widget_destroy(dzd->cmd_dialog); 458 | dzd->cmd_dialog = NULL; 459 | } 460 | 461 | return ret; 462 | } 463 | 464 | -------------------------------------------------------------------------------- /tools/gui/gzbd.desktop.in: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=gzbd 3 | Comment=Manage Zoned Block Devices 4 | Keywords=disk;drive;harddisk;hdd;disc;smr;SMR;ssd;zns;ZNS; 5 | Exec=pkexec --disable-internal-agent "@bindir@/gzbd" 6 | Icon=gzbd 7 | Terminal=false 8 | Type=Application 9 | Categories=GNOME;GTK;System; 10 | -------------------------------------------------------------------------------- /tools/gui/gzbd.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-3.0-or-later */ 2 | /* 3 | * SPDX-FileCopyrightText: 2020 Western Digital Corporation or its affiliates. 4 | * 5 | * Authors: Damien Le Moal (damien.lemoal@wdc.com) 6 | */ 7 | #ifndef __GZBD_H__ 8 | #define __GZBD_H__ 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | 16 | /** 17 | * Default refresh interval (milliseconds). 18 | */ 19 | #define DZ_INTERVAL 1000 20 | 21 | /** 22 | * Zone information list columns. 23 | */ 24 | enum { 25 | DZ_ZONE_NUM = 0, 26 | DZ_ZONE_LIST_COLUMS 27 | }; 28 | 29 | /** 30 | * Device command IDs. 31 | */ 32 | enum { 33 | DZ_CMD_REPORT_ZONES, 34 | DZ_CMD_ZONE_OP, 35 | }; 36 | 37 | /** 38 | * Maximum number of devices that can be open. 39 | */ 40 | #define DZ_MAX_DEV 32 41 | 42 | /** 43 | * Device zone information. 44 | */ 45 | typedef struct dz_dev_zone { 46 | 47 | int no; 48 | int visible; 49 | struct zbd_zone info; 50 | 51 | } dz_dev_zone_t; 52 | 53 | /** 54 | * GUI Tab data. 55 | */ 56 | typedef struct dz_dev { 57 | 58 | char path[128]; 59 | int opening; 60 | 61 | int dev_fd; 62 | struct zbd_info info; 63 | unsigned long long capacity; 64 | int block_size; 65 | int use_hexa; 66 | 67 | int zone_ro; 68 | unsigned int zone_op; 69 | int zone_no; 70 | unsigned int max_nr_zones; 71 | unsigned int nr_zones; 72 | struct zbd_zone *zbdz; 73 | dz_dev_zone_t *zones; 74 | 75 | /** 76 | * Command execution. 77 | */ 78 | int cmd_id; 79 | pthread_t cmd_thread; 80 | GtkWidget *cmd_dialog; 81 | 82 | /** 83 | * Interface stuff. 84 | */ 85 | GtkWidget *page; 86 | GtkWidget *page_frame; 87 | 88 | GtkWidget *zfilter_combo; 89 | GtkWidget *zlist_frame_label; 90 | GtkWidget *zlist_treeview; 91 | GtkTreeModel *zlist_model; 92 | GtkListStore *zlist_store; 93 | unsigned int zlist_start_no; 94 | unsigned int zlist_end_no; 95 | int zlist_selection; 96 | GtkWidget *znum_entry; 97 | GtkWidget *zblock_entry; 98 | 99 | GtkWidget *zones_da; 100 | 101 | } dz_dev_t; 102 | 103 | /** 104 | * GUI data. 105 | */ 106 | typedef struct dz { 107 | 108 | dz_dev_t dev[DZ_MAX_DEV]; 109 | int nr_devs; 110 | 111 | int interval; 112 | int block_size; 113 | int abort; 114 | 115 | /** 116 | * Interface stuff. 117 | */ 118 | GtkWidget *window; 119 | GtkWidget *vbox; 120 | GtkWidget *notebook; 121 | GtkWidget *no_dev_frame; 122 | 123 | GdkRGBA conv_color; 124 | GdkRGBA seqnw_color; 125 | GdkRGBA seqw_color; 126 | GdkRGBA nonw_color; 127 | 128 | /** 129 | * For handling signals. 130 | */ 131 | int sig_pipe[2]; 132 | 133 | } dz_t; 134 | 135 | /** 136 | * System time in usecs. 137 | */ 138 | static inline unsigned long long dz_usec(void) 139 | { 140 | struct timeval tv; 141 | 142 | gettimeofday(&tv, NULL); 143 | 144 | return (unsigned long long) tv.tv_sec * 1000000LL + 145 | (unsigned long long) tv.tv_usec; 146 | } 147 | 148 | extern dz_t dz; 149 | 150 | dz_dev_t *dz_open(char *path); 151 | void dz_close(dz_dev_t *dzd); 152 | 153 | int dz_cmd_exec(dz_dev_t *dzd, int cmd_id, char *msg); 154 | 155 | void dz_if_err(const char *msg, const char *fmt, ...); 156 | void dz_if_create(void); 157 | void dz_if_destroy(void); 158 | void dz_if_add_device(char *dev_path); 159 | dz_dev_t *dz_if_dev_open(char *path); 160 | void dz_if_dev_close(dz_dev_t *dzd); 161 | void dz_if_dev_update(dz_dev_t *dzd, int do_report_zones); 162 | 163 | #endif /* __GZBD_H__ */ 164 | -------------------------------------------------------------------------------- /tools/gui/gzbd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/westerndigitalcorporation/libzbd/d7cf16b234f15bf0e6e9f571ffc1c1cf86af89cd/tools/gui/gzbd.png -------------------------------------------------------------------------------- /tools/gui/gzbd_if.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | /* 3 | * SPDX-FileCopyrightText: 2020 Western Digital Corporation or its affiliates. 4 | * 5 | * Authors: Damien Le Moal (damien.lemoal@wdc.com) 6 | */ 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "gzbd.h" 15 | 16 | static void dz_if_show_nodev(void); 17 | static void dz_if_hide_nodev(void); 18 | 19 | static gboolean dz_if_resize_cb(GtkWidget *widget, GdkEvent *event, 20 | gpointer user_data); 21 | static void dz_if_delete_cb(GtkWidget *widget, GdkEvent *event, 22 | gpointer user_data); 23 | static void dz_if_open_cb(GtkWidget *widget, gpointer user_data); 24 | static void dz_if_close_cb(GtkWidget *widget, gpointer user_data); 25 | static void dz_if_close_page_cb(GtkWidget *widget, gpointer user_data); 26 | static void dz_if_exit_cb(GtkWidget *widget, gpointer user_data); 27 | 28 | void dz_if_err(const char *msg, const char *fmt, ...) 29 | { 30 | GtkWidget *dialog; 31 | 32 | dialog = gtk_message_dialog_new(GTK_WINDOW(dz.window), 33 | GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, 34 | GTK_MESSAGE_ERROR, 35 | GTK_BUTTONS_OK, 36 | "%s", msg); 37 | if (fmt) { 38 | va_list args; 39 | char secondary[256]; 40 | 41 | va_start(args, fmt); 42 | vsnprintf(secondary, 255, fmt, args); 43 | va_end(args); 44 | 45 | gtk_message_dialog_format_secondary_text 46 | (GTK_MESSAGE_DIALOG(dialog), 47 | "%s", secondary); 48 | } 49 | 50 | gtk_dialog_run(GTK_DIALOG(dialog)); 51 | gtk_widget_destroy(dialog); 52 | } 53 | 54 | void dz_if_create(void) 55 | { 56 | GtkWidget *toolbar; 57 | GtkWidget *sep; 58 | GtkToolItem *ti; 59 | 60 | /* Get colors */ 61 | gdk_rgba_parse(&dz.conv_color, "Magenta"); 62 | gdk_rgba_parse(&dz.seqnw_color, "Green"); 63 | gdk_rgba_parse(&dz.seqw_color, "Red"); 64 | gdk_rgba_parse(&dz.nonw_color, "RosyBrown"); 65 | 66 | /* Window */ 67 | dz.window = gtk_window_new(GTK_WINDOW_TOPLEVEL); 68 | gtk_window_set_title(GTK_WINDOW(dz.window), 69 | "Zoned Block Device Zone State"); 70 | gtk_container_set_border_width(GTK_CONTAINER(dz.window), 10); 71 | 72 | g_signal_connect((gpointer) dz.window, "delete-event", 73 | G_CALLBACK(dz_if_delete_cb), 74 | NULL); 75 | 76 | /* Top vbox */ 77 | dz.vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10); 78 | gtk_widget_show(dz.vbox); 79 | gtk_container_add(GTK_CONTAINER(dz.window), dz.vbox); 80 | 81 | /* Toolbar */ 82 | toolbar = gtk_toolbar_new(); 83 | gtk_toolbar_set_style(GTK_TOOLBAR(toolbar), GTK_TOOLBAR_ICONS); 84 | gtk_widget_show(toolbar); 85 | gtk_box_pack_start(GTK_BOX(dz.vbox), toolbar, FALSE, FALSE, 0); 86 | 87 | /* Toolbar open button */ 88 | ti = gtk_tool_button_new(gtk_image_new_from_icon_name("document-open", 89 | GTK_ICON_SIZE_LARGE_TOOLBAR), "Open"); 90 | gtk_tool_item_set_tooltip_text(ti, "Open a device"); 91 | gtk_tool_item_set_is_important(ti, TRUE); 92 | gtk_toolbar_insert(GTK_TOOLBAR(toolbar), ti, -1); 93 | g_signal_connect(G_OBJECT(ti), "clicked", 94 | G_CALLBACK(dz_if_open_cb), NULL); 95 | 96 | /* Toolbar close button */ 97 | ti = gtk_tool_button_new(gtk_image_new_from_icon_name("window-close", 98 | GTK_ICON_SIZE_LARGE_TOOLBAR), "Close"); 99 | gtk_tool_item_set_tooltip_text(ti, "Close current device"); 100 | gtk_tool_item_set_is_important(ti, TRUE); 101 | gtk_toolbar_insert(GTK_TOOLBAR(toolbar), ti, -1); 102 | g_signal_connect(G_OBJECT(ti), "clicked", 103 | G_CALLBACK(dz_if_close_cb), NULL); 104 | 105 | /* Separator */ 106 | ti = gtk_separator_tool_item_new(); 107 | gtk_toolbar_insert(GTK_TOOLBAR(toolbar), ti, -1); 108 | 109 | /* Toolbar exit button */ 110 | ti = gtk_tool_button_new(gtk_image_new_from_icon_name("application-exit", 111 | GTK_ICON_SIZE_LARGE_TOOLBAR), "Quit"); 112 | gtk_tool_item_set_tooltip_text(ti, "Quit"); 113 | gtk_toolbar_insert(GTK_TOOLBAR(toolbar), ti, -1); 114 | g_signal_connect(G_OBJECT(ti), "clicked", 115 | G_CALLBACK(dz_if_exit_cb), NULL); 116 | 117 | /* Separator */ 118 | sep = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL); 119 | gtk_widget_show(sep); 120 | gtk_box_pack_start(GTK_BOX(dz.vbox), sep, FALSE, FALSE, 0); 121 | 122 | /* Initially, no device open: show "no device" frame */ 123 | dz_if_show_nodev(); 124 | 125 | /* Finish setup */ 126 | g_signal_connect((gpointer) dz.window, "configure-event", 127 | G_CALLBACK(dz_if_resize_cb), NULL); 128 | 129 | gtk_window_set_default_size(GTK_WINDOW(dz.window), 1024, 768); 130 | gtk_widget_show_all(dz.window); 131 | } 132 | 133 | void dz_if_destroy(void) 134 | { 135 | if ( dz.window ) { 136 | gtk_widget_destroy(dz.window); 137 | dz.window = NULL; 138 | } 139 | } 140 | 141 | void dz_if_add_device(char *dev_path) 142 | { 143 | GtkWidget *label; 144 | GtkWidget *hbox; 145 | GtkWidget *button; 146 | dz_dev_t *dzd; 147 | int page_no; 148 | char str[256]; 149 | 150 | /* Open device */ 151 | dzd = dz_if_dev_open(dev_path); 152 | if (!dzd) 153 | return; 154 | 155 | dz_if_hide_nodev(); 156 | 157 | /* Add page */ 158 | hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 2); 159 | 160 | snprintf(str, sizeof(str) - 1, "%s", dzd->path); 161 | label = gtk_label_new(NULL); 162 | gtk_label_set_text(GTK_LABEL(label), str); 163 | gtk_label_set_use_markup(GTK_LABEL(label), TRUE); 164 | gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0); 165 | 166 | button = gtk_button_new_from_icon_name("window-close", GTK_ICON_SIZE_BUTTON); 167 | gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0); 168 | g_signal_connect((gpointer) button, "clicked", 169 | G_CALLBACK(dz_if_close_page_cb), dzd); 170 | 171 | gtk_widget_show_all(hbox); 172 | 173 | page_no = gtk_notebook_append_page(GTK_NOTEBOOK(dz.notebook), 174 | dzd->page_frame, hbox); 175 | dzd->page = gtk_notebook_get_nth_page(GTK_NOTEBOOK(dz.notebook), page_no); 176 | gtk_notebook_set_current_page(GTK_NOTEBOOK(dz.notebook), page_no); 177 | } 178 | 179 | static void dz_if_show_nodev(void) 180 | { 181 | 182 | if (dz.notebook) { 183 | /* Remove notebook */ 184 | gtk_widget_destroy(dz.notebook); 185 | dz.notebook = NULL; 186 | } 187 | 188 | if (!dz.no_dev_frame) { 189 | 190 | GtkWidget *frame; 191 | GtkWidget *label; 192 | 193 | frame = gtk_frame_new(NULL); 194 | gtk_widget_show(frame); 195 | gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN); 196 | gtk_container_set_border_width(GTK_CONTAINER(frame), 10); 197 | gtk_box_pack_start(GTK_BOX(dz.vbox), frame, TRUE, TRUE, 0); 198 | 199 | label = gtk_label_new(NULL); 200 | gtk_widget_show(label); 201 | gtk_label_set_text(GTK_LABEL(label), "No device open"); 202 | gtk_label_set_use_markup(GTK_LABEL(label), TRUE); 203 | gtk_container_add(GTK_CONTAINER(frame), label); 204 | 205 | dz.no_dev_frame = frame; 206 | 207 | } 208 | } 209 | 210 | static void dz_if_hide_nodev(void) 211 | { 212 | 213 | if (dz.no_dev_frame) { 214 | /* Remove "no device" frame */ 215 | gtk_widget_destroy(dz.no_dev_frame); 216 | dz.no_dev_frame = NULL; 217 | } 218 | 219 | if (!dz.notebook) { 220 | /* Create the notebook */ 221 | dz.notebook = gtk_notebook_new(); 222 | gtk_notebook_set_tab_pos(GTK_NOTEBOOK(dz.notebook), GTK_POS_TOP); 223 | gtk_notebook_set_show_border(GTK_NOTEBOOK(dz.notebook), TRUE); 224 | gtk_notebook_set_show_tabs(GTK_NOTEBOOK(dz.notebook), TRUE); 225 | gtk_widget_show(dz.notebook); 226 | gtk_box_pack_start(GTK_BOX(dz.vbox), dz.notebook, TRUE, TRUE, 0); 227 | } 228 | } 229 | 230 | static void dz_if_remove_device(dz_dev_t *dzd) 231 | { 232 | int page_no = gtk_notebook_page_num(GTK_NOTEBOOK(dz.notebook), dzd->page_frame); 233 | 234 | /* Close the device */ 235 | dz_if_dev_close(dzd); 236 | 237 | /* Remove the page */ 238 | gtk_notebook_remove_page(GTK_NOTEBOOK(dz.notebook), page_no); 239 | dzd->page = NULL; 240 | 241 | if (dz.nr_devs == 0) 242 | /* Show "no device" */ 243 | dz_if_show_nodev(); 244 | } 245 | 246 | static dz_dev_t *dz_if_get_device(void) 247 | { 248 | dz_dev_t *dzd = NULL; 249 | GtkWidget *page = NULL; 250 | int i; 251 | 252 | if (!dz.notebook) 253 | return NULL; 254 | 255 | page = gtk_notebook_get_nth_page(GTK_NOTEBOOK(dz.notebook), 256 | gtk_notebook_get_current_page(GTK_NOTEBOOK(dz.notebook))); 257 | for (i = 0; i < DZ_MAX_DEV; i++) { 258 | dzd = &dz.dev[i]; 259 | if (dzd->dev_fd >= 0 && dzd->page == page) 260 | return dzd; 261 | } 262 | 263 | return NULL; 264 | } 265 | 266 | static void dz_if_open_cb(GtkWidget *widget, gpointer user_data) 267 | { 268 | GtkFileChooser *chooser; 269 | GtkFileFilter *filter; 270 | GtkWidget *dialog; 271 | char *dev_path = NULL; 272 | gint res; 273 | 274 | /* File chooser */ 275 | dialog = gtk_file_chooser_dialog_new("Open Zoned Block Device", 276 | GTK_WINDOW(dz.window), 277 | GTK_FILE_CHOOSER_ACTION_OPEN, 278 | "_Cancel", GTK_RESPONSE_CANCEL, 279 | "_Open", GTK_RESPONSE_ACCEPT, 280 | NULL); 281 | 282 | chooser = GTK_FILE_CHOOSER(dialog); 283 | gtk_file_chooser_set_current_folder(chooser, "/dev/"); 284 | 285 | filter = gtk_file_filter_new(); 286 | gtk_file_filter_set_name(filter, "Block Device Files"); 287 | gtk_file_filter_add_mime_type(filter, "inode/blockdevice"); 288 | gtk_file_chooser_add_filter(chooser, filter); 289 | 290 | res = gtk_dialog_run(GTK_DIALOG(dialog)); 291 | if (res == GTK_RESPONSE_ACCEPT) 292 | dev_path = gtk_file_chooser_get_filename(chooser); 293 | 294 | gtk_widget_destroy(dialog); 295 | 296 | if (dev_path) { 297 | dz_if_add_device(dev_path); 298 | g_free(dev_path); 299 | } 300 | } 301 | 302 | static void dz_if_close_cb(GtkWidget *widget, gpointer user_data) 303 | { 304 | dz_dev_t *dzd = dz_if_get_device(); 305 | 306 | if (dzd) 307 | dz_if_remove_device(dzd); 308 | } 309 | 310 | static void dz_if_close_page_cb(GtkWidget *widget, gpointer user_data) 311 | { 312 | dz_dev_t *dzd = (dz_dev_t *) user_data; 313 | 314 | if (dzd) 315 | dz_if_remove_device(dzd); 316 | } 317 | 318 | static void dz_if_exit_cb(GtkWidget *widget, gpointer user_data) 319 | { 320 | dz_dev_t *dzd; 321 | int i; 322 | 323 | if (dz.notebook) { 324 | for (i = 0; i < DZ_MAX_DEV; i++) { 325 | dzd = &dz.dev[i]; 326 | if (dzd->dev_fd >= 0) 327 | dz_if_remove_device(dzd); 328 | } 329 | } 330 | 331 | gtk_main_quit(); 332 | } 333 | 334 | static gboolean dz_if_resize_cb(GtkWidget *widget, GdkEvent *event, 335 | gpointer user_data) 336 | { 337 | dz_dev_t *dzd = dz_if_get_device(); 338 | 339 | if (dzd) 340 | dz_if_dev_update(dzd, 0); 341 | 342 | return FALSE; 343 | } 344 | 345 | static void dz_if_delete_cb(GtkWidget *widget, GdkEvent *event, 346 | gpointer user_data) 347 | { 348 | dz_dev_t *dzd; 349 | int i; 350 | 351 | dz.window = NULL; 352 | 353 | if (dz.notebook) { 354 | for (i = 0; i < DZ_MAX_DEV; i++) { 355 | dzd = &dz.dev[i]; 356 | if (dzd->dev_fd >= 0) 357 | dz_if_remove_device(dzd); 358 | } 359 | } 360 | 361 | gtk_main_quit(); 362 | } 363 | -------------------------------------------------------------------------------- /tools/gui/org.gnome.gzbd.policy.in: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | The libzbd Project 7 | https://github.com/westerndigitalcorporation/libzbd 8 | gzbd 9 | 10 | 11 | Run gzbd as root 12 | Authentication is required to run gzbd as root 13 | 14 | auth_admin 15 | auth_admin 16 | auth_admin 17 | 18 | @bindir@/gzbd 19 | true 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /tools/viewer/Makefile.am: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-3.0-or-later 2 | # 3 | # SPDX-FileCopyrightText: 2020 Western Digital Corporation or its affiliates. 4 | 5 | AM_LDFLAGS = -lm 6 | 7 | polkit_action_in_FILES += viewer/org.gnome.gzbd-viewer.policy.in 8 | desktop_in_FILES += viewer/gzbd-viewer.desktop.in 9 | pixmap_FILES += viewer/gzbd-viewer.png 10 | 11 | bin_PROGRAMS += gzbd-viewer 12 | gzbd_viewer_SOURCES = \ 13 | viewer/gzbd_viewer.c \ 14 | viewer/gzbd_viewer_if.c \ 15 | viewer/gzbd_viewer.h 16 | 17 | gzbd_viewer_CFLAGS = $(CFLAGS) $(GTK_CFLAGS) 18 | gzbd_viewer_LDADD = $(libzbd_ldadd) $(GTK_LIBS) 19 | 20 | dist_man8_MANS += viewer/gzbd-viewer.8 21 | 22 | viewer/org.gnome.gzbd-viewer.policy: viewer/org.gnome.gzbd-viewer.policy.in Makefile 23 | $(bin_subst) < viewer/org.gnome.gzbd-viewer.policy.in > viewer/org.gnome.gzbd-viewer.policy 24 | 25 | viewer/gzbd-viewer.desktop: viewer/gzbd-viewer.desktop.in Makefile 26 | $(bin_subst) < viewer/gzbd-viewer.desktop.in > viewer/gzbd-viewer.desktop 27 | -------------------------------------------------------------------------------- /tools/viewer/gzbd-viewer.8: -------------------------------------------------------------------------------- 1 | .\" SPDX-License-Identifier: GPL-3.0-or-later 2 | .\" SPDX-FileCopyrightText: 2020, Western Digital Corporation or its affiliates. 3 | .\" Written by Damien Le Moal 4 | .\" 5 | .TH GZBD-VIEWER 8 6 | .SH NAME 7 | gzbd-viewer \- zoned block devices zone state graphical interface 8 | 9 | .SH SYNOPSIS 10 | .B gzbd-viewer 11 | [options] 12 | .I device 13 | 14 | .SH DESCRIPTION 15 | .B gzbd-viewer 16 | is used to dynamically display the state of zones of a zoned block device. 17 | Each zone is displayed as a box representing the sector range of the zone. 18 | A zone box is filled with different colors based on the zone type and on 19 | the use of sectors within the zone. The condition of zones is indicated 20 | by highlighting a zone box border with different colors. 21 | 22 | .B Zone type and usage color codes 23 | .TS 24 | tab(:); 25 | l l. 26 | Magenta:Conventional zones 27 | Red:Written sectors of a sequential zone 28 | Green:Unwritten and usable sectors of a sequential zone 29 | Rosy brown:Unusable sectors of a sequential zone 30 | .TE 31 | 32 | .B Zone condition color codes 33 | .TS 34 | tab(:); 35 | l l. 36 | Light blue:Explicitly opened zones 37 | Dark blue:Implicitly opened zones 38 | Dark orange:Closed zones 39 | Grey:Offline zones 40 | .TE 41 | 42 | .PP 43 | The 44 | .I device 45 | argument must be the pathname of the target zoned block device. 46 | 47 | .PP 48 | By default, \fBgzbd-viewer\fP will display at most 100 zones using a 49 | 10 x 10 grid. This default display size can be changed using options 50 | as explained below. 51 | 52 | .SH OPTIONS 53 | .TP 54 | .BR \-? , " \-\-help" 55 | Show help options and exit. 56 | .TP 57 | .BR \-\-help\-all 58 | Show all help options and exit. 59 | .TP 60 | .BR \-\-help\-gtk 61 | Show GTK+ options and exit. 62 | .TP 63 | .BR \-v , " \-\-verbose" 64 | Use libzbd verbose mode (for debug). 65 | .TP 66 | .BR \-i , " \-\-interval " \fImsec\fP 67 | Set refresh interval to \fImsec\fP milliseconds (default: 500). 68 | .TP 69 | .BR \-w , " \-\-width " \fIcolumns\fP 70 | Display zones in a matrix of \fIcolumns\fP columns. 71 | .TP 72 | .BR \-h , " \-\-height " \fIrows\fP 73 | Display zones in a matrix of \fIrows\fP rows. 74 | .TP 75 | .BR \-b , " \-\-block " \fIsize\fP 76 | Use \fIsize\fP as the unit for displaying a zone position, 77 | length and write pointer position instead of the default byte 78 | value. 79 | .TP 80 | .BR \-\-display=DISPLAY 81 | Specify the X display to use. 82 | 83 | .SH AUTHOR 84 | .nf 85 | Damien Le Moal 86 | .fi 87 | 88 | .SH SEE ALSO 89 | .BR zbd (8) ", " gzbd (8) 90 | 91 | .SH AVAILABILITY 92 | The gzbd-viewer utility is part of the libzbd library available from 93 | .UR https://\:github.com\:/westerndigitalcorporation\:/libzbd 94 | .UE . 95 | -------------------------------------------------------------------------------- /tools/viewer/gzbd-viewer.desktop.in: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=gzbd-viewer 3 | Comment=Display the zone state of a Zoned Block Device 4 | Keywords=disk;drive;harddisk;hdd;disc;smr;SMR;ssd;zns;ZNS; 5 | Exec=pkexec --disable-internal-agent "@bindir@/gzbd-viewer" 6 | Icon=gzbd-viewer 7 | Terminal=false 8 | Type=Application 9 | Categories=GNOME;GTK;System; 10 | -------------------------------------------------------------------------------- /tools/viewer/gzbd-viewer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/westerndigitalcorporation/libzbd/d7cf16b234f15bf0e6e9f571ffc1c1cf86af89cd/tools/viewer/gzbd-viewer.png -------------------------------------------------------------------------------- /tools/viewer/gzbd_viewer.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | /* 3 | * SPDX-FileCopyrightText: 2020 Western Digital Corporation or its affiliates. 4 | * 5 | * Authors: Damien Le Moal (damien.lemoal@wdc.com) 6 | */ 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include "gzbd_viewer.h" 17 | 18 | /* 19 | * Device control. 20 | */ 21 | struct gzv gzv; 22 | 23 | /* 24 | * Signal handling. 25 | */ 26 | static gboolean gzv_process_signal(GIOChannel *source, 27 | GIOCondition condition, 28 | gpointer user_data) 29 | { 30 | char buf[32]; 31 | ssize_t size; 32 | 33 | if (condition & G_IO_IN) { 34 | size = read(g_io_channel_unix_get_fd(source), buf, sizeof(buf)); 35 | if (size > 0) { 36 | /* Got signal */ 37 | gtk_main_quit(); 38 | return TRUE; 39 | } 40 | } 41 | 42 | return FALSE; 43 | } 44 | 45 | static void gzv_sig_handler(int sig) 46 | { 47 | /* Propagate signal through the pipe */ 48 | if (write(gzv.sig_pipe[1], &sig, sizeof(int)) < 0) 49 | printf("Signal %d processing failed\n", sig); 50 | } 51 | 52 | static void gzv_set_signal_handlers(void) 53 | { 54 | GIOChannel *sig_channel; 55 | long fd_flags; 56 | int ret; 57 | 58 | ret = pipe(gzv.sig_pipe); 59 | if (ret < 0) { 60 | perror("pipe"); 61 | exit(1); 62 | } 63 | 64 | fd_flags = fcntl(gzv.sig_pipe[1], F_GETFL); 65 | if (fd_flags < 0) { 66 | perror("Read descriptor flags"); 67 | exit(1); 68 | } 69 | ret = fcntl(gzv.sig_pipe[1], F_SETFL, fd_flags | O_NONBLOCK); 70 | if (ret < 0) { 71 | perror("Write descriptor flags"); 72 | exit(1); 73 | } 74 | 75 | /* Install the unix signal handler */ 76 | signal(SIGINT, gzv_sig_handler); 77 | signal(SIGQUIT, gzv_sig_handler); 78 | signal(SIGTERM, gzv_sig_handler); 79 | 80 | /* Convert the reading end of the pipe into a GIOChannel */ 81 | sig_channel = g_io_channel_unix_new(gzv.sig_pipe[0]); 82 | g_io_channel_set_encoding(sig_channel, NULL, NULL); 83 | g_io_channel_set_flags(sig_channel, 84 | g_io_channel_get_flags(sig_channel) | 85 | G_IO_FLAG_NONBLOCK, 86 | NULL); 87 | g_io_add_watch(sig_channel, 88 | G_IO_IN | G_IO_PRI, 89 | gzv_process_signal, NULL); 90 | } 91 | 92 | /* 93 | * Fix offset/length values to the specified block size. 94 | */ 95 | static void gzv_fix_zone_values(struct zbd_zone *zbdz, int nrz) 96 | { 97 | int i; 98 | 99 | if (gzv.block_size == 1) 100 | return; 101 | 102 | for (i = 0; i < nrz; i++) { 103 | zbdz->start /= gzv.block_size; 104 | zbdz->len /= gzv.block_size; 105 | zbdz->capacity /= gzv.block_size; 106 | if (!zbd_zone_cnv(zbdz)) 107 | zbdz->wp /= gzv.block_size; 108 | zbdz++; 109 | } 110 | } 111 | 112 | /* 113 | * Close a device. 114 | */ 115 | static void gzv_close(void) 116 | { 117 | 118 | if (gzv.dev_fd < 0) 119 | return; 120 | 121 | zbd_close(gzv.dev_fd); 122 | gzv.dev_fd = -1; 123 | 124 | free(gzv.zones); 125 | free(gzv.grid_zones); 126 | } 127 | 128 | static char *gzv_choose_dev(void) 129 | { 130 | GtkFileChooser *chooser; 131 | GtkFileFilter *filter; 132 | GtkWidget *dialog; 133 | char *path = NULL; 134 | gint res; 135 | 136 | /* File chooser */ 137 | dialog = gtk_file_chooser_dialog_new("Open Zoned Block Device", 138 | GTK_WINDOW(gzv.window), 139 | GTK_FILE_CHOOSER_ACTION_OPEN, 140 | "_Cancel", GTK_RESPONSE_CANCEL, 141 | "_Open", GTK_RESPONSE_ACCEPT, 142 | NULL); 143 | 144 | chooser = GTK_FILE_CHOOSER(dialog); 145 | gtk_file_chooser_set_current_folder(chooser, "/dev"); 146 | 147 | filter = gtk_file_filter_new(); 148 | gtk_file_filter_set_name(filter, "Block Device Files"); 149 | gtk_file_filter_add_mime_type(filter, "inode/blockdevice"); 150 | gtk_file_chooser_add_filter(chooser, filter); 151 | 152 | res = gtk_dialog_run(GTK_DIALOG(dialog)); 153 | if (res == GTK_RESPONSE_ACCEPT) 154 | path = gtk_file_chooser_get_filename(chooser); 155 | 156 | gtk_widget_destroy(dialog); 157 | 158 | return path; 159 | } 160 | 161 | /* 162 | * Open a device. 163 | */ 164 | static int gzv_open(void) 165 | { 166 | unsigned int i; 167 | int ret; 168 | 169 | /* Open device file */ 170 | gzv.dev_fd = zbd_open(gzv.path, O_RDONLY, &gzv.info); 171 | if (gzv.dev_fd < 0) 172 | return -1; 173 | 174 | if (gzv.block_size > 1 && 175 | gzv.info.zone_size % gzv.block_size) { 176 | fprintf(stderr, "Invalid block size\n"); 177 | return 1; 178 | } 179 | 180 | /* Get list of all zones */ 181 | ret = zbd_list_zones(gzv.dev_fd, 0, 0, ZBD_RO_ALL, 182 | &gzv.zones, &gzv.nr_zones); 183 | if (ret != 0) 184 | goto out; 185 | if (!gzv.nr_zones) { 186 | fprintf(stderr, "No zones reported\n"); 187 | ret = 1; 188 | goto out; 189 | } 190 | 191 | gzv_fix_zone_values(gzv.zones, gzv.nr_zones); 192 | 193 | for (i = 0; i < gzv.nr_zones; i++) { 194 | if (zbd_zone_cnv(&gzv.zones[i])) 195 | gzv.nr_conv_zones++; 196 | } 197 | 198 | /* Set defaults */ 199 | if (!gzv.nr_col && !gzv.nr_row && gzv.nr_zones < 100) { 200 | gzv.nr_col = sqrt(gzv.nr_zones); 201 | gzv.nr_row = (gzv.nr_zones + gzv.nr_col - 1) / gzv.nr_col; 202 | } else { 203 | if (!gzv.nr_col) 204 | gzv.nr_col = 10; 205 | if (!gzv.nr_row) 206 | gzv.nr_row = 10; 207 | } 208 | gzv.max_row = (gzv.nr_zones + gzv.nr_col - 1) / gzv.nr_col; 209 | 210 | /* Allocate zone array */ 211 | gzv.nr_grid_zones = gzv.nr_col * gzv.nr_row; 212 | gzv.grid_zones = calloc(gzv.nr_grid_zones, sizeof(struct gzv_zone)); 213 | if (!gzv.grid_zones) { 214 | ret = -ENOMEM; 215 | goto out; 216 | } 217 | 218 | for (i = 0; i < gzv.nr_grid_zones && i < gzv.nr_zones; i++) { 219 | gzv.grid_zones[i].zno = i; 220 | gzv.grid_zones[i].zbdz = &gzv.zones[i]; 221 | } 222 | 223 | out: 224 | if (ret) 225 | gzv_close(); 226 | 227 | return ret; 228 | } 229 | 230 | int main(int argc, char **argv) 231 | { 232 | gboolean init_ret; 233 | gboolean verbose = FALSE; 234 | GError *error = NULL; 235 | int ret = 0; 236 | GOptionEntry options[] = { 237 | { 238 | "verbose", 'v', 0, 239 | G_OPTION_ARG_NONE, &verbose, 240 | "Set libzbd verbose mode", 241 | NULL 242 | }, 243 | { 244 | "interval", 'i', 0, 245 | G_OPTION_ARG_INT, &gzv.refresh_interval, 246 | "Refresh interval (milliseconds)", 247 | NULL 248 | }, 249 | { 250 | "width", 'w', 0, 251 | G_OPTION_ARG_INT, &gzv.nr_col, 252 | "Number of zones per row (default: 10)", 253 | NULL 254 | }, 255 | { 256 | "height", 'h', 0, 257 | G_OPTION_ARG_INT, &gzv.nr_row, 258 | "Number of rows (default: 10)", 259 | NULL 260 | }, 261 | { 262 | "block", 'b', 0, 263 | G_OPTION_ARG_INT, &gzv.block_size, 264 | "Use block bytes as the unit for displaying zone " 265 | "position, length and write pointer position instead " 266 | "of the default byte value", 267 | NULL 268 | }, 269 | { NULL } 270 | }; 271 | 272 | /* Init */ 273 | memset(&gzv, 0, sizeof(gzv)); 274 | gzv.dev_fd = -1; 275 | gzv.block_size = 1; 276 | init_ret = gtk_init_with_args(&argc, &argv, 277 | "", 278 | options, NULL, &error); 279 | if (init_ret == FALSE || 280 | error != NULL) { 281 | printf("Failed to parse command line arguments: %s\n", 282 | error->message); 283 | g_error_free(error); 284 | return 1; 285 | } 286 | 287 | if (gzv.refresh_interval < 0) { 288 | fprintf(stderr, "Invalid update interval\n"); 289 | return 1; 290 | } 291 | 292 | if (gzv.block_size <= 0) { 293 | fprintf(stderr, "Invalid block size\n"); 294 | return 1; 295 | } 296 | 297 | if (verbose) 298 | zbd_set_log_level(ZBD_LOG_DEBUG); 299 | 300 | /* Set default values and open the device */ 301 | if (!gzv.refresh_interval) 302 | gzv.refresh_interval = 500; 303 | 304 | /* Create the main window */ 305 | gzv_if_create_window(); 306 | 307 | /* Check user credentials */ 308 | if (getuid() != 0) { 309 | gzv_if_err("Root privileges are required for running gzbd-viewer", 310 | "Opening a block device file can only be done with " 311 | "elevated priviledges"); 312 | ret = 1; 313 | goto out; 314 | } 315 | 316 | if (argc < 2) { 317 | /* No device specified: use the file chooser */ 318 | gzv.path = gzv_choose_dev(); 319 | if (!gzv.path) { 320 | gzv_if_err("No device specified", 321 | "Specifying a zoned block device is mandatory"); 322 | fprintf(stderr, "No device specified\n"); 323 | ret = 1; 324 | goto out; 325 | } 326 | } else { 327 | gzv.path = argv[1]; 328 | } 329 | 330 | if (gzv_open()) { 331 | ret = errno; 332 | gzv_if_err("Open device failed", 333 | "Opening %s generated error %d (%s)", 334 | gzv.path, ret, strerror(ret)); 335 | fprintf(stderr, "Open device %s failed %d (%s)\n", 336 | gzv.path, ret, strerror(ret)); 337 | ret = 1; 338 | goto out; 339 | } 340 | 341 | gzv_set_signal_handlers(); 342 | 343 | /* Create GUI */ 344 | gzv_if_create(); 345 | 346 | /* Main event loop */ 347 | gtk_main(); 348 | 349 | out: 350 | /* Cleanup GUI */ 351 | gzv_if_destroy(); 352 | 353 | return ret; 354 | } 355 | 356 | /* 357 | * Report zones. 358 | */ 359 | int gzv_report_zones(unsigned int zno_start, unsigned int nr_zones) 360 | { 361 | unsigned int nrz; 362 | int ret; 363 | 364 | if (zno_start >= gzv.nr_zones) 365 | return 0; 366 | 367 | nrz = nr_zones; 368 | if (zno_start + nrz > gzv.nr_zones) 369 | nrz = gzv.nr_zones - zno_start; 370 | 371 | /* Get zone information */ 372 | ret = zbd_report_zones(gzv.dev_fd, 373 | zbd_zone_start(&gzv.zones[zno_start]), 374 | nrz * gzv.info.zone_size, 375 | ZBD_RO_ALL, &gzv.zones[zno_start], &nrz); 376 | if (ret) { 377 | fprintf(stderr, "Get zone information failed %d (%s)\n", 378 | errno, strerror(errno)); 379 | return ret; 380 | } 381 | 382 | gzv_fix_zone_values(&gzv.zones[zno_start], nrz); 383 | 384 | return 0; 385 | } 386 | 387 | -------------------------------------------------------------------------------- /tools/viewer/gzbd_viewer.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-3.0-or-later */ 2 | /* 3 | * SPDX-FileCopyrightText: 2020 Western Digital Corporation or its affiliates. 4 | * 5 | * Authors: Damien Le Moal (damien.lemoal@wdc.com) 6 | */ 7 | #ifndef __GZVIEWER_H__ 8 | #define __GZVIEWER_H__ 9 | 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | /* 16 | * Device zone information. 17 | */ 18 | struct gzv_zone { 19 | unsigned int zno; 20 | struct zbd_zone *zbdz; 21 | GtkWidget *da; 22 | }; 23 | 24 | /* 25 | * GUI data. 26 | */ 27 | struct gzv { 28 | 29 | /* 30 | * Parameters. 31 | */ 32 | int refresh_interval; 33 | int block_size; 34 | int abort; 35 | 36 | /* 37 | * For handling timer and signals. 38 | */ 39 | GSource *refresh_timer; 40 | unsigned long long last_refresh; 41 | int sig_pipe[2]; 42 | 43 | /* 44 | * Interface stuff. 45 | */ 46 | GdkRGBA color_conv; 47 | GdkRGBA color_seq; 48 | GdkRGBA color_seqw; 49 | GdkRGBA color_nonw; 50 | GdkRGBA color_text; 51 | GdkRGBA color_oi; 52 | GdkRGBA color_oe; 53 | GdkRGBA color_cl; 54 | GdkRGBA color_of; 55 | GtkWidget *window; 56 | GtkAdjustment *vadj; 57 | 58 | /* 59 | * Device information. 60 | */ 61 | char *path; 62 | int dev_fd; 63 | struct zbd_info info; 64 | unsigned int nr_zones; 65 | unsigned int nr_conv_zones; 66 | struct zbd_zone *zones; 67 | 68 | /* 69 | * Drawn zones. 70 | */ 71 | unsigned int nr_row; 72 | unsigned int nr_col; 73 | unsigned int nr_grid_zones; 74 | unsigned int max_row; 75 | struct gzv_zone *grid_zones; 76 | unsigned int grid_zno_first; 77 | }; 78 | 79 | /** 80 | * System time in usecs. 81 | */ 82 | static inline unsigned long long gzv_msec(void) 83 | { 84 | struct timeval tv; 85 | 86 | gettimeofday(&tv, NULL); 87 | 88 | return (unsigned long long) tv.tv_sec * 1000LL + 89 | (unsigned long long) tv.tv_usec / 1000; 90 | } 91 | 92 | extern struct gzv gzv; 93 | 94 | int gzv_report_zones(unsigned int zno_start, unsigned int nr_zones); 95 | 96 | void gzv_if_err(const char *msg, const char *fmt, ...); 97 | void gzv_if_create_window(void); 98 | void gzv_if_create(void); 99 | void gzv_if_destroy(void); 100 | 101 | #endif /* __GZWATCH_H__ */ 102 | -------------------------------------------------------------------------------- /tools/viewer/gzbd_viewer_if.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | /* 3 | * SPDX-FileCopyrightText: 2020 Western Digital Corporation or its affiliates. 4 | * 5 | * Authors: Damien Le Moal (damien.lemoal@wdc.com) 6 | */ 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "gzbd_viewer.h" 13 | 14 | static void gzv_set_zone_tooltip(struct gzv_zone *zone) 15 | { 16 | struct zbd_zone *zbdz = zone->zbdz; 17 | char info[512]; 18 | char bs[64]; 19 | 20 | if (!zbdz) { 21 | gtk_widget_set_has_tooltip(GTK_WIDGET(zone->da), false); 22 | gtk_widget_set_tooltip_markup(GTK_WIDGET(zone->da), NULL); 23 | return; 24 | } 25 | 26 | if (gzv.block_size == 1) 27 | snprintf(bs, sizeof(bs) - 1, "B"); 28 | else 29 | snprintf(bs, sizeof(bs) - 1, 30 | "%d-B blocks", gzv.block_size); 31 | 32 | if (zbd_zone_cnv(zbdz)) { 33 | snprintf(info, sizeof(info) - 1, 34 | "Zone %u:\n" 35 | " - Type: %s\n" 36 | " - Condition: %s\n" 37 | " - Start offset: %llu %s\n" 38 | " - Length: %llu %s\n" 39 | " - Capacity: %llu %s", 40 | zone->zno, 41 | zbd_zone_type_str(zbdz, false), 42 | zbd_zone_cond_str(zbdz, false), 43 | zbd_zone_start(zbdz), bs, 44 | zbd_zone_len(zbdz), bs, 45 | zbd_zone_capacity(zbdz), bs); 46 | } else { 47 | snprintf(info, sizeof(info) - 1, 48 | "Zone %u:\n" 49 | " - Type: %s\n" 50 | " - Condition: %s\n" 51 | " - Start offset: %llu %s\n" 52 | " - Length: %llu %s\n" 53 | " - Capacity: %llu %s\n" 54 | " - WP offset: +%llu %s", 55 | zone->zno, 56 | zbd_zone_type_str(zbdz, false), 57 | zbd_zone_cond_str(zbdz, false), 58 | zbd_zone_start(zbdz), bs, 59 | zbd_zone_len(zbdz), bs, 60 | zbd_zone_capacity(zbdz), bs, 61 | zbd_zone_wp(zbdz) - zbd_zone_start(zbdz), bs); 62 | } 63 | 64 | gtk_widget_set_tooltip_markup(GTK_WIDGET(zone->da), info); 65 | gtk_widget_set_has_tooltip(GTK_WIDGET(zone->da), true); 66 | } 67 | 68 | static void gzv_if_update(void) 69 | { 70 | struct zbd_zone *zbdz; 71 | unsigned int i, z; 72 | 73 | if (gzv.grid_zno_first >= gzv.nr_zones) 74 | gzv.grid_zno_first = gzv.nr_zones / gzv.nr_col; 75 | 76 | if (gzv_report_zones(gzv.grid_zno_first, gzv.nr_grid_zones)) 77 | goto out; 78 | 79 | z = gzv.grid_zno_first; 80 | for (i = 0; i < gzv.nr_grid_zones; i++) { 81 | gzv.grid_zones[i].zno = z; 82 | zbdz = gzv.grid_zones[i].zbdz; 83 | if (z >= gzv.nr_zones) { 84 | if (zbdz) 85 | gtk_widget_hide(gzv.grid_zones[i].da); 86 | gzv.grid_zones[i].zbdz = NULL; 87 | } else { 88 | gzv.grid_zones[i].zbdz = &gzv.zones[z]; 89 | if (!zbdz) 90 | gtk_widget_show(gzv.grid_zones[i].da); 91 | } 92 | gzv_set_zone_tooltip(&gzv.grid_zones[i]); 93 | gtk_widget_queue_draw(gzv.grid_zones[i].da); 94 | z++; 95 | } 96 | 97 | out: 98 | gzv.last_refresh = gzv_msec(); 99 | } 100 | 101 | static gboolean gzv_if_timer_cb(gpointer user_data) 102 | { 103 | if (gzv.last_refresh + gzv.refresh_interval <= gzv_msec()) 104 | gzv_if_update(); 105 | 106 | return TRUE; 107 | } 108 | 109 | static gboolean gzv_if_resize_cb(GtkWidget *widget, GdkEvent *event, 110 | gpointer user_data) 111 | { 112 | gzv_if_update(); 113 | 114 | return FALSE; 115 | } 116 | 117 | static void gzv_if_delete_cb(GtkWidget *widget, GdkEvent *event, 118 | gpointer user_data) 119 | { 120 | gzv.window = NULL; 121 | gtk_main_quit(); 122 | } 123 | 124 | static void gzv_if_zone_draw_nonwritable(struct zbd_zone *zbdz, cairo_t *cr, 125 | GtkAllocation *allocation) 126 | { 127 | long long w; 128 | 129 | if (zbd_zone_capacity(zbdz) == zbd_zone_len(zbdz)) 130 | return; 131 | 132 | /* Non-writable space in zone */ 133 | w = (long long)allocation->width * 134 | (zbd_zone_len(zbdz) - zbd_zone_capacity(zbdz)) / 135 | zbd_zone_len(zbdz); 136 | if (w > allocation->width) 137 | w = allocation->width; 138 | 139 | gdk_cairo_set_source_rgba(cr, &gzv.color_nonw); 140 | cairo_rectangle(cr, allocation->width - w, 0, w, allocation->height); 141 | cairo_fill(cr); 142 | } 143 | 144 | static void gzv_if_zone_draw_written(struct zbd_zone *zbdz, cairo_t *cr, 145 | GtkAllocation *allocation) 146 | { 147 | long long w; 148 | 149 | if (zbd_zone_wp(zbdz) == zbd_zone_start(zbdz)) 150 | return; 151 | 152 | /* Written space in zone */ 153 | w = (long long)allocation->width * 154 | (zbd_zone_wp(zbdz) - zbd_zone_start(zbdz)) / 155 | zbd_zone_len(zbdz); 156 | if (w > allocation->width) 157 | w = allocation->width; 158 | 159 | gdk_cairo_set_source_rgba(cr, &gzv.color_seqw); 160 | cairo_rectangle(cr, 0, 0, w, allocation->height); 161 | cairo_fill(cr); 162 | } 163 | 164 | static void gzv_if_zone_draw_num(struct gzv_zone *zone, cairo_t *cr, 165 | GtkAllocation *allocation) 166 | { 167 | cairo_text_extents_t te; 168 | char str[16]; 169 | 170 | /* Draw zone number */ 171 | gdk_cairo_set_source_rgba(cr, &gzv.color_text); 172 | cairo_select_font_face(cr, "Monospace", 173 | CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD); 174 | cairo_set_font_size(cr, 10); 175 | sprintf(str, "%05d", zone->zno); 176 | cairo_text_extents(cr, str, &te); 177 | cairo_move_to(cr, allocation->width / 2 - te.width / 2 - te.x_bearing, 178 | (allocation->height + te.height) / 2); 179 | cairo_show_text(cr, str); 180 | } 181 | 182 | static gboolean gzv_if_zone_draw_cb(GtkWidget *widget, cairo_t *cr, 183 | gpointer user_data) 184 | { 185 | struct gzv_zone *zone = user_data; 186 | struct zbd_zone *zbdz = zone->zbdz; 187 | GtkAllocation allocation; 188 | 189 | /* Current size */ 190 | gtk_widget_get_allocation(zone->da, &allocation); 191 | 192 | gtk_render_background(gtk_widget_get_style_context(widget), 193 | cr, 0, 0, allocation.width, allocation.height); 194 | 195 | if (!zbdz) 196 | return TRUE; 197 | 198 | /* Draw zone background */ 199 | if (zbd_zone_cnv(zbdz)) { 200 | gdk_cairo_set_source_rgba(cr, &gzv.color_conv); 201 | goto out_fill; 202 | } 203 | 204 | if (zbd_zone_full(zbdz)) { 205 | gdk_cairo_set_source_rgba(cr, &gzv.color_seqw); 206 | goto out_fill; 207 | } 208 | 209 | if (zbd_zone_offline(zbdz)) { 210 | gdk_cairo_set_source_rgba(cr, &gzv.color_of); 211 | goto out_fill; 212 | } 213 | 214 | gdk_cairo_set_source_rgba(cr, &gzv.color_seq); 215 | if (zbd_zone_empty(zbdz)) 216 | goto out_fill; 217 | 218 | /* Opened or closed zones */ 219 | cairo_rectangle(cr, 0, 0, allocation.width, allocation.height); 220 | cairo_fill(cr); 221 | 222 | gzv_if_zone_draw_nonwritable(zbdz, cr, &allocation); 223 | gzv_if_zone_draw_written(zbdz, cr, &allocation); 224 | 225 | /* State highlight */ 226 | if (zbd_zone_imp_open(zbdz)) 227 | gdk_cairo_set_source_rgba(cr, &gzv.color_oi); 228 | else if (zbd_zone_exp_open(zbdz)) 229 | gdk_cairo_set_source_rgba(cr, &gzv.color_oe); 230 | else 231 | gdk_cairo_set_source_rgba(cr, &gzv.color_cl); 232 | cairo_set_line_width(cr, 10); 233 | cairo_rectangle(cr, 0, 0, allocation.width, allocation.height); 234 | cairo_stroke(cr); 235 | 236 | /* Draw zone number */ 237 | gzv_if_zone_draw_num(zone, cr, &allocation); 238 | 239 | return TRUE; 240 | 241 | out_fill: 242 | cairo_rectangle(cr, 0, 0, allocation.width, allocation.height); 243 | cairo_fill(cr); 244 | 245 | gzv_if_zone_draw_nonwritable(zbdz, cr, &allocation); 246 | 247 | /* Draw zone number */ 248 | gzv_if_zone_draw_num(zone, cr, &allocation); 249 | 250 | return TRUE; 251 | } 252 | 253 | static gboolean gzv_if_scroll_cb(GtkWidget *widget, GdkEventScroll *scroll, 254 | gpointer user_data) 255 | { 256 | unsigned int row = gtk_adjustment_get_value(gzv.vadj); 257 | unsigned int new_row = row; 258 | 259 | switch (scroll->direction) { 260 | case GDK_SCROLL_UP: 261 | if (row > 0) 262 | new_row = row - 1; 263 | break; 264 | case GDK_SCROLL_DOWN: 265 | if (row < gzv.max_row) 266 | new_row = row + 1; 267 | break; 268 | case GDK_SCROLL_LEFT: 269 | case GDK_SCROLL_RIGHT: 270 | case GDK_SCROLL_SMOOTH: 271 | default: 272 | break; 273 | } 274 | 275 | if (new_row != row) 276 | gtk_adjustment_set_value(gzv.vadj, new_row); 277 | 278 | return TRUE; 279 | } 280 | 281 | static gboolean gzv_if_scroll_value_cb(GtkWidget *widget, 282 | GdkEvent *event, gpointer user_data) 283 | { 284 | unsigned int zno, row = gtk_adjustment_get_value(gzv.vadj); 285 | 286 | if (row >= gzv.max_row) 287 | row = gzv.max_row - 1; 288 | 289 | zno = row * gzv.nr_col; 290 | if (zno != gzv.grid_zno_first) { 291 | gzv.grid_zno_first = zno; 292 | gzv_if_update(); 293 | } 294 | 295 | return TRUE; 296 | } 297 | 298 | static void gzv_if_draw_legend(char *str, const GdkRGBA *color, cairo_t *cr, 299 | gint *x, gint y) 300 | { 301 | cairo_text_extents_t te; 302 | GdkRGBA border_color; 303 | gint w = 10; 304 | 305 | gdk_rgba_parse(&border_color, "Black"); 306 | gdk_cairo_set_source_rgba(cr, &border_color); 307 | cairo_set_line_width(cr, 2); 308 | cairo_rectangle(cr, *x, y - w / 2, w, w); 309 | cairo_stroke_preserve(cr); 310 | gdk_cairo_set_source_rgba(cr, color); 311 | cairo_fill(cr); 312 | *x += w; 313 | 314 | cairo_text_extents(cr, str, &te); 315 | cairo_move_to(cr, 316 | *x + 5 - te.x_bearing, 317 | y - te.height / 2 - te.y_bearing); 318 | cairo_show_text(cr, str); 319 | *x += te.x_advance + 20; 320 | } 321 | 322 | static gboolean gzv_if_draw_legend_cb(GtkWidget *widget, cairo_t *cr, 323 | gpointer data) 324 | { 325 | gint x = 10, y = 10; 326 | 327 | /* Set font */ 328 | cairo_select_font_face(cr, "Monospace", 329 | CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD); 330 | cairo_set_font_size(cr, 10); 331 | 332 | /* Conv zone legend */ 333 | gzv_if_draw_legend("Conventional zone", &gzv.color_conv, cr, &x, y); 334 | 335 | /* Seq zone legend */ 336 | gzv_if_draw_legend("Sequential zone (unwritten)", &gzv.color_seq, cr, 337 | &x, y); 338 | 339 | /* Seq written zone legend */ 340 | gzv_if_draw_legend("Sequential zone (written)", &gzv.color_seqw, cr, 341 | &x, y); 342 | 343 | /* Non-writable space legend */ 344 | gzv_if_draw_legend("Non-writable space", &gzv.color_nonw, cr, &x, y); 345 | 346 | /* Second row */ 347 | x = 10; 348 | y += 20; 349 | 350 | /* Offline zone legend */ 351 | gzv_if_draw_legend("Offline zone", &gzv.color_of, cr, &x, y); 352 | 353 | /* Implicit open zone legend */ 354 | gzv_if_draw_legend("Implicitly opened zone", &gzv.color_oi, cr, &x, y); 355 | 356 | /* Explicit open zone legend */ 357 | gzv_if_draw_legend("Explicitly opened zone", &gzv.color_oe, cr, &x, y); 358 | 359 | /* Closed open zone legend */ 360 | gzv_if_draw_legend("Closed zone", &gzv.color_cl, cr, &x, y); 361 | 362 | return FALSE; 363 | } 364 | 365 | static void gzv_if_get_da_size(unsigned int *w, unsigned int *h) 366 | { 367 | GdkDisplay *disp = gdk_display_get_default(); 368 | GdkMonitor *mon = gdk_display_get_primary_monitor(disp); 369 | GdkRectangle geom; 370 | 371 | gdk_monitor_get_geometry(mon, &geom); 372 | 373 | *w = (geom.width - 200) / gzv.nr_col; 374 | if (*w > 150) 375 | *w = 150; 376 | 377 | *h = (geom.height - 200) / gzv.nr_row; 378 | if (*h > 60) 379 | *h = 60; 380 | } 381 | 382 | void gzv_if_create_window(void) 383 | { 384 | if (gzv.window) 385 | return; 386 | 387 | gzv.window = gtk_window_new(GTK_WINDOW_TOPLEVEL); 388 | gtk_window_set_title(GTK_WINDOW(gzv.window), 389 | "Zoned Block Device Zone State"); 390 | gtk_container_set_border_width(GTK_CONTAINER(gzv.window), 10); 391 | 392 | g_signal_connect((gpointer) gzv.window, "delete-event", 393 | G_CALLBACK(gzv_if_delete_cb), 394 | NULL); 395 | } 396 | 397 | void gzv_if_err(const char *msg, const char *fmt, ...) 398 | { 399 | GtkWidget *dialog; 400 | 401 | dialog = gtk_message_dialog_new(GTK_WINDOW(gzv.window), 402 | GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, 403 | GTK_MESSAGE_ERROR, 404 | GTK_BUTTONS_OK, 405 | "%s", msg); 406 | 407 | if (fmt) { 408 | va_list args; 409 | char secondary[256]; 410 | 411 | va_start(args, fmt); 412 | vsnprintf(secondary, 255, fmt, args); 413 | va_end(args); 414 | 415 | gtk_message_dialog_format_secondary_text 416 | (GTK_MESSAGE_DIALOG(dialog), 417 | "%s", secondary); 418 | } 419 | 420 | gtk_dialog_run(GTK_DIALOG(dialog)); 421 | gtk_widget_destroy(dialog); 422 | } 423 | 424 | void gzv_if_create(void) 425 | { 426 | GtkWidget *top_vbox, *vbox, *frame, *hbox, *scrollbar, *grid, *da; 427 | GtkAdjustment *vadj; 428 | struct gzv_zone *zone; 429 | unsigned int r, c, z = 0; 430 | unsigned int da_w, da_h; 431 | char str[128]; 432 | 433 | /* Get colors */ 434 | gdk_rgba_parse(&gzv.color_conv, "Magenta"); /* Conv zones */ 435 | gdk_rgba_parse(&gzv.color_seq, "Green"); /* Seq zones */ 436 | gdk_rgba_parse(&gzv.color_seqw, "Red"); /* Seq written/full */ 437 | gdk_rgba_parse(&gzv.color_nonw, "RosyBrown"); /* Non-writable */ 438 | gdk_rgba_parse(&gzv.color_text, "Black"); 439 | gdk_rgba_parse(&gzv.color_oe, "Blue"); /* Exp open zones */ 440 | gdk_rgba_parse(&gzv.color_oi, "DeepSkyBlue"); /* Imp open zones */ 441 | gdk_rgba_parse(&gzv.color_cl, "DarkOrange"); /* Closed zones */ 442 | gdk_rgba_parse(&gzv.color_of, "Grey"); /* Offline zones */ 443 | 444 | /* Top vbox */ 445 | top_vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10); 446 | gtk_widget_show(top_vbox); 447 | gtk_container_add(GTK_CONTAINER(gzv.window), top_vbox); 448 | 449 | /* Create a top frame */ 450 | if (!gzv.nr_conv_zones) 451 | snprintf(str, sizeof(str) - 1, 452 | "%s: %u sequential zones", 453 | gzv.path, gzv.nr_zones); 454 | else 455 | snprintf(str, sizeof(str) - 1, 456 | "%s: %u zones (%u conventional + %u sequential)", 457 | gzv.path, gzv.nr_zones, gzv.nr_conv_zones, 458 | gzv.nr_zones - gzv.nr_conv_zones); 459 | frame = gtk_frame_new(str); 460 | gtk_box_pack_start(GTK_BOX(top_vbox), frame, TRUE, TRUE, 0); 461 | gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN); 462 | gtk_label_set_use_markup(GTK_LABEL(gtk_frame_get_label_widget(GTK_FRAME(frame))), TRUE); 463 | gtk_frame_set_label_align(GTK_FRAME(frame), 0.05, 0.5); 464 | gtk_widget_show(frame); 465 | 466 | /* hbox for grid and scrollbar */ 467 | hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 10); 468 | gtk_widget_show(hbox); 469 | gtk_container_set_border_width(GTK_CONTAINER(hbox), 10); 470 | gtk_container_add(GTK_CONTAINER(frame), hbox); 471 | 472 | /* Add a grid */ 473 | grid = gtk_grid_new(); 474 | gtk_container_set_border_width(GTK_CONTAINER(grid), 10); 475 | gtk_grid_set_row_homogeneous(GTK_GRID(grid), true); 476 | gtk_grid_set_row_spacing(GTK_GRID(grid), 2); 477 | gtk_grid_set_column_homogeneous(GTK_GRID(grid), true); 478 | gtk_grid_set_column_spacing(GTK_GRID(grid), 2); 479 | gtk_box_pack_start(GTK_BOX(hbox), grid, TRUE, TRUE, 0); 480 | gtk_widget_show(grid); 481 | 482 | gzv_if_get_da_size(&da_w, &da_h); 483 | for (r = 0; r < gzv.nr_row; r++) { 484 | for (c = 0; c < gzv.nr_col; c++) { 485 | zone = &gzv.grid_zones[z]; 486 | 487 | da = gtk_drawing_area_new(); 488 | gtk_widget_set_size_request(da, da_w, da_h); 489 | gtk_widget_set_hexpand(da, TRUE); 490 | gtk_widget_set_halign(da, GTK_ALIGN_FILL); 491 | gtk_widget_show(da); 492 | 493 | zone->da = da; 494 | gtk_grid_attach(GTK_GRID(grid), da, c, r, 1, 1); 495 | g_signal_connect(da, "draw", 496 | G_CALLBACK(gzv_if_zone_draw_cb), zone); 497 | z++; 498 | } 499 | } 500 | 501 | /* Add scrollbar */ 502 | vadj = gtk_adjustment_new(0, 0, gzv.max_row, 1, 1, gzv.nr_row); 503 | gzv.vadj = vadj; 504 | g_signal_connect(vadj, "value-changed", 505 | G_CALLBACK(gzv_if_scroll_value_cb), NULL); 506 | 507 | scrollbar = gtk_scrollbar_new(GTK_ORIENTATION_VERTICAL, vadj); 508 | gtk_widget_show(scrollbar); 509 | gtk_box_pack_start(GTK_BOX(hbox), scrollbar, FALSE, FALSE, 0); 510 | gtk_widget_add_events(scrollbar, GDK_SCROLL_MASK); 511 | 512 | gtk_widget_add_events(gzv.window, GDK_SCROLL_MASK); 513 | g_signal_connect(gzv.window, "scroll-event", 514 | G_CALLBACK(gzv_if_scroll_cb), NULL); 515 | 516 | /* Legend frame */ 517 | frame = gtk_frame_new("Legend"); 518 | gtk_box_pack_start(GTK_BOX(top_vbox), frame, FALSE, TRUE, 0); 519 | gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN); 520 | gtk_label_set_use_markup(GTK_LABEL(gtk_frame_get_label_widget(GTK_FRAME(frame))), TRUE); 521 | gtk_frame_set_label_align(GTK_FRAME(frame), 0.05, 0.5); 522 | gtk_widget_show(frame); 523 | 524 | vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10); 525 | gtk_container_set_border_width(GTK_CONTAINER(vbox), 10); 526 | gtk_container_add(GTK_CONTAINER(frame), vbox); 527 | gtk_widget_show(vbox); 528 | 529 | da = gtk_drawing_area_new(); 530 | gtk_widget_set_size_request(da, -1, 40); 531 | gtk_widget_show(da); 532 | gtk_container_add(GTK_CONTAINER(vbox), da); 533 | 534 | g_signal_connect((gpointer) da, "draw", 535 | G_CALLBACK(gzv_if_draw_legend_cb), NULL); 536 | 537 | /* Finish setup */ 538 | g_signal_connect(gzv.window, "configure-event", 539 | G_CALLBACK(gzv_if_resize_cb), NULL); 540 | 541 | /* Add timer for automatic refresh */ 542 | gzv.refresh_timer = g_timeout_source_new(gzv.refresh_interval); 543 | g_source_set_name(gzv.refresh_timer, "refresh-timer"); 544 | g_source_set_can_recurse(gzv.refresh_timer, FALSE); 545 | g_source_set_callback(gzv.refresh_timer, gzv_if_timer_cb, 546 | NULL, NULL); 547 | gzv.last_refresh = gzv_msec(); 548 | g_source_attach(gzv.refresh_timer, NULL); 549 | 550 | gtk_widget_show_all(gzv.window); 551 | gzv_if_update(); 552 | } 553 | 554 | void gzv_if_destroy(void) 555 | { 556 | 557 | if (gzv.refresh_timer) { 558 | g_source_destroy(gzv.refresh_timer); 559 | gzv.refresh_timer = NULL; 560 | } 561 | 562 | if (gzv.window) { 563 | gtk_widget_destroy(gzv.window); 564 | gzv.window = NULL; 565 | } 566 | } 567 | -------------------------------------------------------------------------------- /tools/viewer/org.gnome.gzbd-viewer.policy.in: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | The libzbd Project 7 | https://github.com/westerndigitalcorporation/libzbd 8 | gzbd-viewer 9 | 10 | 11 | Run gzbd-viewer as root 12 | Authentication is required to run gzbd-viewer as root 13 | 14 | auth_admin 15 | auth_admin 16 | auth_admin 17 | 18 | @bindir@/gzbd-viewer 19 | true 20 | 21 | 22 | 23 | 24 | --------------------------------------------------------------------------------