Import Debian changes 1.05+ds-2
hd-idle (1.05+ds-2) unstable; urgency=medium . * No-change, Source-only upload . hd-idle (1.05+ds-1) unstable; urgency=medium . * Initial release. Closes: #924749
This commit is contained in:
commit
05b82773a0
13
.gitignore
vendored
13
.gitignore
vendored
@ -1,13 +0,0 @@
|
|||||||
.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
|
|
873
LICENSE
873
LICENSE
@ -1,622 +1,281 @@
|
|||||||
GNU GENERAL PUBLIC LICENSE
|
GNU GENERAL PUBLIC LICENSE
|
||||||
Version 3, 29 June 2007
|
Version 2, June 1991
|
||||||
|
|
||||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
|
||||||
|
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
Everyone is permitted to copy and distribute verbatim copies
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
of this license document, but changing it is not allowed.
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
Preamble
|
Preamble
|
||||||
|
|
||||||
The GNU General Public License is a free, copyleft license for
|
The licenses for most software are designed to take away your
|
||||||
software and other kinds of works.
|
freedom to share and change it. By contrast, the GNU General Public
|
||||||
|
License is intended to guarantee your freedom to share and change free
|
||||||
The licenses for most software and other practical works are designed
|
software--to make sure the software is free for all its users. This
|
||||||
to take away your freedom to share and change the works. By contrast,
|
General Public License applies to most of the Free Software
|
||||||
the GNU General Public License is intended to guarantee your freedom to
|
Foundation's software and to any other program whose authors commit to
|
||||||
share and change all versions of a program--to make sure it remains free
|
using it. (Some other Free Software Foundation software is covered by
|
||||||
software for all its users. We, the Free Software Foundation, use the
|
the GNU Library General Public License instead.) You can apply it to
|
||||||
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.
|
your programs, too.
|
||||||
|
|
||||||
When we speak of free software, we are referring to freedom, not
|
When we speak of free software, we are referring to freedom, not
|
||||||
price. Our General Public Licenses are designed to make sure that you
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
have the freedom to distribute copies of free software (and charge for
|
have the freedom to distribute copies of free software (and charge for
|
||||||
them if you wish), that you receive source code or can get it if you
|
this service if you wish), that you receive source code or can get it
|
||||||
want it, that you can change the software or use pieces of it in new
|
if you want it, that you can change the software or use pieces of it
|
||||||
free programs, and that you know you can do these things.
|
in new free programs; and that you know you can do these things.
|
||||||
|
|
||||||
To protect your rights, we need to prevent others from denying you
|
To protect your rights, we need to make restrictions that forbid
|
||||||
these rights or asking you to surrender the rights. Therefore, you have
|
anyone to deny you these rights or to ask you to surrender the rights.
|
||||||
certain responsibilities if you distribute copies of the software, or if
|
These restrictions translate to certain responsibilities for you if you
|
||||||
you modify it: responsibilities to respect the freedom of others.
|
distribute copies of the software, or if you modify it.
|
||||||
|
|
||||||
For example, if you distribute copies of such a program, whether
|
For example, if you distribute copies of such a program, whether
|
||||||
gratis or for a fee, you must pass on to the recipients the same
|
gratis or for a fee, you must give the recipients all the rights that
|
||||||
freedoms that you received. You must make sure that they, too, receive
|
you have. You must make sure that they, too, receive or can get the
|
||||||
or can get the source code. And you must show them these terms so they
|
source code. And you must show them these terms so they know their
|
||||||
know their rights.
|
rights.
|
||||||
|
|
||||||
Developers that use the GNU GPL protect your rights with two steps:
|
We protect your rights with two steps: (1) copyright the software, and
|
||||||
(1) assert copyright on the software, and (2) offer you this License
|
(2) offer you this license which gives you legal permission to copy,
|
||||||
giving you legal permission to copy, distribute and/or modify it.
|
distribute and/or modify the software.
|
||||||
|
|
||||||
For the developers' and authors' protection, the GPL clearly explains
|
Also, for each author's protection and ours, we want to make certain
|
||||||
that there is no warranty for this free software. For both users' and
|
that everyone understands that there is no warranty for this free
|
||||||
authors' sake, the GPL requires that modified versions be marked as
|
software. If the software is modified by someone else and passed on, we
|
||||||
changed, so that their problems will not be attributed erroneously to
|
want its recipients to know that what they have is not the original, so
|
||||||
authors of previous versions.
|
that any problems introduced by others will not reflect on the original
|
||||||
|
authors' reputations.
|
||||||
|
|
||||||
Some devices are designed to deny users access to install or run
|
Finally, any free program is threatened constantly by software
|
||||||
modified versions of the software inside them, although the manufacturer
|
patents. We wish to avoid the danger that redistributors of a free
|
||||||
can do so. This is fundamentally incompatible with the aim of
|
program will individually obtain patent licenses, in effect making the
|
||||||
protecting users' freedom to change the software. The systematic
|
program proprietary. To prevent this, we have made it clear that any
|
||||||
pattern of such abuse occurs in the area of products for individuals to
|
patent must be licensed for everyone's free use or not licensed at all.
|
||||||
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
|
The precise terms and conditions for copying, distribution and
|
||||||
modification follow.
|
modification follow.
|
||||||
|
|
||||||
TERMS AND CONDITIONS
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||||
0. Definitions.
|
|
||||||
|
0. This License applies to any program or other work which contains
|
||||||
"This License" refers to version 3 of the GNU General Public License.
|
a notice placed by the copyright holder saying it may be distributed
|
||||||
|
under the terms of this General Public License. The "Program", below,
|
||||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
refers to any such program or work, and a "work based on the Program"
|
||||||
works, such as semiconductor masks.
|
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,
|
||||||
"The Program" refers to any copyrightable work licensed under this
|
either verbatim or with modifications and/or translated into another
|
||||||
License. Each licensee is addressed as "you". "Licensees" and
|
language. (Hereinafter, translation is included without limitation in
|
||||||
"recipients" may be individuals or organizations.
|
the term "modification".) Each licensee is addressed as "you".
|
||||||
|
|
||||||
To "modify" a work means to copy from or adapt all or part of the work
|
Activities other than copying, distribution and modification are not
|
||||||
in a fashion requiring copyright permission, other than the making of an
|
covered by this License; they are outside its scope. The act of
|
||||||
exact copy. The resulting work is called a "modified version" of the
|
running the Program is not restricted, and the output from the Program
|
||||||
earlier work or a work "based on" the earlier work.
|
is covered only if its contents constitute a work based on the
|
||||||
|
Program (independent of having been made by running the Program).
|
||||||
A "covered work" means either the unmodified Program or a work based
|
Whether that is true depends on what the Program does.
|
||||||
on the Program.
|
|
||||||
|
1. You may copy and distribute verbatim copies of the Program's
|
||||||
To "propagate" a work means to do anything with it that, without
|
source code as you receive it, in any medium, provided that you
|
||||||
permission, would make you directly or secondarily liable for
|
conspicuously and appropriately publish on each copy an appropriate
|
||||||
infringement under applicable copyright law, except executing it on a
|
copyright notice and disclaimer of warranty; keep intact all the
|
||||||
computer or modifying a private copy. Propagation includes copying,
|
notices that refer to this License and to the absence of any warranty;
|
||||||
distribution (with or without modification), making available to the
|
and give any other recipients of the Program a copy of this License
|
||||||
public, and in some countries other activities as well.
|
along with the Program.
|
||||||
|
|
||||||
To "convey" a work means any kind of propagation that enables other
|
You may charge a fee for the physical act of transferring a copy, and
|
||||||
parties to make or receive copies. Mere interaction with a user through
|
you may at your option offer warranty protection in exchange for a fee.
|
||||||
a computer network, with no transfer of a copy, is not conveying.
|
|
||||||
|
2. You may modify your copy or copies of the Program or any portion
|
||||||
An interactive user interface displays "Appropriate Legal Notices"
|
of it, thus forming a work based on the Program, and copy and
|
||||||
to the extent that it includes a convenient and prominently visible
|
distribute such modifications or work under the terms of Section 1
|
||||||
feature that (1) displays an appropriate copyright notice, and (2)
|
above, provided that you also meet all of these conditions:
|
||||||
tells the user that there is no warranty for the work (except to the
|
|
||||||
extent that warranties are provided), that licensees may convey the
|
a) You must cause the modified files to carry prominent notices
|
||||||
work under this License, and how to view a copy of this License. If
|
stating that you changed the files and the date of any change.
|
||||||
the interface presents a list of user commands or options, such as a
|
|
||||||
menu, a prominent item in the list meets this criterion.
|
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
|
||||||
1. Source Code.
|
part thereof, to be licensed as a whole at no charge to all third
|
||||||
|
parties under the terms 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
|
c) If the modified program normally reads commands interactively
|
||||||
form of a work.
|
when run, you must cause it, when started running for such
|
||||||
|
interactive use in the most ordinary way, to print or display an
|
||||||
A "Standard Interface" means an interface that either is an official
|
announcement including an appropriate copyright notice and a
|
||||||
standard defined by a recognized standards body, or, in the case of
|
notice that there is no warranty (or else, saying that you provide
|
||||||
interfaces specified for a particular programming language, one that
|
a warranty) and that users may redistribute the program under
|
||||||
is widely used among developers working in that language.
|
these conditions, and telling the user how to view a copy of this
|
||||||
|
License. (Exception: if the Program itself is interactive but
|
||||||
The "System Libraries" of an executable work include anything, other
|
does not normally print such an announcement, your work based on
|
||||||
than the work as a whole, that (a) is included in the normal form of
|
the Program is not required to print an announcement.)
|
||||||
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
|
These requirements apply to the modified work as a whole. If
|
||||||
Major Component, or to implement a Standard Interface for which an
|
identifiable sections of that work are not derived from the Program,
|
||||||
implementation is available to the public in source code form. A
|
and can be reasonably considered independent and separate works in
|
||||||
"Major Component", in this context, means a major essential component
|
themselves, then this License, and its terms, do not apply to those
|
||||||
(kernel, window system, and so on) of the specific operating system
|
sections when you distribute them as separate works. But when you
|
||||||
(if any) on which the executable work runs, or a compiler used to
|
distribute the same sections as part of a whole which is a work based
|
||||||
produce the work, or an object code interpreter used to run it.
|
on the Program, the distribution of the whole must be on the terms of
|
||||||
|
this License, whose permissions for other licensees extend to the
|
||||||
The "Corresponding Source" for a work in object code form means all
|
entire whole, and thus to each and every part regardless of who wrote it.
|
||||||
the source code needed to generate, install, and (for an executable
|
|
||||||
work) run the object code and to modify the work, including scripts to
|
Thus, it is not the intent of this section to claim rights or contest
|
||||||
control those activities. However, it does not include the work's
|
your rights to work written entirely by you; rather, the intent is to
|
||||||
System Libraries, or general-purpose tools or generally available free
|
exercise the right to control the distribution of derivative or
|
||||||
programs which are used unmodified in performing those activities but
|
collective works based on the Program.
|
||||||
which are not part of the work. For example, Corresponding Source
|
|
||||||
includes interface definition files associated with source files for
|
In addition, mere aggregation of another work not based on the Program
|
||||||
the work, and the source code for shared libraries and dynamically
|
with the Program (or with a work based on the Program) on a volume of
|
||||||
linked subprograms that the work is specifically designed to require,
|
a storage or distribution medium does not bring the other work under
|
||||||
such as by intimate data communication or control flow between those
|
the scope of this License.
|
||||||
subprograms and other parts of the work.
|
|
||||||
|
3. You may copy and distribute the Program (or a work based on it,
|
||||||
The Corresponding Source need not include anything that users
|
under Section 2) in object code or executable form under the terms of
|
||||||
can regenerate automatically from other parts of the Corresponding
|
Sections 1 and 2 above provided that you also do one of the following:
|
||||||
Source.
|
|
||||||
|
a) Accompany it with the complete corresponding machine-readable
|
||||||
The Corresponding Source for a work in source code form is that
|
source code, which must be distributed under the terms of Sections
|
||||||
same work.
|
1 and 2 above on a medium customarily used for software interchange; or,
|
||||||
|
|
||||||
2. Basic Permissions.
|
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
|
||||||
All rights granted under this License are granted for the term of
|
cost of physically performing source distribution, a complete
|
||||||
copyright on the Program, and are irrevocable provided the stated
|
machine-readable copy of the corresponding source code, to be
|
||||||
conditions are met. This License explicitly affirms your unlimited
|
distributed under the terms of Sections 1 and 2 above on a medium
|
||||||
permission to run the unmodified Program. The output from running a
|
customarily used for software interchange; or,
|
||||||
covered work is covered by this License only if the output, given its
|
|
||||||
content, constitutes a covered work. This License acknowledges your
|
c) Accompany it with the information you received as to the offer
|
||||||
rights of fair use or other equivalent, as provided by copyright law.
|
to distribute corresponding source code. (This alternative is
|
||||||
|
allowed only for noncommercial distribution and only if you
|
||||||
You may make, run and propagate covered works that you do not
|
received the program in object code or executable form with such
|
||||||
convey, without conditions so long as your license otherwise remains
|
an offer, in accord with Subsection b above.)
|
||||||
in force. You may convey covered works to others for the sole purpose
|
|
||||||
of having them make modifications exclusively for you, or provide you
|
The source code for a work means the preferred form of the work for
|
||||||
with facilities for running those works, provided that you comply with
|
making modifications to it. For an executable work, complete source
|
||||||
the terms of this License in conveying all material for which you do
|
code means all the source code for all modules it contains, plus any
|
||||||
not control copyright. Those thus making or running the covered works
|
associated interface definition files, plus the scripts used to
|
||||||
for you must do so exclusively on your behalf, under your direction
|
control compilation and installation of the executable. However, as a
|
||||||
and control, on terms that prohibit them from making any copies of
|
special exception, the source code distributed need not include
|
||||||
your copyrighted material outside their relationship with you.
|
anything that is normally distributed (in either source or binary
|
||||||
|
form) with the major components (compiler, kernel, and so on) of the
|
||||||
Conveying under any other circumstances is permitted solely under
|
operating system on which the executable runs, unless that component
|
||||||
the conditions stated below. Sublicensing is not allowed; section 10
|
itself accompanies the executable.
|
||||||
makes it unnecessary.
|
|
||||||
|
If distribution of executable or object code is made by offering
|
||||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
access to copy from a designated place, then offering equivalent
|
||||||
|
access to copy the source code from the same place counts as
|
||||||
No covered work shall be deemed part of an effective technological
|
distribution of the source code, even though third parties are not
|
||||||
measure under any applicable law fulfilling obligations under article
|
compelled to copy the source along with the object code.
|
||||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
|
||||||
similar laws prohibiting or restricting circumvention of such
|
4. You may not copy, modify, sublicense, or distribute the Program
|
||||||
measures.
|
except as expressly provided under this License. Any attempt
|
||||||
|
otherwise to copy, modify, sublicense or distribute the Program is
|
||||||
When you convey a covered work, you waive any legal power to forbid
|
void, and will automatically terminate your rights under this License.
|
||||||
circumvention of technological measures to the extent such circumvention
|
However, parties who have received copies, or rights, from you under
|
||||||
is effected by exercising rights under this License with respect to
|
this License will not have their licenses terminated so long as such
|
||||||
the covered work, and you disclaim any intention to limit operation or
|
parties remain in full compliance.
|
||||||
modification of the work as a means of enforcing, against the work's
|
|
||||||
users, your or third parties' legal rights to forbid circumvention of
|
5. You are not required to accept this License, since you have not
|
||||||
technological measures.
|
signed it. However, nothing else grants you permission to modify or
|
||||||
|
distribute the Program or its derivative works. These actions are
|
||||||
4. Conveying Verbatim Copies.
|
prohibited by law if you do not accept this License. Therefore, by
|
||||||
|
modifying or distributing the Program (or any work based on the
|
||||||
You may convey verbatim copies of the Program's source code as you
|
Program), you indicate your acceptance of this License to do so, and
|
||||||
receive it, in any medium, provided that you conspicuously and
|
all its terms and conditions for copying, distributing or modifying
|
||||||
appropriately publish on each copy an appropriate copyright notice;
|
the Program or works based on it.
|
||||||
keep intact all notices stating that this License and any
|
|
||||||
non-permissive terms added in accord with section 7 apply to the code;
|
6. Each time you redistribute the Program (or any work based on the
|
||||||
keep intact all notices of the absence of any warranty; and give all
|
Program), the recipient automatically receives a license from the
|
||||||
recipients a copy of this License along with the Program.
|
original licensor to copy, distribute or modify the Program subject to
|
||||||
|
these terms and conditions. You may not impose any further
|
||||||
You may charge any price or no price for each copy that you convey,
|
restrictions on the recipients' exercise of the rights granted herein.
|
||||||
and you may offer support or warranty protection for a fee.
|
You are not responsible for enforcing compliance by third parties to
|
||||||
|
|
||||||
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.
|
this License.
|
||||||
|
|
||||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
7. If, as a consequence of a court judgment or allegation of patent
|
||||||
patent license under the contributor's essential patent claims, to
|
infringement or for any other reason (not limited to patent issues),
|
||||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
conditions are imposed on you (whether by court order, agreement or
|
||||||
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
|
otherwise) that contradict the conditions of this License, they do not
|
||||||
excuse you from the conditions of this License. If you cannot convey a
|
excuse you from the conditions of this License. If you cannot
|
||||||
covered work so as to satisfy simultaneously your obligations under this
|
distribute so as to satisfy simultaneously your obligations under this
|
||||||
License and any other pertinent obligations, then as a consequence you may
|
License and any other pertinent obligations, then as a consequence you
|
||||||
not convey it at all. For example, if you agree to terms that obligate you
|
may not distribute the Program at all. For example, if a patent
|
||||||
to collect a royalty for further conveying from those to whom you convey
|
license would not permit royalty-free redistribution of the Program by
|
||||||
the Program, the only way you could satisfy both those terms and this
|
all those who receive copies directly or indirectly through you, then
|
||||||
License would be to refrain entirely from conveying the Program.
|
the only way you could satisfy both it and this License would be to
|
||||||
|
refrain entirely from distribution of the Program.
|
||||||
|
|
||||||
13. Use with the GNU Affero General Public License.
|
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.
|
||||||
|
|
||||||
Notwithstanding any other provision of this License, you have
|
It is not the purpose of this section to induce you to infringe any
|
||||||
permission to link or combine any covered work with a work licensed
|
patents or other property right claims or to contest validity of any
|
||||||
under version 3 of the GNU Affero General Public License into a single
|
such claims; this section has the sole purpose of protecting the
|
||||||
combined work, and to convey the resulting work. The terms of this
|
integrity of the free software distribution system, which is
|
||||||
License will continue to apply to the part which is the covered work,
|
implemented by public license practices. Many people have made
|
||||||
but the special requirements of the GNU Affero General Public License,
|
generous contributions to the wide range of software distributed
|
||||||
section 13, concerning interaction through a network will apply to the
|
through that system in reliance on consistent application of that
|
||||||
combination as such.
|
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.
|
||||||
|
|
||||||
14. Revised Versions of this License.
|
This section is intended to make thoroughly clear what is believed to
|
||||||
|
be a consequence of the rest of this License.
|
||||||
|
|
||||||
The Free Software Foundation may publish revised and/or new versions of
|
8. If the distribution and/or use of the Program is restricted in
|
||||||
the GNU General Public License from time to time. Such new versions will
|
certain countries either by patents or by copyrighted interfaces, the
|
||||||
|
original copyright holder who places the Program under this License
|
||||||
|
may add an explicit geographical distribution limitation excluding
|
||||||
|
those countries, so that distribution is permitted only in or among
|
||||||
|
countries not thus excluded. In such case, this License incorporates
|
||||||
|
the limitation as if written in the body of this License.
|
||||||
|
|
||||||
|
9. The Free Software Foundation may publish revised and/or new versions
|
||||||
|
of the General Public License from time to time. Such new versions will
|
||||||
be similar in spirit to the present version, but may differ in detail to
|
be similar in spirit to the present version, but may differ in detail to
|
||||||
address new problems or concerns.
|
address new problems or concerns.
|
||||||
|
|
||||||
Each version is given a distinguishing version number. If the
|
Each version is given a distinguishing version number. If the Program
|
||||||
Program specifies that a certain numbered version of the GNU General
|
specifies a version number of this License which applies to it and "any
|
||||||
Public License "or any later version" applies to it, you have the
|
later version", you have the option of following the terms and conditions
|
||||||
option of following the terms and conditions either of that numbered
|
either of that version or of any later version published by the Free
|
||||||
version or of any later version published by the Free Software
|
Software Foundation. If the Program does not specify a version number of
|
||||||
Foundation. If the Program does not specify a version number of the
|
this License, you may choose any version ever published by the Free Software
|
||||||
GNU General Public License, you may choose any version ever published
|
Foundation.
|
||||||
by the Free Software Foundation.
|
|
||||||
|
|
||||||
If the Program specifies that a proxy can decide which future
|
10. If you wish to incorporate parts of the Program into other free
|
||||||
versions of the GNU General Public License can be used, that proxy's
|
programs whose distribution conditions are different, write to the author
|
||||||
public statement of acceptance of a version permanently authorizes you
|
to ask for permission. For software which is copyrighted by the Free
|
||||||
to choose that version for the Program.
|
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.
|
||||||
|
|
||||||
Later license versions may give you additional or different
|
NO WARRANTY
|
||||||
permissions. However, no additional obligations are imposed on any
|
|
||||||
author or copyright holder as a result of your choosing to follow a
|
|
||||||
later version.
|
|
||||||
|
|
||||||
15. Disclaimer of Warranty.
|
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||||
|
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||||
|
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||||
|
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||||
|
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||||
|
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||||
|
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||||
|
REPAIR OR CORRECTION.
|
||||||
|
|
||||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||||
|
POSSIBILITY OF SUCH DAMAGES.
|
||||||
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
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
@ -628,15 +287,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 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
|
to attach them to the start of each source file to most effectively
|
||||||
state the exclusion of warranty; and each file should have at least
|
convey the exclusion of warranty; and each file should have at least
|
||||||
the "copyright" line and a pointer to where the full notice is found.
|
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.>
|
<one line to give the program's name and a brief idea of what it does.>
|
||||||
Copyright (C) <year> <name of author>
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
the Free Software Foundation; either version 2 of the License, or
|
||||||
(at your option) any later version.
|
(at your option) any later version.
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
This program is distributed in the hope that it will be useful,
|
||||||
@ -645,30 +304,38 @@ the "copyright" line and a pointer to where the full notice is found.
|
|||||||
GNU General Public License for more details.
|
GNU General Public License for more details.
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
|
||||||
|
|
||||||
Also add information on how to contact you by electronic and paper mail.
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
If the program does terminal interaction, make it output a short
|
If the program is interactive, make it output a short notice like this
|
||||||
notice like this when it starts in an interactive mode:
|
when it starts in an interactive mode:
|
||||||
|
|
||||||
<program> Copyright (C) <year> <name of author>
|
Gnomovision version 69, Copyright (C) year name of author
|
||||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||||
This is free software, and you are welcome to redistribute it
|
This is free software, and you are welcome to redistribute it
|
||||||
under certain conditions; type `show c' for details.
|
under certain conditions; type `show c' for details.
|
||||||
|
|
||||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||||
parts of the General Public License. Of course, your program's commands
|
parts of the General Public License. Of course, the commands you use may
|
||||||
might be different; for a GUI interface, you would use an "about box".
|
be called something other than `show w' and `show c'; they could even be
|
||||||
|
mouse-clicks or menu items--whatever suits your program.
|
||||||
|
|
||||||
You should also get your employer (if you work as a programmer) or school,
|
You should also get your employer (if you work as a programmer) or your
|
||||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||||
For more information on this, and how to apply and follow the GNU GPL, see
|
necessary. Here is a sample; alter the names:
|
||||||
<http://www.gnu.org/licenses/>.
|
|
||||||
|
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.
|
||||||
|
|
||||||
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,35 +1,8 @@
|
|||||||
MAKEFLAGS += --silent
|
###############################################################################
|
||||||
|
#
|
||||||
TARGET = hd-idle
|
# General Definitions
|
||||||
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
|
ifdef DESTDIR
|
||||||
# dh_auto_install (Debian) sets this variable
|
# dh_auto_install (Debian) sets this variable
|
||||||
@ -38,22 +11,44 @@ else
|
|||||||
TARGET_DIR ?= /usr/local
|
TARGET_DIR ?= /usr/local
|
||||||
endif
|
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)
|
all: $(TARGET)
|
||||||
|
|
||||||
distclean: clean
|
distclean: clean
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f $(TARGET)
|
rm -f $(OBJS) $(TARGET)
|
||||||
|
|
||||||
install: $(TARGET)
|
install: $(TARGET)
|
||||||
install -Dm755 $(TARGET) $(TARGET_DIR)/sbin/$(TARGET)
|
install -D -g root -o root $(TARGET) $(TARGET_DIR)/sbin/$(TARGET)
|
||||||
install -Dm755 debian/$(TARGET).8 $(TARGET_DIR)/share/man/man8/$(TARGET).8
|
install -D -g root -o root $(TARGET).1 $(TARGET_DIR)/share/man/man1/$(TARGET).1
|
||||||
|
|
||||||
uninstall:
|
hd-idle.o: hd-idle.c
|
||||||
rm -f $(TARGET_DIR)/sbin/$(TARGET)
|
|
||||||
|
$(TARGET): $(OBJS)
|
||||||
|
$(LD) $(LDFLAGS) -o $(TARGET) $(OBJS) $(LIB_DIRS) $(LIBS)
|
||||||
|
|
||||||
$(TARGET):
|
|
||||||
GOOS=linux GOARCH=$(GOARCH) go build
|
|
||||||
|
|
||||||
test:
|
|
||||||
go test ./... -race -cover
|
|
||||||
|
131
README
Normal file
131
README
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
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
340
README.md
@ -1,340 +0,0 @@
|
|||||||
# 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).
|
|
3
cvsenv.sh
Normal file
3
cvsenv.sh
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export CVS_RSH=ssh
|
||||||
|
export CVSROOT=cjmueller@hd-idle.cvs.sourceforge.net:/cvsroot/hd-idle
|
||||||
|
|
215
debian/changelog
vendored
215
debian/changelog
vendored
@ -1,214 +1,11 @@
|
|||||||
hd-idle (1.21) unstable; urgency=medium
|
hd-idle (1.05+ds-2) unstable; urgency=medium
|
||||||
|
|
||||||
[ Gray Xu ]
|
* No-change, Source-only upload
|
||||||
* Use GivenName instead of Name in the log
|
|
||||||
|
|
||||||
[ Andoni del Olmo ]
|
-- Alexandre Mestiashvili <mestia@debian.org> Thu, 19 Sep 2019 10:13:38 +0200
|
||||||
* Support Jmicron USB Bridge Controller for ATA command
|
|
||||||
|
|
||||||
-- root <mario.fetka@disconnected-by-peer.at> Wed, 06 Aug 2025 15:39:11 +0200
|
hd-idle (1.05+ds-1) unstable; urgency=medium
|
||||||
|
|
||||||
hd-idle (1.20) unstable; urgency=medium
|
* Initial release. Closes: #924749
|
||||||
|
|
||||||
* Fix force hd-idle into background in init script
|
-- Alexandre Mestiashvili <mestia@debian.org> Sun, 17 Mar 2019 16:37:34 +0100
|
||||||
* Fix missing man page
|
|
||||||
|
|
||||||
-- Andoni del Olmo <andoni.delolmo@gmail.com> Fri, 17 Feb 2023 16:54:38 +0100
|
|
||||||
|
|
||||||
hd-idle (1.19) unstable; urgency=medium
|
|
||||||
|
|
||||||
[ Benjamin Engele ]
|
|
||||||
* Support more than 26 disks.
|
|
||||||
|
|
||||||
[ Paul Webster ]
|
|
||||||
* Use explicit uint64
|
|
||||||
|
|
||||||
[ Benjamin Engele ]
|
|
||||||
* Use standby instead of stop command.
|
|
||||||
* Support configuring power condition.
|
|
||||||
* Add and describe -p parameter usage.
|
|
||||||
* Adjusted documentation of power condition.
|
|
||||||
|
|
||||||
[ Martin Oemus ]
|
|
||||||
* fixed poolInterval calculation when using idle intervals of 0
|
|
||||||
|
|
||||||
[ Andoni del Olmo ]
|
|
||||||
* fixed Use UNIX time to calculate skew interval
|
|
||||||
|
|
||||||
-- Andoni del Olmo <andoni.delolmo@gmail.com> Thu, 09 Feb 2023 11:55:12 +0100
|
|
||||||
|
|
||||||
hd-idle (1.18) unstable; urgency=medium
|
|
||||||
|
|
||||||
* fix cross platform compilation
|
|
||||||
* simplify package generation in rules file
|
|
||||||
* Complete the list of targets for the service restart
|
|
||||||
|
|
||||||
-- Andoni del Olmo <andoni.delolmo@gmail.com> Wed, 17 Aug 2022 13:02:42 +0200
|
|
||||||
|
|
||||||
hd-idle (1.17) unstable; urgency=medium
|
|
||||||
|
|
||||||
[ Alexander Raab ]
|
|
||||||
* Readme cosmetics
|
|
||||||
|
|
||||||
[ Andoni del Olmo ]
|
|
||||||
* restart service after suspend
|
|
||||||
* go mod tidy
|
|
||||||
* Update readme with instructions to build with golang 17 or higher
|
|
||||||
* Add Makefile
|
|
||||||
* build debian package compiling with Makefile
|
|
||||||
* document usage of LUKS encrypted devices
|
|
||||||
* Redo explanation of how the log file works. Thanks to rabelux.
|
|
||||||
* restart service after hibernate
|
|
||||||
|
|
||||||
[ Sylvain Pasche ]
|
|
||||||
* Get statistics from device mapper devices
|
|
||||||
|
|
||||||
[ Andoni del Olmo ]
|
|
||||||
* use /sys/class/block/%s/holders for holderGetter + add test
|
|
||||||
* add test for statsForDisk
|
|
||||||
|
|
||||||
-- Andoni del Olmo <andoni.delolmo@gmail.com> Thu, 28 Jul 2022 18:12:46 +0200
|
|
||||||
|
|
||||||
hd-idle (1.16) unstable; urgency=medium
|
|
||||||
|
|
||||||
[ Maximilian Bichel ]
|
|
||||||
* Update help and man page to inform that "i" parameter with value zero never spins down disks.
|
|
||||||
|
|
||||||
[ Zhenyu Wu ]
|
|
||||||
* Try both ATA standby commands before fail.
|
|
||||||
|
|
||||||
-- Andoni del Olmo <andoni.delolmo@gmail.com> Sun, 05 Sep 2021 19:45:17 +0200
|
|
||||||
|
|
||||||
hd-idle (1.15) unstable; urgency=medium
|
|
||||||
|
|
||||||
* Handle disks with no partitions.
|
|
||||||
Encrypted disks do not have any partitions. In this case, the disk level activity has to be taken into
|
|
||||||
consideration.
|
|
||||||
|
|
||||||
-- Andoni del Olmo <andoni.delolmo@gmail.com> Mon, 05 Apr 2021 09:39:11 +0200
|
|
||||||
|
|
||||||
hd-idle (1.14) unstable; urgency=medium
|
|
||||||
|
|
||||||
* Add logrotate for log file /var/log/hd-idle.log
|
|
||||||
* Use partitions read/write to calculate disk activity:
|
|
||||||
It changes the method to calculate disk activity. Now the disk activity is calculated by watching read/write
|
|
||||||
changes on partition level instead of disk level.
|
|
||||||
|
|
||||||
-- Andoni del Olmo <andoni.delolmo@gmail.com> Sun, 28 Mar 2021 14:34:51 +0200
|
|
||||||
|
|
||||||
hd-idle (1.13) unstable; urgency=medium
|
|
||||||
|
|
||||||
* Fix crash when required arguments are not given. Now it will fail
|
|
||||||
gracefully when required arguments are missing.
|
|
||||||
* Add SystemV init script.
|
|
||||||
|
|
||||||
-- Andoni del Olmo <andoni.delolmo@gmail.com> Thu, 04 Mar 2021 20:33:26 +0100
|
|
||||||
|
|
||||||
hd-idle (1.12) unstable; urgency=medium
|
|
||||||
|
|
||||||
* Fix inconsistent spin down log. The release v1.11 changed the log output on spin down to
|
|
||||||
"/dev/sda spindown". Now is back to the format "sda spindown".
|
|
||||||
* Fix typo in help -h flag. This bug prevented showing the help on cli.
|
|
||||||
|
|
||||||
-- Andoni del Olmo <andoni.delolmo@gmail.com> Sat, 05 Dec 2020 15:43:05 +0100
|
|
||||||
|
|
||||||
hd-idle (1.11) unstable; urgency=medium
|
|
||||||
|
|
||||||
* Ignore sense response data for ata command to prevent error on arm64.
|
|
||||||
* Allow set command type in combination with -t option.
|
|
||||||
* Remove go-co-op dependency.
|
|
||||||
* Clean control and man page.
|
|
||||||
* Add copyright.
|
|
||||||
* Update readme. No need for GOPATH anymore.
|
|
||||||
|
|
||||||
-- Andoni del Olmo <andoni.delolmo@gmail.com> Sat, 31 Oct 2020 21:43:04 +0100
|
|
||||||
|
|
||||||
hd-idle (1.10) unstable; urgency=medium
|
|
||||||
|
|
||||||
* Check sectors read/write to determine disk activity
|
|
||||||
* Package. Move debian files to debian dir
|
|
||||||
* Package. Simplify rules and delete config handle system
|
|
||||||
* Update readme. Explain logs
|
|
||||||
* Update readme. Entry to response not ok error
|
|
||||||
|
|
||||||
-- Andoni del Olmo <andoni.delolmo@gmail.com> Sun, 09 Aug 2020 10:26:32 +0200
|
|
||||||
|
|
||||||
hd-idle (1.9) unstable; urgency=medium
|
|
||||||
|
|
||||||
* Improve log on start up and ATA error reporting.
|
|
||||||
|
|
||||||
-- Andoni del Olmo <andoni.delolmo@gmail.com> Wed, 11 Mar 2020 10:25:00 +0200
|
|
||||||
|
|
||||||
hd-idle (1.8) unstable; urgency=medium
|
|
||||||
|
|
||||||
* Allow usage of symlinks that point to partitions. Like: by-label, by-partlabel,
|
|
||||||
by-partuuid and by-uuid.
|
|
||||||
* Improve error handling when spin down fails.
|
|
||||||
|
|
||||||
-- Andoni del Olmo <andoni.delolmo@gmail.com> Wed, 23 Oct 2019 21:15:00 +0200
|
|
||||||
|
|
||||||
hd-idle (1.7) unstable; urgency=medium
|
|
||||||
|
|
||||||
* Change package section to admin and priority to optional.
|
|
||||||
* Fix man page format error.
|
|
||||||
* Move man page to section 8 (System administration commands and daemons).
|
|
||||||
* Sign package.
|
|
||||||
|
|
||||||
-- Andoni del Olmo <andoni.delolmo@gmail.com> Sun, 8 Sep 2019 08:47:00 +0200
|
|
||||||
|
|
||||||
hd-idle (1.6) unstable; urgency=low
|
|
||||||
|
|
||||||
* The parameter "-s" allows to resolve symlinks for disk names also in runtime.
|
|
||||||
It is disable by default, because resolving symlinks causes an overhead.
|
|
||||||
That means that disk symlinks only get resolved on start up by default.
|
|
||||||
If the parameter "-s" is set to 1, disk symlinks will be also resolve during
|
|
||||||
execution until the symlink is resolved.
|
|
||||||
|
|
||||||
-- Andoni del Olmo <andoni.delolmo@gmail.com> Wed, 28 Aug 2019 19:33:00 +0100
|
|
||||||
|
|
||||||
hd-idle (1.5) unstable; urgency=low
|
|
||||||
|
|
||||||
* Monitor the skew between monitoring cycles, on discovery of clock skew
|
|
||||||
reset the drive spin_down status to "spun up" and reset the time to current
|
|
||||||
in order to capture potential high loading or (more likely) recovery from
|
|
||||||
suspend or sleep
|
|
||||||
|
|
||||||
-- Andoni del Olmo <andoni.delolmo@gmail.com> Sat, 13 Aug 2019 21:15:00 +0100
|
|
||||||
|
|
||||||
hd-idle (1.4) unstable; urgency=low
|
|
||||||
|
|
||||||
* 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.
|
|
||||||
|
|
||||||
* Simply log spinup.
|
|
||||||
|
|
||||||
-- Andoni del Olmo <andoni.delolmo@gmail.com> Sat, 5 Jan 2019 18:42:00 +0100
|
|
||||||
|
|
||||||
hd-idle (1.3) unstable; urgency=low
|
|
||||||
|
|
||||||
* Set sleep time to 1/10th of the shortest idle time.
|
|
||||||
|
|
||||||
-- Andoni del Olmo <andoni.delolmo@gmail.com> Fri, 5 Oct 2018 20:47:10 +0100
|
|
||||||
|
|
||||||
hd-idle (1.2) unstable; urgency=low
|
|
||||||
|
|
||||||
* Persist user's config across package upgrades.
|
|
||||||
|
|
||||||
-- Andoni del Olmo <andoni.delolmo@gmail.com> Mon, 17 Sep 2018 22:03:10 +0100
|
|
||||||
|
|
||||||
hd-idle (1.1) unstable; urgency=low
|
|
||||||
|
|
||||||
* Add missing feature to spin-down the specified disk immediately.
|
|
||||||
|
|
||||||
-- Andoni del Olmo <andoni.delolmo@gmail.com> Sun, 16 Sep 2018 18:13:10 +0100
|
|
||||||
|
|
||||||
hd-idle (1.0) unstable; urgency=low
|
|
||||||
|
|
||||||
* Add "ata" api call to stop devices on top of the original functionality.
|
|
||||||
|
|
||||||
-- Andoni del Olmo <andoni.delolmo@gmail.com> Sun, 16 Sep 2018 10:01:10 +0100
|
|
||||||
|
70
debian/changelog_orig
vendored
Normal file
70
debian/changelog_orig
vendored
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
hd-idle (1.05) unstable; urgency=low
|
||||||
|
|
||||||
|
* Allow SCSI device names with more than one character (e.g. sdaa) in case
|
||||||
|
there are more than 26 SCSI targets.
|
||||||
|
|
||||||
|
-- Christian Mueller <cm1@mumac.de> Sun, 6 Apr 2014 22:02:00 +0200
|
||||||
|
|
||||||
|
hd-idle (1.04) unstable; urgency=low
|
||||||
|
|
||||||
|
* Make hd-idle's build environment compatible to Debian package management;
|
||||||
|
this effort is meant to allow hd-idle to become an official Debian package
|
||||||
|
* Man page for hd-idle
|
||||||
|
|
||||||
|
-- Christian Mueller <cm1@mumac.de> Fri, 30 Sep 2011 22:35:12 +0200
|
||||||
|
|
||||||
|
hd-idle (1.03) unstable; urgency=low
|
||||||
|
|
||||||
|
* 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".
|
||||||
|
|
||||||
|
-- Christian Mueller <cm1@mumac.de> Sun, 5 Dec 2010 19:25:51 +0100
|
||||||
|
|
||||||
|
hd-idle (1.02) unstable; urgency=low
|
||||||
|
|
||||||
|
* 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.
|
||||||
|
|
||||||
|
-- Christian Mueller <cm1@mumac.de> Sat, 6 Nov 2010 15:47:00 +0100
|
||||||
|
|
||||||
|
hd-idle (1.01) unstable; urgency=low
|
||||||
|
|
||||||
|
* 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.
|
||||||
|
|
||||||
|
* 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()
|
||||||
|
|
||||||
|
-- Christian Mueller <cm1@mumac.de> Fri, 26 Feb 2010 14:03:44 +0100
|
||||||
|
|
||||||
|
hd-idle (1.00) unstable; urgency=low
|
||||||
|
|
||||||
|
* 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
|
||||||
|
|
||||||
|
* 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.
|
||||||
|
|
||||||
|
-- Christian Mueller <cm1@mumac.de> Wed, 18 Nov 2009 20:53:17 +0100
|
||||||
|
|
||||||
|
hd-idle (0.99) unstable; urgency=low
|
||||||
|
|
||||||
|
* Initial Release.
|
||||||
|
|
||||||
|
-- Christian Mueller <cm1@mumac.de> Mon, 23 Apr 2007 22:03:10 +0100
|
||||||
|
|
2
debian/compat
vendored
2
debian/compat
vendored
@ -1 +1 @@
|
|||||||
9
|
12
|
||||||
|
24
debian/control
vendored
24
debian/control
vendored
@ -1,16 +1,20 @@
|
|||||||
Source: hd-idle
|
Source: hd-idle
|
||||||
|
Maintainer: Alexandre Mestiashvili <mestia@debian.org>
|
||||||
Section: admin
|
Section: admin
|
||||||
Priority: optional
|
Priority: optional
|
||||||
Maintainer: Andoni del Olmo <andoni.delolmo@gmail.com>
|
Build-Depends: debhelper (>= 12~)
|
||||||
Build-Depends: debhelper (>=9), golang-go:native (>= 1.3.3), dh-golang
|
|
||||||
Standards-Version: 4.3.0
|
Standards-Version: 4.3.0
|
||||||
Vcs-Browser: https://github.com/adelolmo/hd-idle
|
Vcs-Browser: https://salsa.debian.org/debian/hd-idle
|
||||||
Vcs-Git: https://github.com/adelolmo/hd-idle.git
|
Vcs-Git: https://salsa.debian.org/debian/hd-idle.git
|
||||||
Homepage: https://github.com/adelolmo/hd-idle
|
Homepage: http://hd-idle.sf.net
|
||||||
|
|
||||||
Package: hd-idle
|
Package: hd-idle
|
||||||
Architecture: any
|
Architecture: any
|
||||||
Description: Spin down idle hard disks
|
Pre-Depends: ${misc:Pre-Depends}
|
||||||
|
Depends: lsb-base,
|
||||||
|
${misc:Depends},
|
||||||
|
${shlibs:Depends},
|
||||||
|
Description: Spin down idle [USB] hard disks
|
||||||
hd-idle is a utility program for spinning-down external disks after a period
|
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
|
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
|
the IDE idle timer, a program like hd-idle is required to spin down idle disks
|
||||||
@ -22,3 +26,11 @@ Description: Spin down idle hard disks
|
|||||||
stress the spin-up causes on the spindle motor and bearings. It seems that
|
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
|
manufacturers recommend a minimum idle time of 3-5 minutes, the default in
|
||||||
hd-idle is 10 minutes.
|
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
|
||||||
|
It is not recommended to use 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.
|
||||||
|
37
debian/copyright
vendored
37
debian/copyright
vendored
@ -1,12 +1,33 @@
|
|||||||
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||||
Upstream-Name: hd-idle
|
Upstream-Name: hd-idle
|
||||||
Source: https://github.com/adelolmo/hd-idle
|
Source: https://sourceforge.net/projects/hd-idle/files/
|
||||||
|
Files-Excluded:
|
||||||
|
debian/*
|
||||||
|
scripts/*
|
||||||
|
|
||||||
Files: *
|
Files: *
|
||||||
Copyright: 2018 Andoni del Olmo <andoni.delolmo@gmail.com>
|
|
||||||
Author: Andoni del Olmo <andoni.delolmo@gmail.com>
|
|
||||||
License: GPL-3
|
|
||||||
|
|
||||||
Files: debian/hd-idle.8 debian/hd-idle.default debian/control
|
|
||||||
Copyright: 2007 Christian Mueller <cm1@mumac.de>
|
Copyright: 2007 Christian Mueller <cm1@mumac.de>
|
||||||
License: GPL-3
|
License: GPL-2+
|
||||||
|
|
||||||
|
Files: debian/*
|
||||||
|
Copyright: © 2019 Alex Mestiashvili <mestia@debian.org>
|
||||||
|
© 2007 Christian Mueller <cm1@mumac.de>
|
||||||
|
License: GPL-2+
|
||||||
|
|
||||||
|
License: GPL-2+
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
.
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
.
|
||||||
|
You should have received a copy of the GNU General Public License along
|
||||||
|
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
.
|
||||||
|
On Debian systems, the complete text of the GNU General
|
||||||
|
Public License version 2 can be found in "/usr/share/common-licenses/GPL-2".
|
||||||
|
2
debian/docs
vendored
Normal file
2
debian/docs
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
README
|
||||||
|
debian/changelog_orig
|
33
debian/hd-idle.default
vendored
33
debian/hd-idle.default
vendored
@ -1,7 +1,15 @@
|
|||||||
# defaults file for hd-idle
|
# defaults file for hd-idle
|
||||||
|
|
||||||
# start hd-idle automatically?
|
## Debian specific defaults for hd-idle
|
||||||
START_HD_IDLE=false
|
# In order to make init.d script and systemd service consistent, hd-idle
|
||||||
|
# defaults file supports only one option - HD_IDLE_OPTS.
|
||||||
|
|
||||||
|
# Don't start hd-idle by defaulit. By specifying "-h" in HD_IDEL_OPTS,
|
||||||
|
# hd-idle daemon will print usage and successfully exit both for systemd
|
||||||
|
# and systemv scripts
|
||||||
|
# properly update HD_IDLE_OPTS in order to star hd-idle
|
||||||
|
|
||||||
|
HD_IDLE_OPTS="-h"
|
||||||
|
|
||||||
# hd-idle command line options
|
# hd-idle command line options
|
||||||
# Options are:
|
# Options are:
|
||||||
@ -12,20 +20,6 @@ START_HD_IDLE=false
|
|||||||
# parameter. This can also be a symlink
|
# parameter. This can also be a symlink
|
||||||
# (e.g. /dev/disk/by-uuid/...)
|
# (e.g. /dev/disk/by-uuid/...)
|
||||||
# -i <idle_time> Idle time in seconds.
|
# -i <idle_time> Idle time in seconds.
|
||||||
# -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 resolve only on start.
|
|
||||||
# If set to "1", symlinks are also resolved on runtime
|
|
||||||
# until success. By default symlinks are only resolve 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
|
# -l <logfile> Name of logfile (written only after a disk has spun
|
||||||
# up). Please note that this option might cause the
|
# up). Please note that this option might cause the
|
||||||
# disk which holds the logfile to spin up just because
|
# disk which holds the logfile to spin up just because
|
||||||
@ -35,8 +29,9 @@ START_HD_IDLE=false
|
|||||||
# this option should not cause any additional spinups.
|
# this option should not cause any additional spinups.
|
||||||
#
|
#
|
||||||
# Options not exactly useful here:
|
# Options not exactly useful here:
|
||||||
# -t <disk> Spin-down the specified disk immediately and exit.
|
# -t <disk> Spin-down the specfified disk immediately and exit.
|
||||||
# -d Debug mode. It will print debugging info to
|
# -d Debug mode. This will prevent hd-idle from
|
||||||
# stdout/stderr (/var/log/syslog if started as with systemctl)
|
# becoming a daemon and print debugging info to
|
||||||
|
# stdout/stderr
|
||||||
# -h Print usage information.
|
# -h Print usage information.
|
||||||
#HD_IDLE_OPTS="-i 180 -l /var/log/hd-idle.log"
|
#HD_IDLE_OPTS="-i 180 -l /var/log/hd-idle.log"
|
2
debian/hd-idle.init
vendored
Normal file → Executable file
2
debian/hd-idle.init
vendored
Normal file → Executable file
@ -26,7 +26,7 @@ case "$1" in
|
|||||||
start)
|
start)
|
||||||
log_daemon_msg "Starting the hd-idle daemon" "hd-idle"
|
log_daemon_msg "Starting the hd-idle daemon" "hd-idle"
|
||||||
|
|
||||||
start-stop-daemon --start --quiet --oknodo --background --exec $DAEMON -- $HD_IDLE_OPTS
|
start-stop-daemon --start --quiet --oknodo --exec $DAEMON -- $HD_IDLE_OPTS
|
||||||
|
|
||||||
log_end_msg $?
|
log_end_msg $?
|
||||||
;;
|
;;
|
||||||
|
2
debian/hd-idle.logrotate
vendored
2
debian/hd-idle.logrotate
vendored
@ -1,4 +1,4 @@
|
|||||||
/var/log/hd-idle.log {
|
/var/log/hd-idle/*log {
|
||||||
missingok
|
missingok
|
||||||
notifempty
|
notifempty
|
||||||
compress
|
compress
|
||||||
|
6
debian/hd-idle.service
vendored
6
debian/hd-idle.service
vendored
@ -1,13 +1,11 @@
|
|||||||
[Unit]
|
[Unit]
|
||||||
Description=hd-idle - spin down idle hard disks
|
Description=hd-idle - spin down idle hard disks
|
||||||
Documentation=man:hd-idle(8)
|
Documentation=man:hd-idle(1)
|
||||||
After=suspend.target hibernate.target hybrid-sleep.target suspend-then-hibernate.target
|
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=simple
|
Type=forking
|
||||||
EnvironmentFile=/etc/default/hd-idle
|
EnvironmentFile=/etc/default/hd-idle
|
||||||
ExecStart=/usr/sbin/hd-idle $HD_IDLE_OPTS
|
ExecStart=/usr/sbin/hd-idle $HD_IDLE_OPTS
|
||||||
Restart=always
|
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
|
14
debian/rules
vendored
14
debian/rules
vendored
@ -2,13 +2,15 @@
|
|||||||
export DH_VERBOSE=1
|
export DH_VERBOSE=1
|
||||||
export DEB_BUILD_MAINT_OPTIONS = hardening=+all
|
export DEB_BUILD_MAINT_OPTIONS = hardening=+all
|
||||||
|
|
||||||
|
mandir:= $(CURDIR)/debian/hd-idle/usr/share/man
|
||||||
%:
|
%:
|
||||||
dh $@
|
dh $@
|
||||||
|
|
||||||
override_dh_auto_build:
|
|
||||||
|
|
||||||
override_dh_auto_install:
|
override_dh_auto_install:
|
||||||
install -d $(CURDIR)/debian/hd-idle
|
dh_auto_install
|
||||||
make install DESTDIR=$(CURDIR)/debian/hd-idle ARCH=$(DEB_HOST_ARCH)
|
# upstream ships wrong man section, thus the dirty workaround below
|
||||||
|
mkdir -p $(mandir)/man8
|
||||||
override_dh_strip:
|
mv $(mandir)/man1/hd-idle.1 \
|
||||||
|
$(mandir)/man8/hd-idle.8
|
||||||
|
perl -i -pe 's/\A(\.TH\sHD-IDLE\s)1(.*)\Z/$${1}8$$2/' $(mandir)/man8/hd-idle.8
|
||||||
|
rm -rf $(mandir)/man1
|
||||||
|
2
debian/source/format
vendored
2
debian/source/format
vendored
@ -1 +1 @@
|
|||||||
3.0 (native)
|
3.0 (quilt)
|
||||||
|
4
debian/watch
vendored
Normal file
4
debian/watch
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
version=4
|
||||||
|
|
||||||
|
opts=dversionmangle=s/\+ds(@ANY_VERSION@)?$//,repacksuffix=+ds,repack,compression=xz \
|
||||||
|
https://sf.net/@PACKAGE@/@PACKAGE@@ANY_VERSION@@ARCHIVE_EXT@
|
@ -1,236 +0,0 @@
|
|||||||
// 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
|
|
||||||
}
|
|
@ -1,218 +0,0 @@
|
|||||||
// 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
5
go.mod
@ -1,5 +0,0 @@
|
|||||||
module github.com/adelolmo/hd-idle
|
|
||||||
|
|
||||||
go 1.16
|
|
||||||
|
|
||||||
require github.com/benmcclelland/sgio v0.0.0-20180629175614-f710aebf64c1
|
|
2
go.sum
2
go.sum
@ -1,2 +0,0 @@
|
|||||||
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=
|
|
@ -2,7 +2,7 @@
|
|||||||
.\" First parameter, NAME, should be all caps
|
.\" First parameter, NAME, should be all caps
|
||||||
.\" Second parameter, SECTION, should be 1-8, maybe w/ subsection
|
.\" Second parameter, SECTION, should be 1-8, maybe w/ subsection
|
||||||
.\" other parameters are allowed: see man(7), man(1)
|
.\" other parameters are allowed: see man(7), man(1)
|
||||||
.TH HD-IDLE 8 "September 8, 2019"
|
.TH HD-IDLE 1 "September 29, 2011"
|
||||||
.\" Please adjust this date whenever revising the manpage.
|
.\" Please adjust this date whenever revising the manpage.
|
||||||
.\"
|
.\"
|
||||||
.\" Some roff macros, for reference:
|
.\" Some roff macros, for reference:
|
||||||
@ -33,6 +33,14 @@ 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
|
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
|
manufacturers recommend a minimum idle time of 3-5 minutes, the default in
|
||||||
hd-idle is 10 minutes.
|
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
|
.SH OPTIONS
|
||||||
.TP
|
.TP
|
||||||
.B \-a name
|
.B \-a name
|
||||||
@ -45,26 +53,6 @@ also be a symlink (e.g. /dev/disk/by-uuid/...)
|
|||||||
.B \-i idle_time
|
.B \-i idle_time
|
||||||
Idle time in seconds for the currently named disk(s) (-a <name>) or for
|
Idle time in seconds for the currently named disk(s) (-a <name>) or for
|
||||||
all disks.
|
all disks.
|
||||||
Setting this value to "0" will never spin down the disk(s).
|
|
||||||
.TP
|
|
||||||
.B \-c command_type
|
|
||||||
Api call to stop the device. Possible values are "scsi" (default value)
|
|
||||||
and "ata".
|
|
||||||
.TP
|
|
||||||
.B \-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.
|
|
||||||
.TP
|
|
||||||
.B \-s symlink_policy
|
|
||||||
Set the policy to resolve symlinks for devices. If set to "0", symlinks
|
|
||||||
are resolve only on start. If set to "1", symlinks are also resolved on
|
|
||||||
runtime until success. By default symlinks are only resolve on start.
|
|
||||||
If the symlink doesn't resolve to a device, the default configuration
|
|
||||||
will be applied.
|
|
||||||
.TP
|
.TP
|
||||||
.B \-l logfile
|
.B \-l logfile
|
||||||
Name of logfile (written only after a disk has spun up). Please note that
|
Name of logfile (written only after a disk has spun up). Please note that
|
||||||
@ -74,14 +62,11 @@ systems with more than one disk except for tuning purposes. On single-disk
|
|||||||
systems, this option should not cause any additional spinups.
|
systems, this option should not cause any additional spinups.
|
||||||
.TP
|
.TP
|
||||||
.B \-t disk
|
.B \-t disk
|
||||||
Spin-down the specified disk immediately and exit. It can be used in combination
|
Spin-down the specfified disk immediately and exit.
|
||||||
with
|
|
||||||
.B \-c
|
|
||||||
to specify the command type.
|
|
||||||
.TP
|
.TP
|
||||||
.B \-d
|
.B \-d
|
||||||
Debug mode. It will print debugging info to stdout/stderr (/var/log/syslog
|
Debug mode. This will prevent hd-idle from becoming a daemon and print
|
||||||
if started as with systemctl)
|
debugging info to stdout/stderr
|
||||||
.TP
|
.TP
|
||||||
.B \-h
|
.B \-h
|
||||||
Print usage information.
|
Print usage information.
|
||||||
@ -97,7 +82,8 @@ A
|
|||||||
.B \-i
|
.B \-i
|
||||||
option before the first
|
option before the first
|
||||||
.B \-a
|
.B \-a
|
||||||
option will set the default idle time.
|
option will set the default idle time; hence, compatibility with previous
|
||||||
|
releases of hd-idle is maintained.
|
||||||
.TP
|
.TP
|
||||||
.B \2)
|
.B \2)
|
||||||
In order to disable spin-down of disks per default, and then re-enable
|
In order to disable spin-down of disks per default, and then re-enable
|
||||||
@ -106,22 +92,10 @@ spin-down on selected disks, set the default idle time to 0.
|
|||||||
hd-idle -i 0 -a sda -i 300 -a sdb -i 1200
|
hd-idle -i 0 -a sda -i 300 -a sdb -i 1200
|
||||||
.P
|
.P
|
||||||
This example sets the default idle time to 0 (meaning hd-idle will never
|
This example sets the default idle time to 0 (meaning hd-idle will never
|
||||||
try to spin down a disk) and default "scsi" api command, then sets explicit
|
try to spin down a disk), then sets explicit idle times for disks which
|
||||||
idle times for disks which have the string "sda" or "sdb" in their device name.
|
have the string "sda" or "sdb" in their device name.
|
||||||
.SH EXAMPLE
|
|
||||||
hd-idle -i 0 -c ata -a sda -i 300 -a sdb -i 1200 -c scsi
|
|
||||||
.P
|
|
||||||
This example sets the default idle time to 0 (meaning hd-idle will never
|
|
||||||
try to spin down a disk) and default "ata" api command, then sets explicit
|
|
||||||
idle times for disks which have the string "sda" or "sdb" in their device name
|
|
||||||
and sets "sdb" to use "ata" api command.
|
|
||||||
.P
|
|
||||||
The option -c allows to set the api call that sends the spindown command.
|
|
||||||
Possible values are "scsi" (the default value) or "ata".
|
|
||||||
.SH AUTHOR
|
.SH AUTHOR
|
||||||
hd-idle was written by Andoni del Olmo <andoni.delolmo@gmail> based on Chistian Mueller's <chris@mumac.de> work.
|
hd-idle was written by Chistian Mueller <chris@mumac.de>
|
||||||
.PP
|
.PP
|
||||||
This manual page was written by Christian Mueller <chris@mumac.de>, for the Debian
|
This manual page was written by Christian Mueller <chris@mumac.de>,
|
||||||
project (and may be used by others).
|
for the Debian project (and may be used by others).
|
||||||
.PP
|
|
||||||
Modified by Andoni del Olmo <andoni.delolmo@gmail.com>.
|
|
575
hd-idle.c
Normal file
575
hd-idle.c
Normal file
@ -0,0 +1,575 @@
|
|||||||
|
/*
|
||||||
|
* 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
287
hdidle.go
@ -1,287 +0,0 @@
|
|||||||
// 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
50
io/disk.go
@ -1,50 +0,0 @@
|
|||||||
// 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
103
io/disk_test.go
@ -1,103 +0,0 @@
|
|||||||
// 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
245
main.go
@ -1,245 +0,0 @@
|
|||||||
// 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
32
main_test.go
@ -1,32 +0,0 @@
|
|||||||
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
132
sgio/ata.go
@ -1,132 +0,0 @@
|
|||||||
// 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")
|
|
||||||
}
|
|
@ -1,47 +0,0 @@
|
|||||||
// 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
63
sgio/scsi.go
@ -1,63 +0,0 @@
|
|||||||
// 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
127
sgio/type.go
@ -1,127 +0,0 @@
|
|||||||
// 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")
|
|
||||||
}
|
|
@ -1,80 +0,0 @@
|
|||||||
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
24
vendor/github.com/benmcclelland/sgio/.gitignore
generated
vendored
@ -1,24 +0,0 @@
|
|||||||
# 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
21
vendor/github.com/benmcclelland/sgio/LICENSE
generated
vendored
@ -1,21 +0,0 @@
|
|||||||
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
28
vendor/github.com/benmcclelland/sgio/README.md
generated
vendored
@ -1,28 +0,0 @@
|
|||||||
# 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
219
vendor/github.com/benmcclelland/sgio/asc.go
generated
vendored
@ -1,219 +0,0 @@
|
|||||||
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
24
vendor/github.com/benmcclelland/sgio/parse.go
generated
vendored
@ -1,24 +0,0 @@
|
|||||||
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
160
vendor/github.com/benmcclelland/sgio/sg.go
generated
vendored
@ -1,160 +0,0 @@
|
|||||||
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
3
vendor/modules.txt
vendored
@ -1,3 +0,0 @@
|
|||||||
# github.com/benmcclelland/sgio v0.0.0-20180629175614-f710aebf64c1
|
|
||||||
## explicit
|
|
||||||
github.com/benmcclelland/sgio
|
|
Loading…
x
Reference in New Issue
Block a user