This is a read-only snapshot of the ComputerCraft forums, taken in April 2020.
toxicwolf's profile picture

OCL [Operating System Compatibility Layer] 0.4

Started by toxicwolf, 09 July 2012 - 09:13 AM
toxicwolf #1
Posted 09 July 2012 - 11:13 AM
Latest News:


-OCL has now been added to GitHub!



The OCL is designed to act as an interface between custom Operating Systems, and core ComputerCraft functions. This allows program writers to make a program that will run smoothly on any of the OCL compatible OSs. All OS developers need to do to become compatible with the OCL is to adapt the CraftOS version of the api file to their Operating System - the custom OCL shell does the rest!

The OCL also allows for OSs to have custom shell files, without overwriting the default shell in the rom/ folder. The custom OCL shell file does replace the original ComputerCraft one, however it retains all default behaviour. The only additions are loading of the OCL api file (either the custom OS's one, or the CraftOS one) and then loading of the custom shell - if there is one. If not, it will behave like normal.

Rather than program writers re-writing their program many times to provide certain compatibility with different OSs, they may use the OCL functions instead, which will be handled by the currently loaded, OCL compatible Operating System.

The OCL will be better and more useful if more OSs join the project! Any developers are free to suggest features or code to be added to the project - it may provide extra compatibility with certain Operating Systems!

Installation

There is a client (or user) installation required. Just open up the zip archive and drag and drop the 'mods' folder into your '.minecraft' folder, allowing your computer to merge and replace folders and files.
And that's it! The only difference the user will see is the OS version upon boot, which will also include the OCL version number. Apart from that, there should be no other visual difference.


Developers

For program writers wishing to use the OCL in their program(s), a function list may be found in the 'Documentation' section of the README.txt found in the zip archive.

For Operating System developers wishing to make their OS compatible with the OCL, take a look at the OCL file included in the archive. It is written for CraftOS, however it serves as a good template for what you should include in your version of the OCL file.
Some functions require little to no changes, as they just make calls to other internal functions; however other functions do need adapting to how your OS runs. In most cases this will probably just be function calls to your own functions from within your OS that already do the same thing.
Do not change the structure of the file in any way, including adding/removing/changing functions or function arguments, as it will cause issues.

The best way to check if the client has the OCL installed (to keep compatibility with non-OCL clients) is to attempt to call an OCL function. Since the shell will automatically load any OCL api files, attempting to call a function when the api hasn't been loaded will fail.

If you want to help add to the OCL, or require further changes/additions to the project for your OS to work with it, please feel free to contact me here, or at my email address (found inside the files). Also, feel free to make pull requests on the OCL GitHub page, and I will look at merging your additions!


Downloads:

OCL 0.4

Toxic Wolf
Edited on 10 July 2012 - 09:43 PM
ardera #2
Posted 09 July 2012 - 01:02 PM
I think our OS will become Compatible with OCL :)/>/>
EDIT: And: Good Idea!
toxicwolf #3
Posted 09 July 2012 - 04:45 PM
I think our OS will become Compatible with OCL B)/>/>
EDIT: And: Good Idea!
Thanks! Of course at the moment I'm also adding compatibility for the OCL to my own OS, WolfOS :)/>/>

EDIT: If you have already downloaded a copy of the OCL, please re-download, as I have just hotfixed a couple of issues.
Edited on 09 July 2012 - 08:19 PM
Mendax #4
Posted 09 July 2012 - 11:01 PM
Wow, OCL looks good. ShadOS will become compatible. (Quick tip: OSCL makes more sense to newbs but sounds pretty bad. If you like OCL just leave it :)/>/> .)
toxicwolf #5
Posted 09 July 2012 - 11:06 PM
Wow, OCL looks good. ShadOS will become compatible. (Quick tip: OSCL makes more sense to newbs but sounds pretty bad. If you like OCL just leave it B)/>/> .)
Thank you! If there's any specific additions you need for your OS to work better with the OCL, then feel free to tell me! That goes for anyone else too B)/>/>

And I did originally think of OSCL as an acronym when I came up with the name, but I also thought it sounded bad :)/>/> I think for now, I will stick with it B)/>/>
Mendax #6
Posted 10 July 2012 - 02:12 PM
So, how do I actually change all these outside the rom? make a ShadOS API? (My OS is a 'Footprint' OS.)
ardera #7
Posted 10 July 2012 - 04:04 PM
what about a Config file, where youre OS can change the Settings?
toxicwolf #8
Posted 10 July 2012 - 04:52 PM
what about a Config file, where youre OS can change the Settings?
What settings are you talking about? I'm sorry I just don't really understand your post :)/>/>

So, how do I actually change all these outside the rom? make a ShadOS API? (My OS is a 'Footprint' OS.)
Create a copy of the OCL file, and customise it for your OS. Then make sure that it is installed/saved into this directory: //OCL/OCL That is, the OCL file is saved inside a folder called OCL in the root of the computer that your OS is being installed to in game. Like I said, the new shell will take care of the rest.

I will also be making example OSs and programs etc soon!
MysticT #9
Posted 10 July 2012 - 07:49 PM
My OS won't be compatible (at least for now), since it changes the boot, so the shell is never executed. Maybe I'll add an OCL api with all the functions, so it still works, but not for now.

I think it would be better to have a common boot method, to be able install different oses at the same time and have a multiboot to use the one you want.
I have already a multiboot system that we could use, there's some things that could be changed/improved, but it works fine.
minizbot2012 #10
Posted 10 July 2012 - 08:38 PM
i believe this is a bug but my startup programs are not running when i run this, i am not using a custom OS or anything like that, just craftOS.
Mendax #11
Posted 10 July 2012 - 10:13 PM
Ok, easy enough. I'll have a go.
toxicwolf #12
Posted 10 July 2012 - 11:34 PM
i believe this is a bug but my startup programs are not running when i run this, i am not using a custom OS or anything like that, just craftOS.
Not sure what's causing that… I've tested the files and between the OCL shell and the CraftOS version of the OCL api, all default behaviour is maintained. Event without the OCL api file, the shell will just behave like normal.
Don't suppose you can give me anything else to go on? And is it just disk/startup file that aren't working, or startup files on the root as well? :)/>/>

My OS won't be compatible (at least for now), since it changes the boot, so the shell is never executed. Maybe I'll add an OCL api with all the functions, so it still works, but not for now.

I think it would be better to have a common boot method, to be able install different oses at the same time and have a multiboot to use the one you want.
I have already a multiboot system that we could use, there's some things that could be changed/improved, but it works fine.
I take it you mean a standardised bios.lua file? And I think I looked at your multi-boot code a while back, but may I have access to it? B)/>/>

Ok, easy enough. I'll have a go.
Great B)/>/> Just shout for any help or additions etc, like I said! :)/>/>

If anyone uses github, the link is in the OP, so please fork & pull request any changes etc you make! B)/>/>
MysticT #13
Posted 10 July 2012 - 11:54 PM
Sure, here's the current code:
Spoiler

-- CC BIOS

local function write(sText)
    local w, h = term.getSize()
    local x, y = term.getCursorPos()
    local nLinesPrinted = 0
    local function newLine()
        if y + 1 <= h then
            term.setCursorPos(1, y + 1)
        else
            term.scroll(1)
            term.setCursorPos(1, h)
        end
        x, y = term.getCursorPos()
        nLinesPrinted = nLinesPrinted + 1
    end
    -- Print the line with proper word wrapping
    while string.len(sText) > 0 do
        local whitespace = string.match( sText, "^[ t]+" )
        if whitespace then
            -- Print whitespace
            term.write( whitespace )
            x,y = term.getCursorPos()
            sText = string.sub( sText, string.len(whitespace) + 1 )
        end
        
        local newline = string.match( sText, "^n" )
        if newline then
            -- Print newlines
            newLine()
            sText = string.sub( sText, 2 )
        end
        
        local text = string.match( sText, "^[^ tn]+" )
        if text then
            sText = string.sub( sText, string.len(text) + 1 )
            if string.len(text) > w then
                -- Print a multiline word                
                while string.len( text ) > 0 do
                if x > w then
                    newLine()
                end
                    term.write( text )
                    text = string.sub( text, (w-x) + 2 )
                    x,y = term.getCursorPos()
                end
            else
                -- Print a word normally
                if x + string.len(text) > w then
                    newLine()
                end
                term.write( text )
                x,y = term.getCursorPos()
            end
        end
    end
    
    return nLinesPrinted
