Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
| ... | ... |
@@ -16,6 +16,7 @@ golang.org/x/net 0de0cce0169b09b364e001f108dc |
| 16 | 16 |
golang.org/x/sys d5e6a3e2c0ae16fc7480523ebcb7fd4dd3215489 |
| 17 | 17 |
github.com/docker/go-units 519db1ee28dcc9fd2474ae59fca29a810482bfb1 # v0.4.0 |
| 18 | 18 |
github.com/docker/go-connections 7395e3f8aa162843a74ed6d48e79627d9792ac55 # v0.4.0 |
| 19 |
+github.com/moby/sys 6154f11e6840c0d6b0dbb23f4125a6134b3013c9 # mountinfo/v0.1.3 |
|
| 19 | 20 |
golang.org/x/text 342b2e1fbaa52c93f31447ad2c6abc048c63e475 # v0.3.2 |
| 20 | 21 |
gotest.tools/v3 bb0d8a963040ea5048dcef1a14d8f8b58a33d4b3 # v3.0.2 |
| 21 | 22 |
github.com/google/go-cmp 3af367b6b30c263d47e8895973edcca9a49cf029 # v0.2.0 |
| 22 | 23 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,202 @@ |
| 0 |
+ |
|
| 1 |
+ Apache License |
|
| 2 |
+ Version 2.0, January 2004 |
|
| 3 |
+ http://www.apache.org/licenses/ |
|
| 4 |
+ |
|
| 5 |
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION |
|
| 6 |
+ |
|
| 7 |
+ 1. Definitions. |
|
| 8 |
+ |
|
| 9 |
+ "License" shall mean the terms and conditions for use, reproduction, |
|
| 10 |
+ and distribution as defined by Sections 1 through 9 of this document. |
|
| 11 |
+ |
|
| 12 |
+ "Licensor" shall mean the copyright owner or entity authorized by |
|
| 13 |
+ the copyright owner that is granting the License. |
|
| 14 |
+ |
|
| 15 |
+ "Legal Entity" shall mean the union of the acting entity and all |
|
| 16 |
+ other entities that control, are controlled by, or are under common |
|
| 17 |
+ control with that entity. For the purposes of this definition, |
|
| 18 |
+ "control" means (i) the power, direct or indirect, to cause the |
|
| 19 |
+ direction or management of such entity, whether by contract or |
|
| 20 |
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the |
|
| 21 |
+ outstanding shares, or (iii) beneficial ownership of such entity. |
|
| 22 |
+ |
|
| 23 |
+ "You" (or "Your") shall mean an individual or Legal Entity |
|
| 24 |
+ exercising permissions granted by this License. |
|
| 25 |
+ |
|
| 26 |
+ "Source" form shall mean the preferred form for making modifications, |
|
| 27 |
+ including but not limited to software source code, documentation |
|
| 28 |
+ source, and configuration files. |
|
| 29 |
+ |
|
| 30 |
+ "Object" form shall mean any form resulting from mechanical |
|
| 31 |
+ transformation or translation of a Source form, including but |
|
| 32 |
+ not limited to compiled object code, generated documentation, |
|
| 33 |
+ and conversions to other media types. |
|
| 34 |
+ |
|
| 35 |
+ "Work" shall mean the work of authorship, whether in Source or |
|
| 36 |
+ Object form, made available under the License, as indicated by a |
|
| 37 |
+ copyright notice that is included in or attached to the work |
|
| 38 |
+ (an example is provided in the Appendix below). |
|
| 39 |
+ |
|
| 40 |
+ "Derivative Works" shall mean any work, whether in Source or Object |
|
| 41 |
+ form, that is based on (or derived from) the Work and for which the |
|
| 42 |
+ editorial revisions, annotations, elaborations, or other modifications |
|
| 43 |
+ represent, as a whole, an original work of authorship. For the purposes |
|
| 44 |
+ of this License, Derivative Works shall not include works that remain |
|
| 45 |
+ separable from, or merely link (or bind by name) to the interfaces of, |
|
| 46 |
+ the Work and Derivative Works thereof. |
|
| 47 |
+ |
|
| 48 |
+ "Contribution" shall mean any work of authorship, including |
|
| 49 |
+ the original version of the Work and any modifications or additions |
|
| 50 |
+ to that Work or Derivative Works thereof, that is intentionally |
|
| 51 |
+ submitted to Licensor for inclusion in the Work by the copyright owner |
|
| 52 |
+ or by an individual or Legal Entity authorized to submit on behalf of |
|
| 53 |
+ the copyright owner. For the purposes of this definition, "submitted" |
|
| 54 |
+ means any form of electronic, verbal, or written communication sent |
|
| 55 |
+ to the Licensor or its representatives, including but not limited to |
|
| 56 |
+ communication on electronic mailing lists, source code control systems, |
|
| 57 |
+ and issue tracking systems that are managed by, or on behalf of, the |
|
| 58 |
+ Licensor for the purpose of discussing and improving the Work, but |
|
| 59 |
+ excluding communication that is conspicuously marked or otherwise |
|
| 60 |
+ designated in writing by the copyright owner as "Not a Contribution." |
|
| 61 |
+ |
|
| 62 |
+ "Contributor" shall mean Licensor and any individual or Legal Entity |
|
| 63 |
+ on behalf of whom a Contribution has been received by Licensor and |
|
| 64 |
+ subsequently incorporated within the Work. |
|
| 65 |
+ |
|
| 66 |
+ 2. Grant of Copyright License. Subject to the terms and conditions of |
|
| 67 |
+ this License, each Contributor hereby grants to You a perpetual, |
|
| 68 |
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable |
|
| 69 |
+ copyright license to reproduce, prepare Derivative Works of, |
|
| 70 |
+ publicly display, publicly perform, sublicense, and distribute the |
|
| 71 |
+ Work and such Derivative Works in Source or Object form. |
|
| 72 |
+ |
|
| 73 |
+ 3. Grant of Patent License. Subject to the terms and conditions of |
|
| 74 |
+ this License, each Contributor hereby grants to You a perpetual, |
|
| 75 |
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable |
|
| 76 |
+ (except as stated in this section) patent license to make, have made, |
|
| 77 |
+ use, offer to sell, sell, import, and otherwise transfer the Work, |
|
| 78 |
+ where such license applies only to those patent claims licensable |
|
| 79 |
+ by such Contributor that are necessarily infringed by their |
|
| 80 |
+ Contribution(s) alone or by combination of their Contribution(s) |
|
| 81 |
+ with the Work to which such Contribution(s) was submitted. If You |
|
| 82 |
+ institute patent litigation against any entity (including a |
|
| 83 |
+ cross-claim or counterclaim in a lawsuit) alleging that the Work |
|
| 84 |
+ or a Contribution incorporated within the Work constitutes direct |
|
| 85 |
+ or contributory patent infringement, then any patent licenses |
|
| 86 |
+ granted to You under this License for that Work shall terminate |
|
| 87 |
+ as of the date such litigation is filed. |
|
| 88 |
+ |
|
| 89 |
+ 4. Redistribution. You may reproduce and distribute copies of the |
|
| 90 |
+ Work or Derivative Works thereof in any medium, with or without |
|
| 91 |
+ modifications, and in Source or Object form, provided that You |
|
| 92 |
+ meet the following conditions: |
|
| 93 |
+ |
|
| 94 |
+ (a) You must give any other recipients of the Work or |
|
| 95 |
+ Derivative Works a copy of this License; and |
|
| 96 |
+ |
|
| 97 |
+ (b) You must cause any modified files to carry prominent notices |
|
| 98 |
+ stating that You changed the files; and |
|
| 99 |
+ |
|
| 100 |
+ (c) You must retain, in the Source form of any Derivative Works |
|
| 101 |
+ that You distribute, all copyright, patent, trademark, and |
|
| 102 |
+ attribution notices from the Source form of the Work, |
|
| 103 |
+ excluding those notices that do not pertain to any part of |
|
| 104 |
+ the Derivative Works; and |
|
| 105 |
+ |
|
| 106 |
+ (d) If the Work includes a "NOTICE" text file as part of its |
|
| 107 |
+ distribution, then any Derivative Works that You distribute must |
|
| 108 |
+ include a readable copy of the attribution notices contained |
|
| 109 |
+ within such NOTICE file, excluding those notices that do not |
|
| 110 |
+ pertain to any part of the Derivative Works, in at least one |
|
| 111 |
+ of the following places: within a NOTICE text file distributed |
|
| 112 |
+ as part of the Derivative Works; within the Source form or |
|
| 113 |
+ documentation, if provided along with the Derivative Works; or, |
|
| 114 |
+ within a display generated by the Derivative Works, if and |
|
| 115 |
+ wherever such third-party notices normally appear. The contents |
|
| 116 |
+ of the NOTICE file are for informational purposes only and |
|
| 117 |
+ do not modify the License. You may add Your own attribution |
|
| 118 |
+ notices within Derivative Works that You distribute, alongside |
|
| 119 |
+ or as an addendum to the NOTICE text from the Work, provided |
|
| 120 |
+ that such additional attribution notices cannot be construed |
|
| 121 |
+ as modifying the License. |
|
| 122 |
+ |
|
| 123 |
+ You may add Your own copyright statement to Your modifications and |
|
| 124 |
+ may provide additional or different license terms and conditions |
|
| 125 |
+ for use, reproduction, or distribution of Your modifications, or |
|
| 126 |
+ for any such Derivative Works as a whole, provided Your use, |
|
| 127 |
+ reproduction, and distribution of the Work otherwise complies with |
|
| 128 |
+ the conditions stated in this License. |
|
| 129 |
+ |
|
| 130 |
+ 5. Submission of Contributions. Unless You explicitly state otherwise, |
|
| 131 |
+ any Contribution intentionally submitted for inclusion in the Work |
|
| 132 |
+ by You to the Licensor shall be under the terms and conditions of |
|
| 133 |
+ this License, without any additional terms or conditions. |
|
| 134 |
+ Notwithstanding the above, nothing herein shall supersede or modify |
|
| 135 |
+ the terms of any separate license agreement you may have executed |
|
| 136 |
+ with Licensor regarding such Contributions. |
|
| 137 |
+ |
|
| 138 |
+ 6. Trademarks. This License does not grant permission to use the trade |
|
| 139 |
+ names, trademarks, service marks, or product names of the Licensor, |
|
| 140 |
+ except as required for reasonable and customary use in describing the |
|
| 141 |
+ origin of the Work and reproducing the content of the NOTICE file. |
|
| 142 |
+ |
|
| 143 |
+ 7. Disclaimer of Warranty. Unless required by applicable law or |
|
| 144 |
+ agreed to in writing, Licensor provides the Work (and each |
|
| 145 |
+ Contributor provides its Contributions) on an "AS IS" BASIS, |
|
| 146 |
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or |
|
| 147 |
+ implied, including, without limitation, any warranties or conditions |
|
| 148 |
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A |
|
| 149 |
+ PARTICULAR PURPOSE. You are solely responsible for determining the |
|
| 150 |
+ appropriateness of using or redistributing the Work and assume any |
|
| 151 |
+ risks associated with Your exercise of permissions under this License. |
|
| 152 |
+ |
|
| 153 |
+ 8. Limitation of Liability. In no event and under no legal theory, |
|
| 154 |
+ whether in tort (including negligence), contract, or otherwise, |
|
| 155 |
+ unless required by applicable law (such as deliberate and grossly |
|
| 156 |
+ negligent acts) or agreed to in writing, shall any Contributor be |
|
| 157 |
+ liable to You for damages, including any direct, indirect, special, |
|
| 158 |
+ incidental, or consequential damages of any character arising as a |
|
| 159 |
+ result of this License or out of the use or inability to use the |
|
| 160 |
+ Work (including but not limited to damages for loss of goodwill, |
|
| 161 |
+ work stoppage, computer failure or malfunction, or any and all |
|
| 162 |
+ other commercial damages or losses), even if such Contributor |
|
| 163 |
+ has been advised of the possibility of such damages. |
|
| 164 |
+ |
|
| 165 |
+ 9. Accepting Warranty or Additional Liability. While redistributing |
|
| 166 |
+ the Work or Derivative Works thereof, You may choose to offer, |
|
| 167 |
+ and charge a fee for, acceptance of support, warranty, indemnity, |
|
| 168 |
+ or other liability obligations and/or rights consistent with this |
|
| 169 |
+ License. However, in accepting such obligations, You may act only |
|
| 170 |
+ on Your own behalf and on Your sole responsibility, not on behalf |
|
| 171 |
+ of any other Contributor, and only if You agree to indemnify, |
|
| 172 |
+ defend, and hold each Contributor harmless for any liability |
|
| 173 |
+ incurred by, or claims asserted against, such Contributor by reason |
|
| 174 |
+ of your accepting any such warranty or additional liability. |
|
| 175 |
+ |
|
| 176 |
+ END OF TERMS AND CONDITIONS |
|
| 177 |
+ |
|
| 178 |
+ APPENDIX: How to apply the Apache License to your work. |
|
| 179 |
+ |
|
| 180 |
+ To apply the Apache License to your work, attach the following |
|
| 181 |
+ boilerplate notice, with the fields enclosed by brackets "[]" |
|
| 182 |
+ replaced with your own identifying information. (Don't include |
|
| 183 |
+ the brackets!) The text should be enclosed in the appropriate |
|
| 184 |
+ comment syntax for the file format. We also recommend that a |
|
| 185 |
+ file or class name and description of purpose be included on the |
|
| 186 |
+ same "printed page" as the copyright notice for easier |
|
| 187 |
+ identification within third-party archives. |
|
| 188 |
+ |
|
| 189 |
+ Copyright [yyyy] [name of copyright owner] |
|
| 190 |
+ |
|
| 191 |
+ Licensed under the Apache License, Version 2.0 (the "License"); |
|
| 192 |
+ you may not use this file except in compliance with the License. |
|
| 193 |
+ You may obtain a copy of the License at |
|
| 194 |
+ |
|
| 195 |
+ http://www.apache.org/licenses/LICENSE-2.0 |
|
| 196 |
+ |
|
| 197 |
+ Unless required by applicable law or agreed to in writing, software |
|
| 198 |
+ distributed under the License is distributed on an "AS IS" BASIS, |
|
| 199 |
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
| 200 |
+ See the License for the specific language governing permissions and |
|
| 201 |
+ limitations under the License. |
| 0 | 202 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,137 @@ |
| 0 |
+package mount |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "strings" |
|
| 5 |
+) |
|
| 6 |
+ |
|
| 7 |
+var flags = map[string]struct {
|
|
| 8 |
+ clear bool |
|
| 9 |
+ flag int |
|
| 10 |
+}{
|
|
| 11 |
+ "defaults": {false, 0},
|
|
| 12 |
+ "ro": {false, RDONLY},
|
|
| 13 |
+ "rw": {true, RDONLY},
|
|
| 14 |
+ "suid": {true, NOSUID},
|
|
| 15 |
+ "nosuid": {false, NOSUID},
|
|
| 16 |
+ "dev": {true, NODEV},
|
|
| 17 |
+ "nodev": {false, NODEV},
|
|
| 18 |
+ "exec": {true, NOEXEC},
|
|
| 19 |
+ "noexec": {false, NOEXEC},
|
|
| 20 |
+ "sync": {false, SYNCHRONOUS},
|
|
| 21 |
+ "async": {true, SYNCHRONOUS},
|
|
| 22 |
+ "dirsync": {false, DIRSYNC},
|
|
| 23 |
+ "remount": {false, REMOUNT},
|
|
| 24 |
+ "mand": {false, MANDLOCK},
|
|
| 25 |
+ "nomand": {true, MANDLOCK},
|
|
| 26 |
+ "atime": {true, NOATIME},
|
|
| 27 |
+ "noatime": {false, NOATIME},
|
|
| 28 |
+ "diratime": {true, NODIRATIME},
|
|
| 29 |
+ "nodiratime": {false, NODIRATIME},
|
|
| 30 |
+ "bind": {false, BIND},
|
|
| 31 |
+ "rbind": {false, RBIND},
|
|
| 32 |
+ "unbindable": {false, UNBINDABLE},
|
|
| 33 |
+ "runbindable": {false, RUNBINDABLE},
|
|
| 34 |
+ "private": {false, PRIVATE},
|
|
| 35 |
+ "rprivate": {false, RPRIVATE},
|
|
| 36 |
+ "shared": {false, SHARED},
|
|
| 37 |
+ "rshared": {false, RSHARED},
|
|
| 38 |
+ "slave": {false, SLAVE},
|
|
| 39 |
+ "rslave": {false, RSLAVE},
|
|
| 40 |
+ "relatime": {false, RELATIME},
|
|
| 41 |
+ "norelatime": {true, RELATIME},
|
|
| 42 |
+ "strictatime": {false, STRICTATIME},
|
|
| 43 |
+ "nostrictatime": {true, STRICTATIME},
|
|
| 44 |
+} |
|
| 45 |
+ |
|
| 46 |
+var validFlags = map[string]bool{
|
|
| 47 |
+ "": true, |
|
| 48 |
+ "size": true, |
|
| 49 |
+ "mode": true, |
|
| 50 |
+ "uid": true, |
|
| 51 |
+ "gid": true, |
|
| 52 |
+ "nr_inodes": true, |
|
| 53 |
+ "nr_blocks": true, |
|
| 54 |
+ "mpol": true, |
|
| 55 |
+} |
|
| 56 |
+ |
|
| 57 |
+var propagationFlags = map[string]bool{
|
|
| 58 |
+ "bind": true, |
|
| 59 |
+ "rbind": true, |
|
| 60 |
+ "unbindable": true, |
|
| 61 |
+ "runbindable": true, |
|
| 62 |
+ "private": true, |
|
| 63 |
+ "rprivate": true, |
|
| 64 |
+ "shared": true, |
|
| 65 |
+ "rshared": true, |
|
| 66 |
+ "slave": true, |
|
| 67 |
+ "rslave": true, |
|
| 68 |
+} |
|
| 69 |
+ |
|
| 70 |
+// MergeTmpfsOptions merge mount options to make sure there is no duplicate. |
|
| 71 |
+func MergeTmpfsOptions(options []string) ([]string, error) {
|
|
| 72 |
+ // We use collisions maps to remove duplicates. |
|
| 73 |
+ // For flag, the key is the flag value (the key for propagation flag is -1) |
|
| 74 |
+ // For data=value, the key is the data |
|
| 75 |
+ flagCollisions := map[int]bool{}
|
|
| 76 |
+ dataCollisions := map[string]bool{}
|
|
| 77 |
+ |
|
| 78 |
+ var newOptions []string |
|
| 79 |
+ // We process in reverse order |
|
| 80 |
+ for i := len(options) - 1; i >= 0; i-- {
|
|
| 81 |
+ option := options[i] |
|
| 82 |
+ if option == "defaults" {
|
|
| 83 |
+ continue |
|
| 84 |
+ } |
|
| 85 |
+ if f, ok := flags[option]; ok && f.flag != 0 {
|
|
| 86 |
+ // There is only one propagation mode |
|
| 87 |
+ key := f.flag |
|
| 88 |
+ if propagationFlags[option] {
|
|
| 89 |
+ key = -1 |
|
| 90 |
+ } |
|
| 91 |
+ // Check to see if there is collision for flag |
|
| 92 |
+ if !flagCollisions[key] {
|
|
| 93 |
+ // We prepend the option and add to collision map |
|
| 94 |
+ newOptions = append([]string{option}, newOptions...)
|
|
| 95 |
+ flagCollisions[key] = true |
|
| 96 |
+ } |
|
| 97 |
+ continue |
|
| 98 |
+ } |
|
| 99 |
+ opt := strings.SplitN(option, "=", 2) |
|
| 100 |
+ if len(opt) != 2 || !validFlags[opt[0]] {
|
|
| 101 |
+ return nil, fmt.Errorf("Invalid tmpfs option %q", opt)
|
|
| 102 |
+ } |
|
| 103 |
+ if !dataCollisions[opt[0]] {
|
|
| 104 |
+ // We prepend the option and add to collision map |
|
| 105 |
+ newOptions = append([]string{option}, newOptions...)
|
|
| 106 |
+ dataCollisions[opt[0]] = true |
|
| 107 |
+ } |
|
| 108 |
+ } |
|
| 109 |
+ |
|
| 110 |
+ return newOptions, nil |
|
| 111 |
+} |
|
| 112 |
+ |
|
| 113 |
+// Parse fstab type mount options into mount() flags |
|
| 114 |
+// and device specific data |
|
| 115 |
+func parseOptions(options string) (int, string) {
|
|
| 116 |
+ var ( |
|
| 117 |
+ flag int |
|
| 118 |
+ data []string |
|
| 119 |
+ ) |
|
| 120 |
+ |
|
| 121 |
+ for _, o := range strings.Split(options, ",") {
|
|
| 122 |
+ // If the option does not exist in the flags table or the flag |
|
| 123 |
+ // is not supported on the platform, |
|
| 124 |
+ // then it is a data value for a specific fs type |
|
| 125 |
+ if f, exists := flags[o]; exists && f.flag != 0 {
|
|
| 126 |
+ if f.clear {
|
|
| 127 |
+ flag &= ^f.flag |
|
| 128 |
+ } else {
|
|
| 129 |
+ flag |= f.flag |
|
| 130 |
+ } |
|
| 131 |
+ } else {
|
|
| 132 |
+ data = append(data, o) |
|
| 133 |
+ } |
|
| 134 |
+ } |
|
| 135 |
+ return flag, strings.Join(data, ",") |
|
| 136 |
+} |
| 0 | 137 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,48 @@ |
| 0 |
+// +build freebsd,cgo |
|
| 1 |
+ |
|
| 2 |
+package mount |
|
| 3 |
+ |
|
| 4 |
+/* |
|
| 5 |
+#include <sys/mount.h> |
|
| 6 |
+*/ |
|
| 7 |
+import "C" |
|
| 8 |
+ |
|
| 9 |
+const ( |
|
| 10 |
+ // RDONLY will mount the filesystem as read-only. |
|
| 11 |
+ RDONLY = C.MNT_RDONLY |
|
| 12 |
+ |
|
| 13 |
+ // NOSUID will not allow set-user-identifier or set-group-identifier bits to |
|
| 14 |
+ // take effect. |
|
| 15 |
+ NOSUID = C.MNT_NOSUID |
|
| 16 |
+ |
|
| 17 |
+ // NOEXEC will not allow execution of any binaries on the mounted file system. |
|
| 18 |
+ NOEXEC = C.MNT_NOEXEC |
|
| 19 |
+ |
|
| 20 |
+ // SYNCHRONOUS will allow any I/O to the file system to be done synchronously. |
|
| 21 |
+ SYNCHRONOUS = C.MNT_SYNCHRONOUS |
|
| 22 |
+ |
|
| 23 |
+ // NOATIME will not update the file access time when reading from a file. |
|
| 24 |
+ NOATIME = C.MNT_NOATIME |
|
| 25 |
+) |
|
| 26 |
+ |
|
| 27 |
+// These flags are unsupported. |
|
| 28 |
+const ( |
|
| 29 |
+ BIND = 0 |
|
| 30 |
+ DIRSYNC = 0 |
|
| 31 |
+ MANDLOCK = 0 |
|
| 32 |
+ NODEV = 0 |
|
| 33 |
+ NODIRATIME = 0 |
|
| 34 |
+ UNBINDABLE = 0 |
|
| 35 |
+ RUNBINDABLE = 0 |
|
| 36 |
+ PRIVATE = 0 |
|
| 37 |
+ RPRIVATE = 0 |
|
| 38 |
+ SHARED = 0 |
|
| 39 |
+ RSHARED = 0 |
|
| 40 |
+ SLAVE = 0 |
|
| 41 |
+ RSLAVE = 0 |
|
| 42 |
+ RBIND = 0 |
|
| 43 |
+ RELATIME = 0 |
|
| 44 |
+ REMOUNT = 0 |
|
| 45 |
+ STRICTATIME = 0 |
|
| 46 |
+ mntDetach = 0 |
|
| 47 |
+) |
| 0 | 48 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,87 @@ |
| 0 |
+package mount |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "golang.org/x/sys/unix" |
|
| 4 |
+) |
|
| 5 |
+ |
|
| 6 |
+const ( |
|
| 7 |
+ // RDONLY will mount the file system read-only. |
|
| 8 |
+ RDONLY = unix.MS_RDONLY |
|
| 9 |
+ |
|
| 10 |
+ // NOSUID will not allow set-user-identifier or set-group-identifier bits to |
|
| 11 |
+ // take effect. |
|
| 12 |
+ NOSUID = unix.MS_NOSUID |
|
| 13 |
+ |
|
| 14 |
+ // NODEV will not interpret character or block special devices on the file |
|
| 15 |
+ // system. |
|
| 16 |
+ NODEV = unix.MS_NODEV |
|
| 17 |
+ |
|
| 18 |
+ // NOEXEC will not allow execution of any binaries on the mounted file system. |
|
| 19 |
+ NOEXEC = unix.MS_NOEXEC |
|
| 20 |
+ |
|
| 21 |
+ // SYNCHRONOUS will allow I/O to the file system to be done synchronously. |
|
| 22 |
+ SYNCHRONOUS = unix.MS_SYNCHRONOUS |
|
| 23 |
+ |
|
| 24 |
+ // DIRSYNC will force all directory updates within the file system to be done |
|
| 25 |
+ // synchronously. This affects the following system calls: create, link, |
|
| 26 |
+ // unlink, symlink, mkdir, rmdir, mknod and rename. |
|
| 27 |
+ DIRSYNC = unix.MS_DIRSYNC |
|
| 28 |
+ |
|
| 29 |
+ // REMOUNT will attempt to remount an already-mounted file system. This is |
|
| 30 |
+ // commonly used to change the mount flags for a file system, especially to |
|
| 31 |
+ // make a readonly file system writeable. It does not change device or mount |
|
| 32 |
+ // point. |
|
| 33 |
+ REMOUNT = unix.MS_REMOUNT |
|
| 34 |
+ |
|
| 35 |
+ // MANDLOCK will force mandatory locks on a filesystem. |
|
| 36 |
+ MANDLOCK = unix.MS_MANDLOCK |
|
| 37 |
+ |
|
| 38 |
+ // NOATIME will not update the file access time when reading from a file. |
|
| 39 |
+ NOATIME = unix.MS_NOATIME |
|
| 40 |
+ |
|
| 41 |
+ // NODIRATIME will not update the directory access time. |
|
| 42 |
+ NODIRATIME = unix.MS_NODIRATIME |
|
| 43 |
+ |
|
| 44 |
+ // BIND remounts a subtree somewhere else. |
|
| 45 |
+ BIND = unix.MS_BIND |
|
| 46 |
+ |
|
| 47 |
+ // RBIND remounts a subtree and all possible submounts somewhere else. |
|
| 48 |
+ RBIND = unix.MS_BIND | unix.MS_REC |
|
| 49 |
+ |
|
| 50 |
+ // UNBINDABLE creates a mount which cannot be cloned through a bind operation. |
|
| 51 |
+ UNBINDABLE = unix.MS_UNBINDABLE |
|
| 52 |
+ |
|
| 53 |
+ // RUNBINDABLE marks the entire mount tree as UNBINDABLE. |
|
| 54 |
+ RUNBINDABLE = unix.MS_UNBINDABLE | unix.MS_REC |
|
| 55 |
+ |
|
| 56 |
+ // PRIVATE creates a mount which carries no propagation abilities. |
|
| 57 |
+ PRIVATE = unix.MS_PRIVATE |
|
| 58 |
+ |
|
| 59 |
+ // RPRIVATE marks the entire mount tree as PRIVATE. |
|
| 60 |
+ RPRIVATE = unix.MS_PRIVATE | unix.MS_REC |
|
| 61 |
+ |
|
| 62 |
+ // SLAVE creates a mount which receives propagation from its master, but not |
|
| 63 |
+ // vice versa. |
|
| 64 |
+ SLAVE = unix.MS_SLAVE |
|
| 65 |
+ |
|
| 66 |
+ // RSLAVE marks the entire mount tree as SLAVE. |
|
| 67 |
+ RSLAVE = unix.MS_SLAVE | unix.MS_REC |
|
| 68 |
+ |
|
| 69 |
+ // SHARED creates a mount which provides the ability to create mirrors of |
|
| 70 |
+ // that mount such that mounts and unmounts within any of the mirrors |
|
| 71 |
+ // propagate to the other mirrors. |
|
| 72 |
+ SHARED = unix.MS_SHARED |
|
| 73 |
+ |
|
| 74 |
+ // RSHARED marks the entire mount tree as SHARED. |
|
| 75 |
+ RSHARED = unix.MS_SHARED | unix.MS_REC |
|
| 76 |
+ |
|
| 77 |
+ // RELATIME updates inode access times relative to modify or change time. |
|
| 78 |
+ RELATIME = unix.MS_RELATIME |
|
| 79 |
+ |
|
| 80 |
+ // STRICTATIME allows to explicitly request full atime updates. This makes |
|
| 81 |
+ // it possible for the kernel to default to relatime or noatime but still |
|
| 82 |
+ // allow userspace to override it. |
|
| 83 |
+ STRICTATIME = unix.MS_STRICTATIME |
|
| 84 |
+ |
|
| 85 |
+ mntDetach = unix.MNT_DETACH |
|
| 86 |
+) |
| 0 | 87 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,30 @@ |
| 0 |
+// +build !linux,!freebsd freebsd,!cgo |
|
| 1 |
+ |
|
| 2 |
+package mount |
|
| 3 |
+ |
|
| 4 |
+// These flags are unsupported. |
|
| 5 |
+const ( |
|
| 6 |
+ BIND = 0 |
|
| 7 |
+ DIRSYNC = 0 |
|
| 8 |
+ MANDLOCK = 0 |
|
| 9 |
+ NOATIME = 0 |
|
| 10 |
+ NODEV = 0 |
|
| 11 |
+ NODIRATIME = 0 |
|
| 12 |
+ NOEXEC = 0 |
|
| 13 |
+ NOSUID = 0 |
|
| 14 |
+ UNBINDABLE = 0 |
|
| 15 |
+ RUNBINDABLE = 0 |
|
| 16 |
+ PRIVATE = 0 |
|
| 17 |
+ RPRIVATE = 0 |
|
| 18 |
+ SHARED = 0 |
|
| 19 |
+ RSHARED = 0 |
|
| 20 |
+ SLAVE = 0 |
|
| 21 |
+ RSLAVE = 0 |
|
| 22 |
+ RBIND = 0 |
|
| 23 |
+ RELATIME = 0 |
|
| 24 |
+ REMOUNT = 0 |
|
| 25 |
+ STRICTATIME = 0 |
|
| 26 |
+ SYNCHRONOUS = 0 |
|
| 27 |
+ RDONLY = 0 |
|
| 28 |
+ mntDetach = 0 |
|
| 29 |
+) |
| 0 | 8 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,56 @@ |
| 0 |
+// +build go1.13 |
|
| 1 |
+ |
|
| 2 |
+package mount |
|
| 3 |
+ |
|
| 4 |
+import ( |
|
| 5 |
+ "fmt" |
|
| 6 |
+ "sort" |
|
| 7 |
+ |
|
| 8 |
+ "github.com/moby/sys/mountinfo" |
|
| 9 |
+) |
|
| 10 |
+ |
|
| 11 |
+// Mount will mount filesystem according to the specified configuration. |
|
| 12 |
+// Options must be specified like the mount or fstab unix commands: |
|
| 13 |
+// "opt1=val1,opt2=val2". See flags.go for supported option flags. |
|
| 14 |
+func Mount(device, target, mType, options string) error {
|
|
| 15 |
+ flag, data := parseOptions(options) |
|
| 16 |
+ return mount(device, target, mType, uintptr(flag), data) |
|
| 17 |
+} |
|
| 18 |
+ |
|
| 19 |
+// Unmount lazily unmounts a filesystem on supported platforms, otherwise |
|
| 20 |
+// does a normal unmount. |
|
| 21 |
+func Unmount(target string) error {
|
|
| 22 |
+ return unmount(target, mntDetach) |
|
| 23 |
+} |
|
| 24 |
+ |
|
| 25 |
+// RecursiveUnmount unmounts the target and all mounts underneath, starting with |
|
| 26 |
+// the deepsest mount first. |
|
| 27 |
+func RecursiveUnmount(target string) error {
|
|
| 28 |
+ mounts, err := mountinfo.GetMounts(mountinfo.PrefixFilter(target)) |
|
| 29 |
+ if err != nil {
|
|
| 30 |
+ return err |
|
| 31 |
+ } |
|
| 32 |
+ |
|
| 33 |
+ // Make the deepest mount be first |
|
| 34 |
+ sort.Slice(mounts, func(i, j int) bool {
|
|
| 35 |
+ return len(mounts[i].Mountpoint) > len(mounts[j].Mountpoint) |
|
| 36 |
+ }) |
|
| 37 |
+ |
|
| 38 |
+ var suberr error |
|
| 39 |
+ for i, m := range mounts {
|
|
| 40 |
+ err = unmount(m.Mountpoint, mntDetach) |
|
| 41 |
+ if err != nil {
|
|
| 42 |
+ if i == len(mounts)-1 { // last mount
|
|
| 43 |
+ return fmt.Errorf("%w (possible cause: %s)", err, suberr)
|
|
| 44 |
+ } |
|
| 45 |
+ // This is a submount, we can ignore the error for now, |
|
| 46 |
+ // the final unmount will fail if this is a real problem. |
|
| 47 |
+ // With that in mind, the _first_ failed unmount error |
|
| 48 |
+ // might be the real error cause, so let's keep it. |
|
| 49 |
+ if suberr == nil {
|
|
| 50 |
+ suberr = err |
|
| 51 |
+ } |
|
| 52 |
+ } |
|
| 53 |
+ } |
|
| 54 |
+ return nil |
|
| 55 |
+} |
| 0 | 56 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,46 @@ |
| 0 |
+// +build !windows |
|
| 1 |
+ |
|
| 2 |
+package mount |
|
| 3 |
+ |
|
| 4 |
+import "strconv" |
|
| 5 |
+ |
|
| 6 |
+// mountError records an error from mount or unmount operation |
|
| 7 |
+type mountError struct {
|
|
| 8 |
+ op string |
|
| 9 |
+ source, target string |
|
| 10 |
+ flags uintptr |
|
| 11 |
+ data string |
|
| 12 |
+ err error |
|
| 13 |
+} |
|
| 14 |
+ |
|
| 15 |
+func (e *mountError) Error() string {
|
|
| 16 |
+ out := e.op + " " |
|
| 17 |
+ |
|
| 18 |
+ if e.source != "" {
|
|
| 19 |
+ out += e.source + ":" + e.target |
|
| 20 |
+ } else {
|
|
| 21 |
+ out += e.target |
|
| 22 |
+ } |
|
| 23 |
+ |
|
| 24 |
+ if e.flags != uintptr(0) {
|
|
| 25 |
+ out += ", flags: 0x" + strconv.FormatUint(uint64(e.flags), 16) |
|
| 26 |
+ } |
|
| 27 |
+ if e.data != "" {
|
|
| 28 |
+ out += ", data: " + e.data |
|
| 29 |
+ } |
|
| 30 |
+ |
|
| 31 |
+ out += ": " + e.err.Error() |
|
| 32 |
+ return out |
|
| 33 |
+} |
|
| 34 |
+ |
|
| 35 |
+// Cause returns the underlying cause of the error. |
|
| 36 |
+// This is a convention used in github.com/pkg/errors |
|
| 37 |
+func (e *mountError) Cause() error {
|
|
| 38 |
+ return e.err |
|
| 39 |
+} |
|
| 40 |
+ |
|
| 41 |
+// Unwrap returns the underlying error. |
|
| 42 |
+// This is a convention used in golang 1.13+ |
|
| 43 |
+func (e *mountError) Unwrap() error {
|
|
| 44 |
+ return e.err |
|
| 45 |
+} |
| 0 | 46 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,59 @@ |
| 0 |
+package mount |
|
| 1 |
+ |
|
| 2 |
+/* |
|
| 3 |
+#include <errno.h> |
|
| 4 |
+#include <stdlib.h> |
|
| 5 |
+#include <string.h> |
|
| 6 |
+#include <sys/_iovec.h> |
|
| 7 |
+#include <sys/mount.h> |
|
| 8 |
+#include <sys/param.h> |
|
| 9 |
+*/ |
|
| 10 |
+import "C" |
|
| 11 |
+ |
|
| 12 |
+import ( |
|
| 13 |
+ "strings" |
|
| 14 |
+ "syscall" |
|
| 15 |
+ "unsafe" |
|
| 16 |
+) |
|
| 17 |
+ |
|
| 18 |
+func allocateIOVecs(options []string) []C.struct_iovec {
|
|
| 19 |
+ out := make([]C.struct_iovec, len(options)) |
|
| 20 |
+ for i, option := range options {
|
|
| 21 |
+ out[i].iov_base = unsafe.Pointer(C.CString(option)) |
|
| 22 |
+ out[i].iov_len = C.size_t(len(option) + 1) |
|
| 23 |
+ } |
|
| 24 |
+ return out |
|
| 25 |
+} |
|
| 26 |
+ |
|
| 27 |
+func mount(device, target, mType string, flag uintptr, data string) error {
|
|
| 28 |
+ isNullFS := false |
|
| 29 |
+ |
|
| 30 |
+ xs := strings.Split(data, ",") |
|
| 31 |
+ for _, x := range xs {
|
|
| 32 |
+ if x == "bind" {
|
|
| 33 |
+ isNullFS = true |
|
| 34 |
+ } |
|
| 35 |
+ } |
|
| 36 |
+ |
|
| 37 |
+ options := []string{"fspath", target}
|
|
| 38 |
+ if isNullFS {
|
|
| 39 |
+ options = append(options, "fstype", "nullfs", "target", device) |
|
| 40 |
+ } else {
|
|
| 41 |
+ options = append(options, "fstype", mType, "from", device) |
|
| 42 |
+ } |
|
| 43 |
+ rawOptions := allocateIOVecs(options) |
|
| 44 |
+ for _, rawOption := range rawOptions {
|
|
| 45 |
+ defer C.free(rawOption.iov_base) |
|
| 46 |
+ } |
|
| 47 |
+ |
|
| 48 |
+ if errno := C.nmount(&rawOptions[0], C.uint(len(options)), C.int(flag)); errno != 0 {
|
|
| 49 |
+ return &mountError{
|
|
| 50 |
+ op: "mount", |
|
| 51 |
+ source: device, |
|
| 52 |
+ target: target, |
|
| 53 |
+ flags: flag, |
|
| 54 |
+ err: syscall.Errno(errno), |
|
| 55 |
+ } |
|
| 56 |
+ } |
|
| 57 |
+ return nil |
|
| 58 |
+} |
| 0 | 59 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,73 @@ |
| 0 |
+package mount |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "golang.org/x/sys/unix" |
|
| 4 |
+) |
|
| 5 |
+ |
|
| 6 |
+const ( |
|
| 7 |
+ // ptypes is the set propagation types. |
|
| 8 |
+ ptypes = unix.MS_SHARED | unix.MS_PRIVATE | unix.MS_SLAVE | unix.MS_UNBINDABLE |
|
| 9 |
+ |
|
| 10 |
+ // pflags is the full set valid flags for a change propagation call. |
|
| 11 |
+ pflags = ptypes | unix.MS_REC | unix.MS_SILENT |
|
| 12 |
+ |
|
| 13 |
+ // broflags is the combination of bind and read only |
|
| 14 |
+ broflags = unix.MS_BIND | unix.MS_RDONLY |
|
| 15 |
+) |
|
| 16 |
+ |
|
| 17 |
+// isremount returns true if either device name or flags identify a remount request, false otherwise. |
|
| 18 |
+func isremount(device string, flags uintptr) bool {
|
|
| 19 |
+ switch {
|
|
| 20 |
+ // We treat device "" and "none" as a remount request to provide compatibility with |
|
| 21 |
+ // requests that don't explicitly set MS_REMOUNT such as those manipulating bind mounts. |
|
| 22 |
+ case flags&unix.MS_REMOUNT != 0, device == "", device == "none": |
|
| 23 |
+ return true |
|
| 24 |
+ default: |
|
| 25 |
+ return false |
|
| 26 |
+ } |
|
| 27 |
+} |
|
| 28 |
+ |
|
| 29 |
+func mount(device, target, mType string, flags uintptr, data string) error {
|
|
| 30 |
+ oflags := flags &^ ptypes |
|
| 31 |
+ if !isremount(device, flags) || data != "" {
|
|
| 32 |
+ // Initial call applying all non-propagation flags for mount |
|
| 33 |
+ // or remount with changed data |
|
| 34 |
+ if err := unix.Mount(device, target, mType, oflags, data); err != nil {
|
|
| 35 |
+ return &mountError{
|
|
| 36 |
+ op: "mount", |
|
| 37 |
+ source: device, |
|
| 38 |
+ target: target, |
|
| 39 |
+ flags: oflags, |
|
| 40 |
+ data: data, |
|
| 41 |
+ err: err, |
|
| 42 |
+ } |
|
| 43 |
+ } |
|
| 44 |
+ } |
|
| 45 |
+ |
|
| 46 |
+ if flags&ptypes != 0 {
|
|
| 47 |
+ // Change the propagation type. |
|
| 48 |
+ if err := unix.Mount("", target, "", flags&pflags, ""); err != nil {
|
|
| 49 |
+ return &mountError{
|
|
| 50 |
+ op: "remount", |
|
| 51 |
+ target: target, |
|
| 52 |
+ flags: flags & pflags, |
|
| 53 |
+ err: err, |
|
| 54 |
+ } |
|
| 55 |
+ } |
|
| 56 |
+ } |
|
| 57 |
+ |
|
| 58 |
+ if oflags&broflags == broflags {
|
|
| 59 |
+ // Remount the bind to apply read only. |
|
| 60 |
+ if err := unix.Mount("", target, "", oflags|unix.MS_REMOUNT, ""); err != nil {
|
|
| 61 |
+ return &mountError{
|
|
| 62 |
+ op: "remount-ro", |
|
| 63 |
+ target: target, |
|
| 64 |
+ flags: oflags | unix.MS_REMOUNT, |
|
| 65 |
+ err: err, |
|
| 66 |
+ } |
|
| 67 |
+ |
|
| 68 |
+ } |
|
| 69 |
+ } |
|
| 70 |
+ |
|
| 71 |
+ return nil |
|
| 72 |
+} |
| 0 | 7 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,73 @@ |
| 0 |
+package mount |
|
| 1 |
+ |
|
| 2 |
+import "github.com/moby/sys/mountinfo" |
|
| 3 |
+ |
|
| 4 |
+// MakeShared ensures a mounted filesystem has the SHARED mount option enabled. |
|
| 5 |
+// See the supported options in flags.go for further reference. |
|
| 6 |
+func MakeShared(mountPoint string) error {
|
|
| 7 |
+ return ensureMountedAs(mountPoint, SHARED) |
|
| 8 |
+} |
|
| 9 |
+ |
|
| 10 |
+// MakeRShared ensures a mounted filesystem has the RSHARED mount option enabled. |
|
| 11 |
+// See the supported options in flags.go for further reference. |
|
| 12 |
+func MakeRShared(mountPoint string) error {
|
|
| 13 |
+ return ensureMountedAs(mountPoint, RSHARED) |
|
| 14 |
+} |
|
| 15 |
+ |
|
| 16 |
+// MakePrivate ensures a mounted filesystem has the PRIVATE mount option enabled. |
|
| 17 |
+// See the supported options in flags.go for further reference. |
|
| 18 |
+func MakePrivate(mountPoint string) error {
|
|
| 19 |
+ return ensureMountedAs(mountPoint, PRIVATE) |
|
| 20 |
+} |
|
| 21 |
+ |
|
| 22 |
+// MakeRPrivate ensures a mounted filesystem has the RPRIVATE mount option |
|
| 23 |
+// enabled. See the supported options in flags.go for further reference. |
|
| 24 |
+func MakeRPrivate(mountPoint string) error {
|
|
| 25 |
+ return ensureMountedAs(mountPoint, RPRIVATE) |
|
| 26 |
+} |
|
| 27 |
+ |
|
| 28 |
+// MakeSlave ensures a mounted filesystem has the SLAVE mount option enabled. |
|
| 29 |
+// See the supported options in flags.go for further reference. |
|
| 30 |
+func MakeSlave(mountPoint string) error {
|
|
| 31 |
+ return ensureMountedAs(mountPoint, SLAVE) |
|
| 32 |
+} |
|
| 33 |
+ |
|
| 34 |
+// MakeRSlave ensures a mounted filesystem has the RSLAVE mount option enabled. |
|
| 35 |
+// See the supported options in flags.go for further reference. |
|
| 36 |
+func MakeRSlave(mountPoint string) error {
|
|
| 37 |
+ return ensureMountedAs(mountPoint, RSLAVE) |
|
| 38 |
+} |
|
| 39 |
+ |
|
| 40 |
+// MakeUnbindable ensures a mounted filesystem has the UNBINDABLE mount option |
|
| 41 |
+// enabled. See the supported options in flags.go for further reference. |
|
| 42 |
+func MakeUnbindable(mountPoint string) error {
|
|
| 43 |
+ return ensureMountedAs(mountPoint, UNBINDABLE) |
|
| 44 |
+} |
|
| 45 |
+ |
|
| 46 |
+// MakeRUnbindable ensures a mounted filesystem has the RUNBINDABLE mount |
|
| 47 |
+// option enabled. See the supported options in flags.go for further reference. |
|
| 48 |
+func MakeRUnbindable(mountPoint string) error {
|
|
| 49 |
+ return ensureMountedAs(mountPoint, RUNBINDABLE) |
|
| 50 |
+} |
|
| 51 |
+ |
|
| 52 |
+// MakeMount ensures that the file or directory given is a mount point, |
|
| 53 |
+// bind mounting it to itself it case it is not. |
|
| 54 |
+func MakeMount(mnt string) error {
|
|
| 55 |
+ mounted, err := mountinfo.Mounted(mnt) |
|
| 56 |
+ if err != nil {
|
|
| 57 |
+ return err |
|
| 58 |
+ } |
|
| 59 |
+ if mounted {
|
|
| 60 |
+ return nil |
|
| 61 |
+ } |
|
| 62 |
+ |
|
| 63 |
+ return mount(mnt, mnt, "none", uintptr(BIND), "") |
|
| 64 |
+} |
|
| 65 |
+ |
|
| 66 |
+func ensureMountedAs(mnt string, flags int) error {
|
|
| 67 |
+ if err := MakeMount(mnt); err != nil {
|
|
| 68 |
+ return err |
|
| 69 |
+ } |
|
| 70 |
+ |
|
| 71 |
+ return mount("", mnt, "none", uintptr(flags), "")
|
|
| 72 |
+} |
| 0 | 73 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,22 @@ |
| 0 |
+// +build !windows |
|
| 1 |
+ |
|
| 2 |
+package mount |
|
| 3 |
+ |
|
| 4 |
+import "golang.org/x/sys/unix" |
|
| 5 |
+ |
|
| 6 |
+func unmount(target string, flags int) error {
|
|
| 7 |
+ err := unix.Unmount(target, flags) |
|
| 8 |
+ if err == nil || err == unix.EINVAL {
|
|
| 9 |
+ // Ignore "not mounted" error here. Note the same error |
|
| 10 |
+ // can be returned if flags are invalid, so this code |
|
| 11 |
+ // assumes that the flags value is always correct. |
|
| 12 |
+ return nil |
|
| 13 |
+ } |
|
| 14 |
+ |
|
| 15 |
+ return &mountError{
|
|
| 16 |
+ op: "umount", |
|
| 17 |
+ target: target, |
|
| 18 |
+ flags: uintptr(flags), |
|
| 19 |
+ err: err, |
|
| 20 |
+ } |
|
| 21 |
+} |
| 0 | 3 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,67 @@ |
| 0 |
+package mountinfo |
|
| 1 |
+ |
|
| 2 |
+import "io" |
|
| 3 |
+ |
|
| 4 |
+// GetMounts retrieves a list of mounts for the current running process, |
|
| 5 |
+// with an optional filter applied (use nil for no filter). |
|
| 6 |
+func GetMounts(f FilterFunc) ([]*Info, error) {
|
|
| 7 |
+ return parseMountTable(f) |
|
| 8 |
+} |
|
| 9 |
+ |
|
| 10 |
+// GetMountsFromReader retrieves a list of mounts from the |
|
| 11 |
+// reader provided, with an optional filter applied (use nil |
|
| 12 |
+// for no filter). This can be useful in tests or benchmarks |
|
| 13 |
+// that provide a fake mountinfo data. |
|
| 14 |
+func GetMountsFromReader(reader io.Reader, f FilterFunc) ([]*Info, error) {
|
|
| 15 |
+ return parseInfoFile(reader, f) |
|
| 16 |
+} |
|
| 17 |
+ |
|
| 18 |
+// Mounted determines if a specified mountpoint has been mounted. |
|
| 19 |
+// On Linux it looks at /proc/self/mountinfo. |
|
| 20 |
+func Mounted(mountpoint string) (bool, error) {
|
|
| 21 |
+ entries, err := GetMounts(SingleEntryFilter(mountpoint)) |
|
| 22 |
+ if err != nil {
|
|
| 23 |
+ return false, err |
|
| 24 |
+ } |
|
| 25 |
+ |
|
| 26 |
+ return len(entries) > 0, nil |
|
| 27 |
+} |
|
| 28 |
+ |
|
| 29 |
+// Info reveals information about a particular mounted filesystem. This |
|
| 30 |
+// struct is populated from the content in the /proc/<pid>/mountinfo file. |
|
| 31 |
+type Info struct {
|
|
| 32 |
+ // ID is a unique identifier of the mount (may be reused after umount). |
|
| 33 |
+ ID int |
|
| 34 |
+ |
|
| 35 |
+ // Parent indicates the ID of the mount parent (or of self for the top of the |
|
| 36 |
+ // mount tree). |
|
| 37 |
+ Parent int |
|
| 38 |
+ |
|
| 39 |
+ // Major indicates one half of the device ID which identifies the device class. |
|
| 40 |
+ Major int |
|
| 41 |
+ |
|
| 42 |
+ // Minor indicates one half of the device ID which identifies a specific |
|
| 43 |
+ // instance of device. |
|
| 44 |
+ Minor int |
|
| 45 |
+ |
|
| 46 |
+ // Root of the mount within the filesystem. |
|
| 47 |
+ Root string |
|
| 48 |
+ |
|
| 49 |
+ // Mountpoint indicates the mount point relative to the process's root. |
|
| 50 |
+ Mountpoint string |
|
| 51 |
+ |
|
| 52 |
+ // Opts represents mount-specific options. |
|
| 53 |
+ Opts string |
|
| 54 |
+ |
|
| 55 |
+ // Optional represents optional fields. |
|
| 56 |
+ Optional string |
|
| 57 |
+ |
|
| 58 |
+ // Fstype indicates the type of filesystem, such as EXT3. |
|
| 59 |
+ Fstype string |
|
| 60 |
+ |
|
| 61 |
+ // Source indicates filesystem specific information or "none". |
|
| 62 |
+ Source string |
|
| 63 |
+ |
|
| 64 |
+ // VfsOpts represents per super block options. |
|
| 65 |
+ VfsOpts string |
|
| 66 |
+} |
| 0 | 67 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,58 @@ |
| 0 |
+package mountinfo |
|
| 1 |
+ |
|
| 2 |
+import "strings" |
|
| 3 |
+ |
|
| 4 |
+// FilterFunc is a type defining a callback function for GetMount(), |
|
| 5 |
+// used to filter out mountinfo entries we're not interested in, |
|
| 6 |
+// and/or stop further processing if we found what we wanted. |
|
| 7 |
+// |
|
| 8 |
+// It takes a pointer to the Info struct (not fully populated, |
|
| 9 |
+// currently only Mountpoint, Fstype, Source, and (on Linux) |
|
| 10 |
+// VfsOpts are filled in), and returns two booleans: |
|
| 11 |
+// |
|
| 12 |
+// - skip: true if the entry should be skipped |
|
| 13 |
+// - stop: true if parsing should be stopped after the entry |
|
| 14 |
+type FilterFunc func(*Info) (skip, stop bool) |
|
| 15 |
+ |
|
| 16 |
+// PrefixFilter discards all entries whose mount points |
|
| 17 |
+// do not start with a specific prefix |
|
| 18 |
+func PrefixFilter(prefix string) FilterFunc {
|
|
| 19 |
+ return func(m *Info) (bool, bool) {
|
|
| 20 |
+ skip := !strings.HasPrefix(m.Mountpoint, prefix) |
|
| 21 |
+ return skip, false |
|
| 22 |
+ } |
|
| 23 |
+} |
|
| 24 |
+ |
|
| 25 |
+// SingleEntryFilter looks for a specific entry |
|
| 26 |
+func SingleEntryFilter(mp string) FilterFunc {
|
|
| 27 |
+ return func(m *Info) (bool, bool) {
|
|
| 28 |
+ if m.Mountpoint == mp {
|
|
| 29 |
+ return false, true // don't skip, stop now |
|
| 30 |
+ } |
|
| 31 |
+ return true, false // skip, keep going |
|
| 32 |
+ } |
|
| 33 |
+} |
|
| 34 |
+ |
|
| 35 |
+// ParentsFilter returns all entries whose mount points |
|
| 36 |
+// can be parents of a path specified, discarding others. |
|
| 37 |
+// |
|
| 38 |
+// For example, given `/var/lib/docker/something`, entries |
|
| 39 |
+// like `/var/lib/docker`, `/var` and `/` are returned. |
|
| 40 |
+func ParentsFilter(path string) FilterFunc {
|
|
| 41 |
+ return func(m *Info) (bool, bool) {
|
|
| 42 |
+ skip := !strings.HasPrefix(path, m.Mountpoint) |
|
| 43 |
+ return skip, false |
|
| 44 |
+ } |
|
| 45 |
+} |
|
| 46 |
+ |
|
| 47 |
+// FstypeFilter returns all entries that match provided fstype(s). |
|
| 48 |
+func FstypeFilter(fstype ...string) FilterFunc {
|
|
| 49 |
+ return func(m *Info) (bool, bool) {
|
|
| 50 |
+ for _, t := range fstype {
|
|
| 51 |
+ if m.Fstype == t {
|
|
| 52 |
+ return false, false // don't skeep, keep going |
|
| 53 |
+ } |
|
| 54 |
+ } |
|
| 55 |
+ return true, false // skip, keep going |
|
| 56 |
+ } |
|
| 57 |
+} |
| 0 | 58 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,53 @@ |
| 0 |
+package mountinfo |
|
| 1 |
+ |
|
| 2 |
+/* |
|
| 3 |
+#include <sys/param.h> |
|
| 4 |
+#include <sys/ucred.h> |
|
| 5 |
+#include <sys/mount.h> |
|
| 6 |
+*/ |
|
| 7 |
+import "C" |
|
| 8 |
+ |
|
| 9 |
+import ( |
|
| 10 |
+ "fmt" |
|
| 11 |
+ "reflect" |
|
| 12 |
+ "unsafe" |
|
| 13 |
+) |
|
| 14 |
+ |
|
| 15 |
+// parseMountTable returns information about mounted filesystems |
|
| 16 |
+func parseMountTable(filter FilterFunc) ([]*Info, error) {
|
|
| 17 |
+ var rawEntries *C.struct_statfs |
|
| 18 |
+ |
|
| 19 |
+ count := int(C.getmntinfo(&rawEntries, C.MNT_WAIT)) |
|
| 20 |
+ if count == 0 {
|
|
| 21 |
+ return nil, fmt.Errorf("Failed to call getmntinfo")
|
|
| 22 |
+ } |
|
| 23 |
+ |
|
| 24 |
+ var entries []C.struct_statfs |
|
| 25 |
+ header := (*reflect.SliceHeader)(unsafe.Pointer(&entries)) |
|
| 26 |
+ header.Cap = count |
|
| 27 |
+ header.Len = count |
|
| 28 |
+ header.Data = uintptr(unsafe.Pointer(rawEntries)) |
|
| 29 |
+ |
|
| 30 |
+ var out []*Info |
|
| 31 |
+ for _, entry := range entries {
|
|
| 32 |
+ var mountinfo Info |
|
| 33 |
+ var skip, stop bool |
|
| 34 |
+ mountinfo.Mountpoint = C.GoString(&entry.f_mntonname[0]) |
|
| 35 |
+ mountinfo.Fstype = C.GoString(&entry.f_fstypename[0]) |
|
| 36 |
+ mountinfo.Source = C.GoString(&entry.f_mntfromname[0]) |
|
| 37 |
+ |
|
| 38 |
+ if filter != nil {
|
|
| 39 |
+ // filter out entries we're not interested in |
|
| 40 |
+ skip, stop = filter(&mountinfo) |
|
| 41 |
+ if skip {
|
|
| 42 |
+ continue |
|
| 43 |
+ } |
|
| 44 |
+ } |
|
| 45 |
+ |
|
| 46 |
+ out = append(out, &mountinfo) |
|
| 47 |
+ if stop {
|
|
| 48 |
+ break |
|
| 49 |
+ } |
|
| 50 |
+ } |
|
| 51 |
+ return out, nil |
|
| 52 |
+} |
| 0 | 53 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,152 @@ |
| 0 |
+// +build go1.13 |
|
| 1 |
+ |
|
| 2 |
+package mountinfo |
|
| 3 |
+ |
|
| 4 |
+import ( |
|
| 5 |
+ "bufio" |
|
| 6 |
+ "fmt" |
|
| 7 |
+ "io" |
|
| 8 |
+ "os" |
|
| 9 |
+ "strconv" |
|
| 10 |
+ "strings" |
|
| 11 |
+) |
|
| 12 |
+ |
|
| 13 |
+func parseInfoFile(r io.Reader, filter FilterFunc) ([]*Info, error) {
|
|
| 14 |
+ s := bufio.NewScanner(r) |
|
| 15 |
+ out := []*Info{}
|
|
| 16 |
+ var err error |
|
| 17 |
+ for s.Scan() {
|
|
| 18 |
+ if err = s.Err(); err != nil {
|
|
| 19 |
+ return nil, err |
|
| 20 |
+ } |
|
| 21 |
+ /* |
|
| 22 |
+ See http://man7.org/linux/man-pages/man5/proc.5.html |
|
| 23 |
+ |
|
| 24 |
+ 36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue |
|
| 25 |
+ (1)(2)(3) (4) (5) (6) (7) (8) (9) (10) (11) |
|
| 26 |
+ |
|
| 27 |
+ (1) mount ID: unique identifier of the mount (may be reused after umount) |
|
| 28 |
+ (2) parent ID: ID of parent (or of self for the top of the mount tree) |
|
| 29 |
+ (3) major:minor: value of st_dev for files on filesystem |
|
| 30 |
+ (4) root: root of the mount within the filesystem |
|
| 31 |
+ (5) mount point: mount point relative to the process's root |
|
| 32 |
+ (6) mount options: per mount options |
|
| 33 |
+ (7) optional fields: zero or more fields of the form "tag[:value]" |
|
| 34 |
+ (8) separator: marks the end of the optional fields |
|
| 35 |
+ (9) filesystem type: name of filesystem of the form "type[.subtype]" |
|
| 36 |
+ (10) mount source: filesystem specific information or "none" |
|
| 37 |
+ (11) super options: per super block options |
|
| 38 |
+ |
|
| 39 |
+ In other words, we have: |
|
| 40 |
+ * 6 mandatory fields (1)..(6) |
|
| 41 |
+ * 0 or more optional fields (7) |
|
| 42 |
+ * a separator field (8) |
|
| 43 |
+ * 3 mandatory fields (9)..(11) |
|
| 44 |
+ */ |
|
| 45 |
+ |
|
| 46 |
+ text := s.Text() |
|
| 47 |
+ fields := strings.Split(text, " ") |
|
| 48 |
+ numFields := len(fields) |
|
| 49 |
+ if numFields < 10 {
|
|
| 50 |
+ // should be at least 10 fields |
|
| 51 |
+ return nil, fmt.Errorf("Parsing '%s' failed: not enough fields (%d)", text, numFields)
|
|
| 52 |
+ } |
|
| 53 |
+ |
|
| 54 |
+ // separator field |
|
| 55 |
+ sepIdx := numFields - 4 |
|
| 56 |
+ // In Linux <= 3.9 mounting a cifs with spaces in a share |
|
| 57 |
+ // name (like "//srv/My Docs") _may_ end up having a space |
|
| 58 |
+ // in the last field of mountinfo (like "unc=//serv/My Docs"). |
|
| 59 |
+ // Since kernel 3.10-rc1, cifs option "unc=" is ignored, |
|
| 60 |
+ // so spaces should not appear. |
|
| 61 |
+ // |
|
| 62 |
+ // Check for a separator, and work around the spaces bug |
|
| 63 |
+ for fields[sepIdx] != "-" {
|
|
| 64 |
+ sepIdx-- |
|
| 65 |
+ if sepIdx == 5 {
|
|
| 66 |
+ return nil, fmt.Errorf("Parsing '%s' failed: missing - separator", text)
|
|
| 67 |
+ } |
|
| 68 |
+ } |
|
| 69 |
+ |
|
| 70 |
+ p := &Info{}
|
|
| 71 |
+ |
|
| 72 |
+ // Fill in the fields that a filter might check |
|
| 73 |
+ p.Mountpoint, err = strconv.Unquote(`"` + fields[4] + `"`) |
|
| 74 |
+ if err != nil {
|
|
| 75 |
+ return nil, fmt.Errorf("Parsing '%s' failed: unable to unquote mount point field: %w", fields[4], err)
|
|
| 76 |
+ } |
|
| 77 |
+ p.Fstype = fields[sepIdx+1] |
|
| 78 |
+ p.Source = fields[sepIdx+2] |
|
| 79 |
+ p.VfsOpts = fields[sepIdx+3] |
|
| 80 |
+ |
|
| 81 |
+ // Run a filter soon so we can skip parsing/adding entries |
|
| 82 |
+ // the caller is not interested in |
|
| 83 |
+ var skip, stop bool |
|
| 84 |
+ if filter != nil {
|
|
| 85 |
+ skip, stop = filter(p) |
|
| 86 |
+ if skip {
|
|
| 87 |
+ continue |
|
| 88 |
+ } |
|
| 89 |
+ } |
|
| 90 |
+ |
|
| 91 |
+ // Fill in the rest of the fields |
|
| 92 |
+ |
|
| 93 |
+ // ignore any numbers parsing errors, as there should not be any |
|
| 94 |
+ p.ID, _ = strconv.Atoi(fields[0]) |
|
| 95 |
+ p.Parent, _ = strconv.Atoi(fields[1]) |
|
| 96 |
+ mm := strings.Split(fields[2], ":") |
|
| 97 |
+ if len(mm) != 2 {
|
|
| 98 |
+ return nil, fmt.Errorf("Parsing '%s' failed: unexpected minor:major pair %s", text, mm)
|
|
| 99 |
+ } |
|
| 100 |
+ p.Major, _ = strconv.Atoi(mm[0]) |
|
| 101 |
+ p.Minor, _ = strconv.Atoi(mm[1]) |
|
| 102 |
+ |
|
| 103 |
+ p.Root, err = strconv.Unquote(`"` + fields[3] + `"`) |
|
| 104 |
+ if err != nil {
|
|
| 105 |
+ return nil, fmt.Errorf("Parsing '%s' failed: unable to unquote root field: %w", fields[3], err)
|
|
| 106 |
+ } |
|
| 107 |
+ |
|
| 108 |
+ p.Opts = fields[5] |
|
| 109 |
+ |
|
| 110 |
+ // zero or more optional fields |
|
| 111 |
+ switch {
|
|
| 112 |
+ case sepIdx == 6: |
|
| 113 |
+ // zero, do nothing |
|
| 114 |
+ case sepIdx == 7: |
|
| 115 |
+ p.Optional = fields[6] |
|
| 116 |
+ default: |
|
| 117 |
+ p.Optional = strings.Join(fields[6:sepIdx-1], " ") |
|
| 118 |
+ } |
|
| 119 |
+ |
|
| 120 |
+ out = append(out, p) |
|
| 121 |
+ if stop {
|
|
| 122 |
+ break |
|
| 123 |
+ } |
|
| 124 |
+ } |
|
| 125 |
+ return out, nil |
|
| 126 |
+} |
|
| 127 |
+ |
|
| 128 |
+// Parse /proc/self/mountinfo because comparing Dev and ino does not work from |
|
| 129 |
+// bind mounts |
|
| 130 |
+func parseMountTable(filter FilterFunc) ([]*Info, error) {
|
|
| 131 |
+ f, err := os.Open("/proc/self/mountinfo")
|
|
| 132 |
+ if err != nil {
|
|
| 133 |
+ return nil, err |
|
| 134 |
+ } |
|
| 135 |
+ defer f.Close() |
|
| 136 |
+ |
|
| 137 |
+ return parseInfoFile(f, filter) |
|
| 138 |
+} |
|
| 139 |
+ |
|
| 140 |
+// PidMountInfo collects the mounts for a specific process ID. If the process |
|
| 141 |
+// ID is unknown, it is better to use `GetMounts` which will inspect |
|
| 142 |
+// "/proc/self/mountinfo" instead. |
|
| 143 |
+func PidMountInfo(pid int) ([]*Info, error) {
|
|
| 144 |
+ f, err := os.Open(fmt.Sprintf("/proc/%d/mountinfo", pid))
|
|
| 145 |
+ if err != nil {
|
|
| 146 |
+ return nil, err |
|
| 147 |
+ } |
|
| 148 |
+ defer f.Close() |
|
| 149 |
+ |
|
| 150 |
+ return parseInfoFile(f, nil) |
|
| 151 |
+} |
| 0 | 152 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,17 @@ |
| 0 |
+// +build !windows,!linux,!freebsd freebsd,!cgo |
|
| 1 |
+ |
|
| 2 |
+package mountinfo |
|
| 3 |
+ |
|
| 4 |
+import ( |
|
| 5 |
+ "fmt" |
|
| 6 |
+ "io" |
|
| 7 |
+ "runtime" |
|
| 8 |
+) |
|
| 9 |
+ |
|
| 10 |
+func parseMountTable(_ FilterFunc) ([]*Info, error) {
|
|
| 11 |
+ return nil, fmt.Errorf("mount.parseMountTable is not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
|
|
| 12 |
+} |
|
| 13 |
+ |
|
| 14 |
+func parseInfoFile(_ io.Reader, f FilterFunc) ([]*Info, error) {
|
|
| 15 |
+ return parseMountTable(f) |
|
| 16 |
+} |
| 0 | 17 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,12 @@ |
| 0 |
+package mountinfo |
|
| 1 |
+ |
|
| 2 |
+import "io" |
|
| 3 |
+ |
|
| 4 |
+func parseMountTable(_ FilterFunc) ([]*Info, error) {
|
|
| 5 |
+ // Do NOT return an error! |
|
| 6 |
+ return nil, nil |
|
| 7 |
+} |
|
| 8 |
+ |
|
| 9 |
+func parseInfoFile(_ io.Reader, f FilterFunc) ([]*Info, error) {
|
|
| 10 |
+ return parseMountTable(f) |
|
| 11 |
+} |