├── AlienVFS ├── LICENSE ├── README.md ├── gobo └── alienvfs │ ├── config.lua │ ├── cpan.lua │ ├── luarocks.lua │ ├── pip.lua │ └── rubygems.lua └── rockspecs └── alienvfs-scm-1.rockspec /AlienVFS: -------------------------------------------------------------------------------- 1 | #!/usr/bin/lua 2 | 3 | -- AlienVFS: a read-only virtual file system for programming language package managers 4 | -- Written by Lucas C. Villa Real 5 | -- Released under the GNU GPL version 2 6 | 7 | local inspect = require "inspect" 8 | local inotify = require "inotify" 9 | local flu = require "flu" 10 | local posix = require "posix" 11 | local stat = require "posix.sys.stat" 12 | local lanes = require "lanes".configure({track_lanes=true}) 13 | 14 | local pip = require "gobo.alienvfs.pip" 15 | local cpan = require "gobo.alienvfs.cpan" 16 | local lua = require "gobo.alienvfs.luarocks" 17 | local rubygems = require "gobo.alienvfs.rubygems" 18 | local config = require "gobo.alienvfs.config" 19 | 20 | 21 | -- Basic operations on paths 22 | 23 | local Path = {} 24 | Path.__index = Path 25 | 26 | function Path:split(path, istart, iend) 27 | local parts = {} 28 | for name in string.gmatch(path, "[^/]+") do 29 | table.insert(parts, name) 30 | end 31 | if istart and iend then 32 | return {table.unpack(parts, istart, iend)} 33 | end 34 | return parts 35 | end 36 | 37 | function Path:dentry(rootfs, path) 38 | return rootfs:lookup(path) 39 | end 40 | 41 | 42 | -- Basic operations on dentries 43 | 44 | local Dentry = {} 45 | Dentry.__index = Dentry 46 | 47 | function Dentry:create(parent, name, module_dir) 48 | if parent ~= nil and parent.children[name] ~= nil then 49 | return parent.children[name] 50 | else 51 | local dentry = {} 52 | setmetatable(dentry, Dentry) 53 | dentry.module_dir = module_dir 54 | dentry.name = name 55 | dentry.parent = parent 56 | dentry.lower_file = nil 57 | dentry.virtual = false 58 | dentry.children = {} 59 | if parent ~= nil then 60 | parent.children[name] = dentry 61 | end 62 | return dentry 63 | end 64 | end 65 | 66 | function Dentry:remove() 67 | if self.parent ~= nil then 68 | self.parent.children[self.name] = nil 69 | end 70 | end 71 | 72 | function Dentry:childCount() 73 | local count = 0 74 | for _ in pairs(self.children) do 75 | count = count + 1 76 | end 77 | return count 78 | end 79 | 80 | function Dentry:realpath() 81 | if self.lower_file ~= nil then 82 | return self.lower_file 83 | end 84 | local dpath = {} 85 | local dentry = self 86 | while dentry ~= nil do 87 | if dentry.module_dir == nil then break end 88 | table.insert(dpath, 1, dentry.name) 89 | dentry = dentry.parent 90 | end 91 | table.insert(dpath, 1, self.module_dir) 92 | return table.concat(dpath, "/") 93 | end 94 | 95 | function Dentry:lookup(path) 96 | local p = Path 97 | local dentry = self 98 | if type(path) == "string" then 99 | for _,name in ipairs(p:split(path)) do 100 | dentry = dentry.children[name] 101 | if dentry == nil then break end 102 | end 103 | elseif type(path) == "table" then 104 | for _,name in ipairs(path) do 105 | dentry = dentry.children[name] 106 | if dentry == nil then break end 107 | end 108 | end 109 | return dentry 110 | end 111 | 112 | function Dentry:populate(filelist, module_dir) 113 | local p = Path 114 | for _,pathinfo in pairs(filelist) do 115 | local last = nil 116 | local virtualdir = pathinfo[2] ~= nil 117 | for i,name in ipairs(p:split(pathinfo[1])) do 118 | if i == 1 then 119 | last = Dentry:create(self, name, module_dir) 120 | last.virtual = virtualdir 121 | else 122 | local parent = self:lookup(p:split(pathinfo[1], 1, i-1)) 123 | last = Dentry:create(parent, name, module_dir) 124 | last.virtual = virtualdir 125 | end 126 | end 127 | if last ~= nil and pathinfo[2] ~= nil then 128 | last.lower_file = pathinfo[2] 129 | last.virtual = false 130 | end 131 | end 132 | end 133 | 134 | function Dentry:getAttributes() 135 | local path = self:realpath() 136 | if path:len() == 0 or self.virtual == true then 137 | path = "/" 138 | end 139 | local attrs = stat.lstat(path) 140 | if attrs == nil then 141 | error(flu.errno.ENOENT) 142 | end 143 | return { 144 | dev = attrs.st_dev, 145 | ino = attrs.st_ino, 146 | mode = self:makeMode(attrs), 147 | nlink = attrs.st_nlink, 148 | uid = attrs.st_uid, 149 | gid = attrs.st_gid, 150 | rdev = attrs.st_rdev, 151 | access = attrs.st_atime, 152 | modification = attrs.st_mtime, 153 | change = attrs.st_ctime, 154 | size = attrs.st_size, 155 | blocks = attrs.st_blocks, 156 | blksize = attrs.st_blksize, 157 | } 158 | end 159 | 160 | function Dentry:makeMode(attrs) 161 | local set = {} 162 | if stat.S_ISBLK(attrs.st_mode) ~= 0 then set["blk"] = true end 163 | if stat.S_ISCHR(attrs.st_mode) ~= 0 then set["chr"] = true end 164 | if stat.S_ISFIFO(attrs.st_mode) ~= 0 then set["fifo"] = true end 165 | if stat.S_ISREG(attrs.st_mode) ~= 0 then set["reg"] = true end 166 | if stat.S_ISDIR(attrs.st_mode) ~= 0 then set["dir"] = true end 167 | if stat.S_ISLNK(attrs.st_mode) ~= 0 then set["lnk"] = true end 168 | if stat.S_ISSOCK(attrs.st_mode) ~= 0 then set["sock"] = true end 169 | if stat.S_IRUSR & attrs.st_mode ~= 0 then set["rusr"] = true end 170 | if stat.S_IWUSR & attrs.st_mode ~= 0 then set["wusr"] = true end 171 | if stat.S_IXUSR & attrs.st_mode ~= 0 then set["xusr"] = true end 172 | if stat.S_IRGRP & attrs.st_mode ~= 0 then set["rgrp"] = true end 173 | if stat.S_IWGRP & attrs.st_mode ~= 0 then set["wgrp"] = true end 174 | if stat.S_IXGRP & attrs.st_mode ~= 0 then set["xgrp"] = true end 175 | if stat.S_IROTH & attrs.st_mode ~= 0 then set["roth"] = true end 176 | if stat.S_IWOTH & attrs.st_mode ~= 0 then set["woth"] = true end 177 | if stat.S_IXOTH & attrs.st_mode ~= 0 then set["xoth"] = true end 178 | if stat.S_ISUID & attrs.st_mode ~= 0 then set["suid"] = true end 179 | if stat.S_ISGID & attrs.st_mode ~= 0 then set["sgid"] = true end 180 | return set 181 | end 182 | 183 | -- Filesystem notification mechanism 184 | 185 | local inotify_monitor = lanes.gen("*", function(ch, prefix, dirs) 186 | local lanes = require "lanes" 187 | local posix = require "posix" 188 | local inotify = require "inotify" 189 | 190 | -- XXX catches changes to programs, but not to individual versions 191 | local handle = inotify.init() 192 | for _,path in pairs(dirs) do 193 | local wd = handle:addwatch(path, 194 | inotify.IN_CREATE, 195 | inotify.IN_DELETE, 196 | inotify.IN_DELETE_SELF, 197 | inotify.IN_CLOSE_WRITE, 198 | inotify.IN_MOVED_FROM, 199 | inotify.IN_MOVE) 200 | print("Added watch to " .. path) 201 | end 202 | while true do 203 | local events = handle:read() 204 | for _, ev in ipairs(events) do 205 | ch:send("inotify_event", {ev, prefix, dirs[ev.wd]}) 206 | end 207 | end 208 | end) 209 | 210 | local namespace_update = nil 211 | 212 | local function bit_set(event, bit) 213 | return event.mask & bit == bit 214 | end 215 | 216 | local function check_filesystem_watches(ch) 217 | while true do 218 | local key, value = ch:receive(0, "inotify_event", 1) 219 | if not key then 220 | break 221 | end 222 | 223 | local event_type, event_idx = {'CREATE', 'DELETE', nil}, 3 224 | local event, prefix, watchdir = value[1], value[2], value[3] 225 | 226 | if bit_set(event, inotify.IN_CREATE) or 227 | bit_set(event, inotify.IN_MOVED_TO) then 228 | print(" * " .. prefix .. ": CREATED/MOVED_IN/MODIFIED " .. event.name) 229 | event_idx = 1 230 | elseif bit_set(event, inotify.IN_DELETE) or 231 | bit_set(event, inotify.IN_DELETE_SELF) or 232 | bit_set(event, inotify.IN_MOVED_FROM) then 233 | if event.name == nil then 234 | -- event.name will be nil on IN_DELETE_SELF 235 | event.name = watchdir 236 | end 237 | print(" * " .. prefix .. ": DELETED/MOVED_OUT " .. event.name) 238 | event_idx = 2 239 | end 240 | 241 | if event_type[event_idx] ~= nil then 242 | if prefix == "PIP:" or prefix == "PIP3:" then 243 | namespace_update(pip, prefix, watchdir, event.name, event_type[event_idx]) 244 | elseif prefix == "LuaRocks:" then 245 | namespace_update(lua, prefix, watchdir, event.name, event_type[event_idx]) 246 | elseif prefix == "CPAN:" then 247 | namespace_update(cpan, prefix, watchdir, event.name, event_type[event_idx]) 248 | elseif prefix == "RubyGems:" then 249 | namespace_update(rubygems, prefix, watchdir, event.name, event_type[event_idx]) 250 | end 251 | end 252 | end 253 | end 254 | 255 | -- Virtual file system methods 256 | 257 | local vfs = {} 258 | vfs.ch = lanes.linda() 259 | vfs.descriptors = {} 260 | vfs.rootfs = Dentry:create(nil, "/") 261 | 262 | function vfs.getattr(path, stat) 263 | if path == "/" then 264 | return vfs.rootfs:getAttributes() 265 | else 266 | check_filesystem_watches(vfs.ch) 267 | local dentry = Path:dentry(vfs.rootfs, path) 268 | if dentry ~= nil then 269 | return dentry:getAttributes() 270 | end 271 | end 272 | error(flu.errno.ENOENT) 273 | end 274 | 275 | function vfs.readdir(path, filler, fi) 276 | check_filesystem_watches(vfs.ch) 277 | local dentry = Path:dentry(vfs.rootfs, path) 278 | if dentry ~= nil then 279 | filler(".") 280 | filler("..") 281 | for name,dentry in pairs(dentry.children) do 282 | filler(name) 283 | end 284 | end 285 | end 286 | 287 | function vfs.open(path, fi) 288 | -- operates on the real file system 289 | check_filesystem_watches(vfs.ch) 290 | local dentry = Path:dentry(vfs.rootfs, path) 291 | local flags = posix.O_RDONLY 292 | if fi.flags.wronly then 293 | flags = posix.O_WRONLY 294 | elseif fi.flags.rdwr then 295 | flags = posix.O_RDWR 296 | end 297 | fi.fh = posix.open(dentry:realpath(), flags) 298 | if fi.fh == nil then 299 | error(flu.errno.ENOENT) 300 | end 301 | vfs.descriptors[math.floor(fi.fh)] = dentry 302 | end 303 | 304 | function vfs.read(path, size, offset, fi) 305 | posix.lseek(math.floor(fi.fh), math.floor(offset), posix.SEEK_SET) 306 | return posix.read(math.floor(fi.fh), size) 307 | end 308 | 309 | function vfs.release(path, fi) 310 | vfs.descriptors[fi.fh] = nil 311 | posix.close(math.floor(fi.fh)) 312 | fi.fh = nil 313 | end 314 | 315 | 316 | -- Main operation 317 | 318 | local function namespace_add_program(prefix, program) 319 | local pname = Dentry:create(vfs.rootfs, prefix .. program.name) 320 | local pversion = Dentry:create(pname, program.version) 321 | if program.filelist ~= nil then 322 | pversion:populate(program.filelist, program.module_dir) 323 | end 324 | end 325 | 326 | local function namespace_create(backend, prefix, add_watches, module_dirs) 327 | local watchlist = {} 328 | if module_dirs == nil then 329 | module_dirs = backend:moduleDirs() 330 | end 331 | for _,module_dir in pairs(module_dirs) do 332 | local attrs = stat.lstat(module_dir) 333 | if attrs ~= nil then 334 | table.insert(watchlist, module_dir) 335 | local modules = backend:parse(module_dir) 336 | if modules ~= nil then 337 | for _,program in pairs(modules) do 338 | if program.name ~= nil then 339 | namespace_add_program(prefix, program) 340 | end 341 | end 342 | end 343 | end 344 | end 345 | if add_watches then 346 | inotify_monitor(vfs.ch, prefix, watchlist) 347 | end 348 | end 349 | 350 | -- 351 | -- Update namespace upon a DELETE or CREATE event 352 | -- @backend: pip, lua, cpan, rubygems 353 | -- @prefix: "PIP:", "PIP3:", "LuaRocks:", "CPAN:", "RubyGems:" 354 | -- @watchdir: /System/Aliens/PIP, /System/Aliens/LuaRocks, ... 355 | -- @path: foo, bar, anotherdirname, ... 356 | -- @event_type: 'CREATE', 'DELETE' 357 | -- 358 | namespace_update = function(backend, prefix, watchdir, path, event_type) 359 | if event_type == "DELETE" then 360 | local vfsname = backend:map(path, event_type) 361 | if vfsname == nil then 362 | return 363 | end 364 | local fullname = "/" .. prefix .. vfsname 365 | 366 | local dentry = Path:dentry(vfs.rootfs, fullname) 367 | if dentry ~= nil then 368 | local parent = dentry.parent 369 | dentry:remove() 370 | if parent ~= nil and parent:childCount() == 0 then 371 | parent:remove() 372 | end 373 | end 374 | elseif event_type == "CREATE" then 375 | if not backend:valid(path) then 376 | return 377 | end 378 | local vfsname = backend:map(path, event_type) 379 | if vfsname ~= nil then 380 | local fullname = "/" .. prefix .. vfsname 381 | local dentry = Path:dentry(vfs.rootfs, fullname) 382 | if dentry == nil then 383 | local program = backend:populate(watchdir, path) 384 | if program ~= nil then 385 | namespace_add_program(prefix, program) 386 | end 387 | end 388 | end 389 | end 390 | end 391 | 392 | -- Namespace creation 393 | namespace_create(pip, "PIP:", true, config.pip_directories()) 394 | namespace_create(pip, "PIP3:", true, config.pip3_directories()) 395 | namespace_create(lua, "LuaRocks:", true, config.luarocks_directories()) 396 | namespace_create(rubygems, "RubyGems:", false, nil) 397 | namespace_create(cpan, "CPAN:", false, config.cpan_directories()) 398 | 399 | -- Inotify watches to catch removal of CPAN modules and installation of new ones 400 | local packlist = {} 401 | for key in pairs(cpan.packlists) do table.insert(packlist, key) end 402 | inotify_monitor(vfs.ch, "CPAN:", packlist) 403 | inotify_monitor(vfs.ch, "CPAN:", config:cpan_inotify_directories()) 404 | 405 | -- Run FLU main loop 406 | local argv = {"AlienVFS", "-oallow_other", "-oauto_unmount", select(1, ...)} 407 | flu.main(argv, vfs) 408 | 409 | -- vim: ts=4 sts=4 sw=4 expandtab 410 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AlienVFS 2 | 3 | AlienVFS is a virtual filesystem that mirrors modules installed by 4 | programming language package managers under a centralized directory. 5 | In other words, modules installed through CPAN, LuaRocks, PIP and 6 | friends are exposed under a single mount point. The virtual directory 7 | presents the module name and one or more subdirectories holding the 8 | installed version(s) of that module. 9 | 10 | ## Supported package managers 11 | 12 | AlienVFS has built-in support for a few package managers: 13 | 14 | - LuaRocks (Lua) 15 | - CPAN (Perl) 16 | - PIP and PIP3 (Python) 17 | - RubyGems (Ruby) 18 | 19 | The virtual filesystem tree automatically updates whenever 20 | a new programming language module is installed or removed 21 | by the package managers. 22 | 23 | ## Dependencies 24 | 25 | AlienVFS depends on the following packages: 26 | 27 | | Fedora | Ubuntu | GoboLinux | 28 | |---------------|---------------|-----------| 29 | | libattr-devel | libattr1-dev | ATTR | 30 | | luarocks | luarocks | Lua | 31 | | lua | lua5.3 | LuaRocks | 32 | | lua-devel | liblua5.3-dev | Fuse | 33 | | fuse | fuse | | 34 | | fuse-devel | libfuse-dev | | 35 | 36 | ## Installation 37 | 38 | Assuming you want to install from the most recent Git snapshot, 39 | the following two lines are enough to install AlienVFS on a regular 40 | distro: 41 | 42 | ```bash 43 | $ git clone https://github.com/gobolinux/AlienVFS.git 44 | $ sudo luarocks install AlienVFS/rockspecs/alienvfs-scm-1.rockspec 45 | ``` 46 | 47 | You may also instruct LuaRocks to install it under your home directory 48 | with: 49 | 50 | ``` 51 | $ luarocks --local install AlienVFS/rockspecs/alienvfs-scm-1.rockspec 52 | ``` 53 | 54 | Next, make sure that /etc/fuse.conf contains the following line enabling 55 | regular users to access the modules exposed by AlienVFS under the given 56 | mountpoint: 57 | 58 | ``` 59 | user_allow_other 60 | ``` 61 | 62 | ## Usage 63 | 64 | Under a GoboLinux distribution, the main script will be saved under 65 | /System/Aliens/LuaRocks/bin. A local installation will place the main 66 | file under ~/.luarocks/bin. If that directory is not on your $PATH, 67 | make sure to append it to that variable and then invoke AlienVFS, 68 | passing as argument the mount point where modules will be shown: 69 | 70 | ```bash 71 | $ mkdir -p /Mount/Aliens 72 | $ AlienVFS /Mount/Aliens 73 | ``` 74 | 75 | You can now browse the contents of /Mount/Aliens using regular tools. 76 | 77 | ```bash 78 | $ ls /Mount/Aliens 79 | CPAN:Authen::SASL LuaRocks:luafilesystem PIP:google-api-python-client PIP:Pygments 80 | CPAN:Digest::HMAC LuaRocks:luaposix PIP:htmlmin PIP:pyldap 81 | CPAN:Encode::Locale PIP:agate PIP:httplib2 PIP:PyOpenGL 82 | CPAN:Error PIP:agate-dbf PIP:idna PIP:pyparsing 83 | ... 84 | 85 | $ ls /Mount/Aliens/PIP:pyldap 86 | 2.4.28 87 | 88 | $ ls /Mount/Aliens/PIP:pyldap/2.4.28 89 | dsml.py dsml.pyo _ldap.so ldapurl.pyc ldif.py ldif.pyo 90 | dsml.pyc ldap ldapurl.py ldapurl.pyo ldif.pyc pyldap-2.4.28-py2.7.egg-info 91 | ``` 92 | -------------------------------------------------------------------------------- /gobo/alienvfs/config.lua: -------------------------------------------------------------------------------- 1 | -- AlienVFS: directory definitions 2 | -- Written by Lucas C. Villa Real 3 | -- Released under the GNU GPL version 2 4 | 5 | local glob = require "posix.glob" 6 | 7 | local function scan_dirs(patterns) 8 | local dirs = {} 9 | for _, pattern in pairs(patterns) do 10 | local matches = glob.glob(pattern, 0) 11 | if matches then 12 | for _, dirname in pairs(matches) do 13 | table.insert(dirs, dirname) 14 | end 15 | end 16 | end 17 | return dirs 18 | end 19 | 20 | local config = { 21 | pip_directories = function(self) 22 | return scan_dirs({ 23 | "/System/Aliens/PIP", 24 | "/System/Aliens/PIP/lib/python2*/site-packages", 25 | "/Programs/Python/2.*/lib/python2*/site-packages", 26 | "/usr/lib64/python2*/site-packages", 27 | "/usr/lib/python2*/site-packages" 28 | }) 29 | end, 30 | 31 | pip3_directories = function(self) 32 | return scan_dirs({ 33 | "/System/Aliens/PIP/lib/python3*/site-packages", 34 | "/Programs/Python/3.*/lib/python3*/site-packages", 35 | "/usr/lib64/python3*/site-packages", 36 | "/usr/lib/python3*/site-packages" 37 | }) 38 | end, 39 | 40 | luarocks_directories = function(self) 41 | return scan_dirs({ 42 | "/System/Aliens/LuaRocks/lib/luarocks/rocks*", 43 | "/usr/lib64/lua/5.*/luarocks/rocks*", 44 | "/usr/lib/lua/5.*/luarocks/rocks*" 45 | }) 46 | end, 47 | 48 | cpan_directories = function(self) 49 | local f = io.popen("uname -m") 50 | local arch = f:read("*l") 51 | f:close() 52 | return scan_dirs({ 53 | "/System/Aliens/CPAN/lib/perl*/" .. arch .. "*/auto", 54 | "/usr/lib64/perl*/" .. arch .. "*/auto", 55 | "/usr/lib/perl*/" .. arch .. "*/auto" 56 | }) 57 | end, 58 | 59 | cpan_inotify_directories = function(self) 60 | local f = io.popen("uname -m") 61 | local arch = f:read("*l") 62 | local regular_dirs = self:cpan_directories() 63 | local inotify_dirs = scan_dirs({ 64 | "/System/Aliens/CPAN/lib/perl*/" .. arch .. "*/auto/*", 65 | "/usr/lib64/perl*/" .. arch .. "*/auto/*", 66 | "/usr/lib/perl*/" .. arch .. "*/auto/*" 67 | }) 68 | f:close() 69 | for _, dir in pairs(regular_dirs) do 70 | table.insert(inotify_dirs, dir) 71 | end 72 | return inotify_dirs 73 | end 74 | } 75 | 76 | return config 77 | 78 | -- vim: ts=4 sts=4 sw=4 expandtab 79 | -------------------------------------------------------------------------------- /gobo/alienvfs/cpan.lua: -------------------------------------------------------------------------------- 1 | -- AlienVFS: CPAN backend 2 | -- Written by Lucas C. Villa Real 3 | -- Released under the GNU GPL version 2 4 | 5 | local glob = require "posix.glob" 6 | local inspect = require "inspect" 7 | 8 | local cpan = { 9 | cpan_dir = nil, 10 | packlists = {}, 11 | perldoc_output = {}, 12 | 13 | moduleDirs = function(self) 14 | return nil 15 | end, 16 | 17 | parse = function(self, cpan_dir) 18 | if self.cpan_dir == nil then self.cpan_dir = cpan_dir end 19 | local programs = {} 20 | self.perldoc_output = self:_runPerlDoc() 21 | for _,module in pairs(self:_getModules(cpan_dir)) do 22 | if module._packlist ~= nil then 23 | table.insert(programs, module) 24 | if self.packlists[module._packlist] == nil then 25 | self.packlists[module._packlist] = module 26 | end 27 | end 28 | end 29 | return programs 30 | end, 31 | 32 | populate = function(self, directory, programname) 33 | -- Lazy inotify:create handling 34 | local filelist = {} 35 | for _,module in pairs(self:_getModules(directory)) do 36 | if module._packlist ~= nil and self.packlists[module._packlist] == nil then 37 | self.packlists[module._packlist] = module 38 | return module 39 | end 40 | end 41 | return nil 42 | end, 43 | 44 | valid = function(self, path) 45 | -- This function is called to test if a newly created path is a new module 46 | return true 47 | end, 48 | 49 | map = function(self, path, event_type) 50 | -- Given a path, returns the corresponding AlienVFS module name 51 | local module = self.packlists[path] 52 | if module ~= nil then 53 | return module.name 54 | end 55 | 56 | -- Update the packlists and try again 57 | local old_packlist = { table.unpack(self.packlists) } 58 | self:parse(self.cpan_dir) 59 | for key,value in pairs(self.packlists) do 60 | if old_packlist[key] == nil and string.find(key, path, 1, true) ~= nil then 61 | -- Obtain a reference to the module name and then remove the new entry 62 | -- from packlists. We do so in order to keep the logic of populate() simple. 63 | module, self.packlists[key] = self.packlists[key], nil 64 | return module.name 65 | end 66 | end 67 | return nil 68 | end, 69 | 70 | _getModules = function(self, cpan_dir) 71 | -- Get last path element 72 | local modules, last_element = {}, nil 73 | for name in string.gmatch(cpan_dir, "[^/]+") do 74 | last_element = name 75 | end 76 | 77 | -- We only support Perl modules that ship a .packlist file. All other modules are ignored. 78 | local f = io.popen("find " .. cpan_dir .. " -name \"*.packlist\"") 79 | for packlist in f:lines() do 80 | local parts, module = {}, {} 81 | local module_dir = string.gsub(packlist, "/.packlist", "") 82 | for name in string.gmatch(module_dir, "[^/]+") do 83 | table.insert(parts, name) 84 | end 85 | 86 | -- Populate the module table 87 | module._packlist = packlist 88 | module.module_dir = module_dir 89 | if parts[#parts-1] ~= last_element then 90 | module.name = parts[#parts-1] .. "::" .. parts[#parts] 91 | else 92 | module.name = parts[#parts] 93 | end 94 | module.version = self:_getModuleVersion(module.name) 95 | module.filelist = self:_parsePackList(cpan_dir, packlist) 96 | if module.version ~= nil and module.filelist ~= nil then 97 | table.insert(modules, module) 98 | end 99 | end 100 | f:close() 101 | return modules 102 | end, 103 | 104 | _getModuleVersion = function(self, module_name) 105 | local watch, version = false, nil 106 | for _,line in pairs(self.perldoc_output) do 107 | if line:find(module_name .. "$") ~= nil then 108 | watch = true 109 | elseif watch == true then 110 | local istart, iend = line:find('"VERSION: ') 111 | if istart ~= nil then 112 | return line:sub(iend+1, -2) 113 | end 114 | end 115 | end 116 | print("CPAN: failed to get version of module " .. module_name) 117 | return nil 118 | end, 119 | 120 | _runPerlDoc = function(self) 121 | local f, data = io.popen("perldoc -t perllocal"), {} 122 | for line in f:lines() do data[#data + 1] = line end 123 | f:close() 124 | return data 125 | end, 126 | 127 | _parsePackList = function(self, cpan_dir, packlist) 128 | local f = io.open(packlist) 129 | if f ~= nil then 130 | local filelist = {} 131 | for line in f:lines() do 132 | local common = self:_commonPrefix(line, cpan_dir) 133 | local path, lower_path = line:sub(common:len()+1), line 134 | table.insert(filelist, {path, lower_path}) 135 | end 136 | f:close() 137 | return filelist 138 | end 139 | return nil 140 | end, 141 | 142 | _commonPrefix = function(self, path, basedir) 143 | local common = "" 144 | for i=1, #path do 145 | if path:sub(i,i) ~= basedir:sub(i,i) then 146 | break 147 | end 148 | common = common .. path:sub(i,i) 149 | end 150 | return common 151 | end, 152 | } 153 | 154 | return cpan 155 | 156 | -- vim: ts=4 sts=4 sw=4 expandtab 157 | -------------------------------------------------------------------------------- /gobo/alienvfs/luarocks.lua: -------------------------------------------------------------------------------- 1 | -- AlienVFS: LuaRocks backend 2 | -- Written by Lucas C. Villa Real 3 | -- Released under the GNU GPL version 2 4 | 5 | local inspect = require "inspect" 6 | 7 | local lua = { 8 | moduleDirs = function(self) 9 | return nil 10 | end, 11 | 12 | parse = function(self, luarocks_dir) 13 | return self:_parseCommand("luarocks list --porcelain") 14 | end, 15 | 16 | populate = function(self, directory, programname) 17 | local program = self:_parseCommand("luarocks list --porcelain " .. programname) 18 | if #program ~= 0 then 19 | return program[1] 20 | end 21 | return nil 22 | end, 23 | 24 | valid = function(self, path) 25 | return path ~= ".tmpluarockstestwritable" and path ~= "manifest.tmp" 26 | end, 27 | 28 | map = function(self, path, event_type) 29 | return path 30 | end, 31 | 32 | _parseCommand = function(self, command) 33 | local programs = {} 34 | local f = io.popen(command) 35 | for line in f:lines() do 36 | local result = {} 37 | for column in string.gmatch(line, "[^\t]+") do 38 | result[#result + 1] = column 39 | end 40 | local program = {} 41 | program.name = result[1] 42 | program.version = result[2] 43 | program.module_dir = result[4] 44 | program.filelist = self:_parseProgram(program) 45 | table.insert(programs, program) 46 | end 47 | f:close() 48 | return programs 49 | end, 50 | 51 | _parseProgram = function(self, program) 52 | local programdir = program.module_dir .. "/" .. program.name .. "/" .. program.version 53 | local contents = io.popen("find " .. programdir) 54 | local filelist = {} 55 | -- Update module_dir 56 | program.module_dir = programdir 57 | for fname in contents:lines() do 58 | local path, lower_path = "/"..fname:sub(program.module_dir:len()+2), nil 59 | table.insert(filelist, {path, lower_path}) 60 | end 61 | contents:close() 62 | return filelist 63 | end 64 | } 65 | 66 | return lua 67 | 68 | -- vim: ts=4 sts=4 sw=4 expandtab 69 | -------------------------------------------------------------------------------- /gobo/alienvfs/pip.lua: -------------------------------------------------------------------------------- 1 | -- AlienVFS: PIP backend 2 | -- Written by Lucas C. Villa Real 3 | -- Released under the GNU GPL version 2 4 | 5 | local lfs = require "lfs" 6 | local posix = require "posix" 7 | local lunajson = require "lunajson" 8 | 9 | local pip = { 10 | pip_dirs = {}, 11 | programs_table = {}, 12 | 13 | moduleDirs = function(self) 14 | return nil 15 | end, 16 | 17 | parse = function(self, pip_dir) 18 | local programs = {} 19 | table.insert(self.pip_dirs, pip_dir) 20 | for file in lfs.dir(pip_dir) do 21 | if lfs.attributes(pip_dir.."/"..file, "mode") == "directory" then 22 | local fname = pip_dir.."/"..file 23 | if string.find(fname, "egg-info", 1, true) ~= nil then 24 | local info = self:_parseEgg(fname) 25 | if info.name ~= nil then 26 | table.insert(programs, info) 27 | table.insert(self.programs_table, info) 28 | end 29 | elseif string.find(fname, "dist-info", 1, true) ~= nil then 30 | local info = self:_parseDistInfo(fname) 31 | if info.name ~= nil then 32 | table.insert(programs, info) 33 | table.insert(self.programs_table, info) 34 | end 35 | end 36 | end 37 | end 38 | return programs 39 | end, 40 | 41 | populate = function(self, directory, programname) 42 | local path = directory .. "/" .. programname 43 | local info = posix.stat(path) 44 | local programfiles = {} 45 | if info ~= nil and info.type == "directory" then 46 | if string.find(programname, "egg-info", 1, true) ~= nil then 47 | local egginfo = self:_parseEgg(path) 48 | if egginfo.name ~= nil then 49 | table.insert(programfiles, egginfo) 50 | end 51 | elseif string.find(programname, "dist-info", 1, true) ~= nil then 52 | local distinfo = self:_parseDistInfo(path) 53 | if distinfo.name ~= nil then 54 | table.insert(programfiles, distinfo) 55 | end 56 | end 57 | end 58 | module = {} 59 | for _,entry in pairs(programfiles) do 60 | -- Local cache 61 | module.path = path 62 | module.name = entry.name 63 | module.version = entry.version 64 | table.insert(self.programs_table, module) 65 | end 66 | if #module == 0 then 67 | module = nil 68 | end 69 | return module 70 | end, 71 | 72 | valid = function(self, path) 73 | return string.find(path, "egg-info", 1, true) ~= nil or string.find(path, "dist-info", 1, true) ~= nil 74 | end, 75 | 76 | map = function(self, path, event_type) 77 | if event_type == "DELETE" then 78 | for _,info in pairs(self.programs_table) do 79 | if info.name == posix.basename(path) or posix.basename(info.path) == posix.basename(path) then 80 | return info.name .. "/" .. info.version 81 | end 82 | end 83 | elseif event_type == "CREATE" then 84 | return path 85 | end 86 | return nil 87 | end, 88 | 89 | _readNameVersion = function(self, f) 90 | local name = nil 91 | local version = nil 92 | for line in f:lines() do 93 | local name_ = line:find("Name: ") 94 | local version_ = line:find("Version: ") 95 | if name_ ~= nil then 96 | name = self:_getValue(line) 97 | end 98 | if version_ ~= nil then 99 | version = self:_getValue(line) 100 | end 101 | end 102 | return name, version 103 | end, 104 | 105 | _getValue = function(self, line) 106 | return line:sub(line:len()+2 - line:reverse():find(" ")) 107 | end, 108 | 109 | _parseEgg = function(self, egg_dir) 110 | local this_pip_dir = self:_matchingPipDir(egg_dir) 111 | if this_pip_dir == nil then return {} end 112 | 113 | local program = {} 114 | local f = io.open(egg_dir.."/PKG-INFO") 115 | if f ~= nil then 116 | program.name, program.version = self:_readNameVersion(f) 117 | f:close() 118 | end 119 | if program.name == nil then return {} end 120 | 121 | f = io.open(egg_dir.."/installed-files.txt") 122 | if f ~= nil then 123 | program.filelist = {} 124 | for line in f:lines() do 125 | local fname = egg_dir.."/"..line 126 | local path, lower_path = self:_getPath(fname, this_pip_dir) 127 | if path ~= nil then 128 | table.insert(program.filelist, {path, lower_path}) 129 | end 130 | end 131 | f:close() 132 | end 133 | program.path = egg_dir 134 | program.module_dir = this_pip_dir 135 | return program 136 | end, 137 | 138 | _parseDistInfo = function(self, dist_dir) 139 | local this_pip_dir = self:_matchingPipDir(dist_dir) 140 | if this_pip_dir == nil then return {} end 141 | 142 | local program = {} 143 | local f = io.open(dist_dir.."/metadata.json") 144 | if f ~= nil then 145 | local jsonstr = f:read("*all") 146 | local jsondoc = lunajson.decode(jsonstr) 147 | program.name = jsondoc.name 148 | program.version = jsondoc.version 149 | f:close() 150 | else 151 | f = io.open(dist_dir.."/METADATA") 152 | if f ~= nil then 153 | program.name, program.version = self:_readNameVersion(f) 154 | f:close() 155 | end 156 | end 157 | if program.name == nil then return {} end 158 | 159 | f = io.open(dist_dir.."/RECORD") 160 | if f ~= nil then 161 | program.filelist = {} 162 | for line in f:lines() do 163 | local fname = dist_dir.."/../"..line:sub(1, line:find(",")-1) 164 | local path, lower_path = self:_getPath(fname, this_pip_dir) 165 | if path ~= nil then 166 | table.insert(program.filelist, {path, lower_path}) 167 | end 168 | end 169 | f:close() 170 | end 171 | program.path = dist_dir 172 | program.module_dir = this_pip_dir 173 | return program 174 | end, 175 | 176 | _getPath = function(self, fname, basedir) 177 | local path = posix.realpath(fname) 178 | if path ~= nil then 179 | if string.find(path, basedir, 1, true) ~= nil then 180 | return path:sub(basedir:len()+2), nil 181 | end 182 | local common = "" 183 | for i=1, #path do 184 | if path:sub(i,i) ~= basedir:sub(i,i) then 185 | break 186 | end 187 | common = common .. path:sub(i,i) 188 | end 189 | return path:sub(common:len()+1), fname 190 | end 191 | return nil, nil 192 | end, 193 | 194 | _matchingPipDir = function(self, path) 195 | for _,entry in pairs(self.pip_dirs) do 196 | local this_pip_dir = posix.realpath(entry) 197 | if this_pip_dir ~= nil and string.find(path, this_pip_dir, 1, true) ~= nil then 198 | return entry 199 | end 200 | end 201 | return nil 202 | end 203 | } 204 | 205 | return pip 206 | 207 | -- vim: ts=4 sts=4 sw=4 expandtab 208 | -------------------------------------------------------------------------------- /gobo/alienvfs/rubygems.lua: -------------------------------------------------------------------------------- 1 | -- AlienVFS: RubyGems backend 2 | -- Written by Lucas C. Villa Real 3 | -- Released under the GNU GPL version 2 4 | 5 | local stat = require "posix.sys.stat" 6 | 7 | local rubygems = { 8 | programs_list = {}, 9 | 10 | moduleDirs = function(self) 11 | local module_dirs = {} 12 | local f = io.popen("gem environment gempath 2> /dev/null") 13 | local gems_path = f:read("*l") 14 | if gems_path ~= nil then 15 | for module_dir in string.gmatch(gems_path, "[^:]+") do 16 | module_dir = module_dir .. "/gems" 17 | if stat.lstat(module_dir) ~= nil then 18 | table.insert(module_dirs, module_dir) 19 | end 20 | end 21 | end 22 | f:close() 23 | return module_dirs 24 | end, 25 | 26 | parse = function(self, rubygems_dir) 27 | local programs = {} 28 | for _,modinfo in pairs(self:_getModules(rubygems_dir)) do 29 | local nameversion, module_dir = modinfo[1], modinfo[2] 30 | local istart, iend = nameversion:find(" ") 31 | 32 | local program = {} 33 | program.name = nameversion:sub(1,istart-1) 34 | program.version = nameversion:sub(iend+1) 35 | program.filelist = self:_getFileList(module_dir, program.name, program.version) 36 | program.module_dir = module_dir .. "/" .. program.name .. "-" .. program.version .. "/" 37 | 38 | self.programs_list[program.name .. "-" .. program.version] = program 39 | table.insert(programs, program) 40 | end 41 | return programs 42 | end, 43 | 44 | populate = function(self, directory, programname) 45 | local istart, iend = 1, programname:find("-") 46 | while iend ~= nil do 47 | local name = programname:sub(istart, iend-1) 48 | local version = programname:sub(iend+1) 49 | local module_dir = self:_getInstallDir(name, version) 50 | if module_dir ~= nil then 51 | local filelist = self:_getFileList(module_dir, name, version) 52 | if #filelist > 0 then 53 | local program = {} 54 | program.name = name 55 | program.version = version 56 | program.filelist = filelist 57 | program.module_dir = self:_getInstallDir(name, version) .. "/" .. name .. "-" .. version .. "/" 58 | 59 | self.programs_list[program.name .. "-" .. program.version] = program 60 | return program 61 | end 62 | end 63 | iend = programname:find("-", iend+1) 64 | end 65 | return nil 66 | end, 67 | 68 | valid = function(self, path) 69 | return true 70 | end, 71 | 72 | map = function(self, path, event_type) 73 | if event_type == "DELETE" then 74 | local program = self.programs_list[path] 75 | if program ~= nil then 76 | return program.name .. "/" .. program.version 77 | end 78 | elseif event_type == "CREATE" then 79 | return path 80 | end 81 | return nil 82 | end, 83 | 84 | _getInstallDir = function(self, name, version) 85 | local f = io.popen("gem list --local --details " .. name .. " -v " .. version .. " | grep 'Installed at:'") 86 | for line in f:lines() do 87 | local istart, iend = line:find(": ") 88 | return string.sub(line, iend+1) .. "/gems" 89 | end 90 | f:close() 91 | return nil 92 | end, 93 | 94 | _getModules = function(self, module_dir) 95 | local modules = {} 96 | local modinfo = {} 97 | local f = io.popen("gem list --local --details | grep '^[a-zA-Z].*)$\\|Installed at:'") 98 | for line in f:lines() do 99 | if line:find("[(]") ~= nil then 100 | -- module name and version 101 | modinfo[1] = string.gsub(line, "[()]", "") 102 | else 103 | -- installation path 104 | local istart, iend = line:find(": ") 105 | modinfo[2] = string.sub(line, iend+1) .. "/gems" 106 | if modinfo[2]:find(module_dir) == 1 then 107 | table.insert(modules, modinfo) 108 | end 109 | modinfo = {} 110 | end 111 | end 112 | f:close() 113 | return modules 114 | end, 115 | 116 | _getFileList = function(self, module_dir, name, version) 117 | local filelist = {} 118 | local prefix = name .. "-" .. version .. "/" 119 | local f = io.popen("gem contents " .. name .. " -v " .. version) 120 | for line in f:lines() do 121 | local path = line:sub(module_dir:len()+prefix:len()+2) 122 | table.insert(filelist, {path, nil}) 123 | end 124 | f:close() 125 | return filelist 126 | end, 127 | } 128 | 129 | return rubygems 130 | 131 | -- vim: ts=4 sts=4 sw=4 expandtab 132 | -------------------------------------------------------------------------------- /rockspecs/alienvfs-scm-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "AlienVFS" 2 | version = "scm-1" 3 | source = { 4 | url = "git+https://github.com/gobolinux/AlienVFS" 5 | } 6 | description = { 7 | detailed = [[ 8 | AlienVFS is a virtual filesystem that mirrors modules installed by 9 | programming language package managers under a centralized directory. 10 | ]], 11 | homepage = "https://github.com/gobolinux/AlienVFS", 12 | license = "GNU GPL v2" 13 | } 14 | dependencies = { 15 | "flu", 16 | "luaposix", 17 | "lunajson", 18 | "inotify", 19 | "inspect", 20 | "lanes" 21 | } 22 | build = { 23 | type = "builtin", 24 | modules = { 25 | ["gobo.alienvfs.pip"] = "gobo/alienvfs/pip.lua", 26 | ["gobo.alienvfs.cpan"] = "gobo/alienvfs/cpan.lua", 27 | ["gobo.alienvfs.luarocks"] = "gobo/alienvfs/luarocks.lua", 28 | ["gobo.alienvfs.rubygems"] = "gobo/alienvfs/rubygems.lua", 29 | ["gobo.alienvfs.config"] = "gobo/alienvfs/config.lua" 30 | }, 31 | install = { 32 | bin = { 33 | "AlienVFS" 34 | } 35 | } 36 | } 37 | --------------------------------------------------------------------------------