├── COPYING ├── Library ├── org.zfs.snapshot.daily.plist ├── org.zfs.snapshot.frequent.plist ├── org.zfs.snapshot.hourly.plist ├── org.zfs.snapshot.monthly.plist └── org.zfs.snapshot.weekly.plist ├── Makefile ├── README ├── etc ├── zfs-auto-snapshot.cron.daily ├── zfs-auto-snapshot.cron.frequent ├── zfs-auto-snapshot.cron.hourly ├── zfs-auto-snapshot.cron.monthly └── zfs-auto-snapshot.cron.weekly ├── makefile.darwin11 ├── makefile.darwin12 ├── makefile.linux-gnu └── src └── zfs-auto-snapshot.sh /COPYING: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /Library/org.zfs.snapshot.daily.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Label 6 | org.zfs.snapshot.daily 7 | ProgramArguments 8 | 9 | /usr/sbin/zfs-auto-snapshot.sh 10 | --syslog 11 | --quiet 12 | --keep=31 13 | --label=daily 14 | --send-incr=tank/archive/mac/nicks-macbook-air 15 | --remove-local=4 16 | --create 17 | --fallback 18 | // 19 | 20 | RunAtLoad 21 | 22 | StartInterval 23 | 86400 24 | 25 | 26 | -------------------------------------------------------------------------------- /Library/org.zfs.snapshot.frequent.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Label 6 | org.zfs.snapshot.frequent 7 | ProgramArguments 8 | 9 | /usr/sbin/zfs-auto-snapshot.sh 10 | --syslog 11 | --quiet 12 | --keep=4 13 | --label=frequent 14 | --send-incr=tank/archive/mac/nicks-macbook-air 15 | --remove-local=4 16 | --create 17 | --fallback 18 | --destroy 19 | // 20 | 21 | RunAtLoad 22 | 23 | StartInterval 24 | 900 25 | 26 | 27 | -------------------------------------------------------------------------------- /Library/org.zfs.snapshot.hourly.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Label 6 | org.zfs.snapshot.hourly 7 | ProgramArguments 8 | 9 | /usr/sbin/zfs-auto-snapshot.sh 10 | --syslog 11 | --quiet 12 | --keep=24 13 | --label=hourly 14 | --send-incr=tank/archive/mac/nicks-macbook-air 15 | --remove-local=4 16 | --create 17 | --fallback 18 | --destroy 19 | // 20 | 21 | RunAtLoad 22 | 23 | StartInterval 24 | 3600 25 | 26 | 27 | -------------------------------------------------------------------------------- /Library/org.zfs.snapshot.monthly.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Label 6 | org.zfs.snapshot.monthly 7 | ProgramArguments 8 | 9 | /usr/sbin/zfs-auto-snapshot.sh 10 | --syslog 11 | --quiet 12 | --keep=12 13 | --label=monthly 14 | --send-incr=tank/archive/mac/nicks-macbook-air 15 | --remove-local=4 16 | --create 17 | --fallback 18 | --destroy 19 | // 20 | 21 | RunAtLoad 22 | 23 | StartCalendarInterval 24 | 25 | Minute 26 | 0 27 | Hour 28 | 12 29 | Day 30 | 1 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /Library/org.zfs.snapshot.weekly.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Label 6 | org.zfs.snapshot.weekly 7 | ProgramArguments 8 | 9 | /usr/sbin/zfs-auto-snapshot.sh 10 | --syslog 11 | --quiet 12 | --keep=8 13 | --label=weekly 14 | --send-incr=tank/archive/mac/nicks-macbook-air 15 | --remove-local=4 16 | --create 17 | --fallback 18 | --destroy 19 | // 20 | 21 | RunAtLoad 22 | 23 | StartCalendarInterval 24 | 25 | Minute 26 | 0 27 | Hour 28 | 11 29 | Weekday 30 | 1 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SUPPORTED_PLATFORMS=linux-gnu darwin12 darwin11 2 | 3 | ifeq (,$(findstring $(OSTYPE),$(SUPPORTED_PLATFORMS))) 4 | 5 | all %: 6 | @echo The OS environment variable is set to [$(OSTYPE)]. 7 | @echo Please set the OS environment variable to one of the following: 8 | @echo $(SUPPORTED_PLATFORMS) 9 | 10 | else 11 | 12 | all: 13 | 14 | include makefile.$(OSTYPE) 15 | 16 | endif 17 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | zfs-auto-snapshot: 2 | 3 | An alternative implementation of the zfs-auto-snapshot service for Linux 4 | and Macosx (currently tested and compatible with ZEVO Community Edition). 5 | 6 | It can automatically create, rotate, and destroy periodic ZFS snapshots. This 7 | is utility that creates the @zfs-auto-snap_frequent, @zfs-auto-snap_hourly, 8 | @zfs-auto-snap_daily, @zfs-auto-snap_weekly, and @zfs-auto-snap_monthly 9 | snapshots if it is installed. 10 | 11 | It can backup (send) the snapshots to remote systems or external disks, 12 | utilizing zfs send command. On darwin this can replace TimeMachine backups, 13 | currently not running on top of ZFS filesystems. 14 | 15 | This program is a posixly correct bourne shell script. It depends on zfs 16 | utilities only (Linux). Unfortunatelly on Darwin it needs 'getopt' from 17 | macports or homebrew. 18 | 19 | For using --send option, adapt opt_sendtocmd variable accordingly by editing the 20 | script zfs-auto-snapshot.sh. 21 | 22 | sudo make OSTYPE=linux|darwin install 23 | 24 | installs cron / launchd startup scripts and copies script to /usr/sbin 25 | directory. 26 | 27 | On darwin for daily, weekly and monthly stuff, anacron install is highly 28 | recommended. 29 | -------------------------------------------------------------------------------- /etc/zfs-auto-snapshot.cron.daily: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | exec zfs-auto-snapshot --quiet --syslog --label=daily --keep=31 // 3 | -------------------------------------------------------------------------------- /etc/zfs-auto-snapshot.cron.frequent: -------------------------------------------------------------------------------- 1 | PATH="/usr/bin:/bin:/usr/sbin:/sbin" 2 | 3 | */15 * * * * root zfs-auto-snapshot -q -g --label=frequent --keep=4 // 4 | -------------------------------------------------------------------------------- /etc/zfs-auto-snapshot.cron.hourly: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | exec zfs-auto-snapshot --quiet --syslog --label=hourly --keep=24 // 3 | -------------------------------------------------------------------------------- /etc/zfs-auto-snapshot.cron.monthly: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | exec zfs-auto-snapshot --quiet --syslog --label=monthly --keep=12 // 3 | -------------------------------------------------------------------------------- /etc/zfs-auto-snapshot.cron.weekly: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | exec zfs-auto-snapshot --quiet --syslog --label=weekly --keep=8 // 3 | -------------------------------------------------------------------------------- /makefile.darwin11: -------------------------------------------------------------------------------- 1 | 2 | install: 3 | 4 | install Library/org.zfs.snapshot.frequent.plist /Library/LaunchDaemons/org.zfs.snapshot.frequent.plist 5 | install Library/org.zfs.snapshot.hourly.plist /Library/LaunchDaemons/org.zfs.snapshot.hourly.plist 6 | install Library/org.zfs.snapshot.daily.plist /Library/LaunchDaemons/org.zfs.snapshot.daily.plist 7 | install Library/org.zfs.snapshot.weekly.plist /Library/LaunchDaemons/org.zfs.snapshot.weekly.plist 8 | install Library/org.zfs.snapshot.monthly.plist /Library/LaunchDaemons/org.zfs.snapshot.monthly.plist 9 | install src/zfs-auto-snapshot.sh /usr/sbin/zfs-auto-snapshot.sh 10 | launchctl load -w /Library/LaunchDaemons/org.zfs.snapshot.frequent.plist 11 | launchctl load -w /Library/LaunchDaemons/org.zfs.snapshot.hourly.plist 12 | launchctl load -w /Library/LaunchDaemons/org.zfs.snapshot.daily.plist 13 | launchctl load -w /Library/LaunchDaemons/org.zfs.snapshot.weekly.plist 14 | launchctl load -w /Library/LaunchDaemons/org.zfs.snapshot.monthly.plist 15 | 16 | uninstall: 17 | 18 | launchctl unload -w /Library/LaunchDaemons/org.zfs.snapshot.frequent.plist 19 | launchctl unload -w /Library/LaunchDaemons/org.zfs.snapshot.hourly.plist 20 | launchctl unload -w /Library/LaunchDaemons/org.zfs.snapshot.daily.plist 21 | launchctl unload -w /Library/LaunchDaemons/org.zfs.snapshot.weekly.plist 22 | launchctl unload -w /Library/LaunchDaemons/org.zfs.snapshot.monthly.plist 23 | rm /Library/LaunchDaemons/org.zfs.snapshot.frequent.plist 24 | rm /Library/LaunchDaemons/org.zfs.snapshot.hourly.plist 25 | rm /Library/LaunchDaemons/org.zfs.snapshot.daily.plist 26 | rm /Library/LaunchDaemons/org.zfs.snapshot.weekly.plist 27 | rm /Library/LaunchDaemons/org.zfs.snapshot.monthly.plist 28 | rm /usr/sbin/zfs-auto-snapshot.sh 29 | -------------------------------------------------------------------------------- /makefile.darwin12: -------------------------------------------------------------------------------- 1 | 2 | install: 3 | 4 | install Library/org.zfs.snapshot.daily.plist /Library/LaunchDaemons/org.zfs.snapshot.daily.plist 5 | install Library/org.zfs.snapshot.frequent.plist /Library/LaunchDaemons/org.zfs.snapshot.frequent.plist 6 | install Library/org.zfs.snapshot.hourly.plist /Library/LaunchDaemons/org.zfs.snapshot.hourly.plist 7 | install Library/org.zfs.snapshot.monthly.plist /Library/LaunchDaemons/org.zfs.snapshot.monthly.plist 8 | install Library/org.zfs.snapshot.weekly.plist /Library/LaunchDaemons/org.zfs.snapshot.weekly.plist 9 | install src/zfs-auto-snapshot.sh /usr/sbin/zfs-auto-snapshot.sh 10 | launchctl load -w /Library/LaunchDaemons/org.zfs.snapshot.*.plist 11 | 12 | uninstall: 13 | 14 | launchctl unload -w /Library/LaunchDaemons/org.zfs.snapshot.*.plist 15 | rm /Library/LaunchDaemons/org.zfs.snapshot.*.plist 16 | rm /usr/sbin/zfs-auto-snapshot.sh 17 | -------------------------------------------------------------------------------- /makefile.linux-gnu: -------------------------------------------------------------------------------- 1 | 2 | install: 3 | 4 | install -d $(DESTDIR)$(PREFIX)/etc/cron.d 5 | install -d $(DESTDIR)$(PREFIX)/etc/cron.daily 6 | install -d $(DESTDIR)$(PREFIX)/etc/cron.hourly 7 | install -d $(DESTDIR)$(PREFIX)/etc/cron.weekly 8 | install -d $(DESTDIR)$(PREFIX)/etc/cron.monthly 9 | install etc/zfs-auto-snapshot.cron.frequent $(DESTDIR)$(PREFIX)/etc/cron.d/zfs-auto-snapshot 10 | install etc/zfs-auto-snapshot.cron.hourly $(DESTDIR)$(PREFIX)/etc/cron.hourly/zfs-auto-snapshot 11 | install etc/zfs-auto-snapshot.cron.daily $(DESTDIR)$(PREFIX)/etc/cron.daily/zfs-auto-snapshot 12 | install etc/zfs-auto-snapshot.cron.weekly $(DESTDIR)$(PREFIX)/etc/cron.weekly/zfs-auto-snapshot 13 | install etc/zfs-auto-snapshot.cron.monthly $(DESTDIR)$(PREFIX)/etc/cron.monthly/zfs-auto-snapshot 14 | install -d $(DESTDIR)$(PREFIX)/sbin 15 | install src/zfs-auto-snapshot.sh $(DESTDIR)$(PREFIX)/sbin/zfs-auto-snapshot 16 | -------------------------------------------------------------------------------- /src/zfs-auto-snapshot.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # zfs-auto-snapshot for Linux and Macosx 4 | # Automatically create, rotate, and destroy periodic ZFS snapshots. 5 | # Copyright 2011 Darik Horn 6 | # 7 | # zfs send, hanoi rotation, macosx/linux multiplatform changes - 8 | # Matus Kral 9 | # 10 | # This program is free software; you can redistribute it and/or modify it under 11 | # the terms of the GNU General Public License as published by the Free Software 12 | # Foundation; either version 2 of the License, or (at your option) any later 13 | # version. 14 | # 15 | # This program is distributed in the hope that it will be useful, but WITHOUT 16 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 17 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 18 | # details. 19 | # 20 | # You should have received a copy of the GNU General Public License along with 21 | # this program; if not, write to the Free Software Foundation, Inc., 59 Temple 22 | # Place, Suite 330, Boston, MA 02111-1307 USA 23 | # 24 | 25 | # Set the field separator to a literal tab and newline. 26 | IFS=" 27 | " 28 | 29 | # Set default program options. 30 | opt_backup_full='' 31 | opt_backup_incremental='' 32 | opt_default_exclude='' 33 | opt_dry_run='' 34 | opt_event='-' 35 | opt_keep='' 36 | opt_label='regular' 37 | opt_prefix='zfs-auto-snap' 38 | opt_recursive='' 39 | opt_sep='_' 40 | opt_setauto='' 41 | opt_syslog='' 42 | opt_skip_scrub='' 43 | opt_verbose='' 44 | opt_remove='' 45 | opt_fallback='0' 46 | opt_force='' 47 | opt_sendprefix='' 48 | opt_send='no' 49 | opt_atonce='-I' 50 | opt_create='0' 51 | opt_destroy='0' 52 | opt_rotation='rr' 53 | opt_base='day' 54 | opt_namechange='0' 55 | opt_factor='1' 56 | opt_limit='3' 57 | opt_includeall='0' 58 | 59 | # if pipe needs to be used, uncomment opt_pipe="|". arcfour or blowfish will reduce cpu load caused by ssh and mbuffer will 60 | # boost network bandwidth and mitigate low and high peaks during transfer 61 | opt_sendtocmd='ssh -2 root@romulus -c arcfour,blowfish-cbc -i /var/root/.ssh/id_rsa' 62 | opt_buffer='' 63 | #opt_buffer='mbuffer -q -m 250MB |' 64 | opt_pipe='|' 65 | 66 | # Global summary statistics. 67 | DESTRUCTION_COUNT='0' 68 | SNAPSHOT_COUNT='0' 69 | WARNING_COUNT='0' 70 | CREATION_COUNT='0' 71 | SENT_COUNT='0' 72 | KEEP='' 73 | 74 | PLATFORM_LOC='' 75 | PLATFORM_REM='' 76 | 77 | # Other global variables. 78 | SNAPSHOTS_OLD_LOC='' 79 | SNAPSHOTS_OLD_REM='' 80 | CREATED_TARGETS='' 81 | ZFS_REMOTE_LIST='' 82 | ZFS_LOCAL_LIST='' 83 | TARGETS_DRECURSIVE='' 84 | TARGETS_DREGULAR='' 85 | MOUNTED_LIST_LOC='' 86 | MOUNTED_LIST_REM='' 87 | RC='99' 88 | 89 | tmp_dir="/tmp/zfs-auto-snapshot.lock" 90 | 91 | print_usage () 92 | { 93 | echo "Usage: $0 [options] [-l label] <'//' | name [name...]> 94 | 95 | --default-exclude Exclude datasets if com.sun:auto-snapshot is unset 96 | (not explicitly set to true). 97 | --include-all Include datasets even if com.sun:auto-snapshot is set 98 | to false. 99 | --remove-local=n Remove local snapshots after successfully sent via 100 | --send-incr or --send-full but still keeps n newest 101 | snapshots (this will destroy snapshots named according 102 | to --prefix, but regardless of --label). Only valid for 103 | round-robin rotation. 104 | -d, --debug Print debugging messages. 105 | -e, --event=EVENT Set the com.sun:auto-snapshot-desc property to EVENT. 106 | -n, --dry-run Print actions without actually doing anything. 107 | -s, --skip-scrub Do not snapshot filesystems in scrubbing pools. 108 | -h, --help Print this usage message. 109 | -k, --keep=NUM Keep NUM recent snapshots and destroy older snapshots. 110 | -l, --label=LAB LAB is usually 'hourly', 'daily', or 'monthly' (default 111 | is 'regular'). 112 | -p, --prefix=PRE PRE is 'zfs-auto-snap' by default. 113 | -q, --quiet Suppress warnings and notices at the console. 114 | -c, --create Create missing filesystems at destination. 115 | -i, --send-at-once Send more incremental snapshots at once in one package 116 | (-i argument is passed to zfs send instead of -I). 117 | --send-full=F Send zfs full backup. F is target filesystem. 118 | --send-incr=F Send zfs incremental backup. F is target filesystem. 119 | --sep=CHAR Use CHAR to separate date stamps in snapshot names. 120 | -X, --destroy Destroy remote snapshots to allow --send-full if 121 | destination has snapshots (needed for -F in case 122 | incremental snapshots on local and remote do not match). 123 | -f is used automatically. 124 | -F, --fallback Allow fallback from --send-incr to --send-full, 125 | if incremental sending is not possible (filesystem 126 | on remote just created or snapshots do not match - 127 | see -X). 128 | -g, --syslog Write messages into the system log. 129 | -r, --recursive Snapshot named filesystem and all descendants. 130 | -R, --replication Use zfs's replication (zfs send -R) instead of simple 131 | send over newly created snapshots (check man zfs for 132 | details). -f is used automatically. 133 | -v, --verbose Print info messages. 134 | -f, --force Passes -F argument to zfs receive (e.g. makes possible 135 | to overwrite remote filesystem during --send-full). 136 | -o, --rotation Round-robin (rr) or hanoi (hanoi) rotation (if -l nor -p 137 | is specified, default label will change from 'regular' 138 | to 'hanoi_regular'). 139 | -a, --base Base unit for hanoi cycle. Can be minute, hour, day, 140 | week, month or year (should follow your cron schedule 141 | frequency). Default base is day. 142 | --local-only Parameters opt_sendtocmd and opt_buffer are not used, 143 | target for --send will be local machine. 144 | name Filesystem and volume names, or '//' for all ZFS datasets. 145 | " 146 | } 147 | 148 | 149 | print_log () # level, message, ... 150 | { 151 | LEVEL=$1 152 | shift 1 153 | 154 | case $LEVEL in 155 | (eme*) 156 | test -n "$opt_syslog" && logger -t "$opt_prefix" -p daemon.emerge "$*" 157 | echo Emergency: "$*" 1>&2 158 | ;; 159 | (ale*) 160 | test -n "$opt_syslog" && logger -t "$opt_prefix" -p daemon.alert "$*" 161 | echo Alert: "$*" 1>&2 162 | ;; 163 | (cri*) 164 | test -n "$opt_syslog" && logger -t "$opt_prefix" -p daemon.crit "$*" 165 | echo Critical: "$*" 1>&2 166 | ;; 167 | (err*) 168 | test -n "$opt_syslog" && logger -t "$opt_prefix" -p daemon.err "$*" 169 | echo Error: "$*" 1>&2 170 | ;; 171 | (war*) 172 | test -n "$opt_syslog" && logger -t "$opt_prefix" -p daemon.warning "$*" 173 | test -z "$opt_quiet" && echo Warning: "$*" 1>&2 174 | WARNING_COUNT=$(( $WARNING_COUNT + 1 )) 175 | ;; 176 | (not*) 177 | test -n "$opt_syslog" && logger -t "$opt_prefix" -p daemon.notice "$*" 178 | test -z "$opt_quiet" && echo "$*" 179 | ;; 180 | (inf*) 181 | # test -n "$opt_syslog" && logger -t "$opt_prefix" -p daemon.info "$*" 182 | test -n "$opt_verbose" && echo "$*" 183 | ;; 184 | (deb*) 185 | # test -n "$opt_syslog" && logger -t "$opt_prefix" -p daemon.debug "$*" 186 | test -n "$opt_debug" && echo Debug: "$*" 187 | ;; 188 | (*) 189 | test -n "$opt_syslog" && logger -t "$opt_prefix" "$*" 190 | echo "$*" 1>&2 191 | ;; 192 | esac 193 | } 194 | 195 | 196 | do_run () # [argv] 197 | { 198 | if [ -n "$opt_dry_run" ] 199 | then 200 | echo "... Running $*" 201 | RC="$?" 202 | else 203 | eval $* 204 | RC="$?" 205 | if [ "$RC" -eq '0' ] 206 | then 207 | print_log debug "$*" 208 | else 209 | print_log warning "$* returned $RC" 210 | fi 211 | fi 212 | return "$RC" 213 | } 214 | 215 | do_unmount () 216 | { 217 | local TYPE="$1" 218 | local FLAGS="$3" 219 | local FSNAME="$4" 220 | local SNAPNAME="$5" 221 | local rsort_cmd='0' 222 | local remote_cmd='' 223 | 224 | case "$TYPE" in 225 | (remote) 226 | umount_list="$MOUNTED_LIST_REM" 227 | remote_cmd="$opt_sendtocmd" 228 | ;; 229 | (local) 230 | umount_list="$MOUNTED_LIST_LOC" 231 | ;; 232 | esac 233 | 234 | if [ -n "$SNAPNAME" ]; then 235 | SNAPNAME="@$SNAPNAME" 236 | else 237 | rsort_cmd='1' 238 | fi 239 | umount_list=$(printf "%s\n" "$umount_list" | grep ^"$FSNAME$SNAPNAME" ) 240 | 241 | test -z "$umount_list" && return 0 242 | 243 | # reverse sort the list if unmounting filesystem and not only snapshot 244 | umount_list=$(printf "%s\n" "$umount_list" | awk -F'\t' '{print $2}') 245 | test $rsort_cmd -eq '1' && umount_list=$(printf "%s\n" "$umount_list" | sort -r) 246 | 247 | for kk in $umount_list; do 248 | print_log debug "Trying to unmount '$kk'." 249 | umount_cmd="umount '$kk'" 250 | if ! do_run "$remote_cmd" "$umount_cmd"; then return "$RC"; fi 251 | test "$FLAGS" != "-r" && break 252 | done 253 | 254 | return 0 255 | } 256 | 257 | do_delete () 258 | { 259 | 260 | local DEL_TYPE="$1" 261 | local FSSNAPNAME="$2" 262 | local FLAGS="$3" 263 | KEEP="$4" 264 | local FSNAME=$(echo $FSSNAPNAME | awk -F'@' '{print $1}') 265 | local SNAPNAME=$(echo $FSSNAPNAME | awk -F'@' '{print $2}') 266 | local remote_cmd='' 267 | 268 | if [ "$FSSNAPNAME" = "$FSNAME" -a "$FLAGS" = "-r" ]; then 269 | if [ "$opt_destroy" -ne '1' ]; then 270 | print_log warning "Filesystem $FSNAME destroy requested, but option -X not specified. Aborting." 271 | return 1 272 | else 273 | KEEP='0' 274 | fi 275 | fi 276 | 277 | KEEP=$(( $KEEP - 1 )) 278 | if [ "$KEEP" -le '0' ] 279 | then 280 | if do_unmount "$DEL_TYPE" "" "$FLAGS" "$FSNAME" "$SNAPNAME"; then 281 | if [ "$DEL_TYPE" = "remote" ]; then 282 | remote_cmd="$opt_sendtocmd" 283 | fi 284 | if do_run "$remote_cmd" "zfs destroy $FLAGS '$FSSNAPNAME'"; then 285 | DESTRUCTION_COUNT=$(( $DESTRUCTION_COUNT + 1 )) 286 | fi 287 | fi 288 | fi 289 | 290 | return "$RC" 291 | } 292 | 293 | is_member () 294 | { 295 | local ARRAY="$1" 296 | local MEMBER="$2" 297 | local ISMEMBER='1' 298 | local mm='' 299 | 300 | for mm in $ARRAY; do 301 | if test "$mm" = "$MEMBER"; then 302 | ISMEMBER='0' 303 | break 304 | fi 305 | done 306 | 307 | return "$ISMEMBER" 308 | } 309 | 310 | do_send () 311 | { 312 | local SENDTYPE="$1" 313 | local SNAPFROM="$2" 314 | local SNAPTO="$3" 315 | local SENDFLAGS="$4" 316 | local REMOTEFS="$5" 317 | local list_child='' 318 | local lq='' 319 | 320 | if [ "$SENDFLAGS" = "-R" -a "$SENDTYPE" = "full" ]; then 321 | # for full send with -R, target filesystem must be with no snapshots (including snapshots on child filesystems) 322 | list_child=$(printf "%s\n" "$SNAPSHOTS_OLD_REM" | grep ^"$REMOTEFS/" ) 323 | fi 324 | if [ "$SENDTYPE" = "full" ]; then 325 | list_child=$(printf "%s\n%s\n" "$list_child" $(printf "%s\n" "$SNAPSHOTS_OLD_REM" | grep ^"$REMOTEFS@" ) ) 326 | fi 327 | 328 | for ll in $list_child; do 329 | if [ "$opt_destroy" -eq '1' ]; then 330 | if do_delete "remote" "$ll" ""; then 331 | continue 332 | fi 333 | fi 334 | print_log debug "Can't destroy remote objects $REMOTEFS ($ll). Can't continue with send-full. -X allowed?" 335 | return 1 336 | done 337 | 338 | test -n "$opt_buffer" && lq="'" 339 | 340 | test $SENDTYPE = "incr" && do_run "zfs send " "$SENDFLAGS" "$opt_atonce $SNAPFROM $SNAPTO" "$opt_pipe" "$opt_sendtocmd" "$lq$opt_buffer zfs recv $opt_force -u $REMOTEFS$lq" 341 | test $SENDTYPE = "full" && do_run "zfs send " "$SENDFLAGS" "$SNAPTO" "$opt_pipe" "$opt_sendtocmd" "$lq$opt_buffer zfs recv $opt_force -u $REMOTEFS$lq" 342 | 343 | return "$RC" 344 | } 345 | 346 | delete_rotation_hanoi () 347 | { 348 | 349 | local SND_RC="$1" 350 | local FALLBACK="$2" 351 | local FSNAME="$3" 352 | local GLOB="$4" 353 | local FLAGS="$5" 354 | local SNAPNAME="$6" 355 | 356 | local base_minute=$((60 * $opt_factor )) 357 | local base_hour=$(($base_minute * 60)) 358 | local base_day=$(($base_hour * 24)) 359 | local base_week=$(($base_day * 7)) 360 | local base_month=$(($base_day * 31)) 361 | local base="base_$opt_base" 362 | 363 | local opt_hbase=$(eval echo \$$base) 364 | 365 | classify () 366 | { 367 | rec () 368 | { 369 | local class='0' 370 | local nr="$1" 371 | 372 | while test $(( 1 << $(($class)) )) -le $(($nr>>1)); do 373 | class=$(($class+1)) 374 | done 375 | bla=$(($nr - $(( 1 << $(($class)) )) )) 376 | test "$bla" -eq '0' && echo $(($class+1)) || rec "$bla" 377 | } 378 | 379 | local creation="$1" 380 | local creation_std='' 381 | local snapdate='' 382 | 383 | case $PLATFORM_LOC in 384 | (Linux) 385 | snapdate=$(echo "$creation" | awk -F'-' '{print $1"-"$2"-"$3" "$4}') 386 | creation_std=$(($(env LC_ALL=C date -d "$snapdate" +%s ) / $opt_hbase )) 387 | ;; 388 | (Darwin) 389 | creation_std=$(($(env LC_ALL=C date -j -f "%F-%H%M" "$creation" +%s ) / $opt_hbase )) 390 | ;; 391 | esac 392 | 393 | echo $(rec $creation_std) 394 | } 395 | 396 | destroy () 397 | { 398 | local dlist="$1" 399 | local dprefix="$2" 400 | local dtype="$3" 401 | local dFSNAME="$4" 402 | local dFLAGS="$5" 403 | local class='' 404 | local previous_class='0' 405 | 406 | tmp_table=$(printf "%s\n" "$dlist" |\ 407 | grep -e ^"$dprefix$FSNAME@$opt_prefix.$opt_label" | 408 | while read name; do 409 | echo "$(classify ${name#$dprefix$FSNAME@$opt_prefix${opt_label:+?$opt_label}?}) $name" 410 | done | sort -k 1rn -k 2r | awk '{print $1"\t"$2}') 411 | 412 | for mm in $tmp_table 413 | do 414 | class=$(echo "$mm" | awk -F'\t' '{print $1}') 415 | if [ "$class" -eq "$previous_class" ]; then 416 | do_delete "$dtype" $(echo "$mm" | awk -F'\t' '{print $2}') "$FLAGS" 417 | fi 418 | previous_class="$class" 419 | done 420 | 421 | } 422 | 423 | if [ "$SND_RC" -eq '0' -a "$opt_send" != "no" -o "$opt_send" = "no" ]; then 424 | destroy "$(printf "%s\n%s\n" "$SNAPSHOTS_OLD_LOC" "$FSNAME@$SNAPNAME" )" "" "local" "$FSNAME" "$FLAGS" 425 | if [ "$opt_send" != "no" ]; then 426 | destroy "$(printf "%s\n%s\n" "$SNAPSHOTS_OLD_REM" "$opt_sendprefix/$FSNAME@$SNAPNAME" )" "$opt_sendprefix/" "remote" "$FSNAME" "$FLAGS" 427 | fi 428 | fi 429 | 430 | } 431 | 432 | delete_rotation_rr () 433 | { 434 | 435 | local SND_RC="$1" 436 | local FALLBACK="$2" 437 | local FSNAME="$3" 438 | local GLOB="$4" 439 | local FLAGS="$5" 440 | 441 | # Retain at most $opt_keep number of old snapshots of this filesystem, 442 | # including the one that was just recently created. 443 | if [ -z "$opt_keep" ] 444 | then 445 | print_log debug "Number of snapshots not specified. Keeping all." 446 | continue 447 | elif [ "$opt_send" != "no" ] && [ "$SND_RC" -ne '0' ] 448 | then 449 | print_log debug "Sending of filesystem was requested, but send failed. Ommiting destroy procedures." 450 | continue 451 | elif [ "$opt_send" != "no" -a -n "$opt_remove" ] 452 | then 453 | KEEP="$opt_remove" 454 | else 455 | KEEP="$opt_keep" 456 | fi 457 | print_log debug "Destroying local snapshots, keeping $KEEP." 458 | 459 | # ASSERT: The old snapshot list is sorted by increasing age. 460 | for jj in $SNAPSHOTS_OLD_LOC 461 | do 462 | # Check whether this is an old snapshot of the filesystem. 463 | test -z "${jj#$FSNAME@$GLOB}" -o -z "${jj##$FSNAME@$opt_prefix*}" -a -n "$opt_remove" -a "$opt_send" != "no" && do_delete "local" "$jj" "$FLAGS" "$KEEP" 464 | done 465 | 466 | if [ "$opt_send" = "no" ] 467 | then 468 | print_log debug "No sending option specified, skipping remote snapshot removal." 469 | continue 470 | elif [ "$sFLAGS" = "-R" ] 471 | then 472 | print_log debug "Replication specified, remote snapshots were removed while sending." 473 | continue 474 | elif [ "$opt_destroy" -eq '1' -a "$FALLBACK" -ne '0' -o "$opt_send" = "full" ] 475 | then 476 | print_log debug "Sent full copy, all remote snapshots were already destroyed." 477 | continue 478 | else 479 | KEEP="$opt_keep" 480 | print_log debug "Destroying remote snapshots, keeping $KEEP." 481 | fi 482 | 483 | # ASSERT: The old snapshot list is sorted by increasing age. 484 | for jj in $SNAPSHOTS_OLD_REM 485 | do 486 | # Check whether this is an old snapshot of the filesystem. 487 | test -z "${jj#$opt_sendprefix/$FSNAME@$GLOB}" && do_delete "remote" "$jj" "$FLAGS" "$KEEP" 488 | done 489 | 490 | } 491 | 492 | do_snapshots () # properties, flags, snapname, oldglob, [targets...] 493 | { 494 | local PROPS="$1" 495 | local sFLAGS="$2" 496 | local NAME="$3" 497 | local GLOB="$4" 498 | local TARGETS="$5" 499 | local LAST_REMOTE='' 500 | 501 | local FALLBACK='' 502 | 503 | if test "$sFLAGS" = '-R'; then 504 | FLAGS='-r' 505 | SNexp='.*' 506 | else 507 | FLAGS='' 508 | fi 509 | 510 | for ii in $TARGETS 511 | do 512 | FALLBACK='0' 513 | SND_RC='1' 514 | 515 | print_log debug "--> Snapshooting $ii" 516 | 517 | if ! do_run "zfs snapshot $PROPS $FLAGS '$ii@$NAME'" 518 | then 519 | continue 520 | fi 521 | SNAPSHOT_COUNT=$(( $SNAPSHOT_COUNT + 1 )) 522 | 523 | if [ "$opt_send" = "incr" ] 524 | then 525 | 526 | LAST_REMOTE=$(printf "%s\n" "$SNAPSHOTS_OLD_REM" | grep ^"$opt_sendprefix/$ii@" | grep -m1 . | awk -F'@' '{print $2}') 527 | 528 | # in case of -R and incremental send, receiving side needs to have $LAST_REMOTE snapshot for each replicated filesystem 529 | if [ "$FLAGS" = "-r" ]; then 530 | snaps_needed=$(( $(printf "%s\n" "$ZFS_LOCAL_LIST" | grep -c ^"$ii/") + 1 )) 531 | else 532 | snaps_needed='1' 533 | fi 534 | 535 | # remote filesystem just created. if -R run 536 | if is_member "$CREATED_TARGETS" "$opt_sendprefix/$ii" 537 | then 538 | FALLBACK='2' 539 | elif [ -z "$LAST_REMOTE" ] 540 | then 541 | # no snapshot on remote 542 | FALLBACK='1' 543 | elif [ "$snaps_needed" -ne $(printf "%s" "$SNAPSHOTS_OLD_REM" | grep -c -e ^"$opt_sendprefix/$ii$SNexp@$LAST_REMOTE" ) -o \ 544 | "$snaps_needed" -ne $(printf "%s" "$SNAPSHOTS_OLD_LOC" | grep -c -e ^"$ii$SNexp@$LAST_REMOTE" ) ] 545 | then 546 | FALLBACK='3' 547 | else 548 | FALLBACK='0' 549 | fi 550 | 551 | case "$FALLBACK" in 552 | (1) 553 | print_log info "Going back to full send, no snapshot exists at destination: $ii" 554 | ;; 555 | (2) 556 | print_log info "Going back to full send, remote filesystem was just created: $ii" 557 | ;; 558 | (3) 559 | if [ "$FLAGS" = "-r" ]; then 560 | print_log info "Going back to full send, last snapshot on remote is not the last one for whole recursion: $opt_sendprefix/$ii@$LAST_REMOTE" 561 | else 562 | print_log info "Going back to full send, last snapshot on remote is not available on local: $opt_sendprefix/$ii@$LAST_REMOTE" 563 | fi 564 | ;; 565 | (0) 566 | do_send "incr" "$ii@$LAST_REMOTE" "$ii@$NAME" "$sFLAGS" "$opt_sendprefix/$ii" 567 | SND_RC="$?" 568 | ;; 569 | esac 570 | fi 571 | 572 | if [ "$opt_send" = "full" -o "$FALLBACK" -ne '0' -a "$opt_fallback" -eq '1' ]; then 573 | do_send "full" "" "$ii@$NAME" "$sFLAGS" "$opt_sendprefix/$ii" 574 | SND_RC="$?" 575 | fi 576 | test "$SND_RC" -eq '0' && SENT_COUNT=$(( $SENT_COUNT + 1 )) 577 | 578 | case $opt_rotation in 579 | (rr) 580 | delete_rotation_rr "$SND_RC" "$FALLBACK" "$ii" "$GLOB" "$FLAGS" 581 | ;; 582 | (hanoi) 583 | delete_rotation_hanoi "$SND_RC" "$FALLBACK" "$ii" "$GLOB" "$FLAGS" "$NAME" 584 | ;; 585 | esac 586 | 587 | done 588 | } 589 | 590 | do_getmountedfs () 591 | { 592 | 593 | local MOUNTED_TYPE="$1" 594 | local MOUNTED_LIST='' 595 | local remote_cmd='' 596 | 597 | case "$MOUNTED_TYPE" in 598 | (remote) 599 | remote_cmd="$opt_sendtocmd" 600 | PLATFORM="$PLATFORM_REM" 601 | ;; 602 | (local) 603 | PLATFORM="$PLATFORM_LOC" 604 | ;; 605 | esac 606 | 607 | case "$PLATFORM" in 608 | (Linux) 609 | MOUNTED_LIST=$(eval $remote_cmd cat /proc/mounts | grep zfs | awk -F' ' '{OFS="\t"}{ORS="\n"}{print $1,$2}' ) 610 | ;; 611 | (Darwin) 612 | MOUNTED_LIST=$(printf "%s\n%s\n" $(eval $remote_cmd zfs mount | awk -F' ' '{OFS="\t"}{ORS="\n"}{print $1,$2}') \ 613 | $(eval $remote_cmd mount -t zfs | grep @ | awk -F' ' '{OFS="\t"}{ORS="\n"}{print $1,$3}') ) 614 | ;; 615 | esac 616 | 617 | printf "%s\n" "$MOUNTED_LIST" | sort 618 | } 619 | 620 | do_createfs () 621 | { 622 | 623 | local FS="$1" 624 | 625 | for ii in $FS; do 626 | 627 | print_log debug "checking: $opt_sendprefix/$ii" 628 | 629 | if ! is_member "$ZFS_REMOTE_LIST" "$opt_sendprefix/$ii" -eq 0 630 | then 631 | print_log debug "creating: $opt_sendprefix/$ii" 632 | 633 | if do_run "$opt_sendtocmd" "zfs create -p -o canmount=off -o snapdir=hidden $opt_sendprefix/$ii" 634 | then 635 | CREATION_COUNT=$(( $CREATION_COUNT + 1 )) 636 | CREATED_TARGETS=$(printf "%s\n%s\n" "$CREATED_TARGETS" "$opt_sendprefix/$ii" ) 637 | fi 638 | fi 639 | done 640 | 641 | } 642 | 643 | # main () 644 | # { 645 | 646 | PLATFORM_LOC=`uname` 647 | case "$PLATFORM_LOC" in 648 | (Linux) 649 | getopt_cmd='getopt' 650 | ;; 651 | (Darwin) 652 | # macports path as default. Homebrew as fallback 653 | getopt_cmd='/opt/local/bin/getopt' 654 | if [ ! -f $getopt_cmd ]; then 655 | getopt_cmd='/usr/local/opt/gnu-getopt/bin/getopt' 656 | fi 657 | ;; 658 | (*) 659 | print_log error "Local system not known ($PLATFORM_LOC) - needs one of Darwin, Linux. Exiting." 660 | exit 300 661 | ;; 662 | esac 663 | 664 | GETOPT=$("$getopt_cmd" \ 665 | --longoptions=default-exclude,dry-run,skip-scrub,recursive,send-atonce,rotation:,local-only \ 666 | --longoptions=event:,keep:,label:,prefix:,sep:,create,fallback,rollback,base:,factor: \ 667 | --longoptions=debug,help,quiet,syslog,verbose,send-full:,send-incr:,remove-local:,destroy,include-all \ 668 | --options=dnshe:l:k:p:rs:qgvfixcXFRba:o: \ 669 | -- "$@" ) \ 670 | || exit 128 671 | 672 | eval set -- "$GETOPT" 673 | 674 | while [ "$#" -gt '0' ] 675 | do 676 | case "$1" in 677 | (-d|--debug) 678 | opt_debug='1' 679 | opt_quiet='' 680 | opt_verbose='1' 681 | shift 1 682 | ;; 683 | (-c|--create) 684 | opt_create='1' 685 | shift 1 686 | ;; 687 | (-x|--default-exclude) 688 | opt_default_exclude='1' 689 | shift 1 690 | ;; 691 | (-e|--event) 692 | if [ "${#2}" -gt '1024' ] 693 | then 694 | print_log error "The $1 parameter must be less than 1025 characters." 695 | exit 239 696 | elif [ "${#2}" -gt '0' ] 697 | then 698 | opt_event="$2" 699 | fi 700 | shift 2 701 | ;; 702 | (-n|--dry-run) 703 | opt_dry_run='1' 704 | shift 1 705 | ;; 706 | (-s|--skip-scrub) 707 | opt_skip_scrub='1' 708 | shift 1 709 | ;; 710 | (-h|--help) 711 | print_usage 712 | exit 0 713 | ;; 714 | (--local-only) 715 | opt_sendtocmd='' 716 | opt_buffer='' 717 | shift 1 718 | ;; 719 | (--include-all) 720 | opt_includeall='1' 721 | print_log debug "Not considering com.sun:auto-snapshot." 722 | shift 1 723 | ;; 724 | (-k|--keep) 725 | if ! test "$2" -gt '0' 2>/dev/null 726 | then 727 | print_log error "The $1 parameter must be a positive integer." 728 | exit 229 729 | fi 730 | opt_keep="$2" 731 | shift 2 732 | ;; 733 | (-a|--base) 734 | case $2 in 735 | (day|week|month|hour|minute) 736 | opt_base="$2" 737 | ;; 738 | (*) 739 | print_log error "The $1 parameter must be one of: minute, hour, day, week, month, year." 740 | exit 244 741 | ;; 742 | esac 743 | shift 2 744 | ;; 745 | (-o|--rotation) 746 | case $2 in 747 | (hanoi|rr) 748 | opt_rotation="$2" 749 | ;; 750 | (*) 751 | print_log error "Rotation must be one of hanoi or rr 752 | ." 753 | exit 245 754 | ;; 755 | esac 756 | shift 2 757 | ;; 758 | (-l|--label) 759 | opt_label="$2" 760 | opt_namechange='1' 761 | shift 2 762 | ;; 763 | (-p|--prefix) 764 | opt_prefix="$2" 765 | while test "${#opt_prefix}" -gt '0' 766 | do 767 | case $opt_prefix in 768 | ([![:alnum:]_.:\ -]*) 769 | print_log error "The $1 parameter must be alphanumeric." 770 | exit 230 771 | ;; 772 | esac 773 | opt_prefix="${opt_prefix#?}" 774 | done 775 | opt_prefix="$2" 776 | opt_namechange='1' 777 | shift 2 778 | ;; 779 | (--factor) 780 | opt_factor="$2" 781 | shift 2 782 | ;; 783 | (-q|--quiet) 784 | opt_debug='' 785 | opt_quiet='1' 786 | opt_verbose='' 787 | shift 1 788 | ;; 789 | (-r|--recursive) 790 | opt_recursive=' ' 791 | shift 1 792 | ;; 793 | (-R|--replication) 794 | opt_recursive='-R' 795 | opt_force='-F' 796 | shift 1 797 | ;; 798 | (-X|--destroy) 799 | opt_destroy='1' 800 | opt_force='-F' 801 | shift 1 802 | ;; 803 | (-F|--fallback) 804 | opt_fallback='1' 805 | shift 1 806 | ;; 807 | (--sep) 808 | case "$2" in 809 | ([[:alnum:]_.:\ -]) 810 | : 811 | ;; 812 | ('') 813 | print_log error "The $1 parameter must be non-empty." 814 | exit 231 815 | ;; 816 | (*) 817 | print_log error "The $1 parameter must be one alphanumeric character." 818 | exit 232 819 | ;; 820 | esac 821 | opt_sep="$2" 822 | shift 2 823 | ;; 824 | (--send-full) 825 | if [ -n "$opt_sendprefix" ]; then 826 | print_log error "Only one of --send-incr and --send-full must be specified." 827 | exit 239 828 | fi 829 | if [ -z "$2" ]; then 830 | print_log error "Target filesystem needs to be specified with --send-full." 831 | exit 243 832 | fi 833 | opt_sendprefix="$2" 834 | opt_send='full' 835 | shift 2 836 | ;; 837 | (--send-incr) 838 | opt_sendincr="$2" 839 | if [ -n "$opt_sendprefix" ]; then 840 | print_log error "Only one of --send-incr and --send-full must be specified." 841 | exit 240 842 | fi 843 | if [ -z "$2" ]; then 844 | print_log error "Target filesystem needs to be specified with --send-incr." 845 | exit 242 846 | fi 847 | opt_sendprefix="$2" 848 | opt_send='incr' 849 | shift 2 850 | ;; 851 | (-g|--syslog) 852 | opt_syslog='1' 853 | shift 1 854 | ;; 855 | (-i|--send-atonce) 856 | opt_atonce='-i' 857 | shift 1 858 | ;; 859 | (--remove-local) 860 | if ! test "$2" -gt '0' 2>/dev/null 861 | then 862 | print_log error "The $1 parameter must be a positive integer." 863 | exit 241 864 | fi 865 | opt_remove="$2" 866 | shift 2 867 | ;; 868 | (-v|--verbose) 869 | opt_quiet='' 870 | opt_verbose='1' 871 | shift 1 872 | ;; 873 | (-f|--force|-b|--rollback) 874 | opt_force='-F' 875 | shift 1 876 | ;; 877 | (--) 878 | shift 1 879 | break 880 | ;; 881 | esac 882 | done 883 | 884 | if [ "$#" -eq '0' ] 885 | then 886 | print_log error "The filesystem argument list is empty." 887 | exit 133 888 | fi 889 | 890 | # ISO style date; fifteen characters: YYYY-MM-DD-HHMM 891 | # On Solaris %H%M expands to 12h34. 892 | DATE=$(date +%F-%H%M) 893 | 894 | COUNTER='0' 895 | while true; do 896 | if do_run "mkdir '${tmp_dir}'"; then break; fi 897 | print_log error "another copy is running ... $COUNTER" 898 | test "$COUNTER" -gt '11' && exit 99 899 | sleep 5 900 | COUNTER=$(( $COUNTER + 1 )) 901 | done 902 | trap "rm -fr '${tmp_dir}'" INT TERM EXIT 903 | 904 | # Count the number of times '//' appears on the command line. 905 | SLASHIES='0' 906 | for ii in "$@" 907 | do 908 | test "$ii" = '//' && SLASHIES=$(( $SLASHIES + 1 )) 909 | done 910 | 911 | if [ "$#" -gt '1' -a "$SLASHIES" -gt '0' ] 912 | then 913 | print_log error "The // must be the only argument if it is given." 914 | exit 134 915 | fi 916 | 917 | # These are the only times that `zpool status` or `zfs list` are invoked, so 918 | # this program for Linux has a much better runtime complexity than the similar 919 | # Solaris implementation. 920 | 921 | ZPOOL_STATUS=$(env LC_ALL=C zpool status 2>&1 )\ 922 | || { print_log error "zpool status $?: $ZPOOL_STATUS"; exit 135; } 923 | 924 | ZFS_LIST=$(env LC_ALL=C zfs list -H -t filesystem,volume -s name\ 925 | -o name,com.sun:auto-snapshot,com.sun:auto-snapshot:"$opt_label",mountpoint,canmount,snapdir)\ 926 | || { print_log error "zfs list $?: $ZFS_LIST"; exit 136; } 927 | 928 | ZFS_LOCAL_LIST=$(echo "$ZFS_LIST" | awk -F'\t' '{print $1}') 929 | 930 | # Verify that each argument is a filesystem or volume. 931 | for ii in "$@" 932 | do 933 | test "$ii" = '//' && continue 1 934 | for jj in $ZFS_LOCAL_LIST 935 | do 936 | test "$ii" = "$jj" && continue 2 937 | done 938 | print_log error "$ii is not a ZFS filesystem or volume." 939 | exit 138 940 | done 941 | 942 | # Get a list of pools that are being scrubbed. 943 | ZPOOLS_SCRUBBING=$(echo "$ZPOOL_STATUS" | awk -F ': ' \ 944 | '$1 ~ /^ *pool$/ { pool = $2 } ; \ 945 | $1 ~ /^ *scan$/ && $2 ~ /scrub in progress/ { print pool }' \ 946 | | sort ) 947 | 948 | # Get a list of pools that cannot do a snapshot. 949 | ZPOOLS_NOTREADY=$(echo "$ZPOOL_STATUS" | awk -F ': ' \ 950 | '$1 ~ /^ *pool$/ { pool = $2 } ; \ 951 | $1 ~ /^ *state$/ && $2 !~ /ONLINE|DEGRADED/ { print pool } ' \ 952 | | sort ) 953 | 954 | # If the --default-exclude flag is set, then exclude all datasets that lack 955 | # an explicit com.sun:auto-snapshot* property. Otherwise, include them. 956 | if [ "$opt_includeall" -eq '0' ]; then 957 | # Get a list of datasets for which snapshots are not explicitly disabled. 958 | CANDIDATES=$(echo "$ZFS_LIST" | awk -F '\t' \ 959 | 'tolower($2) !~ /false/ && tolower($3) !~ /false/ {print $1}' ) 960 | 961 | if [ -n "$opt_default_exclude" ] 962 | then 963 | # Get a list of datasets for which snapshots are not explicitly enabled. 964 | NOAUTO=$(echo "$ZFS_LIST" | awk -F '\t' \ 965 | 'tolower($2) !~ /true/ && tolower($3) !~ /true/ {print $1}') 966 | else 967 | # Get a list of datasets for which snapshots are explicitly disabled. 968 | NOAUTO=$(echo "$ZFS_LIST" | awk -F '\t' \ 969 | 'tolower($2) ~ /false/ || tolower($3) ~ /false/ {print $1}') 970 | fi 971 | else 972 | CANDIDATES=$(echo "$ZFS_LIST" | awk -F '\t' '{print $1}') 973 | fi 974 | 975 | # Initialize the list of datasets that will get a recursive snapshot. 976 | TARGETS_DRECURSIVE='' 977 | TARGETS_TMP_RECURSIVE='' 978 | 979 | # Initialize the list of datasets that will get a non-recursive snapshot. 980 | TARGETS_DREGULAR='' 981 | 982 | for ii in $CANDIDATES 983 | do 984 | # Qualify dataset names so variable globbing works properly. 985 | # Suppose ii=tanker/foo and jj=tank sometime during the loop. 986 | # Just testing "$ii" != ${ii#$jj} would incorrectly match. 987 | iii="$ii/" 988 | 989 | # Exclude datasets that are not named on the command line. 990 | IN_ARGS='0' 991 | for jj in "$@" 992 | do 993 | if [ "$jj" = '//' -o "$jj" = "$ii" -o -n "$opt_recursive" -a -z "${ii##$jj/*}" ] 994 | then 995 | IN_ARGS=$(( $IN_ARGS + 1 )) 996 | fi 997 | done 998 | if [ "$IN_ARGS" -eq '0' ] 999 | then 1000 | continue 1001 | fi 1002 | 1003 | # Exclude datasets in pools that cannot do a snapshot. 1004 | for jj in $ZPOOLS_NOTREADY 1005 | do 1006 | # Ibid regarding iii. 1007 | jjj="$jj/" 1008 | 1009 | # Check whether the pool name is a prefix of the dataset name. 1010 | if [ "$iii" != "${iii#$jjj}" ] 1011 | then 1012 | print_log info "Excluding $ii because pool $jj is not ready." 1013 | continue 2 1014 | fi 1015 | done 1016 | 1017 | # Exclude datasets in scrubbing pools if the --skip-scrub flag is set. 1018 | test -n "$opt_skip_scrub" && for jj in $ZPOOLS_SCRUBBING 1019 | do 1020 | # Ibid regarding iii. 1021 | jjj="$jj/" 1022 | 1023 | # Check whether the pool name is a prefix of the dataset name. 1024 | if [ "$iii" != "${iii#$jjj}" ] 1025 | then 1026 | print_log info "Excluding $ii because pool $jj is scrubbing." 1027 | continue 2 1028 | fi 1029 | done 1030 | 1031 | noauto_parent='0' 1032 | for jj in $NOAUTO 1033 | do 1034 | # Ibid regarding iii. 1035 | jjj="$jj/" 1036 | 1037 | if [ "$jjj" = "$iii" ] 1038 | then 1039 | continue 2 1040 | # Check whether the candidate name is a prefix of any excluded dataset name. 1041 | elif [ "$jjj" != "${jjj#$iii}" ] 1042 | then 1043 | noauto_parent='1' && break 1044 | fi 1045 | done 1046 | 1047 | # not scrubbing 1048 | if [ -z "$opt_recursive" -a "$1" != '//' -o "$noauto_parent" = '1' ] 1049 | then 1050 | print_log debug "Including $ii for regular snapshot." 1051 | TARGETS_DREGULAR=$(printf "%s\n%s\n" "$TARGETS_DREGULAR" "$ii" ) 1052 | continue 1053 | fi 1054 | 1055 | for jj in $TARGETS_TMP_RECURSIVE 1056 | do 1057 | # Ibid regarding iii. 1058 | jjj="$jj/" 1059 | 1060 | # Check whether any included dataset is a prefix of the candidate name. 1061 | if [ "$iii" != "${iii#$jjj}" ] 1062 | then 1063 | print_log debug "Excluding $ii because $jj includes it recursively." 1064 | continue 2 1065 | fi 1066 | done 1067 | 1068 | # Append this candidate to the recursive snapshot list because it: 1069 | # 1070 | # * Does not have an exclusionary property. 1071 | # * Is in a pool that can currently do snapshots. 1072 | # * Does not have an excluded descendent filesystem. 1073 | # * Is not the descendant of an already included filesystem. 1074 | # 1075 | print_log debug "Including $ii for recursive snapshot." 1076 | TARGETS_TMP_RECURSIVE=$( printf "%s\n%s\n" $TARGETS_TMP_RECURSIVE "$ii" ) 1077 | 1078 | done 1079 | 1080 | # Linux lacks SMF and the notion of an FMRI event, but always set this property 1081 | # because the SUNW program does. The dash character is the default. 1082 | SNAPPROP="-o com.sun:auto-snapshot-desc='$opt_event'" 1083 | 1084 | # if hanoi rotation was requested but prefix or label wasn't changed from default, change label to hanoi to avoid mixing of those backup sets. 1085 | if [ "$opt_namechange" -eq '0' ] && [ "$opt_rotation" = "hanoi" ]; then 1086 | opt_label="hanoi_regular" 1087 | fi 1088 | 1089 | # The snapshot name after the @ symbol. 1090 | SNAPNAME="$opt_prefix${opt_label:+$opt_sep$opt_label-$DATE}" 1091 | 1092 | # The expression for matching old snapshots. -YYYY-MM-DD-HHMM 1093 | SNAPGLOB="$opt_prefix${opt_label:+?$opt_label}????????????????" 1094 | 1095 | msg_to_log="Using $opt_rotation type rotation, with params keep: $opt_keep" 1096 | if test "$opt_rotation" = "hanoi"; then 1097 | msg_to_log=$(echo "$msg_to_log," "base: $opt_base") 1098 | fi 1099 | print_log debug "$msg_to_log." 1100 | 1101 | test -n "$TARGETS_DREGULAR" && \ 1102 | print_log info "Doing regular snapshots of $(echo $TARGETS_DREGULAR)" 1103 | 1104 | test -n "$TARGETS_TMP_RECURSIVE" && \ 1105 | print_log info "Doing recursive snapshots of $(echo $TARGETS_TMP_RECURSIVE)" 1106 | 1107 | SNAPSHOTS_OLD_LOC=$(env LC_ALL=C zfs list -r -H -t snapshot -S creation -o name $(echo "$TARGETS_DREGULAR") $(echo "$TARGETS_TMP_RECURSIVE") ) \ 1108 | || { print_log error "zfs list $?: $SNAPSHOTS_OLD_LOC"; exit 137; } 1109 | 1110 | test -n "$opt_dry_run" \ 1111 | && print_log info "Doing a dry run. Not running these commands..." 1112 | 1113 | # expand FS list if replication is not used 1114 | if [ "$opt_recursive" = ' ' -o "$1" = "//" ] 1115 | then 1116 | for ii in $TARGETS_TMP_RECURSIVE; do TARGETS_DRECURSIVE=$(printf "%s\n%s\n%s\n" "$TARGETS_DRECURSIVE" $(printf "$ii\n") $(printf "%s\n" "$ZFS_LOCAL_LIST" | grep ^"$ii/") ); done 1117 | else 1118 | TARGETS_DRECURSIVE="$TARGETS_TMP_RECURSIVE" 1119 | fi 1120 | 1121 | MOUNTED_LIST_LOC=$(eval do_getmountedfs "local") 1122 | 1123 | # initialize remote system parameters, filesystems, mounts and snapshots 1124 | if [ "$opt_send" != "no" ] 1125 | then 1126 | PLATFORM_REM=$(eval "$opt_sendtocmd" "uname") 1127 | 1128 | case "$PLATFORM_REM" in 1129 | (Linux|Darwin|SunOS|FreeBSD) 1130 | ;; 1131 | (*) 1132 | print_log error "Remote system not known ($PLATFORM_REM) - needs one of Darwin, Linux, SunOS, FreeBSD. Exiting." 1133 | exit 301 1134 | ;; 1135 | esac 1136 | 1137 | if [ -n $opt_limit ]; then 1138 | runs='1' 1139 | condition='1' 1140 | while [ $condition -eq '1' ]; do 1141 | load=$(eval "$opt_sendtocmd" "uptime") 1142 | load=$(echo ${load##*"load average"} | awk '{print $2}' | awk -F'.' '{print $1}') 1143 | if [ $load -ge $opt_limit -a $runs -lt '3' ]; then 1144 | print_log warning "Over load limit on remote machine. Going for sleep for 5 minutes. (run #$runs, load still $load)" 1145 | sleep 300 1146 | else 1147 | if [ $load -ge $opt_limit ]; then 1148 | opt_send="no" 1149 | opt_keep='' 1150 | print_log warning "Over load limit on remote machine. Will not send to remote. (run #$runs, load still $load)" 1151 | fi 1152 | condition='0' 1153 | fi 1154 | runs=$(( $runs + 1 )) 1155 | done 1156 | fi 1157 | fi 1158 | 1159 | if [ "$opt_send" != "no" ]; then 1160 | MOUNTED_LIST_REM=$(eval do_getmountedfs "remote") 1161 | 1162 | ZFS_REMOTE_LIST=$(eval "$opt_sendtocmd" zfs list -H -t filesystem,volume -s name -o name) \ 1163 | || { print_log error "$opt_sendtocmd zfs list $?: $ZFS_REMOTE_LIST"; exit 139; } 1164 | 1165 | if [ "$opt_create" -eq '1' ]; then 1166 | do_createfs "$TARGETS_DREGULAR" 1167 | do_createfs "$TARGETS_DRECURSIVE" 1168 | fi 1169 | 1170 | SNAPSHOTS_OLD_REM=$(eval "$opt_sendtocmd" zfs list -r -H -t snapshot -S creation -o name "$opt_sendprefix") \ 1171 | || { print_log error "zfs remote list $?: $SNAPSHOTS_OLD_REM"; exit 140; } 1172 | fi 1173 | 1174 | 1175 | do_snapshots "$SNAPPROP" "" "$SNAPNAME" "$SNAPGLOB" "$TARGETS_DREGULAR" 1176 | do_snapshots "$SNAPPROP" "$opt_recursive" "$SNAPNAME" "$SNAPGLOB" "$TARGETS_DRECURSIVE" 1177 | 1178 | print_log notice "@$SNAPNAME," \ 1179 | "$SNAPSHOT_COUNT created snapshots," \ 1180 | "$SENT_COUNT sent snapshots," \ 1181 | "$DESTRUCTION_COUNT destroyed," \ 1182 | "$CREATION_COUNT created filesystems," \ 1183 | "$WARNING_COUNT warnings." 1184 | 1185 | exit 0 1186 | # } 1187 | --------------------------------------------------------------------------------