end

local function print( ... )
    local nLinesPrinted = 0
    for n,v in ipairs( { ... } ) do
        nLinesPrinted = nLinesPrinted + write( tostring( v ) )
    end
    nLinesPrinted = nLinesPrinted + write( "n" )
    return nLinesPrinted
end

local sBoot = "rom/CraftOS_1_3.startup"

if fs.exists("boot") then
    if fs.isDir("boot") then
        if fs.exists("boot/loader") and not fs.isDir("boot/loader") then
            sBoot = "boot/loader"
        end
    else
        sBoot = "boot"
    end
end

local function boot()
    local file = fs.open(sBoot, "r")
    if file then
        local func, err = loadstring(file.readAll(), fs.getName(sBoot))
        file.close()
        if func then
            return pcall(func)
        else
            return false, err
        end
    else
        return false, "Error opening file "..sBoot
    end
end

local ok, err = boot()
if not ok then
    term.clear()
    term.setCursorPos(1, 1)
    term.setCursorBlink(false)
    print(err)
    print("Press any key to continue")
    repeat
        local evt = coroutine.yield()
    until evt == "key"
end

os.shutdown()
You need to move bios.lua to rom/CraftOS_1_3.startup, and then replace bios.lua with that code. It will look for a "boot" file or directory in the computer, and if it's present it will boot from it, otherwise it loads CraftOS (from the file you moved).
I wanted to remove the write and print functions or make them smaller. If you have any suggestion, I'll try to implement it.
minizbot2012 #14
Posted 11 July 2012 - 02:15 AM
It is both startup files, I'll take a look at it again tomorrow. I'll also be checking out wolfOS.
toxicwolf #15
Posted 11 July 2012 - 09:21 AM
It is both startup files, I'll take a look at it again tomorrow. I'll also be checking out wolfOS.
I'm running the OCL in my world currently, and the startups are definitely working, because I was doing a lot of WolfOS testing yesterday. Can you just confirm that you installed the shell and api file correctly?

Sure, here's the current code:
Spoiler

-- CC BIOS

local function write(sText)
	local w, h = term.getSize()
	local x, y = term.getCursorPos()
	local nLinesPrinted = 0
	local function newLine()
		if y + 1 <= h then
			term.setCursorPos(1, y + 1)
		else
			term.scroll(1)
			term.setCursorPos(1, h)
		end
		x, y = term.getCursorPos()
		nLinesPrinted = nLinesPrinted + 1
	end
	-- Print the line with proper word wrapping
	while string.len(sText) > 0 do
		local whitespace = string.match( sText, "^[ t]+" )
		if whitespace then
			-- Print whitespace
			term.write( whitespace )
			x,y = term.getCursorPos()
			sText = string.sub( sText, string.len(whitespace) + 1 )
		end
		
		local newline = string.match( sText, "^n" )
		if newline then
			-- Print newlines
			newLine()
			sText = string.sub( sText, 2 )
		end
		
		local text = string.match( sText, "^[^ tn]+" )
		if text then
			sText = string.sub( sText, string.len(text) + 1 )
			if string.len(text) > w then
				-- Print a multiline word				
				while string.len( text ) > 0 do
				if x > w then
					newLine()
				end
					term.write( text )
					text = string.sub( text, (w-x) + 2 )
					x,y = term.getCursorPos()
				end
			else
				-- Print a word normally
				if x + string.len(text) > w then
					newLine()
				end
				term.write( text )
				x,y = term.getCursorPos()
			end
		end
	end
	
	return nLinesPrinted
end

local function print( ... )
	local nLinesPrinted = 0
	for n,v in ipairs( { ... } ) do
		nLinesPrinted = nLinesPrinted + write( tostring( v ) )
	end
	nLinesPrinted = nLinesPrinted + write( "n" )
	return nLinesPrinted
end

local sBoot = "rom/CraftOS_1_3.startup"

if fs.exists("boot") then
	if fs.isDir("boot") then
		if fs.exists("boot/loader") and not fs.isDir("boot/loader") then
			sBoot = "boot/loader"
		end
	else
		sBoot = "boot"
	end
end

local function boot()
	local file = fs.open(sBoot, "r")
	if file then
		local func, err = loadstring(file.readAll(), fs.getName(sBoot))
		file.close()
		if func then
			return pcall(func)
		else
			return false, err
		end
	else
		return false, "Error opening file "..sBoot
	end
end

local ok, err = boot()
if not ok then
	term.clear()
	term.setCursorPos(1, 1)
	term.setCursorBlink(false)
	print(err)
	print("Press any key to continue")
	repeat
		local evt = coroutine.yield()
	until evt == "key"
end

os.shutdown()
You need to move bios.lua to rom/CraftOS_1_3.startup, and then replace bios.lua with that code. It will look for a "boot" file or directory in the computer, and if it's present it will boot from it, otherwise it loads CraftOS (from the file you moved).
I wanted to remove the write and print functions or make them smaller. If you have any suggestion, I'll try to implement it.
Okay, thanks B)/>/> I will see about implementing some kind of multi-boot system into the OCL, or something :)/>/>
minizbot2012 #16
Posted 11 July 2012 - 12:25 PM
It is both startup files, I'll take a look at it again tomorrow. I'll also be checking out wolfOS.
I'm running the OCL in my world currently, and the startups are definitely working, because I was doing a lot of WolfOS testing yesterday. Can you just confirm that you installed the shell and api file correctly?
i did install the shell and the API file correctly
MysticT #17
Posted 11 July 2012 - 02:30 PM
Okay, thanks B)/>/> I will see about implementing some kind of multi-boot system into the OCL, or something :)/>/>
The multi-boot is actually in another file, wich is placed as boot/loader. It checks for files with .startup extension, and shows a menu to choose wich one to boot.
In the .startup file you put all the code to start your os, load every api (since there's no apis loaded, wich is better to make an os) and start the shell (GUI or text-based). This can be changed, but we should make a system that every os follows, so you can have all of them B)/>/>
toxicwolf #18
Posted 11 July 2012 - 04:27 PM
Okay, thanks B)/>/> I will see about implementing some kind of multi-boot system into the OCL, or something :)/>/>
The multi-boot is actually in another file, wich is placed as boot/loader. It checks for files with .startup extension, and shows a menu to choose wich one to boot.
In the .startup file you put all the code to start your os, load every api (since there's no apis loaded, wich is better to make an os) and start the shell (GUI or text-based). This can be changed, but we should make a system that every os follows, so you can have all of them B)/>/>
Thanks :)/>/> I will have a think about how it will work exactly.The current custom shell thing will be made defunct by this new change, but it makes sense B)/>/>

It is both startup files, I'll take a look at it again tomorrow. I'll also be checking out wolfOS.
I'm running the OCL in my world currently, and the startups are definitely working, because I was doing a lot of WolfOS testing yesterday. Can you just confirm that you installed the shell and api file correctly?
i did install the shell and the API file correctly
Hmm.. I honestly don't know what's up with that… Does the string "CraftOS 1.33 - OCL 0.4" appear on the computer after boot? Also, can someone else confirm a working installation of OCL 0.4?
minizbot2012 #19
Posted 11 July 2012 - 08:08 PM
yes the craftOS with the OCL is showing up, it is not launching the startup.
toxicwolf #20
Posted 11 July 2012 - 08:49 PM
yes the craftOS with the OCL is showing up, it is not launching the startup.
Okay, then check that the following code is present in the shell file:

-- If this is the toplevel shell, run the startup programs
if parentShell == nil then
-- Run the startup from the ROM first
local sRomStartup = shell.resolveProgram( "/rom/startup" )
if sRomStartup then
  shell.run( sRomStartup )
end

-- Then run the user created startup, from the disks or the root
local sUserStartup = shell.resolveProgram( "/startup" )
for n,sSide in pairs( redstone.getSides() ) do
  if disk.isPresent( sSide ) and disk.hasData( sSide ) then
   local sDiskStartup = shell.resolveProgram( fs.combine(disk.getMountPath( sSide ), "startup") )
   if sDiskStartup then
    sUserStartup = sDiskStartup
    break
   end
  end
end

if sUserStartup then
  shell.run( sUserStartup )
