pax_global_header00006660000000000000000000000064147617767470014544gustar00rootroot0000000000000052 comment=7785e41995c74ea4825c3fd295b8e1eee46e905b seadrive-fuse-3.0.13/000077500000000000000000000000001476177674700143725ustar00rootroot00000000000000seadrive-fuse-3.0.13/.gitignore000066400000000000000000000044351476177674700163700ustar00rootroot00000000000000*~ *.bak *.o *.exe cscope* *# Makefile.in ltmain.sh libtool *.lo *.la install-sh depcomp config.guess config.h config.log config.status config.sub config.cache configure */.deps autom4te* po/POTFILES po/Makefile* po/stamp-it po/*.gmo po/*.pot missing mkinstalldirs stamp-h1 *.libs/ Makefile aclocal.m4 *core m4/intltool.m4 m4/libtool.m4 m4/ltoptions.m4 m4/ltsugar.m4 m4/ltversion.m4 m4/lt~obsolete.m4 ccnet-*.tar.gz config.h.in py-compile intltool-extract.in intltool-merge.in intltool-update.in *.stamp *.pyc *.tmp.ui *.defs *.log .deps *.db *.dll *.aps *.so build-stamp debian/files debian/seafile debian/*.substvars lib/searpc-marshal.h lib/searpc-signature.h lib/*.tmp lib/dir.c lib/dirent.c lib/seafile-object.h lib/task.c lib/webaccess.c lib/branch.c lib/commit.c lib/crypt.c lib/repo.c lib/copy-task.c app/seafile app/seafserv-tool daemon/seaf-daemon gui/gtk/seafile-applet server/seaf-server server/gc/seafserv-gc monitor/seaf-mon controller/seafile-controller fileserver/fileserver tests/common-conf.sh tools/seaf-server-init *.mo seafile-web tests/basic/conf1/c882e263e9d02c63ca6b61c68508761cbc74c358.peer tests/basic/conf1/c882e263e9d02c63ca6b61c68508761cbc74c358.user tests/basic/conf1/group-db/ tests/basic/conf1/peer-db/ tests/basic/conf1/user-db/ tests/basic/conf2/376cf9b6ef33a6839cf1fc096131893b5ecc673f.peer tests/basic/conf2/376cf9b6ef33a6839cf1fc096131893b5ecc673f.user tests/basic/conf2/group-db/ tests/basic/conf2/peer-db/ tests/basic/conf2/user-db/ tests/basic/conf3/1e5b5e0f49010b94aa6c2995a6e7b7cba462d388.peer tests/basic/conf3/1e5b5e0f49010b94aa6c2995a6e7b7cba462d388.user tests/basic/conf3/group-db/ tests/basic/conf3/peer-db/ tests/basic/conf3/user-db/ tests/basic/conf4/93ae3e01eea6667cbdd03c4afde413ccd9f1eb43.peer tests/basic/conf4/93ae3e01eea6667cbdd03c4afde413ccd9f1eb43.user tests/basic/conf4/peer-db/ tests/basic/conf4/user-db/ web/local_settings.py gui/win/applet-po-gbk.h *.dylib .DS_Store gui/mac/seafile/build/ gui/mac/seafile/ccnet gui/mac/seafile/seaf-daemon gui/mac/seafile/seafileweb.app web/build/ web/dist/ tests/basic/conf2/misc/ tests/basic/conf2/seafile-data/ tests/test-cdc tests/test-index tests/test-seafile-fmt *.pc seaf-fsck *.tar.gz fuse/seaf-fuse server/gc/seaf-migrate /compile /test-driver *.dmp /symbols src/seadrive *.wixobj *.msi /account.conf /x64 /.vs /*.vcxproj.user seadrive-fuse-3.0.13/LICENSE000066400000000000000000001045151476177674700154050ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 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 . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . 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 . seadrive-fuse-3.0.13/Makefile.am000066400000000000000000000005511476177674700164270ustar00rootroot00000000000000SUBDIRS = src python DIST_SUBDIRS = src python INTLTOOL = \ intltool-extract.in \ intltool-merge.in \ intltool-update.in # EXTRA_DIST = install-sh $(INTLTOOL) README.markdown scripts debian msi LICENSE.txt EXTRA_DIST = install-sh $(INTLTOOL) scripts msi debian dmg ACLOCAL_AMFLAGS = -I m4 dist-hook: git log --format='%H' -1 > $(distdir)/latest_commit seadrive-fuse-3.0.13/README.md000066400000000000000000000040551476177674700156550ustar00rootroot00000000000000# seadrive-fuse SeaDrive daemon with FUSE interface # Building ## Ubuntu Linux ``` sudo apt-get install autoconf automake libtool libevent-dev libcurl4-openssl-dev libgtk2.0-dev uuid-dev intltool libsqlite3-dev valac libjansson-dev libssl-dev ``` First, you shoud get the latest source of [libsearpc](https://github.com/haiwen/libsearpc) with `v3.2-latest` tag and [seadrive-fuse](https://github.com/haiwen/seadrive-fuse). To build [seadrive-fuse](https://github.com/haiwen/seadrive-fuse), you need first build [libsearpc](https://github.com/haiwen/libsearpc). ### libsearpc ``` git clone --branch=v3.2-latest https://github.com/haiwen/libsearpc.git cd libsearpc ./autogen.sh ./configure make sudo make install ``` To build [seadrive-fuse](https://github.com/haiwen/seadrive-fuse), you need build [libwebsockets](https://github.com/warmcat/libwebsockets). ### libwebsockets Also you can build a higher version of libwebsockets by yourself. Since version 2.0.28, SeaDrive requires libwebsockets version >= 4.0.20. If the version provided by your distribution is lower than the required version, you can choose to disable this function by set --enable-ws to no. You can also build a higher version of libwebsockets by yourself. ``` git clone --branch=v4.3.0 https://github.com/warmcat/libwebsockets cd libwebsockets mkdir build cd build cmake .. make sudo make install ``` ### seadrive-fuse ``` git clone https://github.com/haiwen/seadrive-fuse.git cd seadrive-fuse ./autogen.sh ./configure make sudo make install ``` **Note:** If you plan to package for distribution, you should compile with the latest tag instead of the master branch. Sometimes the latest tag for seadrive-gui project (https://github.com/haiwen/seadrive-gui) is higher than the tag here. In such case you should follow the tag in this project. For example, the latest tag for seadrive-fuse is v2.0.6 while for seadrive-gui is v2.0.7. You should build the package based on v2.0.6 tag from seadrive-gui too. This is because sometimes there is no update related to Linux in the new versions so this project is not updated. seadrive-fuse-3.0.13/autogen.sh000077500000000000000000000065241476177674700164020ustar00rootroot00000000000000#!/bin/bash # Run this to generate all the initial makefiles, etc. : ${AUTOCONF=autoconf} : ${AUTOHEADER=autoheader} : ${AUTOMAKE=automake} : ${ACLOCAL=aclocal} if test "$(uname)" != "Darwin"; then : ${LIBTOOLIZE=libtoolize} else : ${LIBTOOLIZE=glibtoolize} fi : ${INTLTOOLIZE=intltoolize} : ${LIBTOOL=libtool} srcdir=`dirname $0` test -z "$srcdir" && srcdir=. ORIGDIR=`pwd` cd $srcdir PROJECT=ccnet TEST_TYPE=-f FILE=net/main.c CONFIGURE=configure.ac DIE=0 ($AUTOCONF --version) < /dev/null > /dev/null 2>&1 || { echo echo "You must have autoconf installed to compile $PROJECT." echo "Download the appropriate package for your distribution," echo "or get the source tarball at ftp://ftp.gnu.org/pub/gnu/" DIE=1 } (grep "^AC_PROG_INTLTOOL" $srcdir/$CONFIGURE >/dev/null) && { ($INTLTOOLIZE --version) < /dev/null > /dev/null 2>&1 || { echo echo "You must have \`intltoolize' installed to compile $PROJECT." echo "Get ftp://ftp.gnome.org/pub/GNOME/stable/sources/intltool/intltool-0.22.tar.gz" echo "(or a newer version if it is available)" DIE=1 } } ($AUTOMAKE --version) < /dev/null > /dev/null 2>&1 || { echo echo "You must have automake installed to compile $PROJECT." echo "Get ftp://sourceware.cygnus.com/pub/automake/automake-1.7.tar.gz" echo "(or a newer version if it is available)" DIE=1 } if test "$(uname)" != "Darwin"; then (grep "^AC_PROG_LIBTOOL" $CONFIGURE >/dev/null) && { ($LIBTOOL --version) < /dev/null > /dev/null 2>&1 || { echo echo "**Error**: You must have \`libtool' installed to compile $PROJECT." echo "Get ftp://ftp.gnu.org/pub/gnu/libtool-1.4.tar.gz" echo "(or a newer version if it is available)" DIE=1 } } fi if grep "^AM_[A-Z0-9_]\{1,\}_GETTEXT" "$CONFIGURE" >/dev/null; then if grep "sed.*POTFILES" "$CONFIGURE" >/dev/null; then GETTEXTIZE="" else if grep "^AM_GLIB_GNU_GETTEXT" "$CONFIGURE" >/dev/null; then GETTEXTIZE="glib-gettextize" GETTEXTIZE_URL="ftp://ftp.gtk.org/pub/gtk/v2.0/glib-2.0.0.tar.gz" else GETTEXTIZE="gettextize" GETTEXTIZE_URL="ftp://alpha.gnu.org/gnu/gettext-0.10.35.tar.gz" fi $GETTEXTIZE --version < /dev/null > /dev/null 2>&1 if test $? -ne 0; then echo echo "**Error**: You must have \`$GETTEXTIZE' installed to compile $PKG_NAME." echo "Get $GETTEXTIZE_URL" echo "(or a newer version if it is available)" DIE=1 fi fi fi if test "$DIE" -eq 1; then exit 1 fi dr=`dirname .` echo processing $dr aclocalinclude="$aclocalinclude -I m4" if test x"$MSYSTEM" = x"MINGW32"; then aclocalinclude="$aclocalinclude -I /mingw32/share/aclocal" elif test "$(uname)" = "Darwin"; then aclocalinclude="$aclocalinclude -I /opt/local/share/aclocal" fi echo "Creating $dr/aclocal.m4 ..." test -r $dr/aclocal.m4 || touch $dr/aclocal.m4 echo "Running glib-gettextize... Ignore non-fatal messages." echo "no" | glib-gettextize --force --copy echo "Making $dr/aclocal.m4 writable ..." test -r $dr/aclocal.m4 && chmod u+w $dr/aclocal.m4 echo "Running intltoolize..." intltoolize --copy --force --automake echo "Running $LIBTOOLIZE..." $LIBTOOLIZE --force --copy echo "Running $ACLOCAL $aclocalinclude ..." $ACLOCAL $aclocalinclude echo "Running $AUTOHEADER..." $AUTOHEADER echo "Running $AUTOMAKE --gnu $am_opt ..." $AUTOMAKE --add-missing --gnu $am_opt echo "Running $AUTOCONF ..." $AUTOCONF seadrive-fuse-3.0.13/configure.ac000066400000000000000000000147151476177674700166700ustar00rootroot00000000000000dnl Process this file with autoconf to produce a configure script. AC_PREREQ(2.61) AC_INIT([seadrive], [3.0.13], [jonathan.xu@seafile.com]) AC_CONFIG_HEADER([config.h]) AC_CONFIG_MACRO_DIR([m4]) AM_INIT_AUTOMAKE([1.9 foreign]) AC_CANONICAL_BUILD dnl enable the build of share library by default AC_ENABLE_SHARED AC_SUBST(LIBTOOL_DEPS) # Checks for programs. AC_PROG_CC #AM_C_PROTOTYPES AC_C_CONST AC_PROG_MAKE_SET # AC_PROG_RANLIB LT_INIT # Checks for headers. #AC_CHECK_HEADERS([arpa/inet.h fcntl.h inttypes.h libintl.h limits.h locale.h netdb.h netinet/in.h stdint.h stdlib.h string.h strings.h sys/ioctl.h sys/socket.h sys/time.h termios.h unistd.h utime.h utmp.h]) # Checks for typedefs, structures, and compiler characteristics. AC_SYS_LARGEFILE # Checks for library functions. #AC_CHECK_FUNCS([alarm dup2 ftruncate getcwd gethostbyname gettimeofday memmove memset mkdir rmdir select setlocale socket strcasecmp strchr strdup strrchr strstr strtol uname utime strtok_r sendfile]) # check platform AC_MSG_CHECKING(for WIN32) if test "$build_os" = "mingw32" -o "$build_os" = "mingw64"; then bwin32=true AC_MSG_RESULT(compile in mingw) else AC_MSG_RESULT(no) fi AC_MSG_CHECKING(for Mac) if test "$(uname)" = "Darwin"; then bmac=true AC_MSG_RESULT(compile in mac) else AC_MSG_RESULT(no) fi AC_MSG_CHECKING(for Linux) if test "$bmac" != "true" -a "$bwin32" != "true"; then blinux=true AC_MSG_RESULT(compile in linux) else AC_MSG_RESULT(no) fi AM_CONDITIONAL([WIN32], [test "$bwin32" = "true"]) AM_CONDITIONAL([MACOS], [test "$bmac" = "true"]) AM_CONDITIONAL([LINUX], [test "$blinux" = "true"]) # check libraries if test "$bwin32" != true; then if test "$bmac" = true; then AC_CHECK_LIB(c, uuid_generate, [echo "found library uuid"], AC_MSG_ERROR([*** Unable to find uuid_generate in libc]), ) else AC_CHECK_LIB(uuid, uuid_generate, [echo "found library uuid"], AC_MSG_ERROR([*** Unable to find uuid library]), ) fi fi AC_CHECK_LIB(pthread, pthread_create, [echo "found library pthread"], AC_MSG_ERROR([*** Unable to find pthread library]), ) AC_CHECK_LIB(sqlite3, sqlite3_open,[echo "found library sqlite3"] , AC_MSG_ERROR([*** Unable to find sqlite3 library]), ) dnl Do we need to use AX_LIB_SQLITE3 to check sqlite? dnl AX_LIB_SQLITE3 CONSOLE= if test "$bwin32" = "true"; then AC_ARG_ENABLE(console, AC_HELP_STRING([--enable-console], [enable console]), [console=$enableval],[console="yes"]) if test x${console} != xyes ; then CONSOLE="-Wl,--subsystem,windows -Wl,--entry,_mainCRTStartup" fi fi AC_SUBST(CONSOLE) if test "$bwin32" = true; then LIB_WS32=-lws2_32 LIB_GDI32=-lgdi32 LIB_RT= LIB_INTL=-lintl LIBS= LIB_RESOLV= LIB_UUID=-lRpcrt4 LIB_IPHLPAPI=-liphlpapi LIB_SHELL32=-lshell32 LIB_PSAPI=-lpsapi LIB_MAC= MSVC_CFLAGS="-D__MSVCRT__ -D__MSVCRT_VERSION__=0x0601" LIB_CRYPT32=-lcrypt32 elif test "$bmac" = true ; then LIB_WS32= LIB_GDI32= LIB_RT= LIB_INTL= LIB_RESOLV=-lresolv LIB_UUID= LIB_IPHLPAPI= LIB_SHELL32= LIB_PSAPI= MSVC_CFLAGS= LIB_MAC="-framework CoreServices" LIB_CRYPT32= LIB_ICONV=-liconv else LIB_WS32= LIB_GDI32= LIB_RT= LIB_INTL= LIB_RESOLV=-lresolv LIB_UUID=-luuid LIB_IPHLPAPI= LIB_SHELL32= LIB_PSAPI= LIB_MAC= MSVC_CFLAGS= LIB_CRYPT32= fi AC_SUBST(LIB_WS32) AC_SUBST(LIB_GDI32) AC_SUBST(LIB_RT) AC_SUBST(LIB_INTL) AC_SUBST(LIB_RESOLV) AC_SUBST(LIB_UUID) AC_SUBST(LIB_IPHLPAPI) AC_SUBST(LIB_SHELL32) AC_SUBST(LIB_PSAPI) AC_SUBST(LIB_MAC) AC_SUBST(MSVC_CFLAGS) AC_SUBST(LIB_CRYPT32) AC_SUBST(LIB_ICONV) LIBEVENT_REQUIRED=2.0 GLIB_REQUIRED=2.16.0 SEARPC_REQUIRED=1.0 JANSSON_REQUIRED=2.2.1 CURL_REQUIRED=7.17 FUSE_REQUIRED=2.7.3 ZLIB_REQUIRED=1.2.0 WS_REQUIRED=4.0.20 GNUTLS_REQUIRED=3.3.0 AC_ARG_WITH([gpl-crypto], AS_HELP_STRING([--with-gpl-crypto=[yes|no]], [Use GPL compatible crypto libraries. Default no.]), [ gpl_crypto=$with_gpl_crypto ], [ gpl_crypto="no"]) AS_IF([test "x$gpl_crypto" = "xyes"], [ PKG_CHECK_MODULES([GNUTLS], [gnutls >= $GNUTLS_REQUIRED]) AC_SUBST([GNUTLS_CFLAGS]) AC_SUBST([GNUTLS_LIBS]) PKG_CHECK_MODULES([NETTLE], [nettle]) AC_SUBST([NETTLE_CFLAGS]) AC_SUBST([NETTLE_LIBS]) AC_DEFINE([USE_GPL_CRYPTO], [1], [Use GPL-compatible crypto libraries]) ], [ AC_CHECK_LIB(crypto, SHA1_Init, [echo "found library crypto"], AC_MSG_ERROR([*** Unable to find openssl crypto library]), ) PKG_CHECK_MODULES([SSL], [openssl]) AC_SUBST([SSL_CFLAGS]) AC_SUBST([SSL_LIBS]) ]) PKG_CHECK_MODULES(GLIB2, [glib-2.0 >= $GLIB_REQUIRED]) AC_SUBST(GLIB2_CFLAGS) AC_SUBST(GLIB2_LIBS) PKG_CHECK_MODULES(GOBJECT, [gobject-2.0 >= $GLIB_REQUIRED]) AC_SUBST(GOBJECT_CFLAGS) AC_SUBST(GOBJECT_LIBS) PKG_CHECK_MODULES(SEARPC, [libsearpc >= $SEARPC_REQUIRED]) AC_SUBST(SEARPC_CFLAGS) AC_SUBST(SEARPC_LIBS) PKG_CHECK_MODULES(JANSSON, [jansson >= $JANSSON_REQUIRED]) AC_SUBST(JANSSON_CFLAGS) AC_SUBST(JANSSON_LIBS) PKG_CHECK_MODULES(LIBEVENT, [libevent >= $LIBEVENT_REQUIRED]) AC_SUBST(LIBEVENT_CFLAGS) AC_SUBST(LIBEVENT_LIBS) PKG_CHECK_MODULES(ZLIB, [zlib >= $ZLIB_REQUIRED]) AC_SUBST(ZLIB_CFLAGS) AC_SUBST(ZLIB_LIBS) if test "${blinux}" = "true" -o "$bmac" = "true"; then PKG_CHECK_MODULES(FUSE, [fuse >= $FUSE_REQUIRED]) AC_SUBST(FUSE_CFLAGS) AC_SUBST(FUSE_LIBS) fi PKG_CHECK_MODULES(CURL, [libcurl >= $CURL_REQUIRED]) AC_SUBST(CURL_CFLAGS) AC_SUBST(CURL_LIBS) PKG_CHECK_MODULES(ARGON2, [libargon2]) AC_SUBST(ARGON2_CFLAGS) AC_SUBST(ARGON2_LIBS) AC_ARG_ENABLE(ws, AC_HELP_STRING([--enable-ws], [enable build websockets]), [compile_ws=$enableval],[compile_ws="yes"]) AM_CONDITIONAL([COMPILE_WS], [test "${compile_ws}" = "yes"]) if test "${compile_ws}" = "yes"; then PKG_CHECK_MODULES(WS, [libwebsockets >= $WS_REQUIRED]) AC_DEFINE(COMPILE_WS, 1, [compile linux websockets]) AC_SUBST(WS_CFLAGS) AC_SUBST(WS_LIBS) fi BPWRAPPER_REQUIRED=0.1 AC_ARG_ENABLE(breakpad, AC_HELP_STRING([--enable-breakpad], [build google breadpad support]), [compile_breakpad=$enableval],[compile_breakpad="no"]) AM_CONDITIONAL([HAVE_BREAKPAD_SUPPORT], [test "${compile_breakpad}" = "yes"]) if test "${compile_breakpad}" = "yes"; then PKG_CHECK_MODULES(BPWRAPPER, [bpwrapper]) AC_DEFINE(HAVE_BREAKPAD_SUPPORT, 1, [Breakpad support enabled]) AC_SUBST(BPWRAPPER_CFLAGS) AC_SUBST(BPWRAPPER_LIBS) fi AM_PATH_PYTHON([2.6]) ac_configure_args="$ac_configure_args -q" AC_CONFIG_FILES( Makefile src/Makefile python/Makefile python/seadrive/Makefile ) AC_OUTPUT seadrive-fuse-3.0.13/debian/000077500000000000000000000000001476177674700156145ustar00rootroot00000000000000seadrive-fuse-3.0.13/debian/README.Debian000066400000000000000000000002731476177674700176570ustar00rootroot00000000000000Seafile Drive Client ------- For more information about Seafile Drive Client, please visit http://seafile.com -- Jonathan Xu Fri, 24 Feb 2017 15:27:16 +0800 seadrive-fuse-3.0.13/debian/changelog000066400000000000000000000152621476177674700174740ustar00rootroot00000000000000seadrive-daemon (3.0.13) unstable; urgency=low * new upstream release -- Jonathan Xu Web, 5 Mar 2025 10:30:00 +0800 seadrive-daemon (3.0.12) unstable; urgency=low * new upstream release -- Jonathan Xu Tue, 9 Jan 2025 10:30:00 +0800 seadrive-daemon (2.0.28) unstable; urgency=low * new upstream release -- Jonathan Xu Tue, 15 Aug 2023 13:56:30 +0800 seadrive-daemon (2.0.27) unstable; urgency=low * new upstream release -- Jonathan Xu Mon, 17 July 2023 14:56:30 +0800 seadrive-daemon (2.0.22) unstable; urgency=low * new upstream release -- Jonathan Xu Tue, 21 June 2022 16:56:30 +0800 seadrive-daemon (2.0.16) unstable; urgency=low * new upstream release -- Jonathan Xu Tue, 7 Sep 2021 16:56:30 +0800 seadrive-daemon (2.0.15) unstable; urgency=low * new upstream release -- Jonathan Xu Fri, 20 Aug 2021 15:56:30 +0800 seadrive-daemon (2.0.14) unstable; urgency=low * new upstream release -- Jonathan Xu Thu, 20 May 2021 15:36:28 +0800 seadrive-daemon (2.0.13) unstable; urgency=low * new upstream release -- Jonathan Xu Thu, 18 Mar 2021 16:18:21 +0800 seadrive-daemon (2.0.12) unstable; urgency=low * new upstream release -- Jonathan Xu Mon, 25 Jan 2021 17:22:17 +0800 seadrive-daemon (2.0.10) unstable; urgency=low * new upstream release -- Jonathan Xu Thu, 24 Dec 2020 16:48:30 +0800 seadrive-daemon (2.0.6) unstable; urgency=low * new upstream release -- Jonathan Xu Wed, 9 Sep 2020 14:38:39 +0800 seadrive-daemon (2.0.5) unstable; urgency=low * new upstream release -- Jonathan Xu Wed, 29 Jul 2020 09:53:43 +0800 seadrive-daemon (2.0.4) unstable; urgency=low * new upstream release -- Jonathan Xu Sat, 9 Jul 2020 18:07:19 +0800 seadrive-daemon (2.0.3) unstable; urgency=low * new upstream release -- Jonathan Xu Tue, 16 Jun 2020 10:03:00 +0800 seadrive-daemon (2.0.2) unstable; urgency=low * new upstream release -- Jonathan Xu Mon, 18 May 2020 11:57:00 +0800 seadrive-daemon (1.0.11) unstable; urgency=low * new upstream release -- Jonathan Xu Sat, 11 Jan 2020 11:57:00 +0800 seadrive-daemon (1.0.10) unstable; urgency=low * new upstream release -- Jonathan Xu Thu, 19 Dec 2019 14:36:00 +0800 seadrive-daemon (1.0.9) unstable; urgency=low * new upstream release -- Jonathan Xu Sat, 30 Nov 2019 14:36:00 +0800 seadrive-daemon (1.0.8) unstable; urgency=low * new upstream release -- Jonathan Xu Thu, 24 Oct 2019 15:02:11 +0800 seadrive-daemon (1.0.7) unstable; urgency=low * new upstream release -- Jonathan Xu Thu, 25 Jul 2019 10:01:10 +0800 seadrive-daemon (1.0.6) unstable; urgency=low * new upstream release -- Jonathan Xu Mon, 24 Jun 2019 14:20:20 +0800 seadrive-daemon (1.0.5) unstable; urgency=low * new upstream release -- Jonathan Xu Tue, 4 Jun 2019 15:54:20 +0800 seadrive-daemon (1.0.4) unstable; urgency=low * new upstream release -- Jonathan Xu Fri, 19 Apr 2019 16:28:28 +0800 seadrive-daemon (1.0.3) unstable; urgency=low * new upstream release -- Jonathan Xu Tue, 15 Mar 2019 11:02:28 +0800 seadrive-daemon (1.0.2) unstable; urgency=low * new upstream release -- Jonathan Xu Tue, 5 Mar 2019 11:02:28 +0800 seadrive-daemon (1.0.1) unstable; urgency=low * new upstream release -- Jonathan Xu Fri, 11 Jan 2019 15:58:16 +0800 seadrive-daemon (1.0.0) unstable; urgency=low * new upstream release -- Jonathan Xu Wed, 29 Aug 2018 15:37:16 +0800 seadrive-daemon (0.9.7) unstable; urgency=low * new upstream release -- Jonathan Xu Thu, 25 Oct 2018 18:03:16 +0800 seadrive-daemon (0.9.6) unstable; urgency=low * new upstream release -- Jonathan Xu Fri, 28 Sep 2018 14:57:16 +0800 seadrive-daemon (0.9.5) unstable; urgency=low * new upstream release -- Jonathan Xu Wed, 29 Aug 2018 15:37:16 +0800 seadrive-daemon (0.9.4) unstable; urgency=low * new upstream release -- Jonathan Xu Fri, 17 Aug 2018 11:18:16 +0800 seadrive-daemon (0.9.3) unstable; urgency=low * new upstream release -- Jonathan Xu Tue, 12 May 2018 14:38:16 +0800 seadrive-daemon (0.9.2) unstable; urgency=low * new upstream release -- Jonathan Xu Fri, 4 May 2018 17:33:16 +0800 seadrive-daemon (0.9.1) unstable; urgency=low * new upstream release -- Jonathan Xu Wed, 25 Apr 2018 15:21:16 +0800 seadrive-daemon (0.9.0) unstable; urgency=low * new upstream release -- Jonathan Xu Fri, 20 Apr 2018 15:21:16 +0800 seadrive-daemon (0.8.6) unstable; urgency=low * new upstream release -- Jonathan Xu Thur, 8 Mar 2018 18:30:16 +0800 seadrive-daemon (0.8.5) unstable; urgency=low * new upstream release -- Jonathan Xu Tue, 2 Jan 2018 14:53:16 +0800 seadrive-daemon (0.8.4) unstable; urgency=low * new upstream release -- Jonathan Xu Thur, 30 Nov 2017 14:05:16 +0800 seadrive-daemon (0.8.3) unstable; urgency=low * new upstream release -- Jonathan Xu Fri, 24 Nov 2017 14:11:16 +0800 seadrive-daemon (0.8.2) unstable; urgency=low * new upstream release -- Jonathan Xu Thur, 9 Nov 2017 17:54:16 +0800 seadrive-daemon (0.8.1) unstable; urgency=low * new upstream release -- Jonathan Xu Fri, 3 Nov 2017 15:16:16 +0800 seadrive-daemon (0.8.0) unstable; urgency=low * new upstream release -- Jonathan Xu Sat, 9 Sep 2017 18:03:16 +0800 seadrive-daemon (0.7.0) unstable; urgency=low * new upstream release -- Jonathan Xu Tue, 6 Jun 2017 18:03:16 +0800 seadrive-daemon (0.6.1) unstable; urgency=low * new upstream release -- Jonathan Xu Mon, 27 Mar 2017 15:27:16 +0800 seadrive-daemon (0.5.1) unstable; urgency=low * new upstream release -- Jonathan Xu Fri, 24 Feb 2017 15:27:16 +0800 seadrive-fuse-3.0.13/debian/compat000066400000000000000000000000021476177674700170120ustar00rootroot000000000000007 seadrive-fuse-3.0.13/debian/control000066400000000000000000000020721476177674700172200ustar00rootroot00000000000000Source: seadrive-daemon Section: net Priority: extra Maintainer: Jonathan Xu Build-Depends: debhelper (>= 7), autotools-dev, libssl-dev, libsqlite3-dev, intltool, libglib2.0-dev, libevent-dev, uuid-dev, libtool, libcurl4-openssl-dev, valac, libjansson-dev, dh-python, libfuse-dev, libsearpc-dev, Standards-Version: 3.9.5 Homepage: http://seafile.com Package: seadrive-daemon Section: net Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends}, ${python:Depends}, Suggests: seadrive-gui, seadrive-cli Description: SeaDrive daemon File syncing and sharing software with file encryption and group sharing, emphasis on reliability and high performance. . This package contains the SeaDrive daemon. Package: seadrive-daemon-dbg Section: debug Architecture: any Depends: seadrive-daemon (= ${binary:Version}), ${misc:Depends}, Description: Debugging symbols for the seadrive-daemon package. This package contains the debugging symbols for the seadrive-daemon package. seadrive-fuse-3.0.13/debian/copyright000066400000000000000000001050301476177674700175460ustar00rootroot00000000000000Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: seadrive Upstream-Contact: Jonathan Xu Source: https://github.com/haiwen/seadrive-fuse GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 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 . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . 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 . seadrive-fuse-3.0.13/debian/dirs000066400000000000000000000000071476177674700164750ustar00rootroot00000000000000usr/binseadrive-fuse-3.0.13/debian/docs000066400000000000000000000000001476177674700164550ustar00rootroot00000000000000seadrive-fuse-3.0.13/debian/patches/000077500000000000000000000000001476177674700172435ustar00rootroot00000000000000seadrive-fuse-3.0.13/debian/patches/series000066400000000000000000000000001476177674700204460ustar00rootroot00000000000000seadrive-fuse-3.0.13/debian/rules000077500000000000000000000005421476177674700166750ustar00rootroot00000000000000#!/usr/bin/make -f # -*- makefile -*- %: dh $@ --with python3 --with autotools_dev override_dh_auto_configure: ./autogen.sh dh_auto_configure -- --disable-fuse override_dh_auto_test: # make check seems to be broken override_dh_strip: dh_strip -pseadrive-daemon --dbg-package=seadrive-daemon-dbg override_dh_auto_build: dh_auto_build --parallel seadrive-fuse-3.0.13/debian/seadrive-daemon.install000066400000000000000000000000211476177674700222400ustar00rootroot00000000000000usr/bin/seadrive seadrive-fuse-3.0.13/debian/source/000077500000000000000000000000001476177674700171145ustar00rootroot00000000000000seadrive-fuse-3.0.13/debian/source/format000066400000000000000000000000141476177674700203220ustar00rootroot000000000000003.0 (quilt) seadrive-fuse-3.0.13/python/000077500000000000000000000000001476177674700157135ustar00rootroot00000000000000seadrive-fuse-3.0.13/python/Makefile.am000066400000000000000000000000231476177674700177420ustar00rootroot00000000000000SUBDIRS = seadrive seadrive-fuse-3.0.13/python/seadrive/000077500000000000000000000000001476177674700175155ustar00rootroot00000000000000seadrive-fuse-3.0.13/python/seadrive/Makefile.am000066400000000000000000000001161476177674700215470ustar00rootroot00000000000000seadrivedir=${pyexecdir}/seadrive seadrive_PYTHON = __init__.py rpcclient.py seadrive-fuse-3.0.13/python/seadrive/__init__.py000066400000000000000000000000661476177674700216300ustar00rootroot00000000000000 from rpcclient import SeadriveRpcClient as RpcClient seadrive-fuse-3.0.13/python/seadrive/rpcclient.py000066400000000000000000000013701476177674700220530ustar00rootroot00000000000000from pysearpc import searpc_func, SearpcError, NamedPipeClient class SeadriveRpcClient(NamedPipeClient): """RPC used in client""" def __init__(self, socket_path, *args, **kwargs): NamedPipeClient.__init__( self, socket_path, "seadrive-rpcserver", *args, **kwargs ) @searpc_func("int", []) def seafile_enable_auto_sync(): pass enable_auto_sync = seafile_enable_auto_sync @searpc_func("int", []) def seafile_disable_auto_sync(): pass disable_auto_sync = seafile_disable_auto_sync @searpc_func("json", []) def seafile_get_global_sync_status(): pass get_global_sync_status = seafile_get_global_sync_status seadrive-fuse-3.0.13/src/000077500000000000000000000000001476177674700151615ustar00rootroot00000000000000seadrive-fuse-3.0.13/src/Makefile.am000066400000000000000000000034731476177674700172240ustar00rootroot00000000000000 AM_CFLAGS = -DPKGDATADIR=\"$(pkgdatadir)\" \ -DPACKAGE_DATA_DIR=\""$(pkgdatadir)"\" \ @GLIB2_CFLAGS@ \ @MSVC_CFLAGS@ \ @CURL_CFLAGS@ \ @BPWRAPPER_CFLAGS@ \ @FUSE_CFLAGS@ \ @SEARPC_CFLAGS@ \ @GNUTLS_CFLAGS@ \ -Wall if WIN32 AM_CFLAGS += -I/usr/local/include endif bin_PROGRAMS = seadrive noinst_HEADERS = \ http-tx-mgr.h \ sync-mgr.h \ seafile-session.h \ rpc-service.h \ fuse-ops.h \ repo-tree.h \ journal-mgr.h \ change-set.h \ fs-mgr.h \ repo-mgr.h \ commit-mgr.h \ branch-mgr.h \ filelock-mgr.h \ obj-store.h \ block-mgr.h \ block.h \ block-backend.h \ seafile-crypt.h \ password-hash.h \ diff-simple.h \ seafile-config.h \ seafile-error.h \ log.h \ curl-init.h \ cdc.h \ rabin-checksum.h \ db.h \ job-mgr.h \ timer.h \ utils.h \ file-cache-mgr.h \ sync-status-tree.h \ mq-mgr.h \ notif-mgr.h \ common.h \ obj-backend.h \ searpc-signature.h \ searpc-marshal.h if COMPILE_WS ws_src = notif-mgr.c endif seadrive_SOURCES = \ seadrive.c \ http-tx-mgr.c \ sync-mgr.c \ seafile-session.c \ rpc-service.c \ fuse-ops.c \ repo-tree.c \ journal-mgr.c \ change-set.c \ fs-mgr.c \ repo-mgr.c \ commit-mgr.c \ branch-mgr.c \ filelock-mgr.c \ obj-store.c \ obj-backend-fs.c \ block-mgr.c \ block-backend.c \ block-backend-fs.c \ seafile-crypt.c \ password-hash.c \ diff-simple.c \ seafile-config.c \ log.c \ curl-init.c \ cdc.c \ rabin-checksum.c \ db.c \ job-mgr.c \ timer.c \ utils.c \ file-cache-mgr.c \ sync-status-tree.c \ mq-mgr.c \ $(ws_src) seadrive_LDADD = @LIB_INTL@ @GLIB2_LIBS@ @GOBJECT_LIBS@ \ @SSL_LIBS@ @GNUTLS_LIBS@ @NETTLE_LIBS@ @LIB_RT@ @LIB_UUID@ -lsqlite3 @LIBEVENT_LIBS@ \ @LIB_WS32@ @LIB_CRYPT32@ \ @JANSSON_LIBS@ @LIB_MAC@ @ZLIB_LIBS@ @CURL_LIBS@ @BPWRAPPER_LIBS@ \ @FUSE_LIBS@ @SEARPC_LIBS@ @WS_LIBS@ @ARGON2_LIBS@ seadrive_LDFLAGS = @CONSOLE@ seadrive-fuse-3.0.13/src/block-backend-fs.c000066400000000000000000000300251476177674700204120ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ #include "common.h" #include "utils.h" #include "log.h" #include #include #include "block-backend.h" struct _BHandle { char *store_id; int version; char block_id[41]; int fd; int rw_type; char *tmp_file; }; typedef struct { char *v0_block_dir; int v0_block_dir_len; char *block_dir; int block_dir_len; char *tmp_dir; int tmp_dir_len; } FsPriv; static char * get_block_path (BlockBackend *bend, const char *block_sha1, char path[], const char *store_id, int version); static int open_tmp_file (BlockBackend *bend, const char *basename, char **path); static BHandle * block_backend_fs_open_block (BlockBackend *bend, const char *store_id, int version, const char *block_id, int rw_type) { BHandle *handle; int fd = -1; char *tmp_file; g_return_val_if_fail (block_id != NULL, NULL); g_return_val_if_fail (strlen(block_id) == 40, NULL); g_return_val_if_fail (rw_type == BLOCK_READ || rw_type == BLOCK_WRITE, NULL); if (rw_type == BLOCK_READ) { char path[SEAF_PATH_MAX]; get_block_path (bend, block_id, path, store_id, version); fd = g_open (path, O_RDONLY | O_BINARY, 0); if (fd < 0) { seaf_warning ("[block bend] failed to open block %s for read: %s\n", block_id, strerror(errno)); return NULL; } } else { fd = open_tmp_file (bend, block_id, &tmp_file); if (fd < 0) { seaf_warning ("[block bend] failed to open block %s for write: %s\n", block_id, strerror(errno)); return NULL; } } handle = g_new0(BHandle, 1); handle->fd = fd; memcpy (handle->block_id, block_id, 41); handle->rw_type = rw_type; if (rw_type == BLOCK_WRITE) handle->tmp_file = tmp_file; if (store_id) handle->store_id = g_strdup(store_id); handle->version = version; return handle; } static int block_backend_fs_read_block (BlockBackend *bend, BHandle *handle, void *buf, int len) { return (readn (handle->fd, buf, len)); } static int block_backend_fs_write_block (BlockBackend *bend, BHandle *handle, const void *buf, int len) { return (writen (handle->fd, buf, len)); } static int block_backend_fs_close_block (BlockBackend *bend, BHandle *handle) { int ret; ret = close (handle->fd); return ret; } static void block_backend_fs_block_handle_free (BlockBackend *bend, BHandle *handle) { if (handle->rw_type == BLOCK_WRITE) { /* make sure the tmp file is removed even on failure. */ g_unlink (handle->tmp_file); g_free (handle->tmp_file); } g_free (handle->store_id); g_free (handle); } static int create_parent_path (const char *path) { char *dir = g_path_get_dirname (path); if (!dir) return -1; if (g_file_test (dir, G_FILE_TEST_EXISTS)) { g_free (dir); return 0; } if (g_mkdir_with_parents (dir, 0777) < 0) { seaf_warning ("Failed to create object parent path: %s.\n", dir); g_free (dir); return -1; } g_free (dir); return 0; } static int block_backend_fs_commit_block (BlockBackend *bend, BHandle *handle) { char path[SEAF_PATH_MAX]; g_return_val_if_fail (handle->rw_type == BLOCK_WRITE, -1); get_block_path (bend, handle->block_id, path, handle->store_id, handle->version); if (create_parent_path (path) < 0) { seaf_warning ("Failed to create path for block %s:%s.\n", handle->store_id, handle->block_id); return -1; } if (g_file_test(path, G_FILE_TEST_EXISTS)) { if (g_unlink (path) < 0) { seaf_warning ("[block bend] failed to remove existing block %s: %s\n", path, strerror(errno)); } } if (g_rename (handle->tmp_file, path) < 0) { seaf_warning ("[block bend] failed to commit block %s:%s: %s\n", handle->store_id, handle->block_id, strerror(errno)); return -1; } return 0; } static gboolean block_backend_fs_block_exists (BlockBackend *bend, const char *store_id, int version, const char *block_sha1) { char block_path[SEAF_PATH_MAX]; get_block_path (bend, block_sha1, block_path, store_id, version); return g_file_test(block_path, G_FILE_TEST_EXISTS); } static int block_backend_fs_remove_block (BlockBackend *bend, const char *store_id, int version, const char *block_id) { char path[SEAF_PATH_MAX]; get_block_path (bend, block_id, path, store_id, version); return g_unlink (path); } static BMetadata * block_backend_fs_stat_block (BlockBackend *bend, const char *store_id, int version, const char *block_id) { char path[SEAF_PATH_MAX]; SeafStat st; BMetadata *block_md; get_block_path (bend, block_id, path, store_id, version); if (seaf_stat (path, &st) < 0) { seaf_warning ("[block bend] Failed to stat block %s:%s at %s: %s.\n", store_id, block_id, path, strerror(errno)); return NULL; } block_md = g_new0(BMetadata, 1); memcpy (block_md->id, block_id, 40); block_md->size = (uint32_t) st.st_size; return block_md; } static BMetadata * block_backend_fs_stat_block_by_handle (BlockBackend *bend, BHandle *handle) { SeafStat st; BMetadata *block_md; if (seaf_fstat (handle->fd, &st) < 0) { seaf_warning ("[block bend] Failed to stat block %s:%s.\n", handle->store_id, handle->block_id); return NULL; } block_md = g_new0(BMetadata, 1); memcpy (block_md->id, handle->block_id, 40); block_md->size = (uint32_t) st.st_size; return block_md; } static int block_backend_fs_foreach_block (BlockBackend *bend, const char *store_id, int version, SeafBlockFunc process, void *user_data) { FsPriv *priv = bend->be_priv; char *block_dir = NULL; int dir_len; GDir *dir1 = NULL, *dir2; const char *dname1, *dname2; char block_id[128]; char path[SEAF_PATH_MAX], *pos; int ret = 0; #if defined MIGRATION if (version > 0) block_dir = g_build_filename (priv->block_dir, store_id, NULL); else block_dir = g_strdup(priv->v0_block_dir); #else block_dir = g_build_filename (priv->block_dir, store_id, NULL); #endif dir_len = strlen (block_dir); dir1 = g_dir_open (block_dir, 0, NULL); if (!dir1) { goto out; } memcpy (path, block_dir, dir_len); pos = path + dir_len; while ((dname1 = g_dir_read_name(dir1)) != NULL) { snprintf (pos, sizeof(path) - dir_len, "/%s", dname1); dir2 = g_dir_open (path, 0, NULL); if (!dir2) { seaf_warning ("Failed to open block dir %s.\n", path); continue; } while ((dname2 = g_dir_read_name(dir2)) != NULL) { snprintf (block_id, sizeof(block_id), "%s%s", dname1, dname2); if (!process (store_id, version, block_id, user_data)) { g_dir_close (dir2); goto out; } } g_dir_close (dir2); } out: if (dir1) g_dir_close (dir1); g_free (block_dir); return ret; } static int block_backend_fs_remove_store (BlockBackend *bend, const char *store_id) { FsPriv *priv = bend->be_priv; char *block_dir = NULL; GDir *dir1, *dir2; const char *dname1, *dname2; char *path1, *path2; block_dir = g_build_filename (priv->block_dir, store_id, NULL); dir1 = g_dir_open (block_dir, 0, NULL); if (!dir1) { g_free (block_dir); return 0; } while ((dname1 = g_dir_read_name(dir1)) != NULL) { path1 = g_build_filename (block_dir, dname1, NULL); dir2 = g_dir_open (path1, 0, NULL); if (!dir2) { seaf_warning ("Failed to open block dir %s.\n", path1); g_dir_close (dir1); g_free (path1); g_free (block_dir); return -1; } while ((dname2 = g_dir_read_name(dir2)) != NULL) { path2 = g_build_filename (path1, dname2, NULL); g_unlink (path2); g_free (path2); } g_dir_close (dir2); g_rmdir (path1); g_free (path1); } g_dir_close (dir1); g_rmdir (block_dir); g_free (block_dir); return 0; } static char * get_block_path (BlockBackend *bend, const char *block_sha1, char path[], const char *store_id, int version) { FsPriv *priv = bend->be_priv; char *pos = path; int n; #if defined MIGRATION if (version > 0) { n = snprintf (path, SEAF_PATH_MAX, "%s/%s/", priv->block_dir, store_id); pos += n; } else { memcpy (pos, priv->v0_block_dir, priv->v0_block_dir_len); pos[priv->v0_block_dir_len] = '/'; pos += priv->v0_block_dir_len + 1; } #else n = snprintf (path, SEAF_PATH_MAX, "%s/%s/", priv->block_dir, store_id); pos += n; #endif memcpy (pos, block_sha1, 2); pos[2] = '/'; pos += 3; memcpy (pos, block_sha1 + 2, 41 - 2); return path; } static int open_tmp_file (BlockBackend *bend, const char *basename, char **path) { FsPriv *priv = bend->be_priv; int fd; *path = g_strdup_printf ("%s/%s.XXXXXX", priv->tmp_dir, basename); fd = g_mkstemp (*path); if (fd < 0) g_free (*path); return fd; } BlockBackend * block_backend_fs_new (const char *seaf_dir, const char *tmp_dir) { BlockBackend *bend; FsPriv *priv; bend = g_new0(BlockBackend, 1); priv = g_new0(FsPriv, 1); bend->be_priv = priv; priv->v0_block_dir = g_build_filename (seaf_dir, "blocks", NULL); priv->v0_block_dir_len = strlen(priv->v0_block_dir); priv->block_dir = g_build_filename (seaf_dir, "storage", "blocks", NULL); priv->block_dir_len = strlen (priv->block_dir); priv->tmp_dir = g_strdup (tmp_dir); priv->tmp_dir_len = strlen (tmp_dir); if (g_mkdir_with_parents (priv->block_dir, 0777) < 0) { seaf_warning ("Block dir %s does not exist and" " is unable to create\n", priv->block_dir); goto onerror; } if (g_mkdir_with_parents (tmp_dir, 0777) < 0) { seaf_warning ("Blocks tmp dir %s does not exist and" " is unable to create\n", tmp_dir); goto onerror; } bend->open_block = block_backend_fs_open_block; bend->read_block = block_backend_fs_read_block; bend->write_block = block_backend_fs_write_block; bend->commit_block = block_backend_fs_commit_block; bend->close_block = block_backend_fs_close_block; bend->exists = block_backend_fs_block_exists; bend->remove_block = block_backend_fs_remove_block; bend->stat_block = block_backend_fs_stat_block; bend->stat_block_by_handle = block_backend_fs_stat_block_by_handle; bend->block_handle_free = block_backend_fs_block_handle_free; bend->foreach_block = block_backend_fs_foreach_block; bend->remove_store = block_backend_fs_remove_store; return bend; onerror: g_free (bend); g_free (bend->be_priv); return NULL; } seadrive-fuse-3.0.13/src/block-backend.c000066400000000000000000000023141476177674700200040ustar00rootroot00000000000000 #include "common.h" #include "log.h" #include "block-backend.h" extern BlockBackend * block_backend_fs_new (const char *block_dir, const char *tmp_dir); BlockBackend* load_filesystem_block_backend(GKeyFile *config) { BlockBackend *bend; char *tmp_dir; char *block_dir; block_dir = g_key_file_get_string (config, "block_backend", "block_dir", NULL); if (!block_dir) { seaf_warning ("Block dir not set in config.\n"); return NULL; } tmp_dir = g_key_file_get_string (config, "block_backend", "tmp_dir", NULL); if (!tmp_dir) { seaf_warning ("Block tmp dir not set in config.\n"); return NULL; } bend = block_backend_fs_new (block_dir, tmp_dir); g_free (block_dir); g_free (tmp_dir); return bend; } BlockBackend* load_block_backend (GKeyFile *config) { char *backend; BlockBackend *bend; backend = g_key_file_get_string (config, "block_backend", "name", NULL); if (!backend) { return NULL; } if (strcmp(backend, "filesystem") == 0) { bend = load_filesystem_block_backend(config); g_free (backend); return bend; } seaf_warning ("Unknown backend\n"); return NULL; } seadrive-fuse-3.0.13/src/block-backend.h000066400000000000000000000036021476177674700200120ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ #ifndef BLOCK_BACKEND_H #define BLOCK_BACKEND_H #include "block.h" typedef struct BlockBackend BlockBackend; struct BlockBackend { BHandle* (*open_block) (BlockBackend *bend, const char *store_id, int version, const char *block_id, int rw_type); int (*read_block) (BlockBackend *bend, BHandle *handle, void *buf, int len); int (*write_block) (BlockBackend *bend, BHandle *handle, const void *buf, int len); int (*commit_block) (BlockBackend *bend, BHandle *handle); int (*close_block) (BlockBackend *bend, BHandle *handle); int (*exists) (BlockBackend *bend, const char *store_id, int version, const char *block_id); int (*remove_block) (BlockBackend *bend, const char *store_id, int version, const char *block_id); BMetadata* (*stat_block) (BlockBackend *bend, const char *store_id, int version, const char *block_id); BMetadata* (*stat_block_by_handle) (BlockBackend *bend, BHandle *handle); void (*block_handle_free) (BlockBackend *bend, BHandle *handle); int (*foreach_block) (BlockBackend *bend, const char *store_id, int version, SeafBlockFunc process, void *user_data); /* Only valid for version 1 repo. Remove all blocks for the repo. */ int (*remove_store) (BlockBackend *bend, const char *store_id); void* be_priv; /* backend private field */ }; BlockBackend* load_block_backend (GKeyFile *config); #endif seadrive-fuse-3.0.13/src/block-mgr.c000066400000000000000000000151511476177674700172050ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ #include "common.h" #include "seafile-session.h" #include "utils.h" #include "block-mgr.h" #include "log.h" #include #include #include #include #include #include #include "block-backend.h" #define SEAF_BLOCK_DIR "blocks" extern BlockBackend * block_backend_fs_new (const char *block_dir, const char *tmp_dir); SeafBlockManager * seaf_block_manager_new (struct _SeafileSession *seaf, const char *seaf_dir) { SeafBlockManager *mgr; mgr = g_new0 (SeafBlockManager, 1); mgr->seaf = seaf; mgr->backend = block_backend_fs_new (seaf_dir, seaf->tmp_file_dir); if (!mgr->backend) { seaf_warning ("[Block mgr] Failed to load backend.\n"); goto onerror; } return mgr; onerror: g_free (mgr); return NULL; } int seaf_block_manager_init (SeafBlockManager *mgr) { return 0; } BlockHandle * seaf_block_manager_open_block (SeafBlockManager *mgr, const char *store_id, int version, const char *block_id, int rw_type) { if (!store_id || !is_uuid_valid(store_id) || !block_id || !is_object_id_valid(block_id)) return NULL; return mgr->backend->open_block (mgr->backend, store_id, version, block_id, rw_type); } int seaf_block_manager_read_block (SeafBlockManager *mgr, BlockHandle *handle, void *buf, int len) { return mgr->backend->read_block (mgr->backend, handle, buf, len); } int seaf_block_manager_write_block (SeafBlockManager *mgr, BlockHandle *handle, const void *buf, int len) { return mgr->backend->write_block (mgr->backend, handle, buf, len); } int seaf_block_manager_close_block (SeafBlockManager *mgr, BlockHandle *handle) { return mgr->backend->close_block (mgr->backend, handle); } void seaf_block_manager_block_handle_free (SeafBlockManager *mgr, BlockHandle *handle) { mgr->backend->block_handle_free (mgr->backend, handle); } int seaf_block_manager_commit_block (SeafBlockManager *mgr, BlockHandle *handle) { return mgr->backend->commit_block (mgr->backend, handle); } gboolean seaf_block_manager_block_exists (SeafBlockManager *mgr, const char *store_id, int version, const char *block_id) { if (!store_id || !is_uuid_valid(store_id) || !block_id || !is_object_id_valid(block_id)) return FALSE; return mgr->backend->exists (mgr->backend, store_id, version, block_id); } int seaf_block_manager_remove_block (SeafBlockManager *mgr, const char *store_id, int version, const char *block_id) { if (!store_id || !is_uuid_valid(store_id) || !block_id || !is_object_id_valid(block_id)) return -1; return mgr->backend->remove_block (mgr->backend, store_id, version, block_id); } BlockMetadata * seaf_block_manager_stat_block (SeafBlockManager *mgr, const char *store_id, int version, const char *block_id) { if (!store_id || !is_uuid_valid(store_id) || !block_id || !is_object_id_valid(block_id)) return NULL; return mgr->backend->stat_block (mgr->backend, store_id, version, block_id); } BlockMetadata * seaf_block_manager_stat_block_by_handle (SeafBlockManager *mgr, BlockHandle *handle) { return mgr->backend->stat_block_by_handle (mgr->backend, handle); } int seaf_block_manager_foreach_block (SeafBlockManager *mgr, const char *store_id, int version, SeafBlockFunc process, void *user_data) { return mgr->backend->foreach_block (mgr->backend, store_id, version, process, user_data); } static gboolean get_block_number (const char *store_id, int version, const char *block_id, void *data) { guint64 *n_blocks = data; ++(*n_blocks); return TRUE; } guint64 seaf_block_manager_get_block_number (SeafBlockManager *mgr, const char *store_id, int version) { guint64 n_blocks = 0; seaf_block_manager_foreach_block (mgr, store_id, version, get_block_number, &n_blocks); return n_blocks; } gboolean seaf_block_manager_verify_block (SeafBlockManager *mgr, const char *store_id, int version, const char *block_id, gboolean *io_error) { BlockHandle *h; char buf[10240]; int n; GChecksum *cs; const char *check_id; gboolean ret; h = seaf_block_manager_open_block (mgr, store_id, version, block_id, BLOCK_READ); if (!h) { seaf_warning ("Failed to open block %s:%.8s.\n", store_id, block_id); *io_error = TRUE; return FALSE; } cs = g_checksum_new (G_CHECKSUM_SHA1); while (1) { n = seaf_block_manager_read_block (mgr, h, buf, sizeof(buf)); if (n < 0) { seaf_warning ("Failed to read block %s:%.8s.\n", store_id, block_id); *io_error = TRUE; g_checksum_free (cs); return FALSE; } if (n == 0) break; g_checksum_update (cs, (guchar *)buf, n); } seaf_block_manager_close_block (mgr, h); seaf_block_manager_block_handle_free (mgr, h); check_id = g_checksum_get_string (cs); if (strcmp (check_id, block_id) == 0) ret = TRUE; else ret = FALSE; g_checksum_free (cs); return ret; } int seaf_block_manager_remove_store (SeafBlockManager *mgr, const char *store_id) { return mgr->backend->remove_store (mgr->backend, store_id); } seadrive-fuse-3.0.13/src/block-mgr.h000066400000000000000000000102601476177674700172060ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ #ifndef SEAF_BLOCK_MGR_H #define SEAF_BLOCK_MGR_H #include #include #include #include "block.h" struct _SeafileSession; typedef struct _SeafBlockManager SeafBlockManager; struct _SeafBlockManager { struct _SeafileSession *seaf; struct BlockBackend *backend; }; SeafBlockManager * seaf_block_manager_new (struct _SeafileSession *seaf, const char *seaf_dir); /* * Open a block for read or write. * * @store_id: id for the block store * @version: data format version for the repo * @block_id: ID of block. * @rw_type: BLOCK_READ or BLOCK_WRITE. * Returns: A handle for the block. */ BlockHandle * seaf_block_manager_open_block (SeafBlockManager *mgr, const char *store_id, int version, const char *block_id, int rw_type); /* * Read data from a block. * The semantics is similar to readn. * * @handle: Hanlde returned by seaf_block_manager_open_block(). * @buf: Data wuold be copied into this buf. * @len: At most @len bytes would be read. * * Returns: the bytes read. */ int seaf_block_manager_read_block (SeafBlockManager *mgr, BlockHandle *handle, void *buf, int len); /* * Write data to a block. * The semantics is similar to writen. * * @handle: Hanlde returned by seaf_block_manager_open_block(). * @buf: Data to be written to the block. * @len: At most @len bytes would be written. * * Returns: the bytes written. */ int seaf_block_manager_write_block (SeafBlockManager *mgr, BlockHandle *handle, const void *buf, int len); /* * Commit a block to storage. * The block must be opened for write. * * @handle: Hanlde returned by seaf_block_manager_open_block(). * * Returns: 0 on success, -1 on error. */ int seaf_block_manager_commit_block (SeafBlockManager *mgr, BlockHandle *handle); /* * Close an open block. * * @handle: Hanlde returned by seaf_block_manager_open_block(). * * Returns: 0 on success, -1 on error. */ int seaf_block_manager_close_block (SeafBlockManager *mgr, BlockHandle *handle); void seaf_block_manager_block_handle_free (SeafBlockManager *mgr, BlockHandle *handle); gboolean seaf_block_manager_block_exists (SeafBlockManager *mgr, const char *store_id, int version, const char *block_id); int seaf_block_manager_remove_block (SeafBlockManager *mgr, const char *store_id, int version, const char *block_id); BlockMetadata * seaf_block_manager_stat_block (SeafBlockManager *mgr, const char *store_id, int version, const char *block_id); BlockMetadata * seaf_block_manager_stat_block_by_handle (SeafBlockManager *mgr, BlockHandle *handle); int seaf_block_manager_foreach_block (SeafBlockManager *mgr, const char *store_id, int version, SeafBlockFunc process, void *user_data); /* Remove all blocks for a repo. Only valid for version 1 repo. */ int seaf_block_manager_remove_store (SeafBlockManager *mgr, const char *store_id); guint64 seaf_block_manager_get_block_number (SeafBlockManager *mgr, const char *store_id, int version); gboolean seaf_block_manager_verify_block (SeafBlockManager *mgr, const char *store_id, int version, const char *block_id, gboolean *io_error); #endif seadrive-fuse-3.0.13/src/block.h000066400000000000000000000011631476177674700164250ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ #ifndef BLOCK_H #define BLOCK_H typedef struct _BMetadata BlockMetadata; typedef struct _BMetadata BMetadata; struct _BMetadata { char id[41]; uint32_t size; }; /* Opaque block handle. */ typedef struct _BHandle BlockHandle; typedef struct _BHandle BHandle; enum { BLOCK_READ, BLOCK_WRITE, }; typedef gboolean (*SeafBlockFunc) (const char *store_id, int version, const char *block_id, void *user_data); #endif seadrive-fuse-3.0.13/src/branch-mgr.c000066400000000000000000000212761476177674700173550ustar00rootroot00000000000000#include "common.h" #include "log.h" #include "db.h" #include "seafile-session.h" #include "branch-mgr.h" #define BRANCH_DB "branch.db" SeafBranch * seaf_branch_new (const char *name, const char *repo_id, const char *commit_id, gint64 opid) { SeafBranch *branch; branch = g_new0 (SeafBranch, 1); branch->name = g_strdup (name); memcpy (branch->repo_id, repo_id, 36); branch->repo_id[36] = '\0'; memcpy (branch->commit_id, commit_id, 40); branch->commit_id[40] = '\0'; branch->opid = opid; branch->ref = 1; return branch; } void seaf_branch_free (SeafBranch *branch) { if (branch == NULL) return; g_free (branch->name); g_free (branch); } void seaf_branch_list_free (GList *blist) { GList *ptr; for (ptr = blist; ptr; ptr = ptr->next) { seaf_branch_unref (ptr->data); } g_list_free (blist); } void seaf_branch_set_commit (SeafBranch *branch, const char *commit_id) { memcpy (branch->commit_id, commit_id, 40); branch->commit_id[40] = '\0'; } void seaf_branch_ref (SeafBranch *branch) { branch->ref++; } void seaf_branch_unref (SeafBranch *branch) { if (!branch) return; if (--branch->ref <= 0) seaf_branch_free (branch); } struct _SeafBranchManagerPriv { sqlite3 *db; pthread_mutex_t db_lock; }; static int open_db (SeafBranchManager *mgr); SeafBranchManager * seaf_branch_manager_new (struct _SeafileSession *seaf) { SeafBranchManager *mgr; mgr = g_new0 (SeafBranchManager, 1); mgr->priv = g_new0 (SeafBranchManagerPriv, 1); mgr->seaf = seaf; pthread_mutex_init (&mgr->priv->db_lock, NULL); return mgr; } int seaf_branch_manager_init (SeafBranchManager *mgr) { return open_db (mgr); } static int open_db (SeafBranchManager *mgr) { char *db_path; const char *sql; db_path = g_build_filename (mgr->seaf->seaf_dir, BRANCH_DB, NULL); if (sqlite_open_db (db_path, &mgr->priv->db) < 0) { g_critical ("[Branch mgr] Failed to open branch db\n"); g_free (db_path); return -1; } g_free (db_path); sql = "CREATE TABLE IF NOT EXISTS Branch (" "name TEXT, repo_id TEXT, commit_id TEXT, opid INTEGER);"; if (sqlite_query_exec (mgr->priv->db, sql) < 0) return -1; sql = "CREATE INDEX IF NOT EXISTS branch_index ON Branch(repo_id, name);"; if (sqlite_query_exec (mgr->priv->db, sql) < 0) return -1; return 0; } int seaf_branch_manager_add_branch (SeafBranchManager *mgr, SeafBranch *branch) { char sql[256]; pthread_mutex_lock (&mgr->priv->db_lock); sqlite3_snprintf (sizeof(sql), sql, "SELECT 1 FROM Branch WHERE name=%Q and repo_id=%Q", branch->name, branch->repo_id); if (sqlite_check_for_existence (mgr->priv->db, sql)) sqlite3_snprintf (sizeof(sql), sql, "UPDATE Branch SET commit_id=%Q, opid=%lld" " WHERE name=%Q and repo_id=%Q", branch->commit_id, branch->opid, branch->name, branch->repo_id); else sqlite3_snprintf (sizeof(sql), sql, "INSERT INTO Branch (name, repo_id, commit_id, opid) " "VALUES (%Q, %Q, %Q, %lld)", branch->name, branch->repo_id, branch->commit_id, branch->opid); sqlite_query_exec (mgr->priv->db, sql); pthread_mutex_unlock (&mgr->priv->db_lock); return 0; } int seaf_branch_manager_del_branch (SeafBranchManager *mgr, const char *repo_id, const char *name) { char *sql; pthread_mutex_lock (&mgr->priv->db_lock); sql = sqlite3_mprintf ("DELETE FROM Branch WHERE name = %Q AND " "repo_id = %Q", name, repo_id); if (sqlite_query_exec (mgr->priv->db, sql) < 0) seaf_warning ("Delete branch %s failed\n", name); sqlite3_free (sql); pthread_mutex_unlock (&mgr->priv->db_lock); return 0; } int seaf_branch_manager_update_branch (SeafBranchManager *mgr, SeafBranch *branch) { sqlite3 *db; char *sql; pthread_mutex_lock (&mgr->priv->db_lock); db = mgr->priv->db; sql = sqlite3_mprintf ("UPDATE Branch SET commit_id = %Q, opid = %lld" " WHERE name = %Q AND repo_id = %Q", branch->commit_id, branch->opid, branch->name, branch->repo_id); sqlite_query_exec (db, sql); sqlite3_free (sql); pthread_mutex_unlock (&mgr->priv->db_lock); return 0; } int seaf_branch_manager_update_repo_branches (SeafBranchManager *mgr, SeafBranch *branch_info) { sqlite3 *db; char *sql; pthread_mutex_lock (&mgr->priv->db_lock); db = mgr->priv->db; sql = sqlite3_mprintf ("UPDATE Branch SET commit_id = %Q, opid = %lld" " WHERE repo_id = %Q", branch_info->commit_id, branch_info->opid, branch_info->repo_id); sqlite_query_exec (db, sql); sqlite3_free (sql); pthread_mutex_unlock (&mgr->priv->db_lock); return 0; } static gboolean get_branch_cb (sqlite3_stmt *stmt, void *vdata) { SeafBranch **pbranch = vdata; const char *name, *repo_id, *commit_id; gint64 opid; name = (const char *)sqlite3_column_text (stmt, 0); repo_id = (const char *)sqlite3_column_text (stmt, 1); commit_id = (const char *)sqlite3_column_text (stmt, 2); opid = (gint64)sqlite3_column_int64 (stmt, 3); *pbranch = seaf_branch_new (name, repo_id, commit_id, opid); return FALSE; } SeafBranch * seaf_branch_manager_get_branch (SeafBranchManager *mgr, const char *repo_id, const char *name) { SeafBranch *branch = NULL; sqlite3 *db; char *sql; pthread_mutex_lock (&mgr->priv->db_lock); db = mgr->priv->db; sql = sqlite3_mprintf ("SELECT name, repo_id, commit_id, opid FROM Branch " "WHERE name = %Q and repo_id=%Q", name, repo_id); if (sqlite_foreach_selected_row (db, sql, get_branch_cb, &branch) < 0) { seaf_warning ("Failed to get branch %s of repo %s.\n", name, repo_id); branch = NULL; } sqlite3_free (sql); pthread_mutex_unlock (&mgr->priv->db_lock); return branch; } gboolean seaf_branch_manager_branch_exists (SeafBranchManager *mgr, const char *repo_id, const char *name) { char *sql; gboolean ret; pthread_mutex_lock (&mgr->priv->db_lock); sql = sqlite3_mprintf ("SELECT name FROM Branch WHERE name = %Q " "AND repo_id=%Q", name, repo_id); ret = sqlite_check_for_existence (mgr->priv->db, sql); sqlite3_free (sql); pthread_mutex_unlock (&mgr->priv->db_lock); return ret; } static gboolean get_branch_list_cb (sqlite3_stmt *stmt, void *vdata) { GList **pret = vdata; SeafBranch *branch; const char *name, *repo_id, *commit_id; gint64 opid; name = (const char *)sqlite3_column_text (stmt, 0); repo_id = (const char *)sqlite3_column_text (stmt, 1); commit_id = (const char *)sqlite3_column_text (stmt, 2); opid = (gint64)sqlite3_column_int64 (stmt, 3); branch = seaf_branch_new (name, repo_id, commit_id, opid); *pret = g_list_prepend (*pret, branch); return TRUE; } GList * seaf_branch_manager_get_branch_list (SeafBranchManager *mgr, const char *repo_id) { sqlite3 *db = mgr->priv->db; char *sql; GList *ret = NULL; sql = sqlite3_mprintf ("SELECT name, repo_id, commit_id, opid " "FROM branch WHERE repo_id =%Q", repo_id); pthread_mutex_lock (&mgr->priv->db_lock); if (sqlite_foreach_selected_row (db, sql, get_branch_list_cb, &ret) < 0) { seaf_warning ("Failed to get branch list of repo %s.\n", repo_id); g_list_free_full (ret, (GDestroyNotify)seaf_branch_free); } pthread_mutex_unlock (&mgr->priv->db_lock); sqlite3_free (sql); return g_list_reverse(ret); } int seaf_branch_manager_update_opid (SeafBranchManager *mgr, const char *repo_id, const char *name, gint64 opid) { sqlite3 *db; char *sql; pthread_mutex_lock (&mgr->priv->db_lock); db = mgr->priv->db; sql = sqlite3_mprintf ("UPDATE Branch SET opid = %lld" " WHERE name = %Q AND repo_id = %Q", opid, name, repo_id); sqlite_query_exec (db, sql); sqlite3_free (sql); pthread_mutex_unlock (&mgr->priv->db_lock); return 0; } seadrive-fuse-3.0.13/src/branch-mgr.h000066400000000000000000000044741476177674700173630ustar00rootroot00000000000000#ifndef SEAF_BRANCH_MGR_H #define SEAF_BRANCH_MGR_H #include "commit-mgr.h" #define NO_BRANCH "-" typedef struct _SeafBranch SeafBranch; struct _SeafBranch { int ref; char *name; char repo_id[37]; char commit_id[41]; gint64 opid; }; SeafBranch *seaf_branch_new (const char *name, const char *repo_id, const char *commit_id, gint64 opid); void seaf_branch_free (SeafBranch *branch); void seaf_branch_set_commit (SeafBranch *branch, const char *commit_id); void seaf_branch_ref (SeafBranch *branch); void seaf_branch_unref (SeafBranch *branch); typedef struct _SeafBranchManager SeafBranchManager; typedef struct _SeafBranchManagerPriv SeafBranchManagerPriv; struct _SeafileSession; struct _SeafBranchManager { struct _SeafileSession *seaf; SeafBranchManagerPriv *priv; }; SeafBranchManager *seaf_branch_manager_new (struct _SeafileSession *seaf); int seaf_branch_manager_init (SeafBranchManager *mgr); int seaf_branch_manager_add_branch (SeafBranchManager *mgr, SeafBranch *branch); int seaf_branch_manager_del_branch (SeafBranchManager *mgr, const char *repo_id, const char *name); void seaf_branch_list_free (GList *blist); int seaf_branch_manager_update_branch (SeafBranchManager *mgr, SeafBranch *branch); /* Update 'local' and 'master' branch together. */ int seaf_branch_manager_update_repo_branches (SeafBranchManager *mgr, SeafBranch *new_branch_info); SeafBranch * seaf_branch_manager_get_branch (SeafBranchManager *mgr, const char *repo_id, const char *name); gboolean seaf_branch_manager_branch_exists (SeafBranchManager *mgr, const char *repo_id, const char *name); GList * seaf_branch_manager_get_branch_list (SeafBranchManager *mgr, const char *repo_id); int seaf_branch_manager_update_opid (SeafBranchManager *mgr, const char *repo_id, const char *name, gint64 opid); #endif /* SEAF_BRANCH_MGR_H */ seadrive-fuse-3.0.13/src/cdc.c000066400000000000000000000177231476177674700160700ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ #include "common.h" #include "log.h" #include #include #include #include #include #include #include #include #include "utils.h" #include "cdc.h" #include "seafile-crypt.h" #include "rabin-checksum.h" #define finger rabin_checksum #define rolling_finger rabin_rolling_checksum #define BLOCK_SZ (1024*1024*1) #define BLOCK_MIN_SZ (1024*256) #define BLOCK_MAX_SZ (1024*1024*4) #define BLOCK_WIN_SZ 48 #define NAME_MAX_SZ 4096 #define BREAK_VALUE 0x0013 ///0x0513 #define READ_SIZE 1024 * 4 #define BYTE_TO_HEX(b) (((b)>=10)?('a'+b-10):('0'+b)) static int default_write_chunk (CDCDescriptor *chunk_descr) { char filename[NAME_MAX_SZ]; char chksum_str[CHECKSUM_LENGTH *2 + 1]; int fd_chunk, ret; memset(chksum_str, 0, sizeof(chksum_str)); rawdata_to_hex (chunk_descr->checksum, chksum_str, CHECKSUM_LENGTH); snprintf (filename, NAME_MAX_SZ, "./%s", chksum_str); fd_chunk = g_open (filename, O_RDWR | O_CREAT | O_BINARY, 0644); if (fd_chunk < 0) return -1; ret = writen (fd_chunk, chunk_descr->block_buf, chunk_descr->len); close (fd_chunk); return ret; } static int init_cdc_file_descriptor (int fd, uint64_t file_size, CDCFileDescriptor *file_descr) { uint32_t max_block_nr = 0; uint32_t block_min_sz = 0; file_descr->block_nr = 0; if (file_descr->block_min_sz <= 0) file_descr->block_min_sz = BLOCK_MIN_SZ; if (file_descr->block_max_sz <= 0) file_descr->block_max_sz = BLOCK_MAX_SZ; if (file_descr->block_sz <= 0) file_descr->block_sz = BLOCK_SZ; if (file_descr->write_block == NULL) file_descr->write_block = (WriteblockFunc)default_write_chunk; block_min_sz = file_descr->block_min_sz; max_block_nr = ((file_size + block_min_sz - 1) / block_min_sz); file_descr->blk_sha1s = (uint8_t *)calloc (sizeof(uint8_t), max_block_nr * CHECKSUM_LENGTH); file_descr->max_block_nr = max_block_nr; return 0; } #define WRITE_CDC_BLOCK(block_sz, write_data) \ do { \ int _block_sz = (block_sz); \ chunk_descr.len = _block_sz; \ chunk_descr.offset = offset; \ ret = file_descr->write_block (file_descr->repo_id, \ file_descr->version, \ &chunk_descr, \ crypt, chunk_descr.checksum, \ (write_data)); \ if (ret < 0) { \ free (buf); \ g_warning ("CDC: failed to write chunk.\n"); \ return -1; \ } \ memcpy (file_descr->blk_sha1s + \ file_descr->block_nr * CHECKSUM_LENGTH, \ chunk_descr.checksum, CHECKSUM_LENGTH); \ g_checksum_update (file_ctx, chunk_descr.checksum, 20); \ file_descr->block_nr++; \ offset += _block_sz; \ \ memmove (buf, buf + _block_sz, tail - _block_sz); \ tail = tail - _block_sz; \ cur = 0; \ }while(0); /* content-defined chunking */ int file_chunk_cdc(int fd_src, CDCFileDescriptor *file_descr, SeafileCrypt *crypt, gboolean write_data) { char *buf = NULL; uint32_t buf_sz; unsigned int len; GChecksum *file_ctx = g_checksum_new (G_CHECKSUM_SHA1); CDCDescriptor chunk_descr; int ret = 0; SeafStat sb; if (seaf_fstat (fd_src, &sb) < 0) { seaf_warning ("CDC: failed to stat: %s.\n", strerror(errno)); ret = -1; goto out; } uint64_t expected_size = sb.st_size; init_cdc_file_descriptor (fd_src, expected_size, file_descr); uint32_t block_min_sz = file_descr->block_min_sz; uint32_t block_mask = file_descr->block_sz - 1; int fingerprint = 0; int offset = 0; int tail, cur, rsize; buf_sz = file_descr->block_max_sz; buf = chunk_descr.block_buf = malloc (buf_sz); if (!buf) { ret = -1; goto out; } /* buf: a fix-sized buffer. * cur: data behind (inclusive) this offset has been scanned. * cur + 1 is the bytes that has been scanned. * tail: length of data loaded into memory. buf[tail] is invalid. */ tail = cur = 0; while (1) { if (tail < block_min_sz) { rsize = block_min_sz - tail + READ_SIZE; } else { rsize = (buf_sz - tail < READ_SIZE) ? (buf_sz - tail) : READ_SIZE; } ret = readn (fd_src, buf + tail, rsize); if (ret < 0) { seaf_warning ("CDC: failed to read: %s.\n", strerror(errno)); ret = -1; goto out; } tail += ret; file_descr->file_size += ret; if (file_descr->file_size > expected_size) { seaf_warning ("File size changed while chunking.\n"); ret = -1; goto out; } /* We've read all the data in this file. Output the block immediately * in two cases: * 1. The data left in the file is less than block_min_sz; * 2. We cannot find the break value until the end of this file. */ if (tail < block_min_sz || cur >= tail) { if (tail > 0) { if (file_descr->block_nr == file_descr->max_block_nr) { seaf_warning ("Block id array is not large enough, bail out.\n"); ret = -1; goto out; } WRITE_CDC_BLOCK (tail, write_data); } break; } /* * A block is at least of size block_min_sz. */ if (cur < block_min_sz - 1) cur = block_min_sz - 1; while (cur < tail) { fingerprint = (cur == block_min_sz - 1) ? finger(buf + cur - BLOCK_WIN_SZ + 1, BLOCK_WIN_SZ) : rolling_finger (fingerprint, BLOCK_WIN_SZ, *(buf+cur-BLOCK_WIN_SZ), *(buf + cur)); /* get a chunk, write block info to chunk file */ if (((fingerprint & block_mask) == ((BREAK_VALUE & block_mask))) || cur + 1 >= file_descr->block_max_sz) { if (file_descr->block_nr == file_descr->max_block_nr) { seaf_warning ("Block id array is not large enough, bail out.\n"); ret = -1; goto out; } WRITE_CDC_BLOCK (cur + 1, write_data); break; } else { cur ++; } } } gsize chk_sum_len = CHECKSUM_LENGTH; g_checksum_get_digest (file_ctx, file_descr->file_sum, &chk_sum_len); out: free (buf); g_checksum_free (file_ctx); return ret; } int filename_chunk_cdc(const char *filename, CDCFileDescriptor *file_descr, SeafileCrypt *crypt, gboolean write_data) { int fd_src = seaf_util_open (filename, O_RDONLY | O_BINARY); if (fd_src < 0) { seaf_warning ("CDC: failed to open %s.\n", filename); return -1; } int ret = file_chunk_cdc (fd_src, file_descr, crypt, write_data); close (fd_src); return ret; } void cdc_init () { rabin_init (BLOCK_WIN_SZ); } seadrive-fuse-3.0.13/src/cdc.h000066400000000000000000000030401476177674700160600ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ #ifndef _CDC_H #define _CDC_H #include #include #define CHECKSUM_LENGTH 20 #ifndef O_BINARY #define O_BINARY 0 #endif struct _CDCFileDescriptor; struct _CDCDescriptor; struct SeafileCrypt; typedef int (*WriteblockFunc)(const char *repo_id, int version, struct _CDCDescriptor *chunk_descr, struct SeafileCrypt *crypt, uint8_t *checksum, gboolean write_data); /* define chunk file header and block entry */ typedef struct _CDCFileDescriptor { uint32_t block_min_sz; uint32_t block_max_sz; uint32_t block_sz; uint64_t file_size; uint32_t block_nr; uint8_t *blk_sha1s; uint32_t max_block_nr; uint8_t file_sum[CHECKSUM_LENGTH]; WriteblockFunc write_block; char repo_id[37]; int version; } CDCFileDescriptor; typedef struct _CDCDescriptor { uint64_t offset; uint32_t len; uint8_t checksum[CHECKSUM_LENGTH]; char *block_buf; int result; } CDCDescriptor; int file_chunk_cdc(int fd_src, CDCFileDescriptor *file_descr, struct SeafileCrypt *crypt, gboolean write_data); int filename_chunk_cdc(const char *filename, CDCFileDescriptor *file_descr, struct SeafileCrypt *crypt, gboolean write_data); void cdc_init (); #endif seadrive-fuse-3.0.13/src/change-set.c000066400000000000000000000425031476177674700173470ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ #include "common.h" #include "seafile-session.h" #include "utils.h" #include "log.h" #include "diff-simple.h" #include "change-set.h" struct _ChangeSetDir { int version; char dir_id[41]; /* A hash table of dirents for fast lookup and insertion. */ GHashTable *dents; }; typedef struct _ChangeSetDir ChangeSetDir; struct _ChangeSetDirent { guint32 mode; char id[41]; char *name; gint64 mtime; char *modifier; gint64 size; /* Only used for directory. Most of time this is NULL * unless we change the subdir too. */ ChangeSetDir *subdir; }; typedef struct _ChangeSetDirent ChangeSetDirent; /* Change set dirent. */ static ChangeSetDirent * changeset_dirent_new (const char *id, guint32 mode, const char *name, gint64 mtime, const char *modifier, gint64 size) { ChangeSetDirent *dent = g_new0 (ChangeSetDirent, 1); dent->mode = mode; memcpy (dent->id, id, 40); dent->name = g_strdup(name); dent->mtime = mtime; dent->modifier = g_strdup(modifier); dent->size = size; return dent; } static ChangeSetDirent * seaf_dirent_to_changeset_dirent (SeafDirent *seaf_dent) { return changeset_dirent_new (seaf_dent->id, seaf_dent->mode, seaf_dent->name, seaf_dent->mtime, seaf_dent->modifier, seaf_dent->size); } static SeafDirent * changeset_dirent_to_seaf_dirent (int version, ChangeSetDirent *dent) { return seaf_dirent_new (version, dent->id, dent->mode, dent->name, dent->mtime, dent->modifier, dent->size); } static void changeset_dir_free (ChangeSetDir *dir); static void changeset_dirent_free (ChangeSetDirent *dent) { if (!dent) return; g_free (dent->name); g_free (dent->modifier); /* Recursively free subdir. */ if (dent->subdir) changeset_dir_free (dent->subdir); g_free (dent); } /* Change set dir. */ static void add_dent_to_dir (ChangeSetDir *dir, ChangeSetDirent *dent) { g_hash_table_insert (dir->dents, g_strdup(dent->name), dent); } static void remove_dent_from_dir (ChangeSetDir *dir, const char *dname) { char *key; if (g_hash_table_lookup_extended (dir->dents, dname, (gpointer*)&key, NULL)) { g_hash_table_steal (dir->dents, dname); g_free (key); } } static ChangeSetDir * changeset_dir_new (int version, const char *id, GList *dirents) { ChangeSetDir *dir = g_new0 (ChangeSetDir, 1); GList *ptr; SeafDirent *dent; ChangeSetDirent *changeset_dent; dir->version = version; if (id) memcpy (dir->dir_id, id, 40); dir->dents = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)changeset_dirent_free); for (ptr = dirents; ptr; ptr = ptr->next) { dent = ptr->data; changeset_dent = seaf_dirent_to_changeset_dirent(dent); add_dent_to_dir (dir, changeset_dent); } return dir; } static void changeset_dir_free (ChangeSetDir *dir) { if (!dir) return; g_hash_table_destroy (dir->dents); g_free (dir); } static ChangeSetDir * seaf_dir_to_changeset_dir (SeafDir *seaf_dir) { return changeset_dir_new (seaf_dir->version, seaf_dir->dir_id, seaf_dir->entries); } static gint compare_dents (gconstpointer a, gconstpointer b) { const SeafDirent *denta = a, *dentb = b; return strcmp(dentb->name, denta->name); } static SeafDir * changeset_dir_to_seaf_dir (ChangeSetDir *dir) { GList *dents = NULL, *seaf_dents = NULL; GList *ptr; ChangeSetDirent *dent; SeafDirent *seaf_dent; SeafDir *seaf_dir; dents = g_hash_table_get_values (dir->dents); for (ptr = dents; ptr; ptr = ptr->next) { dent = ptr->data; seaf_dent = changeset_dirent_to_seaf_dirent (dir->version, dent); seaf_dents = g_list_prepend (seaf_dents, seaf_dent); } /* Sort it in descending order. */ seaf_dents = g_list_sort (seaf_dents, compare_dents); /* seaf_dir_new() computes the dir id. */ seaf_dir = seaf_dir_new (NULL, seaf_dents, dir->version); g_list_free (dents); return seaf_dir; } /* Change set. */ ChangeSet * changeset_new (const char *repo_id) { SeafRepo *repo; SeafCommit *commit = NULL; SeafDir *seaf_dir = NULL; ChangeSetDir *changeset_dir = NULL; ChangeSet *changeset = NULL; repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id); if (!repo) { seaf_warning ("Failed to find repo %s.\n", repo_id); return NULL; } commit = seaf_commit_manager_get_commit (seaf->commit_mgr, repo_id, repo->version, repo->head->commit_id); if (!commit) { seaf_warning ("Failed to find head commit %s for repo %s.\n", repo->head->commit_id, repo_id); goto out; } seaf_dir = seaf_fs_manager_get_seafdir_sorted (seaf->fs_mgr, repo_id, repo->version, commit->root_id); if (!seaf_dir) { seaf_warning ("Failed to find root dir %s in repo %s\n", repo->root_id, repo_id); goto out; } changeset_dir = seaf_dir_to_changeset_dir (seaf_dir); if (!changeset_dir) goto out; changeset = g_new0 (ChangeSet, 1); memcpy (changeset->repo_id, repo_id, 36); changeset->tree_root = changeset_dir; out: seaf_repo_unref (repo); seaf_commit_unref (commit); seaf_dir_free (seaf_dir); return changeset; } void changeset_free (ChangeSet *changeset) { if (!changeset) return; changeset_dir_free (changeset->tree_root); g_free (changeset); } static void update_file (ChangeSetDirent *dent, unsigned char *sha1, SeafStat *st, const char *modifier) { if (!st || !S_ISREG(st->st_mode)) return; if (dent->mode == create_mode (st->st_mode) && dent->mtime == st->st_mtime && dent->size == st->st_size) return; dent->mode = create_mode(st->st_mode); dent->mtime = (gint64)st->st_mtime; dent->size = (gint64)st->st_size; if (sha1) rawdata_to_hex (sha1, dent->id, 20); g_free (dent->modifier); dent->modifier = g_strdup(modifier); } static void create_new_dent (ChangeSetDir *dir, const char *dname, unsigned char *sha1, SeafStat *st, const char *modifier, ChangeSetDirent *in_new_dent) { if (in_new_dent) { g_free (in_new_dent->name); in_new_dent->name = g_strdup(dname); add_dent_to_dir (dir, in_new_dent); return; } if (!sha1 || !st || !modifier) return; char id[41]; rawdata_to_hex (sha1, id, 20); ChangeSetDirent *new_dent; new_dent = changeset_dirent_new (id, create_mode(st->st_mode), dname, st->st_mtime, modifier, st->st_size); add_dent_to_dir (dir, new_dent); } static ChangeSetDir * create_intermediate_dir (ChangeSetDir *parent, const char *dname) { ChangeSetDirent *dent; dent = changeset_dirent_new (EMPTY_SHA1, S_IFDIR, dname, 0, NULL, 0); dent->subdir = changeset_dir_new (parent->version, EMPTY_SHA1, NULL); add_dent_to_dir (parent, dent); return dent->subdir; } static void add_to_tree (ChangeSet *changeset, unsigned char *sha1, SeafStat *st, const char *modifier, const char *path, ChangeSetDirent *new_dent) { char *repo_id = changeset->repo_id; ChangeSetDir *root = changeset->tree_root; char **parts, *dname; int n, i; ChangeSetDir *dir; ChangeSetDirent *dent; SeafDir *seaf_dir; parts = g_strsplit (path, "/", 0); n = g_strv_length(parts); dir = root; for (i = 0; i < n; i++) { dname = parts[i]; dent = g_hash_table_lookup (dir->dents, dname); if (dent) { if (S_ISDIR(dent->mode)) { if (i == (n-1)) /* Don't need to update empty dir */ break; if (!dent->subdir) { seaf_dir = seaf_fs_manager_get_seafdir(seaf->fs_mgr, repo_id, root->version, dent->id); if (!seaf_dir) { seaf_warning ("Failed to load seafdir %s:%s\n", repo_id, dent->id); break; } dent->subdir = seaf_dir_to_changeset_dir (seaf_dir); seaf_dir_free (seaf_dir); } dir = dent->subdir; } else if (S_ISREG(dent->mode)) { if (i == (n-1)) { /* File exists, update it. */ update_file (dent, sha1, st, modifier); break; } } } else { if (i == (n-1)) { create_new_dent (dir, dname, sha1, st, modifier, new_dent); } else { dir = create_intermediate_dir (dir, dname); } } } g_strfreev (parts); } static ChangeSetDirent * delete_from_tree (ChangeSet *changeset, const char *path, gboolean *parent_empty) { char *repo_id = changeset->repo_id; ChangeSetDir *root = changeset->tree_root; char **parts, *dname; int n, i; ChangeSetDir *dir; ChangeSetDirent *dent, *ret = NULL; SeafDir *seaf_dir; *parent_empty = FALSE; parts = g_strsplit (path, "/", 0); n = g_strv_length(parts); dir = root; for (i = 0; i < n; i++) { dname = parts[i]; dent = g_hash_table_lookup (dir->dents, dname); if (!dent) break; if (S_ISDIR(dent->mode)) { if (i == (n-1)) { /* Remove from hash table without freeing dent. */ remove_dent_from_dir (dir, dname); if (g_hash_table_size (dir->dents) == 0) *parent_empty = TRUE; ret = dent; break; } if (!dent->subdir) { seaf_dir = seaf_fs_manager_get_seafdir(seaf->fs_mgr, repo_id, root->version, dent->id); if (!seaf_dir) { seaf_warning ("Failed to load seafdir %s:%s\n", repo_id, dent->id); break; } dent->subdir = seaf_dir_to_changeset_dir (seaf_dir); seaf_dir_free (seaf_dir); } dir = dent->subdir; } else if (S_ISREG(dent->mode)) { if (i == (n-1)) { /* Remove from hash table without freeing dent. */ remove_dent_from_dir (dir, dname); if (g_hash_table_size (dir->dents) == 0) *parent_empty = TRUE; ret = dent; break; } } } g_strfreev (parts); return ret; } static void apply_to_tree (ChangeSet *changeset, char status, unsigned char *sha1, SeafStat *st, const char *modifier, const char *path, const char *new_path) { ChangeSetDirent *dent, *dent_dst; gboolean dummy; switch (status) { case DIFF_STATUS_ADDED: case DIFF_STATUS_MODIFIED: case DIFF_STATUS_DIR_ADDED: add_to_tree (changeset, sha1, st, modifier, path, NULL); break; case DIFF_STATUS_RENAMED: dent = delete_from_tree (changeset, path, &dummy); if (!dent) break; dent_dst = delete_from_tree (changeset, new_path, &dummy); changeset_dirent_free (dent_dst); add_to_tree (changeset, NULL, NULL, NULL, new_path, dent); break; } } void add_to_changeset (ChangeSet *changeset, char status, unsigned char *sha1, SeafStat *st, const char *modifier, const char *path, const char *new_path) { /* if (add_to_diff) { */ /* de = diff_entry_new (DIFF_TYPE_INDEX, status, allzero, path); */ /* changeset->diff = g_list_prepend (changeset->diff, de); */ /* } */ apply_to_tree (changeset, status, sha1, st, modifier, path, new_path); } static void remove_from_changeset_recursive (ChangeSet *changeset, const char *path, gboolean remove_parent, const char *top_dir) { ChangeSetDirent *dent; gboolean parent_empty = FALSE; dent = delete_from_tree (changeset, path, &parent_empty); changeset_dirent_free (dent); if (remove_parent && parent_empty) { char *parent = g_strdup(path); char *slash = strrchr (parent, '/'); if (slash) { *slash = '\0'; if (g_strcmp0 (top_dir, parent) != 0) { /* Recursively remove parent dirs. */ remove_from_changeset_recursive (changeset, parent, remove_parent, top_dir); } } g_free (parent); } } void remove_from_changeset (ChangeSet *changeset, const char *path, gboolean remove_parent, const char *top_dir) { /* if (add_to_diff) { */ /* de = diff_entry_new (DIFF_TYPE_INDEX, status, allzero, path); */ /* changeset->diff = g_list_prepend (changeset->diff, de); */ /* } */ remove_from_changeset_recursive (changeset, path, remove_parent, top_dir); } static char * commit_tree_recursive (const char *repo_id, ChangeSetDir *dir, gint64 *new_mtime) { ChangeSetDirent *dent; GHashTableIter iter; gpointer key, value; char *new_id; gint64 subdir_new_mtime; gint64 dir_mtime = 0; SeafDir *seaf_dir; char *ret = NULL; g_hash_table_iter_init (&iter, dir->dents); while (g_hash_table_iter_next (&iter, &key, &value)) { dent = value; if (dent->subdir) { new_id = commit_tree_recursive (repo_id, dent->subdir, &subdir_new_mtime); if (!new_id) return NULL; memcpy (dent->id, new_id, 40); dent->mtime = subdir_new_mtime; g_free (new_id); } if (dir_mtime < dent->mtime) dir_mtime = dent->mtime; } seaf_dir = changeset_dir_to_seaf_dir (dir); memcpy (dir->dir_id, seaf_dir->dir_id, 40); if (!seaf_fs_manager_object_exists (seaf->fs_mgr, repo_id, dir->version, seaf_dir->dir_id)) { if (seaf_dir_save (seaf->fs_mgr, repo_id, dir->version, seaf_dir) < 0) { seaf_warning ("Failed to save dir object %s to repo %s.\n", seaf_dir->dir_id, repo_id); goto out; } } ret = g_strdup(seaf_dir->dir_id); out: if (ret != NULL) *new_mtime = dir_mtime; seaf_dir_free (seaf_dir); return ret; } /* * This function does two things: * - calculate dir id from bottom up; * - create and save seaf dir objects. * It returns root dir id of the new commit. */ char * commit_tree_from_changeset (ChangeSet *changeset) { gint64 mtime; char *root_id = commit_tree_recursive (changeset->repo_id, changeset->tree_root, &mtime); return root_id; } gboolean changeset_check_path (ChangeSet *changeset, const char *path, gint64 size, gint64 mtime) { ChangeSetDir *root = changeset->tree_root; char **parts, *dname; int n, i; ChangeSetDir *dir; ChangeSetDirent *dent; gboolean ret = FALSE; parts = g_strsplit (path, "/", 0); n = g_strv_length(parts); dir = root; for (i = 0; i < n; i++) { dname = parts[i]; dent = g_hash_table_lookup (dir->dents, dname); if (!dent) { break; } if (S_ISDIR(dent->mode)) { if (i == (n-1)) { // Always consider folders consistent ret = TRUE; break; } if (!dent->subdir) { break; } dir = dent->subdir; } else if (S_ISREG(dent->mode)) { if (i == (n-1)) { if (size == dent->size && mtime == dent->mtime) ret = TRUE; break; } // finding a file in the middle of a path is invalid break; } } g_strfreev (parts); return ret; } seadrive-fuse-3.0.13/src/change-set.h000066400000000000000000000024071476177674700173530ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ #ifndef SEAF_CHANGE_SET_H #define SEAF_CHANGE_SET_H #include #include "utils.h" struct _ChangeSetDir; struct _ChangeSet { char repo_id[37]; /* A partial tree for all changed directories. */ struct _ChangeSetDir *tree_root; }; typedef struct _ChangeSet ChangeSet; ChangeSet * changeset_new (const char *repo_id); void changeset_free (ChangeSet *changeset); void add_to_changeset (ChangeSet *changeset, char status, unsigned char *sha1, SeafStat *st, const char *modifier, const char *path, const char *new_path); /* @remove_parent: remove the parent dir when it becomes empty. */ void remove_from_changeset (ChangeSet *changeset, const char *path, gboolean remove_parent, const char *top_dir); char * commit_tree_from_changeset (ChangeSet *changeset); // Check whether the information in changeset is consistent with the input gboolean changeset_check_path (ChangeSet *changeset, const char *path, gint64 size, gint64 mtime); #endif seadrive-fuse-3.0.13/src/commit-mgr.c000066400000000000000000000664021476177674700174100ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ #include "common.h" #include "log.h" #include #include "utils.h" #include "db.h" #include "seafile-session.h" #include "commit-mgr.h" #define MAX_TIME_SKEW 259200 /* 3 days */ struct _SeafCommitManagerPriv { int dummy; }; static SeafCommit * load_commit (SeafCommitManager *mgr, const char *repo_id, int version, const char *commit_id); static int save_commit (SeafCommitManager *manager, const char *repo_id, int version, SeafCommit *commit); static void delete_commit (SeafCommitManager *mgr, const char *repo_id, int version, const char *id); static json_t * commit_to_json_object (SeafCommit *commit); static SeafCommit * commit_from_json_object (const char *id, json_t *object); static void compute_commit_id (SeafCommit* commit) { GChecksum *ctx = g_checksum_new(G_CHECKSUM_SHA1); uint8_t sha1[20]; gint64 ctime_n; g_checksum_update (ctx, (guchar *)commit->root_id, 41); g_checksum_update (ctx, (guchar *)commit->creator_id, 41); if (commit->creator_name) g_checksum_update (ctx, (guchar *)commit->creator_name, strlen(commit->creator_name)+1); g_checksum_update (ctx, (guchar *)commit->desc, strlen(commit->desc)+1); /* convert to network byte order */ ctime_n = hton64 (commit->ctime); g_checksum_update (ctx, (guchar *)&ctime_n, sizeof(ctime_n)); gsize len = 20; g_checksum_get_digest (ctx, sha1, &len); rawdata_to_hex (sha1, commit->commit_id, 20); g_checksum_free (ctx); } SeafCommit* seaf_commit_new (const char *commit_id, const char *repo_id, const char *root_id, const char *creator_name, const char *creator_id, const char *desc, guint64 ctime) { SeafCommit *commit; g_return_val_if_fail (repo_id != NULL, NULL); g_return_val_if_fail (root_id != NULL && creator_id != NULL, NULL); commit = g_new0 (SeafCommit, 1); memcpy (commit->repo_id, repo_id, 36); commit->repo_id[36] = '\0'; memcpy (commit->root_id, root_id, 40); commit->root_id[40] = '\0'; commit->creator_name = g_strdup (creator_name); memcpy (commit->creator_id, creator_id, 40); commit->creator_id[40] = '\0'; commit->desc = g_strdup (desc); if (ctime == 0) { /* TODO: use more precise timer */ commit->ctime = (gint64)time(NULL); } else commit->ctime = ctime; if (commit_id == NULL) compute_commit_id (commit); else { memcpy (commit->commit_id, commit_id, 40); commit->commit_id[40] = '\0'; } commit->ref = 1; return commit; } char * seaf_commit_to_data (SeafCommit *commit, gsize *len) { json_t *object; char *json_data; char *ret; object = commit_to_json_object (commit); json_data = json_dumps (object, 0); *len = strlen (json_data); json_decref (object); ret = g_strdup (json_data); free (json_data); return ret; } SeafCommit * seaf_commit_from_data (const char *id, char *data, gsize len) { json_t *object; SeafCommit *commit; json_error_t jerror; object = json_loadb (data, len, 0, &jerror); if (!object) { /* Perhaps the commit object contains invalid UTF-8 character. */ if (data[len-1] == 0) clean_utf8_data (data, len - 1); else clean_utf8_data (data, len); object = json_loadb (data, len, 0, &jerror); if (!object) { seaf_warning ("Failed to load commit json: %s.\n", jerror.text); return NULL; } } commit = commit_from_json_object (id, object); json_decref (object); return commit; } static void seaf_commit_free (SeafCommit *commit) { g_free (commit->desc); g_free (commit->creator_name); if (commit->parent_id) g_free (commit->parent_id); if (commit->second_parent_id) g_free (commit->second_parent_id); if (commit->repo_name) g_free (commit->repo_name); if (commit->repo_desc) g_free (commit->repo_desc); if (commit->repo_category) g_free (commit->repo_category); if (commit->device_name) g_free (commit->device_name); g_free (commit->client_version); g_free (commit->magic); g_free (commit->random_key); g_free (commit->pwd_hash); g_free (commit->pwd_hash_algo); g_free (commit->pwd_hash_params); g_free (commit); } void seaf_commit_ref (SeafCommit *commit) { commit->ref++; } void seaf_commit_unref (SeafCommit *commit) { if (!commit) return; if (--commit->ref <= 0) seaf_commit_free (commit); } SeafCommitManager* seaf_commit_manager_new (SeafileSession *seaf) { SeafCommitManager *mgr = g_new0 (SeafCommitManager, 1); mgr->priv = g_new0 (SeafCommitManagerPriv, 1); mgr->seaf = seaf; mgr->obj_store = seaf_obj_store_new (mgr->seaf, "commits"); return mgr; } int seaf_commit_manager_init (SeafCommitManager *mgr) { if (seaf_obj_store_init (mgr->obj_store) < 0) { seaf_warning ("[commit mgr] Failed to init commit object store.\n"); return -1; } return 0; } int seaf_commit_manager_add_commit (SeafCommitManager *mgr, SeafCommit *commit) { int ret; /* add_commit_to_cache (mgr, commit); */ if ((ret = save_commit (mgr, commit->repo_id, commit->version, commit)) < 0) return -1; return 0; } void seaf_commit_manager_del_commit (SeafCommitManager *mgr, const char *repo_id, int version, const char *id) { g_return_if_fail (id != NULL); delete_commit (mgr, repo_id, version, id); } SeafCommit* seaf_commit_manager_get_commit (SeafCommitManager *mgr, const char *repo_id, int version, const char *id) { SeafCommit *commit; commit = load_commit (mgr, repo_id, version, id); if (!commit) return NULL; /* add_commit_to_cache (mgr, commit); */ return commit; } SeafCommit * seaf_commit_manager_get_commit_compatible (SeafCommitManager *mgr, const char *repo_id, const char *id) { SeafCommit *commit = NULL; /* First try version 1 layout. */ commit = seaf_commit_manager_get_commit (mgr, repo_id, 1, id); if (commit) return commit; /* For compatibility with version 0. */ commit = seaf_commit_manager_get_commit (mgr, repo_id, 0, id); return commit; } static gint compare_commit_by_time (gconstpointer a, gconstpointer b, gpointer unused) { const SeafCommit *commit_a = a; const SeafCommit *commit_b = b; /* Latest commit comes first in the list. */ return (commit_b->ctime - commit_a->ctime); } inline static int insert_parent_commit (GList **list, GHashTable *hash, const char *repo_id, int version, const char *parent_id, gboolean allow_truncate) { SeafCommit *p; char *key; if (g_hash_table_lookup (hash, parent_id) != NULL) return 0; p = seaf_commit_manager_get_commit (seaf->commit_mgr, repo_id, version, parent_id); if (!p) { if (allow_truncate) return 0; seaf_warning ("Failed to find commit %s\n", parent_id); return -1; } *list = g_list_insert_sorted_with_data (*list, p, compare_commit_by_time, NULL); key = g_strdup (parent_id); g_hash_table_replace (hash, key, key); return 0; } gboolean seaf_commit_manager_traverse_commit_tree_with_limit (SeafCommitManager *mgr, const char *repo_id, int version, const char *head, CommitTraverseFunc func, int limit, void *data, gboolean skip_errors) { SeafCommit *commit; GList *list = NULL; GHashTable *commit_hash; gboolean ret = TRUE; /* A hash table for recording id of traversed commits. */ commit_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); commit = seaf_commit_manager_get_commit (mgr, repo_id, version, head); if (!commit) { seaf_warning ("Failed to find commit %s.\n", head); return FALSE; } list = g_list_insert_sorted_with_data (list, commit, compare_commit_by_time, NULL); char *key = g_strdup (commit->commit_id); g_hash_table_replace (commit_hash, key, key); int count = 0; while (list) { gboolean stop = FALSE; commit = list->data; list = g_list_delete_link (list, list); if (!func (commit, data, &stop)) { if (!skip_errors) { seaf_commit_unref (commit); ret = FALSE; goto out; } } /* Stop when limit is reached. If limit < 0, there is no limit; */ if (limit > 0 && ++count == limit) { seaf_commit_unref (commit); break; } if (stop) { seaf_commit_unref (commit); /* stop traverse down from this commit, * but not stop traversing the tree */ continue; } if (commit->parent_id) { if (insert_parent_commit (&list, commit_hash, repo_id, version, commit->parent_id, FALSE) < 0) { if (!skip_errors) { seaf_commit_unref (commit); ret = FALSE; goto out; } } } if (commit->second_parent_id) { if (insert_parent_commit (&list, commit_hash, repo_id, version, commit->second_parent_id, FALSE) < 0) { if (!skip_errors) { seaf_commit_unref (commit); ret = FALSE; goto out; } } } seaf_commit_unref (commit); } out: g_hash_table_destroy (commit_hash); while (list) { commit = list->data; seaf_commit_unref (commit); list = g_list_delete_link (list, list); } return ret; } static gboolean traverse_commit_tree_common (SeafCommitManager *mgr, const char *repo_id, int version, const char *head, CommitTraverseFunc func, void *data, gboolean skip_errors, gboolean allow_truncate) { SeafCommit *commit; GList *list = NULL; GHashTable *commit_hash; gboolean ret = TRUE; commit = seaf_commit_manager_get_commit (mgr, repo_id, version, head); if (!commit) { seaf_warning ("Failed to find commit %s.\n", head); // For head commit corrupted, directly return FALSE // user can repair head by fsck then retraverse the tree return FALSE; } /* A hash table for recording id of traversed commits. */ commit_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); list = g_list_insert_sorted_with_data (list, commit, compare_commit_by_time, NULL); char *key = g_strdup (commit->commit_id); g_hash_table_replace (commit_hash, key, key); while (list) { gboolean stop = FALSE; commit = list->data; list = g_list_delete_link (list, list); if (!func (commit, data, &stop)) { seaf_warning("[comit-mgr] CommitTraverseFunc failed\n"); /* If skip errors, continue to traverse parents. */ if (!skip_errors) { seaf_commit_unref (commit); ret = FALSE; goto out; } } if (stop) { seaf_commit_unref (commit); /* stop traverse down from this commit, * but not stop traversing the tree */ continue; } if (commit->parent_id) { if (insert_parent_commit (&list, commit_hash, repo_id, version, commit->parent_id, allow_truncate) < 0) { seaf_warning("[comit-mgr] insert parent commit failed\n"); /* If skip errors, try insert second parent. */ if (!skip_errors) { seaf_commit_unref (commit); ret = FALSE; goto out; } } } if (commit->second_parent_id) { if (insert_parent_commit (&list, commit_hash, repo_id, version, commit->second_parent_id, allow_truncate) < 0) { seaf_warning("[comit-mgr]insert second parent commit failed\n"); if (!skip_errors) { seaf_commit_unref (commit); ret = FALSE; goto out; } } } seaf_commit_unref (commit); } out: g_hash_table_destroy (commit_hash); while (list) { commit = list->data; seaf_commit_unref (commit); list = g_list_delete_link (list, list); } return ret; } gboolean seaf_commit_manager_traverse_commit_tree (SeafCommitManager *mgr, const char *repo_id, int version, const char *head, CommitTraverseFunc func, void *data, gboolean skip_errors) { return traverse_commit_tree_common (mgr, repo_id, version, head, func, data, skip_errors, FALSE); } gboolean seaf_commit_manager_traverse_commit_tree_truncated (SeafCommitManager *mgr, const char *repo_id, int version, const char *head, CommitTraverseFunc func, void *data, gboolean skip_errors) { return traverse_commit_tree_common (mgr, repo_id, version, head, func, data, skip_errors, TRUE); } gboolean seaf_commit_manager_commit_exists (SeafCommitManager *mgr, const char *repo_id, int version, const char *id) { #if 0 commit = g_hash_table_lookup (mgr->priv->commit_cache, id); if (commit != NULL) return TRUE; #endif return seaf_obj_store_obj_exists (mgr->obj_store, repo_id, version, id); } inline static const char *json_object_get_string_or_null_member (json_t *object,const char *member_name) { json_t *ret = json_object_get (object, member_name); if (ret) return json_string_value(ret); else return NULL; } inline static void json_object_set_string_or_null_member (json_t *object,const char *member_name,const char *value) { if (value) json_object_set_new(object,member_name, json_string(value)); else json_object_set_new(object,member_name, json_null()); } static json_t * commit_to_json_object (SeafCommit *commit) { json_t *object; object = json_object (); json_object_set_string_member (object, "commit_id", commit->commit_id); json_object_set_string_member (object, "root_id", commit->root_id); json_object_set_string_member (object, "repo_id", commit->repo_id); if (commit->creator_name) json_object_set_string_member (object, "creator_name", commit->creator_name); json_object_set_string_member (object, "creator", commit->creator_id); json_object_set_string_member (object, "description", commit->desc); json_object_set_int_member (object, "ctime", (gint64)commit->ctime); json_object_set_string_or_null_member (object, "parent_id", commit->parent_id); json_object_set_string_or_null_member (object, "second_parent_id", commit->second_parent_id); /* * also save repo's properties to commit file, for easy sharing of * repo info */ json_object_set_string_member (object, "repo_name", commit->repo_name); json_object_set_string_member (object, "repo_desc", commit->repo_desc); json_object_set_string_or_null_member (object, "repo_category", commit->repo_category); if (commit->device_name) json_object_set_string_member (object, "device_name", commit->device_name); if (commit->client_version) json_object_set_string_member (object, "client_version", commit->client_version); if (commit->encrypted) json_object_set_string_member (object, "encrypted", "true"); if (commit->encrypted) { json_object_set_int_member (object, "enc_version", commit->enc_version); if (commit->enc_version >= 1 && !commit->pwd_hash) json_object_set_string_member (object, "magic", commit->magic); if (commit->enc_version >= 2) json_object_set_string_member (object, "key", commit->random_key); if (commit->enc_version >= 3) json_object_set_string_member (object, "salt", commit->salt); if (commit->pwd_hash) { json_object_set_string_member (object, "pwd_hash", commit->pwd_hash); json_object_set_string_member (object, "pwd_hash_algo", commit->pwd_hash_algo); json_object_set_string_member (object, "pwd_hash_params", commit->pwd_hash_params); } } if (commit->version != 0) json_object_set_int_member (object, "version", commit->version); if (commit->conflict) json_object_set_int_member (object, "conflict", 1); if (commit->new_merge) json_object_set_int_member (object, "new_merge", 1); if (commit->repaired) json_object_set_int_member (object, "repaired", 1); return object; } static SeafCommit * commit_from_json_object (const char *commit_id, json_t *object) { SeafCommit *commit = NULL; const char *root_id; const char *repo_id; const char *creator_name = NULL; const char *creator; const char *desc; gint64 ctime; const char *parent_id, *second_parent_id; const char *repo_name; const char *repo_desc; const char *repo_category; const char *device_name; const char *client_version; const char *encrypted = NULL; int enc_version = 0; const char *magic = NULL; const char *random_key = NULL; const char *salt = NULL; const char *pwd_hash = NULL; const char *pwd_hash_algo = NULL; const char *pwd_hash_params = NULL; int version = 0; int conflict = 0, new_merge = 0; int repaired = 0; root_id = json_object_get_string_member (object, "root_id"); repo_id = json_object_get_string_member (object, "repo_id"); if (json_object_has_member (object, "creator_name")) creator_name = json_object_get_string_or_null_member (object, "creator_name"); creator = json_object_get_string_member (object, "creator"); desc = json_object_get_string_member (object, "description"); if (!desc) desc = ""; ctime = (guint64) json_object_get_int_member (object, "ctime"); parent_id = json_object_get_string_or_null_member (object, "parent_id"); second_parent_id = json_object_get_string_or_null_member (object, "second_parent_id"); repo_name = json_object_get_string_member (object, "repo_name"); if (!repo_name) repo_name = ""; repo_desc = json_object_get_string_member (object, "repo_desc"); if (!repo_desc) repo_desc = ""; repo_category = json_object_get_string_or_null_member (object, "repo_category"); device_name = json_object_get_string_or_null_member (object, "device_name"); client_version = json_object_get_string_or_null_member (object, "client_version"); if (json_object_has_member (object, "encrypted")) encrypted = json_object_get_string_or_null_member (object, "encrypted"); if (encrypted && strcmp(encrypted, "true") == 0 && json_object_has_member (object, "enc_version")) { enc_version = json_object_get_int_member (object, "enc_version"); magic = json_object_get_string_member (object, "magic"); pwd_hash = json_object_get_string_member (object, "pwd_hash"); pwd_hash_algo = json_object_get_string_member (object, "pwd_hash_algo"); pwd_hash_params = json_object_get_string_member (object, "pwd_hash_params"); } if (enc_version >= 2) random_key = json_object_get_string_member (object, "key"); if (enc_version >= 3) salt = json_object_get_string_member (object, "salt"); if (json_object_has_member (object, "version")) version = json_object_get_int_member (object, "version"); if (json_object_has_member (object, "new_merge")) new_merge = json_object_get_int_member (object, "new_merge"); if (json_object_has_member (object, "conflict")) conflict = json_object_get_int_member (object, "conflict"); if (json_object_has_member (object, "repaired")) repaired = json_object_get_int_member (object, "repaired"); /* sanity check for incoming values. */ if (!repo_id || !is_uuid_valid(repo_id) || !root_id || !is_object_id_valid(root_id) || !creator || strlen(creator) != 40 || (parent_id && !is_object_id_valid(parent_id)) || (second_parent_id && !is_object_id_valid(second_parent_id))) return commit; // If pwd_hash is set, the magic field is no longer included in the commit of the newly created repo. if (!magic) magic = pwd_hash; switch (enc_version) { case 0: break; case 1: if (!magic || strlen(magic) != 32) return NULL; break; case 2: if (!magic || strlen(magic) != 64) return NULL; if (!random_key || strlen(random_key) != 96) return NULL; break; case 3: if (!magic || strlen(magic) != 64) return NULL; if (!random_key || strlen(random_key) != 96) return NULL; if (!salt || strlen(salt) != 64) return NULL; break; case 4: if (!magic || strlen(magic) != 64) return NULL; if (!random_key || strlen(random_key) != 96) return NULL; if (!salt || strlen(salt) != 64) return NULL; break; default: seaf_warning ("Unknown encryption version %d.\n", enc_version); return NULL; } char *creator_name_l = creator_name ? g_ascii_strdown (creator_name, -1) : NULL; commit = seaf_commit_new (commit_id, repo_id, root_id, creator_name_l, creator, desc, ctime); g_free (creator_name_l); commit->parent_id = parent_id ? g_strdup(parent_id) : NULL; commit->second_parent_id = second_parent_id ? g_strdup(second_parent_id) : NULL; commit->repo_name = g_strdup(repo_name); commit->repo_desc = g_strdup(repo_desc); if (encrypted && strcmp(encrypted, "true") == 0) commit->encrypted = TRUE; else commit->encrypted = FALSE; if (repo_category) commit->repo_category = g_strdup(repo_category); commit->device_name = g_strdup(device_name); commit->client_version = g_strdup(client_version); if (commit->encrypted) { commit->enc_version = enc_version; if (enc_version >= 1 && !pwd_hash) commit->magic = g_strdup(magic); if (enc_version >= 2) commit->random_key = g_strdup (random_key); if (enc_version >= 3) commit->salt = g_strdup (salt); if (pwd_hash) { commit->pwd_hash = g_strdup (pwd_hash); commit->pwd_hash_algo = g_strdup (pwd_hash_algo); commit->pwd_hash_params = g_strdup (pwd_hash_params); } } commit->version = version; if (new_merge) commit->new_merge = TRUE; if (conflict) commit->conflict = TRUE; if (repaired) commit->repaired = TRUE; return commit; } static SeafCommit * load_commit (SeafCommitManager *mgr, const char *repo_id, int version, const char *commit_id) { char *data = NULL; int len; SeafCommit *commit = NULL; json_t *object = NULL; json_error_t jerror; if (!commit_id || strlen(commit_id) != 40) return NULL; if (seaf_obj_store_read_obj (mgr->obj_store, repo_id, version, commit_id, (void **)&data, &len) < 0) return NULL; object = json_loadb (data, len, 0, &jerror); if (!object) { /* Perhaps the commit object contains invalid UTF-8 character. */ if (data[len-1] == 0) clean_utf8_data (data, len - 1); else clean_utf8_data (data, len); object = json_loadb (data, len, 0, &jerror); if (!object) { seaf_warning ("Failed to load commit json object: %s.\n", jerror.text); goto out; } } commit = commit_from_json_object (commit_id, object); if (commit) commit->manager = mgr; out: if (object) json_decref (object); g_free (data); return commit; } static int save_commit (SeafCommitManager *manager, const char *repo_id, int version, SeafCommit *commit) { json_t *object = NULL; char *data; gsize len; object = commit_to_json_object (commit); data = json_dumps (object, 0); len = strlen (data); json_decref (object); #ifdef SEAFILE_SERVER if (seaf_obj_store_write_obj (manager->obj_store, repo_id, version, commit->commit_id, data, (int)len, TRUE) < 0) { g_free (data); return -1; } #else if (seaf_obj_store_write_obj (manager->obj_store, repo_id, version, commit->commit_id, data, (int)len, FALSE) < 0) { g_free (data); return -1; } #endif free (data); return 0; } static void delete_commit (SeafCommitManager *mgr, const char *repo_id, int version, const char *id) { seaf_obj_store_delete_obj (mgr->obj_store, repo_id, version, id); } int seaf_commit_manager_remove_store (SeafCommitManager *mgr, const char *store_id) { return seaf_obj_store_remove_store (mgr->obj_store, store_id); } seadrive-fuse-3.0.13/src/commit-mgr.h000066400000000000000000000145131476177674700174110ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ #ifndef SEAF_COMMIT_MGR_H #define SEAF_COMMIT_MGR_H struct _SeafCommitManager; typedef struct _SeafCommit SeafCommit; #include #include "db.h" #include "obj-store.h" struct _SeafCommit { struct _SeafCommitManager *manager; int ref; char commit_id[41]; char repo_id[37]; char root_id[41]; /* the fs root */ char *desc; char *creator_name; char creator_id[41]; guint64 ctime; /* creation time */ char *parent_id; char *second_parent_id; char *repo_name; char *repo_desc; char *repo_category; char *device_name; char *client_version; gboolean encrypted; int enc_version; char *magic; char *random_key; char *salt; char *pwd_hash; char *pwd_hash_algo; char *pwd_hash_params; int version; gboolean new_merge; gboolean conflict; gboolean repaired; }; /** * @commit_id: if this is NULL, will create a new id. * @ctime: if this is 0, will use current time. * * Any new commit should be added to commit manager before used. */ SeafCommit * seaf_commit_new (const char *commit_id, const char *repo_id, const char *root_id, const char *author_name, const char *creator_id, const char *desc, guint64 ctime); char * seaf_commit_to_data (SeafCommit *commit, gsize *len); SeafCommit * seaf_commit_from_data (const char *id, char *data, gsize len); void seaf_commit_ref (SeafCommit *commit); void seaf_commit_unref (SeafCommit *commit); /* Set stop to TRUE if you want to stop traversing a branch in the history graph. Note, if currently there are multi branches, this function will be called again. So, set stop to TRUE not always stop traversing the history graph. */ typedef gboolean (*CommitTraverseFunc) (SeafCommit *commit, void *data, gboolean *stop); struct _SeafileSession; typedef struct _SeafCommitManager SeafCommitManager; typedef struct _SeafCommitManagerPriv SeafCommitManagerPriv; struct _SeafCommitManager { struct _SeafileSession *seaf; sqlite3 *db; struct SeafObjStore *obj_store; SeafCommitManagerPriv *priv; }; SeafCommitManager * seaf_commit_manager_new (struct _SeafileSession *seaf); int seaf_commit_manager_init (SeafCommitManager *mgr); /** * Add a commit to commit manager and persist it to disk. * Any new commit should be added to commit manager before used. * This function increments ref count of the commit object. * Not MT safe. */ int seaf_commit_manager_add_commit (SeafCommitManager *mgr, SeafCommit *commit); /** * Delete a commit from commit manager and permanently remove it from disk. * A commit object to be deleted should have ref cournt <= 1. * Not MT safe. */ void seaf_commit_manager_del_commit (SeafCommitManager *mgr, const char *repo_id, int version, const char *id); /** * Find a commit object. * This function increments ref count of returned object. * Not MT safe. */ SeafCommit* seaf_commit_manager_get_commit (SeafCommitManager *mgr, const char *repo_id, int version, const char *id); /** * Get a commit object, with compatibility between version 0 and version 1. * It will first try to get commit with version 1 layout; if fails, will * try version 0 layout for compatibility. * This is useful for loading a repo. In that case, we don't know the version * of the repo before loading its head commit. */ SeafCommit * seaf_commit_manager_get_commit_compatible (SeafCommitManager *mgr, const char *repo_id, const char *id); /** * Traverse the commits DAG start from head in topological order. * The ordering is based on commit time. * return FALSE if some commits is missing, TRUE otherwise. */ gboolean seaf_commit_manager_traverse_commit_tree (SeafCommitManager *mgr, const char *repo_id, int version, const char *head, CommitTraverseFunc func, void *data, gboolean skip_errors); /* * The same as the above function, but stops traverse down if parent commit * doesn't exists, instead of returning error. */ gboolean seaf_commit_manager_traverse_commit_tree_truncated (SeafCommitManager *mgr, const char *repo_id, int version, const char *head, CommitTraverseFunc func, void *data, gboolean skip_errors); /** * Works the same as seaf_commit_manager_traverse_commit_tree, but stops * traversing when a total number of _limit_ commits is reached. If * limit <= 0, there is no limit */ gboolean seaf_commit_manager_traverse_commit_tree_with_limit (SeafCommitManager *mgr, const char *repo_id, int version, const char *head, CommitTraverseFunc func, int limit, void *data, gboolean skip_errors); gboolean seaf_commit_manager_commit_exists (SeafCommitManager *mgr, const char *repo_id, int version, const char *id); int seaf_commit_manager_remove_store (SeafCommitManager *mgr, const char *store_id); #endif seadrive-fuse-3.0.13/src/common.h000066400000000000000000000012751476177674700166270ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ #ifndef COMMON_H #define COMMON_H #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include /* uint32_t */ #include /* size_t */ #include #include #include #include #include #include #define EMPTY_SHA1 "0000000000000000000000000000000000000000" #define CURRENT_ENC_VERSION 2 #define DEFAULT_PROTO_VERSION 1 #define CURRENT_PROTO_VERSION 7 #define CURRENT_REPO_VERSION 1 #define SEAF_PATH_MAX 4096 #define MAX_GET_FINISHED_FILES 10 #endif seadrive-fuse-3.0.13/src/curl-init.c000066400000000000000000000024431476177674700172360ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ #include "common.h" #ifndef USE_GPL_CRYPTO #include #include #include #include "curl-init.h" pthread_mutex_t* curl_locks = NULL; void seafile_curl_locking_callback(int mode, int n, const char* file, int line) { if (mode & CRYPTO_LOCK) { pthread_mutex_lock (&curl_locks[n]); } else { pthread_mutex_unlock (&curl_locks[n]); } } void seafile_curl_init() { curl_global_init (CURL_GLOBAL_ALL); int i; curl_locks = malloc (sizeof(pthread_mutex_t) * CRYPTO_num_locks()); for (i = 0; i < CRYPTO_num_locks(); ++i) { pthread_mutex_init (&curl_locks[i], NULL); } /* On Windows it's better to use the default id_function. * As per http://linux.die.net/man/3/crypto_set_id_callback, * the default id_functioin uses system's default thread * identifying API. */ CRYPTO_set_id_callback (pthread_self); CRYPTO_set_locking_callback (seafile_curl_locking_callback); } void seafile_curl_deinit() { int i; CRYPTO_set_id_callback (0); CRYPTO_set_locking_callback (0); for (i = 0; i < CRYPTO_num_locks(); ++i) { pthread_mutex_destroy (&curl_locks[i]); } free (curl_locks); } #endif seadrive-fuse-3.0.13/src/curl-init.h000066400000000000000000000003061476177674700172370ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ #ifndef __CURL_INIT_H__ #define __CURL_INIT_H__ void seafile_curl_init(void); void seafile_curl_deinit(void); #endif seadrive-fuse-3.0.13/src/db.c000066400000000000000000000121011476177674700157050ustar00rootroot00000000000000 #include #include #include "db.h" int sqlite_open_db (const char *db_path, sqlite3 **db) { int result; const char *errmsg; result = sqlite3_open (db_path, db); if (result) { errmsg = sqlite3_errmsg (*db); g_warning ("Couldn't open database:'%s', %s\n", db_path, errmsg ? errmsg : "no error given"); sqlite3_close (*db); return -1; } return 0; } int sqlite_close_db (sqlite3 *db) { return sqlite3_close (db); } sqlite3_stmt * sqlite_query_prepare (sqlite3 *db, const char *sql) { sqlite3_stmt *stmt; int result; result = sqlite3_prepare_v2 (db, sql, -1, &stmt, NULL); if (result != SQLITE_OK) { const gchar *str = sqlite3_errmsg (db); g_warning ("Couldn't prepare query, error:%d->'%s'\n\t%s\n", result, str ? str : "no error given", sql); return NULL; } return stmt; } int sqlite_query_exec (sqlite3 *db, const char *sql) { char *errmsg = NULL; int result; result = sqlite3_exec (db, sql, NULL, NULL, &errmsg); if (result != SQLITE_OK) { if (errmsg != NULL) { g_warning ("SQL error: %d - %s\n:\t%s\n", result, errmsg, sql); sqlite3_free (errmsg); } return -1; } return 0; } int sqlite_begin_transaction (sqlite3 *db) { char *sql = "BEGIN TRANSACTION;"; return sqlite_query_exec (db, sql); } int sqlite_end_transaction (sqlite3 *db) { char *sql = "END TRANSACTION;"; return sqlite_query_exec (db, sql); } int sqlite_rollback_transaction (sqlite3 *db) { char *sql = "ROLLBACK TRANSACTION;"; return sqlite_query_exec (db, sql); } gboolean sqlite_check_for_existence (sqlite3 *db, const char *sql) { sqlite3_stmt *stmt; int result; stmt = sqlite_query_prepare (db, sql); if (!stmt) return FALSE; result = sqlite3_step (stmt); if (result == SQLITE_ERROR) { const gchar *str = sqlite3_errmsg (db); g_warning ("Couldn't execute query, error: %d->'%s'\n", result, str ? str : "no error given"); sqlite3_finalize (stmt); return FALSE; } sqlite3_finalize (stmt); if (result == SQLITE_ROW) return TRUE; return FALSE; } int sqlite_foreach_selected_row (sqlite3 *db, const char *sql, SqliteRowFunc callback, void *data) { sqlite3_stmt *stmt; int result; int n_rows = 0; stmt = sqlite_query_prepare (db, sql); if (!stmt) { return -1; } while (1) { result = sqlite3_step (stmt); if (result != SQLITE_ROW) break; n_rows++; if (!callback (stmt, data)) break; } if (result == SQLITE_ERROR) { const gchar *s = sqlite3_errmsg (db); g_warning ("Couldn't execute query, error: %d->'%s'\n", result, s ? s : "no error given"); sqlite3_finalize (stmt); return -1; } sqlite3_finalize (stmt); return n_rows; } int sqlite_get_int (sqlite3 *db, const char *sql) { int ret = -1; int result; sqlite3_stmt *stmt; if ( !(stmt = sqlite_query_prepare(db, sql)) ) return 0; result = sqlite3_step (stmt); if (result == SQLITE_ROW) { ret = sqlite3_column_int (stmt, 0); sqlite3_finalize (stmt); return ret; } if (result == SQLITE_ERROR) { const gchar *str = sqlite3_errmsg (db); g_warning ("Couldn't execute query, error: %d->'%s'\n", result, str ? str : "no error given"); sqlite3_finalize (stmt); return -1; } sqlite3_finalize(stmt); return ret; } gint64 sqlite_get_int64 (sqlite3 *db, const char *sql) { gint64 ret = -1; int result; sqlite3_stmt *stmt; if ( !(stmt = sqlite_query_prepare(db, sql)) ) return 0; result = sqlite3_step (stmt); if (result == SQLITE_ROW) { ret = sqlite3_column_int64 (stmt, 0); sqlite3_finalize (stmt); return ret; } if (result == SQLITE_ERROR) { const gchar *str = sqlite3_errmsg (db); g_warning ("Couldn't execute query, error: %d->'%s'\n", result, str ? str : "no error given"); sqlite3_finalize (stmt); return -1; } sqlite3_finalize(stmt); return ret; } char *sqlite_get_string (sqlite3 *db, const char *sql) { const char *res = NULL; int result; sqlite3_stmt *stmt; char *ret; if ( !(stmt = sqlite_query_prepare(db, sql)) ) return NULL; result = sqlite3_step (stmt); if (result == SQLITE_ROW) { res = (const char *)sqlite3_column_text (stmt, 0); ret = g_strdup(res); sqlite3_finalize (stmt); return ret; } if (result == SQLITE_ERROR) { const gchar *str = sqlite3_errmsg (db); g_warning ("Couldn't execute query, error: %d->'%s'\n", result, str ? str : "no error given"); sqlite3_finalize (stmt); return NULL; } sqlite3_finalize(stmt); return NULL; } seadrive-fuse-3.0.13/src/db.h000066400000000000000000000016451476177674700157250ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ #ifndef DB_UTILS_H #define DB_UTILS_H #include int sqlite_open_db (const char *db_path, sqlite3 **db); int sqlite_close_db (sqlite3 *db); sqlite3_stmt *sqlite_query_prepare (sqlite3 *db, const char *sql); int sqlite_query_exec (sqlite3 *db, const char *sql); int sqlite_begin_transaction (sqlite3 *db); int sqlite_end_transaction (sqlite3 *db); int sqlite_rollback_transaction (sqlite3 *db); gboolean sqlite_check_for_existence (sqlite3 *db, const char *sql); typedef gboolean (*SqliteRowFunc) (sqlite3_stmt *stmt, void *data); int sqlite_foreach_selected_row (sqlite3 *db, const char *sql, SqliteRowFunc callback, void *data); int sqlite_get_int (sqlite3 *db, const char *sql); gint64 sqlite_get_int64 (sqlite3 *db, const char *sql); char *sqlite_get_string (sqlite3 *db, const char *sql); #endif seadrive-fuse-3.0.13/src/diff-simple.c000066400000000000000000000547061476177674700175400ustar00rootroot00000000000000#include "common.h" #include "diff-simple.h" #include "utils.h" #include "log.h" DiffEntry * diff_entry_new (char type, char status, unsigned char *sha1, const char *name) { DiffEntry *de = g_new0 (DiffEntry, 1); de->type = type; de->status = status; memcpy (de->sha1, sha1, 20); de->name = g_strdup(name); return de; } DiffEntry * diff_entry_new_from_dirent (char type, char status, SeafDirent *dent, const char *basedir) { DiffEntry *de = g_new0 (DiffEntry, 1); unsigned char sha1[20]; char *path; hex_to_rawdata (dent->id, sha1, 20); path = g_strconcat (basedir, dent->name, NULL); de->type = type; de->status = status; memcpy (de->sha1, sha1, 20); de->name = path; if (type == DIFF_TYPE_COMMITS && (status == DIFF_STATUS_ADDED || status == DIFF_STATUS_MODIFIED || status == DIFF_STATUS_DIR_ADDED || status == DIFF_STATUS_DIR_DELETED)) { de->mtime = dent->mtime; de->mode = dent->mode; de->modifier = g_strdup(dent->modifier); de->size = dent->size; } return de; } void diff_entry_free (DiffEntry *de) { g_free (de->name); if (de->new_name) g_free (de->new_name); g_free (de->modifier); g_free (de); } inline static gboolean dirent_same (SeafDirent *denta, SeafDirent *dentb) { return (strcmp (dentb->id, denta->id) == 0 && denta->mode == dentb->mode && denta->mtime == dentb->mtime); } static int diff_files (int n, SeafDirent *dents[], const char *basedir, DiffOptions *opt) { SeafDirent *files[3]; int i, n_files = 0; memset (files, 0, sizeof(files[0])*n); for (i = 0; i < n; ++i) { if (dents[i] && S_ISREG(dents[i]->mode)) { files[i] = dents[i]; ++n_files; } } if (n_files == 0) return 0; return opt->file_cb (n, basedir, files, opt->data); } static int diff_trees_recursive (int n, SeafDir *trees[], const char *basedir, DiffOptions *opt); static int diff_directories (int n, SeafDirent *dents[], const char *basedir, DiffOptions *opt) { SeafDirent *dirs[3]; int i, n_dirs = 0; char *dirname = ""; int ret; SeafDir *sub_dirs[3], *dir; memset (dirs, 0, sizeof(dirs[0])*n); for (i = 0; i < n; ++i) { if (dents[i] && S_ISDIR(dents[i]->mode)) { dirs[i] = dents[i]; ++n_dirs; } } if (n_dirs == 0) return 0; gboolean recurse = TRUE; ret = opt->dir_cb (n, basedir, dirs, opt->data, &recurse); if (ret < 0) return ret; if (!recurse) return 0; memset (sub_dirs, 0, sizeof(sub_dirs[0])*n); for (i = 0; i < n; ++i) { if (dents[i] != NULL && S_ISDIR(dents[i]->mode)) { dir = seaf_fs_manager_get_seafdir (seaf->fs_mgr, opt->store_id, opt->version, dents[i]->id); if (!dir) { seaf_warning ("Failed to find dir %s:%s.\n", opt->store_id, dents[i]->id); ret = -1; goto free_sub_dirs; } sub_dirs[i] = dir; dirname = dents[i]->name; } } char *new_basedir = g_strconcat (basedir, dirname, "/", NULL); ret = diff_trees_recursive (n, sub_dirs, new_basedir, opt); g_free (new_basedir); free_sub_dirs: for (i = 0; i < n; ++i) seaf_dir_free (sub_dirs[i]); return ret; } static int diff_trees_recursive (int n, SeafDir *trees[], const char *basedir, DiffOptions *opt) { GList *ptrs[3]; SeafDirent *dents[3]; int i; SeafDirent *dent; char *first_name; gboolean done; int ret = 0; for (i = 0; i < n; ++i) { if (trees[i]) ptrs[i] = trees[i]->entries; else ptrs[i] = NULL; } while (1) { first_name = NULL; memset (dents, 0, sizeof(dents[0])*n); done = TRUE; /* Find the "largest" name, assuming dirents are sorted. */ for (i = 0; i < n; ++i) { if (ptrs[i] != NULL) { done = FALSE; dent = ptrs[i]->data; if (!first_name) first_name = dent->name; else if (strcmp(dent->name, first_name) > 0) first_name = dent->name; } } if (done) break; /* * Setup dir entries for all names that equal to first_name */ for (i = 0; i < n; ++i) { if (ptrs[i] != NULL) { dent = ptrs[i]->data; if (strcmp(first_name, dent->name) == 0) { dents[i] = dent; ptrs[i] = ptrs[i]->next; } } } if (n == 2 && dents[0] && dents[1] && dirent_same(dents[0], dents[1])) continue; if (n == 3 && dents[0] && dents[1] && dents[2] && dirent_same(dents[0], dents[1]) && dirent_same(dents[0], dents[2])) continue; /* Diff files of this level. */ ret = diff_files (n, dents, basedir, opt); if (ret < 0) return ret; /* Recurse into sub level. */ ret = diff_directories (n, dents, basedir, opt); if (ret < 0) return ret; } return ret; } int diff_trees (int n, const char *roots[], DiffOptions *opt) { SeafDir **trees, *root; int i, ret; g_return_val_if_fail (n == 2 || n == 3, -1); trees = g_new0 (SeafDir *, n); for (i = 0; i < n; ++i) { root = seaf_fs_manager_get_seafdir (seaf->fs_mgr, opt->store_id, opt->version, roots[i]); if (!root) { seaf_warning ("Failed to find dir %s:%s.\n", opt->store_id, roots[i]); g_free (trees); return -1; } trees[i] = root; } ret = diff_trees_recursive (n, trees, "", opt); for (i = 0; i < n; ++i) seaf_dir_free (trees[i]); g_free (trees); return ret; } typedef struct DiffData { GList **results; gboolean fold_dir_diff; } DiffData; static int twoway_diff_files (int n, const char *basedir, SeafDirent *files[], void *vdata) { DiffData *data = vdata; GList **results = data->results; DiffEntry *de; SeafDirent *tree1 = files[0]; SeafDirent *tree2 = files[1]; unsigned char sha1[20]; if (!tree1) { de = diff_entry_new_from_dirent (DIFF_TYPE_COMMITS, DIFF_STATUS_ADDED, tree2, basedir); *results = g_list_prepend (*results, de); return 0; } if (!tree2) { de = diff_entry_new_from_dirent (DIFF_TYPE_COMMITS, DIFF_STATUS_DELETED, tree1, basedir); *results = g_list_prepend (*results, de); return 0; } if (!dirent_same (tree1, tree2)) { de = diff_entry_new_from_dirent (DIFF_TYPE_COMMITS, DIFF_STATUS_MODIFIED, tree2, basedir); hex_to_rawdata (tree1->id, sha1, 20); memcpy (de->old_sha1, sha1, 20); *results = g_list_prepend (*results, de); } return 0; } static int twoway_diff_dirs (int n, const char *basedir, SeafDirent *dirs[], void *vdata, gboolean *recurse) { DiffData *data = vdata; GList **results = data->results; DiffEntry *de; SeafDirent *tree1 = dirs[0]; SeafDirent *tree2 = dirs[1]; if (!tree1) { if (strcmp (tree2->id, EMPTY_SHA1) == 0 || data->fold_dir_diff) { de = diff_entry_new_from_dirent (DIFF_TYPE_COMMITS, DIFF_STATUS_DIR_ADDED, tree2, basedir); *results = g_list_prepend (*results, de); *recurse = FALSE; } else *recurse = TRUE; return 0; } if (!tree2) { de = diff_entry_new_from_dirent (DIFF_TYPE_COMMITS, DIFF_STATUS_DIR_DELETED, tree1, basedir); *results = g_list_prepend (*results, de); if (data->fold_dir_diff) { *recurse = FALSE; } else *recurse = TRUE; return 0; } return 0; } int diff_commits (SeafCommit *commit1, SeafCommit *commit2, GList **results, gboolean fold_dir_diff) { SeafRepo *repo = NULL; DiffOptions opt; const char *roots[2]; repo = seaf_repo_manager_get_repo (seaf->repo_mgr, commit1->repo_id); if (!repo) { seaf_warning ("Failed to get repo %s.\n", commit1->repo_id); return -1; } DiffData data; memset (&data, 0, sizeof(data)); data.results = results; data.fold_dir_diff = fold_dir_diff; memset (&opt, 0, sizeof(opt)); memcpy (opt.store_id, repo->id, 36); opt.version = repo->version; opt.file_cb = twoway_diff_files; opt.dir_cb = twoway_diff_dirs; opt.data = &data; roots[0] = commit1->root_id; roots[1] = commit2->root_id; diff_trees (2, roots, &opt); diff_resolve_renames (results); seaf_repo_unref (repo); return 0; } int diff_commit_roots (const char *store_id, int version, const char *root1, const char *root2, GList **results, gboolean fold_dir_diff) { DiffOptions opt; const char *roots[2]; DiffData data; memset (&data, 0, sizeof(data)); data.results = results; data.fold_dir_diff = fold_dir_diff; memset (&opt, 0, sizeof(opt)); memcpy (opt.store_id, store_id, 36); opt.version = version; opt.file_cb = twoway_diff_files; opt.dir_cb = twoway_diff_dirs; opt.data = &data; roots[0] = root1; roots[1] = root2; diff_trees (2, roots, &opt); diff_resolve_renames (results); return 0; } static int threeway_diff_files (int n, const char *basedir, SeafDirent *files[], void *vdata) { DiffData *data = vdata; SeafDirent *m = files[0]; SeafDirent *p1 = files[1]; SeafDirent *p2 = files[2]; GList **results = data->results; DiffEntry *de; /* diff m with both p1 and p2. */ if (m && p1 && p2) { if (!dirent_same(m, p1) && !dirent_same (m, p2)) { de = diff_entry_new_from_dirent (DIFF_TYPE_COMMITS, DIFF_STATUS_MODIFIED, m, basedir); *results = g_list_prepend (*results, de); } } else if (!m && p1 && p2) { de = diff_entry_new_from_dirent (DIFF_TYPE_COMMITS, DIFF_STATUS_DELETED, p1, basedir); *results = g_list_prepend (*results, de); } else if (m && !p1 && p2) { if (!dirent_same (m, p2)) { de = diff_entry_new_from_dirent (DIFF_TYPE_COMMITS, DIFF_STATUS_MODIFIED, m, basedir); *results = g_list_prepend (*results, de); } } else if (m && p1 && !p2) { if (!dirent_same (m, p1)) { de = diff_entry_new_from_dirent (DIFF_TYPE_COMMITS, DIFF_STATUS_MODIFIED, m, basedir); *results = g_list_prepend (*results, de); } } else if (m && !p1 && !p2) { de = diff_entry_new_from_dirent (DIFF_TYPE_COMMITS, DIFF_STATUS_ADDED, m, basedir); *results = g_list_prepend (*results, de); } /* Nothing to do for: * 1. !m && p1 && !p2; * 2. !m && !p1 && p2; * 3. !m && !p1 && !p2 (should not happen) */ return 0; } static int threeway_diff_dirs (int n, const char *basedir, SeafDirent *dirs[], void *vdata, gboolean *recurse) { *recurse = TRUE; return 0; } int diff_merge (SeafCommit *merge, GList **results, gboolean fold_dir_diff) { SeafRepo *repo = NULL; DiffOptions opt; const char *roots[3]; SeafCommit *parent1, *parent2; g_return_val_if_fail (*results == NULL, -1); g_return_val_if_fail (merge->parent_id != NULL && merge->second_parent_id != NULL, -1); repo = seaf_repo_manager_get_repo (seaf->repo_mgr, merge->repo_id); if (!repo) { seaf_warning ("Failed to get repo %s.\n", merge->repo_id); return -1; } parent1 = seaf_commit_manager_get_commit (seaf->commit_mgr, repo->id, repo->version, merge->parent_id); if (!parent1) { seaf_warning ("failed to find commit %s:%s.\n", repo->id, merge->parent_id); seaf_repo_unref (repo); return -1; } parent2 = seaf_commit_manager_get_commit (seaf->commit_mgr, repo->id, repo->version, merge->second_parent_id); if (!parent2) { seaf_warning ("failed to find commit %s:%s.\n", repo->id, merge->second_parent_id); seaf_repo_unref (repo); seaf_commit_unref (parent1); return -1; } DiffData data; memset (&data, 0, sizeof(data)); data.results = results; data.fold_dir_diff = fold_dir_diff; memset (&opt, 0, sizeof(opt)); memcpy (opt.store_id, repo->id, 36); opt.version = repo->version; opt.file_cb = threeway_diff_files; opt.dir_cb = threeway_diff_dirs; opt.data = &data; roots[0] = merge->root_id; roots[1] = parent1->root_id; roots[2] = parent2->root_id; int ret = diff_trees (3, roots, &opt); diff_resolve_renames (results); seaf_repo_unref (repo); seaf_commit_unref (parent1); seaf_commit_unref (parent2); return ret; } int diff_merge_roots (const char *store_id, int version, const char *merged_root, const char *p1_root, const char *p2_root, GList **results, gboolean fold_dir_diff) { DiffOptions opt; const char *roots[3]; g_return_val_if_fail (*results == NULL, -1); DiffData data; memset (&data, 0, sizeof(data)); data.results = results; data.fold_dir_diff = fold_dir_diff; memset (&opt, 0, sizeof(opt)); memcpy (opt.store_id, store_id, 36); opt.version = version; opt.file_cb = threeway_diff_files; opt.dir_cb = threeway_diff_dirs; opt.data = &data; roots[0] = merged_root; roots[1] = p1_root; roots[2] = p2_root; diff_trees (3, roots, &opt); diff_resolve_renames (results); return 0; } /* This function only resolve "strict" rename, i.e. two files must be * exactly the same. * Don't detect rename of empty files and empty dirs. */ void diff_resolve_renames (GList **diff_entries) { GHashTable *deleted; GList *p; GList *added = NULL; DiffEntry *de; unsigned char empty_sha1[20]; memset (empty_sha1, 0, 20); /* Hash and equal functions for raw sha1. */ deleted = g_hash_table_new (ccnet_sha1_hash, ccnet_sha1_equal); /* Collect all "deleted" entries. */ for (p = *diff_entries; p != NULL; p = p->next) { de = p->data; if ((de->status == DIFF_STATUS_DELETED || de->status == DIFF_STATUS_DIR_DELETED) && memcmp (de->sha1, empty_sha1, 20) != 0) g_hash_table_insert (deleted, de->sha1, p); } /* Collect all "added" entries into a separate list. */ for (p = *diff_entries; p != NULL; p = p->next) { de = p->data; if ((de->status == DIFF_STATUS_ADDED || de->status == DIFF_STATUS_DIR_ADDED) && memcmp (de->sha1, empty_sha1, 20) != 0) added = g_list_prepend (added, p); } /* For each "added" entry, if we find a "deleted" entry with * the same content, we find a rename pair. */ p = added; while (p != NULL) { GList *p_add, *p_del; DiffEntry *de_add, *de_del, *de_rename; int rename_status; p_add = p->data; de_add = p_add->data; p_del = g_hash_table_lookup (deleted, de_add->sha1); if (p_del) { de_del = p_del->data; if (de_add->status == DIFF_STATUS_DIR_ADDED) rename_status = DIFF_STATUS_DIR_RENAMED; else rename_status = DIFF_STATUS_RENAMED; de_rename = diff_entry_new (de_del->type, rename_status, de_del->sha1, de_del->name); de_rename->new_name = g_strdup(de_add->name); *diff_entries = g_list_delete_link (*diff_entries, p_add); *diff_entries = g_list_delete_link (*diff_entries, p_del); *diff_entries = g_list_prepend (*diff_entries, de_rename); g_hash_table_remove (deleted, de_add->sha1); diff_entry_free (de_add); diff_entry_free (de_del); } p = g_list_delete_link (p, p); } g_hash_table_destroy (deleted); } static gboolean is_redundant_empty_dir (DiffEntry *de_dir, DiffEntry *de_file) { int dir_len; if (de_dir->status == DIFF_STATUS_DIR_ADDED && de_file->status == DIFF_STATUS_DELETED) { dir_len = strlen (de_dir->name); if (strlen (de_file->name) > dir_len && strncmp (de_dir->name, de_file->name, dir_len) == 0) return TRUE; } if (de_dir->status == DIFF_STATUS_DIR_DELETED && de_file->status == DIFF_STATUS_ADDED) { dir_len = strlen (de_dir->name); if (strlen (de_file->name) > dir_len && strncmp (de_dir->name, de_file->name, dir_len) == 0) return TRUE; } return FALSE; } /* * An empty dir entry may be added by deleting all the files under it. * Similarly, an empty dir entry may be deleted by adding some file in it. * In both cases, we don't want to include the empty dir entry in the * diff results. */ void diff_resolve_empty_dirs (GList **diff_entries) { GList *empty_dirs = NULL; GList *p, *dir, *file; DiffEntry *de, *de_dir, *de_file; for (p = *diff_entries; p != NULL; p = p->next) { de = p->data; if (de->status == DIFF_STATUS_DIR_ADDED || de->status == DIFF_STATUS_DIR_DELETED) empty_dirs = g_list_prepend (empty_dirs, p); } for (dir = empty_dirs; dir != NULL; dir = dir->next) { de_dir = ((GList *)dir->data)->data; for (file = *diff_entries; file != NULL; file = file->next) { de_file = file->data; if (is_redundant_empty_dir (de_dir, de_file)) { *diff_entries = g_list_delete_link (*diff_entries, dir->data); break; } } } g_list_free (empty_dirs); } inline static char * get_basename (char *path) { char *slash; slash = strrchr (path, '/'); if (!slash) return path; return (slash + 1); } char * diff_results_to_description (GList *results) { GList *p; DiffEntry *de; char *added_file = NULL, *mod_file = NULL, *removed_file = NULL; char *renamed_file = NULL, *renamed_dir = NULL; char *new_dir = NULL, *removed_dir = NULL; int n_added = 0, n_mod = 0, n_removed = 0, n_renamed = 0; int n_new_dir = 0, n_removed_dir = 0, n_renamed_dir = 0; GString *desc; if (results == NULL) return NULL; for (p = results; p != NULL; p = p->next) { de = p->data; switch (de->status) { case DIFF_STATUS_ADDED: if (n_added == 0) added_file = get_basename(de->name); n_added++; break; case DIFF_STATUS_DELETED: if (n_removed == 0) removed_file = get_basename(de->name); n_removed++; break; case DIFF_STATUS_RENAMED: if (n_renamed == 0) renamed_file = get_basename(de->name); n_renamed++; break; case DIFF_STATUS_DIR_RENAMED: if (n_renamed_dir == 0) renamed_dir = get_basename(de->name); n_renamed_dir++; break; case DIFF_STATUS_MODIFIED: if (n_mod == 0) mod_file = get_basename(de->name); n_mod++; break; case DIFF_STATUS_DIR_ADDED: if (n_new_dir == 0) new_dir = get_basename(de->name); n_new_dir++; break; case DIFF_STATUS_DIR_DELETED: if (n_removed_dir == 0) removed_dir = get_basename(de->name); n_removed_dir++; break; } } desc = g_string_new (""); if (n_added == 1) g_string_append_printf (desc, "Added \"%s\".\n", added_file); else if (n_added > 1) g_string_append_printf (desc, "Added \"%s\" and %d more files.\n", added_file, n_added - 1); if (n_mod == 1) g_string_append_printf (desc, "Modified \"%s\".\n", mod_file); else if (n_mod > 1) g_string_append_printf (desc, "Modified \"%s\" and %d more files.\n", mod_file, n_mod - 1); if (n_removed == 1) g_string_append_printf (desc, "Deleted \"%s\".\n", removed_file); else if (n_removed > 1) g_string_append_printf (desc, "Deleted \"%s\" and %d more files.\n", removed_file, n_removed - 1); if (n_renamed == 1) g_string_append_printf (desc, "Renamed \"%s\".\n", renamed_file); else if (n_renamed > 1) g_string_append_printf (desc, "Renamed \"%s\" and %d more files.\n", renamed_file, n_renamed - 1); if (n_renamed_dir == 1) g_string_append_printf (desc, "Renamed directory \"%s\".\n", renamed_dir); else if (n_renamed_dir > 1) g_string_append_printf (desc, "Renamed \"%s\" and %d more directories.\n", renamed_dir, n_renamed_dir - 1); if (n_new_dir == 1) g_string_append_printf (desc, "Added directory \"%s\".\n", new_dir); else if (n_new_dir > 1) g_string_append_printf (desc, "Added \"%s\" and %d more directories.\n", new_dir, n_new_dir - 1); if (n_removed_dir == 1) g_string_append_printf (desc, "Removed directory \"%s\".\n", removed_dir); else if (n_removed_dir > 1) g_string_append_printf (desc, "Removed \"%s\" and %d more directories.\n", removed_dir, n_removed_dir - 1); return g_string_free (desc, FALSE); } seadrive-fuse-3.0.13/src/diff-simple.h000066400000000000000000000055301476177674700175340ustar00rootroot00000000000000#ifndef DIFF_SIMPLE_H #define DIFF_SIMPLE_H #include #include "seafile-session.h" #define DIFF_TYPE_WORKTREE 'W' /* diff from index to worktree */ #define DIFF_TYPE_INDEX 'I' /* diff from commit to index */ #define DIFF_TYPE_COMMITS 'C' /* diff between two commits*/ #define DIFF_STATUS_ADDED 'A' #define DIFF_STATUS_DELETED 'D' #define DIFF_STATUS_MODIFIED 'M' #define DIFF_STATUS_RENAMED 'R' #define DIFF_STATUS_UNMERGED 'U' #define DIFF_STATUS_DIR_ADDED 'B' #define DIFF_STATUS_DIR_DELETED 'C' #define DIFF_STATUS_DIR_RENAMED 'E' typedef struct DiffEntry { char type; char status; unsigned char sha1[20]; /* used for resolve rename */ unsigned char old_sha1[20]; /* set when status is MODIFIED, only for two way diff */ char *name; char *new_name; /* only used in rename. */ /* Fields only used for ADDED, DIR_ADDED, MODIFIED types, * used in check out files/dirs.*/ gint64 mtime; unsigned int mode; char *modifier; gint64 size; } DiffEntry; DiffEntry * diff_entry_new (char type, char status, unsigned char *sha1, const char *name); void diff_entry_free (DiffEntry *de); /* * @fold_dir_diff: if TRUE, only the top level directory will be included * in the diff result if a directory with files is added or removed. * Otherwise all the files in the direcotory will be recursively * included in the diff result. */ int diff_commits (SeafCommit *commit1, SeafCommit *commit2, GList **results, gboolean fold_dir_diff); int diff_commit_roots (const char *store_id, int version, const char *root1, const char *root2, GList **results, gboolean fold_dir_diff); int diff_merge (SeafCommit *merge, GList **results, gboolean fold_dir_diff); int diff_merge_roots (const char *store_id, int version, const char *merged_root, const char *p1_root, const char *p2_root, GList **results, gboolean fold_dir_diff); void diff_resolve_renames (GList **diff_entries); void diff_resolve_empty_dirs (GList **diff_entries); char * diff_results_to_description (GList *results); typedef int (*DiffFileCB) (int n, const char *basedir, SeafDirent *files[], void *data); typedef int (*DiffDirCB) (int n, const char *basedir, SeafDirent *dirs[], void *data, gboolean *recurse); typedef struct DiffOptions { char store_id[37]; int version; DiffFileCB file_cb; DiffDirCB dir_cb; void *data; } DiffOptions; int diff_trees (int n, const char *roots[], DiffOptions *opt); #endif seadrive-fuse-3.0.13/src/file-cache-mgr.c000066400000000000000000002610111476177674700200710ustar00rootroot00000000000000#include "common.h" #include #include #include #include #include "seafile-session.h" #include "seafile-config.h" #include "seafile-error.h" #define DEBUG_FLAG SEAFILE_DEBUG_SYNC #include "log.h" #include "repo-tree.h" #include "utils.h" #include "timer.h" #define MAX_THREADS 3 #define DEFAULT_CLEAN_CACHE_INTERVAL 600 // 10 minutes #define DEFAULT_CACHE_SIZE_LIMIT (10000000000LL) // 10GB /* true if the cached content is already on the server, otherwise false. * This attr is used in cache cleaning. */ #define SEAFILE_UPLOADED_ATTR "user.uploaded" /* These 3 attrs can be seen outside. */ #define SEAFILE_MTIME_ATTR "user.seafile-mtime" #define SEAFILE_SIZE_ATTR "user.seafile-size" #define SEAFILE_FILE_ID_ATTR "user.file-id" typedef struct CachedFile { // Identifier(repo_id/file_path) for CachedFile in memory char *file_key; // Record CachedFile open number gint n_open; // Repo unique name used when get download progress char *repo_uname; // Caculate download progress gint64 downloaded; gint64 total_download; gboolean force_canceled; gint64 last_cancel_time; } CachedFile; typedef struct FileCacheMgrPriv { // Parent dir(seafile_dir/file_cache) to store cached files char *base_path; // repo_id/file_path <-> CachedFile GHashTable *cached_files; pthread_mutex_t cache_lock; GThreadPool *tpool; // repo_id/file_path <-> CachedFileHandle GHashTable *cache_tasks; pthread_mutex_t task_lock; /* Cache block size list for files. */ GHashTable *block_map_cache; pthread_rwlock_t block_map_lock; SeafTimer *clean_timer; int clean_cache_interval; gint64 cache_size_limit; GQueue *downloaded_files; pthread_mutex_t downloaded_files_lock; } FileCacheMgrPriv; typedef struct FileDownloadedInfo { char *file_path; char *server; char *user; } FileDownloadedInfo; static void file_downloaded_info_free (FileDownloadedInfo *info) { if (!info) return; g_free (info->file_path); g_free (info->server); g_free (info->user); g_free (info); return; } static char * cached_file_ondisk_path (CachedFile *file) { FileCacheMgrPriv *priv = seaf->file_cache_mgr->priv; char **tokens = g_strsplit (file->file_key, "/", 2); char *repo_id = tokens[0]; char *path = tokens[1]; char *ondisk_path = g_build_filename (priv->base_path, repo_id, path, NULL); g_strfreev (tokens); return ondisk_path; } static void free_cached_file (CachedFile *file) { if (!file) return; g_free (file->file_key); g_free (file->repo_uname); g_free (file); } static void cached_file_ref (CachedFile *file) { if (!file) return; g_atomic_int_inc (&file->n_open); } static void cached_file_unref (CachedFile *file) { if (!file) return; if (g_atomic_int_dec_and_test (&file->n_open)) free_cached_file (file); } static CachedFileHandle * cached_file_handle_new () { CachedFileHandle *handle = g_new0 (CachedFileHandle, 1); pthread_mutex_init (&handle->lock, NULL); return handle; } static void free_cached_file_handle (CachedFileHandle *file_handle) { if (!file_handle) return; close (file_handle->fd); pthread_mutex_destroy (&file_handle->lock); cached_file_unref (file_handle->cached_file); if (file_handle->crypt) g_free (file_handle->crypt); g_free (file_handle->server); g_free (file_handle->user); g_free (file_handle); } gboolean cached_file_handle_is_readonly (CachedFileHandle *file_handle) { return file_handle->is_readonly; } static void fetch_file_worker (gpointer data, gpointer user_data); static void * clean_cache_worker (void *data); static void * remove_deleted_cache_worker (void *data); static void * check_download_file_time_worker (void *data); FileCacheMgr * file_cache_mgr_new (const char *parent_dir) { GError *error = NULL; FileCacheMgr *mgr = g_new0 (FileCacheMgr, 1); FileCacheMgrPriv *priv = g_new0 (FileCacheMgrPriv, 1); priv->tpool = g_thread_pool_new (fetch_file_worker, priv, MAX_THREADS, FALSE, &error); if (!priv->tpool) { seaf_warning ("Failed to create thread pool for cache file: %s.\n", error == NULL ? "" : error->message); g_free (priv); g_free (mgr); return NULL; } priv->base_path = g_build_filename (parent_dir, "file-cache", NULL); if (g_mkdir_with_parents (priv->base_path, 0777) < 0) { seaf_warning ("Failed to create file_cache dir: %s.\n", strerror (errno)); g_free (priv->base_path); g_thread_pool_free (priv->tpool, TRUE, FALSE); g_free (priv); g_free (mgr); return NULL; } priv->cached_files = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify)cached_file_unref); pthread_mutex_init (&priv->cache_lock, NULL); priv->cache_tasks = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify)g_free, NULL); pthread_mutex_init (&priv->task_lock, NULL); priv->block_map_cache = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); pthread_rwlock_init (&priv->block_map_lock, NULL); priv->downloaded_files = g_queue_new (); pthread_mutex_init (&priv->downloaded_files_lock, NULL); mgr->priv = priv; return mgr; } /* static void */ /* load_downloaded_file_list (FileCacheMgr *mgr); */ void file_cache_mgr_init (FileCacheMgr *mgr) { int clean_cache_interval; gint64 cache_size_limit; gboolean exists; clean_cache_interval = seafile_session_config_get_int (seaf, KEY_CLEAN_CACHE_INTERVAL, &exists); if (!exists) { clean_cache_interval = DEFAULT_CLEAN_CACHE_INTERVAL; } cache_size_limit = seafile_session_config_get_int64 (seaf, KEY_CACHE_SIZE_LIMIT, &exists); if (!exists) { cache_size_limit = DEFAULT_CACHE_SIZE_LIMIT; } mgr->priv->clean_cache_interval = clean_cache_interval; mgr->priv->cache_size_limit = cache_size_limit; /* load_downloaded_file_list (mgr); */ } void file_cache_mgr_start (FileCacheMgr *mgr) { pthread_t tid; int rc; pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); rc = pthread_create (&tid, &attr, clean_cache_worker, mgr); if (rc != 0) { seaf_warning ("Failed to create clean cache worker thread.\n"); } rc = pthread_create (&tid, &attr, remove_deleted_cache_worker, mgr); if (rc != 0) { seaf_warning ("Failed to create remove deleted cache worker thread.\n"); } rc = pthread_create (&tid, &attr, check_download_file_time_worker, mgr->priv); if (rc != 0) { seaf_warning ("Failed to check download file time worker thread.\n"); } } static void cancel_fetch_task_by_file (FileCacheMgr *mgr, const char *file_key) { CachedFileHandle *handle; pthread_mutex_lock (&mgr->priv->task_lock); handle = g_hash_table_lookup (mgr->priv->cache_tasks, file_key); if (handle) handle->fetch_canceled = TRUE; pthread_mutex_unlock (&mgr->priv->task_lock); } /* Cached file handling. */ static void send_file_download_notification (const char *type, const char *repo_id, const char *path) { json_t *msg; char *repo_uname, *fullpath; msg = json_object (); json_object_set_new (msg, "type", json_string(type)); repo_uname = seaf_repo_manager_get_repo_display_name (seaf->repo_mgr, repo_id); fullpath = g_strconcat (repo_uname, "/", path, NULL); json_object_set_new (msg, "path", json_string(fullpath)); mq_mgr_push_msg (seaf->mq_mgr, SEADRIVE_EVENT_CHAN, msg); g_free (repo_uname); g_free (fullpath); } static size_t fill_block (void *contents, size_t size, size_t nmemb, void *userp) { CachedFileHandle *file_handle = userp; size_t realsize = size *nmemb; char *buffer = contents; int ret = 0; char *dec_out = NULL; int dec_out_len = -1; if (file_handle->crypt) { ret = seafile_decrypt (&dec_out, &dec_out_len, buffer, realsize, file_handle->crypt); if (ret != 0){ seaf_warning ("Decrypt block failed.\n"); return -1; } ret = writen (file_handle->fd, dec_out, dec_out_len); } else { ret = writen (file_handle->fd, buffer, realsize); } if (ret < 0) { seaf_warning ("Failed to write cache file %s: %s.\n", file_handle->cached_file->file_key, strerror (errno)); } else { file_handle->cached_file->downloaded += ret; } if (!file_handle->crypt) g_atomic_int_add (&(seaf->sync_mgr->recv_bytes), realsize); g_free (dec_out); return ret; } static size_t get_encrypted_block_cb (void *contents, size_t size, size_t nmemb, void *userp) { size_t realsize = size *nmemb; CachedFileHandle *file_handle = userp; if (file_handle->cached_file->force_canceled) { seaf_message ("Cancel fetching %s\n", file_handle->cached_file->file_key); return 0; } /* If the task is marked as canceled and the fetch task is the only * remaining referer to the cached file, it should be stopped. */ if (file_handle->fetch_canceled && file_handle->cached_file->n_open <= 2) { seaf_debug ("Cancel fetching %s after close\n", file_handle->cached_file->file_key); return 0; } file_handle->blk_buffer.content = g_realloc (file_handle->blk_buffer.content, file_handle->blk_buffer.size + realsize); if (!file_handle->blk_buffer.content) { seaf_warning ("Not enough memory.\n"); return 0; } memcpy (file_handle->blk_buffer.content + file_handle->blk_buffer.size, contents, realsize); file_handle->blk_buffer.size += realsize; g_atomic_int_add (&(seaf->sync_mgr->recv_bytes), realsize); return realsize; } static size_t get_block_cb (void *contents, size_t size, size_t nmemb, void *userp) { size_t realsize = size *nmemb; CachedFileHandle *file_handle = userp; if (file_handle->cached_file->force_canceled) { seaf_message ("Cancel fetching %s\n", file_handle->cached_file->file_key); return 0; } /* If the task is marked as canceled and the fetch task is the only * remaining referer to the cached file, it should be stopped. */ if (file_handle->fetch_canceled && file_handle->cached_file->n_open <= 2) { seaf_debug ("Cancel fetching %s after close\n", file_handle->cached_file->file_key); return 0; } g_atomic_int_add (&(seaf->sync_mgr->recv_bytes), realsize); fill_block (contents, size, nmemb, userp); return realsize; } static int mark_file_cached (const char *ondisk_path, RepoTreeStat *st) { if (seaf_set_file_time (ondisk_path, st->mtime) < 0) { seaf_warning ("Failed to set mtime for %s.\n", ondisk_path); return -1; } char attr[64]; int len; if (seaf_setxattr (ondisk_path, SEAFILE_FILE_ID_ATTR, st->id, 41) < 0) { seaf_warning ("Failed to set file-id xattr for %s: %s.\n", ondisk_path, strerror(errno)); return -1; } len = snprintf (attr, sizeof(attr), "%"G_GINT64_FORMAT, st->size); if (seaf_setxattr (ondisk_path, SEAFILE_SIZE_ATTR, attr, len+1) < 0) { seaf_warning ("Failed to set seafile-size xattr for %s: %s.\n", ondisk_path, strerror(errno)); return -1; } len = snprintf (attr, sizeof(attr), "%"G_GINT64_FORMAT, st->mtime); if (seaf_setxattr (ondisk_path, SEAFILE_MTIME_ATTR, attr, len+1) < 0) { seaf_warning ("Failed to set seafile-mtime xattr for %s: %s.\n", ondisk_path, strerror(errno)); return -1; } return 0; } static int get_file_from_server (HttpServerInfo *server_info, SeafRepo *repo, const char *path, gint64 block_offset, CachedFileHandle *handle) { int http_status = 200; if (http_tx_manager_get_file (seaf->http_tx_mgr, server_info->host, server_info->use_fileserver_port, repo->token, repo->id, path, block_offset, get_block_cb, handle, &http_status) < 0) { if (!handle->fetch_canceled && !handle->cached_file->force_canceled) seaf_message ("Failed to get file %s from server\n", path); if (handle->notified_download_start) send_file_download_notification ("file-download.stop", repo->id, path); return -1; } return 0; } static void calculate_block_offset (Seafile *file, gint64 *block_map, CachedFileHandle *handle, int *block_offset, gint64 *file_offset) { SeafStat st; gint64 size; int i; gint64 offset, prev_offset; if (seaf_fstat (handle->fd, &st) < 0) { seaf_warning ("Failed to stat cached file %s: %s\n", handle->cached_file->file_key, strerror(errno)); return; } size = (gint64)st.st_size; offset = 0; prev_offset = 0; for (i = 0; i < file->n_blocks; ++i) { prev_offset = offset; offset += block_map[i]; if (offset > size) break; } *block_offset = i; *file_offset = prev_offset; } #define CACHE_BLOCK_MAP_THRESHOLD 3000000 /* 3MB */ static void fetch_file_worker (gpointer data, gpointer user_data) { CachedFileHandle *file_handle = data; FileCacheMgrPriv *priv = user_data; SeafRepo *repo = NULL; RepoTreeStat st; Seafile *file = NULL; HttpServerInfo *server_info = NULL; char *file_key = NULL; char **key_comps = NULL; char *repo_id; char *file_path; char *ondisk_path = NULL; int http_status = 200; gint64 *block_map = NULL; int n_blocks = 0; int block_offset = 0; gint64 file_offset = 0; gboolean have_invisible = FALSE; file_key = g_strdup (file_handle->cached_file->file_key); key_comps = g_strsplit (file_handle->cached_file->file_key, "/", 2); repo_id = key_comps[0]; file_path = key_comps[1]; ondisk_path = g_build_filename (priv->base_path, repo_id, file_path, NULL); repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id); if (!repo) { seaf_warning ("Failed to get repo %.8s.\n", repo_id); goto out; } have_invisible = seaf_repo_manager_include_invisible_perm (seaf->repo_mgr, repo->id); server_info = seaf_sync_manager_get_server_info (seaf->sync_mgr, repo->server, repo->user); if (!server_info) { seaf_warning ("Failed to get current server info.\n"); goto out; } if (repo->encrypted && repo->is_passwd_set) file_handle->crypt = seafile_crypt_new (repo->enc_version, repo->enc_key, repo->enc_iv); if (repo_tree_stat_path (repo->tree, file_path, &st) < 0) { seaf_warning ("Failed to stat repo tree path %s in repo %s.\n", file_path, repo_id); goto out; } file_handle->start_download_time = (gint64)time(NULL); if (!seaf_fs_manager_object_exists (seaf->fs_mgr, repo->id, repo->version, st.id)) { if (http_tx_manager_get_fs_object (seaf->http_tx_mgr, server_info->host, server_info->use_fileserver_port, repo->token, repo->id, st.id, file_path) < 0) { seaf_warning ("Failed to get file object %s of %s from server %s.\n", st.id, file_key, server_info->host); goto out; } } file = seaf_fs_manager_get_seafile (seaf->fs_mgr, repo->id, repo->version, st.id); if (!file) { seaf_warning ("Failed to get file object %s in repo %s.\n", st.id, repo_id); goto out; } if (file->file_size >= CACHE_BLOCK_MAP_THRESHOLD) { pthread_rwlock_rdlock (&priv->block_map_lock); block_map = g_hash_table_lookup (priv->block_map_cache, st.id); pthread_rwlock_unlock (&priv->block_map_lock); if (!block_map) { if (http_tx_manager_get_file_block_map (seaf->http_tx_mgr, server_info->host, server_info->use_fileserver_port, repo->token, repo->id, st.id, &block_map, &n_blocks) == 0) { if (n_blocks != file->n_blocks) { seaf_warning ("Block number return from server does not match" "seafile object. File-id is %s. File is %s" "Returned %d, expect %d\n", st.id, file_key, n_blocks, file->n_blocks); } else { pthread_rwlock_wrlock (&priv->block_map_lock); g_hash_table_replace (priv->block_map_cache, g_strdup(st.id), block_map); pthread_rwlock_unlock (&priv->block_map_lock); } } else { seaf_warning ("Failed to get block map for file object %s of %s " "server %s.\n", st.id, file_key, server_info->host); } } if (block_map) { calculate_block_offset (file, block_map, file_handle, &block_offset, &file_offset); } } seaf_sync_manager_update_active_path (seaf->sync_mgr, repo_id, file_path, st.mode, SYNC_STATUS_SYNCING); file_handle->cached_file->downloaded = 0; file_handle->cached_file->total_download = file->file_size; if (file_offset > 0) { seaf_util_lseek (file_handle->fd, file_offset, SEEK_SET); } if (have_invisible && !file_handle->crypt) { if (get_file_from_server (server_info, repo, file_path, block_offset, file_handle) < 0) { goto out; } } else { int i = block_offset; if (file_handle->crypt) { for (; i < file->n_blocks; i++) { http_status = 200; memset (&file_handle->blk_buffer, 0, sizeof(file_handle->blk_buffer)); if (http_tx_manager_get_block (seaf->http_tx_mgr, server_info->host, server_info->use_fileserver_port, repo->token, repo->id, file->blk_sha1s[i], get_encrypted_block_cb, file_handle, &http_status) < 0) { if (!file_handle->fetch_canceled && !file_handle->cached_file->force_canceled) seaf_warning ("Failed to get block %s of %s from server %s.\n", file->blk_sha1s[i], file_key, server_info->host); seaf_sync_manager_delete_active_path (seaf->sync_mgr, repo_id, file_path); if (file_handle->notified_download_start) send_file_download_notification ("file-download.stop", repo_id, file_path); goto out; } fill_block (file_handle->blk_buffer.content, file_handle->blk_buffer.size, 1, file_handle); g_free (file_handle->blk_buffer.content); } } else { for (; i < file->n_blocks; i++) { http_status = 200; if (http_tx_manager_get_block (seaf->http_tx_mgr, server_info->host, server_info->use_fileserver_port, repo->token, repo->id, file->blk_sha1s[i], get_block_cb, file_handle, &http_status) < 0) { if (!file_handle->fetch_canceled && !file_handle->cached_file->force_canceled) seaf_warning ("Failed to get block %s of %s from server %s.\n", file->blk_sha1s[i], file_key, server_info->host); seaf_sync_manager_delete_active_path (seaf->sync_mgr, repo_id, file_path); if (file_handle->notified_download_start) send_file_download_notification ("file-download.stop", repo_id, file_path); goto out; } } } } if (file_handle->cached_file->downloaded != file->file_size) { seaf_warning("Failed to download file %s in repo %s.\n", file_path, repo_id); seaf_sync_manager_delete_active_path(seaf->sync_mgr, repo_id, file_path); seaf_util_unlink (ondisk_path); goto out; } pthread_mutex_lock (&priv->downloaded_files_lock); FileDownloadedInfo *info = g_new0 (FileDownloadedInfo, 1); info->file_path = g_build_filename (file_handle->cached_file->repo_uname, file_path, NULL); info->server = g_strdup (repo->server); info->user = g_strdup (repo->user); g_queue_push_head (priv->downloaded_files, info); if (priv->downloaded_files->length > MAX_GET_FINISHED_FILES) { file_downloaded_info_free (g_queue_pop_tail (priv->downloaded_files)); } pthread_mutex_unlock (&priv->downloaded_files_lock); /* Must close file handle (thus the fd of this handle) before updating file * mtime. On Windows, closing an fd will update file mtime. */ gboolean notified_download_start = file_handle->notified_download_start; free_cached_file_handle (file_handle); file_handle = NULL; if (mark_file_cached (ondisk_path, &st) == 0) { seaf_sync_manager_update_active_path (seaf->sync_mgr, repo_id, file_path, st.mode, SYNC_STATUS_SYNCED); if (notified_download_start) send_file_download_notification ("file-download.done", repo_id, file_path); } /* File content must be on the server when it's first downloaded. */ file_cache_mgr_set_file_uploaded (seaf->file_cache_mgr, repo_id, file_path, TRUE); out: /* If server returns 5xx error, the error response will be written into the * file contents. In this case, remove the "corrupted" cached file. */ if (http_status >= 500) { seaf_util_unlink (ondisk_path); } g_strfreev (key_comps); g_free (ondisk_path); if (server_info) seaf_sync_manager_free_server_info (server_info); if (repo) seaf_repo_unref (repo); if (file) seafile_unref (file); pthread_mutex_lock (&priv->task_lock); g_hash_table_remove (priv->cache_tasks, file_key); pthread_mutex_unlock (&priv->task_lock); if (file_handle) free_cached_file_handle (file_handle); g_free (file_key); } typedef enum CachedFileStatus { CACHED_FILE_STATUS_NOT_EXISTS = 0, CACHED_FILE_STATUS_FETCHING, CACHED_FILE_STATUS_CACHED, CACHED_FILE_STATUS_OUTDATED, } CachedFileStatus; static CachedFileStatus check_cached_file_status (FileCacheMgrPriv *priv, CachedFile *file, RepoTreeStat *st) { char *ondisk_path = NULL; char mtime_attr[64], size_attr[64]; char file_id_attr[41]; gssize len; SeafStat file_st; char mtime_str[16], size_str[64]; CachedFileStatus ret = CACHED_FILE_STATUS_NOT_EXISTS; ondisk_path = cached_file_ondisk_path (file); if (!seaf_util_exists (ondisk_path)) { ret = CACHED_FILE_STATUS_NOT_EXISTS; goto out; } if (seaf_stat (ondisk_path, &file_st) < 0) { seaf_warning ("Failed to stat file %s:%s.\n", file->file_key, strerror(errno)); ret = CACHED_FILE_STATUS_NOT_EXISTS; goto out; } /* seafile-mtime attr is set after seafile-size and file-id attrs, * so if seafile-mtime attr exists, the other two attrs should exist. */ len = seaf_getxattr (ondisk_path, SEAFILE_MTIME_ATTR, mtime_attr, sizeof(mtime_attr)); if (len < 0) { ret = CACHED_FILE_STATUS_FETCHING; goto out; } len = seaf_getxattr (ondisk_path, SEAFILE_SIZE_ATTR, size_attr, sizeof(size_attr)); if (len < 0) { ret = CACHED_FILE_STATUS_FETCHING; goto out; } len = seaf_getxattr (ondisk_path, SEAFILE_FILE_ID_ATTR, file_id_attr, sizeof(file_id_attr)); if (len < 0) { ret = CACHED_FILE_STATUS_FETCHING; goto out; } if (strcmp (st->id, file_id_attr) == 0) { /* Cached file is up-to-date. */ ret = CACHED_FILE_STATUS_CACHED; goto out; } snprintf (mtime_str, sizeof(mtime_str), "%"G_GINT64_FORMAT, (gint64)file_st.st_mtime); snprintf (size_str, sizeof(size_str), "%"G_GINT64_FORMAT, (gint64)file_st.st_size); if (strcmp (mtime_str, mtime_attr) == 0 && strcmp (size_str, size_attr) == 0) { // File id is changed but mtime is not changed, try to refetch the file ret = CACHED_FILE_STATUS_OUTDATED; goto out; } // The cached file is changed locally, while the internal ID of that file // is also updated. This means a conflict situation. The syncing algorithm // tries to avoid this conflict from happening by delaying update to internal // file id. But if this happens, we try not to overwrite local changes. ret = CACHED_FILE_STATUS_CACHED; out: g_free (ondisk_path); return ret; } static int remove_file_attrs (const char *path); static int start_cache_task (FileCacheMgrPriv *priv, CachedFile *file, RepoTreeStat *st, const char *server, const char *user) { char *ondisk_path = NULL; int flags; CachedFileHandle *handle = NULL; int fd; int ret = 0; pthread_mutex_lock (&priv->task_lock); /* Only one fetch task can be running for each file. */ if (!g_hash_table_lookup (priv->cache_tasks, file->file_key)) { CachedFileStatus status = check_cached_file_status (priv, file, st); if (status == CACHED_FILE_STATUS_CACHED) { /* If the cached file is up-to-date, don't need to fetch again. */ goto out; } gboolean overwrite = FALSE; if (status == CACHED_FILE_STATUS_NOT_EXISTS || status == CACHED_FILE_STATUS_OUTDATED) { overwrite = TRUE; } /* If cached file is in "FETCHING" status, its download was * interrupted by restart. */ ondisk_path = cached_file_ondisk_path (file); if (status == CACHED_FILE_STATUS_OUTDATED) { remove_file_attrs (ondisk_path); } flags = (O_WRONLY | O_CREAT); if (overwrite) flags |= O_TRUNC; fd = seaf_util_create (ondisk_path, flags, 0664); if (fd < 0) { seaf_warning ("Failed to open file %s:%s.\n", file->file_key, strerror (errno)); ret = -1; goto out; } /* Only non-zero size file needs to be fetched from server. */ if (st->size != 0) { handle = cached_file_handle_new (); handle->cached_file = file; handle->fd = fd; handle->cached_file->total_download = st->size; handle->server = g_strdup(server); handle->user = g_strdup(user); cached_file_ref (file); g_hash_table_replace (priv->cache_tasks, g_strdup(file->file_key), handle); g_thread_pool_push (priv->tpool, handle, NULL); } else { mark_file_cached (ondisk_path, st); close (fd); } } out: pthread_mutex_unlock (&priv->task_lock); g_free (ondisk_path); return ret; } static int make_parent_dir (CachedFile *cached_file) { char *file_path = NULL; char *parent_dir = NULL; int ret = 0; file_path = cached_file_ondisk_path (cached_file); parent_dir = g_path_get_dirname (file_path); if (g_file_test (parent_dir, G_FILE_TEST_IS_DIR)) { goto out; } // Make sure parent dir has been created if (checkdir_with_mkdir (parent_dir) < 0) { seaf_warning ("Failed to make parent dir %s: %s.\n", parent_dir, strerror (errno)); ret = -1; } out: g_free (parent_dir); g_free (file_path); return ret; } static CachedFile * get_cached_file (FileCacheMgrPriv *priv, const char *repo_id, const char *repo_uname, const char *file_path, const char *server, const char *user, RepoTreeStat *st) { CachedFile *cached_file = NULL; char *file_key; file_key = g_strconcat (repo_id, "/", file_path, NULL); pthread_mutex_lock (&priv->cache_lock); cached_file = g_hash_table_lookup (priv->cached_files, file_key); if (!cached_file) { cached_file = g_new0 (CachedFile, 1); cached_file->file_key = file_key; cached_file->repo_uname = g_strdup (repo_uname); g_hash_table_replace (priv->cached_files, cached_file->file_key, cached_file); /* Keep 1 open number for internal reference. */ cached_file_ref (cached_file); } else { g_free (file_key); /* If a file fetch task was cancelled before, reset this flag when the file * is opened again. Some applications retry the open operation after failure. * We have to prevent opening the file for a while after its download was * cancelled. */ gint64 now = (gint64)time(NULL); if (now - cached_file->last_cancel_time < 2) { pthread_mutex_unlock (&priv->cache_lock); cached_file = NULL; goto out; } cached_file->force_canceled = FALSE; cached_file->last_cancel_time = 0; } cached_file_ref (cached_file); pthread_mutex_unlock (&priv->cache_lock); if (make_parent_dir (cached_file) < 0) { cached_file_unref (cached_file); cached_file = NULL; goto out; } if (start_cache_task (priv, cached_file, st, server, user) < 0) { seaf_warning ("Failed to start cache task for file %s in repo %s.\n", file_path, repo_id); cached_file_unref (cached_file); cached_file = NULL; } out: return cached_file; } void file_cache_mgr_cache_file (FileCacheMgr *mgr, const char *repo_id, const char *path, RepoTreeStat *st) { FileCacheMgrPriv *priv = mgr->priv; CachedFile *cached_file = NULL; char *file_key; SeafRepo *repo; repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id); if (!repo) { seaf_warning ("Failed to find repo %s.\n", repo_id); return; } file_key = g_strconcat (repo_id, "/", path, NULL); pthread_mutex_lock (&priv->cache_lock); cached_file = g_hash_table_lookup (priv->cached_files, file_key); if (!cached_file) { cached_file = g_new0 (CachedFile, 1); cached_file->file_key = file_key; cached_file->repo_uname = g_strdup (repo->repo_uname); g_hash_table_replace (priv->cached_files, cached_file->file_key, cached_file); /* Keep 1 open number for internal reference. */ cached_file_ref (cached_file); } else { g_free (file_key); } cached_file_ref (cached_file); pthread_mutex_unlock (&priv->cache_lock); if (make_parent_dir (cached_file) < 0) { goto out; } if (start_cache_task (priv, cached_file, st, repo->server, repo->user) < 0) { seaf_warning ("Failed to start cache task for file %s in repo %s.\n", path, repo_id); } out: cached_file_unref (cached_file); seaf_repo_unref (repo); } static CachedFileHandle * open_cached_file_handle (FileCacheMgrPriv *priv, const char *repo_id, const char *repo_uname, const char *file_path, const char *server, const char *user, RepoTreeStat *st, int flags) { CachedFile *file = NULL; CachedFileHandle *file_handle = NULL; char *ondisk_path = NULL; int fd; file = get_cached_file (priv, repo_id, repo_uname, file_path, server, user, st); if (!file) { return NULL; } ondisk_path = cached_file_ondisk_path (file); fd = seaf_util_open (ondisk_path, O_RDWR); if (fd < 0) { seaf_warning ("Failed to open file %s:%s.\n", ondisk_path, strerror (errno)); cached_file_unref (file); goto out; } file_handle = cached_file_handle_new (); file_handle->cached_file = file; file_handle->fd = fd; file_handle->file_size = st->size; file_handle->server = g_strdup(server); file_handle->user = g_strdup(user); if (flags & O_RDONLY) file_handle->is_readonly = TRUE; out: g_free (ondisk_path); return file_handle; } CachedFileHandle * file_cache_mgr_open (FileCacheMgr *mgr, const char *repo_id, const char *file_path, int flags) { FileCacheMgrPriv *priv = mgr->priv; SeafRepo *repo = NULL; RepoTreeStat tree_stat; CachedFileHandle *handle = NULL; int rc; repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id); if (!repo) { seaf_warning ("Failed to get repo %s.\n", repo_id); goto out; } rc = repo_tree_stat_path (repo->tree, file_path, &tree_stat); if (rc < 0) { seaf_warning ("Failed to stat tree path %s in repo %s: %s.\n", file_path, repo_id, strerror(-rc)); goto out; } /* If we need to fetch a file from server, but network or server was * already down, return failure. */ if ((!file_cache_mgr_is_file_cached (mgr, repo_id, file_path) || file_cache_mgr_is_file_outdated (mgr, repo_id, file_path)) && tree_stat.size != 0 && seaf_sync_manager_is_server_disconnected (seaf->sync_mgr)) { return NULL; } handle = open_cached_file_handle (priv, repo_id, repo->repo_uname, file_path, repo->server, repo->user, &tree_stat, flags); if (!handle) { seaf_warning ("Failed to open handle to cached file %s in repo %s.\n", file_path, repo_id); } out: seaf_repo_unref (repo); return handle; } void file_cache_mgr_close_file_handle (CachedFileHandle *file_handle) { char *file_key = g_strdup(file_handle->cached_file->file_key); free_cached_file_handle (file_handle); cancel_fetch_task_by_file (seaf->file_cache_mgr, file_key); g_free (file_key); } #define CHECK_CACHE_INTERVAL 100000 /* 100ms */ #define READ_CACHE_TIMEOUT 5000000 /* 5 seconds. */ #define WRITE_CACHE_TIMEOUT 5000000 /* 5 seconds. */ #define RANDOM_READ_THRESHOLD 100000 /* 100KB */ static gssize get_file_range_from_server (const char *server, const char *user, const char *repo_id, const char *path, char *buf, guint64 offset, size_t size) { seaf_debug ("Get file range %"G_GUINT64_FORMAT"-%"G_GUINT64_FORMAT" of file %s/%s.\n", offset, offset+size-1, repo_id, path); SeafAccount *account = seaf_repo_manager_get_account (seaf->repo_mgr, server, user); if (!account) { seaf_warning ("Failed to get account %s %s.\n", server, user); return -1; } gssize ret = http_tx_manager_get_file_range (seaf->http_tx_mgr, account->server, account->token, repo_id, path, buf, offset, size); seaf_account_free (account); if (ret < 0) return -EIO; else return ret; } gssize file_cache_mgr_read (FileCacheMgr *mgr, CachedFileHandle *handle, char *buf, size_t size, guint64 offset) { CachedFile *file = handle->cached_file; char *ondisk_path = NULL; char *repo_id = NULL, *path = NULL; char attr[64]; SeafStat st; gssize ret; int wait_time = 0; SeafRepo *repo = NULL; ondisk_path = cached_file_ondisk_path (file); if (!seaf_util_exists (ondisk_path)) { seaf_warning ("Cached file %s does not exist when read.\n", file->file_key); ret = -EIO; goto out; } file_cache_mgr_get_file_info_from_handle (mgr, handle, &repo_id, &path); repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id); if (!repo) { seaf_warning ("Failed to get repo %s\n", repo_id); ret = -EIO; goto out; } /* Wait until the intended region is cached. */ while (1) { if (seaf_getxattr (ondisk_path, SEAFILE_MTIME_ATTR, attr, sizeof(attr)) > 0) break; if (seaf_stat (ondisk_path, &st) < 0) { seaf_warning ("Failed to stat cache file %s: %s.\n", ondisk_path, strerror (errno)); ret = -EIO; goto out; } if ((offset + (guint64)size <= (guint64)handle->file_size) && (offset + (guint64)size <= (guint64)st.st_size)) { break; } if ((offset > (guint64)st.st_size) && (offset - (guint64)st.st_size > RANDOM_READ_THRESHOLD)) { ret = get_file_range_from_server (repo->server, repo->user, repo_id, path, buf, offset, size); goto out; } if (wait_time >= READ_CACHE_TIMEOUT) { seaf_debug ("Read cache file %s timeout.\n", ondisk_path); ret = -EIO; goto out; } /* Sleep 100ms to wait the file to be cached. */ g_usleep (CHECK_CACHE_INTERVAL); wait_time += CHECK_CACHE_INTERVAL; } pthread_mutex_lock (&handle->lock); gint64 rc = seaf_util_lseek (handle->fd, (gint64)offset, SEEK_SET); if (rc < 0) { seaf_warning ("Failed to lseek file %s: %s.\n", ondisk_path, strerror(errno)); ret = -errno; pthread_mutex_unlock (&handle->lock); goto out; } ret = readn (handle->fd, buf, size); if (ret < 0) { seaf_warning ("Failed to read file %s: %s.\n", ondisk_path, strerror (errno)); ret = -errno; pthread_mutex_unlock (&handle->lock); goto out; } pthread_mutex_unlock (&handle->lock); out: g_free (ondisk_path); g_free (repo_id); g_free (path); return ret; } /* On Windows, read operation may be issued after close is called. * So sometimes when we read, the cache task may have been canceled. * To ensure the file will be cached, we try to start cache task * for every read operation. Since we make sure there can be only one * cache task running for each file, it won't cause problems. */ static CachedFile * start_cache_task_before_read (FileCacheMgr *mgr, const char *repo_id, const char *path, RepoTreeStat *st) { FileCacheMgrPriv *priv = mgr->priv; char *file_key = NULL; CachedFile *file = NULL; SeafRepo *repo = NULL; repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id); if (!repo) { seaf_warning ("Failed to find repo %s.\n", repo_id); return NULL; } file_key = g_strconcat (repo_id, "/", path, NULL); pthread_mutex_lock (&priv->cache_lock); file = g_hash_table_lookup (priv->cached_files, file_key); if (!file) { pthread_mutex_unlock (&priv->cache_lock); goto out; } cached_file_ref (file); pthread_mutex_unlock (&priv->cache_lock); start_cache_task (priv, file, st, repo->server, repo->user); out: seaf_repo_unref (repo); g_free (file_key); return file; } gssize file_cache_mgr_read_by_path (FileCacheMgr *mgr, const char *repo_id, const char *path, char *buf, size_t size, guint64 offset) { SeafRepo *repo = NULL; RepoTreeStat tree_st; char *ondisk_path = NULL; CachedFile *file = NULL; char attr[64]; SeafStat st; int fd; gssize ret; int wait_time = 0; ondisk_path = g_build_path ("/", mgr->priv->base_path, repo_id, path, NULL); if (!seaf_util_exists (ondisk_path)) { seaf_warning ("Cached file %s/%s does not exist when read.\n", repo_id, path); ret = -EIO; goto out; } repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id); if (!repo) { seaf_warning ("Failed to get repo %s.\n", repo_id); ret = -ENOENT; goto out; } if (repo_tree_stat_path (repo->tree, path, &tree_st) < 0) { ret = -ENOENT; goto out; } file = start_cache_task_before_read (mgr, repo_id, path, &tree_st); if (!file) { ret = -EIO; goto out; } /* Wait until the intended region is cached. */ while (1) { if (seaf_getxattr (ondisk_path, SEAFILE_MTIME_ATTR, attr, sizeof(attr)) > 0) break; if (seaf_stat (ondisk_path, &st) < 0) { seaf_warning ("Failed to stat cache file %s: %s.\n", ondisk_path, strerror (errno)); ret = -EIO; goto out; } if ((offset + (guint64)size <= (guint64)tree_st.size) && (offset + (guint64)size <= (guint64)st.st_size)) { break; } if ((offset > (guint64)st.st_size) && (offset - (guint64)st.st_size > RANDOM_READ_THRESHOLD)) { ret = get_file_range_from_server (repo->server, repo->user, repo_id, path, buf, offset, size); goto out; } if (wait_time >= READ_CACHE_TIMEOUT) { seaf_warning ("Read cache file %s timeout.\n", ondisk_path); ret = -EIO; goto out; } /* Sleep 100ms to wait the file to be cached. */ g_usleep (CHECK_CACHE_INTERVAL); wait_time += CHECK_CACHE_INTERVAL; } fd = seaf_util_open (ondisk_path, O_RDONLY); if (fd < 0) { seaf_warning ("Failed to open cached file %s: %s\n", ondisk_path, strerror(errno)); ret = -EIO; goto out; } gint64 rc = seaf_util_lseek (fd, (gint64)offset, SEEK_SET); if (rc < 0) { seaf_warning ("Failed to lseek file %s: %s.\n", ondisk_path, strerror(errno)); ret = -errno; close (fd); goto out; } ret = readn (fd, buf, size); if (ret < 0) { seaf_warning ("Failed to read file %s: %s.\n", ondisk_path, strerror (errno)); ret = -errno; close (fd); goto out; } close (fd); out: seaf_repo_unref (repo); g_free (ondisk_path); cached_file_unref (file); return ret; } /* gssize */ /* file_cache_mgr_read_by_path (FileCacheMgr *mgr, */ /* const char *repo_id, const char *path, */ /* char *buf, size_t size, guint64 offset) */ /* { */ /* char *ondisk_path = NULL; */ /* char attr[64]; */ /* int fd; */ /* gssize ret; */ /* ondisk_path = g_build_path ("/", mgr->priv->base_path, repo_id, path, NULL); */ /* if (!seaf_util_exists (ondisk_path)) { */ /* seaf_warning ("Cached file %s/%s does not exist when read.\n", repo_id, path); */ /* ret = -EIO; */ /* goto out; */ /* } */ /* if (seaf_getxattr (ondisk_path, SEAFILE_MTIME_ATTR, attr, sizeof(attr)) < 0) { */ /* seaf_warning ("File %s/%s is not cached yet when read.\n", repo_id, path); */ /* ret = -EIO; */ /* goto out; */ /* } */ /* fd = seaf_util_open (ondisk_path, O_RDONLY); */ /* if (fd < 0) { */ /* seaf_warning ("Failed to open cached file %s: %s\n", */ /* ondisk_path, strerror(errno)); */ /* ret = -EIO; */ /* goto out; */ /* } */ /* gint64 rc = seaf_util_lseek (fd, (gint64)offset, SEEK_SET); */ /* if (rc < 0) { */ /* seaf_warning ("Failed to lseek file %s: %s.\n", ondisk_path, strerror(errno)); */ /* ret = -errno; */ /* close (fd); */ /* goto out; */ /* } */ /* ret = readn (fd, buf, size); */ /* if (ret < 0) { */ /* seaf_warning ("Failed to read file %s: %s.\n", ondisk_path, */ /* strerror (errno)); */ /* ret = -errno; */ /* close (fd); */ /* goto out; */ /* } */ /* close (fd); */ /* out: */ /* g_free (ondisk_path); */ /* return ret; */ /* } */ gssize file_cache_mgr_write (FileCacheMgr *mgr, CachedFileHandle *handle, const char *buf, size_t size, off_t offset) { CachedFile *file = handle->cached_file; char *ondisk_path = NULL; char attr[64]; gssize ret = 0; int wait_time = 0; ondisk_path = cached_file_ondisk_path (file); if (!seaf_util_exists (ondisk_path)) { seaf_warning ("Cached file %s does not exist when write.\n", file->file_key); ret = -EIO; goto out; } while (1) { if (seaf_getxattr (ondisk_path, SEAFILE_MTIME_ATTR, attr, sizeof(attr)) > 0) break; if (wait_time >= WRITE_CACHE_TIMEOUT) { seaf_warning ("Write cache file %s timeout.\n", ondisk_path); ret = -EIO; goto out; } g_usleep (CHECK_CACHE_INTERVAL); wait_time += CHECK_CACHE_INTERVAL; } pthread_mutex_lock (&handle->lock); gint64 rc; if (offset >= 0) rc = seaf_util_lseek (handle->fd, offset, SEEK_SET); else rc = seaf_util_lseek (handle->fd, 0, SEEK_END); if (rc < 0) { seaf_warning ("Failed to lseek file %s: %s.\n", ondisk_path, strerror(errno)); ret = -errno; pthread_mutex_unlock (&handle->lock); goto out; } ret = writen (handle->fd, buf, size); if (ret < 0) { seaf_warning ("Failed to write file %s: %s.\n", ondisk_path, strerror (errno)); ret = -EIO; pthread_mutex_unlock (&handle->lock); goto out; } pthread_mutex_unlock (&handle->lock); out: g_free (ondisk_path); return ret; } static int fetch_and_truncate_file (FileCacheMgr *mgr, const char *repo_id, const char *file_path, const char *fullpath, off_t length) { CachedFileHandle *handle; char attr[64]; handle = file_cache_mgr_open (mgr, repo_id, file_path, 0); if (!handle) { return -EIO; } while (1) { if (seaf_getxattr (fullpath, SEAFILE_MTIME_ATTR, attr, sizeof(attr)) > 0) break; g_usleep (CHECK_CACHE_INTERVAL); } file_cache_mgr_close_file_handle (handle); int ret = seaf_truncate (fullpath, length); if (ret < 0) { ret = -errno; seaf_warning ("Failed to truncate %s: %s.\n", fullpath, strerror(-ret)); } return ret; } int file_cache_mgr_truncate (FileCacheMgr *mgr, const char *repo_id, const char *file_path, off_t length, gboolean *not_cached) { FileCacheMgrPriv *priv = mgr->priv; char *fullpath = NULL; char attr[64]; int ret = 0; if (not_cached) *not_cached = FALSE; fullpath = g_build_filename (priv->base_path, repo_id, file_path, NULL); if (!seaf_util_exists (fullpath)) { /* We don't need to do anything if the file is not cached and the file * is going to be truncated to zero size. */ if (length != 0) { ret = fetch_and_truncate_file (mgr, repo_id, file_path, fullpath, length); } else { if (not_cached) *not_cached = TRUE; } goto out; } /* If cache file exists, there are two situations: * 1. file is still being fetched; * 2. file is already cached. * We need to wait until file is cached. Otherwise we'll interfere other * processes from reading/writing the file. * Usually case 2 is true. So we can immediately truncate the file. */ while (1) { if (seaf_getxattr (fullpath, SEAFILE_MTIME_ATTR, attr, sizeof(attr)) > 0) break; g_usleep (CHECK_CACHE_INTERVAL); } ret = seaf_truncate (fullpath, length); if (ret < 0) { ret = -errno; seaf_warning ("Failed to truncate %s: %s.\n", fullpath, strerror(-ret)); } out: g_free (fullpath); return ret; } int file_cache_mgr_unlink (FileCacheMgr *mgr, const char *repo_id, const char *file_path) { FileCacheMgrPriv *priv = mgr->priv; char *file_key = g_strconcat (repo_id, "/", file_path, NULL); char *ondisk_path = NULL; pthread_mutex_lock (&priv->cache_lock); /* Removing cached_file from the hash table will decrease n_open by 1. * When all open handle to this file is closed, the cached file structure * will be freed. * If later a file with the same name is created and opened, a new cache file * structure will be allocated. */ g_hash_table_remove (priv->cached_files, file_key); /* The cached file on disk cannot be found after deletion. Access to the * old file path will fail since read/write will check extended attrs of * the ondisk cached file. This is incompatible with POSIX semantics but * is simpler to implement. We don't have to track changes to file paths * in this way. */ ondisk_path = g_build_filename (priv->base_path, repo_id, file_path, NULL); int ret = seaf_util_unlink (ondisk_path); if (ret < 0 && errno != ENOENT) { ret = -errno; seaf_warning ("Failed to unlink %s: %s.\n", ondisk_path, strerror(-ret)); } g_free (ondisk_path); pthread_mutex_unlock (&priv->cache_lock); g_free (file_key); return 0; } int file_cache_mgr_rename (FileCacheMgr *mgr, const char *old_repo_id, const char *old_path, const char *new_repo_id, const char *new_path) { FileCacheMgrPriv *priv = mgr->priv; char *ondisk_path_old = NULL, *ondisk_path_new = NULL; char *new_path_parent = NULL; int ret = 0; /* We don't change the file paths in the CachedFile structures. Instead we * just rename the ondisk cached file paths. The in-memory CachedFile structures * will remain "dangling" and will fail to access files. They'll be cleaned up * when we clean cached files. */ ondisk_path_old = g_build_filename (priv->base_path, old_repo_id, old_path, NULL); ondisk_path_new = g_build_filename (priv->base_path, new_repo_id, new_path, NULL); if (!seaf_util_exists (ondisk_path_old)) { goto out; } new_path_parent = g_path_get_dirname (ondisk_path_new); if (checkdir_with_mkdir (new_path_parent) < 0) { seaf_warning ("[cache] Failed to create path %s: %s\n", new_path_parent, strerror(errno)); ret = -1; goto out; } ret = seaf_util_rename (ondisk_path_old, ondisk_path_new); if (ret < 0) { if (errno == EACCES) { if (seaf_util_unlink (ondisk_path_new) < 0) { seaf_warning ("[cache] Failed to unlink %s before rename: %s.\n", ondisk_path_new, strerror(errno)); } ret = seaf_util_rename (ondisk_path_old, ondisk_path_new); if (ret < 0) { ret = -errno; seaf_warning ("[cache] Failed to rename %s to %s: %s\n", ondisk_path_old, ondisk_path_new, strerror(errno)); } } } out: g_free (ondisk_path_old); g_free (ondisk_path_new); g_free (new_path_parent); return ret; } int file_cache_mgr_mkdir (FileCacheMgr *mgr, const char *repo_id, const char *dir) { FileCacheMgrPriv *priv = mgr->priv; char *ondisk_path = g_build_filename (priv->base_path, repo_id, dir, NULL); int ret = checkdir_with_mkdir (ondisk_path); if (ret < 0) { ret = -errno; seaf_warning ("[cache] Failed to create dir %s: %s\n", ondisk_path, strerror(errno)); } g_free (ondisk_path); return ret; } int file_cache_mgr_rmdir (FileCacheMgr *mgr, const char *repo_id, const char *dir) { FileCacheMgrPriv *priv = mgr->priv; char *ondisk_path = g_build_filename (priv->base_path, repo_id, dir, NULL); int ret = seaf_util_rmdir (ondisk_path); if (ret < 0) { ret = -errno; seaf_warning ("Failed to rmdir %s: %s.\n", ondisk_path, strerror(-ret)); } g_free (ondisk_path); return 0; } int file_cache_mgr_stat_handle (FileCacheMgr *mgr, CachedFileHandle *handle, FileCacheStat *st) { SeafStat file_st; int ret = 0; ret = seaf_fstat (handle->fd, &file_st); if (ret < 0) { return ret; } st->mtime = file_st.st_mtime; st->size = file_st.st_size; return 0; } int file_cache_mgr_stat (FileCacheMgr *mgr, const char *repo_id, const char *path, FileCacheStat *st) { char *fullpath; SeafStat file_st; int ret = 0; fullpath = g_build_filename (mgr->priv->base_path, repo_id, path, NULL); ret = seaf_stat (fullpath, &file_st); if (ret < 0) { g_free (fullpath); return ret; } st->mtime = file_st.st_mtime; st->size = file_st.st_size; g_free (fullpath); return 0; } int file_cache_mgr_set_attrs (FileCacheMgr *mgr, const char *repo_id, const char *path, gint64 mtime, gint64 size, const char *file_id) { char *fullpath = g_build_filename (mgr->priv->base_path, repo_id, path, NULL); char attr[64]; int len; if (!seaf_util_exists (fullpath)) { seaf_warning ("File %s does not exist while trying to set its attr.\n", fullpath); g_free (fullpath); return -1; } if (seaf_setxattr (fullpath, SEAFILE_FILE_ID_ATTR, file_id, 41) < 0) { seaf_warning ("Failed to set file-id attr for %s.\n", fullpath); g_free (fullpath); return -1; } len = snprintf (attr, sizeof(attr), "%"G_GINT64_FORMAT, size); if (seaf_setxattr (fullpath, SEAFILE_SIZE_ATTR, attr, len+1) < 0) { seaf_warning ("Failed to set seafile-size attr for %s.\n", fullpath); g_free (fullpath); return -1; } len = snprintf (attr, sizeof(attr), "%"G_GINT64_FORMAT, mtime); if (seaf_setxattr (fullpath, SEAFILE_MTIME_ATTR, attr, len+1) < 0) { seaf_warning ("Failed to set seafile-mtime attr for %s.\n", fullpath); g_free (fullpath); return -1; } g_free (fullpath); return 0; } static int get_file_attrs (const char *path, FileCacheStat *st) { char attr[64]; gint64 mtime, size; char file_id[41]; gboolean uploaded; if (seaf_getxattr (path, SEAFILE_MTIME_ATTR, attr, sizeof(attr)) < 0) { seaf_debug ("Failed to get seafile-mtime attr of %s.\n", path); return -1; } mtime = strtoll (attr, NULL, 10); if (seaf_getxattr (path, SEAFILE_SIZE_ATTR, attr, sizeof(attr)) < 0) { seaf_debug ("Failed to get seafile-size attr of %s.\n", path); return -1; } size = strtoll (attr, NULL, 10); if (seaf_getxattr (path, SEAFILE_FILE_ID_ATTR, file_id, sizeof(file_id)) < 0) { seaf_debug ("Failed to get file-id attr of %s.\n", path); return -1; } if (seaf_getxattr (path, SEAFILE_UPLOADED_ATTR, attr, sizeof(attr)) < 0) { seaf_debug ("Failed to get uploaded attr of %s.\n", path); uploaded = FALSE; } if (g_strcmp0(attr, "true") == 0) uploaded = TRUE; else uploaded = FALSE; st->mtime = mtime; st->size = size; memcpy (st->file_id, file_id, 40); st->is_uploaded = uploaded; return 0; } int file_cache_mgr_get_attrs (FileCacheMgr *mgr, const char *repo_id, const char *path, FileCacheStat *st) { char *fullpath = g_build_filename (mgr->priv->base_path, repo_id, path, NULL); int ret = get_file_attrs (fullpath, st); g_free (fullpath); return ret; } static int remove_file_attrs (const char *path) { seaf_removexattr (path, SEAFILE_MTIME_ATTR); seaf_removexattr (path, SEAFILE_SIZE_ATTR); seaf_removexattr (path, SEAFILE_FILE_ID_ATTR); return 0; } gboolean file_cache_mgr_is_file_cached (FileCacheMgr *mgr, const char *repo_id, const char *path) { char *fullpath = g_build_filename (mgr->priv->base_path, repo_id, path, NULL); char attr[64]; gboolean ret = (seaf_getxattr(fullpath, SEAFILE_MTIME_ATTR, attr, sizeof(attr)) > 0); g_free (fullpath); return ret; } gboolean file_cache_mgr_is_file_outdated (FileCacheMgr *mgr, const char *repo_id, const char *path) { char *fullpath = g_build_filename (mgr->priv->base_path, repo_id, path, NULL); char file_id_attr[41]; SeafRepo *repo = NULL; int len; RepoTreeStat st; gboolean ret; memset (file_id_attr, 0, sizeof (file_id_attr)); len = seaf_getxattr (fullpath, SEAFILE_FILE_ID_ATTR, file_id_attr, sizeof(file_id_attr)); if (len < 0) { ret = FALSE; goto out; } repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id); if (!repo) { seaf_warning ("Failed to get repo %.8s.\n", repo_id); ret = FALSE; goto out; } if (repo_tree_stat_path (repo->tree, path, &st) < 0) { seaf_warning ("Failed to stat repo tree path %s in repo %s.\n", path, repo_id); ret = FALSE; goto out; } ret = (strcmp (file_id_attr, st.id) != 0); out: g_free (fullpath); if (repo) seaf_repo_unref (repo); return ret; } int file_cache_mgr_set_file_uploaded (FileCacheMgr *mgr, const char *repo_id, const char *path, gboolean uploaded) { char *fullpath = g_build_filename (mgr->priv->base_path, repo_id, path, NULL); const char *attr; int ret = 0; if (!seaf_util_exists (fullpath)) { g_free (fullpath); return -1; } if (uploaded) attr = "true"; else attr = "false"; if (seaf_setxattr (fullpath, SEAFILE_UPLOADED_ATTR, attr, strlen(attr)+1) < 0) ret = -1; g_free (fullpath); return ret; } gboolean file_cache_mgr_is_file_uploaded (FileCacheMgr *mgr, const char *repo_id, const char *path, const char *file_id) { char *fullpath = g_build_filename (mgr->priv->base_path, repo_id, path, NULL); char attr[64]; gboolean ret = FALSE; SeafRepo *repo = NULL; SeafBranch *master = NULL; SeafCommit *commit = NULL; SeafDirent *dent = NULL; if (seaf_getxattr (fullpath, SEAFILE_UPLOADED_ATTR, attr, sizeof(attr)) < 0) ret = FALSE; if (g_strcmp0 (attr, "true") == 0) ret = TRUE; else { ret = FALSE; repo = seaf_repo_manager_get_repo(seaf->repo_mgr, repo_id); if (!repo) { seaf_warning ("Failed to get repo %.8s.\n", repo_id); goto out; } master = seaf_branch_manager_get_branch (seaf->branch_mgr, repo_id, "master"); if (!master) { seaf_warning ("No master branch found for repo %s(%.8s).\n", repo->name, repo_id); goto out; } commit = seaf_commit_manager_get_commit (seaf->commit_mgr, repo_id, repo->version, master->commit_id); if (!commit) { seaf_warning ("Failed to get commit %.8s for repo %.8s.\n", repo->head->commit_id, repo_id); goto out; } dent = seaf_fs_manager_get_dirent_by_path(seaf->fs_mgr, repo_id, repo->version, commit->root_id, path, NULL); if (!dent) { seaf_warning ("Failed to get path %s for repo %.8s.\n", path, repo_id); goto out; } if (strcmp (file_id, dent->id) == 0) ret = TRUE; } out: seaf_repo_unref (repo); seaf_branch_unref (master); seaf_commit_unref (commit); seaf_dirent_free (dent); g_free (fullpath); return ret; } gboolean file_cache_mgr_is_file_opened (FileCacheMgr *mgr, const char *repo_id, const char *path) { char *file_key; CachedFile *file; gboolean ret = FALSE; file_key = g_strconcat (repo_id, "/", path, NULL); pthread_mutex_lock (&mgr->priv->cache_lock); file = g_hash_table_lookup (mgr->priv->cached_files, file_key); if (!file) goto out; if (file->n_open > 1) ret = TRUE; out: pthread_mutex_unlock (&mgr->priv->cache_lock); g_free (file_key); return ret; } gboolean file_cache_mgr_is_file_changed (FileCacheMgr *mgr, const char *repo_id, const char *path, gboolean print_msg) { FileCacheStat st, attrs; char *fullpath; fullpath = g_build_filename (mgr->priv->base_path, repo_id, path, NULL); if (!seaf_util_exists (fullpath)) { g_free (fullpath); return FALSE; } g_free (fullpath); /* Assume unchanged if failed to access file attributes. */ if (file_cache_mgr_stat (mgr, repo_id, path, &st) < 0) return FALSE; if (file_cache_mgr_get_attrs (mgr, repo_id, path, &attrs) < 0) return FALSE; if (st.mtime != attrs.mtime || st.size != attrs.size) { if (print_msg) { seaf_message ("File %s in repo %s is changed in file cache." "Cached file mtime is %"G_GINT64_FORMAT", size is %"G_GINT64_FORMAT". " "Seafile mtime is %"G_GINT64_FORMAT", size is %"G_GINT64_FORMAT".\n", path, repo_id, st.mtime, st.size, attrs.mtime, attrs.size); } return TRUE; } return FALSE; } void file_cache_mgr_get_file_info_from_handle (FileCacheMgr *mgr, CachedFileHandle *handle, char **repo_id, char **file_path) { CachedFile *file = handle->cached_file; char **tokens = g_strsplit (file->file_key, "/", 2); *repo_id = g_strdup(tokens[0]); *file_path = g_strdup(tokens[1]); g_strfreev (tokens); } int file_cache_mgr_index_file (FileCacheMgr *mgr, const char *repo_id, int repo_version, const char *file_path, SeafileCrypt *crypt, gboolean write_data, unsigned char sha1[], gboolean *changed) { char *fullpath = NULL; SeafStat st; char mtime_attr[64], size_attr[64]; char mtime_str[64], size_str[64]; char file_id[41]; int ret = 0; fullpath = g_build_filename (mgr->priv->base_path, repo_id, file_path, NULL); if (seaf_stat (fullpath, &st) < 0) { seaf_warning ("Failed to stat %s: %s.\n", fullpath, strerror(errno)); ret = -1; goto out; } snprintf (mtime_str, sizeof(mtime_str), "%"G_GINT64_FORMAT, st.st_mtime); if (seaf_getxattr (fullpath, SEAFILE_MTIME_ATTR, mtime_attr, sizeof(mtime_attr)) < 0) { seaf_warning ("Failed to get seafile-mtime attr from %s.\n", fullpath); ret = -1; goto out; } snprintf (size_str, sizeof(size_str), "%"G_GINT64_FORMAT, (gint64)st.st_size); if (seaf_getxattr (fullpath, SEAFILE_SIZE_ATTR, size_attr, sizeof(size_attr)) < 0) { seaf_warning ("Failed to get seafile-size attr from %s.\n", fullpath); ret = -1; goto out; } if (seaf_getxattr (fullpath, SEAFILE_FILE_ID_ATTR, file_id, sizeof(file_id)) < 0) { seaf_warning ("Failed to get file-id attr from %s.\n", fullpath); ret = -1; goto out; } if (strcmp(mtime_attr, mtime_str) == 0 && strcmp(size_attr, size_str) == 0) { /* File is not changed. */ *changed = FALSE; hex_to_rawdata (file_id, sha1, 20); goto out; } *changed = TRUE; seaf_debug ("index %s\n", file_path); ret = seaf_fs_manager_index_blocks (seaf->fs_mgr, repo_id, repo_version, fullpath, crypt, write_data, sha1); out: g_free (fullpath); return ret; } static int traverse_dir_recursive (FileCacheMgr *mgr, const char *repo_id, const char *path, FileCacheTraverseCB file_cb, FileCacheTraverseCB dir_cb, void *user_data) { FileCacheMgrPriv *priv = mgr->priv; SeafStat st; char *dir_path = NULL, *subpath, *full_subpath; const char *dname; GDir *dir; GError *error = NULL; int ret = 0; dir_path = g_build_filename (priv->base_path, repo_id, path, NULL); dir = g_dir_open (dir_path, 0, &error); if (!dir) { seaf_warning ("Failed to open dir %s: %s.\n", dir_path, error->message); ret = -1; goto out; } while ((dname = g_dir_read_name (dir)) != NULL) { if (path[0] != '\0') subpath = g_build_filename (path, dname, NULL); else subpath = g_strdup(dname); full_subpath = g_build_filename (dir_path, dname, NULL); if (seaf_stat (full_subpath, &st) < 0) { seaf_warning ("Failed to stat %s: %s.\n", full_subpath, strerror(errno)); g_free (subpath); g_free (full_subpath); continue; } if (S_ISDIR(st.st_mode)) { if (dir_cb) dir_cb (repo_id, subpath, &st, user_data); ret = traverse_dir_recursive (mgr, repo_id, subpath, file_cb, dir_cb, user_data); } else if (S_ISREG(st.st_mode)) { if (file_cb) file_cb (repo_id, subpath, &st, user_data); } g_free (full_subpath); g_free (subpath); } g_dir_close (dir); out: g_free (dir_path); return ret; } int file_cache_mgr_traverse_path (FileCacheMgr *mgr, const char *repo_id, const char *path, FileCacheTraverseCB file_cb, FileCacheTraverseCB dir_cb, void *user_data) { char *fullpath; SeafStat st; fullpath = g_build_filename (mgr->priv->base_path, repo_id, path, NULL); if (!seaf_util_exists (fullpath)) { g_free (fullpath); return 0; } if (seaf_stat (fullpath, &st) < 0) { seaf_warning ("Failed to stat %s: %s.\n", fullpath, strerror(errno)); g_free (fullpath); return -1; } if (S_ISREG(st.st_mode)) { if (file_cb) file_cb (repo_id, path, &st, user_data); } else if (S_ISDIR(st.st_mode)) { if (dir_cb) dir_cb (repo_id, path, &st, user_data); traverse_dir_recursive (mgr, repo_id, path, file_cb, dir_cb, user_data); } g_free (fullpath); return 0; } /* Clean up cached files. */ int file_cache_mgr_set_clean_cache_interval (FileCacheMgr *mgr, int seconds) { mgr->priv->clean_cache_interval = seconds; return seafile_session_config_set_int (seaf, KEY_CLEAN_CACHE_INTERVAL, seconds); } int file_cache_mgr_get_clean_cache_interval (FileCacheMgr *mgr) { return mgr->priv->clean_cache_interval; } int file_cache_mgr_set_cache_size_limit (FileCacheMgr *mgr, gint64 limit) { mgr->priv->cache_size_limit = limit; return seafile_session_config_set_int64 (seaf, KEY_CACHE_SIZE_LIMIT, limit); } gint64 file_cache_mgr_get_cache_size_limit (FileCacheMgr *mgr) { return mgr->priv->cache_size_limit; } struct CachedFileInfo { char repo_id[37]; char *path; gint64 ctime; gint64 size; }; typedef struct CachedFileInfo CachedFileInfo; static void free_cached_file_info (CachedFileInfo *info) { if (!info) return; g_free (info->path); g_free (info); } static gint cached_file_compare_fn (gconstpointer a, gconstpointer b) { const CachedFileInfo *file_a = a; const CachedFileInfo *file_b = b; return file_a->ctime - file_b->ctime; } static void load_cached_file_info_cb (const char *repo_id, const char *file_path, SeafStat *st, void *user_data) { GList **pfiles = user_data; CachedFileInfo *info = g_new0 (CachedFileInfo, 1); memcpy (info->repo_id, repo_id, 36); info->path = g_strdup(file_path); info->ctime = (gint64)st->st_ctime; info->size = (gint64)st->st_size; *pfiles = g_list_prepend (*pfiles, info); } static GList * load_cached_file_list (FileCacheMgr *mgr) { FileCacheMgrPriv *priv = mgr->priv; GDir *dir; GError *error = NULL; const char *repo_id; GList *ret = NULL; dir = g_dir_open (priv->base_path, 0, &error); if (error) { seaf_warning ("Failed to open %s: %s.\n", priv->base_path, error->message); return NULL; } while ((repo_id = g_dir_read_name (dir)) != NULL) { if (!is_uuid_valid (repo_id)) continue; file_cache_mgr_traverse_path (mgr, repo_id, "", load_cached_file_info_cb, NULL, &ret); } g_dir_close (dir); ret = g_list_sort (ret, cached_file_compare_fn); return ret; } static gboolean file_can_be_cleaned (FileCacheMgr *mgr, CachedFileInfo *info) { char *filename; FileCacheStat st, attrs; if (info->size == 0) return FALSE; filename = g_path_get_basename (info->path); if (seaf_repo_manager_ignored_on_commit (filename)) { seaf_debug ("[cache clean] Skip file %s/%s since it is ignored on commit.\n", info->repo_id, info->path); g_free (filename); return FALSE; } g_free (filename); /* Don't remove opened files. */ if (file_cache_mgr_is_file_opened (mgr, info->repo_id, info->path)) { seaf_debug ("[cache clean] Skip file %s/%s since it is opened.\n", info->repo_id, info->path); return FALSE; } /* Don't remove changed files. */ if (file_cache_mgr_stat (mgr, info->repo_id, info->path, &st) < 0) { seaf_debug ("[cache clean] Skip file %s/%s since cannot stat it: %s.\n", info->repo_id, info->path, strerror(errno)); return FALSE; } /* But remove a file that's partially cached. */ if (file_cache_mgr_get_attrs (mgr, info->repo_id, info->path, &attrs) < 0) { seaf_debug ("[cache clean] Remove partially cached file %s/%s\n", info->repo_id, info->path); return TRUE; } if (st.mtime != attrs.mtime || st.size != attrs.size) { seaf_debug ("[cache clean] Skip file %s/%s since it is changed in cache. " "st.mtime = %"G_GINT64_FORMAT", st.size = %"G_GINT64_FORMAT", " "attrs.mtime = %"G_GINT64_FORMAT", attrs.size = %"G_GINT64_FORMAT"\n", info->repo_id, info->path, st.mtime, st.size, attrs.mtime, attrs.size); return FALSE; } /* Don't remove not-uploaded files. */ if (!file_cache_mgr_is_file_uploaded (mgr, info->repo_id, info->path, attrs.file_id)) { seaf_debug ("[cache clean] Skip file %s/%s since it is not uploaded.\n", info->repo_id, info->path); return FALSE; } return TRUE; } static void do_clean_cache (FileCacheMgr *mgr) { GList *files = load_cached_file_list (mgr); GList *ptr; CachedFileInfo *info; gint64 total_size = 0, target, removed_size = 0; int n_files = 0, n_removed = 0; for (ptr = files; ptr; ptr = ptr->next) { info = (CachedFileInfo *)ptr->data; total_size += info->size; ++n_files; } seaf_message ("cache size limit is %"G_GINT64_FORMAT"\n", mgr->priv->cache_size_limit); if (total_size < mgr->priv->cache_size_limit) { goto out; } seaf_message ("Cleaning cache space.\n"); seaf_message ("%d files in cache, total size is %"G_GINT64_FORMAT"\n", n_files, total_size); /* Clean until the total cache size reduce to 70% of limit. */ target = (total_size - mgr->priv->cache_size_limit * 0.7); for (ptr = files; ptr; ptr = ptr->next) { info = (CachedFileInfo *)ptr->data; if (file_can_be_cleaned (mgr, info)) { seaf_debug ("[cache clean] Removing %s/%s.\n", info->repo_id, info->path); file_cache_mgr_unlink (mgr, info->repo_id, info->path); seaf_sync_manager_delete_active_path (seaf->sync_mgr, info->repo_id, info->path); removed_size += info->size; ++n_removed; if (removed_size >= target) break; } } seaf_message ("Cache cleaning done.\n"); seaf_message ("Removed %d files, cleaned up %"G_GINT64_FORMAT" MB\n", n_removed, (gint64)(removed_size/1000000)); out: g_list_free_full (files, (GDestroyNotify)free_cached_file_info); } static void * clean_cache_worker (void *data) { FileCacheMgr *mgr = data; /* Delay 1 minute so that it doesn't conflict with repo tree loading. */ seaf_sleep (60); while (1) { do_clean_cache (mgr); seaf_sleep (mgr->priv->clean_cache_interval); } return NULL; } static void do_remove_deleted_cache (FileCacheMgr *mgr) { GDir *dir; const char *dname; GError *error = NULL; char *top_dir_path; char *deleted_path; top_dir_path = mgr->priv->base_path; dir = g_dir_open (top_dir_path, 0, &error); if (error) { seaf_warning ("Failed to open top file-cache dir: %s\n", error->message); return; } while ((dname = g_dir_read_name (dir)) != NULL) { if (strncmp (dname, "deleted", strlen("deleted")) == 0) { seaf_message ("Removing file cache %s.\n", dname); deleted_path = g_build_filename (top_dir_path, dname, NULL); seaf_rm_recursive (deleted_path); g_free (deleted_path); } } g_dir_close (dir); } #define REMOVE_DELETED_CACHE_INTERVAL 30 static void * remove_deleted_cache_worker (void *data) { FileCacheMgr *mgr = data; while (1) { do_remove_deleted_cache (mgr); seaf_sleep (REMOVE_DELETED_CACHE_INTERVAL); } return NULL; } static char * gen_deleted_cache_path (const char *base_path, const char *repo_id) { int n = 1; char *path = NULL; char *name = NULL; name = g_strdup_printf ("deleted-%s", repo_id); path = g_build_filename (base_path, name, NULL); while (g_file_test(path, G_FILE_TEST_EXISTS) && n < 10) { g_free (path); name = g_strdup_printf ("deleted-%s(%d)", repo_id, n); path = g_build_filename (base_path, name, NULL); g_free (name); ++n; } if (n == 10) { g_free (path); return NULL; } return path; } int file_cache_mgr_delete_repo_cache (FileCacheMgr *mgr, const char *repo_id) { char *cache_path = NULL, *deleted_path = NULL; int ret; cache_path = g_build_filename (mgr->priv->base_path, repo_id, NULL); if (!seaf_util_exists (cache_path)) { g_free (cache_path); return 0; } deleted_path = gen_deleted_cache_path (mgr->priv->base_path, repo_id); if (!deleted_path) { seaf_warning ("Too many deleted cache dirs for repo %s.\n", repo_id); g_free (cache_path); return -1; } int n = 0; while (1) { ret = seaf_util_rename (cache_path, deleted_path); if (ret < 0) { seaf_warning ("Failed to rename file cache dir %s to %s: %s.\n", cache_path, deleted_path, strerror(errno)); if (errno == EACCES && ++n < 3) { seaf_warning ("Retry rename\n"); g_usleep (100000); } else { break; } } else { break; } } g_free (cache_path); g_free (deleted_path); return ret; } gboolean file_cache_mgr_is_fetching_file (FileCacheMgr *mgr) { return (g_hash_table_size(mgr->priv->cache_tasks) != 0); } int file_cache_mgr_utimen (FileCacheMgr *mgr, const char *repo_id, const char *path, time_t mtime, time_t atime) { int ret = 0; struct utimbuf times; char *ondisk_path = g_build_filename (mgr->priv->base_path, repo_id, path, NULL); times.actime = atime; times.modtime = mtime; ret = utime (ondisk_path, ×); if (ret < 0) { seaf_warning ("Failed to utime %s: %s\n", ondisk_path, strerror(errno)); ret = (-errno); } g_free (ondisk_path); return ret; } static void collect_downloading_files (gpointer key, gpointer value, gpointer user_data) { CachedFileHandle *handle = value; char *file_key = key; char **tokens = g_strsplit (file_key, "/", 2); char *path = tokens[1]; FileCacheMgrPriv *priv = seaf->file_cache_mgr->priv; json_t *downloading = user_data; json_t *info_obj = json_object (); CachedFile *cached_file; cached_file = g_hash_table_lookup (priv->cached_files, file_key); char *abs_path = g_build_filename (cached_file->repo_uname, path, NULL); json_object_set_string_member (info_obj, "server", handle->server); json_object_set_string_member (info_obj, "username", handle->user); json_object_set_string_member (info_obj, "file_path", abs_path); json_object_set_int_member (info_obj, "downloaded", cached_file->downloaded); json_object_set_int_member (info_obj, "total_download", cached_file->total_download); json_array_append_new (downloading, info_obj); g_free (abs_path); g_strfreev (tokens); } json_t * file_cache_mgr_get_download_progress (FileCacheMgr *mgr) { FileCacheMgrPriv *priv = mgr->priv; int i = 0; json_t *downloaded = json_array (); json_t *downloading = json_array (); json_t *progress = json_object (); FileDownloadedInfo *info; json_t *downloaded_obj; pthread_mutex_lock (&priv->task_lock); g_hash_table_foreach (priv->cache_tasks, collect_downloading_files, downloading); pthread_mutex_unlock (&priv->task_lock); pthread_mutex_lock (&priv->downloaded_files_lock); while (i < MAX_GET_FINISHED_FILES) { info = g_queue_peek_nth (priv->downloaded_files, i); if (info) { downloaded_obj = json_object (); json_object_set_string_member (downloaded_obj, "server", info->server); json_object_set_string_member (downloaded_obj, "username", info->user); json_object_set_string_member (downloaded_obj, "file_path", info->file_path); json_array_append_new (downloaded, downloaded_obj); } else { break; } i++; } pthread_mutex_unlock (&priv->downloaded_files_lock); json_object_set_new (progress, "downloaded_files", downloaded); json_object_set_new (progress, "downloading_files", downloading); return progress; } #if 0 #define DOWNLOADED_FILE_LIST_NAME "downloaded.json" #define SAVE_DOWNLOADED_FILE_LIST_INTERVAL 5 #define DOWNLOADED_FILE_LIST_VERSION 1 static void * save_downloaded_file_list_worker (void *data) { char *path = g_build_filename (seaf->seaf_dir, DOWNLOADED_FILE_LIST_NAME, NULL); json_t *progress = NULL, *downloaded, *list; char *txt = NULL; GError *error = NULL; while (1) { seaf_sleep (SAVE_DOWNLOADED_FILE_LIST_INTERVAL); progress = file_cache_mgr_get_download_progress (seaf->file_cache_mgr); downloaded = json_object_get (progress, "downloaded_files"); if (json_array_size(downloaded) > 0) { list = json_object(); json_object_set_new (list, "version", json_integer(DOWNLOADED_FILE_LIST_VERSION)); json_object_set (list, "downloaded_files", downloaded); txt = json_dumps (list, 0); if (!g_file_set_contents (path, txt, -1, &error)) { seaf_warning ("Failed to save downloaded file list: %s\n", error->message); g_clear_error (&error); } json_decref (list); free (txt); } json_decref (progress); } g_free (path); return NULL; } static void load_downloaded_file_list (FileCacheMgr *mgr) { char *path = g_build_filename (seaf->seaf_dir, DOWNLOADED_FILE_LIST_NAME, NULL); char *txt = NULL; gsize len; GError *error = NULL; json_t *list = NULL, *downloaded = NULL; json_error_t jerror; if (!g_file_test (path, G_FILE_TEST_IS_REGULAR)) return; if (!g_file_get_contents (path, &txt, &len, &error)) { seaf_warning ("Failed to read downloaded file list: %s\n", error->message); g_clear_error (&error); g_free (path); return; } list = json_loadb (txt, len, 0, &jerror); if (!list) { seaf_warning ("Failed to load downloaded file list: %s\n", jerror.text); goto out; } if (json_typeof(list) != JSON_OBJECT) { seaf_warning ("Bad downloaded file list format.\n"); goto out; } downloaded = json_object_get (list, "downloaded_files"); if (!downloaded) { seaf_warning ("No downloaded_files in json object.\n"); goto out; } if (json_typeof(downloaded) != JSON_ARRAY) { seaf_warning ("Bad downloaded file list format.\n"); goto out; } json_t *iter; size_t n = json_array_size (downloaded), i; for (i = 0; i < n; ++i) { iter = json_array_get (downloaded, i); if (json_typeof(iter) != JSON_STRING) { seaf_warning ("Bad downloaded file list format.\n"); goto out; } g_queue_push_tail (mgr->priv->downloaded_files, g_strdup(json_string_value(iter))); } out: if (list) json_decref (list); g_free (path); g_free (txt); } #endif int file_cache_mgr_cancel_download (FileCacheMgr *mgr, const char *server, const char *user, const char *full_file_path, GError **error) { char **tokens = NULL; char *category = NULL, *repo_name = NULL, *file_path = NULL; char *display_name = NULL; char *repo_id = NULL; char *file_key = NULL; CachedFile *file; int ret = 0; tokens = g_strsplit (full_file_path, "/", 3); if (g_strv_length (tokens) != 3) { g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "invalid file path format."); g_strfreev (tokens); return -1; } category = tokens[0]; repo_name = tokens[1]; file_path = tokens[2]; display_name = g_build_path ("/", category, repo_name, NULL); repo_id = seaf_repo_manager_get_repo_id_by_display_name (seaf->repo_mgr, server, user, display_name); g_free (display_name); if (!repo_id) { g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Repo not found."); ret = -1; goto out; } file_key = g_strconcat (repo_id, "/", file_path, NULL); pthread_mutex_lock (&mgr->priv->task_lock); if (!g_hash_table_lookup (mgr->priv->cache_tasks, file_key)) { g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "%s is not downloading.", file_key); pthread_mutex_unlock (&mgr->priv->task_lock); ret = -1; goto out; } pthread_mutex_unlock (&mgr->priv->task_lock); pthread_mutex_lock (&mgr->priv->cache_lock); file = g_hash_table_lookup (mgr->priv->cached_files, file_key); if (file) { file->force_canceled = TRUE; file->last_cancel_time = (gint64)time(NULL); } pthread_mutex_unlock (&mgr->priv->cache_lock); out: g_strfreev (tokens); g_free (repo_id); g_free (file_key); return ret; } #define BEFORE_NOTIFY_DOWNLOAD_START 5 static void * check_download_file_time_worker (void *data) { FileCacheMgrPriv *priv = data; gint64 now; GHashTableIter iter; gpointer key, value; char *file_key; CachedFileHandle *handle; char **tokens; char *repo_id, *file_path; while (1) { now = (gint64)time(NULL); pthread_mutex_lock (&priv->task_lock); g_hash_table_iter_init (&iter, priv->cache_tasks); while (g_hash_table_iter_next (&iter, &key, &value)) { file_key = key; handle = value; if (!handle->notified_download_start && (!handle->fetch_canceled || handle->cached_file->n_open > 2) && handle->start_download_time > 0 && (now - handle->start_download_time >= BEFORE_NOTIFY_DOWNLOAD_START)) { tokens = g_strsplit (file_key, "/", 2); repo_id = tokens[0]; file_path = tokens[1]; send_file_download_notification ("file-download.start", repo_id, file_path); g_strfreev (tokens); handle->notified_download_start = TRUE; } } pthread_mutex_unlock (&priv->task_lock); seaf_sleep (1); } return NULL; } #ifndef WIN32 static int rm_file_and_active_path_recursive (const char *repo_id, const char *path, const char *active_path) { SeafStat st; int ret = 0; GDir *dir; const char *dname; char *subpath; char *sub_active_path; GError *error = NULL; if (seaf_stat (path, &st) < 0) { seaf_warning ("Failed to stat %s: %s\n", path, strerror(errno)); return -1; } if (S_ISREG(st.st_mode)) { ret = seaf_util_unlink (path); seaf_sync_manager_delete_active_path (seaf->sync_mgr, repo_id, active_path); return ret; } else if (S_ISDIR (st.st_mode)) { dir = g_dir_open (path, 0, &error); if (error) { seaf_warning ("Failed to open dir %s: %s\n", path, error->message); return -1; } while ((dname = g_dir_read_name (dir)) != NULL) { subpath = g_build_filename (path, dname, NULL); sub_active_path = g_build_filename (active_path, dname, NULL); ret = rm_file_and_active_path_recursive (repo_id, subpath, sub_active_path); g_free (subpath); g_free (sub_active_path); if (ret < 0) break; } g_dir_close (dir); if (ret == 0) { ret = seaf_util_rmdir (path); } return ret; } return ret; } int file_cache_mgr_uncache_path (const char *repo_id, const char *path) { FileCacheMgr *mgr = seaf->file_cache_mgr; char *top_dir_path = mgr->priv->base_path; char *deleted_path; char *canon_path = NULL; if (strcmp (path, "") != 0) { canon_path = format_path (path); } else { canon_path = g_strdup(path); } deleted_path = g_build_filename (top_dir_path, repo_id, path, NULL); rm_file_and_active_path_recursive (repo_id, deleted_path, canon_path); g_free (deleted_path); g_free (canon_path); return 0; } #endif seadrive-fuse-3.0.13/src/file-cache-mgr.h000066400000000000000000000166351476177674700201100ustar00rootroot00000000000000#ifndef FILE_CACHE_MGR_H #define FILE_CACHE_MGR_H #include "utils.h" #include "seafile-crypt.h" struct FileCacheMgrPriv; typedef struct FileCacheMgr { struct FileCacheMgrPriv *priv; } FileCacheMgr; typedef struct _BlockBuffer { char *content; int size; } BlockBuffer; typedef struct CachedFileHandle { struct CachedFile *cached_file; pthread_mutex_t lock; int fd; SeafileCrypt *crypt; BlockBuffer blk_buffer; gint64 file_size; gboolean is_readonly; gboolean fetch_canceled; gint64 start_download_time; gboolean notified_download_start; char *server; char *user; } CachedFileHandle; FileCacheMgr * file_cache_mgr_new (const char *parent_dir); void file_cache_mgr_init (FileCacheMgr *mgr); void file_cache_mgr_start (FileCacheMgr *mgr); CachedFileHandle * file_cache_mgr_open (FileCacheMgr *mgr, const char *repo_id, const char *file_path, int flags); void file_cache_mgr_close_file_handle (CachedFileHandle *file_handle); gboolean cached_file_handle_is_readonly (CachedFileHandle *file_handle); gssize file_cache_mgr_read (FileCacheMgr *mgr, CachedFileHandle *handle, char *buf, size_t size, guint64 offset); gssize file_cache_mgr_read_by_path (FileCacheMgr *mgr, const char *repo_id, const char *path, char *buf, size_t size, guint64 offset); gssize file_cache_mgr_write (FileCacheMgr *mgr, CachedFileHandle *handle, const char *buf, size_t size, off_t offset); gssize file_cache_mgr_write_by_path (FileCacheMgr *mgr, const char *repo_id, const char *path, const char *buf, size_t size, off_t offset); int file_cache_mgr_truncate (FileCacheMgr *mgr, const char *repo_id, const char *path, off_t length, gboolean *not_cached); /* This function should only be called after the file was deleted in fs tree. */ int file_cache_mgr_unlink (FileCacheMgr *mgr, const char *repo_id, const char *file_path); int file_cache_mgr_rename (FileCacheMgr *mgr, const char *old_repo_id, const char *old_path, const char *new_repo_id, const char *new_path); int file_cache_mgr_mkdir (FileCacheMgr *mgr, const char *repo_id, const char *dir); int file_cache_mgr_rmdir (FileCacheMgr *mgr, const char *repo_id, const char *dir); struct FileCacheStat { char file_id[41]; gint64 mtime; gint64 size; gboolean is_uploaded; }; typedef struct FileCacheStat FileCacheStat; /* Get stat of the cached file. The stat info is from the local file system, not * the extended attrs associated with the cached file. */ int file_cache_mgr_stat_handle (FileCacheMgr *mgr, CachedFileHandle *handle, FileCacheStat *st); int file_cache_mgr_stat (FileCacheMgr *mgr, const char *repo_id, const char *path, FileCacheStat *st); gboolean file_cache_mgr_is_file_cached (FileCacheMgr *mgr, const char *repo_id, const char *path); gboolean file_cache_mgr_is_file_outdated (FileCacheMgr *mgr, const char *repo_id, const char *path); /* Update extended attrs of the cached file. */ int file_cache_mgr_set_attrs (FileCacheMgr *mgr, const char *repo_id, const char *path, gint64 mtime, gint64 size, const char *file_id); int file_cache_mgr_get_attrs (FileCacheMgr *mgr, const char *repo_id, const char *path, FileCacheStat *st); int file_cache_mgr_set_file_uploaded (FileCacheMgr *mgr, const char *repo_id, const char *path, gboolean uploaded); gboolean file_cache_mgr_is_file_uploaded (FileCacheMgr *mgr, const char *repo_id, const char *path, const char *file_id); gboolean file_cache_mgr_is_file_opened (FileCacheMgr *mgr, const char *repo_id, const char *path); gboolean file_cache_mgr_is_file_changed (FileCacheMgr *mgr, const char *repo_id, const char *path, gboolean print_msg); /* Get repo id and path of the file from a handle. */ void file_cache_mgr_get_file_info_from_handle (FileCacheMgr *mgr, CachedFileHandle *handle, char **repo_id, char **file_path); int file_cache_mgr_index_file (FileCacheMgr *mgr, const char *repo_id, int repo_version, const char *file_path, SeafileCrypt *crypt, gboolean write_data, unsigned char sha1[], gboolean *changed); typedef void (*FileCacheTraverseCB) (const char *repo_id, const char *file_path, SeafStat *st, void *user_data); int file_cache_mgr_traverse_path (FileCacheMgr *mgr, const char *repo_id, const char *path, FileCacheTraverseCB file_cb, FileCacheTraverseCB dir_cb, void *user_data); int file_cache_mgr_set_clean_cache_interval (FileCacheMgr *mgr, int seconds); int file_cache_mgr_get_clean_cache_interval (FileCacheMgr *mgr); int file_cache_mgr_set_cache_size_limit (FileCacheMgr *mgr, gint64 limit); gint64 file_cache_mgr_get_cache_size_limit (FileCacheMgr *mgr); int file_cache_mgr_delete_repo_cache (FileCacheMgr *mgr, const char *repo_id); gboolean file_cache_mgr_is_fetching_file (FileCacheMgr *mgr); void file_cache_mgr_cache_file (FileCacheMgr *mgr, const char *repo_id, const char *path, struct _RepoTreeStat *st); int file_cache_mgr_utimen (FileCacheMgr *mgr, const char *repo_id, const char *path, time_t mtime, time_t atime); /* Functions that only works for files and folders under root directory. * Except for library folders, the root directory can also contain some special * files/folders created by the OS. * These top-level files and folders are directly mapped into the "file-cache/root" * folder. */ int file_cache_mgr_uncache_path (const char *repo_id, const char *path); // {"downloading_files": [{"file_path":, "downloaded":, "total_download":}, ], "downloaded_files": [ten latest downloaded files]} json_t * file_cache_mgr_get_download_progress (FileCacheMgr *mgr); int file_cache_mgr_cancel_download (FileCacheMgr *mgr, const char *server, const char *user, const char *full_file_path, GError **error); #endif seadrive-fuse-3.0.13/src/filelock-mgr.c000066400000000000000000000414661476177674700177130ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ #include "common.h" #include #include "seafile-session.h" #include "filelock-mgr.h" #include "log.h" #include "db.h" struct _FilelockMgrPriv { GHashTable *repo_locked_files; pthread_mutex_t hash_lock; sqlite3 *db; pthread_mutex_t db_lock; }; typedef struct _FilelockMgrPriv FilelockMgrPriv; typedef struct _LockInfo { int locked_by_me; } LockInfo; /* When a file is locked by me, it can have two reasons: * - Locked by the user manually * - Auto-Locked by Seafile when it detects Office opens the file. */ struct _SeafFilelockManager * seaf_filelock_manager_new (struct _SeafileSession *session) { SeafFilelockManager *mgr = g_new0 (SeafFilelockManager, 1); FilelockMgrPriv *priv = g_new0 (FilelockMgrPriv, 1); mgr->session = session; mgr->priv = priv; priv->repo_locked_files = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_hash_table_destroy); pthread_mutex_init (&priv->hash_lock, NULL); pthread_mutex_init (&priv->db_lock, NULL); return mgr; } static void lock_info_free (LockInfo *info) { g_free (info); } static gboolean load_locked_files (sqlite3_stmt *stmt, void *data) { GHashTable *repo_locked_files = data, *files; const char *repo_id, *path; int locked_by_me; repo_id = (const char *)sqlite3_column_text (stmt, 0); path = (const char *)sqlite3_column_text (stmt, 1); locked_by_me = sqlite3_column_int (stmt, 2); files = g_hash_table_lookup (repo_locked_files, repo_id); if (!files) { files = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)lock_info_free); g_hash_table_insert (repo_locked_files, g_strdup(repo_id), files); } char *key = g_strdup(path); LockInfo *info = g_new0 (LockInfo, 1); info->locked_by_me = locked_by_me; g_hash_table_replace (files, key, info); return TRUE; } int seaf_filelock_manager_init (SeafFilelockManager *mgr) { char *db_path; sqlite3 *db; char *sql; db_path = g_build_filename (seaf->seaf_dir, "filelocks.db", NULL); if (sqlite_open_db (db_path, &db) < 0) return -1; g_free (db_path); mgr->priv->db = db; sql = "CREATE TABLE IF NOT EXISTS ServerLockedFiles (" "repo_id TEXT, path TEXT, locked_by_me INTEGER);"; sqlite_query_exec (db, sql); sql = "CREATE INDEX IF NOT EXISTS server_locked_files_repo_id_idx " "ON ServerLockedFiles (repo_id);"; sqlite_query_exec (db, sql); sql = "CREATE TABLE IF NOT EXISTS ServerLockedFilesTimestamp (" "repo_id TEXT, timestamp INTEGER, PRIMARY KEY (repo_id));"; sqlite_query_exec (db, sql); sql = "SELECT repo_id, path, locked_by_me FROM ServerLockedFiles"; pthread_mutex_lock (&mgr->priv->db_lock); pthread_mutex_lock (&mgr->priv->hash_lock); if (sqlite_foreach_selected_row (mgr->priv->db, sql, load_locked_files, mgr->priv->repo_locked_files) < 0) { pthread_mutex_unlock (&mgr->priv->db_lock); pthread_mutex_unlock (&mgr->priv->hash_lock); g_hash_table_destroy (mgr->priv->repo_locked_files); return -1; } pthread_mutex_unlock (&mgr->priv->hash_lock); pthread_mutex_unlock (&mgr->priv->db_lock); return 0; } int seaf_filelock_manager_start (SeafFilelockManager *mgr) { return 0; } gboolean seaf_filelock_manager_is_file_locked (SeafFilelockManager *mgr, const char *repo_id, const char *path) { gboolean ret; pthread_mutex_lock (&mgr->priv->hash_lock); GHashTable *locks = g_hash_table_lookup (mgr->priv->repo_locked_files, repo_id); if (!locks) { pthread_mutex_unlock (&mgr->priv->hash_lock); return FALSE; } LockInfo *info = g_hash_table_lookup (locks, path); if (!info) { pthread_mutex_unlock (&mgr->priv->hash_lock); return FALSE; } ret = !info->locked_by_me; pthread_mutex_unlock (&mgr->priv->hash_lock); return ret; } gboolean seaf_filelock_manager_is_file_locked_by_me (SeafFilelockManager *mgr, const char *repo_id, const char *path) { gboolean ret; pthread_mutex_lock (&mgr->priv->hash_lock); GHashTable *locks = g_hash_table_lookup (mgr->priv->repo_locked_files, repo_id); if (!locks) { pthread_mutex_unlock (&mgr->priv->hash_lock); return FALSE; } LockInfo *info = g_hash_table_lookup (locks, path); if (!info) { pthread_mutex_unlock (&mgr->priv->hash_lock); return FALSE; } ret = (info->locked_by_me > 0); pthread_mutex_unlock (&mgr->priv->hash_lock); return ret; } int seaf_filelock_manager_get_lock_status (SeafFilelockManager *mgr, const char *repo_id, const char *path) { int ret; pthread_mutex_lock (&mgr->priv->hash_lock); GHashTable *locks = g_hash_table_lookup (mgr->priv->repo_locked_files, repo_id); if (!locks) { pthread_mutex_unlock (&mgr->priv->hash_lock); return FILE_NOT_LOCKED; } LockInfo *info = g_hash_table_lookup (locks, path); if (!info) { pthread_mutex_unlock (&mgr->priv->hash_lock); return FILE_NOT_LOCKED; } if (info->locked_by_me == LOCKED_MANUAL) ret = FILE_LOCKED_BY_ME_MANUAL; else if (info->locked_by_me == LOCKED_AUTO) ret = FILE_LOCKED_BY_ME_AUTO; else ret = FILE_LOCKED_BY_OTHERS; pthread_mutex_unlock (&mgr->priv->hash_lock); return ret; } static void update_in_memory (SeafFilelockManager *mgr, const char *repo_id, GHashTable *new_locks) { GHashTable *repo_hash = mgr->priv->repo_locked_files; pthread_mutex_lock (&mgr->priv->hash_lock); GHashTable *locks = g_hash_table_lookup (repo_hash, repo_id); if (!locks) { if (g_hash_table_size (new_locks) == 0) { pthread_mutex_unlock (&mgr->priv->hash_lock); return; } locks = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)lock_info_free); g_hash_table_insert (repo_hash, g_strdup(repo_id), locks); } GHashTableIter iter; gpointer key, value; gpointer new_key, new_val; char *path; LockInfo *info; gboolean exists; int locked_by_me; SeafRepo *repo; repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id); if (!repo) { seaf_warning ("Failed to find repo %s\n", repo_id); return; } g_hash_table_iter_init (&iter, locks); while (g_hash_table_iter_next (&iter, &key, &value)) { path = key; info = value; exists = g_hash_table_lookup_extended (new_locks, path, &new_key, &new_val); if (!exists) { g_hash_table_iter_remove (&iter); } else { locked_by_me = (int)(long)new_val; if (!info->locked_by_me && locked_by_me) { info->locked_by_me = locked_by_me; } else if (info->locked_by_me && !locked_by_me) { info->locked_by_me = locked_by_me; } } } g_hash_table_iter_init (&iter, new_locks); while (g_hash_table_iter_next (&iter, &new_key, &new_val)) { path = new_key; locked_by_me = (int)(long)new_val; if (!g_hash_table_lookup (locks, path)) { info = g_new0 (LockInfo, 1); info->locked_by_me = locked_by_me; g_hash_table_insert (locks, g_strdup(path), info); } } pthread_mutex_unlock (&mgr->priv->hash_lock); seaf_repo_unref (repo); } static gint compare_paths (gconstpointer a, gconstpointer b) { const char *patha = a, *pathb = b; return strcmp (patha, pathb); } static int update_db (SeafFilelockManager *mgr, const char *repo_id) { char *sql; sqlite3_stmt *stmt; GHashTable *locks; GList *paths, *ptr; char *path; LockInfo *info; pthread_mutex_lock (&mgr->priv->db_lock); sql = "DELETE FROM ServerLockedFiles WHERE repo_id = ?"; stmt = sqlite_query_prepare (mgr->priv->db, sql); sqlite3_bind_text (stmt, 1, repo_id, -1, SQLITE_TRANSIENT); if (sqlite3_step (stmt) != SQLITE_DONE) { seaf_warning ("Failed to remove server locked files for %.8s: %s.\n", repo_id, sqlite3_errmsg (mgr->priv->db)); sqlite3_finalize (stmt); pthread_mutex_unlock (&mgr->priv->db_lock); return -1; } sqlite3_finalize (stmt); locks = g_hash_table_lookup (mgr->priv->repo_locked_files, repo_id); if (!locks || g_hash_table_size (locks) == 0) { pthread_mutex_unlock (&mgr->priv->db_lock); return 0; } paths = g_hash_table_get_keys (locks); paths = g_list_sort (paths, compare_paths); sql = "INSERT INTO ServerLockedFiles (repo_id, path, locked_by_me) VALUES (?, ?, ?)"; stmt = sqlite_query_prepare (mgr->priv->db, sql); for (ptr = paths; ptr; ptr = ptr->next) { path = ptr->data; info = g_hash_table_lookup (locks, path); sqlite3_bind_text (stmt, 1, repo_id, -1, SQLITE_TRANSIENT); sqlite3_bind_text (stmt, 2, path, -1, SQLITE_TRANSIENT); sqlite3_bind_int (stmt, 3, info->locked_by_me); if (sqlite3_step (stmt) != SQLITE_DONE) { seaf_warning ("Failed to insert server file lock for %.8s: %s.\n", repo_id, sqlite3_errmsg (mgr->priv->db)); sqlite3_finalize (stmt); pthread_mutex_unlock (&mgr->priv->db_lock); return -1; } sqlite3_reset (stmt); sqlite3_clear_bindings (stmt); } sqlite3_finalize (stmt); g_list_free (paths); pthread_mutex_unlock (&mgr->priv->db_lock); return 0; } int seaf_filelock_manager_update (SeafFilelockManager *mgr, const char *repo_id, GHashTable *new_locked_files) { update_in_memory (mgr, repo_id, new_locked_files); int ret = update_db (mgr, repo_id); return ret; } int seaf_filelock_manager_update_timestamp (SeafFilelockManager *mgr, const char *repo_id, gint64 timestamp) { char sql[256]; int ret; snprintf (sql, sizeof(sql), "REPLACE INTO ServerLockedFilesTimestamp VALUES ('%s', %"G_GINT64_FORMAT")", repo_id, timestamp); pthread_mutex_lock (&mgr->priv->db_lock); ret = sqlite_query_exec (mgr->priv->db, sql); pthread_mutex_unlock (&mgr->priv->db_lock); return ret; } gint64 seaf_filelock_manager_get_timestamp (SeafFilelockManager *mgr, const char *repo_id) { char sql[256]; gint64 ret; sqlite3_snprintf (sizeof(sql), sql, "SELECT timestamp FROM ServerLockedFilesTimestamp WHERE repo_id = '%q'", repo_id); pthread_mutex_lock (&mgr->priv->db_lock); ret = sqlite_get_int64 (mgr->priv->db, sql); pthread_mutex_unlock (&mgr->priv->db_lock); return ret; } int seaf_filelock_manager_remove (SeafFilelockManager *mgr, const char *repo_id) { char *sql; sqlite3_stmt *stmt; pthread_mutex_lock (&mgr->priv->db_lock); sql = "DELETE FROM ServerLockedFiles WHERE repo_id = ?"; stmt = sqlite_query_prepare (mgr->priv->db, sql); sqlite3_bind_text (stmt, 1, repo_id, -1, SQLITE_TRANSIENT); if (sqlite3_step (stmt) != SQLITE_DONE) { seaf_warning ("Failed to remove server locked files for %.8s: %s.\n", repo_id, sqlite3_errmsg (mgr->priv->db)); sqlite3_finalize (stmt); pthread_mutex_unlock (&mgr->priv->db_lock); return -1; } sqlite3_finalize (stmt); sql = "DELETE FROM ServerLockedFilesTimestamp WHERE repo_id = ?"; stmt = sqlite_query_prepare (mgr->priv->db, sql); sqlite3_bind_text (stmt, 1, repo_id, -1, SQLITE_TRANSIENT); if (sqlite3_step (stmt) != SQLITE_DONE) { seaf_warning ("Failed to remove server locked files timestamp for %.8s: %s.\n", repo_id, sqlite3_errmsg (mgr->priv->db)); sqlite3_finalize (stmt); pthread_mutex_unlock (&mgr->priv->db_lock); return -1; } sqlite3_finalize (stmt); pthread_mutex_unlock (&mgr->priv->db_lock); pthread_mutex_lock (&mgr->priv->hash_lock); g_hash_table_remove (mgr->priv->repo_locked_files, repo_id); pthread_mutex_unlock (&mgr->priv->hash_lock); return 0; } static int mark_file_locked_in_db (SeafFilelockManager *mgr, const char *repo_id, const char *path, int locked_by_me) { char *sql; sqlite3_stmt *stmt; pthread_mutex_lock (&mgr->priv->db_lock); sql = "REPLACE INTO ServerLockedFiles (repo_id, path, locked_by_me) VALUES (?, ?, ?)"; stmt = sqlite_query_prepare (mgr->priv->db, sql); sqlite3_bind_text (stmt, 1, repo_id, -1, SQLITE_TRANSIENT); sqlite3_bind_text (stmt, 2, path, -1, SQLITE_TRANSIENT); sqlite3_bind_int (stmt, 3, locked_by_me); if (sqlite3_step (stmt) != SQLITE_DONE) { seaf_warning ("Failed to update server locked files for %.8s: %s.\n", repo_id, sqlite3_errmsg (mgr->priv->db)); sqlite3_finalize (stmt); pthread_mutex_unlock (&mgr->priv->db_lock); return -1; } sqlite3_finalize (stmt); pthread_mutex_unlock (&mgr->priv->db_lock); return 0; } int seaf_filelock_manager_mark_file_locked (SeafFilelockManager *mgr, const char *repo_id, const char *path, FileLockType type) { GHashTable *locks; LockInfo *info; pthread_mutex_lock (&mgr->priv->hash_lock); locks = g_hash_table_lookup (mgr->priv->repo_locked_files, repo_id); if (!locks) { locks = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)lock_info_free); g_hash_table_insert (mgr->priv->repo_locked_files, g_strdup(repo_id), locks); } info = g_hash_table_lookup (locks, path); if (!info) { info = g_new0 (LockInfo, 1); g_hash_table_insert (locks, g_strdup(path), info); } info->locked_by_me = type; pthread_mutex_unlock (&mgr->priv->hash_lock); return mark_file_locked_in_db (mgr, repo_id, path, info->locked_by_me); } static int remove_locked_file_from_db (SeafFilelockManager *mgr, const char *repo_id, const char *path) { char *sql; sqlite3_stmt *stmt; pthread_mutex_lock (&mgr->priv->db_lock); sql = "DELETE FROM ServerLockedFiles WHERE repo_id = ? AND path = ?"; stmt = sqlite_query_prepare (mgr->priv->db, sql); sqlite3_bind_text (stmt, 1, repo_id, -1, SQLITE_TRANSIENT); sqlite3_bind_text (stmt, 2, path, -1, SQLITE_TRANSIENT); if (sqlite3_step (stmt) != SQLITE_DONE) { seaf_warning ("Failed to remove locked file %s from %.8s: %s.\n", path, repo_id, sqlite3_errmsg (mgr->priv->db)); sqlite3_finalize (stmt); pthread_mutex_unlock (&mgr->priv->db_lock); return -1; } sqlite3_finalize (stmt); pthread_mutex_unlock (&mgr->priv->db_lock); return 0; } int seaf_filelock_manager_mark_file_unlocked (SeafFilelockManager *mgr, const char *repo_id, const char *path) { GHashTable *locks; pthread_mutex_lock (&mgr->priv->hash_lock); locks = g_hash_table_lookup (mgr->priv->repo_locked_files, repo_id); if (!locks) { pthread_mutex_unlock (&mgr->priv->hash_lock); return 0; } g_hash_table_remove (locks, path); pthread_mutex_unlock (&mgr->priv->hash_lock); return remove_locked_file_from_db (mgr, repo_id, path); } int seaf_filelock_manager_lock_file (SeafFilelockManager *mgr, const char *repo_id, const char *path, FileLockType type) { int ret = -1; ret = seaf_filelock_manager_mark_file_locked (seaf->filelock_mgr, repo_id, path, type); if (ret < 0) { return ret; } return 0; } int seaf_filelock_manager_unlock_file (SeafFilelockManager *mgr, const char *repo_id, const char *path) { int ret = -1; ret = seaf_filelock_manager_mark_file_unlocked (mgr, repo_id, path); if (ret < 0) { return ret; } return 0; } seadrive-fuse-3.0.13/src/filelock-mgr.h000066400000000000000000000056341476177674700177150ustar00rootroot00000000000000#ifndef SEAF_FILELOCK_MGR_H #define SEAF_FILELOCK_MGR_H #include struct _SeafileSession; struct _FilelockMgrPriv; struct _SeafFilelockManager { struct _SeafileSession *session; struct _FilelockMgrPriv *priv; }; typedef struct _SeafFilelockManager SeafFilelockManager; struct _SeafFilelockManager * seaf_filelock_manager_new (struct _SeafileSession *session); int seaf_filelock_manager_init (SeafFilelockManager *mgr); int seaf_filelock_manager_start (SeafFilelockManager *mgr); gboolean seaf_filelock_manager_is_file_locked (SeafFilelockManager *mgr, const char *repo_id, const char *path); typedef enum FileLockStatus { FILE_NOT_LOCKED = 0, FILE_LOCKED_BY_OTHERS, FILE_LOCKED_BY_ME_MANUAL, FILE_LOCKED_BY_ME_AUTO, } FileLockStatus; /* When a file is locked by me, it can have two reasons: * - Locked by the user manually * - Auto-Locked by Seafile when it detects Office opens the file. */ typedef enum FileLockType { LOCKED_OTHERS = 0, LOCKED_MANUAL, LOCKED_AUTO, } FileLockType; int seaf_filelock_manager_get_lock_status (SeafFilelockManager *mgr, const char *repo_id, const char *path); gboolean seaf_filelock_manager_is_file_locked_by_me (SeafFilelockManager *mgr, const char *repo_id, const char *path); int seaf_filelock_manager_update (SeafFilelockManager *mgr, const char *repo_id, GHashTable *new_locked_files); int seaf_filelock_manager_update_timestamp (SeafFilelockManager *mgr, const char *repo_id, gint64 timestamp); gint64 seaf_filelock_manager_get_timestamp (SeafFilelockManager *mgr, const char *repo_id); int seaf_filelock_manager_remove (SeafFilelockManager *mgr, const char *repo_id); int seaf_filelock_manager_mark_file_locked (SeafFilelockManager *mgr, const char *repo_id, const char *path, FileLockType type); int seaf_filelock_manager_mark_file_unlocked (SeafFilelockManager *mgr, const char *repo_id, const char *path); int seaf_filelock_manager_lock_file (SeafFilelockManager *mgr, const char *repo_id, const char *path, FileLockType type); int seaf_filelock_manager_unlock_file (SeafFilelockManager *mgr, const char *repo_id, const char *path); #endif seadrive-fuse-3.0.13/src/fs-mgr.c000066400000000000000000001730351476177674700165310ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ #include "common.h" #include #include #include #include "seafile-session.h" #include "seafile-error.h" #include "fs-mgr.h" #include "block-mgr.h" #include "utils.h" #define DEBUG_FLAG SEAFILE_DEBUG_OTHER #include "log.h" #include "db.h" #define SEAF_TMP_EXT "~" struct _SeafFSManagerPriv { /* GHashTable *seafile_cache; */ GHashTable *bl_cache; }; typedef struct SeafileOndisk { guint32 type; guint64 file_size; unsigned char block_ids[0]; } SeafileOndisk; typedef struct DirentOndisk { guint32 mode; char id[40]; guint32 name_len; char name[0]; } DirentOndisk; typedef struct SeafdirOndisk { guint32 type; char dirents[0]; } SeafdirOndisk; uint32_t calculate_chunk_size (uint64_t total_size); static int write_seafile (SeafFSManager *fs_mgr, const char *repo_id, int version, CDCFileDescriptor *cdc, unsigned char *obj_sha1); SeafFSManager * seaf_fs_manager_new (SeafileSession *seaf, const char *seaf_dir) { SeafFSManager *mgr = g_new0 (SeafFSManager, 1); mgr->seaf = seaf; mgr->obj_store = seaf_obj_store_new (seaf, "fs"); if (!mgr->obj_store) { g_free (mgr); return NULL; } mgr->priv = g_new0(SeafFSManagerPriv, 1); return mgr; } int seaf_fs_manager_init (SeafFSManager *mgr) { if (seaf_obj_store_init (mgr->obj_store) < 0) { seaf_warning ("[fs mgr] Failed to init fs object store.\n"); return -1; } return 0; } static void * create_seafile_v0 (CDCFileDescriptor *cdc, int *ondisk_size, char *seafile_id) { SeafileOndisk *ondisk; rawdata_to_hex (cdc->file_sum, seafile_id, 20); *ondisk_size = sizeof(SeafileOndisk) + cdc->block_nr * 20; ondisk = (SeafileOndisk *)g_new0 (char, *ondisk_size); ondisk->type = htonl(SEAF_METADATA_TYPE_FILE); ondisk->file_size = hton64 (cdc->file_size); memcpy (ondisk->block_ids, cdc->blk_sha1s, cdc->block_nr * 20); return ondisk; } static void * create_seafile_json (int repo_version, CDCFileDescriptor *cdc, int *ondisk_size, char *seafile_id) { json_t *object, *block_id_array; object = json_object (); json_object_set_int_member (object, "type", SEAF_METADATA_TYPE_FILE); json_object_set_int_member (object, "version", seafile_version_from_repo_version(repo_version)); json_object_set_int_member (object, "size", cdc->file_size); block_id_array = json_array (); int i; uint8_t *ptr = cdc->blk_sha1s; char block_id[41]; for (i = 0; i < cdc->block_nr; ++i) { rawdata_to_hex (ptr, block_id, 20); json_array_append_new (block_id_array, json_string(block_id)); ptr += 20; } json_object_set_new (object, "block_ids", block_id_array); char *data = json_dumps (object, JSON_SORT_KEYS); *ondisk_size = strlen(data); /* The seafile object id is sha1 hash of the json object. */ unsigned char sha1[20]; calculate_sha1 (sha1, data, *ondisk_size); rawdata_to_hex (sha1, seafile_id, 20); json_decref (object); return data; } void seaf_fs_manager_calculate_seafile_id_json (int repo_version, CDCFileDescriptor *cdc, guint8 *file_id_sha1) { json_t *object, *block_id_array; object = json_object (); json_object_set_int_member (object, "type", SEAF_METADATA_TYPE_FILE); json_object_set_int_member (object, "version", seafile_version_from_repo_version(repo_version)); json_object_set_int_member (object, "size", cdc->file_size); block_id_array = json_array (); int i; uint8_t *ptr = cdc->blk_sha1s; char block_id[41]; for (i = 0; i < cdc->block_nr; ++i) { rawdata_to_hex (ptr, block_id, 20); json_array_append_new (block_id_array, json_string(block_id)); ptr += 20; } json_object_set_new (object, "block_ids", block_id_array); char *data = json_dumps (object, JSON_SORT_KEYS); int ondisk_size = strlen(data); /* The seafile object id is sha1 hash of the json object. */ calculate_sha1 (file_id_sha1, data, ondisk_size); json_decref (object); free (data); } static int write_seafile (SeafFSManager *fs_mgr, const char *repo_id, int version, CDCFileDescriptor *cdc, unsigned char sha1[]) { int ret = 0; char seafile_id[41]; void *ondisk; int ondisk_size; if (version > 0) { ondisk = create_seafile_json (version, cdc, &ondisk_size, seafile_id); guint8 *compressed; int outlen; if (seaf_compress (ondisk, ondisk_size, &compressed, &outlen) < 0) { seaf_warning ("Failed to compress seafile obj %s:%s.\n", repo_id, seafile_id); ret = -1; free (ondisk); goto out; } if (seaf_obj_store_write_obj (fs_mgr->obj_store, repo_id, version, seafile_id, compressed, outlen, FALSE) < 0) ret = -1; g_free (compressed); free (ondisk); } else { ondisk = create_seafile_v0 (cdc, &ondisk_size, seafile_id); if (seaf_obj_store_write_obj (fs_mgr->obj_store, repo_id, version, seafile_id, ondisk, ondisk_size, FALSE) < 0) ret = -1; g_free (ondisk); } out: if (ret == 0) hex_to_rawdata (seafile_id, sha1, 20); return ret; } uint32_t calculate_chunk_size (uint64_t total_size) { const uint64_t GiB = 1073741824; const uint64_t MiB = 1048576; if (total_size >= (8 * GiB)) return 8 * MiB; if (total_size >= (4 * GiB)) return 4 * MiB; if (total_size >= (2 * GiB)) return 2 * MiB; return 1 * MiB; } static int do_write_chunk (const char *repo_id, int version, uint8_t *checksum, const char *buf, int len) { SeafBlockManager *blk_mgr = seaf->block_mgr; char chksum_str[41]; BlockHandle *handle; int n; rawdata_to_hex (checksum, chksum_str, 20); /* Don't write if the block already exists. */ if (seaf_block_manager_block_exists (seaf->block_mgr, repo_id, version, chksum_str)) return 0; handle = seaf_block_manager_open_block (blk_mgr, repo_id, version, chksum_str, BLOCK_WRITE); if (!handle) { seaf_warning ("Failed to open block %s.\n", chksum_str); return -1; } n = seaf_block_manager_write_block (blk_mgr, handle, buf, len); if (n < 0) { seaf_warning ("Failed to write chunk %s.\n", chksum_str); seaf_block_manager_close_block (blk_mgr, handle); seaf_block_manager_block_handle_free (blk_mgr, handle); return -1; } if (seaf_block_manager_close_block (blk_mgr, handle) < 0) { seaf_warning ("failed to close block %s.\n", chksum_str); seaf_block_manager_block_handle_free (blk_mgr, handle); return -1; } if (seaf_block_manager_commit_block (blk_mgr, handle) < 0) { seaf_warning ("failed to commit chunk %s.\n", chksum_str); seaf_block_manager_block_handle_free (blk_mgr, handle); return -1; } seaf_block_manager_block_handle_free (blk_mgr, handle); return 0; } /* write the chunk and store its checksum */ int seafile_write_chunk (const char *repo_id, int version, CDCDescriptor *chunk, SeafileCrypt *crypt, uint8_t *checksum, gboolean write_data) { GChecksum *ctx = g_checksum_new (G_CHECKSUM_SHA1); gsize len = 20; int ret = 0; /* Encrypt before write to disk if needed, and we don't encrypt * empty files. */ if (crypt != NULL && chunk->len) { char *encrypted_buf = NULL; /* encrypted output */ int enc_len = -1; /* encrypted length */ ret = seafile_encrypt (&encrypted_buf, /* output */ &enc_len, /* output len */ chunk->block_buf, /* input */ chunk->len, /* input len */ crypt); if (ret != 0) { seaf_warning ("Error: failed to encrypt block\n"); g_checksum_free (ctx); return -1; } g_checksum_update (ctx, (unsigned char *)encrypted_buf, enc_len); g_checksum_get_digest (ctx, checksum, &len); if (write_data) ret = do_write_chunk (repo_id, version, checksum, encrypted_buf, enc_len); g_free (encrypted_buf); } else { /* not a encrypted repo, go ahead */ g_checksum_update (ctx, (unsigned char *)chunk->block_buf, chunk->len); g_checksum_get_digest (ctx, checksum, &len); if (write_data) ret = do_write_chunk (repo_id, version, checksum, chunk->block_buf, chunk->len); } g_checksum_free (ctx); return ret; } static void create_cdc_for_empty_file (CDCFileDescriptor *cdc) { memset (cdc, 0, sizeof(CDCFileDescriptor)); } int seaf_fs_manager_index_blocks (SeafFSManager *mgr, const char *repo_id, int version, const char *file_path, SeafileCrypt *crypt, gboolean write_data, unsigned char sha1[]) { SeafStat sb; CDCFileDescriptor cdc; if (seaf_stat (file_path, &sb) < 0) { seaf_warning ("Bad file %s: %s.\n", file_path, strerror(errno)); return -1; } g_return_val_if_fail (S_ISREG(sb.st_mode), -1); if (sb.st_size == 0) { /* handle empty file. */ memset (sha1, 0, 20); create_cdc_for_empty_file (&cdc); } else { memset (&cdc, 0, sizeof(cdc)); cdc.block_sz = CDC_AVERAGE_BLOCK_SIZE; cdc.block_min_sz = CDC_MIN_BLOCK_SIZE; cdc.block_max_sz = CDC_MAX_BLOCK_SIZE; cdc.write_block = seafile_write_chunk; memcpy (cdc.repo_id, repo_id, 36); cdc.version = version; if (filename_chunk_cdc (file_path, &cdc, crypt, write_data) < 0) { seaf_warning ("Failed to chunk file with CDC.\n"); return -1; } if (write_data && write_seafile (mgr, repo_id, version, &cdc, sha1) < 0) { g_free (cdc.blk_sha1s); seaf_warning ("Failed to write seafile for %s.\n", file_path); return -1; } } if (cdc.blk_sha1s) free (cdc.blk_sha1s); return 0; } void seafile_ref (Seafile *seafile) { ++seafile->ref_count; } static void seafile_free (Seafile *seafile) { int i; if (seafile->blk_sha1s) { for (i = 0; i < seafile->n_blocks; ++i) g_free (seafile->blk_sha1s[i]); g_free (seafile->blk_sha1s); } g_free (seafile); } void seafile_unref (Seafile *seafile) { if (!seafile) return; if (--seafile->ref_count <= 0) seafile_free (seafile); } static Seafile * seafile_from_v0_data (const char *id, const void *data, int len) { const SeafileOndisk *ondisk = data; Seafile *seafile; int id_list_len, n_blocks; if (len < sizeof(SeafileOndisk)) { seaf_warning ("[fs mgr] Corrupt seafile object %s.\n", id); return NULL; } if (ntohl(ondisk->type) != SEAF_METADATA_TYPE_FILE) { seaf_warning ("[fd mgr] %s is not a file.\n", id); return NULL; } id_list_len = len - sizeof(SeafileOndisk); if (id_list_len % 20 != 0) { seaf_warning ("[fs mgr] Corrupt seafile object %s.\n", id); return NULL; } n_blocks = id_list_len / 20; seafile = g_new0 (Seafile, 1); seafile->object.type = SEAF_METADATA_TYPE_FILE; seafile->version = 0; memcpy (seafile->file_id, id, 41); seafile->file_size = ntoh64 (ondisk->file_size); seafile->n_blocks = n_blocks; seafile->blk_sha1s = g_new0 (char*, seafile->n_blocks); const unsigned char *blk_sha1_ptr = ondisk->block_ids; int i; for (i = 0; i < seafile->n_blocks; ++i) { char *blk_sha1 = g_new0 (char, 41); seafile->blk_sha1s[i] = blk_sha1; rawdata_to_hex (blk_sha1_ptr, blk_sha1, 20); blk_sha1_ptr += 20; } seafile->ref_count = 1; return seafile; } static Seafile * seafile_from_json_object (const char *id, json_t *object) { json_t *block_id_array = NULL; int type; int version; guint64 file_size; Seafile *seafile = NULL; /* Sanity checks. */ type = json_object_get_int_member (object, "type"); if (type != SEAF_METADATA_TYPE_FILE) { seaf_debug ("Object %s is not a file.\n", id); return NULL; } version = (int) json_object_get_int_member (object, "version"); if (version < 1) { seaf_debug ("Seafile object %s version should be > 0, version is %d.\n", id, version); return NULL; } file_size = (guint64) json_object_get_int_member (object, "size"); block_id_array = json_object_get (object, "block_ids"); if (!block_id_array) { seaf_debug ("No block id array in seafile object %s.\n", id); return NULL; } seafile = g_new0 (Seafile, 1); seafile->object.type = SEAF_METADATA_TYPE_FILE; memcpy (seafile->file_id, id, 40); seafile->version = version; seafile->file_size = file_size; seafile->n_blocks = json_array_size (block_id_array); seafile->blk_sha1s = g_new0 (char *, seafile->n_blocks); int i; json_t *block_id_obj; const char *block_id; for (i = 0; i < seafile->n_blocks; ++i) { block_id_obj = json_array_get (block_id_array, i); block_id = json_string_value (block_id_obj); if (!block_id || !is_object_id_valid(block_id)) { seafile_free (seafile); return NULL; } seafile->blk_sha1s[i] = g_strdup(block_id); } seafile->ref_count = 1; return seafile; } static Seafile * seafile_from_json (const char *id, void *data, int len) { guint8 *decompressed; int outlen; json_t *object = NULL; json_error_t error; Seafile *seafile; if (seaf_decompress (data, len, &decompressed, &outlen) < 0) { seaf_warning ("Failed to decompress seafile object %s.\n", id); return NULL; } object = json_loadb ((const char *)decompressed, outlen, 0, &error); g_free (decompressed); if (!object) { seaf_warning ("Failed to load seafile json object: %s.\n", error.text); return NULL; } seafile = seafile_from_json_object (id, object); json_decref (object); return seafile; } static Seafile * seafile_from_data (const char *id, void *data, int len, gboolean is_json) { if (is_json) return seafile_from_json (id, data, len); else return seafile_from_v0_data (id, data, len); } Seafile * seaf_fs_manager_get_seafile (SeafFSManager *mgr, const char *repo_id, int version, const char *file_id) { void *data; int len; Seafile *seafile; #if 0 seafile = g_hash_table_lookup (mgr->priv->seafile_cache, file_id); if (seafile) { seafile_ref (seafile); return seafile; } #endif if (memcmp (file_id, EMPTY_SHA1, 40) == 0) { seafile = g_new0 (Seafile, 1); memset (seafile->file_id, '0', 40); seafile->ref_count = 1; return seafile; } if (seaf_obj_store_read_obj (mgr->obj_store, repo_id, version, file_id, &data, &len) < 0) { seaf_warning ("[fs mgr] Failed to read file %s.\n", file_id); return NULL; } seafile = seafile_from_data (file_id, data, len, (version > 0)); g_free (data); return seafile; } static guint8 * seafile_to_v0_data (Seafile *file, int *len) { SeafileOndisk *ondisk; *len = sizeof(SeafileOndisk) + file->n_blocks * 20; ondisk = (SeafileOndisk *)g_new0 (char, *len); ondisk->type = htonl(SEAF_METADATA_TYPE_FILE); ondisk->file_size = hton64 (file->file_size); guint8 *ptr = ondisk->block_ids; int i; for (i = 0; i < file->n_blocks; ++i) { hex_to_rawdata (file->blk_sha1s[i], ptr, 20); ptr += 20; } return (guint8 *)ondisk; } static guint8 * seafile_to_json (Seafile *file, int *len) { json_t *object, *block_id_array; object = json_object (); json_object_set_int_member (object, "type", SEAF_METADATA_TYPE_FILE); json_object_set_int_member (object, "version", file->version); json_object_set_int_member (object, "size", file->file_size); block_id_array = json_array (); int i; for (i = 0; i < file->n_blocks; ++i) { json_array_append_new (block_id_array, json_string(file->blk_sha1s[i])); } json_object_set_new (object, "block_ids", block_id_array); char *data = json_dumps (object, JSON_SORT_KEYS); *len = strlen(data); unsigned char sha1[20]; calculate_sha1 (sha1, data, *len); rawdata_to_hex (sha1, file->file_id, 20); json_decref (object); return (guint8 *)data; } static guint8 * seafile_to_data (Seafile *file, int *len) { if (file->version > 0) { guint8 *data; int orig_len; guint8 *compressed; data = seafile_to_json (file, &orig_len); if (!data) return NULL; if (seaf_compress (data, orig_len, &compressed, len) < 0) { seaf_warning ("Failed to compress file object %s.\n", file->file_id); g_free (data); return NULL; } g_free (data); return compressed; } else return seafile_to_v0_data (file, len); } int seafile_save (SeafFSManager *fs_mgr, const char *repo_id, int version, Seafile *file) { guint8 *data; int len; int ret = 0; data = seafile_to_data (file, &len); if (!data) return -1; if (seaf_obj_store_write_obj (fs_mgr->obj_store, repo_id, version, file->file_id, data, len, FALSE) < 0) ret = -1; g_free (data); return ret; } static void compute_dir_id_v0 (SeafDir *dir, GList *entries) { GChecksum *ctx; GList *p; uint8_t sha1[20]; gsize len = 20; SeafDirent *dent; guint32 mode_le; /* ID for empty dirs is EMPTY_SHA1. */ if (entries == NULL) { memset (dir->dir_id, '0', 40); return; } ctx = g_checksum_new (G_CHECKSUM_SHA1); for (p = entries; p; p = p->next) { dent = (SeafDirent *)p->data; g_checksum_update (ctx, (unsigned char *)dent->id, 40); g_checksum_update (ctx, (unsigned char *)dent->name, dent->name_len); /* Convert mode to little endian before compute. */ if (G_BYTE_ORDER == G_BIG_ENDIAN) mode_le = GUINT32_SWAP_LE_BE (dent->mode); else mode_le = dent->mode; g_checksum_update (ctx, (unsigned char *)&mode_le, sizeof(mode_le)); } g_checksum_get_digest (ctx, sha1, &len); rawdata_to_hex (sha1, dir->dir_id, 20); g_checksum_free (ctx); } SeafDir * seaf_dir_new (const char *id, GList *entries, int version) { SeafDir *dir; dir = g_new0(SeafDir, 1); dir->version = version; if (id != NULL) { memcpy(dir->dir_id, id, 40); dir->dir_id[40] = '\0'; } else if (version == 0) { compute_dir_id_v0 (dir, entries); } dir->entries = entries; if (dir->entries != NULL) dir->ondisk = seaf_dir_to_data (dir, &dir->ondisk_size); else memcpy (dir->dir_id, EMPTY_SHA1, 40); return dir; } void seaf_dir_free (SeafDir *dir) { if (dir == NULL) return; GList *ptr = dir->entries; while (ptr) { seaf_dirent_free ((SeafDirent *)ptr->data); ptr = ptr->next; } g_list_free (dir->entries); g_free (dir->ondisk); g_free(dir); } SeafDirent * seaf_dirent_new (int version, const char *sha1, int mode, const char *name, gint64 mtime, const char *modifier, gint64 size) { SeafDirent *dent; dent = g_new0 (SeafDirent, 1); dent->version = version; memcpy(dent->id, sha1, 40); dent->id[40] = '\0'; /* Mode for files must have 0644 set. To prevent the caller from forgetting, * we set the bits here. */ if (S_ISREG(mode)) dent->mode = (mode | 0644); else dent->mode = mode; dent->name = g_strdup(name); dent->name_len = strlen(name); if (version > 0) { dent->mtime = mtime; if (S_ISREG(mode)) { dent->modifier = g_strdup(modifier); dent->size = size; } } return dent; } void seaf_dirent_free (SeafDirent *dent) { if (!dent) return; g_free (dent->name); g_free (dent->modifier); g_free (dent); } SeafDirent * seaf_dirent_dup (SeafDirent *dent) { SeafDirent *new_dent; new_dent = g_memdup (dent, sizeof(SeafDirent)); new_dent->name = g_strdup(dent->name); new_dent->modifier = g_strdup(dent->modifier); return new_dent; } static SeafDir * seaf_dir_from_v0_data (const char *dir_id, const uint8_t *data, int len) { SeafDir *root; SeafDirent *dent; const uint8_t *ptr; int remain; int dirent_base_size; guint32 meta_type; guint32 name_len; ptr = data; remain = len; meta_type = get32bit (&ptr); remain -= 4; if (meta_type != SEAF_METADATA_TYPE_DIR) { seaf_warning ("Data does not contain a directory.\n"); return NULL; } root = g_new0(SeafDir, 1); root->object.type = SEAF_METADATA_TYPE_DIR; root->version = 0; memcpy(root->dir_id, dir_id, 40); root->dir_id[40] = '\0'; dirent_base_size = 2 * sizeof(guint32) + 40; while (remain > dirent_base_size) { dent = g_new0(SeafDirent, 1); dent->version = 0; dent->mode = get32bit (&ptr); memcpy (dent->id, ptr, 40); dent->id[40] = '\0'; ptr += 40; name_len = get32bit (&ptr); remain -= dirent_base_size; if (remain >= name_len) { dent->name_len = MIN (name_len, SEAF_DIR_NAME_LEN - 1); dent->name = g_strndup((const char *)ptr, dent->name_len); ptr += dent->name_len; remain -= dent->name_len; } else { seaf_warning ("Bad data format for dir objcet %s.\n", dir_id); g_free (dent); goto bad; } root->entries = g_list_prepend (root->entries, dent); } root->entries = g_list_reverse (root->entries); return root; bad: seaf_dir_free (root); return NULL; } static SeafDirent * parse_dirent (const char *dir_id, int version, json_t *object) { guint32 mode; const char *id; const char *name; gint64 mtime; const char *modifier; gint64 size; mode = (guint32) json_object_get_int_member (object, "mode"); id = json_object_get_string_member (object, "id"); if (!id) { seaf_debug ("Dirent id not set for dir object %s.\n", dir_id); return NULL; } if (!is_object_id_valid (id)) { seaf_debug ("Dirent id is invalid for dir object %s.\n", dir_id); return NULL; } name = json_object_get_string_member (object, "name"); if (!name) { seaf_debug ("Dirent name not set for dir object %s.\n", dir_id); return NULL; } mtime = json_object_get_int_member (object, "mtime"); if (S_ISREG(mode)) { modifier = json_object_get_string_member (object, "modifier"); if (!modifier) { seaf_debug ("Dirent modifier not set for dir object %s.\n", dir_id); return NULL; } size = json_object_get_int_member (object, "size"); } SeafDirent *dirent = g_new0 (SeafDirent, 1); dirent->version = version; dirent->mode = mode; memcpy (dirent->id, id, 40); dirent->name_len = strlen(name); dirent->name = g_strdup(name); dirent->mtime = mtime; if (S_ISREG(mode)) { dirent->modifier = g_strdup(modifier); dirent->size = size; } return dirent; } static SeafDir * seaf_dir_from_json_object (const char *dir_id, json_t *object) { json_t *dirent_array = NULL; int type; int version; SeafDir *dir = NULL; /* Sanity checks. */ type = json_object_get_int_member (object, "type"); if (type != SEAF_METADATA_TYPE_DIR) { seaf_debug ("Object %s is not a dir.\n", dir_id); return NULL; } version = (int) json_object_get_int_member (object, "version"); if (version < 1) { seaf_debug ("Dir object %s version should be > 0, version is %d.\n", dir_id, version); return NULL; } dirent_array = json_object_get (object, "dirents"); if (!dirent_array) { seaf_debug ("No dirents in dir object %s.\n", dir_id); return NULL; } dir = g_new0 (SeafDir, 1); dir->object.type = SEAF_METADATA_TYPE_DIR; memcpy (dir->dir_id, dir_id, 40); dir->version = version; size_t n_dirents = json_array_size (dirent_array); int i; json_t *dirent_obj; SeafDirent *dirent; for (i = 0; i < n_dirents; ++i) { dirent_obj = json_array_get (dirent_array, i); dirent = parse_dirent (dir_id, version, dirent_obj); if (!dirent) { seaf_dir_free (dir); return NULL; } dir->entries = g_list_prepend (dir->entries, dirent); } dir->entries = g_list_reverse (dir->entries); return dir; } static SeafDir * seaf_dir_from_json (const char *dir_id, uint8_t *data, int len) { guint8 *decompressed; int outlen; json_t *object = NULL; json_error_t error; SeafDir *dir; if (seaf_decompress (data, len, &decompressed, &outlen) < 0) { seaf_warning ("Failed to decompress dir object %s.\n", dir_id); return NULL; } object = json_loadb ((const char *)decompressed, outlen, 0, &error); g_free (decompressed); if (!object) { seaf_warning ("Failed to load seafdir json object: %s.\n", error.text); return NULL; } dir = seaf_dir_from_json_object (dir_id, object); json_decref (object); return dir; } SeafDir * seaf_dir_from_data (const char *dir_id, uint8_t *data, int len, gboolean is_json) { if (is_json) return seaf_dir_from_json (dir_id, data, len); else return seaf_dir_from_v0_data (dir_id, data, len); } inline static int ondisk_dirent_size (SeafDirent *dirent) { return sizeof(DirentOndisk) + dirent->name_len; } static void * seaf_dir_to_v0_data (SeafDir *dir, int *len) { SeafdirOndisk *ondisk; int dir_ondisk_size = sizeof(SeafdirOndisk); GList *dirents = dir->entries; GList *ptr; SeafDirent *de; char *p; DirentOndisk *de_ondisk; for (ptr = dirents; ptr; ptr = ptr->next) { de = ptr->data; dir_ondisk_size += ondisk_dirent_size (de); } *len = dir_ondisk_size; ondisk = (SeafdirOndisk *) g_new0 (char, dir_ondisk_size); ondisk->type = htonl (SEAF_METADATA_TYPE_DIR); p = ondisk->dirents; for (ptr = dirents; ptr; ptr = ptr->next) { de = ptr->data; de_ondisk = (DirentOndisk *) p; de_ondisk->mode = htonl(de->mode); memcpy (de_ondisk->id, de->id, 40); de_ondisk->name_len = htonl (de->name_len); memcpy (de_ondisk->name, de->name, de->name_len); p += ondisk_dirent_size (de); } return (void *)ondisk; } static void add_to_dirent_array (json_t *array, SeafDirent *dirent) { json_t *object; object = json_object (); json_object_set_int_member (object, "mode", dirent->mode); json_object_set_string_member (object, "id", dirent->id); json_object_set_string_member (object, "name", dirent->name); json_object_set_int_member (object, "mtime", dirent->mtime); if (S_ISREG(dirent->mode)) { json_object_set_string_member (object, "modifier", dirent->modifier); json_object_set_int_member (object, "size", dirent->size); } json_array_append_new (array, object); } static void * seaf_dir_to_json (SeafDir *dir, int *len) { json_t *object, *dirent_array; GList *ptr; SeafDirent *dirent; object = json_object (); json_object_set_int_member (object, "type", SEAF_METADATA_TYPE_DIR); json_object_set_int_member (object, "version", dir->version); dirent_array = json_array (); for (ptr = dir->entries; ptr; ptr = ptr->next) { dirent = ptr->data; add_to_dirent_array (dirent_array, dirent); } json_object_set_new (object, "dirents", dirent_array); char *data = json_dumps (object, JSON_SORT_KEYS); *len = strlen(data); /* The dir object id is sha1 hash of the json object. */ unsigned char sha1[20]; calculate_sha1 (sha1, data, *len); rawdata_to_hex (sha1, dir->dir_id, 20); json_decref (object); return data; } void * seaf_dir_to_data (SeafDir *dir, int *len) { if (dir->version > 0) { guint8 *data; int orig_len; guint8 *compressed; data = seaf_dir_to_json (dir, &orig_len); if (!data) return NULL; if (seaf_compress (data, orig_len, &compressed, len) < 0) { seaf_warning ("Failed to compress dir object %s.\n", dir->dir_id); g_free (data); return NULL; } g_free (data); return compressed; } else return seaf_dir_to_v0_data (dir, len); } int seaf_dir_save (SeafFSManager *fs_mgr, const char *repo_id, int version, SeafDir *dir) { int ret = 0; /* Don't need to save empty dir on disk. */ if (memcmp (dir->dir_id, EMPTY_SHA1, 40) == 0) return 0; if (seaf_obj_store_write_obj (fs_mgr->obj_store, repo_id, version, dir->dir_id, dir->ondisk, dir->ondisk_size, FALSE) < 0) ret = -1; return ret; } SeafDir * seaf_fs_manager_get_seafdir (SeafFSManager *mgr, const char *repo_id, int version, const char *dir_id) { void *data; int len; SeafDir *dir; /* TODO: add hash cache */ if (memcmp (dir_id, EMPTY_SHA1, 40) == 0) { dir = g_new0 (SeafDir, 1); dir->version = version; memset (dir->dir_id, '0', 40); return dir; } if (seaf_obj_store_read_obj (mgr->obj_store, repo_id, version, dir_id, &data, &len) < 0) { seaf_warning ("[fs mgr] Failed to read dir %s.\n", dir_id); return NULL; } dir = seaf_dir_from_data (dir_id, data, len, (version > 0)); g_free (data); return dir; } static gint compare_dirents (gconstpointer a, gconstpointer b) { const SeafDirent *denta = a, *dentb = b; return strcmp (dentb->name, denta->name); } static gboolean is_dirents_sorted (GList *dirents) { GList *ptr; SeafDirent *dent, *dent_n; gboolean ret = TRUE; for (ptr = dirents; ptr != NULL; ptr = ptr->next) { dent = ptr->data; if (!ptr->next) break; dent_n = ptr->next->data; /* If dirents are not sorted in descending order, return FALSE. */ if (strcmp (dent->name, dent_n->name) < 0) { ret = FALSE; break; } } return ret; } SeafDir * seaf_fs_manager_get_seafdir_sorted (SeafFSManager *mgr, const char *repo_id, int version, const char *dir_id) { SeafDir *dir = seaf_fs_manager_get_seafdir(mgr, repo_id, version, dir_id); if (!dir) return NULL; /* Only some very old dir objects are not sorted. */ if (version > 0) return dir; if (!is_dirents_sorted (dir->entries)) dir->entries = g_list_sort (dir->entries, compare_dirents); return dir; } SeafDir * seaf_fs_manager_get_seafdir_sorted_by_path (SeafFSManager *mgr, const char *repo_id, int version, const char *root_id, const char *path) { SeafDir *dir = seaf_fs_manager_get_seafdir_by_path (mgr, repo_id, version, root_id, path, NULL); if (!dir) return NULL; /* Only some very old dir objects are not sorted. */ if (version > 0) return dir; if (!is_dirents_sorted (dir->entries)) dir->entries = g_list_sort (dir->entries, compare_dirents); return dir; } static int parse_metadata_type_v0 (const uint8_t *data, int len) { const uint8_t *ptr = data; if (len < sizeof(guint32)) return SEAF_METADATA_TYPE_INVALID; return (int)(get32bit(&ptr)); } static int parse_metadata_type_json (const char *obj_id, uint8_t *data, int len) { guint8 *decompressed; int outlen; json_t *object; json_error_t error; int type; if (seaf_decompress (data, len, &decompressed, &outlen) < 0) { seaf_warning ("Failed to decompress fs object %s.\n", obj_id); return SEAF_METADATA_TYPE_INVALID; } object = json_loadb ((const char *)decompressed, outlen, 0, &error); g_free (decompressed); if (!object) { seaf_warning ("Failed to load fs json object: %s.\n", error.text); return SEAF_METADATA_TYPE_INVALID; } type = json_object_get_int_member (object, "type"); json_decref (object); return type; } int seaf_metadata_type_from_data (const char *obj_id, uint8_t *data, int len, gboolean is_json) { if (is_json) return parse_metadata_type_json (obj_id, data, len); else return parse_metadata_type_v0 (data, len); } SeafFSObject * fs_object_from_v0_data (const char *obj_id, const uint8_t *data, int len) { int type = parse_metadata_type_v0 (data, len); if (type == SEAF_METADATA_TYPE_FILE) return (SeafFSObject *)seafile_from_v0_data (obj_id, data, len); else if (type == SEAF_METADATA_TYPE_DIR) return (SeafFSObject *)seaf_dir_from_v0_data (obj_id, data, len); else { seaf_warning ("Invalid object type %d.\n", type); return NULL; } } SeafFSObject * fs_object_from_json (const char *obj_id, uint8_t *data, int len) { guint8 *decompressed; int outlen; json_t *object; json_error_t error; int type; SeafFSObject *fs_obj; if (seaf_decompress (data, len, &decompressed, &outlen) < 0) { seaf_warning ("Failed to decompress fs object %s.\n", obj_id); return NULL; } object = json_loadb ((const char *)decompressed, outlen, 0, &error); g_free (decompressed); if (!object) { seaf_warning ("Failed to load fs json object: %s.\n", error.text); return NULL; } type = json_object_get_int_member (object, "type"); if (type == SEAF_METADATA_TYPE_FILE) fs_obj = (SeafFSObject *)seafile_from_json_object (obj_id, object); else if (type == SEAF_METADATA_TYPE_DIR) fs_obj = (SeafFSObject *)seaf_dir_from_json_object (obj_id, object); else { seaf_warning ("Invalid fs type %d.\n", type); json_decref (object); return NULL; } json_decref (object); return fs_obj; } SeafFSObject * seaf_fs_object_from_data (const char *obj_id, uint8_t *data, int len, gboolean is_json) { if (is_json) return fs_object_from_json (obj_id, data, len); else return fs_object_from_v0_data (obj_id, data, len); } void seaf_fs_object_free (SeafFSObject *obj) { if (!obj) return; if (obj->type == SEAF_METADATA_TYPE_FILE) seafile_unref ((Seafile *)obj); else if (obj->type == SEAF_METADATA_TYPE_DIR) seaf_dir_free ((SeafDir *)obj); } BlockList * block_list_new () { BlockList *bl = g_new0 (BlockList, 1); bl->block_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); bl->block_ids = g_ptr_array_new_with_free_func (g_free); return bl; } void block_list_free (BlockList *bl) { if (bl->block_hash) g_hash_table_destroy (bl->block_hash); g_ptr_array_free (bl->block_ids, TRUE); g_free (bl); } void block_list_insert (BlockList *bl, const char *block_id) { if (g_hash_table_lookup (bl->block_hash, block_id)) return; char *key = g_strdup(block_id); g_hash_table_replace (bl->block_hash, key, key); g_ptr_array_add (bl->block_ids, g_strdup(block_id)); ++bl->n_blocks; } BlockList * block_list_difference (BlockList *bl1, BlockList *bl2) { BlockList *bl; int i; char *block_id; char *key; bl = block_list_new (); for (i = 0; i < bl1->block_ids->len; ++i) { block_id = g_ptr_array_index (bl1->block_ids, i); if (g_hash_table_lookup (bl2->block_hash, block_id) == NULL) { key = g_strdup(block_id); g_hash_table_replace (bl->block_hash, key, key); g_ptr_array_add (bl->block_ids, g_strdup(block_id)); ++bl->n_blocks; } } return bl; } static int traverse_file (SeafFSManager *mgr, const char *repo_id, int version, const char *id, TraverseFSTreeCallback callback, void *user_data, gboolean skip_errors) { gboolean stop = FALSE; if (memcmp (id, EMPTY_SHA1, 40) == 0) return 0; if (!callback (mgr, repo_id, version, id, SEAF_METADATA_TYPE_FILE, user_data, &stop) && !skip_errors) return -1; return 0; } static int traverse_dir (SeafFSManager *mgr, const char *repo_id, int version, const char *id, TraverseFSTreeCallback callback, void *user_data, gboolean skip_errors) { SeafDir *dir; GList *p; SeafDirent *seaf_dent; gboolean stop = FALSE; if (!callback (mgr, repo_id, version, id, SEAF_METADATA_TYPE_DIR, user_data, &stop) && !skip_errors) return -1; if (stop) return 0; dir = seaf_fs_manager_get_seafdir (mgr, repo_id, version, id); if (!dir) { seaf_warning ("[fs-mgr]get seafdir %s failed\n", id); if (skip_errors) return 0; return -1; } for (p = dir->entries; p; p = p->next) { seaf_dent = (SeafDirent *)p->data; if (S_ISREG(seaf_dent->mode)) { if (traverse_file (mgr, repo_id, version, seaf_dent->id, callback, user_data, skip_errors) < 0) { if (!skip_errors) { seaf_dir_free (dir); return -1; } } } else if (S_ISDIR(seaf_dent->mode)) { if (traverse_dir (mgr, repo_id, version, seaf_dent->id, callback, user_data, skip_errors) < 0) { if (!skip_errors) { seaf_dir_free (dir); return -1; } } } } seaf_dir_free (dir); return 0; } int seaf_fs_manager_traverse_tree (SeafFSManager *mgr, const char *repo_id, int version, const char *root_id, TraverseFSTreeCallback callback, void *user_data, gboolean skip_errors) { if (strcmp (root_id, EMPTY_SHA1) == 0) { return 0; } return traverse_dir (mgr, repo_id, version, root_id, callback, user_data, skip_errors); } static int traverse_dir_path (SeafFSManager *mgr, const char *repo_id, int version, const char *dir_path, SeafDirent *dent, TraverseFSPathCallback callback, void *user_data) { SeafDir *dir; GList *p; SeafDirent *seaf_dent; gboolean stop = FALSE; char *sub_path; int ret = 0; if (!callback (mgr, dir_path, dent, user_data, &stop)) return -1; if (stop) return 0; dir = seaf_fs_manager_get_seafdir (mgr, repo_id, version, dent->id); if (!dir) { seaf_warning ("get seafdir %s:%s failed\n", repo_id, dent->id); return -1; } for (p = dir->entries; p; p = p->next) { seaf_dent = (SeafDirent *)p->data; sub_path = g_strconcat (dir_path, "/", seaf_dent->name, NULL); if (S_ISREG(seaf_dent->mode)) { if (!callback (mgr, sub_path, seaf_dent, user_data, &stop)) { g_free (sub_path); ret = -1; break; } } else if (S_ISDIR(seaf_dent->mode)) { if (traverse_dir_path (mgr, repo_id, version, sub_path, seaf_dent, callback, user_data) < 0) { g_free (sub_path); ret = -1; break; } } g_free (sub_path); } seaf_dir_free (dir); return ret; } int seaf_fs_manager_traverse_path (SeafFSManager *mgr, const char *repo_id, int version, const char *root_id, const char *dir_path, TraverseFSPathCallback callback, void *user_data) { SeafDirent *dent; int ret = 0; dent = seaf_fs_manager_get_dirent_by_path (mgr, repo_id, version, root_id, dir_path, NULL); if (!dent) { seaf_warning ("Failed to get dirent for %.8s:%s.\n", repo_id, dir_path); return -1; } ret = traverse_dir_path (mgr, repo_id, version, dir_path, dent, callback, user_data); seaf_dirent_free (dent); return ret; } static gboolean fill_blocklist (SeafFSManager *mgr, const char *repo_id, int version, const char *obj_id, int type, void *user_data, gboolean *stop) { BlockList *bl = user_data; Seafile *seafile; int i; if (type == SEAF_METADATA_TYPE_FILE) { seafile = seaf_fs_manager_get_seafile (mgr, repo_id, version, obj_id); if (!seafile) { seaf_warning ("[fs mgr] Failed to find file %s.\n", obj_id); return FALSE; } for (i = 0; i < seafile->n_blocks; ++i) block_list_insert (bl, seafile->blk_sha1s[i]); seafile_unref (seafile); } return TRUE; } int seaf_fs_manager_populate_blocklist (SeafFSManager *mgr, const char *repo_id, int version, const char *root_id, BlockList *bl) { return seaf_fs_manager_traverse_tree (mgr, repo_id, version, root_id, fill_blocklist, bl, FALSE); } gboolean seaf_fs_manager_object_exists (SeafFSManager *mgr, const char *repo_id, int version, const char *id) { /* Empty file and dir always exists. */ if (memcmp (id, EMPTY_SHA1, 40) == 0) return TRUE; return seaf_obj_store_obj_exists (mgr->obj_store, repo_id, version, id); } void seaf_fs_manager_delete_object (SeafFSManager *mgr, const char *repo_id, int version, const char *id) { seaf_obj_store_delete_obj (mgr->obj_store, repo_id, version, id); } gint64 seaf_fs_manager_get_file_size (SeafFSManager *mgr, const char *repo_id, int version, const char *file_id) { Seafile *file; gint64 file_size; file = seaf_fs_manager_get_seafile (seaf->fs_mgr, repo_id, version, file_id); if (!file) { seaf_warning ("Couldn't get file %s:%s\n", repo_id, file_id); return -1; } file_size = file->file_size; seafile_unref (file); return file_size; } static gint64 get_dir_size (SeafFSManager *mgr, const char *repo_id, int version, const char *id) { SeafDir *dir; SeafDirent *seaf_dent; guint64 size = 0; gint64 result; GList *p; dir = seaf_fs_manager_get_seafdir (mgr, repo_id, version, id); if (!dir) return -1; for (p = dir->entries; p; p = p->next) { seaf_dent = (SeafDirent *)p->data; if (S_ISREG(seaf_dent->mode)) { if (dir->version > 0) result = seaf_dent->size; else { result = seaf_fs_manager_get_file_size (mgr, repo_id, version, seaf_dent->id); if (result < 0) { seaf_dir_free (dir); return result; } } size += result; } else if (S_ISDIR(seaf_dent->mode)) { result = get_dir_size (mgr, repo_id, version, seaf_dent->id); if (result < 0) { seaf_dir_free (dir); return result; } size += result; } } seaf_dir_free (dir); return size; } gint64 seaf_fs_manager_get_fs_size (SeafFSManager *mgr, const char *repo_id, int version, const char *root_id) { if (strcmp (root_id, EMPTY_SHA1) == 0) return 0; return get_dir_size (mgr, repo_id, version, root_id); } static int count_dir_files (SeafFSManager *mgr, const char *repo_id, int version, const char *id) { SeafDir *dir; SeafDirent *seaf_dent; int count = 0; int result; GList *p; dir = seaf_fs_manager_get_seafdir (mgr, repo_id, version, id); if (!dir) return -1; for (p = dir->entries; p; p = p->next) { seaf_dent = (SeafDirent *)p->data; if (S_ISREG(seaf_dent->mode)) { count ++; } else if (S_ISDIR(seaf_dent->mode)) { result = count_dir_files (mgr, repo_id, version, seaf_dent->id); if (result < 0) { seaf_dir_free (dir); return result; } count += result; } } seaf_dir_free (dir); return count; } int seaf_fs_manager_count_fs_files (SeafFSManager *mgr, const char *repo_id, int version, const char *root_id) { if (strcmp (root_id, EMPTY_SHA1) == 0) return 0; return count_dir_files (mgr, repo_id, version, root_id); } SeafDir * seaf_fs_manager_get_seafdir_by_path (SeafFSManager *mgr, const char *repo_id, int version, const char *root_id, const char *path, GError **error) { SeafDir *dir; SeafDirent *dent; const char *dir_id = root_id; char *name, *saveptr; char *tmp_path = g_strdup(path); dir = seaf_fs_manager_get_seafdir (mgr, repo_id, version, dir_id); if (!dir) { g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_DIR_MISSING, "directory is missing"); return NULL; } name = strtok_r (tmp_path, "/", &saveptr); while (name != NULL) { GList *l; for (l = dir->entries; l != NULL; l = l->next) { dent = l->data; if (strcmp(dent->name, name) == 0 && S_ISDIR(dent->mode)) { dir_id = dent->id; break; } } if (!l) { g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_PATH_NO_EXIST, "Path does not exists %s", path); seaf_dir_free (dir); dir = NULL; break; } SeafDir *prev = dir; dir = seaf_fs_manager_get_seafdir (mgr, repo_id, version, dir_id); seaf_dir_free (prev); if (!dir) { g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_DIR_MISSING, "directory is missing"); break; } name = strtok_r (NULL, "/", &saveptr); } g_free (tmp_path); return dir; } char * seaf_fs_manager_path_to_obj_id (SeafFSManager *mgr, const char *repo_id, int version, const char *root_id, const char *path, guint32 *mode, GError **error) { char *copy = g_strdup (path); int off = strlen(copy) - 1; char *slash, *name; SeafDir *base_dir = NULL; SeafDirent *dent; GList *p; char *obj_id = NULL; while (off >= 0 && copy[off] == '/') copy[off--] = 0; if (strlen(copy) == 0) { /* the path is root "/" */ if (mode) { *mode = S_IFDIR; } obj_id = g_strdup(root_id); goto out; } slash = strrchr (copy, '/'); if (!slash) { base_dir = seaf_fs_manager_get_seafdir (mgr, repo_id, version, root_id); if (!base_dir) { seaf_warning ("Failed to find root dir %s.\n", root_id); g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL, " "); goto out; } name = copy; } else { *slash = 0; name = slash + 1; GError *tmp_error = NULL; base_dir = seaf_fs_manager_get_seafdir_by_path (mgr, repo_id, version, root_id, copy, &tmp_error); if (tmp_error && !g_error_matches(tmp_error, SEAFILE_DOMAIN, SEAF_ERR_PATH_NO_EXIST)) { seaf_warning ("Failed to get dir for %s.\n", copy); g_propagate_error (error, tmp_error); goto out; } /* The path doesn't exist in this commit. */ if (!base_dir) { g_propagate_error (error, tmp_error); goto out; } } for (p = base_dir->entries; p != NULL; p = p->next) { dent = p->data; if (!is_object_id_valid (dent->id)) continue; if (strcmp (dent->name, name) == 0) { obj_id = g_strdup (dent->id); if (mode) { *mode = dent->mode; } break; } } out: if (base_dir) seaf_dir_free (base_dir); g_free (copy); return obj_id; } char * seaf_fs_manager_get_seafile_id_by_path (SeafFSManager *mgr, const char *repo_id, int version, const char *root_id, const char *path, GError **error) { guint32 mode; char *file_id; file_id = seaf_fs_manager_path_to_obj_id (mgr, repo_id, version, root_id, path, &mode, error); if (!file_id) return NULL; if (file_id && S_ISDIR(mode)) { g_free (file_id); return NULL; } return file_id; } char * seaf_fs_manager_get_seafdir_id_by_path (SeafFSManager *mgr, const char *repo_id, int version, const char *root_id, const char *path, GError **error) { guint32 mode = 0; char *dir_id; dir_id = seaf_fs_manager_path_to_obj_id (mgr, repo_id, version, root_id, path, &mode, error); if (!dir_id) return NULL; if (dir_id && !S_ISDIR(mode)) { g_free (dir_id); return NULL; } return dir_id; } SeafDirent * seaf_fs_manager_get_dirent_by_path (SeafFSManager *mgr, const char *repo_id, int version, const char *root_id, const char *path, GError **error) { SeafDirent *dent = NULL; SeafDir *dir = NULL; char *parent_dir = NULL; char *file_name = NULL; parent_dir = g_path_get_dirname(path); file_name = g_path_get_basename(path); if (strcmp (parent_dir, ".") == 0) { dir = seaf_fs_manager_get_seafdir (mgr, repo_id, version, root_id); if (!dir) { g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_DIR_MISSING, "directory is missing"); } } else dir = seaf_fs_manager_get_seafdir_by_path (mgr, repo_id, version, root_id, parent_dir, error); if (!dir) { seaf_warning ("dir %s doesn't exist in repo %.8s.\n", parent_dir, repo_id); goto out; } GList *p; for (p = dir->entries; p; p = p->next) { SeafDirent *d = p->data; if (strcmp (d->name, file_name) == 0) { dent = seaf_dirent_dup(d); break; } } out: if (dir) seaf_dir_free (dir); g_free (parent_dir); g_free (file_name); return dent; } static gboolean verify_seafdir_v0 (const char *dir_id, const uint8_t *data, int len, gboolean verify_id) { guint32 meta_type; guint32 mode; char id[41]; guint32 name_len; char name[SEAF_DIR_NAME_LEN]; const uint8_t *ptr; int remain; int dirent_base_size; GChecksum *ctx; uint8_t sha1[20]; gsize cs_len = 20; char check_id[41]; if (len < sizeof(SeafdirOndisk)) { seaf_warning ("[fs mgr] Corrupt seafdir object %s.\n", dir_id); return FALSE; } ptr = data; remain = len; meta_type = get32bit (&ptr); remain -= 4; if (meta_type != SEAF_METADATA_TYPE_DIR) { seaf_warning ("Data does not contain a directory.\n"); return FALSE; } if (verify_id) { ctx = g_checksum_new (G_CHECKSUM_SHA1); } dirent_base_size = 2 * sizeof(guint32) + 40; while (remain > dirent_base_size) { mode = get32bit (&ptr); memcpy (id, ptr, 40); id[40] = '\0'; ptr += 40; name_len = get32bit (&ptr); remain -= dirent_base_size; if (remain >= name_len) { name_len = MIN (name_len, SEAF_DIR_NAME_LEN - 1); memcpy (name, ptr, name_len); ptr += name_len; remain -= name_len; } else { seaf_warning ("Bad data format for dir objcet %s.\n", dir_id); return FALSE; } if (verify_id) { /* Convert mode to little endian before compute. */ if (G_BYTE_ORDER == G_BIG_ENDIAN) mode = GUINT32_SWAP_LE_BE (mode); g_checksum_update (ctx, (unsigned char *)id, 40); g_checksum_update (ctx, (unsigned char *)name, name_len); g_checksum_update (ctx, (unsigned char *)&mode, sizeof(mode)); } } if (!verify_id) return TRUE; g_checksum_get_digest (ctx, sha1, &cs_len); rawdata_to_hex (sha1, check_id, 20); g_checksum_free (ctx); if (strcmp (check_id, dir_id) == 0) return TRUE; else return FALSE; } static gboolean verify_fs_object_json (const char *obj_id, uint8_t *data, int len) { guint8 *decompressed; int outlen; unsigned char sha1[20]; char hex[41]; if (seaf_decompress (data, len, &decompressed, &outlen) < 0) { seaf_warning ("Failed to decompress fs object %s.\n", obj_id); return FALSE; } calculate_sha1 (sha1, (const char *)decompressed, outlen); rawdata_to_hex (sha1, hex, 20); g_free (decompressed); return (strcmp(hex, obj_id) == 0); } static gboolean verify_seafdir (const char *dir_id, uint8_t *data, int len, gboolean verify_id, gboolean is_json) { if (is_json) return verify_fs_object_json (dir_id, data, len); else return verify_seafdir_v0 (dir_id, data, len, verify_id); } gboolean seaf_fs_manager_verify_seafdir (SeafFSManager *mgr, const char *repo_id, int version, const char *dir_id, gboolean verify_id, gboolean *io_error) { void *data; int len; if (memcmp (dir_id, EMPTY_SHA1, 40) == 0) { return TRUE; } if (seaf_obj_store_read_obj (mgr->obj_store, repo_id, version, dir_id, &data, &len) < 0) { seaf_warning ("[fs mgr] Failed to read dir %s:%s.\n", repo_id, dir_id); *io_error = TRUE; return FALSE; } gboolean ret = verify_seafdir (dir_id, data, len, verify_id, (version > 0)); g_free (data); return ret; } static gboolean verify_seafile_v0 (const char *id, const void *data, int len, gboolean verify_id) { const SeafileOndisk *ondisk = data; GChecksum *ctx; uint8_t sha1[20]; gsize cs_len = 20; char check_id[41]; if (len < sizeof(SeafileOndisk)) { seaf_warning ("[fs mgr] Corrupt seafile object %s.\n", id); return FALSE; } if (ntohl(ondisk->type) != SEAF_METADATA_TYPE_FILE) { seaf_warning ("[fd mgr] %s is not a file.\n", id); return FALSE; } int id_list_length = len - sizeof(SeafileOndisk); if (id_list_length % 20 != 0) { seaf_warning ("[fs mgr] Bad seafile id list length %d.\n", id_list_length); return FALSE; } if (!verify_id) return TRUE; ctx = g_checksum_new (G_CHECKSUM_SHA1); g_checksum_update (ctx, ondisk->block_ids, len - sizeof(SeafileOndisk)); g_checksum_get_digest (ctx, sha1, &cs_len); g_checksum_free (ctx); rawdata_to_hex (sha1, check_id, 20); if (strcmp (check_id, id) == 0) return TRUE; else return FALSE; } static gboolean verify_seafile (const char *id, void *data, int len, gboolean verify_id, gboolean is_json) { if (is_json) return verify_fs_object_json (id, data, len); else return verify_seafile_v0 (id, data, len, verify_id); } gboolean seaf_fs_manager_verify_seafile (SeafFSManager *mgr, const char *repo_id, int version, const char *file_id, gboolean verify_id, gboolean *io_error) { void *data; int len; if (memcmp (file_id, EMPTY_SHA1, 40) == 0) { return TRUE; } if (seaf_obj_store_read_obj (mgr->obj_store, repo_id, version, file_id, &data, &len) < 0) { seaf_warning ("[fs mgr] Failed to read file %s:%s.\n", repo_id, file_id); *io_error = TRUE; return FALSE; } gboolean ret = verify_seafile (file_id, data, len, verify_id, (version > 0)); g_free (data); return ret; } static gboolean verify_fs_object_v0 (const char *obj_id, uint8_t *data, int len, gboolean verify_id) { gboolean ret = TRUE; int type = seaf_metadata_type_from_data (obj_id, data, len, FALSE); switch (type) { case SEAF_METADATA_TYPE_FILE: ret = verify_seafile_v0 (obj_id, data, len, verify_id); break; case SEAF_METADATA_TYPE_DIR: ret = verify_seafdir_v0 (obj_id, data, len, verify_id); break; default: seaf_warning ("Invalid meta data type: %d.\n", type); return FALSE; } return ret; } gboolean seaf_fs_manager_verify_object (SeafFSManager *mgr, const char *repo_id, int version, const char *obj_id, gboolean verify_id, gboolean *io_error) { void *data; int len; gboolean ret = TRUE; if (memcmp (obj_id, EMPTY_SHA1, 40) == 0) { return TRUE; } if (seaf_obj_store_read_obj (mgr->obj_store, repo_id, version, obj_id, &data, &len) < 0) { seaf_warning ("[fs mgr] Failed to read object %s:%s.\n", repo_id, obj_id); *io_error = TRUE; return FALSE; } if (version == 0) ret = verify_fs_object_v0 (obj_id, data, len, verify_id); else ret = verify_fs_object_json (obj_id, data, len); g_free (data); return ret; } int dir_version_from_repo_version (int repo_version) { if (repo_version == 0) return 0; else return CURRENT_DIR_OBJ_VERSION; } int seafile_version_from_repo_version (int repo_version) { if (repo_version == 0) return 0; else return CURRENT_SEAFILE_OBJ_VERSION; } int seaf_fs_manager_remove_store (SeafFSManager *mgr, const char *store_id) { return seaf_obj_store_remove_store (mgr->obj_store, store_id); } seadrive-fuse-3.0.13/src/fs-mgr.h000066400000000000000000000260611476177674700165320ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ #ifndef SEAF_FILE_MGR_H #define SEAF_FILE_MGR_H #include #include "obj-store.h" #include "cdc.h" #include "seafile-crypt.h" #define CURRENT_DIR_OBJ_VERSION 1 #define CURRENT_SEAFILE_OBJ_VERSION 1 #define CDC_AVERAGE_BLOCK_SIZE (1 << 23) /* 8MB */ #define CDC_MIN_BLOCK_SIZE (6 * (1 << 20)) /* 6MB */ #define CDC_MAX_BLOCK_SIZE (10 * (1 << 20)) /* 10MB */ typedef struct _SeafFSManager SeafFSManager; typedef struct _SeafFSObject SeafFSObject; typedef struct _Seafile Seafile; typedef struct _SeafDir SeafDir; typedef struct _SeafDirent SeafDirent; typedef enum { SEAF_METADATA_TYPE_INVALID, SEAF_METADATA_TYPE_FILE, SEAF_METADATA_TYPE_LINK, SEAF_METADATA_TYPE_DIR, } SeafMetadataType; /* Common to seafile and seafdir objects. */ struct _SeafFSObject { int type; }; struct _Seafile { SeafFSObject object; int version; char file_id[41]; guint64 file_size; guint32 n_blocks; char **blk_sha1s; int ref_count; }; void seafile_ref (Seafile *seafile); void seafile_unref (Seafile *seafile); int seafile_save (SeafFSManager *fs_mgr, const char *repo_id, int version, Seafile *file); #define SEAF_DIR_NAME_LEN 256 struct _SeafDirent { int version; guint32 mode; char id[41]; guint32 name_len; char *name; /* attributes for version > 0 */ gint64 mtime; char *modifier; /* for files only */ gint64 size; /* for files only */ }; struct _SeafDir { SeafFSObject object; int version; char dir_id[41]; GList *entries; /* data in on-disk format. */ void *ondisk; int ondisk_size; }; SeafDir * seaf_dir_new (const char *id, GList *entries, int version); void seaf_dir_free (SeafDir *dir); SeafDir * seaf_dir_from_data (const char *dir_id, uint8_t *data, int len, gboolean is_json); void * seaf_dir_to_data (SeafDir *dir, int *len); int seaf_dir_save (SeafFSManager *fs_mgr, const char *repo_id, int version, SeafDir *dir); SeafDirent * seaf_dirent_new (int version, const char *sha1, int mode, const char *name, gint64 mtime, const char *modifier, gint64 size); void seaf_dirent_free (SeafDirent *dent); SeafDirent * seaf_dirent_dup (SeafDirent *dent); int seaf_metadata_type_from_data (const char *obj_id, uint8_t *data, int len, gboolean is_json); /* Parse an fs object without knowing its type. */ SeafFSObject * seaf_fs_object_from_data (const char *obj_id, uint8_t *data, int len, gboolean is_json); void seaf_fs_object_free (SeafFSObject *obj); typedef struct { /* TODO: GHashTable may be inefficient when we have large number of IDs. */ GHashTable *block_hash; GPtrArray *block_ids; uint32_t n_blocks; uint32_t n_valid_blocks; } BlockList; BlockList * block_list_new (); void block_list_free (BlockList *bl); void block_list_insert (BlockList *bl, const char *block_id); /* Return a blocklist containing block ids which are in @bl1 but * not in @bl2. */ BlockList * block_list_difference (BlockList *bl1, BlockList *bl2); struct _SeafileSession; typedef struct _SeafFSManagerPriv SeafFSManagerPriv; struct _SeafFSManager { struct _SeafileSession *seaf; struct SeafObjStore *obj_store; SeafFSManagerPriv *priv; }; SeafFSManager * seaf_fs_manager_new (struct _SeafileSession *seaf, const char *seaf_dir); int seaf_fs_manager_init (SeafFSManager *mgr); /** * Check in blocks and create seafile object. * Returns file id for the seafile object in @file_id parameter. */ int seaf_fs_manager_index_blocks (SeafFSManager *mgr, const char *repo_id, int version, const char *file_path, SeafileCrypt *crypt, gboolean write_data, unsigned char sha1[]); Seafile * seaf_fs_manager_get_seafile (SeafFSManager *mgr, const char *repo_id, int version, const char *file_id); SeafDir * seaf_fs_manager_get_seafdir (SeafFSManager *mgr, const char *repo_id, int version, const char *dir_id); /* Make sure entries in the returned dir is sorted in descending order. */ SeafDir * seaf_fs_manager_get_seafdir_sorted (SeafFSManager *mgr, const char *repo_id, int version, const char *dir_id); SeafDir * seaf_fs_manager_get_seafdir_sorted_by_path (SeafFSManager *mgr, const char *repo_id, int version, const char *root_id, const char *path); int seaf_fs_manager_populate_blocklist (SeafFSManager *mgr, const char *repo_id, int version, const char *root_id, BlockList *bl); /* * For dir object, set *stop to TRUE to stop traversing the subtree. */ typedef gboolean (*TraverseFSTreeCallback) (SeafFSManager *mgr, const char *repo_id, int version, const char *obj_id, int type, void *user_data, gboolean *stop); int seaf_fs_manager_traverse_tree (SeafFSManager *mgr, const char *repo_id, int version, const char *root_id, TraverseFSTreeCallback callback, void *user_data, gboolean skip_errors); typedef gboolean (*TraverseFSPathCallback) (SeafFSManager *mgr, const char *path, SeafDirent *dent, void *user_data, gboolean *stop); int seaf_fs_manager_traverse_path (SeafFSManager *mgr, const char *repo_id, int version, const char *root_id, const char *dir_path, TraverseFSPathCallback callback, void *user_data); gboolean seaf_fs_manager_object_exists (SeafFSManager *mgr, const char *repo_id, int version, const char *id); void seaf_fs_manager_delete_object (SeafFSManager *mgr, const char *repo_id, int version, const char *id); gint64 seaf_fs_manager_get_file_size (SeafFSManager *mgr, const char *repo_id, int version, const char *file_id); gint64 seaf_fs_manager_get_fs_size (SeafFSManager *mgr, const char *repo_id, int version, const char *root_id); int seaf_fs_manager_count_fs_files (SeafFSManager *mgr, const char *repo_id, int version, const char *root_id); SeafDir * seaf_fs_manager_get_seafdir_by_path(SeafFSManager *mgr, const char *repo_id, int version, const char *root_id, const char *path, GError **error); char * seaf_fs_manager_get_seafile_id_by_path (SeafFSManager *mgr, const char *repo_id, int version, const char *root_id, const char *path, GError **error); char * seaf_fs_manager_path_to_obj_id (SeafFSManager *mgr, const char *repo_id, int version, const char *root_id, const char *path, guint32 *mode, GError **error); char * seaf_fs_manager_get_seafdir_id_by_path (SeafFSManager *mgr, const char *repo_id, int version, const char *root_id, const char *path, GError **error); SeafDirent * seaf_fs_manager_get_dirent_by_path (SeafFSManager *mgr, const char *repo_id, int version, const char *root_id, const char *path, GError **error); /* Check object integrity. */ gboolean seaf_fs_manager_verify_seafdir (SeafFSManager *mgr, const char *repo_id, int version, const char *dir_id, gboolean verify_id, gboolean *io_error); gboolean seaf_fs_manager_verify_seafile (SeafFSManager *mgr, const char *repo_id, int version, const char *file_id, gboolean verify_id, gboolean *io_error); gboolean seaf_fs_manager_verify_object (SeafFSManager *mgr, const char *repo_id, int version, const char *obj_id, gboolean verify_id, gboolean *io_error); int dir_version_from_repo_version (int repo_version); int seafile_version_from_repo_version (int repo_version); struct _CDCFileDescriptor; void seaf_fs_manager_calculate_seafile_id_json (int repo_version, struct _CDCFileDescriptor *cdc, guint8 *file_id_sha1); int seaf_fs_manager_remove_store (SeafFSManager *mgr, const char *store_id); #endif seadrive-fuse-3.0.13/src/fuse-ops.c000066400000000000000000001460071476177674700170760ustar00rootroot00000000000000#include "common.h" #if defined __linux__ || defined __APPLE__ #define FUSE_USE_VERSION 26 #include #ifdef __APPLE__ #include #endif #include "seafile-session.h" #include "fuse-ops.h" #define DEBUG_FLAG SEAFILE_DEBUG_FS #include "log.h" struct _FusePathComps { RepoType repo_type; RepoInfo *repo_info; char *repo_path; char *root_path; // account_info will not be set only if there are multiple accounts and it is the root directory. AccountInfo *account_info; gboolean multi_account; }; typedef struct _FusePathComps FusePathComps; /* * Input path and return result can be: * 1. root dir -> return 0, repo_type == 0 * 2. category dir -> return 0, repo_type == TYPE, repo_info == NULL, repo_path == NULL * 3. new category dir -> return -ENOENT, repo_type == 0 * 4. repo dir -> return 0, repo_type == TYPE, repo_info != NULL, repo_path == NULL * 5. new repo dir && repo_path * -> return -ENOENT, repo_type == TYPE, repo_info == NULL, repo_path == NULL * 6. new repo dir && !repo_path * -> return 0, repo_type == TYPE, repo_info == NULL, repo_path == NULL, root_path == new repo dir * 7. repo path -> return 0, repo_type == TYPE, repo_info != NULL, repo_path != NULL * * Note that case 2 and 6 are quite similar. The only difference is whether root_path is set. * If root_path is set, repo_info and repo_path are always NULL. */ static int parse_fuse_path_single_account (SeafAccount *account, const char *path, FusePathComps *path_comps) { char *path_nfc = NULL; char **tokens; int n; char *repo_name, *display_name; RepoInfo *info; int ret = 0; path_comps->repo_info = NULL; path_comps->repo_path = NULL; path_nfc = g_utf8_normalize (path, -1, G_NORMALIZE_NFC); if (!path_nfc) return -ENOENT; if (*path_nfc == '/') path = path_nfc + 1; else path = path_nfc; tokens = g_strsplit (path, "/", 3); n = g_strv_length (tokens); switch (n) { case 0: break; case 1: path_comps->repo_type = repo_type_from_string (tokens[0]); if (path_comps->repo_type == REPO_TYPE_UNKNOWN) { ret = -ENOENT; goto out; } break; case 2: path_comps->repo_type = repo_type_from_string (tokens[0]); if (path_comps->repo_type == REPO_TYPE_UNKNOWN) { ret = -ENOENT; goto out; } repo_name = tokens[1]; display_name = g_strconcat (tokens[0], "/", tokens[1], NULL); info = seaf_repo_manager_get_repo_info_by_display_name (seaf->repo_mgr, account->server, account->username, display_name); g_free (display_name); if (!info) { path_comps->root_path = g_strdup (repo_name); goto out; } path_comps->repo_info = info; break; case 3: path_comps->repo_type = repo_type_from_string (tokens[0]); if (path_comps->repo_type == REPO_TYPE_UNKNOWN) { ret = -ENOENT; goto out; } repo_name = tokens[1]; display_name = g_strconcat (tokens[0], "/", tokens[1], NULL); info = seaf_repo_manager_get_repo_info_by_display_name (seaf->repo_mgr, account->server, account->username, display_name); g_free (display_name); if (!info) { ret = -ENOENT; goto out; } path_comps->repo_info = info; path_comps->repo_path = g_strdup(tokens[2]); break; } out: g_free (path_nfc); g_strfreev (tokens); return ret; } /* * Input path and return result can be: * 1. root dir -> return 0, repo_type == 0, account_info == NULL * 2. accounts dir -> return 0, repo_type == 0, account_info != NULL * 3. category dir -> return 0, repo_type == TYPE, repo_info == NULL, repo_path == NULL * 4. new category dir -> return -ENOENT, repo_type == 0 * 5. repo dir -> return 0, repo_type == TYPE, repo_info != NULL, repo_path == NULL * 6. new repo dir && repo_path * -> return -ENOENT, repo_type == TYPE, repo_info == NULL, repo_path == NULL * 7. new repo dir && !repo_path * -> return 0, repo_type == TYPE, repo_info == NULL, repo_path == NULL, root_path == new repo dir * 8. repo path -> return 0, repo_type == TYPE, repo_info != NULL, repo_path != NULL * * Note that case 3 and 7 are quite similar. The only difference is whether root_path is set. * If root_path is set, repo_info and repo_path are always NULL. */ static int parse_fuse_path_multi_account (const char *path, FusePathComps *path_comps) { char *path_nfc = NULL; char **tokens; int n; char *repo_name, *display_name; RepoInfo *info; int ret = 0; AccountInfo *account_info = NULL; path_comps->repo_info = NULL; path_comps->repo_path = NULL; path_nfc = g_utf8_normalize (path, -1, G_NORMALIZE_NFC); if (!path_nfc) return -ENOENT; if (*path_nfc == '/') path = path_nfc + 1; else path = path_nfc; tokens = g_strsplit (path, "/", 4); n = g_strv_length (tokens); switch (n) { case 0: break; case 1: account_info = seaf_repo_manager_get_account_info_by_name (seaf->repo_mgr, tokens[0]); if (!account_info) { ret = -ENOENT; goto out; } path_comps->account_info = account_info; break; case 2: account_info = seaf_repo_manager_get_account_info_by_name (seaf->repo_mgr, tokens[0]); if (!account_info) { ret = -ENOENT; goto out; } path_comps->repo_type = repo_type_from_string (tokens[1]); if (path_comps->repo_type == REPO_TYPE_UNKNOWN) { ret = -ENOENT; goto out; } path_comps->account_info = account_info; break; case 3: account_info = seaf_repo_manager_get_account_info_by_name (seaf->repo_mgr, tokens[0]); if (!account_info) { ret = -ENOENT; goto out; } path_comps->repo_type = repo_type_from_string (tokens[1]); if (path_comps->repo_type == REPO_TYPE_UNKNOWN) { ret = -ENOENT; goto out; } path_comps->account_info = account_info; repo_name = tokens[2]; display_name = g_strconcat (tokens[1], "/", tokens[2], NULL); info = seaf_repo_manager_get_repo_info_by_display_name (seaf->repo_mgr, account_info->server, account_info->username, display_name); g_free (display_name); if (!info) { path_comps->root_path = g_strdup (repo_name); goto out; } path_comps->repo_info = info; break; case 4: account_info = seaf_repo_manager_get_account_info_by_name (seaf->repo_mgr, tokens[0]); if (!account_info) { ret = -ENOENT; goto out; } path_comps->repo_type = repo_type_from_string (tokens[1]); if (path_comps->repo_type == REPO_TYPE_UNKNOWN) { ret = -ENOENT; goto out; } repo_name = tokens[2]; display_name = g_strconcat (tokens[1], "/", tokens[2], NULL); info = seaf_repo_manager_get_repo_info_by_display_name (seaf->repo_mgr, account_info->server, account_info->username, display_name); g_free (display_name); if (!info) { ret = -ENOENT; goto out; } path_comps->account_info = account_info; path_comps->repo_info = info; path_comps->repo_path = g_strdup(tokens[3]); break; } out: if (ret != 0 && account_info) { account_info_free (account_info); } g_free (path_nfc); g_strfreev (tokens); return ret; } static int parse_fuse_path (const char *path, FusePathComps *path_comps) { int ret = 0; GList *accounts = NULL; SeafAccount *account; accounts = seaf_repo_manager_get_account_list (seaf->repo_mgr); if (!accounts) { ret = -ENOENT; goto out; } if (g_list_length (accounts) <= 1) { path_comps->multi_account = FALSE; account = accounts->data; ret = parse_fuse_path_single_account (account, path, path_comps); if (ret == 0) { AccountInfo *account_info = g_new0 (AccountInfo, 1); account_info->server = g_strdup (account->server); account_info->username = g_strdup (account->username); path_comps->account_info = account_info; } } else { path_comps->multi_account = TRUE; ret = parse_fuse_path_multi_account (path, path_comps); } out: if (accounts) g_list_free_full (accounts, (GDestroyNotify)seaf_account_free); return ret; } static void path_comps_free (FusePathComps *comps) { if (!comps) return; if (comps->root_path) { g_free (comps->root_path); } else { repo_info_free (comps->repo_info); g_free (comps->repo_path); } if (comps->account_info) account_info_free (comps->account_info); } static void notify_fs_op_error (const char *type, const char *path) { json_t *msg = json_object(); json_object_set_string_member (msg, "type", type); json_object_set_string_member (msg, "path", path); mq_mgr_push_msg (seaf->mq_mgr, SEADRIVE_EVENT_CHAN, msg); } static gint64 get_category_dir_mtime (const char *server, const char *user, RepoType type) { GList *infos, *ptr; RepoInfo *info; gint64 mtime = 0; infos = seaf_repo_manager_get_account_repos (seaf->repo_mgr, server, user); for (ptr = infos; ptr; ptr = ptr->next) { info = ptr->data; if (type != REPO_TYPE_UNKNOWN && info->type != type) continue; if (info->mtime > mtime) mtime = info->mtime; } g_list_free_full (infos, (GDestroyNotify)repo_info_free); return mtime; } int seadrive_fuse_getattr(const char *path, struct stat *stbuf) { FusePathComps comps; int ret = 0; uid_t uid; gid_t gid; SeafRepo *repo = NULL; RepoTreeStat st; seaf_debug ("getattr %s called.\n", path); if (!seaf->started) return -ENOENT; memset (stbuf, 0, sizeof(struct stat)); memset (&comps, 0, sizeof(comps)); if (parse_fuse_path (path, &comps) < 0) { return -ENOENT; } /* Set file/folder owner/group to the process's euid and egid. */ uid = geteuid(); gid = getegid(); if (comps.root_path) { g_free (comps.root_path); return -ENOENT; } if (!comps.repo_type) { /* Root or account directory */ stbuf->st_mode = S_IFDIR | 0755; stbuf->st_nlink = 2; stbuf->st_size = 4096; stbuf->st_uid = uid; stbuf->st_gid = gid; if (comps.multi_account && comps.account_info) // Multiple account directory stbuf->st_mtime = get_category_dir_mtime (comps.account_info->server, comps.account_info->username, comps.repo_type); } else if (!comps.repo_info && !comps.repo_path) { /* Category directory */ stbuf->st_mode = S_IFDIR | 0755; stbuf->st_nlink = 2; stbuf->st_size = 4096; stbuf->st_uid = uid; stbuf->st_gid = gid; stbuf->st_mtime = get_category_dir_mtime (comps.account_info->server, comps.account_info->username, comps.repo_type); } else if (!comps.repo_path) { /* Repo directory */ stbuf->st_mode = S_IFDIR | 0755; stbuf->st_nlink = 2; stbuf->st_size = 4096; stbuf->st_mtime = comps.repo_info->mtime; stbuf->st_uid = uid; stbuf->st_gid = gid; } else { repo = seaf_repo_manager_get_repo (seaf->repo_mgr, comps.repo_info->id); if (!repo || !repo->fs_ready) { ret = -ENOENT; goto out; } int rc = repo_tree_stat_path (repo->tree, comps.repo_path, &st); if (rc < 0) { ret = rc; goto out; } gboolean is_writable = (seaf_repo_manager_is_path_writable(seaf->repo_mgr, comps.repo_info->id, comps.repo_path) && !seaf_filelock_manager_is_file_locked (seaf->filelock_mgr, comps.repo_info->id, comps.repo_path)); if (S_ISDIR(st.mode)) { stbuf->st_size = 4096; if (is_writable) stbuf->st_mode = S_IFDIR | 0755; else stbuf->st_mode = S_IFDIR | 0555; } else { stbuf->st_size = st.size; if (is_writable) stbuf->st_mode = S_IFREG | 0644; else stbuf->st_mode = S_IFREG | 0444; } stbuf->st_mtime = st.mtime; stbuf->st_nlink = 1; stbuf->st_uid = uid; stbuf->st_gid = gid; } out: seaf_repo_unref (repo); path_comps_free (&comps); return ret; } static int readdir_root_accounts (void *buf, fuse_fill_dir_t filler) { GList *accounts = NULL, *ptr; SeafAccount *account; accounts = seaf_repo_manager_get_account_list (seaf->repo_mgr); if (!accounts) { return 0; } for (ptr = accounts; ptr; ptr = ptr->next) { account = ptr->data; filler (buf, account->name, NULL, 0); } g_list_free_full (accounts, (GDestroyNotify)seaf_account_free); return 0; } static int readdir_root (AccountInfo *account, void *buf, fuse_fill_dir_t filler) { GList *types = repo_type_string_list (); GList *ptr; char *type_str; #ifdef __APPLE__ char *dname_nfd; #endif for (ptr = types; ptr; ptr = ptr->next) { type_str = ptr->data; #ifdef __APPLE__ dname_nfd = g_utf8_normalize (type_str, -1, G_NORMALIZE_NFD); filler (buf, dname_nfd, NULL, 0); g_free (dname_nfd); #else filler (buf, type_str, NULL, 0); #endif } g_list_free_full (types, g_free); return 0; } static int readdir_category (AccountInfo *account, RepoType type, void *buf, fuse_fill_dir_t filler) { GList *repos = seaf_repo_manager_get_account_repos (seaf->repo_mgr, account->server, account->username); GList *ptr; RepoInfo *info; SeafRepo *repo; char *dname; #ifdef __APPLE__ char *dname_nfd; #endif for (ptr = repos; ptr; ptr = ptr->next) { info = ptr->data; if (info->type != type) continue; repo = seaf_repo_manager_get_repo (seaf->repo_mgr, info->id); if (repo && repo->encrypted && !repo->is_passwd_set) { seaf_repo_unref (repo); continue; } dname = g_path_get_basename (info->display_name); #ifdef __APPLE__ dname_nfd = g_utf8_normalize (dname, -1, G_NORMALIZE_NFD); filler (buf, dname_nfd, NULL, 0); g_free (dname_nfd); #else filler (buf, dname, NULL, 0); #endif g_free (dname); seaf_repo_unref (repo); } g_list_free_full (repos, (GDestroyNotify)repo_info_free); return 0; } static int readdir_repo (const char *repo_id, const char *path, void *buf, fuse_fill_dir_t filler) { SeafRepo *repo = NULL; GHashTable *dirents; GHashTableIter iter; gpointer key, value; char *dname; int ret = 0; #ifdef __APPLE__ char *dname_nfd; #endif repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id); if (!repo || !repo->fs_ready) { ret = -ENOENT; goto out; } if (repo->encrypted && !repo->is_passwd_set) { ret = -ENOENT; goto out; } dirents = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); int rc = repo_tree_readdir (repo->tree, path, dirents); if (rc < 0) { ret = rc; goto out; } g_hash_table_iter_init (&iter, dirents); while (g_hash_table_iter_next (&iter, &key, &value)) { dname = (char *)key; if (seaf_repo_manager_is_path_invisible (seaf->repo_mgr, repo_id, dname)) { continue; } filler (buf, dname, NULL, 0); } g_hash_table_destroy (dirents); out: seaf_repo_unref (repo); return ret; } int seadrive_fuse_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *info) { FusePathComps comps; int ret = 0; seaf_debug ("readdir %s called.\n", path); if (!seaf->started) return 0; filler(buf, ".", NULL, 0); filler(buf, "..", NULL, 0); memset (&comps, 0, sizeof(comps)); if (parse_fuse_path (path, &comps) < 0) { return -ENOENT; } if (comps.multi_account) { if (comps.root_path) { ret = -ENOENT; } else if (!comps.account_info) { /* Root directory */ ret = readdir_root_accounts (buf, filler); } else if (!comps.repo_type) { /* Account directory */ ret = readdir_root (comps.account_info, buf, filler); } else if (!comps.repo_info && !comps.repo_path) { /* Category directory */ seaf->last_access_fs_time = (gint64)time(NULL); ret = readdir_category (comps.account_info, comps.repo_type, buf, filler); } else if (!comps.repo_path) { /* Repo directory */ seaf->last_access_fs_time = (gint64)time(NULL); ret = readdir_repo (comps.repo_info->id, "", buf, filler); } else { seaf->last_access_fs_time = (gint64)time(NULL); ret = readdir_repo (comps.repo_info->id, comps.repo_path, buf, filler); } } else { if (comps.root_path) { ret = -ENOENT; } else if (!comps.repo_type) { /* Root directory */ ret = readdir_root (comps.account_info, buf, filler); } else if (!comps.repo_info && !comps.repo_path) { /* Category directory */ seaf->last_access_fs_time = (gint64)time(NULL); ret = readdir_category (comps.account_info, comps.repo_type, buf, filler); } else if (!comps.repo_path) { /* Repo directory */ seaf->last_access_fs_time = (gint64)time(NULL); ret = readdir_repo (comps.repo_info->id, "", buf, filler); } else { seaf->last_access_fs_time = (gint64)time(NULL); ret = readdir_repo (comps.repo_info->id, comps.repo_path, buf, filler); } } path_comps_free (&comps); return ret; } static int repo_mknod (const char *repo_id, const char *path, mode_t mode) { SeafRepo *repo = NULL; gint64 mtime; JournalOp *op; int ret = 0; char *office_path; repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id); if (!repo || !repo->fs_ready) { ret = -ENOENT; goto out; } if (!seaf_repo_manager_is_path_writable (seaf->repo_mgr, repo_id, path)) { ret = -EACCES; goto out; } mtime = (gint64)time(NULL); int rc = repo_tree_create_file (repo->tree, path, EMPTY_SHA1, (guint32)mode, mtime, 0); if (rc < 0) { ret = rc; goto out; } op = journal_op_new (OP_TYPE_CREATE_FILE, path, NULL, 0, mtime, mode); if (journal_append_op (repo->journal, op) < 0) { seaf_warning ("Failed to append operation to journal of repo %s.\n", repo_id); ret = -ENOMEM; journal_op_free (op); goto out; } office_path = NULL; if (repo_tree_is_office_lock_file (repo->tree, path, &office_path)) seaf_sync_manager_lock_file_on_server (seaf->sync_mgr, repo->server, repo->user, repo->id, office_path); g_free (office_path); out: seaf_repo_unref (repo); return ret; } int seadrive_fuse_mknod (const char *path, mode_t mode, dev_t dev) { FusePathComps comps; int ret = 0; seaf_debug ("mknod %s called. mode = %o.\n", path, mode); if (!seaf->started) return 0; memset (&comps, 0, sizeof(comps)); if (parse_fuse_path (path, &comps) < 0) { if (!comps.repo_type) return -EACCES; else return -ENOENT; } if (!comps.account_info) { ret = -EACCES; } else if (!comps.repo_type) { ret = -EACCES; } else if (comps.root_path) { /* Don't allow creating files in category. */ ret = -EACCES; } else if (!comps.repo_info && !comps.repo_path) { /* Category directory */ ret = -EACCES; } else if (!comps.repo_path) { /* Repo directory */ ret = -EACCES; } else { if (!S_ISREG(mode)) { ret = -EINVAL; goto out; } seaf->last_access_fs_time = (gint64)time(NULL); ret = repo_mknod (comps.repo_info->id, comps.repo_path, mode); } out: path_comps_free (&comps); return ret; } static int repo_mkdir (const char *repo_id, const char *path) { SeafRepo *repo = NULL; gint64 mtime; JournalOp *op; int ret = 0; repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id); if (!repo || !repo->fs_ready) { ret = -ENOENT; goto out; } if (!seaf_repo_manager_is_path_writable (seaf->repo_mgr, repo_id, path)) { ret = -EACCES; goto out; } mtime = (gint64)time(NULL); int rc = repo_tree_mkdir (repo->tree, path, mtime); if (rc < 0) { ret = rc; goto out; } op = journal_op_new (OP_TYPE_MKDIR, path, NULL, 0, mtime, 0); if (journal_append_op (repo->journal, op) < 0) { seaf_warning ("Failed to append operation to journal of repo %s.\n", repo_id); ret = -ENOMEM; journal_op_free (op); } file_cache_mgr_mkdir (seaf->file_cache_mgr, repo_id, path); out: seaf_repo_unref (repo); return ret; } #define TRASH_PREFIX ".Trash" int seadrive_fuse_mkdir (const char *path, mode_t mode) { FusePathComps comps; int ret = 0; seaf_debug ("mkdir %s called.\n", path); if (!seaf->started) return 0; memset (&comps, 0, sizeof(comps)); if (parse_fuse_path (path, &comps) < 0) { if (!comps.repo_type) return -EACCES; else return -ENOENT; } if (!comps.account_info) { ret = -EACCES; }else if (!comps.repo_type) { /* Root directory */ ret = -EACCES; } else if (comps.root_path) { if (strncmp (comps.root_path, TRASH_PREFIX, strlen(TRASH_PREFIX)) == 0) { // Don't create trash dir now, or will lose delete event ret = -EACCES; } else if (comps.repo_type != REPO_TYPE_MINE) { /* Don't allow creating repo under shared or group categories. */ ret = -EACCES; } else { // Make root dir to create repo seaf->last_access_fs_time = (gint64)time(NULL); ret = seaf_sync_manager_create_repo (seaf->sync_mgr, comps.account_info->server, comps.account_info->username, comps.root_path); if (ret < 0) ret = -EIO; } } else if (!comps.repo_info && !comps.repo_path) { /* Category directory */ ret = -EEXIST; } else if (!comps.repo_path) { /* Repo directory */ ret = -EEXIST; } else { seaf->last_access_fs_time = (gint64)time(NULL); ret = repo_mkdir (comps.repo_info->id, comps.repo_path); } path_comps_free (&comps); return ret; } static int repo_unlink (const char *repo_id, const char *path) { SeafRepo *repo = NULL; JournalOp *op; int ret = 0; char *office_path; repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id); if (!repo || !repo->fs_ready) { ret = -ENOENT; goto out; } if (!seaf_repo_manager_is_path_writable (seaf->repo_mgr, repo_id, path)) { ret = -EACCES; goto out; } if (seaf_filelock_manager_is_file_locked (seaf->filelock_mgr, repo_id, path)) { ret = -EACCES; goto out; } office_path = NULL; if (repo_tree_is_office_lock_file (repo->tree, path, &office_path)) seaf_sync_manager_unlock_file_on_server (seaf->sync_mgr, repo->server, repo->user, repo->id, office_path); g_free (office_path); int rc = repo_tree_unlink (repo->tree, path); if (rc < 0) { ret = rc; goto out; } op = journal_op_new (OP_TYPE_DELETE_FILE, path, NULL, 0, 0, 0); if (journal_append_op (repo->journal, op) < 0) { seaf_warning ("Failed to append operation to journal of repo %s.\n", repo_id); ret = -ENOMEM; journal_op_free (op); goto out; } file_cache_mgr_unlink (seaf->file_cache_mgr, repo_id, path); out: seaf_repo_unref (repo); return ret; } int seadrive_fuse_unlink (const char *path) { FusePathComps comps; int ret = 0; seaf_debug ("unlink %s called.\n", path); if (!seaf->started) return 0; memset (&comps, 0, sizeof(comps)); if (parse_fuse_path (path, &comps) < 0) { return -ENOENT; } if (!comps.account_info) { ret = -EACCES; } else if (!comps.repo_type) { /* Root directory */ ret = -EACCES; } else if (comps.root_path) { ret = -EACCES; } else if (!comps.repo_info && !comps.repo_path) { /* Category directory */ ret = -EACCES; } else if (!comps.repo_path) { /* Repo directory */ ret = -EACCES; } else { seaf->last_access_fs_time = (gint64)time(NULL); ret = repo_unlink (comps.repo_info->id, comps.repo_path); } if (ret == 0) { seaf_sync_manager_delete_active_path (seaf->sync_mgr, comps.repo_info->id, comps.repo_path); } path_comps_free (&comps); return ret; } static int repo_rmdir (const char *repo_id, const char *path) { SeafRepo *repo = NULL; JournalOp *op; int ret = 0; repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id); if (!repo || !repo->fs_ready) { ret = -ENOENT; goto out; } if (!seaf_repo_manager_is_path_writable (seaf->repo_mgr, repo_id, path)) { ret = -EACCES; goto out; } int rc = repo_tree_rmdir (repo->tree, path); if (rc < 0) { ret = rc; goto out; } op = journal_op_new (OP_TYPE_RMDIR, path, NULL, 0, 0, 0); if (journal_append_op (repo->journal, op) < 0) { seaf_warning ("Failed to append operation to journal of repo %s.\n", repo_id); ret = -ENOMEM; journal_op_free (op); goto out; } file_cache_mgr_rmdir (seaf->file_cache_mgr, repo_id, path); out: seaf_repo_unref (repo); return ret; } int seadrive_fuse_rmdir (const char *path) { FusePathComps comps; int ret = 0; seaf_debug ("rmdir %s called.\n", path); if (!seaf->started) return 0; memset (&comps, 0, sizeof(comps)); if (parse_fuse_path (path, &comps) < 0) { return -ENOENT; } if (!comps.account_info) { ret = -EACCES; } else if (!comps.repo_type) { ret = -EACCES; } else if (comps.root_path) { ret = -EACCES; } else if (!comps.repo_info && !comps.repo_path) { /* Root directory */ ret = -EACCES; } else if (!comps.repo_path) { /* Repo directory */ ret = seaf_repo_manager_check_delete_repo (comps.repo_info->id, comps.repo_type); if (ret < 0) goto out; ret = seaf_sync_manager_delete_repo (seaf->sync_mgr, comps.account_info->server, comps.account_info->username, comps.repo_info->id); if (ret < 0) ret = -EIO; goto out; } else { seaf->last_access_fs_time = (gint64)time(NULL); ret = repo_rmdir (comps.repo_info->id, comps.repo_path); } if (ret == 0) { seaf_sync_manager_delete_active_path (seaf->sync_mgr, comps.repo_info->id, comps.repo_path); } out: path_comps_free (&comps); return ret; } static int repo_rename (const char *repo_id, const char *oldpath, const char *newpath) { SeafRepo *repo = NULL; JournalOp *op; int ret = 0; repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id); if (!repo || !repo->fs_ready) { ret = -ENOENT; goto out; } int rc = repo_tree_rename (repo->tree, oldpath, newpath, TRUE); if (rc < 0) { ret = rc; goto out; } op = journal_op_new (OP_TYPE_RENAME, oldpath, newpath, 0, 0, 0); if (journal_append_op (repo->journal, op) < 0) { seaf_warning ("Failed to append operation to journal of repo %s.\n", repo_id); ret = -ENOMEM; journal_op_free (op); goto out; } file_cache_mgr_rename (seaf->file_cache_mgr, repo_id, oldpath, repo_id, newpath); out: seaf_repo_unref (repo); return ret; } typedef struct CrossRepoRenameData { char *server; char *user; char *repo_id1; char *oldpath; char *repo_id2; char *newpath; gboolean is_file; } CrossRepoRenameData; static void cross_repo_rename_data_free (CrossRepoRenameData *data) { if (!data) return; g_free (data->server); g_free (data->user); g_free (data->repo_id1); g_free (data->repo_id2); g_free (data->oldpath); g_free (data->newpath); g_free (data); } static void notify_cross_repo_move (const char *src_repo_id, const char *src_path, const char *dst_repo_id, const char *dst_path, gboolean is_start, gboolean failed) { json_t *msg = json_object (); char *src_repo_name = NULL, *dst_repo_name = NULL; char *srcpath = NULL, *dstpath = NULL; if (is_start) json_object_set_string_member (msg, "type", "cross-repo-move.start"); else if (!failed) json_object_set_string_member (msg, "type", "cross-repo-move.done"); else json_object_set_string_member (msg, "type", "cross-repo-move.error"); src_repo_name = seaf_repo_manager_get_repo_display_name (seaf->repo_mgr, src_repo_id); if (!src_repo_name) { goto out; } srcpath = g_strconcat (src_repo_name, "/", src_path, NULL); dst_repo_name = seaf_repo_manager_get_repo_display_name (seaf->repo_mgr, dst_repo_id); if (!dst_repo_name) { goto out; } dstpath = g_strconcat (dst_repo_name, "/", dst_path, NULL); json_object_set_string_member (msg, "srcpath", srcpath); json_object_set_string_member (msg, "dstpath", dstpath); mq_mgr_push_msg (seaf->mq_mgr, SEADRIVE_NOTIFY_CHAN, msg); out: g_free (src_repo_name); g_free (dst_repo_name); g_free (srcpath); g_free (dstpath); } static void * cross_repo_rename_thread (void *vdata) { CrossRepoRenameData *data = vdata; SeafAccount *account = seaf_repo_manager_get_account (seaf->repo_mgr, data->server, data->user); SeafRepo *repo1 = NULL, *repo2 = NULL; if (!account) { goto out; } notify_cross_repo_move (data->repo_id1, data->oldpath, data->repo_id2, data->newpath, TRUE, FALSE); if (http_tx_manager_api_move_file (seaf->http_tx_mgr, account->server, account->token, data->repo_id1, data->oldpath, data->repo_id2, data->newpath, data->is_file) < 0) { seaf_warning ("Failed to move %s/%s to %s/%s.\n", data->repo_id1, data->oldpath, data->repo_id2, data->newpath); notify_cross_repo_move (data->repo_id1, data->oldpath, data->repo_id2, data->newpath, FALSE, TRUE); goto out; } /* Move files/folders in cache. */ file_cache_mgr_rename (seaf->file_cache_mgr, data->repo_id1, data->oldpath, data->repo_id2, data->newpath); /* Trigger repo sync for both repos. */ repo1 = seaf_repo_manager_get_repo (seaf->repo_mgr, data->repo_id1); if (repo1) repo1->force_sync_pending = TRUE; seaf_repo_unref (repo1); repo2 = seaf_repo_manager_get_repo (seaf->repo_mgr, data->repo_id2); if (repo2) repo2->force_sync_pending = TRUE; seaf_repo_unref (repo2); notify_cross_repo_move (data->repo_id1, data->oldpath, data->repo_id2, data->newpath, FALSE, FALSE); out: seaf_account_free (account); cross_repo_rename_data_free (data); return NULL; } static int cross_repo_rename (const char *repo_id1, const char *oldpath, const char *repo_id2, const char *newpath) { SeafRepo *repo1 = NULL, *repo2 = NULL; RepoTreeStat st; CrossRepoRenameData *data; pthread_t tid; int rc; int ret = 0; repo1 = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id1); if (!repo1) { ret = -ENOENT; goto out; } repo2 = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id2); if (!repo2) { ret = -ENOENT; goto out; } if (repo_tree_stat_path (repo1->tree, oldpath, &st) < 0) { ret = -ENOENT; goto out; } /* TODO: support replacing file/dir by rename. */ if (repo_tree_stat_path (repo2->tree, newpath, &st) == 0) { ret = -EIO; goto out; } data = g_new0 (CrossRepoRenameData, 1); data->server = g_strdup (repo1->server); data->user = g_strdup (repo1->user); data->repo_id1 = g_strdup(repo_id1); data->oldpath = g_strdup(oldpath); data->repo_id2 = g_strdup(repo_id2); data->newpath = g_strdup(newpath); data->is_file = S_ISREG(st.mode); pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); rc = pthread_create (&tid, &attr, cross_repo_rename_thread, data); if (rc != 0) { cross_repo_rename_data_free (data); ret = -ENOMEM; } out: seaf_repo_unref (repo1); seaf_repo_unref (repo2); return ret; } int seadrive_fuse_rename (const char *oldpath, const char *newpath) { FusePathComps comps1, comps2; int ret = 0; seaf_debug ("rename %s to %s called.\n", oldpath, newpath); if (!seaf->started) return 0; memset (&comps1, 0, sizeof(comps1)); memset (&comps2, 0, sizeof(comps2)); if (parse_fuse_path (oldpath, &comps1) < 0) { ret = -ENOENT; goto out; } if (parse_fuse_path (newpath, &comps2) < 0) { if (!comps2.repo_type) ret = -EACCES; else ret = -ENOENT; goto out; } if (!comps1.account_info || !comps2.account_info) { ret = -EACCES; goto out; } if (g_strcmp0 (comps1.account_info->server, comps2.account_info->server) !=0 || g_strcmp0 (comps1.account_info->username, comps2.account_info->username) != 0) { ret = -EACCES; goto out; } if (!comps1.repo_type || !comps2.repo_type) { ret = -EACCES; goto out; } /* Category level. */ if (comps1.root_path) { ret = -ENOENT; goto out; } if (comps2.root_path) { /* Don't allow moving a folder under a repo to category level (become a new repo). */ if (comps1.repo_path) { ret = -EACCES; goto out; } /* Don't allow renaming category dir to a repo dir. */ if (!comps1.repo_info) { ret = -EACCES; goto out; } /* Don't allow moving repos from one category to another; * Don't allow renaming repos shared to me or from group. */ if ((comps1.repo_type != comps2.repo_type) || comps1.repo_type != REPO_TYPE_MINE) { ret = -EACCES; goto out; } seaf->last_access_fs_time = (gint64)time(NULL); // Rename repo ret = seaf_sync_manager_rename_repo (seaf->sync_mgr, comps1.account_info->server, comps1.account_info->username, comps1.repo_info->id, comps2.root_path); if (ret < 0) ret = -EIO; goto out; } if (!comps1.repo_info && !comps1.repo_path) { /* Category directory */ ret = -EACCES; goto out; } if (!comps2.repo_info && !comps2.repo_path) { /* Category directory */ ret = -EACCES; goto out; } /* Repo level. */ if (!comps1.repo_path && !comps2.repo_path) { /* Renaming one repo to another. */ ret = -EEXIST; goto out; } if ((comps1.repo_path && !comps2.repo_path) || (!comps1.repo_path && comps2.repo_path)) { /* Moving repo to sub-folder, or vice versa. */ ret = -EACCES; goto out; } if (!seaf_repo_manager_is_path_writable (seaf->repo_mgr, comps1.repo_info->id, comps1.repo_path) || !seaf_repo_manager_is_path_writable (seaf->repo_mgr, comps2.repo_info->id, comps2.repo_path)) { ret = -EACCES; goto out; } if (seaf_filelock_manager_is_file_locked (seaf->filelock_mgr, comps1.repo_info->id, comps1.repo_path) || seaf_filelock_manager_is_file_locked (seaf->filelock_mgr, comps2.repo_info->id, comps2.repo_path)) { ret = -EACCES; goto out; } seaf->last_access_fs_time = (gint64)time(NULL); if (strcmp (comps1.repo_info->id, comps2.repo_info->id) != 0) { ret = cross_repo_rename (comps1.repo_info->id, comps1.repo_path, comps2.repo_info->id, comps2.repo_path); } else { ret = repo_rename (comps1.repo_info->id, comps1.repo_path, comps2.repo_path); if (ret == 0) { seaf_sync_manager_delete_active_path (seaf->sync_mgr, comps1.repo_info->id, comps1.repo_path); } } out: path_comps_free (&comps1); path_comps_free (&comps2); return ret; } int seadrive_fuse_open(const char *path, struct fuse_file_info *info) { FusePathComps comps; CachedFileHandle *handle; int ret = 0; seaf_debug ("open %s called.\n", path); memset (&comps, 0, sizeof(comps)); if (parse_fuse_path (path, &comps) < 0) { return -ENOENT; } if (!comps.account_info) { ret = -EINVAL; goto out; } else if (!comps.repo_type) { ret = -EINVAL; goto out; } else if (comps.root_path) { ret = -EINVAL; goto out; } else if (!comps.repo_info && !comps.repo_path) { /* Root directory */ ret = -EINVAL; goto out; } else if (!comps.repo_path) { /* Repo directory */ ret = -EINVAL; goto out; } if (((info->flags & O_WRONLY) || (info->flags & O_RDWR)) && (!seaf_repo_manager_is_path_writable (seaf->repo_mgr, comps.repo_info->id, comps.repo_path) || seaf_filelock_manager_is_file_locked (seaf->filelock_mgr, comps.repo_info->id, comps.repo_path))) { ret = -EACCES; goto out; } seaf->last_access_fs_time = (gint64)time(NULL); handle = file_cache_mgr_open (seaf->file_cache_mgr, comps.repo_info->id, comps.repo_path, info->flags); if (!handle) { ret = -EIO; goto out; } info->fh = (uint64_t)handle; out: path_comps_free (&comps); return ret; } int seadrive_fuse_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *info) { seaf_debug ("read %s called. size = %"G_GINT64_FORMAT", offset = %"G_GINT64_FORMAT"\n", path, (gint64)size, (gint64)offset); if (!info->fh) { return -EIO; } CachedFileHandle *handle = (CachedFileHandle *)info->fh; seaf->last_access_fs_time = (gint64)time(NULL); char *repo_id = NULL, *file_path = NULL; file_cache_mgr_get_file_info_from_handle (seaf->file_cache_mgr, handle, &repo_id, &file_path); int ret = 0; ret = file_cache_mgr_read_by_path (seaf->file_cache_mgr, repo_id, file_path, buf, size, offset); if (ret < 0) { seaf_warning ("Failed to read %s/%s: %s.\n", repo_id, file_path, strerror(-ret)); } g_free (repo_id); g_free (file_path); return ret; } int seadrive_fuse_write(const char *path, const char *buf, size_t size, off_t offset, struct fuse_file_info *info) { int ret = 0; CachedFileHandle *handle; FileCacheStat st; char *repo_id = NULL, *file_path = NULL; SeafRepo *repo = NULL; JournalOp *op; RepoTreeStat tree_st; seaf_debug ("write %s called. size = %"G_GINT64_FORMAT", offset = %"G_GINT64_FORMAT"\n", path, size, offset); if (!info->fh) { return -EIO; } handle = (CachedFileHandle *)info->fh; seaf->last_access_fs_time = (gint64)time(NULL); ret = file_cache_mgr_write (seaf->file_cache_mgr, handle, buf, size, offset); if (ret < 0) return ret; if (file_cache_mgr_stat_handle (seaf->file_cache_mgr, handle, &st) < 0) { return -EIO; } file_cache_mgr_get_file_info_from_handle (seaf->file_cache_mgr, handle, &repo_id, &file_path); repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id); if (!repo) { seaf_warning ("Failed to get repo %s.\n", repo_id); ret = -EIO; goto out; } repo_tree_set_file_mtime (repo->tree, file_path, st.mtime); repo_tree_set_file_size (repo->tree, file_path, st.size); if (repo_tree_stat_path (repo->tree, file_path, &tree_st) < 0) { ret = -EIO; goto out; } op = journal_op_new (OP_TYPE_UPDATE_FILE, file_path, NULL, st.size, st.mtime, tree_st.mode); if (journal_append_op (repo->journal, op) < 0) { seaf_warning ("Failed to append op to journal of repo %s.\n", repo->id); ret = -ENOMEM; journal_op_free (op); } out: g_free (repo_id); g_free (file_path); seaf_repo_unref (repo); return ret; } int seadrive_fuse_release (const char *path, struct fuse_file_info *info) { CachedFileHandle *handle = (CachedFileHandle *)info->fh; seaf_debug ("release %s called.\n", path); if (!handle) return 0; file_cache_mgr_close_file_handle (handle); return 0; } int seadrive_fuse_truncate (const char *path, off_t length) { FusePathComps comps; char *repo_id, *file_path; FileCacheStat st; SeafRepo *repo = NULL; JournalOp *op; RepoTreeStat tree_st; int ret = 0; seaf_debug ("truncate %s called, len = %"G_GINT64_FORMAT".\n", path, length); memset (&comps, 0, sizeof(comps)); if (parse_fuse_path (path, &comps) < 0) { return -ENOENT; } if (!comps.account_info) { ret = -EINVAL; goto out; } else if (!comps.repo_type) { ret = -EINVAL; goto out; } else if (comps.root_path) { ret = -EINVAL; goto out; } else if (!comps.repo_info && !comps.repo_path) { /* Root directory */ ret = -EINVAL; goto out; } else if (!comps.repo_path) { /* Repo directory */ ret = -EINVAL; goto out; } if (!seaf_repo_manager_is_path_writable (seaf->repo_mgr, comps.repo_info->id, comps.repo_path) || seaf_filelock_manager_is_file_locked (seaf->filelock_mgr, comps.repo_info->id, comps.repo_path)) { ret = -EACCES; goto out; } seaf->last_access_fs_time = (gint64)time(NULL); repo_id = comps.repo_info->id; file_path = comps.repo_path; gboolean not_cached = FALSE; ret = file_cache_mgr_truncate (seaf->file_cache_mgr, repo_id, file_path, length, ¬_cached); if (ret < 0) { goto out; } if (not_cached) { st.mtime = (gint64)time(NULL); st.size = 0; } else { if (file_cache_mgr_stat (seaf->file_cache_mgr, repo_id, file_path, &st) < 0) { ret = -EIO; goto out; } } repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id); if (!repo) { seaf_warning ("Failed to get repo %s.\n", repo_id); ret = -EIO; goto out; } repo_tree_set_file_mtime (repo->tree, file_path, st.mtime); repo_tree_set_file_size (repo->tree, file_path, st.size); if (repo_tree_stat_path (repo->tree, file_path, &tree_st) < 0) { ret = -EIO; goto out; } op = journal_op_new (OP_TYPE_UPDATE_FILE, file_path, NULL, st.size, st.mtime, tree_st.mode); if (journal_append_op (repo->journal, op) < 0) { seaf_warning ("Failed to append op to journal of repo %s.\n", repo->id); ret = -ENOMEM; journal_op_free (op); } out: path_comps_free (&comps); seaf_repo_unref (repo); return ret; } int seadrive_fuse_statfs (const char *path, struct statvfs *buf) { SeafAccountSpace *space = NULL; gint64 total, used; GList *accounts = NULL, *ptr; SeafAccount *account; accounts = seaf_repo_manager_get_account_list (seaf->repo_mgr); if (!accounts) { total = 0; used = 0; } for (ptr = accounts; ptr; ptr = ptr->next) { account = ptr->data; space = seaf_repo_manager_get_account_space (seaf->repo_mgr, account->server, account->username); total += space->total; used += space->used; } if (accounts) g_list_free_full (accounts, (GDestroyNotify)seaf_account_free); buf->f_namemax = 255; buf->f_bsize = 4096; /* * df seems to use f_bsize instead of f_frsize, so make them * the same */ buf->f_frsize = buf->f_bsize; buf->f_blocks = total / buf->f_frsize; buf->f_bfree = buf->f_bavail = (total - used) / buf->f_frsize; buf->f_files = buf->f_ffree = 1000000000; g_free (space); return 0; } int seadrive_fuse_chmod (const char *path, mode_t mode) { FusePathComps comps; seaf_debug ("chmod %s called. mode = %o.\n", path, mode); memset (&comps, 0, sizeof(comps)); if (parse_fuse_path (path, &comps) < 0) { return -ENOENT; } return 0; } int seadrive_fuse_utimens (const char *path, const struct timespec tv[2]) { RepoTreeStat tree_st; char *repo_id = NULL, *file_path = NULL; SeafRepo *repo = NULL; JournalOp *op = NULL; int ret = 0; FusePathComps comps; time_t atime = tv[0].tv_sec; time_t mtime = tv[1].tv_sec; seaf_debug ("utimens %s called. mtime = %"G_GINT64_FORMAT"\n", path, mtime); memset (&comps, 0, sizeof(comps)); if (parse_fuse_path (path, &comps) < 0) { return -ENOENT; } if (!comps.account_info) { goto out; } if (comps.root_path != NULL || !comps.repo_type || !comps.repo_info || !comps.repo_path) goto out; repo_id = comps.repo_info->id; file_path = comps.repo_path; repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id); if (!repo) { seaf_warning ("Failed to get repo %s.\n", repo_id); ret = -ENOENT; goto out; } ret = file_cache_mgr_utimen (seaf->file_cache_mgr, repo_id, file_path, mtime, atime); if (ret < 0) goto out; if (repo_tree_stat_path (repo->tree, file_path, &tree_st) < 0) { ret = -ENOENT; goto out; } if (tree_st.mtime == mtime) goto out; repo_tree_set_file_mtime (repo->tree, file_path, mtime); op = journal_op_new (OP_TYPE_UPDATE_ATTR, file_path, NULL, tree_st.size, mtime, tree_st.mode); if (journal_append_op (repo->journal, op) < 0) { seaf_warning ("Failed to append op to journal of repo %s.\n", repo->id); ret = -ENOMEM; journal_op_free (op); } out: path_comps_free (&comps); seaf_repo_unref (repo); return ret; } int seadrive_fuse_symlink (const char *from, const char *to) { FusePathComps comps_from, comps_to; seaf_debug ("symlink %s %s called.\n", from, to); memset (&comps_from, 0, sizeof(comps_from)); memset (&comps_to, 0, sizeof(comps_to)); if (parse_fuse_path (from, &comps_from) < 0) { return -ENOENT; } if (parse_fuse_path (to, &comps_to) < 0) { return -ENOENT; } return 0; } int seadrive_fuse_setxattr (const char *path, const char *name, const char *value, size_t size, int flags) { FusePathComps comps; seaf_debug ("setxattr: %s %s\n", path, name); memset (&comps, 0, sizeof(comps)); if (parse_fuse_path (path, &comps) < 0) { return -ENOENT; } return 0; } #ifdef __APPLE__ int seadrive_fuse_getxattr (const char *path, const char *name, char *value, size_t size, uint32_t position) #else int seadrive_fuse_getxattr (const char *path, const char *name, char *value, size_t size) #endif { FusePathComps comps; seaf_debug ("getxattr: %s %s\n", path, name); memset (&comps, 0, sizeof(comps)); if (parse_fuse_path (path, &comps) < 0) { return -ENOENT; } return -ENODATA; } int seadrive_fuse_listxattr (const char *path, char *list, size_t size) { seaf_debug ("listxattr: %s\n", path); return 0; } int seadrive_fuse_removexattr (const char *path, const char *name) { seaf_debug ("removexattr: %s %s\n", path, name); return 0; } #endif // __linux__ seadrive-fuse-3.0.13/src/fuse-ops.h000066400000000000000000000042261476177674700170770ustar00rootroot00000000000000#ifndef SEADRIVE_FUSE_OPS_H #define SEADRIVE_FUSE_OPS_H #if defined __linux__ || defined __APPLE__ #include int seadrive_fuse_getattr(const char *path, struct stat *stbuf); int seadrive_fuse_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *info); int seadrive_fuse_mknod (const char *path, mode_t mode, dev_t dev); int seadrive_fuse_mkdir (const char *path, mode_t mode); int seadrive_fuse_unlink (const char *path); int seadrive_fuse_rmdir (const char *path); int seadrive_fuse_rename (const char *oldpath, const char *newpath); int seadrive_fuse_open (const char *path, struct fuse_file_info *info); int seadrive_fuse_read (const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *info); int seadrive_fuse_write (const char *path, const char *buf, size_t size, off_t offset, struct fuse_file_info *info); int seadrive_fuse_release (const char *path, struct fuse_file_info *fi); int seadrive_fuse_truncate (const char *path, off_t length); int seadrive_fuse_statfs (const char *path, struct statvfs *buf); int seadrive_fuse_chmod (const char *path, mode_t mode); int seadrive_fuse_utimens (const char *, const struct timespec tv[2]); int seadrive_fuse_symlink (const char *from, const char *to); int seadrive_fuse_link (const char *from, const char *to); #ifdef __APPLE__ int seadrive_fuse_setxattr (const char *path, const char *name, const char *value, size_t size, int flags, uint32_t position); #else int seadrive_fuse_setxattr (const char *path, const char *name, const char *value, size_t size, int flags); #endif #ifdef __APPLE__ int seadrive_fuse_getxattr (const char *path, const char *name, char *value, size_t size, uint32_t position); #else int seadrive_fuse_getxattr (const char *path, const char *name, char *value, size_t size); #endif int seadrive_fuse_listxattr (const char *path, char *list, size_t size); int seadrive_fuse_removexattr (const char *path, const char *name); #endif // __linux__ #endif seadrive-fuse-3.0.13/src/http-tx-mgr.c000066400000000000000000005630261476177674700175340ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ #include "common.h" #include #include #include #ifndef USE_GPL_CRYPTO #include #include #include #include #endif #include "seafile-config.h" #include "seafile-session.h" #include "http-tx-mgr.h" #include "seafile-error.h" #include "utils.h" #include "diff-simple.h" #include "job-mgr.h" #include "timer.h" #define DEBUG_FLAG SEAFILE_DEBUG_TRANSFER #include "log.h" #define HTTP_OK 200 #define HTTP_RES_PARTIAL 206 #define HTTP_MOVED_PERMANENTLY 301 #define HTTP_BAD_REQUEST 400 #define HTTP_UNAUTHORIZED 401 #define HTTP_FORBIDDEN 403 #define HTTP_NOT_FOUND 404 #define HTTP_REQUEST_TIME_OUT 408 #define HTTP_NO_QUOTA 443 #define HTTP_REPO_DELETED 444 #define HTTP_REPO_CORRUPTED 445 #define HTTP_BLOCK_MISSING 446 #define HTTP_REPO_TOO_LARGE 447 #define HTTP_INTERNAL_SERVER_ERROR 500 #define RESET_BYTES_INTERVAL_MSEC 1000 #define CLEAR_POOL_ERR_CNT 3 #ifndef SEAFILE_CLIENT_VERSION #define SEAFILE_CLIENT_VERSION PACKAGE_VERSION #endif #ifdef __APPLE__ #define USER_AGENT_OS "Apple OS X" #endif #ifdef __linux__ #define USER_AGENT_OS "Linux" #endif struct _Connection { CURL *curl; gint64 ctime; /* Used to clean up unused connection. */ gboolean release; /* If TRUE, the connection will be released. */ }; typedef struct _Connection Connection; struct _ConnectionPool { char *host; GQueue *queue; pthread_mutex_t lock; int err_cnt; }; typedef struct _ConnectionPool ConnectionPool; struct _HttpTxPriv { GHashTable *download_tasks; GHashTable *upload_tasks; GHashTable *connection_pools; /* host -> connection pool */ pthread_mutex_t pools_lock; SeafTimer *reset_bytes_timer; char *env_ca_bundle_path; // Uploaded file count gint uploaded; // Upload failed count gint upload_err; // Total file count need to upload gint total_upload; // Uploading files: file_path <-> FileUploadProgress GHashTable *uploading_files; // Uploaded files GQueue *uploaded_files; pthread_mutex_t progress_lock; /* Regex to parse error message returned by update-branch. */ GRegex *locked_error_regex; GRegex *folder_perm_error_regex; GRegex *too_many_files_error_regex; }; typedef struct _HttpTxPriv HttpTxPriv; /* Http Tx Task */ static HttpTxTask * http_tx_task_new (HttpTxManager *mgr, const char *repo_id, int repo_version, int type, gboolean is_clone, const char *host, const char *token) { HttpTxTask *task = g_new0 (HttpTxTask, 1); task->manager = mgr; memcpy (task->repo_id, repo_id, 36); task->repo_version = repo_version; task->type = type; task->is_clone = is_clone; task->host = g_strdup(host); task->token = g_strdup(token); return task; } static void http_tx_task_free (HttpTxTask *task) { if (task->repo_uname) g_free (task->repo_uname); g_free (task->unsyncable_path); g_free (task->host); g_free (task->token); g_free (task->server); g_free (task->user); g_free (task); } static const char *http_task_state_str[] = { "normal", "canceled", "finished", "error", }; static const char *http_task_rt_state_str[] = { "init", "check", "commit", "fs", "data", "update-branch", "finished", }; static const char *http_task_error_strs[] = { "Successful", "Permission denied on server", "Network error", "Cannot resolve proxy address", "Cannot resolve server address", "Cannot connect to server", "Failed to establish secure connection", "Data transfer was interrupted", "Data transfer timed out", "Unhandled http redirect from server", "Server error", "Bad request", "Internal data corrupt on the client", "Not enough memory", "Failed to write data on the client", "Storage quota full", "Files are locked by other application", "Library deleted on server", "Library damaged on server", "Unknown error", }; /* Http connection and connection pool. */ static Connection * connection_new () { Connection *conn = g_new0 (Connection, 1); conn->curl = curl_easy_init(); conn->ctime = (gint64)time(NULL); return conn; } static void connection_free (Connection *conn) { curl_easy_cleanup (conn->curl); g_free (conn); } static ConnectionPool * connection_pool_new (const char *host) { ConnectionPool *pool = g_new0 (ConnectionPool, 1); pool->host = g_strdup(host); pool->queue = g_queue_new (); pthread_mutex_init (&pool->lock, NULL); return pool; } static ConnectionPool * find_connection_pool (HttpTxPriv *priv, const char *host) { ConnectionPool *pool; pthread_mutex_lock (&priv->pools_lock); pool = g_hash_table_lookup (priv->connection_pools, host); if (!pool) { pool = connection_pool_new (host); g_hash_table_insert (priv->connection_pools, g_strdup(host), pool); } pthread_mutex_unlock (&priv->pools_lock); return pool; } static Connection * connection_pool_get_connection (ConnectionPool *pool) { Connection *conn = NULL; pthread_mutex_lock (&pool->lock); conn = g_queue_pop_head (pool->queue); if (!conn) { conn = connection_new (); } pthread_mutex_unlock (&pool->lock); return conn; } static void connection_pool_clear (ConnectionPool *pool) { Connection *conn = NULL; while (1) { conn = g_queue_pop_head (pool->queue); if (!conn) break; connection_free (conn); } } static void connection_pool_return_connection (ConnectionPool *pool, Connection *conn) { if (!conn) return; if (conn->release) { connection_free (conn); pthread_mutex_lock (&pool->lock); if (++pool->err_cnt >= CLEAR_POOL_ERR_CNT) { connection_pool_clear (pool); } pthread_mutex_unlock (&pool->lock); return; } curl_easy_reset (conn->curl); /* Reset error count when one connection succeeded. */ pthread_mutex_lock (&pool->lock); pool->err_cnt = 0; g_queue_push_tail (pool->queue, conn); pthread_mutex_unlock (&pool->lock); } #define LOCKED_ERROR_PATTERN "File (.+) is locked" #define FOLDER_PERM_ERROR_PATTERN "Update to path (.+) is not allowed by folder permission settings" #define TOO_MANY_FILES_ERROR_PATTERN "Too many files in library" HttpTxManager * http_tx_manager_new (struct _SeafileSession *seaf) { HttpTxManager *mgr = g_new0 (HttpTxManager, 1); HttpTxPriv *priv = g_new0 (HttpTxPriv, 1); const char *env_ca_path = NULL; mgr->seaf = seaf; priv->download_tasks = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)http_tx_task_free); priv->upload_tasks = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)http_tx_task_free); priv->connection_pools = g_hash_table_new (g_str_hash, g_str_equal); pthread_mutex_init (&priv->pools_lock, NULL); env_ca_path = g_getenv("SEAFILE_SSL_CA_PATH"); if (env_ca_path) priv->env_ca_bundle_path = g_strdup (env_ca_path); priv->uploading_files = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); priv->uploaded_files = g_queue_new (); pthread_mutex_init (&priv->progress_lock, NULL); GError *error = NULL; priv->locked_error_regex = g_regex_new (LOCKED_ERROR_PATTERN, 0, 0, &error); if (error) { seaf_warning ("Failed to create regex '%s': %s\n", LOCKED_ERROR_PATTERN, error->message); g_clear_error (&error); } priv->folder_perm_error_regex = g_regex_new (FOLDER_PERM_ERROR_PATTERN, 0, 0, &error); if (error) { seaf_warning ("Failed to create regex '%s': %s\n", FOLDER_PERM_ERROR_PATTERN, error->message); g_clear_error (&error); } priv->too_many_files_error_regex = g_regex_new (TOO_MANY_FILES_ERROR_PATTERN, 0, 0, &error); if (error) { seaf_warning ("Failed to create regex '%s': %s\n", TOO_MANY_FILES_ERROR_PATTERN, error->message); g_clear_error (&error); } mgr->priv = priv; return mgr; } static int reset_bytes (void *vdata) { HttpTxManager *mgr = vdata; HttpTxPriv *priv = mgr->priv; GHashTableIter iter; gpointer key, value; HttpTxTask *task; g_hash_table_iter_init (&iter, priv->download_tasks); while (g_hash_table_iter_next (&iter, &key, &value)) { task = value; task->last_tx_bytes = g_atomic_int_get (&task->tx_bytes); g_atomic_int_set (&task->tx_bytes, 0); } g_hash_table_iter_init (&iter, priv->upload_tasks); while (g_hash_table_iter_next (&iter, &key, &value)) { task = value; task->last_tx_bytes = g_atomic_int_get (&task->tx_bytes); g_atomic_int_set (&task->tx_bytes, 0); } return 1; } /* static void * */ /* save_uploaded_file_list_worker (void *data); */ /* static void */ /* load_uploaded_file_list (HttpTxManager *mgr); */ int http_tx_manager_start (HttpTxManager *mgr) { /* TODO: add a timer to clean up unused Http connections. */ mgr->priv->reset_bytes_timer = seaf_timer_new (reset_bytes, mgr, RESET_BYTES_INTERVAL_MSEC); /* load_uploaded_file_list (mgr); */ /* pthread_t tid; */ /* int rc; */ /* rc = pthread_create (&tid, NULL, save_uploaded_file_list_worker, mgr); */ /* if (rc != 0) { */ /* seaf_warning ("Failed to create save uploaded files worker thread.\n"); */ /* } */ return 0; } /* Common Utility Functions. */ #ifndef USE_GPL_CRYPTO char *ca_paths[] = { "/etc/ssl/certs/ca-certificates.crt", "/etc/ssl/certs/ca-bundle.crt", "/etc/pki/tls/certs/ca-bundle.crt", "/usr/share/ssl/certs/ca-bundle.crt", "/usr/local/share/certs/ca-root-nss.crt", "/etc/ssl/cert.pem", }; static void load_ca_bundle(CURL *curl) { const char *env_ca_path = seaf->http_tx_mgr->priv->env_ca_bundle_path; int i; const char *ca_path; gboolean found = FALSE; for (i = 0; i < sizeof(ca_paths) / sizeof(ca_paths[0]); i++) { ca_path = ca_paths[i]; if (seaf_util_exists (ca_path)) { found = TRUE; break; } } if (env_ca_path) { if (seaf_util_exists (env_ca_path)) { curl_easy_setopt (curl, CURLOPT_CAINFO, env_ca_path); return; } } if (found) curl_easy_setopt (curl, CURLOPT_CAINFO, ca_path); } #endif /* USE_GPL_CRYPTO */ static void set_proxy (CURL *curl, gboolean is_https) { /* Disable proxy if proxy options are not set properly. */ if (!seaf->use_http_proxy || !seaf->http_proxy_type || !seaf->http_proxy_addr) { curl_easy_setopt (curl, CURLOPT_PROXY, NULL); return; } if (g_strcmp0(seaf->http_proxy_type, PROXY_TYPE_HTTP) == 0) { curl_easy_setopt(curl, CURLOPT_PROXYTYPE, CURLPROXY_HTTP); /* Use CONNECT method create a SSL tunnel if https is used. */ if (is_https) curl_easy_setopt(curl, CURLOPT_HTTPPROXYTUNNEL, 1L); curl_easy_setopt(curl, CURLOPT_PROXY, seaf->http_proxy_addr); curl_easy_setopt(curl, CURLOPT_PROXYPORT, seaf->http_proxy_port > 0 ? seaf->http_proxy_port : 80); if (seaf->http_proxy_username && seaf->http_proxy_password) { curl_easy_setopt(curl, CURLOPT_PROXYAUTH, CURLAUTH_BASIC | CURLAUTH_DIGEST | CURLAUTH_DIGEST_IE | CURLAUTH_GSSNEGOTIATE | CURLAUTH_NTLM); curl_easy_setopt(curl, CURLOPT_PROXYUSERNAME, seaf->http_proxy_username); curl_easy_setopt(curl, CURLOPT_PROXYPASSWORD, seaf->http_proxy_password); } } else if (g_strcmp0(seaf->http_proxy_type, PROXY_TYPE_SOCKS) == 0) { if (seaf->http_proxy_port < 0) return; curl_easy_setopt(curl, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5_HOSTNAME); curl_easy_setopt(curl, CURLOPT_PROXY, seaf->http_proxy_addr); curl_easy_setopt(curl, CURLOPT_PROXYPORT, seaf->http_proxy_port); if (seaf->http_proxy_username && g_strcmp0 (seaf->http_proxy_username, "") != 0) curl_easy_setopt(curl, CURLOPT_PROXYUSERNAME, seaf->http_proxy_username); if (seaf->http_proxy_password && g_strcmp0 (seaf->http_proxy_password, "") != 0) curl_easy_setopt(curl, CURLOPT_PROXYPASSWORD, seaf->http_proxy_password); } } typedef struct _HttpResponse { char *content; size_t size; } HttpResponse; static size_t recv_response (void *contents, size_t size, size_t nmemb, void *userp) { size_t realsize = size * nmemb; HttpResponse *rsp = userp; rsp->content = g_realloc (rsp->content, rsp->size + realsize); if (!rsp->content) { seaf_warning ("Not enough memory.\n"); /* return a value other than realsize to signify an error. */ return 0; } memcpy (rsp->content + rsp->size, contents, realsize); rsp->size += realsize; return realsize; } /* 5 minutes timeout for background requests. */ #define HTTP_TIMEOUT_SEC 300 /* 5 seconds timeout for foreground requests that can impact UI response time. */ #define REPO_OPER_TIMEOUT 5 /* * The @timeout parameter is for detecting network connection problems. * The @timeout parameter should be set to TRUE for data-transfer-only operations, * such as getting objects, blocks. For operations that requires calculations * on the server side, the timeout should be set to FALSE. Otherwise when * the server sometimes takes more than 45 seconds to calculate the result, * the client will time out. */ static int http_get_common (CURL *curl, const char *url, int *rsp_status, char **rsp_content, gint64 *rsp_size, HttpRecvCallback callback, void *cb_data, gboolean timeout, int timeout_sec, int *pcurl_error) { int ret = 0; curl_easy_setopt(curl, CURLOPT_URL, url); curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L); if (seafile_debug_flag_is_set (SEAFILE_DEBUG_CURL)) { curl_easy_setopt (curl, CURLOPT_VERBOSE, 1); curl_easy_setopt (curl, CURLOPT_STDERR, seafile_get_logfp()); } if (timeout) { /* Set low speed limit to 1 bytes. This effectively means no data. */ curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 1); curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, timeout_sec); } #ifndef USE_GPL_CRYPTO if (seaf->disable_verify_certificate) { curl_easy_setopt (curl, CURLOPT_SSL_VERIFYPEER, 0L); curl_easy_setopt (curl, CURLOPT_SSL_VERIFYHOST, 0L); } #endif HttpResponse rsp; memset (&rsp, 0, sizeof(rsp)); if (rsp_content) { curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, recv_response); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &rsp); } else if (callback) { curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, callback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, cb_data); } gboolean is_https = (strncasecmp(url, "https", strlen("https")) == 0); set_proxy (curl, is_https); #ifndef USE_GPL_CRYPTO load_ca_bundle (curl); #endif int rc = curl_easy_perform (curl); if (rc != 0) { if (rc != CURLE_WRITE_ERROR) seaf_warning ("libcurl failed to GET %s: %s.\n", url, curl_easy_strerror(rc)); if (pcurl_error) *pcurl_error = rc; ret = -1; goto out; } long status; rc = curl_easy_getinfo (curl, CURLINFO_RESPONSE_CODE, &status); if (rc != CURLE_OK) { seaf_warning ("Failed to get status code for GET %s.\n", url); ret = -1; goto out; } *rsp_status = status; if (rsp_content) { *rsp_content = rsp.content; *rsp_size = rsp.size; } out: if (ret < 0) g_free (rsp.content); return ret; } static int http_get (CURL *curl, const char *url, const char *token, int *rsp_status, char **rsp_content, gint64 *rsp_size, HttpRecvCallback callback, void *cb_data, gboolean timeout, int timeout_sec, int *pcurl_error) { char *token_header; struct curl_slist *headers = NULL; int ret; headers = curl_slist_append (headers, "User-Agent: SeaDrive/"SEAFILE_CLIENT_VERSION" ("USER_AGENT_OS")"); if (token) { token_header = g_strdup_printf ("Seafile-Repo-Token: %s", token); headers = curl_slist_append (headers, token_header); g_free (token_header); token_header = g_strdup_printf ("Authorization: Token %s", token); headers = curl_slist_append (headers, token_header); g_free (token_header); } curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); ret = http_get_common (curl, url, rsp_status, rsp_content, rsp_size, callback, cb_data, timeout, timeout_sec, pcurl_error); curl_slist_free_all (headers); return ret; } static int http_get_range (CURL *curl, const char *url, const char *token, int *rsp_status, char **rsp_content, gint64 *rsp_size, HttpRecvCallback callback, void *cb_data, gboolean timeout, int timeout_sec, guint64 start, guint64 end) { char *token_header, *range_header; struct curl_slist *headers = NULL; int ret; headers = curl_slist_append (headers, "User-Agent: SeaDrive/"SEAFILE_CLIENT_VERSION" ("USER_AGENT_OS")"); if (token) { token_header = g_strdup_printf ("Seafile-Repo-Token: %s", token); headers = curl_slist_append (headers, token_header); g_free (token_header); token_header = g_strdup_printf ("Authorization: Token %s", token); headers = curl_slist_append (headers, token_header); g_free (token_header); } range_header = g_strdup_printf ("Range: bytes=%"G_GUINT64_FORMAT"-%"G_GUINT64_FORMAT, start, end); headers = curl_slist_append (headers, range_header); g_free (range_header); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); ret = http_get_common (curl, url, rsp_status, rsp_content, rsp_size, callback, cb_data, timeout, timeout_sec, NULL); curl_slist_free_all (headers); return ret; } static int http_api_get (CURL *curl, const char *url, const char *token, int *rsp_status, char **rsp_content, gint64 *rsp_size, HttpRecvCallback callback, void *cb_data, gboolean timeout, int timeout_sec, int *pcurl_error) { char *token_header; struct curl_slist *headers = NULL; int ret; headers = curl_slist_append (headers, "User-Agent: SeaDrive/"SEAFILE_CLIENT_VERSION" ("USER_AGENT_OS")"); if (token) { token_header = g_strdup_printf ("Authorization: Token %s", token); headers = curl_slist_append (headers, token_header); g_free (token_header); } curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); ret = http_get_common (curl, url, rsp_status, rsp_content, rsp_size, callback, cb_data, timeout, timeout_sec, pcurl_error); curl_slist_free_all (headers); return ret; } typedef struct _HttpRequest { const char *content; size_t size; } HttpRequest; static size_t send_request (void *ptr, size_t size, size_t nmemb, void *userp) { size_t realsize = size *nmemb; size_t copy_size; HttpRequest *req = userp; if (req->size == 0) return 0; copy_size = MIN(req->size, realsize); memcpy (ptr, req->content, copy_size); req->size -= copy_size; req->content = req->content + copy_size; return copy_size; } typedef size_t (*HttpSendCallback) (void *, size_t, size_t, void *); static int http_put (CURL *curl, const char *url, const char *token, const char *req_content, gint64 req_size, HttpSendCallback callback, void *cb_data, int *rsp_status, char **rsp_content, gint64 *rsp_size, gboolean timeout, int *pcurl_error) { char *token_header; struct curl_slist *headers = NULL; int ret = 0; if (seafile_debug_flag_is_set (SEAFILE_DEBUG_CURL)) { curl_easy_setopt (curl, CURLOPT_VERBOSE, 1); curl_easy_setopt (curl, CURLOPT_STDERR, seafile_get_logfp()); } headers = curl_slist_append (headers, "User-Agent: SeaDrive/"SEAFILE_CLIENT_VERSION" ("USER_AGENT_OS")"); /* Disable the default "Expect: 100-continue" header */ headers = curl_slist_append (headers, "Expect:"); if (req_content) headers = curl_slist_append (headers, "Content-Type: application/octet-stream"); if (token) { token_header = g_strdup_printf ("Seafile-Repo-Token: %s", token); headers = curl_slist_append (headers, token_header); g_free (token_header); token_header = g_strdup_printf ("Authorization: Token %s", token); headers = curl_slist_append (headers, token_header); g_free (token_header); } curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); curl_easy_setopt(curl, CURLOPT_URL, url); curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L); if (timeout) { /* Set low speed limit to 1 bytes. This effectively means no data. */ curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 1); curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, HTTP_TIMEOUT_SEC); } #ifndef USE_GPL_CRYPTO if (seaf->disable_verify_certificate) { curl_easy_setopt (curl, CURLOPT_SSL_VERIFYPEER, 0L); curl_easy_setopt (curl, CURLOPT_SSL_VERIFYHOST, 0L); } #endif HttpRequest req; if (req_content) { memset (&req, 0, sizeof(req)); req.content = req_content; req.size = req_size; curl_easy_setopt(curl, CURLOPT_READFUNCTION, send_request); curl_easy_setopt(curl, CURLOPT_READDATA, &req); curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)req_size); } else if (callback != NULL) { curl_easy_setopt(curl, CURLOPT_READFUNCTION, callback); curl_easy_setopt(curl, CURLOPT_READDATA, cb_data); curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)req_size); } else { curl_easy_setopt (curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)0); } curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L); HttpResponse rsp; memset (&rsp, 0, sizeof(rsp)); if (rsp_content) { curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, recv_response); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &rsp); } gboolean is_https = (strncasecmp(url, "https", strlen("https")) == 0); set_proxy (curl, is_https); #ifndef USE_GPL_CRYPTO load_ca_bundle (curl); #endif int rc = curl_easy_perform (curl); if (rc != 0) { seaf_warning ("libcurl failed to PUT %s: %s.\n", url, curl_easy_strerror(rc)); if (pcurl_error) *pcurl_error = rc; ret = -1; goto out; } long status; rc = curl_easy_getinfo (curl, CURLINFO_RESPONSE_CODE, &status); if (rc != CURLE_OK) { seaf_warning ("Failed to get status code for PUT %s.\n", url); ret = -1; goto out; } *rsp_status = status; if (rsp_content) { *rsp_content = rsp.content; *rsp_size = rsp.size; } out: if (ret < 0) g_free (rsp.content); curl_slist_free_all (headers); return ret; } static int http_post_common (CURL *curl, const char *url, const char *req_content, gint64 req_size, int *rsp_status, char **rsp_content, gint64 *rsp_size, gboolean timeout, int timeout_sec, int *pcurl_error) { int ret = 0; curl_easy_setopt(curl, CURLOPT_URL, url); curl_easy_setopt(curl, CURLOPT_POST, 1L); if (seafile_debug_flag_is_set (SEAFILE_DEBUG_CURL)) { curl_easy_setopt (curl, CURLOPT_VERBOSE, 1); curl_easy_setopt (curl, CURLOPT_STDERR, seafile_get_logfp()); } if (timeout) { /* Set low speed limit to 1 bytes. This effectively means no data. */ curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 1); curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, timeout_sec); } #ifndef USE_GPL_CRYPTO if (seaf->disable_verify_certificate) { curl_easy_setopt (curl, CURLOPT_SSL_VERIFYPEER, 0L); curl_easy_setopt (curl, CURLOPT_SSL_VERIFYHOST, 0L); } #endif HttpRequest req; memset (&req, 0, sizeof(req)); req.content = req_content; req.size = req_size; curl_easy_setopt(curl, CURLOPT_READFUNCTION, send_request); curl_easy_setopt(curl, CURLOPT_READDATA, &req); curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE_LARGE, (curl_off_t)req_size); curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L); HttpResponse rsp; memset (&rsp, 0, sizeof(rsp)); if (rsp_content) { curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, recv_response); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &rsp); } #ifndef USE_GPL_CRYPTO load_ca_bundle (curl); #endif gboolean is_https = (strncasecmp(url, "https", strlen("https")) == 0); set_proxy (curl, is_https); int rc = curl_easy_perform (curl); if (rc != 0) { seaf_warning ("libcurl failed to POST %s: %s.\n", url, curl_easy_strerror(rc)); if (pcurl_error) *pcurl_error = rc; ret = -1; goto out; } long status; rc = curl_easy_getinfo (curl, CURLINFO_RESPONSE_CODE, &status); if (rc != CURLE_OK) { seaf_warning ("Failed to get status code for POST %s.\n", url); ret = -1; goto out; } *rsp_status = status; if (rsp_content) { *rsp_content = rsp.content; *rsp_size = rsp.size; } out: if (ret < 0) g_free (rsp.content); return ret; } static int http_post (CURL *curl, const char *url, const char *token, const char *req_content, gint64 req_size, int *rsp_status, char **rsp_content, gint64 *rsp_size, gboolean timeout, int timeout_sec, int *pcurl_error) { char *token_header; struct curl_slist *headers = NULL; int ret; g_return_val_if_fail (req_content != NULL, -1); headers = curl_slist_append (headers, "User-Agent: SeaDrive/"SEAFILE_CLIENT_VERSION" ("USER_AGENT_OS")"); /* Disable the default "Expect: 100-continue" header */ headers = curl_slist_append (headers, "Expect:"); if (req_content) { json_t *is_json = NULL; is_json = json_loads (req_content, 0, NULL); if (is_json) { headers = curl_slist_append (headers, "Content-Type: application/json"); json_decref (is_json); } else headers = curl_slist_append (headers, "Content-Type: application/octet-stream"); } if (token) { token_header = g_strdup_printf ("Seafile-Repo-Token: %s", token); headers = curl_slist_append (headers, token_header); g_free (token_header); token_header = g_strdup_printf ("Authorization: Token %s", token); headers = curl_slist_append (headers, token_header); g_free (token_header); } curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); ret = http_post_common (curl, url, req_content, req_size, rsp_status, rsp_content, rsp_size, timeout, timeout_sec, pcurl_error); curl_slist_free_all (headers); return ret; } static int http_api_post_common (CURL *curl, const char *url, const char *token, const char *header, const char *req_content, gint64 req_size, int *rsp_status, char **rsp_content, gint64 *rsp_size, gboolean timeout, int timeout_sec, int *pcurl_error) { char *token_header; struct curl_slist *headers = NULL; int ret; g_return_val_if_fail (req_content != NULL, -1); headers = curl_slist_append (headers, "User-Agent: SeaDrive/"SEAFILE_CLIENT_VERSION" ("USER_AGENT_OS")"); /* Disable the default "Expect: 100-continue" header */ headers = curl_slist_append (headers, "Expect:"); if (req_content) headers = curl_slist_append (headers, header); if (token) { token_header = g_strdup_printf ("Authorization: Token %s", token); headers = curl_slist_append (headers, token_header); g_free (token_header); } curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); ret = http_post_common (curl, url, req_content, req_size, rsp_status, rsp_content, rsp_size, timeout, timeout_sec, pcurl_error); curl_slist_free_all (headers); return ret; } static int http_api_post (CURL *curl, const char *url, const char *token, const char *req_content, gint64 req_size, int *rsp_status, char **rsp_content, gint64 *rsp_size, gboolean timeout, int timeout_sec, int *pcurl_error) { int ret; char *header = "Content-Type: application/x-www-form-urlencoded"; ret = http_api_post_common (curl, url, token, header, req_content, req_size, rsp_status, rsp_content, rsp_size, timeout, timeout_sec, pcurl_error); return ret; } static int http_api_json_post (CURL *curl, const char *url, const char *token, const char *req_content, gint64 req_size, int *rsp_status, char **rsp_content, gint64 *rsp_size, gboolean timeout, int timeout_sec, int *pcurl_error) { int ret; char *header = "Content-Type: application/json"; ret = http_api_post_common (curl, url, token, header, req_content, req_size, rsp_status, rsp_content, rsp_size, timeout, timeout_sec, pcurl_error); return ret; } static int http_delete_common (CURL *curl, const char *url, int *rsp_status, gboolean timeout, int timeout_sec) { int ret = 0; curl_easy_setopt(curl, CURLOPT_URL, url); curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE"); if (seafile_debug_flag_is_set (SEAFILE_DEBUG_CURL)) { curl_easy_setopt (curl, CURLOPT_VERBOSE, 1); curl_easy_setopt (curl, CURLOPT_STDERR, seafile_get_logfp()); } if (timeout) { /* Set low speed limit to 1 bytes. This effectively means no data. */ curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 1); curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, timeout_sec); } #ifndef USE_GPL_CRYPTO if (seaf->disable_verify_certificate) { curl_easy_setopt (curl, CURLOPT_SSL_VERIFYPEER, 0L); curl_easy_setopt (curl, CURLOPT_SSL_VERIFYHOST, 0L); } #endif curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L); #ifndef USE_GPL_CRYPTO load_ca_bundle (curl); #endif gboolean is_https = (strncasecmp(url, "https", strlen("https")) == 0); set_proxy (curl, is_https); int rc = curl_easy_perform (curl); if (rc != 0) { seaf_warning ("libcurl failed to DELETE %s: %s.\n", url, curl_easy_strerror(rc)); ret = -1; goto out; } long status; rc = curl_easy_getinfo (curl, CURLINFO_RESPONSE_CODE, &status); if (rc != CURLE_OK) { seaf_warning ("Failed to get status code for DELETE %s.\n", url); ret = -1; goto out; } *rsp_status = status; out: return ret; } static int http_api_delete (CURL *curl, const char *url, const char *token, int *rsp_status, gboolean timeout, int timeout_sec) { char * token_header; struct curl_slist *headers = NULL; int ret; headers = curl_slist_append (headers, "User-Agent: SeaDrive/"SEAFILE_CLIENT_VERSION" ("USER_AGENT_OS")"); if (token) { token_header = g_strdup_printf ("Authorization: Token %s", token); headers = curl_slist_append (headers, token_header); g_free (token_header); } curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); ret = http_delete_common (curl, url, rsp_status, timeout, timeout_sec); curl_slist_free_all (headers); return ret; } static int http_error_to_http_task_error (int status) { if (status == HTTP_BAD_REQUEST) return HTTP_TASK_ERR_BAD_REQUEST; else if (status == HTTP_FORBIDDEN) return HTTP_TASK_ERR_FORBIDDEN; else if (status == HTTP_UNAUTHORIZED) return HTTP_TASK_ERR_FORBIDDEN; else if (status >= HTTP_INTERNAL_SERVER_ERROR) return HTTP_TASK_ERR_SERVER; else if (status == HTTP_NOT_FOUND) return HTTP_TASK_ERR_SERVER; else if (status == HTTP_NO_QUOTA) return HTTP_TASK_ERR_NO_QUOTA; else if (status == HTTP_REPO_DELETED) return HTTP_TASK_ERR_REPO_DELETED; else if (status == HTTP_REPO_CORRUPTED) return HTTP_TASK_ERR_REPO_CORRUPTED; else if (status == HTTP_REQUEST_TIME_OUT || status == HTTP_REPO_TOO_LARGE) return HTTP_TASK_ERR_LIBRARY_TOO_LARGE; else return HTTP_TASK_ERR_UNKNOWN; } static void handle_http_errors (HttpTxTask *task, int status) { task->error = http_error_to_http_task_error (status); } static int curl_error_to_http_task_error (int curl_error) { if (curl_error == CURLE_SSL_CACERT || curl_error == CURLE_PEER_FAILED_VERIFICATION) return HTTP_TASK_ERR_SSL; switch (curl_error) { case CURLE_COULDNT_RESOLVE_PROXY: return HTTP_TASK_ERR_RESOLVE_PROXY; case CURLE_COULDNT_RESOLVE_HOST: return HTTP_TASK_ERR_RESOLVE_HOST; case CURLE_COULDNT_CONNECT: return HTTP_TASK_ERR_CONNECT; case CURLE_OPERATION_TIMEDOUT: return HTTP_TASK_ERR_TX_TIMEOUT; case CURLE_SSL_CONNECT_ERROR: case CURLE_SSL_CERTPROBLEM: case CURLE_SSL_CACERT_BADFILE: case CURLE_SSL_ISSUER_ERROR: return HTTP_TASK_ERR_SSL; case CURLE_GOT_NOTHING: case CURLE_SEND_ERROR: case CURLE_RECV_ERROR: return HTTP_TASK_ERR_TX; case CURLE_SEND_FAIL_REWIND: return HTTP_TASK_ERR_UNHANDLED_REDIRECT; default: return HTTP_TASK_ERR_NET; } } static void handle_curl_errors (HttpTxTask *task, int curl_error) { task->error = curl_error_to_http_task_error (curl_error); } static void emit_transfer_done_signal (HttpTxTask *task) { if (task->type == HTTP_TASK_TYPE_DOWNLOAD) g_signal_emit_by_name (seaf, "repo-http-fetched", task); else g_signal_emit_by_name (seaf, "repo-http-uploaded", task); } static void transition_state (HttpTxTask *task, int state, int rt_state) { seaf_message ("Transfer repo '%.8s': ('%s', '%s') --> ('%s', '%s')\n", task->repo_id, http_task_state_to_str(task->state), http_task_rt_state_to_str(task->runtime_state), http_task_state_to_str(state), http_task_rt_state_to_str(rt_state)); if (state != task->state) task->state = state; task->runtime_state = rt_state; if (rt_state == HTTP_TASK_RT_STATE_FINISHED) { emit_transfer_done_signal (task); } } typedef struct { char *host; gboolean use_fileserver_port; HttpProtocolVersionCallback callback; void *user_data; gboolean success; gboolean not_supported; int version; int error_code; } CheckProtocolData; static int parse_protocol_version (const char *rsp_content, int rsp_size, CheckProtocolData *data) { json_t *object = NULL; json_error_t jerror; int version; object = json_loadb (rsp_content, rsp_size, 0, &jerror); if (!object) { seaf_warning ("Parse response failed: %s.\n", jerror.text); return -1; } if (json_object_has_member (object, "version")) { version = json_object_get_int_member (object, "version"); data->version = version; } else { seaf_warning ("Response doesn't contain protocol version.\n"); json_decref (object); return -1; } json_decref (object); return 0; } static void * check_protocol_version_thread (void *vdata) { CheckProtocolData *data = vdata; HttpTxPriv *priv = seaf->http_tx_mgr->priv; ConnectionPool *pool; Connection *conn; CURL *curl; char *url; int status; char *rsp_content = NULL; gint64 rsp_size; pool = find_connection_pool (priv, data->host); if (!pool) { seaf_warning ("Failed to create connection pool for host %s.\n", data->host); return vdata; } conn = connection_pool_get_connection (pool); if (!conn) { seaf_warning ("Failed to get connection to host %s.\n", data->host); return vdata; } curl = conn->curl; if (!data->use_fileserver_port) url = g_strdup_printf ("%s/seafhttp/protocol-version", data->host); else url = g_strdup_printf ("%s/protocol-version", data->host); int curl_error; if (http_get (curl, url, NULL, &status, &rsp_content, &rsp_size, NULL, NULL, TRUE, HTTP_TIMEOUT_SEC, &curl_error) < 0) { conn->release = TRUE; data->error_code = curl_error_to_http_task_error (curl_error); goto out; } data->success = TRUE; if (status == HTTP_OK) { if (rsp_size == 0) data->not_supported = TRUE; else if (parse_protocol_version (rsp_content, rsp_size, data) < 0) data->not_supported = TRUE; } else { seaf_warning ("Bad response code for GET %s: %d.\n", url, status); data->not_supported = TRUE; data->error_code = http_error_to_http_task_error (status); } out: g_free (url); g_free (rsp_content); connection_pool_return_connection (pool, conn); return vdata; } static void check_protocol_version_done (void *vdata) { CheckProtocolData *data = vdata; HttpProtocolVersion result; memset (&result, 0, sizeof(result)); result.check_success = data->success; result.not_supported = data->not_supported; result.version = data->version; result.error_code = data->error_code; data->callback (&result, data->user_data); g_free (data->host); g_free (data); } int http_tx_manager_check_protocol_version (HttpTxManager *manager, const char *host, gboolean use_fileserver_port, HttpProtocolVersionCallback callback, void *user_data) { CheckProtocolData *data = g_new0 (CheckProtocolData, 1); data->host = g_strdup(host); data->use_fileserver_port = use_fileserver_port; data->callback = callback; data->user_data = user_data; int ret = seaf_job_manager_schedule_job (seaf->job_mgr, check_protocol_version_thread, check_protocol_version_done, data); if (ret < 0) { g_free (data->host); g_free (data); } return ret; } typedef struct { char *host; gboolean use_notif_server_port; HttpNotifServerCallback callback; void *user_data; gboolean success; gboolean not_supported; } CheckNotifServerData; static void * check_notif_server_thread (void *vdata) { CheckNotifServerData *data = vdata; HttpTxPriv *priv = seaf->http_tx_mgr->priv; ConnectionPool *pool; Connection *conn; CURL *curl; char *url; int status; char *rsp_content = NULL; gint64 rsp_size; pool = find_connection_pool (priv, data->host); if (!pool) { seaf_warning ("Failed to create connection pool for host %s.\n", data->host); return vdata; } conn = connection_pool_get_connection (pool); if (!conn) { seaf_warning ("Failed to get connection to host %s.\n", data->host); return vdata; } curl = conn->curl; if (!data->use_notif_server_port) url = g_strdup_printf ("%s/notification/ping", data->host); else url = g_strdup_printf ("%s/ping", data->host); int curl_error; if (http_get (curl, url, NULL, &status, &rsp_content, &rsp_size, NULL, NULL, TRUE, HTTP_TIMEOUT_SEC, &curl_error) < 0) { conn->release = TRUE; goto out; } data->success = TRUE; if (status != HTTP_OK) { data->not_supported = TRUE; } out: g_free (url); g_free (rsp_content); connection_pool_return_connection (pool, conn); return vdata; } static void check_notif_server_done (void *vdata) { CheckNotifServerData *data = vdata; data->callback ((data->success && !data->not_supported), data->user_data); g_free (data->host); g_free (data); } int http_tx_manager_check_notif_server (HttpTxManager *manager, const char *host, gboolean use_notif_server_port, HttpNotifServerCallback callback, void *user_data) { CheckNotifServerData *data = g_new0 (CheckNotifServerData, 1); data->host = g_strdup(host); data->use_notif_server_port = use_notif_server_port; data->callback = callback; data->user_data = user_data; int ret = seaf_job_manager_schedule_job (seaf->job_mgr, check_notif_server_thread, check_notif_server_done, data); if (ret < 0) { g_free (data->host); g_free (data); } return ret; } /* Check Head Commit. */ typedef struct { char repo_id[41]; int repo_version; char *host; char *token; gboolean use_fileserver_port; HttpHeadCommitCallback callback; void *user_data; gboolean success; gboolean is_corrupt; gboolean is_deleted; gboolean perm_denied; char head_commit[41]; int error_code; } CheckHeadData; static int parse_head_commit_info (const char *rsp_content, int rsp_size, CheckHeadData *data) { json_t *object = NULL; json_error_t jerror; const char *head_commit; object = json_loadb (rsp_content, rsp_size, 0, &jerror); if (!object) { seaf_warning ("Parse response failed: %s.\n", jerror.text); return -1; } if (json_object_has_member (object, "is_corrupted") && json_object_get_int_member (object, "is_corrupted")) data->is_corrupt = TRUE; if (!data->is_corrupt) { head_commit = json_object_get_string_member (object, "head_commit_id"); if (!head_commit) { seaf_warning ("Check head commit for repo %s failed. " "Response doesn't contain head commit id.\n", data->repo_id); json_decref (object); return -1; } memcpy (data->head_commit, head_commit, 40); } json_decref (object); return 0; } static void * check_head_commit_thread (void *vdata) { CheckHeadData *data = vdata; HttpTxPriv *priv = seaf->http_tx_mgr->priv; ConnectionPool *pool; Connection *conn; CURL *curl; char *url; int status; char *rsp_content = NULL; gint64 rsp_size; pool = find_connection_pool (priv, data->host); if (!pool) { seaf_warning ("Failed to create connection pool for host %s.\n", data->host); return vdata; } conn = connection_pool_get_connection (pool); if (!conn) { seaf_warning ("Failed to get connection to host %s.\n", data->host); return vdata; } curl = conn->curl; if (!data->use_fileserver_port) url = g_strdup_printf ("%s/seafhttp/repo/%s/commit/HEAD", data->host, data->repo_id); else url = g_strdup_printf ("%s/repo/%s/commit/HEAD", data->host, data->repo_id); int curl_error; if (http_get (curl, url, data->token, &status, &rsp_content, &rsp_size, NULL, NULL, TRUE, HTTP_TIMEOUT_SEC, &curl_error) < 0) { conn->release = TRUE; data->error_code = curl_error_to_http_task_error (curl_error); goto out; } if (status == HTTP_OK) { if (parse_head_commit_info (rsp_content, rsp_size, data) < 0) goto out; data->success = TRUE; } else if (status == HTTP_FORBIDDEN) { data->perm_denied = TRUE; } else if (status == HTTP_REPO_DELETED) { data->is_deleted = TRUE; data->success = TRUE; } else { seaf_warning ("Bad response code for GET %s: %d.\n", url, status); data->error_code = http_error_to_http_task_error (status); } out: g_free (url); g_free (rsp_content); connection_pool_return_connection (pool, conn); return vdata; } static void check_head_commit_done (void *vdata) { CheckHeadData *data = vdata; HttpHeadCommit result; memset (&result, 0, sizeof(result)); result.check_success = data->success; result.is_corrupt = data->is_corrupt; result.is_deleted = data->is_deleted; result.perm_denied = data->perm_denied; memcpy (result.head_commit, data->head_commit, 40); result.error_code = data->error_code; data->callback (&result, data->user_data); g_free (data->host); g_free (data->token); g_free (data); } int http_tx_manager_check_head_commit (HttpTxManager *manager, const char *repo_id, int repo_version, const char *host, const char *token, gboolean use_fileserver_port, HttpHeadCommitCallback callback, void *user_data) { CheckHeadData *data = g_new0 (CheckHeadData, 1); memcpy (data->repo_id, repo_id, 36); data->repo_version = repo_version; data->host = g_strdup(host); data->token = g_strdup(token); data->callback = callback; data->user_data = user_data; data->use_fileserver_port = use_fileserver_port; if (seaf_job_manager_schedule_job (seaf->job_mgr, check_head_commit_thread, check_head_commit_done, data) < 0) { g_free (data->host); g_free (data->token); g_free (data); return -1; } return 0; } /* Get folder permissions. */ void http_folder_perm_req_free (HttpFolderPermReq *req) { if (!req) return; g_free (req->token); g_free (req); } void http_folder_perm_res_free (HttpFolderPermRes *res) { GList *ptr; if (!res) return; for (ptr = res->user_perms; ptr; ptr = ptr->next) folder_perm_free ((FolderPerm *)ptr->data); for (ptr = res->group_perms; ptr; ptr = ptr->next) folder_perm_free ((FolderPerm *)ptr->data); g_free (res); } typedef struct { char *host; gboolean use_fileserver_port; GList *requests; HttpGetFolderPermsCallback callback; void *user_data; gboolean success; GList *results; } GetFolderPermsData; /* Make sure the path starts with '/' but doesn't end with '/'. */ static char * canonical_perm_path (const char *path) { int len = strlen(path); char *copy, *ret; if (strcmp (path, "/") == 0) return g_strdup(path); if (path[0] == '/' && path[len-1] != '/') return g_strdup(path); copy = g_strdup(path); if (copy[len-1] == '/') copy[len-1] = 0; if (copy[0] != '/') ret = g_strconcat ("/", copy, NULL); else ret = copy; return ret; } static GList * parse_permission_list (json_t *array, gboolean *error) { GList *ret = NULL, *ptr; json_t *object, *member; size_t n; int i; FolderPerm *perm; const char *str; *error = FALSE; n = json_array_size (array); for (i = 0; i < n; ++i) { object = json_array_get (array, i); perm = g_new0 (FolderPerm, 1); member = json_object_get (object, "path"); if (!member) { seaf_warning ("Invalid folder perm response format: no path.\n"); *error = TRUE; goto out; } str = json_string_value(member); if (!str) { seaf_warning ("Invalid folder perm response format: invalid path.\n"); *error = TRUE; goto out; } perm->path = canonical_perm_path (str); member = json_object_get (object, "permission"); if (!member) { seaf_warning ("Invalid folder perm response format: no permission.\n"); *error = TRUE; goto out; } str = json_string_value(member); if (!str) { seaf_warning ("Invalid folder perm response format: invalid permission.\n"); *error = TRUE; goto out; } perm->permission = g_strdup(str); ret = g_list_append (ret, perm); } out: if (*error) { for (ptr = ret; ptr; ptr = ptr->next) folder_perm_free ((FolderPerm *)ptr->data); g_list_free (ret); ret = NULL; } return ret; } static int parse_folder_perms (const char *rsp_content, int rsp_size, GetFolderPermsData *data) { json_t *array = NULL, *object, *member; json_error_t jerror; size_t n; int i; GList *results = NULL, *ptr; HttpFolderPermRes *res; const char *repo_id; int ret = 0; gboolean error; array = json_loadb (rsp_content, rsp_size, 0, &jerror); if (!array) { seaf_warning ("Parse response failed: %s.\n", jerror.text); return -1; } n = json_array_size (array); for (i = 0; i < n; ++i) { object = json_array_get (array, i); res = g_new0 (HttpFolderPermRes, 1); member = json_object_get (object, "repo_id"); if (!member) { seaf_warning ("Invalid folder perm response format: no repo_id.\n"); ret = -1; goto out; } repo_id = json_string_value(member); if (strlen(repo_id) != 36) { seaf_warning ("Invalid folder perm response format: invalid repo_id.\n"); ret = -1; goto out; } memcpy (res->repo_id, repo_id, 36); member = json_object_get (object, "ts"); if (!member) { seaf_warning ("Invalid folder perm response format: no timestamp.\n"); ret = -1; goto out; } res->timestamp = json_integer_value (member); member = json_object_get (object, "user_perms"); if (!member) { seaf_warning ("Invalid folder perm response format: no user_perms.\n"); ret = -1; goto out; } res->user_perms = parse_permission_list (member, &error); if (error) { ret = -1; goto out; } member = json_object_get (object, "group_perms"); if (!member) { seaf_warning ("Invalid folder perm response format: no group_perms.\n"); ret = -1; goto out; } res->group_perms = parse_permission_list (member, &error); if (error) { ret = -1; goto out; } results = g_list_append (results, res); } out: json_decref (array); if (ret < 0) { for (ptr = results; ptr; ptr = ptr->next) http_folder_perm_res_free ((HttpFolderPermRes *)ptr->data); g_list_free (results); } else { data->results = results; } return ret; } static char * compose_get_folder_perms_request (GList *requests) { GList *ptr; HttpFolderPermReq *req; json_t *object, *array; char *req_str = NULL; array = json_array (); for (ptr = requests; ptr; ptr = ptr->next) { req = ptr->data; object = json_object (); json_object_set_new (object, "repo_id", json_string(req->repo_id)); json_object_set_new (object, "token", json_string(req->token)); json_object_set_new (object, "ts", json_integer(req->timestamp)); json_array_append_new (array, object); } req_str = json_dumps (array, 0); if (!req_str) { seaf_warning ("Faile to json_dumps.\n"); } json_decref (array); return req_str; } static void * get_folder_perms_thread (void *vdata) { GetFolderPermsData *data = vdata; HttpTxPriv *priv = seaf->http_tx_mgr->priv; ConnectionPool *pool; Connection *conn; CURL *curl; char *url; char *req_content = NULL; int status; char *rsp_content = NULL; gint64 rsp_size; GList *ptr; pool = find_connection_pool (priv, data->host); if (!pool) { seaf_warning ("Failed to create connection pool for host %s.\n", data->host); return vdata; } conn = connection_pool_get_connection (pool); if (!conn) { seaf_warning ("Failed to get connection to host %s.\n", data->host); return vdata; } curl = conn->curl; if (!data->use_fileserver_port) url = g_strdup_printf ("%s/seafhttp/repo/folder-perm", data->host); else url = g_strdup_printf ("%s/repo/folder-perm", data->host); req_content = compose_get_folder_perms_request (data->requests); if (!req_content) goto out; if (http_post (curl, url, NULL, req_content, strlen(req_content), &status, &rsp_content, &rsp_size, TRUE, HTTP_TIMEOUT_SEC, NULL) < 0) { conn->release = TRUE; goto out; } if (status == HTTP_OK) { if (parse_folder_perms (rsp_content, rsp_size, data) < 0) goto out; data->success = TRUE; } else { seaf_warning ("Bad response code for GET %s: %d.\n", url, status); } out: for (ptr = data->requests; ptr; ptr = ptr->next) http_folder_perm_req_free ((HttpFolderPermReq *)ptr->data); g_list_free (data->requests); g_free (url); g_free (req_content); g_free (rsp_content); connection_pool_return_connection (pool, conn); return vdata; } static void get_folder_perms_done (void *vdata) { GetFolderPermsData *data = vdata; HttpFolderPerms cb_data; memset (&cb_data, 0, sizeof(cb_data)); cb_data.success = data->success; cb_data.results = data->results; data->callback (&cb_data, data->user_data); GList *ptr; for (ptr = data->results; ptr; ptr = ptr->next) http_folder_perm_res_free ((HttpFolderPermRes *)ptr->data); g_list_free (data->results); g_free (data->host); g_free (data); } int http_tx_manager_get_folder_perms (HttpTxManager *manager, const char *host, gboolean use_fileserver_port, GList *folder_perm_requests, HttpGetFolderPermsCallback callback, void *user_data) { GetFolderPermsData *data = g_new0 (GetFolderPermsData, 1); data->host = g_strdup(host); data->requests = folder_perm_requests; data->callback = callback; data->user_data = user_data; data->use_fileserver_port = use_fileserver_port; if (seaf_job_manager_schedule_job (seaf->job_mgr, get_folder_perms_thread, get_folder_perms_done, data) < 0) { g_free (data->host); g_free (data); return -1; } return 0; } /* Get Locked Files. */ void http_locked_files_req_free (HttpLockedFilesReq *req) { if (!req) return; g_free (req->token); g_free (req); } void http_locked_files_res_free (HttpLockedFilesRes *res) { if (!res) return; g_hash_table_destroy (res->locked_files); g_free (res); } typedef struct { char *host; gboolean use_fileserver_port; GList *requests; HttpGetLockedFilesCallback callback; void *user_data; gboolean success; GList *results; } GetLockedFilesData; static GHashTable * parse_locked_file_list (json_t *array) { GHashTable *ret = NULL; size_t n, i; json_t *obj, *string, *integer; ret = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); if (!ret) { return NULL; } n = json_array_size (array); for (i = 0; i < n; ++i) { obj = json_array_get (array, i); string = json_object_get (obj, "path"); if (!string) { g_hash_table_destroy (ret); return NULL; } integer = json_object_get (obj, "by_me"); if (!integer) { g_hash_table_destroy (ret); return NULL; } g_hash_table_insert (ret, g_strdup(json_string_value(string)), (void*)(long)json_integer_value(integer)); } return ret; } static int parse_locked_files (const char *rsp_content, int rsp_size, GetLockedFilesData *data) { json_t *array = NULL, *object, *member; json_error_t jerror; size_t n; int i; GList *results = NULL; HttpLockedFilesRes *res; const char *repo_id; int ret = 0; array = json_loadb (rsp_content, rsp_size, 0, &jerror); if (!array) { seaf_warning ("Parse response failed: %s.\n", jerror.text); return -1; } n = json_array_size (array); for (i = 0; i < n; ++i) { object = json_array_get (array, i); res = g_new0 (HttpLockedFilesRes, 1); member = json_object_get (object, "repo_id"); if (!member) { seaf_warning ("Invalid locked files response format: no repo_id.\n"); ret = -1; goto out; } repo_id = json_string_value(member); if (strlen(repo_id) != 36) { seaf_warning ("Invalid locked files response format: invalid repo_id.\n"); ret = -1; goto out; } memcpy (res->repo_id, repo_id, 36); member = json_object_get (object, "ts"); if (!member) { seaf_warning ("Invalid locked files response format: no timestamp.\n"); ret = -1; goto out; } res->timestamp = json_integer_value (member); member = json_object_get (object, "locked_files"); if (!member) { seaf_warning ("Invalid locked files response format: no locked_files.\n"); ret = -1; goto out; } res->locked_files = parse_locked_file_list (member); if (res->locked_files == NULL) { ret = -1; goto out; } results = g_list_append (results, res); } out: json_decref (array); if (ret < 0) { g_list_free_full (results, (GDestroyNotify)http_locked_files_res_free); } else { data->results = results; } return ret; } static char * compose_get_locked_files_request (GList *requests) { GList *ptr; HttpLockedFilesReq *req; json_t *object, *array; char *req_str = NULL; array = json_array (); for (ptr = requests; ptr; ptr = ptr->next) { req = ptr->data; object = json_object (); json_object_set_new (object, "repo_id", json_string(req->repo_id)); json_object_set_new (object, "token", json_string(req->token)); json_object_set_new (object, "ts", json_integer(req->timestamp)); json_array_append_new (array, object); } req_str = json_dumps (array, 0); if (!req_str) { seaf_warning ("Faile to json_dumps.\n"); } json_decref (array); return req_str; } static void * get_locked_files_thread (void *vdata) { GetLockedFilesData *data = vdata; HttpTxPriv *priv = seaf->http_tx_mgr->priv; ConnectionPool *pool; Connection *conn; CURL *curl; char *url; char *req_content = NULL; int status; char *rsp_content = NULL; gint64 rsp_size; pool = find_connection_pool (priv, data->host); if (!pool) { seaf_warning ("Failed to create connection pool for host %s.\n", data->host); return vdata; } conn = connection_pool_get_connection (pool); if (!conn) { seaf_warning ("Failed to get connection to host %s.\n", data->host); return vdata; } curl = conn->curl; if (!data->use_fileserver_port) url = g_strdup_printf ("%s/seafhttp/repo/locked-files", data->host); else url = g_strdup_printf ("%s/repo/locked-files", data->host); req_content = compose_get_locked_files_request (data->requests); if (!req_content) goto out; if (http_post (curl, url, NULL, req_content, strlen(req_content), &status, &rsp_content, &rsp_size, TRUE, HTTP_TIMEOUT_SEC, NULL) < 0) { conn->release = TRUE; goto out; } if (status == HTTP_OK) { if (parse_locked_files (rsp_content, rsp_size, data) < 0) goto out; data->success = TRUE; } else { seaf_warning ("Bad response code for GET %s: %d.\n", url, status); } out: g_list_free_full (data->requests, (GDestroyNotify)http_locked_files_req_free); g_free (url); g_free (req_content); g_free (rsp_content); connection_pool_return_connection (pool, conn); return vdata; } static void get_locked_files_done (void *vdata) { GetLockedFilesData *data = vdata; HttpLockedFiles cb_data; memset (&cb_data, 0, sizeof(cb_data)); cb_data.success = data->success; cb_data.results = data->results; data->callback (&cb_data, data->user_data); g_list_free_full (data->results, (GDestroyNotify)http_locked_files_res_free); g_free (data->host); g_free (data); } int http_tx_manager_get_locked_files (HttpTxManager *manager, const char *host, gboolean use_fileserver_port, GList *locked_files_requests, HttpGetLockedFilesCallback callback, void *user_data) { GetLockedFilesData *data = g_new0 (GetLockedFilesData, 1); data->host = g_strdup(host); data->requests = locked_files_requests; data->callback = callback; data->user_data = user_data; data->use_fileserver_port = use_fileserver_port; if (seaf_job_manager_schedule_job (seaf->job_mgr, get_locked_files_thread, get_locked_files_done, data) < 0) { g_free (data->host); g_free (data); return -1; } return 0; } /* Synchronous interfaces for locking/unlocking a file on the server. */ int http_tx_manager_lock_file (HttpTxManager *manager, const char *host, gboolean use_fileserver_port, const char *token, const char *repo_id, const char *path) { HttpTxPriv *priv = seaf->http_tx_mgr->priv; ConnectionPool *pool; Connection *conn; CURL *curl; char *url; int status; int ret = 0; pool = find_connection_pool (priv, host); if (!pool) { seaf_warning ("Failed to create connection pool for host %s.\n", host); return -1; } conn = connection_pool_get_connection (pool); if (!conn) { seaf_warning ("Failed to get connection to host %s.\n", host); return -1; } curl = conn->curl; char *esc_path = g_uri_escape_string(path, NULL, FALSE); if (!use_fileserver_port) url = g_strdup_printf ("%s/seafhttp/repo/%s/lock-file?p=%s", host, repo_id, esc_path); else url = g_strdup_printf ("%s/repo/%s/lock-file?p=%s", host, repo_id, esc_path); g_free (esc_path); if (http_put (curl, url, token, NULL, 0, NULL, NULL, &status, NULL, NULL, TRUE, NULL) < 0) { conn->release = TRUE; ret = -1; goto out; } if (status != HTTP_OK) { seaf_warning ("Bad response code for PUT %s: %d.\n", url, status); ret = -1; } out: g_free (url); connection_pool_return_connection (pool, conn); return ret; } int http_tx_manager_unlock_file (HttpTxManager *manager, const char *host, gboolean use_fileserver_port, const char *token, const char *repo_id, const char *path) { HttpTxPriv *priv = seaf->http_tx_mgr->priv; ConnectionPool *pool; Connection *conn; CURL *curl; char *url; int status; int ret = 0; pool = find_connection_pool (priv, host); if (!pool) { seaf_warning ("Failed to create connection pool for host %s.\n", host); return -1; } conn = connection_pool_get_connection (pool); if (!conn) { seaf_warning ("Failed to get connection to host %s.\n", host); return -1; } curl = conn->curl; char *esc_path = g_uri_escape_string(path, NULL, FALSE); if (!use_fileserver_port) url = g_strdup_printf ("%s/seafhttp/repo/%s/unlock-file?p=%s", host, repo_id, esc_path); else url = g_strdup_printf ("%s/repo/%s/unlock-file?p=%s", host, repo_id, esc_path); g_free (esc_path); if (http_put (curl, url, token, NULL, 0, NULL, NULL, &status, NULL, NULL, TRUE, NULL) < 0) { conn->release = TRUE; ret = -1; goto out; } if (status != HTTP_OK) { seaf_warning ("Bad response code for PUT %s: %d.\n", url, status); ret = -1; } out: g_free (url); connection_pool_return_connection (pool, conn); return ret; } /* Asynchronous interface for sending a API GET request to seahub. */ typedef struct { char *host; char *url; char *api_token; HttpAPIGetCallback callback; void *user_data; gboolean success; char *rsp_content; int rsp_size; int error_code; int http_status; } APIGetData; static void * api_get_request_thread (void *vdata) { APIGetData *data = vdata; HttpTxPriv *priv = seaf->http_tx_mgr->priv; ConnectionPool *pool; Connection *conn; CURL *curl; int status; char *rsp_content = NULL; gint64 rsp_size; pool = find_connection_pool (priv, data->host); if (!pool) { seaf_warning ("Failed to create connection pool for host %s.\n", data->host); return vdata; } conn = connection_pool_get_connection (pool); if (!conn) { seaf_warning ("Failed to get connection to host %s.\n", data->host); return vdata; } curl = conn->curl; int curl_error; if (http_api_get (curl, data->url, data->api_token, &status, &rsp_content, &rsp_size, NULL, NULL, TRUE, HTTP_TIMEOUT_SEC, &curl_error) < 0) { conn->release = TRUE; data->error_code = curl_error_to_http_task_error (curl_error); goto out; } // Free rsp_content even if status is not HTTP_OK, prevent memory leak data->rsp_content = rsp_content; data->rsp_size = rsp_size; if (status == HTTP_OK) { data->success = TRUE; } else { seaf_warning ("Bad response code for GET %s: %d.\n", data->url, status); data->error_code = http_error_to_http_task_error (status); } out: connection_pool_return_connection (pool, conn); return vdata; } static void api_get_request_done (void *vdata) { APIGetData *data = vdata; HttpAPIGetResult cb_data; memset (&cb_data, 0, sizeof(cb_data)); cb_data.success = data->success; cb_data.rsp_content = data->rsp_content; cb_data.rsp_size = data->rsp_size; cb_data.error_code = data->error_code; data->callback (&cb_data, data->user_data); g_free (data->rsp_content); g_free (data->host); g_free (data->url); g_free (data->api_token); g_free (data); } int http_tx_manager_api_get (HttpTxManager *manager, const char *host, const char *url, const char *api_token, HttpAPIGetCallback callback, void *user_data) { APIGetData *data = g_new0 (APIGetData, 1); data->host = g_strdup(host); data->url = g_strdup(url); data->api_token = g_strdup(api_token); data->callback = callback; data->user_data = user_data; if (seaf_job_manager_schedule_job (seaf->job_mgr, api_get_request_thread, api_get_request_done, data) < 0) { g_free (data->host); g_free (data->url); g_free (data->api_token); g_free (data); return -1; } return 0; } static void * fileserver_api_get_request (void *vdata) { APIGetData *data = vdata; HttpTxPriv *priv = seaf->http_tx_mgr->priv; ConnectionPool *pool; Connection *conn; CURL *curl; int status; char *rsp_content = NULL; gint64 rsp_size; pool = find_connection_pool (priv, data->host); if (!pool) { seaf_warning ("Failed to create connection pool for host %s.\n", data->host); return vdata; } conn = connection_pool_get_connection (pool); if (!conn) { seaf_warning ("Failed to get connection to host %s.\n", data->host); return vdata; } curl = conn->curl; int curl_error; if (http_get (curl, data->url, data->api_token, &status, &rsp_content, &rsp_size, NULL, NULL, TRUE, HTTP_TIMEOUT_SEC, &curl_error) < 0) { conn->release = TRUE; data->error_code = curl_error_to_http_task_error (curl_error); goto out; } data->rsp_content = rsp_content; data->rsp_size = rsp_size; if (status == HTTP_OK) { data->success = TRUE; } else { seaf_warning ("Bad response code for GET %s: %d.\n", data->url, status); data->error_code = http_error_to_http_task_error (status); data->http_status = status; } out: connection_pool_return_connection (pool, conn); return vdata; } static void fileserver_api_get_request_done (void *vdata) { APIGetData *data = vdata; HttpAPIGetResult cb_data; memset (&cb_data, 0, sizeof(cb_data)); cb_data.success = data->success; cb_data.rsp_content = data->rsp_content; cb_data.rsp_size = data->rsp_size; cb_data.error_code = data->error_code; cb_data.http_status = data->http_status; data->callback (&cb_data, data->user_data); g_free (data->rsp_content); g_free (data->host); g_free (data->url); g_free (data->api_token); g_free (data); } int http_tx_manager_fileserver_api_get (HttpTxManager *manager, const char *host, const char *url, const char *api_token, HttpAPIGetCallback callback, void *user_data) { APIGetData *data = g_new0 (APIGetData, 1); data->host = g_strdup(host); data->url = g_strdup(url); data->api_token = g_strdup(api_token); data->callback = callback; data->user_data = user_data; if (seaf_job_manager_schedule_job (seaf->job_mgr, fileserver_api_get_request, fileserver_api_get_request_done, data) < 0) { g_free (data->rsp_content); g_free (data->host); g_free (data->url); g_free (data->api_token); g_free (data); return -1; } return 0; } static gboolean remove_task_help (gpointer key, gpointer value, gpointer user_data) { HttpTxTask *task = value; const char *repo_id = user_data; if (strcmp(task->repo_id, repo_id) != 0) return FALSE; return TRUE; } static void clean_tasks_for_repo (HttpTxManager *manager, const char *repo_id) { g_hash_table_foreach_remove (manager->priv->download_tasks, remove_task_help, (gpointer)repo_id); g_hash_table_foreach_remove (manager->priv->upload_tasks, remove_task_help, (gpointer)repo_id); } static int check_permission (HttpTxTask *task, Connection *conn) { CURL *curl; char *url; int status; int ret = 0; char *rsp_content = NULL; gint64 rsp_size; json_t *rsp_obj = NULL, *reason = NULL, *unsyncable_path = NULL; const char *reason_str = NULL, *unsyncable_path_str = NULL; json_error_t jerror; curl = conn->curl; const char *type = (task->type == HTTP_TASK_TYPE_DOWNLOAD) ? "download" : "upload"; const char *url_prefix = (task->use_fileserver_port) ? "" : "seafhttp/"; char *client_name = g_uri_escape_string (seaf->client_name, NULL, FALSE); url = g_strdup_printf ("%s/%srepo/%s/permission-check/?op=%s" "&client_id=%s&client_name=%s", task->host, url_prefix, task->repo_id, type, seaf->client_id, client_name); g_free (client_name); int curl_error; if (http_get (curl, url, task->token, &status, &rsp_content, &rsp_size, NULL, NULL, TRUE, HTTP_TIMEOUT_SEC, &curl_error) < 0) { conn->release = TRUE; handle_curl_errors (task, curl_error); ret = -1; goto out; } if (status != HTTP_OK) { seaf_warning ("Bad response code for GET %s: %d.\n", url, status); if (status != HTTP_FORBIDDEN || !rsp_content) { handle_http_errors (task, status); ret = -1; goto out; } rsp_obj = json_loadb (rsp_content, rsp_size, 0 ,&jerror); if (!rsp_obj) { seaf_warning ("Parse check permission response failed: %s.\n", jerror.text); handle_http_errors (task, status); json_decref (rsp_obj); ret = -1; goto out; } reason = json_object_get (rsp_obj, "reason"); if (!reason) { handle_http_errors (task, status); json_decref (rsp_obj); ret = -1; goto out; } reason_str = json_string_value (reason); if (g_strcmp0 (reason_str, "no write permission") == 0) { task->error = HTTP_TASK_ERR_NO_WRITE_PERMISSION; } else if (g_strcmp0 (reason_str, "unsyncable share permission") == 0) { task->error = HTTP_TASK_ERR_NO_PERMISSION_TO_SYNC; unsyncable_path = json_object_get (rsp_obj, "unsyncable_path"); if (!unsyncable_path) { json_decref (rsp_obj); ret = -1; goto out; } unsyncable_path_str = json_string_value (unsyncable_path); if (unsyncable_path_str) task->unsyncable_path = g_strdup (unsyncable_path_str); } else { task->error = HTTP_TASK_ERR_FORBIDDEN; } ret = -1; } out: g_free (url); curl_easy_reset (curl); return ret; } /* Upload. */ static void *http_upload_thread (void *vdata); static void http_upload_done (void *vdata); int http_tx_manager_add_upload (HttpTxManager *manager, const char *repo_id, int repo_version, const char *server, const char *user, const char *repo_uname, const char *host, const char *token, int protocol_version, gboolean use_fileserver_port, GError **error) { HttpTxTask *task; if (!repo_id || !repo_uname || !token || !host) { g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Empty argument(s)"); return -1; } clean_tasks_for_repo (manager, repo_id); task = http_tx_task_new (manager, repo_id, repo_version, HTTP_TASK_TYPE_UPLOAD, FALSE, host, token); task->repo_uname = g_strdup (repo_uname); task->protocol_version = protocol_version; task->state = HTTP_TASK_STATE_NORMAL; task->use_fileserver_port = use_fileserver_port; task->server = g_strdup (server); task->user = g_strdup (user); g_hash_table_insert (manager->priv->upload_tasks, g_strdup(repo_id), task); if (seaf_job_manager_schedule_job (seaf->job_mgr, http_upload_thread, http_upload_done, task) < 0) { g_hash_table_remove (manager->priv->upload_tasks, repo_id); return -1; } return 0; } static gboolean dirent_same (SeafDirent *dent1, SeafDirent *dent2) { return (strcmp(dent1->id, dent2->id) == 0 && dent1->mode == dent2->mode && dent1->mtime == dent2->mtime); } typedef struct { HttpTxTask *task; gint64 delta; GHashTable *active_paths; } CalcQuotaDeltaData; static int check_quota_and_active_paths_diff_files (int n, const char *basedir, SeafDirent *files[], void *vdata) { CalcQuotaDeltaData *data = vdata; SeafDirent *file1 = files[0]; SeafDirent *file2 = files[1]; gint64 size1, size2; char *path; if (file1 && file2) { size1 = file1->size; size2 = file2->size; data->delta += (size1 - size2); if (!dirent_same (file1, file2)) { path = g_strconcat(basedir, file1->name, NULL); g_hash_table_replace (data->active_paths, path, (void*)(long)S_IFREG); } } else if (file1 && !file2) { data->delta += file1->size; path = g_strconcat (basedir, file1->name, NULL); g_hash_table_replace (data->active_paths, path, (void*)(long)S_IFREG); } else if (!file1 && file2) { data->delta -= file2->size; } return 0; } static int check_quota_and_active_paths_diff_dirs (int n, const char *basedir, SeafDirent *dirs[], void *vdata, gboolean *recurse) { CalcQuotaDeltaData *data = vdata; SeafDirent *dir1 = dirs[0]; SeafDirent *dir2 = dirs[1]; char *path; /* When a new empty dir is created, or a dir became empty. */ if ((!dir2 && dir1 && strcmp(dir1->id, EMPTY_SHA1) == 0) || (dir2 && dir1 && strcmp(dir1->id, EMPTY_SHA1) == 0 && strcmp(dir2->id, EMPTY_SHA1) != 0)) { path = g_strconcat (basedir, dir1->name, NULL); g_hash_table_replace (data->active_paths, path, (void*)(long)S_IFDIR); } return 0; } static int calculate_upload_size_delta_and_active_paths (HttpTxTask *task, gint64 *delta, GHashTable *active_paths) { int ret = 0; SeafBranch *local = NULL, *master = NULL; SeafCommit *local_head = NULL, *master_head = NULL; local = seaf_branch_manager_get_branch (seaf->branch_mgr, task->repo_id, "local"); if (!local) { seaf_warning ("Branch local not found for repo %.8s.\n", task->repo_id); ret = -1; goto out; } master = seaf_branch_manager_get_branch (seaf->branch_mgr, task->repo_id, "master"); if (!master) { seaf_warning ("Branch master not found for repo %.8s.\n", task->repo_id); ret = -1; goto out; } local_head = seaf_commit_manager_get_commit (seaf->commit_mgr, task->repo_id, task->repo_version, local->commit_id); if (!local_head) { seaf_warning ("Local head commit not found for repo %.8s.\n", task->repo_id); ret = -1; goto out; } master_head = seaf_commit_manager_get_commit (seaf->commit_mgr, task->repo_id, task->repo_version, master->commit_id); if (!master_head) { seaf_warning ("Master head commit not found for repo %.8s.\n", task->repo_id); ret = -1; goto out; } CalcQuotaDeltaData data; memset (&data, 0, sizeof(data)); data.task = task; data.active_paths = active_paths; DiffOptions opts; memset (&opts, 0, sizeof(opts)); memcpy (opts.store_id, task->repo_id, 36); opts.version = task->repo_version; opts.file_cb = check_quota_and_active_paths_diff_files; opts.dir_cb = check_quota_and_active_paths_diff_dirs; opts.data = &data; const char *trees[2]; trees[0] = local_head->root_id; trees[1] = master_head->root_id; if (diff_trees (2, trees, &opts) < 0) { seaf_warning ("Failed to diff local and master head for repo %.8s.\n", task->repo_id); ret = -1; goto out; } *delta = data.delta; out: seaf_branch_unref (local); seaf_branch_unref (master); seaf_commit_unref (local_head); seaf_commit_unref (master_head); return ret; } static int check_quota (HttpTxTask *task, Connection *conn, gint64 delta) { CURL *curl; char *url; int status; int ret = 0; curl = conn->curl; if (!task->use_fileserver_port) url = g_strdup_printf ("%s/seafhttp/repo/%s/quota-check/?delta=%"G_GINT64_FORMAT"", task->host, task->repo_id, delta); else url = g_strdup_printf ("%s/repo/%s/quota-check/?delta=%"G_GINT64_FORMAT"", task->host, task->repo_id, delta); int curl_error; if (http_get (curl, url, task->token, &status, NULL, NULL, NULL, NULL, TRUE, HTTP_TIMEOUT_SEC, &curl_error) < 0) { conn->release = TRUE; handle_curl_errors (task, curl_error); ret = -1; goto out; } if (status != HTTP_OK) { seaf_warning ("Bad response code for GET %s: %d.\n", url, status); handle_http_errors (task, status); ret = -1; } out: g_free (url); curl_easy_reset (curl); return ret; } static int send_commit_object (HttpTxTask *task, Connection *conn) { CURL *curl; char *url; int status; char *data; int len; int ret = 0; if (seaf_obj_store_read_obj (seaf->commit_mgr->obj_store, task->repo_id, task->repo_version, task->head, (void**)&data, &len) < 0) { seaf_warning ("Failed to read commit %s.\n", task->head); task->error = HTTP_TASK_ERR_BAD_LOCAL_DATA; return -1; } curl = conn->curl; if (!task->use_fileserver_port) url = g_strdup_printf ("%s/seafhttp/repo/%s/commit/%s", task->host, task->repo_id, task->head); else url = g_strdup_printf ("%s/repo/%s/commit/%s", task->host, task->repo_id, task->head); int curl_error; if (http_put (curl, url, task->token, data, len, NULL, NULL, &status, NULL, NULL, TRUE, &curl_error) < 0) { conn->release = TRUE; handle_curl_errors (task, curl_error); ret = -1; goto out; } if (status != HTTP_OK) { seaf_warning ("Bad response code for PUT %s: %d.\n", url, status); handle_http_errors (task, status); ret = -1; } out: g_free (url); curl_easy_reset (curl); g_free (data); return ret; } typedef struct { GList **pret; GHashTable *checked_objs; GHashTable *needed_file_pair; GHashTable *deleted_file_pair; } CalcFsListData; static int collect_file_ids (int n, const char *basedir, SeafDirent *files[], void *vdata) { SeafDirent *file1 = files[0]; SeafDirent *file2 = files[1]; CalcFsListData *data = vdata; GList **pret = data->pret; int dummy; if (!file1 && file2) { g_hash_table_replace (data->deleted_file_pair, g_strdup(file2->id), g_strconcat(basedir, file2->name, NULL)); return 0; } if (strcmp (file1->id, EMPTY_SHA1) == 0) return 0; if (g_hash_table_lookup (data->checked_objs, file1->id)) return 0; if (!file2 || strcmp (file1->id, file2->id) != 0) { *pret = g_list_prepend (*pret, g_strdup(file1->id)); g_hash_table_insert (data->checked_objs, g_strdup(file1->id), &dummy); g_hash_table_replace (data->needed_file_pair, g_strdup(file1->id), g_strconcat (basedir, file1->name, NULL)); } return 0; } static int collect_dir_ids (int n, const char *basedir, SeafDirent *dirs[], void *vdata, gboolean *recurse) { SeafDirent *dir1 = dirs[0]; SeafDirent *dir2 = dirs[1]; CalcFsListData *data = vdata; GList **pret = data->pret; int dummy; if (!dir1 || strcmp (dir1->id, EMPTY_SHA1) == 0) return 0; if (g_hash_table_lookup (data->checked_objs, dir1->id)) return 0; if (!dir2 || strcmp (dir1->id, dir2->id) != 0) { *pret = g_list_prepend (*pret, g_strdup(dir1->id)); g_hash_table_insert (data->checked_objs, g_strdup(dir1->id), &dummy); } return 0; } static gboolean remove_renamed_ids (gpointer key, gpointer value, gpointer user_data) { char *file_id = key; GHashTable *deleted_ids = user_data; if (g_hash_table_lookup (deleted_ids, file_id)) return TRUE; else return FALSE; } static GList * calculate_send_fs_object_list (HttpTxTask *task, GHashTable *needed_file_pair) { GList *ret = NULL; SeafBranch *local = NULL, *master = NULL; SeafCommit *local_head = NULL, *master_head = NULL; GList *ptr; local = seaf_branch_manager_get_branch (seaf->branch_mgr, task->repo_id, "local"); if (!local) { seaf_warning ("Branch local not found for repo %.8s.\n", task->repo_id); goto out; } master = seaf_branch_manager_get_branch (seaf->branch_mgr, task->repo_id, "master"); if (!master) { seaf_warning ("Branch master not found for repo %.8s.\n", task->repo_id); goto out; } local_head = seaf_commit_manager_get_commit (seaf->commit_mgr, task->repo_id, task->repo_version, local->commit_id); if (!local_head) { seaf_warning ("Local head commit not found for repo %.8s.\n", task->repo_id); goto out; } master_head = seaf_commit_manager_get_commit (seaf->commit_mgr, task->repo_id, task->repo_version, master->commit_id); if (!master_head) { seaf_warning ("Master head commit not found for repo %.8s.\n", task->repo_id); goto out; } /* Diff won't traverse the root object itself. */ if (strcmp (local_head->root_id, master_head->root_id) != 0) ret = g_list_prepend (ret, g_strdup(local_head->root_id)); CalcFsListData *data = g_new0(CalcFsListData, 1); data->pret = &ret; data->checked_objs = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); data->needed_file_pair = needed_file_pair; data->deleted_file_pair = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); DiffOptions opts; memset (&opts, 0, sizeof(opts)); memcpy (opts.store_id, task->repo_id, 36); opts.version = task->repo_version; opts.file_cb = collect_file_ids; opts.dir_cb = collect_dir_ids; opts.data = data; const char *trees[2]; trees[0] = local_head->root_id; trees[1] = master_head->root_id; if (diff_trees (2, trees, &opts) < 0) { seaf_warning ("Failed to diff local and master head for repo %.8s.\n", task->repo_id); for (ptr = ret; ptr; ptr = ptr->next) g_free (ptr->data); ret = NULL; } g_hash_table_foreach_remove (needed_file_pair, remove_renamed_ids, data->deleted_file_pair); g_hash_table_destroy (data->checked_objs); g_hash_table_destroy (data->deleted_file_pair); g_free (data); out: seaf_branch_unref (local); seaf_branch_unref (master); seaf_commit_unref (local_head); seaf_commit_unref (master_head); return ret; } #define ID_LIST_SEGMENT_N 1000 static int upload_check_id_list_segment (HttpTxTask *task, Connection *conn, const char *url, GList **send_id_list, GList **recv_id_list) { json_t *array; json_error_t jerror; char *obj_id; int n_sent = 0; char *data = NULL; int len; CURL *curl; int status; char *rsp_content = NULL; gint64 rsp_size; int ret = 0; /* Convert object id list to JSON format. */ array = json_array (); while (*send_id_list != NULL) { obj_id = (*send_id_list)->data; json_array_append_new (array, json_string(obj_id)); *send_id_list = g_list_delete_link (*send_id_list, *send_id_list); g_free (obj_id); if (++n_sent >= ID_LIST_SEGMENT_N) break; } seaf_debug ("Check %d ids for %s:%s.\n", n_sent, task->host, task->repo_id); data = json_dumps (array, 0); len = strlen(data); json_decref (array); /* Send fs object id list. */ curl = conn->curl; int curl_error; if (http_post (curl, url, task->token, data, len, &status, &rsp_content, &rsp_size, TRUE, HTTP_TIMEOUT_SEC, &curl_error) < 0) { conn->release = TRUE; handle_curl_errors (task, curl_error); ret = -1; goto out; } if (status != HTTP_OK) { seaf_warning ("Bad response code for POST %s: %d.\n", url, status); handle_http_errors (task, status); ret = -1; goto out; } /* Process needed object id list. */ array = json_loadb (rsp_content, rsp_size, 0, &jerror); if (!array) { seaf_warning ("Invalid JSON response from the server: %s.\n", jerror.text); task->error = HTTP_TASK_ERR_SERVER; ret = -1; goto out; } int i; size_t n = json_array_size (array); json_t *str; seaf_debug ("%lu objects or blocks are needed for %s:%s.\n", n, task->host, task->repo_id); for (i = 0; i < n; ++i) { str = json_array_get (array, i); if (!str) { seaf_warning ("Invalid JSON response from the server.\n"); json_decref (array); ret = -1; goto out; } *recv_id_list = g_list_prepend (*recv_id_list, g_strdup(json_string_value(str))); } json_decref (array); out: curl_easy_reset (curl); g_free (data); g_free (rsp_content); return ret; } #define MAX_OBJECT_PACK_SIZE (1 << 20) /* 1MB */ struct ObjectHeader { char obj_id[40]; guint32 obj_size; guint8 object[0]; } __attribute__((__packed__)); typedef struct ObjectHeader ObjectHeader; static int send_fs_objects (HttpTxTask *task, Connection *conn, GList **send_fs_list) { struct evbuffer *buf; ObjectHeader hdr; char *obj_id; char *data; int len; int total_size; unsigned char *package; CURL *curl; char *url = NULL; int status; int ret = 0; int n_sent = 0; buf = evbuffer_new (); curl = conn->curl; while (*send_fs_list != NULL) { obj_id = (*send_fs_list)->data; if (seaf_obj_store_read_obj (seaf->fs_mgr->obj_store, task->repo_id, task->repo_version, obj_id, (void **)&data, &len) < 0) { seaf_warning ("Failed to read fs object %s in repo %s.\n", obj_id, task->repo_id); task->error = HTTP_TASK_ERR_BAD_LOCAL_DATA; ret = -1; goto out; } ++n_sent; memcpy (hdr.obj_id, obj_id, 40); hdr.obj_size = htonl (len); evbuffer_add (buf, &hdr, sizeof(hdr)); evbuffer_add (buf, data, len); g_free (data); *send_fs_list = g_list_delete_link (*send_fs_list, *send_fs_list); g_free (obj_id); total_size = evbuffer_get_length (buf); if (total_size >= MAX_OBJECT_PACK_SIZE) break; } seaf_debug ("Sending %d fs objects for %s:%s.\n", n_sent, task->host, task->repo_id); package = evbuffer_pullup (buf, -1); if (!task->use_fileserver_port) url = g_strdup_printf ("%s/seafhttp/repo/%s/recv-fs/", task->host, task->repo_id); else url = g_strdup_printf ("%s/repo/%s/recv-fs/", task->host, task->repo_id); int curl_error; if (http_post (curl, url, task->token, (char *)package, evbuffer_get_length(buf), &status, NULL, NULL, TRUE, HTTP_TIMEOUT_SEC, &curl_error) < 0) { conn->release = TRUE; handle_curl_errors (task, curl_error); ret = -1; goto out; } if (status != HTTP_OK) { seaf_warning ("Bad response code for POST %s: %d.\n", url, status); handle_http_errors (task, status); ret = -1; } out: g_free (url); evbuffer_free (buf); curl_easy_reset (curl); return ret; } #if 0 static void add_to_block_list (GList **block_list, GHashTable *added_blocks, const char *block_id) { int dummy; if (g_hash_table_lookup (added_blocks, block_id)) return; *block_list = g_list_prepend (*block_list, g_strdup(block_id)); g_hash_table_insert (added_blocks, g_strdup(block_id), &dummy); } static int add_one_file_to_block_list (const char *repo_id, int repo_version, const char *path, unsigned char *sha1, GList **block_list, GHashTable *added_blocks, GHashTable *needed_fs) { char file_id1[41]; Seafile *f1 = NULL; int i; rawdata_to_hex (sha1, file_id1, 20); if (!g_hash_table_lookup (needed_fs, file_id1)) return 0; f1 = seaf_fs_manager_get_seafile (seaf->fs_mgr, repo_id, repo_version, file_id1); if (!f1) { seaf_warning ("Failed to get seafile object %s:%s. Path is %s.\n", repo_id, file_id1, path); return -1; } for (i = 0; i < f1->n_blocks; ++i) add_to_block_list (block_list, added_blocks, f1->blk_sha1s[i]); seafile_unref (f1); return 0; } static int diff_two_files_to_block_list (const char *repo_id, int repo_version, const char *path, unsigned char *sha1, unsigned char *old_sha1, GList **block_list, GHashTable *added_blocks) { Seafile *f1 = NULL, *f2 = NULL; char file_id1[41], file_id2[41]; int i; rawdata_to_hex (sha1, file_id1, 20); rawdata_to_hex (old_sha1, file_id2, 20); f1 = seaf_fs_manager_get_seafile (seaf->fs_mgr, repo_id, repo_version, file_id1); if (!f1) { seaf_warning ("Failed to get seafile object %s:%s. Path is %s.\n", repo_id, file_id1, path); return -1; } f2 = seaf_fs_manager_get_seafile (seaf->fs_mgr, repo_id, repo_version, file_id2); if (!f2) { seafile_unref (f1); seaf_warning ("Failed to get seafile object %s:%s. Path is %s.\n", repo_id, file_id2, path); return -1; } GHashTable *h = g_hash_table_new (g_str_hash, g_str_equal); int dummy; for (i = 0; i < f2->n_blocks; ++i) g_hash_table_insert (h, f2->blk_sha1s[i], &dummy); for (i = 0; i < f1->n_blocks; ++i) if (!g_hash_table_lookup (h, f1->blk_sha1s[i])) add_to_block_list (block_list, added_blocks, f1->blk_sha1s[i]); seafile_unref (f1); seafile_unref (f2); g_hash_table_destroy (h); return 0; } typedef struct ExpandAddedDirData { char repo_id[41]; int repo_version; GList **block_list; GHashTable *added_blocks; GHashTable *needed_fs; } ExpandAddedDirData; static gboolean expand_dir_added_cb (SeafFSManager *mgr, const char *path, SeafDirent *dent, void *user_data, gboolean *stop) { ExpandAddedDirData *data = user_data; unsigned char sha1[20]; if (S_ISREG(dent->mode)) { hex_to_rawdata (dent->id, sha1, 20); if (add_one_file_to_block_list (data->repo_id, data->repo_version, dent->name, sha1, data->block_list, data->added_blocks, data->needed_fs) < 0) return FALSE; } return TRUE; } static int add_new_dir_to_block_list (const char *repo_id, int version, const char *root, DiffEntry *de, GList **block_list, GHashTable *added_blocks, GHashTable *needed_fs) { ExpandAddedDirData data; char obj_id[41]; memcpy (data.repo_id, repo_id, 40); data.repo_version = version; data.block_list = block_list; data.added_blocks = added_blocks; data.needed_fs = needed_fs; rawdata_to_hex (de->sha1, obj_id, 20); if (seaf_fs_manager_traverse_path (seaf->fs_mgr, repo_id, version, root, de->name, expand_dir_added_cb, &data) < 0) { return -1; } return 0; } static int calculate_block_list_from_diff_results (HttpTxTask *task, const char *local_root, GHashTable *needed_fs, GList *results, GList **block_list) { GHashTable *added_blocks; GList *ptr; DiffEntry *de; int ret = 0; added_blocks = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); for (ptr = results; ptr; ptr = ptr->next) { de = ptr->data; if (de->status == DIFF_STATUS_ADDED) { ret = add_one_file_to_block_list (task->repo_id, task->repo_version, de->name, de->sha1, block_list, added_blocks, needed_fs); if (ret < 0) break; } else if (de->status == DIFF_STATUS_MODIFIED) { ret = diff_two_files_to_block_list (task->repo_id, task->repo_version, de->name, de->sha1, de->old_sha1, block_list, added_blocks); if (ret < 0) break; } else if (de->status == DIFF_STATUS_DIR_ADDED) { ret = add_new_dir_to_block_list (task->repo_id, task->repo_version, local_root, de, block_list, added_blocks, needed_fs); if (ret < 0) break; } } g_hash_table_destroy (added_blocks); return ret; } static int calculate_block_list (HttpTxTask *task, GList **plist, GHashTable *needed_fs) { int ret = 0; SeafBranch *local = NULL, *master = NULL; SeafCommit *local_head = NULL, *master_head = NULL; GList *results = NULL; GList *block_list = NULL; local = seaf_branch_manager_get_branch (seaf->branch_mgr, task->repo_id, "local"); if (!local) { seaf_warning ("Branch local not found for repo %.8s.\n", task->repo_id); ret = -1; goto out; } master = seaf_branch_manager_get_branch (seaf->branch_mgr, task->repo_id, "master"); if (!master) { seaf_warning ("Branch master not found for repo %.8s.\n", task->repo_id); ret = -1; goto out; } local_head = seaf_commit_manager_get_commit (seaf->commit_mgr, task->repo_id, task->repo_version, local->commit_id); if (!local_head) { seaf_warning ("Local head commit not found for repo %.8s.\n", task->repo_id); ret = -1; goto out; } master_head = seaf_commit_manager_get_commit (seaf->commit_mgr, task->repo_id, task->repo_version, master->commit_id); if (!master_head) { seaf_warning ("Master head commit not found for repo %.8s.\n", task->repo_id); ret = -1; goto out; } if (diff_commit_roots (task->repo_id, task->repo_version, master_head->root_id, local_head->root_id, &results, TRUE) < 0) { seaf_warning ("Failed to diff local and master heads for repo %.8s.\n", task->repo_id); ret = -1; goto out; } if (calculate_block_list_from_diff_results (task, local_head->root_id, needed_fs, results, &block_list) < 0) { ret = -1; goto out; } *plist = block_list; out: seaf_branch_unref (local); seaf_branch_unref (master); seaf_commit_unref (local_head); seaf_commit_unref (master_head); g_list_free_full (results, (GDestroyNotify)diff_entry_free); if (ret < 0) g_list_free_full (block_list, g_free); return ret; } #endif /* 0 */ typedef struct FileUploadProgress { char *server; char *user; guint64 uploaded; guint64 total_upload; } FileUploadProgress; typedef struct FileUploadedInfo { char *server; char *user; char *file_path; } FileUploadedInfo; typedef struct { char block_id[41]; BlockHandle *block; HttpTxTask *task; FileUploadProgress *progress; } SendBlockData; static FileUploadedInfo * file_uploaded_info_new (const char *server, const char *user, const char *file_path) { FileUploadedInfo *info = g_new0 (FileUploadedInfo, 1); info->server = g_strdup (server); info->user = g_strdup (user); info->file_path = g_strdup (file_path); return info; } static void file_uploaded_info_free (FileUploadedInfo *info) { if (!info) return; g_free (info->server); g_free (info->user); g_free (info->file_path); g_free (info); return; } static size_t send_block_callback (void *ptr, size_t size, size_t nmemb, void *userp) { size_t realsize = size *nmemb; SendBlockData *data = userp; HttpTxTask *task = data->task; int n; if (task->state == HTTP_TASK_STATE_CANCELED || task->all_stop) return CURL_READFUNC_ABORT; n = seaf_block_manager_read_block (seaf->block_mgr, data->block, ptr, realsize); if (n < 0) { seaf_warning ("Failed to read block %s in repo %.8s.\n", data->block_id, task->repo_id); task->error = HTTP_TASK_ERR_BAD_LOCAL_DATA; return CURL_READFUNC_ABORT; } /* Update global transferred bytes. */ g_atomic_int_add (&(seaf->sync_mgr->sent_bytes), n); /* Update transferred bytes for this task */ g_atomic_int_add (&task->tx_bytes, n); data->progress->uploaded += n; /* If uploaded bytes exceeds the limit, wait until the counter * is reset. We check the counter every 100 milliseconds, so we * can waste up to 100 milliseconds without sending data after * the counter is reset. */ while (1) { gint sent = g_atomic_int_get(&(seaf->sync_mgr->sent_bytes)); if (seaf->sync_mgr->upload_limit > 0 && sent > seaf->sync_mgr->upload_limit) /* 100 milliseconds */ g_usleep (100000); else break; } return n; } static int send_block (HttpTxTask *task, Connection *conn, const char *block_id, uint32_t size, FileUploadProgress *progress) { CURL *curl; char *url; int status; BlockHandle *block; int ret = 0; block = seaf_block_manager_open_block (seaf->block_mgr, task->repo_id, task->repo_version, block_id, BLOCK_READ); if (!block) { seaf_warning ("Failed to open block %s in repo %s.\n", block_id, task->repo_id); return -1; } SendBlockData data; memset (&data, 0, sizeof(data)); memcpy (data.block_id, block_id, 40); data.block = block; data.task = task; data.progress = progress; curl = conn->curl; if (!task->use_fileserver_port) url = g_strdup_printf ("%s/seafhttp/repo/%s/block/%s", task->host, task->repo_id, block_id); else url = g_strdup_printf ("%s/repo/%s/block/%s", task->host, task->repo_id, block_id); int curl_error; if (http_put (curl, url, task->token, NULL, size, send_block_callback, &data, &status, NULL, NULL, TRUE, &curl_error) < 0) { if (task->state == HTTP_TASK_STATE_CANCELED) goto out; if (task->error == HTTP_TASK_OK) { /* Only release connection when it's a network error */ conn->release = TRUE; handle_curl_errors (task, curl_error); } ret = -1; goto out; } if (status != HTTP_OK) { seaf_warning ("Bad response code for PUT %s: %d.\n", url, status); handle_http_errors (task, status); ret = -1; } out: g_free (url); curl_easy_reset (curl); seaf_block_manager_close_block (seaf->block_mgr, block); seaf_block_manager_block_handle_free (seaf->block_mgr, block); return ret; } gboolean get_needed_block_list (Connection *conn, HttpTxTask *task, char *url, Seafile *file, GList **block_list) { int i, j; int check_num = 0; json_t *check_list = json_array (); char *check_list_str; json_t *ret_list; json_error_t jerror; int status; char *rsp_content = NULL; gint64 rsp_size; const char *block_id; gboolean ret = TRUE; int curl_error; CURL *curl = conn->curl; for (i = 0; i < file->n_blocks; i++) { json_array_append_new (check_list, json_string (file->blk_sha1s[i])); check_num++; if (check_num == ID_LIST_SEGMENT_N || i == file->n_blocks - 1) { check_list_str = json_dumps (check_list, JSON_COMPACT); curl_error = 0; if (http_post (curl, url, task->token, check_list_str, strlen(check_list_str), &status, &rsp_content, &rsp_size, TRUE, HTTP_TIMEOUT_SEC, &curl_error) < 0) { conn->release = TRUE; handle_curl_errors (task, curl_error); g_free (check_list_str); ret = FALSE; goto out; } g_free (check_list_str); if (status != HTTP_OK) { seaf_warning ("Bad response code for POST %s: %d.\n", url, status); handle_http_errors (task, status); g_free (rsp_content); ret = FALSE; goto out; } ret_list = json_loadb (rsp_content, rsp_size, 0, &jerror); if (!ret_list) { seaf_warning ("Invalid JSON response from the server: %s.\n", jerror.text); task->error = HTTP_TASK_ERR_SERVER; g_free (rsp_content); ret = FALSE; goto out; } g_free (rsp_content); for (j = 0; j < json_array_size (ret_list); j++) { block_id = json_string_value (json_array_get (ret_list, j)); if (!block_id || strlen (block_id) != 40) { seaf_warning ("Invalid block_id list returned from server. " "Bad block id %s\n", block_id); task->error = HTTP_TASK_ERR_SERVER; json_decref (ret_list); ret = FALSE; goto out; } *block_list = g_list_prepend (*block_list, g_strdup (block_id)); } json_decref (ret_list); rsp_content = NULL; check_num = 0; json_array_clear (check_list); } } out: curl_easy_reset (curl); json_decref (check_list); return ret; } static int upload_file (HttpTxTask *http_task, Connection *conn, const char *file_path, guint64 file_size, GList *block_list) { HttpTxPriv *priv = seaf->http_tx_mgr->priv; FileUploadProgress progress; GHashTable *block_size_pair; char *abs_path; GList *iter; char *block_id; BlockMetadata *meta; guint64 upload_size = 0; int ret = 0; abs_path = g_build_filename (http_task->repo_uname, file_path, NULL); if (!block_list && file_size == 0) { FileUploadedInfo *file_info = file_uploaded_info_new (http_task->server, http_task->user, abs_path); pthread_mutex_lock (&priv->progress_lock); g_queue_push_head (priv->uploaded_files, file_info); if (priv->uploaded_files->length > MAX_GET_FINISHED_FILES) file_uploaded_info_free (g_queue_pop_tail (priv->uploaded_files)); pthread_mutex_unlock (&priv->progress_lock); g_free (abs_path); return 0; } else if (!block_list) { g_free (abs_path); return 0; } progress.server = http_task->server; progress.user = http_task->user; progress.uploaded = 0; progress.total_upload = file_size; block_size_pair = g_hash_table_new (g_str_hash, g_str_equal); // Collect file total upload size for (iter = block_list; iter; iter = iter->next) { block_id = iter->data; meta = seaf_block_manager_stat_block (seaf->block_mgr, http_task->repo_id, http_task->repo_version, block_id); if (!meta) { seaf_warning ("Failed to stat block %s for file %s in repo %s.\n", block_id, file_path, http_task->repo_id); http_task->error = HTTP_TASK_ERR_BAD_LOCAL_DATA; ret = -1; goto out; } upload_size += meta->size; g_hash_table_replace (block_size_pair, block_id, (void *)meta->size); g_free (meta); } progress.uploaded = file_size - upload_size; // Update upload progress info pthread_mutex_lock (&priv->progress_lock); g_hash_table_replace (priv->uploading_files, g_strdup (abs_path), &progress); pthread_mutex_unlock (&priv->progress_lock); int n = 0; for (iter = block_list; iter; iter = iter->next) { block_id = iter->data; if (send_block (http_task, conn, block_id, (uint32_t)g_hash_table_lookup (block_size_pair, block_id), &progress) < 0) { ret = -1; break; } ++n; } seaf_debug ("Uploaded file %s, %d blocks.\n", file_path, n); // Update upload progress info pthread_mutex_lock (&priv->progress_lock); g_hash_table_remove (priv->uploading_files, abs_path); if (ret == 0) { FileUploadedInfo *file_info = file_uploaded_info_new (http_task->server, http_task->user, abs_path); g_queue_push_head (priv->uploaded_files, file_info); if (priv->uploaded_files->length > MAX_GET_FINISHED_FILES) file_uploaded_info_free (g_queue_pop_tail (priv->uploaded_files)); } pthread_mutex_unlock (&priv->progress_lock); out: g_hash_table_destroy (block_size_pair); g_free (abs_path); return ret; } typedef struct FileUploadData { HttpTxTask *http_task; char *check_block_url; ConnectionPool *cpool; GAsyncQueue *finished_tasks; } FileUploadData; typedef struct FileUploadTask { char *file_id; char *file_path; int result; } FileUploadTask; static void upload_file_thread_func (gpointer data, gpointer user_data) { FileUploadTask *task = data; FileUploadData *tx_data = user_data; HttpTxTask *http_task = tx_data->http_task; Connection *conn; Seafile *file = NULL; GList *block_list = NULL; int ret = 0; conn = connection_pool_get_connection (tx_data->cpool); if (!conn) { seaf_warning ("Failed to get connection to host %s.\n", http_task->host); ret = -1; http_task->error = HTTP_TASK_ERR_NOT_ENOUGH_MEMORY; goto out; } file = seaf_fs_manager_get_seafile (seaf->fs_mgr, http_task->repo_id, http_task->repo_version, task->file_id); if (!file) { seaf_warning ("Failed to get file %s in repo %.8s.\n", task->file_id, http_task->repo_id); http_task->error = HTTP_TASK_ERR_BAD_LOCAL_DATA; ret = -1; goto out; } if (!get_needed_block_list (conn, http_task, tx_data->check_block_url, file, &block_list)) { seaf_warning ("Failed to get needed block list for file %s\n", task->file_path); ret = -1; goto out; } ret = upload_file (http_task, conn, task->file_path, file->file_size, block_list); g_list_free_full (block_list, (GDestroyNotify)g_free); out: task->result = ret; g_async_queue_push (tx_data->finished_tasks, task); if (file) seafile_unref (file); if (conn) connection_pool_return_connection (tx_data->cpool, conn); } #define DEFAULT_UPLOAD_BLOCK_THREADS 3 static int multi_threaded_send_files (HttpTxTask *http_task, GHashTable *needed_file_pair) { HttpTxPriv *priv = seaf->http_tx_mgr->priv; GThreadPool *tpool; GAsyncQueue *finished_tasks; GHashTable *pending_tasks; char *check_block_url; ConnectionPool *cpool; GHashTableIter iter; gpointer key; gpointer value; FileUploadTask *task; int ret = 0; // No file need to be uploaded, return directly if (g_hash_table_size (needed_file_pair) == 0) { return 0; } cpool = find_connection_pool (priv, http_task->host); if (!cpool) { seaf_warning ("Failed to create connection pool for host %s.\n", http_task->host); http_task->error = HTTP_TASK_ERR_NOT_ENOUGH_MEMORY; return -1; } if (!http_task->use_fileserver_port) check_block_url = g_strdup_printf ("%s/seafhttp/repo/%s/check-blocks/", http_task->host, http_task->repo_id); else check_block_url = g_strdup_printf ("%s/repo/%s/check-blocks/", http_task->host, http_task->repo_id); finished_tasks = g_async_queue_new (); FileUploadData data; data.http_task = http_task; data.finished_tasks = finished_tasks; data.check_block_url = check_block_url; data.cpool = cpool; tpool = g_thread_pool_new (upload_file_thread_func, &data, DEFAULT_UPLOAD_BLOCK_THREADS, FALSE, NULL); pending_tasks = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify)g_free); g_hash_table_iter_init (&iter, needed_file_pair); while (g_hash_table_iter_next (&iter, &key, &value)) { task = g_new0 (FileUploadTask, 1); task->file_id = (char *)key; task->file_path = (char *)value; g_hash_table_insert (pending_tasks, task->file_id, task); g_thread_pool_push (tpool, task, NULL); } while ((task = g_async_queue_pop (finished_tasks)) != NULL) { if (task->result < 0 || http_task->state == HTTP_TASK_STATE_CANCELED) { ret = task->result; http_task->all_stop = TRUE; break; } g_hash_table_remove (pending_tasks, task->file_id); if (g_hash_table_size(pending_tasks) == 0) break; } g_thread_pool_free (tpool, TRUE, TRUE); g_hash_table_destroy (pending_tasks); g_async_queue_unref (finished_tasks); g_free (check_block_url); return ret; } static void notify_permission_error (HttpTxTask *task, const char *error_str) { HttpTxPriv *priv = seaf->http_tx_mgr->priv; GMatchInfo *match_info; char *path; if (g_regex_match (priv->locked_error_regex, error_str, 0, &match_info)) { path = g_match_info_fetch (match_info, 1); task->unsyncable_path = path; /* Set more accurate error. */ task->error = HTTP_TASK_ERR_FILE_LOCKED; } else if (g_regex_match (priv->folder_perm_error_regex, error_str, 0, &match_info)) { path = g_match_info_fetch (match_info, 1); task->unsyncable_path = path; task->error = HTTP_TASK_ERR_FOLDER_PERM_DENIED; } else if (g_regex_match (priv->too_many_files_error_regex, error_str, 0, &match_info)) { task->error = HTTP_TASK_ERR_TOO_MANY_FILES; } g_match_info_free (match_info); } static int update_branch (HttpTxTask *task, Connection *conn) { CURL *curl; char *url; int status; int ret = 0; char *rsp_content; char *rsp_content_str = NULL; gint64 rsp_size; curl = conn->curl; if (!task->use_fileserver_port) url = g_strdup_printf ("%s/seafhttp/repo/%s/commit/HEAD/?head=%s", task->host, task->repo_id, task->head); else url = g_strdup_printf ("%s/repo/%s/commit/HEAD/?head=%s", task->host, task->repo_id, task->head); int curl_error; if (http_put (curl, url, task->token, NULL, 0, NULL, NULL, &status, &rsp_content, &rsp_size, TRUE, &curl_error) < 0) { conn->release = TRUE; handle_curl_errors (task, curl_error); ret = -1; goto out; } if (status != HTTP_OK) { seaf_warning ("Bad response code for PUT %s: %d.\n", url, status); handle_http_errors (task, status); if (status == HTTP_FORBIDDEN || status == HTTP_BLOCK_MISSING) { rsp_content_str = g_new0 (gchar, rsp_size + 1); memcpy (rsp_content_str, rsp_content, rsp_size); if (status == HTTP_FORBIDDEN) { seaf_warning ("%s\n", rsp_content_str); notify_permission_error (task, rsp_content_str); } else if (status == HTTP_BLOCK_MISSING) { seaf_warning ("Failed to upload files: %s\n", rsp_content_str); task->error = HTTP_TASK_ERR_BLOCK_MISSING; } g_free (rsp_content_str); } ret = -1; } out: g_free (url); curl_easy_reset (curl); return ret; } static int update_master_branch (HttpTxTask *task) { SeafBranch *master = NULL, *local = NULL; int ret = 0; local = seaf_branch_manager_get_branch (seaf->branch_mgr, task->repo_id, "local"); if (!local) { seaf_warning ("Failed to get local branch for repo %s.\n", task->repo_id); return -1; } master = seaf_branch_manager_get_branch (seaf->branch_mgr, task->repo_id, "master"); if (!master) { master = seaf_branch_new ("master", task->repo_id, task->head, 0); if (seaf_branch_manager_add_branch (seaf->branch_mgr, master) < 0) { ret = -1; goto out; } } else { seaf_branch_set_commit (master, task->head); master->opid = local->opid; if (seaf_branch_manager_update_branch (seaf->branch_mgr, master) < 0) { ret = -1; goto out; } } /* The locally cache repo status from server need to be updated too. * Otherwise sync-mgr will find the master head and server head are different, * then start a download. */ seaf_repo_manager_set_repo_info_head_commit (seaf->repo_mgr, task->repo_id, task->head); out: seaf_branch_unref (master); seaf_branch_unref (local); return ret; } static void set_path_status_syncing (gpointer key, gpointer value, gpointer user_data) { HttpTxTask *task = user_data; char *path = key; int mode = (int)(long)value; seaf_sync_manager_update_active_path (seaf->sync_mgr, task->repo_id, path, mode, SYNC_STATUS_SYNCING); } static void set_path_status_synced (gpointer key, gpointer value, gpointer user_data) { HttpTxTask *task = user_data; char *path = key; int mode = (int)(long)value; seaf_sync_manager_update_active_path (seaf->sync_mgr, task->repo_id, path, mode, SYNC_STATUS_SYNCED); if (S_ISREG(mode)) { file_cache_mgr_set_file_uploaded (seaf->file_cache_mgr, task->repo_id, path, TRUE); } } static void * http_upload_thread (void *vdata) { HttpTxTask *task = vdata; HttpTxPriv *priv = seaf->http_tx_mgr->priv; ConnectionPool *pool; Connection *conn = NULL; char *url = NULL; GList *send_fs_list = NULL, *needed_fs_list = NULL; GHashTable *active_paths = NULL; // file_id <-> file_path GHashTable *need_upload_file_pair = NULL; SeafBranch *local = seaf_branch_manager_get_branch (seaf->branch_mgr, task->repo_id, "local"); if (!local) { seaf_warning ("Failed to get branch local of repo %.8s.\n", task->repo_id); task->error = HTTP_TASK_ERR_BAD_LOCAL_DATA; return vdata; } memcpy (task->head, local->commit_id, 40); seaf_branch_unref (local); pool = find_connection_pool (priv, task->host); if (!pool) { seaf_warning ("Failed to create connection pool for host %s.\n", task->host); task->error = HTTP_TASK_ERR_NOT_ENOUGH_MEMORY; goto out; } conn = connection_pool_get_connection (pool); if (!conn) { seaf_warning ("Failed to get connection to host %s.\n", task->host); task->error = HTTP_TASK_ERR_NOT_ENOUGH_MEMORY; goto out; } seaf_message ("Upload with HTTP sync protocol version %d.\n", task->protocol_version); transition_state (task, task->state, HTTP_TASK_RT_STATE_CHECK); gint64 delta = 0; active_paths = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); if (calculate_upload_size_delta_and_active_paths (task, &delta, active_paths) < 0) { seaf_warning ("Failed to calculate upload size delta for repo %s.\n", task->repo_id); task->error = HTTP_TASK_ERR_BAD_LOCAL_DATA; goto out; } g_hash_table_foreach (active_paths, set_path_status_syncing, task); if (check_permission (task, conn) < 0) { seaf_warning ("Upload permission denied for repo %.8s on server %s.\n", task->repo_id, task->host); goto out; } if (check_quota (task, conn, delta) < 0) { seaf_warning ("Not enough quota for repo %.8s on server %s.\n", task->repo_id, task->host); goto out; } if (task->state == HTTP_TASK_STATE_CANCELED) goto out; transition_state (task, task->state, HTTP_TASK_RT_STATE_COMMIT); if (send_commit_object (task, conn) < 0) { seaf_warning ("Failed to send head commit for repo %.8s.\n", task->repo_id); goto out; } if (task->state == HTTP_TASK_STATE_CANCELED) goto out; transition_state (task, task->state, HTTP_TASK_RT_STATE_FS); need_upload_file_pair = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); send_fs_list = calculate_send_fs_object_list (task, need_upload_file_pair); if (!send_fs_list) { seaf_warning ("Failed to calculate fs object list for repo %.8s.\n", task->repo_id); task->error = HTTP_TASK_ERR_BAD_LOCAL_DATA; goto out; } if (!task->use_fileserver_port) url = g_strdup_printf ("%s/seafhttp/repo/%s/check-fs/", task->host, task->repo_id); else url = g_strdup_printf ("%s/repo/%s/check-fs/", task->host, task->repo_id); while (send_fs_list != NULL) { if (upload_check_id_list_segment (task, conn, url, &send_fs_list, &needed_fs_list) < 0) { seaf_warning ("Failed to check fs list for repo %.8s.\n", task->repo_id); goto out; } if (task->state == HTTP_TASK_STATE_CANCELED) goto out; } g_free (url); url = NULL; while (needed_fs_list != NULL) { if (send_fs_objects (task, conn, &needed_fs_list) < 0) { seaf_warning ("Failed to send fs objects for repo %.8s.\n", task->repo_id); goto out; } if (task->state == HTTP_TASK_STATE_CANCELED) goto out; } transition_state (task, task->state, HTTP_TASK_RT_STATE_BLOCK); if (multi_threaded_send_files(task, need_upload_file_pair) < 0 || task->state == HTTP_TASK_STATE_CANCELED) goto out; transition_state (task, task->state, HTTP_TASK_RT_STATE_UPDATE_BRANCH); if (update_branch (task, conn) < 0) { seaf_warning ("Failed to update branch of repo %.8s.\n", task->repo_id); goto out; } /* After successful upload, the cached 'master' branch should be updated to * the head commit of 'local' branch. */ if (update_master_branch (task) < 0) { task->error = HTTP_TASK_ERR_BAD_LOCAL_DATA; goto out; } if (active_paths != NULL) g_hash_table_foreach (active_paths, set_path_status_synced, task); out: string_list_free (send_fs_list); string_list_free (needed_fs_list); if (active_paths) g_hash_table_destroy (active_paths); if (need_upload_file_pair) g_hash_table_destroy (need_upload_file_pair); connection_pool_return_connection (pool, conn); return vdata; } static void http_upload_done (void *vdata) { HttpTxTask *task = vdata; if (task->error != HTTP_TASK_OK) transition_state (task, HTTP_TASK_STATE_ERROR, HTTP_TASK_RT_STATE_FINISHED); else if (task->state == HTTP_TASK_STATE_CANCELED) transition_state (task, task->state, HTTP_TASK_RT_STATE_FINISHED); else transition_state (task, HTTP_TASK_STATE_FINISHED, HTTP_TASK_RT_STATE_FINISHED); } /* Download */ static void *http_download_thread (void *vdata); static void http_download_done (void *vdata); /* static void notify_conflict (CEvent *event, void *data); */ int http_tx_manager_add_download (HttpTxManager *manager, const char *repo_id, int repo_version, const char *server, const char *user, const char *host, const char *token, const char *server_head_id, gboolean is_clone, int protocol_version, gboolean use_fileserver_port, GError **error) { HttpTxTask *task; if (!repo_id || !token || !host || !server_head_id) { g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Empty argument(s)"); return -1; } if (!is_clone) { if (!seaf_repo_manager_repo_exists (seaf->repo_mgr, repo_id)) { g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Repo not found"); return -1; } } clean_tasks_for_repo (manager, repo_id); task = http_tx_task_new (manager, repo_id, repo_version, HTTP_TASK_TYPE_DOWNLOAD, is_clone, host, token); memcpy (task->head, server_head_id, 40); task->protocol_version = protocol_version; task->state = HTTP_TASK_STATE_NORMAL; task->use_fileserver_port = use_fileserver_port; task->server = g_strdup (server); task->user = g_strdup (user); g_hash_table_insert (manager->priv->download_tasks, g_strdup(repo_id), task); if (seaf_job_manager_schedule_job (seaf->job_mgr, http_download_thread, http_download_done, task) < 0) { g_hash_table_remove (manager->priv->download_tasks, repo_id); return -1; } return 0; } static int get_commit_object (HttpTxTask *task, Connection *conn) { CURL *curl; char *url; int status; char *rsp_content = NULL; gint64 rsp_size; int ret = 0; curl = conn->curl; if (!task->use_fileserver_port) url = g_strdup_printf ("%s/seafhttp/repo/%s/commit/%s", task->host, task->repo_id, task->head); else url = g_strdup_printf ("%s/repo/%s/commit/%s", task->host, task->repo_id, task->head); int curl_error; if (http_get (curl, url, task->token, &status, &rsp_content, &rsp_size, NULL, NULL, TRUE, HTTP_TIMEOUT_SEC, &curl_error) < 0) { conn->release = TRUE; handle_curl_errors (task, curl_error); ret = -1; goto out; } if (status != HTTP_OK) { seaf_warning ("Bad response code for GET %s: %d.\n", url, status); handle_http_errors (task, status); ret = -1; goto out; } int rc = seaf_obj_store_write_obj (seaf->commit_mgr->obj_store, task->repo_id, task->repo_version, task->head, rsp_content, rsp_size, FALSE); if (rc < 0) { seaf_warning ("Failed to save commit %s in repo %.8s.\n", task->head, task->repo_id); task->error = HTTP_TASK_ERR_WRITE_LOCAL_DATA; ret = -1; } out: g_free (url); g_free (rsp_content); curl_easy_reset (curl); return ret; } static int get_needed_fs_id_list (HttpTxTask *task, Connection *conn, GList **fs_id_list) { SeafBranch *master; CURL *curl; char *url = NULL; int status; char *rsp_content = NULL; gint64 rsp_size; int ret = 0; json_t *array; json_error_t jerror; const char *obj_id; const char *url_prefix = (task->use_fileserver_port) ? "" : "seafhttp/"; if (!task->is_clone) { master = seaf_branch_manager_get_branch (seaf->branch_mgr, task->repo_id, "master"); if (!master) { seaf_warning ("Failed to get branch master for repo %.8s.\n", task->repo_id); return -1; } url = g_strdup_printf ("%s/%srepo/%s/fs-id-list/" "?server-head=%s&client-head=%s&dir-only=1", task->host, url_prefix, task->repo_id, task->head, master->commit_id); seaf_branch_unref (master); } else { url = g_strdup_printf ("%s/%srepo/%s/fs-id-list/?server-head=%s&dir-only=1", task->host, url_prefix, task->repo_id, task->head); } curl = conn->curl; int curl_error; if (http_get (curl, url, task->token, &status, &rsp_content, &rsp_size, NULL, NULL, FALSE, HTTP_TIMEOUT_SEC, &curl_error) < 0) { conn->release = TRUE; handle_curl_errors (task, curl_error); ret = -1; goto out; } if (status != HTTP_OK) { seaf_warning ("Bad response code for GET %s: %d.\n", url, status); handle_http_errors (task, status); ret = -1; goto out; } array = json_loadb (rsp_content, rsp_size, 0, &jerror); if (!array) { seaf_warning ("Invalid JSON response from the server: %s.\n", jerror.text); task->error = HTTP_TASK_ERR_SERVER; ret = -1; goto out; } int i; size_t n = json_array_size (array); json_t *str; seaf_debug ("Received fs object list size %lu from %s:%s.\n", n, task->host, task->repo_id); task->n_fs_objs = (int)n; GHashTable *checked_objs = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); for (i = 0; i < n; ++i) { str = json_array_get (array, i); if (!str) { seaf_warning ("Invalid JSON response from the server.\n"); json_decref (array); string_list_free (*fs_id_list); ret = -1; goto out; } obj_id = json_string_value(str); if (g_hash_table_lookup (checked_objs, obj_id)) { ++(task->done_fs_objs); continue; } char *key = g_strdup(obj_id); g_hash_table_replace (checked_objs, key, key); if (!seaf_obj_store_obj_exists (seaf->fs_mgr->obj_store, task->repo_id, task->repo_version, obj_id)) { *fs_id_list = g_list_prepend (*fs_id_list, g_strdup(obj_id)); } else if (task->is_clone) { gboolean io_error = FALSE; gboolean sound; sound = seaf_fs_manager_verify_object (seaf->fs_mgr, task->repo_id, task->repo_version, obj_id, FALSE, &io_error); if (!sound && !io_error) { *fs_id_list = g_list_prepend (*fs_id_list, g_strdup(obj_id)); } else { ++(task->done_fs_objs); } } else { ++(task->done_fs_objs); } } json_decref (array); g_hash_table_destroy (checked_objs); out: g_free (url); g_free (rsp_content); curl_easy_reset (curl); return ret; } #define GET_FS_OBJECT_N 100 static int get_fs_objects (HttpTxTask *task, Connection *conn, GList **fs_list) { json_t *array; char *obj_id; int n_sent = 0; char *data = NULL; int len; CURL *curl; char *url = NULL; int status; char *rsp_content = NULL; gint64 rsp_size; int ret = 0; GHashTable *requested; /* Convert object id list to JSON format. */ requested = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); array = json_array (); while (*fs_list != NULL) { obj_id = (*fs_list)->data; json_array_append_new (array, json_string(obj_id)); *fs_list = g_list_delete_link (*fs_list, *fs_list); g_hash_table_replace (requested, obj_id, obj_id); if (++n_sent >= GET_FS_OBJECT_N) break; } seaf_debug ("Requesting %d fs objects from %s:%s.\n", n_sent, task->host, task->repo_id); data = json_dumps (array, 0); len = strlen(data); json_decref (array); /* Send fs object id list. */ curl = conn->curl; if (!task->use_fileserver_port) url = g_strdup_printf ("%s/seafhttp/repo/%s/pack-fs/", task->host, task->repo_id); else url = g_strdup_printf ("%s/repo/%s/pack-fs/", task->host, task->repo_id); int curl_error; if (http_post (curl, url, task->token, data, len, &status, &rsp_content, &rsp_size, TRUE, HTTP_TIMEOUT_SEC, &curl_error) < 0) { conn->release = TRUE; handle_curl_errors (task, curl_error); ret = -1; goto out; } if (status != HTTP_OK) { seaf_warning ("Bad response code for POST %s: %d.\n", url, status); handle_http_errors (task, status); ret = -1; goto out; } /* Save received fs objects. */ int n_recv = 0; char *p = rsp_content; ObjectHeader *hdr = (ObjectHeader *)p; char recv_obj_id[41]; int n = 0; int size; int rc; while (n < rsp_size) { memcpy (recv_obj_id, hdr->obj_id, 40); recv_obj_id[40] = 0; size = ntohl (hdr->obj_size); if (n + sizeof(ObjectHeader) + size > rsp_size) { seaf_warning ("Incomplete object package received for repo %.8s.\n", task->repo_id); task->error = HTTP_TASK_ERR_SERVER; ret = -1; goto out; } ++n_recv; rc = seaf_obj_store_write_obj (seaf->fs_mgr->obj_store, task->repo_id, task->repo_version, recv_obj_id, hdr->object, size, FALSE); if (rc < 0) { seaf_warning ("Failed to write fs object %s in repo %.8s.\n", recv_obj_id, task->repo_id); task->error = HTTP_TASK_ERR_WRITE_LOCAL_DATA; ret = -1; goto out; } g_hash_table_remove (requested, recv_obj_id); ++(task->done_fs_objs); p += (sizeof(ObjectHeader) + size); n += (sizeof(ObjectHeader) + size); hdr = (ObjectHeader *)p; } seaf_debug ("Received %d fs objects from %s:%s.\n", n_recv, task->host, task->repo_id); /* The server may not return all the objects we requested. * So we need to add back the remaining object ids into fs_list. */ GHashTableIter iter; gpointer key, value; g_hash_table_iter_init (&iter, requested); while (g_hash_table_iter_next (&iter, &key, &value)) { obj_id = key; *fs_list = g_list_prepend (*fs_list, g_strdup(obj_id)); } g_hash_table_destroy (requested); out: g_free (url); g_free (data); g_free (rsp_content); curl_easy_reset (curl); return ret; } int http_tx_manager_get_block (HttpTxManager *mgr, const char *host, gboolean use_fileserver_port, const char *token, const char *repo_id, const char *block_id, HttpRecvCallback get_blk_cb, void *user_data, int *http_status) { HttpTxPriv *priv = mgr->priv; ConnectionPool *pool; Connection *conn; CURL *curl; char *url; int ret = 0; pool = find_connection_pool (priv, host); if (!pool) { seaf_warning ("Failed to create connection pool for host %s.\n", host); return -1; } conn = connection_pool_get_connection (pool); if (!conn) { seaf_warning ("Failed to get connection to host %s.\n", host); return -1; } curl = conn->curl; if (!use_fileserver_port) url = g_strdup_printf ("%s/seafhttp/repo/%s/block/%s", host, repo_id, block_id); else url = g_strdup_printf ("%s/repo/%s/block/%s", host, repo_id, block_id); if (http_get (curl, url, token, http_status, NULL, NULL, get_blk_cb, user_data, TRUE, HTTP_TIMEOUT_SEC, NULL) < 0) { conn->release = TRUE; ret = -1; goto out; } if (*http_status != HTTP_OK) { seaf_warning ("Bad response code for GET %s: %d.\n", url, *http_status); ret = -1; } out: g_free (url); connection_pool_return_connection (pool, conn); return ret; } int http_tx_manager_get_fs_object (HttpTxManager *mgr, const char *host, gboolean use_fileserver_port, const char *token, const char *repo_id, const char *obj_id, const char *file_path) { HttpTxPriv *priv = mgr->priv; ConnectionPool *pool; Connection *conn; json_t *array; char *data = NULL; int len; CURL *curl; char *url = NULL; int status; char *rsp_content = NULL; gint64 rsp_size; int ret = 0; pool = find_connection_pool (priv, host); if (!pool) { seaf_warning ("Failed to create connection pool for host %s.\n", host); return -1; } conn = connection_pool_get_connection (pool); if (!conn) { seaf_warning ("Failed to get connection to host %s.\n", host); return -1; } /* Convert object id list to JSON format. */ array = json_array (); json_array_append_new (array, json_string(obj_id)); data = json_dumps (array, 0); len = strlen(data); json_decref (array); /* Send fs object id list. */ curl = conn->curl; char *esc_path = g_uri_escape_string (file_path, NULL, FALSE); if (!use_fileserver_port) url = g_strdup_printf ("%s/seafhttp/repo/%s/pack-fs/?path=%s", host, repo_id, esc_path); else url = g_strdup_printf ("%s/repo/%s/pack-fs/?path=%s", host, repo_id, esc_path); g_free (esc_path); if (http_post (curl, url, token, data, len, &status, &rsp_content, &rsp_size, TRUE, HTTP_TIMEOUT_SEC, NULL) < 0) { conn->release = TRUE; ret = -1; goto out; } if (status != HTTP_OK) { seaf_warning ("Bad response code for POST %s: %d.\n", url, status); ret = -1; goto out; } /* Save received fs objects. */ char *p = rsp_content; ObjectHeader *hdr = (ObjectHeader *)p; char recv_obj_id[41]; int n = 0; int size; int rc; while (n < rsp_size) { memcpy (recv_obj_id, hdr->obj_id, 40); recv_obj_id[40] = 0; size = ntohl (hdr->obj_size); if (n + sizeof(ObjectHeader) + size > rsp_size) { seaf_warning ("Incomplete object package received for repo %.8s.\n", repo_id); ret = -1; goto out; } rc = seaf_obj_store_write_obj (seaf->fs_mgr->obj_store, repo_id, 1, recv_obj_id, hdr->object, size, FALSE); if (rc < 0) { seaf_warning ("Failed to write fs object %s in repo %.8s.\n", recv_obj_id, repo_id); ret = -1; goto out; } p += (sizeof(ObjectHeader) + size); n += (sizeof(ObjectHeader) + size); hdr = (ObjectHeader *)p; } out: g_free (url); g_free (data); g_free (rsp_content); connection_pool_return_connection (pool, conn); return ret; } int http_tx_manager_get_file (HttpTxManager *mgr, const char *host, gboolean use_fileserver_port, const char *token, const char *repo_id, const char *path, gint64 block_offset, HttpRecvCallback get_blk_cb, void *user_data, int *http_status) { HttpTxPriv *priv = mgr->priv; ConnectionPool *pool; Connection *conn; CURL *curl; char *url; int ret = 0; pool = find_connection_pool (priv, host); if (!pool) { seaf_warning ("Failed to create connection pool for host %s.\n", host); return -1; } conn = connection_pool_get_connection (pool); if (!conn) { seaf_warning ("Failed to get connection to host %s.\n", host); return -1; } curl = conn->curl; char *esc_path = g_uri_escape_string(path, NULL, FALSE); if (!use_fileserver_port) url = g_strdup_printf ("%s/seafhttp/repo/%s/file/?path=%s&offset=%"G_GINT64_FORMAT"", host, repo_id, esc_path, block_offset); else url = g_strdup_printf ("%s/repo/%s/file/?path=%s&offset=%"G_GINT64_FORMAT"", host, repo_id, esc_path, block_offset); g_free (esc_path); if (http_get (curl, url, token, http_status, NULL, NULL, get_blk_cb, user_data, TRUE, HTTP_TIMEOUT_SEC, NULL) < 0) { conn->release = TRUE; ret = -1; goto out; } if (*http_status != HTTP_OK) { seaf_warning ("Bad response code for GET %s: %d.\n", url, *http_status); ret = -1; } out: g_free (url); connection_pool_return_connection (pool, conn); return ret; } /* typedef struct { */ /* char block_id[41]; */ /* BlockHandle *block; */ /* HttpTxTask *task; */ /* } GetBlockData; */ /* static size_t */ /* get_block_callback (void *ptr, size_t size, size_t nmemb, void *userp) */ /* { */ /* size_t realsize = size *nmemb; */ /* SendBlockData *data = userp; */ /* HttpTxTask *task = data->task; */ /* size_t n; */ /* if (task->state == HTTP_TASK_STATE_CANCELED || task->all_stop) */ /* return 0; */ /* n = seaf_block_manager_write_block (seaf->block_mgr, */ /* data->block, */ /* ptr, realsize); */ /* if (n < realsize) { */ /* seaf_warning ("Failed to write block %s in repo %.8s.\n", */ /* data->block_id, task->repo_id); */ /* task->error = HTTP_TASK_ERR_BAD_LOCAL_DATA; */ /* return n; */ /* } */ /* /\* Update global transferred bytes. *\/ */ /* g_atomic_int_add (&(seaf->sync_mgr->recv_bytes), n); */ /* /\* Update transferred bytes for this task *\/ */ /* g_atomic_int_add (&task->tx_bytes, n); */ /* /\* If uploaded bytes exceeds the limit, wait until the counter */ /* * is reset. We check the counter every 100 milliseconds, so we */ /* * can waste up to 100 milliseconds without sending data after */ /* * the counter is reset. */ /* *\/ */ /* while (1) { */ /* gint sent = g_atomic_int_get(&(seaf->sync_mgr->recv_bytes)); */ /* if (seaf->sync_mgr->download_limit > 0 && */ /* sent > seaf->sync_mgr->download_limit) */ /* /\* 100 milliseconds *\/ */ /* g_usleep (100000); */ /* else */ /* break; */ /* } */ /* return n; */ /* } */ /* int */ /* get_block (HttpTxTask *task, Connection *conn, const char *block_id) */ /* { */ /* CURL *curl; */ /* char *url; */ /* int status; */ /* BlockHandle *block; */ /* int ret = 0; */ /* int *pcnt; */ /* block = seaf_block_manager_open_block (seaf->block_mgr, */ /* task->repo_id, task->repo_version, */ /* block_id, BLOCK_WRITE); */ /* if (!block) { */ /* seaf_warning ("Failed to open block %s in repo %.8s.\n", */ /* block_id, task->repo_id); */ /* return -1; */ /* } */ /* GetBlockData data; */ /* memcpy (data.block_id, block_id, 40); */ /* data.block = block; */ /* data.task = task; */ /* curl = conn->curl; */ /* if (!task->use_fileserver_port) */ /* url = g_strdup_printf ("%s/seafhttp/repo/%s/block/%s", */ /* task->host, task->repo_id, block_id); */ /* else */ /* url = g_strdup_printf ("%s/repo/%s/block/%s", */ /* task->host, task->repo_id, block_id); */ /* if (http_get (curl, url, task->token, &status, NULL, NULL, */ /* get_block_callback, &data, TRUE) < 0) { */ /* if (task->state == HTTP_TASK_STATE_CANCELED) */ /* goto error; */ /* if (task->error == HTTP_TASK_OK) { */ /* /\* Only release the connection when it's a network error. *\/ */ /* conn->release = TRUE; */ /* task->error = HTTP_TASK_ERR_NET; */ /* } */ /* ret = -1; */ /* goto error; */ /* } */ /* if (status != HTTP_OK) { */ /* seaf_warning ("Bad response code for GET %s: %d.\n", url, status); */ /* handle_http_errors (task, status); */ /* ret = -1; */ /* goto error; */ /* } */ /* seaf_block_manager_close_block (seaf->block_mgr, block); */ /* pthread_mutex_lock (&task->ref_cnt_lock); */ /* /\* Don't overwrite the block if other thread already downloaded it. */ /* * Since we've locked ref_cnt_lock, we can be sure the block won't be removed. */ /* *\/ */ /* if (!seaf_block_manager_block_exists (seaf->block_mgr, */ /* task->repo_id, task->repo_version, */ /* block_id) && */ /* seaf_block_manager_commit_block (seaf->block_mgr, block) < 0) */ /* { */ /* seaf_warning ("Failed to commit block %s in repo %.8s.\n", */ /* block_id, task->repo_id); */ /* task->error = HTTP_TASK_ERR_WRITE_LOCAL_DATA; */ /* ret = -1; */ /* } */ /* if (ret == 0) { */ /* pcnt = g_hash_table_lookup (task->blk_ref_cnts, block_id); */ /* if (!pcnt) { */ /* pcnt = g_new0(int, 1); */ /* g_hash_table_insert (task->blk_ref_cnts, g_strdup(block_id), pcnt); */ /* } */ /* *pcnt += 1; */ /* } */ /* pthread_mutex_unlock (&task->ref_cnt_lock); */ /* seaf_block_manager_block_handle_free (seaf->block_mgr, block); */ /* g_free (url); */ /* return ret; */ /* error: */ /* g_free (url); */ /* seaf_block_manager_close_block (seaf->block_mgr, block); */ /* seaf_block_manager_block_handle_free (seaf->block_mgr, block); */ /* return ret; */ /* } */ /* int */ /* http_tx_task_download_file_blocks (HttpTxTask *task, const char *file_id) */ /* { */ /* Seafile *file; */ /* HttpTxPriv *priv = seaf->http_tx_mgr->priv; */ /* ConnectionPool *pool; */ /* Connection *conn; */ /* int ret = 0; */ /* file = seaf_fs_manager_get_seafile (seaf->fs_mgr, */ /* task->repo_id, */ /* task->repo_version, */ /* file_id); */ /* if (!file) { */ /* seaf_warning ("Failed to find seafile object %s in repo %.8s.\n", */ /* file_id, task->repo_id); */ /* return -1; */ /* } */ /* pool = find_connection_pool (priv, task->host); */ /* if (!pool) { */ /* seaf_warning ("Failed to create connection pool for host %s.\n", task->host); */ /* task->error = HTTP_TASK_ERR_NOT_ENOUGH_MEMORY; */ /* seafile_unref (file); */ /* return -1; */ /* } */ /* conn = connection_pool_get_connection (pool); */ /* if (!conn) { */ /* seaf_warning ("Failed to get connection to host %s.\n", task->host); */ /* task->error = HTTP_TASK_ERR_NOT_ENOUGH_MEMORY; */ /* seafile_unref (file); */ /* return -1; */ /* } */ /* int i; */ /* char *block_id; */ /* for (i = 0; i < file->n_blocks; ++i) { */ /* block_id = file->blk_sha1s[i]; */ /* ret = get_block (task, conn, block_id); */ /* if (ret < 0 || task->state == HTTP_TASK_STATE_CANCELED) */ /* break; */ /* } */ /* connection_pool_return_connection (pool, conn); */ /* seafile_unref (file); */ /* return ret; */ /* } */ static int update_local_repo (HttpTxTask *task) { SeafRepo *repo = NULL; SeafCommit *new_head; SeafBranch *branch; int ret = 0; new_head = seaf_commit_manager_get_commit (seaf->commit_mgr, task->repo_id, task->repo_version, task->head); if (!new_head) { seaf_warning ("Failed to get commit %s:%s.\n", task->repo_id, task->head); task->error = HTTP_TASK_ERR_BAD_LOCAL_DATA; return -1; } if (task->is_clone) { /* If repo doesn't exist, create it. * Note that branch doesn't exist either in this case. */ repo = seaf_repo_manager_get_repo (seaf->repo_mgr, new_head->repo_id); if (repo != NULL) { seaf_repo_unref (repo); goto out; } repo = seaf_repo_new (new_head->repo_id, NULL, NULL); if (repo == NULL) { /* create repo failed */ task->error = HTTP_TASK_ERR_NOT_ENOUGH_MEMORY; ret = -1; goto out; } repo->server = g_strdup (task->server); repo->user = g_strdup (task->user); seaf_repo_from_commit (repo, new_head); /* If it's a new repo, create 'local' and 'master' branch */ branch = seaf_branch_new ("local", task->repo_id, task->head, 0); seaf_branch_manager_add_branch (seaf->branch_mgr, branch); /* Set repo head branch to local */ seaf_repo_set_head (repo, branch); seaf_branch_unref (branch); branch = seaf_branch_new ("master", task->repo_id, task->head, 0); seaf_branch_manager_add_branch (seaf->branch_mgr, branch); seaf_branch_unref (branch); seaf_repo_manager_add_repo (seaf->repo_mgr, repo); } /* If repo already exists, update branches in sync-mgr. */ out: seaf_commit_unref (new_head); return ret; } static void * http_download_thread (void *vdata) { HttpTxTask *task = vdata; HttpTxPriv *priv = seaf->http_tx_mgr->priv; ConnectionPool *pool; Connection *conn = NULL; GList *fs_id_list = NULL; pool = find_connection_pool (priv, task->host); if (!pool) { seaf_warning ("Failed to create connection pool for host %s.\n", task->host); task->error = HTTP_TASK_ERR_NOT_ENOUGH_MEMORY; goto out; } conn = connection_pool_get_connection (pool); if (!conn) { seaf_warning ("failed to get connection to host %s.\n", task->host); task->error = HTTP_TASK_ERR_NOT_ENOUGH_MEMORY; goto out; } seaf_message ("Download with HTTP sync protocol version %d.\n", task->protocol_version); transition_state (task, task->state, HTTP_TASK_RT_STATE_CHECK); if (check_permission (task, conn) < 0) { seaf_warning ("Download permission denied for repo %.8s on server %s.\n", task->repo_id, task->host); goto out; } if (task->state == HTTP_TASK_STATE_CANCELED) goto out; transition_state (task, task->state, HTTP_TASK_RT_STATE_COMMIT); if (get_commit_object (task, conn) < 0) { seaf_warning ("Failed to get server head commit for repo %.8s on server %s.\n", task->repo_id, task->host); goto out; } if (task->state == HTTP_TASK_STATE_CANCELED) goto out; transition_state (task, task->state, HTTP_TASK_RT_STATE_FS); if (get_needed_fs_id_list (task, conn, &fs_id_list) < 0) { seaf_warning ("Failed to get fs id list for repo %.8s on server %s.\n", task->repo_id, task->host); goto out; } if (task->state == HTTP_TASK_STATE_CANCELED) goto out; while (fs_id_list != NULL) { if (get_fs_objects (task, conn, &fs_id_list) < 0) { seaf_warning ("Failed to get fs objects for repo %.8s on server %s.\n", task->repo_id, task->host); goto out; } if (task->state == HTTP_TASK_STATE_CANCELED) goto out; } update_local_repo (task); out: connection_pool_return_connection (pool, conn); string_list_free (fs_id_list); return vdata; } static void http_download_done (void *vdata) { HttpTxTask *task = vdata; if (task->error != HTTP_TASK_OK) transition_state (task, HTTP_TASK_STATE_ERROR, HTTP_TASK_RT_STATE_FINISHED); else if (task->state == HTTP_TASK_STATE_CANCELED) transition_state (task, task->state, HTTP_TASK_RT_STATE_FINISHED); else transition_state (task, HTTP_TASK_STATE_FINISHED, HTTP_TASK_RT_STATE_FINISHED); } #define SYNC_MOVE_TIMEOUT 600 int synchronous_move (Connection *conn, const char *host, const char *api_token, const char *repo_id1, const char *oldpath, const char *repo_id2, const char *newpath) { CURL *curl = conn->curl; char *dst_dir = NULL, *esc_oldpath = NULL; char *url = NULL; char *req_content = NULL; int status; char *rsp_content = NULL; gint64 rsp_size; int ret = 0; dst_dir = g_path_get_dirname (newpath); if (strcmp(dst_dir, ".") == 0) { g_free (dst_dir); dst_dir = g_strdup(""); } esc_oldpath = g_uri_escape_string (oldpath, NULL, FALSE); req_content = g_strdup_printf ("operation=move&dst_repo=%s&dst_dir=/%s", repo_id2, dst_dir); url = g_strdup_printf ("%s/api2/repos/%s/file/?p=/%s", host, repo_id1, esc_oldpath); if (http_api_post (curl, url, api_token, req_content, strlen(req_content), &status, &rsp_content, &rsp_size, TRUE, SYNC_MOVE_TIMEOUT, NULL) < 0) { conn->release = TRUE; ret = -1; goto out; } if (status != HTTP_MOVED_PERMANENTLY) { seaf_warning ("Bad response code for POST %s: %d, response error: %s.\n", url, status, (rsp_content ? rsp_content : "no response body")); ret = -1; } out: g_free (dst_dir); g_free (esc_oldpath); g_free (req_content); g_free (url); return ret; } int asynchronous_move (Connection *conn, const char *host, const char *api_token, const char *repo_id1, const char *oldpath, const char *repo_id2, const char *newpath, gboolean is_file, gboolean *no_api) { CURL *curl = conn->curl; char *src_dir = NULL, *src_filename = NULL, *dst_dir = NULL; char *url = NULL; char *req_content = NULL; int status; char *rsp_content = NULL; gint64 rsp_size; int ret = 0; json_t *obj = NULL; json_t *req_obj = NULL; json_t *array = NULL; json_error_t jerror; src_dir = g_path_get_dirname (oldpath); if (strcmp(src_dir, ".") == 0) { g_free (src_dir); src_dir = g_strdup("/"); } src_filename = g_path_get_basename (oldpath); dst_dir = g_path_get_dirname (newpath); if (strcmp(dst_dir, ".") == 0) { g_free (dst_dir); dst_dir = g_strdup("/"); } req_obj = json_object (); json_object_set_new (req_obj, "src_repo_id", json_string(repo_id1)); json_object_set_new (req_obj, "src_parent_dir", json_string(src_dir)); array = json_array (); json_array_append_new (array, json_string(src_filename)); json_object_set_new (req_obj, "src_dirents", array); json_object_set_new (req_obj, "dst_repo_id", json_string(repo_id2)); json_object_set_new (req_obj, "dst_parent_dir", json_string(dst_dir)); req_content = json_dumps (req_obj, 0); url = g_strdup_printf ("%s/api/v2.1/repos/async-batch-move-item/", host); if (http_api_json_post (curl, url, api_token, req_content, strlen(req_content), &status, &rsp_content, &rsp_size, TRUE, REPO_OPER_TIMEOUT, NULL) < 0) { conn->release = TRUE; ret = -1; goto out; } curl_easy_reset (curl); if (status != HTTP_OK) { seaf_warning ("Bad response code for POST %s: %d, response error: %s.\n", url, status, (rsp_content ? rsp_content : "no response body")); if (status == HTTP_NOT_FOUND && no_api) *no_api = TRUE; ret = -1; goto out; } obj = json_loadb (rsp_content, rsp_size, 0, &jerror); if (!obj) { seaf_warning ("Failed to load json response: %s\n", jerror.text); ret = -1; goto out; } if (json_typeof(obj) != JSON_OBJECT) { seaf_warning ("Response is not a json object.\n"); ret = -1; goto out; } const char *task_id = json_string_value(json_object_get(obj, "task_id")); if (!task_id || strlen(task_id) == 0) { if (g_strcmp0 (repo_id1, repo_id2) == 0) goto out; seaf_warning ("No copy move task id returned from server.\n"); ret = -1; goto out; } g_free (url); url = g_strdup_printf ("%s/api/v2.1/query-copy-move-progress/?task_id=%s", host, task_id); while (1) { g_free (rsp_content); rsp_content = NULL; if (http_api_get (curl, url, api_token, &status, &rsp_content, &rsp_size, NULL, NULL, TRUE, REPO_OPER_TIMEOUT, NULL) < 0) { conn->release = TRUE; ret = -1; goto out; } curl_easy_reset (curl); if (status != HTTP_OK) { seaf_warning ("Bad response code GET %s: %d\n", url, status); ret = -1; goto out; } json_decref (obj); obj = json_loadb (rsp_content, rsp_size, 0, &jerror); if (!obj) { seaf_warning ("Failed to load json response: %s\n", jerror.text); ret = -1; goto out; } if (json_typeof(obj) != JSON_OBJECT) { seaf_warning ("Response is not a json object.\n"); ret = -1; goto out; } json_t *succ = json_object_get (obj, "successful"); if (!succ) { seaf_warning ("Invalid json object format.\n"); ret = -1; goto out; } json_t *failed = json_object_get (obj, "failed"); if (!failed) { seaf_warning ("Invalid json object format.\n"); ret = -1; goto out; } if (succ == json_true()) { break; } else if (failed == json_true()) { seaf_warning ("Move %s/%s to %s/%s failed on server.\n", repo_id1, oldpath, repo_id2, newpath); ret = -1; break; } seaf_sleep (1); } out: g_free (src_dir); g_free (src_filename); g_free (dst_dir); g_free (url); g_free (req_content); g_free (rsp_content); json_decref (obj); json_decref (req_obj); return ret; } int http_tx_manager_api_move_file (HttpTxManager *mgr, const char *host, const char *api_token, const char *repo_id1, const char *oldpath, const char *repo_id2, const char *newpath, gboolean is_file) { HttpTxPriv *priv = seaf->http_tx_mgr->priv; ConnectionPool *pool; Connection *conn; int ret = 0; pool = find_connection_pool (priv, host); if (!pool) { seaf_warning ("Failed to create connection pool for host %s.\n", host); return -1; } conn = connection_pool_get_connection (pool); if (!conn) { seaf_warning ("Failed to get connection to host %s.\n", host); return -1; } gboolean no_api = FALSE; ret = asynchronous_move (conn, host, api_token, repo_id1, oldpath, repo_id2, newpath, is_file, &no_api); if (ret < 0 && no_api) { /* Async move api not found, try old api. */ ret = synchronous_move (conn, host, api_token, repo_id1, oldpath, repo_id2, newpath); } connection_pool_return_connection (pool, conn); return ret; } static int parse_space_usage (const char *rsp_content, int rsp_size, gint64 *total, gint64 *used) { json_t *object, *member; json_error_t jerror; int ret = 0; object = json_loadb (rsp_content, rsp_size, 0, &jerror); if (!object) { seaf_warning ("Failed to load json object: %s\n", jerror.text); return -1; } if (json_typeof(object) != JSON_OBJECT) { seaf_warning ("Json response is not object\n"); ret = -1; goto out; } member = json_object_get (object, "total"); if (json_typeof(member) != JSON_INTEGER) { seaf_warning ("Space usage not an integer\n"); ret = -1; goto out; } *total = json_integer_value (member); member = json_object_get (object, "usage"); if (json_typeof(member) != JSON_INTEGER) { seaf_warning ("Space usage not an integer\n"); ret = -1; goto out; } *used = json_integer_value (member); out: json_decref (object); return ret; } int http_tx_manager_api_get_space_usage (HttpTxManager *mgr, const char *host, const char *api_token, gint64 *total, gint64 *used) { HttpTxPriv *priv = seaf->http_tx_mgr->priv; ConnectionPool *pool; Connection *conn; CURL *curl; char *url = NULL; int status; char *rsp_content = NULL; gint64 rsp_size; int ret = 0; pool = find_connection_pool (priv, host); if (!pool) { seaf_warning ("Failed to create connection pool for host %s.\n", host); return -1; } conn = connection_pool_get_connection (pool); if (!conn) { seaf_warning ("Failed to get connection to host %s.\n", host); return -1; } curl = conn->curl; url = g_strdup_printf ("%s/api2/account/info/", host); if (http_api_get (curl, url, api_token, &status, &rsp_content, &rsp_size, NULL, NULL, TRUE, HTTP_TIMEOUT_SEC, NULL) < 0) { conn->release = TRUE; ret = -1; goto out; } if (status != HTTP_OK) { seaf_warning ("Bad response code for GET %s: %d.\n", url, status); ret = -1; goto out; } if (parse_space_usage (rsp_content, rsp_size, total, used) < 0) { ret = -1; } out: connection_pool_return_connection (pool, conn); g_free (url); g_free (rsp_content); return ret; } static int parse_block_map (const char *rsp_content, gint64 rsp_size, gint64 **pblock_map, int *n_blocks) { json_t *array, *element; json_error_t jerror; size_t n, i; gint64 *block_map = NULL; int ret = 0; array = json_loadb (rsp_content, rsp_size, 0, &jerror); if (!array) { seaf_warning ("Failed to load json: %s\n", jerror.text); return -1; } if (json_typeof (array) != JSON_ARRAY) { seaf_warning ("Response is not a json array.\n"); ret = -1; goto out; } n = json_array_size (array); block_map = g_new0 (gint64, n); for (i = 0; i < n; ++i) { element = json_array_get (array, i); if (json_typeof (element) != JSON_INTEGER) { seaf_warning ("Block map element not an integer.\n"); ret = -1; goto out; } block_map[i] = (gint64)json_integer_value (element); } out: json_decref (array); if (ret < 0) { g_free (block_map); *pblock_map = NULL; } else { *pblock_map = block_map; *n_blocks = (int)n; } return ret; } int http_tx_manager_get_file_block_map (HttpTxManager *mgr, const char *host, gboolean use_fileserver_port, const char *token, const char *repo_id, const char *file_id, gint64 **pblock_map, int *n_blocks) { HttpTxPriv *priv = mgr->priv; ConnectionPool *pool; Connection *conn; CURL *curl; char *url; int ret = 0; char *rsp_content = NULL; gint64 rsp_size; int status; pool = find_connection_pool (priv, host); if (!pool) { seaf_warning ("Failed to create connection pool for host %s.\n", host); return -1; } conn = connection_pool_get_connection (pool); if (!conn) { seaf_warning ("Failed to get connection to host %s.\n", host); return -1; } curl = conn->curl; if (!use_fileserver_port) url = g_strdup_printf ("%s/seafhttp/repo/%s/block-map/%s", host, repo_id, file_id); else url = g_strdup_printf ("%s/repo/%s/block-map/%s", host, repo_id, file_id); if (http_get (curl, url, token, &status, &rsp_content, &rsp_size, NULL, NULL, TRUE, HTTP_TIMEOUT_SEC, NULL) < 0) { conn->release = TRUE; ret = -1; goto out; } if (status != HTTP_OK) { seaf_warning ("Bad response code for GET %s: %d.\n", url, status); ret = -1; goto out; } if (parse_block_map (rsp_content, rsp_size, pblock_map, n_blocks) < 0) { ret = -1; goto out; } out: g_free (url); g_free (rsp_content); connection_pool_return_connection (pool, conn); return ret; } int http_tx_manager_get_commit (HttpTxManager *mgr, const char *host, gboolean use_fileserver_port, const char *sync_token, const char *repo_id, const char *commit_id, char **resp, gint64 *resp_size) { HttpTxPriv *priv = mgr->priv; ConnectionPool *pool; Connection *conn; CURL *curl; char *url = NULL; int status; int ret = 0; pool = find_connection_pool (priv, host); if (!pool) { seaf_warning ("Failed to create connection pool for host %s.\n", host); return -1; } conn = connection_pool_get_connection (pool); if (!conn) { seaf_warning ("Failed to get connection to host %s.\n", host); return -1; } curl = conn->curl; if (!use_fileserver_port) url = g_strdup_printf ("%s/seafhttp/repo/%s/commit/%s", host, repo_id, commit_id); else url = g_strdup_printf ("%s/repo/%s/commit/%s", host, repo_id, commit_id); if (http_get (curl, url, sync_token, &status, resp, resp_size, NULL, NULL, TRUE, REPO_OPER_TIMEOUT, NULL) < 0) { conn->release = TRUE; ret = -1; goto out; } if (status != HTTP_OK) { seaf_warning ("Bad response code for GET %s: %d.\n", url, status); ret = -1; } out: g_free (url); connection_pool_return_connection (pool, conn); return ret; } int http_tx_manager_api_create_repo (HttpTxManager *mgr, const char *host, const char *api_token, const char *repo_name, char **resp, gint64 *resp_size) { HttpTxPriv *priv = mgr->priv; ConnectionPool *pool; Connection *conn; CURL *curl; char *url = NULL; char *req_content = NULL; int status; int ret = 0; pool = find_connection_pool (priv, host); if (!pool) { seaf_warning ("Failed to create connection pool for host %s.\n", host); return -1; } conn = connection_pool_get_connection (pool); if (!conn) { seaf_warning ("Failed to get connection to host %s.\n", host); return -1; } curl = conn->curl; url = g_strdup_printf ("%s/api2/repos/", host); req_content = g_strdup_printf ("name=%s&desc=%s", repo_name, repo_name); if (http_api_post (curl, url, api_token, req_content, strlen(req_content), &status, resp, resp_size, TRUE, REPO_OPER_TIMEOUT, NULL) < 0) { conn->release = TRUE; ret = -1; goto out; } if (status != HTTP_OK) { seaf_warning ("Bad response code for POST %s: %d.\n", url, status); ret = -1; } out: connection_pool_return_connection (pool, conn); g_free (req_content); g_free (url); return ret; } int http_tx_manager_api_rename_repo (HttpTxManager *mgr, const char *host, const char *api_token, const char *repo_id, const char *new_name) { HttpTxPriv *priv = mgr->priv; ConnectionPool *pool; Connection *conn; CURL *curl; char *url = NULL; char *req_content = NULL; char *resp = NULL; gint64 resp_size; int status; int ret = 0; pool = find_connection_pool (priv, host); if (!pool) { seaf_warning ("Failed to create connection pool for host %s.\n", host); return -1; } conn = connection_pool_get_connection (pool); if (!conn) { seaf_warning ("Failed to get connection to host %s.\n", host); return -1; } curl = conn->curl; url = g_strdup_printf ("%s/api2/repos/%s/?op=rename", host, repo_id); req_content = g_strdup_printf ("repo_name=%s", new_name); if (http_api_post (curl, url, api_token, req_content, strlen(req_content), &status, &resp, &resp_size, TRUE, REPO_OPER_TIMEOUT, NULL) < 0) { conn->release = TRUE; ret = -1; goto out; } if (status != HTTP_OK) { seaf_warning ("Bad response code for POST %s: %d(%s).\n", url, status, resp ? resp : ""); ret = -1; } out: connection_pool_return_connection (pool, conn); g_free (req_content); g_free (resp); g_free (url); return ret; } int http_tx_manager_api_delete_repo (HttpTxManager *mgr, const char *host, const char *api_token, const char *repo_id) { HttpTxPriv *priv = mgr->priv; ConnectionPool *pool; Connection *conn; CURL *curl; char *url = NULL; int status; int ret = 0; pool = find_connection_pool (priv, host); if (!pool) { seaf_warning ("Failed to create connection pool for host %s.\n", host); return -1; } conn = connection_pool_get_connection (pool); if (!conn) { seaf_warning ("Failed to get connection to host %s.\n", host); return -1; } curl = conn->curl; url = g_strdup_printf ("%s/api/v2.1/repos/%s/", host, repo_id); if (http_api_delete (curl, url, api_token, &status, TRUE, REPO_OPER_TIMEOUT) < 0) { conn->release = TRUE; ret = -1; goto out; } if (status != HTTP_OK) { seaf_warning ("Bad response code for DELETE %s:%d.\n", url, status); ret = -1; } out: connection_pool_return_connection (pool, conn); g_free (url); return ret; } typedef struct FileRangeData { char *buf; size_t size; } FileRangeData; static size_t get_file_range_cb (void *contents, size_t size, size_t nmemb, void *userp) { FileRangeData *data = userp; size_t realsize = size * nmemb; if (data->size < realsize) { seaf_warning ("Get file range failed. Returned data size larger than buffer.\n"); return data->size; } memcpy (data->buf, contents, realsize); data->buf += realsize; data->size -= realsize; return realsize; } gssize http_tx_manager_get_file_range (HttpTxManager *mgr, const char *host, const char *api_token, const char *repo_id, const char *path, char *buf, guint64 offset, size_t size) { HttpTxPriv *priv = mgr->priv; ConnectionPool *pool; Connection *conn; CURL *curl; char *url = NULL, *file_url; char *resp = NULL; gint64 resp_size; int status; int ret = 0; pool = find_connection_pool (priv, host); if (!pool) { seaf_warning ("Failed to create connection pool for host %s.\n", host); return -1; } conn = connection_pool_get_connection (pool); if (!conn) { seaf_warning ("Failed to get connection to host %s.\n", host); return -1; } curl = conn->curl; char *esc_path = g_uri_escape_string(path, NULL, FALSE); url = g_strdup_printf ("%s/api2/repos/%s/file/?p=%s", host, repo_id, esc_path); g_free (esc_path); if (http_api_get (curl, url, api_token, &status, &resp, &resp_size, NULL, NULL, TRUE, REPO_OPER_TIMEOUT, NULL) < 0) { conn->release = TRUE; ret = -1; goto out; } if (status != HTTP_OK) { seaf_warning ("Bad response code for GET %s: %d(%s).\n", url, status, resp ? resp : ""); ret = -1; goto out; } /* Returned file url is in "url" format. */ resp[resp_size-1] = '\0'; file_url = resp + 1; FileRangeData data; data.buf = buf; data.size = size; curl_easy_reset (curl); if (http_get_range (curl, file_url, NULL, &status, NULL, NULL, get_file_range_cb, &data, TRUE, REPO_OPER_TIMEOUT, offset, offset+size-1) < 0) { conn->release = TRUE; ret = -1; goto out; } if (status != HTTP_RES_PARTIAL) { seaf_warning ("Failed to get file range. Bad response code for GET %s: %d.\n", url, status); ret = -1; goto out; } ret = (int)(size - data.size); out: connection_pool_return_connection (pool, conn); g_free (resp); g_free (url); return ret; } /* typedef struct FileConflictData { */ /* char *repo_id; */ /* char *repo_name; */ /* char *path; */ /* } FileConflictData; */ /* static void */ /* notify_conflict (CEvent *event, void *handler_data) */ /* { */ /* FileConflictData *data = event->data; */ /* json_t *object; */ /* char *str; */ /* object = json_object (); */ /* json_object_set_new (object, "repo_id", json_string(data->repo_id)); */ /* json_object_set_new (object, "repo_name", json_string(data->repo_name)); */ /* json_object_set_new (object, "path", json_string(data->path)); */ /* str = json_dumps (object, 0); */ /* seaf_mq_manager_publish_notification (seaf->mq_mgr, */ /* "sync.conflict", */ /* str); */ /* free (str); */ /* json_decref (object); */ /* g_free (data->repo_id); */ /* g_free (data->repo_name); */ /* g_free (data->path); */ /* g_free (data); */ /* } */ /* void */ /* http_tx_manager_notify_conflict (HttpTxTask *task, const char *path) */ /* { */ /* FileConflictData *data = g_new0 (FileConflictData, 1); */ /* data->repo_id = g_strdup(task->repo_id); */ /* data->repo_name = g_strdup(task->repo_name); */ /* data->path = g_strdup(path); */ /* cevent_manager_add_event (seaf->ev_mgr, task->cevent_id, data); */ /* } */ GList* http_tx_manager_get_upload_tasks (HttpTxManager *manager) { return g_hash_table_get_values (manager->priv->upload_tasks); } GList* http_tx_manager_get_download_tasks (HttpTxManager *manager) { return g_hash_table_get_values (manager->priv->download_tasks); } HttpTxTask * http_tx_manager_find_task (HttpTxManager *manager, const char *repo_id) { HttpTxTask *task = NULL; task = g_hash_table_lookup (manager->priv->upload_tasks, repo_id); if (task) return task; task = g_hash_table_lookup (manager->priv->download_tasks, repo_id); return task; } void http_tx_manager_cancel_task (HttpTxManager *manager, const char *repo_id, int task_type) { HttpTxTask *task = NULL; if (task_type == HTTP_TASK_TYPE_DOWNLOAD) task = g_hash_table_lookup (manager->priv->download_tasks, repo_id); else task = g_hash_table_lookup (manager->priv->upload_tasks, repo_id); if (!task) return; if (task->state != HTTP_TASK_STATE_NORMAL) { return; } if (task->runtime_state == HTTP_TASK_RT_STATE_INIT) { transition_state (task, HTTP_TASK_STATE_CANCELED, HTTP_TASK_RT_STATE_FINISHED); return; } /* Only change state. runtime_state will be changed in worker thread. */ transition_state (task, HTTP_TASK_STATE_CANCELED, task->runtime_state); } int http_tx_task_get_rate (HttpTxTask *task) { return task->last_tx_bytes; } const char * http_task_state_to_str (int state) { if (state < 0 || state >= N_HTTP_TASK_STATE) return "unknown"; return http_task_state_str[state]; } const char * http_task_rt_state_to_str (int rt_state) { if (rt_state < 0 || rt_state >= N_HTTP_TASK_RT_STATE) return "unknown"; return http_task_rt_state_str[rt_state]; } const char * http_task_error_str (int task_errno) { if (task_errno < 0 || task_errno >= N_HTTP_TASK_ERROR) return "unknown error"; return http_task_error_strs[task_errno]; } static void collect_uploading_files (gpointer key, gpointer value, gpointer user_data) { char *abs_path = key; FileUploadProgress *progress = value; json_t *uploading = user_data; json_t *upload_info = json_object (); json_object_set_string_member (upload_info, "server", progress->server); json_object_set_string_member (upload_info, "username", progress->user); json_object_set_string_member (upload_info, "file_path", abs_path); json_object_set_int_member (upload_info, "uploaded", progress->uploaded); json_object_set_int_member (upload_info, "total_upload", progress->total_upload); json_array_append_new (uploading, upload_info); } json_t * http_tx_manager_get_upload_progress (HttpTxManager *mgr) { HttpTxPriv *priv = mgr->priv; int i = 0; FileUploadedInfo *uploaded_info; json_t *uploaded = json_array (); json_t *uploading = json_array (); json_t *progress = json_object (); json_t *uploaded_obj; pthread_mutex_lock (&priv->progress_lock); g_hash_table_foreach (priv->uploading_files, collect_uploading_files, uploading); while (i < MAX_GET_FINISHED_FILES) { uploaded_info = g_queue_peek_nth (priv->uploaded_files, i); if (uploaded_info) { uploaded_obj = json_object (); json_object_set_string_member (uploaded_obj, "server", uploaded_info->server); json_object_set_string_member (uploaded_obj, "username", uploaded_info->user); json_object_set_string_member (uploaded_obj, "file_path", uploaded_info->file_path); json_array_append_new (uploaded, uploaded_obj); } else { break; } i++; } pthread_mutex_unlock (&priv->progress_lock); json_object_set_new (progress, "uploaded_files", uploaded); json_object_set_new (progress, "uploading_files", uploading); return progress; } #if 0 #define UPLOADED_FILE_LIST_NAME "uploaded.json" #define SAVE_UPLOADED_FILE_LIST_INTERVAL 5 #define UPLOADED_FILE_LIST_VERSION 1 static void * save_uploaded_file_list_worker (void *data) { HttpTxManager *mgr = data; char *path = g_build_filename (seaf->seaf_dir, UPLOADED_FILE_LIST_NAME, NULL); json_t *progress = NULL, *uploaded, *list; char *txt = NULL; GError *error = NULL; while (1) { seaf_sleep (SAVE_UPLOADED_FILE_LIST_INTERVAL); progress = http_tx_manager_get_upload_progress (mgr); uploaded = json_object_get (progress, "uploaded_files"); if (json_array_size(uploaded) > 0) { list = json_object(); json_object_set_new (list, "version", json_integer(UPLOADED_FILE_LIST_VERSION)); json_object_set (list, "uploaded_files", uploaded); txt = json_dumps (list, 0); if (!g_file_set_contents (path, txt, -1, &error)) { seaf_warning ("Failed to save uploaded file list: %s\n", error->message); g_clear_error (&error); } json_decref (list); free (txt); } json_decref (progress); } g_free (path); return NULL; } static void load_uploaded_file_list (HttpTxManager *mgr) { char *path = g_build_filename (seaf->seaf_dir, UPLOADED_FILE_LIST_NAME, NULL); char *txt = NULL; gsize len; GError *error = NULL; json_t *list = NULL, *uploaded = NULL; json_error_t jerror; if (!g_file_test (path, G_FILE_TEST_IS_REGULAR)) { g_free (path); return; } if (!g_file_get_contents (path, &txt, &len, &error)) { seaf_warning ("Failed to read uploaded file list: %s\n", error->message); g_clear_error (&error); g_free (path); return; } list = json_loadb (txt, len, 0, &jerror); if (!list) { seaf_warning ("Failed to load uploaded file list: %s\n", jerror.text); goto out; } if (json_typeof(list) != JSON_OBJECT) { seaf_warning ("Bad uploaded file list format.\n"); goto out; } uploaded = json_object_get (list, "uploaded_files"); if (!uploaded) { seaf_warning ("No uploaded_files in json object.\n"); goto out; } if (json_typeof(uploaded) != JSON_ARRAY) { seaf_warning ("Bad uploaded file list format.\n"); goto out; } json_t *iter; size_t n = json_array_size (uploaded), i; for (i = 0; i < n; ++i) { iter = json_array_get (uploaded, i); if (json_typeof(iter) != JSON_STRING) { seaf_warning ("Bad uploaded file list format.\n"); goto out; } g_queue_push_tail (mgr->priv->uploaded_files, g_strdup(json_string_value(iter))); } out: if (list) json_decref (list); g_free (path); g_free (txt); } #endif seadrive-fuse-3.0.13/src/http-tx-mgr.h000066400000000000000000000352521476177674700175340ustar00rootroot00000000000000#ifndef HTTP_TX_MGR_H #define HTTP_TX_MGR_H #include #include enum { HTTP_TASK_TYPE_DOWNLOAD = 0, HTTP_TASK_TYPE_UPLOAD, }; /** * The state that can be set by user. * * A task in NORMAL state can be canceled; * A task in RT_STATE_FINISHED can be removed. */ enum HttpTaskState { HTTP_TASK_STATE_NORMAL = 0, HTTP_TASK_STATE_CANCELED, HTTP_TASK_STATE_FINISHED, HTTP_TASK_STATE_ERROR, N_HTTP_TASK_STATE, }; enum HttpTaskRuntimeState { HTTP_TASK_RT_STATE_INIT = 0, HTTP_TASK_RT_STATE_CHECK, HTTP_TASK_RT_STATE_COMMIT, HTTP_TASK_RT_STATE_FS, HTTP_TASK_RT_STATE_BLOCK, /* Only used in upload. */ HTTP_TASK_RT_STATE_UPDATE_BRANCH, /* Only used in upload. */ HTTP_TASK_RT_STATE_FINISHED, N_HTTP_TASK_RT_STATE, }; enum HttpTaskError { HTTP_TASK_OK = 0, HTTP_TASK_ERR_FORBIDDEN, HTTP_TASK_ERR_NO_WRITE_PERMISSION, HTTP_TASK_ERR_NO_PERMISSION_TO_SYNC, HTTP_TASK_ERR_NET, HTTP_TASK_ERR_RESOLVE_PROXY, HTTP_TASK_ERR_RESOLVE_HOST, HTTP_TASK_ERR_CONNECT, HTTP_TASK_ERR_SSL, HTTP_TASK_ERR_TX, HTTP_TASK_ERR_TX_TIMEOUT, HTTP_TASK_ERR_UNHANDLED_REDIRECT, HTTP_TASK_ERR_SERVER, HTTP_TASK_ERR_BAD_REQUEST, HTTP_TASK_ERR_BAD_LOCAL_DATA, HTTP_TASK_ERR_NOT_ENOUGH_MEMORY, HTTP_TASK_ERR_WRITE_LOCAL_DATA, HTTP_TASK_ERR_NO_QUOTA, HTTP_TASK_ERR_FILES_LOCKED, HTTP_TASK_ERR_REPO_DELETED, HTTP_TASK_ERR_REPO_CORRUPTED, HTTP_TASK_ERR_FILE_LOCKED, HTTP_TASK_ERR_FOLDER_PERM_DENIED, HTTP_TASK_ERR_LIBRARY_TOO_LARGE, HTTP_TASK_ERR_TOO_MANY_FILES, HTTP_TASK_ERR_BLOCK_MISSING, HTTP_TASK_ERR_UNKNOWN, N_HTTP_TASK_ERROR, }; struct _SeafileSession; struct _HttpTxPriv; struct _HttpTxManager { struct _SeafileSession *seaf; struct _HttpTxPriv *priv; }; typedef struct _HttpTxManager HttpTxManager; struct _HttpTxTask { HttpTxManager *manager; char repo_id[37]; int repo_version; // repo_uname is the same as display_name. char *repo_uname; char *token; int protocol_version; int type; char *host; gboolean is_clone; char *email; gboolean use_fileserver_port; char *server; char *user; char head[41]; int state; int runtime_state; int error; /* Used to signify stop transfer for all threads. */ gboolean all_stop; /* For clone fs object progress */ int n_fs_objs; int done_fs_objs; /* For upload progress */ int n_blocks; int done_blocks; /* For download progress */ int n_files; int done_files; gint tx_bytes; /* bytes transferred in this second. */ gint last_tx_bytes; /* bytes transferred in the last second. */ char *unsyncable_path; }; typedef struct _HttpTxTask HttpTxTask; HttpTxManager * http_tx_manager_new (struct _SeafileSession *seaf); int http_tx_manager_start (HttpTxManager *mgr); int http_tx_manager_add_download (HttpTxManager *manager, const char *repo_id, int repo_version, const char *server, const char *user, const char *host, const char *token, const char *server_head_id, gboolean is_clone, int protocol_version, gboolean use_fileserver_port, GError **error); int http_tx_manager_add_upload (HttpTxManager *manager, const char *repo_id, int repo_version, const char *server, const char *user, const char *repo_uname, const char *host, const char *token, int protocol_version, gboolean use_fileserver_port, GError **error); struct _HttpProtocolVersion { gboolean check_success; /* TRUE if we get response from the server. */ gboolean not_supported; int version; int error_code; }; typedef struct _HttpProtocolVersion HttpProtocolVersion; typedef void (*HttpProtocolVersionCallback) (HttpProtocolVersion *result, void *user_data); /* Asynchronous interface for getting protocol version from a server. * Also used to determine if the server support http sync. */ int http_tx_manager_check_protocol_version (HttpTxManager *manager, const char *host, gboolean use_fileserver_port, HttpProtocolVersionCallback callback, void *user_data); typedef void (*HttpNotifServerCallback) (gboolean is_alive, void *user_data); int http_tx_manager_check_notif_server (HttpTxManager *manager, const char *host, gboolean use_notif_server_port, HttpNotifServerCallback callback, void *user_data); struct _HttpHeadCommit { gboolean check_success; gboolean perm_denied; gboolean is_corrupt; gboolean is_deleted; char head_commit[41]; int error_code; }; typedef struct _HttpHeadCommit HttpHeadCommit; typedef void (*HttpHeadCommitCallback) (HttpHeadCommit *result, void *user_data); /* Asynchronous interface for getting head commit info from a server. */ int http_tx_manager_check_head_commit (HttpTxManager *manager, const char *repo_id, int repo_version, const char *host, const char *token, gboolean use_fileserver_port, HttpHeadCommitCallback callback, void *user_data); typedef struct _HttpFolderPermReq { char repo_id[37]; char *token; gint64 timestamp; } HttpFolderPermReq; typedef struct _HttpFolderPermRes { char repo_id[37]; gint64 timestamp; GList *user_perms; GList *group_perms; } HttpFolderPermRes; void http_folder_perm_req_free (HttpFolderPermReq *req); void http_folder_perm_res_free (HttpFolderPermRes *res); struct _HttpFolderPerms { gboolean success; GList *results; /* List of HttpFolderPermRes */ }; typedef struct _HttpFolderPerms HttpFolderPerms; typedef void (*HttpGetFolderPermsCallback) (HttpFolderPerms *result, void *user_data); /* Asynchronous interface for getting folder permissions for a repo. */ int http_tx_manager_get_folder_perms (HttpTxManager *manager, const char *host, gboolean use_fileserver_port, GList *folder_perm_requests, /* HttpFolderPermReq */ HttpGetFolderPermsCallback callback, void *user_data); typedef struct _HttpLockedFilesReq { char repo_id[37]; char *token; gint64 timestamp; } HttpLockedFilesReq; typedef struct _HttpLockedFilesRes { char repo_id[37]; gint64 timestamp; GHashTable *locked_files; /* path -> by_me */ } HttpLockedFilesRes; void http_locked_files_req_free (HttpLockedFilesReq *req); void http_locked_files_res_free (HttpLockedFilesRes *res); struct _HttpLockedFiles { gboolean success; GList *results; /* List of HttpLockedFilesRes */ }; typedef struct _HttpLockedFiles HttpLockedFiles; typedef void (*HttpGetLockedFilesCallback) (HttpLockedFiles *result, void *user_data); /* Asynchronous interface for getting locked files for a repo. */ int http_tx_manager_get_locked_files (HttpTxManager *manager, const char *host, gboolean use_fileserver_port, GList *locked_files_requests, HttpGetLockedFilesCallback callback, void *user_data); /* Synchronous interface for locking/unlocking a file on the server. */ int http_tx_manager_lock_file (HttpTxManager *manager, const char *host, gboolean use_fileserver_port, const char *token, const char *repo_id, const char *path); int http_tx_manager_unlock_file (HttpTxManager *manager, const char *host, gboolean use_fileserver_port, const char *token, const char *repo_id, const char *path); /* Asynchronous interface for sending a API GET request to seahub. */ struct _HttpAPIGetResult { gboolean success; char *rsp_content; int rsp_size; int error_code; int http_status; }; typedef struct _HttpAPIGetResult HttpAPIGetResult; typedef void (*HttpAPIGetCallback) (HttpAPIGetResult *result, void *user_data); int http_tx_manager_api_get (HttpTxManager *manager, const char *host, const char *url, const char *token, HttpAPIGetCallback callback, void *user_data); int http_tx_manager_fileserver_api_get (HttpTxManager *manager, const char *host, const char *url, const char *token, HttpAPIGetCallback callback, void *user_data); int http_tx_task_download_file_blocks (HttpTxTask *task, const char *file_id); GList* http_tx_manager_get_upload_tasks (HttpTxManager *manager); GList* http_tx_manager_get_download_tasks (HttpTxManager *manager); HttpTxTask * http_tx_manager_find_task (HttpTxManager *manager, const char *repo_id); void http_tx_manager_cancel_task (HttpTxManager *manager, const char *repo_id, int task_type); /* Only useful for download task. */ void http_tx_manager_notify_conflict (HttpTxTask *task, const char *path); int http_tx_task_get_rate (HttpTxTask *task); const char * http_task_state_to_str (int state); const char * http_task_rt_state_to_str (int rt_state); const char * http_task_error_str (int task_errno); typedef size_t (*HttpRecvCallback) (void *, size_t, size_t, void *); int http_tx_manager_get_block (HttpTxManager *mgr, const char *host, gboolean use_fileserver_port, const char *token, const char *repo_id, const char *block_id, HttpRecvCallback blk_cb, void *user_data, int *http_status); int http_tx_manager_get_fs_object (HttpTxManager *mgr, const char *host, gboolean use_fileserver_port, const char *token, const char *repo_id, const char *obj_id, const char *file_path); int http_tx_manager_get_file (HttpTxManager *mgr, const char *host, gboolean use_fileserver_port, const char *token, const char *repo_id, const char *path, gint64 block_offset, HttpRecvCallback get_blk_cb, void *user_data, int *http_status); int http_tx_manager_api_move_file (HttpTxManager *mgr, const char *host, const char *api_token, const char *repo_id1, const char *oldpath, const char *repo_id2, const char *newpath, gboolean is_file); int http_tx_manager_get_file_block_map (HttpTxManager *mgr, const char *host, gboolean use_fileserver_port, const char *token, const char *repo_id, const char *file_id, gint64 **block_map, int *n_blocks); int http_tx_manager_api_get_space_usage (HttpTxManager *mgr, const char *host, const char *api_token, gint64 *total, gint64 *used); int http_tx_manager_get_commit (HttpTxManager *mgr, const char *host, gboolean use_fileserver_port, const char *sync_token, const char *repo_id, const char *commit_id, char **resp, gint64 *resp_size); int http_tx_manager_api_create_repo (HttpTxManager *mgr, const char *host, const char *api_token, const char *repo_name, char **resp, gint64 *resp_size); int http_tx_manager_api_rename_repo (HttpTxManager *mgr, const char *host, const char *api_token, const char *repo_id, const char *new_name); int http_tx_manager_api_delete_repo (HttpTxManager *mgr, const char *host, const char *api_token, const char *repo_id); gssize http_tx_manager_get_file_range (HttpTxManager *mgr, const char *host, const char *api_token, const char *repo_id, const char *path, char *buf, guint64 offset, size_t size); // "uploading_files": [{"file_path":, "uploaded":, "total_upload":}, ], "uploaded_files": [ten latest uploaded files]} json_t * http_tx_manager_get_upload_progress (HttpTxManager *mgr); #endif seadrive-fuse-3.0.13/src/job-mgr.c000066400000000000000000000063441476177674700166710ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ #include "common.h" #if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) #include #include #else #include #endif #include #include #include #include #include #include "utils.h" #include "log.h" #include "seafile-session.h" #include "job-mgr.h" struct _SeafJobManager { SeafileSession *session; GThreadPool *thread_pool; int next_job_id; }; struct _SeafJob { SeafJobManager *manager; int id; seaf_pipe_t pipefd[2]; JobThreadFunc thread_func; JobDoneCallback done_func; /* called when the thread is done */ void *data; /* the done callback should only access this field */ void *result; }; typedef struct _SeafJob SeafJob; SeafJob * seaf_job_new () { SeafJob *job; job = g_new0 (SeafJob, 1); return job; } void seaf_job_free (SeafJob *job) { g_free (job); } static void job_thread_wrapper (void *vdata, void *unused) { SeafJob *job = vdata; job->result = job->thread_func (job->data); if (seaf_pipe_writen (job->pipefd[1], "a", 1) != 1) { seaf_warning ("[Job Manager] write to pipe error: %s\n", strerror(errno)); } } static void job_done_cb (evutil_socket_t fd, short event, void *vdata) { SeafJob *job = vdata; char buf[1]; if (seaf_pipe_readn (job->pipefd[0], buf, 1) != 1) { seaf_warning ("[Job Manager] read pipe error: %s\n", strerror(errno)); } seaf_pipe_close (job->pipefd[0]); seaf_pipe_close (job->pipefd[1]); if (job->done_func) { job->done_func (job->result); } seaf_job_free (job); } int job_thread_create (SeafJob *job) { SeafileSession *session = job->manager->session; if (seaf_pipe (job->pipefd) < 0) { seaf_warning ("[Job Manager] pipe error: %s\n", strerror(errno)); return -1; } g_thread_pool_push (job->manager->thread_pool, job, NULL); event_base_once (session->ev_base, job->pipefd[0], EV_READ, job_done_cb, job, NULL); return 0; } SeafJobManager * seaf_job_manager_new (SeafileSession *session, int max_threads) { SeafJobManager *mgr; mgr = g_new0 (SeafJobManager, 1); mgr->session = session; mgr->thread_pool = g_thread_pool_new (job_thread_wrapper, NULL, max_threads, FALSE, NULL); return mgr; } void seaf_job_manager_free (SeafJobManager *mgr) { g_thread_pool_free (mgr->thread_pool, TRUE, FALSE); g_free (mgr); } int seaf_job_manager_schedule_job (SeafJobManager *mgr, JobThreadFunc func, JobDoneCallback done_func, void *data) { SeafJob *job = seaf_job_new (); job->id = mgr->next_job_id++; job->manager = mgr; job->thread_func = func; job->done_func = done_func; job->data = data; if (job_thread_create (job) < 0) { seaf_job_free (job); return -1; } return 0; } seadrive-fuse-3.0.13/src/job-mgr.h000066400000000000000000000016241476177674700166720ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /** * Job Manager manages long term jobs. These jobs are run in their * own threads. */ #ifndef SEAF_JOB_MGR_H #define SEAF_JOB_MGR_H struct _SeafJobManager; typedef struct _SeafJobManager SeafJobManager; struct _SeafileSession; /* The thread func should return the result back by return (void *)result; The result will be passed to JobDoneCallback. */ typedef void* (*JobThreadFunc)(void *data); typedef void (*JobDoneCallback)(void *result); SeafJobManager * seaf_job_manager_new (struct _SeafileSession *session, int max_threads); void seaf_job_manager_free (struct _SeafJobManager *mgr); int seaf_job_manager_schedule_job (struct _SeafJobManager *mgr, JobThreadFunc func, JobDoneCallback done_func, void *data); #endif seadrive-fuse-3.0.13/src/journal-mgr.c000066400000000000000000000367551476177674700176020ustar00rootroot00000000000000#include "common.h" #include #include "log.h" #include "db.h" #include "utils.h" #include "seafile-session.h" #include "journal-mgr.h" struct _Journal { char *repo_id; /* The journal associates a worker thread that continuously writes * op entries into the database. It reads from @op_queue and commits a * transaction to the database in two cases: * 1. 100 op entries have been received. * 2. no new op entry is received in 1 second. */ GAsyncQueue *op_queue; gint pushed_ops; gint committed_ops; /* The underlying database for the journal. * To reduce the write latency, ops are inserted into sqlite in * 100-entry transactions. */ char *jdb_path; sqlite3 *jdb; pthread_mutex_t jdb_lock; gboolean write_failed; char *error_message; /* Statistics used for generating commit. */ /* Time of last operation written to db */ gint last_change_time; /* Time of last commit generation */ gint last_commit_time; /* Total size of files waiting for commit. */ gint64 total_size; }; struct _JournalManager { SeafileSession *session; char *journal_dir; }; JournalManager * journal_manager_new (struct _SeafileSession *session) { JournalManager *manager = g_new0 (JournalManager, 1); manager->session = session; char *journal_dir = g_build_filename (session->seaf_dir, "journals", NULL); if (g_mkdir_with_parents (journal_dir, 0777) < 0) { seaf_warning ("Journal dir doesn't exist and failed to create it.\n"); g_free (manager); g_free (journal_dir); return NULL; } manager->journal_dir = journal_dir; return manager; } int journal_manager_start (JournalManager *mgr) { return 0; } JournalOp * journal_op_new (JournalOpType type, const char *path, const char *new_path, gint64 size, gint64 mtime, guint32 mode) { JournalOp *op = g_new0 (JournalOp, 1); op->type = type; op->path = g_strdup(path); op->new_path = g_strdup(new_path); op->size = size; op->mtime = mtime; op->mode = mode; return op; } void journal_op_free (JournalOp *op) { if (!op) return; g_free (op->path); g_free (op->new_path); g_free (op); } static gboolean journal_op_same (JournalOp *op1, JournalOp *op2) { return ((op1->type == op2->type) && (g_strcmp0(op1->path, op2->path) == 0) && (g_strcmp0(op1->new_path, op2->new_path) == 0)); } JournalOp * journal_op_from_json (const char *json_str) { json_t *obj; json_error_t error; int type; const char *path = NULL, *new_path = NULL; gint64 size = 0, mtime = 0; guint32 mode = 0; JournalOp *op = NULL; obj = json_loads (json_str, 0, &error); if (!obj) { seaf_warning ("Invalid json format for operation: %s.\n" "JSON string: %s\n", error.text, json_str); return NULL; } if (!json_object_has_member (obj, "type")) { seaf_warning ("Invalid operation format: no type.\n"); goto out; } type = (int)json_object_get_int_member (obj, "type"); if (!json_object_has_member (obj, "path")) { seaf_warning ("Invalid operation format: no path.\n"); goto out; } path = json_object_get_string_member (obj, "path"); if (!path) { seaf_warning ("Invalid operation format: null path.\n"); goto out; } if (type == OP_TYPE_RENAME) { new_path = json_object_get_string_member (obj, "new_path"); if (!new_path) { seaf_warning ("Invalid operation format: no new_path.\n"); goto out; } } else if (type == OP_TYPE_CREATE_FILE || type == OP_TYPE_UPDATE_FILE || type == OP_TYPE_UPDATE_ATTR) { if (!json_object_has_member (obj, "size")) { seaf_warning ("Invalid operation format: no size.\n"); goto out; } size = json_object_get_int_member (obj, "size"); if (!json_object_has_member (obj, "mtime")) { seaf_warning ("Invalid operation format: no mtime.\n"); goto out; } mtime = json_object_get_int_member (obj, "mtime"); if (!json_object_has_member (obj, "mode")) { seaf_warning ("Invalid operation format: no mode.\n"); goto out; } mode = json_object_get_int_member (obj, "mode"); } op = journal_op_new (type, path, new_path, size, mtime, mode); out: json_decref (obj); return op; } char * journal_op_to_json (JournalOp *op) { json_t *obj; char *json_str = NULL; obj = json_object (); json_object_set_int_member (obj, "type", (int)op->type); json_object_set_string_member (obj, "path", op->path); if (op->type == OP_TYPE_RENAME) { json_object_set_string_member (obj, "new_path", op->new_path); } else if (op->type == OP_TYPE_CREATE_FILE || op->type == OP_TYPE_UPDATE_FILE || op->type == OP_TYPE_UPDATE_ATTR) { json_object_set_int_member (obj, "size", op->size); json_object_set_int_member (obj, "mtime", op->mtime); json_object_set_int_member (obj, "mode", op->mode); } json_str = json_dumps (obj, JSON_COMPACT); if (!json_str) { seaf_warning ("Failed to dump json for operation.\n"); } json_decref (obj); return json_str; } static void *write_journal_worker (void *vdata); static void journal_free (Journal *journal) { if (!journal) return; g_free (journal->repo_id); g_async_queue_unref (journal->op_queue); g_free (journal->jdb_path); if (journal->jdb) sqlite_close_db (journal->jdb); pthread_mutex_destroy (&journal->jdb_lock); g_free (journal); } static int do_open_journal (JournalManager *mgr, Journal *journal) { sqlite3 *db = NULL; char *sql; if (sqlite_open_db (journal->jdb_path, &db) < 0) { return -1; } sql = "CREATE TABLE IF NOT EXISTS Operations (" "opid INTEGER PRIMARY KEY AUTOINCREMENT, operation TEXT)"; if (sqlite_query_exec (db, sql) < 0) { sqlite_close_db (db); return -1; } pthread_t tid; pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); if (pthread_create (&tid, &attr, write_journal_worker, journal) < 0) { seaf_warning ("Failed to start write journal worker.\n"); sqlite_close_db (db); return -1; } journal->jdb = db; return 0; } Journal * journal_manager_open_journal (JournalManager *mgr, const char *repo_id) { char *jdb_path = NULL; Journal *jnl = NULL; jdb_path = g_strdup_printf ("%s/%s.journal", mgr->journal_dir, repo_id); jnl = g_new0 (Journal, 1); jnl->repo_id = g_strdup(repo_id); jnl->op_queue = g_async_queue_new_full ((GDestroyNotify)journal_op_free); jnl->jdb_path = jdb_path; pthread_mutex_init (&jnl->jdb_lock, NULL); /* Delay creating jdb and worker thread to the time when an op is appended. */ if (seaf_util_exists (jdb_path)) { if (do_open_journal (mgr, jnl) < 0) { journal_free (jnl); return NULL; } } return jnl; } int journal_manager_delete_journal (JournalManager *mgr, const char *repo_id) { char *jdb_path = NULL; int ret; int n = 0; jdb_path = g_strdup_printf ("%s/%s.journal", mgr->journal_dir, repo_id); do { if (n > 0) g_usleep (1000000); ret = seaf_util_unlink (jdb_path); n++; if (ret < 0 && n == 10) seaf_warning ("Failed to delete journal for repo %s after %d retries " "due to permission issues. Continue retrying.\n", repo_id, n); } while (ret < 0 && errno == EACCES); g_free (jdb_path); return ret; } void journal_flush (Journal *journal, int timeout) { gint pushed, committed; int elapsed = 0; while (1) { pushed = g_atomic_int_get (&journal->pushed_ops); committed = g_atomic_int_get (&journal->committed_ops); if (pushed == committed) { break; } else if (timeout < 0 || elapsed < timeout) { g_usleep (G_USEC_PER_SEC); elapsed++; } else { break; } } } int journal_close (Journal *journal, gboolean wait) { if (!journal->jdb) { journal_free (journal); return 0; } if (wait) { journal_flush (journal, -1); } JournalOp *op = journal_op_new (OP_TYPE_TERMINATE, NULL, NULL, 0, 0, 0); g_async_queue_push (journal->op_queue, op); /* journal will be free in the worker thread. Otherwise the worker thread * may dereference the freed journal structure. */ return 0; } static int insert_op (Journal *journal, JournalOp *op, sqlite3_stmt *stmt) { char *op_json; int ret = 0; op_json = journal_op_to_json (op); if (sqlite3_bind_text (stmt, 1, op_json, -1, SQLITE_TRANSIENT) != SQLITE_OK) { journal->write_failed = TRUE; journal->error_message = g_strdup(sqlite3_errmsg(journal->jdb)); seaf_warning ("sqlite3_bind_text failed: %s\n", journal->error_message); ret = -1; goto out; } if (sqlite3_step (stmt) != SQLITE_DONE) { journal->write_failed = TRUE; journal->error_message = g_strdup(sqlite3_errmsg(journal->jdb)); seaf_warning ("sqlite3_step failed: %s\n", journal->error_message); ret = -1; goto out; } out: sqlite3_clear_bindings (stmt); sqlite3_reset (stmt); g_free (op_json); return ret; } static void flush_write_queue (Journal *journal, GQueue *queue) { JournalOp *op; gint committed = 0; sqlite3 *jdb = journal->jdb; char *sql; sqlite3_stmt *stmt = NULL; if (g_queue_get_length (queue) == 0) return; pthread_mutex_lock (&journal->jdb_lock); sqlite_begin_transaction (jdb); sql = "INSERT INTO Operations (operation) VALUES (?)"; if (sqlite3_prepare_v2 (jdb, sql, -1, &stmt, NULL) != SQLITE_OK) { journal->write_failed = TRUE; journal->error_message = g_strdup(sqlite3_errmsg(jdb)); sqlite_rollback_transaction (jdb); goto out; } while ((op = (JournalOp *)g_queue_pop_head(queue)) != NULL) { if (insert_op (journal, op, stmt) < 0) { /* Put the failed op back to the queue for retry in the next second. * But previously inserted ops should still be committed. */ g_queue_push_head (queue, op); break; } journal_op_free (op); ++committed; } if (sqlite_end_transaction (jdb) < 0) { journal->write_failed = TRUE; journal->error_message = g_strdup(sqlite3_errmsg(jdb)); sqlite_rollback_transaction (jdb); goto out; } if (journal->write_failed) { journal->write_failed = FALSE; g_free (journal->error_message); journal->error_message = NULL; } g_atomic_int_add (&journal->committed_ops, committed); g_atomic_int_set (&journal->last_change_time, (gint)time(NULL)); out: pthread_mutex_unlock (&journal->jdb_lock); if (stmt) sqlite3_finalize (stmt); } #define FLUSH_QUEUE_TIMEOUT G_USEC_PER_SEC #define WRITE_BATCH 100 static void * write_journal_worker (void *vdata) { Journal *journal = (Journal *)vdata; JournalOp *op, *prev_op; GQueue *write_queue = g_queue_new (); while (1) { op = (JournalOp *)g_async_queue_timeout_pop (journal->op_queue, FLUSH_QUEUE_TIMEOUT); if (!op) { /* Time out. No op came in in the last second. * Flush the write queue to db. */ flush_write_queue (journal, write_queue); continue; } if (op->type == OP_TYPE_TERMINATE) { g_queue_free_full (write_queue, (GDestroyNotify)journal_op_free); journal_free (journal); break; } prev_op = g_queue_peek_tail (write_queue); if (prev_op && journal_op_same (op, prev_op)) { if (op->type == OP_TYPE_UPDATE_FILE || op->type == OP_TYPE_UPDATE_ATTR) { prev_op->size = op->size; prev_op->mtime = op->mtime; } journal_op_free (op); g_atomic_int_inc (&journal->committed_ops); continue; } else { g_queue_push_tail (write_queue, op); } if (g_queue_get_length (write_queue) >= WRITE_BATCH || ((gint)time(NULL) - journal->last_change_time >= FLUSH_QUEUE_TIMEOUT)) { flush_write_queue (journal, write_queue); } } return NULL; } int journal_append_op (Journal *journal, JournalOp *op) { if (!seaf_util_exists (journal->jdb_path)) { if (do_open_journal (seaf->journal_mgr, journal) < 0) return -1; } g_async_queue_push (journal->op_queue, op); g_atomic_int_inc (&journal->pushed_ops); return 0; } static gboolean collect_ops (sqlite3_stmt *stmt, void *vdata) { GList **pret = vdata; gint64 opid; const char *op_json; JournalOp *op; opid = (gint64) sqlite3_column_int64 (stmt, 0); op_json = (const char *) sqlite3_column_text (stmt, 1); op = journal_op_from_json (op_json); if (!op) return TRUE; op->opid = opid; *pret = g_list_prepend (*pret, op); return TRUE; } GList * journal_read_ops (Journal *journal, gint64 start, gint64 end, gboolean *error) { char *sql; GList *ret = NULL; if (error) *error = FALSE; if (!journal->jdb) return NULL; if (start < 0 || end < 0) { seaf_warning ("BUG: start or end out of range.\n"); return NULL; } if (end != G_MAXINT64) { sql = sqlite3_mprintf ("SELECT opid, operation FROM Operations " "WHERE opid >= %lld AND " "opid <= %lld ORDER BY opid", start, end); } else { sql = sqlite3_mprintf ("SELECT opid, operation FROM Operations " "WHERE opid >= %lld ORDER BY opid", start, end); } pthread_mutex_lock (&journal->jdb_lock); if (sqlite_foreach_selected_row (journal->jdb, sql, collect_ops, &ret) < 0) { seaf_warning ("Failed to read ops from journal for repo %s.\n", journal->repo_id); g_list_free_full (ret, (GDestroyNotify)journal_op_free); ret = NULL; if (error) *error = TRUE; goto out; } ret = g_list_reverse (ret); out: pthread_mutex_unlock (&journal->jdb_lock); sqlite3_free (sql); return ret; } int journal_truncate (Journal *journal, gint64 opid) { char *sql; int ret = 0; if (!journal->jdb) return 0; sql = sqlite3_mprintf ("DELETE FROM Operations WHERE opid <= %lld", opid); pthread_mutex_lock (&journal->jdb_lock); if (sqlite_query_exec (journal->jdb, sql) < 0) { seaf_warning ("Failed to truncate journal for repo %s.\n", journal->repo_id); ret = -1; } pthread_mutex_unlock (&journal->jdb_lock); sqlite3_free (sql); return ret; } void journal_get_stat (Journal *journal, JournalStat *st) { st->last_change_time = g_atomic_int_get (&journal->last_change_time); st->last_commit_time = journal->last_commit_time; st->total_size = journal->total_size; } void journal_set_last_commit_time (Journal *journal, gint time) { journal->last_commit_time = time; } seadrive-fuse-3.0.13/src/journal-mgr.h000066400000000000000000000047701476177674700175770ustar00rootroot00000000000000#ifndef JOURNAL_MGR_H #define JOURNAL_MGR_H #include struct _SeafileSession; struct _JournalManager; typedef struct _JournalManager JournalManager; JournalManager * journal_manager_new (struct _SeafileSession *session); int journal_manager_start (JournalManager *mgr); struct _Journal; typedef struct _Journal Journal; Journal * journal_manager_open_journal (JournalManager *mgr, const char *repo_id); int journal_manager_delete_journal (JournalManager *mgr, const char *repo_id); /* New types should only be added to the end of this declaration. * The type numbers are stored directly to db. Inserting new types in the middle * will break compatibility. */ enum _JournalOpType { OP_TYPE_CREATE_FILE = 0, OP_TYPE_DELETE_FILE, OP_TYPE_UPDATE_FILE, OP_TYPE_RENAME, OP_TYPE_MKDIR, OP_TYPE_RMDIR, OP_TYPE_UPDATE_ATTR, OP_TYPE_TERMINATE, /* Special op for journal's internal use. */ }; typedef enum _JournalOpType JournalOpType; struct _JournalOp { gint64 opid; /* Only set when the op is read from journal */ JournalOpType type; char *path; char *new_path; gint64 size; gint64 mtime; guint32 mode; }; typedef struct _JournalOp JournalOp; JournalOp * journal_op_new (JournalOpType type, const char *path, const char *new_path, gint64 size, gint64 mtime, guint32 mode); void journal_op_free (JournalOp *op); /* This function takes the ownership of @op. */ int journal_append_op (Journal *journal, JournalOp *op); /* Returns the list of operations with opid from @start to @end, inclusively. * The returned list is sorted by opid, from small to large. * To get operations from the every beginning, set @start to 0; * To get operations up to the last one, set @end to G_MAXINT64. */ GList * journal_read_ops (Journal *journal, gint64 start, gint64 end, gboolean *error); /* * Truncate journal entries before @opid, inclusive. */ int journal_truncate (Journal *journal, gint64 opid); /* Wait until all journal ops are flush to disk, with timeout. * Wait indefinitely if timeout < 0. */ void journal_flush (Journal *journal, int timeout); /* @wait: wait until all ops are committed to disk. */ int journal_close (Journal *journal, gboolean wait); struct _JournalStat { gint last_change_time; gint last_commit_time; gint64 total_size; }; typedef struct _JournalStat JournalStat; void journal_get_stat (Journal *journal, JournalStat *st); void journal_set_last_commit_time (Journal *journal, gint time); #endif seadrive-fuse-3.0.13/src/log.c000066400000000000000000000061231476177674700161100ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ #include "common.h" #include #include #include #include "log.h" #include "utils.h" /* message with greater log levels will be ignored */ static int seafile_log_level; static char *logfile; static FILE *logfp; static void seafile_log (const gchar *log_domain, GLogLevelFlags log_level, const gchar *message, gpointer user_data) { time_t t; struct tm *tm; char buf[1024]; int len; if (log_level > seafile_log_level) return; t = time(NULL); tm = localtime(&t); len = strftime (buf, 1024, "[%x %X] ", tm); g_return_if_fail (len < 1024); if (logfp != NULL) { fputs (buf, logfp); fputs (message, logfp); fflush (logfp); } else { // log file not available printf("%s %s", buf, message); } } int seafile_log_init (const char *_logfile) { g_log_set_handler (NULL, G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION, seafile_log, NULL); /* record all log message */ seafile_log_level = G_LOG_LEVEL_DEBUG; if (strcmp(_logfile, "-") == 0) { logfp = stdout; logfile = g_strdup (_logfile); } else { logfile = ccnet_expand_path(_logfile); if ((logfp = g_fopen (logfile, "a+")) == NULL) { seaf_message ("Failed to open file %s\n", logfile); return -1; } } return 0; } int seafile_log_reopen () { FILE *fp, *oldfp; if (strcmp(logfile, "-") == 0) return 0; if ((fp = g_fopen (logfile, "a+")) == NULL) { seaf_message ("Failed to open file %s\n", logfile); return -1; } //TODO: check file's health oldfp = logfp; logfp = fp; if (fclose(oldfp) < 0) { seaf_message ("Failed to close file %s\n", logfile); return -1; } return 0; } static SeafileDebugFlags debug_flags = 0; static GDebugKey debug_keys[] = { { "Transfer", SEAFILE_DEBUG_TRANSFER }, { "Sync", SEAFILE_DEBUG_SYNC }, { "Watch", SEAFILE_DEBUG_WATCH }, { "Http", SEAFILE_DEBUG_HTTP }, { "Merge", SEAFILE_DEBUG_MERGE }, { "Fs", SEAFILE_DEBUG_FS }, { "Curl", SEAFILE_DEBUG_CURL }, { "Notification", SEAFILE_DEBUG_NOTIFICATION }, { "Other", SEAFILE_DEBUG_OTHER }, }; gboolean seafile_debug_flag_is_set (SeafileDebugFlags flag) { return (debug_flags & flag) != 0; } void seafile_debug_set_flags (SeafileDebugFlags flags) { g_message ("Set debug flags %#x\n", flags); debug_flags |= flags; } void seafile_debug_set_flags_string (const gchar *flags_string) { guint nkeys = G_N_ELEMENTS (debug_keys); if (flags_string) seafile_debug_set_flags ( g_parse_debug_string (flags_string, debug_keys, nkeys)); } void seafile_debug_impl (SeafileDebugFlags flag, const gchar *format, ...) { if (flag & debug_flags) { va_list args; va_start (args, format); g_logv (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, format, args); va_end (args); } } FILE * seafile_get_logfp () { return logfp; } seadrive-fuse-3.0.13/src/log.h000066400000000000000000000023741476177674700161210ustar00rootroot00000000000000#ifndef LOG_H #define LOG_H #include #define SEAFILE_DOMAIN g_quark_from_string("seafile") #ifndef seaf_warning #define seaf_warning(fmt, ...) g_warning("%s(%d): " fmt, __FILE__, __LINE__, ##__VA_ARGS__) #endif #ifndef seaf_message #define seaf_message(fmt, ...) g_message("%s(%d): " fmt, __FILE__, __LINE__, ##__VA_ARGS__) #endif int seafile_log_init (const char *logfile); int seafile_log_reopen (); void seafile_debug_set_flags_string (const gchar *flags_string); typedef enum { SEAFILE_DEBUG_TRANSFER = 1 << 1, SEAFILE_DEBUG_SYNC = 1 << 2, SEAFILE_DEBUG_WATCH = 1 << 3, /* wt-monitor */ SEAFILE_DEBUG_HTTP = 1 << 4, /* http server */ SEAFILE_DEBUG_MERGE = 1 << 5, SEAFILE_DEBUG_FS = 1 << 6, /* Virtual FS */ SEAFILE_DEBUG_CURL = 1 << 7, /* libcurl verbose output */ SEAFILE_DEBUG_NOTIFICATION = 1 << 8, SEAFILE_DEBUG_OTHER = 1 << 9, } SeafileDebugFlags; gboolean seafile_debug_flag_is_set (SeafileDebugFlags flag); void seafile_debug_impl (SeafileDebugFlags flag, const gchar *format, ...); #ifdef DEBUG_FLAG #undef seaf_debug #define seaf_debug(fmt, ...) \ seafile_debug_impl (DEBUG_FLAG, "%.10s(%d): " fmt, __FILE__, __LINE__, ##__VA_ARGS__) #endif /* DEBUG_FLAG */ #endif FILE *seafile_get_logfp (); seadrive-fuse-3.0.13/src/mq-mgr.c000066400000000000000000000030171476177674700165260ustar00rootroot00000000000000#include "common.h" #include "log.h" #include "utils.h" #include "mq-mgr.h" typedef struct MqMgrPriv { // chan <-> async_queue GHashTable *chans; } MqMgrPriv; MqMgr * mq_mgr_new () { MqMgr *mgr = g_new0 (MqMgr, 1); mgr->priv = g_new0 (MqMgrPriv, 1); mgr->priv->chans = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify)g_free, (GDestroyNotify)g_async_queue_unref); return mgr; } void mq_mgr_init (MqMgr *mgr) { g_hash_table_replace (mgr->priv->chans, g_strdup (SEADRIVE_NOTIFY_CHAN), g_async_queue_new_full ((GDestroyNotify)json_decref)); g_hash_table_replace (mgr->priv->chans, g_strdup (SEADRIVE_EVENT_CHAN), g_async_queue_new_full ((GDestroyNotify)json_decref)); } void mq_mgr_push_msg (MqMgr *mgr, const char *chan, json_t *msg) { GAsyncQueue *async_queue = g_hash_table_lookup (mgr->priv->chans, chan); if (!async_queue) { seaf_warning ("Unkonwn message channel %s.\n", chan); return; } if (!msg) { seaf_warning ("Msg should not be NULL.\n"); return; } g_async_queue_push (async_queue, msg); } json_t * mq_mgr_pop_msg (MqMgr *mgr, const char *chan) { GAsyncQueue *async_queue = g_hash_table_lookup (mgr->priv->chans, chan); if (!async_queue) { seaf_warning ("Unkonwn message channel %s.\n", chan); return NULL; } return g_async_queue_try_pop (async_queue); } seadrive-fuse-3.0.13/src/mq-mgr.h000066400000000000000000000006211476177674700165310ustar00rootroot00000000000000#ifndef MQ_MGR_H #define MQ_MGR_H #define SEADRIVE_NOTIFY_CHAN "seadrive.notification" #define SEADRIVE_EVENT_CHAN "seadrive.event" struct MqMgrPriv; typedef struct MqMgr { struct MqMgrPriv *priv; } MqMgr; MqMgr * mq_mgr_new (); void mq_mgr_init (MqMgr *mgr); void mq_mgr_push_msg (MqMgr *mgr, const char *chan, json_t *msg); json_t * mq_mgr_pop_msg (MqMgr *mgr, const char *chan); #endif seadrive-fuse-3.0.13/src/notif-mgr.c000066400000000000000000000670521476177674700172410ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ #include "common.h" #include #include #include #include "seafile-session.h" #include "notif-mgr.h" #include "sync-mgr.h" #define DEBUG_FLAG SEAFILE_DEBUG_NOTIFICATION #include "log.h" #define NOTIF_PORT 8083 #define RECONNECT_INTERVAL 60 /* 60s */ #define STATUS_DISCONNECTED 0 #define STATUS_CONNECTED 1 #define STATUS_ERROR 2 #define STATUS_CANCELLED 3 typedef struct NotifServer { struct lws_context *context; struct lws_client_connect_info i; struct lws *wsi; // status of the notification server. int status; // whether to close the connection to the server. gboolean close; GHashTable *subscriptions; pthread_mutex_t sub_lock; GAsyncQueue *messages; gboolean use_ssl; char *server_url; char *addr; char *path; int port; gint refcnt; } NotifServer; struct _SeafNotifManagerPriv { pthread_mutex_t server_lock; GHashTable *servers; }; // The Message structure is used to send messages to the server. typedef struct Message { void *payload; size_t len; int type; } Message; static Message* notif_message_new (const char *str, int type) { int len, n; len = strlen(str) + 1; Message *msg = g_new0 (Message, 1); msg->payload = malloc((unsigned int)(LWS_PRE + len)); if (!msg->payload) { g_free (msg); return NULL; } // The libwebsockets library requires the message to be sent with a LWS_PRE header. n = lws_snprintf((char *)msg->payload + LWS_PRE, (unsigned int)len, "%s", str); msg->len = (unsigned int)n; msg->type = type; return msg; } static void notif_message_free (Message *msg) { if (!msg) return; g_free (msg->payload); g_free (msg); } SeafNotifManager * seaf_notif_manager_new (SeafileSession *seaf) { SeafNotifManager *mgr = g_new0 (SeafNotifManager, 1); mgr->seaf = seaf; mgr->priv = g_new0 (SeafNotifManagerPriv, 1); pthread_mutex_init (&mgr->priv->server_lock, NULL); mgr->priv->servers = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); return mgr; } typedef struct URI { char *scheme; char *host; int port; } URI; // Assume that the input url format is http[s]://host:port. static URI* parse_notification_url (const char *url) { const char *server = url; char *colon; char *url_no_port; char *scheme = NULL; URI *uri = NULL; int port; if (strncmp(url, "https://", 8) == 0) { scheme = g_strdup ("https"); server = url + 8; port = 443; } else if (strncmp (url, "http://", 7) == 0) { scheme = g_strdup ("http"); server = url + 7; port = 80; } if (!server) { return NULL; } uri = g_new0 (URI, 1); uri->scheme = scheme; colon = strrchr (server, ':'); if (colon) { url_no_port = g_strndup(server, colon - server); uri->host = g_strdup (url_no_port); if (colon + 1) port = atoi (colon + 1); uri->host = url_no_port; uri->port = port; return uri; } uri->host = g_strdup (server); uri->port = port; return uri; } static void notif_server_ref (NotifServer *server); static struct lws_context * lws_context_new (int port); static NotifServer* notif_new_server (const char *server_url, gboolean use_notif_server_port) { NotifServer *server = NULL; static struct lws_context *context; URI *uri = NULL; int port = NOTIF_PORT; gboolean use_ssl = FALSE; uri = parse_notification_url (server_url); if (!uri) { seaf_warning ("failed to parse notification url from %s\n", server_url); return NULL; } // If use_notif_server_port is FALSE, the server should be deployed behind Nginx. // In this case we'll use the same port as Seafile server. if (!use_notif_server_port) { port = uri->port; } if (strncmp(server_url, "https", 5) == 0) { use_ssl = TRUE; } context = lws_context_new (use_ssl); if (!context) { g_free (uri->scheme); g_free (uri->host); g_free (uri); seaf_warning ("failed to create libwebsockets context\n"); return NULL; } server = g_new0 (NotifServer, 1); server->messages = g_async_queue_new (); server->context = context; server->server_url = g_strdup (server_url); server->addr = g_strdup (uri->host); server->use_ssl = use_ssl; if (use_notif_server_port) server->path = g_strdup ("/"); else server->path = g_strdup ("/notification"); server->port = port; pthread_mutex_init (&server->sub_lock, NULL); server->subscriptions = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); notif_server_ref (server); g_free (uri->scheme); g_free (uri->host); g_free (uri); return server; } NotifServer* get_notif_server (SeafNotifManager *mgr, const char *url) { NotifServer *server = NULL; pthread_mutex_lock (&mgr->priv->server_lock); server = g_hash_table_lookup (mgr->priv->servers, url); if (!server) { pthread_mutex_unlock (&mgr->priv->server_lock); return NULL; } notif_server_ref (server); pthread_mutex_unlock (&mgr->priv->server_lock); return server; } static void delete_subscribed_repos (NotifServer *server); static void delete_unsent_messages (NotifServer *server); static void notif_server_free (NotifServer *server) { if (!server) return; if (server->context) lws_context_destroy(server->context); g_free (server->server_url); g_free (server->addr); g_free (server->path); if (server->subscriptions) g_hash_table_destroy (server->subscriptions); delete_unsent_messages (server); g_async_queue_unref (server->messages); g_free (server); } static void notif_server_ref (NotifServer *server) { g_atomic_int_inc (&server->refcnt); } static void notif_server_unref (NotifServer *server) { if (!server) return; if (g_atomic_int_dec_and_test (&server->refcnt)) notif_server_free (server); } static void init_client_connect_info (NotifServer *server); static void * notification_worker (void *vdata); // This function will check whether the notification server has been created, // if not, it will create a new one, otherwise it will return directly. // The host is the server's url and use_notif_server_port is used to check whether the server has nginx deployed. void seaf_notif_manager_connect_server (SeafNotifManager *mgr, const char *host, gboolean use_notif_server_port) { pthread_t tid; int rc; NotifServer *existing_server = NULL; NotifServer *server = NULL; // Don't connect a connected server. existing_server = get_notif_server (mgr, host); if (existing_server) { notif_server_unref (existing_server); return; } server = notif_new_server (host, use_notif_server_port); if (!server) return; init_client_connect_info (server); rc = pthread_create (&tid, NULL, notification_worker, server); if (rc != 0) { seaf_warning ("Failed to create event notification new thread: %s.\n", strerror(rc)); notif_server_unref (server); return; } pthread_mutex_lock (&mgr->priv->server_lock); g_hash_table_insert (mgr->priv->servers, g_strdup (host), server); pthread_mutex_unlock (&mgr->priv->server_lock); return; } static void disconnect_server (NotifServer *server) { // lws_cancel_service will produce a cancel event to break out of lws_service loop. lws_cancel_service (server->context); server->close = TRUE; } // This policy will send a ping packet to the server per second. // If we don't receive pong messages within 5 seconds, it is considered that the connection is unavailable. // We will exit the event loop, and reconnect to the notification server. static const lws_retry_bo_t ping_policy = { .secs_since_valid_ping = 1, .secs_since_valid_hangup = 5, }; static void init_client_connect_info (NotifServer *server) { struct lws_client_connect_info *i = &server->i; memset(i, 0, sizeof(server->i)); i->context = server->context; i->port = server->port; i->address = server->addr; i->path = server->path; i->host = i->address; i->origin = i->address; if (server->use_ssl) { i->ssl_connection = LCCSCF_USE_SSL | LCCSCF_ALLOW_SELFSIGNED; } i->protocol = "notification.seafile.com"; i->local_protocol_name = "notification.seafile.com"; i->pwsi = &server->wsi; i->retry_and_idle_policy = &ping_policy; i->userdata = server; } static void handle_messages (const char *msg, size_t len); // success:0 static int event_callback (struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { NotifServer *server = (NotifServer *)user; Message *msg = NULL; int m; int ret = 0; if (!server) { return ret; } seaf_debug ("Notification event: %d\n", reason); switch (reason) { case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: server->status = STATUS_ERROR; seaf_debug ("websocket connection error: %s\n", in ? (char *)in : "(null)"); ret = -1; break; case LWS_CALLBACK_CLIENT_RECEIVE: handle_messages (in, len); break; case LWS_CALLBACK_CLIENT_WRITEABLE: msg = g_async_queue_try_pop (server->messages); if (!msg) { break; } /* notice we allowed for LWS_PRE in the payload already */ m = lws_write(wsi, ((unsigned char *)msg->payload) + LWS_PRE, msg->len, msg->type); if (m < (int)msg->len) { notif_message_free (msg); seaf_warning ("Failed to write message to websocket\n"); server->status = STATUS_ERROR; return -1; } notif_message_free (msg); break; case LWS_CALLBACK_CLIENT_ESTABLISHED: seaf_sync_manager_check_locks_and_folder_perms (seaf->sync_mgr, server->server_url); server->status = STATUS_CONNECTED; seaf_debug ("Successfully connected to the server: %s\n", server->server_url); break; case LWS_CALLBACK_CLIENT_CLOSED: ret = -1; server->status = STATUS_ERROR; break; case LWS_CALLBACK_EVENT_WAIT_CANCELLED: ret = -1; server->status = STATUS_CANCELLED; break; default: break; } return ret; } static int handle_repo_update (json_t *content) { json_t *member; const char *repo_id; const char *commit_id; SeafRepo *repo = NULL; int ret = 0; member = json_object_get (content, "repo_id"); if (!member) { seaf_warning ("Invalid repo update notification: no repo_id.\n"); return -1; } repo_id = json_string_value (member); repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id); if (!repo) { return -1; } if (!seaf_notif_manager_is_repo_subscribed (seaf->notif_mgr, repo)) { ret = -1; goto out; } member = json_object_get (content, "commit_id"); if (!member) { seaf_warning ("Invalid repo update notification: no commit_id.\n"); ret = -1; goto out; } commit_id = json_string_value (member); if (!commit_id) { seaf_warning ("Invalid repo update notification: commit_id is null.\n"); ret = -1; goto out; } seaf_repo_manager_set_repo_info_head_commit (seaf->repo_mgr, repo->id, commit_id); // Set last_sync_time to 0 to allow the repo to be sync immediately. // Otherwise it only gets synced after 30 seconds since the last sync. seaf_sync_manager_set_last_sync_time (seaf->sync_mgr, repo->id, 0); out: seaf_repo_unref (repo); return ret; } static int handle_file_lock (json_t *content) { json_t *member; const char *repo_id; const char *change_event; const char *path; const char *lock_user; SeafRepo *repo = NULL; int ret = 0; member = json_object_get (content, "repo_id"); if (!member) { seaf_warning ("Invalid file lock notification: no repo_id.\n"); return -1; } repo_id = json_string_value (member); repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id); if (!repo) { return -1; } if (!seaf_repo_manager_account_is_pro (seaf->repo_mgr, repo->server, repo->user)) { ret = -1; goto out; } if (!seaf_notif_manager_is_repo_subscribed (seaf->notif_mgr, repo)) { ret = -1; goto out; } member = json_object_get (content, "path"); if (!member) { seaf_warning ("Invalid file lock notification: no path.\n"); ret = -1; goto out; } path = json_string_value (member); if (!path) { seaf_warning ("Invalid repo file lock notification: path is null.\n"); ret = -1; goto out; } member = json_object_get (content, "change_event"); if (!member) { seaf_warning ("Invalid file lock notification: no change_event.\n"); ret = -1; goto out; } change_event = json_string_value (member); if (g_strcmp0 (change_event, "locked") == 0) { member = json_object_get (content, "lock_user"); if (!member) { seaf_warning ("Invalid file lock notification: no lock_user.\n"); ret = -1; goto out; } SeafAccount *account = seaf_repo_manager_get_account (seaf->repo_mgr, repo->server, repo->user); if (!account) { seaf_warning ("Failed to get current account.\n"); ret = -1; goto out; } lock_user = json_string_value (member); // don't need to lock file when file has beed locked.\n if (seaf_filelock_manager_get_lock_status (seaf->filelock_mgr, repo_id, path) != FILE_NOT_LOCKED) { goto out; } FileLockType type = LOCKED_OTHERS; if (g_strcmp0 (lock_user, account->username) == 0) type = LOCKED_MANUAL; seaf_filelock_manager_lock_file (seaf->filelock_mgr, repo_id, path, type); seaf_account_free (account); } else if (g_strcmp0 (change_event, "unlocked") == 0) { if (seaf_filelock_manager_get_lock_status (seaf->filelock_mgr, repo_id, path) == FILE_NOT_LOCKED) { goto out; } seaf_filelock_manager_mark_file_unlocked (seaf->filelock_mgr, repo_id, path); } out: seaf_repo_unref (repo); return ret; } static int handle_folder_perm (json_t *content) { json_t *member; const char *repo_id; const char *change_event; const char *type; const char *path; const char *permission; SeafRepo *repo = NULL; int ret = 0; member = json_object_get (content, "repo_id"); if (!member) { seaf_warning ("Invalid folder perm notification: no repo_id.\n"); return -1; } repo_id = json_string_value (member); repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id); if (!repo) { return -1; } if (!seaf_repo_manager_account_is_pro (seaf->repo_mgr, repo->server, repo->user)) { ret = -1; goto out; } if (!seaf_notif_manager_is_repo_subscribed (seaf->notif_mgr, repo)) { ret = -1; goto out; } member = json_object_get (content, "path"); if (!member) { seaf_warning ("Invalid folder perm notification: no path.\n"); ret = -1; goto out; } path = json_string_value (member); if (!path) { seaf_warning ("Invalid repo folder perm notification: path is null.\n"); ret = -1; goto out; } member = json_object_get (content, "type"); if (!member) { seaf_warning ("Invalid folder perm notification: no type.\n"); ret = -1; goto out; } type = json_string_value (member); member = json_object_get (content, "change_event"); if (!member) { seaf_warning ("Invalid folder perm notification: no change_event.\n"); ret = -1; goto out; } change_event = json_string_value (member); member = json_object_get (content, "perm"); if (!member) { seaf_warning ("Invalid folder perm notification: no perm.\n"); ret = -1; goto out; } permission = json_string_value (member); FolderPerm *perm = g_new0 (FolderPerm, 1); perm->path = g_strdup (path); perm->permission = g_strdup (permission); if (g_strcmp0 (type, "user") == 0) { if (g_strcmp0 (change_event, "add") == 0 || g_strcmp0 (change_event, "modify") == 0) seaf_repo_manager_update_folder_perm (seaf->repo_mgr, repo_id, FOLDER_PERM_TYPE_USER, perm); else if (g_strcmp0 (change_event, "del") == 0) seaf_repo_manager_delete_folder_perm (seaf->repo_mgr, repo_id, FOLDER_PERM_TYPE_USER, perm); } else if (g_strcmp0 (type, "group") == 0) { if (g_strcmp0 (change_event, "add") == 0 || g_strcmp0 (change_event, "modify") == 0) seaf_repo_manager_update_folder_perm (seaf->repo_mgr, repo_id, FOLDER_PERM_TYPE_GROUP, perm); else if (g_strcmp0 (change_event, "del") == 0) seaf_repo_manager_delete_folder_perm (seaf->repo_mgr, repo_id, FOLDER_PERM_TYPE_GROUP, perm); } g_free (perm); out: seaf_repo_unref (repo); return ret; } static int handle_jwt_expired (json_t *content) { NotifServer *server = NULL; json_t *member; const char *repo_id; SeafRepo *repo = NULL; int ret = 0; SeafAccount *account = NULL; member = json_object_get (content, "repo_id"); if (!member) { seaf_warning ("Invalid jwt expired notification: no repo_id.\n"); ret = -1; goto out; } repo_id = json_string_value (member); repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id); if (!repo) { ret = -1; goto out; } account = seaf_repo_manager_get_account (seaf->repo_mgr, repo->server, repo->user); if (!account) { seaf_warning ("Failed to get current account.\n"); ret = -1; goto out; } if (!seaf_notif_manager_is_repo_subscribed (seaf->notif_mgr, repo)) { ret = -1; goto out; } server = get_notif_server (seaf->notif_mgr, account->fileserver_addr); if (!server || server->status != STATUS_CONNECTED) { ret = -1; goto out; } pthread_mutex_lock (&server->sub_lock); g_hash_table_remove (server->subscriptions, repo_id); pthread_mutex_unlock (&server->sub_lock); // Set last_check_jwt_token to 0 to allow the repo to re-acquire a jwt token. repo->last_check_jwt_token = 0; out: seaf_account_free (account); notif_server_unref (server); seaf_repo_unref (repo); return ret; } static void handle_messages (const char *msg, size_t len) { json_t *object, *content, *member; json_error_t jerror; const char *type; seaf_debug ("Receive repo notification: %s\n", msg); object = json_loadb (msg, len, 0, &jerror); if (!object) { seaf_warning ("Failed to parse notification: %s.\n", jerror.text); return; } member = json_object_get (object, "type"); if (!member) { seaf_warning ("Invalid notification info: no type.\n"); goto out; } type = json_string_value (member); content = json_object_get (object, "content"); if (!content) { seaf_warning ("Invalid notification info: no content.\n"); goto out; } if (g_strcmp0 (type, "repo-update") == 0) { if (handle_repo_update (content) < 0) { goto out; } } else if (g_strcmp0 (type, "file-lock-changed") == 0) { if (handle_file_lock (content) < 0) { goto out; } } else if (g_strcmp0 (type, "folder-perm-changed") == 0) { if (handle_folder_perm (content) < 0) { goto out; } } else if (g_strcmp0 (type, "jwt-expired") == 0) { if (handle_jwt_expired (content) < 0) { goto out; } } out: if (object) json_decref (object); } static const struct lws_protocols protocols[] = { { "notification.seafile.com", event_callback, 0, 0, 0, NULL, 0 }, {NULL, NULL, 0, 0, 0, NULL, 0} }; static struct lws_context * lws_context_new (gboolean use_ssl) { struct lws_context_creation_info info; struct lws_context *context = NULL; memset(&info, 0, sizeof info); info.port = CONTEXT_PORT_NO_LISTEN; info.protocols = protocols; // Since we know this lws context is only ever going to be used with // one client wsis / fds / sockets at a time, let lws know it doesn't // have to use the default allocations for fd tables up to ulimit -n. // It will just allocate for 1 internal and 1 (+ 1 http2 nwsi) that we will use. info.fd_limit_per_thread = 1 + 1 + 1; char *ca_path = g_build_filename (seaf->seaf_dir, "ca-bundle.pem", NULL); if (use_ssl) { info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; info.client_ssl_ca_filepath = ca_path; } context = lws_create_context(&info); if (!context) { g_free (ca_path); seaf_warning ("failed to create libwebsockets context\n"); return NULL; } g_free (ca_path); return context; } static void delete_subscribed_repos (NotifServer *server) { GHashTableIter iter; gpointer key, value; if (!server->subscriptions) return; pthread_mutex_lock (&server->sub_lock); g_hash_table_iter_init (&iter, server->subscriptions); while (g_hash_table_iter_next (&iter, &key, &value)) { g_hash_table_iter_remove (&iter); } pthread_mutex_unlock (&server->sub_lock); } static void delete_unsent_messages (NotifServer *server) { Message *msg = NULL; if (!server->messages) return; while (1) { msg = g_async_queue_try_pop (server->messages); if (!msg) { break; } notif_message_free (msg); } return; } static void * notification_worker (void *vdata) { NotifServer *server = (NotifServer *)vdata; if (!server) { return 0; } struct lws_client_connect_info *i = &server->i; int n = 0; while (!server->close) { // We don't need to check the return value of this function, the connection will be processed in the event loop. lws_client_connect_via_info(i); while (n >= 0 && !server->close && server->status != STATUS_ERROR && server->status != STATUS_CANCELLED) { n = lws_service(server->context, 0); } delete_subscribed_repos (server); delete_unsent_messages (server); if (server->status == STATUS_CANCELLED) break; // Wait a minute to reconnect to the notification server. seaf_sleep (RECONNECT_INTERVAL); n = 0; server->status = STATUS_DISCONNECTED; } seaf_message ("Notification worker for server %s exiting.\n", server->server_url); pthread_mutex_lock (&seaf->notif_mgr->priv->server_lock); g_hash_table_remove (seaf->notif_mgr->priv->servers, server->server_url); pthread_mutex_unlock (&seaf->notif_mgr->priv->server_lock); notif_server_unref (server); return 0; } void seaf_notif_manager_subscribe_repo (SeafNotifManager *mgr, SeafRepo *repo) { NotifServer *server = NULL; json_t *json_msg = NULL; json_t *content = NULL; char *str = NULL; char *sub_id = NULL; json_t *array, *obj; SeafAccount *account = NULL; account = seaf_repo_manager_get_account (seaf->repo_mgr, repo->server, repo->user); if (!account) { goto out; } server = get_notif_server (mgr, account->fileserver_addr); if (!server || server->status != STATUS_CONNECTED) goto out; json_msg = json_object (); json_object_set_new (json_msg, "type", json_string("subscribe")); content = json_object (); array = json_array (); obj = json_object (); json_object_set_new (obj, "id", json_string(repo->id)); json_object_set_new (obj, "jwt_token", json_string(repo->jwt_token)); json_array_append_new (array, obj); json_object_set_new (content, "repos", array); json_object_set_new (json_msg, "content", content); str = json_dumps (json_msg, JSON_COMPACT); if (!str) goto out; Message *msg = notif_message_new (str, LWS_WRITE_TEXT); if (!msg) goto out; g_async_queue_push (server->messages, msg); sub_id = g_strdup (repo->id); pthread_mutex_lock (&server->sub_lock); g_hash_table_insert (server->subscriptions, sub_id, sub_id); pthread_mutex_unlock (&server->sub_lock); seaf_debug ("Successfully subscribe repo %s\n", repo->id); out: seaf_account_free (account); g_free (str); json_decref (json_msg); notif_server_unref (server); } void seaf_notif_manager_unsubscribe_repo (SeafNotifManager *mgr, SeafRepo *repo) { NotifServer *server = NULL; json_t *json_msg = NULL; json_t *content = NULL; char *str = NULL; json_t *array, *obj; SeafAccount *account = NULL; account = seaf_repo_manager_get_account (seaf->repo_mgr, repo->server, repo->user); if (!account) { goto out; } server = get_notif_server (mgr, account->fileserver_addr); if (!server || server->status != STATUS_CONNECTED) { goto out; } json_msg = json_object (); json_object_set_new (json_msg, "type", json_string("unsubscribe")); content = json_object (); array = json_array (); obj = json_object (); json_object_set_new (obj, "id", json_string(repo->id)); json_array_append_new (array, obj); json_object_set_new (content, "repos", array); json_object_set_new (json_msg, "content", content); str = json_dumps (json_msg, JSON_COMPACT); if (!str) goto out; Message *msg = notif_message_new (str, LWS_WRITE_TEXT); if (!msg) goto out; g_async_queue_push (server->messages, msg); pthread_mutex_lock (&server->sub_lock); g_hash_table_remove (server->subscriptions, repo->id); pthread_mutex_unlock (&server->sub_lock); seaf_debug ("Successfully unsubscribe repo %s\n", repo->id); out: seaf_account_free (account); g_free (str); json_decref (json_msg); notif_server_unref (server); } gboolean seaf_notif_manager_is_repo_subscribed (SeafNotifManager *mgr, SeafRepo *repo) { NotifServer *server = NULL; gboolean subscribed = FALSE; SeafAccount *account = NULL; account = seaf_repo_manager_get_account (seaf->repo_mgr, repo->server, repo->user); if (!account) { goto out; } server = get_notif_server (mgr, account->fileserver_addr); if (!server || server->status != STATUS_CONNECTED) { goto out; } pthread_mutex_lock (&server->sub_lock); if (g_hash_table_lookup (server->subscriptions, repo->id)) { pthread_mutex_unlock (&server->sub_lock); subscribed = TRUE; goto out; } pthread_mutex_unlock (&server->sub_lock); out: seaf_account_free (account); notif_server_unref (server); return subscribed; } seadrive-fuse-3.0.13/src/notif-mgr.h000066400000000000000000000015101476177674700172310ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ #ifndef NOTIF_MGR_H #define NOTIF_MGR_H typedef struct _SeafNotifManager SeafNotifManager; typedef struct _SeafNotifManagerPriv SeafNotifManagerPriv; struct _SeafileSession; struct _SeafNotifManager { struct _SeafileSession *seaf; SeafNotifManagerPriv *priv; }; SeafNotifManager * seaf_notif_manager_new (struct _SeafileSession *seaf); void seaf_notif_manager_connect_server (SeafNotifManager *mgr, const char *host, gboolean use_notif_server_port); void seaf_notif_manager_subscribe_repo (SeafNotifManager *mgr, SeafRepo *repo); void seaf_notif_manager_unsubscribe_repo (SeafNotifManager *mgr, SeafRepo *repo); gboolean seaf_notif_manager_is_repo_subscribed (SeafNotifManager *mgr, SeafRepo *repo); #endif seadrive-fuse-3.0.13/src/obj-backend-fs.c000066400000000000000000000265721476177674700201060ustar00rootroot00000000000000#include "common.h" #include "utils.h" #include "obj-backend.h" #include #include #include #define DEBUG_FLAG SEAFILE_DEBUG_OTHER #include "log.h" typedef struct FsPriv { char *v0_obj_dir; int v0_dir_len; char *obj_dir; int dir_len; } FsPriv; static void id_to_path (FsPriv *priv, const char *obj_id, char path[], const char *repo_id, int version) { char *pos = path; int n; if (version > 0) { n = snprintf (path, SEAF_PATH_MAX, "%s/%s/", priv->obj_dir, repo_id); pos += n; } else { memcpy (pos, priv->v0_obj_dir, priv->v0_dir_len); pos[priv->v0_dir_len] = '/'; pos += priv->v0_dir_len + 1; } memcpy (pos, obj_id, 2); pos[2] = '/'; pos += 3; memcpy (pos, obj_id + 2, 41 - 2); } static int obj_backend_fs_read (ObjBackend *bend, const char *repo_id, int version, const char *obj_id, void **data, int *len) { char path[SEAF_PATH_MAX]; gsize tmp_len; GError *error = NULL; id_to_path (bend->priv, obj_id, path, repo_id, version); /* seaf_debug ("object path: %s\n", path); */ g_file_get_contents (path, (gchar**)data, &tmp_len, &error); if (error) { #ifdef MIGRATION g_clear_error (&error); id_to_path (bend->priv, obj_id, path, repo_id, 1); g_file_get_contents (path, (gchar**)data, &tmp_len, &error); if (error) { seaf_debug ("[obj backend] Failed to read object %s: %s.\n", obj_id, error->message); g_clear_error (&error); return -1; } #else seaf_debug ("[obj backend] Failed to read object %s: %s.\n", obj_id, error->message); g_clear_error (&error); return -1; #endif } *len = (int)tmp_len; return 0; } /* * Flush operating system and disk caches for @fd. */ static int fsync_obj_contents (int fd) { #ifdef __linux__ /* Some file systems may not support fsync(). * In this case, just skip the error. */ if (fsync (fd) < 0) { if (errno == EINVAL) return 0; else { seaf_warning ("Failed to fsync: %s.\n", strerror(errno)); return -1; } } return 0; #endif #ifdef __APPLE__ /* OS X: fcntl() is required to flush disk cache, fsync() only * flushes operating system cache. */ if (fcntl (fd, F_FULLFSYNC, NULL) < 0) { seaf_warning ("Failed to fsync: %s.\n", strerror(errno)); return -1; } return 0; #endif } /* * Rename file from @tmp_path to @obj_path. * This also makes sure the changes to @obj_path's parent folder * is flushed to disk. */ static int rename_and_sync (const char *tmp_path, const char *obj_path) { #ifdef __linux__ char *parent_dir; int ret = 0; if (rename (tmp_path, obj_path) < 0) { seaf_warning ("Failed to rename from %s to %s: %s.\n", tmp_path, obj_path, strerror(errno)); return -1; } parent_dir = g_path_get_dirname (obj_path); int dir_fd = open (parent_dir, O_RDONLY); if (dir_fd < 0) { seaf_warning ("Failed to open dir %s: %s.\n", parent_dir, strerror(errno)); goto out; } /* Some file systems don't support fsyncing a directory. Just ignore the error. */ if (fsync (dir_fd) < 0) { if (errno != EINVAL) { seaf_warning ("Failed to fsync dir %s: %s.\n", parent_dir, strerror(errno)); ret = -1; } goto out; } out: g_free (parent_dir); if (dir_fd >= 0) close (dir_fd); return ret; #endif #ifdef __APPLE__ /* * OS X garantees an existence of obj_path always exists, * even when the system crashes. */ if (rename (tmp_path, obj_path) < 0) { seaf_warning ("Failed to rename from %s to %s: %s.\n", tmp_path, obj_path, strerror(errno)); return -1; } return 0; #endif } static int save_obj_contents (const char *path, const void *data, int len, gboolean need_sync) { char tmp_path[SEAF_PATH_MAX]; int fd; snprintf (tmp_path, SEAF_PATH_MAX, "%s.XXXXXX", path); fd = g_mkstemp (tmp_path); if (fd < 0) { seaf_warning ("[obj backend] Failed to open tmp file %s: %s.\n", tmp_path, strerror(errno)); return -1; } if (writen (fd, data, len) < 0) { seaf_warning ("[obj backend] Failed to write obj %s: %s.\n", tmp_path, strerror(errno)); return -1; } if (need_sync && fsync_obj_contents (fd) < 0) return -1; /* Close may return error, especially in NFS. */ if (close (fd) < 0) { seaf_warning ("[obj backend Failed close obj %s: %s.\n", tmp_path, strerror(errno)); return -1; } if (need_sync) { if (rename_and_sync (tmp_path, path) < 0) return -1; } else { if (g_file_test(path, G_FILE_TEST_EXISTS)) { if (g_unlink (path) < 0) { seaf_warning ("[obj bend] failed to remove existing obj %s: %s\n", path, strerror(errno)); } } if (g_rename (tmp_path, path) < 0) { seaf_warning ("[obj backend] Failed to rename %s: %s.\n", path, strerror(errno)); return -1; } } return 0; } static int create_parent_path (const char *path) { char *dir = g_path_get_dirname (path); if (!dir) return -1; if (g_file_test (dir, G_FILE_TEST_EXISTS)) { g_free (dir); return 0; } if (g_mkdir_with_parents (dir, 0777) < 0) { seaf_warning ("Failed to create object parent path %s: %s.\n", dir, strerror(errno)); g_free (dir); return -1; } g_free (dir); return 0; } static int obj_backend_fs_write (ObjBackend *bend, const char *repo_id, int version, const char *obj_id, void *data, int len, gboolean need_sync) { char path[SEAF_PATH_MAX]; id_to_path (bend->priv, obj_id, path, repo_id, version); /* GTimeVal s, e; */ /* g_get_current_time (&s); */ if (create_parent_path (path) < 0) { seaf_warning ("[obj backend] Failed to create path for obj %s:%s.\n", repo_id, obj_id); return -1; } if (save_obj_contents (path, data, len, need_sync) < 0) { seaf_warning ("[obj backend] Failed to write obj %s:%s.\n", repo_id, obj_id); return -1; } /* g_get_current_time (&e); */ /* seaf_message ("write obj time: %ldus.\n", */ /* ((e.tv_sec*1000000+e.tv_usec) - (s.tv_sec*1000000+s.tv_usec))); */ return 0; } static gboolean obj_backend_fs_exists (ObjBackend *bend, const char *repo_id, int version, const char *obj_id) { char path[SEAF_PATH_MAX]; SeafStat st; id_to_path (bend->priv, obj_id, path, repo_id, version); if (seaf_stat (path, &st) == 0) return TRUE; return FALSE; } static void obj_backend_fs_delete (ObjBackend *bend, const char *repo_id, int version, const char *obj_id) { char path[SEAF_PATH_MAX]; id_to_path (bend->priv, obj_id, path, repo_id, version); g_unlink (path); } static int obj_backend_fs_foreach_obj (ObjBackend *bend, const char *repo_id, int version, SeafObjFunc process, void *user_data) { FsPriv *priv = bend->priv; char *obj_dir = NULL; int dir_len; GDir *dir1 = NULL, *dir2; const char *dname1, *dname2; char obj_id[128]; char path[SEAF_PATH_MAX], *pos; int ret = 0; if (version > 0) obj_dir = g_build_filename (priv->obj_dir, repo_id, NULL); else obj_dir = g_strdup(priv->v0_obj_dir); dir_len = strlen (obj_dir); dir1 = g_dir_open (obj_dir, 0, NULL); if (!dir1) { goto out; } memcpy (path, obj_dir, dir_len); pos = path + dir_len; while ((dname1 = g_dir_read_name(dir1)) != NULL) { snprintf (pos, sizeof(path) - dir_len, "/%s", dname1); dir2 = g_dir_open (path, 0, NULL); if (!dir2) { seaf_warning ("Failed to open object dir %s.\n", path); continue; } while ((dname2 = g_dir_read_name(dir2)) != NULL) { snprintf (obj_id, sizeof(obj_id), "%s%s", dname1, dname2); if (!process (repo_id, version, obj_id, user_data)) { g_dir_close (dir2); goto out; } } g_dir_close (dir2); } out: if (dir1) g_dir_close (dir1); g_free (obj_dir); return ret; } static int obj_backend_fs_remove_store (ObjBackend *bend, const char *store_id) { FsPriv *priv = bend->priv; char *obj_dir = NULL; GDir *dir1, *dir2; const char *dname1, *dname2; char *path1, *path2; obj_dir = g_build_filename (priv->obj_dir, store_id, NULL); dir1 = g_dir_open (obj_dir, 0, NULL); if (!dir1) { g_free (obj_dir); return 0; } while ((dname1 = g_dir_read_name(dir1)) != NULL) { path1 = g_build_filename (obj_dir, dname1, NULL); dir2 = g_dir_open (path1, 0, NULL); if (!dir2) { seaf_warning ("Failed to open obj dir %s.\n", path1); g_dir_close (dir1); g_free (path1); g_free (obj_dir); return -1; } while ((dname2 = g_dir_read_name(dir2)) != NULL) { path2 = g_build_filename (path1, dname2, NULL); g_unlink (path2); g_free (path2); } g_dir_close (dir2); g_rmdir (path1); g_free (path1); } g_dir_close (dir1); g_rmdir (obj_dir); g_free (obj_dir); return 0; } ObjBackend * obj_backend_fs_new (const char *seaf_dir, const char *obj_type) { ObjBackend *bend; FsPriv *priv; bend = g_new0(ObjBackend, 1); priv = g_new0(FsPriv, 1); bend->priv = priv; priv->v0_obj_dir = g_build_filename (seaf_dir, obj_type, NULL); priv->v0_dir_len = strlen(priv->v0_obj_dir); priv->obj_dir = g_build_filename (seaf_dir, "storage", obj_type, NULL); priv->dir_len = strlen (priv->obj_dir); if (g_mkdir_with_parents (priv->v0_obj_dir, 0777) < 0) { seaf_warning ("[Obj Backend] Objects dir %s does not exist and" " is unable to create\n", priv->v0_obj_dir); goto onerror; } if (g_mkdir_with_parents (priv->obj_dir, 0777) < 0) { seaf_warning ("[Obj Backend] Objects dir %s does not exist and" " is unable to create\n", priv->obj_dir); goto onerror; } bend->read = obj_backend_fs_read; bend->write = obj_backend_fs_write; bend->exists = obj_backend_fs_exists; bend->delete = obj_backend_fs_delete; bend->foreach_obj = obj_backend_fs_foreach_obj; bend->remove_store = obj_backend_fs_remove_store; return bend; onerror: g_free (priv->v0_obj_dir); g_free (priv->obj_dir); g_free (priv); g_free (bend); return NULL; } seadrive-fuse-3.0.13/src/obj-backend.h000066400000000000000000000026371476177674700175010ustar00rootroot00000000000000#ifndef OBJ_BACKEND_H #define OBJ_BACKEND_H #include #include "obj-store.h" typedef struct ObjBackend ObjBackend; struct ObjBackend { int (*read) (ObjBackend *bend, const char *repo_id, int version, const char *obj_id, void **data, int *len); int (*write) (ObjBackend *bend, const char *repo_id, int version, const char *obj_id, void *data, int len, gboolean need_sync); gboolean (*exists) (ObjBackend *bend, const char *repo_id, int version, const char *obj_id); void (*delete) (ObjBackend *bend, const char *repo_id, int version, const char *obj_id); int (*foreach_obj) (ObjBackend *bend, const char *repo_id, int version, SeafObjFunc process, void *user_data); int (*remove_store) (ObjBackend *bend, const char *store_id); void *priv; }; #endif seadrive-fuse-3.0.13/src/obj-store.c000066400000000000000000000062601476177674700172350ustar00rootroot00000000000000#include "common.h" #include "log.h" #include "seafile-session.h" #include "utils.h" #include "obj-backend.h" #include "obj-store.h" struct SeafObjStore { ObjBackend *bend; }; typedef struct SeafObjStore SeafObjStore; extern ObjBackend * obj_backend_fs_new (const char *seaf_dir, const char *obj_type); struct SeafObjStore * seaf_obj_store_new (SeafileSession *seaf, const char *obj_type) { SeafObjStore *store = g_new0 (SeafObjStore, 1); if (!store) return NULL; store->bend = obj_backend_fs_new (seaf->seaf_dir, obj_type); if (!store->bend) { seaf_warning ("[Object store] Failed to load backend.\n"); g_free (store); return NULL; } return store; } int seaf_obj_store_init (SeafObjStore *obj_store) { return 0; } int seaf_obj_store_read_obj (struct SeafObjStore *obj_store, const char *repo_id, int version, const char *obj_id, void **data, int *len) { ObjBackend *bend = obj_store->bend; if (!repo_id || !is_uuid_valid(repo_id) || !obj_id || !is_object_id_valid(obj_id)) return -1; return bend->read (bend, repo_id, version, obj_id, data, len); } int seaf_obj_store_write_obj (struct SeafObjStore *obj_store, const char *repo_id, int version, const char *obj_id, void *data, int len, gboolean need_sync) { ObjBackend *bend = obj_store->bend; if (!repo_id || !is_uuid_valid(repo_id) || !obj_id || !is_object_id_valid(obj_id)) return -1; return bend->write (bend, repo_id, version, obj_id, data, len, need_sync); } gboolean seaf_obj_store_obj_exists (struct SeafObjStore *obj_store, const char *repo_id, int version, const char *obj_id) { ObjBackend *bend = obj_store->bend; if (!repo_id || !is_uuid_valid(repo_id) || !obj_id || !is_object_id_valid(obj_id)) return FALSE; return bend->exists (bend, repo_id, version, obj_id); } void seaf_obj_store_delete_obj (struct SeafObjStore *obj_store, const char *repo_id, int version, const char *obj_id) { ObjBackend *bend = obj_store->bend; if (!repo_id || !is_uuid_valid(repo_id) || !obj_id || !is_object_id_valid(obj_id)) return; bend->delete (bend, repo_id, version, obj_id); } int seaf_obj_store_foreach_obj (struct SeafObjStore *obj_store, const char *repo_id, int version, SeafObjFunc process, void *user_data) { ObjBackend *bend = obj_store->bend; return bend->foreach_obj (bend, repo_id, version, process, user_data); } int seaf_obj_store_remove_store (struct SeafObjStore *obj_store, const char *store_id) { ObjBackend *bend = obj_store->bend; return bend->remove_store (bend, store_id); } seadrive-fuse-3.0.13/src/obj-store.h000066400000000000000000000035331476177674700172420ustar00rootroot00000000000000#ifndef OBJ_STORE_H #define OBJ_STORE_H #include #include struct _SeafileSession; struct SeafObjStore; struct SeafObjStore * seaf_obj_store_new (struct _SeafileSession *seaf, const char *obj_type); int seaf_obj_store_init (struct SeafObjStore *obj_store); /* Synchronous I/O interface. */ int seaf_obj_store_read_obj (struct SeafObjStore *obj_store, const char *repo_id, int version, const char *obj_id, void **data, int *len); int seaf_obj_store_write_obj (struct SeafObjStore *obj_store, const char *repo_id, int version, const char *obj_id, void *data, int len, gboolean need_sync); gboolean seaf_obj_store_obj_exists (struct SeafObjStore *obj_store, const char *repo_id, int version, const char *obj_id); void seaf_obj_store_delete_obj (struct SeafObjStore *obj_store, const char *repo_id, int version, const char *obj_id); typedef gboolean (*SeafObjFunc) (const char *repo_id, int version, const char *obj_id, void *user_data); int seaf_obj_store_foreach_obj (struct SeafObjStore *obj_store, const char *repo_id, int version, SeafObjFunc process, void *user_data); int seaf_obj_store_remove_store (struct SeafObjStore *obj_store, const char *store_id); #endif seadrive-fuse-3.0.13/src/password-hash.c000066400000000000000000000121471476177674700201150ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ #include "common.h" #include #include #include #include "password-hash.h" #include "seafile-crypt.h" #ifdef USE_GPL_CRYPTO #include #include #include #else #include #include #include #endif #include "utils.h" #include "log.h" // pbkdf2 typedef struct Pbkdf2Params { int iteration; } Pbkdf2Params; static Pbkdf2Params * parse_pbkdf2_sha256_params (const char *params_str) { Pbkdf2Params *params = NULL; if (!params_str) { params = g_new0 (Pbkdf2Params, 1); params->iteration = 1000; return params; } int iteration; iteration = atoi (params_str); if (iteration <= 0) { iteration = 1000; } params = g_new0 (Pbkdf2Params, 1); params->iteration = iteration; return params; } static int pbkdf2_sha256_derive_key (const char *data_in, int in_len, const char *salt, Pbkdf2Params *params, unsigned char *key) { int iteration = params->iteration; unsigned char salt_bin[32] = {0}; hex_to_rawdata (salt, salt_bin, 32); #ifdef USE_GPL_CRYPTO pbkdf2_hmac_sha256 (in_len, (const guchar *)data_in, iteration, sizeof(salt_bin), salt_bin, 32, key); #else PKCS5_PBKDF2_HMAC (data_in, in_len, salt_bin, sizeof(salt_bin), iteration, EVP_sha256(), 32, key); #endif return 0; } // argon2id typedef struct Argon2idParams{ gint64 time_cost; gint64 memory_cost; gint64 parallelism; } Argon2idParams; // The arguments to argon2 are separated by commas. // Example arguments format: // 2,102400,8 // The parameters are time_cost, memory_cost, parallelism from left to right. static Argon2idParams * parse_argon2id_params (const char *params_str) { char **params; Argon2idParams *argon2_params = g_new0 (Argon2idParams, 1); if (params_str) params = g_strsplit (params_str, ",", 3); if (!params_str || g_strv_length(params) != 3) { if (params_str) g_strfreev (params); argon2_params->time_cost = 2; // 2-pass computation argon2_params->memory_cost = 102400; // 100 mebibytes memory usage argon2_params->parallelism = 8; // number of threads and lanes return argon2_params; } char *p = NULL; p = g_strstrip (params[0]); argon2_params->time_cost = atoll (p); if (argon2_params->time_cost <= 0) { argon2_params->time_cost = 2; } p = g_strstrip (params[1]); argon2_params->memory_cost = atoll (p); if (argon2_params->memory_cost <= 0) { argon2_params->memory_cost = 102400; } p = g_strstrip (params[2]); argon2_params->parallelism = atoll (p); if (argon2_params->parallelism <= 0) { argon2_params->parallelism = 8; } g_strfreev (params); return argon2_params; } static int argon2id_derive_key (const char *data_in, int in_len, const char *salt, Argon2idParams *params, unsigned char *key) { unsigned char salt_bin[32] = {0}; hex_to_rawdata (salt, salt_bin, 32); argon2id_hash_raw(params->time_cost, params->memory_cost, params->parallelism, data_in, in_len, salt_bin, sizeof(salt_bin), key, 32); return 0; } // parse_pwd_hash_params is used to parse default pwd hash algorithms. void parse_pwd_hash_params (const char *algo, const char *params_str, PwdHashParams *params) { if (g_strcmp0 (algo, PWD_HASH_PDKDF2) == 0) { params->algo = g_strdup (PWD_HASH_PDKDF2); if (params_str) params->params_str = g_strdup (params_str); else params->params_str = g_strdup ("1000"); } else if (g_strcmp0 (algo, PWD_HASH_ARGON2ID) == 0) { params->algo = g_strdup (PWD_HASH_ARGON2ID); if (params_str) params->params_str = g_strdup (params_str); else params->params_str = g_strdup ("2,102400,8"); } else { params->algo = NULL; } seaf_message ("password hash algorithms: %s, params: %s\n ", params->algo, params->params_str); } int pwd_hash_derive_key (const char *data_in, int in_len, const char *salt, const char *algo, const char *params_str, unsigned char *key) { int ret = 0; if (g_strcmp0 (algo, PWD_HASH_ARGON2ID) == 0) { Argon2idParams *algo_params = parse_argon2id_params (params_str); ret = argon2id_derive_key (data_in, in_len, salt, algo_params, key); g_free (algo_params); return ret; } else { Pbkdf2Params *algo_params = parse_pbkdf2_sha256_params (params_str); ret = pbkdf2_sha256_derive_key (data_in, in_len, salt, algo_params, key); g_free (algo_params); return ret; } } seadrive-fuse-3.0.13/src/password-hash.h000066400000000000000000000011361476177674700201160ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ #ifndef _PASSWORD_HASH_H #define _PASSWORD_HASH_H #define PWD_HASH_PDKDF2 "pbkdf2_sha256" #define PWD_HASH_ARGON2ID "argon2id" typedef struct _PwdHashParams { char *algo; char *params_str; } PwdHashParams; void parse_pwd_hash_params (const char *algo, const char *params_str, PwdHashParams *params); int pwd_hash_derive_key (const char *data_in, int in_len, const char *repo_salt, const char *algo, const char *params_str, unsigned char *key); #endif seadrive-fuse-3.0.13/src/rabin-checksum.c000066400000000000000000000071361476177674700202270ustar00rootroot00000000000000#include #include "rabin-checksum.h" #define INT64(n) n##LL #define MSB64 INT64(0x8000000000000000) static u_int64_t poly = 0xbfe6b8a5bf378d83LL; static u_int64_t T[256]; static u_int64_t U[256]; static int shift; /* Highest bit set in a byte */ static const char bytemsb[0x100] = { 0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, }; /* Find last set (most significant bit) */ static inline u_int fls32 (u_int32_t v) { if (v & 0xffff0000) { if (v & 0xff000000) return 24 + bytemsb[v>>24]; else return 16 + bytemsb[v>>16]; } if (v & 0x0000ff00) return 8 + bytemsb[v>>8]; else return bytemsb[v]; } static inline char fls64 (u_int64_t v) { u_int32_t h; if ((h = v >> 32)) return 32 + fls32 (h); else return fls32 ((u_int32_t) v); } u_int64_t polymod (u_int64_t nh, u_int64_t nl, u_int64_t d) { int i = 0; int k = fls64 (d) - 1; d <<= 63 - k; if (nh) { if (nh & MSB64) nh ^= d; for (i = 62; i >= 0; i--) if (nh & ((u_int64_t) 1) << i) { nh ^= d >> (63 - i); nl ^= d << (i + 1); } } for (i = 63; i >= k; i--) { if (nl & INT64 (1) << i) nl ^= d >> (63 - i); } return nl; } void polymult (u_int64_t *php, u_int64_t *plp, u_int64_t x, u_int64_t y) { int i; u_int64_t ph = 0, pl = 0; if (x & 1) pl = y; for (i = 1; i < 64; i++) if (x & (INT64 (1) << i)) { ph ^= y >> (64 - i); pl ^= y << i; } if (php) *php = ph; if (plp) *plp = pl; } u_int64_t polymmult (u_int64_t x, u_int64_t y, u_int64_t d) { u_int64_t h, l; polymult (&h, &l, x, y); return polymod (h, l, d); } static u_int64_t append8 (u_int64_t p, u_char m) { return ((p << 8) | m) ^ T[p >> shift]; } static void calcT (u_int64_t poly) { int j = 0; int xshift = fls64 (poly) - 1; shift = xshift - 8; u_int64_t T1 = polymod (0, INT64 (1) << xshift, poly); for (j = 0; j < 256; j++) { T[j] = polymmult (j, T1, poly) | ((u_int64_t) j << xshift); } } static void calcU(int size) { int i; u_int64_t sizeshift = 1; for (i = 1; i < size; i++) sizeshift = append8 (sizeshift, 0); for (i = 0; i < 256; i++) U[i] = polymmult (i, sizeshift, poly); } void rabin_init(int len) { calcT(poly); calcU(len); } /* * a simple 32 bit checksum that can be upadted from end */ unsigned int rabin_checksum(char *buf, int len) { int i; unsigned int sum = 0; for (i = 0; i < len; ++i) { sum = rabin_rolling_checksum (sum, len, 0, buf[i]); } return sum; } unsigned int rabin_rolling_checksum(unsigned int csum, int len, char c1, char c2) { return append8(csum ^ U[(unsigned char)c1], c2); } seadrive-fuse-3.0.13/src/rabin-checksum.h000066400000000000000000000003361476177674700202270ustar00rootroot00000000000000#ifndef _RABIN_CHECKSUM_H #define _RABIN_CHECKSUM_H unsigned int rabin_checksum(char *buf, int len); unsigned int rabin_rolling_checksum(unsigned int csum, int len, char c1, char c2); void rabin_init (int len); #endif seadrive-fuse-3.0.13/src/repo-mgr.c000066400000000000000000004001171476177674700170600ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ #include "common.h" #include #include #include "utils.h" #define DEBUG_FLAG SEAFILE_DEBUG_SYNC #include "log.h" #include "seafile-session.h" #include "seafile-config.h" #include "commit-mgr.h" #include "branch-mgr.h" #include "repo-mgr.h" #include "fs-mgr.h" #include "seafile-error.h" #include "seafile-crypt.h" #include "db.h" struct _SeafRepoManagerPriv { GHashTable *repo_hash; pthread_rwlock_t lock; /* Cache of repos for current account. */ // The key is 'server_username', the value is server and user of account. GHashTable *accounts; // The key is repo id, the value is repo info. GHashTable *repo_infos; // The key is 'server_username', the value a hash table of name_to_repo. GHashTable *repo_names; pthread_rwlock_t account_lock; sqlite3 *db; pthread_mutex_t db_lock; GHashTable *user_perms; /* repo_id -> folder user perms */ GHashTable *group_perms; /* repo_id -> folder group perms */ pthread_mutex_t perm_lock; }; static SeafRepo * load_repo (SeafRepoManager *manager, const char *repo_id); static void load_repos (SeafRepoManager *manager, const char *seaf_dir); static void seaf_repo_manager_del_repo_property (SeafRepoManager *manager, const char *repo_id); static int save_branch_repo_map (SeafRepoManager *manager, SeafBranch *branch); static void save_repo_property (SeafRepoManager *manager, const char *repo_id, const char *key, const char *value); static void locked_file_free (LockedFile *file) { if (!file) return; g_free (file->operation); g_free (file); } static gboolean load_locked_file (sqlite3_stmt *stmt, void *data) { GHashTable *ret = data; LockedFile *file; const char *path, *operation, *file_id; gint64 old_mtime; path = (const char *)sqlite3_column_text (stmt, 0); operation = (const char *)sqlite3_column_text (stmt, 1); old_mtime = sqlite3_column_int64 (stmt, 2); file_id = (const char *)sqlite3_column_text (stmt, 3); file = g_new0 (LockedFile, 1); file->operation = g_strdup(operation); file->old_mtime = old_mtime; if (file_id) memcpy (file->file_id, file_id, 40); g_hash_table_insert (ret, g_strdup(path), file); return TRUE; } LockedFileSet * seaf_repo_manager_get_locked_file_set (SeafRepoManager *mgr, const char *repo_id) { GHashTable *locked_files = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)locked_file_free); char sql[256]; sqlite3_snprintf (sizeof(sql), sql, "SELECT path, operation, old_mtime, file_id FROM LockedFiles " "WHERE repo_id = '%q'", repo_id); pthread_mutex_lock (&mgr->priv->db_lock); /* Ingore database error. We return an empty set on error. */ sqlite_foreach_selected_row (mgr->priv->db, sql, load_locked_file, locked_files); pthread_mutex_unlock (&mgr->priv->db_lock); LockedFileSet *ret = g_new0 (LockedFileSet, 1); ret->mgr = mgr; memcpy (ret->repo_id, repo_id, 36); ret->locked_files = locked_files; return ret; } void locked_file_set_free (LockedFileSet *fset) { if (!fset) return; g_hash_table_destroy (fset->locked_files); g_free (fset); } int locked_file_set_add_update (LockedFileSet *fset, const char *path, const char *operation, gint64 old_mtime, const char *file_id) { SeafRepoManager *mgr = fset->mgr; char *sql; sqlite3_stmt *stmt; LockedFile *file; gboolean exists; exists = (g_hash_table_lookup (fset->locked_files, path) != NULL); pthread_mutex_lock (&mgr->priv->db_lock); if (!exists) { seaf_debug ("New locked file record %.8s, %s, %s, %" G_GINT64_FORMAT".\n", fset->repo_id, path, operation, old_mtime); sql = "INSERT INTO LockedFiles VALUES (?, ?, ?, ?, ?, NULL)"; stmt = sqlite_query_prepare (mgr->priv->db, sql); sqlite3_bind_text (stmt, 1, fset->repo_id, -1, SQLITE_TRANSIENT); sqlite3_bind_text (stmt, 2, path, -1, SQLITE_TRANSIENT); sqlite3_bind_text (stmt, 3, operation, -1, SQLITE_TRANSIENT); sqlite3_bind_int64 (stmt, 4, old_mtime); sqlite3_bind_text (stmt, 5, file_id, -1, SQLITE_TRANSIENT); if (sqlite3_step (stmt) != SQLITE_DONE) { seaf_warning ("Failed to insert locked file %s to db: %s.\n", path, sqlite3_errmsg (mgr->priv->db)); sqlite3_finalize (stmt); pthread_mutex_unlock (&mgr->priv->db_lock); return -1; } sqlite3_finalize (stmt); file = g_new0 (LockedFile, 1); file->operation = g_strdup(operation); file->old_mtime = old_mtime; if (file_id) memcpy (file->file_id, file_id, 40); g_hash_table_insert (fset->locked_files, g_strdup(path), file); } else { seaf_debug ("Update locked file record %.8s, %s, %s.\n", fset->repo_id, path, operation); /* If a UPDATE record exists, don't update the old_mtime. * We need to keep the old mtime when the locked file was first detected. */ sql = "UPDATE LockedFiles SET operation = ?, file_id = ? " "WHERE repo_id = ? AND path = ?"; stmt = sqlite_query_prepare (mgr->priv->db, sql); sqlite3_bind_text (stmt, 1, operation, -1, SQLITE_TRANSIENT); sqlite3_bind_text (stmt, 2, file_id, -1, SQLITE_TRANSIENT); sqlite3_bind_text (stmt, 3, fset->repo_id, -1, SQLITE_TRANSIENT); sqlite3_bind_text (stmt, 4, path, -1, SQLITE_TRANSIENT); if (sqlite3_step (stmt) != SQLITE_DONE) { seaf_warning ("Failed to update locked file %s to db: %s.\n", path, sqlite3_errmsg (mgr->priv->db)); sqlite3_finalize (stmt); pthread_mutex_unlock (&mgr->priv->db_lock); return -1; } sqlite3_finalize (stmt); file = g_hash_table_lookup (fset->locked_files, path); g_free (file->operation); file->operation = g_strdup(operation); if (file_id) memcpy (file->file_id, file_id, 40); } pthread_mutex_unlock (&mgr->priv->db_lock); return 0; } int locked_file_set_remove (LockedFileSet *fset, const char *path, gboolean db_only) { SeafRepoManager *mgr = fset->mgr; char *sql; sqlite3_stmt *stmt; if (g_hash_table_lookup (fset->locked_files, path) == NULL) return 0; seaf_debug ("Remove locked file record %.8s, %s.\n", fset->repo_id, path); pthread_mutex_lock (&mgr->priv->db_lock); sql = "DELETE FROM LockedFiles WHERE repo_id = ? AND path = ?"; stmt = sqlite_query_prepare (mgr->priv->db, sql); sqlite3_bind_text (stmt, 1, fset->repo_id, -1, SQLITE_TRANSIENT); sqlite3_bind_text (stmt, 2, path, -1, SQLITE_TRANSIENT); if (sqlite3_step (stmt) != SQLITE_DONE) { seaf_warning ("Failed to remove locked file %s from db: %s.\n", path, sqlite3_errmsg (mgr->priv->db)); sqlite3_finalize (stmt); pthread_mutex_unlock (&mgr->priv->db_lock); return -1; } sqlite3_finalize (stmt); pthread_mutex_unlock (&mgr->priv->db_lock); if (!db_only) g_hash_table_remove (fset->locked_files, path); return 0; } LockedFile * locked_file_set_lookup (LockedFileSet *fset, const char *path) { return (LockedFile *) g_hash_table_lookup (fset->locked_files, path); } /* Folder permissions. */ FolderPerm * folder_perm_new (const char *path, const char *permission) { FolderPerm *perm = g_new0 (FolderPerm, 1); perm->path = g_strdup(path); perm->permission = g_strdup(permission); return perm; } void folder_perm_free (FolderPerm *perm) { if (!perm) return; g_free (perm->path); g_free (perm->permission); g_free (perm); } static GList * folder_perm_list_copy (GList *perms) { GList *ret = NULL, *ptr; FolderPerm *perm, *new_perm; for (ptr = perms; ptr; ptr = ptr->next) { perm = ptr->data; new_perm = folder_perm_new (perm->path, perm->permission); ret = g_list_append (ret, new_perm); } return ret; } static gint comp_folder_perms (gconstpointer a, gconstpointer b) { const FolderPerm *perm_a = a, *perm_b = b; return (strcmp (perm_b->path, perm_a->path)); } static void delete_folder_perm (SeafRepoManager *mgr, const char *repo_id, FolderPermType type, FolderPerm *perm) { GList *folder_perms = NULL; if (type == FOLDER_PERM_TYPE_USER) { folder_perms = g_hash_table_lookup (mgr->priv->user_perms, repo_id); if (!folder_perms) return; // if path is empty string, delete all folder perms in this repo. if (g_strcmp0 (perm->path, "") == 0) { g_list_free_full (folder_perms, (GDestroyNotify)folder_perm_free); g_hash_table_remove (mgr->priv->user_perms, repo_id); return; } GList *existing = g_list_find_custom (folder_perms, perm, comp_folder_perms); if (existing) { FolderPerm *old_perm = existing->data; folder_perms = g_list_remove (folder_perms, old_perm); g_hash_table_insert (mgr->priv->user_perms, g_strdup(repo_id), folder_perms); folder_perm_free (old_perm); } } else if (type == FOLDER_PERM_TYPE_GROUP) { folder_perms = g_hash_table_lookup (mgr->priv->group_perms, repo_id); if (!folder_perms) return; if (g_strcmp0 (perm->path, "") == 0) { g_list_free_full (folder_perms, (GDestroyNotify)folder_perm_free); g_hash_table_remove (mgr->priv->group_perms, repo_id); return; } GList *existing = g_list_find_custom (folder_perms, perm, comp_folder_perms); if (existing) { FolderPerm *old_perm = existing->data; folder_perms = g_list_remove (folder_perms, old_perm); g_hash_table_insert (mgr->priv->group_perms, g_strdup(repo_id), folder_perms); folder_perm_free (old_perm); } } } int seaf_repo_manager_delete_folder_perm (SeafRepoManager *mgr, const char *repo_id, FolderPermType type, FolderPerm *perm) { char *sql; sqlite3_stmt *stmt; g_return_val_if_fail ((type == FOLDER_PERM_TYPE_USER || type == FOLDER_PERM_TYPE_GROUP), -1); if (!perm) { return 0; } /* Update db. */ pthread_mutex_lock (&mgr->priv->db_lock); if (g_strcmp0 (perm->path, "") == 0) { if (type == FOLDER_PERM_TYPE_USER) sql = "DELETE FROM FolderUserPerms WHERE repo_id = ?"; else sql = "DELETE FROM FolderGroupPerms WHERE repo_id = ?"; } else { if (type == FOLDER_PERM_TYPE_USER) sql = "DELETE FROM FolderUserPerms WHERE repo_id = ? and path = ?"; else sql = "DELETE FROM FolderGroupPerms WHERE repo_id = ? and path = ?"; } stmt = sqlite_query_prepare (mgr->priv->db, sql); sqlite3_bind_text (stmt, 1, repo_id, -1, SQLITE_TRANSIENT); if (g_strcmp0 (perm->path, "") != 0) sqlite3_bind_text (stmt, 2, perm->path, -1, SQLITE_TRANSIENT); if (sqlite3_step (stmt) != SQLITE_DONE) { seaf_warning ("Failed to remove folder perm for %.8s: %s.\n", repo_id, sqlite3_errmsg (mgr->priv->db)); sqlite3_finalize (stmt); pthread_mutex_unlock (&mgr->priv->db_lock); return -1; } sqlite3_finalize (stmt); pthread_mutex_unlock (&mgr->priv->db_lock); /* Update in memory */ pthread_mutex_lock (&mgr->priv->perm_lock); delete_folder_perm (mgr, repo_id, type, perm); pthread_mutex_unlock (&mgr->priv->perm_lock); return 0; } int seaf_repo_manager_update_folder_perm (SeafRepoManager *mgr, const char *repo_id, FolderPermType type, FolderPerm *perm) { char *sql; sqlite3_stmt *stmt; g_return_val_if_fail ((type == FOLDER_PERM_TYPE_USER || type == FOLDER_PERM_TYPE_GROUP), -1); if (!perm) { return 0; } /* Update db. */ pthread_mutex_lock (&mgr->priv->db_lock); if (type == FOLDER_PERM_TYPE_USER) sql = "DELETE FROM FolderUserPerms WHERE repo_id = ? and path = ?"; else sql = "DELETE FROM FolderGroupPerms WHERE repo_id = ? and path = ?"; stmt = sqlite_query_prepare (mgr->priv->db, sql); sqlite3_bind_text (stmt, 1, repo_id, -1, SQLITE_TRANSIENT); sqlite3_bind_text (stmt, 2, perm->path, -1, SQLITE_TRANSIENT); if (sqlite3_step (stmt) != SQLITE_DONE) { seaf_warning ("Failed to remove folder perm for %.8s(%s): %s.\n", repo_id, perm->path, sqlite3_errmsg (mgr->priv->db)); sqlite3_finalize (stmt); pthread_mutex_unlock (&mgr->priv->db_lock); return -1; } sqlite3_finalize (stmt); if (type == FOLDER_PERM_TYPE_USER) sql = "INSERT INTO FolderUserPerms VALUES (?, ?, ?)"; else sql = "INSERT INTO FolderGroupPerms VALUES (?, ?, ?)"; stmt = sqlite_query_prepare (mgr->priv->db, sql); sqlite3_bind_text (stmt, 1, repo_id, -1, SQLITE_TRANSIENT); sqlite3_bind_text (stmt, 2, perm->path, -1, SQLITE_TRANSIENT); sqlite3_bind_text (stmt, 3, perm->permission, -1, SQLITE_TRANSIENT); if (sqlite3_step (stmt) != SQLITE_DONE) { seaf_warning ("Failed to insert folder perm for %.8s(%s): %s.\n", repo_id, perm->path, sqlite3_errmsg (mgr->priv->db)); sqlite3_finalize (stmt); pthread_mutex_unlock (&mgr->priv->db_lock); return -1; } sqlite3_finalize (stmt); pthread_mutex_unlock (&mgr->priv->db_lock); /* Update in memory */ GList *folder_perms; pthread_mutex_lock (&mgr->priv->perm_lock); if (type == FOLDER_PERM_TYPE_USER) { folder_perms = g_hash_table_lookup (mgr->priv->user_perms, repo_id); if (folder_perms) { GList *existing = g_list_find_custom (folder_perms, perm, comp_folder_perms); if (existing) { FolderPerm *old_perm = existing->data; g_free (old_perm->permission); old_perm->permission = g_strdup (perm->permission); } else { FolderPerm *new_perm = folder_perm_new (perm->path, perm->permission); folder_perms = g_list_insert_sorted (folder_perms, new_perm, comp_folder_perms); } } else { FolderPerm *new_perm = folder_perm_new (perm->path, perm->permission); folder_perms = g_list_insert_sorted (folder_perms, new_perm, comp_folder_perms); } g_hash_table_insert (mgr->priv->user_perms, g_strdup(repo_id), folder_perms); } else if (type == FOLDER_PERM_TYPE_GROUP) { folder_perms = g_hash_table_lookup (mgr->priv->group_perms, repo_id); if (folder_perms) { GList *existing = g_list_find_custom (folder_perms, perm, comp_folder_perms); if (existing) { FolderPerm *old_perm = existing->data; g_free (old_perm->permission); old_perm->permission = g_strdup (perm->permission); } else { FolderPerm *new_perm = folder_perm_new (perm->path, perm->permission); folder_perms = g_list_insert_sorted (folder_perms, new_perm, comp_folder_perms); } } else { FolderPerm *new_perm = folder_perm_new (perm->path, perm->permission); folder_perms = g_list_insert_sorted (folder_perms, new_perm, comp_folder_perms); } g_hash_table_insert (mgr->priv->group_perms, g_strdup(repo_id), folder_perms); } pthread_mutex_unlock (&mgr->priv->perm_lock); return 0; } int seaf_repo_manager_update_folder_perms (SeafRepoManager *mgr, const char *repo_id, FolderPermType type, GList *folder_perms) { char *sql; sqlite3_stmt *stmt; GList *ptr; FolderPerm *perm; GList *new, *old; g_return_val_if_fail ((type == FOLDER_PERM_TYPE_USER || type == FOLDER_PERM_TYPE_GROUP), -1); /* Update db. */ pthread_mutex_lock (&mgr->priv->db_lock); if (type == FOLDER_PERM_TYPE_USER) sql = "DELETE FROM FolderUserPerms WHERE repo_id = ?"; else sql = "DELETE FROM FolderGroupPerms WHERE repo_id = ?"; stmt = sqlite_query_prepare (mgr->priv->db, sql); sqlite3_bind_text (stmt, 1, repo_id, -1, SQLITE_TRANSIENT); if (sqlite3_step (stmt) != SQLITE_DONE) { seaf_warning ("Failed to remove folder perms for %.8s: %s.\n", repo_id, sqlite3_errmsg (mgr->priv->db)); sqlite3_finalize (stmt); pthread_mutex_unlock (&mgr->priv->db_lock); return -1; } sqlite3_finalize (stmt); if (!folder_perms) { pthread_mutex_unlock (&mgr->priv->db_lock); pthread_mutex_lock (&mgr->priv->perm_lock); if (type == FOLDER_PERM_TYPE_USER) { old = g_hash_table_lookup (mgr->priv->user_perms, repo_id); if (old) { g_list_free_full (old, (GDestroyNotify)folder_perm_free); } g_hash_table_insert (mgr->priv->user_perms, g_strdup(repo_id), NULL); } else if (type == FOLDER_PERM_TYPE_GROUP) { old = g_hash_table_lookup (mgr->priv->group_perms, repo_id); if (old) { g_list_free_full (old, (GDestroyNotify)folder_perm_free); } g_hash_table_insert (mgr->priv->group_perms, g_strdup(repo_id), NULL); } pthread_mutex_unlock (&mgr->priv->perm_lock); return 0; } if (type == FOLDER_PERM_TYPE_USER) sql = "INSERT INTO FolderUserPerms VALUES (?, ?, ?)"; else sql = "INSERT INTO FolderGroupPerms VALUES (?, ?, ?)"; stmt = sqlite_query_prepare (mgr->priv->db, sql); for (ptr = folder_perms; ptr; ptr = ptr->next) { perm = ptr->data; sqlite3_bind_text (stmt, 1, repo_id, -1, SQLITE_TRANSIENT); sqlite3_bind_text (stmt, 2, perm->path, -1, SQLITE_TRANSIENT); sqlite3_bind_text (stmt, 3, perm->permission, -1, SQLITE_TRANSIENT); if (sqlite3_step (stmt) != SQLITE_DONE) { seaf_warning ("Failed to insert folder perms for %.8s: %s.\n", repo_id, sqlite3_errmsg (mgr->priv->db)); sqlite3_finalize (stmt); pthread_mutex_unlock (&mgr->priv->db_lock); return -1; } sqlite3_reset (stmt); sqlite3_clear_bindings (stmt); } sqlite3_finalize (stmt); pthread_mutex_unlock (&mgr->priv->db_lock); /* Update in memory */ new = folder_perm_list_copy (folder_perms); new = g_list_sort (new, comp_folder_perms); pthread_mutex_lock (&mgr->priv->perm_lock); if (type == FOLDER_PERM_TYPE_USER) { old = g_hash_table_lookup (mgr->priv->user_perms, repo_id); if (old) g_list_free_full (old, (GDestroyNotify)folder_perm_free); g_hash_table_insert (mgr->priv->user_perms, g_strdup(repo_id), new); } else if (type == FOLDER_PERM_TYPE_GROUP) { old = g_hash_table_lookup (mgr->priv->group_perms, repo_id); if (old) g_list_free_full (old, (GDestroyNotify)folder_perm_free); g_hash_table_insert (mgr->priv->group_perms, g_strdup(repo_id), new); } pthread_mutex_unlock (&mgr->priv->perm_lock); return 0; } static gboolean load_folder_perm (sqlite3_stmt *stmt, void *data) { GList **p_perms = data; const char *path, *permission; path = (const char *)sqlite3_column_text (stmt, 0); permission = (const char *)sqlite3_column_text (stmt, 1); FolderPerm *perm = folder_perm_new (path, permission); *p_perms = g_list_prepend (*p_perms, perm); return TRUE; } static GList * load_folder_perms_for_repo (SeafRepoManager *mgr, const char *repo_id, FolderPermType type) { GList *perms = NULL; char sql[256]; g_return_val_if_fail ((type == FOLDER_PERM_TYPE_USER || type == FOLDER_PERM_TYPE_GROUP), NULL); if (type == FOLDER_PERM_TYPE_USER) sqlite3_snprintf (sizeof(sql), sql, "SELECT path, permission FROM FolderUserPerms " "WHERE repo_id = '%q'", repo_id); else sqlite3_snprintf (sizeof(sql), sql, "SELECT path, permission FROM FolderGroupPerms " "WHERE repo_id = '%q'", repo_id); pthread_mutex_lock (&mgr->priv->db_lock); if (sqlite_foreach_selected_row (mgr->priv->db, sql, load_folder_perm, &perms) < 0) { pthread_mutex_unlock (&mgr->priv->db_lock); GList *ptr; for (ptr = perms; ptr; ptr = ptr->next) folder_perm_free ((FolderPerm *)ptr->data); g_list_free (perms); return NULL; } pthread_mutex_unlock (&mgr->priv->db_lock); /* Sort list in descending order by perm->path (longer path first). */ perms = g_list_sort (perms, comp_folder_perms); return perms; } static void init_folder_perms (SeafRepoManager *mgr) { SeafRepoManagerPriv *priv = mgr->priv; GList *repo_ids = g_hash_table_get_keys (priv->repo_hash); GList *ptr; GList *perms; char *repo_id; for (ptr = repo_ids; ptr; ptr = ptr->next) { repo_id = ptr->data; perms = load_folder_perms_for_repo (mgr, repo_id, FOLDER_PERM_TYPE_USER); if (perms) { pthread_mutex_lock (&priv->perm_lock); g_hash_table_insert (priv->user_perms, g_strdup(repo_id), perms); pthread_mutex_unlock (&priv->perm_lock); } perms = load_folder_perms_for_repo (mgr, repo_id, FOLDER_PERM_TYPE_GROUP); if (perms) { pthread_mutex_lock (&priv->perm_lock); g_hash_table_insert (priv->group_perms, g_strdup(repo_id), perms); pthread_mutex_unlock (&priv->perm_lock); } } g_list_free (repo_ids); } static void remove_folder_perms (SeafRepoManager *mgr, const char *repo_id) { GList *perms = NULL; pthread_mutex_lock (&mgr->priv->perm_lock); perms = g_hash_table_lookup (mgr->priv->user_perms, repo_id); if (perms) { g_list_free_full (perms, (GDestroyNotify)folder_perm_free); g_hash_table_remove (mgr->priv->user_perms, repo_id); } perms = g_hash_table_lookup (mgr->priv->group_perms, repo_id); if (perms) { g_list_free_full (perms, (GDestroyNotify)folder_perm_free); g_hash_table_remove (mgr->priv->group_perms, repo_id); } pthread_mutex_unlock (&mgr->priv->perm_lock); } int seaf_repo_manager_update_folder_perm_timestamp (SeafRepoManager *mgr, const char *repo_id, gint64 timestamp) { char sql[256]; int ret; snprintf (sql, sizeof(sql), "REPLACE INTO FolderPermTimestamp VALUES ('%s', %"G_GINT64_FORMAT")", repo_id, timestamp); pthread_mutex_lock (&mgr->priv->db_lock); ret = sqlite_query_exec (mgr->priv->db, sql); pthread_mutex_unlock (&mgr->priv->db_lock); return ret; } gint64 seaf_repo_manager_get_folder_perm_timestamp (SeafRepoManager *mgr, const char *repo_id) { char sql[256]; gint64 ret; sqlite3_snprintf (sizeof(sql), sql, "SELECT timestamp FROM FolderPermTimestamp WHERE repo_id = '%q'", repo_id); pthread_mutex_lock (&mgr->priv->db_lock); ret = sqlite_get_int64 (mgr->priv->db, sql); pthread_mutex_unlock (&mgr->priv->db_lock); return ret; } static char * lookup_folder_perm (GList *perms, const char *path) { GList *ptr; FolderPerm *perm; char *folder; int len; char *permission = NULL; for (ptr = perms; ptr; ptr = ptr->next) { perm = ptr->data; if (strcmp (perm->path, "/") != 0) folder = g_strconcat (perm->path, "/", NULL); else folder = g_strdup(perm->path); len = strlen(folder); if (strcmp (perm->path, path) == 0 || strncmp(folder, path, len) == 0) { permission = perm->permission; g_free (folder); break; } g_free (folder); } return permission; } char * get_folder_perm_by_path (const char *repo_id, const char *path) { SeafRepoManager *mgr = seaf->repo_mgr; GList *user_perms = NULL, *group_perms = NULL; char *permission = NULL; char *abs_path = NULL; pthread_mutex_lock (&mgr->priv->perm_lock); user_perms = g_hash_table_lookup (mgr->priv->user_perms, repo_id); group_perms = g_hash_table_lookup (mgr->priv->group_perms, repo_id); if (user_perms || group_perms) { if (path[0] != '/') { abs_path = g_strconcat ("/", path, NULL); } else { abs_path = g_strdup (path); } } if (user_perms) permission = lookup_folder_perm (user_perms, abs_path); if (!permission && group_perms) permission = lookup_folder_perm (group_perms, abs_path); pthread_mutex_unlock (&mgr->priv->perm_lock); g_free (abs_path); return permission; } static gboolean is_path_writable (const char *repo_id, gboolean is_repo_readonly, const char *path) { char *permission = NULL; permission = get_folder_perm_by_path (repo_id, path); if (!permission) return !is_repo_readonly; if (strcmp (permission, "rw") == 0) return TRUE; else return FALSE; } gboolean seaf_repo_manager_is_path_writable (SeafRepoManager *mgr, const char *repo_id, const char *path) { SeafRepo *repo; gboolean ret; repo = seaf_repo_manager_get_repo (mgr, repo_id); if (!repo) { return FALSE; } ret = is_path_writable (repo_id, repo->is_readonly, path); seaf_repo_unref (repo); return ret; } // When checking whether a path is visible, we need to consider whether the parent folder is visible or not. // If the parent folder is not visible, then even if the subfolder has visible permissions, the subfolder is not visible. gboolean is_path_invisible_recursive (const char *repo_id, const char *path) { char *permission = NULL; if (!path || g_strcmp0 (path, ".") == 0) { return FALSE; } permission = get_folder_perm_by_path (repo_id, path); // Check if path is / after get folder perm. if (g_strcmp0 (path, "/") == 0) { if (!permission) { return FALSE; } if (strcmp (permission, "rw") == 0 || strcmp (permission, "r") == 0) { return FALSE; } return TRUE; } if (!permission) { char *folder = g_path_get_dirname (path); gboolean is_invisible = is_path_invisible_recursive(repo_id, folder); g_free (folder); return is_invisible; } if (strcmp (permission, "rw") == 0 || strcmp (permission, "r") == 0) { char *folder = g_path_get_dirname (path); gboolean is_invisible = is_path_invisible_recursive(repo_id, folder); g_free (folder); return is_invisible; } else { return TRUE; } } gboolean seaf_repo_manager_is_path_invisible (SeafRepoManager *mgr, const char *repo_id, const char *path) { SeafRepo *repo; gboolean ret; repo = seaf_repo_manager_get_repo (mgr, repo_id); if (!repo) { return TRUE; } ret = is_path_invisible_recursive (repo_id, path); seaf_repo_unref (repo); return ret; } static gboolean include_invisible_perm (GList *perms) { GList *ptr; FolderPerm *perm; for (ptr = perms; ptr; ptr = ptr->next) { perm = ptr->data; if (strcmp (perm->permission, "rw") != 0 && strcmp (perm->permission, "r") != 0) return TRUE; } return FALSE; } gboolean seaf_repo_manager_include_invisible_perm (SeafRepoManager *mgr, const char *repo_id) { GList *user_perms = NULL, *group_perms = NULL; pthread_mutex_lock (&mgr->priv->perm_lock); user_perms = g_hash_table_lookup (mgr->priv->user_perms, repo_id); if (user_perms && include_invisible_perm (user_perms)) { pthread_mutex_unlock (&mgr->priv->perm_lock); return TRUE; } group_perms = g_hash_table_lookup (mgr->priv->group_perms, repo_id); if (group_perms && include_invisible_perm (group_perms)) { pthread_mutex_unlock (&mgr->priv->perm_lock); return TRUE; } pthread_mutex_unlock (&mgr->priv->perm_lock); return FALSE; } gboolean is_repo_id_valid (const char *id) { if (!id) return FALSE; return is_uuid_valid (id); } /* * Repo object life cycle management: * * Each repo object has a refcnt. A repo object is inserted into an internal * hash table in repo manager when it's created or loaded from db on startup. * The initial refcnt is 1, for the reference by the internal hash table. * * A repo object can only be obtained from outside by calling * seaf_repo_manager_get_repo(), which increase refcnt by 1. * Once the caller is done with the object, it must call seaf_repo_unref() to * decrease refcnt. * * When a repo needs to be deleted, call seaf_repo_manager_mark_repo_deleted(). * The function sets the "delete_pending" flag of the repo object. * Repo manager won't return the repo to callers once it's marked as deleted. * Repo manager regularly checks every repo in the hash table. * If a repo is marked as delete pending and refcnt is 1, it removes the repo * on disk. After the repo is removed on disk, repo manager removes the repo * object from internal hash table. * * Sync manager must make sure a repo won't be downloaded when it's marked as * delete pending. Otherwise repo manager may remove the downloaded metadata * as it cleans up the old repo on disk. */ SeafRepo* seaf_repo_new (const char *id, const char *name, const char *desc) { SeafRepo* repo; repo = g_new0 (SeafRepo, 1); memcpy (repo->id, id, 36); repo->id[36] = '\0'; repo->name = g_strdup(name); repo->desc = g_strdup(desc); repo->tree = repo_tree_new (id); return repo; } void seaf_repo_free (SeafRepo *repo) { if (repo->head) seaf_branch_unref (repo->head); repo_tree_free (repo->tree); if (repo->journal) journal_close (repo->journal, TRUE); g_free (repo->name); g_free (repo->desc); g_free (repo->category); g_free (repo->token); g_free (repo->jwt_token); if (repo->repo_uname) g_free (repo->repo_uname); if (repo->worktree) g_free (repo->worktree); g_free (repo->server); g_free (repo->user); g_free (repo->fileserver_addr); g_free (repo->pwd_hash_algo); g_free (repo->pwd_hash_params); g_free (repo); } void seaf_repo_ref (SeafRepo *repo) { g_atomic_int_inc (&repo->refcnt); } void seaf_repo_unref (SeafRepo *repo) { if (!repo) return; pthread_rwlock_wrlock (&seaf->repo_mgr->priv->lock); if (g_atomic_int_dec_and_test (&repo->refcnt)) seaf_repo_free (repo); pthread_rwlock_unlock (&seaf->repo_mgr->priv->lock); } static void set_head_common (SeafRepo *repo, SeafBranch *branch) { if (repo->head) seaf_branch_unref (repo->head); repo->head = branch; seaf_branch_ref(branch); } int seaf_repo_set_head (SeafRepo *repo, SeafBranch *branch) { if (save_branch_repo_map (seaf->repo_mgr, branch) < 0) return -1; set_head_common (repo, branch); return 0; } SeafCommit * seaf_repo_get_head_commit (const char *repo_id) { SeafRepo *repo = NULL; SeafCommit *head = NULL; repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id); if (!repo) { seaf_warning ("Failed to get repo %s.\n", repo_id); return NULL; } head = seaf_commit_manager_get_commit (seaf->commit_mgr, repo_id, repo->version, repo->head->commit_id); if (!head) { seaf_warning ("Failed to get head for repo %s.\n", repo_id); } seaf_repo_unref (repo); return head; } void seaf_repo_from_commit (SeafRepo *repo, SeafCommit *commit) { repo->name = g_strdup (commit->repo_name); repo->desc = g_strdup (commit->repo_desc); repo->encrypted = commit->encrypted; repo->last_modify = commit->ctime; memcpy (repo->root_id, commit->root_id, 40); if (repo->encrypted) { repo->enc_version = commit->enc_version; if (repo->enc_version == 1 && !commit->pwd_hash_algo) memcpy (repo->magic, commit->magic, 32); else if (repo->enc_version == 2) { memcpy (repo->random_key, commit->random_key, 96); } else if (repo->enc_version == 3) { memcpy (repo->salt, commit->salt, 64); memcpy (repo->random_key, commit->random_key, 96); } else if (repo->enc_version == 4) { memcpy (repo->salt, commit->salt, 64); memcpy (repo->random_key, commit->random_key, 96); } if (repo->enc_version >= 2 && !commit->pwd_hash_algo) { memcpy (repo->magic, commit->magic, 64); } if (commit->pwd_hash_algo) { memcpy (repo->pwd_hash, commit->pwd_hash, 64); repo->pwd_hash_algo = g_strdup (commit->pwd_hash_algo); repo->pwd_hash_params = g_strdup (commit->pwd_hash_params); } } repo->version = commit->version; } void seaf_repo_to_commit (SeafRepo *repo, SeafCommit *commit) { commit->repo_name = g_strdup (repo->name); commit->repo_desc = g_strdup (repo->desc); commit->encrypted = repo->encrypted; if (commit->encrypted) { commit->enc_version = repo->enc_version; if (commit->enc_version == 1 && !repo->pwd_hash_algo) commit->magic = g_strdup (repo->magic); else if (commit->enc_version == 2) { commit->random_key = g_strdup (repo->random_key); } else if (commit->enc_version == 3) { commit->salt = g_strdup (repo->salt); commit->random_key = g_strdup (repo->random_key); } else if (commit->enc_version == 4) { commit->salt = g_strdup (repo->salt); commit->random_key = g_strdup (repo->random_key); } if (commit->enc_version >= 2 && !repo->pwd_hash_algo) { commit->magic = g_strdup (repo->magic); } if (repo->pwd_hash_algo) { commit->pwd_hash = g_strdup (repo->pwd_hash); commit->pwd_hash_algo = g_strdup (repo->pwd_hash_algo); commit->pwd_hash_params = g_strdup (repo->pwd_hash_params); } } commit->version = repo->version; } void seaf_repo_set_readonly (SeafRepo *repo) { repo->is_readonly = TRUE; save_repo_property (repo->manager, repo->id, REPO_PROP_IS_READONLY, "true"); } void seaf_repo_unset_readonly (SeafRepo *repo) { repo->is_readonly = FALSE; save_repo_property (repo->manager, repo->id, REPO_PROP_IS_READONLY, "false"); } void seaf_repo_set_worktree (SeafRepo *repo, const char *repo_uname) { if (repo->repo_uname) g_free (repo->repo_uname); repo->repo_uname = g_strdup (repo_uname); if (repo->worktree) g_free (repo->worktree); repo->worktree = g_build_path ("/", seaf->mount_point, repo_uname, NULL); } static void print_apply_op_error (const char *repo_id, JournalOp *op, int err) { seaf_warning ("Failed to apply op to repo tree of %s: %s.\n" "Op details: %d %s %s %"G_GINT64_FORMAT" %"G_GINT64_FORMAT" %u\n", repo_id, strerror(err), op->type, op->path, op->new_path, op->size, op->mtime, op->mode); } static int apply_journal_ops_to_repo_tree (SeafRepo *repo) { GList *ops, *ptr; gboolean error; JournalOp *op; RepoTree *tree = repo->tree; int rc; int ret = 0; ops = journal_read_ops (repo->journal, repo->head->opid + 1, G_MAXINT64, &error); if (error) { seaf_warning ("Failed to read operations from journal for repo %s.\n", repo->id); return -1; } for (ptr = ops; ptr; ptr = ptr->next) { op = (JournalOp *)ptr->data; switch (op->type) { case OP_TYPE_CREATE_FILE: rc = repo_tree_create_file (tree, op->path, EMPTY_SHA1, op->mode, op->mtime, op->size); if (rc < 0) { print_apply_op_error (repo->id, op, -rc); ret = -1; goto out; } break; case OP_TYPE_DELETE_FILE: rc = repo_tree_unlink (tree, op->path); if (rc < 0) { print_apply_op_error (repo->id, op, -rc); ret = -1; goto out; } break; case OP_TYPE_UPDATE_FILE: rc = repo_tree_set_file_mtime (tree, op->path, op->mtime); if (rc < 0) { print_apply_op_error (repo->id, op, -rc); ret = -1; goto out; } rc = repo_tree_set_file_size (tree, op->path, op->size); if (rc < 0) { print_apply_op_error (repo->id, op, -rc); ret = -1; goto out; } break; case OP_TYPE_RENAME: rc = repo_tree_rename (tree, op->path, op->new_path, TRUE); if (rc < 0) { print_apply_op_error (repo->id, op, -rc); ret = -1; goto out; } break; case OP_TYPE_MKDIR: rc = repo_tree_mkdir (tree, op->path, op->mtime); if (rc < 0) { print_apply_op_error (repo->id, op, -rc); ret = -1; goto out; } break; case OP_TYPE_RMDIR: rc = repo_tree_rmdir (tree, op->path); if (rc < 0) { print_apply_op_error (repo->id, op, -rc); ret = -1; goto out; } break; case OP_TYPE_UPDATE_ATTR: rc = repo_tree_set_file_mtime (tree, op->path, op->mtime); if (rc < 0) { print_apply_op_error (repo->id, op, -rc); ret = -1; goto out; } break; default: seaf_warning ("Unknown op type %d, skipped.\n", op->type); } } out: g_list_free_full (ops, (GDestroyNotify)journal_op_free); return ret; } static char * build_conflict_path (const char *user, const char *path, time_t t) { char time_buf[64]; char *copy = g_strdup (path); GString *conflict_path = g_string_new (NULL); char *dot, *ext; strftime(time_buf, 64, "%Y-%m-%d-%H-%M-%S", localtime(&t)); dot = strrchr (copy, '.'); if (dot != NULL) { *dot = '\0'; ext = dot + 1; g_string_printf (conflict_path, "%s (SFConflict %s %s).%s", copy, user, time_buf, ext); } else { g_string_printf (conflict_path, "%s (SFConflict %s %s)", copy, user, time_buf); } g_free (copy); return g_string_free (conflict_path, FALSE); } static int copy_file_to_conflict_file (SeafRepo *repo, SeafStat *st, const char *path, const char *conflict_path) { JournalOp *op; int ret = 0; repo_tree_create_file (repo->tree, conflict_path, EMPTY_SHA1, st->st_mode, st->st_mtime, st->st_size); op = journal_op_new (OP_TYPE_CREATE_FILE, conflict_path, NULL, 0, 0, st->st_mode); if (journal_append_op (repo->journal, op) < 0) { journal_op_free (op); ret = -1; goto out; } op = journal_op_new (OP_TYPE_UPDATE_FILE, conflict_path, NULL, st->st_size, st->st_mtime, st->st_mode); if (journal_append_op (repo->journal, op) < 0) { journal_op_free (op); ret = -1; goto out; } file_cache_mgr_rename (seaf->file_cache_mgr, repo->id, path, repo->id, conflict_path); /* Reset extended attrs so that the file will be indexed on commit. */ file_cache_mgr_set_attrs (seaf->file_cache_mgr, repo->id, conflict_path, 0, 0, EMPTY_SHA1); out: return ret; } static int create_parent_dirs (SeafRepo *repo, const char *parent_path) { char **parts = g_strsplit (parent_path, "/", 0); guint len = g_strv_length (parts); guint i; GString *dir_path = NULL; int rc; RepoTreeStat tree_st; JournalOp *op; int ret = 0; if (len == 0) return 0; dir_path = g_string_new (""); for (i = 0; i < len; ++i) { if (i == 0) g_string_append (dir_path, parts[0]); else g_string_append_printf (dir_path, "/%s", parts[i]); if (repo_tree_stat_path (repo->tree, dir_path->str, &tree_st) == 0) { continue; } rc = repo_tree_mkdir (repo->tree, dir_path->str, (gint64)time(NULL)); if (rc < 0) { seaf_warning ("Failed to create dir %s/%s in repo tree: %s.\n", repo->id, dir_path->str, strerror(rc)); ret = -1; break; } op = journal_op_new (OP_TYPE_MKDIR, dir_path->str, NULL, 0, (gint64)time(NULL), 0); if (journal_append_op (repo->journal, op) < 0) { seaf_warning ("Failed to append operation to journal of repo %s.\n", repo->id); journal_op_free (op); ret = -1; break; } } g_string_free (dir_path, TRUE); g_strfreev (parts); return ret; } static int add_cached_file_to_tree (SeafRepo *repo, SeafStat *st, const char *path) { JournalOp *op; char *parent_path; parent_path = g_path_get_dirname (path); if (g_strcmp0 (parent_path, ".") != 0) { if (create_parent_dirs (repo, parent_path) < 0) { g_free (parent_path); return -1; } } g_free (parent_path); repo_tree_create_file (repo->tree, path, EMPTY_SHA1, st->st_mode, st->st_mtime, st->st_size); op = journal_op_new (OP_TYPE_CREATE_FILE, path, NULL, 0, 0, st->st_mode); if (journal_append_op (repo->journal, op) < 0) { journal_op_free (op); return -1; } op = journal_op_new (OP_TYPE_UPDATE_FILE, path, NULL, st->st_size, st->st_mtime, st->st_mode); if (journal_append_op (repo->journal, op) < 0) { journal_op_free (op); return -1; } /* Reset extended attrs so that the file will be indexed on commit. */ file_cache_mgr_set_attrs (seaf->file_cache_mgr, repo->id, path, 0, 0, EMPTY_SHA1); return 0; } static int update_cached_file_in_tree (SeafRepo *repo, SeafStat *st, const char *path) { JournalOp *op; repo_tree_set_file_mtime (repo->tree, path, (gint64)st->st_mtime); repo_tree_set_file_size (repo->tree, path, (gint64)st->st_size); op = journal_op_new (OP_TYPE_UPDATE_FILE, path, NULL, st->st_size, st->st_mtime, st->st_mode); if (journal_append_op (repo->journal, op) < 0) { journal_op_free (op); return -1; } return 0; } typedef struct TraverseCachedFileAux { SeafRepo *repo; gboolean after_clone; char *nickname; } TraverseCachedFileAux; static void check_cached_file_status_cb (const char *repo_id, const char *file_path, SeafStat *st, void *user_data) { TraverseCachedFileAux *aux = (TraverseCachedFileAux *)user_data; SeafRepo *repo = aux->repo; RepoTreeStat tree_st; char *filename; FileCacheStat attrs; /* If file is not completely cached yet, don't rely on its status. */ if (!file_cache_mgr_is_file_cached (seaf->file_cache_mgr, repo_id, file_path)) return; if (file_cache_mgr_get_attrs (seaf->file_cache_mgr, repo_id, file_path, &attrs) < 0) { seaf_warning ("Failed to get cached attrs for file %s/%s\n", repo_id, file_path); return; } filename = g_path_get_basename (file_path); if (repo_tree_stat_path (repo->tree, file_path, &tree_st) < 0) { if (seaf_repo_manager_ignored_on_commit(filename)) { /* Internally ignored files are not committed to the tree. * So after restart, these files are not loaded. * We can pick them up in the cache files folder. */ repo_tree_create_file (repo->tree, file_path, attrs.file_id, (guint32)st->st_mode, (gint64)st->st_mtime, (gint64)st->st_size); } else if (aux->after_clone || attrs.mtime != (gint64)st->st_mtime || attrs.size != (gint64)st->st_size) { /* If the program was hard shutdown, some operations were not flushed * into journal db. In this case, there could be some files missing from * journal and repo tree while present in the file cache. */ seaf_message ("Adding cached file %s/%s to tree.\n", repo->id, file_path); add_cached_file_to_tree (repo, st, file_path); } g_free (filename); return; } if (aux->after_clone) { /* Merge existing but not-yet committed cached files into repo after clone. */ if (((gint64)st->st_mtime != tree_st.mtime || (gint64)st->st_size != tree_st.size) && ((gint64)st->st_mtime != attrs.mtime || (gint64)st->st_size != attrs.size || !attrs.is_uploaded) && !seaf_repo_manager_ignored_on_commit(filename)) { seaf_message ("Cached file %s of repo %s(%.8s) is changed and different from repo tree status.\n" "Cached file: mtime %"G_GINT64_FORMAT", size %"G_GINT64_FORMAT"\n" "Extended attrs: mtime: %"G_GINT64_FORMAT", size %"G_GINT64_FORMAT"\n" "Repo tree: mtime %"G_GINT64_FORMAT", size %"G_GINT64_FORMAT"\n", file_path, repo->name, repo->id, (gint64)st->st_mtime, (gint64)st->st_size, attrs.mtime, attrs.size, tree_st.mtime, tree_st.size); char *conflict_path = build_conflict_path (aux->nickname, file_path, st->st_mtime); if (conflict_path) { seaf_message ("Generating conflict file %s in repo %s.\n", conflict_path, repo_id); copy_file_to_conflict_file (repo, st, file_path, conflict_path); g_free (conflict_path); } } } else { /* On start-up, if a cached file is not committed and its timestamp or size is * different from the repo tree, some updates to it was not flushed into journal * on the last shutdown. */ if (((gint64)st->st_mtime != tree_st.mtime || (gint64)st->st_size != tree_st.size) && ((gint64)st->st_mtime != attrs.mtime || (gint64)st->st_size != attrs.size) && !seaf_repo_manager_ignored_on_commit(filename)) { seaf_message ("Updating cached file %s/%s in tree.\n", repo->id, file_path); seaf_message ("Cached file: mtime %"G_GINT64_FORMAT", size %"G_GINT64_FORMAT"\n" "Extended attrs: mtime: %"G_GINT64_FORMAT", size %"G_GINT64_FORMAT"\n" "Repo tree: mtime: %"G_GINT64_FORMAT", size %"G_GINT64_FORMAT"\n", (gint64)st->st_mtime, (gint64)st->st_size, attrs.mtime, attrs.size, tree_st.mtime, tree_st.size); update_cached_file_in_tree (repo, st, file_path); } } g_free (filename); if ((gint64)st->st_mtime == attrs.mtime && (gint64)st->st_size == attrs.size) { seaf_sync_manager_update_active_path (seaf->sync_mgr, repo_id, file_path, st->st_mode, SYNC_STATUS_SYNCED); } } /* static void */ /* check_cached_dir_cb (const char *repo_id, */ /* const char *dir_path, */ /* SeafStat *st, */ /* void *user_data) */ /* { */ /* TraverseCachedFileAux *aux = (TraverseCachedFileAux *)user_data; */ /* SeafRepo *repo = aux->repo; */ /* RepoTreeStat tree_st; */ /* JournalOp *op; */ /* if (repo_tree_stat_path (repo->tree, dir_path, &tree_st) == 0) { */ /* return; */ /* } */ /* int rc = repo_tree_mkdir (repo->tree, dir_path, (gint64)st->st_mtime); */ /* if (rc < 0) { */ /* return; */ /* } */ /* op = journal_op_new (OP_TYPE_MKDIR, dir_path, NULL, 0, (gint64)st->st_mtime, 0); */ /* if (journal_append_op (repo->journal, op) < 0) { */ /* seaf_warning ("Failed to append operation to journal of repo %s.\n", */ /* repo_id); */ /* journal_op_free (op); */ /* } */ /* } */ int seaf_repo_load_fs (SeafRepo *repo, gboolean after_clone) { if (repo->fs_ready) return 0; if (!repo->journal) { repo->journal = journal_manager_open_journal (seaf->journal_mgr, repo->id); if (!repo->journal) { seaf_warning ("Failed to open journal for repo %s(%s).\n", repo->name, repo->id); return -1; } } if (repo_tree_load_commit (repo->tree, repo->head->commit_id) < 0) { seaf_warning ("Failed to load tree for repo %s.\n", repo->id); journal_close (repo->journal, TRUE); repo->journal = NULL; return -1; } /* Ignore errors when replaying the journal. * There can sometimes be edge case that fails an operation replay, like deleting * a non-existing file. These cases shouldn't harm. */ apply_journal_ops_to_repo_tree (repo); TraverseCachedFileAux aux; aux.repo = repo; aux.after_clone = after_clone; SeafAccount *account = seaf_repo_manager_get_account (seaf->repo_mgr, repo->server, repo->user); if (account && account->nickname) { aux.nickname = g_strdup (account->nickname); } else { aux.nickname = g_strdup (repo->user); } seaf_account_free (account); file_cache_mgr_traverse_path (seaf->file_cache_mgr, repo->id, "", check_cached_file_status_cb, NULL, &aux); g_free (aux.nickname); repo->fs_ready = TRUE; return 0; } SeafRepoManager* seaf_repo_manager_new (SeafileSession *seaf) { SeafRepoManager *mgr = g_new0 (SeafRepoManager, 1); mgr->priv = g_new0 (SeafRepoManagerPriv, 1); mgr->seaf = seaf; pthread_mutex_init (&mgr->priv->db_lock, NULL); mgr->priv->repo_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); pthread_rwlock_init (&mgr->priv->lock, NULL); mgr->priv->user_perms = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); mgr->priv->group_perms = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); pthread_mutex_init (&mgr->priv->perm_lock, NULL); mgr->priv->accounts = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)seaf_account_free); pthread_rwlock_init (&mgr->priv->account_lock, NULL); mgr->priv->repo_infos = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)repo_info_free); mgr->priv->repo_names = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_hash_table_destroy); return mgr; } int seaf_repo_manager_init (SeafRepoManager *mgr) { /* Load all the repos into memory on the client side. */ load_repos (mgr, mgr->seaf->seaf_dir); /* Load folder permissions from db. */ init_folder_perms (mgr); return 0; } #define REMOVE_OBJECTS_BATCH 1000 static int remove_store (const char *top_store_dir, const char *store_id, int *count) { char *obj_dir = NULL; GDir *dir1, *dir2; const char *dname1, *dname2; char *path1, *path2; obj_dir = g_build_filename (top_store_dir, store_id, NULL); dir1 = g_dir_open (obj_dir, 0, NULL); if (!dir1) { g_free (obj_dir); return 0; } seaf_message ("Removing store %s\n", obj_dir); while ((dname1 = g_dir_read_name(dir1)) != NULL) { path1 = g_build_filename (obj_dir, dname1, NULL); dir2 = g_dir_open (path1, 0, NULL); if (!dir2) { seaf_warning ("Failed to open obj dir %s.\n", path1); g_dir_close (dir1); g_free (path1); g_free (obj_dir); return -1; } while ((dname2 = g_dir_read_name(dir2)) != NULL) { path2 = g_build_filename (path1, dname2, NULL); g_unlink (path2); /* To prevent using too much IO, only remove 1000 objects per 5 seconds. */ if (++(*count) > REMOVE_OBJECTS_BATCH) { g_usleep (5 * G_USEC_PER_SEC); *count = 0; } g_free (path2); } g_dir_close (dir2); g_rmdir (path1); g_free (path1); } g_dir_close (dir1); g_rmdir (obj_dir); g_free (obj_dir); return 0; } static void cleanup_deleted_stores_by_type (const char *type) { char *top_store_dir; const char *repo_id; top_store_dir = g_build_filename (seaf->seaf_dir, "deleted_store", type, NULL); GError *error = NULL; GDir *dir = g_dir_open (top_store_dir, 0, &error); if (!dir) { seaf_warning ("Failed to open store dir %s: %s.\n", top_store_dir, error->message); g_free (top_store_dir); return; } int count = 0; while ((repo_id = g_dir_read_name(dir)) != NULL) { remove_store (top_store_dir, repo_id, &count); } g_free (top_store_dir); g_dir_close (dir); } static void * cleanup_deleted_stores (void *vdata) { while (1) { cleanup_deleted_stores_by_type ("commits"); cleanup_deleted_stores_by_type ("fs"); cleanup_deleted_stores_by_type ("blocks"); g_usleep (60 * G_USEC_PER_SEC); } return NULL; } int del_repo (SeafRepoManager *mgr, SeafRepo *repo) { char *repo_id = g_strdup (repo->id); gboolean remove_cache = repo->remove_cache; seaf_sync_manager_remove_active_path_info (seaf->sync_mgr, repo->id); seaf_repo_manager_remove_repo_ondisk (mgr, repo->id, remove_cache); /* Only remove the repo object from hash table after it's removed on disk. * Otherwise the sync manager may find that the repo doesn't exist and clone it * while the on-disk removal is still in progress. */ pthread_rwlock_wrlock (&mgr->priv->lock); g_hash_table_remove (mgr->priv->repo_hash, repo->id); pthread_rwlock_unlock (&mgr->priv->lock); seaf_repo_unref (repo); //Journal database is closed when object is released.So we have to delete //database file after repo object is release,otherwise the file is still //opened and not possible to delete on windows. journal_manager_delete_journal (seaf->journal_mgr, repo_id); g_free (repo_id); return 0; } static void * cleanup_deleted_repos (void *vdata) { SeafRepoManager *mgr = vdata; GHashTableIter iter; gpointer key, value; SeafRepo *repo; GList *deleted = NULL; GList *ptr; while (1) { pthread_rwlock_wrlock (&mgr->priv->lock); g_hash_table_iter_init (&iter, mgr->priv->repo_hash); while (g_hash_table_iter_next (&iter, &key, &value)) { repo = value; /* Delete a repo if it's marked as delete pending and * it's only referenced by the internal hash table. */ if (repo->delete_pending && repo->refcnt == 1) { deleted = g_list_prepend (deleted, repo); } } pthread_rwlock_unlock (&mgr->priv->lock); for (ptr = deleted; ptr; ptr = ptr->next) { repo = ptr->data; del_repo (mgr, repo); } g_list_free (deleted); deleted = NULL; g_usleep (G_USEC_PER_SEC); } return NULL; } int seaf_repo_manager_start (SeafRepoManager *mgr) { pthread_t tid; pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); int rc = pthread_create (&tid, &attr, cleanup_deleted_stores, NULL); if (rc != 0) { seaf_warning ("Failed to start cleanup thread: %s\n", strerror(rc)); return -1; } rc = pthread_create (&tid, &attr, cleanup_deleted_repos, mgr); if (rc != 0) { seaf_warning ("Failed to start cleanup deleted repo thread: %s\n", strerror(rc)); return -1; } return 0; } int seaf_repo_manager_add_repo (SeafRepoManager *manager, SeafRepo *repo) { char sql[256]; sqlite3 *db = manager->priv->db; pthread_mutex_lock (&manager->priv->db_lock); snprintf (sql, sizeof(sql), "REPLACE INTO Repo VALUES ('%s');", repo->id); sqlite_query_exec (db, sql); pthread_mutex_unlock (&manager->priv->db_lock); repo->manager = manager; if (pthread_rwlock_wrlock (&manager->priv->lock) < 0) { seaf_warning ("[repo mgr] failed to lock repo cache.\n"); return -1; } g_hash_table_insert (manager->priv->repo_hash, g_strdup(repo->id), repo); seaf_repo_ref (repo); pthread_rwlock_unlock (&manager->priv->lock); return 0; } int seaf_repo_manager_mark_repo_deleted (SeafRepoManager *mgr, SeafRepo *repo, gboolean remove_cache) { char sql[256]; seaf_message ("Mark repo %s(%.8s) as deleted.\n", repo->name, repo->id); pthread_mutex_lock (&mgr->priv->db_lock); snprintf (sql, sizeof(sql), "INSERT INTO DeletedRepo VALUES ('%s', %d)", repo->id, remove_cache); if (sqlite_query_exec (mgr->priv->db, sql) < 0) { pthread_mutex_unlock (&mgr->priv->db_lock); return -1; } pthread_mutex_unlock (&mgr->priv->db_lock); repo->delete_pending = TRUE; repo->remove_cache = remove_cache; #ifdef COMPILE_WS seaf_notif_manager_unsubscribe_repo (seaf->notif_mgr, repo); #endif return 0; } static char * gen_deleted_store_path (const char *type, const char *repo_id) { int n = 1; char *path = NULL; char *name = NULL; path = g_build_filename (seaf->deleted_store, type, repo_id, NULL); while (g_file_test(path, G_FILE_TEST_EXISTS) && n < 10) { g_free (path); name = g_strdup_printf ("%s(%d)", repo_id, n); path = g_build_filename (seaf->deleted_store, type, name, NULL); g_free (name); ++n; } if (n == 10) { g_free (path); return NULL; } return path; } void seaf_repo_manager_move_repo_store (SeafRepoManager *mgr, const char *type, const char *repo_id) { char *src = NULL; char *dst = NULL; src = g_build_filename (seaf->seaf_dir, "storage", type, repo_id, NULL); if (!seaf_util_exists (src)) { return; } dst = gen_deleted_store_path (type, repo_id); if (dst) { g_rename (src, dst); } g_free (src); g_free (dst); } /* Move commits, fs stores into "deleted_store" directory. */ static void move_repo_stores (SeafRepoManager *mgr, const char *repo_id) { seaf_repo_manager_move_repo_store (mgr, "commits", repo_id); seaf_repo_manager_move_repo_store (mgr, "fs", repo_id); } void seaf_repo_manager_remove_repo_ondisk (SeafRepoManager *mgr, const char *repo_id, gboolean remove_cache) { char sql[256]; seaf_message ("Removing repo %s on disk.\n", repo_id); pthread_mutex_lock (&mgr->priv->db_lock); snprintf (sql, sizeof(sql), "DELETE FROM Repo WHERE repo_id = '%s'", repo_id); if (sqlite_query_exec (mgr->priv->db, sql) < 0) { pthread_mutex_unlock (&mgr->priv->db_lock); return; } pthread_mutex_unlock (&mgr->priv->db_lock); /* remove branch */ GList *p; GList *branch_list = seaf_branch_manager_get_branch_list (seaf->branch_mgr, repo_id); for (p = branch_list; p; p = p->next) { SeafBranch *b = (SeafBranch *)p->data; seaf_repo_manager_branch_repo_unmap (mgr, b); seaf_branch_manager_del_branch (seaf->branch_mgr, repo_id, b->name); } seaf_branch_list_free (branch_list); /* delete repo property firstly */ seaf_repo_manager_del_repo_property (mgr, repo_id); pthread_mutex_lock (&mgr->priv->db_lock); snprintf (sql, sizeof(sql), "DELETE FROM RepoKeys WHERE repo_id = '%s'", repo_id); sqlite_query_exec (mgr->priv->db, sql); snprintf (sql, sizeof(sql), "DELETE FROM FolderUserPerms WHERE repo_id = '%s'", repo_id); sqlite_query_exec (mgr->priv->db, sql); snprintf (sql, sizeof(sql), "DELETE FROM FolderGroupPerms WHERE repo_id = '%s'", repo_id); sqlite_query_exec (mgr->priv->db, sql); snprintf (sql, sizeof(sql), "DELETE FROM FolderPermTimestamp WHERE repo_id = '%s'", repo_id); sqlite_query_exec (mgr->priv->db, sql); pthread_mutex_unlock (&mgr->priv->db_lock); seaf_filelock_manager_remove (seaf->filelock_mgr, repo_id); remove_folder_perms (mgr, repo_id); move_repo_stores (mgr, repo_id); if (remove_cache) file_cache_mgr_delete_repo_cache (seaf->file_cache_mgr, repo_id); /* At last remove the deleted record from db. If the above procedure is * interrupted, we can still redo the operations on next start. */ pthread_mutex_lock (&mgr->priv->db_lock); snprintf (sql, sizeof(sql), "DELETE FROM DeletedRepo WHERE repo_id = '%s'", repo_id); sqlite_query_exec (mgr->priv->db, sql); pthread_mutex_unlock (&mgr->priv->db_lock); } SeafRepo* seaf_repo_manager_get_repo (SeafRepoManager *manager, const gchar *id) { SeafRepo *repo, *res = NULL; if (!id) { return NULL; } if (pthread_rwlock_rdlock (&manager->priv->lock) < 0) { seaf_warning ("[repo mgr] failed to lock repo cache.\n"); return NULL; } repo = g_hash_table_lookup (manager->priv->repo_hash, id); if (repo && !repo->delete_pending) { seaf_repo_ref (repo); res = repo; } pthread_rwlock_unlock (&manager->priv->lock); return res; } gboolean seaf_repo_manager_repo_exists (SeafRepoManager *manager, const gchar *id) { SeafRepo *repo; gboolean res = FALSE; if (pthread_rwlock_rdlock (&manager->priv->lock) < 0) { seaf_warning ("[repo mgr] failed to lock repo cache.\n"); return FALSE; } repo = g_hash_table_lookup (manager->priv->repo_hash, id); if (repo && !repo->delete_pending) res = TRUE; pthread_rwlock_unlock (&manager->priv->lock); return res; } static int save_branch_repo_map (SeafRepoManager *manager, SeafBranch *branch) { char *sql; sqlite3 *db = manager->priv->db; pthread_mutex_lock (&manager->priv->db_lock); sql = sqlite3_mprintf ("REPLACE INTO RepoBranch VALUES (%Q, %Q)", branch->repo_id, branch->name); sqlite_query_exec (db, sql); sqlite3_free (sql); pthread_mutex_unlock (&manager->priv->db_lock); return 0; } int seaf_repo_manager_branch_repo_unmap (SeafRepoManager *manager, SeafBranch *branch) { char *sql; sqlite3 *db = manager->priv->db; pthread_mutex_lock (&manager->priv->db_lock); sql = sqlite3_mprintf ("DELETE FROM RepoBranch WHERE branch_name = %Q" " AND repo_id = %Q", branch->name, branch->repo_id); if (sqlite_query_exec (db, sql) < 0) { seaf_warning ("Unmap branch repo failed\n"); pthread_mutex_unlock (&manager->priv->db_lock); sqlite3_free (sql); return -1; } sqlite3_free (sql); pthread_mutex_unlock (&manager->priv->db_lock); return 0; } static void load_repo_commit (SeafRepoManager *manager, SeafRepo *repo, SeafBranch *branch) { SeafCommit *commit; commit = seaf_commit_manager_get_commit_compatible (manager->seaf->commit_mgr, repo->id, branch->commit_id); if (!commit) { seaf_warning ("Commit %s/%s is missing\n", repo->id, branch->commit_id); repo->is_corrupted = TRUE; return; } set_head_common (repo, branch); seaf_repo_from_commit (repo, commit); seaf_commit_unref (commit); } static gboolean load_keys_cb (sqlite3_stmt *stmt, void *vrepo) { SeafRepo *repo = vrepo; const char *key, *iv; key = (const char *)sqlite3_column_text(stmt, 0); iv = (const char *)sqlite3_column_text(stmt, 1); if (repo->enc_version == 1) { hex_to_rawdata (key, repo->enc_key, 16); hex_to_rawdata (iv, repo->enc_iv, 16); } else if (repo->enc_version >= 2) { hex_to_rawdata (key, repo->enc_key, 32); hex_to_rawdata (iv, repo->enc_iv, 16); } repo->is_passwd_set = TRUE; return FALSE; } static int load_repo_passwd (SeafRepoManager *manager, SeafRepo *repo) { sqlite3 *db = manager->priv->db; char sql[256]; int n; pthread_mutex_lock (&manager->priv->db_lock); snprintf (sql, sizeof(sql), "SELECT key, iv FROM RepoKeys WHERE repo_id='%s'", repo->id); n = sqlite_foreach_selected_row (db, sql, load_keys_cb, repo); if (n < 0) { pthread_mutex_unlock (&manager->priv->db_lock); return -1; } pthread_mutex_unlock (&manager->priv->db_lock); return 0; } static gboolean load_property_cb (sqlite3_stmt *stmt, void *pvalue) { char **value = pvalue; char *v = (char *) sqlite3_column_text (stmt, 0); *value = g_strdup (v); /* Only one result. */ return FALSE; } static char * load_repo_property (SeafRepoManager *manager, const char *repo_id, const char *key) { sqlite3 *db = manager->priv->db; char sql[256]; char *value = NULL; pthread_mutex_lock (&manager->priv->db_lock); snprintf(sql, 256, "SELECT value FROM RepoProperty WHERE " "repo_id='%s' and key='%s'", repo_id, key); if (sqlite_foreach_selected_row (db, sql, load_property_cb, &value) < 0) { seaf_warning ("Error read property %s for repo %s.\n", key, repo_id); pthread_mutex_unlock (&manager->priv->db_lock); return NULL; } pthread_mutex_unlock (&manager->priv->db_lock); return value; } static gboolean load_branch_cb (sqlite3_stmt *stmt, void *vrepo) { SeafRepo *repo = vrepo; SeafRepoManager *manager = repo->manager; char *branch_name = (char *) sqlite3_column_text (stmt, 0); SeafBranch *branch = seaf_branch_manager_get_branch (manager->seaf->branch_mgr, repo->id, branch_name); if (branch == NULL) { seaf_warning ("Broken branch name for repo %s\n", repo->id); repo->is_corrupted = TRUE; return FALSE; } load_repo_commit (manager, repo, branch); seaf_branch_unref (branch); /* Only one result. */ return FALSE; } static gboolean load_account_info_cb (sqlite3_stmt *stmt, void *vrepo) { SeafRepo *repo = vrepo; const char *server, *user; server = (const char *)sqlite3_column_text(stmt, 0); user = (const char *)sqlite3_column_text(stmt, 1); repo->server = g_strdup (server); repo->user = g_strdup (user); return FALSE; } static int load_repo_account_info (SeafRepoManager *manager, SeafRepo *repo) { sqlite3 *db = manager->priv->db; char sql[256]; int n; pthread_mutex_lock (&manager->priv->db_lock); snprintf (sql, sizeof(sql), "SELECT server, username FROM AccountRepos WHERE repo_id='%s'", repo->id); n = sqlite_foreach_selected_row (db, sql, load_account_info_cb, repo); if (n < 0) { pthread_mutex_unlock (&manager->priv->db_lock); return -1; } pthread_mutex_unlock (&manager->priv->db_lock); return 0; } static SeafRepo * load_repo (SeafRepoManager *manager, const char *repo_id) { char sql[256]; SeafRepo *repo = seaf_repo_new(repo_id, NULL, NULL); if (!repo) { seaf_warning ("[repo mgr] failed to alloc repo.\n"); return NULL; } repo->manager = manager; snprintf(sql, 256, "SELECT branch_name FROM RepoBranch WHERE repo_id='%s'", repo->id); if (sqlite_foreach_selected_row (manager->priv->db, sql, load_branch_cb, repo) <= 0) { seaf_warning ("Error read branch for repo %s.\n", repo->id); seaf_repo_free (repo); return NULL; } /* If repo head is set but failed to load branch or commit. */ if (repo->is_corrupted) { seaf_repo_free (repo); return NULL; } load_repo_passwd (manager, repo); load_repo_account_info (manager, repo); char *value; repo->token = load_repo_property (manager, repo->id, REPO_PROP_TOKEN); /* load readonly property */ value = load_repo_property (manager, repo->id, REPO_PROP_IS_READONLY); if (g_strcmp0(value, "true") == 0) repo->is_readonly = TRUE; else repo->is_readonly = FALSE; g_free (value); g_hash_table_insert (manager->priv->repo_hash, g_strdup(repo->id), repo); seaf_repo_ref (repo); return repo; } static sqlite3* open_db (SeafRepoManager *manager, const char *seaf_dir) { sqlite3 *db; char *db_path; db_path = g_build_filename (seaf_dir, "repo.db", NULL); if (sqlite_open_db (db_path, &db) < 0) return NULL; g_free (db_path); manager->priv->db = db; char *sql = "CREATE TABLE IF NOT EXISTS Repo (repo_id TEXT PRIMARY KEY);"; sqlite_query_exec (db, sql); sql = "CREATE TABLE IF NOT EXISTS DeletedRepo (repo_id TEXT PRIMARY KEY, remove_cache INTEGER);"; sqlite_query_exec (db, sql); /* Locally cache repo list for each account. */ sql = "CREATE TABLE IF NOT EXISTS AccountRepos " "(server TEXT, username TEXT, repo_id TEXT, " "name TEXT, display_name TEXT, mtime INTEGER);"; sqlite_query_exec (db, sql); sql = "CREATE UNIQUE INDEX IF NOT EXISTS AccountRepoIdIdx on AccountRepos " "(server, username, repo_id);"; sqlite_query_exec (db, sql); sql = "CREATE UNIQUE INDEX IF NOT EXISTS AccountRepoDisplayNameIdx on AccountRepos " "(server, username, display_name);"; sqlite_query_exec (db, sql); sql = "CREATE TABLE IF NOT EXISTS AccountSpace " "(server TEXT, username TEXT, total INTEGER, used INTEGER, " "PRIMARY KEY (server, username));"; sqlite_query_exec (db, sql); sql = "CREATE TABLE IF NOT EXISTS RepoBranch (" "repo_id TEXT PRIMARY KEY, branch_name TEXT);"; sqlite_query_exec (db, sql); sql = "CREATE TABLE IF NOT EXISTS RepoKeys " "(repo_id TEXT PRIMARY KEY, key TEXT NOT NULL, iv TEXT NOT NULL);"; sqlite_query_exec (db, sql); sql = "CREATE TABLE IF NOT EXISTS RepoProperty (" "repo_id TEXT, key TEXT, value TEXT);"; sqlite_query_exec (db, sql); sql = "CREATE INDEX IF NOT EXISTS RepoIndex ON RepoProperty (repo_id);"; sqlite_query_exec (db, sql); sql = "CREATE TABLE IF NOT EXISTS FolderUserPerms (" "repo_id TEXT, path TEXT, permission TEXT);"; sqlite_query_exec (db, sql); sql = "CREATE INDEX IF NOT EXISTS folder_user_perms_repo_id_idx " "ON FolderUserPerms (repo_id);"; sqlite_query_exec (db, sql); sql = "CREATE TABLE IF NOT EXISTS FolderGroupPerms (" "repo_id TEXT, path TEXT, permission TEXT);"; sqlite_query_exec (db, sql); sql = "CREATE INDEX IF NOT EXISTS folder_group_perms_repo_id_idx " "ON FolderGroupPerms (repo_id);"; sqlite_query_exec (db, sql); sql = "CREATE TABLE IF NOT EXISTS FolderPermTimestamp (" "repo_id TEXT, timestamp INTEGER, PRIMARY KEY (repo_id));"; sqlite_query_exec (db, sql); sql = "CREATE TABLE IF NOT EXISTS LockedFiles (repo_id TEXT, path TEXT, " "operation TEXT, old_mtime INTEGER, file_id TEXT, new_path TEXT, " "PRIMARY KEY (repo_id, path));"; sqlite_query_exec (db, sql); return db; } static gboolean load_repo_cb (sqlite3_stmt *stmt, void *vmanager) { SeafRepoManager *manager = vmanager; const char *repo_id; repo_id = (const char *) sqlite3_column_text (stmt, 0); load_repo (manager, repo_id); return TRUE; } static gboolean mark_repo_deleted (sqlite3_stmt *stmt, void *vmanager) { SeafRepoManager *manager = vmanager; const char *repo_id; int remove_cache; SeafRepo *repo; repo_id = (const char *) sqlite3_column_text (stmt, 0); remove_cache = sqlite3_column_int (stmt, 1); repo = seaf_repo_manager_get_repo (manager, repo_id); if (!repo) return TRUE; repo->delete_pending = TRUE; repo->remove_cache = remove_cache; seaf_repo_unref (repo); return TRUE; } static void load_repos (SeafRepoManager *manager, const char *seaf_dir) { sqlite3 *db = open_db(manager, seaf_dir); if (!db) return; char *sql; sql = "SELECT repo_id FROM Repo;"; if (sqlite_foreach_selected_row (db, sql, load_repo_cb, manager) < 0) { seaf_warning ("Error read repo db.\n"); return; } sql = "SELECT repo_id, remove_cache FROM DeletedRepo"; if (sqlite_foreach_selected_row (db, sql, mark_repo_deleted, manager) < 0) { seaf_warning ("Error mark repos deleted.\n"); return; } } static void save_repo_property (SeafRepoManager *manager, const char *repo_id, const char *key, const char *value) { char *sql; sqlite3 *db = manager->priv->db; pthread_mutex_lock (&manager->priv->db_lock); sql = sqlite3_mprintf ("SELECT repo_id FROM RepoProperty WHERE repo_id=%Q AND key=%Q", repo_id, key); if (sqlite_check_for_existence(db, sql)) { sqlite3_free (sql); sql = sqlite3_mprintf ("UPDATE RepoProperty SET value=%Q" "WHERE repo_id=%Q and key=%Q", value, repo_id, key); sqlite_query_exec (db, sql); sqlite3_free (sql); } else { sqlite3_free (sql); sql = sqlite3_mprintf ("INSERT INTO RepoProperty VALUES (%Q, %Q, %Q)", repo_id, key, value); sqlite_query_exec (db, sql); sqlite3_free (sql); } pthread_mutex_unlock (&manager->priv->db_lock); } int seaf_repo_manager_set_repo_property (SeafRepoManager *manager, const char *repo_id, const char *key, const char *value) { if (!seaf_repo_manager_repo_exists (manager, repo_id)) return -1; save_repo_property (manager, repo_id, key, value); return 0; } char * seaf_repo_manager_get_repo_property (SeafRepoManager *manager, const char *repo_id, const char *key) { return load_repo_property (manager, repo_id, key); } static void seaf_repo_manager_del_repo_property (SeafRepoManager *manager, const char *repo_id) { char *sql; sqlite3 *db = manager->priv->db; pthread_mutex_lock (&manager->priv->db_lock); sql = sqlite3_mprintf ("DELETE FROM RepoProperty WHERE repo_id = %Q", repo_id); sqlite_query_exec (db, sql); sqlite3_free (sql); pthread_mutex_unlock (&manager->priv->db_lock); } static void seaf_repo_manager_del_repo_property_by_key (SeafRepoManager *manager, const char *repo_id, const char *key) { char *sql; sqlite3 *db = manager->priv->db; pthread_mutex_lock (&manager->priv->db_lock); sql = sqlite3_mprintf ("DELETE FROM RepoProperty " "WHERE repo_id = %Q " " AND key = %Q", repo_id, key); sqlite_query_exec (db, sql); sqlite3_free (sql); pthread_mutex_unlock (&manager->priv->db_lock); } static int save_repo_enc_info (SeafRepoManager *manager, SeafRepo *repo) { sqlite3 *db = manager->priv->db; char sql[512]; char key[65], iv[33]; if (repo->enc_version == 1) { rawdata_to_hex (repo->enc_key, key, 16); rawdata_to_hex (repo->enc_iv, iv, 16); } else if (repo->enc_version >= 2) { rawdata_to_hex (repo->enc_key, key, 32); rawdata_to_hex (repo->enc_iv, iv, 16); } snprintf (sql, sizeof(sql), "REPLACE INTO RepoKeys VALUES ('%s', '%s', '%s')", repo->id, key, iv); if (sqlite_query_exec (db, sql) < 0) return -1; return 0; } GList* seaf_repo_manager_get_repo_list (SeafRepoManager *manager, int start, int limit) { GList *repo_list = NULL; GHashTableIter iter; SeafRepo *repo; gpointer key, value; if (pthread_rwlock_rdlock (&manager->priv->lock) < 0) { seaf_warning ("[repo mgr] failed to lock repo cache.\n"); return NULL; } g_hash_table_iter_init (&iter, manager->priv->repo_hash); while (g_hash_table_iter_next (&iter, &key, &value)) { repo = value; if (!repo->delete_pending) { repo_list = g_list_prepend (repo_list, repo); seaf_repo_ref (repo); } } pthread_rwlock_unlock (&manager->priv->lock); return repo_list; } GList* seaf_repo_manager_get_enc_repo_list (SeafRepoManager *manager, int start, int limit) { GList *repo_list = NULL; GHashTableIter iter; SeafRepo *repo; gpointer key, value; if (pthread_rwlock_rdlock (&manager->priv->lock) < 0) { seaf_warning ("[repo mgr] failed to lock repo cache.\n"); return NULL; } g_hash_table_iter_init (&iter, manager->priv->repo_hash); while (g_hash_table_iter_next (&iter, &key, &value)) { repo = value; if (!repo->delete_pending && repo->encrypted) { repo_list = g_list_prepend (repo_list, repo); seaf_repo_ref (repo); } } pthread_rwlock_unlock (&manager->priv->lock); return repo_list; } gboolean seaf_repo_manager_is_repo_delete_pending (SeafRepoManager *manager, const char *id) { SeafRepo *repo; gboolean ret = FALSE; pthread_rwlock_rdlock (&manager->priv->lock); repo = g_hash_table_lookup (manager->priv->repo_hash, id); if (repo && repo->delete_pending) ret = TRUE; pthread_rwlock_unlock (&manager->priv->lock); return ret; } int seaf_repo_manager_set_repo_token (SeafRepoManager *manager, SeafRepo *repo, const char *token) { g_free (repo->token); repo->token = g_strdup(token); save_repo_property (manager, repo->id, REPO_PROP_TOKEN, token); return 0; } int seaf_repo_manager_remove_repo_token (SeafRepoManager *manager, SeafRepo *repo) { g_free (repo->token); repo->token = NULL; seaf_repo_manager_del_repo_property_by_key(manager, repo->id, REPO_PROP_TOKEN); return 0; } void seaf_repo_manager_rename_repo (SeafRepoManager *manager, const char *repo_id, const char *new_name) { SeafRepo *repo; pthread_rwlock_wrlock (&manager->priv->lock); repo = g_hash_table_lookup (manager->priv->repo_hash, repo_id); if (repo) { g_free (repo->name); repo->name = g_strdup (new_name); } pthread_rwlock_unlock (&manager->priv->lock); } /* Account cache related functions. */ void seaf_account_free (SeafAccount *account) { if (!account) return; g_free (account->server); g_free (account->username); g_free (account->nickname); g_free (account->token); g_free (account->fileserver_addr); g_free (account->unique_id); g_free (account); } static const char *repo_type_strings[] = { "", "My Libraries", "Shared with me", "Shared with groups", "Shared with all", NULL }; static const char *repo_type_strings_zh[] = { "", "我的资料库", "共享资料库", "群组资料库", "公共资料库", NULL }; static const char *repo_type_strings_fr[] = { "", "Mes bibliothèques", "Partagé avec moi", "Partagé avec des groupes", "Partagé avec tout le monde", NULL }; static const char *repo_type_strings_de[] = { "", "Meine Bibliotheken", "Für mich freigegeben", "Für meine Gruppen", "Für alle freigegeben", NULL }; static const char ** get_repo_type_string_table () { const char **string_table; if (seaf->language == SEAF_LANG_ZH_CN) string_table = repo_type_strings_zh; else if (seaf->language == SEAF_LANG_FR_FR) string_table = repo_type_strings_fr; else if (seaf->language == SEAF_LANG_DE_DE) string_table = repo_type_strings_de; else string_table = repo_type_strings; return string_table; } RepoType repo_type_from_string (const char *type_str) { const char *str; int i; RepoType type = REPO_TYPE_UNKNOWN; const char **string_table = get_repo_type_string_table (); for (i = 0; i < N_REPO_TYPE; ++i) { str = string_table[i]; if (g_strcmp0 (type_str, str) == 0) { type = i; break; } } return type; } GList * repo_type_string_list () { GList *ret = NULL; const char *str; int i = 0; const char **string_table = get_repo_type_string_table (); for (i = 1; i < N_REPO_TYPE; ++i) { str = string_table[i]; ret = g_list_append (ret, g_strdup(str)); } return ret; } RepoInfo * repo_info_new (const char *id, const char *head_commit_id, const char *name, gint64 mtime, gboolean is_readonly) { RepoInfo *ret = g_new0 (RepoInfo, 1); memcpy (ret->id, id, 36); ret->head_commit_id = g_strdup(head_commit_id); ret->name = g_strdup(name); ret->mtime = mtime; ret->is_readonly = is_readonly; return ret; } RepoInfo * repo_info_copy (RepoInfo *info) { RepoInfo *ret = g_new0 (RepoInfo, 1); memcpy (ret->id, info->id, 36); ret->head_commit_id = g_strdup(info->head_commit_id); ret->name = g_strdup(info->name); ret->display_name = g_strdup(info->display_name); ret->mtime = info->mtime; ret->is_readonly = info->is_readonly; ret->is_corrupted = info->is_corrupted; ret->type = info->type; return ret; } void repo_info_free (RepoInfo *info) { if (!info) return; g_free (info->head_commit_id); g_free (info->name); g_free (info->display_name); g_free (info); } typedef struct _LoadRepoInfoRes { GHashTable *repos; GHashTable *name_to_repo; } LoadRepoInfoRes; static RepoType repo_type_from_display_name (const char *display_name) { char **tokens; char *type_str; RepoType type = REPO_TYPE_UNKNOWN; const char **string_table = get_repo_type_string_table (); int i; tokens = g_strsplit (display_name, "/", 0); if (g_strv_length (tokens) > 1) { type_str = tokens[0]; for (i = 1; i < N_REPO_TYPE; ++i) { if (strcmp (type_str, string_table[i]) == 0) type = i; } } g_strfreev (tokens); return type; } static gboolean load_repo_info_cb (sqlite3_stmt *stmt, void *data) { LoadRepoInfoRes *res = data; GHashTable *repos = res->repos; const char *repo_id, *name, *display_name; gint64 mtime; SeafRepo* repo = NULL; repo_id = (const char *)sqlite3_column_text (stmt, 0); name = (const char *)sqlite3_column_text (stmt, 1); display_name = (const char *)sqlite3_column_text (stmt, 2); mtime = (gint64)sqlite3_column_int64 (stmt, 3); RepoInfo *info = g_new0 (RepoInfo, 1); memcpy (info->id, repo_id, 36); info->name = g_strdup(name); info->display_name = g_strdup(display_name); info->mtime = mtime; info->type = repo_type_from_display_name (display_name); repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id); if (!repo) { seaf_warning ("Failed to find repo %s.\n", repo_id); } else { info->is_readonly = repo->is_readonly; seaf_repo_unref(repo); } g_hash_table_insert (repos, g_strdup(repo_id), info); return TRUE; } static int load_current_account_repo_info (SeafRepoManager *mgr, const char *server, const char *username, GHashTable *repos) { LoadRepoInfoRes res; res.repos = repos; pthread_mutex_lock (&mgr->priv->db_lock); char *sql = sqlite3_mprintf ("SELECT repo_id, name, display_name, mtime " "FROM AccountRepos WHERE " "server='%q' AND username='%q'", server, username); if (sqlite_foreach_selected_row (mgr->priv->db, sql, load_repo_info_cb, &res) < 0) { sqlite3_free (sql); pthread_mutex_unlock (&mgr->priv->db_lock); return -1; } pthread_mutex_unlock (&mgr->priv->db_lock); sqlite3_free (sql); return 0; } /* static void */ /* update_file_sync_status_cb (const char *repo_id, */ /* const char *file_path, */ /* SeafStat *st, */ /* void *user_data) */ /* { */ /* if (file_cache_mgr_is_file_cached (seaf->file_cache_mgr, */ /* repo_id, file_path) && */ /* !file_cache_mgr_is_file_changed (seaf->file_cache_mgr, */ /* repo_id, file_path, FALSE)) { */ /* seaf_sync_manager_update_active_path (seaf->sync_mgr, repo_id, */ /* file_path, st->st_mode, SYNC_STATUS_SYNCED); */ /* } */ /* } */ static void load_repo_fs_worker (gpointer data, gpointer user_data) { SeafRepo *repo = (SeafRepo *)data; seaf_repo_load_fs (repo, FALSE); seaf_repo_unref (repo); } #define MAX_LOAD_REPO_TREE_THREADS 5 static void * load_repo_file_systems_thread (void *vdata) { AccountInfo *account_info = vdata; GList *info_list, *ptr; RepoInfo *info; SeafRepo *repo; GThreadPool *pool; pool = g_thread_pool_new (load_repo_fs_worker, NULL, MAX_LOAD_REPO_TREE_THREADS, FALSE, NULL); if (!pool) { seaf_warning ("Failed to create thread pool.\n"); goto out; } info_list = seaf_repo_manager_get_account_repos (seaf->repo_mgr, account_info->server, account_info->username); for (ptr = info_list; ptr; ptr = ptr->next) { info = (RepoInfo *)ptr->data; repo = seaf_repo_manager_get_repo (seaf->repo_mgr, info->id); if (!repo) { continue; } seaf_repo_set_worktree (repo, info->display_name); g_thread_pool_push (pool, repo, NULL); } /* Wait until all tasks being done. */ g_thread_pool_free (pool, FALSE, TRUE); g_list_free_full (info_list, (GDestroyNotify)repo_info_free); /* for (ptr = info_list; ptr; ptr = ptr->next) { */ /* info = (RepoInfo *)ptr->data; */ /* file_cache_mgr_traverse_path (seaf->file_cache_mgr, info->id, "", */ /* update_file_sync_status_cb, NULL, NULL); */ /* } */ out: account_info_free (account_info); return NULL; } static char * create_unique_id (const char *server, const char *username) { char *id = g_strconcat (server, "_", username, NULL); char *p; for (p = id; *p != '\0'; ++p) { if (*p == ':' || *p == '/') *p = '_'; } return id; } void account_info_free (AccountInfo *info) { if (!info) return; g_free (info->server); g_free (info->username); g_free (info); } AccountInfo * seaf_repo_manager_get_account_info_by_name (SeafRepoManager *mgr, const char *name) { GHashTableIter iter; gpointer key, value; SeafAccount *account = NULL; AccountInfo *account_info = NULL; pthread_rwlock_rdlock (&mgr->priv->account_lock); g_hash_table_iter_init (&iter, mgr->priv->accounts); while (g_hash_table_iter_next (&iter, &key, &value)) { account = (SeafAccount *)value; if (g_strcmp0 (account->name, name) == 0) { account_info = g_new0 (AccountInfo, 1); account_info->server = g_strdup (account->server); account_info->username = g_strdup (account->username); break; } } pthread_rwlock_unlock (&mgr->priv->account_lock); return account_info; } static void add_repo_info (SeafRepoManager *mgr, char *account_key, GHashTable *new_repos) { GHashTable *new_name_to_repo; RepoInfo *info, *copy; GHashTableIter iter; gpointer key, value; new_name_to_repo = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); g_hash_table_iter_init (&iter, new_repos); while (g_hash_table_iter_next (&iter, &key, &value)) { info = value; copy = repo_info_copy (info); g_hash_table_insert (mgr->priv->repo_infos, g_strdup(copy->id), copy); g_hash_table_insert (new_name_to_repo, g_strdup(copy->display_name), copy); } g_hash_table_insert (mgr->priv->repo_names, g_strdup(account_key), new_name_to_repo); return; } int seaf_repo_manager_add_account (SeafRepoManager *mgr, const char *server, const char *username, const char *nickname, const char *token, const char *name, gboolean is_pro) { SeafAccount *account = NULL; SeafAccount *new_account = NULL; GHashTable *new_repos; seaf_message ("adding account %s %s %s.\n", server, username, name); account = seaf_repo_manager_get_account (seaf->repo_mgr, server, username); if (account) { seaf_account_free (account); return 0; } new_repos = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)repo_info_free); if (load_current_account_repo_info (mgr, server, username, new_repos ) < 0) { g_hash_table_destroy (new_repos); return -1; } char *account_key = g_strconcat (server, "_", username, NULL); new_account = g_new0 (SeafAccount, 1); new_account->server = g_strdup(server); new_account->username = g_strdup(username); new_account->nickname = g_strdup(nickname); new_account->token = g_strdup(token); new_account->name = g_strdup(name); new_account->fileserver_addr = parse_fileserver_addr(server); new_account->is_pro = is_pro; new_account->unique_id = create_unique_id (server, username); new_account->repo_list_fetched = FALSE; new_account->all_repos_loaded = FALSE; pthread_rwlock_wrlock (&mgr->priv->account_lock); add_repo_info (mgr, account_key, new_repos); g_hash_table_insert (mgr->priv->accounts, g_strdup(account_key), new_account); pthread_rwlock_unlock (&mgr->priv->account_lock); g_hash_table_destroy (new_repos); g_free (account_key); seaf->last_check_repo_list_time = 0; seaf->last_access_fs_time = 0; /* Update current repo list immediately after account switch. */ seaf_sync_manager_update_account_repo_list (seaf->sync_mgr, server, username); pthread_t tid; AccountInfo *account_info = g_new0(AccountInfo, 1); account_info->server = g_strdup (server); account_info->username = g_strdup (username); int rc = pthread_create (&tid, NULL, load_repo_file_systems_thread, account_info); if (rc != 0) { seaf_warning ("Failed to start load repo fs thread: %s\n", strerror(rc)); } return 0; } static SeafAccount * copy_account (SeafAccount *account) { SeafAccount *ret = g_new0 (SeafAccount, 1); ret->server = g_strdup(account->server); ret->username = g_strdup(account->username); ret->nickname = g_strdup(account->nickname); ret->token = g_strdup(account->token); ret->name = g_strdup (account->name); ret->fileserver_addr = g_strdup(account->fileserver_addr); ret->is_pro = account->is_pro; ret->unique_id = g_strdup(account->unique_id); ret->repo_list_fetched = account->repo_list_fetched; ret->all_repos_loaded = account->all_repos_loaded; ret->server_disconnected = account->server_disconnected; return ret; } SeafAccount * seaf_repo_manager_get_account (SeafRepoManager *mgr, const char *server, const char *username) { SeafAccount *account = NULL, *ret = NULL; if (!server && !username) { return NULL; } char *key = g_strconcat (server, "_", username, NULL); pthread_rwlock_rdlock (&mgr->priv->account_lock); account = g_hash_table_lookup (mgr->priv->accounts, key); if (!account) { goto out; } ret = copy_account (account); out: pthread_rwlock_unlock (&mgr->priv->account_lock); g_free (key); return ret; } gboolean seaf_repo_manager_account_exists (SeafRepoManager *mgr, const char *server, const char *username) { SeafAccount *account = NULL; gboolean exists = TRUE; char *key = g_strconcat (server, "_", username, NULL); pthread_rwlock_rdlock (&mgr->priv->account_lock); account = g_hash_table_lookup (mgr->priv->accounts, key); if (!account) { exists = FALSE; goto out; } out: pthread_rwlock_unlock (&mgr->priv->account_lock); g_free (key); return exists; } GList * seaf_repo_manager_get_account_list (SeafRepoManager *mgr) { GList *ret = NULL; GHashTableIter iter; gpointer key, value; SeafAccount *account = NULL; pthread_rwlock_rdlock (&mgr->priv->account_lock); g_hash_table_iter_init (&iter, mgr->priv->accounts); while (g_hash_table_iter_next (&iter, &key, &value)) { account = copy_account ((SeafAccount *)value); ret = g_list_prepend (ret, account); } pthread_rwlock_unlock (&mgr->priv->account_lock); return ret; } gboolean seaf_repo_manager_account_is_pro (SeafRepoManager *mgr, const char *server, const char *user) { gboolean ret = FALSE; SeafAccount *account = seaf_repo_manager_get_account (mgr, server, user); if (account) { ret = account->is_pro; } seaf_account_free (account); return ret; } void seaf_repo_manager_set_repo_list_fetched (SeafRepoManager *mgr, const char *server, const char *username) { SeafAccount *account = NULL; char *key = g_strconcat (server, "_", username, NULL); pthread_rwlock_wrlock (&mgr->priv->account_lock); account = g_hash_table_lookup (mgr->priv->accounts, key); if (!account) { goto out; } account->repo_list_fetched = TRUE; out: pthread_rwlock_unlock (&mgr->priv->account_lock); g_free (key); } void seaf_repo_manager_set_account_all_repos_loaded (SeafRepoManager *mgr, const char *server, const char *username) { SeafAccount *account = NULL; char *key = g_strconcat (server, "_", username, NULL); pthread_rwlock_wrlock (&mgr->priv->account_lock); account = g_hash_table_lookup (mgr->priv->accounts, key); if (!account) { goto out; } account->all_repos_loaded = TRUE; out: pthread_rwlock_unlock (&mgr->priv->account_lock); g_free (key); } void seaf_repo_manager_set_account_server_disconnected (SeafRepoManager *mgr, const char *server, const char *username, gboolean server_disconnected) { SeafAccount *account = NULL; char *key = g_strconcat (server, "_", username, NULL); pthread_rwlock_wrlock (&mgr->priv->account_lock); account = g_hash_table_lookup (mgr->priv->accounts, key); if (!account) { goto out; } account->server_disconnected = server_disconnected; out: pthread_rwlock_unlock (&mgr->priv->account_lock); g_free (key); } static int delete_account_repos_from_db (SeafRepoManager *mgr, const char *server, const char *username) { char *sql; int ret; pthread_mutex_lock (&mgr->priv->db_lock); sql = sqlite3_mprintf ("DELETE FROM AccountRepos WHERE server='%q' AND username='%q'", server, username); ret = sqlite_query_exec (mgr->priv->db, sql); pthread_mutex_unlock (&mgr->priv->db_lock); sqlite3_free (sql); return ret; } static gboolean collect_account_repo_cb (sqlite3_stmt *stmt, void *data) { GList **prepos = data; const char *repo_id; repo_id = (const char *)sqlite3_column_text (stmt, 0); *prepos = g_list_prepend (*prepos, g_strdup(repo_id)); return TRUE; } static int delete_account_repos (SeafRepoManager *mgr, const char *server, const char *username, gboolean remove_cache) { char *sql; GList *repos = NULL, *ptr; char *repo_id; SeafRepo *repo; int ret = 0; pthread_mutex_lock (&mgr->priv->db_lock); sql = sqlite3_mprintf ("SELECT repo_id FROM AccountRepos WHERE " "server='%q' AND username='%q'", server, username); if (sqlite_foreach_selected_row (mgr->priv->db, sql, collect_account_repo_cb, &repos) < 0) { ret = -1; goto out; } pthread_mutex_unlock (&mgr->priv->db_lock); for (ptr = repos; ptr; ptr = ptr->next) { repo_id = ptr->data; repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id); if (repo) { seaf_repo_manager_mark_repo_deleted (seaf->repo_mgr, repo, remove_cache); http_tx_manager_cancel_task (seaf->http_tx_mgr, repo->id, HTTP_TASK_TYPE_DOWNLOAD); http_tx_manager_cancel_task (seaf->http_tx_mgr, repo->id, HTTP_TASK_TYPE_UPLOAD); } seaf_repo_unref (repo); } g_list_free_full (repos, g_free); delete_account_repos_from_db (mgr, server, username); out: sqlite3_free (sql); return ret; } int seaf_repo_manager_delete_account (SeafRepoManager *mgr, const char *server, const char *username, gboolean remove_cache, GError **error) { SeafAccount *account = NULL; GHashTable *name_to_repo = NULL; GHashTableIter iter; gpointer key, value; RepoInfo *info; int ret = 0; char *account_key = g_strconcat (server, "_", username, NULL); pthread_rwlock_wrlock (&mgr->priv->account_lock); account = g_hash_table_lookup (mgr->priv->accounts, account_key); if (!account) { pthread_rwlock_unlock (&mgr->priv->account_lock); g_free (account_key); return 0; } name_to_repo = g_hash_table_lookup (mgr->priv->repo_names, account_key); g_hash_table_iter_init (&iter, name_to_repo); while (g_hash_table_iter_next (&iter, &key, &value)) { info = value; g_hash_table_remove (mgr->priv->repo_infos, info->id); } g_hash_table_remove (mgr->priv->repo_names, account_key); g_hash_table_remove (mgr->priv->accounts, account_key); pthread_rwlock_unlock (&mgr->priv->account_lock); if (delete_account_repos (mgr, server, username, remove_cache) < 0) { ret = -1; } g_free (account_key); return ret; } GList * seaf_repo_manager_get_account_repos (SeafRepoManager *mgr, const char *server, const char *user) { GList *ret = NULL; GHashTable *name_to_repo = NULL; GHashTableIter iter; gpointer key, value; RepoInfo *info, *copy; char *account_key = NULL; account_key = g_strconcat (server, "_", user, NULL); pthread_rwlock_rdlock (&mgr->priv->account_lock); name_to_repo = g_hash_table_lookup (mgr->priv->repo_names, account_key); if (!name_to_repo) { pthread_rwlock_unlock (&mgr->priv->account_lock); g_free (account_key); return NULL; } g_hash_table_iter_init (&iter, name_to_repo); while (g_hash_table_iter_next (&iter, &key, &value)) { info = value; copy = repo_info_copy (info); ret = g_list_prepend (ret, copy); } ret = g_list_reverse (ret); pthread_rwlock_unlock (&mgr->priv->account_lock); return ret; } GList * seaf_repo_manager_get_account_repo_ids (SeafRepoManager *mgr, const char *server, const char *user) { GList *ret = NULL; GHashTable *name_to_repo = NULL; GHashTableIter iter; gpointer key, value; RepoInfo *info; char *account_key = NULL; account_key = g_strconcat (server, "_", user, NULL); pthread_rwlock_rdlock (&mgr->priv->account_lock); name_to_repo = g_hash_table_lookup (mgr->priv->repo_names, account_key); if (!name_to_repo) { pthread_rwlock_unlock (&mgr->priv->account_lock); g_free (account_key); return NULL; } g_hash_table_iter_init (&iter, name_to_repo); while (g_hash_table_iter_next (&iter, &key, &value)) { info = value; ret = g_list_prepend (ret, g_strdup (info->id)); } pthread_rwlock_unlock (&mgr->priv->account_lock); return ret; } RepoInfo * seaf_repo_manager_get_repo_info_by_display_name (SeafRepoManager *mgr, const char *server, const char *user, const char *name) { GHashTable *name_to_repo = NULL; RepoInfo *info, *ret = NULL; char *account_key = NULL; if (!seaf_repo_manager_account_exists (mgr, server, user)) { return NULL; } account_key = g_strconcat (server, "_", user, NULL); pthread_rwlock_rdlock (&mgr->priv->account_lock); name_to_repo = g_hash_table_lookup (mgr->priv->repo_names, account_key); info = g_hash_table_lookup (name_to_repo, name); if (info) ret = repo_info_copy (info); pthread_rwlock_unlock (&mgr->priv->account_lock); g_free (account_key); return ret; } char * seaf_repo_manager_get_repo_id_by_display_name (SeafRepoManager *mgr, const char *server, const char *user, const char *name) { GHashTable *name_to_repo = NULL; RepoInfo *info; char *repo_id = NULL; char *account_key = NULL; if (!seaf_repo_manager_account_exists (mgr, server, user)) { return NULL; } account_key = g_strconcat (server, "_", user, NULL); pthread_rwlock_rdlock (&mgr->priv->account_lock); name_to_repo = g_hash_table_lookup (mgr->priv->repo_names, account_key); info = g_hash_table_lookup (name_to_repo, name); if (info) repo_id = g_strdup (info->id); pthread_rwlock_unlock (&mgr->priv->account_lock); g_free (account_key); return repo_id; } char * seaf_repo_manager_get_repo_display_name (SeafRepoManager *mgr, const char *id) { RepoInfo *info; char *display_name = NULL; pthread_rwlock_rdlock (&mgr->priv->account_lock); info = g_hash_table_lookup (mgr->priv->repo_infos, id); if (info) display_name = g_strdup(info->display_name); pthread_rwlock_unlock (&mgr->priv->account_lock); return display_name; } static char * find_unique_display_name (GHashTable *name_to_repo, RepoType type, const char *name) { char *base_display_name, *display_name; int n = 0; const char **string_table = get_repo_type_string_table (); if (type <= REPO_TYPE_UNKNOWN || type >= N_REPO_TYPE) { base_display_name = g_strdup(name); } else { base_display_name = g_build_path ("/", string_table[type], name, NULL); } display_name = g_strdup(base_display_name); while (g_hash_table_lookup (name_to_repo, display_name) != NULL) { ++n; g_free (display_name); display_name = g_strdup_printf ("%s (%d)", base_display_name, n); } g_free (base_display_name); return display_name; } static int remove_repo_from_account (sqlite3 *db, const char *server, const char *username, const char *repo_id) { char *sql; int ret = 0; sql = sqlite3_mprintf ("DELETE FROM AccountRepos WHERE " "server='%q' AND username='%q' AND repo_id='%q'", server, username, repo_id); ret = sqlite_query_exec (db, sql); sqlite3_free (sql); return ret; } static int add_repo_to_account (sqlite3 *db, const char *server, const char *username, RepoInfo *info) { char *sql; int ret = 0; sql = sqlite3_mprintf ("INSERT INTO AccountRepos VALUES " "('%q', '%q', '%q', '%q', '%q', %lld)", server, username, info->id, info->name, info->display_name, info->mtime); ret = sqlite_query_exec (db, sql); sqlite3_free (sql); return ret; } static int update_repo_to_account (sqlite3 *db, const char *server, const char *username, RepoInfo *info) { char *sql; int ret = 0; sql = sqlite3_mprintf ("UPDATE AccountRepos SET name = '%q', display_name = '%q', " "mtime = %lld" " WHERE server = '%q' AND username = '%q' AND repo_id = '%q'", info->name, info->display_name, info->mtime, server, username, info->id); ret = sqlite_query_exec (db, sql); sqlite3_free (sql); return ret; } void seaf_repo_manager_remove_account_repo (SeafRepoManager *mgr, const char *repo_id, const char *server, const char *user) { GHashTable *repo_infos = NULL, *name_to_repo = NULL; char *account_key = NULL; RepoInfo *info = NULL, *copy = NULL; SeafRepo *repo = NULL; if (!seaf_repo_manager_account_exists (mgr, server, user)) { return; } account_key = g_strconcat (server, "_", user, NULL); pthread_rwlock_wrlock (&mgr->priv->account_lock); repo_infos = mgr->priv->repo_infos; name_to_repo = g_hash_table_lookup (mgr->priv->repo_names, account_key); info = g_hash_table_lookup (repo_infos, repo_id); if (info) { copy = repo_info_copy (info); g_hash_table_remove (repo_infos, repo_id); g_hash_table_remove (name_to_repo, copy->display_name); } pthread_rwlock_unlock (&mgr->priv->account_lock); pthread_mutex_lock (&mgr->priv->db_lock); remove_repo_from_account (mgr->priv->db, server, user, repo_id); pthread_mutex_unlock (&mgr->priv->db_lock); repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id); if (repo) { seaf_message ("Repo %s(%.8s) was deleted on server. Remove local repo.\n", repo->name, repo->id); seaf_repo_manager_mark_repo_deleted (seaf->repo_mgr, repo, TRUE); http_tx_manager_cancel_task (seaf->http_tx_mgr, repo_id, HTTP_TASK_TYPE_DOWNLOAD); http_tx_manager_cancel_task (seaf->http_tx_mgr, repo_id, HTTP_TASK_TYPE_UPLOAD); seaf_repo_unref (repo); } g_free (account_key); repo_info_free (copy); return; } int seaf_repo_manager_update_account_repos (SeafRepoManager *mgr, const char *server, const char *username, GHashTable *server_repos, GList **added, GList **removed) { GHashTable *repo_infos, *name_to_repo; GList *account_repos = NULL; GHashTableIter iter; gpointer key, value; char *repo_id; RepoInfo *info, *copy, *server_info; GList *ptr; char *display_name; char *account_key = NULL; SeafRepo *repo; gboolean need_update; GList *updated = NULL; if (!seaf_repo_manager_account_exists (mgr, server, username)) { return 0; } account_key = g_strconcat (server, "_", username, NULL); pthread_rwlock_wrlock (&mgr->priv->account_lock); repo_infos = mgr->priv->repo_infos; name_to_repo = g_hash_table_lookup (mgr->priv->repo_names, account_key); g_hash_table_iter_init (&iter, name_to_repo); while (g_hash_table_iter_next (&iter, &key, &value)) { info = value; account_repos = g_list_prepend (account_repos, info); } for (ptr = account_repos; ptr; ptr = ptr->next) { info = ptr->data; repo_id = info->id; server_info = g_hash_table_lookup (server_repos, repo_id); if (!server_info) { /* Remove local repos that don't exist in server_repos. */ copy = repo_info_copy (info); *removed = g_list_prepend (*removed, copy); g_hash_table_remove (repo_infos, repo_id); g_hash_table_remove (name_to_repo, copy->display_name); } else { /* Update existing local repos. */ if (g_strcmp0 (info->head_commit_id, server_info->head_commit_id) != 0) { g_free (info->head_commit_id); info->head_commit_id = g_strdup(server_info->head_commit_id); } if (info->is_readonly != server_info->is_readonly) { info->is_readonly = server_info->is_readonly; repo = seaf_repo_manager_get_repo (seaf->repo_mgr, info->id); if (repo) { if (info->is_readonly) seaf_repo_set_readonly (repo); else seaf_repo_unset_readonly (repo); seaf_repo_unref (repo); } } need_update = FALSE; if (info->type != server_info->type || g_strcmp0 (info->name, server_info->name) != 0) { repo = seaf_repo_manager_get_repo (seaf->repo_mgr, info->id); if (repo && !repo->fs_ready) { seaf_repo_unref (repo); continue; } g_hash_table_remove (name_to_repo, info->name); g_free (info->name); info->name = g_strdup(server_info->name); display_name = find_unique_display_name (name_to_repo, server_info->type, server_info->name); info->display_name = display_name; info->type = server_info->type; g_hash_table_insert (name_to_repo, g_strdup(display_name), info); if (repo) { char *worktree = g_strdup (repo->worktree); seaf_repo_set_worktree (repo, display_name); g_free (worktree); seaf_repo_unref (repo); } need_update = TRUE; } if (info->mtime != server_info->mtime) { info->mtime = server_info->mtime; need_update = TRUE; } if (need_update) updated = g_list_append (updated, repo_info_copy (info)); if (server_info->is_corrupted) info->is_corrupted = TRUE; } } /* Add new repos from server_repos to local cache. */ g_hash_table_iter_init (&iter, server_repos); while (g_hash_table_iter_next (&iter, &key, &value)) { repo_id = key; if (!g_hash_table_lookup (repo_infos, repo_id)) { info = value; copy = repo_info_copy (info); *added = g_list_prepend (*added, copy); } } for (ptr = *added; ptr; ptr = ptr->next) { info = ptr->data; display_name = find_unique_display_name (name_to_repo, info->type, info->name); info->display_name = display_name; copy = repo_info_copy (info); g_hash_table_insert (repo_infos, g_strdup(info->id), copy); g_hash_table_insert (name_to_repo, g_strdup(display_name), copy); } pthread_rwlock_unlock (&mgr->priv->account_lock); pthread_mutex_lock (&mgr->priv->db_lock); for (ptr = *removed; ptr; ptr = ptr->next) { info = ptr->data; remove_repo_from_account (mgr->priv->db, server, username, info->id); } for (ptr = *added; ptr; ptr = ptr->next) { info = ptr->data; add_repo_to_account (mgr->priv->db, server, username, info); } for (ptr = updated; ptr; ptr = ptr->next) { info = ptr->data; update_repo_to_account (mgr->priv->db, server, username, info); } pthread_mutex_unlock (&mgr->priv->db_lock); g_free (account_key); g_list_free (account_repos); g_list_free_full (updated, (GDestroyNotify)repo_info_free); return 0; } int seaf_repo_manager_add_repo_to_account (SeafRepoManager *mgr, const char *server, const char *username, SeafRepo *repo) { int ret = 0; RepoInfo *info = NULL; GHashTable *name_to_repo = NULL; char *account_key = NULL; if (!seaf_repo_manager_account_exists (mgr, server, username)) { return -1; } account_key = g_strconcat (server, "_", username, NULL); pthread_rwlock_wrlock (&mgr->priv->account_lock); if (g_hash_table_lookup (mgr->priv->repo_infos, repo->id) != NULL) { pthread_rwlock_unlock (&mgr->priv->account_lock); goto out; } name_to_repo = g_hash_table_lookup (mgr->priv->repo_names, account_key); info = repo_info_new (repo->id, repo->head->commit_id, repo->name, repo->last_modify, FALSE); info->type = REPO_TYPE_MINE; info->display_name = find_unique_display_name (name_to_repo, REPO_TYPE_MINE, repo->name); g_hash_table_replace (mgr->priv->repo_infos, g_strdup (info->id), info); g_hash_table_replace (name_to_repo, g_strdup (info->display_name), info); pthread_rwlock_unlock (&mgr->priv->account_lock); pthread_mutex_lock (&mgr->priv->db_lock); ret = add_repo_to_account (mgr->priv->db, server, username, info); if (ret < 0) { pthread_mutex_unlock (&mgr->priv->db_lock); g_free (account_key); repo_info_free (info); return -1; } pthread_mutex_unlock (&mgr->priv->db_lock); out: g_free (account_key); return ret; } int seaf_repo_manager_rename_repo_on_account (SeafRepoManager *mgr, const char *server, const char *username, const char *repo_id, const char *new_name) { GHashTable *name_to_repo = NULL; RepoInfo *info; SeafRepo *repo; char *orig_repo_name; char *orig_display_name; char *display_name; char *account_key = NULL; int ret = 0; if (!seaf_repo_manager_account_exists (mgr, server, username)) { ret = -1; goto out; } account_key = g_strconcat (server, "_", username, NULL); display_name = seaf_repo_manager_get_repo_display_name (mgr, repo_id); pthread_rwlock_wrlock (&mgr->priv->account_lock); name_to_repo = g_hash_table_lookup (mgr->priv->repo_names, account_key); info = g_hash_table_lookup (name_to_repo, display_name); g_free (display_name); if (!info) { ret = -1; goto out; } g_hash_table_remove (name_to_repo, info->display_name); orig_repo_name = info->name; orig_display_name = info->display_name; info->name = g_strdup (new_name); info->display_name = find_unique_display_name (name_to_repo, info->type, new_name); g_hash_table_replace (name_to_repo, g_strdup (info->display_name), info); pthread_rwlock_unlock (&mgr->priv->account_lock); pthread_mutex_lock (&mgr->priv->db_lock); ret = update_repo_to_account (mgr->priv->db, server, username, info); if (ret < 0) { pthread_mutex_unlock (&mgr->priv->db_lock); g_free (info->name); g_free (info->display_name); info->name = orig_repo_name; info->display_name = orig_display_name; g_hash_table_replace (name_to_repo, g_strdup(info->display_name), info); goto out; } pthread_mutex_unlock (&mgr->priv->db_lock); repo = seaf_repo_manager_get_repo (mgr, repo_id); if (repo) seaf_repo_set_worktree (repo, info->display_name); seaf_repo_unref (repo); g_free (orig_repo_name); g_free (orig_display_name); out: g_free (account_key); return ret; } void seaf_repo_manager_set_repo_info_head_commit (SeafRepoManager *mgr, const char *repo_id, const char *commit_id) { RepoInfo *info; pthread_rwlock_wrlock (&mgr->priv->account_lock); info = g_hash_table_lookup (mgr->priv->repo_infos, repo_id); if (!info) goto out; g_free (info->head_commit_id); info->head_commit_id = g_strdup(commit_id); out: pthread_rwlock_unlock (&mgr->priv->account_lock); } #define JOURNAL_FLUSH_TIMEOUT 5 void seaf_repo_manager_flush_account_repo_journals (SeafRepoManager *mgr, const char *server, const char *user) { GList *repo_ids, *ptr; char *repo_id; SeafRepo *repo; repo_ids = seaf_repo_manager_get_account_repo_ids (seaf->repo_mgr, server, user); for (ptr = repo_ids; ptr; ptr = ptr->next) { repo_id = ptr->data; repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id); if (repo) { if (repo->journal) journal_flush (repo->journal, JOURNAL_FLUSH_TIMEOUT); seaf_repo_unref (repo); } } g_list_free_full (repo_ids, g_free); } static gboolean get_account_space_cb (sqlite3_stmt *stmt, void *data) { SeafAccountSpace *space = data; gint64 total, used; total = (gint64)sqlite3_column_int64 (stmt, 0); used = (gint64)sqlite3_column_int64 (stmt, 1); space->total = total; space->used = used; return FALSE; } #define DEFAULT_SPACE_USED (1000000000LL) /* 1GB */ #define DEFAULT_SPACE_TOTAL (100000000000000LL) /* 100TB */ #define MINIMAL_SPACE_TOTAL (10000000LL) /* 10MB */ #define UNLIMITED_SPACE (-2LL) SeafAccountSpace * seaf_repo_manager_get_account_space (SeafRepoManager *mgr, const char *server, const char *username) { char *sql; SeafAccountSpace *space = g_new0 (SeafAccountSpace, 1); sql = sqlite3_mprintf ("SELECT total, used FROM AccountSpace WHERE " "server='%q' AND username='%q'", server, username); pthread_mutex_lock (&mgr->priv->db_lock); if (sqlite_foreach_selected_row (mgr->priv->db, sql, get_account_space_cb, space) <= 0) { space->total = DEFAULT_SPACE_TOTAL; space->used = DEFAULT_SPACE_USED; } pthread_mutex_unlock (&mgr->priv->db_lock); if (space->total == UNLIMITED_SPACE) space->total = (DEFAULT_SPACE_TOTAL > space->used ? DEFAULT_SPACE_TOTAL : (10*space->used)); else if (space->total == 0) /* If the user's quota is set to 0 on server, set it to a default quota so that * the user can still write to shared libraries. Otherwise the OS may prevent * writing data to the virtual drive. */ space->total = DEFAULT_SPACE_TOTAL; sqlite3_free (sql); return space; } int seaf_repo_manager_set_account_space (SeafRepoManager *mgr, const char *server, const char *username, gint64 total, gint64 used) { char *sql; int ret = 0; sql = sqlite3_mprintf ("REPLACE INTO AccountSpace (server, username, total, used) " "VALUES ('%q', '%q', %lld, %lld)", server, username, total, used); pthread_mutex_lock (&mgr->priv->db_lock); ret = sqlite_query_exec (mgr->priv->db, sql); pthread_mutex_unlock (&mgr->priv->db_lock); sqlite3_free (sql); return ret; } int seaf_repo_manager_check_delete_repo (const char *repo_id, RepoType repo_type) { SeafRepo *repo = NULL; int ret = 0; if (repo_type != REPO_TYPE_MINE) { seaf_warning ("rm repo: repo %s is not belong to the user.\n", repo_id); ret = -EACCES; goto out; } repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id); if (!repo || !repo->fs_ready) { ret = -ENOENT; goto out; } if (!repo_tree_is_empty_repo(repo->tree)) { ret = -ENOTEMPTY; } out: seaf_repo_unref (repo); return ret; } char * seaf_repo_manager_get_display_name_by_repo_name (SeafRepoManager *mgr, const char *server, const char *user, const char *repo_name) { GHashTable *name_to_repo = NULL; char *account_key = NULL; if (!seaf_repo_manager_account_exists (mgr, server, user)) { return NULL; } account_key = g_strconcat (server, "_", user, NULL); pthread_rwlock_rdlock (&mgr->priv->account_lock); name_to_repo = g_hash_table_lookup (mgr->priv->repo_names, account_key); pthread_rwlock_unlock (&mgr->priv->account_lock); g_free (account_key); return find_unique_display_name (name_to_repo, REPO_TYPE_MINE, repo_name); } static int count_of_repo_id_in_account_repos (SeafRepoManager *mgr, const char *repo_id) { char sql[256]; gint64 ret; sqlite3_snprintf (sizeof(sql), sql, "SELECT count(*) AS count FROM AccountRepos WHERE repo_id = '%q'", repo_id); pthread_mutex_lock (&mgr->priv->db_lock); ret = sqlite_get_int64 (mgr->priv->db, sql); pthread_mutex_unlock (&mgr->priv->db_lock); return ret; } char * seaf_repo_manager_get_first_repo_token_from_account_repos (SeafRepoManager *mgr, const char *server, const char *username, char **repo_token) { GHashTable *name_to_repo; GHashTableIter iter; gpointer key, value; RepoInfo *info = NULL; char *repo_id = NULL; char *account_key = NULL; SeafRepo *repo; account_key = g_strconcat (server, "_", username, NULL); name_to_repo = g_hash_table_lookup (mgr->priv->repo_names, account_key); if (!name_to_repo) { goto out; } g_hash_table_iter_init (&iter, name_to_repo); while (g_hash_table_iter_next (&iter, &key, &value)) { info = value; repo = seaf_repo_manager_get_repo (mgr, info->id); if (!repo || repo->delete_pending || !repo->token) { seaf_repo_unref (repo); continue; } //if the repo appears twice in AccountRepos, it may be shared //to different users, skip it. if (count_of_repo_id_in_account_repos (mgr, repo->id) >= 2) { seaf_repo_unref (repo); continue; } repo_id = g_strdup (repo->id); *repo_token = g_strdup (repo->token); seaf_repo_unref (repo); break; } out: g_free (account_key); return repo_id; } int seaf_repo_manager_set_repo_passwd (SeafRepoManager *manager, SeafRepo *repo, const char *passwd) { int ret; if (seafile_decrypt_repo_enc_key (repo->enc_version, passwd, repo->random_key, repo->salt, repo->enc_key, repo->enc_iv) < 0) return -1; pthread_mutex_lock (&manager->priv->db_lock); ret = save_repo_enc_info (manager, repo); pthread_mutex_unlock (&manager->priv->db_lock); if (ret != 0) return ret; repo->is_passwd_set = TRUE; return ret; } int seaf_repo_manager_clear_enc_repo_passwd (const char *repo_id) { SeafRepo *repo; sqlite3 *db = seaf->repo_mgr->priv->db; char sql[512]; sqlite3_snprintf(sizeof(sql), sql, "DELETE FROM RepoKeys WHERE repo_id = %Q", repo_id); pthread_mutex_lock (&seaf->repo_mgr->priv->db_lock); if (sqlite_query_exec (db, sql) < 0) { pthread_mutex_unlock (&seaf->repo_mgr->priv->db_lock); return -1; } pthread_mutex_unlock (&seaf->repo_mgr->priv->db_lock); repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id); if (repo) { repo->is_passwd_set = FALSE; memset (repo->enc_key, 0, 32); memset (repo->enc_iv, 0, 16); } seaf_repo_unref (repo); return 0; } json_t * seaf_repo_manager_get_account_by_repo_id (SeafRepoManager *mgr, const char *repo_id) { json_t *object = NULL; SeafRepo *repo = seaf_repo_manager_get_repo (mgr, repo_id); if (!repo) { return NULL; } object = json_object(); json_object_set (object, "server", json_string(repo->server)); json_object_set (object, "username", json_string(repo->user)); seaf_repo_unref (repo); return object; } seadrive-fuse-3.0.13/src/repo-mgr.h000066400000000000000000000404051476177674700170650ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ #ifndef SEAF_REPO_MGR_H #define SEAF_REPO_MGR_H #include "common.h" #include #include #include "commit-mgr.h" #include "branch-mgr.h" #include "repo-tree.h" #include "journal-mgr.h" #define REPO_PROP_TOKEN "token" #define REPO_PROP_IS_READONLY "is-readonly" struct _SeafRepoManager; typedef struct _SeafRepo SeafRepo; /* The caller can use the properties directly. But the caller should * always write on repos via the API. */ struct _SeafRepo { struct _SeafRepoManager *manager; gchar id[37]; gchar *name; gchar *desc; gchar *category; /* not used yet */ gboolean encrypted; gboolean is_passwd_set; int enc_version; gchar magic[65]; /* hash(repo_id + passwd), key stretched. */ gchar pwd_hash[65]; /* hash(repo_id + passwd), key stretched. */ gchar *pwd_hash_algo; gchar *pwd_hash_params; gchar random_key[97]; /* key length is 48 after encryption */ gchar salt[65]; gint64 last_modify; SeafBranch *head; gchar root_id[41]; gboolean is_corrupted; gboolean delete_pending; gboolean remove_cache; int last_sync_time; /* Last time check locked files. */ int last_check_locked_time; gboolean checking_locked_files; unsigned char enc_key[32]; /* 256-bit encryption key */ unsigned char enc_iv[16]; gchar *token; /* token for access this repo on server */ gchar *jwt_token; gint64 last_check_jwt_token; gboolean is_readonly; // repo_uname is the same as display_name. char *repo_uname; char *worktree; char *server; char *user; // use to connect notification server. char *fileserver_addr; unsigned int quota_full_notified : 1; unsigned int access_denied_notified : 1; int version; gint refcnt; /* Is this repo ready for file system operations. */ gboolean fs_ready; /* In-memory representation of the repo directory hierarchy. */ struct _RepoTree *tree; /* Journal for this repo */ Journal *journal; /* Marks the repo into "partial-commit" mode. Once in this mode, * we'll create commits for about every 100MB data. Commits are created * and uploaded until all operations in the journal are processd. */ gboolean partial_commit_mode; gboolean force_sync_pending; }; gboolean is_repo_id_valid (const char *id); SeafRepo* seaf_repo_new (const char *id, const char *name, const char *desc); void seaf_repo_free (SeafRepo *repo); void seaf_repo_ref (SeafRepo *repo); void seaf_repo_unref (SeafRepo *repo); int seaf_repo_set_head (SeafRepo *repo, SeafBranch *branch); SeafCommit * seaf_repo_get_head_commit (const char *repo_id); void seaf_repo_set_readonly (SeafRepo *repo); void seaf_repo_unset_readonly (SeafRepo *repo); void seaf_repo_set_worktree (SeafRepo *repo, const char *repo_uname); /* Update repo name, desc, magic etc from commit. */ void seaf_repo_from_commit (SeafRepo *repo, SeafCommit *commit); /* Update repo-related fields to commit. */ void seaf_repo_to_commit (SeafRepo *repo, SeafCommit *commit); int seaf_repo_load_fs (SeafRepo *repo, gboolean after_clone); GList * seaf_repo_diff (SeafRepo *repo, const char *old, const char *new, int fold_dir_diff, char **error); typedef struct _SeafRepoManager SeafRepoManager; typedef struct _SeafRepoManagerPriv SeafRepoManagerPriv; struct _SeafRepoManager { struct _SeafileSession *seaf; SeafRepoManagerPriv *priv; }; SeafRepoManager* seaf_repo_manager_new (struct _SeafileSession *seaf); int seaf_repo_manager_init (SeafRepoManager *mgr); int seaf_repo_manager_start (SeafRepoManager *mgr); int seaf_repo_manager_add_repo (SeafRepoManager *mgr, SeafRepo *repo); int seaf_repo_manager_mark_repo_deleted (SeafRepoManager *mgr, SeafRepo *repo, gboolean remove_cache); int seaf_repo_manager_del_repo (SeafRepoManager *mgr, SeafRepo *repo); void seaf_repo_manager_move_repo_store (SeafRepoManager *mgr, const char *type, const char *repo_id); void seaf_repo_manager_remove_repo_ondisk (SeafRepoManager *mgr, const char *repo_id, gboolean remove_cache); SeafRepo* seaf_repo_manager_get_repo (SeafRepoManager *manager, const gchar *id); gboolean seaf_repo_manager_repo_exists (SeafRepoManager *manager, const gchar *id); GList* seaf_repo_manager_get_repo_list (SeafRepoManager *mgr, int start, int limit); GList * seaf_repo_manager_get_enc_repo_list (SeafRepoManager *mgr, int start, int limit); gboolean seaf_repo_manager_is_repo_delete_pending (SeafRepoManager *manager, const char *id); int seaf_repo_manager_set_repo_token (SeafRepoManager *manager, SeafRepo *repo, const char *token); int seaf_repo_manager_remove_repo_token (SeafRepoManager *manager, SeafRepo *repo); void seaf_repo_manager_rename_repo (SeafRepoManager *manager, const char *repo_id, const char *new_name); int seaf_repo_manager_branch_repo_unmap (SeafRepoManager *manager, SeafBranch *branch); int seaf_repo_manager_set_repo_property (SeafRepoManager *manager, const char *repo_id, const char *key, const char *value); char * seaf_repo_manager_get_repo_property (SeafRepoManager *manager, const char *repo_id, const char *key); /* Locked files. */ #define LOCKED_OP_UPDATE "update" #define LOCKED_OP_DELETE "delete" typedef struct _LockedFile { char *operation; gint64 old_mtime; char file_id[41]; } LockedFile; typedef struct _LockedFileSet { SeafRepoManager *mgr; char repo_id[37]; GHashTable *locked_files; } LockedFileSet; LockedFileSet * seaf_repo_manager_get_locked_file_set (SeafRepoManager *mgr, const char *repo_id); void locked_file_set_free (LockedFileSet *fset); int locked_file_set_add_update (LockedFileSet *fset, const char *path, const char *operation, gint64 old_mtime, const char *file_id); int locked_file_set_remove (LockedFileSet *fset, const char *path, gboolean db_only); LockedFile * locked_file_set_lookup (LockedFileSet *fset, const char *path); /* Folder Permissions. */ typedef enum FolderPermType { FOLDER_PERM_TYPE_USER = 0, FOLDER_PERM_TYPE_GROUP, } FolderPermType; typedef struct _FolderPerm { char *path; char *permission; } FolderPerm; void folder_perm_free (FolderPerm *perm); int seaf_repo_manager_delete_folder_perm (SeafRepoManager *mgr, const char *repo_id, FolderPermType type, FolderPerm *perm); int seaf_repo_manager_update_folder_perm (SeafRepoManager *mgr, const char *repo_id, FolderPermType type, FolderPerm *perm); int seaf_repo_manager_update_folder_perms (SeafRepoManager *mgr, const char *repo_id, FolderPermType type, GList *folder_perms); int seaf_repo_manager_update_folder_perm_timestamp (SeafRepoManager *mgr, const char *repo_id, gint64 timestamp); gint64 seaf_repo_manager_get_folder_perm_timestamp (SeafRepoManager *mgr, const char *repo_id); gboolean seaf_repo_manager_is_path_writable (SeafRepoManager *mgr, const char *repo_id, const char *path); gboolean seaf_repo_manager_is_path_invisible(SeafRepoManager *mgr, const char *repo_id, const char *path); gboolean seaf_repo_manager_include_invisible_perm (SeafRepoManager *mgr, const char *repo_id); /* Account repo list cache management. */ struct _SeafAccount { char *server; char *username; char *nickname; char *token; char *name; char *fileserver_addr; gboolean is_pro; char *unique_id; /* server + username */ gboolean repo_list_fetched; gboolean all_repos_loaded; gboolean server_disconnected; }; typedef struct _SeafAccount SeafAccount; void seaf_account_free (SeafAccount *account); typedef struct AccountInfo { char *server; char *username; } AccountInfo; void account_info_free (AccountInfo *info); AccountInfo * seaf_repo_manager_get_account_info_by_name (SeafRepoManager *mgr, const char *name); int seaf_repo_manager_add_account (SeafRepoManager *mgr, const char *server, const char *username, const char *nickname, const char *token, const char *name, gboolean is_pro); SeafAccount * seaf_repo_manager_get_account (SeafRepoManager *mgr, const char *server, const char *username); gboolean seaf_repo_manager_account_exists (SeafRepoManager *mgr, const char *server, const char *username); GList * seaf_repo_manager_get_account_list (SeafRepoManager *mgr); gboolean seaf_repo_manager_account_is_pro (SeafRepoManager *mgr, const char *server, const char *user); int seaf_repo_manager_delete_account (SeafRepoManager *mgr, const char *server, const char *username, gboolean remoce_cache, GError **error); typedef enum _RepoType { REPO_TYPE_UNKNOWN = 0, REPO_TYPE_MINE, REPO_TYPE_SHARED, REPO_TYPE_GROUP, REPO_TYPE_PUBLIC, N_REPO_TYPE, } RepoType; struct _RepoInfo { char id[37]; char *head_commit_id; /* in-memory only. */ gboolean is_corrupted; /* in-memory only. */ gboolean is_readonly; char *name; /* Unique name to used in file system. */ char *display_name; RepoType type; gint64 mtime; }; typedef struct _RepoInfo RepoInfo; RepoInfo * repo_info_new (const char *id, const char *head_commit_id, const char *name, gint64 mtime, gboolean is_readonly); RepoInfo * repo_info_copy (RepoInfo *info); void repo_info_free (RepoInfo *info); RepoType repo_type_from_string (const char *type_str); GList * repo_type_string_list (); GList * seaf_repo_manager_get_account_repos (SeafRepoManager *mgr, const char *server, const char *user); GList * seaf_repo_manager_get_account_repo_ids (SeafRepoManager *mgr, const char *server, const char *user); RepoInfo * seaf_repo_manager_get_repo_info_by_display_name (SeafRepoManager *mgr, const char *server, const char *user, const char *name); char * seaf_repo_manager_get_repo_id_by_display_name (SeafRepoManager *mgr, const char *server, const char *user, const char *name); char * seaf_repo_manager_get_repo_display_name (SeafRepoManager *mgr, const char *id); int seaf_repo_manager_update_account_repos (SeafRepoManager *mgr, const char *server, const char *username, GHashTable *server_repos, GList **added, GList **removed); void seaf_repo_manager_remove_account_repo (SeafRepoManager *mgr, const char *repo_id, const char *server, const char *user); void seaf_repo_manager_set_repo_info_head_commit (SeafRepoManager *mgr, const char *repo_id, const char *commit_id); int seaf_repo_manager_add_repo_to_account (SeafRepoManager *mgr, const char *server, const char *username, SeafRepo *repo); int seaf_repo_manager_rename_repo_on_account (SeafRepoManager *mgr, const char *server, const char *username, const char *repo_id, const char *new_name); void seaf_repo_manager_set_repo_list_fetched (SeafRepoManager *mgr, const char *server, const char *user); void seaf_repo_manager_set_account_all_repos_loaded (SeafRepoManager *mgr, const char *server, const char *username); void seaf_repo_manager_flush_account_repo_journals (SeafRepoManager *mgr, const char *server, const char *username); void seaf_repo_manager_set_account_server_disconnected (SeafRepoManager *mgr, const char *server, const char *username, gboolean server_disconnected); struct _SeafAccountSpace { gint64 total; gint64 used; }; typedef struct _SeafAccountSpace SeafAccountSpace; SeafAccountSpace * seaf_repo_manager_get_account_space (SeafRepoManager *mgr, const char *server, const char *username); int seaf_repo_manager_set_account_space (SeafRepoManager *mgr, const char *server, const char *username, gint64 total, gint64 used); int seaf_repo_manager_check_delete_repo (const char *repo_id, RepoType repo_type); int seaf_repo_manager_create_placeholders (SeafRepo* repo); char * seaf_repo_manager_get_display_name_by_repo_name (SeafRepoManager *mgr, const char *server, const char *user, const char *repo_name); void seaf_repo_manager_create_dir_placeholder_recursive (const char *repo_id, const char *dir_id, const char *fullpath, const char *path); gboolean seaf_repo_manager_is_deleted_repo (SeafRepoManager *mgr, const char *display_name); void seaf_repo_manager_del_deleted_repo (SeafRepoManager *mgr, const char *display_name); int seaf_repo_manager_set_repo_passwd (SeafRepoManager *manager, SeafRepo *repo, const char *passwd); int seaf_repo_manager_clear_enc_repo_passwd (const char *repo_id); char * seaf_repo_manager_get_first_repo_token_from_account_repos (SeafRepoManager *mgr, const char *server, const char *username, char **repo_token); json_t * seaf_repo_manager_get_account_by_repo_id (SeafRepoManager *mgr, const char *repo_id); #endif seadrive-fuse-3.0.13/src/repo-tree.c000066400000000000000000000621631476177674700172370ustar00rootroot00000000000000#include "common.h" #include #include #include "repo-tree.h" #include "log.h" #include "utils.h" #include "seafile-session.h" struct _RepoTreeDir; typedef struct _RepoTreeDir RepoTreeDir; struct _RepoTreeDirent; typedef struct _RepoTreeDirent RepoTreeDirent; struct _RepoTree { char repo_id[37]; RepoTreeDir *root; pthread_rwlock_t lock; }; struct _RepoTreeDir { char id[41]; GHashTable *dirents; }; struct _RepoTreeDirent { char id[41]; char *name; guint32 mode; gint64 mtime; gint64 size; RepoTreeDir *subdir; }; static RepoTreeDir * repo_tree_dir_new (const char *id); static void repo_tree_dir_free (RepoTreeDir *dir); static RepoTreeDirent * repo_tree_dirent_new (const char *id, const char *name, guint32 mode, gint64 mtime, gint64 size, RepoTreeDir *subdir) { RepoTreeDirent *dirent = g_new0 (RepoTreeDirent, 1); memcpy (dirent->id, id, 40); dirent->name = g_strdup(name); dirent->mode = mode; dirent->mtime = mtime; dirent->size = size; if (S_ISDIR(mode)) { if (!subdir) subdir = repo_tree_dir_new (EMPTY_SHA1); dirent->subdir = subdir; } return dirent; } static void repo_tree_dirent_free (RepoTreeDirent *dirent) { if (!dirent) return; g_free (dirent->name); repo_tree_dir_free (dirent->subdir); g_free (dirent); } static RepoTreeDir * repo_tree_dir_new (const char *id) { RepoTreeDir *dir = g_new0 (RepoTreeDir, 1); memcpy (dir->id, id, 40); dir->dirents = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify)repo_tree_dirent_free); return dir; } static void repo_tree_dir_free (RepoTreeDir *dir) { if (!dir) return; g_hash_table_destroy (dir->dirents); g_free (dir); } static gboolean repo_tree_dir_is_empty (RepoTreeDir *dir) { return (g_hash_table_size(dir->dirents) == 0); } RepoTree * repo_tree_new (const char *repo_id) { RepoTree *tree = NULL; tree = g_new0 (RepoTree, 1); memcpy (tree->repo_id, repo_id, 36); pthread_rwlock_init (&tree->lock, NULL); return tree; } void repo_tree_free (RepoTree *tree) { if (!tree) return; repo_tree_dir_free (tree->root); pthread_rwlock_destroy (&tree->lock); g_free (tree); } static RepoTreeDir * load_dir_recursive (const char *repo_id, const char *dir_id); int repo_tree_load_commit (RepoTree *tree, const char *commit_id) { SeafCommit *commit; RepoTreeDir *root; int ret = 0; commit = seaf_commit_manager_get_commit (seaf->commit_mgr, tree->repo_id, 1, commit_id); if (!commit) { seaf_warning ("Failed to get commit %s from repo %s.\n", commit_id, tree->repo_id); return -1; } root = load_dir_recursive (tree->repo_id, commit->root_id); if (!root) { ret = -1; goto out; } pthread_rwlock_wrlock (&tree->lock); repo_tree_dir_free (tree->root); tree->root = root; pthread_rwlock_unlock (&tree->lock); out: seaf_commit_unref (commit); return ret; } static RepoTreeDir * load_dir_recursive (const char *repo_id, const char *dir_id) { SeafDir *seafdir; GList *ptr; SeafDirent *seafdirent; RepoTreeDirent *dirent; RepoTreeDir *subdir; RepoTreeDir *dir = NULL; seafdir = seaf_fs_manager_get_seafdir (seaf->fs_mgr, repo_id, 1, dir_id); if (!seafdir) { seaf_warning ("Failed to get dir %s from repo %s.\n", dir_id, repo_id); return NULL; } dir = repo_tree_dir_new (dir_id); gboolean error = FALSE; for (ptr = seafdir->entries; ptr; ptr = ptr->next) { seafdirent = (SeafDirent *)ptr->data; if (seaf_sync_manager_ignored_on_checkout (seafdirent->name, NULL)) continue; if (S_ISDIR(seafdirent->mode)) { subdir = load_dir_recursive (repo_id, seafdirent->id); if (!subdir) { seaf_warning ("Failed to get dir %s from repo %s.\n", seafdirent->id, repo_id); error = TRUE; break; } } else { subdir = NULL; } dirent = repo_tree_dirent_new (seafdirent->id, seafdirent->name, seafdirent->mode, seafdirent->mtime, seafdirent->size, subdir); g_hash_table_insert (dir->dirents, dirent->name, dirent); } if (error) { repo_tree_dir_free (dir); dir = NULL; } seaf_dir_free (seafdir); return dir; } void repo_tree_clear (RepoTree *tree) { pthread_rwlock_wrlock (&tree->lock); repo_tree_dir_free (tree->root); tree->root = NULL; pthread_rwlock_unlock (&tree->lock); } gboolean repo_tree_is_loaded (RepoTree *tree) { gboolean ret = FALSE; pthread_rwlock_rdlock (&tree->lock); ret = (tree->root != NULL); pthread_rwlock_unlock (&tree->lock); return ret; } static RepoTreeDirent * resolve_path (RepoTree *tree, const char *path) { char **comps = NULL; char *comp; guint i, n; RepoTreeDir *dir; RepoTreeDirent *dirent = NULL; comps = g_strsplit (path, "/", -1); n = g_strv_length (comps); if (!comps || n == 0) { g_strfreev (comps); return NULL; } dir = tree->root; for (i = 0; i < n; ++i) { comp = comps[i]; dirent = g_hash_table_lookup (dir->dirents, comp); if (!dirent) break; if (S_ISDIR(dirent->mode)) { dir = dirent->subdir; } else { if (i < n - 1) { dirent = NULL; } break; } } g_strfreev (comps); return dirent; } int repo_tree_stat_path (RepoTree *tree, const char *path, RepoTreeStat *st) { if (path[0] == '\0') return -ENOENT; pthread_rwlock_rdlock (&tree->lock); if (!tree->root) { pthread_rwlock_unlock (&tree->lock); return -ENOENT; } RepoTreeDirent *dirent = resolve_path (tree, path); if (!dirent) { pthread_rwlock_unlock (&tree->lock); return -ENOENT; } memcpy (st->id, dirent->id, sizeof (st->id)); st->mode = dirent->mode; st->size = dirent->size; st->mtime = dirent->mtime; pthread_rwlock_unlock (&tree->lock); return 0; } int repo_tree_readdir (RepoTree *tree, const char *path, GHashTable *dirents) { RepoTreeDirent *dirent; RepoTreeDir *dir; GHashTableIter iter; gpointer key, value; RepoTreeStat *st; int ret = 0; pthread_rwlock_rdlock (&tree->lock); if (!tree->root) { ret = -ENOENT; goto out; } if (path[0] == '\0') { dir = tree->root; } else { dirent = resolve_path (tree, path); if (!dirent) { ret = -ENOENT; goto out; } if (!S_ISDIR(dirent->mode)) { ret = -ENOTDIR; goto out; } dir = dirent->subdir; } char *sub_path = NULL; g_hash_table_iter_init (&iter, dir->dirents); while (g_hash_table_iter_next (&iter, &key, &value)) { dirent = (RepoTreeDirent *)value; st = g_new0 (RepoTreeStat, 1); st->mode = dirent->mode; st->size = dirent->size; st->mtime = dirent->mtime; sub_path = g_build_path ("/", path, dirent->name, NULL); if (seaf_repo_manager_is_path_invisible (seaf->repo_mgr, tree->repo_id, sub_path)) { g_free (sub_path); continue; } g_free (sub_path); g_hash_table_insert (dirents, g_strdup(dirent->name), st); } out: pthread_rwlock_unlock (&tree->lock); return ret; } static int repo_tree_create_common (RepoTree *tree, const char *path, const char *id, guint32 mode, gint64 mtime, gint64 size) { char *parent = NULL, *dname = NULL; RepoTreeDirent *dirent; RepoTreeDir *dir, *empty_dir = NULL; int ret = 0; pthread_rwlock_wrlock (&tree->lock); if (!tree->root) { ret = -ENOENT; goto out; } if (path[0] == '\0') { ret = -EINVAL; goto out; } parent = g_path_get_dirname (path); dname = g_path_get_basename (path); if (strcmp (parent, ".") == 0) { dir = tree->root; } else { dirent = resolve_path (tree, parent); if (!dirent) { ret = -ENOENT; goto out; } dir = dirent->subdir; } if (g_hash_table_lookup (dir->dirents, dname) != NULL) { ret = -EEXIST; goto out; } if (S_ISDIR(mode)) { empty_dir = repo_tree_dir_new (EMPTY_SHA1); } dirent = repo_tree_dirent_new (id, dname, create_mode(mode), mtime, size, empty_dir); g_hash_table_insert (dir->dirents, dirent->name, dirent); out: g_free (parent); g_free (dname); pthread_rwlock_unlock (&tree->lock); return ret; } int repo_tree_create_file (RepoTree *tree, const char *path, const char *id, guint32 mode, gint64 mtime, gint64 size) { return repo_tree_create_common (tree, path, id, mode, mtime, size); } int repo_tree_mkdir (RepoTree *tree, const char *path, gint64 mtime) { return repo_tree_create_common (tree, path, EMPTY_SHA1, S_IFDIR, mtime, 0); } static int repo_tree_remove_common (RepoTree *tree, const char *path, gboolean is_dir) { char *parent = NULL, *dname = NULL; RepoTreeDirent *dirent; RepoTreeDir *dir; int ret = 0; pthread_rwlock_wrlock (&tree->lock); if (!tree->root) { ret = -ENOENT; goto out; } if (path[0] == '\0') { ret = -EINVAL; goto out; } parent = g_path_get_dirname (path); dname = g_path_get_basename (path); if (strcmp (parent, ".") == 0) { dir = tree->root; } else { dirent = resolve_path (tree, parent); if (!dirent) { ret = -ENOENT; goto out; } dir = dirent->subdir; } dirent = g_hash_table_lookup (dir->dirents, dname); if (!dirent) { ret = -ENOENT; goto out; } if (is_dir && !S_ISDIR(dirent->mode)) { ret = -ENOTDIR; goto out; } else if (!is_dir && S_ISDIR(dirent->mode)) { ret = -EISDIR; goto out; } if (S_ISDIR(dirent->mode) && !repo_tree_dir_is_empty(dirent->subdir)) { ret = -ENOTEMPTY; goto out; } g_hash_table_remove (dir->dirents, dname); out: g_free (parent); g_free (dname); pthread_rwlock_unlock (&tree->lock); return ret; } int repo_tree_unlink (RepoTree *tree, const char *path) { return repo_tree_remove_common (tree, path, FALSE); } int repo_tree_rmdir (RepoTree *tree, const char *path) { return repo_tree_remove_common (tree, path, TRUE); } int repo_tree_rename (RepoTree *tree, const char *oldpath, const char *newpath, gboolean replace_existing) { char *parent1 = NULL, *dname1 = NULL; char *parent2 = NULL, *dname2 = NULL; RepoTreeDirent *dirent, *dirent1, *dirent2, *new_dirent; RepoTreeDir *dir1, *dir2; int ret = 0; pthread_rwlock_wrlock (&tree->lock); if (!tree->root) { ret = -ENOENT; goto out; } if (oldpath[0] == '\0' || newpath[0] == '\0') { ret = -EINVAL; goto out; } parent1 = g_path_get_dirname (oldpath); dname1 = g_path_get_basename (oldpath); if (strcmp (parent1, ".") == 0) { dir1 = tree->root; } else { dirent = resolve_path (tree, parent1); if (!dirent) { ret = -ENOENT; goto out; } dir1 = dirent->subdir; } dirent1 = g_hash_table_lookup (dir1->dirents, dname1); if (!dirent1) { ret = -ENOENT; goto out; } parent2 = g_path_get_dirname (newpath); dname2 = g_path_get_basename (newpath); if (strcmp (parent2, ".") == 0) { dir2 = tree->root; } else { dirent = resolve_path (tree, parent2); if (!dirent) { ret = -ENOENT; goto out; } dir2 = dirent->subdir; } dirent2 = g_hash_table_lookup (dir2->dirents, dname2); if (dirent2) { if (!replace_existing) { ret = -EEXIST; goto out; } if (S_ISDIR(dirent2->mode) && !S_ISDIR(dirent1->mode)) { ret = -EISDIR; goto out; } if (S_ISDIR(dirent1->mode) && !S_ISDIR(dirent2->mode)) { ret = -ENOTDIR; goto out; } if (S_ISDIR(dirent1->mode) && S_ISDIR(dirent2->mode) && !repo_tree_dir_is_empty (dirent2->subdir)) { ret = -ENOTEMPTY; goto out; } } /* OK, all checks are passed. Replace dirent2 with dirent1. */ /* Remove dirent1 from dir1 but don't free it. */ g_hash_table_steal (dir1->dirents, dname1); new_dirent = dirent1; g_free (new_dirent->name); new_dirent->name = g_strdup(dname2); g_hash_table_replace (dir2->dirents, new_dirent->name, new_dirent); out: g_free (parent1); g_free (dname1); g_free (parent2); g_free (dname2); pthread_rwlock_unlock (&tree->lock); return ret; } int repo_tree_add_subtree (RepoTree *tree, const char *path, const char *root_id, gint64 mtime) { char *parent = NULL, *dname = NULL; RepoTreeDirent *dirent; RepoTreeDir *dir, *subdir = NULL; int ret = 0; subdir = load_dir_recursive (tree->repo_id, root_id); if (!subdir) { seaf_warning ("Failed to load repo dir %s of repo %s.\n", root_id, tree->repo_id); return -1; } pthread_rwlock_wrlock (&tree->lock); if (!tree->root) { ret = -ENOENT; goto out; } if (path[0] == '\0') { ret = -EINVAL; goto out; } parent = g_path_get_dirname (path); dname = g_path_get_basename (path); if (strcmp (parent, ".") == 0) { dir = tree->root; } else { dirent = resolve_path (tree, parent); if (!dirent) { ret = -ENOENT; goto out; } dir = dirent->subdir; } if (g_hash_table_lookup (dir->dirents, dname) != NULL) { ret = -EEXIST; goto out; } dirent = repo_tree_dirent_new (root_id, dname, create_mode(S_IFDIR), mtime, 0, subdir); g_hash_table_insert (dir->dirents, dirent->name, dirent); out: g_free (parent); g_free (dname); if (ret < 0) repo_tree_dir_free (subdir); pthread_rwlock_unlock (&tree->lock); return ret; } static int remove_dir_recursive (const char *repo_id, RepoTreeDir *dir, const char *path) { GHashTableIter iter; gpointer key, value; RepoTreeDirent *dirent; char *sub_path; g_hash_table_iter_init (&iter, dir->dirents); while (g_hash_table_iter_next (&iter, &key, &value)) { dirent = (RepoTreeDirent *)value; sub_path = g_strconcat (path, "/", dirent->name, NULL); if (S_ISREG(dirent->mode)) { if (file_cache_mgr_is_file_changed (seaf->file_cache_mgr, repo_id, sub_path, TRUE)) { g_free (sub_path); continue; } g_hash_table_iter_remove (&iter); file_cache_mgr_unlink (seaf->file_cache_mgr, repo_id, sub_path); } else { if (remove_dir_recursive (repo_id, dirent->subdir, sub_path) == 0) { g_hash_table_iter_remove (&iter); file_cache_mgr_rmdir (seaf->file_cache_mgr, repo_id, sub_path); } } g_free (sub_path); } int ret = g_hash_table_size(dir->dirents); return ret; } int repo_tree_remove_subtree (RepoTree *tree, const char *path) { char *parent = NULL, *dname = NULL; RepoTreeDirent *dirent; RepoTreeDir *dir; int ret = 0; pthread_rwlock_wrlock (&tree->lock); if (!tree->root) { ret = -ENOENT; goto out; } if (path[0] == '\0') { ret = -EINVAL; goto out; } parent = g_path_get_dirname (path); dname = g_path_get_basename (path); if (strcmp (parent, ".") == 0) { dir = tree->root; } else { dirent = resolve_path (tree, parent); if (!dirent) { ret = -ENOENT; goto out; } dir = dirent->subdir; } dirent = g_hash_table_lookup (dir->dirents, dname); if (!dirent) { goto out; } if (remove_dir_recursive (tree->repo_id, dirent->subdir, path) == 0) { g_hash_table_remove (dir->dirents, dname); file_cache_mgr_rmdir (seaf->file_cache_mgr, tree->repo_id, path); } out: g_free (parent); g_free (dname); pthread_rwlock_unlock (&tree->lock); return ret; } int repo_tree_set_file_size (RepoTree *tree, const char *path, gint64 size) { RepoTreeDirent *dirent; pthread_rwlock_wrlock (&tree->lock); dirent = resolve_path (tree, path); if (!dirent || !S_ISREG(dirent->mode)) { pthread_rwlock_unlock (&tree->lock); return -1; } dirent->size = size; pthread_rwlock_unlock (&tree->lock); return 0; } int repo_tree_set_file_mtime (RepoTree *tree, const char *path, gint64 mtime) { RepoTreeDirent *dirent; pthread_rwlock_wrlock (&tree->lock); dirent = resolve_path (tree, path); if (!dirent || !S_ISREG(dirent->mode)) { pthread_rwlock_unlock (&tree->lock); return -1; } dirent->mtime = mtime; pthread_rwlock_unlock (&tree->lock); return 0; } int repo_tree_set_file_id (RepoTree *tree, const char *path, const char *new_id) { RepoTreeDirent *dirent; pthread_rwlock_wrlock (&tree->lock); dirent = resolve_path (tree, path); if (!dirent || !S_ISREG(dirent->mode)) { pthread_rwlock_unlock (&tree->lock); return -1; } memcpy (dirent->id, new_id, 40); pthread_rwlock_unlock (&tree->lock); return 0; } gboolean repo_tree_is_empty_repo (RepoTree *tree) { GHashTable *dirents = tree->root->dirents; return g_hash_table_size(dirents) == 0 ? TRUE:FALSE; } gboolean repo_tree_is_empty_dir (RepoTree *tree, const char *path) { RepoTreeDirent *dirent; RepoTreeDir *dir; gboolean ret; pthread_rwlock_rdlock (&tree->lock); if (!tree->root) { pthread_rwlock_unlock (&tree->lock); return FALSE; } if (path[0] == '\0') { dir = tree->root; } else { dirent = resolve_path (tree, path); if (!dirent) { pthread_rwlock_unlock (&tree->lock); return FALSE; } dir = dirent->subdir; } if (!dir) { ret = FALSE; goto out; } ret = repo_tree_dir_is_empty (dir); out: pthread_rwlock_unlock (&tree->lock); return ret; } static void traverse_recursive (RepoTree *tree, RepoTreeDir *dir, const char *path, RepoTreeTraverseCallback callback) { GHashTableIter iter; gpointer key, value; RepoTreeStat st; char *dname; char *subpath; RepoTreeDirent *dirent; g_hash_table_iter_init (&iter, dir->dirents); while (g_hash_table_iter_next (&iter, &key, &value)) { dname = (char *)key; dirent = (RepoTreeDirent *)value; subpath = g_build_path ("/", path, dname, NULL); if (S_ISREG(dirent->mode)) { memset (&st, 0, sizeof(st)); memcpy (st.id, dirent->id, 40); st.mode = dirent->mode; st.size = dirent->size; st.mtime = dirent->mtime; callback (tree->repo_id, subpath, &st); } else { memset (&st, 0, sizeof(st)); memcpy (st.id, dirent->id, 40); st.mode = dirent->mode; callback (tree->repo_id, subpath, &st); traverse_recursive (tree, dirent->subdir, subpath, callback); } g_free (subpath); } } int repo_tree_traverse (RepoTree *tree, const char *root_path, RepoTreeTraverseCallback callback) { RepoTreeDirent *dirent; RepoTreeDir *dir; int ret = 0; RepoTreeStat st; pthread_rwlock_rdlock (&tree->lock); if (!tree->root) { ret = -ENOENT; goto out; } if (root_path[0] == '\0') { dir = tree->root; } else { dirent = resolve_path (tree, root_path); if (!dirent) { ret = -ENOENT; goto out; } if (!S_ISDIR(dirent->mode)) { memset (&st, 0, sizeof(st)); memcpy (st.id, dirent->id, 40); st.mode = dirent->mode; st.size = dirent->size; st.mtime = dirent->mtime; callback (tree->repo_id, root_path, &st); goto out; } else { memset (&st, 0, sizeof(st)); memcpy (st.id, dirent->id, 40); st.mode = dirent->mode; callback (tree->repo_id, root_path, &st); dir = dirent->subdir; } } traverse_recursive (tree, dir, root_path, callback); out: pthread_rwlock_unlock (&tree->lock); return ret; } static gboolean find_office_file_path (RepoTree *tree, const char *parent_dir, const char *lock_file_name, gboolean is_wps, char **office_path) { GHashTable *dirents = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); GHashTableIter iter; gpointer key, value; char *dname, *dname_nohead; gboolean ret = FALSE; if (repo_tree_readdir (tree, parent_dir, dirents) < 0) { g_hash_table_destroy (dirents); return FALSE; } g_hash_table_iter_init (&iter, dirents); while (g_hash_table_iter_next (&iter, &key, &value)) { dname = (char *)key; if (strlen(dname) < 2 || strncmp(dname, "~$", 2) == 0 || strncmp(dname, ".~", 2) == 0) { continue; } if (is_wps) { dname_nohead = g_utf8_next_char(dname); // WPS may replace the first one or two characters of the filename with ".~" based on the length of the filename. if (strcmp (dname_nohead, lock_file_name) == 0) { *office_path = g_build_path("/", parent_dir, dname, NULL); ret = TRUE; break; } dname_nohead = g_utf8_next_char(dname_nohead); } else { dname_nohead = g_utf8_next_char(g_utf8_next_char(dname)); } if (strcmp (dname_nohead, lock_file_name) == 0) { *office_path = g_build_path("/", parent_dir, dname, NULL); ret = TRUE; break; } } g_hash_table_destroy (dirents); return ret; } gboolean repo_tree_is_office_lock_file (RepoTree *tree, const char *path, char **office_path) { gboolean ret; gboolean is_wps = FALSE; if (!seaf->enable_auto_lock || !seaf->office_lock_file_regex || !seaf->libre_office_lock_file_regex || !seaf->wps_lock_file_regex) return FALSE; if (g_regex_match (seaf->office_lock_file_regex, path, 0, NULL)) /* Replace ~$abc.docx with abc.docx */ *office_path = g_regex_replace (seaf->office_lock_file_regex, path, -1, 0, "\\1", 0, NULL); else if (g_regex_match (seaf->libre_office_lock_file_regex, path, 0, NULL)) /* Replace .~lock.abc.docx# with abc.docx */ *office_path = g_regex_replace (seaf->libre_office_lock_file_regex, path, -1, 0, "\\1", 0, NULL); else if (g_regex_match (seaf->wps_lock_file_regex, path, 0, NULL)) { /* Replace .~abc.docx with abc.docx */ *office_path = g_regex_replace (seaf->wps_lock_file_regex, path, -1, 0, "\\1", 0, NULL); is_wps = TRUE; } else return FALSE; /* When the filename is long, sometimes the first two characters in the filename will be directly replaced with ~$. So if the office_path file doesn't exist, we have to match against all filenames in this directory, to find the office file's name. */ RepoTreeStat st; if (repo_tree_stat_path (tree, *office_path, &st) == 0) { return TRUE; } char *lock_file_name = g_path_get_basename(*office_path); char *parent_dir = g_path_get_dirname(*office_path); if (strcmp(parent_dir, ".") == 0) { g_free (parent_dir); parent_dir = g_strdup(""); } g_free (*office_path); *office_path = NULL; ret = find_office_file_path (tree, parent_dir, lock_file_name, is_wps, office_path); g_free (lock_file_name); g_free (parent_dir); return ret; } seadrive-fuse-3.0.13/src/repo-tree.h000066400000000000000000000047251476177674700172440ustar00rootroot00000000000000#ifndef SEAF_REPO_TREE_H #define SEAF_REPO_TREE_H #include /* * RepoTree is a in-memory representation of the directory hierarchy of a repo. */ struct _RepoTree; typedef struct _RepoTree RepoTree; RepoTree * repo_tree_new (const char *repo_id); int repo_tree_load_commit (RepoTree *tree, const char *commit_id); /* Clear the contents of the tree, but not free it. */ void repo_tree_clear (RepoTree *tree); void repo_tree_free (RepoTree *tree); gboolean repo_tree_is_loaded (RepoTree *tree); struct _RepoTreeStat { /* Id is only valid for files. The ID of dirs are not always correct. */ char id[41]; guint32 mode; gint64 size; gint64 mtime; }; typedef struct _RepoTreeStat RepoTreeStat; int repo_tree_stat_path (RepoTree *tree, const char *path, RepoTreeStat *st); int repo_tree_readdir (RepoTree *tree, const char *path, GHashTable *dirents); int repo_tree_create_file (RepoTree *tree, const char *path, const char *id, guint32 mode, gint64 mtime, gint64 size); int repo_tree_mkdir (RepoTree *tree, const char *path, gint64 mtime); int repo_tree_unlink (RepoTree *tree, const char *path); int repo_tree_rmdir (RepoTree *tree, const char *path); /* Rename @oldpath from @tree to @newpath. */ int repo_tree_rename (RepoTree *tree, const char *oldpath, const char *newpath, gboolean replace_existing); /* Load the subtree pointed to by @root_id and add this subtree to @path. */ int repo_tree_add_subtree (RepoTree *tree, const char *path, const char *root_id, gint64 mtime); /* Recursively remove a subtree on @path */ int repo_tree_remove_subtree (RepoTree *tree, const char *path); int repo_tree_set_file_mtime (RepoTree *tree, const char *path, gint64 mtime); int repo_tree_set_file_size (RepoTree *tree, const char *path, gint64 size); int repo_tree_set_file_id (RepoTree *tree, const char *path, const char *new_id); gboolean repo_tree_is_empty_repo (RepoTree *tree); gboolean repo_tree_is_empty_dir (RepoTree *tree, const char *path); typedef void (*RepoTreeTraverseCallback) (const char * repo_id, const char *path, RepoTreeStat *st); int repo_tree_traverse (RepoTree *tree, const char *root_path, RepoTreeTraverseCallback callback); gboolean repo_tree_is_office_lock_file (RepoTree *tree, const char *path, char **office_path); #endif seadrive-fuse-3.0.13/src/rpc-service.c000066400000000000000000000710561476177674700175600ustar00rootroot00000000000000#include "common.h" #include #include #include "searpc-signature.h" #include "searpc-marshal.h" #include #include #include "repo-mgr.h" #include "seafile-session.h" #include "seafile-config.h" #include "seafile-crypt.h" #include "seafile-error.h" #include "utils.h" #include "log.h" static int seafile_add_account (const char *server, const char *username, const char *nickname, const char *token, const char *name, int is_pro, GError **error) { if (!server || !username || !token || !name ) { g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Empty Arguments"); return -1; } return seaf_repo_manager_add_account (seaf->repo_mgr, server, username, nickname, token, name, (is_pro > 0)); } /* static int */ /* seafile_update_account (const char *server, const char *username, const char *token, */ /* GError **error) */ /* { */ /* if (!server || !username || !token) { */ /* g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Empty Arguments"); */ /* return -1; */ /* } */ /* return seaf_repo_manager_update_current_account (seaf->repo_mgr, */ /* server, */ /* username, */ /* token); */ /* } */ static int seafile_delete_account (const char *server, const char *username, gboolean remove_cache, GError **error) { if (!server || !username ) { g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Empty Arguments"); return -1; } return seaf_repo_manager_delete_account (seaf->repo_mgr, server, username, remove_cache, error); } static int seafile_set_config (const char *key, const char *value, GError **error) { return seafile_session_config_set_string(seaf, key, value); } static char * seafile_get_config (const char *key, GError **error) { return seafile_session_config_get_string(seaf, key); } static int seafile_set_config_int (const char *key, int value, GError **error) { return seafile_session_config_set_int(seaf, key, value); } static int seafile_get_config_int (const char *key, GError **error) { gboolean exists = TRUE; int ret = seafile_session_config_get_int(seaf, key, &exists); if (!exists) { g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL, "Config not exists"); return -1; } return ret; } static int seafile_set_upload_rate_limit (int limit, GError **error) { if (limit < 0) limit = 0; seaf->sync_mgr->upload_limit = limit; return seafile_session_config_set_int (seaf, KEY_UPLOAD_LIMIT, limit); } static int seafile_set_download_rate_limit (int limit, GError **error) { if (limit < 0) limit = 0; seaf->sync_mgr->download_limit = limit; return seafile_session_config_set_int (seaf, KEY_DOWNLOAD_LIMIT, limit); } static int seafile_get_upload_rate(GError **error) { return seaf->sync_mgr->last_sent_bytes; } static int seafile_get_download_rate(GError **error) { return seaf->sync_mgr->last_recv_bytes; } static int seafile_set_clean_cache_interval (int seconds, GError **error) { if (seconds < 0) return 0; return file_cache_mgr_set_clean_cache_interval (seaf->file_cache_mgr, seconds); } static int seafile_get_clean_cache_interval (GError **error) { return file_cache_mgr_get_clean_cache_interval (seaf->file_cache_mgr); } static int seafile_set_cache_size_limit (gint64 limit, GError **error) { if (limit < 0) return 0; return file_cache_mgr_set_cache_size_limit (seaf->file_cache_mgr, limit); } static gint64 seafile_get_cache_size_limit (GError **error) { return file_cache_mgr_get_cache_size_limit (seaf->file_cache_mgr); } static int seafile_is_file_cached (const char *repo_id, const char *file_path, GError **error) { return file_cache_mgr_is_file_cached (seaf->file_cache_mgr, repo_id, file_path); } static json_t * seafile_get_global_sync_status (GError **error) { json_t *object = json_object(); int is_syncing = seaf_sync_manager_is_syncing (seaf->sync_mgr); int auto_sync_enabled = seaf_sync_manager_is_auto_sync_enabled (seaf->sync_mgr); json_object_set (object, "auto_sync_enabled", json_integer(auto_sync_enabled)); json_object_set (object, "is_syncing", json_integer(is_syncing)); json_object_set (object, "sent_bytes", json_integer(seaf->sync_mgr->last_sent_bytes)); json_object_set (object, "recv_bytes", json_integer(seaf->sync_mgr->last_recv_bytes)); return object; } int seafile_disable_auto_sync (GError **error) { return seaf_sync_manager_disable_auto_sync (seaf->sync_mgr); } int seafile_enable_auto_sync (GError **error) { return seaf_sync_manager_enable_auto_sync (seaf->sync_mgr); } static char * seafile_get_path_sync_status (const char *server, const char *username, const char *repo_uname, const char *path, GError **error) { char *repo_id; char *canon_path = NULL; char *status; if (!repo_uname || !path) { g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Argument should not be null"); return NULL; } repo_id = seaf_repo_manager_get_repo_id_by_display_name (seaf->repo_mgr, server, username, repo_uname); if (!repo_id) { g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Invalid repo unique name"); return NULL; } /* Empty path means to get status of the worktree folder. */ if (strcmp (path, "") != 0) { canon_path = format_path (path); } else { canon_path = g_strdup(path); } status = seaf_sync_manager_get_path_sync_status (seaf->sync_mgr, repo_id, canon_path); g_free (repo_id); g_free (canon_path); return status; } static json_t * seafile_get_sync_notification (GError **error) { return mq_mgr_pop_msg (seaf->mq_mgr, SEADRIVE_NOTIFY_CHAN); } static json_t * seafile_get_events_notification (GError **error) { return mq_mgr_pop_msg (seaf->mq_mgr, SEADRIVE_EVENT_CHAN); } static char* seafile_get_repo_id_by_uname (const char *server, const char *username, const char *repo_uname, GError **error) { char *repo_id; if (!repo_uname) { g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Argument should not be null"); return NULL; } repo_id = seaf_repo_manager_get_repo_id_by_display_name (seaf->repo_mgr, server, username, repo_uname); if (!repo_id) { g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Invalid repo unique name"); return NULL; } return repo_id; } static char* seafile_get_repo_display_name_by_id (const char *repo_id, GError **error) { char *repo_uname; if (!repo_id) { g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Argument should not be null"); return NULL; } repo_uname = seaf_repo_manager_get_repo_display_name (seaf->repo_mgr, repo_id); if (!repo_uname) { g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Invalid repo id"); return NULL; } return repo_uname; } static int seafile_unmount (GError **error) { return seafile_session_unmount (seaf); } static json_t * seafile_get_upload_progress (GError **error) { return http_tx_manager_get_upload_progress (seaf->http_tx_mgr); } static json_t * seafile_get_download_progress (GError **error) { return file_cache_mgr_get_download_progress (seaf->file_cache_mgr); } static int seafile_mark_file_locked (const char *repo_id, const char *path, GError **error) { char *canon_path = NULL; int len; int ret; if (!repo_id || !path) { g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Argument should not be null"); return -1; } if (*path == '/') ++path; if (path[0] == 0) { g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Invalid path"); return -1; } canon_path = g_strdup(path); len = strlen(canon_path); if (canon_path[len-1] == '/') canon_path[len-1] = 0; ret = seaf_filelock_manager_mark_file_locked (seaf->filelock_mgr, repo_id, canon_path, LOCKED_MANUAL); g_free (canon_path); return ret; } static int seafile_mark_file_unlocked (const char *repo_id, const char *path, GError **error) { char *canon_path = NULL; int len; int ret; if (!repo_id || !path) { g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Argument should not be null"); return -1; } if (*path == '/') ++path; if (path[0] == 0) { g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Invalid path"); return -1; } canon_path = g_strdup(path); len = strlen(canon_path); if (canon_path[len-1] == '/') canon_path[len-1] = 0; ret = seaf_filelock_manager_mark_file_unlocked (seaf->filelock_mgr, repo_id, path); g_free (canon_path); return ret; } static int seafile_cancel_download (const char *server, const char *user, const char *full_file_path, GError **error) { if (!full_file_path) { g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Argument should not be null"); return -1; } return file_cache_mgr_cancel_download (seaf->file_cache_mgr, server, user, full_file_path, error); } static json_t * seafile_list_sync_errors (GError **error) { return seaf_sync_manager_list_sync_errors (seaf->sync_mgr); } static int seafile_cache_path (const char *repo_id, const char *path, GError **error) { char *canon_path = NULL; int len; if (!repo_id || !path) { g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Argument should not be null"); return -1; } if (*path == '/') ++path; canon_path = g_strdup(path); len = strlen(canon_path); if (len > 0) { if (canon_path[len-1] == '/') canon_path[len-1] = 0; } seaf_sync_manager_cache_path (seaf->sync_mgr, repo_id, canon_path); g_free (canon_path); return 0; } int seafile_uncache_path (const char *repo_id, const char *path, GError **error) { return file_cache_mgr_uncache_path(repo_id, path); } static json_t * seafile_get_enc_repo_list (GError **error) { GList *repos = seaf_repo_manager_get_enc_repo_list (seaf->repo_mgr, -1, -1); json_t *array, *obj; array = json_array (); GList *ptr; for (ptr = repos; ptr != NULL; ptr = ptr->next) { SeafRepo *repo = ptr->data; char *display_name = NULL; obj = json_object (); display_name = seaf_repo_manager_get_repo_display_name (seaf->repo_mgr,repo->id); //used to determine whether the repo is in the current account when switch account. if (!display_name) continue; json_object_set_new (obj, "server", json_string(repo->server)); json_object_set_new (obj, "username", json_string(repo->user)); json_object_set_new (obj, "repo_id", json_string(repo->id)); json_object_set_new (obj, "repo_display_name", json_string(display_name)); if (repo->is_passwd_set) json_object_set_new (obj, "is_passwd_set", json_true()); else json_object_set_new (obj, "is_passwd_set", json_false()); json_array_append_new (array, obj); seaf_repo_unref ((SeafRepo *)ptr->data); } g_list_free (repos); return array; } static int seafile_set_enc_repo_passwd (const char *repo_id, const char *passwd, GError **error) { SeafRepo *repo; int ret = 0; if (!repo_id || !passwd) { g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Argument should not be null"); return -1; } repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id); if (!repo) { g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "No such library"); return -1; } if (repo->pwd_hash_algo) { if (seafile_pwd_hash_verify_repo_passwd (repo->enc_version, repo_id, passwd, repo->salt, repo->pwd_hash, repo->pwd_hash_algo, repo->pwd_hash_params) < 0) { g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Wrong password"); ret = -1; goto out; } } else { if (seafile_verify_repo_passwd (repo_id, passwd, repo->magic, repo->enc_version, repo->salt) < 0) { g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Wrong password"); ret = -1; goto out; } } ret = seaf_repo_manager_set_repo_passwd (seaf->repo_mgr, repo, passwd); if (ret < 0) { g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Failed to insert data into db"); goto out; } out: seaf_repo_unref (repo); return ret; } static int seafile_clear_enc_repo_passwd (const char *repo_id, GError **error) { int ret = seaf_repo_manager_clear_enc_repo_passwd (repo_id); if (ret < 0) { g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Failed to delete data from db"); } return ret; } static int seafile_add_del_confirmation (const char *confirmation_id, int resync, GError **error) { return seaf_sync_manager_add_del_confirmation (seaf->sync_mgr, confirmation_id, resync); } static json_t * seafile_get_account_by_repo_id (const char *repo_id, GError **error) { return seaf_repo_manager_get_account_by_repo_id (seaf->repo_mgr, repo_id); } static char * seafile_ping (GError **error) { return g_strdup ("pong"); } #if 0 static GObject * seafile_generate_magic_and_random_key(int enc_version, const char* repo_id, const char *passwd, GError **error) { if (!repo_id || !passwd) { g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Argument should not be null"); return NULL; } gchar magic[65] = {0}; gchar random_key[97] = {0}; seafile_generate_magic (CURRENT_ENC_VERSION, repo_id, passwd, magic); seafile_generate_random_key (passwd, random_key); SeafileEncryptionInfo *sinfo; sinfo = g_object_new (SEAFILE_TYPE_ENCRYPTION_INFO, "repo_id", repo_id, "passwd", passwd, "enc_version", CURRENT_ENC_VERSION, "magic", magic, "random_key", random_key, NULL); return (GObject *)sinfo; } #include "diff-simple.h" inline static const char* get_diff_status_str(char status) { if (status == DIFF_STATUS_ADDED) return "add"; if (status == DIFF_STATUS_DELETED) return "del"; if (status == DIFF_STATUS_MODIFIED) return "mod"; if (status == DIFF_STATUS_RENAMED) return "mov"; if (status == DIFF_STATUS_DIR_ADDED) return "newdir"; if (status == DIFF_STATUS_DIR_DELETED) return "deldir"; return NULL; } static GList * seafile_diff (const char *repo_id, const char *arg1, const char *arg2, int fold_dir_diff, GError **error) { SeafRepo *repo; char *err_msgs = NULL; GList *diff_entries, *p; GList *ret = NULL; if (!repo_id || !arg1 || !arg2) { g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Argument should not be null"); return NULL; } if (!is_uuid_valid (repo_id)) { g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Invalid repo id"); return NULL; } if ((arg1[0] != 0 && !is_object_id_valid (arg1)) || !is_object_id_valid(arg2)) { g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Invalid commit id"); return NULL; } repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id); if (!repo) { g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "No such repository"); return NULL; } diff_entries = seaf_repo_diff (repo, arg1, arg2, fold_dir_diff, &err_msgs); if (err_msgs) { g_set_error (error, SEAFILE_DOMAIN, -1, "%s", err_msgs); g_free (err_msgs); seaf_repo_unref (repo); return NULL; } seaf_repo_unref (repo); for (p = diff_entries; p != NULL; p = p->next) { DiffEntry *de = p->data; SeafileDiffEntry *entry = g_object_new ( SEAFILE_TYPE_DIFF_ENTRY, "status", get_diff_status_str(de->status), "name", de->name, "new_name", de->new_name, NULL); ret = g_list_prepend (ret, entry); } for (p = diff_entries; p != NULL; p = p->next) { DiffEntry *de = p->data; diff_entry_free (de); } g_list_free (diff_entries); return g_list_reverse (ret); } #endif static void register_rpc_service () { searpc_server_init (register_marshals); searpc_create_service ("seadrive-rpcserver"); searpc_server_register_function ("seadrive-rpcserver", seafile_add_account, "seafile_add_account", searpc_signature_int__string_string_string_string_string_int()); searpc_server_register_function ("seadrive-rpcserver", seafile_delete_account, "seafile_delete_account", searpc_signature_int__string_string_int()); searpc_server_register_function ("seadrive-rpcserver", seafile_get_config, "seafile_get_config", searpc_signature_string__string()); searpc_server_register_function ("seadrive-rpcserver", seafile_set_config, "seafile_set_config", searpc_signature_int__string_string()); searpc_server_register_function ("seadrive-rpcserver", seafile_get_config_int, "seafile_get_config_int", searpc_signature_int__string()); searpc_server_register_function ("seadrive-rpcserver", seafile_set_config_int, "seafile_set_config_int", searpc_signature_int__string_int()); searpc_server_register_function ("seadrive-rpcserver", seafile_set_upload_rate_limit, "seafile_set_upload_rate_limit", searpc_signature_int__int()); searpc_server_register_function ("seadrive-rpcserver", seafile_set_download_rate_limit, "seafile_set_download_rate_limit", searpc_signature_int__int()); searpc_server_register_function ("seadrive-rpcserver", seafile_get_upload_rate, "seafile_get_upload_rate", searpc_signature_int__void()); searpc_server_register_function ("seadrive-rpcserver", seafile_get_download_rate, "seafile_get_download_rate", searpc_signature_int__void()); searpc_server_register_function ("seadrive-rpcserver", seafile_get_clean_cache_interval, "seafile_get_clean_cache_interval", searpc_signature_int__void()); searpc_server_register_function ("seadrive-rpcserver", seafile_set_clean_cache_interval, "seafile_set_clean_cache_interval", searpc_signature_int__int()); searpc_server_register_function ("seadrive-rpcserver", seafile_get_cache_size_limit, "seafile_get_cache_size_limit", searpc_signature_int64__void()); searpc_server_register_function ("seadrive-rpcserver", seafile_is_file_cached, "seafile_is_file_cached", searpc_signature_int__string_string()); searpc_server_register_function ("seadrive-rpcserver", seafile_set_cache_size_limit, "seafile_set_cache_size_limit", searpc_signature_int__int64()); searpc_server_register_function ("seadrive-rpcserver", seafile_get_global_sync_status, "seafile_get_global_sync_status", searpc_signature_json__void()); searpc_server_register_function ("seadrive-rpcserver", seafile_disable_auto_sync, "seafile_disable_auto_sync", searpc_signature_int__void()); searpc_server_register_function ("seadrive-rpcserver", seafile_enable_auto_sync, "seafile_enable_auto_sync", searpc_signature_int__void()); searpc_server_register_function ("seadrive-rpcserver", seafile_get_path_sync_status, "seafile_get_path_sync_status", searpc_signature_string__string_string_string_string()); searpc_server_register_function ("seadrive-rpcserver", seafile_get_sync_notification, "seafile_get_sync_notification", searpc_signature_json__void()); searpc_server_register_function ("seadrive-rpcserver", seafile_get_events_notification, "seafile_get_events_notification", searpc_signature_json__void()); searpc_server_register_function ("seadrive-rpcserver", seafile_get_repo_id_by_uname, "seafile_get_repo_id_by_uname", searpc_signature_string__string_string_string()); searpc_server_register_function ("seadrive-rpcserver", seafile_get_repo_display_name_by_id, "seafile_get_repo_display_name_by_id", searpc_signature_string__string()); searpc_server_register_function ("seadrive-rpcserver", seafile_unmount, "seafile_unmount", searpc_signature_int__void()); searpc_server_register_function ("seadrive-rpcserver", seafile_get_upload_progress, "seafile_get_upload_progress", searpc_signature_json__void()); searpc_server_register_function ("seadrive-rpcserver", seafile_get_download_progress, "seafile_get_download_progress", searpc_signature_json__void()); searpc_server_register_function ("seadrive-rpcserver", seafile_mark_file_locked, "seafile_mark_file_locked", searpc_signature_int__string_string()); searpc_server_register_function ("seadrive-rpcserver", seafile_mark_file_unlocked, "seafile_mark_file_unlocked", searpc_signature_int__string_string()); searpc_server_register_function ("seadrive-rpcserver", seafile_cancel_download, "seafile_cancel_download", searpc_signature_int__string_string_string()); searpc_server_register_function ("seadrive-rpcserver", seafile_list_sync_errors, "seafile_list_sync_errors", searpc_signature_json__void()); searpc_server_register_function ("seadrive-rpcserver", seafile_cache_path, "seafile_cache_path", searpc_signature_int__string_string()); searpc_server_register_function ("seadrive-rpcserver", seafile_uncache_path, "seafile_uncache_path", searpc_signature_int__string_string()); searpc_server_register_function ("seadrive-rpcserver", seafile_get_enc_repo_list, "seafile_get_enc_repo_list", searpc_signature_json__void()); searpc_server_register_function ("seadrive-rpcserver", seafile_set_enc_repo_passwd, "seafile_set_enc_repo_passwd", searpc_signature_int__string_string()); searpc_server_register_function ("seadrive-rpcserver", seafile_clear_enc_repo_passwd, "seafile_clear_enc_repo_passwd", searpc_signature_int__string()); searpc_server_register_function ("seadrive-rpcserver", seafile_add_del_confirmation, "seafile_add_del_confirmation", searpc_signature_int__string_int()); searpc_server_register_function ("seadrive-rpcserver", seafile_get_account_by_repo_id, "seafile_get_account_by_repo_id", searpc_signature_json__string()); searpc_server_register_function ("seadrive-rpcserver", seafile_ping, "seafile_ping", searpc_signature_string__void()); #if 0 searpc_server_register_function ("seadrive-rpcserver", seafile_generate_magic_and_random_key, "seafile_generate_magic_and_random_key", searpc_signature_object__int_string_string()); searpc_server_register_function ("seadrive-rpcserver", seafile_diff, "seafile_diff", searpc_signature_objlist__string_string_string_int()); #endif } #define SEADRIVE_SOCKET_NAME "seadrive.sock" int start_searpc_server () { register_rpc_service (); char *path = g_build_filename (seaf->seaf_dir, SEADRIVE_SOCKET_NAME, NULL); SearpcNamedPipeServer *server = searpc_create_named_pipe_server (path); if (!server) { seaf_warning ("Failed to create named pipe server.\n"); g_free (path); return -1; } seaf->rpc_socket_path = path; return searpc_named_pipe_server_start (server); } seadrive-fuse-3.0.13/src/rpc-service.h000066400000000000000000000001311476177674700175470ustar00rootroot00000000000000#ifndef SEAF_RPC_SERVER_H #define SEAF_RPC_SERVER_H int start_searpc_server (); #endif seadrive-fuse-3.0.13/src/rpc_table.py000066400000000000000000000112501476177674700174650ustar00rootroot00000000000000""" Define RPC functions needed to generate """ # [ , [] ] func_table = [ [ "int", [] ], [ "int", ["int"] ], [ "int", ["int", "int"] ], [ "int", ["int", "string"] ], [ "int", ["int", "string", "int"] ], [ "int", ["int", "string", "string"] ], [ "int", ["int", "string", "int", "int"] ], [ "int", ["int", "int", "string", "string"] ], [ "int", ["string"] ], [ "int", ["string", "int"] ], [ "int", ["string", "int", "int"] ], [ "int", ["string", "int", "string"] ], [ "int", ["string", "int", "string", "string"] ], [ "int", ["string", "int", "int", "string", "string"] ], [ "int", ["string", "string"] ], [ "int", ["string", "string", "string"] ], [ "int", ["string", "string", "int"] ], [ "int", ["string", "string", "string", "int"] ], [ "int", ["string", "string", "string", "string", "int"] ], [ "int", ["string", "string", "string", "string", "string", "int"] ], [ "int", ["string", "string", "int", "int"] ], [ "int", ["string", "string", "string", "string"] ], [ "int", ["string", "string", "string", "string", "string"] ], [ "int", ["string", "string", "string", "string", "string", "string"] ], [ "int", ["string", "string", "string", "string", "string", "string", "string"] ], [ "int", ["string", "int64"]], [ "int", ["int", "int64"]], [ "int", ["int", "string", "int64"]], [ "int", ["int64"] ], [ "int64", [] ], [ "int64", ["string"] ], [ "int64", ["int"]], [ "int64", ["int", "string"]], [ "int64", ["string", "int", "string"] ], [ "string", [] ], [ "string", ["int"] ], [ "string", ["int", "int"] ], [ "string", ["int", "string"] ], [ "string", ["int", "int", "string"] ], [ "string", ["string"] ], [ "string", ["string", "int"] ], [ "string", ["string", "int", "int"] ], [ "string", ["string", "string"] ], [ "string", ["string", "string", "int"] ], [ "string", ["string", "string", "int", "int"] ], [ "string", ["string", "string", "string"] ], [ "string", ["string", "string", "string", "string"] ], [ "string", ["string", "string", "string", "string", "int"] ], [ "string", ["string", "string", "string", "string", "string"] ], [ "string", ["string", "string", "string", "string", "string", "int"] ], [ "string", ["string", "string", "string", "string", "string", "string", "int"] ], [ "string", ["string", "string", "string", "string", "string", "string", "int", "int"] ], [ "string", ["string", "string", "string", "string", "string", "string"] ], [ "string", ["string", "string", "string", "string", "string", "string", "int64"] ], [ "string", ["string", "string", "string", "string", "string", "string", "int64", "int"] ], [ "string", ["string", "string", "string", "string", "string", "string", "string"] ], [ "string", ["string", "string", "string", "string", "string", "string", "string", "int64"] ], [ "string", ["string", "string", "string", "string", "string", "string", "string", "string", "string"] ], [ "string", ["string", "int", "string", "string", "string", "string", "string", "string", "string", "string", "string", "string", "int", "string"] ], [ "string", ["string", "int", "string", "int", "int"] ], [ "string", ["string", "int", "string", "string", "string"] ], [ "objlist", [] ], [ "objlist", ["int"] ], [ "objlist", ["int", "int"] ], [ "objlist", ["int", "string"] ], [ "objlist", ["int", "int", "int"] ], [ "objlist", ["string"] ], [ "objlist", ["string", "int"] ], [ "objlist", ["string", "int", "int"] ], [ "objlist", ["string", "int", "string"] ], [ "objlist", ["string", "string"] ], [ "objlist", ["string", "string", "string"] ], [ "objlist", ["string", "string", "int"] ], [ "objlist", ["string", "string", "string", "int"] ], [ "objlist", ["string", "string", "int", "int"] ], [ "objlist", ["string", "string", "int", "int", "int"] ], [ "objlist", ["int", "string", "string", "int", "int"] ], [ "objlist", ["string", "int", "string", "string", "string"] ], [ "objlist", ["string", "int", "string", "int", "int"] ], [ "objlist", ["string", "int", "string", "string", "int"] ], [ "objlist", ["string", "string", "string", "string", "int", "int"] ], [ "object", [] ], [ "object", ["int"] ], [ "object", ["string"] ], [ "object", ["string", "string"] ], [ "object", ["string", "string", "string"] ], [ "object", ["string", "int", "string"] ], [ "object", ["int", "string", "string"] ], [ "object", ["string", "string", "string", "string", "string", "string", "string", "int", "int"] ], [ "json", []], [ "json", ["string"]], ] seadrive-fuse-3.0.13/src/seadrive.c000066400000000000000000000362041476177674700171340ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ #include "common.h" #include #include #include #include #include #include #include #ifdef HAVE_BREAKPAD_SUPPORT #include #endif // HAVE_BREAKPAD_SUPPORT #include "seafile-session.h" #include "log.h" #include "utils.h" #include "seafile-config.h" #ifndef USE_GPL_CRYPTO #include "curl-init.h" #endif #include "cdc.h" #include "rpc-service.h" #ifndef SEAFILE_CLIENT_VERSION #define SEAFILE_CLIENT_VERSION PACKAGE_VERSION #endif SeafileSession *seaf; static void print_version () { fprintf (stdout, "SeaDrive "SEAFILE_CLIENT_VERSION"\n"); } #include #include #define FUSE_USE_VERSION 26 #include #include #include "fuse-ops.h" struct options { char *seafile_dir; char *log_file; char *debug_str; char *config_file; char *language; } options; #define SEAF_FUSE_OPT_KEY(t, p, v) { t, offsetof(struct options, p), v } enum { KEY_VERSION, KEY_HELP, }; static struct fuse_opt seadrive_fuse_opts[] = { SEAF_FUSE_OPT_KEY("-d %s", seafile_dir, 0), SEAF_FUSE_OPT_KEY("--seafdir %s", seafile_dir, 0), SEAF_FUSE_OPT_KEY("-l %s", log_file, 0), SEAF_FUSE_OPT_KEY("--logfile %s", log_file, 0), SEAF_FUSE_OPT_KEY("-D %s", debug_str, 0), SEAF_FUSE_OPT_KEY("--debug %s", debug_str, 0), SEAF_FUSE_OPT_KEY("-c %s", config_file, 0), SEAF_FUSE_OPT_KEY("--config %s", config_file, 0), SEAF_FUSE_OPT_KEY("-L %s", language, 0), SEAF_FUSE_OPT_KEY("--language %s", language, 0), FUSE_OPT_KEY("-V", KEY_VERSION), FUSE_OPT_KEY("--version", KEY_VERSION), FUSE_OPT_KEY("-h", KEY_HELP), FUSE_OPT_KEY("--help", KEY_HELP), FUSE_OPT_END }; static struct fuse_operations seadrive_fuse_ops = { .getattr = seadrive_fuse_getattr, .readdir = seadrive_fuse_readdir, .mknod = seadrive_fuse_mknod, .mkdir = seadrive_fuse_mkdir, .unlink = seadrive_fuse_unlink, .rmdir = seadrive_fuse_rmdir, .rename = seadrive_fuse_rename, .open = seadrive_fuse_open, .read = seadrive_fuse_read, .write = seadrive_fuse_write, .release = seadrive_fuse_release, .truncate = seadrive_fuse_truncate, .statfs = seadrive_fuse_statfs, .chmod = seadrive_fuse_chmod, .utimens = seadrive_fuse_utimens, .symlink = seadrive_fuse_symlink, /* .setxattr = seadrive_fuse_setxattr, */ /* .getxattr = seadrive_fuse_getxattr, */ /* .listxattr = seadrive_fuse_listxattr, */ /* .removexattr = seadrive_fuse_removexattr */ }; static void usage () { fprintf (stderr, "usage: seadrive -d \n" "Options:\n" "-f: run in foreground\n" "-o : please refer to fuse manpage\n" "-l \n" "-c \n" "-L \n"); } static int seadrive_fuse_opt_proc_func(void *data, const char *arg, int key, struct fuse_args *outargs) { if (key == KEY_VERSION) { print_version (); exit (0); } else if (key == KEY_HELP) { usage (); exit (0); } return 1; } static void set_signal_handlers (SeafileSession *session) { signal (SIGPIPE, SIG_IGN); } static void * seafile_session_thread (void *vdata) { seafile_session_start (seaf); return NULL; } #define ACCOUNT_GROUP "account" #define ACCOUNT_SERVER "server" #define ACCOUNT_USERNAME "username" #define ACCOUNT_TOKEN "token" #define ACCOUNT_IS_PRO "is_pro" static void load_account_from_file (const char *account_file) { GKeyFile *key_file = g_key_file_new (); char *full_account_file = NULL; GError *error = NULL; char *server = NULL, *username = NULL, *token = NULL; gboolean is_pro; full_account_file = ccnet_expand_path (account_file); if (!g_key_file_load_from_file (key_file, full_account_file, 0, &error)) { seaf_warning ("Failed to load account file %s: %s.\n", full_account_file, error->message); g_clear_error (&error); goto out; } server = g_key_file_get_string (key_file, ACCOUNT_GROUP, ACCOUNT_SERVER, &error); if (!server) { g_clear_error (&error); goto out; } username = g_key_file_get_string (key_file, ACCOUNT_GROUP, ACCOUNT_USERNAME, &error); if (!username) { g_clear_error (&error); goto out; } token = g_key_file_get_string (key_file, ACCOUNT_GROUP, ACCOUNT_TOKEN, &error); if (!token) { g_clear_error (&error); goto out; } /* Default false. */ is_pro = g_key_file_get_boolean (key_file, ACCOUNT_GROUP, ACCOUNT_IS_PRO, NULL); seaf_repo_manager_add_account (seaf->repo_mgr, server, username, username, token, username, is_pro); out: g_free (server); g_free (username); g_free (token); g_free (full_account_file); g_key_file_free (key_file); } #define GENERAL_GROUP "general" #define CLIENT_NAME "client_name" #define DELETE_THRESHOLD "delete_confirm_threshold" #define NETWORK_GROUP "network" #define DISABLE_VERIFY_CERTIFICATE "disable_verify_certificate" #define PROXY_GROUP "proxy" #define PROXY_TYPE "type" #define PROXY_ADDR "addr" #define PROXY_PORT "port" #define PROXY_USERNAME "username" #define PROXY_PASSWORD "password" #define CACHE_GROUP "cache" #define CACHE_SIZE_LIMIT "size_limit" #define CLEAN_CACHE_INTERVAL "clean_cache_interval" static gint64 convert_size_str (const char *size_str) { #define KB 1000L #define MB 1000000L #define GB 1000000000L #define TB 1000000000000L char *end; gint64 size_int; gint64 multiplier = GB; gint64 size; size_int = strtoll (size_str, &end, 10); if (size_int == LLONG_MIN || size_int == LLONG_MAX) { return -1; } if (*end != '\0') { if (strcasecmp(end, "kb") == 0 || strcasecmp(end, "k") == 0) multiplier = KB; else if (strcasecmp(end, "mb") == 0 || strcasecmp(end, "m") == 0) multiplier = MB; else if (strcasecmp(end, "gb") == 0 || strcasecmp(end, "g") == 0) multiplier = GB; else if (strcasecmp(end, "tb") == 0 || strcasecmp(end, "t") == 0) multiplier = TB; else { seaf_warning ("Unrecognized %s\n", size_str); return -1; } } size = size_int * multiplier; return size; } static void load_config_from_file (const char *config_file) { GKeyFile *key_file = g_key_file_new (); char *full_config_file = NULL; GError *error = NULL; char *client_name = NULL; gboolean disable_verify_certificate; char *proxy_type = NULL, *proxy_addr = NULL, *proxy_username = NULL, *proxy_password = NULL; int proxy_port; char *cache_size_limit_str = NULL; gint64 cache_size_limit; int clean_cache_interval; int delete_confirm_threshold = 1000000; full_config_file = ccnet_expand_path (config_file); if (!g_key_file_load_from_file (key_file, full_config_file, 0, &error)) { seaf_warning ("Failed to load config file %s: %s.\n", full_config_file, error->message); g_clear_error (&error); g_free (full_config_file); return; } client_name = g_key_file_get_string (key_file, GENERAL_GROUP, CLIENT_NAME, NULL); if (client_name) { g_free (seaf->client_name); seaf->client_name = g_strdup(client_name); } delete_confirm_threshold = g_key_file_get_integer (key_file, GENERAL_GROUP, DELETE_THRESHOLD, &error); if (!error) { if (delete_confirm_threshold > 0) seaf->delete_confirm_threshold = delete_confirm_threshold; } g_clear_error (&error); disable_verify_certificate = g_key_file_get_boolean (key_file, NETWORK_GROUP, DISABLE_VERIFY_CERTIFICATE, &error); if (!error) { seaf->disable_verify_certificate = disable_verify_certificate; } g_clear_error (&error); proxy_type = g_key_file_get_string (key_file, PROXY_GROUP, PROXY_TYPE, &error); if (!error && (g_strcmp0 (proxy_type, "http") == 0 || g_strcmp0 (proxy_type, "socks") == 0)) { seaf->use_http_proxy = TRUE; g_free (seaf->http_proxy_type); seaf->http_proxy_type = g_strdup(proxy_type); proxy_addr = g_key_file_get_string (key_file, PROXY_GROUP, PROXY_ADDR, &error); if (!error) { g_free (seaf->http_proxy_addr); seaf->http_proxy_addr = g_strdup(proxy_addr); } g_clear_error (&error); proxy_port = g_key_file_get_integer (key_file, PROXY_GROUP, PROXY_PORT, &error); if (!error) { seaf->http_proxy_port = proxy_port; } g_clear_error (&error); proxy_username = g_key_file_get_string (key_file, PROXY_GROUP, PROXY_USERNAME, &error); if (!error) { g_free (seaf->http_proxy_username); seaf->http_proxy_username = g_strdup(proxy_username); } g_clear_error (&error); proxy_password = g_key_file_get_string (key_file, PROXY_GROUP, PROXY_PASSWORD, &error); if (!error) { g_free (seaf->http_proxy_password); seaf->http_proxy_password = g_strdup(proxy_password); } g_clear_error (&error); } g_clear_error (&error); cache_size_limit_str = g_key_file_get_string (key_file, CACHE_GROUP, CACHE_SIZE_LIMIT, &error); if (!error) { cache_size_limit = convert_size_str (cache_size_limit_str); if (cache_size_limit >= 0) file_cache_mgr_set_cache_size_limit (seaf->file_cache_mgr, cache_size_limit); } g_clear_error (&error); clean_cache_interval = g_key_file_get_integer (key_file, CACHE_GROUP, CLEAN_CACHE_INTERVAL, &error); if (!error) { file_cache_mgr_set_clean_cache_interval (seaf->file_cache_mgr, clean_cache_interval * 60); } g_clear_error (&error); g_free (client_name); g_free (proxy_type); g_free (proxy_addr); g_free (proxy_username); g_free (proxy_password); g_free (cache_size_limit_str); g_free (full_config_file); g_key_file_free (key_file); } static int write_pidfile (char *pidfile) { int fd = open (pidfile, O_WRONLY | O_CREAT, 0644); if (fd < 0) { seaf_warning ("Failed to open pidfile %s: %s\n", pidfile, strerror(errno)); return -1; } if (flock (fd, LOCK_EX | LOCK_NB) < 0) { seaf_warning ("Failed to lock pidfile %s: %s\n", pidfile, strerror(errno)); close (fd); return -1; } return 0; } int main (int argc, char **argv) { char *seafile_dir = NULL; char *full_seafdir = NULL, *log_dir = NULL; char *logfile = NULL; const char *debug_str = NULL; const char *mount_point = NULL; char *config_file = NULL; char *language = NULL; char *pidfile = NULL; struct fuse_args args = FUSE_ARGS_INIT(argc, argv); memset(&options, 0, sizeof(struct options)); if (fuse_opt_parse(&args, &options, seadrive_fuse_opts, seadrive_fuse_opt_proc_func) == -1) { usage(); exit(1); } seafile_dir = options.seafile_dir; logfile = options.log_file; debug_str = options.debug_str; config_file = options.config_file; language = options.language; mount_point = args.argc[args.argv - 1]; if (!seafile_dir) { usage (); exit(1); } if (!mount_point) { usage (); exit (1); } if (language != NULL && strcmp (language, "zh_cn") != 0 && strcmp (language, "en_us") != 0 && strcmp (language, "fr_fr") != 0 && strcmp (language, "de_de") != 0) { fprintf (stderr, "Unknown language %s\n", language); exit (1); } cdc_init (); #if !GLIB_CHECK_VERSION(2, 35, 0) g_type_init(); #endif #if !GLIB_CHECK_VERSION(2, 31, 0) g_thread_init(NULL); #endif full_seafdir = ccnet_expand_path (seafile_dir); fprintf (stderr, "seafile dir: %s\n", full_seafdir); log_dir = g_build_filename (full_seafdir, "logs", NULL); if (checkdir_with_mkdir (log_dir) < 0) { fprintf (stderr, "Failed to create logs directory.\n"); exit (1); } if (!debug_str) debug_str = g_getenv("SEADRIVE_DEBUG"); seafile_debug_set_flags_string (debug_str); if (logfile == NULL) logfile = g_build_filename (log_dir, "seadrive.log", NULL); if (seafile_log_init (logfile) < 0) { seaf_warning ("Failed to init log.\n"); exit (1); } pidfile = g_build_filename (full_seafdir, "seadrive.pid", NULL); if (write_pidfile (pidfile) < 0) { seaf_warning ("Seadrive is already running.\n"); exit (1); } #if defined(HAVE_BREAKPAD_SUPPORT) char *real_logdir = g_path_get_dirname (logfile); char *dump_dir = g_build_filename (real_logdir, "dumps", NULL); checkdir_with_mkdir(dump_dir); CBPWrapperExceptionHandler bp_exception_handler = newCBPWrapperExceptionHandler(dump_dir); g_free (real_logdir); g_free (dump_dir); #endif g_free (full_seafdir); g_free (log_dir); g_free (logfile); set_signal_handlers (seaf); #ifndef USE_GPL_CRYPTO seafile_curl_init(); #endif seaf = seafile_session_new (seafile_dir); if (!seaf) { seaf_warning ("Failed to create seafile session.\n"); exit (1); } if (mount_point) { seaf->mount_point = ccnet_expand_path (mount_point); } if (g_strcmp0 (language, "zh_cn") == 0) seaf->language = SEAF_LANG_ZH_CN; else if (g_strcmp0 (language, "fr_fr") == 0) seaf->language = SEAF_LANG_FR_FR; else if (g_strcmp0 (language, "de_de") == 0) seaf->language = SEAF_LANG_DE_DE; else seaf->language = SEAF_LANG_EN_US; seaf_message ("Starting SeaDrive client "SEAFILE_CLIENT_VERSION"\n"); #if defined(SEADRIVE_SOURCE_COMMIT_ID) seaf_message ("SeaDrive client source code version "SEADRIVE_SOURCE_COMMIT_ID"\n"); #endif seafile_session_prepare (seaf); if (config_file) { load_account_from_file (config_file); load_config_from_file (config_file); } /* Start seafile session thread. */ pthread_t tid; pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); if (pthread_create (&tid, &attr, seafile_session_thread, NULL) < 0) { seaf_warning ("Failed to create seafile session thread.\n"); exit (1); } if (start_searpc_server () < 0) { seaf_warning ("Failed to start searpc server.\n"); exit (1); } seaf_message ("rpc server started.\n"); fuse_main(args.argc, args.argv, &seadrive_fuse_ops, NULL); fuse_opt_free_args (&args); #ifndef USE_GPL_CRYPTO seafile_curl_deinit(); #endif return 0; } seadrive-fuse-3.0.13/src/seafile-config.c000066400000000000000000000145441476177674700202100ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ #include "common.h" #include "db.h" #include "seafile-config.h" gboolean seafile_session_config_exists (SeafileSession *session, const char *key) { char sql[256]; snprintf (sql, sizeof(sql), "SELECT 1 FROM Config WHERE key = '%s'", key); return sqlite_check_for_existence (session->config_db, sql); } static gboolean get_value (sqlite3_stmt *stmt, void *data) { char **p_value = data; *p_value = g_strdup((char *) sqlite3_column_text (stmt, 0)); /* Only one result. */ return FALSE; } static char * config_get_string (sqlite3 *config_db, const char *key) { char sql[256]; char *value = NULL; snprintf (sql, sizeof(sql), "SELECT value FROM Config WHERE key='%s';", key); if (sqlite_foreach_selected_row (config_db, sql, get_value, &value) < 0) return NULL; return value; } char * seafile_session_config_get_string (SeafileSession *session, const char *key) { return (config_get_string (session->config_db, key)); } int seafile_session_config_get_int (SeafileSession *session, const char *key, gboolean *exists) { char *value; int ret; value = config_get_string (session->config_db, key); if (!value) { if (exists) *exists = FALSE; return -1; } if (exists) *exists = TRUE; ret = atoi (value); g_free (value); return ret; } gint64 seafile_session_config_get_int64 (SeafileSession *session, const char *key, gboolean *exists) { char *value; gint64 ret; value = config_get_string (session->config_db, key); if (!value) { if (exists) *exists = FALSE; return -1; } if (exists) *exists = TRUE; ret = strtoll(value, NULL, 10); g_free (value); return ret; } gboolean seafile_session_config_get_bool (SeafileSession *session, const char *key) { char *value; gboolean ret = FALSE; value = config_get_string (session->config_db, key); if (g_strcmp0(value, "true") == 0) ret = TRUE; g_free (value); return ret; } int seafile_session_config_set_string (SeafileSession *session, const char *key, const char *value) { char sql[256]; sqlite3_snprintf (sizeof(sql), sql, "REPLACE INTO Config VALUES ('%q', '%q');", key, value); if (sqlite_query_exec (session->config_db, sql) < 0) return -1; if (g_strcmp0 (key, KEY_CLIENT_ID) == 0) { g_free (session->client_id); session->client_id = g_strdup(value); } if (g_strcmp0 (key, KEY_CLIENT_NAME) == 0) { g_free (session->client_name); session->client_name = g_strdup(value); } if (g_strcmp0(key, KEY_SYNC_EXTRA_TEMP_FILE) == 0) { if (g_strcmp0(value, "true") == 0) session->sync_extra_temp_file = TRUE; else session->sync_extra_temp_file = FALSE; } if (g_strcmp0(key, KEY_DISABLE_VERIFY_CERTIFICATE) == 0) { if (g_strcmp0(value, "true") == 0) session->disable_verify_certificate = TRUE; else session->disable_verify_certificate = FALSE; } if (g_strcmp0(key, KEY_USE_PROXY) == 0) { if (g_strcmp0(value, "true") == 0) session->use_http_proxy = TRUE; else session->use_http_proxy = FALSE; } if (g_strcmp0(key, KEY_PROXY_TYPE) == 0) { session->http_proxy_type = g_strcmp0(value, "none") == 0 ? NULL : g_strdup(value); } if (g_strcmp0(key, KEY_PROXY_ADDR) == 0) { session->http_proxy_addr = g_strdup(value); } if (g_strcmp0(key, KEY_PROXY_USERNAME) == 0) { session->http_proxy_username = g_strdup(value); } if (g_strcmp0(key, KEY_PROXY_PASSWORD) == 0) { session->http_proxy_password = g_strdup(value); } if (g_strcmp0 (key, KEY_ENABLE_AUTO_LOCK) == 0) { if (g_strcmp0 (value, "true") == 0) session->enable_auto_lock = TRUE; else session->enable_auto_lock = FALSE; } if (g_strcmp0(key, KEY_HIDE_WINDOWS_INCOMPATIBLE_PATH_NOTIFICATION) == 0) { if (g_strcmp0(value, "true") == 0) session->hide_windows_incompatible_path_notification = TRUE; else session->hide_windows_incompatible_path_notification = FALSE; } return 0; } int seafile_session_config_set_int (SeafileSession *session, const char *key, int value) { char sql[256]; sqlite3_snprintf (sizeof(sql), sql, "REPLACE INTO Config VALUES ('%q', %d);", key, value); if (sqlite_query_exec (session->config_db, sql) < 0) return -1; if (g_strcmp0(key, KEY_PROXY_PORT) == 0) { session->http_proxy_port = value; } if (g_strcmp0 (key, DELETE_CONFIRM_THRESHOLD) == 0) { session->delete_confirm_threshold = value; } return 0; } int seafile_session_config_set_int64 (SeafileSession *session, const char *key, gint64 value) { char sql[256]; sqlite3_snprintf (sizeof(sql), sql, "REPLACE INTO Config VALUES ('%q', %lld);", key, value); if (sqlite_query_exec (session->config_db, sql) < 0) return -1; return 0; } sqlite3 * seafile_session_config_open_db (const char *db_path) { sqlite3 *db; if (sqlite_open_db (db_path, &db) < 0) return NULL; /* * Values are stored in text. You should convert it * back to integer if needed when you read it from * db. */ char *sql = "CREATE TABLE IF NOT EXISTS Config (" "key TEXT PRIMARY KEY, " "value TEXT);"; sqlite_query_exec (db, sql); return db; } gboolean seafile_session_config_get_allow_repo_not_found_on_server(SeafileSession *session) { return seafile_session_config_get_bool (session, KEY_ALLOW_REPO_NOT_FOUND_ON_SERVER); } seadrive-fuse-3.0.13/src/seafile-config.h000066400000000000000000000056451476177674700202170ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ #ifndef SEAFILE_CONFIG_H #define SEAFILE_CONFIG_H #include "seafile-session.h" #include "db.h" #define KEY_CLIENT_ID "client_id" #define KEY_CLIENT_NAME "client_name" #define KEY_UPLOAD_LIMIT "upload_limit" #define KEY_DOWNLOAD_LIMIT "download_limit" #define KEY_ALLOW_REPO_NOT_FOUND_ON_SERVER "allow_repo_not_found_on_server" #define KEY_SYNC_EXTRA_TEMP_FILE "sync_extra_temp_file" /* Http sync settings. */ #define KEY_ENABLE_HTTP_SYNC "enable_http_sync" #define KEY_DISABLE_VERIFY_CERTIFICATE "disable_verify_certificate" /* Http sync proxy settings. */ #define KEY_USE_PROXY "use_proxy" #define KEY_PROXY_TYPE "proxy_type" #define KEY_PROXY_ADDR "proxy_addr" #define KEY_PROXY_PORT "proxy_port" #define KEY_PROXY_USERNAME "proxy_username" #define KEY_PROXY_PASSWORD "proxy_password" #define PROXY_TYPE_HTTP "http" #define PROXY_TYPE_SOCKS "socks" /* Cache cleaning settings. */ #define KEY_CACHE_SIZE_LIMIT "cache_size_limit" #define KEY_CLEAN_CACHE_INTERVAL "clean_cache_interval" #define KEY_ENABLE_AUTO_LOCK "enable_auto_lock" #define KEY_CURRENT_SESSION_ACCESS "current_session_access" #define DELETE_CONFIRM_THRESHOLD "delete_confirm_threshold" #define KEY_HIDE_WINDOWS_INCOMPATIBLE_PATH_NOTIFICATION "hide_windows_incompatible_path_notification" gboolean seafile_session_config_exists (SeafileSession *session, const char *key); /* * Returns: config value in string. The string should be freed by caller. */ char * seafile_session_config_get_string (SeafileSession *session, const char *key); /* * Returns: * If key exists, @exists will be set to TRUE and returns the value; * otherwise, @exists will be set to FALSE and returns -1. */ int seafile_session_config_get_int (SeafileSession *session, const char *key, gboolean *exists); gint64 seafile_session_config_get_int64 (SeafileSession *session, const char *key, gboolean *exists); /* * Returns: config value in boolean. Return FALSE if the value is not configured. */ gboolean seafile_session_config_get_bool (SeafileSession *session, const char *key); int seafile_session_config_set_string (SeafileSession *session, const char *key, const char *value); int seafile_session_config_set_int (SeafileSession *session, const char *key, int value); int seafile_session_config_set_int64 (SeafileSession *session, const char *key, gint64 value); gboolean seafile_session_config_get_allow_repo_not_found_on_server(SeafileSession *session); sqlite3 * seafile_session_config_open_db (const char *db_path); #endif seadrive-fuse-3.0.13/src/seafile-crypt.c000066400000000000000000000515431476177674700201040ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ #include "common.h" #include #include #include "utils.h" #include "seafile-crypt.h" #ifdef USE_GPL_CRYPTO #include #include #include #else #include #include #include #endif #include "password-hash.h" #include "log.h" /* The EVP_EncryptXXX and EVP_DecryptXXX series of functions have a weird choice of returned value. */ #define ENC_SUCCESS 1 #define ENC_FAILURE 0 #define DEC_SUCCESS 1 #define DEC_FAILURE 0 #define KEYGEN_ITERATION 1 << 19 #define KEYGEN_ITERATION2 1000 /* Should generate random salt for each repo. */ static unsigned char salt[8] = { 0xda, 0x90, 0x45, 0xc3, 0x06, 0xc7, 0xcc, 0x26 }; SeafileCrypt * seafile_crypt_new (int version, unsigned char *key, unsigned char *iv) { SeafileCrypt *crypt = g_new0 (SeafileCrypt, 1); crypt->version = version; if (version == 1) memcpy (crypt->key, key, 16); else memcpy (crypt->key, key, 32); memcpy (crypt->iv, iv, 16); return crypt; } int seafile_derive_key (const char *data_in, int in_len, int version, const char *repo_salt, unsigned char *key, unsigned char *iv) { #ifdef USE_GPL_CRYPTO unsigned char repo_salt_bin[32]; if (version == 4) hex_to_rawdata (repo_salt, repo_salt_bin, 32); switch (version) { case 1: seaf_warning ("Encrypted library version %d is not supported.\n", version); return -1; case 2: pbkdf2_hmac_sha256 (in_len, (const guchar *)data_in, KEYGEN_ITERATION2, sizeof(salt), salt, 32, key); pbkdf2_hmac_sha256 (32, (const guchar *)key, 10, sizeof(salt), salt, 16, iv); case 3: seaf_warning ("Encrypted library version %d is not supported.\n", version); return -1; case 4: pbkdf2_hmac_sha256 (in_len, (const guchar *)data_in, KEYGEN_ITERATION2, sizeof(repo_salt_bin), repo_salt_bin, 32, key); pbkdf2_hmac_sha256 (32, (const guchar *)key, 10, sizeof(repo_salt_bin), repo_salt_bin, 16, iv); default: seaf_warning ("Encrypted library version %d is not supported.\n", version); return -1; } return 0; #else if (version >= 3) { unsigned char repo_salt_bin[32]; hex_to_rawdata (repo_salt, repo_salt_bin, 32); PKCS5_PBKDF2_HMAC (data_in, in_len, repo_salt_bin, sizeof(repo_salt_bin), KEYGEN_ITERATION2, EVP_sha256(), 32, key); PKCS5_PBKDF2_HMAC ((char *)key, 32, repo_salt_bin, sizeof(repo_salt_bin), 10, EVP_sha256(), 16, iv); return 0; } else if (version == 2) { PKCS5_PBKDF2_HMAC (data_in, in_len, salt, sizeof(salt), KEYGEN_ITERATION2, EVP_sha256(), 32, key); PKCS5_PBKDF2_HMAC ((char *)key, 32, salt, sizeof(salt), 10, EVP_sha256(), 16, iv); return 0; } else if (version == 1) return EVP_BytesToKey (EVP_aes_128_cbc(), /* cipher mode */ EVP_sha1(), /* message digest */ salt, /* salt */ (unsigned char*)data_in, in_len, KEYGEN_ITERATION, /* iteration times */ key, /* the derived key */ iv); /* IV, initial vector */ else return EVP_BytesToKey (EVP_aes_128_ecb(), /* cipher mode */ EVP_sha1(), /* message digest */ NULL, /* salt */ (unsigned char*)data_in, in_len, 3, /* iteration times */ key, /* the derived key */ iv); /* IV, initial vector */ #endif } int seafile_generate_random_key (const char *passwd, char *random_key, int version, const char *repo_salt) { SeafileCrypt *crypt; unsigned char secret_key[32], *rand_key; int outlen; unsigned char key[32], iv[16]; #ifdef USE_GPL_CRYPTO if (gnutls_rnd (GNUTLS_RND_RANDOM, secret_key, sizeof(secret_key)) < 0) { seaf_warning ("Failed to generate secret key for repo encryption.\n"); return -1; } #else if (RAND_bytes (secret_key, sizeof(secret_key)) != 1) { seaf_warning ("Failed to generate secret key for repo encryption " "with RAND_bytes(), use RAND_pseudo_bytes().\n"); return -1; } #endif seafile_derive_key (passwd, strlen(passwd), version, repo_salt, key, iv); crypt = seafile_crypt_new (version, key, iv); seafile_encrypt ((char **)&rand_key, &outlen, (char *)secret_key, sizeof(secret_key), crypt); rawdata_to_hex (rand_key, random_key, 48); g_free (crypt); g_free (rand_key); return 0; } void seafile_generate_magic (int version, const char *repo_id, const char *passwd, const char *repo_salt, char *magic) { GString *buf = g_string_new (NULL); unsigned char key[32], iv[16]; /* Compute a "magic" string from repo_id and passwd. * This is used to verify the password given by user before decrypting * data. */ g_string_append_printf (buf, "%s%s", repo_id, passwd); seafile_derive_key (buf->str, buf->len, version, repo_salt, key, iv); g_string_free (buf, TRUE); rawdata_to_hex (key, magic, 32); } void seafile_generate_pwd_hash (int version, const char *repo_id, const char *passwd, const char *repo_salt, const char *algo, const char *params_str, char *pwd_hash) { GString *buf = g_string_new (NULL); unsigned char key[32]; /* Compute a "pwd_hash" string from repo_id and passwd. * This is used to verify the password given by user before decrypting * data. */ g_string_append_printf (buf, "%s%s", repo_id, passwd); if (version <= 2) { // use fixed repo salt char fixed_salt[64] = {0}; rawdata_to_hex(salt, fixed_salt, 8); pwd_hash_derive_key (buf->str, buf->len, fixed_salt, algo, params_str, key); } else { pwd_hash_derive_key (buf->str, buf->len, repo_salt, algo, params_str, key); } g_string_free (buf, TRUE); rawdata_to_hex (key, pwd_hash, 32); } int seafile_verify_repo_passwd (const char *repo_id, const char *passwd, const char *magic, int version, const char *repo_salt) { GString *buf = g_string_new (NULL); unsigned char key[32], iv[16]; char hex[65]; if (version != 1 && version != 2 && version != 3 && version != 4) { seaf_warning ("Unsupported enc_version %d.\n", version); return -1; } /* Recompute the magic and compare it with the one comes with the repo. */ g_string_append_printf (buf, "%s%s", repo_id, passwd); seafile_derive_key (buf->str, buf->len, version, repo_salt, key, iv); g_string_free (buf, TRUE); if (version >= 2) rawdata_to_hex (key, hex, 32); else rawdata_to_hex (key, hex, 16); if (g_strcmp0 (hex, magic) == 0) return 0; else return -1; } int seafile_pwd_hash_verify_repo_passwd (int version, const char *repo_id, const char *passwd, const char *repo_salt, const char *pwd_hash, const char *algo, const char *params_str) { GString *buf = g_string_new (NULL); unsigned char key[32]; char hex[65]; g_string_append_printf (buf, "%s%s", repo_id, passwd); if (version <= 2) { // use fixed repo salt char fixed_salt[64] = {0}; rawdata_to_hex(salt, fixed_salt, 8); pwd_hash_derive_key (buf->str, buf->len, fixed_salt, algo, params_str, key); } else { pwd_hash_derive_key (buf->str, buf->len, repo_salt, algo, params_str, key); } g_string_free (buf, TRUE); rawdata_to_hex (key, hex, 32); if (g_strcmp0 (hex, pwd_hash) == 0) return 0; else return -1; } int seafile_decrypt_repo_enc_key (int enc_version, const char *passwd, const char *random_key, const char *repo_salt, unsigned char *key_out, unsigned char *iv_out) { unsigned char key[32], iv[16]; seafile_derive_key (passwd, strlen(passwd), enc_version, repo_salt, key, iv); if (enc_version == 1) { memcpy (key_out, key, 16); memcpy (iv_out, iv, 16); return 0; } else if (enc_version >= 2) { unsigned char enc_random_key[48], *dec_random_key; int outlen; SeafileCrypt *crypt; if (random_key == NULL || random_key[0] == 0) { seaf_warning ("Empty random key.\n"); return -1; } hex_to_rawdata (random_key, enc_random_key, 48); crypt = seafile_crypt_new (enc_version, key, iv); if (seafile_decrypt ((char **)&dec_random_key, &outlen, (char *)enc_random_key, 48, crypt) < 0) { seaf_warning ("Failed to decrypt random key.\n"); g_free (crypt); return -1; } g_free (crypt); seafile_derive_key ((char *)dec_random_key, 32, enc_version, repo_salt, key, iv); memcpy (key_out, key, 32); memcpy (iv_out, iv, 16); g_free (dec_random_key); return 0; } return -1; } int seafile_update_random_key (const char *old_passwd, const char *old_random_key, const char *new_passwd, char *new_random_key, int enc_version, const char *repo_salt) { unsigned char key[32], iv[16]; unsigned char random_key_raw[48], *secret_key, *new_random_key_raw; int secret_key_len, random_key_len; SeafileCrypt *crypt; /* First, use old_passwd to decrypt secret key from old_random_key. */ seafile_derive_key (old_passwd, strlen(old_passwd), enc_version, repo_salt, key, iv); hex_to_rawdata (old_random_key, random_key_raw, 48); crypt = seafile_crypt_new (enc_version, key, iv); if (seafile_decrypt ((char **)&secret_key, &secret_key_len, (char *)random_key_raw, 48, crypt) < 0) { seaf_warning ("Failed to decrypt random key.\n"); g_free (crypt); return -1; } g_free (crypt); /* Second, use new_passwd to encrypt secret key. */ seafile_derive_key (new_passwd, strlen(new_passwd), enc_version, repo_salt, key, iv); crypt = seafile_crypt_new (enc_version, key, iv); seafile_encrypt ((char **)&new_random_key_raw, &random_key_len, (char *)secret_key, secret_key_len, crypt); rawdata_to_hex (new_random_key_raw, new_random_key, 48); g_free (secret_key); g_free (new_random_key_raw); g_free (crypt); return 0; } #ifdef USE_GPL_CRYPTO int seafile_encrypt (char **data_out, int *out_len, const char *data_in, const int in_len, SeafileCrypt *crypt) { char *buf = NULL, *enc_buf = NULL; int buf_size, remain; guint8 padding; gnutls_cipher_hd_t handle; gnutls_datum_t key, iv; int rc, ret = 0; buf_size = BLK_SIZE * ((in_len / BLK_SIZE) + 1); remain = buf_size - in_len; buf = g_new (char, buf_size); memcpy (buf, data_in, in_len); padding = (guint8)remain; memset (buf + in_len, padding, remain); key.data = crypt->key; key.size = sizeof(crypt->key); iv.data = crypt->iv; iv.size = sizeof(crypt->iv); if (crypt->version == 1 || crypt->version == 3) { seaf_warning ("Encrypted library version % is not supported.\n", crypt->version); ret = -1; goto out; } else { rc = gnutls_cipher_init (&handle, GNUTLS_CIPHER_AES_256_CBC, &key, &iv); if (rc < 0) { seaf_warning ("Failed to init cipher: %s\n", gnutls_strerror(rc)); ret = -1; goto out; } } enc_buf = g_new (char, buf_size); rc = gnutls_cipher_encrypt2 (handle, buf, buf_size, enc_buf, buf_size); if (rc < 0) { seaf_warning ("Failed to encrypt: %s\n", gnutls_strerror(rc)); ret = -1; gnutls_cipher_deinit (handle); goto out; } gnutls_cipher_deinit (handle); out: g_free (buf); if (ret < 0) { g_free (enc_buf); *data_out = NULL; *out_len = -1; } else { *data_out = enc_buf; *out_len = buf_size; } return ret; } int seafile_decrypt (char **data_out, int *out_len, const char *data_in, const int in_len, SeafileCrypt *crypt) { char *dec_buf = NULL; gnutls_cipher_hd_t handle; gnutls_datum_t key, iv; int rc, ret = 0; guint8 padding; int remain; if (in_len <= 0 || in_len % BLK_SIZE != 0) { seaf_warning ("Invalid encrypted buffer size.\n"); return -1; } key.data = crypt->key; key.size = sizeof(crypt->key); iv.data = crypt->iv; iv.size = sizeof(crypt->iv); if (crypt->version == 1 || crypt->version == 3) { seaf_warning ("Encrypted library version %d is not supported.\n", crypt->version); ret = -1; goto out; } else { rc = gnutls_cipher_init (&handle, GNUTLS_CIPHER_AES_256_CBC, &key, &iv); if (rc < 0) { seaf_warning ("Failed to init cipher: %s\n", gnutls_strerror(rc)); ret = -1; goto out; } } dec_buf = g_new (char, in_len); rc = gnutls_cipher_decrypt2 (handle, data_in, in_len, dec_buf, in_len); if (rc < 0) { seaf_warning ("Failed to decrypt data: %s\n", gnutls_strerror(rc)); ret = -1; gnutls_cipher_deinit (handle); goto out; } padding = dec_buf[in_len - 1]; remain = padding; *out_len = (in_len - remain); *data_out = dec_buf; gnutls_cipher_deinit (handle); out: if (ret < 0) { g_free (dec_buf); *data_out = NULL; *out_len = -1; } return ret; } #else int seafile_encrypt (char **data_out, int *out_len, const char *data_in, const int in_len, SeafileCrypt *crypt) { *data_out = NULL; *out_len = -1; /* check validation */ if ( data_in == NULL || in_len <= 0 || crypt == NULL) { seaf_warning ("Invalid params.\n"); return -1; } EVP_CIPHER_CTX *ctx; int ret; int blks; /* Prepare CTX for encryption. */ ctx = EVP_CIPHER_CTX_new (); if (crypt->version == 1) ret = EVP_EncryptInit_ex (ctx, EVP_aes_128_cbc(), /* cipher mode */ NULL, /* engine, NULL for default */ crypt->key, /* derived key */ crypt->iv); /* initial vector */ else if (crypt->version == 3) ret = EVP_EncryptInit_ex (ctx, EVP_aes_128_ecb(), /* cipher mode */ NULL, /* engine, NULL for default */ crypt->key, /* derived key */ crypt->iv); /* initial vector */ else ret = EVP_EncryptInit_ex (ctx, EVP_aes_256_cbc(), /* cipher mode */ NULL, /* engine, NULL for default */ crypt->key, /* derived key */ crypt->iv); /* initial vector */ if (ret == ENC_FAILURE) { EVP_CIPHER_CTX_free (ctx); return -1; } /* Allocating output buffer. */ /* For EVP symmetric encryption, padding is always used __even if__ data size is a multiple of block size, in which case the padding length is the block size. so we have the following: */ blks = (in_len / BLK_SIZE) + 1; *data_out = (char *)g_malloc (blks * BLK_SIZE); if (*data_out == NULL) { seaf_warning ("failed to allocate the output buffer.\n"); goto enc_error; } int update_len, final_len; /* Do the encryption. */ ret = EVP_EncryptUpdate (ctx, (unsigned char*)*data_out, &update_len, (unsigned char*)data_in, in_len); if (ret == ENC_FAILURE) goto enc_error; /* Finish the possible partial block. */ ret = EVP_EncryptFinal_ex (ctx, (unsigned char*)*data_out + update_len, &final_len); *out_len = update_len + final_len; /* out_len should be equal to the allocated buffer size. */ if (ret == ENC_FAILURE || *out_len != (blks * BLK_SIZE)) goto enc_error; EVP_CIPHER_CTX_free (ctx); return 0; enc_error: EVP_CIPHER_CTX_free (ctx); *out_len = -1; if (*data_out != NULL) g_free (*data_out); *data_out = NULL; return -1; } int seafile_decrypt (char **data_out, int *out_len, const char *data_in, const int in_len, SeafileCrypt *crypt) { *data_out = NULL; *out_len = -1; /* Check validation. Because padding is always used, in_len must * be a multiple of BLK_SIZE */ if ( data_in == NULL || in_len <= 0 || in_len % BLK_SIZE != 0 || crypt == NULL) { seaf_warning ("Invalid param(s).\n"); return -1; } EVP_CIPHER_CTX *ctx; int ret; /* Prepare CTX for decryption. */ ctx = EVP_CIPHER_CTX_new (); if (crypt->version == 1) ret = EVP_DecryptInit_ex (ctx, EVP_aes_128_cbc(), /* cipher mode */ NULL, /* engine, NULL for default */ crypt->key, /* derived key */ crypt->iv); /* initial vector */ else if (crypt->version == 3) ret = EVP_DecryptInit_ex (ctx, EVP_aes_128_ecb(), /* cipher mode */ NULL, /* engine, NULL for default */ crypt->key, /* derived key */ crypt->iv); /* initial vector */ else ret = EVP_DecryptInit_ex (ctx, EVP_aes_256_cbc(), /* cipher mode */ NULL, /* engine, NULL for default */ crypt->key, /* derived key */ crypt->iv); /* initial vector */ if (ret == DEC_FAILURE) { EVP_CIPHER_CTX_free (ctx); return -1; } /* Allocating output buffer. */ *data_out = (char *)g_malloc (in_len); if (*data_out == NULL) { seaf_warning ("failed to allocate the output buffer.\n"); goto dec_error; } int update_len, final_len; /* Do the decryption. */ ret = EVP_DecryptUpdate (ctx, (unsigned char*)*data_out, &update_len, (unsigned char*)data_in, in_len); if (ret == DEC_FAILURE) goto dec_error; /* Finish the possible partial block. */ ret = EVP_DecryptFinal_ex (ctx, (unsigned char*)*data_out + update_len, &final_len); *out_len = update_len + final_len; /* out_len should be smaller than in_len. */ if (ret == DEC_FAILURE || *out_len > in_len) goto dec_error; EVP_CIPHER_CTX_free (ctx); return 0; dec_error: EVP_CIPHER_CTX_free (ctx); *out_len = -1; if (*data_out != NULL) g_free (*data_out); *data_out = NULL; return -1; } #endif /* USE_GPL_CRYPTO */ seadrive-fuse-3.0.13/src/seafile-crypt.h000066400000000000000000000074511476177674700201100ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Description: The function pair "seafile_encrypt/seafile_decrypt" are used to encrypt/decrypt data in the seafile system, using AES 128 bit ecb algorithm provided by openssl. */ #ifndef _SEAFILE_CRYPT_H #define _SEAFILE_CRYPT_H #include #include /* Block size, in bytes. For AES it can only be 16 bytes. */ #define BLK_SIZE 16 #define ENCRYPT_BLK_SIZE BLK_SIZE struct SeafileCrypt { int version; unsigned char key[32]; /* set when enc_version >= 1 */ unsigned char iv[16]; }; typedef struct SeafileCrypt SeafileCrypt; SeafileCrypt * seafile_crypt_new (int version, unsigned char *key, unsigned char *iv); /* Derive key and iv used by AES encryption from @data_in. key and iv is 16 bytes for version 1, and 32 bytes for version 2. @data_out: pointer to the output of the encrpyted/decrypted data, whose content must be freed by g_free when not used. @out_len: pointer to length of output, in bytes @data_in: address of input buffer @in_len: length of data to be encrpyted/decrypted, in bytes @crypt: container of crypto info. RETURN VALUES: On success, 0 is returned, and the encrpyted/decrypted data is in *data_out, with out_len set to its length. On failure, -1 is returned and *data_out is set to NULL, with out_len set to -1; */ int seafile_derive_key (const char *data_in, int in_len, int version, const char *repo_salt, unsigned char *key, unsigned char *iv); /* * Generate the real key used to encrypt data. * The key 32 bytes long and encrpted with @passwd. */ int seafile_generate_random_key (const char *passwd, char *random_key, int version, const char *repo_salt); void seafile_generate_magic (int version, const char *repo_id, const char *passwd, const char *repo_salt, char *magic); void seafile_generate_pwd_hash (int version, const char *repo_id, const char *passwd, const char *repo_salt, const char *algo, const char *params_str, char *pwd_hash); int seafile_verify_repo_passwd (const char *repo_id, const char *passwd, const char *magic, int version, const char *repo_salt); int seafile_pwd_hash_verify_repo_passwd (int version, const char *repo_id, const char *passwd, const char *repo_salt, const char *pwd_hash, const char *algo, const char *params_str); int seafile_decrypt_repo_enc_key (int enc_version, const char *passwd, const char *random_key, const char *repo_salt, unsigned char *key_out, unsigned char *iv_out); int seafile_update_random_key (const char *old_passwd, const char *old_random_key, const char *new_passwd, char *new_random_key, int version, const char *repo_salt); int seafile_encrypt (char **data_out, int *out_len, const char *data_in, const int in_len, SeafileCrypt *crypt); int seafile_decrypt (char **data_out, int *out_len, const char *data_in, const int in_len, SeafileCrypt *crypt); #endif /* _SEAFILE_CRYPT_H */ seadrive-fuse-3.0.13/src/seafile-error.h000066400000000000000000000013321476177674700200700ustar00rootroot00000000000000#ifndef SEAFILE_ERROR_H #define SEAFILE_ERROR_H #define SEAF_ERR_GENERAL 500 #define SEAF_ERR_BAD_REPO 501 #define SEAF_ERR_BAD_COMMIT 502 #define SEAF_ERR_BAD_ARGS 503 #define SEAF_ERR_INTERNAL 504 #define SEAF_ERR_BAD_FILE 505 #define SEAF_ERR_BAD_RELAY 506 #define SEAF_ERR_LIST_COMMITS 507 #define SEAF_ERR_REPO_AUTH 508 #define SEAF_ERR_GC_NOT_STARTED 509 #define SEAF_ERR_MONITOR_NOT_CONNECTED 510 #define SEAF_ERR_BAD_DIR_ID 511 #define SEAF_ERR_NO_WORKTREE 512 #define SEAF_ERR_BAD_PEER_ID 513 #define SEAF_ERR_REPO_LOCKED 514 #define SEAF_ERR_DIR_MISSING 515 #define SEAF_ERR_PATH_NO_EXIST 516 /* the dir or file pointed by this path not exists */ #endif seadrive-fuse-3.0.13/src/seafile-session.c000066400000000000000000000373351476177674700204310ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ #include "common.h" #include #include #include #include #include #include #include #include #if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) #include #include #include #else #include #endif #include #include "utils.h" #include "seafile-session.h" #include "seafile-config.h" #include "log.h" #define MAX_THREADS 50 enum { REPO_COMMITTED, REPO_FETCHED, REPO_UPLOADED, REPO_HTTP_FETCHED, REPO_HTTP_UPLOADED, REPO_WORKTREE_CHECKED, LAST_SIGNAL }; int signals[LAST_SIGNAL]; G_DEFINE_TYPE (SeafileSession, seafile_session, G_TYPE_OBJECT); static void seafile_session_class_init (SeafileSessionClass *klass) { signals[REPO_COMMITTED] = g_signal_new ("repo-committed", SEAFILE_TYPE_SESSION, G_SIGNAL_RUN_LAST, 0, /* no class singal handler */ NULL, NULL, /* no accumulator */ g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER); signals[REPO_FETCHED] = g_signal_new ("repo-fetched", SEAFILE_TYPE_SESSION, G_SIGNAL_RUN_LAST, 0, /* no class singal handler */ NULL, NULL, /* no accumulator */ g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER); signals[REPO_UPLOADED] = g_signal_new ("repo-uploaded", SEAFILE_TYPE_SESSION, G_SIGNAL_RUN_LAST, 0, /* no class singal handler */ NULL, NULL, /* no accumulator */ g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER); signals[REPO_HTTP_FETCHED] = g_signal_new ("repo-http-fetched", SEAFILE_TYPE_SESSION, G_SIGNAL_RUN_LAST, 0, /* no class singal handler */ NULL, NULL, /* no accumulator */ g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER); signals[REPO_HTTP_UPLOADED] = g_signal_new ("repo-http-uploaded", SEAFILE_TYPE_SESSION, G_SIGNAL_RUN_LAST, 0, /* no class singal handler */ NULL, NULL, /* no accumulator */ g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER); } static int create_deleted_store_dirs (const char *deleted_store) { char *commits = NULL, *fs = NULL, *blocks = NULL; int ret = 0; if (checkdir_with_mkdir (deleted_store) < 0) { seaf_warning ("Directory %s does not exist and is unable to create\n", deleted_store); return -1; } commits = g_build_filename (deleted_store, "commits", NULL); if (checkdir_with_mkdir (commits) < 0) { seaf_warning ("Directory %s does not exist and is unable to create\n", commits); ret = -1; goto out; } fs = g_build_filename (deleted_store, "fs", NULL); if (checkdir_with_mkdir (fs) < 0) { seaf_warning ("Directory %s does not exist and is unable to create\n", fs); ret = -1; goto out; } blocks = g_build_filename (deleted_store, "blocks", NULL); if (checkdir_with_mkdir (blocks) < 0) { seaf_warning ("Directory %s does not exist and is unable to create\n", blocks); ret = -1; goto out; } out: g_free (commits); g_free (fs); g_free (blocks); return ret; } SeafileSession * seafile_session_new(const char *seafile_dir) { char *abs_seafile_dir; char *tmp_file_dir; char *db_path; char *deleted_store; sqlite3 *config_db; SeafileSession *session = NULL; abs_seafile_dir = ccnet_expand_path (seafile_dir); tmp_file_dir = g_build_filename (abs_seafile_dir, "tmpfiles", NULL); db_path = g_build_filename (abs_seafile_dir, "config.db", NULL); deleted_store = g_build_filename (abs_seafile_dir, "deleted_store", NULL); if (checkdir_with_mkdir (abs_seafile_dir) < 0) { seaf_warning ("Config dir %s does not exist and is unable to create\n", abs_seafile_dir); goto onerror; } if (checkdir_with_mkdir (tmp_file_dir) < 0) { seaf_warning ("Temp file dir %s does not exist and is unable to create\n", tmp_file_dir); goto onerror; } if (create_deleted_store_dirs (deleted_store) < 0) goto onerror; config_db = seafile_session_config_open_db (db_path); if (!config_db) { seaf_warning ("Failed to open config db.\n"); goto onerror; } g_free (db_path); session = g_object_new (SEAFILE_TYPE_SESSION, NULL); session->ev_base = event_base_new (); session->seaf_dir = abs_seafile_dir; session->tmp_file_dir = tmp_file_dir; session->config_db = config_db; session->deleted_store = deleted_store; session->fs_mgr = seaf_fs_manager_new (session, abs_seafile_dir); if (!session->fs_mgr) goto onerror; session->block_mgr = seaf_block_manager_new (session, abs_seafile_dir); if (!session->block_mgr) goto onerror; session->commit_mgr = seaf_commit_manager_new (session); if (!session->commit_mgr) goto onerror; session->repo_mgr = seaf_repo_manager_new (session); if (!session->repo_mgr) goto onerror; session->branch_mgr = seaf_branch_manager_new (session); if (!session->branch_mgr) goto onerror; session->sync_mgr = seaf_sync_manager_new (session); if (!session->sync_mgr) goto onerror; session->http_tx_mgr = http_tx_manager_new (session); if (!session->http_tx_mgr) goto onerror; session->filelock_mgr = seaf_filelock_manager_new (session); if (!session->filelock_mgr) goto onerror; session->journal_mgr = journal_manager_new (session); if (!session->journal_mgr) goto onerror; session->file_cache_mgr = file_cache_mgr_new (abs_seafile_dir); if (!session->file_cache_mgr) goto onerror; session->mq_mgr = mq_mgr_new (); session->job_mgr = seaf_job_manager_new (session, MAX_THREADS); #ifdef COMPILE_WS session->notif_mgr = seaf_notif_manager_new (session); #endif return session; onerror: free (abs_seafile_dir); g_free (tmp_file_dir); g_free (db_path); g_free (deleted_store); g_free (session); return NULL; } static void seafile_session_init (SeafileSession *session) { } static void load_system_proxy (SeafileSession *session) { char *system_proxy_txt = g_build_filename (seaf->seaf_dir, "system-proxy.txt", NULL); json_t *json = NULL; if (!g_file_test (system_proxy_txt, G_FILE_TEST_EXISTS)) { seaf_warning ("Can't load system proxy: file %s doesn't exist\n", system_proxy_txt); goto out; } json_error_t jerror; json = json_load_file(system_proxy_txt, 0, &jerror); if (!json) { if (strlen(jerror.text) > 0) seaf_warning ("Failed to load system proxy information: %s.\n", jerror.text); else seaf_warning ("Failed to load system proxy information\n"); goto out; } const char *type; type = json_object_get_string_member (json, "type"); if (!type) { seaf_warning ("Failed to load system proxy information: proxy type missing\n"); goto out; } if (strcmp(type, "none") != 0 && strcmp(type, "socks") != 0 && strcmp(type, "http") != 0) { seaf_warning ("Failed to load system proxy information: invalid proxy type %s\n", type); goto out; } if (g_strcmp0(type, "none") == 0) { goto out; } session->http_proxy_type = g_strdup(type); session->http_proxy_addr = g_strdup(json_object_get_string_member (json, "addr")); session->http_proxy_port = json_object_get_int_member (json, "port"); session->http_proxy_username = g_strdup(json_object_get_string_member (json, "username")); session->http_proxy_password = g_strdup(json_object_get_string_member (json, "password")); out: g_free (system_proxy_txt); if (json) json_decref(json); } static char * generate_client_id () { char *uuid = gen_uuid(); unsigned char buf[20]; char sha1[41]; calculate_sha1 (buf, uuid, 20); rawdata_to_hex (buf, sha1, 20); g_free (uuid); return g_strdup(sha1); } #define OFFICE_LOCK_PATTERN "~\\$(.+)$" #define LIBRE_OFFICE_LOCK_PATTERN "\\.~lock\\.(.+)#$" #define WPS_LOCK_PATTERN "\\.~(.+)$" #define MAX_DELETED_FILES_NUM 500 void seafile_session_prepare (SeafileSession *session) { /* load config */ session->client_id = seafile_session_config_get_string (session, KEY_CLIENT_ID); if (!session->client_id) { session->client_id = generate_client_id(); /*seafile_session_config_set_string (session, KEY_CLIENT_ID, session->client_id);*/ } session->client_name = seafile_session_config_get_string (session, KEY_CLIENT_NAME); if (!session->client_name) { session->client_name = g_strdup("unknown"); } session->sync_extra_temp_file = seafile_session_config_get_bool (session, KEY_SYNC_EXTRA_TEMP_FILE); /* Enable http sync by default. */ session->enable_http_sync = TRUE; session->disable_verify_certificate = seafile_session_config_get_bool (session, KEY_DISABLE_VERIFY_CERTIFICATE); session->use_http_proxy = seafile_session_config_get_bool(session, KEY_USE_PROXY); gboolean use_system_proxy = seafile_session_config_get_bool(session, "use_system_proxy"); if (use_system_proxy) { load_system_proxy(session); } else { session->http_proxy_type = seafile_session_config_get_string(session, KEY_PROXY_TYPE); session->http_proxy_addr = seafile_session_config_get_string(session, KEY_PROXY_ADDR); session->http_proxy_port = seafile_session_config_get_int(session, KEY_PROXY_PORT, NULL); session->http_proxy_username = seafile_session_config_get_string(session, KEY_PROXY_USERNAME); session->http_proxy_password = seafile_session_config_get_string(session, KEY_PROXY_PASSWORD); } session->enable_auto_lock = TRUE; if (seafile_session_config_exists (session, KEY_ENABLE_AUTO_LOCK)) { session->enable_auto_lock = seafile_session_config_get_bool (session, KEY_ENABLE_AUTO_LOCK); } GError *error = NULL; session->office_lock_file_regex = g_regex_new (OFFICE_LOCK_PATTERN, 0, 0, &error); if (error) { seaf_warning ("Failed to init office lock file pattern: %s.\n", error->message); g_regex_unref (session->office_lock_file_regex); session->office_lock_file_regex = NULL; g_clear_error (&error); } session->libre_office_lock_file_regex = g_regex_new (LIBRE_OFFICE_LOCK_PATTERN, 0, 0, &error); if (error) { seaf_warning ("Failed to init libre office lock file pattern: %s.\n", error->message); g_regex_unref (session->libre_office_lock_file_regex); session->libre_office_lock_file_regex = NULL; g_clear_error (&error); } session->wps_lock_file_regex = g_regex_new (WPS_LOCK_PATTERN, 0, 0, &error); if (error) { seaf_warning ("Failed to init wps lock file pattern: %s.\n", error->message); g_regex_unref (session->wps_lock_file_regex); session->wps_lock_file_regex = NULL; g_clear_error (&error); } session->delete_confirm_threshold = seafile_session_config_get_int(session, DELETE_CONFIRM_THRESHOLD, NULL); if (session->delete_confirm_threshold <= 0) { session->delete_confirm_threshold = MAX_DELETED_FILES_NUM; } session->hide_windows_incompatible_path_notification = seafile_session_config_get_bool (session, KEY_HIDE_WINDOWS_INCOMPATIBLE_PATH_NOTIFICATION); seaf_commit_manager_init (session->commit_mgr); seaf_fs_manager_init (session->fs_mgr); seaf_branch_manager_init (session->branch_mgr); seaf_filelock_manager_init (session->filelock_mgr); seaf_repo_manager_init (session->repo_mgr); file_cache_mgr_init (session->file_cache_mgr); seaf_sync_manager_init (session->sync_mgr); mq_mgr_init (session->mq_mgr); } static gboolean is_repo_store_in_use (const char *repo_id) { if (seaf_repo_manager_repo_exists (seaf->repo_mgr, repo_id)) return TRUE; return FALSE; } static void cleanup_unused_repo_stores (const char *type) { char *top_store_dir; const char *repo_id; top_store_dir = g_build_filename (seaf->seaf_dir, "storage", type, NULL); GError *error = NULL; GDir *dir = g_dir_open (top_store_dir, 0, &error); if (!dir) { seaf_warning ("Failed to open store dir %s: %s.\n", top_store_dir, error->message); g_free (top_store_dir); return; } while ((repo_id = g_dir_read_name(dir)) != NULL) { if (!is_repo_store_in_use (repo_id)) { seaf_message ("Moving %s for deleted repo %s.\n", type, repo_id); seaf_repo_manager_move_repo_store (seaf->repo_mgr, type, repo_id); } } g_free (top_store_dir); g_dir_close (dir); } static void * on_start_cleanup_job (void *vdata) { cleanup_unused_repo_stores ("commits"); cleanup_unused_repo_stores ("fs"); cleanup_unused_repo_stores ("blocks"); return vdata; } static void cleanup_job_done (void *vdata) { SeafileSession *session = vdata; if (http_tx_manager_start (session->http_tx_mgr) < 0) { g_error ("Failed to start http transfer manager.\n"); return; } if (seaf_sync_manager_start (session->sync_mgr) < 0) { g_error ("Failed to start sync manager.\n"); return; } if (seaf_repo_manager_start (session->repo_mgr) < 0) { g_error ("Failed to start repo manager.\n"); return; } if (seaf_filelock_manager_start (session->filelock_mgr) < 0) { g_error ("Failed to start filelock manager.\n"); return; } file_cache_mgr_start (session->file_cache_mgr); /* The system is up and running. */ session->started = TRUE; } static void on_start_cleanup (SeafileSession *session) { seaf_job_manager_schedule_job (seaf->job_mgr, on_start_cleanup_job, cleanup_job_done, session); } void seafile_session_start (SeafileSession *session) { /* Finish cleanup task before anything is run. */ on_start_cleanup (session); event_base_loop (session->ev_base, 0); } int seafile_session_unmount (SeafileSession *session) { /* On Linux and Mac OS, the mount point is not unmounted. The GUI has to * unmount the mount point before starting seadrive on next start up. */ GList *accounts = NULL, *ptr; SeafAccount *account; accounts = seaf_repo_manager_get_account_list (seaf->repo_mgr); if (!accounts) { return 0; } for (ptr = accounts; ptr; ptr = ptr->next) { account = ptr->data; seaf_repo_manager_flush_account_repo_journals (session->repo_mgr, account->server, account->username); } g_list_free_full (accounts, (GDestroyNotify)seaf_account_free); return 0; } seadrive-fuse-3.0.13/src/seafile-session.h000066400000000000000000000067221476177674700204320ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ #ifndef SEAFILE_SESSION_H #define SEAFILE_SESSION_H #include #include "job-mgr.h" #include "db.h" #include "block-mgr.h" #include "fs-mgr.h" #include "commit-mgr.h" #include "branch-mgr.h" #include "repo-mgr.h" #include "sync-mgr.h" #include "http-tx-mgr.h" #include "filelock-mgr.h" #include "file-cache-mgr.h" #include "mq-mgr.h" #include "notif-mgr.h" #define SEAFILE_TYPE_SESSION (seafile_session_get_type ()) #define SEAFILE_SESSION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SEAFILE_TYPE_SESSION, SeafileSession)) #define SEAFILE_IS_SESSION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SEAFILE_TYPE_SESSION)) #define SEAFILE_SESSION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SEAFILE_TYPE_SESSION, SeafileSessionClass)) #define SEAFILE_IS_SESSION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SEAFILE_TYPE_SESSION)) #define SEAFILE_SESSION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SEAFILE_TYPE_SESSION, SeafileSessionClass)) typedef struct _SeafileSession SeafileSession; typedef struct _SeafileSessionClass SeafileSessionClass; struct event_base; typedef enum SeafLang { SEAF_LANG_EN_US = 0, SEAF_LANG_ZH_CN, SEAF_LANG_FR_FR, SEAF_LANG_DE_DE, } SeafLang; struct _SeafileSession { GObject parent_instance; struct event_base *ev_base; char *client_id; char *client_name; char *seaf_dir; char *tmp_file_dir; sqlite3 *config_db; char *deleted_store; char *rpc_socket_path; char *mount_point; SeafBlockManager *block_mgr; SeafFSManager *fs_mgr; SeafCommitManager *commit_mgr; SeafBranchManager *branch_mgr; SeafRepoManager *repo_mgr; SeafSyncManager *sync_mgr; SeafNotifManager *notif_mgr; SeafJobManager *job_mgr; HttpTxManager *http_tx_mgr; SeafFilelockManager *filelock_mgr; FileCacheMgr *file_cache_mgr; JournalManager *journal_mgr; MqMgr *mq_mgr; /* Set after all components are up and running. */ gboolean started; gboolean sync_extra_temp_file; gboolean enable_http_sync; gboolean disable_verify_certificate; gboolean use_http_proxy; char *http_proxy_type; char *http_proxy_addr; int http_proxy_port; char *http_proxy_username; char *http_proxy_password; gboolean hide_windows_incompatible_path_notification; gboolean enable_auto_lock; GRegex *office_lock_file_regex; GRegex *libre_office_lock_file_regex; GRegex *wps_lock_file_regex; gint64 last_check_repo_list_time; gint64 last_access_fs_time; int delete_confirm_threshold; SeafLang language; }; struct _SeafileSessionClass { GObjectClass parent_class; }; extern SeafileSession *seaf; SeafileSession * seafile_session_new(const char *seafile_dir); void seafile_session_prepare (SeafileSession *session); void seafile_session_start (SeafileSession *session); int seafile_session_unmount (SeafileSession *session); #endif /* SEAFILE_H */ seadrive-fuse-3.0.13/src/searpc-marshal.h000066400000000000000000002207071476177674700202440ustar00rootroot00000000000000 static char * marshal_int__void (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; int ret = ((int (*)(GError **))func) (&error); json_t *object = json_object (); searpc_set_int_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_int__int (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; int param1 = json_array_get_int_element (param_array, 1); int ret = ((int (*)(int, GError **))func) (param1, &error); json_t *object = json_object (); searpc_set_int_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_int__int_int (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; int param1 = json_array_get_int_element (param_array, 1); int param2 = json_array_get_int_element (param_array, 2); int ret = ((int (*)(int, int, GError **))func) (param1, param2, &error); json_t *object = json_object (); searpc_set_int_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_int__int_string (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; int param1 = json_array_get_int_element (param_array, 1); const char* param2 = json_array_get_string_or_null_element (param_array, 2); int ret = ((int (*)(int, const char*, GError **))func) (param1, param2, &error); json_t *object = json_object (); searpc_set_int_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_int__int_string_int (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; int param1 = json_array_get_int_element (param_array, 1); const char* param2 = json_array_get_string_or_null_element (param_array, 2); int param3 = json_array_get_int_element (param_array, 3); int ret = ((int (*)(int, const char*, int, GError **))func) (param1, param2, param3, &error); json_t *object = json_object (); searpc_set_int_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_int__int_string_string (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; int param1 = json_array_get_int_element (param_array, 1); const char* param2 = json_array_get_string_or_null_element (param_array, 2); const char* param3 = json_array_get_string_or_null_element (param_array, 3); int ret = ((int (*)(int, const char*, const char*, GError **))func) (param1, param2, param3, &error); json_t *object = json_object (); searpc_set_int_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_int__int_string_int_int (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; int param1 = json_array_get_int_element (param_array, 1); const char* param2 = json_array_get_string_or_null_element (param_array, 2); int param3 = json_array_get_int_element (param_array, 3); int param4 = json_array_get_int_element (param_array, 4); int ret = ((int (*)(int, const char*, int, int, GError **))func) (param1, param2, param3, param4, &error); json_t *object = json_object (); searpc_set_int_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_int__int_int_string_string (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; int param1 = json_array_get_int_element (param_array, 1); int param2 = json_array_get_int_element (param_array, 2); const char* param3 = json_array_get_string_or_null_element (param_array, 3); const char* param4 = json_array_get_string_or_null_element (param_array, 4); int ret = ((int (*)(int, int, const char*, const char*, GError **))func) (param1, param2, param3, param4, &error); json_t *object = json_object (); searpc_set_int_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_int__string (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; const char* param1 = json_array_get_string_or_null_element (param_array, 1); int ret = ((int (*)(const char*, GError **))func) (param1, &error); json_t *object = json_object (); searpc_set_int_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_int__string_int (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; const char* param1 = json_array_get_string_or_null_element (param_array, 1); int param2 = json_array_get_int_element (param_array, 2); int ret = ((int (*)(const char*, int, GError **))func) (param1, param2, &error); json_t *object = json_object (); searpc_set_int_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_int__string_int_int (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; const char* param1 = json_array_get_string_or_null_element (param_array, 1); int param2 = json_array_get_int_element (param_array, 2); int param3 = json_array_get_int_element (param_array, 3); int ret = ((int (*)(const char*, int, int, GError **))func) (param1, param2, param3, &error); json_t *object = json_object (); searpc_set_int_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_int__string_int_string (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; const char* param1 = json_array_get_string_or_null_element (param_array, 1); int param2 = json_array_get_int_element (param_array, 2); const char* param3 = json_array_get_string_or_null_element (param_array, 3); int ret = ((int (*)(const char*, int, const char*, GError **))func) (param1, param2, param3, &error); json_t *object = json_object (); searpc_set_int_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_int__string_int_string_string (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; const char* param1 = json_array_get_string_or_null_element (param_array, 1); int param2 = json_array_get_int_element (param_array, 2); const char* param3 = json_array_get_string_or_null_element (param_array, 3); const char* param4 = json_array_get_string_or_null_element (param_array, 4); int ret = ((int (*)(const char*, int, const char*, const char*, GError **))func) (param1, param2, param3, param4, &error); json_t *object = json_object (); searpc_set_int_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_int__string_int_int_string_string (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; const char* param1 = json_array_get_string_or_null_element (param_array, 1); int param2 = json_array_get_int_element (param_array, 2); int param3 = json_array_get_int_element (param_array, 3); const char* param4 = json_array_get_string_or_null_element (param_array, 4); const char* param5 = json_array_get_string_or_null_element (param_array, 5); int ret = ((int (*)(const char*, int, int, const char*, const char*, GError **))func) (param1, param2, param3, param4, param5, &error); json_t *object = json_object (); searpc_set_int_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_int__string_string (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; const char* param1 = json_array_get_string_or_null_element (param_array, 1); const char* param2 = json_array_get_string_or_null_element (param_array, 2); int ret = ((int (*)(const char*, const char*, GError **))func) (param1, param2, &error); json_t *object = json_object (); searpc_set_int_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_int__string_string_string (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; const char* param1 = json_array_get_string_or_null_element (param_array, 1); const char* param2 = json_array_get_string_or_null_element (param_array, 2); const char* param3 = json_array_get_string_or_null_element (param_array, 3); int ret = ((int (*)(const char*, const char*, const char*, GError **))func) (param1, param2, param3, &error); json_t *object = json_object (); searpc_set_int_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_int__string_string_int (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; const char* param1 = json_array_get_string_or_null_element (param_array, 1); const char* param2 = json_array_get_string_or_null_element (param_array, 2); int param3 = json_array_get_int_element (param_array, 3); int ret = ((int (*)(const char*, const char*, int, GError **))func) (param1, param2, param3, &error); json_t *object = json_object (); searpc_set_int_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_int__string_string_string_int (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; const char* param1 = json_array_get_string_or_null_element (param_array, 1); const char* param2 = json_array_get_string_or_null_element (param_array, 2); const char* param3 = json_array_get_string_or_null_element (param_array, 3); int param4 = json_array_get_int_element (param_array, 4); int ret = ((int (*)(const char*, const char*, const char*, int, GError **))func) (param1, param2, param3, param4, &error); json_t *object = json_object (); searpc_set_int_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_int__string_string_string_string_int (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; const char* param1 = json_array_get_string_or_null_element (param_array, 1); const char* param2 = json_array_get_string_or_null_element (param_array, 2); const char* param3 = json_array_get_string_or_null_element (param_array, 3); const char* param4 = json_array_get_string_or_null_element (param_array, 4); int param5 = json_array_get_int_element (param_array, 5); int ret = ((int (*)(const char*, const char*, const char*, const char*, int, GError **))func) (param1, param2, param3, param4, param5, &error); json_t *object = json_object (); searpc_set_int_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_int__string_string_string_string_string_int (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; const char* param1 = json_array_get_string_or_null_element (param_array, 1); const char* param2 = json_array_get_string_or_null_element (param_array, 2); const char* param3 = json_array_get_string_or_null_element (param_array, 3); const char* param4 = json_array_get_string_or_null_element (param_array, 4); const char* param5 = json_array_get_string_or_null_element (param_array, 5); int param6 = json_array_get_int_element (param_array, 6); int ret = ((int (*)(const char*, const char*, const char*, const char*, const char*, int, GError **))func) (param1, param2, param3, param4, param5, param6, &error); json_t *object = json_object (); searpc_set_int_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_int__string_string_int_int (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; const char* param1 = json_array_get_string_or_null_element (param_array, 1); const char* param2 = json_array_get_string_or_null_element (param_array, 2); int param3 = json_array_get_int_element (param_array, 3); int param4 = json_array_get_int_element (param_array, 4); int ret = ((int (*)(const char*, const char*, int, int, GError **))func) (param1, param2, param3, param4, &error); json_t *object = json_object (); searpc_set_int_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_int__string_string_string_string (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; const char* param1 = json_array_get_string_or_null_element (param_array, 1); const char* param2 = json_array_get_string_or_null_element (param_array, 2); const char* param3 = json_array_get_string_or_null_element (param_array, 3); const char* param4 = json_array_get_string_or_null_element (param_array, 4); int ret = ((int (*)(const char*, const char*, const char*, const char*, GError **))func) (param1, param2, param3, param4, &error); json_t *object = json_object (); searpc_set_int_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_int__string_string_string_string_string (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; const char* param1 = json_array_get_string_or_null_element (param_array, 1); const char* param2 = json_array_get_string_or_null_element (param_array, 2); const char* param3 = json_array_get_string_or_null_element (param_array, 3); const char* param4 = json_array_get_string_or_null_element (param_array, 4); const char* param5 = json_array_get_string_or_null_element (param_array, 5); int ret = ((int (*)(const char*, const char*, const char*, const char*, const char*, GError **))func) (param1, param2, param3, param4, param5, &error); json_t *object = json_object (); searpc_set_int_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_int__string_string_string_string_string_string (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; const char* param1 = json_array_get_string_or_null_element (param_array, 1); const char* param2 = json_array_get_string_or_null_element (param_array, 2); const char* param3 = json_array_get_string_or_null_element (param_array, 3); const char* param4 = json_array_get_string_or_null_element (param_array, 4); const char* param5 = json_array_get_string_or_null_element (param_array, 5); const char* param6 = json_array_get_string_or_null_element (param_array, 6); int ret = ((int (*)(const char*, const char*, const char*, const char*, const char*, const char*, GError **))func) (param1, param2, param3, param4, param5, param6, &error); json_t *object = json_object (); searpc_set_int_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_int__string_string_string_string_string_string_string (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; const char* param1 = json_array_get_string_or_null_element (param_array, 1); const char* param2 = json_array_get_string_or_null_element (param_array, 2); const char* param3 = json_array_get_string_or_null_element (param_array, 3); const char* param4 = json_array_get_string_or_null_element (param_array, 4); const char* param5 = json_array_get_string_or_null_element (param_array, 5); const char* param6 = json_array_get_string_or_null_element (param_array, 6); const char* param7 = json_array_get_string_or_null_element (param_array, 7); int ret = ((int (*)(const char*, const char*, const char*, const char*, const char*, const char*, const char*, GError **))func) (param1, param2, param3, param4, param5, param6, param7, &error); json_t *object = json_object (); searpc_set_int_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_int__string_int64 (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; const char* param1 = json_array_get_string_or_null_element (param_array, 1); gint64 param2 = json_array_get_int_element (param_array, 2); int ret = ((int (*)(const char*, gint64, GError **))func) (param1, param2, &error); json_t *object = json_object (); searpc_set_int_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_int__int_int64 (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; int param1 = json_array_get_int_element (param_array, 1); gint64 param2 = json_array_get_int_element (param_array, 2); int ret = ((int (*)(int, gint64, GError **))func) (param1, param2, &error); json_t *object = json_object (); searpc_set_int_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_int__int_string_int64 (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; int param1 = json_array_get_int_element (param_array, 1); const char* param2 = json_array_get_string_or_null_element (param_array, 2); gint64 param3 = json_array_get_int_element (param_array, 3); int ret = ((int (*)(int, const char*, gint64, GError **))func) (param1, param2, param3, &error); json_t *object = json_object (); searpc_set_int_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_int__int64 (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; gint64 param1 = json_array_get_int_element (param_array, 1); int ret = ((int (*)(gint64, GError **))func) (param1, &error); json_t *object = json_object (); searpc_set_int_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_int64__void (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; gint64 ret = ((gint64 (*)(GError **))func) (&error); json_t *object = json_object (); searpc_set_int_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_int64__string (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; const char* param1 = json_array_get_string_or_null_element (param_array, 1); gint64 ret = ((gint64 (*)(const char*, GError **))func) (param1, &error); json_t *object = json_object (); searpc_set_int_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_int64__int (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; int param1 = json_array_get_int_element (param_array, 1); gint64 ret = ((gint64 (*)(int, GError **))func) (param1, &error); json_t *object = json_object (); searpc_set_int_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_int64__int_string (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; int param1 = json_array_get_int_element (param_array, 1); const char* param2 = json_array_get_string_or_null_element (param_array, 2); gint64 ret = ((gint64 (*)(int, const char*, GError **))func) (param1, param2, &error); json_t *object = json_object (); searpc_set_int_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_int64__string_int_string (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; const char* param1 = json_array_get_string_or_null_element (param_array, 1); int param2 = json_array_get_int_element (param_array, 2); const char* param3 = json_array_get_string_or_null_element (param_array, 3); gint64 ret = ((gint64 (*)(const char*, int, const char*, GError **))func) (param1, param2, param3, &error); json_t *object = json_object (); searpc_set_int_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_string__void (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; char* ret = ((char* (*)(GError **))func) (&error); json_t *object = json_object (); searpc_set_string_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_string__int (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; int param1 = json_array_get_int_element (param_array, 1); char* ret = ((char* (*)(int, GError **))func) (param1, &error); json_t *object = json_object (); searpc_set_string_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_string__int_int (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; int param1 = json_array_get_int_element (param_array, 1); int param2 = json_array_get_int_element (param_array, 2); char* ret = ((char* (*)(int, int, GError **))func) (param1, param2, &error); json_t *object = json_object (); searpc_set_string_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_string__int_string (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; int param1 = json_array_get_int_element (param_array, 1); const char* param2 = json_array_get_string_or_null_element (param_array, 2); char* ret = ((char* (*)(int, const char*, GError **))func) (param1, param2, &error); json_t *object = json_object (); searpc_set_string_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_string__int_int_string (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; int param1 = json_array_get_int_element (param_array, 1); int param2 = json_array_get_int_element (param_array, 2); const char* param3 = json_array_get_string_or_null_element (param_array, 3); char* ret = ((char* (*)(int, int, const char*, GError **))func) (param1, param2, param3, &error); json_t *object = json_object (); searpc_set_string_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_string__string (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; const char* param1 = json_array_get_string_or_null_element (param_array, 1); char* ret = ((char* (*)(const char*, GError **))func) (param1, &error); json_t *object = json_object (); searpc_set_string_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_string__string_int (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; const char* param1 = json_array_get_string_or_null_element (param_array, 1); int param2 = json_array_get_int_element (param_array, 2); char* ret = ((char* (*)(const char*, int, GError **))func) (param1, param2, &error); json_t *object = json_object (); searpc_set_string_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_string__string_int_int (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; const char* param1 = json_array_get_string_or_null_element (param_array, 1); int param2 = json_array_get_int_element (param_array, 2); int param3 = json_array_get_int_element (param_array, 3); char* ret = ((char* (*)(const char*, int, int, GError **))func) (param1, param2, param3, &error); json_t *object = json_object (); searpc_set_string_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_string__string_string (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; const char* param1 = json_array_get_string_or_null_element (param_array, 1); const char* param2 = json_array_get_string_or_null_element (param_array, 2); char* ret = ((char* (*)(const char*, const char*, GError **))func) (param1, param2, &error); json_t *object = json_object (); searpc_set_string_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_string__string_string_int (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; const char* param1 = json_array_get_string_or_null_element (param_array, 1); const char* param2 = json_array_get_string_or_null_element (param_array, 2); int param3 = json_array_get_int_element (param_array, 3); char* ret = ((char* (*)(const char*, const char*, int, GError **))func) (param1, param2, param3, &error); json_t *object = json_object (); searpc_set_string_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_string__string_string_int_int (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; const char* param1 = json_array_get_string_or_null_element (param_array, 1); const char* param2 = json_array_get_string_or_null_element (param_array, 2); int param3 = json_array_get_int_element (param_array, 3); int param4 = json_array_get_int_element (param_array, 4); char* ret = ((char* (*)(const char*, const char*, int, int, GError **))func) (param1, param2, param3, param4, &error); json_t *object = json_object (); searpc_set_string_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_string__string_string_string (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; const char* param1 = json_array_get_string_or_null_element (param_array, 1); const char* param2 = json_array_get_string_or_null_element (param_array, 2); const char* param3 = json_array_get_string_or_null_element (param_array, 3); char* ret = ((char* (*)(const char*, const char*, const char*, GError **))func) (param1, param2, param3, &error); json_t *object = json_object (); searpc_set_string_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_string__string_string_string_string (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; const char* param1 = json_array_get_string_or_null_element (param_array, 1); const char* param2 = json_array_get_string_or_null_element (param_array, 2); const char* param3 = json_array_get_string_or_null_element (param_array, 3); const char* param4 = json_array_get_string_or_null_element (param_array, 4); char* ret = ((char* (*)(const char*, const char*, const char*, const char*, GError **))func) (param1, param2, param3, param4, &error); json_t *object = json_object (); searpc_set_string_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_string__string_string_string_string_int (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; const char* param1 = json_array_get_string_or_null_element (param_array, 1); const char* param2 = json_array_get_string_or_null_element (param_array, 2); const char* param3 = json_array_get_string_or_null_element (param_array, 3); const char* param4 = json_array_get_string_or_null_element (param_array, 4); int param5 = json_array_get_int_element (param_array, 5); char* ret = ((char* (*)(const char*, const char*, const char*, const char*, int, GError **))func) (param1, param2, param3, param4, param5, &error); json_t *object = json_object (); searpc_set_string_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_string__string_string_string_string_string (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; const char* param1 = json_array_get_string_or_null_element (param_array, 1); const char* param2 = json_array_get_string_or_null_element (param_array, 2); const char* param3 = json_array_get_string_or_null_element (param_array, 3); const char* param4 = json_array_get_string_or_null_element (param_array, 4); const char* param5 = json_array_get_string_or_null_element (param_array, 5); char* ret = ((char* (*)(const char*, const char*, const char*, const char*, const char*, GError **))func) (param1, param2, param3, param4, param5, &error); json_t *object = json_object (); searpc_set_string_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_string__string_string_string_string_string_int (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; const char* param1 = json_array_get_string_or_null_element (param_array, 1); const char* param2 = json_array_get_string_or_null_element (param_array, 2); const char* param3 = json_array_get_string_or_null_element (param_array, 3); const char* param4 = json_array_get_string_or_null_element (param_array, 4); const char* param5 = json_array_get_string_or_null_element (param_array, 5); int param6 = json_array_get_int_element (param_array, 6); char* ret = ((char* (*)(const char*, const char*, const char*, const char*, const char*, int, GError **))func) (param1, param2, param3, param4, param5, param6, &error); json_t *object = json_object (); searpc_set_string_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_string__string_string_string_string_string_string_int (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; const char* param1 = json_array_get_string_or_null_element (param_array, 1); const char* param2 = json_array_get_string_or_null_element (param_array, 2); const char* param3 = json_array_get_string_or_null_element (param_array, 3); const char* param4 = json_array_get_string_or_null_element (param_array, 4); const char* param5 = json_array_get_string_or_null_element (param_array, 5); const char* param6 = json_array_get_string_or_null_element (param_array, 6); int param7 = json_array_get_int_element (param_array, 7); char* ret = ((char* (*)(const char*, const char*, const char*, const char*, const char*, const char*, int, GError **))func) (param1, param2, param3, param4, param5, param6, param7, &error); json_t *object = json_object (); searpc_set_string_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_string__string_string_string_string_string_string_int_int (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; const char* param1 = json_array_get_string_or_null_element (param_array, 1); const char* param2 = json_array_get_string_or_null_element (param_array, 2); const char* param3 = json_array_get_string_or_null_element (param_array, 3); const char* param4 = json_array_get_string_or_null_element (param_array, 4); const char* param5 = json_array_get_string_or_null_element (param_array, 5); const char* param6 = json_array_get_string_or_null_element (param_array, 6); int param7 = json_array_get_int_element (param_array, 7); int param8 = json_array_get_int_element (param_array, 8); char* ret = ((char* (*)(const char*, const char*, const char*, const char*, const char*, const char*, int, int, GError **))func) (param1, param2, param3, param4, param5, param6, param7, param8, &error); json_t *object = json_object (); searpc_set_string_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_string__string_string_string_string_string_string (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; const char* param1 = json_array_get_string_or_null_element (param_array, 1); const char* param2 = json_array_get_string_or_null_element (param_array, 2); const char* param3 = json_array_get_string_or_null_element (param_array, 3); const char* param4 = json_array_get_string_or_null_element (param_array, 4); const char* param5 = json_array_get_string_or_null_element (param_array, 5); const char* param6 = json_array_get_string_or_null_element (param_array, 6); char* ret = ((char* (*)(const char*, const char*, const char*, const char*, const char*, const char*, GError **))func) (param1, param2, param3, param4, param5, param6, &error); json_t *object = json_object (); searpc_set_string_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_string__string_string_string_string_string_string_int64 (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; const char* param1 = json_array_get_string_or_null_element (param_array, 1); const char* param2 = json_array_get_string_or_null_element (param_array, 2); const char* param3 = json_array_get_string_or_null_element (param_array, 3); const char* param4 = json_array_get_string_or_null_element (param_array, 4); const char* param5 = json_array_get_string_or_null_element (param_array, 5); const char* param6 = json_array_get_string_or_null_element (param_array, 6); gint64 param7 = json_array_get_int_element (param_array, 7); char* ret = ((char* (*)(const char*, const char*, const char*, const char*, const char*, const char*, gint64, GError **))func) (param1, param2, param3, param4, param5, param6, param7, &error); json_t *object = json_object (); searpc_set_string_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_string__string_string_string_string_string_string_int64_int (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; const char* param1 = json_array_get_string_or_null_element (param_array, 1); const char* param2 = json_array_get_string_or_null_element (param_array, 2); const char* param3 = json_array_get_string_or_null_element (param_array, 3); const char* param4 = json_array_get_string_or_null_element (param_array, 4); const char* param5 = json_array_get_string_or_null_element (param_array, 5); const char* param6 = json_array_get_string_or_null_element (param_array, 6); gint64 param7 = json_array_get_int_element (param_array, 7); int param8 = json_array_get_int_element (param_array, 8); char* ret = ((char* (*)(const char*, const char*, const char*, const char*, const char*, const char*, gint64, int, GError **))func) (param1, param2, param3, param4, param5, param6, param7, param8, &error); json_t *object = json_object (); searpc_set_string_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_string__string_string_string_string_string_string_string (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; const char* param1 = json_array_get_string_or_null_element (param_array, 1); const char* param2 = json_array_get_string_or_null_element (param_array, 2); const char* param3 = json_array_get_string_or_null_element (param_array, 3); const char* param4 = json_array_get_string_or_null_element (param_array, 4); const char* param5 = json_array_get_string_or_null_element (param_array, 5); const char* param6 = json_array_get_string_or_null_element (param_array, 6); const char* param7 = json_array_get_string_or_null_element (param_array, 7); char* ret = ((char* (*)(const char*, const char*, const char*, const char*, const char*, const char*, const char*, GError **))func) (param1, param2, param3, param4, param5, param6, param7, &error); json_t *object = json_object (); searpc_set_string_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_string__string_string_string_string_string_string_string_int64 (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; const char* param1 = json_array_get_string_or_null_element (param_array, 1); const char* param2 = json_array_get_string_or_null_element (param_array, 2); const char* param3 = json_array_get_string_or_null_element (param_array, 3); const char* param4 = json_array_get_string_or_null_element (param_array, 4); const char* param5 = json_array_get_string_or_null_element (param_array, 5); const char* param6 = json_array_get_string_or_null_element (param_array, 6); const char* param7 = json_array_get_string_or_null_element (param_array, 7); gint64 param8 = json_array_get_int_element (param_array, 8); char* ret = ((char* (*)(const char*, const char*, const char*, const char*, const char*, const char*, const char*, gint64, GError **))func) (param1, param2, param3, param4, param5, param6, param7, param8, &error); json_t *object = json_object (); searpc_set_string_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_string__string_string_string_string_string_string_string_string_string (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; const char* param1 = json_array_get_string_or_null_element (param_array, 1); const char* param2 = json_array_get_string_or_null_element (param_array, 2); const char* param3 = json_array_get_string_or_null_element (param_array, 3); const char* param4 = json_array_get_string_or_null_element (param_array, 4); const char* param5 = json_array_get_string_or_null_element (param_array, 5); const char* param6 = json_array_get_string_or_null_element (param_array, 6); const char* param7 = json_array_get_string_or_null_element (param_array, 7); const char* param8 = json_array_get_string_or_null_element (param_array, 8); const char* param9 = json_array_get_string_or_null_element (param_array, 9); char* ret = ((char* (*)(const char*, const char*, const char*, const char*, const char*, const char*, const char*, const char*, const char*, GError **))func) (param1, param2, param3, param4, param5, param6, param7, param8, param9, &error); json_t *object = json_object (); searpc_set_string_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_string__string_int_string_string_string_string_string_string_string_string_string_string_int_string (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; const char* param1 = json_array_get_string_or_null_element (param_array, 1); int param2 = json_array_get_int_element (param_array, 2); const char* param3 = json_array_get_string_or_null_element (param_array, 3); const char* param4 = json_array_get_string_or_null_element (param_array, 4); const char* param5 = json_array_get_string_or_null_element (param_array, 5); const char* param6 = json_array_get_string_or_null_element (param_array, 6); const char* param7 = json_array_get_string_or_null_element (param_array, 7); const char* param8 = json_array_get_string_or_null_element (param_array, 8); const char* param9 = json_array_get_string_or_null_element (param_array, 9); const char* param10 = json_array_get_string_or_null_element (param_array, 10); const char* param11 = json_array_get_string_or_null_element (param_array, 11); const char* param12 = json_array_get_string_or_null_element (param_array, 12); int param13 = json_array_get_int_element (param_array, 13); const char* param14 = json_array_get_string_or_null_element (param_array, 14); char* ret = ((char* (*)(const char*, int, const char*, const char*, const char*, const char*, const char*, const char*, const char*, const char*, const char*, const char*, int, const char*, GError **))func) (param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, &error); json_t *object = json_object (); searpc_set_string_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_string__string_int_string_int_int (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; const char* param1 = json_array_get_string_or_null_element (param_array, 1); int param2 = json_array_get_int_element (param_array, 2); const char* param3 = json_array_get_string_or_null_element (param_array, 3); int param4 = json_array_get_int_element (param_array, 4); int param5 = json_array_get_int_element (param_array, 5); char* ret = ((char* (*)(const char*, int, const char*, int, int, GError **))func) (param1, param2, param3, param4, param5, &error); json_t *object = json_object (); searpc_set_string_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_string__string_int_string_string_string (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; const char* param1 = json_array_get_string_or_null_element (param_array, 1); int param2 = json_array_get_int_element (param_array, 2); const char* param3 = json_array_get_string_or_null_element (param_array, 3); const char* param4 = json_array_get_string_or_null_element (param_array, 4); const char* param5 = json_array_get_string_or_null_element (param_array, 5); char* ret = ((char* (*)(const char*, int, const char*, const char*, const char*, GError **))func) (param1, param2, param3, param4, param5, &error); json_t *object = json_object (); searpc_set_string_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_objlist__void (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; GList* ret = ((GList* (*)(GError **))func) (&error); json_t *object = json_object (); searpc_set_objlist_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_objlist__int (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; int param1 = json_array_get_int_element (param_array, 1); GList* ret = ((GList* (*)(int, GError **))func) (param1, &error); json_t *object = json_object (); searpc_set_objlist_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_objlist__int_int (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; int param1 = json_array_get_int_element (param_array, 1); int param2 = json_array_get_int_element (param_array, 2); GList* ret = ((GList* (*)(int, int, GError **))func) (param1, param2, &error); json_t *object = json_object (); searpc_set_objlist_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_objlist__int_string (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; int param1 = json_array_get_int_element (param_array, 1); const char* param2 = json_array_get_string_or_null_element (param_array, 2); GList* ret = ((GList* (*)(int, const char*, GError **))func) (param1, param2, &error); json_t *object = json_object (); searpc_set_objlist_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_objlist__int_int_int (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; int param1 = json_array_get_int_element (param_array, 1); int param2 = json_array_get_int_element (param_array, 2); int param3 = json_array_get_int_element (param_array, 3); GList* ret = ((GList* (*)(int, int, int, GError **))func) (param1, param2, param3, &error); json_t *object = json_object (); searpc_set_objlist_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_objlist__string (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; const char* param1 = json_array_get_string_or_null_element (param_array, 1); GList* ret = ((GList* (*)(const char*, GError **))func) (param1, &error); json_t *object = json_object (); searpc_set_objlist_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_objlist__string_int (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; const char* param1 = json_array_get_string_or_null_element (param_array, 1); int param2 = json_array_get_int_element (param_array, 2); GList* ret = ((GList* (*)(const char*, int, GError **))func) (param1, param2, &error); json_t *object = json_object (); searpc_set_objlist_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_objlist__string_int_int (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; const char* param1 = json_array_get_string_or_null_element (param_array, 1); int param2 = json_array_get_int_element (param_array, 2); int param3 = json_array_get_int_element (param_array, 3); GList* ret = ((GList* (*)(const char*, int, int, GError **))func) (param1, param2, param3, &error); json_t *object = json_object (); searpc_set_objlist_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_objlist__string_int_string (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; const char* param1 = json_array_get_string_or_null_element (param_array, 1); int param2 = json_array_get_int_element (param_array, 2); const char* param3 = json_array_get_string_or_null_element (param_array, 3); GList* ret = ((GList* (*)(const char*, int, const char*, GError **))func) (param1, param2, param3, &error); json_t *object = json_object (); searpc_set_objlist_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_objlist__string_string (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; const char* param1 = json_array_get_string_or_null_element (param_array, 1); const char* param2 = json_array_get_string_or_null_element (param_array, 2); GList* ret = ((GList* (*)(const char*, const char*, GError **))func) (param1, param2, &error); json_t *object = json_object (); searpc_set_objlist_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_objlist__string_string_string (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; const char* param1 = json_array_get_string_or_null_element (param_array, 1); const char* param2 = json_array_get_string_or_null_element (param_array, 2); const char* param3 = json_array_get_string_or_null_element (param_array, 3); GList* ret = ((GList* (*)(const char*, const char*, const char*, GError **))func) (param1, param2, param3, &error); json_t *object = json_object (); searpc_set_objlist_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_objlist__string_string_int (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; const char* param1 = json_array_get_string_or_null_element (param_array, 1); const char* param2 = json_array_get_string_or_null_element (param_array, 2); int param3 = json_array_get_int_element (param_array, 3); GList* ret = ((GList* (*)(const char*, const char*, int, GError **))func) (param1, param2, param3, &error); json_t *object = json_object (); searpc_set_objlist_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_objlist__string_string_string_int (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; const char* param1 = json_array_get_string_or_null_element (param_array, 1); const char* param2 = json_array_get_string_or_null_element (param_array, 2); const char* param3 = json_array_get_string_or_null_element (param_array, 3); int param4 = json_array_get_int_element (param_array, 4); GList* ret = ((GList* (*)(const char*, const char*, const char*, int, GError **))func) (param1, param2, param3, param4, &error); json_t *object = json_object (); searpc_set_objlist_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_objlist__string_string_int_int (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; const char* param1 = json_array_get_string_or_null_element (param_array, 1); const char* param2 = json_array_get_string_or_null_element (param_array, 2); int param3 = json_array_get_int_element (param_array, 3); int param4 = json_array_get_int_element (param_array, 4); GList* ret = ((GList* (*)(const char*, const char*, int, int, GError **))func) (param1, param2, param3, param4, &error); json_t *object = json_object (); searpc_set_objlist_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_objlist__string_string_int_int_int (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; const char* param1 = json_array_get_string_or_null_element (param_array, 1); const char* param2 = json_array_get_string_or_null_element (param_array, 2); int param3 = json_array_get_int_element (param_array, 3); int param4 = json_array_get_int_element (param_array, 4); int param5 = json_array_get_int_element (param_array, 5); GList* ret = ((GList* (*)(const char*, const char*, int, int, int, GError **))func) (param1, param2, param3, param4, param5, &error); json_t *object = json_object (); searpc_set_objlist_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_objlist__int_string_string_int_int (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; int param1 = json_array_get_int_element (param_array, 1); const char* param2 = json_array_get_string_or_null_element (param_array, 2); const char* param3 = json_array_get_string_or_null_element (param_array, 3); int param4 = json_array_get_int_element (param_array, 4); int param5 = json_array_get_int_element (param_array, 5); GList* ret = ((GList* (*)(int, const char*, const char*, int, int, GError **))func) (param1, param2, param3, param4, param5, &error); json_t *object = json_object (); searpc_set_objlist_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_objlist__string_int_string_string_string (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; const char* param1 = json_array_get_string_or_null_element (param_array, 1); int param2 = json_array_get_int_element (param_array, 2); const char* param3 = json_array_get_string_or_null_element (param_array, 3); const char* param4 = json_array_get_string_or_null_element (param_array, 4); const char* param5 = json_array_get_string_or_null_element (param_array, 5); GList* ret = ((GList* (*)(const char*, int, const char*, const char*, const char*, GError **))func) (param1, param2, param3, param4, param5, &error); json_t *object = json_object (); searpc_set_objlist_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_objlist__string_int_string_int_int (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; const char* param1 = json_array_get_string_or_null_element (param_array, 1); int param2 = json_array_get_int_element (param_array, 2); const char* param3 = json_array_get_string_or_null_element (param_array, 3); int param4 = json_array_get_int_element (param_array, 4); int param5 = json_array_get_int_element (param_array, 5); GList* ret = ((GList* (*)(const char*, int, const char*, int, int, GError **))func) (param1, param2, param3, param4, param5, &error); json_t *object = json_object (); searpc_set_objlist_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_objlist__string_int_string_string_int (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; const char* param1 = json_array_get_string_or_null_element (param_array, 1); int param2 = json_array_get_int_element (param_array, 2); const char* param3 = json_array_get_string_or_null_element (param_array, 3); const char* param4 = json_array_get_string_or_null_element (param_array, 4); int param5 = json_array_get_int_element (param_array, 5); GList* ret = ((GList* (*)(const char*, int, const char*, const char*, int, GError **))func) (param1, param2, param3, param4, param5, &error); json_t *object = json_object (); searpc_set_objlist_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_objlist__string_string_string_string_int_int (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; const char* param1 = json_array_get_string_or_null_element (param_array, 1); const char* param2 = json_array_get_string_or_null_element (param_array, 2); const char* param3 = json_array_get_string_or_null_element (param_array, 3); const char* param4 = json_array_get_string_or_null_element (param_array, 4); int param5 = json_array_get_int_element (param_array, 5); int param6 = json_array_get_int_element (param_array, 6); GList* ret = ((GList* (*)(const char*, const char*, const char*, const char*, int, int, GError **))func) (param1, param2, param3, param4, param5, param6, &error); json_t *object = json_object (); searpc_set_objlist_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_object__void (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; GObject* ret = ((GObject* (*)(GError **))func) (&error); json_t *object = json_object (); searpc_set_object_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_object__int (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; int param1 = json_array_get_int_element (param_array, 1); GObject* ret = ((GObject* (*)(int, GError **))func) (param1, &error); json_t *object = json_object (); searpc_set_object_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_object__string (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; const char* param1 = json_array_get_string_or_null_element (param_array, 1); GObject* ret = ((GObject* (*)(const char*, GError **))func) (param1, &error); json_t *object = json_object (); searpc_set_object_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_object__string_string (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; const char* param1 = json_array_get_string_or_null_element (param_array, 1); const char* param2 = json_array_get_string_or_null_element (param_array, 2); GObject* ret = ((GObject* (*)(const char*, const char*, GError **))func) (param1, param2, &error); json_t *object = json_object (); searpc_set_object_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_object__string_string_string (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; const char* param1 = json_array_get_string_or_null_element (param_array, 1); const char* param2 = json_array_get_string_or_null_element (param_array, 2); const char* param3 = json_array_get_string_or_null_element (param_array, 3); GObject* ret = ((GObject* (*)(const char*, const char*, const char*, GError **))func) (param1, param2, param3, &error); json_t *object = json_object (); searpc_set_object_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_object__string_int_string (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; const char* param1 = json_array_get_string_or_null_element (param_array, 1); int param2 = json_array_get_int_element (param_array, 2); const char* param3 = json_array_get_string_or_null_element (param_array, 3); GObject* ret = ((GObject* (*)(const char*, int, const char*, GError **))func) (param1, param2, param3, &error); json_t *object = json_object (); searpc_set_object_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_object__int_string_string (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; int param1 = json_array_get_int_element (param_array, 1); const char* param2 = json_array_get_string_or_null_element (param_array, 2); const char* param3 = json_array_get_string_or_null_element (param_array, 3); GObject* ret = ((GObject* (*)(int, const char*, const char*, GError **))func) (param1, param2, param3, &error); json_t *object = json_object (); searpc_set_object_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_object__string_string_string_string_string_string_string_int_int (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; const char* param1 = json_array_get_string_or_null_element (param_array, 1); const char* param2 = json_array_get_string_or_null_element (param_array, 2); const char* param3 = json_array_get_string_or_null_element (param_array, 3); const char* param4 = json_array_get_string_or_null_element (param_array, 4); const char* param5 = json_array_get_string_or_null_element (param_array, 5); const char* param6 = json_array_get_string_or_null_element (param_array, 6); const char* param7 = json_array_get_string_or_null_element (param_array, 7); int param8 = json_array_get_int_element (param_array, 8); int param9 = json_array_get_int_element (param_array, 9); GObject* ret = ((GObject* (*)(const char*, const char*, const char*, const char*, const char*, const char*, const char*, int, int, GError **))func) (param1, param2, param3, param4, param5, param6, param7, param8, param9, &error); json_t *object = json_object (); searpc_set_object_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_json__void (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; json_t* ret = ((json_t* (*)(GError **))func) (&error); json_t *object = json_object (); searpc_set_json_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static char * marshal_json__string (void *func, json_t *param_array, gsize *ret_len) { GError *error = NULL; const char* param1 = json_array_get_string_or_null_element (param_array, 1); json_t* ret = ((json_t* (*)(const char*, GError **))func) (param1, &error); json_t *object = json_object (); searpc_set_json_to_ret_object (object, ret); return searpc_marshal_set_ret_common (object, ret_len, error); } static void register_marshals(void) { { searpc_server_register_marshal (searpc_signature_int__void(), marshal_int__void); } { searpc_server_register_marshal (searpc_signature_int__int(), marshal_int__int); } { searpc_server_register_marshal (searpc_signature_int__int_int(), marshal_int__int_int); } { searpc_server_register_marshal (searpc_signature_int__int_string(), marshal_int__int_string); } { searpc_server_register_marshal (searpc_signature_int__int_string_int(), marshal_int__int_string_int); } { searpc_server_register_marshal (searpc_signature_int__int_string_string(), marshal_int__int_string_string); } { searpc_server_register_marshal (searpc_signature_int__int_string_int_int(), marshal_int__int_string_int_int); } { searpc_server_register_marshal (searpc_signature_int__int_int_string_string(), marshal_int__int_int_string_string); } { searpc_server_register_marshal (searpc_signature_int__string(), marshal_int__string); } { searpc_server_register_marshal (searpc_signature_int__string_int(), marshal_int__string_int); } { searpc_server_register_marshal (searpc_signature_int__string_int_int(), marshal_int__string_int_int); } { searpc_server_register_marshal (searpc_signature_int__string_int_string(), marshal_int__string_int_string); } { searpc_server_register_marshal (searpc_signature_int__string_int_string_string(), marshal_int__string_int_string_string); } { searpc_server_register_marshal (searpc_signature_int__string_int_int_string_string(), marshal_int__string_int_int_string_string); } { searpc_server_register_marshal (searpc_signature_int__string_string(), marshal_int__string_string); } { searpc_server_register_marshal (searpc_signature_int__string_string_string(), marshal_int__string_string_string); } { searpc_server_register_marshal (searpc_signature_int__string_string_int(), marshal_int__string_string_int); } { searpc_server_register_marshal (searpc_signature_int__string_string_string_int(), marshal_int__string_string_string_int); } { searpc_server_register_marshal (searpc_signature_int__string_string_string_string_int(), marshal_int__string_string_string_string_int); } { searpc_server_register_marshal (searpc_signature_int__string_string_string_string_string_int(), marshal_int__string_string_string_string_string_int); } { searpc_server_register_marshal (searpc_signature_int__string_string_int_int(), marshal_int__string_string_int_int); } { searpc_server_register_marshal (searpc_signature_int__string_string_string_string(), marshal_int__string_string_string_string); } { searpc_server_register_marshal (searpc_signature_int__string_string_string_string_string(), marshal_int__string_string_string_string_string); } { searpc_server_register_marshal (searpc_signature_int__string_string_string_string_string_string(), marshal_int__string_string_string_string_string_string); } { searpc_server_register_marshal (searpc_signature_int__string_string_string_string_string_string_string(), marshal_int__string_string_string_string_string_string_string); } { searpc_server_register_marshal (searpc_signature_int__string_int64(), marshal_int__string_int64); } { searpc_server_register_marshal (searpc_signature_int__int_int64(), marshal_int__int_int64); } { searpc_server_register_marshal (searpc_signature_int__int_string_int64(), marshal_int__int_string_int64); } { searpc_server_register_marshal (searpc_signature_int__int64(), marshal_int__int64); } { searpc_server_register_marshal (searpc_signature_int64__void(), marshal_int64__void); } { searpc_server_register_marshal (searpc_signature_int64__string(), marshal_int64__string); } { searpc_server_register_marshal (searpc_signature_int64__int(), marshal_int64__int); } { searpc_server_register_marshal (searpc_signature_int64__int_string(), marshal_int64__int_string); } { searpc_server_register_marshal (searpc_signature_int64__string_int_string(), marshal_int64__string_int_string); } { searpc_server_register_marshal (searpc_signature_string__void(), marshal_string__void); } { searpc_server_register_marshal (searpc_signature_string__int(), marshal_string__int); } { searpc_server_register_marshal (searpc_signature_string__int_int(), marshal_string__int_int); } { searpc_server_register_marshal (searpc_signature_string__int_string(), marshal_string__int_string); } { searpc_server_register_marshal (searpc_signature_string__int_int_string(), marshal_string__int_int_string); } { searpc_server_register_marshal (searpc_signature_string__string(), marshal_string__string); } { searpc_server_register_marshal (searpc_signature_string__string_int(), marshal_string__string_int); } { searpc_server_register_marshal (searpc_signature_string__string_int_int(), marshal_string__string_int_int); } { searpc_server_register_marshal (searpc_signature_string__string_string(), marshal_string__string_string); } { searpc_server_register_marshal (searpc_signature_string__string_string_int(), marshal_string__string_string_int); } { searpc_server_register_marshal (searpc_signature_string__string_string_int_int(), marshal_string__string_string_int_int); } { searpc_server_register_marshal (searpc_signature_string__string_string_string(), marshal_string__string_string_string); } { searpc_server_register_marshal (searpc_signature_string__string_string_string_string(), marshal_string__string_string_string_string); } { searpc_server_register_marshal (searpc_signature_string__string_string_string_string_int(), marshal_string__string_string_string_string_int); } { searpc_server_register_marshal (searpc_signature_string__string_string_string_string_string(), marshal_string__string_string_string_string_string); } { searpc_server_register_marshal (searpc_signature_string__string_string_string_string_string_int(), marshal_string__string_string_string_string_string_int); } { searpc_server_register_marshal (searpc_signature_string__string_string_string_string_string_string_int(), marshal_string__string_string_string_string_string_string_int); } { searpc_server_register_marshal (searpc_signature_string__string_string_string_string_string_string_int_int(), marshal_string__string_string_string_string_string_string_int_int); } { searpc_server_register_marshal (searpc_signature_string__string_string_string_string_string_string(), marshal_string__string_string_string_string_string_string); } { searpc_server_register_marshal (searpc_signature_string__string_string_string_string_string_string_int64(), marshal_string__string_string_string_string_string_string_int64); } { searpc_server_register_marshal (searpc_signature_string__string_string_string_string_string_string_int64_int(), marshal_string__string_string_string_string_string_string_int64_int); } { searpc_server_register_marshal (searpc_signature_string__string_string_string_string_string_string_string(), marshal_string__string_string_string_string_string_string_string); } { searpc_server_register_marshal (searpc_signature_string__string_string_string_string_string_string_string_int64(), marshal_string__string_string_string_string_string_string_string_int64); } { searpc_server_register_marshal (searpc_signature_string__string_string_string_string_string_string_string_string_string(), marshal_string__string_string_string_string_string_string_string_string_string); } { searpc_server_register_marshal (searpc_signature_string__string_int_string_string_string_string_string_string_string_string_string_string_int_string(), marshal_string__string_int_string_string_string_string_string_string_string_string_string_string_int_string); } { searpc_server_register_marshal (searpc_signature_string__string_int_string_int_int(), marshal_string__string_int_string_int_int); } { searpc_server_register_marshal (searpc_signature_string__string_int_string_string_string(), marshal_string__string_int_string_string_string); } { searpc_server_register_marshal (searpc_signature_objlist__void(), marshal_objlist__void); } { searpc_server_register_marshal (searpc_signature_objlist__int(), marshal_objlist__int); } { searpc_server_register_marshal (searpc_signature_objlist__int_int(), marshal_objlist__int_int); } { searpc_server_register_marshal (searpc_signature_objlist__int_string(), marshal_objlist__int_string); } { searpc_server_register_marshal (searpc_signature_objlist__int_int_int(), marshal_objlist__int_int_int); } { searpc_server_register_marshal (searpc_signature_objlist__string(), marshal_objlist__string); } { searpc_server_register_marshal (searpc_signature_objlist__string_int(), marshal_objlist__string_int); } { searpc_server_register_marshal (searpc_signature_objlist__string_int_int(), marshal_objlist__string_int_int); } { searpc_server_register_marshal (searpc_signature_objlist__string_int_string(), marshal_objlist__string_int_string); } { searpc_server_register_marshal (searpc_signature_objlist__string_string(), marshal_objlist__string_string); } { searpc_server_register_marshal (searpc_signature_objlist__string_string_string(), marshal_objlist__string_string_string); } { searpc_server_register_marshal (searpc_signature_objlist__string_string_int(), marshal_objlist__string_string_int); } { searpc_server_register_marshal (searpc_signature_objlist__string_string_string_int(), marshal_objlist__string_string_string_int); } { searpc_server_register_marshal (searpc_signature_objlist__string_string_int_int(), marshal_objlist__string_string_int_int); } { searpc_server_register_marshal (searpc_signature_objlist__string_string_int_int_int(), marshal_objlist__string_string_int_int_int); } { searpc_server_register_marshal (searpc_signature_objlist__int_string_string_int_int(), marshal_objlist__int_string_string_int_int); } { searpc_server_register_marshal (searpc_signature_objlist__string_int_string_string_string(), marshal_objlist__string_int_string_string_string); } { searpc_server_register_marshal (searpc_signature_objlist__string_int_string_int_int(), marshal_objlist__string_int_string_int_int); } { searpc_server_register_marshal (searpc_signature_objlist__string_int_string_string_int(), marshal_objlist__string_int_string_string_int); } { searpc_server_register_marshal (searpc_signature_objlist__string_string_string_string_int_int(), marshal_objlist__string_string_string_string_int_int); } { searpc_server_register_marshal (searpc_signature_object__void(), marshal_object__void); } { searpc_server_register_marshal (searpc_signature_object__int(), marshal_object__int); } { searpc_server_register_marshal (searpc_signature_object__string(), marshal_object__string); } { searpc_server_register_marshal (searpc_signature_object__string_string(), marshal_object__string_string); } { searpc_server_register_marshal (searpc_signature_object__string_string_string(), marshal_object__string_string_string); } { searpc_server_register_marshal (searpc_signature_object__string_int_string(), marshal_object__string_int_string); } { searpc_server_register_marshal (searpc_signature_object__int_string_string(), marshal_object__int_string_string); } { searpc_server_register_marshal (searpc_signature_object__string_string_string_string_string_string_string_int_int(), marshal_object__string_string_string_string_string_string_string_int_int); } { searpc_server_register_marshal (searpc_signature_json__void(), marshal_json__void); } { searpc_server_register_marshal (searpc_signature_json__string(), marshal_json__string); } } seadrive-fuse-3.0.13/src/searpc-signature.h000066400000000000000000000346411476177674700206160ustar00rootroot00000000000000 inline static gchar * searpc_signature_int__void(void) { return searpc_compute_signature ("int", 0); } inline static gchar * searpc_signature_int__int(void) { return searpc_compute_signature ("int", 1, "int"); } inline static gchar * searpc_signature_int__int_int(void) { return searpc_compute_signature ("int", 2, "int", "int"); } inline static gchar * searpc_signature_int__int_string(void) { return searpc_compute_signature ("int", 2, "int", "string"); } inline static gchar * searpc_signature_int__int_string_int(void) { return searpc_compute_signature ("int", 3, "int", "string", "int"); } inline static gchar * searpc_signature_int__int_string_string(void) { return searpc_compute_signature ("int", 3, "int", "string", "string"); } inline static gchar * searpc_signature_int__int_string_int_int(void) { return searpc_compute_signature ("int", 4, "int", "string", "int", "int"); } inline static gchar * searpc_signature_int__int_int_string_string(void) { return searpc_compute_signature ("int", 4, "int", "int", "string", "string"); } inline static gchar * searpc_signature_int__string(void) { return searpc_compute_signature ("int", 1, "string"); } inline static gchar * searpc_signature_int__string_int(void) { return searpc_compute_signature ("int", 2, "string", "int"); } inline static gchar * searpc_signature_int__string_int_int(void) { return searpc_compute_signature ("int", 3, "string", "int", "int"); } inline static gchar * searpc_signature_int__string_int_string(void) { return searpc_compute_signature ("int", 3, "string", "int", "string"); } inline static gchar * searpc_signature_int__string_int_string_string(void) { return searpc_compute_signature ("int", 4, "string", "int", "string", "string"); } inline static gchar * searpc_signature_int__string_int_int_string_string(void) { return searpc_compute_signature ("int", 5, "string", "int", "int", "string", "string"); } inline static gchar * searpc_signature_int__string_string(void) { return searpc_compute_signature ("int", 2, "string", "string"); } inline static gchar * searpc_signature_int__string_string_string(void) { return searpc_compute_signature ("int", 3, "string", "string", "string"); } inline static gchar * searpc_signature_int__string_string_int(void) { return searpc_compute_signature ("int", 3, "string", "string", "int"); } inline static gchar * searpc_signature_int__string_string_string_int(void) { return searpc_compute_signature ("int", 4, "string", "string", "string", "int"); } inline static gchar * searpc_signature_int__string_string_string_string_int(void) { return searpc_compute_signature ("int", 5, "string", "string", "string", "string", "int"); } inline static gchar * searpc_signature_int__string_string_string_string_string_int(void) { return searpc_compute_signature ("int", 6, "string", "string", "string", "string", "string", "int"); } inline static gchar * searpc_signature_int__string_string_int_int(void) { return searpc_compute_signature ("int", 4, "string", "string", "int", "int"); } inline static gchar * searpc_signature_int__string_string_string_string(void) { return searpc_compute_signature ("int", 4, "string", "string", "string", "string"); } inline static gchar * searpc_signature_int__string_string_string_string_string(void) { return searpc_compute_signature ("int", 5, "string", "string", "string", "string", "string"); } inline static gchar * searpc_signature_int__string_string_string_string_string_string(void) { return searpc_compute_signature ("int", 6, "string", "string", "string", "string", "string", "string"); } inline static gchar * searpc_signature_int__string_string_string_string_string_string_string(void) { return searpc_compute_signature ("int", 7, "string", "string", "string", "string", "string", "string", "string"); } inline static gchar * searpc_signature_int__string_int64(void) { return searpc_compute_signature ("int", 2, "string", "int64"); } inline static gchar * searpc_signature_int__int_int64(void) { return searpc_compute_signature ("int", 2, "int", "int64"); } inline static gchar * searpc_signature_int__int_string_int64(void) { return searpc_compute_signature ("int", 3, "int", "string", "int64"); } inline static gchar * searpc_signature_int__int64(void) { return searpc_compute_signature ("int", 1, "int64"); } inline static gchar * searpc_signature_int64__void(void) { return searpc_compute_signature ("int64", 0); } inline static gchar * searpc_signature_int64__string(void) { return searpc_compute_signature ("int64", 1, "string"); } inline static gchar * searpc_signature_int64__int(void) { return searpc_compute_signature ("int64", 1, "int"); } inline static gchar * searpc_signature_int64__int_string(void) { return searpc_compute_signature ("int64", 2, "int", "string"); } inline static gchar * searpc_signature_int64__string_int_string(void) { return searpc_compute_signature ("int64", 3, "string", "int", "string"); } inline static gchar * searpc_signature_string__void(void) { return searpc_compute_signature ("string", 0); } inline static gchar * searpc_signature_string__int(void) { return searpc_compute_signature ("string", 1, "int"); } inline static gchar * searpc_signature_string__int_int(void) { return searpc_compute_signature ("string", 2, "int", "int"); } inline static gchar * searpc_signature_string__int_string(void) { return searpc_compute_signature ("string", 2, "int", "string"); } inline static gchar * searpc_signature_string__int_int_string(void) { return searpc_compute_signature ("string", 3, "int", "int", "string"); } inline static gchar * searpc_signature_string__string(void) { return searpc_compute_signature ("string", 1, "string"); } inline static gchar * searpc_signature_string__string_int(void) { return searpc_compute_signature ("string", 2, "string", "int"); } inline static gchar * searpc_signature_string__string_int_int(void) { return searpc_compute_signature ("string", 3, "string", "int", "int"); } inline static gchar * searpc_signature_string__string_string(void) { return searpc_compute_signature ("string", 2, "string", "string"); } inline static gchar * searpc_signature_string__string_string_int(void) { return searpc_compute_signature ("string", 3, "string", "string", "int"); } inline static gchar * searpc_signature_string__string_string_int_int(void) { return searpc_compute_signature ("string", 4, "string", "string", "int", "int"); } inline static gchar * searpc_signature_string__string_string_string(void) { return searpc_compute_signature ("string", 3, "string", "string", "string"); } inline static gchar * searpc_signature_string__string_string_string_string(void) { return searpc_compute_signature ("string", 4, "string", "string", "string", "string"); } inline static gchar * searpc_signature_string__string_string_string_string_int(void) { return searpc_compute_signature ("string", 5, "string", "string", "string", "string", "int"); } inline static gchar * searpc_signature_string__string_string_string_string_string(void) { return searpc_compute_signature ("string", 5, "string", "string", "string", "string", "string"); } inline static gchar * searpc_signature_string__string_string_string_string_string_int(void) { return searpc_compute_signature ("string", 6, "string", "string", "string", "string", "string", "int"); } inline static gchar * searpc_signature_string__string_string_string_string_string_string_int(void) { return searpc_compute_signature ("string", 7, "string", "string", "string", "string", "string", "string", "int"); } inline static gchar * searpc_signature_string__string_string_string_string_string_string_int_int(void) { return searpc_compute_signature ("string", 8, "string", "string", "string", "string", "string", "string", "int", "int"); } inline static gchar * searpc_signature_string__string_string_string_string_string_string(void) { return searpc_compute_signature ("string", 6, "string", "string", "string", "string", "string", "string"); } inline static gchar * searpc_signature_string__string_string_string_string_string_string_int64(void) { return searpc_compute_signature ("string", 7, "string", "string", "string", "string", "string", "string", "int64"); } inline static gchar * searpc_signature_string__string_string_string_string_string_string_int64_int(void) { return searpc_compute_signature ("string", 8, "string", "string", "string", "string", "string", "string", "int64", "int"); } inline static gchar * searpc_signature_string__string_string_string_string_string_string_string(void) { return searpc_compute_signature ("string", 7, "string", "string", "string", "string", "string", "string", "string"); } inline static gchar * searpc_signature_string__string_string_string_string_string_string_string_int64(void) { return searpc_compute_signature ("string", 8, "string", "string", "string", "string", "string", "string", "string", "int64"); } inline static gchar * searpc_signature_string__string_string_string_string_string_string_string_string_string(void) { return searpc_compute_signature ("string", 9, "string", "string", "string", "string", "string", "string", "string", "string", "string"); } inline static gchar * searpc_signature_string__string_int_string_string_string_string_string_string_string_string_string_string_int_string(void) { return searpc_compute_signature ("string", 14, "string", "int", "string", "string", "string", "string", "string", "string", "string", "string", "string", "string", "int", "string"); } inline static gchar * searpc_signature_string__string_int_string_int_int(void) { return searpc_compute_signature ("string", 5, "string", "int", "string", "int", "int"); } inline static gchar * searpc_signature_string__string_int_string_string_string(void) { return searpc_compute_signature ("string", 5, "string", "int", "string", "string", "string"); } inline static gchar * searpc_signature_objlist__void(void) { return searpc_compute_signature ("objlist", 0); } inline static gchar * searpc_signature_objlist__int(void) { return searpc_compute_signature ("objlist", 1, "int"); } inline static gchar * searpc_signature_objlist__int_int(void) { return searpc_compute_signature ("objlist", 2, "int", "int"); } inline static gchar * searpc_signature_objlist__int_string(void) { return searpc_compute_signature ("objlist", 2, "int", "string"); } inline static gchar * searpc_signature_objlist__int_int_int(void) { return searpc_compute_signature ("objlist", 3, "int", "int", "int"); } inline static gchar * searpc_signature_objlist__string(void) { return searpc_compute_signature ("objlist", 1, "string"); } inline static gchar * searpc_signature_objlist__string_int(void) { return searpc_compute_signature ("objlist", 2, "string", "int"); } inline static gchar * searpc_signature_objlist__string_int_int(void) { return searpc_compute_signature ("objlist", 3, "string", "int", "int"); } inline static gchar * searpc_signature_objlist__string_int_string(void) { return searpc_compute_signature ("objlist", 3, "string", "int", "string"); } inline static gchar * searpc_signature_objlist__string_string(void) { return searpc_compute_signature ("objlist", 2, "string", "string"); } inline static gchar * searpc_signature_objlist__string_string_string(void) { return searpc_compute_signature ("objlist", 3, "string", "string", "string"); } inline static gchar * searpc_signature_objlist__string_string_int(void) { return searpc_compute_signature ("objlist", 3, "string", "string", "int"); } inline static gchar * searpc_signature_objlist__string_string_string_int(void) { return searpc_compute_signature ("objlist", 4, "string", "string", "string", "int"); } inline static gchar * searpc_signature_objlist__string_string_int_int(void) { return searpc_compute_signature ("objlist", 4, "string", "string", "int", "int"); } inline static gchar * searpc_signature_objlist__string_string_int_int_int(void) { return searpc_compute_signature ("objlist", 5, "string", "string", "int", "int", "int"); } inline static gchar * searpc_signature_objlist__int_string_string_int_int(void) { return searpc_compute_signature ("objlist", 5, "int", "string", "string", "int", "int"); } inline static gchar * searpc_signature_objlist__string_int_string_string_string(void) { return searpc_compute_signature ("objlist", 5, "string", "int", "string", "string", "string"); } inline static gchar * searpc_signature_objlist__string_int_string_int_int(void) { return searpc_compute_signature ("objlist", 5, "string", "int", "string", "int", "int"); } inline static gchar * searpc_signature_objlist__string_int_string_string_int(void) { return searpc_compute_signature ("objlist", 5, "string", "int", "string", "string", "int"); } inline static gchar * searpc_signature_objlist__string_string_string_string_int_int(void) { return searpc_compute_signature ("objlist", 6, "string", "string", "string", "string", "int", "int"); } inline static gchar * searpc_signature_object__void(void) { return searpc_compute_signature ("object", 0); } inline static gchar * searpc_signature_object__int(void) { return searpc_compute_signature ("object", 1, "int"); } inline static gchar * searpc_signature_object__string(void) { return searpc_compute_signature ("object", 1, "string"); } inline static gchar * searpc_signature_object__string_string(void) { return searpc_compute_signature ("object", 2, "string", "string"); } inline static gchar * searpc_signature_object__string_string_string(void) { return searpc_compute_signature ("object", 3, "string", "string", "string"); } inline static gchar * searpc_signature_object__string_int_string(void) { return searpc_compute_signature ("object", 3, "string", "int", "string"); } inline static gchar * searpc_signature_object__int_string_string(void) { return searpc_compute_signature ("object", 3, "int", "string", "string"); } inline static gchar * searpc_signature_object__string_string_string_string_string_string_string_int_int(void) { return searpc_compute_signature ("object", 9, "string", "string", "string", "string", "string", "string", "string", "int", "int"); } inline static gchar * searpc_signature_json__void(void) { return searpc_compute_signature ("json", 0); } inline static gchar * searpc_signature_json__string(void) { return searpc_compute_signature ("json", 1, "string"); } seadrive-fuse-3.0.13/src/set-perm.c000066400000000000000000000017531476177674700170670ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ #include "common.h" #include "utils.h" #define DEBUG_FLAG SEAFILE_DEBUG_SYNC #include "log.h" #include "set-perm.h" #include int seaf_set_path_permission (const char *path, SeafPathPerm perm, gboolean recursive) { struct stat st; mode_t new_mode; if (stat (path, &st) < 0) { seaf_warning ("Failed to stat %s: %s\n", path, strerror(errno)); return -1; } new_mode = st.st_mode; if (perm == SEAF_PATH_PERM_RO) new_mode &= ~(S_IWUSR); else if (perm == SEAF_PATH_PERM_RW) new_mode |= S_IWUSR; if (chmod (path, new_mode) < 0) { seaf_warning ("Failed to chmod %s to %d: %s\n", path, new_mode, strerror(errno)); return -1; } return 0; } int seaf_unset_path_permission (const char *path, gboolean recursive) { return 0; } SeafPathPerm seaf_get_path_permission (const char *path) { return SEAF_PATH_PERM_UNKNOWN; } seadrive-fuse-3.0.13/src/set-perm.h000066400000000000000000000006421476177674700170700ustar00rootroot00000000000000#ifndef SEAF_SET_PERM_H #define SEAF_SET_PERM_H enum SeafPathPerm { SEAF_PATH_PERM_UNKNOWN = 0, SEAF_PATH_PERM_RO, SEAF_PATH_PERM_RW, }; typedef enum SeafPathPerm SeafPathPerm; int seaf_set_path_permission (const char *path, SeafPathPerm perm, gboolean recursive); int seaf_unset_path_permission (const char *path, gboolean recursive); SeafPathPerm seaf_get_path_permission (const char *path); #endif seadrive-fuse-3.0.13/src/sync-mgr.c000066400000000000000000004727561476177674700171110ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ #include "common.h" #include #include "db.h" #include "seafile-session.h" #include "seafile-config.h" #include "sync-mgr.h" #include "seafile-error.h" #include "utils.h" #include "timer.h" #include "change-set.h" #include "diff-simple.h" #include "sync-status-tree.h" #define DEBUG_FLAG SEAFILE_DEBUG_SYNC #include "log.h" #ifndef SEAFILE_CLIENT_VERSION #define SEAFILE_CLIENT_VERSION PACKAGE_VERSION #endif #define DEFAULT_SYNC_INTERVAL 30 /* 30s */ #define CHECK_SYNC_INTERVAL 1000 /* 1s */ #define UPDATE_TX_STATE_INTERVAL 1000 /* 1s */ #define MAX_RUNNING_SYNC_TASKS 5 #define CHECK_SERVER_LOCKED_FILES_INTERVAL 10 /* 10s */ #define CHECK_LOCKED_FILES_INTERVAL 30 /* 30s */ #define CHECK_FOLDER_PERMS_INTERVAL 30 /* 30s */ #define MAX_RESYNC_COUNT 3 #define CHECK_REPO_LIST_INTERVAL 1 /* 1s */ #define JWT_TOKEN_EXPIRE_TIME 3*24*3600 /* 3 days */ struct _HttpServerState { int http_version; gboolean checking; gint64 last_http_check_time; char *testing_host; /* Can be server_url or server_url:8082, depends on which one works. */ char *effective_host; gboolean use_fileserver_port; gboolean notif_server_checked; gboolean notif_server_alive; gboolean folder_perms_not_supported; gint64 last_check_perms_time; gboolean checking_folder_perms; gboolean locked_files_not_supported; gint64 last_check_locked_files_time; gboolean checking_locked_files; gboolean immediate_check_folder_perms; gboolean immediate_check_locked_files; gboolean is_fileserver_repo_list_api_disabled; gint64 n_jwt_token_request; }; typedef struct _HttpServerState HttpServerState; struct _SyncTask; struct _SyncInfo { SeafSyncManager *manager; char repo_id[37]; /* the repo */ struct _SyncTask *current_task; RepoInfo *repo_info; int resync_count; gint64 last_sync_time; gboolean in_sync; /* set to FALSE when sync state is DONE or ERROR */ gint err_cnt; gboolean in_error; /* set to TRUE if err_cnt >= 3 */ gboolean del_confirmation_pending; }; typedef struct _SyncInfo SyncInfo; struct _RepoToken { char token[41]; int repo_version; }; typedef struct _RepoToken RepoToken; enum { SYNC_STATE_DONE, SYNC_STATE_COMMIT, SYNC_STATE_INIT, SYNC_STATE_GET_TOKEN, SYNC_STATE_FETCH, SYNC_STATE_LOAD_REPO, SYNC_STATE_UPLOAD, SYNC_STATE_ERROR, SYNC_STATE_CANCELED, SYNC_STATE_CANCEL_PENDING, SYNC_STATE_NUM, }; enum { SYNC_ERROR_NONE, SYNC_ERROR_ACCESS_DENIED, SYNC_ERROR_NO_WRITE_PERMISSION, SYNC_ERROR_PERM_NOT_SYNCABLE, SYNC_ERROR_QUOTA_FULL, SYNC_ERROR_DATA_CORRUPT, SYNC_ERROR_START_UPLOAD, SYNC_ERROR_UPLOAD, SYNC_ERROR_START_FETCH, SYNC_ERROR_FETCH, SYNC_ERROR_NOREPO, SYNC_ERROR_REPO_CORRUPT, SYNC_ERROR_COMMIT, SYNC_ERROR_FILES_LOCKED, SYNC_ERROR_GET_SYNC_INFO, SYNC_ERROR_FILES_LOCKED_BY_USER, SYNC_ERROR_FOLDER_PERM_DENIED, SYNC_ERROR_DEL_CONFIRMATION_PENDING, SYNC_ERROR_TOO_MANY_FILES, SYNC_ERROR_BLOCK_MISSING, SYNC_ERROR_UNKNOWN, SYNC_ERROR_NUM, }; struct _SyncTask { SyncInfo *info; HttpServerState *server_state; int state; int error; int tx_error_code; gboolean is_clone; gboolean uploaded; char *token; char *password; SeafRepo *repo; /* for convenience, only valid when in_sync. */ char *unsyncable_path; char *server; char *user; }; typedef struct _SyncTask SyncTask; struct _SyncError { char *repo_id; char *repo_name; char *path; int err_id; gint64 timestamp; }; typedef struct _SyncError SyncError; typedef struct DelConfirmationResult { gboolean resync; } DelConfirmationResult; struct _SeafSyncManagerPriv { GHashTable *sync_infos; pthread_mutex_t infos_lock; GHashTable *repo_tokens; int n_running_tasks; GHashTable *http_server_states; pthread_mutex_t server_states_lock; struct SeafTimer *check_sync_timer; struct SeafTimer *update_repo_list_timer; struct SeafTimer *update_tx_state_timer; struct SeafTimer *update_sync_status_timer; GHashTable *active_paths; pthread_mutex_t paths_lock; GAsyncQueue *lock_file_job_queue; GList *sync_errors; pthread_mutex_t errors_lock; gint server_disconnected; GAsyncQueue *cache_file_task_queue; gboolean auto_sync_enabled; pthread_mutex_t del_confirmation_lock; GHashTable *del_confirmation_tasks; }; struct _ActivePathsInfo { struct SyncStatusTree *syncing_tree; struct SyncStatusTree *synced_tree; }; typedef struct _ActivePathsInfo ActivePathsInfo; struct _CacheFileTask { char *repo_id; char *path; }; typedef struct _CacheFileTask CacheFileTask; typedef struct SyncErrorInfo { int error_id; int error_level; } SyncErrorInfo; static SyncErrorInfo sync_error_info_tbl[] = { { SYNC_ERROR_ID_FILE_LOCKED_BY_APP, SYNC_ERROR_LEVEL_FILE, }, // 0 { SYNC_ERROR_ID_FOLDER_LOCKED_BY_APP, SYNC_ERROR_LEVEL_FILE, }, // 1 { SYNC_ERROR_ID_FILE_LOCKED, SYNC_ERROR_LEVEL_FILE, }, // 2 { SYNC_ERROR_ID_INVALID_PATH, SYNC_ERROR_LEVEL_FILE, }, // 3 { SYNC_ERROR_ID_INDEX_ERROR, SYNC_ERROR_LEVEL_FILE, }, // 4 { SYNC_ERROR_ID_ACCESS_DENIED, SYNC_ERROR_LEVEL_REPO, }, // 5 { SYNC_ERROR_ID_QUOTA_FULL, SYNC_ERROR_LEVEL_REPO, }, // 6 { SYNC_ERROR_ID_NETWORK, SYNC_ERROR_LEVEL_NETWORK, }, // 7 { SYNC_ERROR_ID_RESOLVE_PROXY, SYNC_ERROR_LEVEL_NETWORK, }, // 8 { SYNC_ERROR_ID_RESOLVE_HOST, SYNC_ERROR_LEVEL_NETWORK, }, // 9 { SYNC_ERROR_ID_CONNECT, SYNC_ERROR_LEVEL_NETWORK, }, // 10 { SYNC_ERROR_ID_SSL, SYNC_ERROR_LEVEL_NETWORK, }, // 11 { SYNC_ERROR_ID_TX, SYNC_ERROR_LEVEL_NETWORK, }, // 12 { SYNC_ERROR_ID_TX_TIMEOUT, SYNC_ERROR_LEVEL_NETWORK, }, // 13 { SYNC_ERROR_ID_UNHANDLED_REDIRECT, SYNC_ERROR_LEVEL_NETWORK, }, // 14 { SYNC_ERROR_ID_SERVER, SYNC_ERROR_LEVEL_REPO, }, // 15 { SYNC_ERROR_ID_LOCAL_DATA_CORRUPT, SYNC_ERROR_LEVEL_REPO, }, // 16 { SYNC_ERROR_ID_WRITE_LOCAL_DATA, SYNC_ERROR_LEVEL_REPO, }, // 17 { SYNC_ERROR_ID_PERM_NOT_SYNCABLE, SYNC_ERROR_LEVEL_FILE, }, // 18 { SYNC_ERROR_ID_NO_WRITE_PERMISSION, SYNC_ERROR_LEVEL_REPO, }, // 19 { SYNC_ERROR_ID_FOLDER_PERM_DENIED, SYNC_ERROR_LEVEL_FILE, }, // 20 { SYNC_ERROR_ID_PATH_END_SPACE_PERIOD, SYNC_ERROR_LEVEL_FILE, }, // 21 { SYNC_ERROR_ID_PATH_INVALID_CHARACTER, SYNC_ERROR_LEVEL_FILE, }, // 22 { SYNC_ERROR_ID_UPDATE_TO_READ_ONLY_REPO, SYNC_ERROR_LEVEL_FILE, }, // 23 { SYNC_ERROR_ID_CONFLICT, SYNC_ERROR_LEVEL_FILE, }, // 24 { SYNC_ERROR_ID_UPDATE_NOT_IN_REPO, SYNC_ERROR_LEVEL_FILE, }, // 25 { SYNC_ERROR_ID_LIBRARY_TOO_LARGE, SYNC_ERROR_LEVEL_REPO, }, // 26 { SYNC_ERROR_ID_MOVE_NOT_IN_REPO, SYNC_ERROR_LEVEL_FILE, }, // 27 { SYNC_ERROR_ID_DEL_CONFIRMATION_PENDING, SYNC_ERROR_LEVEL_REPO, }, // 28 { SYNC_ERROR_ID_INVALID_PATH_ON_WINDOWS, SYNC_ERROR_LEVEL_FILE, }, // 29 { SYNC_ERROR_ID_TOO_MANY_FILES, SYNC_ERROR_LEVEL_REPO, }, // 30 { SYNC_ERROR_ID_BLOCK_MISSING, SYNC_ERROR_LEVEL_REPO, }, // 31 { SYNC_ERROR_ID_CHECKOUT_FILE, SYNC_ERROR_LEVEL_FILE, }, // 32 { SYNC_ERROR_ID_CASE_CONFLICT, SYNC_ERROR_LEVEL_FILE, }, // 33 { SYNC_ERROR_ID_GENERAL_ERROR, SYNC_ERROR_LEVEL_REPO, }, // 34 { SYNC_ERROR_ID_NO_ERROR, SYNC_ERROR_LEVEL_REPO, }, // 35 }; int sync_error_level (int error) { g_return_val_if_fail ((error >= 0 && error < SYNC_ERROR_ID_NO_ERROR), -1); return sync_error_info_tbl[error].error_level; } static const char *ignore_table[] = { /* tmp files under Linux */ "*~", /* Seafile's backup file */ "*.sbak", /* Emacs tmp files */ "#*#", /* windows image cache */ "Thumbs.db", /* For Mac */ ".DS_Store", "._*", ".fuse_hidden*", NULL, }; static GPatternSpec **ignore_patterns; static GPatternSpec* office_temp_ignore_patterns[5]; static int update_repo_list_pulse (void *vmanager); static int auto_sync_pulse (void *vmanager); static int update_tx_state_pulse (void *vmanager); static SyncInfo* get_sync_info (SeafSyncManager *manager, const char *repo_id) { SyncInfo *info; pthread_mutex_lock (&manager->priv->infos_lock); info = g_hash_table_lookup (manager->priv->sync_infos, repo_id); if (info) goto out; info = g_new0 (SyncInfo, 1); info->manager = manager; memcpy (info->repo_id, repo_id, 36); g_hash_table_insert (manager->priv->sync_infos, info->repo_id, info); out: pthread_mutex_unlock (&manager->priv->infos_lock); return info; } SyncInfo * seaf_sync_manager_get_sync_info (SeafSyncManager *mgr, const char *repo_id) { return g_hash_table_lookup (mgr->priv->sync_infos, repo_id); } void seaf_sync_manager_set_last_sync_time (SeafSyncManager *mgr, const char *repo_id, gint64 last_sync_time) { SyncInfo *info; info = get_sync_info (mgr, repo_id); info->last_sync_time = last_sync_time; } static inline gboolean has_trailing_space_or_period (const char *path) { int len = strlen(path); if (path[len - 1] == ' ' || path[len - 1] == '.') { return TRUE; } return FALSE; } static gboolean seaf_sync_manager_ignored_on_checkout_on_windows (const char *file_path, IgnoreReason *ignore_reason) { gboolean ret = FALSE; static char illegals[] = {'\\', ':', '*', '?', '"', '<', '>', '|', '\b', '\t'}; char **components = g_strsplit (file_path, "/", -1); int n_comps = g_strv_length (components); int j = 0; char *file_name; int i; char c; for (; j < n_comps; ++j) { file_name = components[j]; if (has_trailing_space_or_period (file_name)) { /* Ignore files/dir whose path has trailing spaces. It would cause * problem on windows. */ /* g_debug ("ignore '%s' which contains trailing space in path\n", path); */ ret = TRUE; if (ignore_reason) *ignore_reason = IGNORE_REASON_END_SPACE_PERIOD; goto out; } for (i = 0; i < G_N_ELEMENTS(illegals); i++) { if (strchr (file_name, illegals[i])) { ret = TRUE; if (ignore_reason) *ignore_reason = IGNORE_REASON_INVALID_CHARACTER; goto out; } } for (c = 1; c <= 31; c++) { if (strchr (file_name, c)) { ret = TRUE; if (ignore_reason) *ignore_reason = IGNORE_REASON_INVALID_CHARACTER; goto out; } } } out: g_strfreev (components); return ret; } static HttpServerState * get_http_server_state (SeafSyncManagerPriv *priv, const char *fileserver_addr) { HttpServerState *state; pthread_mutex_lock (&priv->server_states_lock); state = g_hash_table_lookup (priv->http_server_states, fileserver_addr); pthread_mutex_unlock (&priv->server_states_lock); if (state && state->http_version == 0) { return NULL; } return state; } static const char *sync_state_str[] = { "synchronized", "committing", "get sync info", "get token", "downloading", "load repo", "uploading", "error", "canceled", "cancel pending" }; static const char *sync_error_str[] = { "Success", "You do not have permission to access this library", "Do not have write permission to the library", "Do not have permission to sync the library", "The storage space of the library owner has been used up", "Internal data corrupted.", "Failed to start upload.", "Error occurred in upload.", "Failed to start download.", "Error occurred in download.", "No such library on server.", "Library is damaged on server.", "Failed to index files.", "Files are locked by other application", "Failed to get sync info from server.", "File is locked by another user.", "Update to file denied by folder permission setting.", "Waiting for confirmation to delete files.", "Too many files in library.", "Failed to upload file blocks.", "Unknown error.", }; static void sync_task_free (SyncTask *task) { if (task->is_clone) { g_free (task->server); g_free (task->user); } g_free (task->token); g_free (task); } static SyncTask * sync_task_new (SyncInfo *info, HttpServerState *server_state, const char *server, const char *user, const char *token, gboolean is_clone) { SyncTask *task = g_new0 (SyncTask, 1); SeafRepo *repo = NULL; if (!is_clone) { repo = seaf_repo_manager_get_repo (seaf->repo_mgr, info->repo_id); if (!repo) { seaf_warning ("Failed to get repo %s.\n", info->repo_id); return NULL; } } task->info = info; task->server_state = server_state; task->token = g_strdup(token); task->is_clone = is_clone; if (!is_clone) task->repo = repo; if (is_clone) { task->server = g_strdup (server); task->user = g_strdup (user); } info->in_sync = TRUE; info->last_sync_time = time(NULL); ++(info->manager->priv->n_running_tasks); /* Free the last task when a new task is started. * This way we can always get the state of the last task even * after it's done. */ if (task->info->current_task) sync_task_free (task->info->current_task); task->info->current_task = task; return task; } static void send_sync_error_notification (const char *repo_id, const char *repo_name, const char *path, int err_id) { json_t *msg; msg = json_object (); json_object_set_new (msg, "type", json_string("sync.error")); if (repo_id) json_object_set_new (msg, "repo_id", json_string(repo_id)); if (repo_name) json_object_set_new (msg, "repo_name", json_string(repo_name)); if (path) json_object_set_new (msg, "path", json_string(path)); json_object_set_new (msg, "err_id", json_integer(err_id)); mq_mgr_push_msg (seaf->mq_mgr, SEADRIVE_NOTIFY_CHAN, msg); } static void record_sync_error (SeafSyncManager *mgr, const char *repo_id, const char *repo_name, const char *path, int err_id) { GList *errors = mgr->priv->sync_errors, *ptr; SyncError *err, *new_err; gboolean found = FALSE; pthread_mutex_lock (&mgr->priv->errors_lock); for (ptr = errors; ptr; ptr = ptr->next) { err = ptr->data; if (g_strcmp0 (err->repo_id, repo_id) == 0 && g_strcmp0 (err->path, path) == 0) { found = TRUE; if (err->err_id != err_id) { err->err_id = err_id; send_sync_error_notification (repo_id, repo_name, path, err_id); } err->timestamp = (gint64)time(NULL); break; } } if (!found) { new_err = g_new0 (SyncError, 1); new_err->repo_id = g_strdup(repo_id); new_err->repo_name = g_strdup(repo_name); new_err->path = g_strdup(path); new_err->err_id = err_id; new_err->timestamp = (gint64)time(NULL); mgr->priv->sync_errors = g_list_prepend (mgr->priv->sync_errors, new_err); send_sync_error_notification (repo_id, repo_name, path, err_id); } pthread_mutex_unlock (&mgr->priv->errors_lock); } static void remove_sync_error (SeafSyncManager *mgr, const char *repo_id, const char *path) { GList *ptr; SyncError *err; int err_level = -1; pthread_mutex_lock (&mgr->priv->errors_lock); for (ptr = mgr->priv->sync_errors; ptr; ptr = ptr->next) { err = ptr->data; err_level = sync_error_level (err->err_id); // Even though the repo was successfully synced, a file-level sync error was displayed. if (err_level == SYNC_ERROR_LEVEL_FILE) { continue; } if (g_strcmp0 (err->repo_id, repo_id) == 0 && g_strcmp0 (err->path, path) == 0) { mgr->priv->sync_errors = g_list_delete_link (mgr->priv->sync_errors, ptr); g_free (err->repo_id); g_free (err->repo_name); g_free (err->path); g_free (err); break; } } pthread_mutex_unlock (&mgr->priv->errors_lock); } json_t * seaf_sync_manager_list_sync_errors (SeafSyncManager *mgr) { GList *ptr; SyncError *err; json_t *array, *obj; array = json_array (); pthread_mutex_lock (&mgr->priv->errors_lock); for (ptr = mgr->priv->sync_errors; ptr; ptr = ptr->next) { err = ptr->data; obj = json_object (); if (err->repo_id) json_object_set_new (obj, "repo_id", json_string(err->repo_id)); if (err->repo_name) json_object_set_new (obj, "repo_name", json_string(err->repo_name)); if (err->path) json_object_set_new (obj, "path", json_string(err->path)); json_object_set_new (obj, "err_id", json_integer(err->err_id)); json_object_set_new (obj, "timestamp", json_integer(err->timestamp)); json_array_append_new (array, obj); } pthread_mutex_unlock (&mgr->priv->errors_lock); return array; } static int transfer_error_to_error_id (int http_tx_error) { switch (http_tx_error) { case HTTP_TASK_ERR_NET: return SYNC_ERROR_ID_NETWORK; case HTTP_TASK_ERR_RESOLVE_PROXY: return SYNC_ERROR_ID_RESOLVE_PROXY; case HTTP_TASK_ERR_RESOLVE_HOST: return SYNC_ERROR_ID_RESOLVE_HOST; case HTTP_TASK_ERR_CONNECT: return SYNC_ERROR_ID_CONNECT; case HTTP_TASK_ERR_SSL: return SYNC_ERROR_ID_SSL; case HTTP_TASK_ERR_TX: return SYNC_ERROR_ID_TX; case HTTP_TASK_ERR_TX_TIMEOUT: return SYNC_ERROR_ID_TX_TIMEOUT; case HTTP_TASK_ERR_UNHANDLED_REDIRECT: return SYNC_ERROR_ID_UNHANDLED_REDIRECT; case HTTP_TASK_ERR_SERVER: return SYNC_ERROR_ID_SERVER; case HTTP_TASK_ERR_BAD_LOCAL_DATA: return SYNC_ERROR_ID_LOCAL_DATA_CORRUPT; case HTTP_TASK_ERR_WRITE_LOCAL_DATA: return SYNC_ERROR_ID_WRITE_LOCAL_DATA; case HTTP_TASK_ERR_LIBRARY_TOO_LARGE: return SYNC_ERROR_ID_LIBRARY_TOO_LARGE; default: return SYNC_ERROR_ID_GENERAL_ERROR; } } /* Check the notify setting by user. */ static gboolean need_notify_sync (SeafRepo *repo) { char *notify_setting = seafile_session_config_get_string(seaf, "notify_sync"); if (notify_setting == NULL) { seafile_session_config_set_string(seaf, "notify_sync", "on"); return TRUE; } gboolean result = (g_strcmp0(notify_setting, "on") == 0); g_free (notify_setting); return result; } static gboolean find_meaningful_commit (SeafCommit *commit, void *data, gboolean *stop) { SeafCommit **p_head = data; if (commit->second_parent_id && commit->new_merge && !commit->conflict) return TRUE; *stop = TRUE; seaf_commit_ref (commit); *p_head = commit; return TRUE; } static void notify_sync (SeafRepo *repo) { SeafCommit *head = NULL; if (!seaf_commit_manager_traverse_commit_tree_truncated (seaf->commit_mgr, repo->id, repo->version, repo->head->commit_id, find_meaningful_commit, &head, FALSE)) { seaf_warning ("Failed to traverse commit tree of %.8s.\n", repo->id); return; } if (!head) return; json_t *msg = json_object (); if (!repo->partial_commit_mode) json_object_set_string_member (msg, "type", "sync.done"); else json_object_set_string_member (msg, "type", "sync.multipart_upload"); json_object_set_string_member (msg, "repo_id", repo->id); json_object_set_string_member (msg, "repo_name", repo->name); json_object_set_string_member (msg, "commit_id", head->commit_id); json_object_set_string_member (msg, "parent_commit_id", head->parent_id); json_object_set_string_member (msg, "commit_desc", head->desc); mq_mgr_push_msg (seaf->mq_mgr, SEADRIVE_NOTIFY_CHAN, msg); seaf_commit_unref (head); } #define IN_ERROR_THRESHOLD 3 static gboolean is_permanent_error (SyncTask *task) { if (task->error == SYNC_ERROR_ACCESS_DENIED || task->error == SYNC_ERROR_NO_WRITE_PERMISSION || task->error == SYNC_ERROR_FOLDER_PERM_DENIED || task->error == SYNC_ERROR_PERM_NOT_SYNCABLE || task->error == SYNC_ERROR_QUOTA_FULL || task->error == SYNC_ERROR_TOO_MANY_FILES || task->error == SYNC_ERROR_BLOCK_MISSING) { return TRUE; } return FALSE; } static void update_sync_info_error_state (SyncTask *task, int new_state) { SyncInfo *info = task->info; if (new_state == SYNC_STATE_ERROR && is_permanent_error (task)) { info->err_cnt++; if (info->err_cnt >= IN_ERROR_THRESHOLD) { info->in_error = TRUE; /* Only delete a local repo after 3 consecutive permission errors. * This prevents mistakenly re-syncing a repo on temporary database * errors in the server. */ /* TODO: Currently, when there is a database error on the server when * checking permissions, HTTP_FORBIDDEN is returned. On massive database * errors, it'll cause all clients to un-sync all repos. To prevent * confusing users, we don't un-sync repos automatically for the mean * time. */ /* seaf_repo_manager_mark_repo_deleted (seaf->repo_mgr, task->repo, FALSE); */ } } else if (info->err_cnt > 0) { info->err_cnt = 0; info->in_error = FALSE; } if (task->tx_error_code == HTTP_TASK_ERR_LIBRARY_TOO_LARGE) info->in_error = TRUE; } static inline void transition_sync_state (SyncTask *task, int new_state) { g_return_if_fail (new_state >= 0 && new_state < SYNC_STATE_NUM); if (task->state != new_state) { seaf_message ("Repo '%s' sync state transition from '%s' to '%s'.\n", task->info->repo_info->name, sync_state_str[task->state], sync_state_str[new_state]); if ((task->state == SYNC_STATE_INIT && task->uploaded && new_state == SYNC_STATE_DONE && need_notify_sync(task->repo))) notify_sync (task->repo); task->state = new_state; if (new_state == SYNC_STATE_DONE || new_state == SYNC_STATE_CANCELED || new_state == SYNC_STATE_ERROR) { task->info->in_sync = FALSE; --(task->info->manager->priv->n_running_tasks); update_sync_info_error_state (task, new_state); if (new_state == SYNC_STATE_DONE) remove_sync_error (seaf->sync_mgr, task->info->repo_info->id, NULL); if (task->repo) seaf_repo_unref (task->repo); } } } void seaf_sync_manager_set_task_error (SyncTask *task, int error) { g_return_if_fail (error >= 0 && error < SYNC_ERROR_NUM); if (task->state != SYNC_STATE_ERROR) { seaf_message ("Repo '%s' sync state transition from %s to '%s': '%s'.\n", task->info->repo_info->name, sync_state_str[task->state], sync_state_str[SYNC_STATE_ERROR], sync_error_str[error]); task->state = SYNC_STATE_ERROR; task->error = error; task->info->in_sync = FALSE; --(task->info->manager->priv->n_running_tasks); update_sync_info_error_state (task, SYNC_STATE_ERROR); int sync_error_id; if (task->error == SYNC_ERROR_ACCESS_DENIED) sync_error_id = SYNC_ERROR_ID_ACCESS_DENIED; else if (task->error == SYNC_ERROR_NO_WRITE_PERMISSION) sync_error_id = SYNC_ERROR_ID_NO_WRITE_PERMISSION; else if (task->error == SYNC_ERROR_PERM_NOT_SYNCABLE) sync_error_id = SYNC_ERROR_ID_PERM_NOT_SYNCABLE; else if (task->error == SYNC_ERROR_QUOTA_FULL) sync_error_id = SYNC_ERROR_ID_QUOTA_FULL; else if (task->error == SYNC_ERROR_DATA_CORRUPT) sync_error_id = SYNC_ERROR_ID_LOCAL_DATA_CORRUPT; else if (task->error == SYNC_ERROR_FILES_LOCKED_BY_USER) sync_error_id = SYNC_ERROR_ID_FILE_LOCKED; else if (task->error == SYNC_ERROR_FOLDER_PERM_DENIED) sync_error_id = SYNC_ERROR_ID_FOLDER_PERM_DENIED; else if (task->error == SYNC_ERROR_DEL_CONFIRMATION_PENDING) sync_error_id = SYNC_ERROR_ID_DEL_CONFIRMATION_PENDING; else if (task->error == SYNC_ERROR_TOO_MANY_FILES) sync_error_id = SYNC_ERROR_ID_TOO_MANY_FILES; else if (task->error == SYNC_ERROR_BLOCK_MISSING) sync_error_id = SYNC_ERROR_ID_BLOCK_MISSING; else if (task->tx_error_code > 0) sync_error_id = transfer_error_to_error_id (task->tx_error_code); else sync_error_id = SYNC_ERROR_ID_GENERAL_ERROR; record_sync_error (seaf->sync_mgr, task->info->repo_info->id, task->info->repo_info->name, task->unsyncable_path, sync_error_id); /* If local metadata is corrupted, remove local repo and resync later. */ if (sync_error_id == SYNC_ERROR_ID_LOCAL_DATA_CORRUPT) { if (task->info->resync_count < MAX_RESYNC_COUNT) { seaf_message ("Repo %s(%s) local metadata is corrupted. Remove and resync later.\n", task->repo->name, task->repo->id); seaf_repo_manager_mark_repo_deleted (seaf->repo_mgr, task->repo, FALSE); ++(task->info->resync_count); } } if (task->repo) seaf_repo_unref (task->repo); } } static void active_paths_info_free (ActivePathsInfo *info); SeafSyncManager* seaf_sync_manager_new (SeafileSession *seaf) { SeafSyncManager *mgr = g_new0 (SeafSyncManager, 1); mgr->priv = g_new0 (SeafSyncManagerPriv, 1); mgr->seaf = seaf; mgr->priv->sync_infos = g_hash_table_new (g_str_hash, g_str_equal); pthread_mutex_init (&mgr->priv->infos_lock, NULL); mgr->priv->repo_tokens = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); mgr->priv->http_server_states = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); pthread_mutex_init (&mgr->priv->server_states_lock, NULL); gboolean exists; int download_limit = seafile_session_config_get_int (seaf, KEY_DOWNLOAD_LIMIT, &exists); if (exists) mgr->download_limit = download_limit; int upload_limit = seafile_session_config_get_int (seaf, KEY_UPLOAD_LIMIT, &exists); if (exists) mgr->upload_limit = upload_limit; mgr->priv->active_paths = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)active_paths_info_free); pthread_mutex_init (&mgr->priv->paths_lock, NULL); ignore_patterns = g_new0 (GPatternSpec *, G_N_ELEMENTS(ignore_table)); int i; for (i = 0; ignore_table[i] != NULL; i++) { ignore_patterns[i] = g_pattern_spec_new (ignore_table[i]); } office_temp_ignore_patterns[0] = g_pattern_spec_new("~$*"); /* for files like ~WRL0001.tmp for docx and *.tmp for xlsx and pptx */ office_temp_ignore_patterns[1] = g_pattern_spec_new("*.tmp"); office_temp_ignore_patterns[2] = g_pattern_spec_new(".~lock*#"); /* for temporary files of WPS Office */ office_temp_ignore_patterns[3] = g_pattern_spec_new(".~*"); office_temp_ignore_patterns[4] = NULL; mgr->priv->del_confirmation_tasks = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); pthread_mutex_init (&mgr->priv->del_confirmation_lock, NULL); mgr->priv->lock_file_job_queue = g_async_queue_new (); pthread_mutex_init (&mgr->priv->errors_lock, NULL); mgr->priv->cache_file_task_queue = g_async_queue_new (); mgr->priv->auto_sync_enabled = TRUE; return mgr; } int seaf_sync_manager_init (SeafSyncManager *mgr) { return 0; } static void on_repo_http_fetched (SeafileSession *seaf, HttpTxTask *tx_task, SeafSyncManager *mgr); static void on_repo_http_uploaded (SeafileSession *seaf, HttpTxTask *tx_task, SeafSyncManager *mgr); static void* check_space_usage_thread (void *data); static void* lock_file_worker (void *data); static void* cache_file_task_worker (void *data); int seaf_sync_manager_start (SeafSyncManager *mgr) { mgr->priv->check_sync_timer = seaf_timer_new ( auto_sync_pulse, mgr, CHECK_SYNC_INTERVAL); mgr->priv->update_repo_list_timer = seaf_timer_new ( update_repo_list_pulse, mgr, CHECK_REPO_LIST_INTERVAL*1000); mgr->priv->update_tx_state_timer = seaf_timer_new ( update_tx_state_pulse, mgr, UPDATE_TX_STATE_INTERVAL); g_signal_connect (seaf, "repo-http-fetched", (GCallback)on_repo_http_fetched, mgr); g_signal_connect (seaf, "repo-http-uploaded", (GCallback)on_repo_http_uploaded, mgr); pthread_t tid; pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); int rc = pthread_create (&tid, &attr, check_space_usage_thread, NULL); if (rc != 0) { seaf_warning ("Failed to create check space thread: %s.\n", strerror(rc)); } rc = pthread_create (&tid, &attr, lock_file_worker, mgr->priv->lock_file_job_queue); if (rc != 0) { seaf_warning ("Failed to create lock file thread: %s.\n", strerror(rc)); } rc = pthread_create (&tid, NULL, cache_file_task_worker, mgr->priv->cache_file_task_queue); if (rc != 0) { seaf_warning ("Failed to create cache file task worker thread: %s.\n", strerror(rc)); } return 0; } /* Synchronization framework. */ typedef struct _FolderPermInfo { HttpServerState *server_state; SyncTask *task; char *server; char *user; } FolderPermInfo; static void folder_perm_info_free (FolderPermInfo *info) { if (!info) return; g_free (info->server); g_free (info->user); g_free (info); } static void check_account_space_usage (SeafAccount *account) { gint64 total, used; if (http_tx_manager_api_get_space_usage (seaf->http_tx_mgr, account->server, account->token, &total, &used) < 0) { seaf_warning ("Failed to get space usage for account %s/%s\n", account->server, account->username); return; } seaf_repo_manager_set_account_space (seaf->repo_mgr, account->server, account->username, total, used); } static void check_accounts_space_usage () { GList *accounts = NULL, *ptr; SeafAccount *account; accounts = seaf_repo_manager_get_account_list (seaf->repo_mgr); if (!accounts) return; for (ptr = accounts; ptr; ptr = ptr->next) { account = ptr->data; check_account_space_usage ( account); } g_list_free_full (accounts, (GDestroyNotify)seaf_account_free); } static void* check_space_usage_thread (void *data) { seaf_sleep (5); while (1) { check_accounts_space_usage (); seaf_sleep (60); } return NULL; } static int parse_repo_list (const char *rsp_content, int rsp_size, GHashTable *repos) { json_t *array = NULL, *object, *member; json_error_t jerror; size_t n; int i; RepoInfo *repo; const char *repo_id, *head_commit_id, *name = NULL, *permission, *type_str, *owner; gint64 mtime; int version; gboolean is_readonly, is_corrupted = FALSE; RepoType type; int ret = 0; RepoInfo *repo_info = NULL; array = json_loadb (rsp_content, rsp_size, 0, &jerror); if (!array) { seaf_warning ("Parse response failed: %s.\n", jerror.text); return -1; } n = json_array_size (array); for (i = 0; i < n; ++i) { object = json_array_get (array, i); member = json_object_get (object, "version"); if (!member) { seaf_warning ("Invalid repo list response: no version.\n"); ret = -1; goto out; } version = json_integer_value (member); if (version == 0) { continue; } member = json_object_get (object, "id"); if (!member) { seaf_warning ("Invalid repo list response: no id.\n"); ret = -1; goto out; } repo_id = json_string_value (member); if (!is_uuid_valid (repo_id)) { seaf_warning ("Invalid repo list response: invalid repo_id.\n"); ret = -1; goto out; } member = json_object_get (object, "head_commit_id"); if (!member) { seaf_warning ("Invalid repo list response: no head_commit_id.\n"); ret = -1; goto out; } head_commit_id = json_string_value (member); if (!is_object_id_valid (head_commit_id)) { seaf_warning ("Invalid repo list response: invalid head_commit_id.\n"); ret = -1; goto out; } member = json_object_get (object, "name"); if (!member) { seaf_warning ("Invalid repo list response: no name.\n"); ret = -1; goto out; } if (member == json_null()) { is_corrupted = TRUE; } else { name = json_string_value (member); } member = json_object_get (object, "mtime"); if (!member) { seaf_warning ("Invalid repo list response: no mtime.\n"); ret = -1; goto out; } mtime = json_integer_value (member); member = json_object_get (object, "permission"); if (!member) { seaf_warning ("Invalid repo list response: no permission.\n"); ret = -1; goto out; } permission = json_string_value (member); if (g_strcmp0 (permission, "rw") == 0) is_readonly = FALSE; else is_readonly = TRUE; member = json_object_get (object, "owner"); if (!member) { seaf_warning ("Invalid repo list response: no owner.\n"); ret = -1; goto out; } owner = json_string_value (member); member = json_object_get (object, "type"); if (!member) { seaf_warning ("Invalid repo list response: no repo type.\n"); ret = -1; goto out; } type_str = json_string_value (member); if (g_strcmp0 (type_str, "repo") == 0) { type = REPO_TYPE_MINE; } else if (g_strcmp0 (type_str, "srepo") == 0) { type = REPO_TYPE_SHARED; } else if (g_strcmp0 (type_str, "grepo") == 0) { if (g_strcmp0 (owner, "Organization") == 0) type = REPO_TYPE_PUBLIC; else type = REPO_TYPE_GROUP; } else { seaf_warning ("Unknown repo type: %s.\n", type_str); ret = -1; goto out; } if ((repo_info = g_hash_table_lookup (repos, repo_id))) { if (repo_info->type == REPO_TYPE_MINE || (!repo_info->is_readonly && is_readonly)) continue; } repo = repo_info_new (repo_id, head_commit_id, name, mtime, is_readonly); repo->is_corrupted = is_corrupted; repo->type = type; g_hash_table_insert (repos, g_strdup(repo_id), repo); } out: json_decref (array); return ret; } #define HTTP_SERVERR_BAD_GATEWAY 502 #define HTTP_SERVERR_UNAVAILABLE 503 #define HTTP_SERVERR_TIMEOUT 504 static void update_current_repos(HttpAPIGetResult *result, void *user_data) { SeafAccount *account = user_data; SeafAccount *curr_account; GHashTable *repo_hash; GList *added = NULL, *removed = NULL, *ptr; RepoInfo *info; SeafRepo *repo; if (!result->success) { if (result->http_status == HTTP_SERVERR_BAD_GATEWAY || result->http_status == HTTP_SERVERR_UNAVAILABLE || result->http_status == HTTP_SERVERR_TIMEOUT) { seaf_repo_manager_set_account_server_disconnected (seaf->repo_mgr, account->server, account->username, TRUE); } record_sync_error (seaf->sync_mgr, NULL, NULL, NULL, transfer_error_to_error_id (result->error_code)); g_atomic_int_set (&seaf->sync_mgr->priv->server_disconnected, 1); return; } else { //If the fileserver hangs up before sending events to the notification server, // the client will not receive the updates, and some updates will be lost. // Therefore, after the fileserver recovers, immediately checking locks and folder perms. if (account->server_disconnected) { seaf_sync_manager_check_locks_and_folder_perms (seaf->sync_mgr, account->fileserver_addr); } remove_sync_error (seaf->sync_mgr, NULL, NULL); g_atomic_int_set (&seaf->sync_mgr->priv->server_disconnected, 0); seaf_repo_manager_set_account_server_disconnected (seaf->repo_mgr, account->server, account->username, FALSE); } /* If the get repo list request was sent around account deleting, * this callback may be called after the account has been deleted. */ curr_account = seaf_repo_manager_get_account (seaf->repo_mgr, account->server, account->username); if (!curr_account) return; seaf_account_free (curr_account); repo_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)repo_info_free); if (parse_repo_list (result->rsp_content, result->rsp_size, repo_hash) < 0) { goto out; } seaf_repo_manager_set_repo_list_fetched (seaf->repo_mgr, account->server, account->username); seaf_repo_manager_update_account_repos (seaf->repo_mgr, account->server, account->username, repo_hash, &added, &removed); /* Mark repos removed on the server as delete-pending. */ for (ptr = removed; ptr; ptr = ptr->next) { info = ptr->data; repo = seaf_repo_manager_get_repo (seaf->repo_mgr, info->id); if (repo) { seaf_message ("Repo %s(%.8s) was deleted on server. Remove local repo.\n", repo->name, repo->id); seaf_repo_manager_mark_repo_deleted (seaf->repo_mgr, repo, TRUE); http_tx_manager_cancel_task (seaf->http_tx_mgr, info->id, HTTP_TASK_TYPE_DOWNLOAD); http_tx_manager_cancel_task (seaf->http_tx_mgr, info->id, HTTP_TASK_TYPE_UPLOAD); seaf_repo_unref (repo); } } out: g_list_free_full (added, (GDestroyNotify)repo_info_free); g_list_free_full (removed, (GDestroyNotify)repo_info_free); g_hash_table_destroy (repo_hash); seaf_account_free (account); } static void get_repo_list_cb (HttpAPIGetResult *result, void *user_data) { update_current_repos (result, user_data); } #define HTTP_FORBIDDEN 403 #define HTTP_NOT_FOUND 404 static void fileserver_get_repo_list_cb (HttpAPIGetResult *result, void *user_data) { SeafAccount *account = user_data; if (result->http_status == HTTP_NOT_FOUND || result->http_status == HTTP_FORBIDDEN) { char *url = NULL; if (result->http_status == HTTP_NOT_FOUND) { HttpServerState *state; state = get_http_server_state (seaf->sync_mgr->priv, account->fileserver_addr); if (state) state->is_fileserver_repo_list_api_disabled = TRUE; } // use seahub API url = g_strdup_printf ("%s/api2/repos/", account->server); if (http_tx_manager_api_get (seaf->http_tx_mgr, account->server, url, account->token, get_repo_list_cb, account) < 0) { seaf_account_free (account); } g_free (url); return; } update_current_repos (result, user_data); } static int update_account_repo_list (SeafSyncManager *manager, const char *server, const char *username) { HttpServerState *state; char *url = NULL; char *repo_id = NULL; char *repo_token = NULL; SeafAccount *account = seaf_repo_manager_get_account (seaf->repo_mgr, server, username); if (!account) return TRUE; //get the first repo_id and repo_token from curr_repos which will be used to validate the user repo_id = seaf_repo_manager_get_first_repo_token_from_account_repos (seaf->repo_mgr, server, username, &repo_token); state = get_http_server_state (manager->priv, account->fileserver_addr); if (!state || !repo_id || state->is_fileserver_repo_list_api_disabled) { url = g_strdup_printf ("%s/api2/repos/", account->server); if (http_tx_manager_api_get (seaf->http_tx_mgr, account->server, url, account->token, get_repo_list_cb, account) < 0) { seaf_warning ("Failed to start get repo list from server %s\n", account->server); seaf_account_free (account); } g_free (repo_id); g_free (repo_token); g_free (url); return TRUE; } if (!state->use_fileserver_port) url = g_strdup_printf ("%s/seafhttp/accessible-repos/?repo_id=%s", state->effective_host, repo_id); else url = g_strdup_printf ("%s/accessible-repos/?repo_id=%s", state->effective_host, repo_id); if (http_tx_manager_fileserver_api_get (seaf->http_tx_mgr, state->effective_host, url, repo_token, fileserver_get_repo_list_cb, account) < 0) { seaf_warning ("Failed to start get repo list from server %s\n", account->server); seaf_account_free (account); } g_free (repo_id); g_free (repo_token); g_free (url); return TRUE; } static int update_repo_list_pulse (void *vmanager) { SeafSyncManager *manager = vmanager; GList *accounts = NULL, *ptr; SeafAccount *account; accounts = seaf_repo_manager_get_account_list (seaf->repo_mgr); if (!accounts) return TRUE; for (ptr = accounts; ptr; ptr = ptr->next) { account = ptr->data; update_account_repo_list (manager, account->server, account->username); } g_list_free_full (accounts, (GDestroyNotify)seaf_account_free); return TRUE; } int seaf_sync_manager_update_account_repo_list (SeafSyncManager *mgr, const char *server, const char *user) { return update_account_repo_list (mgr, server, user); } static void check_folder_perms_done (HttpFolderPerms *result, void *user_data) { FolderPermInfo *perm_info = user_data; HttpServerState *server_state = perm_info->server_state; GList *ptr; HttpFolderPermRes *res; gint64 now = (gint64)time(NULL); server_state->checking_folder_perms = FALSE; if (!result->success) { /* If on star-up we find that checking folder perms fails, * we assume the server doesn't support it. */ if (server_state->last_check_perms_time == 0) server_state->folder_perms_not_supported = TRUE; server_state->last_check_perms_time = now; folder_perm_info_free (perm_info); return; } SyncInfo *info; for (ptr = result->results; ptr; ptr = ptr->next) { res = ptr->data; info = get_sync_info (seaf->sync_mgr, res->repo_id); // Getting folder perms before clone repo will set task, at this point we need to update the folder perms. if (info->in_sync && !perm_info->task) continue; seaf_repo_manager_update_folder_perms (seaf->repo_mgr, res->repo_id, FOLDER_PERM_TYPE_USER, res->user_perms); seaf_repo_manager_update_folder_perms (seaf->repo_mgr, res->repo_id, FOLDER_PERM_TYPE_GROUP, res->group_perms); seaf_repo_manager_update_folder_perm_timestamp (seaf->repo_mgr, res->repo_id, res->timestamp); } server_state->last_check_perms_time = now; folder_perm_info_free (perm_info); } static void check_folder_permissions_immediately (SeafSyncManager *mgr, HttpServerState *server_state, const char *server, const char *user, gboolean force) { GList *repo_ids; GList *ptr; char *repo_id; SeafRepo *repo; gint64 timestamp; HttpFolderPermReq *req; GList *requests = NULL; repo_ids = seaf_repo_manager_get_account_repo_ids (seaf->repo_mgr, server, user); for (ptr = repo_ids; ptr; ptr = ptr->next) { repo_id = ptr->data; repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id); if (!repo) continue; #ifdef COMPILE_WS // Don't need to check folder perms regularly when we get folder perms from notification server. if (!force && seaf_notif_manager_is_repo_subscribed (seaf->notif_mgr, repo)) { seaf_repo_unref (repo); continue; } #endif if (!repo->token) { seaf_repo_unref (repo); continue; } timestamp = seaf_repo_manager_get_folder_perm_timestamp (seaf->repo_mgr, repo->id); if (timestamp < 0) timestamp = 0; req = g_new0 (HttpFolderPermReq, 1); memcpy (req->repo_id, repo->id, 36); req->token = g_strdup(repo->token); req->timestamp = timestamp; requests = g_list_append (requests, req); seaf_repo_unref (repo); } g_list_free_full (repo_ids, g_free); if (!requests) return; server_state->checking_folder_perms = TRUE; FolderPermInfo *info = g_new0 (FolderPermInfo, 1); info->server_state = server_state; info->server = g_strdup (server); info->user = g_strdup (user); /* The requests list will be freed in http tx manager. */ if (http_tx_manager_get_folder_perms (seaf->http_tx_mgr, server_state->effective_host, server_state->use_fileserver_port, requests, check_folder_perms_done, info) < 0) { seaf_warning ("Failed to schedule check folder permissions\n"); server_state->checking_folder_perms = FALSE; folder_perm_info_free (info); } } static void check_folder_permissions (SeafSyncManager *mgr, HttpServerState *server_state, const char *server, const char *user) { gint64 now = (gint64)time(NULL); if (server_state->http_version == 0 || server_state->folder_perms_not_supported || server_state->checking_folder_perms) return; if (server_state->immediate_check_folder_perms) { server_state->immediate_check_folder_perms = FALSE; check_folder_permissions_immediately (mgr, server_state, server, user, TRUE); return; } if (server_state->last_check_perms_time > 0 && now - server_state->last_check_perms_time < CHECK_FOLDER_PERMS_INTERVAL) return; check_folder_permissions_immediately (mgr, server_state, server, user, FALSE); } static void check_server_locked_files_done (HttpLockedFiles *result, void *user_data) { HttpServerState *server_state = user_data; GList *ptr; HttpLockedFilesRes *locked_res; gint64 now = (gint64)time(NULL); server_state->checking_locked_files = FALSE; if (!result->success) { /* If on star-up we find that checking locked files fails, * we assume the server doesn't support it. */ if (server_state->last_check_locked_files_time == 0) server_state->locked_files_not_supported = TRUE; server_state->last_check_locked_files_time = now; return; } SyncInfo *info; for (ptr = result->results; ptr; ptr = ptr->next) { locked_res = ptr->data; info = get_sync_info (seaf->sync_mgr, locked_res->repo_id); if (info->in_sync) continue; seaf_filelock_manager_update (seaf->filelock_mgr, locked_res->repo_id, locked_res->locked_files); seaf_filelock_manager_update_timestamp (seaf->filelock_mgr, locked_res->repo_id, locked_res->timestamp); } server_state->last_check_locked_files_time = now; } static void check_locked_files_immediately (SeafSyncManager *mgr, HttpServerState *server_state, const char *server, const char *user, gboolean force) { GList *repo_ids; GList *ptr; char *repo_id; SeafRepo *repo; gint64 timestamp; HttpLockedFilesReq *req; GList *requests = NULL; repo_ids = seaf_repo_manager_get_account_repo_ids (seaf->repo_mgr, server, user); for (ptr = repo_ids; ptr; ptr = ptr->next) { repo_id = ptr->data; repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id); if (!repo) continue; #ifdef COMPILE_WS // Don't need to check locked files regularly when we get locked files from notification server. if (!force && seaf_notif_manager_is_repo_subscribed (seaf->notif_mgr, repo)) { seaf_repo_unref (repo); continue; } #endif if (!repo->token) { seaf_repo_unref (repo); continue; } timestamp = seaf_filelock_manager_get_timestamp (seaf->filelock_mgr, repo->id); if (timestamp < 0) timestamp = 0; req = g_new0 (HttpLockedFilesReq, 1); memcpy (req->repo_id, repo->id, 36); req->token = g_strdup(repo->token); req->timestamp = timestamp; requests = g_list_append (requests, req); seaf_repo_unref (repo); } g_list_free_full (repo_ids, g_free); if (!requests) return; server_state->checking_locked_files = TRUE; /* The requests list will be freed in http tx manager. */ if (http_tx_manager_get_locked_files (seaf->http_tx_mgr, server_state->effective_host, server_state->use_fileserver_port, requests, check_server_locked_files_done, server_state) < 0) { seaf_warning ("Failed to schedule check server locked files\n"); server_state->checking_locked_files = FALSE; } } static void check_locked_files (SeafSyncManager *mgr, HttpServerState *server_state, const char *server, const char *user) { gint64 now = (gint64)time(NULL); if (server_state->http_version == 0 || server_state->locked_files_not_supported || server_state->checking_locked_files) return; if (server_state->immediate_check_locked_files) { server_state->immediate_check_locked_files = FALSE; check_locked_files_immediately (mgr, server_state, server, user, TRUE); return; } if (server_state->last_check_locked_files_time > 0 && now - server_state->last_check_locked_files_time < CHECK_SERVER_LOCKED_FILES_INTERVAL) return; check_locked_files_immediately (mgr, server_state, server, user, FALSE); } static char * http_fileserver_url (const char *url) { const char *host; char *colon; char *url_no_port; char *ret = NULL; /* Just return the url itself if it's invalid. */ if (strlen(url) <= strlen("http://")) return g_strdup(url); /* Skip protocol schem. */ host = url + strlen("http://"); colon = strrchr (host, ':'); if (colon) { url_no_port = g_strndup(url, colon - url); ret = g_strconcat(url_no_port, ":8082", NULL); g_free (url_no_port); } else { ret = g_strconcat(url, ":8082", NULL); } return ret; } static void check_http_fileserver_protocol_done (HttpProtocolVersion *result, void *user_data) { HttpServerState *state = user_data; state->checking = FALSE; if (result->check_success && !result->not_supported) { state->http_version = result->version; state->effective_host = http_fileserver_url(state->testing_host); state->use_fileserver_port = TRUE; } } static void check_http_protocol_done (HttpProtocolVersion *result, void *user_data) { HttpServerState *state = user_data; if (result->check_success && !result->not_supported) { state->http_version = result->version; state->effective_host = g_strdup(state->testing_host); state->checking = FALSE; } else if (strncmp(state->testing_host, "https", 5) != 0) { char *host_fileserver = http_fileserver_url(state->testing_host); if (http_tx_manager_check_protocol_version (seaf->http_tx_mgr, host_fileserver, TRUE, check_http_fileserver_protocol_done, state) < 0) state->checking = FALSE; g_free (host_fileserver); } else { state->checking = FALSE; } } #define CHECK_HTTP_INTERVAL 10 /* * Returns TRUE if we're ready to use http-sync; otherwise FALSE. */ static gboolean check_http_protocol (SeafSyncManager *mgr, const char *server_url) { pthread_mutex_lock (&mgr->priv->server_states_lock); HttpServerState *state = g_hash_table_lookup (mgr->priv->http_server_states, server_url); if (!state) { state = g_new0 (HttpServerState, 1); g_hash_table_insert (mgr->priv->http_server_states, g_strdup(server_url), state); } pthread_mutex_unlock (&mgr->priv->server_states_lock); if (state->checking) { return FALSE; } if (state->http_version > 0) { return TRUE; } /* If we haven't detected the server url successfully, retry every 10 seconds. */ gint64 now = time(NULL); if (now - state->last_http_check_time < CHECK_HTTP_INTERVAL) return FALSE; /* First try server_url. * If it fails and https is not used, try server_url:8082 instead. */ g_free (state->testing_host); state->testing_host = g_strdup(server_url); state->last_http_check_time = (gint64)time(NULL); if (http_tx_manager_check_protocol_version (seaf->http_tx_mgr, server_url, FALSE, check_http_protocol_done, state) < 0) return FALSE; state->checking = TRUE; return FALSE; } static void check_notif_server_done (gboolean is_alive, void *user_data) { HttpServerState *state = user_data; if (is_alive) { state->notif_server_alive = TRUE; seaf_message ("Notification server is enabled on the remote server %s.\n", state->effective_host); } } static char * http_notification_url (const char *url) { const char *host; char *colon; char *url_no_port; char *ret = NULL; /* Just return the url itself if it's invalid. */ if (strlen(url) <= strlen("http://")) return g_strdup(url); /* Skip protocol schem. */ host = url + strlen("http://"); colon = strrchr (host, ':'); if (colon) { url_no_port = g_strndup(url, colon - url); ret = g_strconcat(url_no_port, ":8083", NULL); g_free (url_no_port); } else { ret = g_strconcat(url, ":8083", NULL); } return ret; } #ifdef COMPILE_WS // Returns TRUE if notification server is alive; otherwise FALSE. // We only check notification server once. static gboolean check_notif_server (SeafSyncManager *mgr, const char *server_url) { pthread_mutex_lock (&mgr->priv->server_states_lock); HttpServerState *state = g_hash_table_lookup (mgr->priv->http_server_states, server_url); pthread_mutex_unlock (&mgr->priv->server_states_lock); if (!state) { return FALSE; } if (state->notif_server_alive) { return TRUE; } if (state->notif_server_checked) { return FALSE; } char *notif_url = NULL; if (state->use_fileserver_port) { notif_url = http_notification_url (server_url); } else { notif_url = g_strdup (server_url); } if (http_tx_manager_check_notif_server (seaf->http_tx_mgr, notif_url, state->use_fileserver_port, check_notif_server_done, state) < 0) { g_free (notif_url); return FALSE; } state->notif_server_checked = TRUE; g_free (notif_url); return FALSE; } #endif gint cmp_sync_info_by_sync_time (gconstpointer a, gconstpointer b, gpointer user_data) { const SyncInfo *info_a = a; const SyncInfo *info_b = b; return (info_a->last_sync_time - info_b->last_sync_time); } inline static gboolean exceed_max_tasks (SeafSyncManager *manager) { if (manager->priv->n_running_tasks >= MAX_RUNNING_SYNC_TASKS) return TRUE; return FALSE; } static void notify_fs_loaded () { json_t *msg = json_object (); json_object_set_string_member (msg, "type", "fs-loaded"); mq_mgr_push_msg (seaf->mq_mgr, SEADRIVE_NOTIFY_CHAN, msg); seaf_message ("All repo fs trees are loaded.\n"); } static char * parse_jwt_token (const char *rsp_content, gint64 rsp_size) { json_t *object = NULL; json_error_t jerror; const char *member = NULL; char *jwt_token = NULL; object = json_loadb (rsp_content, rsp_size, 0, &jerror); if (!object) { return NULL; } if (json_object_has_member (object, "jwt_token")) { member = json_object_get_string_member (object, "jwt_token"); if (member) jwt_token = g_strdup (member); } else { json_decref (object); return NULL; } json_decref (object); return jwt_token; } #define HTTP_FORBIDDEN 403 #define HTTP_NOT_FOUND 404 #define HTTP_SERVERR 500 typedef struct _GetJwtTokenAux { HttpServerState *state; char *repo_id; } GetJwtTokenAux; static void fileserver_get_jwt_token_cb (HttpAPIGetResult *result, void *user_data) { GetJwtTokenAux *aux = user_data; HttpServerState *state = aux->state; char *repo_id = aux->repo_id; SeafRepo *repo = NULL; char *jwt_token = NULL; state->n_jwt_token_request--; if (result->http_status == HTTP_NOT_FOUND || result->http_status == HTTP_FORBIDDEN || result->http_status == HTTP_SERVERR) { goto out; } if (!result->success) { goto out; } repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id); if (!repo) goto out; jwt_token = parse_jwt_token (result->rsp_content,result->rsp_size); if (!jwt_token) { seaf_warning ("Failed to parse jwt token for repo %s\n", repo->id); goto out; } g_free (repo->jwt_token); repo->jwt_token = jwt_token; out: g_free (aux->repo_id); g_free (aux); seaf_repo_unref (repo); return; } #ifdef COMPILE_WS static int check_and_subscribe_repo (HttpServerState *state, SeafRepo *repo) { char *url = NULL; if (!state->notif_server_alive) { return 0; } if (state->n_jwt_token_request > 10) { return 0; } gint64 now = (gint64)time(NULL); if (now - repo->last_check_jwt_token > JWT_TOKEN_EXPIRE_TIME) { repo->last_check_jwt_token = now; if (!state->use_fileserver_port) url = g_strdup_printf ("%s/seafhttp/repo/%s/jwt-token", state->effective_host, repo->id); else url = g_strdup_printf ("%s/repo/%s/jwt-token", state->effective_host, repo->id); state->n_jwt_token_request++; GetJwtTokenAux *aux = g_new0 (GetJwtTokenAux, 1); aux->repo_id = g_strdup (repo->id); aux->state = state; if (http_tx_manager_fileserver_api_get (seaf->http_tx_mgr, state->effective_host, url, repo->token, fileserver_get_jwt_token_cb, aux) < 0) { g_free (aux->repo_id); g_free (aux); state->n_jwt_token_request--; } g_free (url); return 0; } if (!seaf_notif_manager_is_repo_subscribed (seaf->notif_mgr, repo)) { if (repo->jwt_token) seaf_notif_manager_subscribe_repo (seaf->notif_mgr, repo); } return 0; } #endif static void clone_repo (SeafSyncManager *manager, SeafAccount *account, HttpServerState *state, SyncInfo *sync_info); static int sync_repo (SeafSyncManager *manager, HttpServerState *state, SyncInfo *sync_info, SeafRepo *repo); static int auto_sync_account_repos (SeafSyncManager *manager, const char *server, const char *user) { SeafAccount *account; GList *repo_info_list, *ptr, *sync_info_list = NULL; RepoInfo *repo_info; SyncInfo *sync_info; SeafRepo *repo; HttpServerState *state; gboolean all_repos_loaded = TRUE; if (!manager->priv->auto_sync_enabled) return TRUE; account = seaf_repo_manager_get_account (seaf->repo_mgr, server, user); if (!account) return TRUE; if (!check_http_protocol (manager, account->fileserver_addr)) { seaf_account_free (account); return TRUE; } state = get_http_server_state (manager->priv, account->fileserver_addr); if (!state) { seaf_account_free (account); return TRUE; } #ifdef COMPILE_WS if (check_notif_server (manager, account->fileserver_addr)) { seaf_notif_manager_connect_server (seaf->notif_mgr, account->fileserver_addr, state->use_fileserver_port); } #endif if (account->is_pro) { check_folder_permissions (manager, state, account->server, account->username); check_locked_files (manager, state, account->server, account->username); } /* Find sync_infos coresponded to repos in the current account. */ repo_info_list = seaf_repo_manager_get_account_repos (seaf->repo_mgr, server, user); for (ptr = repo_info_list; ptr; ptr = ptr->next) { repo_info = (RepoInfo *)ptr->data; if (!account->all_repos_loaded) { repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_info->id); if (!repo) all_repos_loaded = FALSE; else if (!repo->fs_ready) { if (repo->encrypted && !repo->is_passwd_set) { seaf_repo_unref (repo); continue; } all_repos_loaded = FALSE; } seaf_repo_unref (repo); } /* The head commit id of the repo is fetched when getting repo list. * If this is not set yet, we don't have enough information to sync * the repo. */ if (!repo_info->head_commit_id) { repo_info_free (repo_info); continue; } if (repo_info->is_corrupted) { repo_info_free (repo_info); continue; } sync_info = get_sync_info (manager, repo_info->id); if (sync_info->in_sync) { repo_info_free (repo_info); continue; } repo_info_free (sync_info->repo_info); sync_info->repo_info = repo_info; sync_info_list = g_list_prepend (sync_info_list, sync_info); } if (account->repo_list_fetched && !account->all_repos_loaded && all_repos_loaded) { seaf_repo_manager_set_account_all_repos_loaded (seaf->repo_mgr, server, user); notify_fs_loaded (); } /* Sort sync_infos by last_sync_time, so that we don't "starve" any repo. */ sync_info_list = g_list_sort_with_data (sync_info_list, cmp_sync_info_by_sync_time, NULL); for (ptr = sync_info_list; ptr != NULL; ptr = ptr->next) { sync_info = (SyncInfo *)ptr->data; if (sync_info->in_error) { continue; } repo = seaf_repo_manager_get_repo (seaf->repo_mgr, sync_info->repo_id); if (!repo) { if (exceed_max_tasks (manager)) continue; /* Don't re-download a repo that's still marked as delete-pending. * It should be downloaded after the local repo is removed. */ if (seaf_repo_manager_is_repo_delete_pending (seaf->repo_mgr, sync_info->repo_id)) continue; seaf_message ("Cloning repo %s(%s).\n", sync_info->repo_info->name, sync_info->repo_id); clone_repo (manager, account, state, sync_info); continue; } #ifdef USE_GPL_CRYPTO if (repo->version == 0 || (repo->encrypted && repo->enc_version < 2)) { seaf_repo_unref (repo); continue; } #endif if (!repo->token) { /* If the user has logged out of the account, the repo token would * be null */ seaf_repo_unref (repo); continue; } /* If repo tree is not loaded yet, we should wait until it's loaded. */ if (!repo->fs_ready) { seaf_repo_unref (repo); continue; } if (exceed_max_tasks (manager) && !repo->force_sync_pending) { seaf_repo_unref (repo); continue; } if (repo->encrypted && !repo->is_passwd_set) { seaf_repo_unref (repo); continue; } sync_repo (manager, state, sync_info, repo); #ifdef COMPILE_WS check_and_subscribe_repo (state, repo); #endif seaf_repo_unref (repo); } seaf_account_free (account); g_list_free (repo_info_list); g_list_free (sync_info_list); return TRUE; } static int auto_sync_pulse (void *vmanager) { SeafSyncManager *manager = vmanager; GList *accounts = NULL, *ptr; SeafAccount *account; accounts = seaf_repo_manager_get_account_list (seaf->repo_mgr); if (!accounts) return TRUE; for (ptr = accounts; ptr; ptr = ptr->next) { account = ptr->data; auto_sync_account_repos (manager, account->server, account->username); } g_list_free_full (accounts, (GDestroyNotify)seaf_account_free); return TRUE; } static void check_repo_folder_perms_done (HttpFolderPerms *result, void *user_data) { FolderPermInfo *perm_info = user_data; HttpServerState *server_state = perm_info->server_state; SyncTask *task = perm_info->task; SyncInfo *sync_info = task->info; check_folder_perms_done (result, user_data); if (!result->success) { seaf_sync_manager_set_task_error (task, SYNC_ERROR_FETCH); return; } RepoToken *repo_token = g_hash_table_lookup (seaf->sync_mgr->priv->repo_tokens, sync_info->repo_id); if (!repo_token) { seaf_warning ("Failed to get reop sync token fro %s.\n", sync_info->repo_id); seaf_sync_manager_set_task_error (task, SYNC_ERROR_FETCH); return; } int rc = http_tx_manager_add_download (seaf->http_tx_mgr, sync_info->repo_id, repo_token->repo_version, task->server, task->user, server_state->effective_host, repo_token->token, sync_info->repo_info->head_commit_id, TRUE, server_state->http_version, server_state->use_fileserver_port, NULL); if (rc < 0) { seaf_warning ("Failed to add download task for repo %s from %s.\n", sync_info->repo_id, server_state->effective_host); seaf_sync_manager_set_task_error (task, SYNC_ERROR_FETCH); return; } transition_sync_state (task, SYNC_STATE_FETCH); } static void check_folder_permissions_by_repo (HttpServerState *server_state, const char *server, const char *user, const char *repo_id, const char *token, SyncTask *task) { HttpFolderPermReq *req; GList *requests = NULL; req = g_new0 (HttpFolderPermReq, 1); memcpy (req->repo_id, repo_id, 36); req->token = g_strdup(token); req->timestamp = 0; requests = g_list_append (requests, req); server_state->checking_folder_perms = TRUE; FolderPermInfo *info = g_new0 (FolderPermInfo, 1); info->server_state = server_state; info->task = task; info->server = g_strdup (server); info->user = g_strdup (user); /* The requests list will be freed in http tx manager. */ if (http_tx_manager_get_folder_perms (seaf->http_tx_mgr, server_state->effective_host, server_state->use_fileserver_port, requests, check_repo_folder_perms_done, info) < 0) { seaf_warning ("Failed to schedule check repo %s folder permissions\n", repo_id); server_state->checking_folder_perms = FALSE; folder_perm_info_free (info); seaf_sync_manager_set_task_error (task, SYNC_ERROR_FETCH); } } static void get_repo_sync_token_cb (HttpAPIGetResult *result, void *user_data) { SyncTask *task = user_data; SyncInfo *sync_info = task->info; HttpServerState *state = task->server_state; json_t *object, *member; json_error_t jerror; const char *token; int repo_version; if (!result->success) { task->tx_error_code = result->error_code; if (task->tx_error_code == HTTP_TASK_ERR_FORBIDDEN) seaf_sync_manager_set_task_error (task, SYNC_ERROR_ACCESS_DENIED); else seaf_sync_manager_set_task_error (task, SYNC_ERROR_FETCH); return; } object = json_loadb (result->rsp_content, result->rsp_size, 0, &jerror); if (!object) { seaf_warning ("Parse response failed: %s.\n", jerror.text); seaf_sync_manager_set_task_error (task, SYNC_ERROR_FETCH); return; } member = json_object_get (object, "token"); if (!member) { seaf_warning ("Invalid download info response: no token.\n"); seaf_sync_manager_set_task_error (task, SYNC_ERROR_FETCH); goto out; } token = json_string_value (member); if (!token) { seaf_warning ("Invalid download info response: no token.\n"); seaf_sync_manager_set_task_error (task, SYNC_ERROR_FETCH); goto out; } member = json_object_get (object, "repo_version"); if (!member) { seaf_warning ("Invalid download info response: no repo_version.\n"); seaf_sync_manager_set_task_error (task, SYNC_ERROR_FETCH); goto out; } repo_version = json_integer_value (member); task->token = g_strdup (token); // save repo token RepoToken *repo_token = g_new0 (RepoToken, 1); memcpy (repo_token->token, token, 40); repo_token->token[40] = '\0'; repo_token->repo_version = repo_version; g_hash_table_insert (seaf->sync_mgr->priv->repo_tokens, g_strdup (sync_info->repo_id), repo_token); if (seaf_repo_manager_account_is_pro (seaf->repo_mgr, task->server, task->user)) { int timestamp = seaf_repo_manager_get_folder_perm_timestamp (seaf->repo_mgr, sync_info->repo_id); if (timestamp <= 0) { check_folder_permissions_by_repo (state, task->server, task->user, sync_info->repo_id, token, task); goto out; } } int rc = http_tx_manager_add_download (seaf->http_tx_mgr, sync_info->repo_id, repo_version, task->server, task->user, state->effective_host, token, sync_info->repo_info->head_commit_id, TRUE, state->http_version, state->use_fileserver_port, NULL); if (rc < 0) { seaf_warning ("Failed to add download task for repo %s from %s.\n", sync_info->repo_id, state->effective_host); seaf_sync_manager_set_task_error (task, SYNC_ERROR_FETCH); goto out; } transition_sync_state (task, SYNC_STATE_FETCH); out: json_decref (object); } static void get_repo_sync_token (SeafSyncManager *manager, SeafAccount *account, HttpServerState *state, SyncInfo *sync_info) { SyncTask *task; task = sync_task_new (sync_info, state, account->server, account->username, NULL, TRUE); RepoToken *repo_token = g_hash_table_lookup (seaf->sync_mgr->priv->repo_tokens, sync_info->repo_id); if(repo_token == NULL) { char *url = g_strdup_printf ("%s/api2/repos/%s/download-info/", account->server, sync_info->repo_id); if (http_tx_manager_api_get (seaf->http_tx_mgr, account->server, url, account->token, get_repo_sync_token_cb, task) < 0) { seaf_warning ("Failed to start get repo sync token for %s from server %s\n", sync_info->repo_id, account->server); seaf_sync_manager_set_task_error (task, SYNC_ERROR_FETCH); goto out; } transition_sync_state (task, SYNC_STATE_GET_TOKEN); out: g_free (url); } else { task->token = g_strdup(repo_token->token); // In order to use folder perms when cloning repo, we nned to get folder perms once immediately after getting rep token. if (account->is_pro) { int timestamp = seaf_repo_manager_get_folder_perm_timestamp (seaf->repo_mgr, sync_info->repo_id); if (timestamp <= 0) { check_folder_permissions_by_repo (state, account->server, account->username, sync_info->repo_id, repo_token->token, task); return; } } int rc = http_tx_manager_add_download (seaf->http_tx_mgr, sync_info->repo_id, repo_token->repo_version, account->server, account->username, state->effective_host, repo_token->token, sync_info->repo_info->head_commit_id, TRUE, state->http_version, state->use_fileserver_port, NULL); if (rc < 0) { seaf_warning ("Failed to add download task for repo %s from %s.\n", sync_info->repo_id, state->effective_host); seaf_sync_manager_set_task_error (task, SYNC_ERROR_FETCH); return; } transition_sync_state (task, SYNC_STATE_FETCH); } } static void clone_repo (SeafSyncManager *manager, SeafAccount *account, HttpServerState *state, SyncInfo *sync_info) { get_repo_sync_token (manager, account, state, sync_info); } static gboolean can_schedule_repo (SyncInfo *info) { gint64 now = (gint64)time(NULL); return (info->last_sync_time == 0 || info->last_sync_time < now - DEFAULT_SYNC_INTERVAL); } static gboolean create_commit_from_journal (SyncInfo *info, HttpServerState *state, SeafRepo *repo); static int check_head_commit_http (SyncTask *task); inline static char * get_basename (char *path) { char *slash; slash = strrchr (path, '/'); if (!slash) return path; return (slash + 1); } static char * exceed_max_deleted_files (SeafRepo *repo) { SeafBranch *master = NULL, *local = NULL; SeafCommit *local_head = NULL, *master_head = NULL; GList *diff_results = NULL; char *deleted_file = NULL; GString *desc = NULL; char *ret = NULL; local = seaf_branch_manager_get_branch (seaf->branch_mgr, repo->id, "local"); if (!local) { seaf_warning ("No local branch found for repo %s(%.8s).\n", repo->name, repo->id); goto out; } master = seaf_branch_manager_get_branch (seaf->branch_mgr, repo->id, "master"); if (!master) { seaf_warning ("No master branch found for repo %s(%.8s).\n", repo->name, repo->id); goto out; } local_head = seaf_commit_manager_get_commit (seaf->commit_mgr, repo->id, repo->version, local->commit_id); if (!local_head) { seaf_warning ("Failed to get head of local branch for repo %s.\n", repo->id); goto out; } master_head = seaf_commit_manager_get_commit (seaf->commit_mgr, repo->id, repo->version, master->commit_id); if (!master_head) { seaf_warning ("Failed to get head of master branch for repo %s.\n", repo->id); goto out; } diff_commit_roots (repo->id, repo->version, master_head->root_id, local_head->root_id, &diff_results, TRUE); if (!diff_results) { goto out; } GList *p; DiffEntry *de; int n_deleted = 0; for (p = diff_results; p != NULL; p = p->next) { de = p->data; switch (de->status) { case DIFF_STATUS_DELETED: if (n_deleted == 0) deleted_file = get_basename(de->name); n_deleted++; break; } } if (n_deleted >= seaf->delete_confirm_threshold) { desc = g_string_new (""); g_string_append_printf (desc, "Deleted \"%s\" and %d more files.\n", deleted_file, n_deleted - 1); ret = g_string_free (desc, FALSE); } out: seaf_branch_unref (local); seaf_branch_unref (master); seaf_commit_unref (local_head); seaf_commit_unref (master_head); g_list_free_full (diff_results, (GDestroyNotify)diff_entry_free); return ret; } static void notify_delete_confirmation (const char *repo_name, const char *desc, const char *confirmation_id) { json_t *msg = json_object (); json_object_set_string_member (msg, "type", "del_confirmation"); json_object_set_string_member (msg, "repo_name", repo_name); json_object_set_string_member (msg, "delete_files", desc); json_object_set_string_member (msg, "confirmation_id", confirmation_id); mq_mgr_push_msg (seaf->mq_mgr, SEADRIVE_NOTIFY_CHAN, msg); } int seaf_sync_manager_add_del_confirmation (SeafSyncManager *mgr, const char *confirmation_id, gboolean resync) { SeafSyncManagerPriv *priv = seaf->sync_mgr->priv; DelConfirmationResult *result = NULL; result = g_new0 (DelConfirmationResult, 1); result->resync = resync; pthread_mutex_lock (&priv->del_confirmation_lock); g_hash_table_insert (priv->del_confirmation_tasks, g_strdup (confirmation_id), result); pthread_mutex_unlock (&priv->del_confirmation_lock); return 0; } static DelConfirmationResult * get_del_confirmation_result (const char *confirmation_id) { SeafSyncManagerPriv *priv = seaf->sync_mgr->priv; DelConfirmationResult *result, *copy = NULL; pthread_mutex_lock (&priv->del_confirmation_lock); result = g_hash_table_lookup (priv->del_confirmation_tasks, confirmation_id); if (result) { copy = g_new0 (DelConfirmationResult, 1); copy->resync = result->resync; g_hash_table_remove (priv->del_confirmation_tasks, confirmation_id); } pthread_mutex_unlock (&priv->del_confirmation_lock); return copy; } static int sync_repo (SeafSyncManager *manager, HttpServerState *state, SyncInfo *sync_info, SeafRepo *repo) { SeafBranch *master = NULL, *local = NULL; SyncTask *task; int ret = 0; master = seaf_branch_manager_get_branch (seaf->branch_mgr, repo->id, "master"); if (!master) { seaf_warning ("No master branch found for repo %s(%.8s).\n", repo->name, repo->id); ret = -1; goto out; } local = seaf_branch_manager_get_branch (seaf->branch_mgr, repo->id, "local"); if (!local) { seaf_warning ("No local branch found for repo %s(%.8s).\n", repo->name, repo->id); ret = -1; goto out; } if (strcmp (local->commit_id, master->commit_id) != 0) { if (can_schedule_repo (sync_info) || repo->force_sync_pending || sync_info->del_confirmation_pending) { if (repo->force_sync_pending) repo->force_sync_pending = FALSE; task = sync_task_new (sync_info, state, NULL, NULL, repo->token, FALSE); if (!sync_info->del_confirmation_pending) { char *desc = NULL; desc = exceed_max_deleted_files (repo); if (desc) { notify_delete_confirmation (repo->name, desc, local->commit_id); seaf_warning ("Delete more than %d files, add delete confirmation.\n", seaf->delete_confirm_threshold); sync_info->del_confirmation_pending = TRUE; seaf_sync_manager_set_task_error (task, SYNC_ERROR_DEL_CONFIRMATION_PENDING); g_free (desc); goto out; } } else { DelConfirmationResult *result = get_del_confirmation_result (local->commit_id); if (!result) { // User has not confirmed whether to continue syncing. seaf_sync_manager_set_task_error (task, SYNC_ERROR_DEL_CONFIRMATION_PENDING); goto out; } else if (result->resync) { // User chooses to resync. g_free (result); seaf_sync_manager_set_task_error (task, SYNC_ERROR_DEL_CONFIRMATION_PENDING); // Delete this repo. It'll be re-synced when checking repo list next time. seaf_repo_manager_mark_repo_deleted (seaf->repo_mgr, repo, FALSE); goto out; } // User chooes to continue syncing. g_free (result); sync_info->del_confirmation_pending = FALSE; } int rc = http_tx_manager_add_upload (seaf->http_tx_mgr, repo->id, repo->version, repo->server, repo->user, repo->repo_uname, state->effective_host, repo->token, state->http_version, state->use_fileserver_port, NULL); if (rc < 0) { seaf_warning ("Failed to add upload task for repo %s to %s.\n", sync_info->repo_id, state->effective_host); seaf_sync_manager_set_task_error (task, SYNC_ERROR_START_UPLOAD); ret = -1; goto out; } transition_sync_state (task, SYNC_STATE_UPLOAD); } goto out; } if (create_commit_from_journal (sync_info, state, repo)) { if (repo->force_sync_pending) repo->force_sync_pending = FALSE; goto out; } if ((strcmp (sync_info->repo_info->head_commit_id, master->commit_id) != 0 && can_schedule_repo (sync_info)) || repo->force_sync_pending) { if (repo->force_sync_pending) repo->force_sync_pending = FALSE; task = sync_task_new (sync_info, state, NULL, NULL, repo->token, FALSE); /* In some cases, sync_info->repo_info->head_commit_id may be outdated. * To avoid mistakenly download a repo, we check server head commit * before starting download. */ check_head_commit_http (task); } out: seaf_branch_unref (local); seaf_branch_unref (master); return ret; } static void check_head_commit_done (HttpHeadCommit *result, void *user_data) { SyncTask *task = user_data; SyncInfo *info = task->info; SeafRepo *repo = task->repo; HttpServerState *state = task->server_state; SeafBranch *master = NULL; if (!result->check_success) { task->tx_error_code = result->error_code; if (result->perm_denied) seaf_sync_manager_set_task_error (task, SYNC_ERROR_ACCESS_DENIED); else seaf_sync_manager_set_task_error (task, SYNC_ERROR_GET_SYNC_INFO); /* if (result->perm_denied) */ /* seaf_repo_manager_mark_repo_deleted (seaf->repo_mgr, repo, FALSE); */ return; } if (result->is_deleted) { seaf_sync_manager_set_task_error (task, SYNC_ERROR_NOREPO); return; } if (result->is_corrupt) { seaf_sync_manager_set_task_error (task, SYNC_ERROR_REPO_CORRUPT); return; } /* The cached head commit id may be outdated. */ if (strcmp (result->head_commit, info->repo_info->head_commit_id) != 0) { seaf_repo_manager_set_repo_info_head_commit (seaf->repo_mgr, repo->id, result->head_commit); } master = seaf_branch_manager_get_branch (seaf->branch_mgr, repo->id, "master"); if (!master) { seaf_warning ("master branch not found for repo %s.\n", repo->id); seaf_sync_manager_set_task_error (task, SYNC_ERROR_DATA_CORRUPT); return; } if (strcmp (result->head_commit, master->commit_id) != 0) { int rc = http_tx_manager_add_download (seaf->http_tx_mgr, repo->id, repo->version, repo->server, repo->user, state->effective_host, repo->token, result->head_commit, FALSE, state->http_version, state->use_fileserver_port, NULL); if (rc < 0) { seaf_warning ("Failed to add download task for repo %s from %s.\n", info->repo_id, state->effective_host); seaf_sync_manager_set_task_error (task, SYNC_ERROR_START_FETCH); goto out; } transition_sync_state (task, SYNC_STATE_FETCH); } else { transition_sync_state (task, SYNC_STATE_DONE); } out: seaf_branch_unref (master); } static int check_head_commit_http (SyncTask *task) { SeafRepo *repo = task->repo; HttpServerState *state = task->server_state; int ret = http_tx_manager_check_head_commit (seaf->http_tx_mgr, repo->id, repo->version, state->effective_host, repo->token, state->use_fileserver_port, check_head_commit_done, task); if (ret == 0) transition_sync_state (task, SYNC_STATE_INIT); else if (ret < 0) seaf_sync_manager_set_task_error (task, SYNC_ERROR_GET_SYNC_INFO); return ret; } static gboolean server_is_pro (const char *server_url) { GList *accounts = NULL, *ptr; SeafAccount *account; gboolean is_pro = FALSE; accounts = seaf_repo_manager_get_account_list (seaf->repo_mgr); if (!accounts) return FALSE; for (ptr = accounts; ptr; ptr = ptr->next) { account = ptr->data; if (g_strcmp0 (account->fileserver_addr, server_url) == 0) { is_pro = account->is_pro; break; } } g_list_free_full (accounts, (GDestroyNotify)seaf_account_free); return is_pro; } void seaf_sync_manager_check_locks_and_folder_perms (SeafSyncManager *manager, const char *server_url) { HttpServerState *state; state = get_http_server_state (manager->priv, server_url); if (!state) { return; } if (server_is_pro (server_url)) { state->immediate_check_folder_perms = TRUE; state->immediate_check_locked_files = TRUE; } return; } gboolean seaf_sync_manager_ignored_on_checkout (const char *file_path, IgnoreReason *ignore_reason) { gboolean ret = FALSE; return ret; } gboolean seaf_repo_manager_ignored_on_commit (const char *filename) { GPatternSpec **spec = ignore_patterns; if (!g_utf8_validate (filename, -1, NULL)) { seaf_warning ("File name %s contains non-UTF8 characters, skip.\n", filename); return TRUE; } /* Ignore file/dir if its name is too long. */ if (strlen(filename) >= SEAF_DIR_NAME_LEN) return TRUE; if (strchr (filename, '/')) return TRUE; while (*spec) { if (g_pattern_match_string(*spec, filename)) return TRUE; spec++; } if (!seaf->sync_extra_temp_file) { spec = office_temp_ignore_patterns; while (*spec) { if (g_pattern_match_string(*spec, filename)) return TRUE; spec++; } } return FALSE; } #if 0 static int copy_file_to_conflict_file (const char *repo_id, const char *path, const char *conflict_path) { SeafRepo *repo = NULL; RepoTreeStat st; JournalOp *op; int ret = 0; repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id); if (!repo) { ret = -1; goto out; } if (repo_tree_stat_path (repo->tree, path, &st) < 0) { ret = -1; goto out; } repo_tree_create_file (repo->tree, conflict_path, st.id, st.mode, st.mtime, st.size); op = journal_op_new (OP_TYPE_CREATE_FILE, conflict_path, NULL, st.size, st.mtime, st.mode); if (journal_append_op (repo->journal, op) < 0) { journal_op_free (op); ret = -1; goto out; } file_cache_mgr_rename (seaf->file_cache_mgr, repo_id, path, repo_id, conflict_path); out: seaf_repo_unref (repo); return ret; } #endif static int merge_remote_head_to_repo_tree (SeafRepo *repo, const char *new_head_id) { SeafBranch *master = NULL; SeafCommit *local_head = NULL, *remote_head = NULL; GList *diff_results = NULL, *ptr; DiffEntry *de; char id[41]; /* gboolean conflicted; */ int ret = 0; master = seaf_branch_manager_get_branch (seaf->branch_mgr, repo->id, "master"); if (!master) { seaf_warning ("Failed to get master branch of repo %s.\n", repo->id); ret = -1; goto out; } local_head = seaf_commit_manager_get_commit (seaf->commit_mgr, repo->id, repo->version, repo->head->commit_id); if (!local_head) { seaf_warning ("Failed to get head commit of local branch for repo %s.\n", repo->id); ret = -1; goto out; } remote_head = seaf_commit_manager_get_commit (seaf->commit_mgr, repo->id, repo->version, new_head_id); if (!remote_head) { seaf_warning ("Failed to get remote head commit %s for repo %s.\n", new_head_id, repo->id); ret = -1; goto out; } if (diff_commits (local_head, remote_head, &diff_results, TRUE) < 0) { seaf_warning ("Failed to diff for repo %s.\n", repo->id); ret = -1; goto out; } /* Process delete/rename before add/update. Since rename of empty files/dirs * are interpreted as delete+create, we need to be sure about the order. */ for (ptr = diff_results; ptr; ptr = ptr->next) { de = (DiffEntry *)ptr->data; switch (de->status) { case DIFF_STATUS_DELETED: /* If local file was changed, don't delete it. */ if (file_cache_mgr_is_file_changed (seaf->file_cache_mgr, repo->id, de->name, TRUE)) break; repo_tree_unlink (repo->tree, de->name); file_cache_mgr_unlink (seaf->file_cache_mgr, repo->id, de->name); break; case DIFF_STATUS_DIR_DELETED: repo_tree_remove_subtree (repo->tree, de->name); break; case DIFF_STATUS_RENAMED: case DIFF_STATUS_DIR_RENAMED: if (seaf_sync_manager_ignored_on_checkout(de->new_name, NULL)) { seaf_message ("Path %s is invalid on Windows, skip rename.\n", de->new_name); /* send_sync_error_notification (repo->id, repo->name, de->new_name, */ /* SYNC_ERROR_ID_INVALID_PATH); */ break; } else if (seaf_sync_manager_ignored_on_checkout(de->name, NULL)) { /* If the server renames an invalid path to a valid path, * directly checkout the valid path. */ rawdata_to_hex (de->sha1, id, 20); repo_tree_add_subtree (repo->tree, de->new_name, id, de->mtime); break; } repo_tree_rename (repo->tree, de->name, de->new_name, TRUE); file_cache_mgr_rename (seaf->file_cache_mgr, repo->id, de->name, repo->id, de->new_name); break; } } for (ptr = diff_results; ptr; ptr = ptr->next) { de = (DiffEntry *)ptr->data; switch (de->status) { case DIFF_STATUS_ADDED: if (seaf_sync_manager_ignored_on_checkout(de->name, NULL)) { seaf_message ("Path %s is invalid on Windows, skip creating.\n", de->name); /* send_sync_error_notification (repo->id, repo->name, de->name, */ /* SYNC_ERROR_ID_INVALID_PATH); */ break; } /* handle_file_conflict (repo->id, de->name, &conflicted); */ rawdata_to_hex (de->sha1, id, 20); /* if (conflicted) { */ /* repo_tree_set_file_mtime (repo->tree, */ /* de->name, */ /* de->mtime); */ /* repo_tree_set_file_size (repo->tree, */ /* de->name, */ /* de->size); */ /* repo_tree_set_file_id (repo->tree, de->name, id); */ /* } else { */ repo_tree_create_file (repo->tree, de->name, id, de->mode, de->mtime, de->size); /* } */ break; case DIFF_STATUS_DIR_ADDED: if (seaf_sync_manager_ignored_on_checkout(de->name, NULL)) { seaf_message ("Path %s is invalid on Windows, skip creating.\n", de->name); /* send_sync_error_notification (repo->id, repo->name, de->name, */ /* SYNC_ERROR_ID_INVALID_PATH); */ break; } rawdata_to_hex (de->sha1, id, 20); repo_tree_add_subtree (repo->tree, de->name, id, de->mtime); break; case DIFF_STATUS_MODIFIED: if (seaf_sync_manager_ignored_on_checkout(de->name, NULL)) { seaf_message ("Path %s is invalid on Windows, skip update.\n", de->name); /* send_sync_error_notification (repo->id, repo->name, de->name, */ /* SYNC_ERROR_ID_INVALID_PATH); */ break; } /* handle_file_conflict (repo->id, de->name, &conflicted); */ rawdata_to_hex (de->sha1, id, 20); repo_tree_set_file_mtime (repo->tree, de->name, de->mtime); repo_tree_set_file_size (repo->tree, de->name, de->size); repo_tree_set_file_id (repo->tree, de->name, id); break; } } seaf_branch_set_commit (master, new_head_id); master->opid = repo->head->opid; seaf_branch_set_commit (repo->head, new_head_id); /* Update both branches in db in a single operatoin, to prevent race conditions. */ seaf_branch_manager_update_repo_branches (seaf->branch_mgr, repo->head); /* Update repo name if it's changed on server. */ if (g_strcmp0 (local_head->repo_name, remote_head->repo_name) != 0) { seaf_repo_manager_rename_repo (seaf->repo_mgr, repo->id, remote_head->repo_name); } out: seaf_branch_unref (master); seaf_commit_unref (local_head); seaf_commit_unref (remote_head); g_list_free_full (diff_results, (GDestroyNotify)diff_entry_free); return ret; } typedef struct _LoadRepoTreeAux { SyncTask *task; char new_head_id[41]; gboolean success; } LoadRepoTreeAux; static void * load_repo_tree_job (void *vdata) { LoadRepoTreeAux *aux = vdata; SyncTask *task = aux->task; SeafRepo *repo = task->repo; if (task->is_clone) { if (seaf_repo_load_fs (repo, TRUE) < 0) { aux->success = FALSE; } else { aux->success = TRUE; } } else { if (merge_remote_head_to_repo_tree (repo, aux->new_head_id) < 0) { aux->success = FALSE; } else { aux->success = TRUE; } } return aux; } static void load_repo_tree_done (void *vresult) { LoadRepoTreeAux *aux = vresult; SyncTask *task = aux->task; SeafSyncManagerPriv *priv = seaf->sync_mgr->priv; if (aux->success) { g_hash_table_remove (priv->repo_tokens, task->repo->id); transition_sync_state (task, SYNC_STATE_DONE); } else { seaf_sync_manager_set_task_error (task, SYNC_ERROR_DATA_CORRUPT); } g_free (aux); } static void handle_repo_fetched_clone (SeafileSession *seaf, HttpTxTask *tx_task, SeafSyncManager *mgr) { SyncInfo *info = get_sync_info (mgr, tx_task->repo_id); SyncTask *task = info->current_task; if (!task) { seaf_warning ("BUG: sync task not found after fetch is done.\n"); return; } if (tx_task->state == HTTP_TASK_STATE_CANCELED) { transition_sync_state (task, SYNC_STATE_CANCELED); return; } else if (tx_task->state == HTTP_TASK_STATE_ERROR) { task->tx_error_code = tx_task->error; if (tx_task->error == HTTP_TASK_ERR_FORBIDDEN) { seaf_sync_manager_set_task_error (task, SYNC_ERROR_ACCESS_DENIED); } else if (tx_task->error == HTTP_TASK_ERR_FILE_LOCKED) { task->unsyncable_path = tx_task->unsyncable_path; seaf_sync_manager_set_task_error (task, SYNC_ERROR_FILES_LOCKED_BY_USER); } else if (tx_task->error == HTTP_TASK_ERR_FOLDER_PERM_DENIED) { task->unsyncable_path = tx_task->unsyncable_path; seaf_sync_manager_set_task_error (task, SYNC_ERROR_FOLDER_PERM_DENIED); } else if (tx_task->error == HTTP_TASK_ERR_NO_PERMISSION_TO_SYNC) { task->unsyncable_path = tx_task->unsyncable_path; seaf_sync_manager_set_task_error (task, SYNC_ERROR_PERM_NOT_SYNCABLE); } else if (tx_task->error == HTTP_TASK_ERR_NO_WRITE_PERMISSION) { seaf_sync_manager_set_task_error (task, SYNC_ERROR_NO_WRITE_PERMISSION); } else seaf_sync_manager_set_task_error (task, SYNC_ERROR_FETCH); return; } SeafRepo *repo = seaf_repo_manager_get_repo (seaf->repo_mgr, tx_task->repo_id); if (repo == NULL) { seaf_warning ("Cannot find repo %s after fetched.\n", tx_task->repo_id); seaf_sync_manager_set_task_error (task, SYNC_ERROR_DATA_CORRUPT); return; } seaf_repo_manager_set_repo_token (seaf->repo_mgr, repo, task->token); if (info->repo_info->is_readonly) seaf_repo_set_readonly (repo); if (!repo->worktree) seaf_repo_set_worktree (repo, info->repo_info->display_name); /* Set task->repo since the repo exists now. */ task->repo = repo; LoadRepoTreeAux *aux = g_new(LoadRepoTreeAux, 1); aux->task = task; if (seaf_job_manager_schedule_job (seaf->job_mgr, load_repo_tree_job, load_repo_tree_done, aux) < 0) { seaf_warning ("Failed to start load repo tree job.\n"); seaf_sync_manager_set_task_error (task, SYNC_ERROR_UNKNOWN); g_free (aux); return; } transition_sync_state (task, SYNC_STATE_LOAD_REPO); } static void handle_repo_fetched_sync (SeafileSession *seaf, HttpTxTask *tx_task, SeafSyncManager *mgr) { SyncInfo *info = get_sync_info (mgr, tx_task->repo_id); SyncTask *task = info->current_task; SeafRepo *repo; if (!task) { seaf_warning ("BUG: sync task not found after fetch is done.\n"); return; } repo = task->repo; if (repo->delete_pending) { transition_sync_state (task, SYNC_STATE_CANCELED); return; } if (tx_task->state == HTTP_TASK_STATE_CANCELED) { transition_sync_state (task, SYNC_STATE_CANCELED); return; } else if (tx_task->state == HTTP_TASK_STATE_ERROR) { task->tx_error_code = tx_task->error; if (tx_task->error == HTTP_TASK_ERR_FORBIDDEN) { seaf_sync_manager_set_task_error (task, SYNC_ERROR_ACCESS_DENIED); } else if (tx_task->error == HTTP_TASK_ERR_FILE_LOCKED) { seaf_sync_manager_set_task_error (task, SYNC_ERROR_FILES_LOCKED_BY_USER); } else if (tx_task->error == HTTP_TASK_ERR_FOLDER_PERM_DENIED) { seaf_sync_manager_set_task_error (task, SYNC_ERROR_FOLDER_PERM_DENIED); } else if (tx_task->error == HTTP_TASK_ERR_NO_PERMISSION_TO_SYNC) { task->unsyncable_path = tx_task->unsyncable_path; seaf_sync_manager_set_task_error (task, SYNC_ERROR_PERM_NOT_SYNCABLE); } else if (tx_task->error == HTTP_TASK_ERR_NO_WRITE_PERMISSION) { seaf_sync_manager_set_task_error (task, SYNC_ERROR_NO_WRITE_PERMISSION); } else seaf_sync_manager_set_task_error (task, SYNC_ERROR_FETCH); return; } LoadRepoTreeAux *aux = g_new0 (LoadRepoTreeAux, 1); aux->task = task; memcpy (aux->new_head_id, tx_task->head, 40); if (seaf_job_manager_schedule_job (seaf->job_mgr, load_repo_tree_job, load_repo_tree_done, aux) < 0) { seaf_warning ("Failed to start load repo tree job.\n"); seaf_sync_manager_set_task_error (task, SYNC_ERROR_UNKNOWN); g_free (aux); return; } transition_sync_state (task, SYNC_STATE_LOAD_REPO); } static void on_repo_http_fetched (SeafileSession *seaf, HttpTxTask *tx_task, SeafSyncManager *mgr) { if (tx_task->is_clone) handle_repo_fetched_clone (seaf, tx_task, mgr); else handle_repo_fetched_sync (seaf, tx_task, mgr); } static void print_upload_corrupt_debug_info (SeafRepo *repo) { SeafBranch *local = NULL, *master = NULL; SeafCommit *local_head = NULL, *master_head = NULL; seaf_message ("Repo %s(%s) local metadata is found corrupted when upload. " "Printing debug information and removing the local repo. " "It will be resynced later.\n", repo->name, repo->id); local = seaf_branch_manager_get_branch (seaf->branch_mgr, repo->id, "local"); if (!local) { seaf_warning ("Failed to get local branch of repo %s.\n", repo->id); goto out; } master = seaf_branch_manager_get_branch (seaf->branch_mgr, repo->id, "master"); if (!master) { seaf_warning ("Failed to get master branch of repo %s.\n", repo->id); goto out; } GList *ops = NULL, *ptr; JournalOp *op; gboolean error = FALSE; seaf_message ("Operations in journal used to create the commit " "(op id %"G_GINT64_FORMAT" to %"G_GINT64_FORMAT"):\n", master->opid + 1, local->opid); ops = journal_read_ops (repo->journal, master->opid + 1, local->opid, &error); for (ptr = ops; ptr; ptr = ptr->next) { op = ptr->data; seaf_message ("%"G_GINT64_FORMAT", %d, %s, %s, %" G_GINT64_FORMAT", %"G_GINT64_FORMAT", %u\n", op->opid, op->type, op->path, op->new_path, op->size, op->mtime, op->mode); } g_list_free_full (ops, (GDestroyNotify)journal_op_free); local_head = seaf_commit_manager_get_commit (seaf->commit_mgr, repo->id, repo->version, local->commit_id); if (!local_head) { seaf_warning ("Failed to get head of local branch for repo %s.\n", repo->id); goto out; } master_head = seaf_commit_manager_get_commit (seaf->commit_mgr, repo->id, repo->version, master->commit_id); if (!master_head) { seaf_warning ("Failed to get head of master branch for repo %s.\n", repo->id); goto out; } GList *results = NULL; DiffEntry *de; if (diff_commits (master_head, local_head, &results, TRUE) < 0) { seaf_warning ("Failed to diff repo %s.\n", repo->id); goto out; } seaf_message ("Diff results:\n"); for (ptr = results; ptr; ptr = ptr->next) { de = ptr->data; seaf_message ("%c %s %s\n", de->status, de->name, de->new_name); } g_list_free_full (results, (GDestroyNotify)diff_entry_free); out: seaf_branch_unref (local); seaf_branch_unref (master); seaf_commit_unref (local_head); seaf_commit_unref (master_head); return; } static void on_repo_http_uploaded (SeafileSession *seaf, HttpTxTask *tx_task, SeafSyncManager *manager) { SyncInfo *info = get_sync_info (manager, tx_task->repo_id); SyncTask *task = info->current_task; if (task->repo->delete_pending) { transition_sync_state (task, SYNC_STATE_CANCELED); return; } if (tx_task->state == HTTP_TASK_STATE_FINISHED) { seaf_message ("removing blocks for repo %s\n", tx_task->repo_id); /* Since the sync loop is a background thread, it should be no problem * to block it a bit. */ seaf_block_manager_remove_store (seaf->block_mgr, task->repo->id); task->uploaded = TRUE; check_head_commit_http (task); } else if (tx_task->state == HTTP_TASK_STATE_CANCELED) { transition_sync_state (task, SYNC_STATE_CANCELED); } else if (tx_task->state == HTTP_TASK_STATE_ERROR) { task->tx_error_code = tx_task->error; if (tx_task->error == HTTP_TASK_ERR_FORBIDDEN) { seaf_sync_manager_set_task_error (task, SYNC_ERROR_ACCESS_DENIED); } else if (tx_task->error == HTTP_TASK_ERR_FILE_LOCKED) { task->unsyncable_path = tx_task->unsyncable_path; seaf_sync_manager_set_task_error (task, SYNC_ERROR_FILES_LOCKED_BY_USER); } else if (tx_task->error == HTTP_TASK_ERR_FOLDER_PERM_DENIED) { task->unsyncable_path = tx_task->unsyncable_path; seaf_sync_manager_set_task_error (task, SYNC_ERROR_FOLDER_PERM_DENIED); } else if (tx_task->error == HTTP_TASK_ERR_TOO_MANY_FILES) { seaf_sync_manager_set_task_error (task, SYNC_ERROR_TOO_MANY_FILES); } else if (tx_task->error == HTTP_TASK_ERR_NO_WRITE_PERMISSION) { seaf_sync_manager_set_task_error (task, SYNC_ERROR_NO_WRITE_PERMISSION); } else if (tx_task->error == HTTP_TASK_ERR_NO_PERMISSION_TO_SYNC) { task->unsyncable_path = tx_task->unsyncable_path; seaf_sync_manager_set_task_error (task, SYNC_ERROR_PERM_NOT_SYNCABLE); } else if (tx_task->error == HTTP_TASK_ERR_NO_QUOTA) { seaf_sync_manager_set_task_error (task, SYNC_ERROR_QUOTA_FULL); /* Only notify "quota full" once. */ /* if (!task->repo->quota_full_notified) { */ /* send_sync_error_notification (repo->id, repo->name, NULL, */ /* SYNC_ERROR_ID_QUOTA_FULL); */ /* task->repo->quota_full_notified = 1; */ /* } */ } else if (tx_task->error == HTTP_TASK_ERR_BLOCK_MISSING) { seaf_sync_manager_set_task_error (task, SYNC_ERROR_BLOCK_MISSING); } else { if (tx_task->error == HTTP_TASK_ERR_BAD_LOCAL_DATA) print_upload_corrupt_debug_info (task->repo); seaf_sync_manager_set_task_error (task, SYNC_ERROR_UPLOAD); } } } #define MAX_COMMIT_SIZE 100 * (1 << 20) /* 100MB */ static gboolean is_repo_writable (SeafRepo *repo) { if (repo->is_readonly && seaf_repo_manager_get_folder_perm_timestamp (seaf->repo_mgr, repo->id) < 0) return FALSE; return TRUE; } static void handle_update_file_op (SeafRepo *repo, ChangeSet *changeset, const char *username, JournalOp *op, gboolean renamed_from_ignored, GHashTable *updated_files, gint64 *total_size, SeafileCrypt *crypt) { unsigned char sha1[20] = {0}; SeafStat st; FileCacheStat cache_st, *file_info; gboolean changed = FALSE; /* If index doesn't complete, st will be all-zero. */ memset (&st, 0, sizeof(st)); if (file_cache_mgr_index_file (seaf->file_cache_mgr, repo->id, repo->version, op->path, crypt, TRUE, sha1, &changed) < 0) { seaf_warning ("Failed to index file %s in repo %s, skip.\n", op->path, repo->id); goto out; } if (file_cache_mgr_stat (seaf->file_cache_mgr, repo->id, op->path, &cache_st) == 0) { st.st_size = cache_st.size; st.st_mtime = cache_st.mtime; st.st_mode = op->mode; } else { st.st_size = op->size; st.st_mtime = op->mtime; st.st_mode = op->mode; } add_to_changeset (changeset, DIFF_STATUS_MODIFIED, sha1, &st, username, op->path, NULL); if (changed) *total_size += (gint64)st.st_size; out: file_info = g_new0 (FileCacheStat, 1); rawdata_to_hex (sha1, file_info->file_id, 20); file_info->mtime = st.st_mtime; file_info->size = st.st_size; /* Keep the last information if there was one in the hash table. */ g_hash_table_replace (updated_files, g_strdup(op->path), file_info); } typedef struct CheckRenameAux { SeafRepo *repo; ChangeSet *changeset; const char *username; GHashTable *updated_files; gint64 *total_size; gboolean renamed_from_ignored; } CheckRenameAux; static void check_renamed_file_cb (const char *repo_id, const char *file_path, SeafStat *st, void *user_data) { char *filename = g_path_get_basename (file_path); gboolean file_ignored = seaf_repo_manager_ignored_on_commit (filename); if (file_ignored) return; CheckRenameAux *aux = (CheckRenameAux *)user_data; JournalOp *op; SeafRepo *repo = NULL; SeafileCrypt *crypt = NULL; repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id); if (!repo) return; if (repo->encrypted) crypt = seafile_crypt_new (repo->enc_version, repo->enc_key, repo->enc_iv); /* Create a faked journal op. */ op = journal_op_new (OP_TYPE_UPDATE_FILE, file_path, NULL, (gint64)st->st_size, (gint64)st->st_mtime, (guint32)st->st_mode); handle_update_file_op (aux->repo, aux->changeset, aux->username, op, aux->renamed_from_ignored, aux->updated_files, aux->total_size, crypt); g_free (crypt); journal_op_free (op); seaf_repo_unref (repo); } /* Excel first writes update to a temporary file and then rename the file to * xlsx/xls. Unfortunately the temp file dosen't have specific pattern. * We can only ignore renaming from non xlsx file to xlsx file. */ static gboolean ignore_xlsx_update (const char *src_path, const char *dst_path) { GPatternSpec *pattern_xlsx = g_pattern_spec_new ("*.xlsx"); GPatternSpec *pattern_xls = g_pattern_spec_new ("*.xls"); int ret = FALSE; if (!g_pattern_match_string(pattern_xlsx, src_path) && g_pattern_match_string(pattern_xlsx, dst_path)) ret = TRUE; if (!g_pattern_match_string(pattern_xls, src_path) && g_pattern_match_string(pattern_xls, dst_path)) ret = TRUE; g_pattern_spec_free (pattern_xlsx); g_pattern_spec_free (pattern_xls); return ret; } static void handle_rename_op (SeafRepo *repo, ChangeSet *changeset, const char *username, GHashTable *updated_files, JournalOp *op, gint64 *total_size) { char *src_filename, *dst_filename; gboolean src_ignored, dst_ignored; src_filename = g_path_get_basename (op->path); dst_filename = g_path_get_basename (op->new_path); src_ignored = (seaf_repo_manager_ignored_on_commit (src_filename) || ignore_xlsx_update (src_filename, dst_filename)); dst_ignored = seaf_repo_manager_ignored_on_commit (dst_filename); /* If destination path is ignored, just remove the src path. */ if (dst_ignored) { remove_from_changeset (changeset, op->path, FALSE, NULL); goto out; } /* Now destination is not ignored. */ if (!src_ignored) { add_to_changeset (changeset, DIFF_STATUS_RENAMED, NULL, NULL, username, op->path, op->new_path); } /* We should always scan the destination to check whether files are * changed. For example, in the following case: * 1. file a.txt is updated; * 2. a.txt is moved to test/a.txt; * If the two operations are executed in a batch, the updated content * of a.txt won't be committed if we don't scan the destination, because * when we process the update operation, a.txt is already not in its * original place. */ CheckRenameAux aux; aux.repo = repo; aux.changeset = changeset; aux.username = username; aux.updated_files = updated_files; aux.total_size = total_size; aux.renamed_from_ignored = src_ignored; file_cache_mgr_traverse_path (seaf->file_cache_mgr, repo->id, op->new_path, check_renamed_file_cb, NULL, &aux); out: g_free (src_filename); g_free (dst_filename); } static int apply_journal_ops_to_changeset (SeafRepo *repo, ChangeSet *changeset, const char *username, gint64 *last_opid, GHashTable *updated_files) { GList *ops, *ptr; JournalOp *op, *next_op; SeafStat st; gboolean error; unsigned char allzero[20] = {0}; gint64 total_size = 0; char *filename; RepoTreeStat tree_st; SeafileCrypt *crypt = NULL; ops = journal_read_ops (repo->journal, *last_opid + 1, G_MAXINT64, &error); if (error) { seaf_warning ("Failed to read operations from journal for repo %s.\n", repo->id); return -1; } if (!ops) { seaf_message ("All operations of repo %s(%.8s) have been processed.\n", repo->name, repo->id); repo->partial_commit_mode = FALSE; return 0; } if (repo->encrypted) crypt = seafile_crypt_new (repo->enc_version, repo->enc_key, repo->enc_iv); for (ptr = ops; ptr; ptr = ptr->next) { op = (JournalOp *)ptr->data; if (ptr->next) next_op = (JournalOp *)(ptr->next->data); else next_op = NULL; filename = g_path_get_basename (op->path); seaf_debug ("Processing event %d %s\n", op->type, op->path); switch (op->type) { case OP_TYPE_CREATE_FILE: /* If the next op is update the same file, skip this one. */ if (next_op && next_op->type == OP_TYPE_UPDATE_FILE && strcmp (next_op->path, op->path) == 0) break; if (seaf_repo_manager_ignored_on_commit (filename)) break; st.st_size = op->size; st.st_mtime = op->mtime; st.st_mode = op->mode; add_to_changeset (changeset, DIFF_STATUS_ADDED, allzero, &st, username, op->path, NULL); break; case OP_TYPE_DELETE_FILE: if (seaf_repo_manager_ignored_on_commit (filename)) break; remove_from_changeset (changeset, op->path, FALSE, NULL); g_hash_table_remove (updated_files, op->path); break; case OP_TYPE_UPDATE_FILE: /* If the next op is update the same file, skip this one. */ if (next_op && next_op->type == OP_TYPE_UPDATE_FILE && strcmp (next_op->path, op->path) == 0) break; if (seaf_repo_manager_ignored_on_commit (filename)) break; handle_update_file_op (repo, changeset, username, op, FALSE, updated_files, &total_size, crypt); if (total_size >= MAX_COMMIT_SIZE) { seaf_message ("Creating partial commit after adding %s in repo %s(%.8s).\n", op->path, repo->name, repo->id); repo->partial_commit_mode = TRUE; *last_opid = op->opid; g_free (filename); goto out; } break; case OP_TYPE_RENAME: handle_rename_op (repo, changeset, username, updated_files, op, &total_size); if (total_size >= MAX_COMMIT_SIZE) { seaf_message ("Creating partial commit after rename %s in repo %s(%.8s).\n", op->path, repo->name, repo->id); repo->partial_commit_mode = TRUE; *last_opid = op->opid; g_free (filename); goto out; } break; case OP_TYPE_MKDIR: if (seaf_repo_manager_ignored_on_commit (filename)) break; st.st_size = op->size; st.st_mtime = op->mtime; st.st_mode = S_IFDIR; add_to_changeset (changeset, DIFF_STATUS_DIR_ADDED, allzero, &st, username, op->path, NULL); break; case OP_TYPE_RMDIR: if (seaf_repo_manager_ignored_on_commit (filename)) break; remove_from_changeset (changeset, op->path, FALSE, NULL); break; case OP_TYPE_UPDATE_ATTR: if (seaf_repo_manager_ignored_on_commit (filename)) break; /* Don't update the file if it doesn't exist in the current * repo tree. */ if (repo_tree_stat_path (repo->tree, op->path, &tree_st) < 0) { break; } st.st_size = op->size; st.st_mtime = op->mtime; st.st_mode = op->mode; add_to_changeset (changeset, DIFF_STATUS_MODIFIED, NULL, &st, username, op->path, NULL); break; default: seaf_warning ("Unknwon op type %d, skipped.\n", op->type); } g_free (filename); if (!ptr->next) { seaf_message ("All operations of repo %s(%.8s) have been processed.\n", repo->name, repo->id); repo->partial_commit_mode = FALSE; *last_opid = op->opid; } } out: g_free (crypt); g_list_free_full (ops, (GDestroyNotify)journal_op_free); return 0; } static int update_head_commit (SeafRepo *repo, const char *root_id, const char *desc, const char *username, gint64 last_opid) { SeafCommit *commit; int ret = 0; commit = seaf_commit_new (NULL, repo->id, root_id, username, seaf->client_id, desc, 0); commit->parent_id = g_strdup (repo->head->commit_id); /* Add this computer's name to commit. */ commit->device_name = g_strdup(seaf->client_name); commit->client_version = g_strdup("seadrive_"SEAFILE_CLIENT_VERSION); seaf_repo_to_commit (repo, commit); if (seaf_commit_manager_add_commit (seaf->commit_mgr, commit) < 0) { ret = -1; goto out; } seaf_branch_set_commit (repo->head, commit->commit_id); repo->head->opid = last_opid; if (seaf_branch_manager_update_branch (seaf->branch_mgr, repo->head) < 0) { ret = -1; } out: seaf_commit_unref (commit); return ret; } static int update_head_opid (SeafRepo *repo, gint64 opid) { repo->head->opid = opid; if (seaf_branch_manager_update_opid (seaf->branch_mgr, repo->id, "local", opid) < 0) return -1; if (seaf_branch_manager_update_opid (seaf->branch_mgr, repo->id, "master", opid) < 0) return -1; return 0; } static int create_commit_from_changeset (SeafRepo *repo, ChangeSet *changeset, gint64 last_opid, const char *username, gboolean *changed) { char *desc = NULL; char *root_id = NULL; SeafCommit *head = NULL; GList *diff_results = NULL; int ret = 0; *changed = TRUE; root_id = commit_tree_from_changeset (changeset); if (!root_id) { seaf_warning ("Failed to create commit tree for repo %s.\n", repo->id); ret = -1; goto out; } head = seaf_commit_manager_get_commit (seaf->commit_mgr, repo->id, repo->version, repo->head->commit_id); if (!head) { seaf_warning ("Head commit %s for repo %s not found\n", repo->head->commit_id, repo->id); ret = -1; goto out; } if (strcmp (head->root_id, root_id) != 0) { if (!is_repo_writable (repo)) { seaf_warning ("Skip creating commit for read-only repo: %s.\n", repo->id); ret = -1; goto out; } /* Calculate diff for more accurate commit descriptions. */ diff_commit_roots (repo->id, repo->version, head->root_id, root_id, &diff_results, TRUE); desc = diff_results_to_description (diff_results); if (!desc) desc = g_strdup(""); if (update_head_commit (repo, root_id, desc, username, last_opid) < 0) { seaf_warning ("Failed to update head commit for repo %s.\n", repo->id); ret = -1; goto out; } } else { *changed = FALSE; if (update_head_opid (repo, last_opid) < 0) { seaf_warning ("Failed to update head opid for repo %s.\n", repo->id); ret = -1; goto out; } } out: g_free (desc); g_free (root_id); seaf_commit_unref (head); g_list_free_full (diff_results, (GDestroyNotify)diff_entry_free); return ret; } static int commit_repo (SeafRepo *repo, gboolean *changed) { ChangeSet *changeset; gint64 last_opid; SeafAccount *account; GHashTable *updated_files = NULL; GHashTableIter iter; gpointer key, value; int ret = 0; changeset = changeset_new (repo->id); if (!changeset) { seaf_warning ("Failed to create changeset for repo %s.\n", repo->id); return -1; } account = seaf_repo_manager_get_account (seaf->repo_mgr, repo->server, repo->user); if (!account) { seaf_warning ("No current account found.\n"); ret = -1; goto out; } last_opid = repo->head->opid; updated_files = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); if (apply_journal_ops_to_changeset (repo, changeset, account->username, &last_opid, updated_files) < 0) { seaf_warning ("Failed to apply journal operations to changeset for repo %s.\n", repo->id); ret = -1; goto out; } /* No new operations. */ if (last_opid == repo->head->opid) { goto out; } if (create_commit_from_changeset (repo, changeset, last_opid, account->username, changed) < 0) { ret = -1; goto out; } /* Update cached file attrs after commit is done. */ char *path; FileCacheStat *st; g_hash_table_iter_init (&iter, updated_files); while (g_hash_table_iter_next (&iter, &key, &value)) { path = (char *)key; st = (FileCacheStat *)value; /* If mtime is 0, the file was actually not indexed. Just skip this. */ if (st->mtime != 0) { /* Mark file content as not-yet-uploaded to server, so that * they won't be removed by cache cleaning routine. * They'll be marked as uploaded again when upload finishes. */ file_cache_mgr_set_file_uploaded (seaf->file_cache_mgr, repo->id, path, FALSE); repo_tree_set_file_id (repo->tree, path, st->file_id); file_cache_mgr_set_attrs (seaf->file_cache_mgr, repo->id, path, st->mtime, st->size, st->file_id); } } out: seaf_account_free (account); changeset_free (changeset); if (updated_files) g_hash_table_destroy (updated_files); return ret; } struct CommitResult { SyncTask *task; gboolean changed; gboolean success; }; static void * commit_job (void *vtask) { SyncTask *task = vtask; struct CommitResult *res = g_new0 (struct CommitResult, 1); res->task = task; res->success = TRUE; if (commit_repo (task->repo, &res->changed) < 0) { res->success = FALSE; } return res; } static void commit_job_done (void *vres) { struct CommitResult *res = (struct CommitResult *)vres; SyncTask *task = res->task; SeafRepo *repo = task->repo; HttpServerState *state = task->server_state; if (repo->delete_pending) { transition_sync_state (task, SYNC_STATE_CANCELED); g_free (res); return; } if (!res->success) { seaf_sync_manager_set_task_error (res->task, SYNC_ERROR_COMMIT); g_free (res); return; } if (res->changed) { char *desc = NULL; desc = exceed_max_deleted_files (repo); if (desc) { notify_delete_confirmation (repo->name, desc, repo->head->commit_id); seaf_warning ("Delete more than %d files, add delete confirmation.\n", seaf->delete_confirm_threshold); task->info->del_confirmation_pending = TRUE; seaf_sync_manager_set_task_error (res->task, SYNC_ERROR_DEL_CONFIRMATION_PENDING); g_free (desc); g_free (res); return; } int rc = http_tx_manager_add_upload (seaf->http_tx_mgr, repo->id, repo->version, repo->server, repo->user, repo->repo_uname, state->effective_host, repo->token, state->http_version, state->use_fileserver_port, NULL); if (rc < 0) { seaf_warning ("Failed to add upload task for repo %s to server %s.\n", repo->id, state->effective_host); seaf_sync_manager_set_task_error (task, SYNC_ERROR_START_UPLOAD); } else { transition_sync_state (task, SYNC_STATE_UPLOAD); } } else { transition_sync_state (res->task, SYNC_STATE_DONE); } g_free (res); } static gboolean create_commit_from_journal (SyncInfo *info, HttpServerState *state, SeafRepo *repo) { JournalStat st; gint now = (gint)time(NULL); journal_get_stat (repo->journal, &st); if (st.last_commit_time == 0 || (st.last_change_time >= st.last_commit_time && now - st.last_change_time >= 2) || repo->partial_commit_mode) { if (!repo->partial_commit_mode) journal_set_last_commit_time (repo->journal, now); SyncTask *task = sync_task_new (info, state, NULL, NULL, repo->token, FALSE); if (seaf_job_manager_schedule_job (seaf->job_mgr, commit_job, commit_job_done, task) < 0) { seaf_warning ("Failed to schedule commit task for repo %s.\n", repo->id); seaf_sync_manager_set_task_error (task, SYNC_ERROR_COMMIT); return TRUE; } transition_sync_state (task, SYNC_STATE_COMMIT); return TRUE; } return FALSE; } HttpServerInfo * seaf_sync_manager_get_server_info (SeafSyncManager *mgr, const char *server, const char *user) { SeafAccount *account; HttpServerState *state; HttpServerInfo *info = NULL; account = seaf_repo_manager_get_account (seaf->repo_mgr, server, user); if (!account) { return NULL; } state = get_http_server_state (mgr->priv, account->fileserver_addr); seaf_account_free (account); if (!state) { return NULL; } info = g_new0 (HttpServerInfo, 1); info->host = g_strdup (state->effective_host); info->use_fileserver_port = state->use_fileserver_port; return info; } void seaf_sync_manager_free_server_info (HttpServerInfo *info) { if (!info) return; g_free (info->host); g_free (info); } /* Path sync status. */ static ActivePathsInfo * active_paths_info_new (SeafRepo *repo) { ActivePathsInfo *info = g_new0 (ActivePathsInfo, 1); info->syncing_tree = sync_status_tree_new (repo->worktree); info->synced_tree = sync_status_tree_new (repo->worktree); return info; } static void active_paths_info_free (ActivePathsInfo *info) { if (!info) return; sync_status_tree_free (info->syncing_tree); sync_status_tree_free (info->synced_tree); g_free (info); } void seaf_sync_manager_update_active_path (SeafSyncManager *mgr, const char *repo_id, const char *path, int mode, SyncStatus status) { // Don't need to update active path on Linux. return; /* ActivePathsInfo *info; SeafRepo *repo = NULL; if (!repo_id || !path) { seaf_warning ("BUG: empty repo_id or path.\n"); return; } if (status <= SYNC_STATUS_NONE || status >= N_SYNC_STATUS) { seaf_warning ("BUG: invalid sync status %d.\n", status); return; } repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id); if (!repo) return; pthread_mutex_lock (&mgr->priv->paths_lock); info = g_hash_table_lookup (mgr->priv->active_paths, repo_id); if (!info) { info = active_paths_info_new (repo); g_hash_table_insert (mgr->priv->active_paths, g_strdup(repo_id), info); } if (status == SYNC_STATUS_SYNCING) { sync_status_tree_del (info->synced_tree, path); sync_status_tree_add (info->syncing_tree, path, mode); } else if (status == SYNC_STATUS_SYNCED) { sync_status_tree_del (info->syncing_tree, path); sync_status_tree_add (info->synced_tree, path, mode); } pthread_mutex_unlock (&mgr->priv->paths_lock); seaf_repo_unref (repo); */ } void seaf_sync_manager_delete_active_path (SeafSyncManager *mgr, const char *repo_id, const char *path) { // Don't need to update active path on Linux. return; /* ActivePathsInfo *info; if (!repo_id || !path) { seaf_warning ("BUG: empty repo_id or path.\n"); return; } pthread_mutex_lock (&mgr->priv->paths_lock); info = g_hash_table_lookup (mgr->priv->active_paths, repo_id); if (!info) { pthread_mutex_unlock (&mgr->priv->paths_lock); return; } sync_status_tree_del (info->syncing_tree, path); sync_status_tree_del (info->synced_tree, path); pthread_mutex_unlock (&mgr->priv->paths_lock); */ } static char *path_status_tbl[] = { "none", "syncing", "error", "synced", "partial_synced", "cloud", "readonly", "locked", "locked_by_me", NULL, }; static SyncStatus get_repo_sync_status (SeafSyncManager *mgr, const char *repo_id) { ActivePathsInfo *info; SyncStatus status = SYNC_STATUS_CLOUD; pthread_mutex_lock (&mgr->priv->paths_lock); info = g_hash_table_lookup (mgr->priv->active_paths, repo_id); if (!info) goto out; if (sync_status_tree_has_file (info->syncing_tree)) status = SYNC_STATUS_SYNCING; else if (sync_status_tree_has_file (info->synced_tree)) status = SYNC_STATUS_PARTIAL_SYNCED; out: pthread_mutex_unlock (&mgr->priv->paths_lock); return status; } char * seaf_sync_manager_get_path_sync_status (SeafSyncManager *mgr, const char *repo_id, const char *path) { ActivePathsInfo *info; SyncStatus ret = SYNC_STATUS_NONE; if (!repo_id || !path) { seaf_warning ("BUG: empty repo_id or path.\n"); return NULL; } if (path[0] == 0) { ret = get_repo_sync_status (mgr, repo_id); goto check_special_states; } pthread_mutex_lock (&mgr->priv->paths_lock); info = g_hash_table_lookup (mgr->priv->active_paths, repo_id); if (!info) { pthread_mutex_unlock (&mgr->priv->paths_lock); ret = SYNC_STATUS_CLOUD; goto check_special_states; } gboolean is_dir = FALSE; if (sync_status_tree_exists (info->syncing_tree, path, &is_dir)) { ret = SYNC_STATUS_SYNCING; } else if (sync_status_tree_exists (info->synced_tree, path, &is_dir)) { if (is_dir) ret = SYNC_STATUS_PARTIAL_SYNCED; else if (!file_cache_mgr_is_file_outdated (seaf->file_cache_mgr, repo_id, path)) ret = SYNC_STATUS_SYNCED; else ret = SYNC_STATUS_CLOUD; } else { SeafRepo *repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id); if (!repo) { pthread_mutex_unlock (&mgr->priv->paths_lock); goto out; } RepoTreeStat st; if (repo_tree_stat_path (repo->tree, path, &st) < 0) { pthread_mutex_unlock (&mgr->priv->paths_lock); seaf_repo_unref (repo); goto out; } if (S_ISDIR(st.mode) || !file_cache_mgr_is_file_cached (seaf->file_cache_mgr, repo_id, path)) { ret = SYNC_STATUS_CLOUD; } seaf_repo_unref (repo); } pthread_mutex_unlock (&mgr->priv->paths_lock); check_special_states: if (ret == SYNC_STATUS_SYNCED || ret == SYNC_STATUS_PARTIAL_SYNCED || ret == SYNC_STATUS_CLOUD) { if (!seaf_repo_manager_is_path_writable(seaf->repo_mgr, repo_id, path)) ret = SYNC_STATUS_READONLY; else if (seaf_filelock_manager_is_file_locked_by_me (seaf->filelock_mgr, repo_id, path)) ret = SYNC_STATUS_LOCKED_BY_ME; else if (seaf_filelock_manager_is_file_locked (seaf->filelock_mgr, repo_id, path)) ret = SYNC_STATUS_LOCKED; } out: return g_strdup(path_status_tbl[ret]); } void seaf_sync_manager_remove_active_path_info (SeafSyncManager *mgr, const char *repo_id) { pthread_mutex_lock (&mgr->priv->paths_lock); g_hash_table_remove (mgr->priv->active_paths, repo_id); pthread_mutex_unlock (&mgr->priv->paths_lock); } int seaf_sync_manager_is_syncing (SeafSyncManager *mgr) { GHashTableIter iter; gpointer key, value; SyncInfo *info; gboolean is_syncing = FALSE; pthread_mutex_lock (&mgr->priv->infos_lock); g_hash_table_iter_init (&iter, mgr->priv->sync_infos); while (g_hash_table_iter_next (&iter, &key, &value)) { info = (SyncInfo *)value; if (!info->in_sync || !info->current_task) continue; if (info->current_task->state == SYNC_STATE_UPLOAD) { is_syncing = TRUE; break; } } pthread_mutex_unlock (&mgr->priv->infos_lock); if (!is_syncing) { is_syncing = file_cache_mgr_is_fetching_file (seaf->file_cache_mgr); } return is_syncing; } static int update_tx_state_pulse (void *vmanager) { SeafSyncManager *mgr = vmanager; mgr->last_sent_bytes = g_atomic_int_get (&mgr->sent_bytes); g_atomic_int_set (&mgr->sent_bytes, 0); mgr->last_recv_bytes = g_atomic_int_get (&mgr->recv_bytes); g_atomic_int_set (&mgr->recv_bytes, 0); return TRUE; } /* Lock/unlock files on server */ typedef struct LockFileJob { char repo_id[37]; char *path; gboolean lock; /* False if unlock */ } LockFileJob; static void lock_file_job_free (LockFileJob *job) { if (!job) return; g_free (job->path); g_free (job); } static void do_lock_file (LockFileJob *job) { SeafRepo *repo = NULL; HttpServerInfo *server = NULL; repo = seaf_repo_manager_get_repo (seaf->repo_mgr, job->repo_id); if (!repo) return; seaf_message ("Auto lock file %s/%s\n", repo->name, job->path); int status = seaf_filelock_manager_get_lock_status (seaf->filelock_mgr, repo->id, job->path); if (status != FILE_NOT_LOCKED) { goto out; } server = seaf_sync_manager_get_server_info (seaf->sync_mgr, repo->server, repo->user); if (!server) goto out; if (http_tx_manager_lock_file (seaf->http_tx_mgr, server->host, server->use_fileserver_port, repo->token, repo->id, job->path) < 0) { seaf_warning ("Failed to lock %s in repo %.8s on server.\n", job->path, repo->id); goto out; } /* Mark file as locked locally so that the user can see the effect immediately. */ seaf_filelock_manager_mark_file_locked (seaf->filelock_mgr, repo->id, job->path, LOCKED_AUTO); out: seaf_repo_unref (repo); seaf_sync_manager_free_server_info (server); } static void do_unlock_file (LockFileJob *job) { SeafRepo *repo = NULL; HttpServerInfo *server = NULL; repo = seaf_repo_manager_get_repo (seaf->repo_mgr, job->repo_id); if (!repo) return; seaf_message ("Auto unlock file %s/%s\n", repo->name, job->path); int status = seaf_filelock_manager_get_lock_status (seaf->filelock_mgr, repo->id, job->path); if (status != FILE_LOCKED_BY_ME_AUTO) { goto out; } server = seaf_sync_manager_get_server_info (seaf->sync_mgr, repo->server, repo->user); if (!server) goto out; if (http_tx_manager_unlock_file (seaf->http_tx_mgr, server->host, server->use_fileserver_port, repo->token, repo->id, job->path) < 0) { seaf_warning ("Failed to unlock %s in repo %.8s on server.\n", job->path, repo->id); goto out; } /* Mark file as unlocked locally so that the user can see the effect immediately. */ seaf_filelock_manager_mark_file_unlocked (seaf->filelock_mgr, repo->id, job->path); out: seaf_repo_unref (repo); seaf_sync_manager_free_server_info (server); } static void * lock_file_worker (void *vdata) { GAsyncQueue *queue = (GAsyncQueue *)vdata; LockFileJob *job; while (1) { job = g_async_queue_pop (queue); if (!job) break; if (job->lock) do_lock_file (job); else do_unlock_file (job); lock_file_job_free (job); } return NULL; } void seaf_sync_manager_lock_file_on_server (SeafSyncManager *mgr, const char *server, const char *user, const char *repo_id, const char *path) { LockFileJob *job; GAsyncQueue *queue = mgr->priv->lock_file_job_queue; if (!seaf_repo_manager_account_is_pro (seaf->repo_mgr, server, user)) return; job = g_new0 (LockFileJob, 1); memcpy (job->repo_id, repo_id, 36); job->path = g_strdup(path); job->lock = TRUE; g_async_queue_push (queue, job); } void seaf_sync_manager_unlock_file_on_server (SeafSyncManager *mgr, const char *server, const char *user, const char *repo_id, const char *path) { LockFileJob *job; GAsyncQueue *queue = mgr->priv->lock_file_job_queue; if (!seaf_repo_manager_account_is_pro (seaf->repo_mgr, server, user)) return; job = g_new0 (LockFileJob, 1); memcpy (job->repo_id, repo_id, 36); job->path = g_strdup(path); job->lock = FALSE; g_async_queue_push (queue, job); } /* Repo operations on server. */ // Create repo when mkdir in root, it contain follow procedures: // 1. Invoke api to create repo // 2. Get head commit from seaf-server // 3. Create and init repo to make it can sync in local int seaf_sync_manager_create_repo (SeafSyncManager *mgr, const char *server, const char *user, const char *repo_name) { SeafAccount *account; char *resp = NULL; gint64 resp_size; json_t *info = NULL; json_error_t jerror; const char *repo_id; const char *head_commit_id; const char *sync_token; HttpServerState *state; SeafCommit *head_commit = NULL; int ret; char *display_name; account = seaf_repo_manager_get_account (seaf->repo_mgr, server ,user); if (!account) return -1; ret = http_tx_manager_api_create_repo (seaf->http_tx_mgr, account->server, account->token, repo_name, &resp, &resp_size); if (ret < 0) { ret = -1; goto out; } info = json_loadb (resp, resp_size, 0, &jerror); if (!info) { seaf_warning ("Invalid resp from create repo api: %s.\n", jerror.text); ret = -1; goto out; } repo_id = json_object_get_string_member (info, "repo_id"); head_commit_id = json_object_get_string_member (info, "head_commit_id"); sync_token = json_object_get_string_member (info, "token"); if (!repo_id || !head_commit_id || !sync_token) { seaf_warning ("Invalid resp from create repo api.\n"); ret = -1; goto out; } g_free (resp); resp = NULL; state = get_http_server_state (mgr->priv, account->fileserver_addr); if (!state) { seaf_warning ("Invalid http server state.\n"); ret = -1; goto out; } ret = http_tx_manager_get_commit (seaf->http_tx_mgr, state->effective_host, state->use_fileserver_port, sync_token, repo_id, head_commit_id, &resp, &resp_size); if (ret < 0) { goto out; } head_commit = seaf_commit_from_data (head_commit_id, resp, resp_size); if (!head_commit) { seaf_warning ("Invalid commit info returned from server.\n"); ret = -1; goto out; } if (seaf_obj_store_write_obj (seaf->commit_mgr->obj_store, repo_id, head_commit->version, head_commit_id, resp, (int)resp_size, TRUE) < 0) { ret = -1; goto out; } SeafRepo *repo = seaf_repo_new (repo_id, NULL, NULL); seaf_repo_from_commit (repo, head_commit); SeafBranch *branch = seaf_branch_new ("local", repo_id, head_commit_id, 0); seaf_branch_manager_add_branch (seaf->branch_mgr, branch); seaf_repo_set_head (repo, branch); seaf_branch_unref (branch); branch = seaf_branch_new ("master", repo_id, head_commit_id, 0); seaf_branch_manager_add_branch (seaf->branch_mgr, branch); seaf_branch_unref (branch); seaf_repo_manager_set_repo_token (seaf->repo_mgr, repo, sync_token); display_name = seaf_repo_manager_get_display_name_by_repo_name (seaf->repo_mgr, server, user, repo_name); seaf_repo_set_worktree (repo, display_name); g_free (display_name); if (seaf_repo_load_fs (repo, FALSE) < 0) { seaf_repo_free (repo); ret = -1; goto out; } repo->server = g_strdup (server); repo->user = g_strdup (user); seaf_repo_manager_add_repo (seaf->repo_mgr, repo); ret = seaf_repo_manager_add_repo_to_account (seaf->repo_mgr, account->server, account->username, repo); out: seaf_account_free (account); if (resp) g_free (resp); if (info) json_decref (info); if (head_commit) seaf_commit_unref (head_commit); return ret; } // Rename repo when rename dir in root, it contain follow procedures: // 1. Invoke api to rename repo // 2. Change some internal mapping int seaf_sync_manager_rename_repo (SeafSyncManager *mgr, const char *server, const char *user, const char *repo_id, const char *new_name) { SeafAccount *account; int ret; account = seaf_repo_manager_get_account (seaf->repo_mgr, server, user); if (!account) return -1; ret = http_tx_manager_api_rename_repo (seaf->http_tx_mgr, account->server, account->token, repo_id, new_name); if (ret < 0) goto out; ret = seaf_repo_manager_rename_repo_on_account (seaf->repo_mgr, account->server, account->username, repo_id, new_name); if (ret < 0) goto out; seaf_repo_manager_rename_repo (seaf->repo_mgr, repo_id, new_name); out: seaf_account_free (account); return ret; } int seaf_sync_manager_delete_repo (SeafSyncManager *mgr, const char *server, const char *user, const char *repo_id) { SeafAccount *account; int ret; account = seaf_repo_manager_get_account (seaf->repo_mgr, server, user); if (!account) return -1; ret = http_tx_manager_api_delete_repo (seaf->http_tx_mgr, account->server, account->token, repo_id); if (ret < 0) goto out; seaf_repo_manager_remove_account_repo (seaf->repo_mgr, repo_id, server, user); out: seaf_account_free (account); return ret; } gboolean seaf_sync_manager_is_server_disconnected (SeafSyncManager *mgr) { gint disconnected; disconnected = g_atomic_int_get (&mgr->priv->server_disconnected); if (disconnected == 0) return FALSE; else return TRUE; } void seaf_sync_manager_reset_server_connected_state (SeafSyncManager *mgr) { g_atomic_int_set (&mgr->priv->server_disconnected, 0); } static void schedule_cache_file (const char *repo_id, const char *path, RepoTreeStat *st) { if (S_ISREG(st->mode)) file_cache_mgr_cache_file (seaf->file_cache_mgr, repo_id, path, st); else file_cache_mgr_mkdir (seaf->file_cache_mgr, repo_id, path); } static void check_server_connectivity (const char *server, const char *user) { HttpServerInfo *server_info = NULL; while (1) { server_info = seaf_sync_manager_get_server_info (seaf->sync_mgr, server, user); if (server_info) break; seaf_sleep (1); } seaf_sync_manager_free_server_info (server_info); } static void * cache_file_task_worker (void *vdata) { GAsyncQueue *task_queue = vdata; CacheFileTask *task; SeafRepo *repo = NULL; while (1) { task = g_async_queue_pop (task_queue); repo = seaf_repo_manager_get_repo (seaf->repo_mgr, task->repo_id); if (!repo) { seaf_warning ("Failed to find repo %s.\n", task->repo_id); goto next; } check_server_connectivity (repo->server, repo->user); seaf_message ("Start to cache %s/%s\n", repo->name, task->path); repo_tree_traverse (repo->tree, task->path, schedule_cache_file); seaf_repo_unref (repo); next: g_free (task->repo_id); g_free (task->path); g_free (task); } return NULL; } void seaf_sync_manager_cache_path (SeafSyncManager *mgr, const char *repo_id, const char *path) { CacheFileTask *task = g_new0 (CacheFileTask, 1); task->repo_id = g_strdup(repo_id); task->path = g_strdup(path); g_async_queue_push (mgr->priv->cache_file_task_queue, task); } /* static void */ /* disable_auto_sync_for_repos (SeafSyncManager *mgr) */ /* { */ /* GList *repos; */ /* GList *ptr; */ /* SeafRepo *repo; */ /* repos = seaf_repo_manager_get_repo_list (seaf->repo_mgr, -1, -1); */ /* for (ptr = repos; ptr; ptr = ptr->next) { */ /* repo = ptr->data; */ /* seaf_sync_manager_cancel_sync_task (mgr, repo->id); */ /* } */ /* g_list_free (repos); */ /* } */ int seaf_sync_manager_disable_auto_sync (SeafSyncManager *mgr) { if (!seaf->started) { seaf_message ("sync manager is not started, skip disable auto sync.\n"); return -1; } seaf_message ("Disabled auto sync.\n"); mgr->priv->auto_sync_enabled = FALSE; return 0; } int seaf_sync_manager_enable_auto_sync (SeafSyncManager *mgr) { if (!seaf->started) { seaf_message ("sync manager is not started, skip enable auto sync.\n"); return -1; } seaf_message ("Enabled auto sync.\n"); mgr->priv->auto_sync_enabled = TRUE; return 0; } int seaf_sync_manager_is_auto_sync_enabled (SeafSyncManager *mgr) { if (mgr->priv->auto_sync_enabled) return 1; else return 0; } seadrive-fuse-3.0.13/src/sync-mgr.h000066400000000000000000000216551476177674700171020ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ #ifndef SYNC_MGR_H #define SYNC_MGR_H #include enum { SYNC_ERROR_LEVEL_REPO, SYNC_ERROR_LEVEL_FILE, SYNC_ERROR_LEVEL_NETWORK, }; #define SYNC_ERROR_ID_FILE_LOCKED_BY_APP 0 #define SYNC_ERROR_ID_FOLDER_LOCKED_BY_APP 1 #define SYNC_ERROR_ID_FILE_LOCKED 2 #define SYNC_ERROR_ID_INVALID_PATH 3 #define SYNC_ERROR_ID_INDEX_ERROR 4 #define SYNC_ERROR_ID_ACCESS_DENIED 5 #define SYNC_ERROR_ID_QUOTA_FULL 6 #define SYNC_ERROR_ID_NETWORK 7 #define SYNC_ERROR_ID_RESOLVE_PROXY 8 #define SYNC_ERROR_ID_RESOLVE_HOST 9 #define SYNC_ERROR_ID_CONNECT 10 #define SYNC_ERROR_ID_SSL 11 #define SYNC_ERROR_ID_TX 12 #define SYNC_ERROR_ID_TX_TIMEOUT 13 #define SYNC_ERROR_ID_UNHANDLED_REDIRECT 14 #define SYNC_ERROR_ID_SERVER 15 #define SYNC_ERROR_ID_LOCAL_DATA_CORRUPT 16 #define SYNC_ERROR_ID_WRITE_LOCAL_DATA 17 #define SYNC_ERROR_ID_PERM_NOT_SYNCABLE 18 #define SYNC_ERROR_ID_NO_WRITE_PERMISSION 19 #define SYNC_ERROR_ID_FOLDER_PERM_DENIED 20 #define SYNC_ERROR_ID_PATH_END_SPACE_PERIOD 21 #define SYNC_ERROR_ID_PATH_INVALID_CHARACTER 22 #define SYNC_ERROR_ID_UPDATE_TO_READ_ONLY_REPO 23 #define SYNC_ERROR_ID_CONFLICT 24 #define SYNC_ERROR_ID_UPDATE_NOT_IN_REPO 25 #define SYNC_ERROR_ID_LIBRARY_TOO_LARGE 26 #define SYNC_ERROR_ID_MOVE_NOT_IN_REPO 27 #define SYNC_ERROR_ID_DEL_CONFIRMATION_PENDING 28 #define SYNC_ERROR_ID_INVALID_PATH_ON_WINDOWS 29 #define SYNC_ERROR_ID_TOO_MANY_FILES 30 #define SYNC_ERROR_ID_BLOCK_MISSING 31 #define SYNC_ERROR_ID_CHECKOUT_FILE 32 #define SYNC_ERROR_ID_CASE_CONFLICT 33 #define SYNC_ERROR_ID_GENERAL_ERROR 34 #define SYNC_ERROR_ID_NO_ERROR 35 int sync_error_level (int error); typedef struct _SeafSyncManager SeafSyncManager; typedef struct _SeafSyncManagerPriv SeafSyncManagerPriv; struct _SeafileSession; struct _SeafSyncManager { struct _SeafileSession *seaf; /* Sent/recv bytes from all transfer tasks in this second. */ gint sent_bytes; gint recv_bytes; gint last_sent_bytes; gint last_recv_bytes; /* Upload/download rate limits. */ gint upload_limit; gint download_limit; SeafSyncManagerPriv *priv; }; SeafSyncManager* seaf_sync_manager_new (struct _SeafileSession *seaf); int seaf_sync_manager_init (SeafSyncManager *mgr); int seaf_sync_manager_start (SeafSyncManager *mgr); const char * sync_error_to_str (int error); const char * sync_state_to_str (int state); typedef struct HttpServerInfo { char *host; gboolean use_fileserver_port; } HttpServerInfo; HttpServerInfo * seaf_sync_manager_get_server_info (SeafSyncManager *mgr, const char *server, const char *user); void seaf_sync_manager_free_server_info (HttpServerInfo *info); int seaf_sync_manager_update_account_repo_list (SeafSyncManager *mgr, const char *server, const char *username); int seaf_sync_manager_is_syncing (SeafSyncManager *mgr); int seaf_handle_file_conflict (const char *repo_id, const char *path, gboolean *conflicted); /* Path sync status */ enum _SyncStatus { SYNC_STATUS_NONE = 0, SYNC_STATUS_SYNCING, SYNC_STATUS_ERROR, SYNC_STATUS_SYNCED, SYNC_STATUS_PARTIAL_SYNCED, SYNC_STATUS_CLOUD, SYNC_STATUS_READONLY, SYNC_STATUS_LOCKED, SYNC_STATUS_LOCKED_BY_ME, N_SYNC_STATUS, }; typedef enum _SyncStatus SyncStatus; void seaf_sync_manager_update_active_path (SeafSyncManager *mgr, const char *repo_id, const char *path, int mode, SyncStatus status); void seaf_sync_manager_delete_active_path (SeafSyncManager *mgr, const char *repo_id, const char *path); char * seaf_sync_manager_get_path_sync_status (SeafSyncManager *mgr, const char *repo_id, const char *path); char * seaf_sync_manager_list_active_paths_json (SeafSyncManager *mgr); int seaf_sync_manager_active_paths_number (SeafSyncManager *mgr); void seaf_sync_manager_remove_active_path_info (SeafSyncManager *mgr, const char *repo_id); int seaf_sync_manager_create_repo (SeafSyncManager *mgr, const char *server, const char *user, const char *repo_name); int seaf_sync_manager_rename_repo (SeafSyncManager *mgr, const char *server, const char *user, const char *repo_id, const char *new_name); int seaf_sync_manager_delete_repo (SeafSyncManager *mgr, const char *server, const char *user, const char *repo_id); /* Lock/unlock files on server. */ void seaf_sync_manager_lock_file_on_server (SeafSyncManager *mgr, const char *server, const char *user, const char *repo_id, const char *path); void seaf_sync_manager_unlock_file_on_server (SeafSyncManager *mgr, const char *server, const char *user, const char *repo_id, const char *path); /* Sync errors */ json_t * seaf_sync_manager_list_sync_errors (SeafSyncManager *mgr); gboolean seaf_sync_manager_is_server_disconnected (SeafSyncManager *mgr); void seaf_sync_manager_reset_server_connected_state (SeafSyncManager *mgr); void seaf_sync_manager_cache_path (SeafSyncManager *mgr, const char *repo_id, const char *path); int seaf_sync_manager_disable_auto_sync (SeafSyncManager *mgr); int seaf_sync_manager_enable_auto_sync (SeafSyncManager *mgr); int seaf_sync_manager_is_auto_sync_enabled (SeafSyncManager *mgr); // File Cache Entry Management typedef struct _CacheEntry { gint64 mtime; gint64 size; } CacheEntry; CacheEntry* seaf_sync_manager_find_cache_entry (SeafSyncManager* mgr, const char* repo_id, const char* path); int seaf_sync_manager_handle_file_conflict (CacheEntry *entry, const char *repo_id, const char *fullpath, const char *path, gboolean *conflicted); int seaf_sync_manager_add_cache_entry (SeafSyncManager* mgr, const char* repo_id, const char* path, gint64 mtime, gint64 size); int seaf_sync_manager_update_cache_entry (SeafSyncManager* mgr, const char* repo_id, const char* path, gint64 mtime, gint64 size); int seaf_sync_manager_clean_cache_entries (SeafSyncManager* mgr, const char* repo_id); int seaf_sync_manager_del_cached_entry (SeafSyncManager* mgr, const char* repo_id, const char* path); void seaf_sync_manager_lock_cache_db(); void seaf_sync_manager_unlock_cache_db(); // Rename or move entries in repo. // old_prefix is the prefix of existing entries. They will be renamed to start with new_prefix. // Can be used to rename a file or a folder. int seaf_sync_manager_rename_cache_entries (SeafSyncManager* mgr, const char* repo_id, const char* old_prefix, const char* new_prefix); typedef enum IgnoreReason { IGNORE_REASON_END_SPACE_PERIOD = 0, IGNORE_REASON_INVALID_CHARACTER = 1, } IgnoreReason; gboolean seaf_sync_manager_ignored_on_checkout (const char *file_path, IgnoreReason *ignore_reason); gboolean seaf_repo_manager_ignored_on_commit (const char *filename); void seaf_sync_manager_check_locks_and_folder_perms (SeafSyncManager *manager, const char *server_url); void seaf_sync_manager_set_last_sync_time (SeafSyncManager *mgr, const char *repo_id, gint64 last_sync_time); int seaf_sync_manager_add_del_confirmation (SeafSyncManager *mgr, const char *confirmation_id, gboolean resync); #endif seadrive-fuse-3.0.13/src/sync-status-tree.c000066400000000000000000000154751476177674700205730ustar00rootroot00000000000000#include "common.h" #include "seafile-session.h" #include "sync-status-tree.h" #include "log.h" struct _SyncStatusDir { GHashTable *dirents; /* name -> dirent. */ }; typedef struct _SyncStatusDir SyncStatusDir; struct _SyncStatusDirent { char *name; int mode; /* Only used for directories. */ SyncStatusDir *subdir; }; typedef struct _SyncStatusDirent SyncStatusDirent; struct SyncStatusTree { SyncStatusDir *root; char *worktree; }; typedef struct SyncStatusTree SyncStatusTree; static void sync_status_dirent_free (SyncStatusDirent *dirent); static SyncStatusDir * sync_status_dir_new () { SyncStatusDir *dir = g_new0 (SyncStatusDir, 1); dir->dirents = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)sync_status_dirent_free); return dir; } static void sync_status_dir_free (SyncStatusDir *dir) { if (!dir) return; g_hash_table_destroy (dir->dirents); g_free (dir); } static SyncStatusDirent * sync_status_dirent_new (const char *name, int mode) { SyncStatusDirent *dirent = g_new0(SyncStatusDirent, 1); dirent->name = g_strdup(name); dirent->mode = mode; if (S_ISDIR(mode)) dirent->subdir = sync_status_dir_new (); return dirent; } static void sync_status_dirent_free (SyncStatusDirent *dirent) { if (!dirent) return; g_free (dirent->name); sync_status_dir_free (dirent->subdir); g_free (dirent); } SyncStatusTree * sync_status_tree_new (const char *worktree) { SyncStatusTree *tree = g_new0(SyncStatusTree, 1); tree->root = sync_status_dir_new (); tree->worktree = g_strdup (worktree); return tree; } #if 0 #ifdef WIN32 static void refresh_recursive (const char *basedir, SyncStatusDir *dir) { GHashTableIter iter; gpointer key, value; char *dname, *path; SyncStatusDirent *dirent; g_hash_table_iter_init (&iter, dir->dirents); while (g_hash_table_iter_next (&iter, &key, &value)) { dname = key; dirent = value; path = g_strconcat(basedir, "/", dname, NULL); seaf_sync_manager_add_refresh_path (seaf->sync_mgr, path); if (S_ISDIR(dirent->mode)) refresh_recursive (path, dirent->subdir); g_free (path); } } #endif #endif /* 0 */ void sync_status_tree_free (struct SyncStatusTree *tree) { if (!tree) return; /* Free the tree recursively. */ sync_status_dir_free (tree->root); g_free (tree->worktree); g_free (tree); } void sync_status_tree_add (SyncStatusTree *tree, const char *path, int mode) { char **dnames = NULL; guint n, i; char *dname; SyncStatusDir *dir = tree->root; SyncStatusDirent *dirent; GString *buf; dnames = g_strsplit (path, "/", 0); if (!dnames) return; n = g_strv_length (dnames); buf = g_string_new (""); g_string_append (buf, tree->worktree); for (i = 0; i < n; i++) { dname = dnames[i]; dirent = g_hash_table_lookup (dir->dirents, dname); g_string_append (buf, "/"); g_string_append (buf, dname); if (dirent) { if (S_ISDIR(dirent->mode)) { if (i == (n-1)) { goto out; } else { dir = dirent->subdir; } } else { goto out; } } else { if (i == (n-1)) { dirent = sync_status_dirent_new (dname, mode); g_hash_table_insert (dir->dirents, g_strdup(dname), dirent); } else { dirent = sync_status_dirent_new (dname, S_IFDIR); g_hash_table_insert (dir->dirents, g_strdup(dname), dirent); dir = dirent->subdir; } } } out: g_string_free (buf, TRUE); g_strfreev (dnames); } inline static gboolean is_empty_dir (SyncStatusDirent *dirent) { return (g_hash_table_size(dirent->subdir->dirents) == 0); } static void remove_item (SyncStatusDir *dir, const char *dname, const char *fullpath) { g_hash_table_remove (dir->dirents, dname); } static void delete_recursive (SyncStatusDir *dir, char **dnames, guint n, guint i, const char *base) { char *dname; SyncStatusDirent *dirent; char *fullpath = NULL; dname = dnames[i]; fullpath = g_strconcat (base, "/", dname, NULL); dirent = g_hash_table_lookup (dir->dirents, dname); if (dirent) { if (S_ISDIR(dirent->mode)) { if (i == (n-1)) { if (is_empty_dir(dirent)) remove_item (dir, dname, fullpath); } else { delete_recursive (dirent->subdir, dnames, n, ++i, fullpath); /* If this dir becomes empty after deleting the entry below, * remove the dir itself too. */ if (is_empty_dir(dirent)) remove_item (dir, dname, fullpath); } } else if (i == (n-1)) { remove_item (dir, dname, fullpath); } } g_free (fullpath); } void sync_status_tree_del (SyncStatusTree *tree, const char *path) { char **dnames = NULL; guint n; SyncStatusDir *dir = tree->root; dnames = g_strsplit (path, "/", 0); if (!dnames) return; n = g_strv_length (dnames); delete_recursive (dir, dnames, n, 0, tree->worktree); g_strfreev (dnames); } int sync_status_tree_exists (SyncStatusTree *tree, const char *path, gboolean *is_dir) { char **dnames = NULL; guint n, i; char *dname; SyncStatusDir *dir = tree->root; SyncStatusDirent *dirent; int ret = 0; dnames = g_strsplit (path, "/", 0); if (!dnames) return ret; n = g_strv_length (dnames); for (i = 0; i < n; i++) { dname = dnames[i]; dirent = g_hash_table_lookup (dir->dirents, dname); if (dirent) { if (S_ISDIR(dirent->mode)) { if (i == (n-1)) { if (is_dir) *is_dir = TRUE; ret = 1; goto out; } else { dir = dirent->subdir; } } else { if (i == (n-1)) { if (is_dir) *is_dir = FALSE; ret = 1; goto out; } else { goto out; } } } else { goto out; } } out: g_strfreev (dnames); return ret; } gboolean sync_status_tree_has_file (SyncStatusTree *tree) { if (!tree) return FALSE; return g_hash_table_size (tree->root->dirents) > 0; } seadrive-fuse-3.0.13/src/sync-status-tree.h000066400000000000000000000020571476177674700205700ustar00rootroot00000000000000#ifndef SYNC_STATUS_TREE_H #define SYNC_STATUS_TREE_H struct SyncStatusTree; struct SyncStatusTree * sync_status_tree_new (const char *worktree); void sync_status_tree_free (struct SyncStatusTree *tree); /* * Add a @path into the @tree. If any directory along the path is missing, * it will be created. If the path already exists, it won't be overwritten. */ void sync_status_tree_add (struct SyncStatusTree *tree, const char *path, int mode); /* * Delete a path from the tree. If directory becomes empty after the deletion, * it will be deleted too. All empty direcotries along the path will be deleted. */ void sync_status_tree_del (struct SyncStatusTree *tree, const char *path); int sync_status_tree_exists (struct SyncStatusTree *tree, const char *path, gboolean *is_dir); // Judge whether the tree has file, to idenify whether the repo is in related sync status gboolean sync_status_tree_has_file (struct SyncStatusTree *tree); #endif seadrive-fuse-3.0.13/src/timer.c000066400000000000000000000035101476177674700164440ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ #if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) #include #include #include #else #include #endif #ifdef __linux__ #include #endif #include "seafile-session.h" #include "utils.h" #include "timer.h" struct SeafTimer { struct event *event; struct timeval tv; TimerCB func; void *user_data; }; static void timer_callback (evutil_socket_t fd, short event, void *vtimer) { int more; struct SeafTimer *timer = vtimer; more = (*timer->func) (timer->user_data); if (more) evtimer_add (timer->event, &timer->tv); else seaf_timer_free (&timer); } void seaf_timer_free (SeafTimer **ptimer) { SeafTimer *timer; /* zero out the argument passed in */ g_return_if_fail (ptimer); timer = *ptimer; *ptimer = NULL; /* destroy the timer directly or via the command queue */ if (timer) { event_del (timer->event); event_free (timer->event); g_free (timer); } } struct timeval timeval_from_msec (uint64_t milliseconds) { struct timeval ret; const uint64_t microseconds = milliseconds * 1000; ret.tv_sec = microseconds / 1000000; ret.tv_usec = microseconds % 1000000; return ret; } SeafTimer* seaf_timer_new (TimerCB func, void *user_data, uint64_t interval_milliseconds) { SeafTimer *timer = g_new0 (SeafTimer, 1); timer->tv = timeval_from_msec (interval_milliseconds); timer->func = func; timer->user_data = user_data; timer->event = evtimer_new (seaf->ev_base, timer_callback, timer); evtimer_add (timer->event, &timer->tv); return timer; } seadrive-fuse-3.0.13/src/timer.h000066400000000000000000000013641476177674700164560ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ #ifndef SEAF_TIMER_H #define SEAF_TIMER_H /* return TRUE to reschedule the timer, return FALSE to cancle the timer */ typedef int (*TimerCB) (void *data); struct SeafTimer; typedef struct SeafTimer SeafTimer; /** * Calls timer_func(user_data) after the specified interval. * The timer is freed if timer_func returns zero. * Otherwise, it's called again after the same interval. */ SeafTimer* seaf_timer_new (TimerCB func, void *user_data, uint64_t timeout_milliseconds); /** * Frees a timer and sets the timer pointer to NULL. */ void seaf_timer_free (SeafTimer **timer); #endif seadrive-fuse-3.0.13/src/utils.c000066400000000000000000000364771476177674700165060ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ #ifdef HAVE_CONFIG_H #include #endif // HAVE_CONFIG_H #include "common.h" #include "utils.h" #include "log.h" #include #include #include #include #include #include #include #include #include #include #include #include #ifndef USE_GPL_CRYPTO #include #include #include #include #endif #include #include #include #include #include void rawdata_to_hex (const unsigned char *rawdata, char *hex_str, int n_bytes) { static const char hex[] = "0123456789abcdef"; int i; for (i = 0; i < n_bytes; i++) { unsigned int val = *rawdata++; *hex_str++ = hex[val >> 4]; *hex_str++ = hex[val & 0xf]; } *hex_str = '\0'; } static unsigned hexval(char c) { if (c >= '0' && c <= '9') return c - '0'; if (c >= 'a' && c <= 'f') return c - 'a' + 10; if (c >= 'A' && c <= 'F') return c - 'A' + 10; return ~0; } int hex_to_rawdata (const char *hex_str, unsigned char *rawdata, int n_bytes) { int i; for (i = 0; i < n_bytes; i++) { unsigned int val = (hexval(hex_str[0]) << 4) | hexval(hex_str[1]); if (val & ~0xff) return -1; *rawdata++ = val; hex_str += 2; } return 0; } int checkdir_with_mkdir (const char *pathname) { return g_mkdir_with_parents(pathname, 0755); } int seaf_stat (const char *path, SeafStat *st) { return stat (path, st); } int seaf_fstat (int fd, SeafStat *st) { return fstat (fd, st); } int seaf_set_file_time (const char *path, guint64 mtime) { struct stat st; struct utimbuf times; if (stat (path, &st) < 0) { seaf_warning ("Failed to stat %s: %s.\n", path, strerror(errno)); return -1; } times.actime = st.st_atime; times.modtime = (time_t)mtime; return utime (path, ×); } int seaf_util_unlink (const char *path) { return unlink (path); } int seaf_util_rmdir (const char *path) { return rmdir (path); } int seaf_util_mkdir (const char *path, mode_t mode) { return mkdir (path, mode); } int seaf_util_open (const char *path, int flags) { return open (path, flags); } int seaf_util_create (const char *path, int flags, mode_t mode) { return open (path, flags, mode); } int seaf_util_rename (const char *oldpath, const char *newpath) { return rename (oldpath, newpath); } gboolean seaf_util_exists (const char *path) { return (access (path, F_OK) == 0); } gint64 seaf_util_lseek (int fd, gint64 offset, int whence) { return lseek (fd, offset, whence); } gssize seaf_getxattr (const char *path, const char *name, void *value, size_t size) { #ifdef __linux__ return getxattr (path, name, value, size); #endif #ifdef __APPLE__ return getxattr (path, name, value, size, 0, 0); #endif } int seaf_setxattr (const char *path, const char *name, const void *value, size_t size) { #ifdef __linux__ return setxattr (path, name, value, size, 0); #endif #ifdef __APPLE__ return setxattr (path, name, value, size, 0, 0); #endif } int seaf_removexattr (const char *path, const char *name) { #ifdef __APPLE__ return removexattr (path, name, 0); #else return removexattr (path, name); #endif } int seaf_truncate (const char *path, gint64 length) { return truncate (path, (off_t)length); } int seaf_rm_recursive (const char *path) { SeafStat st; int ret = 0; GDir *dir; const char *dname; char *subpath; GError *error = NULL; if (seaf_stat (path, &st) < 0) { seaf_warning ("Failed to stat %s: %s\n", path, strerror(errno)); return -1; } if (S_ISREG(st.st_mode)) { ret = seaf_util_unlink (path); return ret; } else if (S_ISDIR (st.st_mode)) { dir = g_dir_open (path, 0, &error); if (error) { seaf_warning ("Failed to open dir %s: %s\n", path, error->message); return -1; } while ((dname = g_dir_read_name (dir)) != NULL) { subpath = g_build_filename (path, dname, NULL); ret = seaf_rm_recursive (subpath); g_free (subpath); if (ret < 0) break; } g_dir_close (dir); if (ret == 0) ret = seaf_util_rmdir (path); return ret; } return ret; } gssize /* Read "n" bytes from a descriptor. */ readn(int fd, void *vptr, size_t n) { size_t nleft; gssize nread; char *ptr; ptr = vptr; nleft = n; while (nleft > 0) { if ( (nread = read(fd, ptr, nleft)) < 0) { if (errno == EINTR) nread = 0; /* and call read() again */ else return(-1); } else if (nread == 0) break; /* EOF */ nleft -= nread; ptr += nread; } return(n - nleft); /* return >= 0 */ } gssize /* Write "n" bytes to a descriptor. */ writen(int fd, const void *vptr, size_t n) { size_t nleft; gssize nwritten; const char *ptr; ptr = vptr; nleft = n; while (nleft > 0) { if ( (nwritten = write(fd, ptr, nleft)) <= 0) { if (nwritten < 0 && errno == EINTR) nwritten = 0; /* and call write() again */ else return(-1); /* error */ } nleft -= nwritten; ptr += nwritten; } return(n); } gssize /* Read "n" bytes from a descriptor. */ recvn(evutil_socket_t fd, void *vptr, size_t n) { size_t nleft; gssize nread; char *ptr; ptr = vptr; nleft = n; while (nleft > 0) { if ( (nread = read(fd, ptr, nleft)) < 0) { if (errno == EINTR) { nread = 0; /* and call read() again */ } else { return(-1); } } else if (nread == 0) break; /* EOF */ nleft -= nread; ptr += nread; } return(n - nleft); /* return >= 0 */ } gssize /* Write "n" bytes to a descriptor. */ sendn(evutil_socket_t fd, const void *vptr, size_t n) { size_t nleft; gssize nwritten; const char *ptr; ptr = vptr; nleft = n; while (nleft > 0) { if ( (nwritten = write(fd, ptr, nleft)) <= 0) { if (nwritten < 0 && errno == EINTR) { nwritten = 0; /* and call write() again */ } else { return -1; } } nleft -= nwritten; ptr += nwritten; } return(n); } int seaf_pipe (seaf_pipe_t handles[2]) { return pipe (handles); } int seaf_pipe_read (seaf_pipe_t fd, char *buf, int len) { return read (fd, buf, len); } int seaf_pipe_write (seaf_pipe_t fd, const char *buf, int len) { return write (fd, buf, len); } int seaf_pipe_close (seaf_pipe_t fd) { return close (fd); } gssize seaf_pipe_readn (seaf_pipe_t fd, void *vptr, size_t n) { return readn (fd, vptr, n); } gssize seaf_pipe_writen (seaf_pipe_t fd, const void *vptr, size_t n) { return writen (fd, vptr, n); } void seaf_sleep (unsigned int seconds) { sleep (seconds); } char* ccnet_expand_path (const char *src) { const char *next_in, *ntoken; char new_path[SEAF_PATH_MAX + 1]; char *next_out; int len; /* special cases */ if (!src || *src == '\0') return NULL; if (strlen(src) > SEAF_PATH_MAX) return NULL; next_in = src; next_out = new_path; *next_out = '\0'; if (*src == '~') { /* handle src start with '~' or '~' like '~plt' */ struct passwd *pw = NULL; for ( ; *next_in != '/' && *next_in != '\0'; next_in++) ; len = next_in - src; if (len == 1) { pw = getpwuid (geteuid()); } else { /* copy '~' to new_path */ memcpy (new_path, src, len); new_path[len] = '\0'; pw = getpwnam (new_path + 1); } if (pw == NULL) return NULL; len = strlen (pw->pw_dir); memcpy (new_path, pw->pw_dir, len); next_out = new_path + len; *next_out = '\0'; if (*next_in == '\0') return strdup (new_path); } else if (*src != '/') { getcwd (new_path, SEAF_PATH_MAX); for ( ; *next_out; next_out++) ; /* to '\0' */ } while (*next_in != '\0') { /* move ntoken to the next not '/' char */ for (ntoken = next_in; *ntoken == '/'; ntoken++) ; for (next_in = ntoken; *next_in != '/' && *next_in != '\0'; next_in++) ; len = next_in - ntoken; if (len == 0) { /* the path ends with '/', keep it */ *next_out++ = '/'; *next_out = '\0'; break; } if (len == 2 && ntoken[0] == '.' && ntoken[1] == '.') { /* '..' */ for (; next_out > new_path && *next_out != '/'; next_out--) ; *next_out = '\0'; } else if (ntoken[0] != '.' || len != 1) { /* not '.' */ *next_out++ = '/'; memcpy (next_out, ntoken, len); next_out += len; *next_out = '\0'; } } /* the final special case */ if (new_path[0] == '\0') { new_path[0] = '/'; new_path[1] = '\0'; } return strdup (new_path); } int calculate_sha1 (unsigned char *sha1, const char *msg, int len) { GChecksum *c; gsize cs_len = 20; if (len < 0) len = strlen(msg); c = g_checksum_new (G_CHECKSUM_SHA1); g_checksum_update(c, (const unsigned char *)msg, len); g_checksum_get_digest (c, sha1, &cs_len); g_checksum_free (c); return 0; } uint32_t ccnet_sha1_hash (const void *v) { /* 31 bit hash function */ const unsigned char *p = v; uint32_t h = 0; int i; for (i = 0; i < 20; i++) h = (h << 5) - h + p[i]; return h; } int ccnet_sha1_equal (const void *v1, const void *v2) { const unsigned char *p1 = v1; const unsigned char *p2 = v2; int i; for (i = 0; i < 20; i++) if (p1[i] != p2[i]) return 0; return 1; } char* gen_uuid () { char *uuid_str = g_malloc (37); uuid_t uuid; uuid_generate (uuid); uuid_unparse_lower (uuid, uuid_str); return uuid_str; } void gen_uuid_inplace (char *buf) { uuid_t uuid; uuid_generate (uuid); uuid_unparse_lower (uuid, buf); } gboolean is_uuid_valid (const char *uuid_str) { uuid_t uuid; if (!uuid_str) return FALSE; if (uuid_parse (uuid_str, uuid) < 0) return FALSE; return TRUE; } gboolean is_object_id_valid (const char *obj_id) { if (!obj_id) return FALSE; int len = strlen(obj_id); int i; char c; if (len != 40) return FALSE; for (i = 0; i < len; ++i) { c = obj_id[i]; if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')) continue; return FALSE; } return TRUE; } void string_list_free (GList *str_list) { GList *ptr = str_list; while (ptr) { g_free (ptr->data); ptr = ptr->next; } g_list_free (str_list); } /* JSON related utils. For compatibility with json-glib. */ const char * json_object_get_string_member (json_t *object, const char *key) { json_t *string = json_object_get (object, key); if (!string) return NULL; return json_string_value (string); } gboolean json_object_has_member (json_t *object, const char *key) { return (json_object_get (object, key) != NULL); } gint64 json_object_get_int_member (json_t *object, const char *key) { json_t *integer = json_object_get (object, key); return json_integer_value (integer); } void json_object_set_string_member (json_t *object, const char *key, const char *value) { json_object_set_new (object, key, json_string (value)); } void json_object_set_int_member (json_t *object, const char *key, gint64 value) { json_object_set_new (object, key, json_integer (value)); } void clean_utf8_data (char *data, int len) { const char *s, *e; char *p; gboolean is_valid; s = data; p = data; while ((s - data) != len) { is_valid = g_utf8_validate (s, len - (s - data), &e); if (is_valid) break; if (s != e) p += (e - s); *p = '?'; ++p; s = e + 1; } } /* zlib related wrapper functions. */ #define ZLIB_BUF_SIZE 16384 int seaf_compress (guint8 *input, int inlen, guint8 **output, int *outlen) { int ret; unsigned have; z_stream strm; guint8 out[ZLIB_BUF_SIZE]; GByteArray *barray; if (inlen == 0) return -1; /* allocate deflate state */ strm.zalloc = Z_NULL; strm.zfree = Z_NULL; strm.opaque = Z_NULL; ret = deflateInit(&strm, Z_DEFAULT_COMPRESSION); if (ret != Z_OK) { seaf_warning ("deflateInit failed.\n"); return -1; } strm.avail_in = inlen; strm.next_in = input; barray = g_byte_array_new (); do { strm.avail_out = ZLIB_BUF_SIZE; strm.next_out = out; ret = deflate(&strm, Z_FINISH); /* no bad return value */ have = ZLIB_BUF_SIZE - strm.avail_out; g_byte_array_append (barray, out, have); } while (ret != Z_STREAM_END); *outlen = barray->len; *output = g_byte_array_free (barray, FALSE); /* clean up and return */ (void)deflateEnd(&strm); return 0; } int seaf_decompress (guint8 *input, int inlen, guint8 **output, int *outlen) { int ret; unsigned have; z_stream strm; unsigned char out[ZLIB_BUF_SIZE]; GByteArray *barray; if (inlen == 0) { seaf_warning ("Empty input for zlib, invalid.\n"); return -1; } /* allocate inflate state */ strm.zalloc = Z_NULL; strm.zfree = Z_NULL; strm.opaque = Z_NULL; strm.avail_in = 0; strm.next_in = Z_NULL; ret = inflateInit(&strm); if (ret != Z_OK) { seaf_warning ("inflateInit failed.\n"); return -1; } strm.avail_in = inlen; strm.next_in = input; barray = g_byte_array_new (); do { strm.avail_out = ZLIB_BUF_SIZE; strm.next_out = out; ret = inflate(&strm, Z_NO_FLUSH); if (ret < 0) { seaf_warning ("Failed to inflate.\n"); goto out; } have = ZLIB_BUF_SIZE - strm.avail_out; g_byte_array_append (barray, out, have); } while (ret != Z_STREAM_END); out: /* clean up and return */ (void)inflateEnd(&strm); if (ret == Z_STREAM_END) { *outlen = barray->len; *output = g_byte_array_free (barray, FALSE); return 0; } else { g_byte_array_free (barray, TRUE); return -1; } } char* format_path (const char *path) { int path_len = strlen (path); char *rpath; if (path[0] == '/') { rpath = g_strdup (path + 1); path_len--; } else { rpath = g_strdup (path); } while (path_len > 1 && rpath[path_len-1] == '/') { rpath[path_len-1] = '\0'; path_len--; } return rpath; } char * parse_fileserver_addr (const char *server_addr) { char *location = strstr(server_addr, "//"); if (!location) return NULL; location += 2; char *sep = strchr (location, '/'); if (!sep) { return g_strdup(server_addr); } else { return g_strndup (server_addr, sep - server_addr); } } seadrive-fuse-3.0.13/src/utils.h000066400000000000000000000130161476177674700164730ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ #ifndef CCNET_UTILS_H #define CCNET_UTILS_H #include #include #include #include #include #include #include #include #include #if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) #include #else #include #endif #ifdef __linux__ #include #endif #ifdef __OpenBSD__ #include #endif #define SeafStat struct stat int seaf_stat (const char *path, SeafStat *st); int seaf_fstat (int fd, SeafStat *st); int seaf_set_file_time (const char *path, guint64 mtime); int seaf_util_unlink (const char *path); int seaf_util_rmdir (const char *path); int seaf_util_mkdir (const char *path, mode_t mode); int seaf_util_open (const char *path, int flags); int seaf_util_create (const char *path, int flags, mode_t mode); int seaf_util_rename (const char *oldpath, const char *newpath); gboolean seaf_util_exists (const char *path); gint64 seaf_util_lseek (int fd, gint64 offset, int whence); gssize seaf_getxattr (const char *path, const char *name, void *value, size_t size); int seaf_setxattr (const char *path, const char *name, const void *value, size_t size); int seaf_removexattr (const char *path, const char *name); int seaf_truncate (const char *path, gint64 length); int seaf_rm_recursive (const char *path); #ifndef O_BINARY #define O_BINARY 0 #endif /* Read "n" bytes from a descriptor. */ gssize readn(int fd, void *vptr, size_t n); gssize writen(int fd, const void *vptr, size_t n); /* Read "n" bytes from a socket. */ gssize recvn(evutil_socket_t fd, void *vptr, size_t n); gssize sendn(evutil_socket_t fd, const void *vptr, size_t n); #define seaf_pipe_t int int seaf_pipe (seaf_pipe_t handles[2]); int seaf_pipe_read (seaf_pipe_t fd, char *buf, int len); int seaf_pipe_write (seaf_pipe_t fd, const char *buf, int len); int seaf_pipe_close (seaf_pipe_t fd); gssize seaf_pipe_readn (seaf_pipe_t fd, void *vptr, size_t n); gssize seaf_pipe_writen (seaf_pipe_t fd, const void *vptr, size_t n); void seaf_sleep (unsigned int seconds); void rawdata_to_hex (const unsigned char *rawdata, char *hex_str, int n_bytes); int hex_to_rawdata (const char *hex_str, unsigned char *rawdata, int n_bytes); #define sha1_to_hex(sha1, hex) rawdata_to_hex((sha1), (hex), 20) #define hex_to_sha1(hex, sha1) hex_to_rawdata((hex), (sha1), 20) /* If msg is NULL-terminated, set len to -1 */ int calculate_sha1 (unsigned char *sha1, const char *msg, int len); int ccnet_sha1_equal (const void *v1, const void *v2); unsigned int ccnet_sha1_hash (const void *v); char* gen_uuid (); void gen_uuid_inplace (char *buf); gboolean is_uuid_valid (const char *uuid_str); gboolean is_object_id_valid (const char *obj_id); /* dir operations */ int checkdir_with_mkdir (const char *path); char* ccnet_expand_path (const char *src); void string_list_free (GList *str_list); /* * Utility functions for converting data to/from network byte order. */ static inline uint64_t bswap64 (uint64_t val) { uint64_t ret; uint8_t *ptr = (uint8_t *)&ret; ptr[0]=((val)>>56)&0xFF; ptr[1]=((val)>>48)&0xFF; ptr[2]=((val)>>40)&0xFF; ptr[3]=((val)>>32)&0xFF; ptr[4]=((val)>>24)&0xFF; ptr[5]=((val)>>16)&0xFF; ptr[6]=((val)>>8)&0xFF; ptr[7]=(val)&0xFF; return ret; } static inline uint64_t hton64(uint64_t val) { #if __BYTE_ORDER == __LITTLE_ENDIAN || defined __APPLE__ return bswap64 (val); #else return val; #endif } static inline uint64_t ntoh64(uint64_t val) { #if __BYTE_ORDER == __LITTLE_ENDIAN || defined __APPLE__ return bswap64 (val); #else return val; #endif } static inline void put64bit(uint8_t **ptr,uint64_t val) { uint64_t val_n = hton64 (val); *((uint64_t *)(*ptr)) = val_n; (*ptr)+=8; } static inline void put32bit(uint8_t **ptr,uint32_t val) { uint32_t val_n = htonl (val); *((uint32_t *)(*ptr)) = val_n; (*ptr)+=4; } static inline void put16bit(uint8_t **ptr,uint16_t val) { uint16_t val_n = htons (val); *((uint16_t *)(*ptr)) = val_n; (*ptr)+=2; } static inline uint64_t get64bit(const uint8_t **ptr) { uint64_t val_h = ntoh64 (*((uint64_t *)(*ptr))); (*ptr)+=8; return val_h; } static inline uint32_t get32bit(const uint8_t **ptr) { uint32_t val_h = ntohl (*((uint32_t *)(*ptr))); (*ptr)+=4; return val_h; } static inline uint16_t get16bit(const uint8_t **ptr) { uint16_t val_h = ntohs (*((uint16_t *)(*ptr))); (*ptr)+=2; return val_h; } #include const char * json_object_get_string_member (json_t *object, const char *key); gboolean json_object_has_member (json_t *object, const char *key); gint64 json_object_get_int_member (json_t *object, const char *key); void json_object_set_string_member (json_t *object, const char *key, const char *value); void json_object_set_int_member (json_t *object, const char *key, gint64 value); /* Replace invalid UTF-8 bytes with '?' */ void clean_utf8_data (char *data, int len); /* zlib related functions. */ int seaf_compress (guint8 *input, int inlen, guint8 **output, int *outlen); int seaf_decompress (guint8 *input, int inlen, guint8 **output, int *outlen); char* format_path (const char *path); #define mk_permissions(mode) (((mode) & 0100) ? 0755 : 0644) static inline unsigned int create_mode(unsigned int mode) { if (S_ISDIR(mode)) return S_IFDIR; return S_IFREG | mk_permissions(mode); } char * parse_fileserver_addr (const char *server_addr); #endif