Import Upstream version 1.21+ds
This commit is contained in:
parent
7527bfa483
commit
aab8caab26
13
.gitignore
vendored
Normal file
13
.gitignore
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
.idea
|
||||
.run
|
||||
*.iml
|
||||
hd-idle
|
||||
build
|
||||
obj*
|
||||
debian/hd-idle
|
||||
debian/debhelper-build-stamp
|
||||
debian/*.debhelper
|
||||
debian/files
|
||||
debian/hd-idle.debhelper.log
|
||||
debian/hd-idle.substvars
|
||||
release
|
833
LICENSE
833
LICENSE
@ -1,281 +1,622 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
|
||||
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Library General Public License instead.) You can apply it to
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
0. Definitions.
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
1. Source Code.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
2. Basic Permissions.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
14. Revised Versions of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
NO WARRANTY
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
@ -287,15 +628,15 @@ free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
@ -304,38 +645,30 @@ the "copyright" line and a pointer to where the full notice is found.
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Library General
|
||||
Public License instead of this License.
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
||||
|
77
Makefile
77
Makefile
@ -1,8 +1,35 @@
|
||||
###############################################################################
|
||||
#
|
||||
# General Definitions
|
||||
#
|
||||
###############################################################################
|
||||
MAKEFLAGS += --silent
|
||||
|
||||
TARGET = hd-idle
|
||||
PLATFORM := $(shell uname -m)
|
||||
|
||||
ARCH :=
|
||||
ifeq ($(PLATFORM),x86_64)
|
||||
ARCH = amd64
|
||||
endif
|
||||
ifeq ($(PLATFORM),aarch64)
|
||||
ARCH = arm64
|
||||
endif
|
||||
ifeq ($(PLATFORM),armv7l)
|
||||
ARCH = armhf
|
||||
endif
|
||||
GOARCH :=
|
||||
ifeq ($(ARCH),amd64)
|
||||
GOARCH = amd64
|
||||
endif
|
||||
ifeq ($(ARCH),i386)
|
||||
GOARCH = 386
|
||||
endif
|
||||
ifeq ($(ARCH),arm64)
|
||||
GOARCH = arm64
|
||||
endif
|
||||
ifeq ($(ARCH),armhf)
|
||||
GOARCH = arm
|
||||
endif
|
||||
|
||||
ifeq ($(GOARCH),)
|
||||
$(error Invalid ARCH: $(ARCH))
|
||||
endif
|
||||
|
||||
ifdef DESTDIR
|
||||
# dh_auto_install (Debian) sets this variable
|
||||
@ -11,44 +38,22 @@ else
|
||||
TARGET_DIR ?= /usr/local
|
||||
endif
|
||||
|
||||
LIB_DIRS =
|
||||
|
||||
INC_DIRS =
|
||||
|
||||
CC ?= gcc
|
||||
CFLAGS += $(INC_DIRS) -Wall
|
||||
|
||||
LD = $(CC)
|
||||
LDFLAGS += $(LIB_DIRS)
|
||||
|
||||
###############################################################################
|
||||
#
|
||||
# Main Dependencies
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
TARGET = hd-idle
|
||||
|
||||
LIBS =
|
||||
|
||||
SRCS = hd-idle.c
|
||||
|
||||
OBJS = $(SRCS:.c=.o)
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
distclean: clean
|
||||
|
||||
clean:
|
||||
rm -f $(OBJS) $(TARGET)
|
||||
rm -f $(TARGET)
|
||||
|
||||
install: $(TARGET)
|
||||
install -D -g root -o root $(TARGET) $(TARGET_DIR)/sbin/$(TARGET)
|
||||
install -D -g root -o root $(TARGET).1 $(TARGET_DIR)/share/man/man1/$(TARGET).1
|
||||
install -Dm755 $(TARGET) $(TARGET_DIR)/sbin/$(TARGET)
|
||||
install -Dm755 debian/$(TARGET).8 $(TARGET_DIR)/share/man/man8/$(TARGET).8
|
||||
|
||||
hd-idle.o: hd-idle.c
|
||||
|
||||
$(TARGET): $(OBJS)
|
||||
$(LD) $(LDFLAGS) -o $(TARGET) $(OBJS) $(LIB_DIRS) $(LIBS)
|
||||
uninstall:
|
||||
rm -f $(TARGET_DIR)/sbin/$(TARGET)
|
||||
|
||||
$(TARGET):
|
||||
GOOS=linux GOARCH=$(GOARCH) go build
|
||||
|
||||
test:
|
||||
go test ./... -race -cover
|
||||
|
131
README
131
README
@ -1,131 +0,0 @@
|
||||
Hard Disk Idle Spin-Down Utility
|
||||
==============================================================================
|
||||
|
||||
hd-idle is a utility program for spinning-down external disks after a period
|
||||
of idle time. Since most external IDE disk enclosures don't support setting
|
||||
the IDE idle timer, a program like hd-idle is required to spin down idle
|
||||
disks automatically.
|
||||
|
||||
A word of caution: hard disks don't like spinning up too often. Laptop disks
|
||||
are more robust in this respect than desktop disks but if you set your disks
|
||||
to spin down after a few seconds you may damage the disk over time due to the
|
||||
stress the spin-up causes on the spindle motor and bearings. It seems that
|
||||
manufacturers recommend a minimum idle time of 3-5 minutes, the default in
|
||||
hd-idle is 10 minutes.
|
||||
|
||||
One more word of caution: hd-idle will spin down any disk accessible via the
|
||||
SCSI layer (USB, IEEE1394, ...) but it will NOT work with real SCSI disks
|
||||
because they don't spin up automatically. Thus it's not called scsi-idle and
|
||||
I don't recommend using it on a real SCSI system unless you have a kernel
|
||||
patch that automatically starts the SCSI disks after receiving a sense buffer
|
||||
indicating the disk has been stopped. Without such a patch, real SCSI disks
|
||||
won't start again and you can as well pull the plug.
|
||||
|
||||
You have been warned...
|
||||
|
||||
The latest version of hd-idle can be found on SourceForge:
|
||||
|
||||
http://hd-idle.sf.net
|
||||
|
||||
hd-idle is not public domain software. It's copyrighted by myself,
|
||||
Christian Mueller, according to the terms of the GNU General Public
|
||||
License (GPL). Please see the file LICENSE for additional information.
|
||||
|
||||
Copyright (c) Christian Mueller 2007
|
||||
|
||||
==============================================================================
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
The compile process is rather simple, thus there's no automake or configure
|
||||
script at this point, just a makefile for Linux. Since hd-idle is using the
|
||||
Linux generic SCSI layer, it requires the include files scsi/sg.h and
|
||||
scsi/scsi.h which should come with libc6-dev (at least on Debian they do).
|
||||
|
||||
Non-Debian Systems:
|
||||
* In order to compile the program, type "make".
|
||||
* In order to install the program into /usr/local/sbin, type "make install"
|
||||
(this will also install the manpage into /usr/local/share/man/man1)
|
||||
|
||||
Debian Systems:
|
||||
* Run "dpkg-buildpackage -rfakeroot"
|
||||
* Run "dpkg -i ../hd-idle_*.deb" to install the package
|
||||
|
||||
NOTE: The build framework has been changed to be compatible to the Debian
|
||||
package management with the intention of making hd-idle an official
|
||||
Debian package. Once this effort has completed, hd-idle can be
|
||||
installed with "apt-get install hd-idle". The changes to the Debian
|
||||
build instructions as outlined above (previous releases used "make
|
||||
install_debian") are a side effect of this effort.
|
||||
|
||||
Once completed, please check /etc/default/hd-idle for configuration
|
||||
information. The default settings will *not* start hd-idle automatically.
|
||||
|
||||
Running hd-idle
|
||||
---------------
|
||||
|
||||
In order to run hd-idle, type "hd-idle". This will start hd-idle with the
|
||||
default options, causing all SCSI (read: USB, Firewire, SCSI, ...) hard disks
|
||||
to spin down after 10 minutes of inactivity.
|
||||
|
||||
On a Debian system, after editing /etc/default/hd-idle and enabling it,
|
||||
use "/etc/init.d/hd-idle start" to run hd-idle.
|
||||
|
||||
Please note that hd-idle uses /proc/diskstats to read disk statistics. If
|
||||
this file is not present, hd-idle won't work.
|
||||
|
||||
In case of problems, use the debug option (-d) tp get further information.
|
||||
|
||||
Command line options:
|
||||
|
||||
-a <name> Set device name of disks for subsequent idle-time
|
||||
parameters (-i). This parameter is optional in the
|
||||
sense that there's a default entry for all disks
|
||||
which are not named otherwise by using this
|
||||
parameter. This can also be a symlink
|
||||
(e.g. /dev/disk/by-uuid/...)
|
||||
-i <idle_time> Idle time in seconds for the currently named disk(s)
|
||||
(-a <name>) or for all disks.
|
||||
-l <logfile> Name of logfile (written only after a disk has spun
|
||||
up). Please note that this option might cause the
|
||||
disk which holds the logfile to spin up just because
|
||||
another disk had some activity. This option should
|
||||
not be used on systems with more than one disk
|
||||
except for tuning purposes. On single-disk systems,
|
||||
this option should not cause any additional spinups.
|
||||
|
||||
Miscellaneous options:
|
||||
-t <disk> Spin-down the specfified disk immediately and exit.
|
||||
-d Debug mode. This will prevent hd-idle from
|
||||
becoming a daemon and print debugging info to
|
||||
stdout/stderr
|
||||
-h Print usage information.
|
||||
|
||||
Regarding the parameter "-a":
|
||||
|
||||
Users of hd-idle have asked for means to set idle-time parameters for
|
||||
individual disks. This makes a lot of sense, not only because some [SCSI]
|
||||
disks may not react well to being stopped. Originally, hd-idle had one idle
|
||||
time for all disks. The parameter "-a" can now be used to set a filter on
|
||||
the disk's device name (omit /dev/) for subsequent idle-time settings.
|
||||
|
||||
1) A -i option before the first -a option will set the default idle time;
|
||||
hence, compatibility with previous releases of hd-idle is maintained.
|
||||
|
||||
2) In order to disable spin-down of disks per default, and then re-enable
|
||||
spin-down on selected disks, set the default idle time to 0.
|
||||
|
||||
Example:
|
||||
hd-idle -i 0 -a sda -i 300 -a sdb -i 1200
|
||||
|
||||
This example sets the default idle time to 0 (meaning hd-idle will never
|
||||
try to spin down a disk), then sets explicit idle times for disks which
|
||||
have the string "sda" or "sdb" in their device name.
|
||||
|
||||
Stopping hd-idle
|
||||
----------------
|
||||
|
||||
Use "killall hd-idle" to stop hd-idle. On a Debian system, use
|
||||
"/etc/init.d/hd-idle stop".
|
||||
|
340
README.md
Normal file
340
README.md
Normal file
@ -0,0 +1,340 @@
|
||||
# hd-idle
|
||||
|
||||
Reimplementation of _Christian Mueller's_ [hd-idle](http://hd-idle.sf.net) with some extra features.
|
||||
|
||||
`hd-idle` is a utility program for spinning-down external disks after a period of idle time.
|
||||
Since most external IDE disk enclosures don't support setting the IDE idle timer,
|
||||
a program like `hd-idle` is required to spin down idle disks automatically.
|
||||
|
||||
**Index**
|
||||
* [Extra features](#extra-features)
|
||||
* [Support ATA commands](#support-ata-commands)
|
||||
* [Monitor the skew between monitoring cycles](#monitor-the-skew-between-monitoring-cycles)
|
||||
* [Resolve symlinks in runtime](#resolve-symlinks-in-runtime)
|
||||
* [Log disk spin up](#log-disk-spin-up)
|
||||
* [Use disk partitions or device mapper to calculate activity](#use-disk-partitions-or-device-mapper-to-calculate-activity)
|
||||
* [Install](#Install)
|
||||
* [Precompiled binaries](#precompiled-binaries)
|
||||
* [Build from source](#build-from-source)
|
||||
* [Run hd-idle](#run-hd-idle)
|
||||
* [Configuration](#Configuration)
|
||||
* [Understand the logs](#understand-the-logs)
|
||||
* [Standard log](#standard-log)
|
||||
* [Log file](#log-file)
|
||||
* [Warning on spinning down disks](#warning-on-spinning-down-disks)
|
||||
* [Troubleshot](#Troubleshot)
|
||||
* [LUKS support](#luks-support)
|
||||
* [SCSI response not ok](#scsi-response-not-ok)
|
||||
|
||||
## Extra features
|
||||
|
||||
List of extra features compared to the original `hd-idle`:
|
||||
|
||||
### Support ATA commands
|
||||
|
||||
The implementation of `hd-idle` written by _Christian Mueller_ relies on the `SCSI` api to work.
|
||||
When listing the drives by id, disks starting with `usb` stop using the original implementation,
|
||||
but disk starting with `ata` do not.
|
||||
|
||||
$ ls /dev/disk/by-id/
|
||||
|
||||
ata-WDC_WD40EZRX-
|
||||
ata-WDC_WD50EZRX-
|
||||
usb-WD_My_Book_1140_
|
||||
|
||||
[hdparm](https://en.wikipedia.org/wiki/Hdparm) on the other hand always stops the drives without any problems.
|
||||
It uses `ATA` api calls to send disks to standby. `hd-idle` comes with `ATA` commands support to replicate `hdparm`'s api calls.
|
||||
|
||||
### Monitor the skew between monitoring cycles
|
||||
|
||||
Identify if the sleep took longer than expected and reset the spun down flag if it waited too long for the main loop sleep.
|
||||
This should capture suspend events as well as excessive machine load.
|
||||
|
||||
### Resolve symlinks in runtime
|
||||
|
||||
`hd-idle` can resolve disk symlinks also in runtime. Disks added after application's start won't be hidden.
|
||||
|
||||
### Log disk spin up
|
||||
|
||||
Show in standard output when disks spin up.
|
||||
|
||||
### Use disk partitions or device mapper to calculate activity
|
||||
|
||||
The disk activity is calculated by watching read/write changes on partition or device mapper level instead of disk level.
|
||||
This is required for kernels newer than 5.4 LTS, because disk monitoring tools change read/write values on disk level,
|
||||
although there's no real activity on the disk itself.
|
||||
When using LUKS, activity will happen on the device mapper device mapped to the corresponding disk.
|
||||
|
||||
## Install
|
||||
|
||||
There are various ways of installing `hd-idle`:
|
||||
|
||||
### Precompiled binaries
|
||||
|
||||
Precompiled binaries for released versions are available in the
|
||||
[*releases*](https://github.com/adelolmo/hd-idle/releases) section.
|
||||
|
||||
### Build from source
|
||||
|
||||
To build `hd-idle` from the source code yourself you need to have a working
|
||||
Go environment with [version 1.16 or greater installed](http://golang.org/doc/install).
|
||||
|
||||
Open a terminal and execute these commands:
|
||||
|
||||
git clone https://github.com/adelolmo/hd-idle
|
||||
cd hd-idle
|
||||
make
|
||||
|
||||
On Debian you can also build the package yourself using `dpkg-buildpackage`:
|
||||
|
||||
git clone https://github.com/adelolmo/hd-idle.git
|
||||
cd hd-idle
|
||||
dpkg-buildpackage -a armhf -us -uc -b
|
||||
|
||||
In the example above, the package is built for `armhf`, but you can build it also for the platforms `i386`, `amd64`, and `arm64`
|
||||
by substituting the parameter `-a`.
|
||||
|
||||
Then install the package:
|
||||
|
||||
# dpkg -i ../hd-idle*.deb
|
||||
|
||||
## Run hd-idle
|
||||
|
||||
In order to run `hd-idle`, type:
|
||||
|
||||
$ hd-idle
|
||||
|
||||
This will start `hd-idle` with the default options, causing all `SCSI`
|
||||
(read: USB, Firewire, SCSI, ...) hard disks to spin down after 10 minutes of inactivity.
|
||||
|
||||
If the Debian package was installed, after editing `/etc/default/hd-idle` and enabling it (`START_HD_IDLE=true`),
|
||||
run hd-idle with:
|
||||
|
||||
# systemctl start hd-idle
|
||||
|
||||
To enable `hd-idle` on reboot:
|
||||
|
||||
# systemctl enable hd-idle
|
||||
|
||||
Please note that `hd-idle` uses */proc/diskstats* to read disk statistics. If
|
||||
this file is not present, `hd-idle` won't work.
|
||||
|
||||
In case of problems, use the debug option *-d* to get further information.
|
||||
|
||||
## Configuration
|
||||
|
||||
Command line options:
|
||||
|
||||
+ -a *name*
|
||||
Set device name of disks for subsequent idle-time
|
||||
parameters *-i*. This parameter is optional in the
|
||||
sense that there's a default entry for all disks
|
||||
which are not named otherwise by using this
|
||||
parameter. This can also be a symlink
|
||||
(e.g. /dev/disk/by-uuid/...)
|
||||
|
||||
+ -i *idle_time*
|
||||
Idle time in seconds for the currently named disk(s)
|
||||
(-a *name*) or for all disks.
|
||||
Setting this value to `0` will never spin down the disk(s).
|
||||
|
||||
+ -c *command_type*
|
||||
Api call to stop the device. Possible values are `scsi`
|
||||
(default value) and `ata`.
|
||||
|
||||
+ -p *power_condition*
|
||||
Power condition to send with the issued SCSI START STOP UNIT command. Possible values
|
||||
are `0-15` (inclusive). The default value of `0` works fine for disks accessible via the
|
||||
SCSI layer (USB, IEEE1394, ...), but it will *NOT* work as intended with real SCSI / SAS disks.
|
||||
A stopped SAS disk will not start up automatically on access, but requires a startup command for reactivation.
|
||||
Useful values for SAS disks are `2` for idle and `3` for standby.
|
||||
|
||||
+ -s *symlink_policy*
|
||||
Set the policy to resolve symlinks for devices. If set
|
||||
to `0`, symlinks are resolved only on start. If set to `1`,
|
||||
symlinks are also resolved on runtime until success.
|
||||
By default symlinks are only resolved on start. If the
|
||||
symlink doesn't resolve to a device, the default
|
||||
configuration will be applied.
|
||||
|
||||
+ -l *logfile*
|
||||
Name of logfile (written only after a disk has spun
|
||||
up or down). Please note that this option might cause the
|
||||
disk which holds the logfile to spin up just because
|
||||
another disk had some activity. On single-disk systems,
|
||||
this option should not cause any additional spinups.
|
||||
On systems with more than one disk, the disk where the log
|
||||
is written will be spun up. On raspberry based systems the
|
||||
log should be written to the SD card.
|
||||
|
||||
Miscellaneous options:
|
||||
|
||||
+ -t *disk*
|
||||
Spin-down the specified disk immediately and exit.
|
||||
|
||||
+ -d
|
||||
Debug mode. It will print debugging info to
|
||||
stdout/stderr (/var/log/syslog if started with systemctl)
|
||||
|
||||
+ -h
|
||||
Print usage information.
|
||||
|
||||
Regarding the parameter *-a*:
|
||||
|
||||
The parameter *-a* can be used to set a filter on the disk's device name (omit /dev/)
|
||||
for subsequent idle-time settings.
|
||||
|
||||
1)
|
||||
A *-i* option before the first *-a* option will set the default idle time.
|
||||
|
||||
2)
|
||||
In order to disable spin-down of disks per default, and then re-enable
|
||||
spin-down on selected disks, set the default idle time to 0.
|
||||
|
||||
Example:
|
||||
```
|
||||
hd-idle -i 0 -a sda -i 300 -a sdb -i 1200
|
||||
```
|
||||
This example sets the default idle time to 0 (meaning hd-idle will never
|
||||
try to spin down a disk) and the default api command to `scsi`, then sets explicit
|
||||
idle times for disks which have the string `sda` or `sdb` in their device name.
|
||||
|
||||
3)
|
||||
The option *-c* allows to set the api call that sends the spindown command.
|
||||
Possible values are `scsi` (the default value) or `ata`.
|
||||
|
||||
Example:
|
||||
```
|
||||
hd-idle -i 0 -c ata -a sda -i 300 -a sdb -i 1200 -c scsi
|
||||
```
|
||||
This example sets the default idle time to 0 (meaning hd-idle will never
|
||||
try to spin down a disk) and the default api command to `ata`, then sets explicit
|
||||
idle times for disks which have the string `sda` or `sdb` in their device name
|
||||
and sets `sdb` to use `scsi` api command.
|
||||
|
||||
## Understand the logs
|
||||
|
||||
By default `hd-idle` only logs to the standard output. You can find them in the syslog if the application starts via service.
|
||||
|
||||
If you set the log file (`-l` flag) then the application writes extra details to it. (Check the [Configuration](#Configuration) section).
|
||||
|
||||
### Standard log
|
||||
|
||||
The standard log output registers two kinds of events:
|
||||
|
||||
* disk spin up
|
||||
* disk spin down
|
||||
|
||||
```
|
||||
Aug 8 00:14:55 enterprise hd-idle[9958]: sda spindown
|
||||
Aug 8 00:14:55 enterprise hd-idle[9958]: sdb spindown
|
||||
Aug 8 00:14:56 enterprise hd-idle[9958]: sdc spindown
|
||||
Aug 8 00:17:55 enterprise hd-idle[9958]: sdb spinup
|
||||
Aug 8 00:28:55 enterprise hd-idle[9958]: sdb spindown
|
||||
```
|
||||
|
||||
### Log file
|
||||
|
||||
You can enable the log file with the flag `-l` followed by the log path. (Check the [Configuration](#Configuration) section).
|
||||
|
||||
This is the kind of entry shown in the log file:
|
||||
|
||||
```
|
||||
date: 2020-07-30, time: 05:28:01, disk: sdc, running: 601, stopped: 76654
|
||||
```
|
||||
|
||||
Explanation:
|
||||
* `date` and `time` when the disk spins up.
|
||||
* `disk` involved.
|
||||
* `running` seconds the device was running before it spun down the last time.
|
||||
* `stopped` seconds since last spin down. This is the time the disk was asleep before spinning up.
|
||||
|
||||
**Important Note:**
|
||||
|
||||
The log file is written after a full cycle of running-stopped-wakeup.
|
||||
|
||||
A bit more on `running` explained with the above example:
|
||||
|
||||
| timestamp |disk spin| event |new disk spin| running | stopped |
|
||||
|:-----------------:|:-------:|:-----------:|:-----------:|:------------------------:|:-------------------------------------------------------:|
|
||||
|2020-07-29 07:59:57| down |disk activity| up | ? | ? |
|
||||
|2020-07-29 08:09:58| up | go to sleep | down | - | - |
|
||||
|2020-07-30 05:28:01| down |disk activity| up |08:09:58 - 07:59:57 = 601s|2020-07-30 05:28:01 - 2020-07-29 08:09:58 = ~21h (76654s)|
|
||||
|
||||
Explanation:
|
||||
|
||||
At 07:59:57 the disk is on standby and hd-idle detects disk activity.
|
||||
|
||||
At 08:09:58 the disk is active and hd-idle determines inactivity of the disk and spins it down.
|
||||
|
||||
At 05:28:01 on the next day the disk is on standby and hd-idle detects disk activity. It writes on the log file 601s of previous disk spin up and ~21h of standby.
|
||||
|
||||
|
||||
## Warning on spinning down disks
|
||||
|
||||
A word of caution: hard disks don't like spinning up too often. Laptop disks
|
||||
are more robust in this respect than desktop disks but if you set your disks
|
||||
to spin down after a few seconds you may damage the disk over time due to the
|
||||
stress the spin-up causes on the spindle motor and bearings. It seems that
|
||||
manufacturers recommend a minimum idle time of 3-5 minutes, the default in
|
||||
`hd-idle` is 10 minutes.
|
||||
|
||||
You have been warned...
|
||||
|
||||
# Troubleshot
|
||||
|
||||
This section covers some usual issues that user's face while using `hd-idle`.
|
||||
|
||||
## LUKS support
|
||||
|
||||
Using encrypted disk or partitions with LUKS is supported by the use of symlinks.
|
||||
|
||||
1. Run the following command with you're disk mounted:
|
||||
`sudo lsblk /dev/sd* -o PATH,FSSIZE,LABEL,UUID,PARTLABEL,PARTUUID,MODEL,SIZE,SERIAL,TYPE,WWN`
|
||||
|
||||
```
|
||||
PATH FSSIZE LABEL UUID PARTLABEL PARTUUID MODEL SIZE SERIAL TYPE WWN
|
||||
/dev/sde ST400 3.7T ZGY0LB disk 0x5000c500a3d1d419
|
||||
/dev/sde1 100e952e-0ffb-4b73-bb1a-8401d4fe56c0 dropbox 14a81aa8-c2c9-448e-967b-85d87dc9b488 1T part 0x5000c500a3d1d419
|
||||
/dev/sde1 100e952e-0ffb-4b73-bb1a-8401d4fe56c0 dropbox 14a81aa8-c2c9-448e-967b-85d87dc9b488 1T part 0x5000c500a3d1d419
|
||||
/dev/sde2 2.6T three 175e2227-d24f-4ad0-9e42-2ddb8846682d d2792423-3c07-44fe-ab6b-a1aca61c73a5 2.7T part 0x5000c500a3d1d419
|
||||
/dev/sde2 2.6T three 175e2227-d24f-4ad0-9e42-2ddb8846682d d2792423-3c07-44fe-ab6b-a1aca61c73a5 2.7T part 0x5000c500a3d1d419
|
||||
/dev/mapper/luks-100e952e-0ffb-4b73-bb1a-8401d4fe56c0
|
||||
1007.8G dropbox
|
||||
649dd15e-6750-472c-8185-4d76bffc2490 1024G crypt
|
||||
```
|
||||
|
||||
You have to take symlinks that resolve to disk devices: `/dev/sd*`.
|
||||
|
||||
In the example above `/dev/mapper/luks-100e952e-0ffb-4b73-bb1a-8401d4fe56c0` is the Path to the encrypted partition,
|
||||
which WWN is `0x5000c500a3d1d419`.
|
||||
|
||||
2. Run the following command to see which devices the system has identified using `by-id`:
|
||||
`sudo ls -lv /dev/disk/by-id/`
|
||||
|
||||
Output:
|
||||
```
|
||||
lrwxrwxrwx 1 root root 9 Jul 18 15:56 ata-ST4000DM005-2DP166_ZGY0LBRB -> ../../sde
|
||||
lrwxrwxrwx 1 root root 10 Jul 18 16:01 ata-ST4000DM005-2DP166_ZGY0LBRB-part1 -> ../../sde1
|
||||
lrwxrwxrwx 1 root root 10 Jul 18 15:56 ata-ST4000DM005-2DP166_ZGY0LBRB-part2 -> ../../sde2
|
||||
lrwxrwxrwx 1 root root 9 Jul 18 15:56 wwn-0x5000c500a3d1d419 -> ../../sde
|
||||
lrwxrwxrwx 1 root root 10 Jul 18 16:01 wwn-0x5000c500a3d1d419-part1 -> ../../sde1
|
||||
lrwxrwxrwx 1 root root 10 Jul 18 15:56 wwn-0x5000c500a3d1d419-part2 -> ../../sde2
|
||||
```
|
||||
|
||||
Here we see that we can either use `ata-ST4000DM005-2DP166_ZGY0LBRB` or `wwn-0x5000c500a3d1d419` as symlinks.
|
||||
|
||||
3. Edit `/etc/default/hd-idle` to use the symlink you prefer.
|
||||
In my case, I went with the symlink using WWN (unique storage identifier), yet I could have chosen MODEL (device identifier) instead.
|
||||
|
||||
`HD_IDLE_OPTS='-i 0 -c ata -s 1 -l /var/log/hd-idle.log -a /dev/disk/by-id/wwn-0x5000c500a3d1d419 -i 600'`
|
||||
Or
|
||||
`HD_IDLE_OPTS='-i 0 -c ata -s 1 -l /var/log/hd-idle.log -a /dev/disk/by-id/ata-ST4000DM005-2DP166_ZGY0LBRB -i 600'`
|
||||
|
||||
## SCSI response not ok
|
||||
|
||||
You can find information about the issue here: [SCSI-response-not-ok](https://github.com/adelolmo/hd-idle/wiki/SCSI-response-not-ok)
|
||||
|
||||
## License
|
||||
|
||||
GNU General Public License v3.0, see [LICENSE](https://github.com/adelolmo/hd-idle/blob/master/LICENSE).
|
@ -1,3 +0,0 @@
|
||||
export CVS_RSH=ssh
|
||||
export CVSROOT=cjmueller@hd-idle.cvs.sourceforge.net:/cvsroot/hd-idle
|
||||
|
236
diskstats/snapshot.go
Normal file
236
diskstats/snapshot.go
Normal file
@ -0,0 +1,236 @@
|
||||
// hd-idle - spin down idle hard disks
|
||||
// Copyright (C) 2018 Andoni del Olmo
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package diskstats
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
/*
|
||||
https://www.kernel.org/doc/Documentation/ABI/testing/procfs-diskstats
|
||||
|
||||
The /proc/diskstats file displays the I/O statistics
|
||||
of block devices. Each line contains the following 14
|
||||
fields:
|
||||
|
||||
1 - major number
|
||||
2 - minor mumber
|
||||
3 - device name
|
||||
4 - reads completed successfully
|
||||
5 - reads merged
|
||||
6 - sectors read
|
||||
7 - time spent reading (ms)
|
||||
8 - writes completed
|
||||
9 - writes merged
|
||||
10 - sectors written
|
||||
11 - time spent writing (ms)
|
||||
12 - I/Os currently in progress
|
||||
13 - time spent doing I/Os (ms)
|
||||
14 - weighted time spent doing I/Os (ms)
|
||||
|
||||
Kernel 4.18+ appends four more fields for discard
|
||||
tracking putting the total at 18:
|
||||
|
||||
15 - discards completed successfully
|
||||
16 - discards merged
|
||||
17 - sectors discarded
|
||||
18 - time spent discarding
|
||||
|
||||
Kernel 5.5+ appends two more fields for flush requests:
|
||||
|
||||
19 - flush requests completed successfully
|
||||
20 - time spent flushing
|
||||
|
||||
For more details refer to Documentation/admin-guide/iostats.rst
|
||||
*/
|
||||
|
||||
const (
|
||||
deviceNameCol = 2 // field 3 - device name
|
||||
readsCol = 5 // field 6 - sectors read
|
||||
writesCol = 9 // field 10 - sectors written
|
||||
)
|
||||
|
||||
type DeviceType int
|
||||
|
||||
const (
|
||||
Unknown DeviceType = iota
|
||||
Disk
|
||||
Partition
|
||||
DeviceMapper
|
||||
)
|
||||
|
||||
type ReadWriteStats struct {
|
||||
Name string
|
||||
Type DeviceType
|
||||
Reads uint64
|
||||
Writes uint64
|
||||
}
|
||||
|
||||
var scsiDiskRegex *regexp.Regexp
|
||||
var scsiPartitionRegex *regexp.Regexp
|
||||
var deviceMapperRegex *regexp.Regexp
|
||||
|
||||
type diskHolderGetterFunc func(string, string) (string, error)
|
||||
|
||||
func init() {
|
||||
scsiDiskRegex = regexp.MustCompile("sd[a-z]+$")
|
||||
scsiPartitionRegex = regexp.MustCompile("sd[a-z]+[0-9]+$")
|
||||
deviceMapperRegex = regexp.MustCompile("dm-.*$")
|
||||
}
|
||||
|
||||
func Snapshot() []ReadWriteStats {
|
||||
f, err := os.Open("/proc/diskstats")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
return readSnapshot(f, getDiskHolder)
|
||||
}
|
||||
|
||||
func readSnapshot(r io.Reader, holderGetter diskHolderGetterFunc) []ReadWriteStats {
|
||||
diskStatsMap := make(map[string]ReadWriteStats)
|
||||
partitionStatsMap := make(map[string]ReadWriteStats)
|
||||
deviceMapperHolderMap := make(map[string]string)
|
||||
|
||||
scanner := bufio.NewScanner(r)
|
||||
for scanner.Scan() {
|
||||
diskStats, err := statsForDisk(scanner.Text())
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if diskStats.Type == Disk {
|
||||
diskStatsMap[diskStats.Name] = *diskStats
|
||||
|
||||
if dmName, err := holderGetter(diskStats.Name, "/sys/class/block/%s/holders/"); err == nil && dmName != "" {
|
||||
deviceMapperHolderMap[dmName] = diskStats.Name
|
||||
}
|
||||
} else {
|
||||
partitionStatsMap[diskStats.Name] = *diskStats
|
||||
}
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
for _, partitionStats := range partitionStatsMap {
|
||||
|
||||
var diskName string
|
||||
var ok bool
|
||||
|
||||
switch partitionStats.Type {
|
||||
case Partition:
|
||||
diskName = strings.TrimRight(partitionStats.Name,"0123456789")
|
||||
case DeviceMapper:
|
||||
if diskName, ok = deviceMapperHolderMap[partitionStats.Name]; !ok {
|
||||
continue
|
||||
}
|
||||
default:
|
||||
continue
|
||||
}
|
||||
|
||||
var diskStats ReadWriteStats
|
||||
if diskStats, ok = diskStatsMap[diskName]; !ok {
|
||||
continue
|
||||
}
|
||||
if diskStats.Type == Disk {
|
||||
// replace disk statistics by partition or holder stats
|
||||
diskStats.Type = partitionStats.Type
|
||||
diskStats.Writes = partitionStats.Writes
|
||||
diskStats.Reads = partitionStats.Reads
|
||||
} else {
|
||||
// otherwise, accumulate stats of all partitions and holder if any
|
||||
diskStats.Writes += partitionStats.Writes
|
||||
diskStats.Reads += partitionStats.Reads
|
||||
}
|
||||
diskStatsMap[diskName] = diskStats
|
||||
}
|
||||
|
||||
return toSlice(diskStatsMap)
|
||||
}
|
||||
|
||||
func getDiskHolder(diskName, pathFormat string) (string, error) {
|
||||
/* This returns only the first holder. In practice when using LUKS, there is only one holder */
|
||||
|
||||
holdersDir := fmt.Sprintf(pathFormat, diskName)
|
||||
if _, err := os.Stat(holdersDir); os.IsNotExist(err) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
files, err := os.ReadDir(holdersDir)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
for _, file := range files {
|
||||
return file.Name(), nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func statsForDisk(rawStats string) (*ReadWriteStats, error) {
|
||||
reader := strings.NewReader(rawStats)
|
||||
scanner := bufio.NewScanner(reader)
|
||||
for scanner.Scan() {
|
||||
cols := strings.Fields(scanner.Text())
|
||||
|
||||
name := cols[deviceNameCol]
|
||||
deviceType := Unknown
|
||||
reads, _ := strconv.ParseUint(cols[readsCol], 10, 64)
|
||||
writes, _ := strconv.ParseUint(cols[writesCol], 10, 64)
|
||||
|
||||
|
||||
if scsiDiskRegex.MatchString(name) {
|
||||
deviceType = Disk
|
||||
} else if scsiPartitionRegex.MatchString(name) {
|
||||
deviceType = Partition
|
||||
} else if deviceMapperRegex.MatchString(name) {
|
||||
deviceType = DeviceMapper
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
|
||||
stats := &ReadWriteStats{
|
||||
Name: name,
|
||||
Type: deviceType,
|
||||
Reads: reads,
|
||||
Writes: writes,
|
||||
}
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, errors.New("cannot read disk stats")
|
||||
}
|
||||
|
||||
func toSlice(rws map[string]ReadWriteStats) []ReadWriteStats {
|
||||
var snapshot []ReadWriteStats
|
||||
for _, r := range rws {
|
||||
snapshot = append(snapshot, r)
|
||||
}
|
||||
return snapshot
|
||||
}
|
218
diskstats/snapshot_test.go
Normal file
218
diskstats/snapshot_test.go
Normal file
@ -0,0 +1,218 @@
|
||||
// hd-idle - spin down idle hard disks
|
||||
// Copyright (C) 2018 Andoni del Olmo
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package diskstats
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func mockGetDiskHolder(diskName, format string) (string, error) {
|
||||
if diskName == "sdf" {
|
||||
return "dm-4", nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func TestTakeSnapshot(t *testing.T) {
|
||||
s := ` 7 0 loop0 0 0 0 0 0 0 0 0 0 0 0
|
||||
7 1 loop1 0 0 0 0 0 0 0 0 0 0 0
|
||||
7 2 loop2 0 0 0 0 0 0 0 0 0 0 0
|
||||
7 3 loop3 0 0 0 0 0 0 0 0 0 0 0
|
||||
7 4 loop4 0 0 0 0 0 0 0 0 0 0 0
|
||||
7 5 loop5 0 0 0 0 0 0 0 0 0 0 0
|
||||
7 6 loop6 0 0 0 0 0 0 0 0 0 0 0
|
||||
7 7 loop7 0 0 0 0 0 0 0 0 0 0 0
|
||||
179 0 mmcblk0 133145 53235 6878634 3020910 1544414 1254150 48441345 240124500 0 13439150 243142800
|
||||
179 1 mmcblk0p1 80 40 1760 210 1 0 1 0 0 140 210
|
||||
179 2 mmcblk0p2 132931 53195 6874482 3020440 1544413 1254150 48441344 240124500 0 13439000 243278260
|
||||
8 0 sda 321553 158156 37537568 5961590 50820 94361 10439592 26691430 0 3357150 32650910
|
||||
8 1 sda1 321454 158156 37536344 5725790 50820 94361 10439592 26691430 0 3121370 32415240
|
||||
8 32 sdc 52147 2738 6494584 913050 28092 1251 6370936 8938800 0 506360 9852970
|
||||
8 33 sdc1 52087 2738 6493672 905390 28092 1251 6370936 8938800 0 498700 9892750
|
||||
8 16 sdb 5650742 34516 727476416 92732820 1728864 35618 404215912 705303450 0 22944140 798112260
|
||||
8 17 sdb1 5650643 34516 727475192 92673920 1728864 35618 404215912 705303450 0 22893010 798071230
|
||||
8 16 sdd 982501 110903 37074938 9468348 60870 112203 15682640 17081448 0 2086868 26550788
|
||||
8 17 sdd1 369 0 39960 1288 0 0 0 0 0 792 1288
|
||||
8 18 sdd2 443 0 53496 1224 0 0 0 0 0 1204 1224
|
||||
8 19 sdd3 52 0 2632 76 0 0 0 0 0 76 76
|
||||
8 21 sdd5 76541 1090 22221672 979636 8845 2335 8316352 14986056 0 470364 15965544
|
||||
8 22 sdd6 904573 109813 14736818 8485644 51818 109868 7366288 2094080 0 1642436 10580612
|
||||
8 22 sdd11 904573 109813 1 8485644 51818 109868 1 2094080 0 1642436 10580612
|
||||
8 32 sde 2891 192 57743 13523 1085550 14035 982686648 9819204 0 5102050 10209416 0 0 0 0 12140 376689
|
||||
8 34 sdf 207814 251309 3670180 1378314 38505 27680 21787544 926421 0 552176 2325026 0 0 0 0 272 20290
|
||||
253 4 dm-4 25371 0 206376 195492 1330 0 10640 657392 0 19480 852884 0 0 0 0 0 0
|
||||
65 160 sdaa 157371 937 11375913 1617587 8304860 2117223236 17004224768 98631435 0 49649267 100249022 0 0 0 0 0 0
|
||||
65 161 sdaa1 157257 937 11371536 1617417 8304860 2117223236 17004224768 98631435 0 49649104 100248853 0 0 0 0 0 0
|
||||
65 176 sdab 54244 803 1223811 596585 368 9 3008 1051 0 342387 597828 0 0 0 0 8 191`
|
||||
|
||||
stats := readSnapshot(strings.NewReader(s), mockGetDiskHolder)
|
||||
sort.Slice(stats, func(i, j int) bool {
|
||||
return stats[i].Name < stats[j].Name
|
||||
})
|
||||
|
||||
expected := []ReadWriteStats{
|
||||
{Name: "sda", Type: Partition, Reads: 37536344, Writes: 10439592},
|
||||
{Name: "sdaa", Type: Partition, Reads: 11371536, Writes: 17004224768},
|
||||
{Name: "sdab", Type: Disk, Reads: 1223811, Writes: 3008},
|
||||
{Name: "sdb", Type: Partition, Reads: 727475192, Writes: 404215912},
|
||||
{Name: "sdc", Type: Partition, Reads: 6493672, Writes: 6370936},
|
||||
{Name: "sdd", Type: Partition, Reads: 37054579, Writes: 15682641},
|
||||
{Name: "sde", Type: Disk, Reads: 57743, Writes: 982686648},
|
||||
{Name: "sdf", Type: DeviceMapper, Reads: 206376, Writes: 10640},
|
||||
}
|
||||
|
||||
if len(expected) != len(stats) {
|
||||
t.Fatalf("Expected %d disks but found %d", len(expected), len(stats))
|
||||
}
|
||||
for i := 0; i < len(expected); i++ {
|
||||
exp := expected[i]
|
||||
act := stats[i]
|
||||
|
||||
if exp != act {
|
||||
t.Fatalf("Expected %v but found %v", exp, act)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetDiskHolder(t *testing.T) {
|
||||
type wantParams struct {
|
||||
name string
|
||||
errorMessage string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
diskName string
|
||||
holderPath string
|
||||
want wantParams
|
||||
}{
|
||||
{
|
||||
name: "disk not found",
|
||||
diskName: "sda",
|
||||
holderPath: "",
|
||||
want: wantParams{
|
||||
name: "",
|
||||
errorMessage: "stat /tmp/sys/class/block/sda/holders/: no such file or directory",
|
||||
},
|
||||
}, {
|
||||
name: "disk found",
|
||||
diskName: "sda",
|
||||
holderPath: "/tmp/sys/class/block/sda/holders/dm-0",
|
||||
want: wantParams{
|
||||
name: "dm-0",
|
||||
errorMessage: "",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
err := os.RemoveAll("/tmp/sys")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
if len(test.holderPath) > 0 {
|
||||
if err := os.MkdirAll(filepath.Dir(test.holderPath), 0770); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
_, err := os.Create(test.holderPath)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
got, err := getDiskHolder(test.diskName, "/tmp/sys/class/block/%s/holders/")
|
||||
|
||||
if len(test.want.errorMessage) > 0 &&
|
||||
test.want.errorMessage != err.Error() {
|
||||
|
||||
t.Fatalf("Expected %v but found %v", test.want.errorMessage, err.Error())
|
||||
}
|
||||
|
||||
if test.want.name != got {
|
||||
t.Fatalf("Expected %v but found %v", test.want.name, got)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestStatsForDisk(t *testing.T) {
|
||||
type wantParams struct {
|
||||
name string
|
||||
deviceType DeviceType
|
||||
errorMessage string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
line string
|
||||
want wantParams
|
||||
}{
|
||||
{
|
||||
name: "disk type",
|
||||
line: "8 0 sda 321553 158156 37537568 5961590 50820 94361 10439592 26691430 0 3357150 32650910",
|
||||
want: wantParams{
|
||||
name: "sda",
|
||||
deviceType: Disk,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "partition type",
|
||||
line: "8 17 sdd1 369 0 39960 1288 0 0 0 0 0 792 1288",
|
||||
want: wantParams{
|
||||
name: "sdd1",
|
||||
deviceType: Partition,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "device mapper type",
|
||||
line: "253 4 dm-4 25371 0 206376 195492 1330 0 10640 657392 0 19480 852884 0 0 0 0 0 0",
|
||||
want: wantParams{
|
||||
name: "dm-4",
|
||||
deviceType: DeviceMapper,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "unknown type",
|
||||
line: "7 1 loop1 0 0 0 0 0 0 0 0 0 0 0",
|
||||
want: wantParams{
|
||||
errorMessage: "cannot read disk stats",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
got, gotError := statsForDisk(test.line)
|
||||
|
||||
if test.want.errorMessage != "" && test.want.errorMessage != gotError.Error() {
|
||||
t.Fatalf("Expected %v but found %v", test.want.errorMessage, gotError.Error())
|
||||
}
|
||||
if gotError != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if test.want.name != got.Name {
|
||||
t.Fatalf("Expected %v but found %v", test.want.name, got.Name)
|
||||
}
|
||||
|
||||
if test.want.deviceType != got.Type {
|
||||
t.Fatalf("Expected %v but found %v", test.want.deviceType, got.Type)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
5
go.mod
Normal file
5
go.mod
Normal file
@ -0,0 +1,5 @@
|
||||
module github.com/adelolmo/hd-idle
|
||||
|
||||
go 1.16
|
||||
|
||||
require github.com/benmcclelland/sgio v0.0.0-20180629175614-f710aebf64c1
|
2
go.sum
Normal file
2
go.sum
Normal file
@ -0,0 +1,2 @@
|
||||
github.com/benmcclelland/sgio v0.0.0-20180629175614-f710aebf64c1 h1:f1AIRyf6d21xBd1DirrIa6fk41O3LB0WvVuVqhPN4co=
|
||||
github.com/benmcclelland/sgio v0.0.0-20180629175614-f710aebf64c1/go.mod h1:WdrapyVn/Aduwwf/OMW6sEtk9+7BSoMst1kGrx4E4xE=
|
101
hd-idle.1
101
hd-idle.1
@ -1,101 +0,0 @@
|
||||
.\" Hey, EMACS: -*- nroff -*-
|
||||
.\" First parameter, NAME, should be all caps
|
||||
.\" Second parameter, SECTION, should be 1-8, maybe w/ subsection
|
||||
.\" other parameters are allowed: see man(7), man(1)
|
||||
.TH HD-IDLE 1 "September 29, 2011"
|
||||
.\" Please adjust this date whenever revising the manpage.
|
||||
.\"
|
||||
.\" Some roff macros, for reference:
|
||||
.\" .nh disable hyphenation
|
||||
.\" .hy enable hyphenation
|
||||
.\" .ad l left justify
|
||||
.\" .ad b justify to both left and right margins
|
||||
.\" .nf disable filling
|
||||
.\" .fi enable filling
|
||||
.\" .br insert line break
|
||||
.\" .sp <n> insert n+1 empty lines
|
||||
.\" for manpage-specific macros, see man(7)
|
||||
.SH NAME
|
||||
hd-idle \- spin down idle hard disks
|
||||
.SH SYNOPSIS
|
||||
.B hd-idle
|
||||
.RI [ options ]
|
||||
.P
|
||||
.SH DESCRIPTION
|
||||
hd-idle is a utility program for spinning down external disks after a period
|
||||
of idle time. Since most external IDE disk enclosures don't support setting
|
||||
the IDE idle timer, a program like hd-idle is required to spin down idle
|
||||
disks automatically.
|
||||
.P
|
||||
A word of caution: hard disks don't like spinning up too often. Laptop disks
|
||||
are more robust in this respect than desktop disks but if you set your disks
|
||||
to spin down after a few seconds you may damage the disk over time due to the
|
||||
stress the spin-up causes on the spindle motor and bearings. It seems that
|
||||
manufacturers recommend a minimum idle time of 3-5 minutes, the default in
|
||||
hd-idle is 10 minutes.
|
||||
.P
|
||||
One more word of caution: hd-idle will spin down any disk accessible via the
|
||||
SCSI layer (USB, IEEE1394, ...) but it will NOT work with real SCSI disks
|
||||
because they won't spin up automatically. Thus it's not called scsi-idle and
|
||||
I don't recommend using it on a real SCSI system unless you have a kernel
|
||||
patch that automatically starts the SCSI disks after receiving a sense buffer
|
||||
indicating the disk has been stopped. Without such a patch, real SCSI disks
|
||||
won't start again and you can as well pull the plug.
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
.B \-a name
|
||||
Set device name of disks for subsequent idle-time parameters
|
||||
.B (-i).
|
||||
This parameter is optional in the sense that there's a default entry for
|
||||
all disks which are not named otherwise by using this parameter. This can
|
||||
also be a symlink (e.g. /dev/disk/by-uuid/...)
|
||||
.TP
|
||||
.B \-i idle_time
|
||||
Idle time in seconds for the currently named disk(s) (-a <name>) or for
|
||||
all disks.
|
||||
.TP
|
||||
.B \-l logfile
|
||||
Name of logfile (written only after a disk has spun up). Please note that
|
||||
this option might cause the disk which holds the logfile to spin up just
|
||||
because another disk had some activity. This option should not be used on
|
||||
systems with more than one disk except for tuning purposes. On single-disk
|
||||
systems, this option should not cause any additional spinups.
|
||||
.TP
|
||||
.B \-t disk
|
||||
Spin-down the specfified disk immediately and exit.
|
||||
.TP
|
||||
.B \-d
|
||||
Debug mode. This will prevent hd-idle from becoming a daemon and print
|
||||
debugging info to stdout/stderr
|
||||
.TP
|
||||
.B \-h
|
||||
Print usage information.
|
||||
.SH "DISK SELECTION"
|
||||
The parameter
|
||||
.B \-a
|
||||
can be used to set a filter on the disk's device name (omit /dev/) for
|
||||
subsequent idle-time settings. The default is all disks:
|
||||
.P
|
||||
.TP
|
||||
.B \1)
|
||||
A
|
||||
.B \-i
|
||||
option before the first
|
||||
.B \-a
|
||||
option will set the default idle time; hence, compatibility with previous
|
||||
releases of hd-idle is maintained.
|
||||
.TP
|
||||
.B \2)
|
||||
In order to disable spin-down of disks per default, and then re-enable
|
||||
spin-down on selected disks, set the default idle time to 0.
|
||||
.SH EXAMPLE
|
||||
hd-idle -i 0 -a sda -i 300 -a sdb -i 1200
|
||||
.P
|
||||
This example sets the default idle time to 0 (meaning hd-idle will never
|
||||
try to spin down a disk), then sets explicit idle times for disks which
|
||||
have the string "sda" or "sdb" in their device name.
|
||||
.SH AUTHOR
|
||||
hd-idle was written by Chistian Mueller <chris@mumac.de>
|
||||
.PP
|
||||
This manual page was written by Christian Mueller <chris@mumac.de>,
|
||||
for the Debian project (and may be used by others).
|
575
hd-idle.c
575
hd-idle.c
@ -1,575 +0,0 @@
|
||||
/*
|
||||
* hd-idle.c - external disk idle daemon
|
||||
*
|
||||
* Copyright (c) 2007 Christian Mueller.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
/*
|
||||
* hd-idle is a utility program for spinning-down external disks after a period
|
||||
* of idle time. Since most external IDE disk enclosures don't support setting
|
||||
* the IDE idle timer, a program like hd-idle is required to spin down idle
|
||||
* disks automatically.
|
||||
*
|
||||
* A word of caution: hard disks don't like spinning-up too often. Laptop disks
|
||||
* are more robust in this respect than desktop disks but if you set your disks
|
||||
* to spin down after a few seconds you may damage the disk over time due to the
|
||||
* stress the spin-up causes on the spindle motor and bearings. It seems that
|
||||
* manufacturers recommend a minimum idle time of 3-5 minutes, the default in
|
||||
* hd-idle is 10 minutes.
|
||||
*
|
||||
* Please note that hd-idle can spin down any disk accessible via the SCSI
|
||||
* layer (USB, IEEE1394, ...) but it will NOT work with real SCSI disks because
|
||||
* they don't spin up automatically. Thus it's not called scsi-idle and I don't
|
||||
* recommend using it on a real SCSI system unless you have a kernel patch that
|
||||
* automatically starts the SCSI disks after receiving a sense buffer indicating
|
||||
* the disk has been stopped. Without such a patch, real SCSI disks won't start
|
||||
* again and you can as well pull the plug.
|
||||
*
|
||||
* You have been warned...
|
||||
*
|
||||
* CVS Change Log:
|
||||
* ---------------
|
||||
*
|
||||
* $Log: hd-idle.c,v $
|
||||
* Revision 1.7 2014/04/06 19:53:51 cjmueller
|
||||
* Version 1.05
|
||||
* ------------
|
||||
*
|
||||
* Bugs:
|
||||
* - Allow SCSI device names with more than one character (e.g. sdaa) in case
|
||||
* there are more than 26 SCSI targets.
|
||||
*
|
||||
* Revision 1.6 2010/12/05 19:25:51 cjmueller
|
||||
* Version 1.03
|
||||
* ------------
|
||||
*
|
||||
* Bugs
|
||||
* - Use %u in dprintf() when reporting number of reads and writes (the
|
||||
* corresponding variable is an unsigned int).
|
||||
* - Fix example in README where the parameter "-a" was written as "-n".
|
||||
*
|
||||
* Revision 1.5 2010/11/06 15:30:04 cjmueller
|
||||
* Version 1.02
|
||||
* ------------
|
||||
*
|
||||
* Features
|
||||
* - In case the SCSI stop unit command fails with "check condition", print a
|
||||
* hex dump of the sense buffer to stderr. This is supposed to help
|
||||
* debugging.
|
||||
*
|
||||
* Revision 1.4 2010/02/26 14:03:44 cjmueller
|
||||
* Version 1.01
|
||||
* ------------
|
||||
*
|
||||
* Features
|
||||
* - The parameter "-a" now also supports symlinks for disk names. Thus, disks
|
||||
* can be specified using something like /dev/disk/by-uuid/... Use "-d" to
|
||||
* verify that the resulting disk name is what you want.
|
||||
*
|
||||
* Please note that disk names are resolved to device nodes at startup. Also,
|
||||
* since many entries in /dev/disk/by-xxx are actually partitions, partition
|
||||
* numbers are automatically removed from the resulting device node.
|
||||
*
|
||||
* Bugs
|
||||
* - Not really a bug, but the disk name comparison used strstr which is a bit
|
||||
* useless because only disks starting with "sd" and a single letter after
|
||||
* that are currently considered. Replaced the comparison with strcmp()
|
||||
*
|
||||
* Revision 1.3 2009/11/18 20:53:17 cjmueller
|
||||
* Features
|
||||
* - New parameter "-a" to allow selecting idle timeouts for individual disks;
|
||||
* compatibility to previous releases is maintained by having an implicit
|
||||
* default which matches all SCSI disks
|
||||
*
|
||||
* Bugs
|
||||
* - Changed comparison operator for idle periods from '>' to '>=' to prevent
|
||||
* adding one polling interval to idle time
|
||||
* - Changed sleep time before calling sync after updating the log file to 1s
|
||||
* (from 3s) to accumulate fewer dirty blocks before synching. It's still
|
||||
* a compromize but the log file is for debugging purposes, anyway. A test
|
||||
* with fsync() was unsuccessful because the next bdflush-initiated sync
|
||||
* still caused spin-ups.
|
||||
*
|
||||
* Revision 1.2 2007/04/23 22:14:27 cjmueller
|
||||
* Bug fixes
|
||||
* - Comment changes; no functionality changes...
|
||||
*
|
||||
* Revision 1.1.1.1 2007/04/23 21:49:43 cjmueller
|
||||
* initial import into CVS
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <scsi/sg.h>
|
||||
#include <scsi/scsi.h>
|
||||
|
||||
#define STAT_FILE "/proc/diskstats"
|
||||
#define DEFAULT_IDLE_TIME 600
|
||||
|
||||
#define dprintf if (debug) printf
|
||||
|
||||
/* typedefs and structures */
|
||||
typedef struct IDLE_TIME {
|
||||
struct IDLE_TIME *next;
|
||||
char *name;
|
||||
int idle_time;
|
||||
} IDLE_TIME;
|
||||
|
||||
typedef struct DISKSTATS {
|
||||
struct DISKSTATS *next;
|
||||
char name[50];
|
||||
int idle_time;
|
||||
time_t last_io;
|
||||
time_t spindown;
|
||||
time_t spinup;
|
||||
unsigned int spun_down : 1;
|
||||
unsigned int reads;
|
||||
unsigned int writes;
|
||||
} DISKSTATS;
|
||||
|
||||
/* function prototypes */
|
||||
static void daemonize (void);
|
||||
static DISKSTATS *get_diskstats (const char *name);
|
||||
static void spindown_disk (const char *name);
|
||||
static void log_spinup (DISKSTATS *ds);
|
||||
static char *disk_name (char *name);
|
||||
static void phex (const void *p, int len,
|
||||
const char *fmt, ...);
|
||||
|
||||
/* global/static variables */
|
||||
IDLE_TIME *it_root;
|
||||
DISKSTATS *ds_root;
|
||||
char *logfile = "/dev/null";
|
||||
int debug;
|
||||
|
||||
/* main function */
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
IDLE_TIME *it;
|
||||
int have_logfile = 0;
|
||||
int min_idle_time;
|
||||
int sleep_time;
|
||||
int opt;
|
||||
|
||||
/* create default idle-time parameter entry */
|
||||
if ((it = malloc(sizeof(*it))) == NULL) {
|
||||
fprintf(stderr, "out of memory\n");
|
||||
exit(1);
|
||||
}
|
||||
it->next = NULL;
|
||||
it->name = NULL;
|
||||
it->idle_time = DEFAULT_IDLE_TIME;
|
||||
it_root = it;
|
||||
|
||||
/* process command line options */
|
||||
while ((opt = getopt(argc, argv, "t:a:i:l:dh")) != -1) {
|
||||
switch (opt) {
|
||||
|
||||
case 't':
|
||||
/* just spin-down the specified disk and exit */
|
||||
spindown_disk(optarg);
|
||||
return(0);
|
||||
|
||||
case 'a':
|
||||
/* add a new set of idle-time parameters for this particular disk */
|
||||
if ((it = malloc(sizeof(*it))) == NULL) {
|
||||
fprintf(stderr, "out of memory\n");
|
||||
return(2);
|
||||
}
|
||||
it->name = disk_name(optarg);
|
||||
it->idle_time = DEFAULT_IDLE_TIME;
|
||||
it->next = it_root;
|
||||
it_root = it;
|
||||
break;
|
||||
|
||||
case 'i':
|
||||
/* set idle-time parameters for current (or default) disk */
|
||||
it->idle_time = atoi(optarg);
|
||||
break;
|
||||
|
||||
case 'l':
|
||||
logfile = optarg;
|
||||
have_logfile = 1;
|
||||
break;
|
||||
|
||||
case 'd':
|
||||
debug = 1;
|
||||
break;
|
||||
|
||||
case 'h':
|
||||
printf("usage: hd-idle [-t <disk>] [-a <name>] [-i <idle_time>] [-l <logfile>] [-d] [-h]\n");
|
||||
return(0);
|
||||
|
||||
case ':':
|
||||
fprintf(stderr, "error: option -%c requires an argument\n", optopt);
|
||||
return(1);
|
||||
|
||||
case '?':
|
||||
fprintf(stderr, "error: unknown option -%c\n", optopt);
|
||||
return(1);
|
||||
}
|
||||
}
|
||||
|
||||
/* set sleep time to 1/10th of the shortest idle time */
|
||||
min_idle_time = 1 << 30;
|
||||
for (it = it_root; it != NULL; it = it->next) {
|
||||
if (it->idle_time != 0 && it->idle_time < min_idle_time) {
|
||||
min_idle_time = it->idle_time;
|
||||
}
|
||||
}
|
||||
if ((sleep_time = min_idle_time / 10) == 0) {
|
||||
sleep_time = 1;
|
||||
}
|
||||
|
||||
/* daemonize unless we're running in debug mode */
|
||||
if (!debug) {
|
||||
daemonize();
|
||||
}
|
||||
|
||||
/* main loop: probe for idle disks and stop them */
|
||||
for (;;) {
|
||||
DISKSTATS tmp;
|
||||
FILE *fp;
|
||||
char buf[200];
|
||||
|
||||
if ((fp = fopen(STAT_FILE, "r")) == NULL) {
|
||||
perror(STAT_FILE);
|
||||
return(2);
|
||||
}
|
||||
|
||||
memset(&tmp, 0x00, sizeof(tmp));
|
||||
|
||||
while (fgets(buf, sizeof(buf), fp) != NULL) {
|
||||
if (sscanf(buf, "%*d %*d %s %*u %*u %u %*u %*u %*u %u %*u %*u %*u %*u",
|
||||
tmp.name, &tmp.reads, &tmp.writes) == 3) {
|
||||
DISKSTATS *ds;
|
||||
time_t now = time(NULL);
|
||||
const char *s;
|
||||
|
||||
/* make sure this is a SCSI disk (sd[a-z]+) without partition number */
|
||||
if (tmp.name[0] != 's' || tmp.name[1] != 'd') {
|
||||
continue;
|
||||
}
|
||||
for (s = tmp.name + 2; isalpha(*s); s++);
|
||||
if (*s != '\0') {
|
||||
/* ignore disk partitions */
|
||||
continue;
|
||||
}
|
||||
|
||||
dprintf("probing %s: reads: %u, writes: %u\n", tmp.name, tmp.reads, tmp.writes);
|
||||
|
||||
/* get previous statistics for this disk */
|
||||
ds = get_diskstats(tmp.name);
|
||||
|
||||
if (ds == NULL) {
|
||||
/* new disk; just add it to the linked list */
|
||||
if ((ds = malloc(sizeof(*ds))) == NULL) {
|
||||
fprintf(stderr, "out of memory\n");
|
||||
return(2);
|
||||
}
|
||||
memcpy(ds, &tmp, sizeof(*ds));
|
||||
ds->last_io = now;
|
||||
ds->spinup = ds->last_io;
|
||||
ds->next = ds_root;
|
||||
ds_root = ds;
|
||||
|
||||
/* find idle time for this disk (falling-back to default; default means
|
||||
* 'it->name == NULL' and this entry will always be the last due to the
|
||||
* way this single-linked list is built when parsing command line
|
||||
* arguments)
|
||||
*/
|
||||
for (it = it_root; it != NULL; it = it->next) {
|
||||
if (it->name == NULL || !strcmp(ds->name, it->name)) {
|
||||
ds->idle_time = it->idle_time;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} else if (ds->reads == tmp.reads && ds->writes == tmp.writes) {
|
||||
if (!ds->spun_down) {
|
||||
/* no activity on this disk and still running */
|
||||
if (ds->idle_time != 0 && now - ds->last_io >= ds->idle_time) {
|
||||
spindown_disk(ds->name);
|
||||
ds->spindown = now;
|
||||
ds->spun_down = 1;
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
/* disk had some activity */
|
||||
if (ds->spun_down) {
|
||||
/* disk was spun down, thus it has just spun up */
|
||||
if (have_logfile) {
|
||||
log_spinup(ds);
|
||||
}
|
||||
ds->spinup = now;
|
||||
}
|
||||
ds->reads = tmp.reads;
|
||||
ds->writes = tmp.writes;
|
||||
ds->last_io = now;
|
||||
ds->spun_down = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
sleep(sleep_time);
|
||||
}
|
||||
|
||||
return(0);
|
||||
}
|
||||
|
||||
/* become a daemon */
|
||||
static void daemonize(void)
|
||||
{
|
||||
int maxfd;
|
||||
int i;
|
||||
|
||||
/* fork #1: exit parent process and continue in the background */
|
||||
if ((i = fork()) < 0) {
|
||||
perror("couldn't fork");
|
||||
exit(2);
|
||||
} else if (i > 0) {
|
||||
_exit(0);
|
||||
}
|
||||
|
||||
/* fork #2: detach from terminal and fork again so we can never regain
|
||||
* access to the terminal */
|
||||
setsid();
|
||||
if ((i = fork()) < 0) {
|
||||
perror("couldn't fork #2");
|
||||
exit(2);
|
||||
} else if (i > 0) {
|
||||
_exit(0);
|
||||
}
|
||||
|
||||
/* change to root directory and close file descriptors */
|
||||
chdir("/");
|
||||
maxfd = getdtablesize();
|
||||
for (i = 0; i < maxfd; i++) {
|
||||
close(i);
|
||||
}
|
||||
|
||||
/* use /dev/null for stdin, stdout and stderr */
|
||||
open("/dev/null", O_RDONLY);
|
||||
open("/dev/null", O_WRONLY);
|
||||
open("/dev/null", O_WRONLY);
|
||||
}
|
||||
|
||||
/* get DISKSTATS entry by name of disk */
|
||||
static DISKSTATS *get_diskstats(const char *name)
|
||||
{
|
||||
DISKSTATS *ds;
|
||||
|
||||
for (ds = ds_root; ds != NULL; ds = ds->next) {
|
||||
if (!strcmp(ds->name, name)) {
|
||||
return(ds);
|
||||
}
|
||||
}
|
||||
|
||||
return(NULL);
|
||||
}
|
||||
|
||||
/* spin-down a disk */
|
||||
static void spindown_disk(const char *name)
|
||||
{
|
||||
struct sg_io_hdr io_hdr;
|
||||
unsigned char sense_buf[255];
|
||||
char dev_name[100];
|
||||
int fd;
|
||||
|
||||
dprintf("spindown: %s\n", name);
|
||||
|
||||
/* fabricate SCSI IO request */
|
||||
memset(&io_hdr, 0x00, sizeof(io_hdr));
|
||||
io_hdr.interface_id = 'S';
|
||||
io_hdr.dxfer_direction = SG_DXFER_NONE;
|
||||
|
||||
/* SCSI stop unit command */
|
||||
io_hdr.cmdp = (unsigned char *) "\x1b\x00\x00\x00\x00\x00";
|
||||
|
||||
io_hdr.cmd_len = 6;
|
||||
io_hdr.sbp = sense_buf;
|
||||
io_hdr.mx_sb_len = (unsigned char) sizeof(sense_buf);
|
||||
|
||||
/* open disk device (kernel 2.4 will probably need "sg" names here) */
|
||||
snprintf(dev_name, sizeof(dev_name), "/dev/%s", name);
|
||||
if ((fd = open(dev_name, O_RDONLY)) < 0) {
|
||||
perror(dev_name);
|
||||
return;
|
||||
}
|
||||
|
||||
/* execute SCSI request */
|
||||
if (ioctl(fd, SG_IO, &io_hdr) < 0) {
|
||||
char buf[100];
|
||||
snprintf(buf, sizeof(buf), "ioctl on %s:", name);
|
||||
perror(buf);
|
||||
|
||||
} else if (io_hdr.masked_status != 0) {
|
||||
fprintf(stderr, "error: SCSI command failed with status 0x%02x\n",
|
||||
io_hdr.masked_status);
|
||||
if (io_hdr.masked_status == CHECK_CONDITION) {
|
||||
phex(sense_buf, io_hdr.sb_len_wr, "sense buffer:\n");
|
||||
}
|
||||
}
|
||||
|
||||
close(fd);
|
||||
}
|
||||
|
||||
/* write a spin-up event message to the log file */
|
||||
static void log_spinup(DISKSTATS *ds)
|
||||
{
|
||||
FILE *fp;
|
||||
|
||||
if ((fp = fopen(logfile, "a")) != NULL) {
|
||||
/* Print statistics to logfile
|
||||
*
|
||||
* Note: This doesn't work too well if there are multiple disks
|
||||
* because the I/O we're dealing with might be on another
|
||||
* disk so we effectively wake up the disk the log file is
|
||||
* stored on as well. Then again the logfile is a debugging
|
||||
* option, so what...
|
||||
*/
|
||||
time_t now = time(NULL);
|
||||
char tstr[20];
|
||||
char dstr[20];
|
||||
|
||||
strftime(dstr, sizeof(dstr), "%Y-%m-%d", localtime(&now));
|
||||
strftime(tstr, sizeof(tstr), "%H:%M:%S", localtime(&now));
|
||||
fprintf(fp,
|
||||
"date: %s, time: %s, disk: %s, running: %ld, stopped: %ld\n",
|
||||
dstr, tstr, ds->name,
|
||||
(long) ds->spindown - (long) ds->spinup,
|
||||
(long) time(NULL) - (long) ds->spindown);
|
||||
|
||||
/* Sync to make sure writing to the logfile won't cause another
|
||||
* spinup in 30 seconds (or whatever bdflush uses as flush interval).
|
||||
*/
|
||||
fclose(fp);
|
||||
sleep(1);
|
||||
sync();
|
||||
}
|
||||
}
|
||||
|
||||
/* Resolve disk names specified as "/dev/disk/by-xxx" or some other symlink.
|
||||
* Please note that this function is only called during command line parsing
|
||||
* and hd-idle per se does not support dynamic disk additions or removals at
|
||||
* runtime.
|
||||
*
|
||||
* This might change in the future but would require some fiddling to avoid
|
||||
* needless overhead -- after all, this was designed to run on tiny embedded
|
||||
* devices, too.
|
||||
*/
|
||||
static char *disk_name(char *path)
|
||||
{
|
||||
ssize_t len;
|
||||
char buf[256];
|
||||
char *s;
|
||||
|
||||
if (*path != '/') {
|
||||
/* just a disk name without /dev prefix */
|
||||
return(path);
|
||||
}
|
||||
|
||||
if ((len = readlink(path, buf, sizeof(buf) - 1)) <= 0) {
|
||||
if (errno != EINVAL) {
|
||||
/* couldn't resolve disk name */
|
||||
return(path);
|
||||
}
|
||||
|
||||
/* 'path' is not a symlink */
|
||||
strncpy(buf, path, sizeof(buf) - 1);
|
||||
buf[sizeof(buf)-1] = '\0';
|
||||
len = strlen(buf);
|
||||
}
|
||||
buf[len] = '\0';
|
||||
|
||||
/* remove partition numbers, if any */
|
||||
for (s = buf + strlen(buf) - 1; s >= buf && isdigit(*s); s--) {
|
||||
*s = '\0';
|
||||
}
|
||||
|
||||
/* Extract basename of the disk in /dev. Note that this assumes that the
|
||||
* final target of the symlink (if any) resolves to /dev/sd*
|
||||
*/
|
||||
if ((s = strrchr(buf, '/')) != NULL) {
|
||||
s++;
|
||||
} else {
|
||||
s = buf;
|
||||
}
|
||||
|
||||
if ((s = strdup(s)) == NULL) {
|
||||
fprintf(stderr, "out of memory");
|
||||
exit(2);
|
||||
}
|
||||
|
||||
if (debug) {
|
||||
printf("using %s for %s\n", s, path);
|
||||
}
|
||||
return(s);
|
||||
}
|
||||
|
||||
/* print hex dump to stderr (e.g. sense buffers) */
|
||||
static void phex(const void *p, int len, const char *fmt, ...)
|
||||
{
|
||||
va_list va;
|
||||
const unsigned char *buf = p;
|
||||
int pos = 0;
|
||||
int i;
|
||||
|
||||
/* print header */
|
||||
va_start(va, fmt);
|
||||
vfprintf(stderr, fmt, va);
|
||||
|
||||
/* print hex block */
|
||||
while (len > 0) {
|
||||
fprintf(stderr, "%08x ", pos);
|
||||
|
||||
/* print hex block */
|
||||
for (i = 0; i < 16; i++) {
|
||||
if (i < len) {
|
||||
fprintf(stderr, "%c%02x", ((i == 8) ? '-' : ' '), buf[i]);
|
||||
} else {
|
||||
fprintf(stderr, " ");
|
||||
}
|
||||
}
|
||||
|
||||
/* print ASCII block */
|
||||
fprintf(stderr, " ");
|
||||
for (i = 0; i < ((len > 16) ? 16 : len); i++) {
|
||||
fprintf(stderr, "%c", (buf[i] >= 32 && buf[i] < 128) ? buf[i] : '.');
|
||||
}
|
||||
fprintf(stderr, "\n");
|
||||
|
||||
pos += 16;
|
||||
buf += 16;
|
||||
len -= 16;
|
||||
}
|
||||
}
|
||||
|
287
hdidle.go
Normal file
287
hdidle.go
Normal file
@ -0,0 +1,287 @@
|
||||
// hd-idle - spin down idle hard disks
|
||||
// Copyright (C) 2018 Andoni del Olmo
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/adelolmo/hd-idle/diskstats"
|
||||
"github.com/adelolmo/hd-idle/io"
|
||||
"github.com/adelolmo/hd-idle/sgio"
|
||||
"log"
|
||||
"math"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
SCSI = "scsi"
|
||||
ATA = "ata"
|
||||
dateFormat = "2006-01-02T15:04:05"
|
||||
)
|
||||
|
||||
type DefaultConf struct {
|
||||
Idle time.Duration
|
||||
CommandType string
|
||||
PowerCondition uint8
|
||||
Debug bool
|
||||
LogFile string
|
||||
SymlinkPolicy int
|
||||
}
|
||||
|
||||
type DeviceConf struct {
|
||||
Name string
|
||||
GivenName string
|
||||
Idle time.Duration
|
||||
CommandType string
|
||||
PowerCondition uint8
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Devices []DeviceConf
|
||||
Defaults DefaultConf
|
||||
SkewTime time.Duration
|
||||
NameMap map[string]string
|
||||
}
|
||||
|
||||
func (c *Config) resolveDeviceGivenName(name string) string {
|
||||
if givenName, ok := c.NameMap[name]; ok {
|
||||
return givenName
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
type DiskStats struct {
|
||||
Name string
|
||||
GivenName string
|
||||
IdleTime time.Duration
|
||||
CommandType string
|
||||
PowerCondition uint8
|
||||
Reads uint64
|
||||
Writes uint64
|
||||
SpinDownAt time.Time
|
||||
SpinUpAt time.Time
|
||||
LastIoAt time.Time
|
||||
SpunDown bool
|
||||
}
|
||||
|
||||
var previousSnapshots []DiskStats
|
||||
var now = time.Now()
|
||||
var lastNow = time.Now()
|
||||
|
||||
func ObserveDiskActivity(config *Config) {
|
||||
actualSnapshot := diskstats.Snapshot()
|
||||
|
||||
now = time.Now()
|
||||
resolveSymlinks(config)
|
||||
for _, stats := range actualSnapshot {
|
||||
d := &DiskStats{
|
||||
Name: stats.Name,
|
||||
Reads: stats.Reads,
|
||||
Writes: stats.Writes,
|
||||
}
|
||||
updateState(*d, config)
|
||||
}
|
||||
lastNow = now
|
||||
}
|
||||
|
||||
func resolveSymlinks(config *Config) {
|
||||
if config.Defaults.SymlinkPolicy == 0 {
|
||||
return
|
||||
}
|
||||
for i := range config.Devices {
|
||||
device := config.Devices[i]
|
||||
if len(device.Name) == 0 {
|
||||
realPath, err := io.RealPath(device.GivenName)
|
||||
if err == nil {
|
||||
config.Devices[i].Name = realPath
|
||||
logToFile(config.Defaults.LogFile,
|
||||
fmt.Sprintf("symlink %s resolved to %s", device.GivenName, realPath))
|
||||
}
|
||||
if err != nil && config.Defaults.Debug {
|
||||
fmt.Printf("Cannot resolve sysmlink %s\n", device.GivenName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateState(tmp DiskStats, config *Config) {
|
||||
dsi := previousDiskStatsIndex(tmp.Name)
|
||||
if dsi < 0 {
|
||||
previousSnapshots = append(previousSnapshots, initDevice(tmp, config))
|
||||
return
|
||||
}
|
||||
|
||||
intervalDurationInSeconds := now.Unix() - lastNow.Unix()
|
||||
if intervalDurationInSeconds > config.SkewTime.Milliseconds()/1000 {
|
||||
/* we slept too long, assume a suspend event and disks may be spun up */
|
||||
/* reset spin status and timers */
|
||||
previousSnapshots[dsi].SpinUpAt = now
|
||||
previousSnapshots[dsi].LastIoAt = now
|
||||
previousSnapshots[dsi].SpunDown = false
|
||||
logSpinupAfterSleep(previousSnapshots[dsi].Name, config.Defaults.LogFile)
|
||||
}
|
||||
|
||||
ds := previousSnapshots[dsi]
|
||||
if ds.Writes == tmp.Writes && ds.Reads == tmp.Reads {
|
||||
if !ds.SpunDown {
|
||||
/* no activity on this disk and still running */
|
||||
idleDuration := now.Sub(ds.LastIoAt)
|
||||
if ds.IdleTime != 0 && idleDuration > ds.IdleTime {
|
||||
fmt.Printf("%s spindown\n", config.resolveDeviceGivenName(ds.Name))
|
||||
device := fmt.Sprintf("/dev/%s", ds.Name)
|
||||
if err := spindownDisk(device, ds.CommandType, ds.PowerCondition, config.Defaults.Debug); err != nil {
|
||||
fmt.Println(err.Error())
|
||||
}
|
||||
previousSnapshots[dsi].SpinDownAt = now
|
||||
previousSnapshots[dsi].SpunDown = true
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
/* disk had some activity */
|
||||
if ds.SpunDown {
|
||||
/* disk was spun down, thus it has just spun up */
|
||||
fmt.Printf("%s spinup\n", config.resolveDeviceGivenName(ds.Name))
|
||||
logSpinup(ds, config.Defaults.LogFile, config.resolveDeviceGivenName(ds.Name))
|
||||
previousSnapshots[dsi].SpinUpAt = now
|
||||
}
|
||||
previousSnapshots[dsi].Reads = tmp.Reads
|
||||
previousSnapshots[dsi].Writes = tmp.Writes
|
||||
previousSnapshots[dsi].LastIoAt = now
|
||||
previousSnapshots[dsi].SpunDown = false
|
||||
}
|
||||
|
||||
if config.Defaults.Debug {
|
||||
ds = previousSnapshots[dsi]
|
||||
idleDuration := now.Sub(ds.LastIoAt)
|
||||
fmt.Printf("disk=%s command=%s spunDown=%t "+
|
||||
"reads=%d writes=%d idleTime=%v idleDuration=%v "+
|
||||
"spindown=%s spinup=%s lastIO=%s\n",
|
||||
ds.Name, ds.CommandType, ds.SpunDown,
|
||||
ds.Reads, ds.Writes, ds.IdleTime.Seconds(), math.RoundToEven(idleDuration.Seconds()),
|
||||
ds.SpinDownAt.Format(dateFormat), ds.SpinUpAt.Format(dateFormat), ds.LastIoAt.Format(dateFormat))
|
||||
}
|
||||
}
|
||||
|
||||
func previousDiskStatsIndex(diskName string) int {
|
||||
for i, stats := range previousSnapshots {
|
||||
if stats.Name == diskName {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func initDevice(stats DiskStats, config *Config) DiskStats {
|
||||
idle := config.Defaults.Idle
|
||||
command := config.Defaults.CommandType
|
||||
powerCondition := config.Defaults.PowerCondition
|
||||
deviceConf := deviceConfig(stats.Name, config)
|
||||
if deviceConf != nil {
|
||||
idle = deviceConf.Idle
|
||||
command = deviceConf.CommandType
|
||||
powerCondition = deviceConf.PowerCondition
|
||||
}
|
||||
|
||||
return DiskStats{
|
||||
Name: stats.Name,
|
||||
LastIoAt: time.Now(),
|
||||
SpinUpAt: time.Now(),
|
||||
SpunDown: false,
|
||||
Writes: stats.Writes,
|
||||
Reads: stats.Reads,
|
||||
IdleTime: idle,
|
||||
CommandType: command,
|
||||
PowerCondition: powerCondition,
|
||||
}
|
||||
}
|
||||
|
||||
func deviceConfig(diskName string, config *Config) *DeviceConf {
|
||||
for _, device := range config.Devices {
|
||||
if device.Name == diskName {
|
||||
return &device
|
||||
}
|
||||
}
|
||||
return &DeviceConf{
|
||||
Name: diskName,
|
||||
CommandType: config.Defaults.CommandType,
|
||||
PowerCondition: config.Defaults.PowerCondition,
|
||||
Idle: config.Defaults.Idle,
|
||||
}
|
||||
}
|
||||
|
||||
func spindownDisk(device, command string, powerCondition uint8, debug bool) error {
|
||||
switch command {
|
||||
case SCSI:
|
||||
if err := sgio.StartStopScsiDevice(device, powerCondition); err != nil {
|
||||
return fmt.Errorf("cannot spindown scsi disk %s:\n%s\n", device, err.Error())
|
||||
}
|
||||
return nil
|
||||
case ATA:
|
||||
if err := sgio.StopAtaDevice(device, debug); err != nil {
|
||||
return fmt.Errorf("cannot spindown ata disk %s:\n%s\n", device, err.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func logSpinup(ds DiskStats, file, givenName string) {
|
||||
now := time.Now()
|
||||
text := fmt.Sprintf("date: %s, time: %s, disk: %s, running: %d, stopped: %d",
|
||||
now.Format("2006-01-02"), now.Format("15:04:05"), givenName,
|
||||
int(ds.SpinDownAt.Sub(ds.SpinUpAt).Seconds()), int(now.Sub(ds.SpinDownAt).Seconds()))
|
||||
logToFile(file, text)
|
||||
}
|
||||
|
||||
func logSpinupAfterSleep(name, file string) {
|
||||
text := fmt.Sprintf("date: %s, time: %s, disk: %s, assuming disk spun up after long sleep",
|
||||
now.Format("2006-01-02"), now.Format("15:04:05"), name)
|
||||
logToFile(file, text)
|
||||
}
|
||||
|
||||
func logToFile(file, text string) {
|
||||
if len(file) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
cacheFile, err := os.OpenFile(file, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
|
||||
if err != nil {
|
||||
log.Fatalf("Cannot open file %s. Error: %s", file, err)
|
||||
}
|
||||
if _, err = cacheFile.WriteString(text + "\n"); err != nil {
|
||||
log.Fatalf("Cannot write into file %s. Error: %s", file, err)
|
||||
}
|
||||
err = cacheFile.Close()
|
||||
if err != nil {
|
||||
log.Fatalf("Cannot close file %s. Error: %s", file, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Config) String() string {
|
||||
var devices string
|
||||
for _, device := range c.Devices {
|
||||
devices += "{" + device.String() + "}"
|
||||
}
|
||||
return fmt.Sprintf("symlinkPolicy=%d, defaultIdle=%v, defaultCommand=%s, defaultPowerCondition=%v, debug=%t, logFile=%s, devices=%s",
|
||||
c.Defaults.SymlinkPolicy, c.Defaults.Idle.Seconds(), c.Defaults.CommandType, c.Defaults.PowerCondition, c.Defaults.Debug, c.Defaults.LogFile, devices)
|
||||
}
|
||||
|
||||
func (dc *DeviceConf) String() string {
|
||||
return fmt.Sprintf("name=%s, givenName=%s, idle=%v, commandType=%s, powerCondition=%v",
|
||||
dc.Name, dc.GivenName, dc.Idle.Seconds(), dc.CommandType, dc.PowerCondition)
|
||||
}
|
50
io/disk.go
Normal file
50
io/disk.go
Normal file
@ -0,0 +1,50 @@
|
||||
// hd-idle - spin down idle hard disks
|
||||
// Copyright (C) 2018 Andoni del Olmo
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package io
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func RealPath(path string) (string, error) {
|
||||
if path[0] != '/' {
|
||||
return path, nil
|
||||
}
|
||||
if !strings.Contains(path, "by-") {
|
||||
return filepath.Base(path), nil
|
||||
}
|
||||
s, err := os.Readlink(path)
|
||||
if err == nil {
|
||||
device := filepath.Base(s)
|
||||
/* remove partition numbers, if any */
|
||||
for {
|
||||
i := device[len(device)-1:]
|
||||
_, err := strconv.Atoi(i)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
device = device[:len(device)-1]
|
||||
}
|
||||
return device, nil
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("cannot find device for %s", path)
|
||||
}
|
103
io/disk_test.go
Normal file
103
io/disk_test.go
Normal file
@ -0,0 +1,103 @@
|
||||
// hd-idle - spin down idle hard disks
|
||||
// Copyright (C) 2018 Andoni del Olmo
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package io
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRealPath(t *testing.T) {
|
||||
|
||||
type args struct {
|
||||
path string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
symlinkTarget string
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "only device name",
|
||||
args: args{path: "sda"},
|
||||
want: "sda",
|
||||
},
|
||||
{
|
||||
name: "full device path",
|
||||
args: args{path: "/tmp/dev/sda"},
|
||||
want: "sda",
|
||||
},
|
||||
{
|
||||
name: "wrong symlink by id",
|
||||
args: args{path: "/tmp/dev/disk/by-id/ata-SAMSUNG_HD103SJ"},
|
||||
want: "",
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "symlink by id",
|
||||
args: args{path: "/tmp/dev/disk/by-id/ata-SAMSUNG_HD103SJ"},
|
||||
want: "sdc",
|
||||
symlinkTarget: "/tmp/dev/sdc",
|
||||
},
|
||||
{
|
||||
name: "symlink to partition by id",
|
||||
args: args{path: "/tmp/dev/disk/by-label/disk2"},
|
||||
want: "sdc",
|
||||
symlinkTarget: "/tmp/dev/sdc1",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
err := os.RemoveAll("/tmp/dev")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
err = os.MkdirAll("/tmp/dev/disk/by-id", os.ModePerm)
|
||||
if err != nil {
|
||||
panic("cannot create tmp dir")
|
||||
}
|
||||
err = os.MkdirAll("/tmp/dev/disk/by-label", os.ModePerm)
|
||||
if err != nil {
|
||||
panic("cannot create tmp dir")
|
||||
}
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if len(tt.want) > 0 {
|
||||
disk := fmt.Sprintf("/tmp/dev/%s", tt.want)
|
||||
_, err := os.Create(disk)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if len(tt.symlinkTarget) > 0 {
|
||||
err = os.Symlink(tt.symlinkTarget, tt.args.path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
got, err := RealPath(tt.args.path)
|
||||
|
||||
if err != nil && tt.expectError == false {
|
||||
panic(err)
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("RealPath() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
245
main.go
Normal file
245
main.go
Normal file
@ -0,0 +1,245 @@
|
||||
// hd-idle - spin down idle hard disks
|
||||
// Copyright (C) 2018 Andoni del Olmo
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/adelolmo/hd-idle/io"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultIdleTime = 600 * time.Second
|
||||
symlinkResolveOnce = 0
|
||||
symlinkResolveRetry = 1
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
if os.Getenv("START_HD_IDLE") == "false" {
|
||||
fmt.Println("START_HD_IDLE=false exiting now.")
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
singleDiskMode := false
|
||||
var disk string
|
||||
defaultConf := DefaultConf{
|
||||
Idle: defaultIdleTime,
|
||||
CommandType: SCSI,
|
||||
PowerCondition: 0,
|
||||
Debug: false,
|
||||
SymlinkPolicy: 0,
|
||||
}
|
||||
var config = &Config{
|
||||
Devices: []DeviceConf{},
|
||||
Defaults: defaultConf,
|
||||
NameMap: map[string]string{},
|
||||
}
|
||||
var deviceConf *DeviceConf
|
||||
|
||||
if len(os.Args) == 0 {
|
||||
usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
for index, arg := range os.Args[1:] {
|
||||
switch arg {
|
||||
case "-t":
|
||||
var err error
|
||||
disk, err = argument(index)
|
||||
if err != nil {
|
||||
fmt.Println("Missing disk argument after -t. Must be a device (e.g. -t sda).")
|
||||
os.Exit(1)
|
||||
}
|
||||
singleDiskMode = true
|
||||
|
||||
case "-s":
|
||||
s, err := argument(index)
|
||||
if err != nil {
|
||||
fmt.Println("Missing symlink_policy. Must be 0 or 1.")
|
||||
os.Exit(1)
|
||||
}
|
||||
switch s {
|
||||
case "0":
|
||||
config.Defaults.SymlinkPolicy = symlinkResolveOnce
|
||||
case "1":
|
||||
config.Defaults.SymlinkPolicy = symlinkResolveRetry
|
||||
default:
|
||||
fmt.Printf("Wrong symlink_policy -s %s. Must be 0 or 1.\n", s)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
case "-a":
|
||||
if deviceConf != nil {
|
||||
config.Devices = append(config.Devices, *deviceConf)
|
||||
}
|
||||
|
||||
name, err := argument(index)
|
||||
if err != nil {
|
||||
fmt.Println("Missing disk argument after -a. Must be a device (e.g. -a sda).")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
deviceRealPath, err := io.RealPath(name)
|
||||
if err != nil {
|
||||
deviceRealPath = ""
|
||||
fmt.Printf("Unable to resolve symlink: %s\n", name)
|
||||
}
|
||||
deviceConf = &DeviceConf{
|
||||
Name: deviceRealPath,
|
||||
GivenName: name,
|
||||
Idle: config.Defaults.Idle,
|
||||
CommandType: config.Defaults.CommandType,
|
||||
PowerCondition: config.Defaults.PowerCondition,
|
||||
}
|
||||
config.NameMap[deviceRealPath] = name
|
||||
|
||||
case "-i":
|
||||
s, err := argument(index)
|
||||
if err != nil {
|
||||
fmt.Println("Missing idle_time after -i. Must be a number.")
|
||||
os.Exit(1)
|
||||
}
|
||||
idle, err := strconv.Atoi(s)
|
||||
if err != nil {
|
||||
fmt.Printf("Wrong idle_time -i %d. Must be a number.", idle)
|
||||
os.Exit(1)
|
||||
}
|
||||
if deviceConf == nil {
|
||||
config.Defaults.Idle = time.Duration(idle) * time.Second
|
||||
break
|
||||
}
|
||||
deviceConf.Idle = time.Duration(idle) * time.Second
|
||||
|
||||
case "-c":
|
||||
command, err := argument(index)
|
||||
if err != nil {
|
||||
fmt.Println("Missing command_type after -c. Must be one of: scsi, ata.")
|
||||
os.Exit(1)
|
||||
}
|
||||
switch command {
|
||||
case SCSI, ATA:
|
||||
if deviceConf == nil {
|
||||
config.Defaults.CommandType = command
|
||||
break
|
||||
}
|
||||
deviceConf.CommandType = command
|
||||
default:
|
||||
fmt.Printf("Wrong command_type -c %s. Must be one of: scsi, ata.", command)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
case "-p":
|
||||
s, err := argument(index)
|
||||
if err != nil {
|
||||
fmt.Println("Missing power condition after -p. Must be a number from 0-15.")
|
||||
os.Exit(1)
|
||||
}
|
||||
powerCondition, err := strconv.ParseUint(s, 0, 4)
|
||||
if err != nil {
|
||||
fmt.Printf("Invalid power condition %s: %s", s, err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
if deviceConf == nil {
|
||||
config.Defaults.PowerCondition = uint8(powerCondition)
|
||||
break
|
||||
}
|
||||
deviceConf.PowerCondition = uint8(powerCondition)
|
||||
|
||||
case "-l":
|
||||
logfile, err := argument(index)
|
||||
if err != nil {
|
||||
fmt.Println("Missing logfile after -l.")
|
||||
os.Exit(1)
|
||||
}
|
||||
config.Defaults.LogFile = logfile
|
||||
|
||||
case "-d":
|
||||
config.Defaults.Debug = true
|
||||
|
||||
case "-h":
|
||||
usage()
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
if singleDiskMode {
|
||||
if err := spindownDisk(
|
||||
disk,
|
||||
config.Defaults.CommandType,
|
||||
config.Defaults.PowerCondition,
|
||||
config.Defaults.Debug,
|
||||
); err != nil {
|
||||
fmt.Println(err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if deviceConf != nil {
|
||||
config.Devices = append(config.Devices, *deviceConf)
|
||||
}
|
||||
fmt.Println(config.String())
|
||||
|
||||
interval := poolInterval(config.Devices)
|
||||
config.SkewTime = interval * 3
|
||||
for {
|
||||
ObserveDiskActivity(config)
|
||||
time.Sleep(interval)
|
||||
}
|
||||
}
|
||||
|
||||
func argument(index int) (string, error) {
|
||||
argIndex := index + 2
|
||||
if argIndex >= len(os.Args) {
|
||||
return "", fmt.Errorf("option requires argument")
|
||||
}
|
||||
arg := os.Args[argIndex]
|
||||
if arg[:1] == "-" {
|
||||
return "", fmt.Errorf("option requires argument")
|
||||
}
|
||||
return arg, nil
|
||||
}
|
||||
|
||||
func usage() {
|
||||
fmt.Println("usage: hd-idle [-t <disk>] [-s <symlink_policy>] [-a <name>] [-i <idle_time>] " +
|
||||
"[-c <command_type>] [-p power_condition] [-l <logfile>] [-d] [-h]")
|
||||
}
|
||||
|
||||
func poolInterval(deviceConfs []DeviceConf) time.Duration {
|
||||
if len(deviceConfs) == 0 {
|
||||
return defaultIdleTime / 10
|
||||
}
|
||||
|
||||
interval := defaultIdleTime
|
||||
for _, dev := range deviceConfs {
|
||||
if dev.Idle == 0 {
|
||||
continue
|
||||
}
|
||||
if dev.Idle < interval {
|
||||
interval = dev.Idle
|
||||
}
|
||||
}
|
||||
|
||||
sleepTime := interval / 10
|
||||
if sleepTime == 0 {
|
||||
return time.Second
|
||||
}
|
||||
return sleepTime
|
||||
}
|
32
main_test.go
Normal file
32
main_test.go
Normal file
@ -0,0 +1,32 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestIntervalWithZeroSecondsIdle(t *testing.T) {
|
||||
confs := []DeviceConf{{
|
||||
Name: "test",
|
||||
GivenName: "test",
|
||||
Idle: 0,
|
||||
CommandType: "ata",
|
||||
}}
|
||||
interval := poolInterval(confs)
|
||||
if interval != defaultIdleTime/10 {
|
||||
t.Fatalf("interval should be the default. it was %d", interval)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntervalWith300SecondsIdle(t *testing.T) {
|
||||
confs := []DeviceConf{{
|
||||
Name: "test",
|
||||
GivenName: "test",
|
||||
Idle: 300 * time.Second,
|
||||
CommandType: "ata",
|
||||
}}
|
||||
interval := poolInterval(confs)
|
||||
if interval != 30*time.Second {
|
||||
t.Fatalf("interval should be the 30s. it was %v", interval)
|
||||
}
|
||||
}
|
132
sgio/ata.go
Normal file
132
sgio/ata.go
Normal file
@ -0,0 +1,132 @@
|
||||
// hd-idle - spin down idle hard disks
|
||||
// Copyright (C) 2018 Andoni del Olmo
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package sgio
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/benmcclelland/sgio"
|
||||
"os"
|
||||
)
|
||||
|
||||
const (
|
||||
sgAta16 = 0x85 // ATA PASS-THROUGH(16)
|
||||
sgAta12 = 0xa1 // ATA PASS-THROUGH (12)
|
||||
|
||||
sgAtaProtoNonData = 3 << 1
|
||||
ataUsingLba = 1 << 6
|
||||
|
||||
ataOpStandbyNow1 = 0xe0 // https://wiki.osdev.org/ATA/ATAPI_Power_Management
|
||||
ataOpStandbyNow2 = 0x94 // Retired in ATA4. Did not coexist with ATAPI.
|
||||
)
|
||||
|
||||
func StopAtaDevice(device string, debug bool) error {
|
||||
f, err := openDevice(device)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch NewAtaDevice(device, debug).deviceType() {
|
||||
case Jmicron:
|
||||
if err = sendSgio(f, jmicronGetRegisters(), debug); err != nil {
|
||||
return err
|
||||
}
|
||||
if debug {
|
||||
fmt.Println(" issuing standby command")
|
||||
}
|
||||
if err = sendSgio(f, jmicronStandby(), debug); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
if debug {
|
||||
fmt.Println(" issuing standby command")
|
||||
}
|
||||
if err = sendAtaCommand(f, ataOpStandbyNow1, debug); err != nil {
|
||||
if err = sendAtaCommand(f, ataOpStandbyNow2, debug); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := f.Close(); err != nil {
|
||||
return fmt.Errorf("cannot close file %s. Error: %s", device, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func jmicronGetRegisters() []uint8 {
|
||||
cbd := []uint8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} // len 12
|
||||
cbd[0] = 0xdf
|
||||
cbd[1] = 0x10 // read
|
||||
cbd[4] = 0x01
|
||||
cbd[6] = 0x72
|
||||
cbd[7] = 0x0f
|
||||
cbd[11] = 0xfd
|
||||
return cbd
|
||||
}
|
||||
|
||||
func jmicronStandby() []uint8 {
|
||||
cbd := []uint8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} // len 12
|
||||
cbd[0] = 0xdf
|
||||
cbd[1] = 0x10
|
||||
cbd[10] = 0xa0 // device port. either 0xa0 or 0xb0
|
||||
cbd[11] = ataOpStandbyNow1
|
||||
return cbd
|
||||
}
|
||||
|
||||
func sendAtaCommand(f *os.File, command uint8, debug bool) error {
|
||||
cbd := []uint8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} // len 16
|
||||
cbd[0] = sgAta16
|
||||
cbd[1] = sgAtaProtoNonData
|
||||
cbd[13] = ataUsingLba
|
||||
cbd[14] = command
|
||||
return sendSgio(f, cbd, debug)
|
||||
}
|
||||
|
||||
func sendSgio(f *os.File, inqCmdBlk []uint8, debug bool) error {
|
||||
senseBuf := make([]byte, sgio.SENSE_BUF_LEN)
|
||||
ioHdr := &sgio.SgIoHdr{
|
||||
InterfaceID: 'S', // 0 4
|
||||
DxferDirection: SgDxferNone, // 4 4
|
||||
CmdLen: uint8(len(inqCmdBlk)), // 8 1
|
||||
MxSbLen: sgio.SENSE_BUF_LEN, // 9 1
|
||||
Cmdp: &inqCmdBlk[0], // 24 8
|
||||
Sbp: &senseBuf[0], // 32 8
|
||||
Timeout: 0, // 40 4
|
||||
}
|
||||
|
||||
if debug {
|
||||
dumpBytes(inqCmdBlk)
|
||||
}
|
||||
|
||||
if err := sgio.SgioSyscall(f, ioHdr); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := sgio.CheckSense(ioHdr, &senseBuf); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func dumpBytes(p []uint8) {
|
||||
fmt.Print("outgoing cdb: ")
|
||||
for i := range p {
|
||||
fmt.Printf("%02x ", p[i])
|
||||
}
|
||||
fmt.Print("\n")
|
||||
}
|
47
sgio/common.go
Normal file
47
sgio/common.go
Normal file
@ -0,0 +1,47 @@
|
||||
// hd-idle - spin down idle hard disks
|
||||
// Copyright (C) 2018 Andoni del Olmo
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package sgio
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/benmcclelland/sgio"
|
||||
"os"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const SgDxferNone = -1
|
||||
|
||||
func openDevice(fname string) (*os.File, error) {
|
||||
f, err := os.OpenFile(fname, os.O_RDONLY, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var version uint32
|
||||
if (ioctl(f.Fd(), sgio.SG_GET_VERSION_NUM, uintptr(unsafe.Pointer(&version))) != nil) || (version < 30000) {
|
||||
return nil, fmt.Errorf("device does not appear to be an sg device")
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
func ioctl(fd, cmd, ptr uintptr) error {
|
||||
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, cmd, ptr)
|
||||
if err != 0 {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
63
sgio/scsi.go
Normal file
63
sgio/scsi.go
Normal file
@ -0,0 +1,63 @@
|
||||
// hd-idle - spin down idle hard disks
|
||||
// Copyright (C) 2018 Andoni del Olmo
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package sgio
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/benmcclelland/sgio"
|
||||
)
|
||||
|
||||
// https://en.wikipedia.org/wiki/SCSI_command
|
||||
const startStopUnit = 0x1b
|
||||
|
||||
func StartStopScsiDevice(device string, powerCondition uint8) error {
|
||||
f, err := openDevice(device)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
senseBuf := make([]byte, sgio.SENSE_BUF_LEN)
|
||||
//See https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf - 3.49 START STOP UNIT command
|
||||
inqCmdBlk := []uint8{
|
||||
startStopUnit,
|
||||
0, //Reserved (7 bit) + IMMED
|
||||
0, //Reserved (8 bit)
|
||||
0, //Reserved (4 bit) + POWER CONDITION MODIFER
|
||||
powerCondition << 4, //POWER CONDITION + Reserved (1 bit) + NO_ FLUSH + LOEJ + LOEJ
|
||||
0} //CONTROL
|
||||
ioHdr := &sgio.SgIoHdr{
|
||||
InterfaceID: 'S',
|
||||
DxferDirection: SgDxferNone,
|
||||
Cmdp: &inqCmdBlk[0],
|
||||
CmdLen: uint8(len(inqCmdBlk)),
|
||||
Sbp: &senseBuf[0],
|
||||
MxSbLen: sgio.SENSE_BUF_LEN,
|
||||
}
|
||||
|
||||
if err := sgio.SgioSyscall(f, ioHdr); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := sgio.CheckSense(ioHdr, &senseBuf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := f.Close(); err != nil {
|
||||
return fmt.Errorf("cannot close file %s. Error: %s", device, err)
|
||||
}
|
||||
return nil
|
||||
}
|
127
sgio/type.go
Normal file
127
sgio/type.go
Normal file
@ -0,0 +1,127 @@
|
||||
// hd-idle - spin down idle hard disks
|
||||
// Copyright (C) 2023 Andoni del Olmo
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package sgio
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
Jmicron = iota
|
||||
Unknown = iota
|
||||
|
||||
sysblock = "/sys/block"
|
||||
|
||||
jmicron = "152d"
|
||||
)
|
||||
|
||||
type AtaDevice struct {
|
||||
device string
|
||||
debug bool
|
||||
fsRoot string
|
||||
}
|
||||
|
||||
func NewAtaDevice(device string, debug bool) AtaDevice {
|
||||
return AtaDevice{
|
||||
device: device,
|
||||
debug: debug,
|
||||
fsRoot: sysblock,
|
||||
}
|
||||
}
|
||||
|
||||
type apt struct {
|
||||
idVendor, idProduct, bcdDevice string
|
||||
}
|
||||
|
||||
func (a apt) isJmicron() bool {
|
||||
if a.idVendor != jmicron {
|
||||
return false
|
||||
}
|
||||
switch a.idProduct {
|
||||
case "2329", "2336", "2338", "2339":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (ad AtaDevice) deviceType() int {
|
||||
a, err := ad.identifyDevice(ad.device)
|
||||
if err != nil {
|
||||
if ad.debug {
|
||||
fmt.Println("APT: Unsupported device")
|
||||
}
|
||||
return Unknown
|
||||
}
|
||||
if a.isJmicron() {
|
||||
if ad.debug {
|
||||
fmt.Println("APT: Found supported device jmicron")
|
||||
}
|
||||
return Jmicron
|
||||
}
|
||||
|
||||
if ad.debug {
|
||||
fmt.Println("APT: Unsupported device")
|
||||
}
|
||||
return Unknown
|
||||
}
|
||||
|
||||
func (ad AtaDevice) identifyDevice(device string) (apt, error) {
|
||||
diskname := strings.Split(device, "/")[2]
|
||||
sysblockdisk := filepath.Join(ad.fsRoot, diskname)
|
||||
idVendor, err := findSystemFile(sysblockdisk, "idVendor")
|
||||
if err != nil {
|
||||
return apt{}, err
|
||||
}
|
||||
idProduct, err := findSystemFile(sysblockdisk, "idProduct")
|
||||
if err != nil {
|
||||
return apt{}, err
|
||||
}
|
||||
bcdDevice, err := findSystemFile(sysblockdisk, "bcdDevice")
|
||||
if err != nil {
|
||||
return apt{}, err
|
||||
}
|
||||
if ad.debug {
|
||||
fmt.Printf("APT: USB ID = 0x%s:0x%s (0x%3s)\n", idVendor, idProduct, bcdDevice)
|
||||
}
|
||||
return apt{
|
||||
idVendor: idVendor,
|
||||
idProduct: idProduct,
|
||||
bcdDevice: bcdDevice,
|
||||
},
|
||||
nil
|
||||
}
|
||||
|
||||
func findSystemFile(systemRoot, filename string) (string, error) {
|
||||
_, err := os.ReadFile(filepath.Join(systemRoot, filename))
|
||||
relativeDir := ""
|
||||
var content []byte
|
||||
|
||||
depth := 0
|
||||
for depth < 20 {
|
||||
if err == nil {
|
||||
return strings.TrimSpace(string(content)), nil
|
||||
}
|
||||
relativeDir += "/.."
|
||||
content, err = os.ReadFile(systemRoot + relativeDir + "/" + filename)
|
||||
depth++
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("device not found")
|
||||
}
|
80
sgio/type_test.go
Normal file
80
sgio/type_test.go
Normal file
@ -0,0 +1,80 @@
|
||||
package sgio
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const (
|
||||
tmpDir = "/tmp/hd-idle/ata"
|
||||
)
|
||||
|
||||
func TestAtaDevice_deviceType(t *testing.T) {
|
||||
type fields struct {
|
||||
device string
|
||||
debug bool
|
||||
fsRoot string
|
||||
idVendor, idProduct, bcdDevice string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
want int
|
||||
}{
|
||||
{
|
||||
name: "find jmicron controller",
|
||||
fields: fields{
|
||||
device: "/dev/sde",
|
||||
debug: true,
|
||||
fsRoot: filepath.Join(tmpDir, "sys", "block"),
|
||||
idVendor: "152d",
|
||||
idProduct: "2339",
|
||||
bcdDevice: "100",
|
||||
},
|
||||
want: Jmicron,
|
||||
},
|
||||
{
|
||||
name: "unknown device",
|
||||
fields: fields{
|
||||
device: "/dev/sde",
|
||||
debug: true,
|
||||
fsRoot: filepath.Join(tmpDir, "sys", "block"),
|
||||
idVendor: "1058",
|
||||
idProduct: "25a3",
|
||||
bcdDevice: "1021",
|
||||
},
|
||||
want: Unknown,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ad := AtaDevice{
|
||||
device: tt.fields.device,
|
||||
debug: tt.fields.debug,
|
||||
fsRoot: tt.fields.fsRoot,
|
||||
}
|
||||
|
||||
err := os.RemoveAll(tmpDir)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
infoDir := filepath.Join(tmpDir, "/sys/devices/pci0000:00/0000:00:15.0/usb2/2-2/2-2.3/2-2.3.2")
|
||||
diskname := strings.Split(tt.fields.device, "/")[2]
|
||||
deviceRoot := infoDir + "/2-2.3.2:1.0/host5/target5:0:0/5:0:0:0/block/" + diskname
|
||||
_ = os.MkdirAll(deviceRoot, 0755)
|
||||
_ = os.WriteFile(filepath.Join(infoDir, "idVendor"), []byte(tt.fields.idVendor), 0666)
|
||||
_ = os.WriteFile(filepath.Join(infoDir, "idProduct"), []byte(tt.fields.idProduct), 0666)
|
||||
_ = os.WriteFile(filepath.Join(infoDir, "bcdDevice"), []byte(tt.fields.bcdDevice), 0666)
|
||||
_ = os.MkdirAll(filepath.Join(tmpDir, "/sys/block"), 0755)
|
||||
if err = os.Symlink(deviceRoot, filepath.Join(tmpDir, "/sys/block", diskname)); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if got := ad.deviceType(); got != tt.want {
|
||||
t.Errorf("deviceType() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
24
vendor/github.com/benmcclelland/sgio/.gitignore
generated
vendored
Normal file
24
vendor/github.com/benmcclelland/sgio/.gitignore
generated
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
21
vendor/github.com/benmcclelland/sgio/LICENSE
generated
vendored
Normal file
21
vendor/github.com/benmcclelland/sgio/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 Ben McClelland
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
28
vendor/github.com/benmcclelland/sgio/README.md
generated
vendored
Normal file
28
vendor/github.com/benmcclelland/sgio/README.md
generated
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
# sgio
|
||||
golang library for issuing SCSI commands with SG_IO ioctl
|
||||
|
||||
[](https://godoc.org/github.com/benmcclelland/sgio)
|
||||
|
||||
See TestUnitReady() for example function using SG_IO
|
||||
|
||||
example:
|
||||
```
|
||||
f, err := OpenScsiDevice("/dev/sg0")
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
defer f.Close()
|
||||
```
|
||||
Fill out SgIoHdr for SCSI command
|
||||
```
|
||||
ioHdr := &SgIoHdr{...}
|
||||
err := SgioSyscall(f, ioHdr)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
err = CheckSense(ioHdr, &senseBuf)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
```
|
219
vendor/github.com/benmcclelland/sgio/asc.go
generated
vendored
Normal file
219
vendor/github.com/benmcclelland/sgio/asc.go
generated
vendored
Normal file
@ -0,0 +1,219 @@
|
||||
package sgio
|
||||
|
||||
var errmap map[string]string
|
||||
|
||||
func GetErrString(a, b byte) string {
|
||||
return errmap[stringify(a, b)]
|
||||
}
|
||||
|
||||
func init() {
|
||||
errmap = make(map[string]string)
|
||||
errmap[stringify(0x00, 0x00)] = "NO ADDITIONAL SENSE INFORMATION"
|
||||
errmap[stringify(0x00, 0x01)] = "FILEMARK DETECTED"
|
||||
errmap[stringify(0x00, 0x02)] = "END-OF-PARTITION/MEDIUM DETECTED"
|
||||
errmap[stringify(0x00, 0x03)] = "SETMARK DETECTED"
|
||||
errmap[stringify(0x00, 0x04)] = "BEGINNING-OF-PARTITION/MEDIUM DETECTED"
|
||||
errmap[stringify(0x00, 0x05)] = "END-OF-DATA DETECTED"
|
||||
errmap[stringify(0x00, 0x06)] = "I/O PROCESS TERMINATED"
|
||||
errmap[stringify(0x00, 0x11)] = "AUDIO PLAY OPERATION IN PROGRESS"
|
||||
errmap[stringify(0x00, 0x12)] = "AUDIO PLAY OPERATION PAUSED"
|
||||
errmap[stringify(0x00, 0x13)] = "AUDIO PLAY OPERATION SUCCESSFULLY COMPLETED"
|
||||
errmap[stringify(0x00, 0x14)] = "AUDIO PLAY OPERATION STOPPED DUE TO ERROR"
|
||||
errmap[stringify(0x00, 0x15)] = "NO CURRENT AUDIO STATUS TO RETURN"
|
||||
errmap[stringify(0x01, 0x00)] = "NO INDEX/SECTOR SIGNAL"
|
||||
errmap[stringify(0x02, 0x00)] = "NO SEEK COMPLETE"
|
||||
errmap[stringify(0x03, 0x00)] = "PERIPHERAL DEVICE WRITE FAULT"
|
||||
errmap[stringify(0x03, 0x01)] = "NO WRITE CURRENT"
|
||||
errmap[stringify(0x03, 0x02)] = "EXCESSIVE WRITE ERRORS"
|
||||
errmap[stringify(0x04, 0x00)] = "LOGICAL UNIT NOT READY, CAUSE NOT REPORTABLE"
|
||||
errmap[stringify(0x04, 0x01)] = "LOGICAL UNIT IS IN PROCESS OF BECOMING READY"
|
||||
errmap[stringify(0x04, 0x02)] = "LOGICAL UNIT NOT READY, INITIALIZING COMMAND REQUIRED"
|
||||
errmap[stringify(0x04, 0x03)] = "LOGICAL UNIT NOT READY, MANUAL INTERVENTION REQUIRED"
|
||||
errmap[stringify(0x04, 0x04)] = "LOGICAL UNIT NOT READY, FORMAT IN PROGRESS"
|
||||
errmap[stringify(0x05, 0x00)] = "LOGICAL UNIT DOES NOT RESPOND TO SELECTION"
|
||||
errmap[stringify(0x06, 0x00)] = "REFERENCE POSITION FOUND"
|
||||
errmap[stringify(0x07, 0x00)] = "MULTIPLE PERIPHERAL DEVICES SELECTED"
|
||||
errmap[stringify(0x08, 0x00)] = "LOGICAL UNIT COMMUNICATION FAILURE"
|
||||
errmap[stringify(0x08, 0x01)] = "LOGICAL UNIT COMMUNICATION TIME-OUT"
|
||||
errmap[stringify(0x08, 0x02)] = "LOGICAL UNIT COMMUNICATION PARITY ERROR"
|
||||
errmap[stringify(0x09, 0x00)] = "TRACK FOLLOWING ERROR"
|
||||
errmap[stringify(0x09, 0x01)] = "TRA CKING SERVO FAILURE"
|
||||
errmap[stringify(0x09, 0x02)] = "FOC US SERVO FAILURE"
|
||||
errmap[stringify(0x09, 0x03)] = "SPI NDLE SERVO FAILURE"
|
||||
errmap[stringify(0x0A, 0x00)] = "ERROR LOG OVERFLOW"
|
||||
errmap[stringify(0x0B, 0x00)] = ""
|
||||
errmap[stringify(0x0C, 0x00)] = "WRITE ERROR"
|
||||
errmap[stringify(0x0C, 0x01)] = "WRITE ERROR RECOVERED WITH AUTO REALLOCATION"
|
||||
errmap[stringify(0x0C, 0x02)] = "WRITE ERROR - AUTO REALLOCATION FAILED"
|
||||
errmap[stringify(0x0D, 0x00)] = ""
|
||||
errmap[stringify(0x0E, 0x00)] = ""
|
||||
errmap[stringify(0x0F, 0x00)] = ""
|
||||
errmap[stringify(0x10, 0x00)] = "ID CRC OR ECC ERROR"
|
||||
errmap[stringify(0x11, 0x00)] = "UNRECOVERED READ ERROR"
|
||||
errmap[stringify(0x11, 0x01)] = "READ RETRIES EXHAUSTED"
|
||||
errmap[stringify(0x11, 0x02)] = "ERROR TOO LONG TO CORRECT"
|
||||
errmap[stringify(0x11, 0x03)] = "MULTIPLE READ ERRORS"
|
||||
errmap[stringify(0x11, 0x04)] = "UNRECOVERED READ ERROR - AUTO REALLOCATE FAILED"
|
||||
errmap[stringify(0x11, 0x05)] = "L-EC UNCORRECTABLE ERROR"
|
||||
errmap[stringify(0x11, 0x06)] = "CIRC UNRECOVERED ERROR"
|
||||
errmap[stringify(0x11, 0x07)] = "DATA RESYCHRONIZATION ERROR"
|
||||
errmap[stringify(0x11, 0x08)] = "INCOMPLETE BLOCK READ"
|
||||
errmap[stringify(0x11, 0x09)] = "NO GAP FOUND"
|
||||
errmap[stringify(0x11, 0x0A)] = "MISCORRECTED ERROR"
|
||||
errmap[stringify(0x11, 0x0B)] = "UNRECOVERED READ ERROR - RECOMMEND REASSIGNMENT"
|
||||
errmap[stringify(0x11, 0x0C)] = "UNRECOVERED READ ERROR - RECOMMEND REWRITE THE DATA"
|
||||
errmap[stringify(0x12, 0x00)] = "ADDRESS MARK NOT FOUND FOR ID FIELD"
|
||||
errmap[stringify(0x13, 0x00)] = "ADDRESS MARK NOT FOUND FOR DATA FIELD"
|
||||
errmap[stringify(0x14, 0x00)] = "RECORDED ENTITY NOT FOUND"
|
||||
errmap[stringify(0x14, 0x01)] = "RECORD NOT FOUND"
|
||||
errmap[stringify(0x14, 0x02)] = "FILEMARK OR SETMARK NOT FOUND"
|
||||
errmap[stringify(0x14, 0x03)] = "END-OF-DATA NOT FOUND"
|
||||
errmap[stringify(0x14, 0x04)] = "BLOCK SEQUENCE ERROR"
|
||||
errmap[stringify(0x15, 0x00)] = "RANDOM POSITIONING ERROR"
|
||||
errmap[stringify(0x15, 0x01)] = "MECHANICAL POSITIONING ERROR"
|
||||
errmap[stringify(0x15, 0x02)] = "POSITIONING ERROR DETECTED BY READ OF MEDIUM"
|
||||
errmap[stringify(0x16, 0x00)] = "DATA SYNCHRONIZATION MARK ERROR"
|
||||
errmap[stringify(0x17, 0x00)] = "RECOVERED DATA WITH NO ERROR CORRECTION APPLIED"
|
||||
errmap[stringify(0x17, 0x01)] = "RECOVERED DATA WITH RETRIES"
|
||||
errmap[stringify(0x17, 0x02)] = "RECOVERED DATA WITH POSITIVE HEAD OFFSET"
|
||||
errmap[stringify(0x17, 0x03)] = "RECOVERED DATA WITH NEGATIVE HEAD OFFSET"
|
||||
errmap[stringify(0x17, 0x04)] = "RECOVERED DATA WITH RETRIES AND/OR CIRC APPLIED"
|
||||
errmap[stringify(0x17, 0x05)] = "RECOVERED DATA USING PREVIOUS SECTOR ID"
|
||||
errmap[stringify(0x17, 0x06)] = "RECOVERED DATA WITHOUT ECC - DATA AUTO-REALLOCATED"
|
||||
errmap[stringify(0x17, 0x07)] = "RECOVERED DATA WITHOUT ECC - RECOMMEND REASSIGNMENT"
|
||||
errmap[stringify(0x17, 0x08)] = "RECOVERED DATA WITHOUT ECC - RECOMMEND REWRITE"
|
||||
errmap[stringify(0x18, 0x00)] = "RECOVERED DATA WITH ERROR CORRECTION APPLIED"
|
||||
errmap[stringify(0x18, 0x01)] = "RECOVERED DATA WITH ERROR CORRECTION & RETRIES APPLIED"
|
||||
errmap[stringify(0x18, 0x02)] = "RECOVERED DATA - DATA AUTO-REALLOCATED"
|
||||
errmap[stringify(0x18, 0x03)] = "RECOVERED DATA WITH CIRC"
|
||||
errmap[stringify(0x18, 0x04)] = "RECOVERED DATA WITH LEC"
|
||||
errmap[stringify(0x18, 0x05)] = "RECOVERED DATA - RECOMMEND REASSIGNMENT"
|
||||
errmap[stringify(0x18, 0x06)] = "RECOVERED DATA - RECOMMEND REWRITE"
|
||||
errmap[stringify(0x19, 0x00)] = "DEFECT LIST ERROR"
|
||||
errmap[stringify(0x19, 0x01)] = "DEFECT LIST NOT AVAILABLE"
|
||||
errmap[stringify(0x19, 0x02)] = "DEFECT LIST ERROR IN PRIMARY LIST"
|
||||
errmap[stringify(0x19, 0x03)] = "DEFECT LIST ERROR IN GROWN LIST"
|
||||
errmap[stringify(0x1A, 0x00)] = "PARAMETER LIST LENGTH ERROR"
|
||||
errmap[stringify(0x1B, 0x00)] = "SYNCHRONOUS DATA TRANSFER ERROR"
|
||||
errmap[stringify(0x1C, 0x00)] = "DEFECT LIST NOT FOUND"
|
||||
errmap[stringify(0x1C, 0x01)] = "PRIMARY DEFECT LIST NOT FOUND"
|
||||
errmap[stringify(0x1C, 0x02)] = "GROWN DEFECT LIST NOT FOUND"
|
||||
errmap[stringify(0x1D, 0x00)] = "MISCOMPARE DURING VERIFY OPERATION"
|
||||
errmap[stringify(0x1E, 0x00)] = "RECOVERED ID WITH ECC"
|
||||
errmap[stringify(0x1F, 0x00)] = ""
|
||||
errmap[stringify(0x20, 0x00)] = "INVALID COMMAND OPERATION CODE"
|
||||
errmap[stringify(0x21, 0x00)] = "LOGICAL BLOCK ADDRESS OUT OF RANGE"
|
||||
errmap[stringify(0x21, 0x01)] = "INVALID ELEMENT ADDRESS"
|
||||
errmap[stringify(0x22, 0x00)] = "ILLEGAL FUNCTION (SHOULD USE 20 00, 24 00, OR 26 00)"
|
||||
errmap[stringify(0x23, 0x00)] = ""
|
||||
errmap[stringify(0x24, 0x00)] = "INVALID FIELD IN CDB"
|
||||
errmap[stringify(0x25, 0x00)] = "LOGICAL UNIT NOT SUPPORTED"
|
||||
errmap[stringify(0x26, 0x00)] = "INVALID FIELD IN PARAMETER LIST"
|
||||
errmap[stringify(0x26, 0x01)] = "PARAMETER NOT SUPPORTED"
|
||||
errmap[stringify(0x26, 0x02)] = "PARAMETER VALUE INVALID"
|
||||
errmap[stringify(0x26, 0x03)] = "THRESHOLD PARAMETERS NOT SUPPORTED"
|
||||
errmap[stringify(0x27, 0x00)] = "WRITE PROTECTED"
|
||||
errmap[stringify(0x28, 0x00)] = "NOT READY TO READY TRANSITION(MEDIUM MAY HAVE CHANGED)"
|
||||
errmap[stringify(0x28, 0x01)] = "IMPORT OR EXPORT ELEMENT ACCESSED"
|
||||
errmap[stringify(0x29, 0x00)] = "POWER ON, RESET, OR BUS DEVICE RESET OCCURRED"
|
||||
errmap[stringify(0x2A, 0x00)] = "PARAMETERS CHANGED"
|
||||
errmap[stringify(0x2A, 0x01)] = "MODE PARAMETERS CHANGED"
|
||||
errmap[stringify(0x2A, 0x02)] = "LOG PARAMETERS CHANGED"
|
||||
errmap[stringify(0x2B, 0x00)] = "COPY CANNOT EXECUTE SINCE HOST CANNOT DISCONNECT"
|
||||
errmap[stringify(0x2C, 0x00)] = "COMMAND SEQUENCE ERROR"
|
||||
errmap[stringify(0x2C, 0x01)] = "TOO MANY WINDOWS SPECIFIED"
|
||||
errmap[stringify(0x2C, 0x02)] = "INVALID COMBINATION OF WINDOWS SPECIFIED"
|
||||
errmap[stringify(0x2D, 0x00)] = "OVERWRITE ERROR ON UPDATE IN PLACE"
|
||||
errmap[stringify(0x2E, 0x00)] = ""
|
||||
errmap[stringify(0x2F, 0x00)] = "COMMANDS CLEARED BY ANOTHER INITIATOR"
|
||||
errmap[stringify(0x30, 0x00)] = "INCOMPATIBLE MEDIUM INSTALLED"
|
||||
errmap[stringify(0x30, 0x01)] = "CANNOT READ MEDIUM - UNKNOWN FORMAT"
|
||||
errmap[stringify(0x30, 0x02)] = "CANNOT READ MEDIUM - INCOMPATIBLE FORMAT"
|
||||
errmap[stringify(0x30, 0x03)] = "CLEANING CARTRIDGE INSTALLED"
|
||||
errmap[stringify(0x31, 0x00)] = "MEDIUM FORMAT CORRUPTED"
|
||||
errmap[stringify(0x31, 0x01)] = "FORMAT COMMAND FAILED"
|
||||
errmap[stringify(0x32, 0x00)] = "NO DEFECT SPARE LOCATION AVAILABLE"
|
||||
errmap[stringify(0x32, 0x01)] = "DEFECT LIST UPDATE FAILURE"
|
||||
errmap[stringify(0x33, 0x00)] = "TAPE LENGTH ERROR"
|
||||
errmap[stringify(0x34, 0x00)] = ""
|
||||
errmap[stringify(0x35, 0x00)] = ""
|
||||
errmap[stringify(0x36, 0x00)] = "RIBBON, INK, OR TONER FAILURE"
|
||||
errmap[stringify(0x37, 0x00)] = "ROUNDED PARAMETER"
|
||||
errmap[stringify(0x38, 0x00)] = ""
|
||||
errmap[stringify(0x39, 0x00)] = "SAVING PARAMETERS NOT SUPPORTED"
|
||||
errmap[stringify(0x3A, 0x00)] = "MEDIUM NOT PRESENT"
|
||||
errmap[stringify(0x3B, 0x00)] = "SEQUENTIAL POSITIONING ERROR"
|
||||
errmap[stringify(0x3B, 0x01)] = "TAPE POSITION ERROR AT BEGINNING-OF-MEDIUM"
|
||||
errmap[stringify(0x3B, 0x02)] = "TAPE POSITION ERROR AT END-OF-MEDIUM"
|
||||
errmap[stringify(0x3B, 0x03)] = "TAPE OR ELECTRONIC VERTICAL FORMS UNIT NOT READY"
|
||||
errmap[stringify(0x3B, 0x04)] = "SLEW FAILURE"
|
||||
errmap[stringify(0x3B, 0x05)] = "PAPER JAM"
|
||||
errmap[stringify(0x3B, 0x06)] = "FAILED TO SENSE TOP-OF-FORM"
|
||||
errmap[stringify(0x3B, 0x07)] = "FAILED TO SENSE BOTTOM-OF-FORM"
|
||||
errmap[stringify(0x3B, 0x08)] = "REPOSITION ERROR"
|
||||
errmap[stringify(0x3B, 0x09)] = "READ PAST END OF MEDIUM"
|
||||
errmap[stringify(0x3B, 0x0A)] = "READ PAST BEGINNING OF MEDIUM"
|
||||
errmap[stringify(0x3B, 0x0B)] = "POSITION PAST END OF MEDIUM"
|
||||
errmap[stringify(0x3B, 0x0C)] = "POSITION PAST BEGINNING OF MEDIUM"
|
||||
errmap[stringify(0x3B, 0x0D)] = "MEDIUM DESTINATION ELEMENT FULL"
|
||||
errmap[stringify(0x3B, 0x0E)] = "MEDIUM SOURCE ELEMENT EMPTY"
|
||||
errmap[stringify(0x3C, 0x00)] = ""
|
||||
errmap[stringify(0x3D, 0x00)] = "INVALID BITS IN IDENTIFY MESSAGE"
|
||||
errmap[stringify(0x3E, 0x00)] = "LOGICAL UNIT HAS NOT SELF-CONFIGURED YET"
|
||||
errmap[stringify(0x3F, 0x00)] = "TARGET OPERATING CONDITIONS HAVE CHANGED"
|
||||
errmap[stringify(0x3F, 0x01)] = "MICROCODE HAS BEEN CHANGED"
|
||||
errmap[stringify(0x3F, 0x02)] = "CHANGED OPERATING DEFINITION"
|
||||
errmap[stringify(0x3F, 0x03)] = "INQUIRY DATA HAS CHANGED"
|
||||
errmap[stringify(0x40, 0x00)] = "RAM FAILURE (SHOULD USE 40 NN)"
|
||||
//errmap[stringify(0x40, 0xNN)] = "DIAGNOSTIC FAILURE ON COMPONENT NN (80H-FFH)"
|
||||
errmap[stringify(0x41, 0x00)] = "DATA PATH FAILURE (SHOULD USE 40 NN)"
|
||||
errmap[stringify(0x42, 0x00)] = "POWER-ON OR SELF-TEST FAILURE (SHOULD USE 40 NN)"
|
||||
errmap[stringify(0x43, 0x00)] = "MESSAGE ERROR"
|
||||
errmap[stringify(0x44, 0x00)] = "INTERNAL TARGET FAILURE"
|
||||
errmap[stringify(0x45, 0x00)] = "SELECT OR RESELECT FAILURE"
|
||||
errmap[stringify(0x46, 0x00)] = "UNSUCCESSFUL SOFT RESET"
|
||||
errmap[stringify(0x47, 0x00)] = "SCSI PARITY ERROR"
|
||||
errmap[stringify(0x48, 0x00)] = "INITIATOR DETECTED ERROR MESSAGE RECEIVED"
|
||||
errmap[stringify(0x49, 0x00)] = "INVALID MESSAGE ERROR"
|
||||
errmap[stringify(0x4A, 0x00)] = "COMMAND PHASE ERROR"
|
||||
errmap[stringify(0x4B, 0x00)] = "DATA PHASE ERROR"
|
||||
errmap[stringify(0x4C, 0x00)] = "LOGICAL UNIT FAILED SELF-CONFIGURATION"
|
||||
errmap[stringify(0x4D, 0x00)] = ""
|
||||
errmap[stringify(0x4E, 0x00)] = "OVERLAPPED COMMANDS ATTEMPTED"
|
||||
errmap[stringify(0x4F, 0x00)] = ""
|
||||
errmap[stringify(0x50, 0x00)] = "WRITE APPEND ERROR"
|
||||
errmap[stringify(0x50, 0x01)] = "WRITE APPEND POSITION ERROR"
|
||||
errmap[stringify(0x50, 0x02)] = "POSITION ERROR RELATED TO TIMING"
|
||||
errmap[stringify(0x51, 0x00)] = "ERASE FAILURE"
|
||||
errmap[stringify(0x52, 0x00)] = "CARTRIDGE FAULT"
|
||||
errmap[stringify(0x53, 0x00)] = "MEDIA LOAD OR EJECT FAILED"
|
||||
errmap[stringify(0x53, 0x01)] = "UNLOAD TAPE FAILURE"
|
||||
errmap[stringify(0x53, 0x02)] = "MEDIUM REMOVAL PREVENTED"
|
||||
errmap[stringify(0x54, 0x00)] = "SCSI TO HOST SYSTEM INTERFACE FAILURE"
|
||||
errmap[stringify(0x55, 0x00)] = "SYSTEM RESOURCE FAILURE"
|
||||
errmap[stringify(0x56, 0x00)] = ""
|
||||
errmap[stringify(0x57, 0x00)] = "UNABLE TO RECOVER TABLE-OF-CONTENTS"
|
||||
errmap[stringify(0x58, 0x00)] = "GENERATION DOES NOT EXIST"
|
||||
errmap[stringify(0x59, 0x00)] = "UPDATED BLOCK READ"
|
||||
errmap[stringify(0x5A, 0x00)] = "OPERATOR REQUEST OR STATE CHANGE INPUT (UNSPECIFIED)"
|
||||
errmap[stringify(0x5A, 0x01)] = "OPERATOR MEDIUM REMOVAL REQUEST"
|
||||
errmap[stringify(0x5A, 0x02)] = "OPERATOR SELECTED WRITE PROTECT"
|
||||
errmap[stringify(0x5A, 0x03)] = "OPERATOR SELECTED WRITE PERMIT"
|
||||
errmap[stringify(0x5B, 0x00)] = "LOG EXCEPTION"
|
||||
errmap[stringify(0x5B, 0x01)] = "THRESHOLD CONDITION MET"
|
||||
errmap[stringify(0x5B, 0x02)] = "LOG COUNTER AT MAXIMUM"
|
||||
errmap[stringify(0x5B, 0x03)] = "LOG LIST CODES EXHAUSTED"
|
||||
errmap[stringify(0x5C, 0x00)] = "RPL STATUS CHANGE"
|
||||
errmap[stringify(0x5C, 0x01)] = "SPINDLES SYNCHRONIZED"
|
||||
errmap[stringify(0x5C, 0x02)] = "SPINDLES NOT SYNCHRONIZED"
|
||||
errmap[stringify(0x5D, 0x00)] = ""
|
||||
errmap[stringify(0x5E, 0x00)] = ""
|
||||
errmap[stringify(0x5F, 0x00)] = ""
|
||||
errmap[stringify(0x60, 0x00)] = "LAMP FAILURE"
|
||||
errmap[stringify(0x61, 0x00)] = "VIDEO ACQUISITION ERROR"
|
||||
errmap[stringify(0x61, 0x01)] = "UNABLE TO ACQUIRE VIDEO"
|
||||
errmap[stringify(0x61, 0x02)] = "OUT OF FOCUS"
|
||||
errmap[stringify(0x62, 0x00)] = "SCAN HEAD POSITIONING ERROR"
|
||||
errmap[stringify(0x63, 0x00)] = "END OF USER AREA ENCOUNTERED ON THIS TRACK"
|
||||
errmap[stringify(0x64, 0x00)] = "ILLEGAL MODE FOR THIS TRACK"
|
||||
}
|
24
vendor/github.com/benmcclelland/sgio/parse.go
generated
vendored
Normal file
24
vendor/github.com/benmcclelland/sgio/parse.go
generated
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
package sgio
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
)
|
||||
|
||||
func stringify(a, b byte) string {
|
||||
return dumpHex(append([]byte{a}, b))
|
||||
}
|
||||
|
||||
func dumpHex(data []byte) string {
|
||||
var buf bytes.Buffer
|
||||
var tmp [3]byte
|
||||
for i := range data {
|
||||
hex.Encode(tmp[:], data[i:i+1])
|
||||
tmp[2] = ' '
|
||||
_, err := buf.Write(tmp[:3])
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
}
|
160
vendor/github.com/benmcclelland/sgio/sg.go
generated
vendored
Normal file
160
vendor/github.com/benmcclelland/sgio/sg.go
generated
vendored
Normal file
@ -0,0 +1,160 @@
|
||||
package sgio
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
SG_GET_VERSION_NUM = 0x2282
|
||||
SG_IO = 0x2285
|
||||
SG_INFO_OK_MASK = 0x1
|
||||
SG_INFO_OK = 0x0
|
||||
SG_DXFER_TO_DEV = -2
|
||||
SG_DXFER_FROM_DEV = -3
|
||||
SG_DXFER_TO_FROM_DEV = -4
|
||||
INQ_CMD_CODE = 0x12
|
||||
INQ_REPLY_LEN = 96
|
||||
SENSE_BUF_LEN = 32
|
||||
TIMEOUT_20_SECS = 20000
|
||||
)
|
||||
|
||||
// pahole for sg_io_hdr_t on amd64
|
||||
/*
|
||||
* struct sg_io_hdr {
|
||||
* int interface_id; // 0 4
|
||||
* int dxfer_direction; // 4 4
|
||||
* unsigned char cmd_len; // 8 1
|
||||
* unsigned char mx_sb_len; // 9 1
|
||||
* short unsigned int iovec_count; // 10 2
|
||||
* unsigned int dxfer_len; // 12 4
|
||||
* void * dxferp; // 16 8
|
||||
* unsigned char * cmdp; // 24 8
|
||||
* unsigned char * sbp; // 32 8
|
||||
* unsigned int timeout; // 40 4
|
||||
* unsigned int flags; // 44 4
|
||||
* int pack_id; // 48 4
|
||||
*
|
||||
* // XXX 4 bytes hole, try to pack
|
||||
*
|
||||
* void * usr_ptr; // 56 8
|
||||
* // --- cacheline 1 boundary (64 bytes) ---
|
||||
* unsigned char status; // 64 1
|
||||
* unsigned char masked_status; // 65 1
|
||||
* unsigned char msg_status; // 66 1
|
||||
* unsigned char sb_len_wr; // 67 1
|
||||
* short unsigned int host_status; // 68 2
|
||||
* short unsigned int driver_status; // 70 2
|
||||
* int resid; // 72 4
|
||||
* unsigned int duration; // 76 4
|
||||
* unsigned int info; // 80 4
|
||||
*
|
||||
* // size: 88, cachelines: 2, members: 22
|
||||
* // sum members: 80, holes: 1, sum holes: 4
|
||||
* // padding: 4
|
||||
* // last cacheline: 24 bytes
|
||||
* };
|
||||
*/
|
||||
|
||||
// SgIoHdr is our version of sg_io_hdr_t that gets passed to the SG_IO ioctl
|
||||
type SgIoHdr struct {
|
||||
InterfaceID int32
|
||||
DxferDirection int32
|
||||
CmdLen uint8
|
||||
MxSbLen uint8
|
||||
IovecCount uint16
|
||||
DxferLen uint32
|
||||
Dxferp *byte
|
||||
Cmdp *uint8
|
||||
Sbp *byte
|
||||
Timeout uint32
|
||||
Flags uint32
|
||||
PackID int32
|
||||
pad0 [4]byte
|
||||
UsrPtr *byte
|
||||
Status uint8
|
||||
MaskedStatus uint8
|
||||
MsgStatus uint8
|
||||
SbLenWr uint8
|
||||
HostStatus uint16
|
||||
DriverStatus uint16
|
||||
Resid int32
|
||||
Duration uint32
|
||||
Info uint32
|
||||
}
|
||||
|
||||
func TestUnitReady(f *os.File) error {
|
||||
senseBuf := make([]byte, SENSE_BUF_LEN)
|
||||
inqCmdBlk := []uint8{0, 0, 0, 0, 0, 0}
|
||||
ioHdr := &SgIoHdr{
|
||||
InterfaceID: int32('S'),
|
||||
CmdLen: uint8(len(inqCmdBlk)),
|
||||
MxSbLen: SENSE_BUF_LEN,
|
||||
DxferDirection: SG_DXFER_FROM_DEV,
|
||||
Cmdp: &inqCmdBlk[0],
|
||||
Sbp: &senseBuf[0],
|
||||
Timeout: TIMEOUT_20_SECS,
|
||||
}
|
||||
|
||||
err := SgioSyscall(f, ioHdr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = CheckSense(ioHdr, &senseBuf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func CheckSense(i *SgIoHdr, s *[]byte) error {
|
||||
var b bytes.Buffer
|
||||
if (i.Info & SG_INFO_OK_MASK) != SG_INFO_OK {
|
||||
_, err := b.WriteString(
|
||||
fmt.Sprintf("SCSI response not ok\n"+
|
||||
"SCSI status: %v host status: %v driver status: %v",
|
||||
i.Status, i.HostStatus, i.DriverStatus))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if i.SbLenWr > 0 {
|
||||
_, err := b.WriteString(
|
||||
fmt.Sprintf("\nSENSE:\n%v\n%v",
|
||||
dumpHex(*s), GetErrString((*s)[12], (*s)[13])))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return fmt.Errorf(b.String())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func SgioSyscall(f *os.File, i *SgIoHdr) error {
|
||||
return ioctl(f.Fd(), SG_IO, uintptr(unsafe.Pointer(i)))
|
||||
}
|
||||
|
||||
func ioctl(fd, cmd, ptr uintptr) error {
|
||||
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, cmd, ptr)
|
||||
if err != 0 {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func OpenScsiDevice(fname string) (*os.File, error) {
|
||||
f, err := os.OpenFile(fname, os.O_RDWR, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var version uint32
|
||||
if (ioctl(f.Fd(), SG_GET_VERSION_NUM, uintptr(unsafe.Pointer(&version))) != nil) || (version < 30000) {
|
||||
return nil, fmt.Errorf("device does not appear to be an sg device")
|
||||
}
|
||||
return f, nil
|
||||
}
|
3
vendor/modules.txt
vendored
Normal file
3
vendor/modules.txt
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
# github.com/benmcclelland/sgio v0.0.0-20180629175614-f710aebf64c1
|
||||
## explicit
|
||||
github.com/benmcclelland/sgio
|
Loading…
x
Reference in New Issue
Block a user