end
end
It can be found directly after the print() function on line 152 that prints the CraftOS version and OCL version number.
minizbot2012 #21
Posted 11 July 2012 - 09:14 PM

if parentShell == nil then
-- Run the startup from the ROM first
local sRomStartup = shell.resolveProgram( "/rom/startup" )
if sRomStartup then
  shell.run( sRomStartup )
end

-- Then run the user created startup, from the disks or the root
local sUserStartup = shell.resolveProgram( "/startup" )
for n,sSide in pairs( redstone.getSides() ) do
  if disk.isPresent( sSide ) and disk.hasData( sSide ) then
   local sDiskStartup = shell.resolveProgram( fs.combine(disk.getMountPath( sSide ), "startup") )
   if sDiskStartup then
	sUserStartup = sDiskStartup
	break
   end
  end
end

if sUserStartup then
  shell.run( sUserStartup )
end
end
that is what i have in the shell file, i am going to try a CC re-install
EDIT:
after reinstalling computercraft it started to work!!, it was apparently a conflict with the events api.
toxicwolf #22
Posted 11 July 2012 - 09:28 PM
EDIT:
after reinstalling computercraft it started to work!!, it was apparently a conflict with the events api.
Hmm.. Could you link me to this events api, so I could try and find what might have caused the conflict?
minizbot2012 #23
Posted 11 July 2012 - 09:46 PM
http://www.computercraft.info/forums2/index.php?/topic/2217-075-events-api/ is the one i was using. (front page of apis and utilities section of the program library)
toxicwolf #24
Posted 11 July 2012 - 10:48 PM
http://www.computerc...075-events-api/ is the one i was using. (front page of apis and utilities section of the program library)
Okay, thanks for that, I will take a look into the issue. https://github.com/t...lf/OCL/issues/3

EDIT: Okay, I reproduced the problem, and as far as I can tell, it actually seems to be purely an Events API bug, with nothing to do with the OCL. I'll go ahead and close the GitHub issue for now.
I've let the creator know about the issue, so hopefully something can be resolved, as it does look like a very good resource.
Edited on 12 July 2012 - 07:42 PM
toxicwolf #25
Posted 13 July 2012 - 04:04 AM
Okay, thanks B)/>/> I will see about implementing some kind of multi-boot system into the OCL, or something :)/>/>
The multi-boot is actually in another file, wich is placed as boot/loader. It checks for files with .startup extension, and shows a menu to choose wich one to boot.
In the .startup file you put all the code to start your os, load every api (since there's no apis loaded, wich is better to make an os) and start the shell (GUI or text-based). This can be changed, but we should make a system that every os follows, so you can have all of them B)/>/>
Take a look at today's commits on the GitHub page for the OCL (links are above in OP), and see if you think of what I've done. I hope it was a step or three in the right direction (after chucking the custom shell code out of the window etc) B)/>/>

I also changed the write function to something of my own creation, and while it is probably inferior, it does the job well enough to warrant the change. It is also a lot smaller than the original.
kazagistar #26
Posted 13 July 2012 - 06:31 PM
Unless I am misunderstanding the source code I see, I feel like OCL is trying to add too much functionality, which might be incompatible with some operating systems. What EXACTLY is the stated purpose of this program? I tend to come from the unix-y school of multiple small utilities functioning independently, and I feel that you are adding too many features in one monolithic component. Here is what I feel would be most useful for me, each as a separate concern in some way.

(1) Parallel boot loader, that can be used to run a custom bios. Allows the user to select which OS they want to run.

(2) A standard compatibility library between operating systems.

In my opinion, these two should be separate, to the point of being essentially separate projects, so that if someone does not want OCL, they can just use the bootloader, and if someone does not want to use the boot-loader, they can still run an OCL operating system without any problems. I propose that bios.lua look into "/rom/boot/" and "/boot/" which will contain replacement bios files for each possible OS, and run the selected one. Other features can be added, but I really don't think it should be forcing the programmers to conform to the OCL specifications if they don't want to for whatever reason. If you don't, people might be forced to run your system in a compatibility layer just to run their system properly which would be a silly state of affairs.

As for OCL itself, it makes a lot of assumptions about the nature of your OS which might not hold true. For me, this is particularly relevant, because the way I am designing my OS breaks a lot of those assumptions.

(1) What if an operating system does not load it's programs and such from the hard drive, but rather from some other source, like HTTP or rednet? In other words, I might want to, as part of booting my OS, load my shell/programs/libraries from a specific remote location, and then use loadstring to parse them directly into functions, so the system is at no point stored on the local computer? For example, on a multiplayer server, you might be worried your turtles could be captured by "the enemy", and so you run the OS in such a way that if the turtle is captured, there is no way to recover your code. I don't see how I can sensibly implement OCL.hasCustomShell() on such a system. Maybe this is not really the concern of OCL.

(2) The ID and read/write functions are cool.

(3) I don't understand getRoot(). What is the use case for this? All the use cases that I can think of are covered by the other directory-getting functions.

(4) The other path getting functions are neat, and a good idea, but they share the same problem as #1. I really don't see a fix for this that does not increase complexity by a significant amount; I guess the only way to create a "remote" filesystem would probably be to modify "fs" significantly and allow special paths that link to virtual files.

(5) I really like the abstraction on the idea of "apps". It is clean, simple, and allows programs to be written for many systems at once. I feel like this can and should be the core of this library. Here is how I would recommend expanding this: make it possible to query the system for what apps can be run, and then run apps directly. Thus, someone wants to write a multi-OS custom shell, they can easily see what apps are visible, and run one, irrelevent of how the system is assembled. Or, if you wish to deploy a system as multiple separate apps, you can call one from the other, without having to deal with any of the implementation details.

(6) Centralized logging system is an awesome, amazing idea. Not sure what the purpose of getLogFile is though… what if the OS does not actually write the logged information to a file, but just keeps it in memory? Or sends it to be stored on another computer somewhere? What multi-OS program needs to be able to read a log file? If you have user-based file permissions, it is very possible that it wont be able to open the file anyway.

(7) Unless I missed it, there is no removeFromStack(). Thus, if you addToStack, you will be permanently one off. Am I wrong? Also, I don't see the point of this exactly. If the OS wishes to track what programs you have run, it can override the various methods of calling a program to do so. What is the use case for this exactly?

(8) I have no idea how exit program possibly works.

(9) OCL.run() does not allow arguments to be passed into the called program. This is a major, major flaw, in my opinion.

This kind of API really is a very cool boon to authors of operating systems and software. However, I feel that the focus is too scattered, and forces certain ideas of what an OS should and shouldn't do which prevents it from reaching it's full potential. The problem I feel is that there is a disconnect between the natural lua way of viewing a program as a function, and the way this program view it, as a file. In lua, there is no real difference between a program file and a function, save the fact that one is stored on the disk. You load em up into a function, pass in parameters, and get return results: this is how it SHOULD be, how I structure my code, how I structure my OS. My "run" functions can take a path or a function: both work the same.

One other thing: what kind of functionality are you assuming is availible to the program? Are you assuming that all the standard libraries exist unchanged? Because I certainly have done my fair share of tinkering with libraries. What if "fs" is modified or removed? The program will silently fail. This should at the very least be made explicit, but here is another proposal: turn this into a more generic "dependancy manager". The interface would let you view what programs and libraries are availible, at what versions. You could load the libraries you need (if they are not loaded) via a standard interface, or run installed apps.

Then, separately, you would define a number of "library interfaces". These could include a read-write interface, a logging interface, a "standard filesystem locations" interface, etc. An OS can implement whichever ones in wants to, and the user can require whichever libraries and programs they want to, and the system would determine if the program can or cannot run in the given environment. The program would look like this:

requireLib("OCL.term", 1.0)
requireLib("OCL.log", 1.0)
requireLib("OCL.paths", 1.0)
requireLib("someNetworkingSystem", 0.32)
requireApp("programCore") -- these could have versions too in theory
requireApp("someDatabase")
OCL.log("Program started")
OCL.term.nPrint("This program is going to call another")
local result = runApp("programCore")
someNetworkingSystem.send(result)
runApp("database","store",result)
-- etc
requireLib would make sure the library exists at the correct version. It might load the library from a file if needed or whatever else the OS writer decides. If it cannot load a correct version of the library into the current global namespace for whatever reason, it will throw an error. Thus, the program writer will be assured that all the correct functionality exists, and the user will be informed with a helpful error message infoming them exactly what is missing from their system.

