├── .gitignore ├── COPYING ├── Makefile ├── README.md ├── fuse-grootfs.c ├── groot-ns.c ├── groot-ns.h ├── groot-preload.c ├── groot.c ├── grootfs.c ├── grootfs.h ├── utils.c └── utils.h /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | groot 3 | fuse-grootfs 4 | libgroot.so 5 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | GNU LIBRARY GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1991 Free Software Foundation, Inc. 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | [This is the first released version of the library GPL. It is 10 | numbered 2 because it goes with version 2 of the ordinary GPL.] 11 | 12 | Preamble 13 | 14 | The licenses for most software are designed to take away your 15 | freedom to share and change it. By contrast, the GNU General Public 16 | Licenses are intended to guarantee your freedom to share and change 17 | free software--to make sure the software is free for all its users. 18 | 19 | This license, the Library General Public License, applies to some 20 | specially designated Free Software Foundation software, and to any 21 | other libraries whose authors decide to use it. You can use it for 22 | your libraries, too. 23 | 24 | When we speak of free software, we are referring to freedom, not 25 | price. Our General Public Licenses are designed to make sure that you 26 | have the freedom to distribute copies of free software (and charge for 27 | this service if you wish), that you receive source code or can get it 28 | if you want it, that you can change the software or use pieces of it 29 | in new free programs; and that you know you can do these things. 30 | 31 | To protect your rights, we need to make restrictions that forbid 32 | anyone to deny you these rights or to ask you to surrender the rights. 33 | These restrictions translate to certain responsibilities for you if 34 | you distribute copies of the library, or if you modify it. 35 | 36 | For example, if you distribute copies of the library, whether gratis 37 | or for a fee, you must give the recipients all the rights that we gave 38 | you. You must make sure that they, too, receive or can get the source 39 | code. If you link a program with the library, you must provide 40 | complete object files to the recipients so that they can relink them 41 | with the library, after making changes to the library and recompiling 42 | it. And you must show them these terms so they know their rights. 43 | 44 | Our method of protecting your rights has two steps: (1) copyright 45 | the library, and (2) offer you this license which gives you legal 46 | permission to copy, distribute and/or modify the library. 47 | 48 | Also, for each distributor's protection, we want to make certain 49 | that everyone understands that there is no warranty for this free 50 | library. If the library is modified by someone else and passed on, we 51 | want its recipients to know that what they have is not the original 52 | version, so that any problems introduced by others will not reflect on 53 | the original authors' reputations. 54 | 55 | Finally, any free program is threatened constantly by software 56 | patents. We wish to avoid the danger that companies distributing free 57 | software will individually obtain patent licenses, thus in effect 58 | transforming the program into proprietary software. To prevent this, 59 | we have made it clear that any patent must be licensed for everyone's 60 | free use or not licensed at all. 61 | 62 | Most GNU software, including some libraries, is covered by the ordinary 63 | GNU General Public License, which was designed for utility programs. This 64 | license, the GNU Library General Public License, applies to certain 65 | designated libraries. This license is quite different from the ordinary 66 | one; be sure to read it in full, and don't assume that anything in it is 67 | the same as in the ordinary license. 68 | 69 | The reason we have a separate public license for some libraries is that 70 | they blur the distinction we usually make between modifying or adding to a 71 | program and simply using it. Linking a program with a library, without 72 | changing the library, is in some sense simply using the library, and is 73 | analogous to running a utility program or application program. However, in 74 | a textual and legal sense, the linked executable is a combined work, a 75 | derivative of the original library, and the ordinary General Public License 76 | treats it as such. 77 | 78 | Because of this blurred distinction, using the ordinary General 79 | Public License for libraries did not effectively promote software 80 | sharing, because most developers did not use the libraries. We 81 | concluded that weaker conditions might promote sharing better. 82 | 83 | However, unrestricted linking of non-free programs would deprive the 84 | users of those programs of all benefit from the free status of the 85 | libraries themselves. This Library General Public License is intended to 86 | permit developers of non-free programs to use free libraries, while 87 | preserving your freedom as a user of such programs to change the free 88 | libraries that are incorporated in them. (We have not seen how to achieve 89 | this as regards changes in header files, but we have achieved it as regards 90 | changes in the actual functions of the Library.) The hope is that this 91 | will lead to faster development of free libraries. 92 | 93 | The precise terms and conditions for copying, distribution and 94 | modification follow. Pay close attention to the difference between a 95 | "work based on the library" and a "work that uses the library". The 96 | former contains code derived from the library, while the latter only 97 | works together with the library. 98 | 99 | Note that it is possible for a library to be covered by the ordinary 100 | General Public License rather than by this special one. 101 | 102 | GNU LIBRARY GENERAL PUBLIC LICENSE 103 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 104 | 105 | 0. This License Agreement applies to any software library which 106 | contains a notice placed by the copyright holder or other authorized 107 | party saying it may be distributed under the terms of this Library 108 | General Public License (also called "this License"). Each licensee is 109 | addressed as "you". 110 | 111 | A "library" means a collection of software functions and/or data 112 | prepared so as to be conveniently linked with application programs 113 | (which use some of those functions and data) to form executables. 114 | 115 | The "Library", below, refers to any such software library or work 116 | which has been distributed under these terms. A "work based on the 117 | Library" means either the Library or any derivative work under 118 | copyright law: that is to say, a work containing the Library or a 119 | portion of it, either verbatim or with modifications and/or translated 120 | straightforwardly into another language. (Hereinafter, translation is 121 | included without limitation in the term "modification".) 122 | 123 | "Source code" for a work means the preferred form of the work for 124 | making modifications to it. For a library, complete source code means 125 | all the source code for all modules it contains, plus any associated 126 | interface definition files, plus the scripts used to control compilation 127 | and installation of the library. 128 | 129 | Activities other than copying, distribution and modification are not 130 | covered by this License; they are outside its scope. The act of 131 | running a program using the Library is not restricted, and output from 132 | such a program is covered only if its contents constitute a work based 133 | on the Library (independent of the use of the Library in a tool for 134 | writing it). Whether that is true depends on what the Library does 135 | and what the program that uses the Library does. 136 | 137 | 1. You may copy and distribute verbatim copies of the Library's 138 | complete source code as you receive it, in any medium, provided that 139 | you conspicuously and appropriately publish on each copy an 140 | appropriate copyright notice and disclaimer of warranty; keep intact 141 | all the notices that refer to this License and to the absence of any 142 | warranty; and distribute a copy of this License along with the 143 | Library. 144 | 145 | You may charge a fee for the physical act of transferring a copy, 146 | and you may at your option offer warranty protection in exchange for a 147 | fee. 148 | 149 | 2. You may modify your copy or copies of the Library or any portion 150 | of it, thus forming a work based on the Library, and copy and 151 | distribute such modifications or work under the terms of Section 1 152 | above, provided that you also meet all of these conditions: 153 | 154 | a) The modified work must itself be a software library. 155 | 156 | b) You must cause the files modified to carry prominent notices 157 | stating that you changed the files and the date of any change. 158 | 159 | c) You must cause the whole of the work to be licensed at no 160 | charge to all third parties under the terms of this License. 161 | 162 | d) If a facility in the modified Library refers to a function or a 163 | table of data to be supplied by an application program that uses 164 | the facility, other than as an argument passed when the facility 165 | is invoked, then you must make a good faith effort to ensure that, 166 | in the event an application does not supply such function or 167 | table, the facility still operates, and performs whatever part of 168 | its purpose remains meaningful. 169 | 170 | (For example, a function in a library to compute square roots has 171 | a purpose that is entirely well-defined independent of the 172 | application. Therefore, Subsection 2d requires that any 173 | application-supplied function or table used by this function must 174 | be optional: if the application does not supply it, the square 175 | root function must still compute square roots.) 176 | 177 | These requirements apply to the modified work as a whole. If 178 | identifiable sections of that work are not derived from the Library, 179 | and can be reasonably considered independent and separate works in 180 | themselves, then this License, and its terms, do not apply to those 181 | sections when you distribute them as separate works. But when you 182 | distribute the same sections as part of a whole which is a work based 183 | on the Library, the distribution of the whole must be on the terms of 184 | this License, whose permissions for other licensees extend to the 185 | entire whole, and thus to each and every part regardless of who wrote 186 | it. 187 | 188 | Thus, it is not the intent of this section to claim rights or contest 189 | your rights to work written entirely by you; rather, the intent is to 190 | exercise the right to control the distribution of derivative or 191 | collective works based on the Library. 192 | 193 | In addition, mere aggregation of another work not based on the Library 194 | with the Library (or with a work based on the Library) on a volume of 195 | a storage or distribution medium does not bring the other work under 196 | the scope of this License. 197 | 198 | 3. You may opt to apply the terms of the ordinary GNU General Public 199 | License instead of this License to a given copy of the Library. To do 200 | this, you must alter all the notices that refer to this License, so 201 | that they refer to the ordinary GNU General Public License, version 2, 202 | instead of to this License. (If a newer version than version 2 of the 203 | ordinary GNU General Public License has appeared, then you can specify 204 | that version instead if you wish.) Do not make any other change in 205 | these notices. 206 | 207 | Once this change is made in a given copy, it is irreversible for 208 | that copy, so the ordinary GNU General Public License applies to all 209 | subsequent copies and derivative works made from that copy. 210 | 211 | This option is useful when you wish to copy part of the code of 212 | the Library into a program that is not a library. 213 | 214 | 4. You may copy and distribute the Library (or a portion or 215 | derivative of it, under Section 2) in object code or executable form 216 | under the terms of Sections 1 and 2 above provided that you accompany 217 | it with the complete corresponding machine-readable source code, which 218 | must be distributed under the terms of Sections 1 and 2 above on a 219 | medium customarily used for software interchange. 220 | 221 | If distribution of object code is made by offering access to copy 222 | from a designated place, then offering equivalent access to copy the 223 | source code from the same place satisfies the requirement to 224 | distribute the source code, even though third parties are not 225 | compelled to copy the source along with the object code. 226 | 227 | 5. A program that contains no derivative of any portion of the 228 | Library, but is designed to work with the Library by being compiled or 229 | linked with it, is called a "work that uses the Library". Such a 230 | work, in isolation, is not a derivative work of the Library, and 231 | therefore falls outside the scope of this License. 232 | 233 | However, linking a "work that uses the Library" with the Library 234 | creates an executable that is a derivative of the Library (because it 235 | contains portions of the Library), rather than a "work that uses the 236 | library". The executable is therefore covered by this License. 237 | Section 6 states terms for distribution of such executables. 238 | 239 | When a "work that uses the Library" uses material from a header file 240 | that is part of the Library, the object code for the work may be a 241 | derivative work of the Library even though the source code is not. 242 | Whether this is true is especially significant if the work can be 243 | linked without the Library, or if the work is itself a library. The 244 | threshold for this to be true is not precisely defined by law. 245 | 246 | If such an object file uses only numerical parameters, data 247 | structure layouts and accessors, and small macros and small inline 248 | functions (ten lines or less in length), then the use of the object 249 | file is unrestricted, regardless of whether it is legally a derivative 250 | work. (Executables containing this object code plus portions of the 251 | Library will still fall under Section 6.) 252 | 253 | Otherwise, if the work is a derivative of the Library, you may 254 | distribute the object code for the work under the terms of Section 6. 255 | Any executables containing that work also fall under Section 6, 256 | whether or not they are linked directly with the Library itself. 257 | 258 | 6. As an exception to the Sections above, you may also compile or 259 | link a "work that uses the Library" with the Library to produce a 260 | work containing portions of the Library, and distribute that work 261 | under terms of your choice, provided that the terms permit 262 | modification of the work for the customer's own use and reverse 263 | engineering for debugging such modifications. 264 | 265 | You must give prominent notice with each copy of the work that the 266 | Library is used in it and that the Library and its use are covered by 267 | this License. You must supply a copy of this License. If the work 268 | during execution displays copyright notices, you must include the 269 | copyright notice for the Library among them, as well as a reference 270 | directing the user to the copy of this License. Also, you must do one 271 | of these things: 272 | 273 | a) Accompany the work with the complete corresponding 274 | machine-readable source code for the Library including whatever 275 | changes were used in the work (which must be distributed under 276 | Sections 1 and 2 above); and, if the work is an executable linked 277 | with the Library, with the complete machine-readable "work that 278 | uses the Library", as object code and/or source code, so that the 279 | user can modify the Library and then relink to produce a modified 280 | executable containing the modified Library. (It is understood 281 | that the user who changes the contents of definitions files in the 282 | Library will not necessarily be able to recompile the application 283 | to use the modified definitions.) 284 | 285 | b) Accompany the work with a written offer, valid for at 286 | least three years, to give the same user the materials 287 | specified in Subsection 6a, above, for a charge no more 288 | than the cost of performing this distribution. 289 | 290 | c) If distribution of the work is made by offering access to copy 291 | from a designated place, offer equivalent access to copy the above 292 | specified materials from the same place. 293 | 294 | d) Verify that the user has already received a copy of these 295 | materials or that you have already sent this user a copy. 296 | 297 | For an executable, the required form of the "work that uses the 298 | Library" must include any data and utility programs needed for 299 | reproducing the executable from it. However, as a special exception, 300 | the source code distributed need not include anything that is normally 301 | distributed (in either source or binary form) with the major 302 | components (compiler, kernel, and so on) of the operating system on 303 | which the executable runs, unless that component itself accompanies 304 | the executable. 305 | 306 | It may happen that this requirement contradicts the license 307 | restrictions of other proprietary libraries that do not normally 308 | accompany the operating system. Such a contradiction means you cannot 309 | use both them and the Library together in an executable that you 310 | distribute. 311 | 312 | 7. You may place library facilities that are a work based on the 313 | Library side-by-side in a single library together with other library 314 | facilities not covered by this License, and distribute such a combined 315 | library, provided that the separate distribution of the work based on 316 | the Library and of the other library facilities is otherwise 317 | permitted, and provided that you do these two things: 318 | 319 | a) Accompany the combined library with a copy of the same work 320 | based on the Library, uncombined with any other library 321 | facilities. This must be distributed under the terms of the 322 | Sections above. 323 | 324 | b) Give prominent notice with the combined library of the fact 325 | that part of it is a work based on the Library, and explaining 326 | where to find the accompanying uncombined form of the same work. 327 | 328 | 8. You may not copy, modify, sublicense, link with, or distribute 329 | the Library except as expressly provided under this License. Any 330 | attempt otherwise to copy, modify, sublicense, link with, or 331 | distribute the Library is void, and will automatically terminate your 332 | rights under this License. However, parties who have received copies, 333 | or rights, from you under this License will not have their licenses 334 | terminated so long as such parties remain in full compliance. 335 | 336 | 9. You are not required to accept this License, since you have not 337 | signed it. However, nothing else grants you permission to modify or 338 | distribute the Library or its derivative works. These actions are 339 | prohibited by law if you do not accept this License. Therefore, by 340 | modifying or distributing the Library (or any work based on the 341 | Library), you indicate your acceptance of this License to do so, and 342 | all its terms and conditions for copying, distributing or modifying 343 | the Library or works based on it. 344 | 345 | 10. Each time you redistribute the Library (or any work based on the 346 | Library), the recipient automatically receives a license from the 347 | original licensor to copy, distribute, link with or modify the Library 348 | subject to these terms and conditions. You may not impose any further 349 | restrictions on the recipients' exercise of the rights granted herein. 350 | You are not responsible for enforcing compliance by third parties to 351 | this License. 352 | 353 | 11. If, as a consequence of a court judgment or allegation of patent 354 | infringement or for any other reason (not limited to patent issues), 355 | conditions are imposed on you (whether by court order, agreement or 356 | otherwise) that contradict the conditions of this License, they do not 357 | excuse you from the conditions of this License. If you cannot 358 | distribute so as to satisfy simultaneously your obligations under this 359 | License and any other pertinent obligations, then as a consequence you 360 | may not distribute the Library at all. For example, if a patent 361 | license would not permit royalty-free redistribution of the Library by 362 | all those who receive copies directly or indirectly through you, then 363 | the only way you could satisfy both it and this License would be to 364 | refrain entirely from distribution of the Library. 365 | 366 | If any portion of this section is held invalid or unenforceable under any 367 | particular circumstance, the balance of the section is intended to apply, 368 | and the section as a whole is intended to apply in other circumstances. 369 | 370 | It is not the purpose of this section to induce you to infringe any 371 | patents or other property right claims or to contest validity of any 372 | such claims; this section has the sole purpose of protecting the 373 | integrity of the free software distribution system which is 374 | implemented by public license practices. Many people have made 375 | generous contributions to the wide range of software distributed 376 | through that system in reliance on consistent application of that 377 | system; it is up to the author/donor to decide if he or she is willing 378 | to distribute software through any other system and a licensee cannot 379 | impose that choice. 380 | 381 | This section is intended to make thoroughly clear what is believed to 382 | be a consequence of the rest of this License. 383 | 384 | 12. If the distribution and/or use of the Library is restricted in 385 | certain countries either by patents or by copyrighted interfaces, the 386 | original copyright holder who places the Library under this License may add 387 | an explicit geographical distribution limitation excluding those countries, 388 | so that distribution is permitted only in or among countries not thus 389 | excluded. In such case, this License incorporates the limitation as if 390 | written in the body of this License. 391 | 392 | 13. The Free Software Foundation may publish revised and/or new 393 | versions of the Library General Public License from time to time. 394 | Such new versions will be similar in spirit to the present version, 395 | but may differ in detail to address new problems or concerns. 396 | 397 | Each version is given a distinguishing version number. If the Library 398 | specifies a version number of this License which applies to it and 399 | "any later version", you have the option of following the terms and 400 | conditions either of that version or of any later version published by 401 | the Free Software Foundation. If the Library does not specify a 402 | license version number, you may choose any version ever published by 403 | the Free Software Foundation. 404 | 405 | 14. If you wish to incorporate parts of the Library into other free 406 | programs whose distribution conditions are incompatible with these, 407 | write to the author to ask for permission. For software which is 408 | copyrighted by the Free Software Foundation, write to the Free 409 | Software Foundation; we sometimes make exceptions for this. Our 410 | decision will be guided by the two goals of preserving the free status 411 | of all derivatives of our free software and of promoting the sharing 412 | and reuse of software generally. 413 | 414 | NO WARRANTY 415 | 416 | 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO 417 | WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. 418 | EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR 419 | OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY 420 | KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE 421 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 422 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE 423 | LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME 424 | THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 425 | 426 | 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN 427 | WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY 428 | AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU 429 | FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR 430 | CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE 431 | LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING 432 | RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A 433 | FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF 434 | SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH 435 | DAMAGES. 436 | 437 | END OF TERMS AND CONDITIONS 438 | 439 | How to Apply These Terms to Your New Libraries 440 | 441 | If you develop a new library, and you want it to be of the greatest 442 | possible use to the public, we recommend making it free software that 443 | everyone can redistribute and change. You can do so by permitting 444 | redistribution under these terms (or, alternatively, under the terms of the 445 | ordinary General Public License). 446 | 447 | To apply these terms, attach the following notices to the library. It is 448 | safest to attach them to the start of each source file to most effectively 449 | convey the exclusion of warranty; and each file should have at least the 450 | "copyright" line and a pointer to where the full notice is found. 451 | 452 | 453 | Copyright (C) 454 | 455 | This library is free software; you can redistribute it and/or 456 | modify it under the terms of the GNU Library General Public 457 | License as published by the Free Software Foundation; either 458 | version 2 of the License, or (at your option) any later version. 459 | 460 | This library is distributed in the hope that it will be useful, 461 | but WITHOUT ANY WARRANTY; without even the implied warranty of 462 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 463 | Library General Public License for more details. 464 | 465 | You should have received a copy of the GNU Library General Public 466 | License along with this library; if not, write to the Free Software 467 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 468 | 469 | Also add information on how to contact you by electronic and paper mail. 470 | 471 | You should also get your employer (if you work as a programmer) or your 472 | school, if any, to sign a "copyright disclaimer" for the library, if 473 | necessary. Here is a sample; alter the names: 474 | 475 | Yoyodyne, Inc., hereby disclaims all copyright interest in the 476 | library `Frob' (a library for tweaking knobs) written by James Random Hacker. 477 | 478 | , 1 April 1990 479 | Ty Coon, President of Vice 480 | 481 | That's all there is to it! 482 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PREFIX=/usr 2 | BINDIR = ${PREFIX}/bin 3 | LIBDIR = ${PREFIX}/lib 4 | 5 | FUSE_FLAGS=`pkg-config --cflags --libs fuse` 6 | 7 | GROOT_WARN_CFLAGS=-Wall -Werror 8 | GROOT_CFLAGS=-D_GNU_SOURCE -D_FILE_OFFSET_BITS=64 $(GROOT_WARN_CFLAGS) 9 | 10 | all: groot libgroot.so 11 | 12 | groot: groot.c grootfs.c grootfs.h groot-ns.c groot-ns.h utils.h utils.c 13 | $(CC) groot.c grootfs.c groot-ns.c utils.c \ 14 | $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $(GROOT_CFLAGS) $(FUSE_FLAGS) \ 15 | -o groot 16 | 17 | libgroot.so: groot-preload.c grootfs.c grootfs.h groot-ns.c groot-ns.h utils.h utils.c 18 | $(CC) groot-preload.c grootfs.c groot-ns.c utils.c \ 19 | $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $(GROOT_CFLAGS) $(FUSE_FLAGS) \ 20 | -fvisibility=hidden -Bsymbolic-functions -Bgroup -fPIC -shared -o libgroot.so 21 | 22 | fuse-grootfs: fuse-grootfs.c grootfs.c grootfs.h utils.h utils.c 23 | $(CC) fuse-grootfs.c grootfs.c utils.c \ 24 | $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $(GROOT_CFLAGS) $(FUSE_FLAGS) \ 25 | -o fuse-grootfs 26 | 27 | install: groot libgroot.so 28 | mkdir -p $(DESTDIR)$(BINDIR) 29 | install groot $(DESTDIR)$(BINDIR)/ 30 | mkdir -p $(DESTDIR)$(LIBDIR) 31 | install libgroot.so $(DESTDIR)$(LIBDIR)/ 32 | 33 | clean: 34 | rm -f fuse-grootfs groot libgroot.so 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Groot is a tool that uses modern kernel features to emulate running a 2 | process as root, without actually being root. It is similar to other 3 | tools like fakeroot or pseudo. 4 | 5 | It is used like sudo: 6 | 7 | ``` 8 | $ groot whoami 9 | root 10 | ``` 11 | 12 | This is achieved using unprivileged user namespaces, which maps the 13 | current users uid/gid to 0. In order to use uids other than 0 it uses 14 | newuidmap/newgidmap, which means that the user needs to have blocks 15 | allocated in `/etc/subuid` and `/etc/subgid` for non-0 uids and gids 16 | to be usable in the groot. 17 | 18 | The most common usecase for groot is to run some kind of distro 19 | installation into a chroot, and to later use the files in the chroot 20 | as a basis for an install. Normally a user cannot create files owned 21 | by other users or with special permissions, so to support this groot 22 | is able to wrap locations in the filesystem with a fuse-base 23 | filesystem that fakes permissions and ownership of the files, storing 24 | the faked metadata in xattrs on the underlying filesystem. 25 | 26 | For example, on my Fedora system I am able to create a minimal chroot 27 | and turn it into a tar like this: 28 | 29 | ``` 30 | $ mkdir rootfs 31 | $ groot -w rootfs dnf -y --releasever 33 --repo fedora --installroot=`pwd`/rootfs install bash 32 | $ groot -w rootfs dnf --installroot=`pwd`/rootfs clean all 33 | $ groot -w rootfs tar cvf rootfs.tar.gz -C rootfs . 34 | ``` 35 | 36 | This will produce a directory `rootfs` where all the files are owned by the 37 | current user, and a `rootfs.tar.gz` that records whatever the permissions 38 | were set during the actual install. 39 | -------------------------------------------------------------------------------- /fuse-grootfs.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 Alexander Larsson 3 | * 4 | * SPDX-License-Identifier: LGPL-2.0+ 5 | * 6 | * This library is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 2 of the License, or (at your option) any later version. 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this library; if not, write to the 18 | * Free Software Foundation, Inc., 59 Temple Place - Suite 330, 19 | * Boston, MA 02111-1307, USA. 20 | */ 21 | 22 | #define FUSE_USE_VERSION 26 23 | 24 | #include "utils.h" 25 | #include "grootfs.h" 26 | 27 | #include 28 | 29 | static char *base_path = NULL; 30 | 31 | enum { 32 | KEY_HELP, 33 | }; 34 | 35 | static void 36 | usage (const char *progname) 37 | { 38 | fprintf (stdout, 39 | "usage: %s basepath mountpoint [options]\n" 40 | "\n" 41 | "general options:\n" 42 | " -o opt,[opt...] mount options\n" 43 | " -h --help print help\n" 44 | "\n", progname); 45 | } 46 | 47 | static int 48 | grootfs_opt_proc (void *data, 49 | const char *arg, 50 | int key, 51 | struct fuse_args *outargs) 52 | { 53 | (void) data; 54 | 55 | switch (key) 56 | { 57 | case FUSE_OPT_KEY_NONOPT: 58 | if (base_path == NULL) 59 | { 60 | base_path = xstrdup (arg); 61 | return 0; 62 | } 63 | return 1; 64 | case FUSE_OPT_KEY_OPT: 65 | return 1; 66 | case KEY_HELP: 67 | usage (outargs->argv[0]); 68 | exit (EXIT_SUCCESS); 69 | default: 70 | fprintf (stderr, "see `%s -h' for usage\n", outargs->argv[0]); 71 | exit (EXIT_FAILURE); 72 | } 73 | return 1; 74 | } 75 | 76 | struct grootfs_config { 77 | int dummy; 78 | }; 79 | 80 | #define GROOTFS_OPT(t, p, v) { t, offsetof(struct grootfs_config, p), v } 81 | 82 | static struct fuse_opt grootfs_opts[] = { 83 | 84 | FUSE_OPT_KEY ("-h", KEY_HELP), 85 | FUSE_OPT_KEY ("--help", KEY_HELP), 86 | FUSE_OPT_END 87 | }; 88 | 89 | int 90 | main (int argc, char *argv[]) 91 | { 92 | struct fuse_args args = FUSE_ARGS_INIT (argc, argv); 93 | int res; 94 | int dirfd; 95 | struct grootfs_config conf = { 0 }; 96 | 97 | res = fuse_opt_parse (&args, &conf, grootfs_opts, grootfs_opt_proc); 98 | if (res != 0) 99 | { 100 | fprintf (stderr, "Invalid arguments\n"); 101 | fprintf (stderr, "see `%s -h' for usage\n", argv[0]); 102 | exit (EXIT_FAILURE); 103 | } 104 | 105 | if (base_path == NULL) 106 | { 107 | fprintf (stderr, "Missing basepath\n"); 108 | fprintf (stderr, "see `%s -h' for usage\n", argv[0]); 109 | exit (EXIT_FAILURE); 110 | } 111 | 112 | dirfd = openat (AT_FDCWD, base_path, O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC | O_NOCTTY); 113 | if (dirfd == -1) 114 | { 115 | perror ("opening basepath: "); 116 | exit (EXIT_FAILURE); 117 | } 118 | 119 | if (start_grootfs (args.argc, args.argv, dirfd) != 0) 120 | die ("Unable to start fuse filesystem"); 121 | 122 | return 0; 123 | } 124 | -------------------------------------------------------------------------------- /groot-ns.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 Alexander Larsson 3 | * 4 | * SPDX-License-Identifier: LGPL-2.0+ 5 | * 6 | * This library is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 2 of the License, or (at your option) any later version. 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this library; if not, write to the 18 | * Free Software Foundation, Inc., 59 Temple Place - Suite 330, 19 | * Boston, MA 02111-1307, USA. 20 | */ 21 | 22 | #include "utils.h" 23 | #include "groot-ns.h" 24 | #include "grootfs.h" 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | 38 | static void 39 | launch_newidmap (const char *bin, char **idmapping, pid_t main_pid) 40 | { 41 | pid_t pid = fork (); 42 | int status; 43 | 44 | if (pid == -1) 45 | die_with_error ("fork failed"); 46 | 47 | if (pid == 0) 48 | { 49 | autofree char **argv = xmalloc ((2 + strv_length (idmapping) + 1) * sizeof (char *)); 50 | autofree char *pid_str = xasprintf ("%ld", main_pid); 51 | size_t i; 52 | 53 | argv[0] = (char *)bin; 54 | argv[1] = pid_str; 55 | for (i = 0; idmapping[i] != NULL; i++) 56 | argv[2+i] = idmapping[i]; 57 | argv[2+i] = NULL; 58 | 59 | if (execvp (argv[0], argv) == -1) 60 | die_with_error ("exec newuidmap failed"); 61 | 62 | exit (1); 63 | } 64 | 65 | if (waitpid (pid, &status, 0) == -1) 66 | die_with_error ("waitpid failed"); 67 | 68 | if (!WIFEXITED (status)) 69 | die_with_error ("%s did not exit", bin); 70 | 71 | if (WEXITSTATUS (status) != 0) 72 | die_with_error ("%s exited with status %d", bin, WEXITSTATUS (status)); 73 | } 74 | 75 | static int 76 | double_fork_with_socket (int *socket_out) 77 | { 78 | pid_t pid, pid2; 79 | int status; 80 | int status_sockets[2] = { -1, -1 }; 81 | 82 | if (socketpair (AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, status_sockets) != 0) 83 | die_with_error ("socketpair"); 84 | 85 | pid = fork (); 86 | if (pid == -1) 87 | die_with_error ("fork failed"); 88 | 89 | if (pid != 0) 90 | { 91 | close (status_sockets[1]); 92 | 93 | waitpid (pid, &status, 0); /* Don't leave zombies */ 94 | 95 | *socket_out = status_sockets[0]; 96 | return 1; /* In parent */ 97 | } 98 | 99 | pid2 = fork (); 100 | if (pid2 == -1) 101 | die_with_error ("fork failed"); 102 | 103 | if (pid2 != 0) 104 | exit (0); 105 | 106 | if (setsid () == -1) 107 | die_with_error ("setsid"); 108 | 109 | close (status_sockets[0]); 110 | 111 | *socket_out = status_sockets[1]; 112 | return 0; /* In grandchild */ 113 | } 114 | 115 | static int 116 | start_uidmap_process (pid_t main_pid, 117 | char **uid_mapping, 118 | char **gid_mapping) 119 | { 120 | char buf = 'x'; 121 | ssize_t s; 122 | int status_socket; 123 | 124 | if (double_fork_with_socket (&status_socket) != 0) 125 | return status_socket; 126 | 127 | /* Block until namespace started */ 128 | do 129 | s = read (status_socket, &buf, 1); 130 | while (s == -1 && errno == EINTR); 131 | 132 | if (s == 1) 133 | { 134 | launch_newidmap ("newuidmap", uid_mapping, main_pid); 135 | launch_newidmap ("newgidmap", gid_mapping, main_pid); 136 | 137 | /* Signal that uidmaps are set up */ 138 | if (write (status_socket, &buf, 1) < 0) 139 | report ("Failed write to status pipe"); 140 | } 141 | 142 | exit (0); 143 | } 144 | 145 | static int 146 | start_fuse_process (const char **wrapdirs, 147 | int num_wrapdirs, 148 | long max_uid, 149 | long max_gid) 150 | { 151 | char buf = 'x'; 152 | int status_socket; 153 | int *wrapdir_fds; 154 | 155 | wrapdir_fds = xmalloc (sizeof (int) * num_wrapdirs); 156 | for (int i = 0; i < num_wrapdirs; i++) 157 | wrapdir_fds[i] = -1; 158 | 159 | /* We do the opens before forking for nicer error reporting */ 160 | for (int i = 0; i < num_wrapdirs; i++) 161 | { 162 | const char *wrapdir = wrapdirs[i]; 163 | 164 | wrapdir_fds[i] = openat (AT_FDCWD, wrapdir, O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC | O_NOCTTY); 165 | if (wrapdir_fds[i] == -1) 166 | wrapdirs[i] = NULL; /* Ensure we don't try to mount this */ 167 | } 168 | 169 | if (double_fork_with_socket (&status_socket) != 0) 170 | { 171 | /* Close dirfds in parent */ 172 | for (int i = 0; i < num_wrapdirs; i++) 173 | if (wrapdir_fds[i] != -1) 174 | close (wrapdir_fds[i]); 175 | return status_socket; 176 | } 177 | 178 | for (int i = 0; i < num_wrapdirs; i++) 179 | { 180 | const char *wrapdir = wrapdirs[i]; 181 | int wrapdir_fd = wrapdir_fds[i]; 182 | 183 | if (wrapdir_fd == -1) 184 | continue; 185 | 186 | int dev_fuse_fd = recv_fd (status_socket); 187 | if (dev_fuse_fd == -1) 188 | die_with_error ("no /dev/fuse fd recieved"); 189 | 190 | if (start_grootfs_lowlevel (wrapdir_fd, dev_fuse_fd, wrapdir, max_uid, max_gid) != 0) 191 | die ("start_grootfs_lowlevel"); 192 | } 193 | 194 | if (write (status_socket, &buf, 1) == -1) 195 | die ("fuse proc write socket_fd"); 196 | 197 | exit (0); 198 | } 199 | 200 | static void 201 | keep_caps (void) 202 | { 203 | struct __user_cap_header_struct header = { _LINUX_CAPABILITY_VERSION_3, 0 }; 204 | struct __user_cap_data_struct data[2] = {{ 0 }}; 205 | uint64_t effective, cap; 206 | 207 | if (capget (&header, data) < 0) 208 | die_with_error ("capget failed"); 209 | 210 | effective = ((uint64_t)data[1].effective << 32) | (uint64_t)data[0].effective; 211 | 212 | /* Make all caps inheritable */ 213 | data[0].inheritable = data[0].permitted; 214 | data[1].inheritable = data[1].permitted; 215 | if (capset (&header, data) < 0) 216 | die_with_error ("capset failed"); 217 | 218 | /* Make caps ambient */ 219 | for (cap = 0; cap <= CAP_LAST_CAP; cap++) 220 | { 221 | if ((effective & (1 << cap)) && 222 | prctl (PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, cap, 0, 0) != 0) 223 | { 224 | if (errno != EINVAL) 225 | die_with_error ("Adding ambient capability %ld", cap); 226 | } 227 | } 228 | } 229 | 230 | static char ** 231 | make_idmap (const char *username, const char *filename, long base_id, long *max_id) 232 | { 233 | autofree char *content = NULL; 234 | autofreev char **mapping = NULL; 235 | size_t mapping_size; 236 | long next_id; 237 | 238 | mapping_size = 3; 239 | next_id = 1; 240 | mapping = xmalloc ((mapping_size + 1) * sizeof (char *)); 241 | mapping[0] = xstrdup ("0"); 242 | mapping[1] = xasprintf("%ld", base_id); 243 | mapping[2] = xstrdup ("1"); 244 | mapping[3] = NULL; 245 | 246 | content = load_file_at (AT_FDCWD, filename); 247 | if (content) 248 | { 249 | char *line_iterator = content; 250 | 251 | while (line_iterator) 252 | { 253 | char *line = strsep (&line_iterator, "\n"); 254 | char *end, *colon; 255 | 256 | if (!has_prefix (line, username) || line[strlen (username)] != ':') 257 | continue; 258 | line = line + strlen (username) + 1; 259 | 260 | colon = strchr (line, ':'); 261 | if (colon == NULL) 262 | { 263 | report ("WARNING: Invalid format of %s", filename); 264 | continue; // Error parsing int 265 | } 266 | *colon = 0; 267 | 268 | long subid_base = strtol (line, &end, 10); 269 | if (*end != 0) 270 | { 271 | report ("WARNING: Invalid format of %s", filename); 272 | continue; // Error parsing int 273 | } 274 | 275 | long subid_count = strtol (colon + 1, &end, 10); 276 | if (*end != 0) 277 | { 278 | report ("WARNING: Invalid format of %s", filename); 279 | continue; // Error parsing int 280 | } 281 | 282 | mapping = xrealloc (mapping, (mapping_size + 3 + 1) * sizeof (char *)); 283 | mapping[mapping_size++] = xasprintf("%ld", next_id); 284 | mapping[mapping_size++] = xasprintf("%ld", subid_base); 285 | mapping[mapping_size++] = xasprintf("%ld", subid_count); 286 | mapping[mapping_size] = NULL; 287 | next_id += subid_count; 288 | } 289 | } 290 | 291 | if (next_id == 1) 292 | report ("Warning: no defined ids for user %s in %s, limited user/group support", username, filename); 293 | 294 | *max_id = next_id - 1; 295 | 296 | return steal_pointer (&mapping); 297 | } 298 | 299 | 300 | static int 301 | mount_fuse_fd_at (const char *mountpoint) 302 | { 303 | autofree char *mountopts = NULL; 304 | int dev_fuse_fd; 305 | int res; 306 | 307 | dev_fuse_fd = open ("/dev/fuse", O_RDWR); 308 | if (dev_fuse_fd == -1) 309 | die_with_error ("Failed to open /dev/fuse"); 310 | 311 | mountopts = xasprintf ("fd=%i,rootmode=%o,user_id=%u,group_id=%u,allow_other", 312 | dev_fuse_fd, 0x4000, 0, 0); 313 | 314 | res = mount("fuse-grootfs", mountpoint, "fuse.fuse-grootfs", MS_NOSUID|MS_NODEV, mountopts); 315 | if (res != 0) 316 | die_with_error ("mount fuse"); 317 | 318 | return dev_fuse_fd; 319 | } 320 | 321 | int 322 | groot_setup_ns (const char **wrapdirs, int num_wrapdirs) 323 | { 324 | autofd int fuse_status_socket = -1; 325 | autofd int uidmap_status_socket = -1; 326 | ssize_t s; 327 | struct passwd *passwd; 328 | const char *username = NULL; 329 | autofreev char **uid_mapping = NULL; 330 | autofreev char **gid_mapping = NULL; 331 | uid_t real_uid; 332 | gid_t real_gid; 333 | pid_t main_pid; 334 | long max_uid, max_gid; 335 | int res; 336 | char buf = 'x'; 337 | 338 | real_uid = getuid (); 339 | real_gid = getgid (); 340 | main_pid = getpid (); 341 | 342 | 343 | /* Avoid calling getpwuid() and thus nss, etc in a preload init constructor if possible */ 344 | username = getenv ("GROOT_USER"); 345 | if (username == NULL) 346 | { 347 | passwd = getpwuid (real_uid); 348 | if (passwd != NULL) 349 | username = xstrdup (passwd->pw_name); 350 | } 351 | 352 | uid_mapping = make_idmap (username, "/etc/subuid", real_uid, &max_uid); 353 | gid_mapping = make_idmap (username, "/etc/subgid", real_gid, &max_gid); 354 | 355 | if (num_wrapdirs > 0) 356 | fuse_status_socket = start_fuse_process (wrapdirs, num_wrapdirs, max_uid, max_gid); 357 | 358 | uidmap_status_socket = start_uidmap_process (main_pid, uid_mapping, gid_mapping); 359 | 360 | /* Never gain any more privs during exec */ 361 | if (prctl (PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0) 362 | die_with_error ("prctl(PR_SET_NO_NEW_PRIVS) failed"); 363 | 364 | if (unshare(CLONE_NEWNS | CLONE_NEWUSER) != 0) 365 | die_with_error ("unshare failed"); 366 | 367 | /* Wake up uidmap process */ 368 | s = write (uidmap_status_socket, &buf, 1); 369 | if (s == -1) 370 | die ("write to status socket"); 371 | 372 | /* Wait on uidmap process */ 373 | do 374 | s = read (uidmap_status_socket, &buf, 1); 375 | while (s == -1 && errno == EINTR); 376 | 377 | if (s == 0) 378 | die ("Failed to setup uid/gid mappings"); 379 | 380 | /* Then set up fuse mounts for the wraps, if needed */ 381 | 382 | if (num_wrapdirs > 0) 383 | { 384 | for (int i = 0; i < num_wrapdirs; i++) 385 | { 386 | const char *wrapdir = wrapdirs[i]; 387 | int dev_fuse_fd; 388 | 389 | if (wrapdir == NULL) 390 | continue; // We failed to open the dir, ignore 391 | 392 | dev_fuse_fd = mount_fuse_fd_at (wrapdir); 393 | 394 | res = send_fd (fuse_status_socket, dev_fuse_fd); 395 | if (res < 0) 396 | die_with_error ("send fd"); 397 | 398 | close (dev_fuse_fd); /* Not used more on this side */ 399 | } 400 | 401 | res = read (fuse_status_socket, &buf, sizeof (buf)); 402 | if (res == -1) 403 | die_with_error ("read fs_socket"); 404 | 405 | if (res == 0) 406 | die ("Fuse setup failed, exiting"); 407 | } 408 | 409 | keep_caps (); 410 | 411 | return 0; 412 | } 413 | -------------------------------------------------------------------------------- /groot-ns.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 Alexander Larsson 3 | * 4 | * SPDX-License-Identifier: LGPL-2.0+ 5 | * 6 | * This library is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 2 of the License, or (at your option) any later version. 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this library; if not, write to the 18 | * Free Software Foundation, Inc., 59 Temple Place - Suite 330, 19 | * Boston, MA 02111-1307, USA. 20 | */ 21 | 22 | int groot_setup_ns (const char **wrapdirs, 23 | int num_wrapdirs); 24 | -------------------------------------------------------------------------------- /groot-preload.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "utils.h" 5 | #include "groot-ns.h" 6 | 7 | /* For some reason the regular unsetenv doesn't seem to work well in an initializer... */ 8 | static void 9 | __unsetenv (const char *name) 10 | { 11 | size_t len = strlen (name); 12 | char **ep; 13 | 14 | ep = __environ; 15 | while (*ep != NULL) 16 | { 17 | if (strncmp (*ep, name, len) == 0 && (*ep)[len] == '=') 18 | { 19 | char **dp = ep; 20 | do 21 | dp[0] = dp[1]; 22 | while (*dp++); 23 | } 24 | else 25 | ++ep; 26 | } 27 | } 28 | 29 | static void 30 | _groot_init_main (int argc, char *argv[]) 31 | { 32 | const char *debug = NULL; 33 | const char *env_wrap = NULL; 34 | const char *disabled = NULL; 35 | char **wrapdirs = NULL; 36 | int num_wrapdirs = 0; 37 | 38 | disabled = getenv ("GROOT_DISABLED"); 39 | env_wrap = getenv ("GROOT_WRAPFS"); 40 | debug = getenv ("GROOT_DEBUG"); 41 | 42 | /* Don't recursively enable groot */ 43 | __unsetenv ("LD_PRELOAD"); 44 | 45 | if (disabled != NULL) 46 | return; 47 | 48 | /* If something re-enables LD_PRELOAD inside, disable it anyway */ 49 | if (disabled == NULL) 50 | setenv ("GROOT_DISABLED", "1", 1); 51 | 52 | if (env_wrap) 53 | { 54 | autofree char *data = xstrdup (env_wrap); 55 | char *iterator = data; 56 | 57 | while (iterator) 58 | { 59 | char *path = strsep (&iterator, ":"); 60 | num_wrapdirs++; 61 | wrapdirs = xrealloc (wrapdirs, num_wrapdirs * sizeof (char *)); 62 | wrapdirs[num_wrapdirs-1] = xstrdup (path); 63 | } 64 | } 65 | 66 | if (debug) 67 | enable_debuglog (); 68 | 69 | __debug__(("Enabling grootfs for %s - wrap %s", argv[0], env_wrap)); 70 | 71 | groot_setup_ns ((const char **)wrapdirs, num_wrapdirs); 72 | strfreev (wrapdirs); 73 | } 74 | 75 | __attribute__((section(".init_array"))) void *_groot_init_main_constructor = &_groot_init_main; 76 | -------------------------------------------------------------------------------- /groot.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 Alexander Larsson 3 | * 4 | * SPDX-License-Identifier: LGPL-2.0+ 5 | * 6 | * This library is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 2 of the License, or (at your option) any later version. 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this library; if not, write to the 18 | * Free Software Foundation, Inc., 59 Temple Place - Suite 330, 19 | * Boston, MA 02111-1307, USA. 20 | */ 21 | 22 | #define FUSE_USE_VERSION 26 23 | 24 | #include "utils.h" 25 | #include "groot-ns.h" 26 | 27 | #include 28 | 29 | enum { 30 | KEY_HELP, 31 | KEY_WRAP, 32 | KEY_DEBUG 33 | }; 34 | 35 | 36 | struct groot_config { 37 | char **wrapdirs; 38 | int num_wrapdirs; 39 | bool debug; 40 | }; 41 | 42 | static void 43 | usage (const char *progname) 44 | { 45 | fprintf (stdout, 46 | "usage: %s [options] command [args..]\n" 47 | "\n" 48 | "options:\n" 49 | " -h --help print help\n" 50 | " -w DIR wrap directory\n" 51 | " -d log debug info\n" 52 | "\n", progname); 53 | } 54 | 55 | int opt_got_command = FALSE; 56 | 57 | static void 58 | add_wrap_dir (struct groot_config *conf, 59 | const char *path) 60 | { 61 | conf->num_wrapdirs++; 62 | conf->wrapdirs = xrealloc (conf->wrapdirs, conf->num_wrapdirs * sizeof (char *)); 63 | conf->wrapdirs[conf->num_wrapdirs-1] = xstrdup (path); 64 | } 65 | 66 | static int 67 | groot_opt_proc (void *data, 68 | const char *arg, 69 | int key, 70 | struct fuse_args *outargs) 71 | { 72 | struct groot_config *conf = data; 73 | 74 | /* Ignore everything after first command */ 75 | if (opt_got_command) 76 | return 1; 77 | 78 | switch (key) 79 | { 80 | case FUSE_OPT_KEY_NONOPT: 81 | opt_got_command = TRUE; 82 | return 1; 83 | 84 | case KEY_HELP: 85 | usage (outargs->argv[0]); 86 | exit (EXIT_SUCCESS); 87 | 88 | case KEY_WRAP: 89 | add_wrap_dir (conf, arg + 2); 90 | return 0; 91 | 92 | case KEY_DEBUG: 93 | conf->debug = TRUE; 94 | return 0; 95 | 96 | default: 97 | fprintf (stderr, "see `%s -h' for usage\n", outargs->argv[0]); 98 | exit (EXIT_FAILURE); 99 | } 100 | 101 | return 1; 102 | } 103 | 104 | 105 | #define GROOT_OPT(t, p, v) { t, offsetof(struct groot_config, p), v } 106 | 107 | static struct fuse_opt groot_opts[] = { 108 | 109 | FUSE_OPT_KEY ("-h", KEY_HELP), 110 | FUSE_OPT_KEY ("--help", KEY_HELP), 111 | FUSE_OPT_KEY ("-w ", KEY_WRAP), 112 | FUSE_OPT_KEY ("-d", KEY_DEBUG), 113 | FUSE_OPT_END 114 | }; 115 | 116 | int 117 | main (int argc, char *argv[]) 118 | { 119 | char **argv_clone; 120 | int res; 121 | struct fuse_args args = FUSE_ARGS_INIT (argc, argv); 122 | struct groot_config conf = { 0 }; 123 | const char *env_wrap = NULL; 124 | 125 | res = fuse_opt_parse (&args, &conf, groot_opts, groot_opt_proc); 126 | if (res != 0) 127 | { 128 | fprintf (stderr, "Invalid arguments\n"); 129 | fprintf (stderr, "see `%s -h' for usage\n", argv[0]); 130 | exit (EXIT_FAILURE); 131 | } 132 | 133 | if (!opt_got_command) 134 | { 135 | fprintf (stderr, "No command specified\n"); 136 | fprintf (stderr, "see `%s -h' for usage\n", argv[0]); 137 | exit (EXIT_FAILURE); 138 | } 139 | 140 | env_wrap = getenv ("GROOT_WRAPFS"); 141 | if (env_wrap) 142 | { 143 | autofree char *data = xstrdup (env_wrap); 144 | char *iterator = data; 145 | 146 | while (iterator) 147 | { 148 | char *path = strsep (&iterator, ":"); 149 | add_wrap_dir (&conf, path); 150 | } 151 | } 152 | 153 | if (conf.debug) 154 | enable_debuglog (); 155 | 156 | groot_setup_ns ((const char **)conf.wrapdirs, conf.num_wrapdirs); 157 | 158 | argv_clone = xmalloc (sizeof(char *) * args.argc); 159 | for (int i = 1; i < args.argc; i++) 160 | argv_clone[i-1] = args.argv[i]; 161 | argv_clone[args.argc-1] = NULL; 162 | 163 | if (execvp (argv_clone[0], argv_clone) == -1) 164 | die_with_error ("exec failed"); 165 | 166 | return 1; 167 | } 168 | -------------------------------------------------------------------------------- /grootfs.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 Alexander Larsson 3 | * 4 | * SPDX-License-Identifier: LGPL-2.0+ 5 | * 6 | * This library is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 2 of the License, or (at your option) any later version. 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this library; if not, write to the 18 | * Free Software Foundation, Inc., 59 Temple Place - Suite 330, 19 | * Boston, MA 02111-1307, USA. 20 | */ 21 | 22 | #define FUSE_USE_VERSION 26 23 | 24 | #include "utils.h" 25 | #include "grootfs.h" 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | 42 | typedef struct { 43 | int basefd; 44 | long max_uid; 45 | long max_gid; 46 | } GRootFS; 47 | 48 | #define ST_MODE_PERM_MASK (S_IRWXU | S_IRWXG | S_IRWXO | S_ISUID | S_ISGID | S_ISVTX) 49 | #define GROOT_CUSTOM_XATTR_PREFIX "user.grootfs." 50 | #define GROOT_DATA_XATTR "user.grootfs" 51 | 52 | static GRootFS * 53 | get_grootfs (void) 54 | { 55 | struct fuse_context *ctx = fuse_get_context (); 56 | return (GRootFS * )ctx->private_data; 57 | } 58 | 59 | static inline const char * 60 | ensure_relpath (const char *path) 61 | { 62 | path = path + strspn (path, "/"); 63 | if (*path == 0) 64 | return "."; 65 | return path; 66 | } 67 | 68 | static int 69 | open_dirfd (const char *path) 70 | { 71 | GRootFS *fs = get_grootfs (); 72 | path = ensure_relpath (path); 73 | int dirfd = openat (fs->basefd, path, O_DIRECTORY | O_RDONLY, 0); 74 | if (dirfd == -1) 75 | return -errno; 76 | return dirfd; 77 | } 78 | 79 | static int 80 | open_parent_dirfd (const char *path, 81 | char **out_basename) 82 | { 83 | int dirfd = -1; 84 | autofree char *dirpath = xstrdup (ensure_relpath (path)); 85 | char *end = dirpath + strlen (dirpath); 86 | const char *dir; 87 | const char *base; 88 | 89 | /* Remove any slashes at the end */ 90 | while (*end == '/' && end > dirpath) 91 | { 92 | *end = 0; 93 | end--; 94 | } 95 | 96 | /* Find first slash (not at end), if any */ 97 | while (*end != '/' && end > dirpath) 98 | end--; 99 | 100 | if (end == dirpath) 101 | { 102 | base = dirpath; 103 | dir = "."; 104 | } 105 | else 106 | { 107 | base = end + 1; 108 | *end = 0; 109 | dir = dirpath; 110 | } 111 | 112 | dirfd = open_dirfd (dir); 113 | if (dirfd >= 0) 114 | *out_basename = xstrdup (base); 115 | return dirfd; 116 | } 117 | 118 | static char * 119 | get_proc_fd_path (int dirfd, 120 | const char *opt_file) 121 | { 122 | if (opt_file) 123 | return xasprintf ("/proc/self/fd/%d/%s", dirfd, opt_file); 124 | else 125 | return xasprintf ("/proc/self/fd/%d", dirfd); 126 | } 127 | 128 | /* Computes the real file pemissions for a a faked file. 129 | * In order to correctly do things like set permissions and 130 | * execute/search the files we set everything to rw for the user, 131 | * r-only for rest. For dirs we always set x, and mirror the user x 132 | * bit for other files. */ 133 | static mode_t 134 | get_real_mode (int is_dir, 135 | int executable_default) 136 | { 137 | mode_t real_mode = S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR; 138 | if (is_dir || executable_default) 139 | real_mode |= S_IXUSR | S_IXGRP | S_IXOTH; 140 | return real_mode; 141 | } 142 | 143 | typedef enum { 144 | GROOTFS_FLAGS_UID_SET = 1<<0, 145 | GROOTFS_FLAGS_GID_SET = 1<<1, 146 | GROOTFS_FLAGS_MODE_SET = 1<<2, 147 | } GrootFSFlags; 148 | 149 | typedef struct { 150 | uint32_t flags; 151 | uint32_t uid; 152 | uint32_t gid; 153 | uint32_t mode; 154 | } GRootFSData; 155 | 156 | static void 157 | fake_data_htonl (const GRootFSData *data, 158 | GRootFSData *data_dst) 159 | { 160 | data_dst->flags = htonl (data->flags); 161 | data_dst->mode = htonl (data->mode); 162 | data_dst->uid = htonl (data->uid); 163 | data_dst->gid = htonl (data->gid); 164 | } 165 | 166 | static void 167 | fake_data_ntohl (const GRootFSData *data, 168 | GRootFSData *data_dst) 169 | { 170 | data_dst->flags = ntohl (data->flags); 171 | data_dst->mode = ntohl (data->mode); 172 | data_dst->uid = ntohl (data->uid); 173 | data_dst->gid = ntohl (data->gid); 174 | } 175 | 176 | static void 177 | apply_fake_data (struct stat *st_data, 178 | const GRootFSData *data) 179 | { 180 | GRootFS *fs = get_grootfs (); 181 | 182 | if (data->flags & GROOTFS_FLAGS_UID_SET) 183 | st_data->st_uid = data->uid; 184 | 185 | if (data->flags & GROOTFS_FLAGS_GID_SET) 186 | st_data->st_gid = data->gid; 187 | 188 | if (data->flags & GROOTFS_FLAGS_MODE_SET) 189 | st_data->st_mode = (st_data->st_mode & (~ST_MODE_PERM_MASK)) | (data->mode & ST_MODE_PERM_MASK); 190 | 191 | /* Don't expose nobody user or other weird things not useful in the namespace */ 192 | 193 | if (st_data->st_uid > fs->max_uid) 194 | st_data->st_uid = 0; 195 | 196 | if (st_data->st_gid > fs->max_gid) 197 | st_data->st_gid = 0; 198 | } 199 | 200 | static int 201 | get_fake_data (int dirfd, 202 | const char *file, 203 | int allow_noent, 204 | GRootFSData *data) 205 | { 206 | autofree char *proc_file = get_proc_fd_path (dirfd, file); 207 | ssize_t res; 208 | 209 | res = lgetxattr (proc_file, "user.grootfs", data, sizeof (GRootFSData)); 210 | if (res == -1) 211 | { 212 | int errsv = errno; 213 | 214 | /* Handle the case were there was no data set by returning all zeros */ 215 | if ((allow_noent && errsv == ENOENT) || 216 | errsv == ENODATA || 217 | errsv == ENOTSUP) 218 | { 219 | GRootFSData zero = {0}; 220 | *data = zero; 221 | return 0; 222 | } 223 | 224 | if (errsv == ERANGE) 225 | report ("Internal error: Wrong xattr size for file %s", file); 226 | else 227 | report ("Internal error: lgetxattr %s returned %s", file, strerror (errsv)); 228 | 229 | return -errsv; 230 | } 231 | 232 | if (res != sizeof (GRootFSData)) 233 | { 234 | report ("Internal error: Wrong xattr size for file %s", file); 235 | return -ERANGE; 236 | } 237 | 238 | fake_data_ntohl (data, data); 239 | 240 | return 0; 241 | } 242 | 243 | static int 244 | get_fake_dataf (int fd, 245 | GRootFSData *data) 246 | { 247 | ssize_t res; 248 | 249 | res = fgetxattr (fd, "user.grootfs", data, sizeof(GRootFSData)); 250 | if (res == -1) 251 | { 252 | int errsv = errno; 253 | 254 | /* Handle the case were there was no data set by returning all zeros */ 255 | if (errsv == ENODATA || 256 | errsv == ENOTSUP) 257 | { 258 | GRootFSData zero = {0}; 259 | *data = zero; 260 | return 0; 261 | } 262 | 263 | if (errsv == ERANGE) 264 | report ("Internal error: Wrong xattr size for fd %d", fd); 265 | else 266 | report ("Internal error: fgetxattr %d returned %s", fd, strerror (errsv)); 267 | 268 | return -errsv; 269 | } 270 | 271 | if (res != sizeof (GRootFSData)) 272 | { 273 | report ("Internal error: Wrong xattr size for fd %d", fd); 274 | return -ERANGE; 275 | } 276 | 277 | fake_data_ntohl (data, data); 278 | 279 | return 0; 280 | } 281 | 282 | static int 283 | set_fake_data (int dirfd, 284 | const char *file, 285 | int ensure_exist, 286 | const GRootFSData *data) 287 | { 288 | autofree char *proc_file = get_proc_fd_path (dirfd, file); 289 | ssize_t res; 290 | GRootFSData data2; 291 | 292 | fake_data_htonl (data, &data2); 293 | 294 | if (ensure_exist) 295 | { 296 | int fd = openat (dirfd, file, O_CREAT | O_EXCL | O_WRONLY, 0666); 297 | if (fd == -1) 298 | { 299 | if (errno != EEXIST) 300 | return -errno; 301 | } 302 | 303 | close (fd); 304 | } 305 | 306 | res = lsetxattr (proc_file, "user.grootfs", &data2, sizeof(GRootFSData), 0); 307 | if (res == -1) 308 | { 309 | int errsv = errno; 310 | report ("Internal error: lsetxattr %s returned %s", file, strerror (errsv)); 311 | return -errsv; 312 | } 313 | 314 | return 0; 315 | } 316 | 317 | static int 318 | set_fake_dataf (int fd, 319 | const GRootFSData *data) 320 | { 321 | ssize_t res; 322 | GRootFSData data2; 323 | 324 | fake_data_htonl (data, &data2); 325 | 326 | res = fsetxattr (fd, "user.grootfs", &data2, sizeof(GRootFSData), 0); 327 | if (res == -1) 328 | { 329 | int errsv = errno; 330 | report ("Internal error: fsetxattr %d returned %s", fd, strerror (errsv)); 331 | return -errsv; 332 | } 333 | 334 | return 0; 335 | } 336 | 337 | typedef struct { 338 | const char *path; 339 | int fd; 340 | int dirfd; 341 | char *basename; 342 | char *datafile; 343 | bool exists; 344 | struct stat st_data; 345 | GRootFSData fake_data; 346 | } GRootPathInfo; 347 | 348 | #define GROOT_PATH_INFO_INIT { NULL, -1, -1 } 349 | 350 | static void 351 | groot_path_info_cleanup (GRootPathInfo *info) 352 | { 353 | if (info->dirfd != -1) 354 | close (info->dirfd); 355 | 356 | if (info->basename) 357 | free (info->basename); 358 | 359 | if (info->datafile) 360 | free (info->datafile); 361 | } 362 | 363 | DEFINE_AUTO_CLEANUP_CLEAR_FUNC(GRootPathInfo, groot_path_info_cleanup); 364 | 365 | static int 366 | _groot_path_info_init_base (GRootPathInfo *info, bool allow_noent) 367 | { 368 | info->exists = TRUE; 369 | 370 | if (S_ISLNK (info->st_data.st_mode)) 371 | { 372 | GRootFS *fs = get_grootfs (); 373 | info->datafile = xasprintf (".groot.symlink.%lx_%lx", 374 | info->st_data.st_dev, info->st_data.st_ino); 375 | 376 | if (get_fake_data (fs->basefd, info->datafile, TRUE, &info->fake_data) != 0) 377 | return -EIO; 378 | } 379 | else if (info->fd != -1) 380 | { 381 | if (get_fake_dataf (info->fd, &info->fake_data) != 0) 382 | return -EIO; 383 | } 384 | else 385 | { 386 | if (get_fake_data (info->dirfd, info->basename, allow_noent, &info->fake_data) != 0) 387 | return -EIO; 388 | } 389 | 390 | apply_fake_data (&info->st_data, &info->fake_data); 391 | 392 | return 0; 393 | } 394 | 395 | static int 396 | groot_path_info_init_path (GRootPathInfo *info, const char *path, bool allow_noent) 397 | { 398 | info->path = ensure_relpath (path); 399 | info->dirfd = open_parent_dirfd (info->path, &info->basename); 400 | if (info->dirfd < 0) 401 | return -errno; 402 | 403 | if (fstatat (info->dirfd, info->basename, &info->st_data, AT_SYMLINK_NOFOLLOW) == -1) 404 | { 405 | if (allow_noent) 406 | return 0; 407 | 408 | return -errno; 409 | } 410 | 411 | return _groot_path_info_init_base (info, allow_noent); 412 | } 413 | 414 | static int 415 | groot_path_info_init_fd (GRootPathInfo *info, int fd) 416 | { 417 | info->fd = fd; 418 | 419 | if (fstat (info->fd, &info->st_data) == -1) 420 | return -errno; 421 | 422 | return _groot_path_info_init_base (info, FALSE); 423 | } 424 | 425 | static int 426 | groot_path_info_update_data (GRootPathInfo *info) 427 | { 428 | assert (info->exists); // TODO: Later handle non-exist for e.g. symlinks 429 | 430 | if (info->datafile) /* A symlink with separate data file, could be path *or* fd backed */ 431 | { 432 | GRootFS *fs = get_grootfs (); 433 | if (set_fake_data (fs->basefd, info->datafile, TRUE, &info->fake_data) != 0) 434 | return -EIO; 435 | } 436 | else if (info->fd != -1) 437 | { 438 | if (set_fake_dataf (info->fd, &info->fake_data) != 0) 439 | return -EIO; 440 | } 441 | else 442 | { 443 | if (set_fake_data (info->dirfd, info->basename, FALSE, &info->fake_data) != 0) 444 | return -EIO; 445 | } 446 | 447 | return 0; 448 | } 449 | 450 | static int 451 | grootfs_getattr (const char *path, struct stat *st_data) 452 | { 453 | __debug__ (("getattr %s", path)); 454 | 455 | int res; 456 | auto(GRootPathInfo) info = GROOT_PATH_INFO_INIT; 457 | res = groot_path_info_init_path (&info, path, FALSE); 458 | if (res != 0) 459 | return res; 460 | 461 | *st_data = info.st_data; 462 | 463 | return 0; 464 | } 465 | 466 | static int 467 | grootfs_fgetattr (const char *path, struct stat *st_data, struct fuse_file_info *fi) 468 | { 469 | __debug__ (("fgetattr %s", path)); 470 | 471 | int res; 472 | auto(GRootPathInfo) info = GROOT_PATH_INFO_INIT; 473 | res = groot_path_info_init_fd (&info, fi->fh); 474 | if (res != 0) 475 | return res; 476 | 477 | *st_data = info.st_data; 478 | 479 | return 0; 480 | } 481 | 482 | static int 483 | grootfs_chmod (const char *path, mode_t mode) 484 | { 485 | __debug__ (("chmod %s %x", path, mode)); 486 | 487 | int res; 488 | auto(GRootPathInfo) info = GROOT_PATH_INFO_INIT; 489 | res = groot_path_info_init_path (&info, path, FALSE); 490 | if (res != 0) 491 | return res; 492 | 493 | mode_t real_mode = get_real_mode (S_ISDIR (info.st_data.st_mode), (mode & S_IXUSR) != 0); 494 | 495 | /* For permissions like execute to work for others, we set all the 496 | permissions, to the users perms and strip out any extra perms. */ 497 | 498 | /* Note we can't use AT_SYMLINK_NOFOLLOW yet; 499 | * https://marc.info/?l=linux-kernel&m=148830147803162&w=2 500 | * https://marc.info/?l=linux-fsdevel&m=149193779929561&w=2 501 | * Fuse always resolves the symlink and calls us on the target 502 | */ 503 | if (fchmodat (info.dirfd, info.basename, real_mode, 0) != 0) 504 | return -errno; 505 | 506 | info.fake_data.mode = mode & ST_MODE_PERM_MASK; 507 | info.fake_data.flags |= GROOTFS_FLAGS_MODE_SET; 508 | 509 | res = groot_path_info_update_data (&info); 510 | if (res != 0) 511 | return res; 512 | 513 | return 0; 514 | } 515 | 516 | static int 517 | grootfs_chown (const char *path, uid_t uid, gid_t gid) 518 | { 519 | __debug__ (("chown %s to %d %d", path, uid, gid)); 520 | 521 | int res; 522 | auto(GRootPathInfo) info = GROOT_PATH_INFO_INIT; 523 | res = groot_path_info_init_path (&info, path, FALSE); 524 | if (res != 0) 525 | return res; 526 | 527 | if (uid != -1) 528 | { 529 | info.fake_data.uid = uid; 530 | info.fake_data.flags |= GROOTFS_FLAGS_UID_SET; 531 | } 532 | 533 | if (gid != -1) 534 | { 535 | info.fake_data.gid = gid; 536 | info.fake_data.flags |= GROOTFS_FLAGS_GID_SET; 537 | } 538 | 539 | res = groot_path_info_update_data (&info); 540 | if (res != 0) 541 | return res; 542 | 543 | return 0; 544 | } 545 | 546 | static int 547 | grootfs_readlink (const char *path, char *buf, size_t size) 548 | { 549 | GRootFS *fs = get_grootfs (); 550 | int r; 551 | 552 | __debug__ (("readlink %s", path)); 553 | 554 | path = ensure_relpath (path); 555 | 556 | /* Note FUSE wants the string to be always nul-terminated, even if 557 | * truncated. 558 | */ 559 | r = readlinkat (fs->basefd, path, buf, size - 1); 560 | if (r == -1) 561 | return -errno; 562 | buf[r] = '\0'; 563 | return 0; 564 | } 565 | 566 | static int 567 | grootfs_readdir (const char *path, void *buf, fuse_fill_dir_t filler, 568 | off_t offset, struct fuse_file_info *fi) 569 | { 570 | GRootFS *fs = get_grootfs (); 571 | DIR *dp; 572 | struct dirent *de; 573 | int dfd; 574 | 575 | __debug__ (("readdir %s", path)); 576 | 577 | path = ensure_relpath (path); 578 | 579 | if (!*path) 580 | { 581 | dfd = fcntl (fs->basefd, F_DUPFD_CLOEXEC, 3); 582 | if (dfd < 0) 583 | return -errno; 584 | lseek (dfd, 0, SEEK_SET); 585 | } 586 | else 587 | { 588 | dfd = openat (fs->basefd, path, O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC | O_NOCTTY); 589 | if (dfd == -1) 590 | return -errno; 591 | } 592 | 593 | /* Transfers ownership of fd */ 594 | dp = fdopendir (dfd); 595 | if (dp == NULL) 596 | return -errno; 597 | 598 | while ((de = readdir (dp)) != NULL) 599 | { 600 | struct stat st; 601 | 602 | if (has_prefix (de->d_name, ".groot.")) 603 | continue; 604 | 605 | memset (&st, 0, sizeof (st)); 606 | st.st_ino = de->d_ino; 607 | // TODO: Ensure right mode if fake devnode/socket 608 | st.st_mode = de->d_type << 12; 609 | if (filler (buf, de->d_name, &st, 0)) 610 | break; 611 | } 612 | 613 | (void) closedir (dp); 614 | return 0; 615 | } 616 | 617 | static int 618 | grootfs_mknod (const char *path, mode_t mode, dev_t rdev) 619 | { 620 | __debug__ (("mknod %s %ld %ld", path, (long)mode, (long)rdev)); 621 | // TODO: Implement 622 | return -EROFS; 623 | } 624 | 625 | static int 626 | grootfs_mkdir (const char *path, mode_t mode) 627 | { 628 | __debug__ (("mkdir %s %x", path, mode)); 629 | 630 | int res; 631 | autofree char *basename = NULL; 632 | autofd int dirfd = open_parent_dirfd (path, &basename); 633 | if (dirfd < 0) 634 | return dirfd; 635 | 636 | mode_t real_mode = get_real_mode (TRUE, FALSE); 637 | 638 | if (mkdirat (dirfd, basename, real_mode) == -1) 639 | return -errno; 640 | 641 | /* mkdir succeeded, so its guaranteed to be a not-previously 642 | existing dir, just set the fake data */ 643 | GRootFSData data = { 0 }; 644 | struct fuse_context *ctx = fuse_get_context (); 645 | 646 | data.mode = mode & ST_MODE_PERM_MASK; 647 | data.uid = ctx->uid; 648 | data.gid = ctx->gid; 649 | data.flags = GROOTFS_FLAGS_MODE_SET | GROOTFS_FLAGS_UID_SET | GROOTFS_FLAGS_GID_SET; 650 | 651 | res = set_fake_data (dirfd, basename, FALSE, &data); 652 | if (res != 0) 653 | return res; 654 | 655 | return 0; 656 | } 657 | 658 | static int 659 | grootfs_unlink (const char *path) 660 | { 661 | __debug__ (("unlink %s", path)); 662 | 663 | int res; 664 | auto(GRootPathInfo) info = GROOT_PATH_INFO_INIT; 665 | res = groot_path_info_init_path (&info, path, FALSE); 666 | if (res != 0) 667 | return res; 668 | 669 | if (unlinkat (info.dirfd, info.basename, 0) == -1) 670 | return -errno; 671 | 672 | /* When unlinking a symlink, also unlink symlink datafile. 673 | * This should be fine because we can't hardlink symlinks, so its the last reference. */ 674 | if (info.datafile) 675 | { 676 | GRootFS *fs = get_grootfs (); 677 | unlinkat (fs->basefd, info.datafile, 0); 678 | } 679 | 680 | return 0; 681 | } 682 | 683 | static int 684 | grootfs_rmdir (const char *path) 685 | { 686 | GRootFS *fs = get_grootfs (); 687 | 688 | __debug__ (("rmdir %s", path)); 689 | path = ensure_relpath (path); 690 | 691 | if (unlinkat (fs->basefd, path, AT_REMOVEDIR) == -1) 692 | return -errno; 693 | 694 | return 0; 695 | } 696 | 697 | static int 698 | grootfs_symlink (const char *from, const char *to) 699 | { 700 | GRootFS *fs = get_grootfs (); 701 | 702 | __debug__ (("symlink %s %s", from, to)); 703 | to = ensure_relpath (to); 704 | 705 | if (symlinkat (from, fs->basefd, to) == -1) 706 | return -errno; 707 | 708 | /* We created a new symlink file, set default ownership */ 709 | auto(GRootPathInfo) info = GROOT_PATH_INFO_INIT; 710 | if (groot_path_info_init_path (&info, to, FALSE) == 0) 711 | { 712 | struct fuse_context *ctx = fuse_get_context (); 713 | 714 | info.fake_data.uid = ctx->uid; 715 | info.fake_data.gid = ctx->gid; 716 | info.fake_data.flags = GROOTFS_FLAGS_UID_SET | GROOTFS_FLAGS_GID_SET; 717 | 718 | groot_path_info_update_data (&info); 719 | } 720 | 721 | return 0; 722 | } 723 | 724 | static int 725 | grootfs_rename (const char *from, const char *to) 726 | { 727 | GRootFS *fs = get_grootfs (); 728 | 729 | __debug__ (("rename %s %s", from, to)); 730 | from = ensure_relpath (from); 731 | to = ensure_relpath (to); 732 | 733 | if (renameat (fs->basefd, from, fs->basefd, to) == -1) 734 | return -errno; 735 | 736 | return 0; 737 | } 738 | 739 | static int 740 | grootfs_link (const char *from, const char *to) 741 | { 742 | GRootFS *fs = get_grootfs (); 743 | 744 | __debug__ (("link %s %s", from, to)); 745 | from = ensure_relpath (from); 746 | to = ensure_relpath (to); 747 | 748 | if (linkat (fs->basefd, from, fs->basefd, to, 0) == -1) 749 | return -errno; 750 | 751 | return 0; 752 | } 753 | 754 | static int 755 | grootfs_truncate (const char *path, off_t size) 756 | { 757 | GRootFS *fs = get_grootfs (); 758 | 759 | __debug__ (("truncate %s", path)); 760 | path = ensure_relpath (path); 761 | 762 | autofd int fd = openat (fs->basefd, path, O_NOFOLLOW|O_WRONLY); 763 | if (fd == -1) 764 | return -errno; 765 | 766 | if (ftruncate (fd, size) == -1) 767 | return -errno; 768 | 769 | return 0; 770 | } 771 | 772 | static int 773 | grootfs_ftruncate (const char *path, off_t size, struct fuse_file_info *fi) 774 | { 775 | __debug__ (("ftruncate %s", path)); 776 | 777 | if (ftruncate (fi->fh, size) == -1) 778 | return -errno; 779 | 780 | return 0; 781 | } 782 | 783 | static int 784 | grootfs_utimens (const char *path, const struct timespec tv[2]) 785 | { 786 | GRootFS *fs = get_grootfs (); 787 | 788 | __debug__ (("utimens %s", path)); 789 | path = ensure_relpath (path); 790 | 791 | if (utimensat (fs->basefd, path, tv, AT_SYMLINK_NOFOLLOW) == -1) 792 | return -errno; 793 | 794 | return 0; 795 | } 796 | 797 | static int 798 | do_open (const char *path, mode_t mode, struct fuse_file_info *finfo) 799 | { 800 | GRootFS *fs = get_grootfs (); 801 | int fd; 802 | mode_t real_mode; 803 | int o_creat = (O_CREAT & finfo->flags) != 0; 804 | int o_excl = (O_EXCL & finfo->flags) != 0; 805 | int created_file = o_creat; 806 | int flags; 807 | 808 | // TODO: Rewrite path for fake devnodes, etc 809 | 810 | path = ensure_relpath (path); 811 | 812 | real_mode = get_real_mode (FALSE, (mode & S_IXUSR) != 0); 813 | 814 | flags = finfo->flags; 815 | if (o_creat && !o_excl) 816 | { 817 | flags |= O_EXCL; /* We really need to know if the file was created or not, so we try EXCL first */ 818 | } 819 | 820 | fd = openat (fs->basefd, path, flags, real_mode); 821 | if (fd == -1 && o_creat && !o_excl && errno == EEXIST) 822 | { 823 | created_file = FALSE; /* We know the file existed */ 824 | /* We faked the o_excl, and it exists, retry again witout forced o_excl */ 825 | fd = openat (fs->basefd, path, finfo->flags, real_mode); 826 | } 827 | 828 | if (fd == -1) 829 | return -errno; 830 | 831 | if (created_file) 832 | { 833 | GRootFSData data = { 0 }; 834 | int res; 835 | struct fuse_context *ctx = fuse_get_context (); 836 | 837 | data.mode = mode & ST_MODE_PERM_MASK; 838 | data.uid = ctx->uid; 839 | data.gid = ctx->gid; 840 | data.flags = GROOTFS_FLAGS_MODE_SET | GROOTFS_FLAGS_UID_SET | GROOTFS_FLAGS_GID_SET; 841 | 842 | res = set_fake_dataf (fd, &data); 843 | if (res != 0) 844 | { 845 | close (fd); 846 | return res; 847 | } 848 | } 849 | 850 | 851 | finfo->fh = fd; 852 | 853 | return 0; 854 | } 855 | 856 | static int 857 | grootfs_open (const char *path, struct fuse_file_info *finfo) 858 | { 859 | __debug__ (("open %s", path)); 860 | return do_open (path, 0, finfo); 861 | } 862 | 863 | static int 864 | grootfs_create(const char *path, mode_t mode, struct fuse_file_info *finfo) 865 | { 866 | __debug__ (("create %s", path)); 867 | return do_open (path, mode, finfo); 868 | } 869 | 870 | static int 871 | grootfs_read (const char *path, char *buf, size_t size, off_t offset, 872 | struct fuse_file_info *finfo) 873 | { 874 | int r; 875 | 876 | r = pread (finfo->fh, buf, size, offset); 877 | if (r == -1) 878 | return -errno; 879 | return r; 880 | } 881 | 882 | static int 883 | grootfs_write (const char *path, const char *buf, size_t size, off_t offset, 884 | struct fuse_file_info *finfo) 885 | { 886 | int r; 887 | 888 | r = pwrite (finfo->fh, buf, size, offset); 889 | if (r == -1) 890 | return -errno; 891 | 892 | return r; 893 | } 894 | 895 | static int 896 | grootfs_statfs (const char *path, struct statvfs *st_buf) 897 | { 898 | GRootFS *fs = get_grootfs (); 899 | 900 | if (fstatvfs (fs->basefd, st_buf) == -1) 901 | return -errno; 902 | 903 | return 0; 904 | } 905 | 906 | static int 907 | grootfs_release (const char *path, struct fuse_file_info *finfo) 908 | { 909 | (void) close (finfo->fh); 910 | return 0; 911 | } 912 | 913 | static int 914 | grootfs_fsync (const char *path, int crap, struct fuse_file_info *finfo) 915 | { 916 | if (fsync (finfo->fh) == -1) 917 | return -errno; 918 | return 0; 919 | } 920 | 921 | static int 922 | grootfs_access (const char *path, int mode) 923 | { 924 | GRootFS *fs = get_grootfs (); 925 | 926 | __debug__ (("access %s", path)); 927 | path = ensure_relpath (path); 928 | 929 | // TODO: Rewrite path for fake devnodes, etc 930 | 931 | /* Apparently at least GNU coreutils rm calls `faccessat(W_OK)` 932 | * before trying to do an unlink. So...we'll just lie about 933 | * writable access here. 934 | */ 935 | if (faccessat (fs->basefd, path, mode, AT_SYMLINK_NOFOLLOW) == -1) 936 | return -errno; 937 | 938 | return 0; 939 | } 940 | 941 | static int 942 | grootfs_setxattr (const char *path, const char *name, const char *value, 943 | size_t size, int flags) 944 | { 945 | __debug__ (("setxattr %s %s", path, name)); 946 | 947 | autofree char *basename = NULL; 948 | autofd int dirfd = open_parent_dirfd (path, &basename); 949 | if (dirfd < 0) 950 | return dirfd; 951 | 952 | autofree char *proc_file = get_proc_fd_path (dirfd, basename); 953 | autofree char *fake_name = xasprintf (GROOT_CUSTOM_XATTR_PREFIX"%s", name); 954 | 955 | if (lsetxattr (proc_file, fake_name, value, size, flags) != 0) 956 | return -errno; 957 | 958 | return 0; 959 | } 960 | 961 | static int 962 | grootfs_getxattr (const char *path, const char *name, char *value, 963 | size_t size) 964 | { 965 | __debug__ (("getxattr %s %s", path, name)); 966 | 967 | autofree char *basename = NULL; 968 | autofd int dirfd = open_parent_dirfd (path, &basename); 969 | ssize_t res; 970 | if (dirfd < 0) 971 | return dirfd; 972 | 973 | autofree char *proc_file = get_proc_fd_path (dirfd, basename); 974 | autofree char *fake_name = xasprintf (GROOT_CUSTOM_XATTR_PREFIX"%s", name); 975 | 976 | res = lgetxattr (proc_file, fake_name, value, size); 977 | if (res == -1) 978 | return -errno; 979 | 980 | return res; 981 | } 982 | 983 | /* 984 | * List the supported extended attributes. 985 | */ 986 | static int 987 | grootfs_listxattr (const char *path, char *list, size_t size) 988 | { 989 | __debug__ (("listxattr %s", path)); 990 | 991 | autofree char *basename = NULL; 992 | autofd int dirfd = open_parent_dirfd (path, &basename); 993 | if (dirfd < 0) 994 | return dirfd; 995 | 996 | autofree char *proc_file = get_proc_fd_path (dirfd, basename); 997 | char buf_data[4096]; 998 | autofree char *buf_free = NULL; 999 | char *buf = buf_data; 1000 | size_t buf_size = sizeof(buf_data); 1001 | char *real_list, *real_list_end; 1002 | ssize_t res; 1003 | size_t fake_size; 1004 | 1005 | while (1) 1006 | { 1007 | res = llistxattr (proc_file, buf, buf_size); 1008 | if (res < 0) 1009 | { 1010 | int errsv = errno; 1011 | 1012 | if (errsv == ERANGE) 1013 | { 1014 | if (buf_free) 1015 | free (buf_free); 1016 | buf_size *= 2; 1017 | buf_free = xmalloc (buf_size); 1018 | buf = buf_free; 1019 | continue; 1020 | } 1021 | 1022 | return -errsv; 1023 | } 1024 | 1025 | break; 1026 | } 1027 | 1028 | fake_size = 0; 1029 | real_list = buf; 1030 | real_list_end = real_list + res; 1031 | 1032 | while (real_list < real_list_end) 1033 | { 1034 | const char *name = real_list; 1035 | real_list = real_list + strlen (name) + 1; 1036 | 1037 | if (has_prefix (name, GROOT_CUSTOM_XATTR_PREFIX)) 1038 | { 1039 | name = name + strlen (GROOT_CUSTOM_XATTR_PREFIX); 1040 | fake_size += strlen (name) + 1; 1041 | } 1042 | } 1043 | 1044 | if (size == 0) 1045 | return fake_size; 1046 | 1047 | if (size < fake_size) 1048 | return -ERANGE; 1049 | 1050 | real_list = buf; 1051 | while (real_list < real_list_end) 1052 | { 1053 | const char *name = real_list; 1054 | real_list = real_list + strlen (name) + 1; 1055 | 1056 | if (has_prefix (name, GROOT_CUSTOM_XATTR_PREFIX)) 1057 | { 1058 | name = name + strlen (GROOT_CUSTOM_XATTR_PREFIX); 1059 | memcpy (list, name, strlen (name) + 1); 1060 | list += strlen (name) + 1; 1061 | } 1062 | } 1063 | 1064 | return fake_size; 1065 | } 1066 | 1067 | /* 1068 | * Remove an extended attribute. 1069 | */ 1070 | static int 1071 | grootfs_removexattr (const char *path, const char *name) 1072 | { 1073 | __debug__ (("removexattr %s %s", path, name)); 1074 | 1075 | autofree char *basename = NULL; 1076 | autofd int dirfd = open_parent_dirfd (path, &basename); 1077 | if (dirfd < 0) 1078 | return dirfd; 1079 | 1080 | autofree char *proc_file = get_proc_fd_path (dirfd, basename); 1081 | autofree char *fake_name = xasprintf (GROOT_CUSTOM_XATTR_PREFIX"%s", name); 1082 | 1083 | if (lremovexattr (proc_file, fake_name) != 0) 1084 | return -errno; 1085 | 1086 | return 0; 1087 | } 1088 | 1089 | static void * 1090 | grootfs_init (struct fuse_conn_info *conn) 1091 | { 1092 | struct fuse_context *ctx = fuse_get_context (); 1093 | GRootFS *fs = ctx->private_data; 1094 | 1095 | return fs; 1096 | } 1097 | 1098 | static void 1099 | grootfs_destroy (void *private_data) 1100 | { 1101 | GRootFS *fs = private_data; 1102 | 1103 | close (fs->basefd); 1104 | free (fs); 1105 | } 1106 | 1107 | struct fuse_operations grootfs_oper = { 1108 | .init = grootfs_init, 1109 | .destroy = grootfs_destroy, 1110 | .getattr = grootfs_getattr, 1111 | .fgetattr = grootfs_fgetattr, 1112 | .readlink = grootfs_readlink, 1113 | .readdir = grootfs_readdir, 1114 | .mknod = grootfs_mknod, 1115 | .mkdir = grootfs_mkdir, 1116 | .symlink = grootfs_symlink, 1117 | .unlink = grootfs_unlink, 1118 | .rmdir = grootfs_rmdir, 1119 | .rename = grootfs_rename, 1120 | .link = grootfs_link, 1121 | .chmod = grootfs_chmod, 1122 | .chown = grootfs_chown, 1123 | .truncate = grootfs_truncate, 1124 | .ftruncate = grootfs_ftruncate, 1125 | .utimens = grootfs_utimens, 1126 | .create = grootfs_create, 1127 | .open = grootfs_open, 1128 | .read = grootfs_read, 1129 | .write = grootfs_write, 1130 | .statfs = grootfs_statfs, 1131 | .release = grootfs_release, 1132 | .fsync = grootfs_fsync, 1133 | .access = grootfs_access, 1134 | .setxattr = grootfs_setxattr, 1135 | .getxattr = grootfs_getxattr, 1136 | .listxattr = grootfs_listxattr, 1137 | .removexattr = grootfs_removexattr, 1138 | }; 1139 | 1140 | static GRootFS * 1141 | new_grootfs (int basefd, 1142 | long max_uid, 1143 | long max_gid) 1144 | { 1145 | GRootFS *fs = xmalloc (sizeof (GRootFS)); 1146 | fs->basefd = basefd; 1147 | fs->max_uid = max_uid; 1148 | fs->max_gid = max_gid; 1149 | return fs; 1150 | } 1151 | 1152 | int 1153 | start_grootfs (int argc, 1154 | char *argv[], 1155 | int dirfd) 1156 | { 1157 | GRootFS *fs = new_grootfs (dirfd, LONG_MAX, LONG_MAX); 1158 | 1159 | return fuse_main (argc, argv, &grootfs_oper, fs); 1160 | } 1161 | 1162 | static int 1163 | dev_fuse_chan_receive (struct fuse_chan **chp, 1164 | char *buf, 1165 | size_t size) 1166 | { 1167 | struct fuse_chan *ch = *chp; 1168 | struct fuse_session *se = fuse_chan_session (ch); 1169 | ssize_t res; 1170 | int err; 1171 | 1172 | restart: 1173 | res = read (fuse_chan_fd (ch), buf, size); 1174 | err = errno; 1175 | 1176 | if (fuse_session_exited (se)) 1177 | return 0; 1178 | 1179 | if (res == -1) 1180 | { 1181 | /* ENOENT means the operation was interrupted, it's safe to restart */ 1182 | if (err == ENOENT) 1183 | goto restart; 1184 | 1185 | if (err == ENODEV) /* Happens on unmount */ 1186 | { 1187 | fuse_session_exit (se); 1188 | return 0; 1189 | } 1190 | if (err != EINTR && err != EAGAIN) 1191 | report ("reading fuse device"); 1192 | return -err; 1193 | } 1194 | 1195 | return res; 1196 | } 1197 | 1198 | static int 1199 | dev_fuse_chan_send (struct fuse_chan *ch, 1200 | const struct iovec iov[], 1201 | size_t count) 1202 | { 1203 | if (iov) 1204 | { 1205 | ssize_t res = writev (fuse_chan_fd (ch), iov, count); 1206 | int err = errno; 1207 | 1208 | if (res == -1) 1209 | { 1210 | struct fuse_session *se = fuse_chan_session(ch); 1211 | if (!fuse_session_exited(se) && err != ENOENT) 1212 | report ("writing fuse device"); 1213 | return -err; 1214 | } 1215 | } 1216 | 1217 | return 0; 1218 | } 1219 | 1220 | static void 1221 | dev_fuse_chan_destroy (struct fuse_chan *ch) 1222 | { 1223 | int fd = fuse_chan_fd (ch); 1224 | 1225 | if (fd != -1) 1226 | close (fd); 1227 | } 1228 | 1229 | #define MIN_BUFSIZE 0x21000 1230 | 1231 | struct fuse_chan * 1232 | dev_fuse_chan_new (int fd) 1233 | { 1234 | struct fuse_chan_ops op = { 1235 | .receive = dev_fuse_chan_receive, 1236 | .send = dev_fuse_chan_send, 1237 | .destroy = dev_fuse_chan_destroy, 1238 | }; 1239 | 1240 | size_t bufsize = getpagesize() + 0x1000; 1241 | bufsize = bufsize < MIN_BUFSIZE ? MIN_BUFSIZE : bufsize; 1242 | return fuse_chan_new (&op, fd, bufsize, NULL); 1243 | } 1244 | 1245 | 1246 | static struct fuse_session *fuse_instance; 1247 | 1248 | static void 1249 | exit_handler (int sig) 1250 | { 1251 | __debug__ (("grootfs got signal %d", sig)); 1252 | 1253 | (void) sig; 1254 | if (fuse_instance) 1255 | fuse_session_exit (fuse_instance); 1256 | } 1257 | 1258 | static void 1259 | set_one_signal_handler (int sig, 1260 | void (*handler)(int), 1261 | int remove) 1262 | { 1263 | struct sigaction sa; 1264 | struct sigaction old_sa; 1265 | 1266 | memset (&sa, 0, sizeof(struct sigaction)); 1267 | sa.sa_handler = remove ? SIG_DFL : handler; 1268 | sigemptyset (&(sa.sa_mask)); 1269 | sa.sa_flags = 0; 1270 | 1271 | if (sigaction (sig, NULL, &old_sa) == -1) 1272 | die ("cannot get old signal handler"); 1273 | 1274 | if (old_sa.sa_handler == (remove ? handler : SIG_DFL) && 1275 | sigaction (sig, &sa, NULL) == -1) 1276 | die("cannot set signal handler"); 1277 | } 1278 | 1279 | static void 1280 | set_signal_handlers (struct fuse_session *se) 1281 | { 1282 | set_one_signal_handler (SIGHUP, exit_handler, 0); 1283 | set_one_signal_handler (SIGINT, exit_handler, 0); 1284 | set_one_signal_handler (SIGTERM, exit_handler, 0); 1285 | set_one_signal_handler (SIGPIPE, SIG_IGN, 0); 1286 | fuse_instance = se; 1287 | } 1288 | 1289 | int 1290 | start_grootfs_lowlevel (int dirfd, 1291 | int dev_fuse, 1292 | const char *mountpoint, 1293 | long max_uid, 1294 | long max_gid) 1295 | { 1296 | const char *argv[] = { mountpoint }; 1297 | struct fuse_args args = FUSE_ARGS_INIT(N_ELEMENTS (argv), (char **)argv); 1298 | int status_pipes[2]; 1299 | char pipe_buf = 'x'; 1300 | pid_t pid; 1301 | int res; 1302 | 1303 | if (pipe (status_pipes) != 0) 1304 | { 1305 | report ("failed to create status_pipes"); 1306 | return -1; 1307 | } 1308 | 1309 | pid = fork (); 1310 | if (pid == -1) 1311 | { 1312 | report ("failed to fork fuse process"); 1313 | return -1; 1314 | } 1315 | 1316 | if (pid != 0) 1317 | { 1318 | ssize_t s; 1319 | 1320 | close (status_pipes[1]); /* Close write side */ 1321 | close (dirfd); /* Not needed on this side */ 1322 | close (dev_fuse); /* Not needed on this side */ 1323 | 1324 | /* Wait for child process and report status */ 1325 | 1326 | do 1327 | s = read (status_pipes[0], &pipe_buf, 1); 1328 | while (s == -1 && errno == EINTR); 1329 | 1330 | if (s == -1) 1331 | { 1332 | report ("Failed to read grootfs pipe"); 1333 | return -1; 1334 | } 1335 | 1336 | if (s == 0) 1337 | { 1338 | // Short read, something failed and the child exited 1339 | // If known error we already printed something 1340 | return -1; 1341 | } 1342 | 1343 | return 0; 1344 | } 1345 | 1346 | /* Continue in child process */ 1347 | 1348 | close (status_pipes[0]); /* Close read side */ 1349 | 1350 | struct fuse_chan *ch = dev_fuse_chan_new (dev_fuse); 1351 | if (ch == NULL) 1352 | die ("Unable to create fuse channel"); 1353 | 1354 | GRootFS *fs = new_grootfs (dirfd, max_uid, max_gid); 1355 | struct fuse *fuse = fuse_new (ch, &args, &grootfs_oper, sizeof (grootfs_oper), fs); 1356 | 1357 | set_signal_handlers (fuse_get_session (fuse)); 1358 | 1359 | if (write (status_pipes[1], &pipe_buf, 1) < 0) 1360 | report ("Failed write to status pipe"); 1361 | 1362 | res = fuse_loop (fuse); 1363 | 1364 | /* Unmount even on failure */ 1365 | fuse_unmount (mountpoint, ch); 1366 | 1367 | if (res == -1) 1368 | die ("Error handling fuse requests"); 1369 | 1370 | fuse_destroy (fuse); 1371 | 1372 | __debug__ (("exiting grootfs")); 1373 | 1374 | exit (0); 1375 | } 1376 | -------------------------------------------------------------------------------- /grootfs.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 Alexander Larsson 3 | * 4 | * SPDX-License-Identifier: LGPL-2.0+ 5 | * 6 | * This library is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 2 of the License, or (at your option) any later version. 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this library; if not, write to the 18 | * Free Software Foundation, Inc., 59 Temple Place - Suite 330, 19 | * Boston, MA 02111-1307, USA. 20 | */ 21 | 22 | int start_grootfs (int argc, 23 | char *argv[], 24 | int dirfd); 25 | int start_grootfs_lowlevel (int dirfd, 26 | int dev_fuse, 27 | const char *mountpoint, 28 | long max_uid, 29 | long max_gid); 30 | -------------------------------------------------------------------------------- /utils.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 Alexander Larsson 3 | * 4 | * SPDX-License-Identifier: LGPL-2.0+ 5 | * 6 | * This library is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 2 of the License, or (at your option) any later version. 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this library; if not, write to the 18 | * Free Software Foundation, Inc., 59 Temple Place - Suite 330, 19 | * Boston, MA 02111-1307, USA. 20 | */ 21 | 22 | #include "utils.h" 23 | 24 | #include 25 | 26 | static bool debuglog_enabled = FALSE; 27 | 28 | void 29 | enable_debuglog (void) 30 | { 31 | debuglog_enabled = TRUE; 32 | } 33 | 34 | void 35 | debuglog (const char *format, ...) 36 | { 37 | va_list args; 38 | 39 | if (!debuglog_enabled) 40 | return; 41 | 42 | fprintf (stderr, "groot: "); 43 | 44 | va_start (args, format); 45 | vfprintf (stderr, format, args); 46 | va_end (args); 47 | 48 | fprintf (stderr, "\n"); 49 | } 50 | 51 | 52 | void 53 | report (const char *format, ...) 54 | { 55 | va_list args; 56 | 57 | fprintf (stderr, "groot: "); 58 | 59 | va_start (args, format); 60 | vfprintf (stderr, format, args); 61 | va_end (args); 62 | 63 | fprintf (stderr, "\n"); 64 | } 65 | 66 | void 67 | die_with_error (const char *format, ...) 68 | { 69 | va_list args; 70 | int errsv; 71 | 72 | errsv = errno; 73 | 74 | fprintf (stderr, "groot: "); 75 | 76 | va_start (args, format); 77 | vfprintf (stderr, format, args); 78 | va_end (args); 79 | 80 | fprintf (stderr, ": %s\n", strerror (errsv)); 81 | 82 | exit (1); 83 | } 84 | 85 | void 86 | die (const char *format, ...) 87 | { 88 | va_list args; 89 | 90 | fprintf (stderr, "groot: "); 91 | 92 | va_start (args, format); 93 | vfprintf (stderr, format, args); 94 | va_end (args); 95 | 96 | fprintf (stderr, "\n"); 97 | 98 | exit (1); 99 | } 100 | 101 | void 102 | die_oom (void) 103 | { 104 | fputs ("Out of memory\n", stderr); 105 | exit (1); 106 | } 107 | 108 | void * 109 | xmalloc (size_t size) 110 | { 111 | void *res = malloc (size); 112 | 113 | if (res == NULL) 114 | die_oom (); 115 | return res; 116 | } 117 | 118 | void * 119 | xcalloc (size_t size) 120 | { 121 | void *res = calloc (1, size); 122 | 123 | if (res == NULL) 124 | die_oom (); 125 | return res; 126 | } 127 | 128 | void * 129 | xrealloc (void *ptr, size_t size) 130 | { 131 | void *res = realloc (ptr, size); 132 | 133 | if (size != 0 && res == NULL) 134 | die_oom (); 135 | return res; 136 | } 137 | 138 | char * 139 | xstrdup (const char *str) 140 | { 141 | char *res; 142 | 143 | assert (str != NULL); 144 | 145 | res = strdup (str); 146 | if (res == NULL) 147 | die_oom (); 148 | 149 | return res; 150 | } 151 | 152 | 153 | void 154 | strfreev (char **str_array) 155 | { 156 | if (str_array) 157 | { 158 | int i; 159 | 160 | for (i = 0; str_array[i] != NULL; i++) 161 | free (str_array[i]); 162 | 163 | free (str_array); 164 | } 165 | } 166 | 167 | size_t 168 | strv_length (char **str_array) 169 | { 170 | size_t i = 0; 171 | 172 | if (str_array != NULL) 173 | { 174 | while (str_array[i]) 175 | ++i; 176 | } 177 | 178 | return i; 179 | } 180 | 181 | 182 | char * 183 | xasprintf (const char *format, 184 | ...) 185 | { 186 | char *buffer = NULL; 187 | va_list args; 188 | 189 | va_start (args, format); 190 | if (vasprintf (&buffer, format, args) == -1) 191 | die_oom (); 192 | va_end (args); 193 | 194 | return buffer; 195 | } 196 | 197 | int 198 | has_prefix (const char *str, 199 | const char *prefix) 200 | { 201 | return strncmp (str, prefix, strlen (prefix)) == 0; 202 | } 203 | 204 | /* Sets errno on error (== NULL), 205 | * Always ensures terminating zero */ 206 | char * 207 | load_file_data (int fd, 208 | size_t *size) 209 | { 210 | autofree char *data = NULL; 211 | ssize_t data_read; 212 | ssize_t data_len; 213 | ssize_t res; 214 | 215 | data_read = 0; 216 | data_len = 4080; 217 | data = xmalloc (data_len); 218 | 219 | do 220 | { 221 | if (data_len == data_read + 1) 222 | { 223 | data_len *= 2; 224 | data = xrealloc (data, data_len); 225 | } 226 | 227 | do 228 | res = read (fd, data + data_read, data_len - data_read - 1); 229 | while (res < 0 && errno == EINTR); 230 | 231 | if (res < 0) 232 | return NULL; 233 | 234 | data_read += res; 235 | } 236 | while (res > 0); 237 | 238 | data[data_read] = 0; 239 | 240 | if (size) 241 | *size = (size_t) data_read; 242 | 243 | return steal_pointer (&data); 244 | } 245 | 246 | /* Sets errno on error (== NULL), 247 | * Always ensures terminating zero */ 248 | char * 249 | load_file_at (int dirfd, 250 | const char *path) 251 | { 252 | int fd; 253 | char *data; 254 | int errsv; 255 | 256 | fd = openat (dirfd, path, O_CLOEXEC | O_RDONLY); 257 | if (fd == -1) 258 | return NULL; 259 | 260 | data = load_file_data (fd, NULL); 261 | 262 | errsv = errno; 263 | close (fd); 264 | errno = errsv; 265 | 266 | return data; 267 | 268 | } 269 | 270 | int 271 | send_fd (int socket, 272 | int fd) 273 | { 274 | struct msghdr msg = { 0 }; 275 | struct cmsghdr *cmsg; 276 | char iobuf[1]; 277 | struct iovec io = { 278 | .iov_base = iobuf, 279 | .iov_len = sizeof(iobuf) 280 | }; 281 | char buf[CMSG_ALIGN(CMSG_SPACE(sizeof(int)))]; 282 | 283 | msg.msg_iov = &io; 284 | msg.msg_iovlen = 1; 285 | msg.msg_control = buf; 286 | msg.msg_controllen = sizeof(buf); 287 | cmsg = CMSG_FIRSTHDR(&msg); 288 | cmsg->cmsg_level = SOL_SOCKET; 289 | cmsg->cmsg_type = SCM_RIGHTS; 290 | cmsg->cmsg_len = CMSG_LEN(sizeof(int)); 291 | memcpy(CMSG_DATA(cmsg), &fd, sizeof(int)); 292 | 293 | return sendmsg (socket, &msg, 0); 294 | } 295 | 296 | int 297 | recv_fd (int socket) 298 | { 299 | ssize_t res; 300 | char iobuf[1]; 301 | struct iovec io = { 302 | .iov_base = iobuf, 303 | .iov_len = sizeof(iobuf) 304 | }; 305 | char buf[CMSG_ALIGN(CMSG_SPACE(sizeof(int)))]; 306 | struct msghdr msg = { 307 | .msg_iov = &io, 308 | .msg_iovlen = 1, 309 | .msg_control = &buf, 310 | .msg_controllen = sizeof(buf), 311 | }; 312 | struct cmsghdr *cmsg; 313 | int received_fd = -1; 314 | 315 | res = recvmsg (socket, &msg, 0); 316 | if (res < 0) 317 | return -1; 318 | 319 | for (cmsg = CMSG_FIRSTHDR(&msg); 320 | cmsg != NULL; 321 | cmsg = CMSG_NXTHDR(&msg, cmsg)) 322 | { 323 | if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) 324 | { 325 | memcpy (&received_fd, CMSG_DATA(cmsg), sizeof(int)); 326 | break; 327 | } 328 | } 329 | 330 | if (received_fd < 0) 331 | { 332 | errno = ENOENT; 333 | return -1; 334 | } 335 | 336 | return received_fd; 337 | } 338 | -------------------------------------------------------------------------------- /utils.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 Alexander Larsson 3 | * 4 | * SPDX-License-Identifier: LGPL-2.0+ 5 | * 6 | * This library is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 2 of the License, or (at your option) any later version. 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this library; if not, write to the 18 | * Free Software Foundation, Inc., 59 Temple Place - Suite 330, 19 | * Boston, MA 02111-1307, USA. 20 | */ 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #define TRUE 1 34 | #define FALSE 0 35 | typedef int bool; 36 | 37 | #if 1 38 | #define __debug__(x) debuglog x 39 | #else 40 | #define __debug__(x) 41 | #endif 42 | 43 | #define UNUSED __attribute__((__unused__)) 44 | 45 | #define N_ELEMENTS(arr) (sizeof (arr) / sizeof ((arr)[0])) 46 | 47 | void die_with_error (const char *format, 48 | ...); 49 | void die (const char *format, 50 | ...); 51 | void report (const char *format, 52 | ...); 53 | void debuglog (const char *format, 54 | ...); 55 | void enable_debuglog (void); 56 | void die_oom (void); 57 | void * xmalloc (size_t size); 58 | void * xcalloc (size_t size); 59 | void * xrealloc (void *ptr, 60 | size_t size); 61 | void strfreev (char **str_array); 62 | size_t strv_length (char **str_array); 63 | char * xstrdup (const char *str); 64 | int has_prefix (const char *str, 65 | const char *prefix); 66 | char * xasprintf (const char *format, 67 | ...); 68 | char * load_file_data (int fd, 69 | size_t *size); 70 | char * load_file_at (int dirfd, 71 | const char *path); 72 | int send_fd (int socket, 73 | int fd); 74 | int recv_fd (int socket); 75 | 76 | static inline int 77 | steal_fd (int *fdp) 78 | { 79 | int fd = *fdp; 80 | *fdp = -1; 81 | return fd; 82 | } 83 | 84 | static inline void * 85 | steal_pointer (void *pp) 86 | { 87 | void **ptr = (void **) pp; 88 | void *ref; 89 | 90 | ref = *ptr; 91 | *ptr = NULL; 92 | 93 | return ref; 94 | } 95 | 96 | /* type safety */ 97 | #define steal_pointer(pp) \ 98 | (0 ? (*(pp)) : (steal_pointer) (pp)) 99 | 100 | 101 | static inline void 102 | cleanup_fdp (int *fdp) 103 | { 104 | int errsv; 105 | 106 | assert (fdp); 107 | 108 | int fd = steal_fd (fdp); 109 | if (fd >= 0) 110 | { 111 | errsv = errno; 112 | if (close (fd) < 0) 113 | assert (errno != EBADF); 114 | errno = errsv; 115 | } 116 | } 117 | 118 | static inline void 119 | cleanup_strvp (void *p) 120 | { 121 | void **pp = (void **) p; 122 | 123 | strfreev (*pp); 124 | } 125 | 126 | static inline void 127 | cleanup_freep (void *p) 128 | { 129 | void **pp = (void **) p; 130 | 131 | if (*pp) 132 | free (*pp); 133 | } 134 | 135 | #define _CLEANUP(func) __attribute__((cleanup(func))) 136 | 137 | #define autofd _CLEANUP(cleanup_fdp) 138 | #define autofree _CLEANUP(cleanup_freep) 139 | #define autofreev _CLEANUP(cleanup_strvp) 140 | 141 | #define _AUTOPTR_FUNC_NAME(TypeName) _autoptr_cleanup_##TypeName 142 | #define _AUTO_FUNC_NAME(TypeName) _auto_cleanup_##TypeName 143 | #define _AUTOPTR_TYPENAME(TypeName) TypeName##_autoptr 144 | 145 | #define DEFINE_AUTOPTR_CLEANUP_FUNC(TypeName, func) \ 146 | typedef TypeName *_AUTOPTR_TYPENAME(TypeName); \ 147 | static inline void _AUTOPTR_FUNC_NAME(TypeName) (TypeName **_ptr) { if (*_ptr) (func) (*_ptr); } 148 | 149 | #define DEFINE_AUTO_CLEANUP_CLEAR_FUNC(TypeName, func) \ 150 | static inline void _AUTO_FUNC_NAME(TypeName) (TypeName *_ptr) { (func) (_ptr); } 151 | 152 | #define autoptr(TypeName) _CLEANUP(_AUTOPTR_FUNC_NAME(TypeName)) _AUTOPTR_TYPENAME(TypeName) 153 | #define auto(TypeName) _CLEANUP(_AUTO_FUNC_NAME(TypeName)) TypeName 154 | --------------------------------------------------------------------------------