PS: Could you please license it? Linking or distributing non-licensed copyrighted code is illegal, so it would be helpful if you tossed in some kind of disclaimer or whatever allowing usage. I am a big fan of WTFPL, but whatever you prefer.
toxicwolf #27
Posted 13 July 2012 - 07:12 PM
kazagistar, thanks you for your very insightful (albeit very long B)/>/> ) post. To be quite honest, some of the things you mention I have not thought about or didn't really know about - I'm quite the amateur.
Thanks for pointing out some things that I missed, such as for the run function. I agree with the points you bring up though, you have a clear idea on a much better way for the OCL to work.
I will try to rebuild the system, but I could probably do with help. After all, I do want this to be a community run project, so that it can be as good and as useful as possible.

For now, I'll be thoroughly re-reading your post and try to see what I can come up with.

PS: Thanks for the heads up on the license thing, I really didn't know about that! :)/>/>
kazagistar #28
Posted 13 July 2012 - 07:51 PM
I will very gladly start submitting patches to your github project as soon as the licensing is set up. The main reasons I haven't started writing code already are the following.
  • Since the idea was yours, I wouldn't want to just start hacking away at it in a way wouldn't like at all.
  • If you didn't like my alternative version, we would end up with competing standards, which would weaken both. I would rather have a standard that I feel is flawed but functional then have two competing standards that conflict.
  • I have a tendency to over-abstract, which can be really really bad for something like a voluntary interface standard. If the minimum requirements are easy to understand, then it is more likely that an OS author or program writer will work to conform to them. So it is better for me to work with others.
Code, like every written work, is copyrighted automatically under the full possible protections of the law, whether you write a copyright statement or not. In theory, code could belong under a "procedure", which is something that cannot be copyrighted, but alas, that is not how the courts decided many decades ago. Thus you have the same legal protections for code as you would for a song, book, or picture. Of course, in a community like this, it is very very unlikely that anyone will ever sue (that would be silly) but the principle still stands. That is why there exists a range of licenses for code. If you just give someone code, they cannot legally redistribute it, even if they modify it, in the same way as they cannot redistribute a mp3, but if you can grant them that permission, and you can attach terms to that permission. For example, they cannot sell it commercially. Or they have to give the author due credit. Or they have to give you a cut of all the profits. Or they have to distribute any modified copies with the source code, under the same license. As I said, I prefer the WTFPL because I honestly don't care what people do with my code, who they sell it to, or if they claim to have made it.
MysticT #29
Posted 13 July 2012 - 08:18 PM
I agree with kazagistar, that's why I said my os won't be compatible (with the current version), cause it would be to hard to adapt my os design to that api.
And what I said was to have a boot loader independant of the api, so you can use one without the other.
I would start writing code now, but I think it would be better to agree on the way it will work before starting to code it.
kazagistar #30
Posted 13 July 2012 - 09:15 PM
Here is some ideas on how I think the basic bootloader should work:

First, all defined variables and functions should be local. The it should pass on to the custom loaded bios the same global state that was passed to it, unmodified.

There should be three folders to stick the custom bios: "/rom/bios/", "/bios/" and "/disk/bios/". These should be declared as constants at the top of the program, so it is easy to modify these path names when installing the custom loader.

There should be some way for a user to set a default and effectively disable it, even if they are on SMP and don't have the ability to modify the ROM. I recommend having a file called "default" that, if it exists, is run without prompting. If there are multiple default files, it will select in this order: "/rom/bios", "/bios/", "/disk/bios". (Server owners should have final say or be able to disable it in this way if they want, and the local system needs to be searched before disks, so people can make hack-proof systems.)

The bootloader will then assemble a list of all the bios files in each location by path.

If there are no items in any bios folder, the bootloader will display an error, explaining that you will need a disk to unbrick the computer.

If there is only one option, the bootloader will silently start that bios. This way, a server owner can put the craftos bootloader into "/rom/bios", and everything will work exactly like it normally does, until a user adds more systems.

If there is more then one option, the bootloader will prompt the user to select an option, and then run that option.

Comments or ideas?
MysticT #31
Posted 13 July 2012 - 09:47 PM
First, all defined variables and functions should be local. The it should pass on to the custom loaded bios the same global state that was passed to it, unmodified.
Of course, the bios shouldn't modify anything, just load the boot files.

There should be three folders to stick the custom bios: "/rom/bios/", "/bios/" and "/disk/bios/". These should be declared as constants at the top of the program, so it is easy to modify these path names when installing the custom loader.
How could I never thought of booting from disk, really needed. Just one thing, It would be better to use "boot" instead of "bios", since the bios is what searches the files and loads them, and the files are used to boot the os. Just a naming thing, not really important.

There should be some way for a user to set a default and effectively disable it, even if they are on SMP and don't have the ability to modify the ROM. I recommend having a file called "default" that, if it exists, is run without prompting. If there are multiple default files, it will select in this order: "/rom/bios", "/bios/", "/disk/bios". (Server owners should have final say or be able to disable it in this way if they want, and the local system needs to be searched before disks, so people can make hack-proof systems.)
Good idea, so you can make some kind of single-boot to load just one os. It helps with computers that need to startup without user input, like servers, gps towers, etc.

The bootloader will then assemble a list of all the bios files in each location by path.

If there are no items in any bios folder, the bootloader will display an error, explaining that you will need a disk to unbrick the computer.

If there is only one option, the bootloader will silently start that bios. This way, a server owner can put the craftos bootloader into "/rom/bios", and everything will work exactly like it normally does, until a user adds more systems.

If there is more then one option, the bootloader will prompt the user to select an option, and then run that option.
Nothing to add here.
Ok, I'll start writing something and post what I get.
MysticT #32
Posted 13 July 2012 - 10:40 PM
Ok, here's what I have so far, tell me what you think about it:
Spoiler

-- Version string
local sVersion = "CC Boot Loader 0.1"

-- Paths to search for boot files
local tBootPaths = {
"/rom/boot",
"/boot"
}
-- Wheter to load boot files from disks or not
local bBootFromDisk = true
-- The boot files extension
local sBootExt = "boot"

-- clear function:
-- clears the screen and sets the cursor to the top-left corner
local function clear()
	term.clear()
	term.setCursorPos(1, 1)
end

-- print function:
-- writes the given text to the screen
local function print(s)
	-- get the current cursor position
	local x, y = term.getCursorPos()
	-- get the screen size
	local w, h = term.getSize()
	-- write the text
	term.write(s)
	if y >= h then
		-- scroll the screen
		term.scroll((h - y) + 1)
	else
		-- move the cursor down
		term.setCursorPos(1, y + 1)
	end
end

-- boot function:
-- loads a boot file as a function and then calls it
local function boot(sPath)
	-- open the file
	local file = fs.open(sPath, "r")
	-- if the file is open
	if file then
		-- read the file contents and load the function
		local func, err = loadstring(file.readAll(), fs.getName(sPath))
		-- close the file handle
		file.close()
		-- if the function was loaded correctly
		if func then
			-- make a protected call in case of error
			return pcall(func)
		else
			-- return false to indicate the error and the error message
			return false, err
		end
	end
	-- error opening the file
	return false, "Error opening file "..sPath
end

-- getBootFiles function:
-- searches a directory for boot files
local function getBootFiles(sPath, tList)
	-- for each entry in the directory
	for _,name in ipairs(fs.list(sPath)) do
		-- get the full path to the file/directory
		local path = fs.combine(sPath, name)
		-- check if it's a file
		if not fs.isDir(path) then
			-- check if it's a boot file
			local s = string.match(v, "^(.*)%."..sBootExt.."$")
			if s then
				-- add it to the list
				table.insert(tList, path)
			end
		end
	end
end

-- The list of boot files
local tList = {}

-- Get every boot file on the specified paths
for _,path in ipairs(tBootPaths) do
	if fs.exists(path) and fs.isDir(path) then
		getBootFiles(path, tList)
	end
end
-- Get boot files from disks
if bBootFromDisk then
	for _,side in ipairs(rs.getSides()) do
		if peripheral.isPresent(side) and peripheral.getType(side) == "drive" then
			local drive = peripheral.wrap(side)
			if drive.hasData() then
				local sDiskPath = fs.combine(drive.getMountPath, "boot")
				if fs.exists(sDiskPath) and fs.isDir(sDiskPath) then
					getBootFiles(sDiskPath, tList)
				end
			end
		end
	end
end

if #tList == 0 then
	-- No boot file found, let the user know
	print("No boot file found.")
elseif #tList == 1 then
	-- Only one boot file, load it
	boot(tList[1])
else
	-- More than one boot file, let the user choose one
	local nSelected = 1 -- currently selected option
	-- redraw function: redraws the menu
	local function redraw()
		-- clear the screen
		clear()
		-- print the options
		print(sVersion)
		for i, s in ipairs(tList) do
			if i == nSelected then
				print(">"..s)
			else
				print(" "..s)
			end
		end
	end
	while true do
		-- redraw the menu
		redraw()
		-- get a key press
		local evt, k = coroutine.yield("key")
		if k == 28 then -- Enter
			-- clear the screen
			clear()
			-- load the boot file
			local ok, err = boot(tList[nSelected])
			-- check if there was an error
			if not ok then
				-- throw the error
				error(err)
			end
			-- stop the loop
			break
		elseif k == 200 then -- Up
			-- Move the cursor up
			if nSelected > 1 then
				nSelected = nSelected - 1
			else
				nSelected = #tList
			end
		elseif k == 208 then -- Down
			-- Move the cursor down
			if nSelected < #tList then
				nSelected = nSelected + 1
			else
				nSelected = 1
			end
		end
	end
end

os.shutdown() -- just in case
Warning: not tested.
kazagistar #33
Posted 13 July 2012 - 11:01 PM
About the naming, I said "bios" because that is what the file was originally called that the files in that folder replace, but boot is a good idea. If they are defined near the top of the file, it is easy for a server owner or whatever to modify them to their needs. I just read through some of Mysticos, and you seem to have a lot of this sort of thing in there already with the multi-boot.

Now, for the other part, there is a lot I am still unclear myself about how it should be done. Here is my current idea: the entire standard interface for this consists of just one function, "require(…)", in the global scope (or maybe some other scope, like OS?) You pass to it a set of strings, each one representing to the OS what "requirement sets" you wish to demand, that look somewhat like a path. Here is an example of what might look like:

require("std:turtle", "std:term", "ocl:log(1.0)", "ocl:paths(1.0)", "ocl:io(0.7)", "myos:networking(3.2)")
The only requirement to be minimally OCL compliant that you have a function that when called, tells you if it cannot provide any of the given parameters, and throws a helpful error telling you what is missing, and maybe what you can do to fix it. Now, I am not sure if this is too confusing or not: the names in the strings are defined as separate "interfaces", that you can be compliant with, but they don't have to all be in a single table. For example, you could have an interface which guarantees specific functions in multiple tables, or that guarantees functions directly in the global namespace, etc. Now, most of the standard interfaces should be well behaved, but this allows custom interface authors some wiggle room. Also note, none of the interfaces have to actually exist before the function is called. It is perfectly viable for an OS to load the particular needed interface directly in require.

(BTW, I tried to keep it simple, at just one function, but tell me if this seems over the top.)

The rest of the OCL standard would be a set of these interfaces. There would be an "std:(whatever)" for a full, functioning implementation of each standard API that comes with craftos (except maybe OS?), and just calling "std" would ensure all of them. I am not sure where the global functions would go, or if we even want to deal with that (what if you cannot access "print()" or "getfenv()" or even "ipairs")? The alternative is to just roll that into a OCL library. There would also be the OCL standard libraries. Here is a few ideas for those (names are placeholders):
OCL:log = OCL.log(text)
OCL:io = OCL.nWrite(…) OCL.read(…) etc
OCL:app = OCL.run(app, arguments) OCL.deployApp(?) OCL.removeApp(?) etc
OCL:base = OCL.shutdown() OCL.restart() OCL.version() OCL.info() etc

The number one thing I am worried about is how fine grained these should be, for permissions use cases.
kazagistar #34
Posted 13 July 2012 - 11:31 PM
I can't test it yet, cause I am on my netbook at the moment, which is unable run minecraft, but at a glance it seems ok. The test would be to copy bios.lua to /rom/boot and see if it works.

Hmm, why did you chose to search for os "by extention"? In what ways is this better then just declaring the "boot" folder to be reserved for OS bootloaders and leave it at that? If users or OS authors want store other files, they can put them elsewhere, I don’t see a case in which this might be a problem. Also there is issue #5 in the bugtracker already which relates to fitting more options on the screen, and being able to use number keys instead of arrows again might be nice, but these are minor details that can wait indefinitely.

Anyways, I am going to head home so I can start slinging some code… hopefully my coding style is not too weird. If only there was some way I could run computercraft without minecraft (/me stares meaningfully at mysticT).
MysticT #35
Posted 13 July 2012 - 11:46 PM
I like the require function idea. Each os can implement it the way they want, checking if it's already loaded or loading it from a predefined folder or something.
For global functions, it's better to have some defined in the ocl api, and maybe add a function that loads them to the global scope, so you can use them like always (you could assign them yourself, but having a function that does it would be nice).

Hmm, why did you chose to search for os "by extention"? In what ways is this better then just declaring the "boot" folder to be reserved for OS bootloaders and leave it at that? If users or OS authors want store other files, they can put them elsewhere, I don’t see a case in which this might be a problem. Also there is issue #5 in the bugtracker already which relates to fitting more options on the screen, and being able to use number keys instead of arrows again might be nice, but these are minor details that can wait indefinitely.
Well, I copied most of the code from the boot loader I already had, and I used extensions because I needed to add more files that shouldn't be seen as a boot file.
It's not really needed, since you can simply put those files in a folder, so it can be removed. (I don't like extensions, but it was the easier solution I found at that time)
I also thought about the menu being limited, but it shouldn't be a problem for now (there isn't that much oses yet). Anyway, it wouldn't be too hard to implement it, but let's focus on more important things now.

Anyways, I am going to head home so I can start slinging some code… hopefully my coding style is not too weird. If only there was some way I could run computercraft without minecraft (/me stares meaningfully at mysticT).
:P/>/>
CCEmu won't be ready in some time, I stoped development for now, to focus on the os. The funny thing is that I started working on it because it was really annoying to have to open minecraft each time I wanted to test something, and I thought it would help develop the os :)/>/>
There's not too much left to do, I need to make the fs api and I can start using it. Then adding redstone and peripherals support shouldn't be to hard, and I already have some ideas on how to do it.
Anyway, this is kinda off-topic, so I better stop.
MysticT #36
Posted 14 July 2012 - 01:55 AM
Ok, new version:
Spoiler

-- Version string
local sVersion = "CC Boot Loader 0.2"

-- Paths to search for boot files
local tBootPaths = {
"/rom/boot",
"/boot"
}
-- Wheter to load boot files from disks or not
local bBootFromDisk = true

-- clear function:
-- clears the screen and sets the cursor to the top-left corner
local function clear()
    term.clear()
    term.setCursorPos(1, 1)
end

-- write function:
-- writes the given text to the screen on the specified line
local function write(s, y)
    -- set the cursor position
    term.setCursorPos(1, y)
    -- write the text
    term.write(s)
end

-- boot function:
-- loads a boot file as a function and then calls it
local function boot(sPath)
    -- open the file
    local file = fs.open(sPath, "r")
    -- if the file is open
    if file then
        -- read the file contents and load the function
        local func, err = loadstring(file.readAll(), fs.getName(sPath))
        -- close the file handle
        file.close()
        -- if the function was loaded correctly
        if func then
            -- make a protected call in case of error
            return pcall(func)
        else
            -- return false to indicate the error and the error message
            return false, err
        end
    end
    -- error opening the file
    return false, "Error opening file "..sPath
end

-- getBootFiles function:
-- searches a directory for boot files
local function getBootFiles(sPath, tList)
    -- for each entry in the directory
    for _,name in ipairs(fs.list(sPath)) do
        -- get the full path to the file/directory
        local path = fs.combine(sPath, name)
        -- check if it's a file
        if not fs.isDir(path) then
            -- add the path to the list
            table.insert(tList, path)
        end
    end
end

-- The list of boot files
local tList = {}

-- Get every boot file on the specified paths
for _,path in ipairs(tBootPaths) do
    -- check if it's a directroy
    if fs.exists(path) and fs.isDir(path) then
        -- check for boot files
        getBootFiles(path, tList)
    end
end
-- Get boot files from disks
if bBootFromDisk then
    -- for each side
    for _,side in ipairs(rs.getSides()) do
        -- check if there's a disk drive
        if peripheral.isPresent(side) and peripheral.getType(side) == "drive" then
            -- get the drive on the current side
            local drive = peripheral.wrap(side)
            -- check if it has a floppy disk
            if drive.hasData() then
                -- get the disk boot path
                local sDiskPath = fs.combine(drive.getMountPath, "boot")
                -- check if it's a directory
                if fs.exists(sDiskPath) and fs.isDir(sDiskPath) then
                    -- check for boot files
                    getBootFiles(sDiskPath, tList)
                end
            end
        end
    end
end

if #tList == 0 then
    -- No boot file found, let the user know
    write("No boot file found.", 1)
    write("Press any key to shutdown", 2)
    coroutine.yield("key")
elseif #tList == 1 then
    -- Only one boot file, load it
    boot(tList[1])
else
    -- More than one boot file, let the user choose one
    -- currently selected option
    local nSelected = 1
    -- scroll amount
    local nScroll = 0
    -- get the terminal size
    local w, h = term.getSize()
    -- redraw function: redraws the menu
    local function redraw()
        -- clear the screen
        clear()
        -- print the boot loader version
        write(sVersion, 1)
        -- print the options
        for i = 1, math.min(#tList, h - 1) do
            if i + nScroll == nSelected then
                write(">"..tList[i + nScroll], i + 1)
            else
                write(" "..tList[i + nScroll], i + 1)
            end
        end
    end
    while true do
        -- redraw the menu
        redraw()
        -- get a key press
        local evt, k = coroutine.yield("key")
        if k == 28 then -- Enter
            -- clear the screen
            clear()
            -- load the boot file
            local ok, err = boot(tList[nSelected])
            -- check if there was an error
            if not ok then
                -- show the error
                write(err, 1)
                write("Press any key to continue", 2)
                coroutine.yield("key")
            end
            -- stop the loop
            break
        elseif k == 200 then -- Up
            -- Move the cursor up
            if nSelected > 1 then
                nSelected = nSelected - 1
                if nSelected == nScroll then
                    nScroll = nScroll - 1
                end
            else
                nSelected = #tList
                if #tList >= h then
                    nScroll = #tList - (h - 1)
                end
            end
        elseif k == 208 then -- Down
            -- Move the cursor down
            if nSelected < #tList then
                nSelected = nSelected + 1
                if nSelected - nScroll >= h then
                    nScroll = nScroll + 1
                end
            else
                nSelected = 1
                nScroll = 0
            end
        end
    end
end

os.shutdown() -- just in case
Added the scrollable menu, so you can have as many boot files as you want. Also removed the need for file extensions.
I tested it and it works, but there might be bugs anyway.
If there's only one boot file, it loads it and you don't even notice the loader was there :P/>/>
kazagistar #37
Posted 14 July 2012 - 05:04 AM
I talked to the owner of the server I play on a bit, and he had a different philosophy for this, along more minimalist lines

-- Bios file name
local biosName = ".bios"
-- a function to try booting a bios, returns true if successful
local function tryBoot(path)
    local file = fs.open(path..biosName, "r")
    if file then
	    local func, err = loadstring(file.readAll())
	    file.close()
	    if func then
		    return pcall(func)
	    else
		    return false
	    end
    end
    return false
end
-- search the root, the drives, and the rom for a valid working bios file
if tryBoot("") then return end
for _,side in ipairs({"top","bottom","left","right","front","back"}) do
    if peripheral.getType(side) == "drive" then
	    local path = peripheral.call(side, "getMountPath")
	    if path then
		    if tryBoot(path.."/") then return end
	    end
    end
end
if tryBoot("rom/") then return end
-- print error and wait for keystroke
term.write("No bios file found, please use a valid boot disk")
while coroutine.yield() ~= "key" do end
(Note, I made this, and it is probably still buggy as well).

The idea is, if you want to create a multi-boot selector, why should we force one on you? Why not let that be selected by the user as well? Thus, this program would load a multi-boot selector, which would in turn load a bios, but you could select a different one if you like. Higher customizability.

Honestly, I prefer your approach more, but just having this as an option is nice, for people to decide what they want more.

BTW, I don't see the "default" thing built in to your system. Any plans on that front?
toxicwolf #38
Posted 14 July 2012 - 11:21 AM
I'm loving the ideas you guys are throwing around, especially the interface and require function idea. I'm wondering what use I'll actually be to this project now, since my knowledge on how this all should work is very limited…I do however seriously appreciate the effort you are putting in!
MysticT #39
Posted 14 July 2012 - 05:52 PM
The idea is, if you want to create a multi-boot selector, why should we force one on you? Why not let that be selected by the user as well? Thus, this program would load a multi-boot selector, which would in turn load a bios, but you could select a different one if you like. Higher customizability.
Well, that was my original idea. If you look at my os bios, it looks for a boot file or directory in the computer's root folder, and loads it, if there isn't one it loads CraftOS. Then, there's two boot files, one for multi-boot and one for single boot of my os.
We could make it that way, and provide a multi-boot loader for those who want it, so you can use it if you don't want to make one yourself.

BTW, I don't see the "default" thing built in to your system. Any plans on that front?
I wasn't sure on how to do it. Should it look for an specific file name in the boot list? Or first check if the file exists and load it without checking for other boot files?

EDIT:
Ok, here's a version without the multi-boot:
Spoiler

-- The boot file name
local sBootName = ".boot"
-- Paths to search for boot files
local tBootPaths = {
"/rom",
"/"
}
-- Wheter to load boot files from disks or not
local bBootFromDisk = true

-- clear function:
-- clears the screen and sets the cursor to the top-left corner
local function clear()
	term.clear()
	term.setCursorPos(1, 1)
end

-- print function:
-- writes the given text to the screen
local function print(s)
	-- get the current cursor position
	local x, y = term.getCursorPos()
	-- get the terminal size
	local w, h = term.getSize()
	-- write the text
	term.write(s)
	-- check if the cursor is at the last line
	if y >= h then
		-- scroll the screen
		scroll((y - h) + 1)
	else
		-- move the cursor down
		term.setCursorPos(1, y + 1)
	end
end

-- getBoot function:
-- loads a boot file as a function
local function getBoot(sPath)
	-- open the file
	local file = fs.open(sPath, "r")
	-- if the file is open
	if file then
		-- read the file contents and load the function
		local func, err = loadstring(file.readAll(), fs.getName(sPath))
		-- close the file handle
		file.close()
		-- if the function was loaded correctly
		if not func then
			-- return nil to indicate the error and the error message
			return nil, err
		end
		-- return the boot function
		return func
	end
	-- error opening the file
	return nil, "Error opening file "..sPath
end

local bootFunc

-- Try to load boot files from the specified paths
for _,path in ipairs(tBootPaths) do
	-- get the full path to the file
	local sPath = fs.combine(path, sBootName)
	-- check if it exists and is a file
	if fs.exists(sPath) and not fs.isDir(sPath) then
		-- try to load the boot
		bootFunc = getBoot(sPath)
		-- if the boot loaded
		if bootFunc ~= nil then
			-- stop searching
			break
		end
	end
end
-- try to load boot files from disks
if bootFunc == nil and bBootFromDisk then
	-- for each side
	for _,side in ipairs(rs.getSides()) do
		-- check if there's a disk drive
		if peripheral.isPresent(side) and peripheral.getType(side) == "drive" then
			-- get the drive on the current side
			local drive = peripheral.wrap(side)
			-- check if it has a floppy disk
			if drive.hasData() then
				-- get the disk boot path
				local sDiskPath = fs.combine(drive.getMountPath, sBootName)
				-- check if it exists and is a file
				if fs.exists(sDiskPath) and not fs.isDir(sDiskPath) then
					-- try to load the boot
					bootFunc = getBoot(sPath)
					-- if the boot loaded
					if bootFunc ~= nil then
						-- stop searching
						break
					end
				end
			end
		end
	end
end

if bootFunc == nil then
	-- No boot loaded, let the user know
	print("No boot files could be loaded")
	print("Press any key to shutdown")
	coroutine.yield("key")
else
	-- try to boot
	local ok, err = pcall(bootFunc)
	-- check if there was an error
	if not ok then
		-- show the error
		clear()
		print(err)
		print("Press any key to continue")
		coroutine.yield("key")
	end
end

os.shutdown() -- just in case
I'm not sure on how to display if there's an error on the boot files. Currently, if all the boot files have an error, it will just say "No boot files could be loaded", but won't say what the error was.
Edited on 14 July 2012 - 09:38 PM
Lyqyd #40
Posted 14 July 2012 - 11:06 PM
@toxicwolf - Fix submitted for #7 on github.
toxicwolf #41
Posted 15 July 2012 - 11:33 AM
@toxicwolf - Fix submitted for #7 on github.
Thank you for that :P/>/> Have merged the commit and will test now.

EDIT: The fix doesn't work properly, I've added the information to the issue on github.
Edited on 16 July 2012 - 08:40 AM
toxicwolf #42
Posted 16 July 2012 - 10:38 AM
Ok, new version:
Spoiler

snip
Added the scrollable menu, so you can have as many boot files as you want. Also removed the need for file extensions.
I tested it and it works, but there might be bugs anyway.
If there's only one boot file, it loads it and you don't even notice the loader was there :P/>/>
What file were you saving that code to? I take it you replaced the bios with it, but when I did, peripheral.wrap() throws an error.
Anyway, should there be a way to bypass the bootloader unless you want to boot a different bios than the one that was last time/the one set as default? This way, users won't have to go through the menu each time they want to boot the same OS on a computer that has multiple boot files.
MysticT #43
Posted 16 July 2012 - 03:46 PM
peripheral.wrap is defined in the CraftOS bios, so if you don't start CraftOS before your os (or whatever you were booting) it doesn't exist. If you want to boot an os with this loader, you need to define everything yourself and load all the apis.
Most of the "oses" just load on top of CraftOS, so they are not real oses, they are just custom shells on top of the os.
toxicwolf #44
Posted 16 July 2012 - 04:31 PM
peripheral.wrap is defined in the CraftOS bios, so if you don't start CraftOS before your os (or whatever you were booting) it doesn't exist. If you want to boot an os with this loader, you need to define everything yourself and load all the apis.
Most of the "oses" just load on top of CraftOS, so they are not real oses, they are just custom shells on top of the os.
Yeah, I get that :P/>/> I just mean, how exactly is the code supposed to be used - when is the file loaded etc.
MysticT #45
Posted 16 July 2012 - 04:50 PM
If you use the one with multi-boot, it will search for files in "/rom/boot", "/boot" and "/disk/boot" (and every disk present). So you just place your os loader in one of those directories and it will be on the menu (or will be loaded if there's no more boot files).
toxicwolf #46
Posted 16 July 2012 - 06:37 PM
If you use the one with multi-boot, it will search for files in "/rom/boot", "/boot" and "/disk/boot" (and every disk present). So you just place your os loader in one of those directories and it will be on the menu (or will be loaded if there's no more boot files).
Ah, no, I mean where does the multi-boot go?
MysticT #47
Posted 16 July 2012 - 07:42 PM
You need to replace bios.lua with that code, like you already did.
toxicwolf #48
Posted 16 July 2012 - 07:45 PM
You need to replace bios.lua with that code, like you already did.
Thought so, but then peripheral.wrap() isn't defined.. Sorry for being slow, by the way.
MysticT #49
Posted 16 July 2012 - 07:51 PM
Like I said before, that's because peripheral.wrap is defined in the CraftOS bios/boot file. So, unless you boot CraftOS (by placing the old bios.lua in a file inside one of the boot paths), it's not defined, it's nil. You have to define it on your os.
toxicwolf #50
Posted 16 July 2012 - 08:00 PM
So the bootloader already requires CraftOS to run, isn't that kind of backwards? Could you just define peripheral.wrap as a local function in the bootloader?
MysticT #51
Posted 16 July 2012 - 08:07 PM
Ok, now I understand the problem. No, it doesn't require CraftOS, I just didn't test it with disk, so didn't see the error.
New version, fixed the peripheral.wrap error:
Multi-boot version:
Spoiler

-- Version string
local sVersion = "CC Boot Loader 0.3"

-- Paths to search for boot files
local tBootPaths = {
"/rom/boot",
"/boot"
}
-- Wheter to load boot files from disks or not
local bBootFromDisk = true

-- clear function:
-- clears the screen and sets the cursor to the top-left corner
local function clear()
    term.clear()
    term.setCursorPos(1, 1)
end

-- write function:
-- writes the given text to the screen on the specified line
local function write(s, y)
    -- set the cursor position
    term.setCursorPos(1, y)
    -- write the text
    term.write(s)
end

-- boot function:
-- loads a boot file as a function and then calls it
local function boot(sPath)
    -- open the file
    local file = fs.open(sPath, "r")
    -- if the file is open
    if file then
        -- read the file contents and load the function
        local func, err = loadstring(file.readAll(), fs.getName(sPath))
        -- close the file handle
        file.close()
        -- if the function was loaded correctly
        if func then
            -- make a protected call in case of error
            return pcall(func)
        else
            -- return false to indicate the error and the error message
            return false, err
        end
    end
    -- error opening the file
    return false, "Error opening file "..sPath
end

-- getBootFiles function:
-- searches a directory for boot files
local function getBootFiles(sPath, tList)
    -- for each entry in the directory
    for _,name in ipairs(fs.list(sPath)) do
        -- get the full path to the file/directory
        local path = fs.combine(sPath, name)
        -- check if it's a file
        if not fs.isDir(path) then
            -- add the path to the list
            table.insert(tList, path)
        end
    end
end

-- The list of boot files
local tList = {}

-- Get every boot file on the specified paths
for _,path in ipairs(tBootPaths) do
    -- check if it's a directroy
    if fs.exists(path) and fs.isDir(path) then
        -- check for boot files
        getBootFiles(path, tList)
    end
end
-- Get boot files from disks
if bBootFromDisk then
    -- for each side
    for _,side in ipairs(rs.getSides()) do
        -- check if there's a disk drive
        if peripheral.isPresent(side) and peripheral.getType(side) == "drive" then
            -- check if it has a floppy disk
            if peripheral.call(side, "hasData") then
                -- get the disk boot path
                local sDiskPath = fs.combine(peripheral.call(side, "getMountPath"), "boot")
                -- check if it's a directory
                if fs.exists(sDiskPath) and fs.isDir(sDiskPath) then
                    -- check for boot files
                    getBootFiles(sDiskPath, tList)
                end
            end
        end
    end
end

if #tList == 0 then
    -- No boot file found, let the user know
    write("No boot file found.", 1)
    write("Press any key to shutdown", 2)
    coroutine.yield("key")
elseif #tList == 1 then
    -- Only one boot file, load it
    boot(tList[1])
else
    -- More than one boot file, let the user choose one
    -- currently selected option
    local nSelected = 1
    -- scroll amount
    local nScroll = 0
    -- get the terminal size
    local w, h = term.getSize()
    -- redraw function: redraws the menu
    local function redraw()
        -- clear the screen
        clear()
        -- print the boot loader version
        write(sVersion, 1)
        -- print the options
        for i = 1, math.min(#tList, h - 1) do
            if i + nScroll == nSelected then
                write(">"..tList[i + nScroll], i + 1)
            else
                write(" "..tList[i + nScroll], i + 1)
            end
        end
    end
    while true do
        -- redraw the menu
        redraw()
        -- get a key press
        local evt, k = coroutine.yield("key")
        if k == 28 then -- Enter
            -- clear the screen
            clear()
            -- load the boot file
            local ok, err = boot(tList[nSelected])
            -- check if there was an error
            if not ok then
                -- show the error
                write(err, 1)
                write("Press any key to continue", 2)
                coroutine.yield("key")
            end
            -- stop the loop
            break
        elseif k == 200 then -- Up
            -- Move the cursor up
            if nSelected > 1 then
                nSelected = nSelected - 1
                if nSelected == nScroll then
                    nScroll = nScroll - 1
                end
            else
                nSelected = #tList
                if #tList >= h then
                    nScroll = #tList - (h - 1)
                end
            end
        elseif k == 208 then -- Down
            -- Move the cursor down
            if nSelected < #tList then
                nSelected = nSelected + 1
                if nSelected - nScroll >= h then
                    nScroll = nScroll + 1
                end
            else
                nSelected = 1
                nScroll = 0
            end
        end
    end
end

os.shutdown() -- just in case
Non multi-boot version:
Spoiler

-- The boot file name
local sBootName = ".boot"
-- Paths to search for boot files
local tBootPaths = {
"/rom",
"/"
}
-- Wheter to load boot files from disks or not
local bBootFromDisk = true

-- clear function:
-- clears the screen and sets the cursor to the top-left corner
local function clear()
    term.clear()
    term.setCursorPos(1, 1)
end

-- print function:
-- writes the given text to the screen
local function print(s)
    -- get the current cursor position
    local x, y = term.getCursorPos()
    -- get the terminal size
    local w, h = term.getSize()
    -- write the text
    term.write(s)
    -- check if the cursor is at the last line
    if y >= h then
        -- scroll the screen
        scroll((y - h) + 1)
    else
        -- move the cursor down
        term.setCursorPos(1, y + 1)
    end
end

-- getBoot function:
-- loads a boot file as a function
local function getBoot(sPath)
    -- open the file
    local file = fs.open(sPath, "r")
    -- if the file is open
    if file then
        -- read the file contents and load the function
        local func, err = loadstring(file.readAll(), fs.getName(sPath))
        -- close the file handle
        file.close()
        -- if the function was loaded correctly
        if not func then
            -- return nil to indicate the error and the error message
            return nil, err
        end
        -- return the boot function
        return func
    end
    -- error opening the file
    return nil, "Error opening file "..sPath
end

local bootFunc

-- try to load boot files from the specified paths
for _,path in ipairs(tBootPaths) do
    -- get the full path to the file
    local sPath = fs.combine(path, sBootName)
    -- check if it exists and is a file
    if fs.exists(sPath) and not fs.isDir(sPath) then
        -- try to load the boot
        bootFunc = getBoot(sPath)
        -- if the boot loaded
        if bootFunc ~= nil then
            -- stop searching
            break
        end
    end
end
-- try to load boot files from disks
if bootFunc == nil and bBootFromDisk then
    -- for each side
    for _,side in ipairs(rs.getSides()) do
        -- check if there's a disk drive
        if peripheral.isPresent(side) and peripheral.getType(side) == "drive" then
            -- check if it has a floppy disk
            if peripheral.call(side, "hasData") then
                -- get the disk boot path
                local sDiskPath = fs.combine(peripheral.call(side, "getMountPath"), sBootName)
                -- check if it exists and is a file
                if fs.exists(sDiskPath) and not fs.isDir(sDiskPath) then
                    -- try to load the boot
                    bootFunc = getBoot(sPath)
                    -- if the boot loaded
                    if bootFunc ~= nil then
                        -- stop searching
                        break
                    end
                end
            end
        end
    end
end

if bootFunc == nil then
    -- No boot loaded, let the user know
    print("No boot files could be loaded")
    print("Press any key to shutdown")
    coroutine.yield("key")
else
    -- try to boot
    local ok, err = pcall(bootFunc)
    -- check if there was an error
    if not ok then
        -- show the error
        clear()
        print(err)
        print("Press any key to continue")
        coroutine.yield("key")
    end
end

os.shutdown() -- just in case
toxicwolf #52
Posted 16 July 2012 - 08:21 PM
Ok, now I understand the problem. No, it doesn't require CraftOS, I just didn't test it with disk, so didn't see the error.
New version, fixed the peripheral.wrap error:
Nice :P/>/> I had forgotten about the peripheral.call function, makes for a simple work around.

Also, I wondered what your view is on this:
Anyway, should there be a way to bypass the bootloader unless you want to boot a different bios than the one that was last time/the one set as default? This way, users won't have to go through the menu each time they want to boot the same OS on a computer that has multiple boot files.
This is about the multiboot, not the other one, since that obviously just loads the first boot file without a fuss.

And could you explain to me about the 'default' thing a bit more? From what I gather, it's a boot file that is loaded above all others, if it's present (with the default file in /rom having a higher priority than the root or a disk). I just am not sure how it would work; would it look for a specific file name?
Edited on 16 July 2012 - 06:32 PM
MysticT #53
Posted 16 July 2012 - 09:25 PM
That's what I'm not sure about how it should be implemented. I think looking for an specific file name is the easier way. If someone has any idea on that, I'll like to hear it.

Also, I just figured that it would be better to look first on the computer and then the rom (for the non multi-boot version), since it wouldn't alow oses to load if CraftOS is in the rom. I'll change that later.
toxicwolf #54
Posted 16 July 2012 - 09:44 PM
That's what I'm not sure about how it should be implemented. I think looking for an specific file name is the easier way. If someone has any idea on that, I'll like to hear it.
I think it would also make more sense to a user or server admin too.
PatriotBob #55
Posted 20 July 2012 - 07:47 AM
I looked over a lot of code and while I think the OCL is something computer craft needs, I just don't agree with the way OCL is going about it.

I've been going about writing bios.lua and the rom to some extent to create better seperation.
Bios should handle basic boot device detection and selection.
Once a device is chosen, there needs to be a standard boot loader for all devices. Disks, local stores and rom.

So for the OCL to be successful (read: useful) it needs to provide a standardized bios and mandated boot loader config. Some of these things you have.

But to do it right things have to be separate and concise.
I've been tried to follow current computer hardware separation as much as possible and have been building a *nix based OS on top of it.
Maybe I'm just partial to my implementation though… :P/>/>
neojames #56
Posted 02 September 2012 - 11:09 PM
Whats happend to this? The download link is down and the repo no longer exists. I think a inter-OS compatibility layer is a brillient idea and would love to use it in my programs, but I can't find where I would get it!
Lyqyd #57
Posted 03 September 2012 - 02:10 AM
There's a fork still up on my github, I believe.
MysticT #58
Posted 03 September 2012 - 03:07 AM
I don't know why toxicwolf removed the repo. I'm working on a new version and will post it when it's ready. It will be a little different, it's based on one of kazagistar's suggestions.
Mailmanq! #59
Posted 21 November 2012 - 05:47 AM
I like the idea of a custom shell, of course I am too stupid to do it.
comp500 #60
Posted 19 February 2015 - 05:11 PM
One other thing: what kind of functionality are you assuming is availible to the program? Are you assuming that all the standard libraries exist unchanged? Because I certainly have done my fair share of tinkering with libraries. What if "fs" is modified or removed? The program will silently fail. This should at the very least be made explicit, but here is another proposal: turn this into a more generic "dependancy manager". The interface would let you view what programs and libraries are availible, at what versions. You could load the libraries you need (if they are not loaded) via a standard interface, or run installed apps.

Then, separately, you would define a number of "library interfaces". These could include a read-write interface, a logging interface, a "standard filesystem locations" interface, etc. An OS can implement whichever ones in wants to, and the user can require whichever libraries and programs they want to, and the system would determine if the program can or cannot run in the given environment. The program would look like this:

requireLib("OCL.term", 1.0)
requireLib("OCL.log", 1.0)
requireLib("OCL.paths", 1.0)
requireLib("someNetworkingSystem", 0.32)
requireApp("programCore") -- these could have versions too in theory
requireApp("someDatabase")
OCL.log("Program started")
OCL.term.nPrint("This program is going to call another")
local result = runApp("programCore")
someNetworkingSystem.send(result)
runApp("database","store",result)
-- etc
requireLib would make sure the library exists at the correct version. It might load the library from a file if needed or whatever else the OS writer decides. If it cannot load a correct version of the library into the current global namespace for whatever reason, it will throw an error. Thus, the program writer will be assured that all the correct functionality exists, and the user will be informed with a helpful error message infoming them exactly what is missing from their system.

PS: Could you please license it? Linking or distributing non-licensed copyrighted code is illegal, so it would be helpful if you tossed in some kind of disclaimer or whatever allowing usage. I am a big fan of WTFPL, but whatever you prefer.
I can't find the code for OCL (github 404, dropbox 401) but I have made a require() function… it could be used with a package manager (like in npm) to install the packages as dependencies… or something like that.
http://www.computerc...o-include-apis/

also another thought, this sort of thing could implement POSIX, or some CC equivalent