├── LICENSE ├── LICENSE.p9p ├── NinePea.cpp ├── NinePea.h ├── README ├── doc ├── macros.ms ├── mkfile └── paper.ms ├── examples ├── eia9p.c ├── pinfs │ └── pinfs.ino ├── randomfs │ └── randomfs.ino ├── robotfs │ └── robotfs.ino └── tty9p.c └── mkfile /LICENSE: -------------------------------------------------------------------------------- 1 | MIT/X Consortium License 2 | 3 | (C)opyright MMV-MMVI Anselm R. Garbe 4 | (C)opyright MMVI Kris Maglione 5 | (C)opyright MMXII Eli Cohen 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a 8 | copy of this software and associated documentation files (the "Software"), 9 | to deal in the Software without restriction, including without limitation 10 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 11 | and/or sell copies of the Software, and to permit persons to whom the 12 | Software is furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 20 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /LICENSE.p9p: -------------------------------------------------------------------------------- 1 | The bulk of this software is derived from Plan 9 and is thus distributed 2 | under the Lucent Public License, Version 1.02, reproduced below. 3 | 4 | There are a few exceptions: libutf, libfmt, and libregexp are distributed 5 | under simpler BSD-like boilerplates. See the LICENSE files in those 6 | directories. There are other exceptions, also marked with LICENSE files 7 | in their directories. 8 | 9 | The bitmap fonts in the font/luc, font/lucm, font/lucsans, and font/pelm 10 | directory are copyright B&H Inc. and distributed under more restricted 11 | terms under agreement with B&H. See the NOTICE file in those directories. 12 | 13 | =================================================================== 14 | 15 | Lucent Public License Version 1.02 16 | 17 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS PUBLIC 18 | LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE 19 | PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 20 | 21 | 1. DEFINITIONS 22 | 23 | "Contribution" means: 24 | 25 | a. in the case of Lucent Technologies Inc. ("LUCENT"), the Original 26 | Program, and 27 | b. in the case of each Contributor, 28 | 29 | i. changes to the Program, and 30 | ii. additions to the Program; 31 | 32 | where such changes and/or additions to the Program were added to the 33 | Program by such Contributor itself or anyone acting on such 34 | Contributor's behalf, and the Contributor explicitly consents, in 35 | accordance with Section 3C, to characterization of the changes and/or 36 | additions as Contributions. 37 | 38 | "Contributor" means LUCENT and any other entity that has Contributed a 39 | Contribution to the Program. 40 | 41 | "Distributor" means a Recipient that distributes the Program, 42 | modifications to the Program, or any part thereof. 43 | 44 | "Licensed Patents" mean patent claims licensable by a Contributor 45 | which are necessarily infringed by the use or sale of its Contribution 46 | alone or when combined with the Program. 47 | 48 | "Original Program" means the original version of the software 49 | accompanying this Agreement as released by LUCENT, including source 50 | code, object code and documentation, if any. 51 | 52 | "Program" means the Original Program and Contributions or any part 53 | thereof 54 | 55 | "Recipient" means anyone who receives the Program under this 56 | Agreement, including all Contributors. 57 | 58 | 2. GRANT OF RIGHTS 59 | 60 | a. Subject to the terms of this Agreement, each Contributor hereby 61 | grants Recipient a non-exclusive, worldwide, royalty-free copyright 62 | license to reproduce, prepare derivative works of, publicly display, 63 | publicly perform, distribute and sublicense the Contribution of such 64 | Contributor, if any, and such derivative works, in source code and 65 | object code form. 66 | 67 | b. Subject to the terms of this Agreement, each Contributor hereby 68 | grants Recipient a non-exclusive, worldwide, royalty-free patent 69 | license under Licensed Patents to make, use, sell, offer to sell, 70 | import and otherwise transfer the Contribution of such Contributor, if 71 | any, in source code and object code form. The patent license granted 72 | by a Contributor shall also apply to the combination of the 73 | Contribution of that Contributor and the Program if, at the time the 74 | Contribution is added by the Contributor, such addition of the 75 | Contribution causes such combination to be covered by the Licensed 76 | Patents. The patent license granted by a Contributor shall not apply 77 | to (i) any other combinations which include the Contribution, nor to 78 | (ii) Contributions of other Contributors. No hardware per se is 79 | licensed hereunder. 80 | 81 | c. Recipient understands that although each Contributor grants the 82 | licenses to its Contributions set forth herein, no assurances are 83 | provided by any Contributor that the Program does not infringe the 84 | patent or other intellectual property rights of any other entity. Each 85 | Contributor disclaims any liability to Recipient for claims brought by 86 | any other entity based on infringement of intellectual property rights 87 | or otherwise. As a condition to exercising the rights and licenses 88 | granted hereunder, each Recipient hereby assumes sole responsibility 89 | to secure any other intellectual property rights needed, if any. For 90 | example, if a third party patent license is required to allow 91 | Recipient to distribute the Program, it is Recipient's responsibility 92 | to acquire that license before distributing the Program. 93 | 94 | d. Each Contributor represents that to its knowledge it has sufficient 95 | copyright rights in its Contribution, if any, to grant the copyright 96 | license set forth in this Agreement. 97 | 98 | 3. REQUIREMENTS 99 | 100 | A. Distributor may choose to distribute the Program in any form under 101 | this Agreement or under its own license agreement, provided that: 102 | 103 | a. it complies with the terms and conditions of this Agreement; 104 | 105 | b. if the Program is distributed in source code or other tangible 106 | form, a copy of this Agreement or Distributor's own license agreement 107 | is included with each copy of the Program; and 108 | 109 | c. if distributed under Distributor's own license agreement, such 110 | license agreement: 111 | 112 | i. effectively disclaims on behalf of all Contributors all warranties 113 | and conditions, express and implied, including warranties or 114 | conditions of title and non-infringement, and implied warranties or 115 | conditions of merchantability and fitness for a particular purpose; 116 | ii. effectively excludes on behalf of all Contributors all liability 117 | for damages, including direct, indirect, special, incidental and 118 | consequential damages, such as lost profits; and 119 | iii. states that any provisions which differ from this Agreement are 120 | offered by that Contributor alone and not by any other party. 121 | 122 | B. Each Distributor must include the following in a conspicuous 123 | location in the Program: 124 | 125 | Copyright (C) 2003, Lucent Technologies Inc. and others. All Rights 126 | Reserved. 127 | 128 | C. In addition, each Contributor must identify itself as the 129 | originator of its Contribution in a manner that reasonably allows 130 | subsequent Recipients to identify the originator of the Contribution. 131 | Also, each Contributor must agree that the additions and/or changes 132 | are intended to be a Contribution. Once a Contribution is contributed, 133 | it may not thereafter be revoked. 134 | 135 | 4. COMMERCIAL DISTRIBUTION 136 | 137 | Commercial distributors of software may accept certain 138 | responsibilities with respect to end users, business partners and the 139 | like. While this license is intended to facilitate the commercial use 140 | of the Program, the Distributor who includes the Program in a 141 | commercial product offering should do so in a manner which does not 142 | create potential liability for Contributors. Therefore, if a 143 | Distributor includes the Program in a commercial product offering, 144 | such Distributor ("Commercial Distributor") hereby agrees to defend 145 | and indemnify every Contributor ("Indemnified Contributor") against 146 | any losses, damages and costs (collectively"Losses") arising from 147 | claims, lawsuits and other legal actions brought by a third party 148 | against the Indemnified Contributor to the extent caused by the acts 149 | or omissions of such Commercial Distributor in connection with its 150 | distribution of the Program in a commercial product offering. The 151 | obligations in this section do not apply to any claims or Losses 152 | relating to any actual or alleged intellectual property infringement. 153 | In order to qualify, an Indemnified Contributor must: a) promptly 154 | notify the Commercial Distributor in writing of such claim, and b) 155 | allow the Commercial Distributor to control, and cooperate with the 156 | Commercial Distributor in, the defense and any related settlement 157 | negotiations. The Indemnified Contributor may participate in any such 158 | claim at its own expense. 159 | 160 | For example, a Distributor might include the Program in a commercial 161 | product offering, Product X. That Distributor is then a Commercial 162 | Distributor. If that Commercial Distributor then makes performance 163 | claims, or offers warranties related to Product X, those performance 164 | claims and warranties are such Commercial Distributor's responsibility 165 | alone. Under this section, the Commercial Distributor would have to 166 | defend claims against the Contributors related to those performance 167 | claims and warranties, and if a court requires any Contributor to pay 168 | any damages as a result, the Commercial Distributor must pay those 169 | damages. 170 | 171 | 5. NO WARRANTY 172 | 173 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS 174 | PROVIDED ON AN"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 175 | KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY 176 | WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY 177 | OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely 178 | responsible for determining the appropriateness of using and 179 | distributing the Program and assumes all risks associated with its 180 | exercise of rights under this Agreement, including but not limited to 181 | the risks and costs of program errors, compliance with applicable 182 | laws, damage to or loss of data, programs or equipment, and 183 | unavailability or interruption of operations. 184 | 185 | 6. DISCLAIMER OF LIABILITY 186 | 187 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR 188 | ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, 189 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING 190 | WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF 191 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 192 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR 193 | DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED 194 | HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 195 | 196 | 7. EXPORT CONTROL 197 | 198 | Recipient agrees that Recipient alone is responsible for compliance 199 | with the United States export administration regulations (and the 200 | export control laws and regulation of any other countries). 201 | 202 | 8. GENERAL 203 | 204 | If any provision of this Agreement is invalid or unenforceable under 205 | applicable law, it shall not affect the validity or enforceability of 206 | the remainder of the terms of this Agreement, and without further 207 | action by the parties hereto, such provision shall be reformed to the 208 | minimum extent necessary to make such provision valid and enforceable. 209 | 210 | If Recipient institutes patent litigation against a Contributor with 211 | respect to a patent applicable to software (including a cross-claim or 212 | counterclaim in a lawsuit), then any patent licenses granted by that 213 | Contributor to such Recipient under this Agreement shall terminate as 214 | of the date such litigation is filed. In addition, if Recipient 215 | institutes patent litigation against any entity (including a 216 | cross-claim or counterclaim in a lawsuit) alleging that the Program 217 | itself (excluding combinations of the Program with other software or 218 | hardware) infringes such Recipient's patent(s), then such Recipient's 219 | rights granted under Section 2(b) shall terminate as of the date such 220 | litigation is filed. 221 | 222 | All Recipient's rights under this Agreement shall terminate if it 223 | fails to comply with any of the material terms or conditions of this 224 | Agreement and does not cure such failure in a reasonable period of 225 | time after becoming aware of such noncompliance. If all Recipient's 226 | rights under this Agreement terminate, Recipient agrees to cease use 227 | and distribution of the Program as soon as reasonably practicable. 228 | However, Recipient's obligations under this Agreement and any licenses 229 | granted by Recipient relating to the Program shall continue and 230 | survive. 231 | 232 | LUCENT may publish new versions (including revisions) of this 233 | Agreement from time to time. Each new version of the Agreement will be 234 | given a distinguishing version number. The Program (including 235 | Contributions) may always be distributed subject to the version of the 236 | Agreement under which it was received. In addition, after a new 237 | version of the Agreement is published, Contributor may elect to 238 | distribute the Program (including its Contributions) under the new 239 | version. No one other than LUCENT has the right to modify this 240 | Agreement. Except as expressly stated in Sections 2(a) and 2(b) above, 241 | Recipient receives no rights or licenses to the intellectual property 242 | of any Contributor under this Agreement, whether expressly, by 243 | implication, estoppel or otherwise. All rights in the Program not 244 | expressly granted under this Agreement are reserved. 245 | 246 | This Agreement is governed by the laws of the State of New York and 247 | the intellectual property laws of the United States of America. No 248 | party to this Agreement will bring a legal action under this Agreement 249 | more than one year after the cause of action arose. Each party waives 250 | its rights to a jury trial in any resulting litigation. 251 | 252 | -------------------------------------------------------------------------------- /NinePea.cpp: -------------------------------------------------------------------------------- 1 | #include "NinePea.h" 2 | 3 | struct htable *fs_fids; 4 | 5 | int 6 | puthdr(unsigned char *buf, unsigned long index, unsigned char type, unsigned int tag, unsigned long size) { 7 | put4(buf, index, size); 8 | buf[index++] = type; 9 | put2(buf, index, tag); 10 | 11 | return index; 12 | } 13 | 14 | int 15 | mkerr(unsigned char *buffer, unsigned char tag, char *errstr) { 16 | int index; 17 | int slen = strlen(errstr); 18 | int size = slen + 9; 19 | 20 | index = puthdr(buffer, 0, RError, tag, size); 21 | 22 | put2(buffer, index, slen); 23 | memcpy(&buffer[index], errstr, slen); 24 | 25 | return size; 26 | } 27 | 28 | unsigned long 29 | putstat(unsigned char *buffer, unsigned long index, Stat *stat) { 30 | unsigned int namelen = strlen(stat->name); 31 | unsigned int uidlen = strlen(stat->uid); 32 | unsigned int gidlen = strlen(stat->gid); 33 | unsigned int muidlen = strlen(stat->muid); 34 | 35 | unsigned int size = 2 + 4 + (1 + 4 + 8) + 4 + 4 + 4 + 8 + (2 * 4) + namelen + uidlen + gidlen + muidlen; 36 | put2(buffer, index, size); 37 | 38 | put2(buffer, index, stat->type); 39 | put4(buffer, index, stat->dev); 40 | buffer[index++] = stat->qid.type; 41 | put4(buffer, index, 0); 42 | put8(buffer, index, stat->qid.path, 0); 43 | put4(buffer, index, stat->mode); 44 | put4(buffer, index, stat->atime); 45 | put4(buffer, index, stat->mtime); 46 | put8(buffer, index, stat->length, 0); 47 | 48 | put2(buffer, index, namelen); 49 | memcpy(&buffer[index], stat->name, namelen); 50 | index += namelen; 51 | 52 | put2(buffer, index, uidlen); 53 | memcpy(&buffer[index], stat->uid, uidlen); 54 | index += uidlen; 55 | 56 | put2(buffer, index, gidlen); 57 | memcpy(&buffer[index], stat->gid, gidlen); 58 | index += gidlen; 59 | 60 | put2(buffer, index, muidlen); 61 | memcpy(&buffer[index], stat->muid, muidlen); 62 | index += muidlen; 63 | 64 | return (size + 2); 65 | } 66 | 67 | unsigned long 68 | getstat(unsigned char *buffer, unsigned long index, Stat *stat) { 69 | unsigned int size; 70 | unsigned long tmp; 71 | unsigned int namelen; 72 | unsigned int uidlen; 73 | unsigned int gidlen; 74 | unsigned int muidlen; 75 | 76 | get2(buffer, index, size); 77 | 78 | get2(buffer, index, stat->type); 79 | get4(buffer, index, stat->dev); 80 | stat->qid.type = buffer[index++]; 81 | index += 4; //get4(buffer, index, 0); 82 | get8(buffer, index, stat->qid.path, tmp); 83 | get4(buffer, index, stat->mode); 84 | get4(buffer, index, stat->atime); 85 | get4(buffer, index, stat->mtime); 86 | get8(buffer, index, stat->length, tmp); 87 | 88 | get2(buffer, index, namelen); 89 | stat->name = (char*)malloc (sizeof (char) * (namelen + 1)); 90 | stat->name[namelen] = '\0'; 91 | memcpy(stat->name, &buffer[index], namelen); 92 | index += namelen; 93 | 94 | get2(buffer, index, uidlen); 95 | stat->uid = (char*)malloc (sizeof (char) * (uidlen + 1)); 96 | stat->uid[uidlen] = '\0'; 97 | memcpy(stat->uid, &buffer[index], uidlen); 98 | index += uidlen; 99 | 100 | get2(buffer, index, gidlen); 101 | stat->gid = (char*)malloc (sizeof (char) * (gidlen + 1)); 102 | stat->gid[gidlen] = '\0'; 103 | memcpy(stat->gid, &buffer[index], gidlen); 104 | index += gidlen; 105 | 106 | get2(buffer, index, muidlen); 107 | stat->muid = (char*)malloc (sizeof (char) * (muidlen + 1)); 108 | stat->muid[muidlen] = '\0'; 109 | memcpy(stat->muid, &buffer[index], muidlen); 110 | index += muidlen; 111 | 112 | return index; 113 | } 114 | 115 | char Etoobig[] = "bad count"; 116 | char Ebadtype[] = "9P protocol botch"; 117 | char Enofile[] = "file not found"; 118 | char Eperm[] = "permission denied"; 119 | 120 | unsigned long 121 | proc9p(unsigned char *msg, unsigned long size, Callbacks *cb) { 122 | Fcall ifcall; 123 | Fcall *ofcall = NULL; 124 | unsigned long slen, tmp; 125 | unsigned long index; 126 | unsigned char i; 127 | 128 | msg[size] = '\0'; 129 | index = 4; 130 | ifcall.type = msg[index++]; 131 | get2(msg, index, ifcall.tag); 132 | 133 | if (size > MAX_MSG) { 134 | return mkerr(msg, ifcall.tag, Etoobig); 135 | } 136 | 137 | // if it isn't here, it isn't implemented 138 | switch(ifcall.type) { 139 | case TVersion: 140 | i = index; 141 | index = 7; 142 | 143 | get4(msg, i, ifcall.msize); 144 | get2(msg, i, slen); 145 | 146 | if (ifcall.msize > MAX_IO) 147 | ifcall.msize = MAX_IO; 148 | 149 | put4(msg, index, ifcall.msize); 150 | put2(msg, index, slen); 151 | 152 | index += slen; 153 | puthdr(msg, 0, RVersion, ifcall.tag, index); 154 | 155 | break; 156 | case TAuth: 157 | index = mkerr(msg, ifcall.tag, (char*)"no auth"); 158 | break; 159 | case TAttach: 160 | get4(msg, index, ifcall.fid); 161 | get4(msg, index, ifcall.afid); 162 | 163 | get2(msg, index, slen); 164 | index += slen; 165 | 166 | get2(msg, index, slen); 167 | msg[index-2] = '\0'; 168 | ifcall.uname = strdup((char*)&msg[index-2-slen]); 169 | 170 | index += slen; 171 | msg[index] = '\0'; 172 | ifcall.aname = strdup((char*)&msg[index-slen]); 173 | 174 | ofcall = cb->attach(&ifcall); 175 | 176 | free(ifcall.uname); 177 | free(ifcall.aname); 178 | 179 | if (ofcall->type == RError) { 180 | index = mkerr(msg, ifcall.tag, ofcall->ename); 181 | 182 | break; 183 | } 184 | 185 | index = 7; 186 | msg[index++] = ofcall->qid.type; 187 | put4(msg, index, 0); //ofcall->qid.version); 188 | put8(msg, index, ofcall->qid.path, 0); 189 | puthdr(msg, 0, RAttach, ifcall.tag, index); 190 | 191 | break; 192 | case TWalk: 193 | get4(msg, index, ifcall.fid); 194 | get4(msg, index, ifcall.newfid); 195 | get2(msg, index, ifcall.nwname); 196 | 197 | if (ifcall.nwname > MAX_WELEM) 198 | ifcall.nwname = MAX_WELEM; 199 | 200 | get2(msg, index, slen); 201 | for (i = 0; i < ifcall.nwname; i++) { 202 | index += slen; 203 | get2(msg, index, tmp); 204 | msg[index-2] = '\0'; 205 | ifcall.wname[i] = strdup((char*)&msg[index-2-slen]); 206 | slen = tmp; 207 | } 208 | 209 | ofcall = cb->walk(&ifcall); 210 | 211 | for (i = 0; i < ifcall.nwname; i++) 212 | free(ifcall.wname[i]); 213 | 214 | if (ofcall->type == RError) { 215 | index = mkerr(msg, ifcall.tag, ofcall->ename); 216 | 217 | break; 218 | } 219 | 220 | index = puthdr(msg, 0, RWalk, ifcall.tag, 9 + ofcall->nwqid * 13); 221 | put2(msg, index, ofcall->nwqid); 222 | 223 | for (i = 0; i < ofcall->nwqid; i++) { 224 | msg[index++] = ofcall->wqid[i].type; 225 | index += 4; //put4(msg, index, ofcall->wqid[i].version); 226 | put8(msg, index, ofcall->wqid[i].path, 0); 227 | } 228 | 229 | break; 230 | case TStat: 231 | get4(msg, index, ifcall.fid); 232 | 233 | ofcall = cb->stat(&ifcall); 234 | 235 | if (ofcall->type == RError) { 236 | index = mkerr(msg, ifcall.tag, ofcall->ename); 237 | 238 | break; 239 | } 240 | 241 | slen = putstat(msg, 9, &(ofcall->stat)); 242 | index = puthdr(msg, 0, RStat, ifcall.tag, slen + 9); 243 | put2(msg, index, slen); // bleh? 244 | index += slen; 245 | 246 | break; 247 | case TClunk: 248 | get4(msg, index, ifcall.fid); 249 | 250 | ofcall = cb->clunk(&ifcall); 251 | 252 | if (ofcall->type == RError) { 253 | index = mkerr(msg, ifcall.tag, ofcall->ename); 254 | 255 | break; 256 | } 257 | 258 | index = puthdr(msg, 0, RClunk, ifcall.tag, 7); 259 | 260 | break; 261 | case TOpen: 262 | get4(msg, index, ifcall.fid); 263 | ifcall.mode = msg[index++]; 264 | 265 | ofcall = cb->open(&ifcall); 266 | 267 | if (ofcall->type == RError) { 268 | index = mkerr(msg, ifcall.tag, ofcall->ename); 269 | 270 | break; 271 | } 272 | 273 | index = puthdr(msg, 0, ROpen, ifcall.tag, 24); 274 | msg[index++] = ofcall->qid.type; 275 | index += 4; //put4(msg, index, ofcall->qid.version); 276 | put8(msg, index, ofcall->qid.path, 0); 277 | put4(msg, index, MAX_IO); 278 | 279 | break; 280 | case TRead: 281 | get4(msg, index, ifcall.fid); 282 | get4(msg, index, ifcall.offset); 283 | index += 4; // :( 284 | get4(msg, index, ifcall.count); 285 | 286 | if (ifcall.count > MAX_IO) { 287 | index = mkerr(msg, ifcall.tag, Etoobig); 288 | 289 | break; 290 | } 291 | 292 | ofcall = cb->read(&ifcall, &msg[11]); 293 | 294 | if (ofcall->count > MAX_IO) { 295 | index = mkerr(msg, ifcall.tag, Etoobig); 296 | 297 | break; 298 | } 299 | 300 | 301 | if (ofcall->type == RError) { 302 | index = mkerr(msg, ifcall.tag, ofcall->ename); 303 | 304 | break; 305 | } 306 | 307 | index = puthdr(msg, 0, RRead, ifcall.tag, 11 + ofcall->count); 308 | put4(msg, index, ofcall->count); 309 | index += ofcall->count; 310 | 311 | break; 312 | case TCreate: 313 | get4(msg, index, ifcall.fid); 314 | get2(msg, index, slen); 315 | ifcall.name = (char*)&msg[index]; 316 | index += slen; 317 | get4(msg, index, ifcall.perm); 318 | msg[index-4] = '\0'; 319 | ifcall.mode = msg[index++]; 320 | 321 | ofcall = cb->create(&ifcall); 322 | 323 | if (ofcall->type == RError) { 324 | index = mkerr(msg, ifcall.tag, ofcall->ename); 325 | 326 | break; 327 | } 328 | 329 | index = puthdr(msg, 0, RCreate, ifcall.tag, 24); 330 | msg[index++] = ofcall->qid.type; 331 | index += 4; //put4(msg, index, ofcall->qid.version); 332 | put8(msg, index, ofcall->qid.path, 0); 333 | put4(msg, index, MAX_IO); 334 | 335 | break; 336 | case TWrite: 337 | get4(msg, index, ifcall.fid); 338 | get4(msg, index, ifcall.offset); 339 | index += 4; // bleh... again 340 | get4(msg, index, ifcall.count); 341 | 342 | if (ifcall.count > MAX_IO) { 343 | index = mkerr(msg, ifcall.tag, Etoobig); 344 | 345 | break; 346 | } 347 | 348 | ofcall = cb->write(&ifcall, &msg[index]); 349 | 350 | if (ofcall->type == RError) { 351 | index = mkerr(msg, ifcall.tag, ofcall->ename); 352 | 353 | break; 354 | } 355 | 356 | index = puthdr(msg, 0, RWrite, ifcall.tag, 11); 357 | put4(msg, index, ofcall->count); 358 | 359 | break; 360 | case TRemove: 361 | get4(msg, index, ifcall.fid); 362 | 363 | ofcall = cb->remove(&ifcall); 364 | 365 | if (ofcall->type == RError) { 366 | index = mkerr(msg, ifcall.tag, ofcall->ename); 367 | 368 | break; 369 | } 370 | 371 | index = puthdr(msg, 0, RRemove, ifcall.tag, 7); 372 | 373 | break; 374 | case TFlush: 375 | get2(msg, index, ifcall.oldtag); 376 | 377 | ofcall = cb->flush(&ifcall); 378 | 379 | if (ofcall->type == RError) { 380 | index = mkerr(msg, ifcall.tag, ofcall->ename); 381 | 382 | break; 383 | } 384 | 385 | index = puthdr(msg, 0, RFlush, ifcall.tag, 7); 386 | 387 | break; 388 | case TWStat: 389 | get4(msg, index, ifcall.fid); 390 | get2(msg, index, slen); 391 | index = getstat(msg, index, &ifcall.st); 392 | 393 | ofcall = cb->wstat(&ifcall); 394 | 395 | free (ifcall.st.name); 396 | free (ifcall.st.uid); 397 | free (ifcall.st.gid); 398 | free (ifcall.st.muid); 399 | 400 | if (ofcall->type == RError) { 401 | index = mkerr(msg, ifcall.tag, ofcall->ename); 402 | 403 | break; 404 | } 405 | 406 | index = puthdr(msg, 0, RWStat, ifcall.tag, 7); 407 | break; 408 | default: 409 | index = mkerr(msg, ifcall.tag, Ebadtype); 410 | break; 411 | } 412 | 413 | if (index > MAX_MSG) { 414 | index = mkerr(msg, ifcall.tag, Etoobig); 415 | } 416 | 417 | return index; 418 | } 419 | 420 | /* fid mapping functions */ 421 | 422 | unsigned long 423 | hashf(struct htable *tbl, unsigned long id) { 424 | return id % tbl->length; 425 | } 426 | 427 | struct hentry* 428 | fs_fid_find(unsigned long id) { 429 | struct hentry **cur; 430 | 431 | for (cur = &(fs_fids->data[hashf(fs_fids, id)]); 432 | *cur != NULL; cur = &(*cur)->next) { 433 | if ((*cur)->id == id) 434 | break; 435 | } 436 | 437 | return *cur; 438 | } 439 | 440 | struct hentry* 441 | fs_fid_add(unsigned long id, unsigned long data) { 442 | struct hentry *cur = fs_fid_find(id); 443 | unsigned char h; 444 | 445 | if (cur == NULL) { 446 | cur = (struct hentry*)calloc(1, sizeof(*cur)); 447 | cur->id = id; 448 | h = hashf(fs_fids, id); 449 | 450 | if (fs_fids->data[h]) { 451 | cur->next = fs_fids->data[h]; 452 | cur->next->prev = cur; 453 | } 454 | fs_fids->data[h] = cur; 455 | } 456 | 457 | cur->data = data; 458 | 459 | return cur; 460 | } 461 | 462 | void 463 | fs_fid_del(unsigned long id) { 464 | unsigned char h = hashf(fs_fids, id); 465 | struct hentry *cur = fs_fids->data[h]; 466 | 467 | if (cur->id == id) 468 | fs_fids->data[h] = cur->next; 469 | else { 470 | cur = cur->next; 471 | while (cur) { 472 | if (cur->id == id) 473 | break; 474 | 475 | cur = cur->next; 476 | } 477 | } 478 | 479 | if (cur == NULL) { 480 | return; 481 | } 482 | 483 | if (cur->prev) 484 | cur->prev->next = cur->next; 485 | if (cur->next) 486 | cur->next->prev = cur->prev; 487 | 488 | free(cur); 489 | } 490 | 491 | void 492 | fs_fid_init(int l) { 493 | fs_fids = (struct htable*)malloc(sizeof(struct htable)); 494 | fs_fids->length = l; 495 | fs_fids->data = (struct hentry**)calloc(fs_fids->length, sizeof(struct hentry*)); 496 | } 497 | -------------------------------------------------------------------------------- /NinePea.h: -------------------------------------------------------------------------------- 1 | #ifndef NINEPEA_H 2 | #define NINEPEA_H 3 | 4 | #ifdef Plan9 5 | #include 6 | #include 7 | #define NULL nil 8 | #else 9 | #include 10 | #include 11 | #endif 12 | 13 | #define put2(buffer, index, value) \ 14 | buffer[index++] = value & 0xFF; \ 15 | buffer[index++] = (value >> 8) & 0xFF; \ 16 | 17 | #define put4(buffer, index, value) \ 18 | put2(buffer, index, value); \ 19 | put2(buffer, index, (unsigned long)value >> 16UL); \ 20 | 21 | #define put8(buffer, index, lvalue, hvalue) \ 22 | put4(buffer, index, lvalue); \ 23 | put4(buffer, index, hvalue); \ 24 | 25 | #define get2(buffer, index, value) \ 26 | value = buffer[index++]; \ 27 | value |= buffer[index++] << 8; \ 28 | 29 | #define get4(buffer, index, value) \ 30 | get2(buffer, index, value); \ 31 | value |= (unsigned long)buffer[index++] << 16UL; \ 32 | value |= (unsigned long)buffer[index++] << 24UL; \ 33 | 34 | #define get8(buffer, index, lvalue, hvalue) \ 35 | get4(buffer, index, lvalue); \ 36 | get4(buffer, index, hvalue); \ 37 | 38 | // might have to change these depending on memory allocated 39 | #define MAX_IO 2048 40 | #define MAX_MSG MAX_IO+128 41 | #define MAX_WELEM 16 42 | #define MAX_PGMBUF 80 43 | #define NOTAG ~0 44 | 45 | /* 9P message types */ 46 | enum { 47 | TVersion = 'd', 48 | RVersion, 49 | TAuth = 'f', 50 | RAuth, 51 | TAttach = 'h', 52 | RAttach, 53 | TError = 'j', /* illegal */ 54 | RError, 55 | TFlush = 'l', 56 | RFlush, 57 | TWalk = 'n', 58 | RWalk, 59 | TOpen = 'p', 60 | ROpen, 61 | TCreate = 'r', 62 | RCreate, 63 | TRead = 't', 64 | RRead, 65 | TWrite = 'v', 66 | RWrite, 67 | TClunk = 'x', 68 | RClunk, 69 | TRemove = 'z', 70 | RRemove, 71 | TStat = 124, 72 | RStat, 73 | TWStat = 126, 74 | RWStat, 75 | }; 76 | #ifndef Plan9 77 | /* bits in Qid.type */ 78 | enum { 79 | QTDIR = 0x80, /* type bit for directories */ 80 | QTAPPEND = 0x40, /* type bit for append only files */ 81 | QTEXCL = 0x20, /* type bit for exclusive use files */ 82 | QTMOUNT = 0x10, /* type bit for mounted channel */ 83 | QTAUTH = 0x08, /* type bit for authentication file */ 84 | QTTMP = 0x04, /* type bit for non-backed-up file */ 85 | QTSYMLINK = 0x02, /* type bit for symbolic link */ 86 | QTFILE = 0x00 /* type bits for plain file */ 87 | }; 88 | 89 | /* from libc.h in p9p */ 90 | enum { 91 | OREAD = 0, /* open for read */ 92 | OWRITE = 1, /* write */ 93 | ORDWR = 2, /* read and write */ 94 | OEXEC = 3, /* execute, == read but check execute permission */ 95 | OTRUNC = 16, /* or'ed in (except for exec), truncate file first */ 96 | OCEXEC = 32, /* or'ed in, close on exec */ 97 | ORCLOSE = 64, /* or'ed in, remove on close */ 98 | ODIRECT = 128, /* or'ed in, direct access */ 99 | ONONBLOCK = 256, /* or'ed in, non-blocking call */ 100 | OEXCL = 0x1000, /* or'ed in, exclusive use (create only) */ 101 | OLOCK = 0x2000, /* or'ed in, lock after opening */ 102 | OAPPEND = 0x4000 /* or'ed in, append only */ 103 | }; 104 | 105 | /* Larger than int, can't be enum */ 106 | #define DMDIR 0x80000000 /* mode bit for directories */ 107 | #define DMAPPEND 0x40000000 /* mode bit for append only files */ 108 | #define DMEXCL 0x20000000 /* mode bit for exclusive use files */ 109 | #define DMMOUNT 0x10000000 /* mode bit for mounted channel */ 110 | #define DMAUTH 0x08000000 /* mode bit for authentication file */ 111 | #define DMTMP 0x04000000 /* mode bit for non-backed-up file */ 112 | 113 | typedef struct { 114 | unsigned char type; 115 | unsigned long version; 116 | unsigned long path; 117 | } Qid; 118 | #endif 119 | 120 | typedef struct { 121 | unsigned int type; 122 | unsigned long dev; 123 | Qid qid; 124 | unsigned long mode; 125 | unsigned long atime; 126 | unsigned long mtime; 127 | unsigned long length; 128 | char *name; 129 | char *uid; 130 | char *gid; 131 | char *muid; 132 | } Stat; 133 | 134 | typedef struct { 135 | unsigned char type; 136 | unsigned long tag; 137 | unsigned long fid; 138 | 139 | union { 140 | struct {/* Tversion, Rversion */ 141 | unsigned long msize; 142 | // char *version; 143 | }; 144 | struct { /* Tflush */ 145 | unsigned int oldtag; 146 | }; 147 | struct { /* Rerror */ 148 | char *ename; 149 | }; 150 | struct { /* Ropen, Rcreate */ 151 | Qid qid; /* +Rattach */ 152 | // unsigned long iounit; 153 | }; 154 | struct { /* Rauth */ 155 | Qid aqid; 156 | }; 157 | struct { /* Tauth, Tattach */ 158 | unsigned long afid; 159 | char *uname; 160 | char *aname; 161 | }; 162 | struct { /* Tcreate */ 163 | unsigned long perm; 164 | char *name; 165 | unsigned char mode; /* +Topen */ 166 | }; 167 | struct { /* Twalk */ 168 | unsigned long newfid; 169 | unsigned int nwname; 170 | char *wname[MAX_WELEM]; 171 | }; 172 | struct { /* Rwalk */ 173 | unsigned int nwqid; 174 | Qid wqid[MAX_WELEM]; 175 | }; 176 | struct { 177 | unsigned long offset; /* Tread, Twrite */ 178 | unsigned long count; /* Tread, Twrite, Rread */ 179 | // char *data; /* Twrite, Rread */ 180 | }; 181 | struct { /* Rstat */ 182 | unsigned long nstat; 183 | Stat stat; 184 | }; 185 | struct { /* Twstat */ 186 | Stat st; 187 | }; 188 | }; 189 | } Fcall; 190 | 191 | typedef struct { 192 | // Fcall* (*version)(Fcall*); 193 | // Fcall* (*auth)(Fcall*); 194 | Fcall* (*attach)(Fcall*); 195 | Fcall* (*flush)(Fcall*); 196 | Fcall* (*walk)(Fcall*); 197 | Fcall* (*open)(Fcall*); 198 | Fcall* (*create)(Fcall*); 199 | Fcall* (*read)(Fcall*, unsigned char*); 200 | Fcall* (*write)(Fcall*, unsigned char*); 201 | Fcall* (*clunk)(Fcall*); 202 | Fcall* (*remove)(Fcall*); 203 | Fcall* (*stat)(Fcall*); 204 | Fcall* (*wstat)(Fcall*); 205 | } Callbacks; 206 | 207 | unsigned long putstat(unsigned char *buffer, unsigned long index, Stat *stat); 208 | unsigned long proc9p(unsigned char *msg, unsigned long size, Callbacks *cb); 209 | int mkerr(unsigned char*, unsigned char, char*); 210 | int puthdr(unsigned char *buf, unsigned long index, unsigned char type, unsigned int tag, unsigned long size); 211 | 212 | /* fid mapping functions */ 213 | 214 | struct hentry { 215 | unsigned long id; 216 | unsigned long data; 217 | struct hentry *next; 218 | struct hentry *prev; 219 | void *aux; 220 | }; 221 | 222 | struct htable { 223 | unsigned char length; 224 | struct hentry **data; 225 | }; 226 | 227 | struct hentry* fs_fid_find(unsigned long id); 228 | struct hentry* fs_fid_add(unsigned long id, unsigned long data); 229 | void fs_fid_del(unsigned long id); 230 | void fs_fid_init(int l); 231 | 232 | extern char Etoobig[]; 233 | extern char Ebadtype[]; 234 | extern char Enofile[]; 235 | extern char Eperm[]; 236 | 237 | #endif 238 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | 9p library and example for arduino mega 1280 2 | -------------------------------------------------------------------------------- /doc/macros.ms: -------------------------------------------------------------------------------- 1 | .de F1 2 | .nr OI \\n(.iu 3 | .nr PW 1v 4 | .KF 5 | .sp 0.3v 6 | .. 7 | .de T1 8 | .F1 9 | .. 10 | .de F2 11 | .ds Fp Figure\ \\n(Fi 12 | .ds Fn Figure\ \\n+(Fi 13 | .ds Fq \\*(Fp 14 | .F0 15 | .. 16 | .de T2 17 | .ds Tp Table\ \\n(Ti 18 | .ds Tn Table\ \\n+(Ti 19 | .ds Tq \\*(Tp 20 | .T0 21 | .. 22 | .de F0 23 | .nr BD 1 24 | .if t .ps \\n(PS-1 25 | .ie \\n(VS>=41 .vs \\n(VSu-1p 26 | .el .vs \\n(VSp-1p 27 | .ft 1 28 | .di DD 29 | .ll \\n(.lu*3u/4u 30 | .in 0 31 | .fi 32 | .ad b 33 | .sp 0.5v 34 | \f3\\*(Fq\f1\ \ \c 35 | .. 36 | .de T0 37 | .nr BD 1 38 | .if t .ps \\n(PS-1 39 | .ie \\n(VS>=41 .vs \\n(VSu-1p 40 | .el .vs \\n(VSp-1p 41 | .ft 1 42 | .di DD 43 | .ll \\n(.lu*3u/4u 44 | .in 0 45 | .fi 46 | .ad b 47 | .sp 0.5v 48 | \f3\\*(Tq\f1\ \ \c 49 | .. 50 | .de F3 51 | .sp 0.5v 52 | .di 53 | .br 54 | .ll \\n(.lu*4u/3u 55 | .if \\n(dl>\\n(BD .nr BD \\n(dl 56 | .if \\n(BD<\\n(.l .in (\\n(.lu-\\n(BDu)/2u 57 | .nf 58 | .DD 59 | .in \\n(OIu 60 | .nr BD 0 61 | .fi 62 | .KE 63 | .ie \\n(VS>=41 .vs \\n(VSu 64 | .el .vs \\n(VSp 65 | .. 66 | .de T3 67 | .F3 68 | .. 69 | .de EX 70 | .P1 71 | \s-4 72 | .. 73 | .de EE 74 | \s+4 75 | .P2 76 | .. 77 | .nr Fi 1 +1 78 | .nr Ti 1 +1 79 | .ds Fn Figure\ \\n(Fi 80 | .ds Tn Table\ \\n(Ti 81 | .nr XP 2 \" delta point size for program 82 | .nr XV 2p \" delta vertical for programs 83 | .nr XT 4 \" delta tab stop for programs 84 | .nr DV .5v \" space before start of program 85 | .FP lucidasans 86 | .nr PS 11 87 | .nr VS 13 88 | .nr LL 6.6i 89 | .nr PI 0 \" paragraph indent 90 | .nr PD 4p \" extra space between paragraphs 91 | .pl 11i 92 | .rm CH 93 | -------------------------------------------------------------------------------- /doc/mkfile: -------------------------------------------------------------------------------- 1 | $target 6 | 7 | %.pdf:DQ: %.ps 8 | cat /sys/doc/docfonts $stem.ps >_$stem.ps 9 | ps2pdf _$stem.ps $stem.pdf && rm -f _$stem.ps 10 | 11 | > pipe & 39 | $ 9mount tcp!localhost!2000 /mnt/arduino 40 | $ ls /mnt/arduino 41 | d00 d05 d10 d15 d20 d25 d30 d35 d40 d45 d50 d55 d60 d65 42 | d01 d06 d11 d16 d21 d26 d31 d36 d41 d46 d51 d56 d61 d66 43 | d02 d07 d12 d17 d22 d27 d32 d37 d42 d47 d52 d57 d62 d67 44 | d03 d08 d13 d18 d23 d28 d33 d38 d43 d48 d53 d58 d63 d68 45 | d04 d09 d14 d19 d24 d29 d34 d39 d44 d49 d54 d59 d64 d69 46 | .P2 47 | This is the pinfs example running on the Arduino. First a fifo pipe is made with the standard mkfifo command. The tty9p utility is used to set serial port parameters and packetize the 9P messages on the serial port, and it is looped through the fifo pipe and netcat to listen on TCP port 2000. 9mount is a convenience program for making 9P mounting simpler on Linux, and here it is being used to mount the serial port through the above command. Then the filesystem can just be accessed with the standard Linux tools. Shown here is a listing of all the synthetic files being generated by the Arduino board. This example is running on Linux, but a Plan 9 computer can easily mount the service on the Linux computer over TCP. The user can write a 1 or 0 to d13 to turn on or off the LED on digital pin 13 of the Arduino board, and can read or write any of the 70 files to access the digital I/O state of any pin. 48 | .PP 49 | On Plan 9, the serial port can be mounted directly. 9P doesn't care about the underlying transport, so on Plan 9 this works with any file giving a 9P server: 50 | .P1 51 | % mount /shr/usb/eiaU50b4c /n/arduino 52 | .P2 53 | .NH 54 | Randomfs Example 55 | .PP 56 | Another example of 57 | .I NinePea 58 | is the randomfs sketch for Arduino. It presents one file, 59 | .CW random, 60 | which can be mounted over 61 | .CW /dev 62 | to replace Plan 9's 63 | .CW /dev/random. 64 | This sketch is a bare example of a 9P fileserver, it only presents one file. On read it reads each of the 16 analog inputs on the Arduino Mega and XORs them with the system milliseconds counter: 65 | .P1 66 | for (pin = A0; pin < (NUM_ANALOG_INPUTS + A0); pin++) { 67 | seed <<= 1; 68 | seed ^= analogRead(pin) ^ millis(); 69 | } 70 | .P2 71 | This is to seed a Chacha20 pseudo-random number generator. The Chacha20 cipher code [5] in this sketch is borrowed from 9front. The file it presents can be bound before 72 | .CW /dev/random 73 | to replace it with the random number generation from the Arduino. 74 | .CW AnalogRead(pin) 75 | returns the ambient voltage on that pin which is XOR'ed with 76 | .CW millis() 77 | which is used as the seed for a PRNG. 78 | .CW Millis() 79 | returns the number of milliseconds the Arduino has been booted, and the ambient voltage is similar on each pin, so the randomness of the seed might not be great, but then the sketch uses a Chacha20 stream cipher from that seed. The manual page for 80 | .CW /dev/random 81 | specifically says it is only to be used on Plan 9 as a seed for better pseudo-random number generators in the programs themselves. This sketch has not been thoroughly assessed for how well it generates randomness or how cryptographically secure it is. Additional hardware could be attached to the analog inputs to better seed the hardware random number generator device. For example, decaying particles from a block of radioactive metal could be used to input random voltages to the pins for seeding the hardware random number generator. 82 | .NH 83 | Design Choices and Further Examples 84 | .PP 85 | The 86 | .I NinePea 87 | library was designed to be compact. It leaves out a lot of functionality that other 9P libraries have. It doesn't have anything in it specifically for any transport layer. It only operates on buffers which are filled and read from by the implementation. It doesn't multiplex connections; this has to be handled by the implementation, if at all. 88 | .I NinePea 89 | doesn't do anything special for delayed RReads or TFlushes. This can be handled separately by the implementation. TReads can be buffered and replied to only when they are ready. By default, everything is handled in-order by the proc9p function. An incoming TRead is processed immediately and an RRead is sent. One project using this library was a hardware cons device using an Arduino Mega 2560, a keyboard, and a television monitor. In that project, RReads sometimes needed to be delayed, and TFlushes needed to be handled. The way this was achieved was by buffering TReads and handling them separately from other message types elsewhere in a loop. 90 | .PP 91 | Another unfinished project used an ESP32 board as a 9P-speaking wifi dongle. The goal of this project was to make a wifi dongle for Plan 9 that only required a USB-to-serial driver, and then could simply be mounted as a 9P device. In that project delayed RReads were handled by every incoming 9P message spawning a separate thread. When threads were complete, they locked a shared lock and sent their response. 92 | .PP 93 | The fileserver introduced above is an example of 94 | .I NinePea 95 | being used on Linux, instead of an MCU. It layers 96 | .I NinePea 97 | over Linux's video4linux subsystem for webcams. It presents only one synthetic file, 98 | .CW jpeg 99 | , which is a synthetic jpeg generated by taking a picture from the webcam. The contents of this jpeg change over time, always showing the latest image from the webcam. Most people who have used a modern computer are familiar with what a JPEG file is. It's a picture which is generally in disk storage. The user can open it, delete it, etc. A synthetic file's contents are generated, rather than stored on disk. Imagine opening a JPEG and it's the most recent image from a webcam. An implementation of exactly this is V4LFS for Linux which uses 100 | .I NinePea 101 | directly. 102 | .I NinePea 103 | was designed to be portable C which can be included on many different types of systems to create fileservers such as this. 104 | .NH 105 | Comparisons to Other 9P Libraries 106 | .PP 107 | .I NinePea 108 | is intended to be very portable and compact. Running 109 | .CW wc -l 110 | to count the lines of code in all the .c files in /sys/src/lib9p shows that Plan 9's 9P library is only 2781 lines of code. 111 | Libixp for Unix-like systems shows 4045 lines of code in all the C files of the main library. 112 | .I NinePea 113 | however, is only 496 lines of code in NinePea.cpp. Arduino labels the file as C++, but it is completely portable C which can be used on many other systems. Plan 9's Lib9p does a lot of things that NinePea does not. NinePea does not handle authentication, it doesn't have built-in queueing, and it doesn't have any functionality to post a service or to handle threading or any type of listening. 114 | .I NinePea 115 | only operates on one buffer in memory, which the programmer must handle separately on their own. Outgoing response messages overwrite incoming messages in the same buffer, which is only 4 KB by default. 116 | .NH 117 | NinePea Library Usage 118 | .PP 119 | .I NinePea 120 | is a minimal 9P server library which was originally intended for use with Arduino Megas. Some code in the header file for the structs, message types, and bitfield flags was borrowed from Plan9port [6]. Everything for a 9P server runs on the Atmega chip on the Arduino board. To use the library, first callback functions for each of the 9P message types [7] must be written and pointed to in a callbacks struct. Fids are handled manually with helper fid hash table functions. 121 | .P1 122 | Serial.begin(115200); 123 | 124 | fs_fid_init(64); 125 | 126 | callbacks.attach = fs_attach; 127 | callbacks.flush = fs_flush; 128 | callbacks.walk = fs_walk; 129 | callbacks.open = fs_open; 130 | callbacks.create = fs_create; 131 | callbacks.read = fs_read; 132 | callbacks.write = fs_write; 133 | callbacks.clunk = fs_clunk; 134 | callbacks.remove = fs_remove; 135 | callbacks.stat = fs_stat; 136 | callbacks.wstat = fs_wstat; 137 | .P2 138 | After that, a 9P message is read from the serial device and buffered into RAM. The buffer and callbacks structure are then passed to the proc9p function which processes the message. Proc9p calls the callbacks with at least an Fcall struct as a parameter, and also buffers for reads and writes. The 9P message buffer is overwritten by proc9p with 9P data to send back to the client, and proc9p returns the total length of the resulting 9P response. An Arduino sketch can then send that buffer back over the serial port. 139 | .P1 140 | r = 0; 141 | while (r < 5) { 142 | while (Serial.available() < 1); 143 | msg[r++] = Serial.read(); 144 | } 145 | 146 | i = 0; 147 | get4(msg, i, msglen); 148 | 149 | if (msg[i] & 1 || msglen > MAX_MSG || msg[i] < TVersion || msg[i] > TWStat) { 150 | // error 151 | } 152 | 153 | while (r < msglen) { 154 | while (Serial.available() < 1); 155 | msg[r++] = Serial.read(); 156 | } 157 | 158 | msglen = proc9p(msg, msglen, &callbacks); 159 | 160 | Serial.write(msg, msglen); 161 | .P2 162 | .PP 163 | Handling fids is a bit tricky. Some helper functions make this easier: 164 | .P1 165 | struct hentry { 166 | unsigned long id; 167 | unsigned long data; 168 | struct hentry *next; 169 | struct hentry *prev; 170 | void *aux; 171 | }; 172 | 173 | struct htable { 174 | unsigned char length; 175 | struct hentry **data; 176 | }; 177 | 178 | struct hentry* fs_fid_find(unsigned long id); 179 | struct hentry* fs_fid_add(unsigned long id, unsigned long data); 180 | void fs_fid_del(unsigned long id); 181 | void fs_fid_init(int l); 182 | .P2 183 | .PP 184 | 9P typically has a maximum of 8 KB per message, and these chips have only 8 KB of RAM. Linux's 9P support has a minimum of 4 KB message size, which barely fits here. The iounit is set to 4 KB by default. On a Linux computer the included tty9p program ensures that an entire 9P message is sent or received one at a time, but other than that everything runs on the Arduino. The serial port itself becomes a 9P fileserver endpoint. 185 | .NH 186 | Previous Work 187 | .PP 188 | Inferno's Styx protocol, which is compatible with 9P, was previously used [8] on Lego Mindstorms RCX bricks. That work was specific to the Lego RCX; it was never meant as a library. Styx-on-a-Brick was only used for one server for the Lego brick that exposed the motors and sensors. NinePea is a more general purpose library in portable C. It is a very small implementation of a 9P server intended for systems without many resources. It does borrow some structs and other header data from Plan9port, but it dispenses with a lot of functionality that would be available in other 9P server libraries. 189 | .NH 190 | Other Uses 191 | .PP 192 | .I NinePea 193 | was originally meant for use on Arduino boards. It could be made to work with a wifi shield to present a slow ethernet device for Plan 9 without writing any drivers. One could add a speaker and have a simple audio device. Arduino is meant for electronics prototyping, and although 194 | .I NinePea 195 | only builds under the Arduino IDE, once the board is configured and programmed as desired it can be plugged into a Plan 9 system and mounted like any other 9P server. It can be used to gather information from I2C or SPI sensors, to construct or read a signal, or for many other electronics prototyping applications. 196 | .I NinePea 197 | is also flexible; the Arduino library is labelled a C++ file but it's really just portable C. It's just a header and a C file that can be included with a project for simple 9P support. It does have some drawbacks. It isn't meant to be public-facing. It doesn't do authentication and it barely does any error checking. The V4LFS program shows how it can be included and used on Linux to wrap a webcam as a synthetic JPEG. 198 | .NH 199 | Performance of 9P 200 | .PP 201 | This is an example of the pinfs sketch. One interesting use of 202 | .I NinePea 203 | was using Plan 9 methodologies to bind the networking stack of the Linux machine the Arduino was plugged into over /net of a computer across the country and mounting the Arduino remotely: 204 | .P1 205 | linux$ ./tty9p /dev/ttyUSB0 < pipe | nc -l -p 2000 >> pipe 206 | 207 | cpu% bind /mnt/term/net /net 208 | cpu% srv tcp!localhost!2000 arduino 209 | cpu% mount /srv/arduino /n/a 210 | .P2 211 | This command sequence serves the serial port on TCP port 2000 from Linux, switches over to using the Linux machine's networking stack on the remote Plan 9 computer, posts a 9P service for connecting to port 2000, and finally mounts the service. After doing so, writing a 0 or 1 across the country and back takes almost a full second of 9P traffic back and forth: 212 | .P1 213 | cpu% echo 1 > /n/a/d13 214 | .P2 215 | 9P adds a lot of overhead of messages going back and forth, besides the data inside it. In this case only one byte was being sent, but the overhead of 9P and the Internet across the country and back caused the data to take quite a long time to be sent out across the Internet, return, and finally go out and back over the 115200 baud serial port. The serial port was not the main bottleneck in this case. 9P is still very slow over long distances because of all the overhead going back and forth for each operation. Each operation of writing a 1 to the d13 file involved several bytes of 9P out and back for walks, opens, writes, and closes. Mounting 216 | .I NinePea 217 | locally on the Linux computer, the main bottleneck as expected was the serial port itself, sending 9P back and forth as quickly as it could. The Arduino has an LED on digital pin 13 and LEDs for serial recieve and transmit. When it was mounted locally the LEDs for the serial port stayed on continuously while blinking the pin 13 LED in a loop, whereas when it was mounted remotely there was a visible delay as each 9P message was received. 218 | .NH 219 | References 220 | .PP 221 | .br 222 | [1] 223 | .CW https://github.com/echoline/NinePea 224 | The source for this project 225 | .br 226 | [2] 227 | .CW https://9p.cat-v.org 228 | A site about 9P 229 | .br 230 | [3] 231 | .CW https://github.com/echoline/V4LFS 232 | NinePea-based V4LFS Linux webcam fileserver 233 | .br 234 | [4] 235 | .CW https://github.com/echoline/etherESP32 236 | ESP32 WiFi dongle 237 | .br 238 | [5] 239 | .CW http://git.9front.org/plan9front/plan9front/HEAD/sys/src 240 | .CW /libsec/port/chachablock.c/raw 241 | Chacha20 stream cipher code 242 | .br 243 | [6] 244 | .CW https://9fans.github.io/plan9port/ 245 | Plan9port website 246 | .br 247 | [7] 248 | .CW intro(5) 249 | Introduction to manual section 5 of Plan 9 250 | .br 251 | [8] 252 | .CW http://doc.cat-v.org/inferno/4th_edition/styx-on-a-brick/ 253 | Inferno "Styx-on-a-Brick" paper 254 | -------------------------------------------------------------------------------- /examples/eia9p.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | void 5 | main(int argc, char **argv) 6 | { 7 | int pid; 8 | int fd; 9 | unsigned char in[16384]; 10 | unsigned char out[16384]; 11 | int inlen, outlen; 12 | 13 | if (argc < 2) 14 | exits("args"); 15 | 16 | fd = open(argv[1], ORDWR); 17 | if (fd < 0) 18 | exits("%r"); 19 | 20 | pid = fork(); 21 | if (pid < 0) 22 | exits("%r"); 23 | else if (pid == 0) { 24 | while(1) { 25 | if (readn(fd, in, 4) != 4) 26 | exits("%r"); 27 | inlen = in[0] | in[1] << 8 | in[2] << 16 | in[3] << 24; 28 | inlen -= 4; 29 | if (readn(fd, &in[4], inlen) != inlen) 30 | exits("%r"); 31 | inlen += 4; 32 | write(1, in, inlen); 33 | } 34 | } 35 | else { 36 | while(1) { 37 | if (readn(0, out, 4) != 4) 38 | exits("%r"); 39 | outlen = out[0] | out[1] << 8 | out[2] << 16 | out[3] << 24; 40 | outlen -= 4; 41 | if (readn(0, &out[4], outlen) != outlen) 42 | exits("%r"); 43 | outlen += 4; 44 | write(fd, out, outlen); 45 | } 46 | } 47 | } 48 | 49 | -------------------------------------------------------------------------------- /examples/pinfs/pinfs.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | Fcall ofcall; 4 | char errstr[64]; 5 | char Snone[] = "a"; 6 | char Sroot[] = "/"; 7 | char Sdigital[] = "dXX"; 8 | 9 | #define Qroot 0 10 | 11 | /* 9p handlers */ 12 | 13 | Fcall* 14 | fs_attach(Fcall *ifcall) { 15 | ofcall.qid.type = QTDIR | QTTMP; 16 | ofcall.qid.version = 0; 17 | ofcall.qid.path = Qroot; 18 | 19 | fs_fid_add(ifcall->fid, Qroot); 20 | 21 | return &ofcall; 22 | } 23 | 24 | Fcall* 25 | fs_walk(Fcall *ifcall) { 26 | unsigned long path; 27 | struct hentry *ent = fs_fid_find(ifcall->fid); 28 | int i, pin; 29 | char *ep; 30 | 31 | if (!ent) { 32 | ofcall.type = RError; 33 | ofcall.ename = Enofile; 34 | 35 | return &ofcall; 36 | } 37 | 38 | path = ent->data; 39 | 40 | for (i = 0; i < ifcall->nwname; i++) { 41 | switch(ent->data) { 42 | case Qroot: 43 | if (ifcall->wname[i][0] == 'd') { 44 | if (strlen(ifcall->wname[i]) != 3) 45 | goto WALKERROR; 46 | ep = ifcall->wname[i] + 3; 47 | pin = strtod(ifcall->wname[i] + 1, &ep); 48 | if (pin >= 0 && pin < NUM_DIGITAL_PINS) { 49 | ofcall.wqid[i].type = QTFILE; 50 | ofcall.wqid[i].version = 0; 51 | ofcall.wqid[i].path = path = pin + 1; 52 | } 53 | else { 54 | goto WALKERROR; 55 | } 56 | } 57 | else if (!strcmp(ifcall->wname[i], ".")) { 58 | ofcall.wqid[i].type = QTDIR; 59 | ofcall.wqid[i].version = 0; 60 | ofcall.wqid[i].path = path = Qroot; 61 | } 62 | else { 63 | WALKERROR: 64 | ofcall.type = RError; 65 | ofcall.ename = Enofile; 66 | return &ofcall; 67 | } 68 | break; 69 | default: 70 | ofcall.type = RError; 71 | ofcall.ename = Enofile; 72 | 73 | return &ofcall; 74 | break; 75 | } 76 | } 77 | 78 | ofcall.nwqid = i; 79 | 80 | if (fs_fid_find(ifcall->newfid) != NULL) { 81 | ofcall.type = RError; 82 | strcpy(errstr, "new fid exists"); 83 | ofcall.ename = errstr; 84 | return &ofcall; 85 | } 86 | 87 | fs_fid_add(ifcall->newfid, path); 88 | 89 | return &ofcall; 90 | } 91 | 92 | Fcall* 93 | fs_stat(Fcall *ifcall) { 94 | struct hentry *ent; 95 | 96 | if ((ent = fs_fid_find(ifcall->fid)) == NULL) { 97 | ofcall.type = RError; 98 | ofcall.ename = Enofile; 99 | 100 | return &ofcall; 101 | } 102 | 103 | ofcall.stat.qid.type = QTTMP; 104 | ofcall.stat.mode = 0666 | DMTMP; 105 | ofcall.stat.atime = ofcall.stat.mtime = ofcall.stat.length = 0; 106 | ofcall.stat.uid = Snone; 107 | ofcall.stat.gid = Snone; 108 | ofcall.stat.muid = Snone; 109 | 110 | if (ent->data == Qroot) { 111 | ofcall.stat.qid.type |= QTDIR; 112 | ofcall.stat.qid.path = Qroot; 113 | ofcall.stat.mode |= 0111 | DMDIR; 114 | ofcall.stat.name = Sroot; 115 | } 116 | else if (ent->data > 0 && ent->data <= NUM_DIGITAL_PINS) { 117 | ofcall.stat.qid.path = ent->data; 118 | ofcall.stat.name = Sdigital; 119 | sprintf(Sdigital, "d%02d", ent->data - 1); 120 | } 121 | 122 | return &ofcall; 123 | } 124 | 125 | Fcall* 126 | fs_clunk(Fcall *ifcall) { 127 | fs_fid_del(ifcall->fid); 128 | 129 | return ifcall; 130 | } 131 | 132 | Fcall* 133 | fs_open(Fcall *ifcall) { 134 | struct hentry *cur = fs_fid_find(ifcall->fid); 135 | 136 | if (cur == NULL) { 137 | ofcall.type = RError; 138 | ofcall.ename = Enofile; 139 | 140 | return &ofcall; 141 | } 142 | 143 | ofcall.qid.type = QTFILE; 144 | ofcall.qid.path = cur->data; 145 | 146 | if (cur->data == Qroot) 147 | ofcall.qid.type = QTDIR; 148 | 149 | return &ofcall; 150 | } 151 | 152 | Fcall* 153 | fs_read(Fcall *ifcall, unsigned char *out) { 154 | struct hentry *cur = fs_fid_find(ifcall->fid); 155 | Stat stat; 156 | char tmpstr[32]; 157 | unsigned char i; 158 | unsigned long value; 159 | 160 | ofcall.count = 0; 161 | 162 | if (cur == NULL) { 163 | ofcall.type = RError; 164 | ofcall.ename = Enofile; 165 | } 166 | // offset? too hard, sorry 167 | else if (ifcall->offset != 0) { 168 | out[0] = '\0'; 169 | } 170 | else if (((unsigned long)cur->data) == Qroot) { 171 | stat.type = 0; 172 | stat.dev = 0; 173 | stat.qid.type = QTFILE; 174 | stat.mode = 0666; 175 | stat.atime = 0; 176 | stat.mtime = 0; 177 | stat.length = 0; 178 | 179 | for (i = 1; i <= NUM_DIGITAL_PINS; i++) { 180 | stat.qid.path = i; 181 | stat.name = Sdigital; 182 | sprintf(Sdigital, "d%02d", i - 1); 183 | stat.uid = Snone; 184 | stat.gid = Snone; 185 | stat.muid = Snone; 186 | ofcall.count += putstat(out, ofcall.count, &stat); 187 | } 188 | } 189 | else if (((unsigned long)cur->data) > 0 && ((unsigned long)cur->data) <= NUM_DIGITAL_PINS) { 190 | i = (unsigned long)cur->data; 191 | pinMode(i - 1, INPUT); 192 | sprintf((char*)out, "%01d\n", digitalRead(i - 1)); 193 | ofcall.count = strlen((const char*)out); 194 | } 195 | else { 196 | ofcall.type = RError; 197 | ofcall.ename = Enofile; 198 | } 199 | 200 | return &ofcall; 201 | } 202 | 203 | Fcall* 204 | fs_create(Fcall *ifcall) { 205 | ofcall.type = RError; 206 | ofcall.ename = Eperm; 207 | 208 | return &ofcall; 209 | } 210 | 211 | Fcall* 212 | fs_write(Fcall *ifcall, unsigned char *in) { 213 | struct hentry *cur = fs_fid_find(ifcall->fid); 214 | int i, j; 215 | 216 | ofcall.count = ifcall->count; 217 | 218 | if (cur == NULL) { 219 | ofcall.type = RError; 220 | ofcall.ename = Enofile; 221 | } 222 | else if (((unsigned long)cur->data) == Qroot) { 223 | ofcall.type = RError; 224 | ofcall.ename = Eperm; 225 | } 226 | else if (((unsigned long)cur->data) > 0 && ((unsigned long)cur->data) <= NUM_DIGITAL_PINS) { 227 | i = ((unsigned long)cur->data) - 1; 228 | if (strcmp((const char*)in, "1\n") == 0) 229 | j = HIGH; 230 | else if (strcmp((const char*)in, "0\n") == 0) 231 | j = LOW; 232 | else 233 | goto WRITEERROR; 234 | pinMode(i, OUTPUT); 235 | digitalWrite(i, j); 236 | } 237 | else { 238 | WRITEERROR: 239 | ofcall.type = RError; 240 | ofcall.ename = Eperm; 241 | } 242 | 243 | return &ofcall; 244 | } 245 | 246 | Fcall* 247 | fs_remove(Fcall *ifcall) { 248 | ofcall.type = RError; 249 | ofcall.ename = Eperm; 250 | 251 | return &ofcall; 252 | } 253 | 254 | Fcall* 255 | fs_flush(Fcall *ifcall) { 256 | return ifcall; 257 | } 258 | 259 | Fcall* 260 | fs_wstat(Fcall *ifcall) { 261 | ofcall.type = RError; 262 | ofcall.ename = Eperm; 263 | 264 | return &ofcall; 265 | } 266 | 267 | Callbacks callbacks; 268 | 269 | void 270 | sysfatal(int code) 271 | { 272 | pinMode(13, OUTPUT); 273 | 274 | while(true) { 275 | digitalWrite(13, HIGH); 276 | delay(1000); 277 | digitalWrite(13, LOW); 278 | delay(code * 1000); 279 | } 280 | } 281 | 282 | void 283 | setup() 284 | { 285 | Serial.begin(115200); 286 | 287 | fs_fid_init(64); 288 | 289 | // this is REQUIRED by proc9p (see below) 290 | callbacks.attach = fs_attach; 291 | callbacks.flush = fs_flush; 292 | callbacks.walk = fs_walk; 293 | callbacks.open = fs_open; 294 | callbacks.create = fs_create; 295 | callbacks.read = fs_read; 296 | callbacks.write = fs_write; 297 | callbacks.clunk = fs_clunk; 298 | callbacks.remove = fs_remove; 299 | callbacks.stat = fs_stat; 300 | callbacks.wstat = fs_wstat; 301 | } 302 | 303 | unsigned char msg[MAX_MSG+1]; 304 | unsigned long msglen = 0; 305 | unsigned long r = 0; 306 | 307 | void 308 | loop() 309 | { 310 | unsigned long i; 311 | 312 | while (r < 5) { 313 | while (Serial.available() < 1); 314 | msg[r++] = Serial.read(); 315 | } 316 | 317 | i = 0; 318 | get4(msg, i, msglen); 319 | 320 | // sanity check 321 | if (msg[i] & 1 || msglen > MAX_MSG || msg[i] < TVersion || msg[i] > TWStat) { 322 | sysfatal(3); 323 | } 324 | 325 | while (r < msglen) { 326 | while (Serial.available() < 1); 327 | msg[r++] = Serial.read(); 328 | } 329 | 330 | memset(&ofcall, 0, sizeof(ofcall)); 331 | 332 | // proc9p accepts valid 9P msgs of length msglen, 333 | // processes them using callbacks->various(functions); 334 | // returns variable out's msglen 335 | msglen = proc9p(msg, msglen, &callbacks); 336 | 337 | Serial.write(msg, msglen); 338 | 339 | r = msglen = 0; 340 | } 341 | -------------------------------------------------------------------------------- /examples/randomfs/randomfs.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | Fcall ofcall; 4 | char errstr[64]; 5 | char Snone[] = "arduino"; 6 | char Sroot[] = "/"; 7 | char Srandom[] = "random"; 8 | 9 | /* paths */ 10 | 11 | enum { 12 | Qroot = 0, 13 | Qrandom, 14 | QNUM, 15 | }; 16 | 17 | /* 9p handlers */ 18 | 19 | Fcall* 20 | fs_attach(Fcall *ifcall) { 21 | ofcall.qid.type = QTDIR | QTTMP; 22 | ofcall.qid.version = 0; 23 | ofcall.qid.path = Qroot; 24 | 25 | fs_fid_add(ifcall->fid, Qroot); 26 | 27 | return &ofcall; 28 | } 29 | 30 | Fcall* 31 | fs_walk(Fcall *ifcall) { 32 | unsigned long path; 33 | struct hentry *ent = fs_fid_find(ifcall->fid); 34 | int i; 35 | 36 | if (!ent) { 37 | ofcall.type = RError; 38 | ofcall.ename = Enofile; 39 | 40 | return &ofcall; 41 | } 42 | 43 | path = ent->data; 44 | 45 | for (i = 0; i < ifcall->nwname; i++) { 46 | switch(ent->data) { 47 | case Qroot: 48 | if (!strcmp(ifcall->wname[i], "random")) { 49 | ofcall.wqid[i].type = QTFILE; 50 | ofcall.wqid[i].version = 0; 51 | ofcall.wqid[i].path = path = Qrandom; 52 | } 53 | else if (!strcmp(ifcall->wname[i], ".")) { 54 | ofcall.wqid[i].type = QTDIR; 55 | ofcall.wqid[i].version = 0; 56 | ofcall.wqid[i].path = path = Qroot; 57 | } 58 | else { 59 | ofcall.type = RError; 60 | ofcall.ename = Enofile; 61 | return &ofcall; 62 | } 63 | break; 64 | default: 65 | ofcall.type = RError; 66 | ofcall.ename = Enofile; 67 | 68 | return &ofcall; 69 | break; 70 | } 71 | } 72 | 73 | ofcall.nwqid = i; 74 | 75 | if (fs_fid_find(ifcall->newfid) != NULL) { 76 | ofcall.type = RError; 77 | strcpy(errstr, "new fid exists"); 78 | ofcall.ename = errstr; 79 | return &ofcall; 80 | } 81 | 82 | fs_fid_add(ifcall->newfid, path); 83 | 84 | return &ofcall; 85 | } 86 | 87 | Fcall* 88 | fs_stat(Fcall *ifcall) { 89 | struct hentry *ent; 90 | 91 | if ((ent = fs_fid_find(ifcall->fid)) == NULL) { 92 | ofcall.type = RError; 93 | ofcall.ename = Enofile; 94 | 95 | return &ofcall; 96 | } 97 | 98 | ofcall.stat.qid.type = QTTMP; 99 | ofcall.stat.mode = 0444 | DMTMP; 100 | ofcall.stat.atime = ofcall.stat.mtime = ofcall.stat.length = 0; 101 | ofcall.stat.uid = Snone; 102 | ofcall.stat.gid = Snone; 103 | ofcall.stat.muid = Snone; 104 | 105 | switch (ent->data) { 106 | case Qroot: 107 | ofcall.stat.qid.type |= QTDIR; 108 | ofcall.stat.qid.path = Qroot; 109 | ofcall.stat.mode |= 0111 | DMDIR; 110 | ofcall.stat.name = Sroot; 111 | break; 112 | case Qrandom: 113 | ofcall.stat.qid.path = Qrandom; 114 | ofcall.stat.name = Srandom; 115 | break; 116 | } 117 | 118 | return &ofcall; 119 | } 120 | 121 | Fcall* 122 | fs_clunk(Fcall *ifcall) { 123 | fs_fid_del(ifcall->fid); 124 | 125 | return ifcall; 126 | } 127 | 128 | Fcall* 129 | fs_open(Fcall *ifcall) { 130 | struct hentry *cur = fs_fid_find(ifcall->fid); 131 | 132 | if (cur == NULL) { 133 | ofcall.type = RError; 134 | ofcall.ename = Enofile; 135 | 136 | return &ofcall; 137 | } 138 | 139 | ofcall.qid.type = QTFILE; 140 | ofcall.qid.path = cur->data; 141 | 142 | if (cur->data == Qroot) 143 | ofcall.qid.type = QTDIR; 144 | 145 | return &ofcall; 146 | } 147 | 148 | #define ROTATE(v,c) ((unsigned long)((v) << (c)) | ((v) >> (32 - (c)))) 149 | 150 | #define QUARTERROUND(ia,ib,ic,id) { \ 151 | unsigned long a, b, c, d, t; \ 152 | a = x[ia]; b = x[ib]; c = x[ic]; d = x[id]; \ 153 | a += b; t = d^a; d = ROTATE(t,16); \ 154 | c += d; t = b^c; b = ROTATE(t,12); \ 155 | a += b; t = d^a; d = ROTATE(t, 8); \ 156 | c += d; t = b^c; b = ROTATE(t, 7); \ 157 | x[ia] = a; x[ib] = b; x[ic] = c; x[id] = d; \ 158 | } 159 | 160 | void 161 | _chachablock(unsigned long x[16], int rounds) 162 | { 163 | for(; rounds > 0; rounds -= 2) { 164 | QUARTERROUND(0, 4, 8,12) 165 | QUARTERROUND(1, 5, 9,13) 166 | QUARTERROUND(2, 6,10,14) 167 | QUARTERROUND(3, 7,11,15) 168 | 169 | QUARTERROUND(0, 5,10,15) 170 | QUARTERROUND(1, 6,11,12) 171 | QUARTERROUND(2, 7, 8,13) 172 | QUARTERROUND(3, 4, 9,14) 173 | } 174 | } 175 | 176 | Fcall* 177 | fs_read(Fcall *ifcall, unsigned char *out) { 178 | struct hentry *cur = fs_fid_find(ifcall->fid); 179 | Stat stat; 180 | unsigned long i, j, k; 181 | unsigned long x[16]; 182 | 183 | if (cur == NULL) { 184 | ofcall.type = RError; 185 | ofcall.ename = Enofile; 186 | } 187 | else if (((unsigned long)cur->data) == Qroot) { 188 | if (ifcall->offset != 0) { 189 | out[0] = '\0'; 190 | ofcall.count = 0; 191 | goto ENDREAD; 192 | } 193 | stat.type = 0; 194 | stat.dev = 0; 195 | stat.qid.type = QTFILE; 196 | stat.mode = 0666; 197 | stat.atime = 0; 198 | stat.mtime = 0; 199 | stat.length = 0; 200 | 201 | stat.qid.path = Qrandom; 202 | stat.name = Srandom; 203 | stat.uid = Snone; 204 | stat.gid = Snone; 205 | stat.muid = Snone; 206 | ofcall.count = putstat(out, 0, &stat); 207 | } 208 | else if (((unsigned long)cur->data) == Qrandom) { 209 | for (i = 0; i < 16; i++) { 210 | x[i] = 0; 211 | for (j = A0; j < (NUM_ANALOG_INPUTS + A0); j++) { 212 | x[i] <<= 1; 213 | x[i] ^= analogRead(j) ^ micros(); 214 | } 215 | } 216 | for (k = 0; k < ifcall->count; k++) { 217 | i = k & 15; 218 | if (i == 0) 219 | _chachablock(x, 20); 220 | out[k] = x[i] & 0xFF; 221 | } 222 | ofcall.count = ifcall->count; 223 | } 224 | else { 225 | ofcall.type = RError; 226 | ofcall.ename = Enofile; 227 | } 228 | ENDREAD: 229 | 230 | return &ofcall; 231 | } 232 | 233 | Fcall* 234 | fs_create(Fcall *ifcall) { 235 | ofcall.type = RError; 236 | ofcall.ename = Eperm; 237 | 238 | return &ofcall; 239 | } 240 | 241 | Fcall* 242 | fs_write(Fcall *ifcall, unsigned char *in) { 243 | ofcall.type = RError; 244 | ofcall.ename = Eperm; 245 | 246 | return &ofcall; 247 | } 248 | 249 | Fcall* 250 | fs_remove(Fcall *ifcall) { 251 | ofcall.type = RError; 252 | ofcall.ename = Eperm; 253 | 254 | return &ofcall; 255 | } 256 | 257 | Fcall* 258 | fs_flush(Fcall *ifcall) { 259 | return ifcall; 260 | } 261 | 262 | Fcall* 263 | fs_wstat(Fcall *ifcall) { 264 | ofcall.type = RError; 265 | ofcall.ename = Eperm; 266 | 267 | return &ofcall; 268 | } 269 | 270 | Callbacks callbacks; 271 | 272 | void 273 | sysfatal(int code) 274 | { 275 | pinMode(13, OUTPUT); 276 | 277 | while(true) { 278 | digitalWrite(13, HIGH); 279 | delay(1000); 280 | digitalWrite(13, LOW); 281 | delay(code * 1000); 282 | } 283 | } 284 | 285 | void 286 | setup() 287 | { 288 | int j; 289 | 290 | for (j = A0; j < (NUM_ANALOG_INPUTS+A0); j++) 291 | pinMode(j, INPUT); 292 | 293 | Serial.begin(115200); 294 | 295 | fs_fid_init(64); 296 | 297 | // this is REQUIRED by proc9p (see below) 298 | callbacks.attach = fs_attach; 299 | callbacks.flush = fs_flush; 300 | callbacks.walk = fs_walk; 301 | callbacks.open = fs_open; 302 | callbacks.create = fs_create; 303 | callbacks.read = fs_read; 304 | callbacks.write = fs_write; 305 | callbacks.clunk = fs_clunk; 306 | callbacks.remove = fs_remove; 307 | callbacks.stat = fs_stat; 308 | callbacks.wstat = fs_wstat; 309 | } 310 | 311 | unsigned char msg[MAX_MSG+1]; 312 | unsigned long msglen = 0; 313 | unsigned long r = 0; 314 | 315 | void 316 | loop() 317 | { 318 | unsigned long i; 319 | 320 | while (r < 5) { 321 | while (Serial.available() < 1); 322 | msg[r++] = Serial.read(); 323 | } 324 | 325 | i = 0; 326 | get4(msg, i, msglen); 327 | 328 | // sanity check 329 | if (msg[i] & 1 || msglen > MAX_MSG || msg[i] < TVersion || msg[i] > TWStat) { 330 | sysfatal(3); 331 | } 332 | 333 | while (r < msglen) { 334 | while (Serial.available() < 1); 335 | msg[r++] = Serial.read(); 336 | } 337 | 338 | memset(&ofcall, 0, sizeof(ofcall)); 339 | 340 | // proc9p accepts valid 9P msgs of length msglen, 341 | // processes them using callbacks->various(functions); 342 | // returns variable out's msglen 343 | msglen = proc9p(msg, msglen, &callbacks); 344 | 345 | Serial.write(msg, msglen); 346 | 347 | r = msglen = 0; 348 | } 349 | -------------------------------------------------------------------------------- /examples/robotfs/robotfs.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | Fcall ofcall; 4 | char errstr[64]; 5 | char Snone[] = "arduino"; 6 | char Sroot[] = "/"; 7 | char Sleft[] = "left"; 8 | char Sright[] = "right"; 9 | 10 | int Mleft, Mright; 11 | #define LEFT 1 12 | #define RIGHT 2 13 | #define BOTH 3 14 | 15 | void 16 | motors(int which) 17 | { 18 | if (which & LEFT) 19 | analogWrite(6, Mleft + 127); 20 | if (which & RIGHT) 21 | analogWrite(5, Mright + 127); 22 | } 23 | 24 | /* paths */ 25 | 26 | enum { 27 | Qroot = 0, 28 | Qleft, 29 | Qright, 30 | QNUM, 31 | }; 32 | 33 | /* 9p handlers */ 34 | 35 | Fcall* 36 | fs_attach(Fcall *ifcall) { 37 | ofcall.qid.type = QTDIR | QTTMP; 38 | ofcall.qid.version = 0; 39 | ofcall.qid.path = Qroot; 40 | 41 | fs_fid_add(ifcall->fid, Qroot); 42 | 43 | return &ofcall; 44 | } 45 | 46 | Fcall* 47 | fs_walk(Fcall *ifcall) { 48 | unsigned long path; 49 | struct hentry *ent = fs_fid_find(ifcall->fid); 50 | int i; 51 | 52 | if (!ent) { 53 | ofcall.type = RError; 54 | ofcall.ename = Enofile; 55 | 56 | return &ofcall; 57 | } 58 | 59 | path = ent->data; 60 | 61 | for (i = 0; i < ifcall->nwname; i++) { 62 | switch(ent->data) { 63 | case Qroot: 64 | if (!strcmp(ifcall->wname[i], "left")) { 65 | ofcall.wqid[i].type = QTFILE; 66 | ofcall.wqid[i].version = 0; 67 | ofcall.wqid[i].path = path = Qleft; 68 | } 69 | else if (!strcmp(ifcall->wname[i], "right")) { 70 | ofcall.wqid[i].type = QTFILE; 71 | ofcall.wqid[i].version = 0; 72 | ofcall.wqid[i].path = path = Qright; 73 | } 74 | else if (!strcmp(ifcall->wname[i], ".")) { 75 | ofcall.wqid[i].type = QTDIR; 76 | ofcall.wqid[i].version = 0; 77 | ofcall.wqid[i].path = path = Qroot; 78 | } 79 | else { 80 | ofcall.type = RError; 81 | ofcall.ename = Enofile; 82 | return &ofcall; 83 | } 84 | break; 85 | default: 86 | ofcall.type = RError; 87 | ofcall.ename = Enofile; 88 | 89 | return &ofcall; 90 | break; 91 | } 92 | } 93 | 94 | ofcall.nwqid = i; 95 | 96 | if (fs_fid_find(ifcall->newfid) != NULL) { 97 | ofcall.type = RError; 98 | strcpy(errstr, "new fid exists"); 99 | ofcall.ename = errstr; 100 | return &ofcall; 101 | } 102 | 103 | fs_fid_add(ifcall->newfid, path); 104 | 105 | return &ofcall; 106 | } 107 | 108 | Fcall* 109 | fs_stat(Fcall *ifcall) { 110 | struct hentry *ent; 111 | 112 | if ((ent = fs_fid_find(ifcall->fid)) == NULL) { 113 | ofcall.type = RError; 114 | ofcall.ename = Enofile; 115 | 116 | return &ofcall; 117 | } 118 | 119 | ofcall.stat.qid.type = QTTMP; 120 | ofcall.stat.mode = 0666 | DMTMP; 121 | ofcall.stat.atime = ofcall.stat.mtime = ofcall.stat.length = 0; 122 | ofcall.stat.uid = Snone; 123 | ofcall.stat.gid = Snone; 124 | ofcall.stat.muid = Snone; 125 | 126 | switch (ent->data) { 127 | case Qroot: 128 | ofcall.stat.qid.type |= QTDIR; 129 | ofcall.stat.qid.path = Qroot; 130 | ofcall.stat.mode |= 0111 | DMDIR; 131 | ofcall.stat.name = Sroot; 132 | break; 133 | case Qleft: 134 | ofcall.stat.qid.path = Qleft; 135 | ofcall.stat.name = Sleft; 136 | break; 137 | case Qright: 138 | ofcall.stat.qid.path = Qright; 139 | ofcall.stat.name = Sright; 140 | break; 141 | } 142 | 143 | return &ofcall; 144 | } 145 | 146 | Fcall* 147 | fs_clunk(Fcall *ifcall) { 148 | fs_fid_del(ifcall->fid); 149 | 150 | return ifcall; 151 | } 152 | 153 | Fcall* 154 | fs_open(Fcall *ifcall) { 155 | struct hentry *cur = fs_fid_find(ifcall->fid); 156 | 157 | if (cur == NULL) { 158 | ofcall.type = RError; 159 | ofcall.ename = Enofile; 160 | 161 | return &ofcall; 162 | } 163 | 164 | ofcall.qid.type = QTFILE; 165 | ofcall.qid.path = cur->data; 166 | 167 | if (cur->data == Qroot) 168 | ofcall.qid.type = QTDIR; 169 | 170 | return &ofcall; 171 | } 172 | 173 | Fcall* 174 | fs_read(Fcall *ifcall, unsigned char *out) { 175 | struct hentry *cur = fs_fid_find(ifcall->fid); 176 | Stat stat; 177 | char tmpstr[32]; 178 | unsigned char i; 179 | unsigned long value; 180 | 181 | if (cur == NULL) { 182 | ofcall.type = RError; 183 | ofcall.ename = Enofile; 184 | } 185 | // offset? too hard, sorry 186 | else if (ifcall->offset != 0) { 187 | out[0] = '\0'; 188 | ofcall.count = 0; 189 | } 190 | else if (((unsigned long)cur->data) == Qroot) { 191 | stat.type = 0; 192 | stat.dev = 0; 193 | stat.qid.type = QTFILE; 194 | stat.mode = 0666; 195 | stat.atime = 0; 196 | stat.mtime = 0; 197 | stat.length = 0; 198 | 199 | stat.qid.path = Qright; 200 | stat.name = Sright; 201 | stat.uid = Snone; 202 | stat.gid = Snone; 203 | stat.muid = Snone; 204 | ofcall.count = putstat(out, 0, &stat); 205 | 206 | stat.qid.path = Qleft; 207 | stat.name = Sleft; 208 | stat.uid = Snone; 209 | stat.gid = Snone; 210 | stat.muid = Snone; 211 | ofcall.count += putstat(out, ofcall.count, &stat); 212 | } 213 | else if (((unsigned long)cur->data) == Qleft) { 214 | snprintf((char*)out, MAX_IO - 1, "%d\n", Mleft); 215 | ofcall.count = strlen((const char*)out); 216 | } 217 | else if (((unsigned long)cur->data) == Qright) { 218 | snprintf((char*)out, MAX_IO - 1, "%d\n", Mright); 219 | ofcall.count = strlen((const char*)out); 220 | } 221 | else { 222 | ofcall.type = RError; 223 | ofcall.ename = Enofile; 224 | } 225 | 226 | return &ofcall; 227 | } 228 | 229 | Fcall* 230 | fs_create(Fcall *ifcall) { 231 | ofcall.type = RError; 232 | ofcall.ename = Eperm; 233 | 234 | return &ofcall; 235 | } 236 | 237 | Fcall* 238 | fs_write(Fcall *ifcall, unsigned char *in) { 239 | struct hentry *cur = fs_fid_find(ifcall->fid); 240 | char *ep = (char*)&in[ifcall->count]; 241 | 242 | ofcall.count = ifcall->count; 243 | 244 | if (cur == NULL) { 245 | ofcall.type = RError; 246 | ofcall.ename = Enofile; 247 | } 248 | else if (((unsigned long)cur->data) == Qroot) { 249 | ofcall.type = RError; 250 | ofcall.ename = Eperm; 251 | } 252 | else if (((unsigned long)cur->data) == Qleft) { 253 | Mleft = strtod((const char*)in, &ep); 254 | motors(LEFT); 255 | } 256 | else if (((unsigned long)cur->data) == Qright) { 257 | Mright = strtod((const char*)in, &ep); 258 | motors(RIGHT); 259 | } 260 | else { 261 | ofcall.type = RError; 262 | ofcall.ename = Eperm; 263 | } 264 | 265 | return &ofcall; 266 | } 267 | 268 | Fcall* 269 | fs_remove(Fcall *ifcall) { 270 | ofcall.type = RError; 271 | ofcall.ename = Eperm; 272 | 273 | return &ofcall; 274 | } 275 | 276 | Fcall* 277 | fs_flush(Fcall *ifcall) { 278 | return ifcall; 279 | } 280 | 281 | Fcall* 282 | fs_wstat(Fcall *ifcall) { 283 | ofcall.type = RError; 284 | ofcall.ename = Eperm; 285 | 286 | return &ofcall; 287 | } 288 | 289 | Callbacks callbacks; 290 | 291 | void 292 | sysfatal(int code) 293 | { 294 | pinMode(13, OUTPUT); 295 | 296 | while(true) { 297 | digitalWrite(13, HIGH); 298 | delay(1000); 299 | digitalWrite(13, LOW); 300 | delay(code * 1000); 301 | } 302 | } 303 | 304 | void 305 | setup() 306 | { 307 | pinMode(5, OUTPUT); 308 | pinMode(6, OUTPUT); 309 | Mleft = Mright = 0; 310 | motors(BOTH); 311 | 312 | Serial.begin(115200); 313 | 314 | fs_fid_init(64); 315 | 316 | // this is REQUIRED by proc9p (see below) 317 | callbacks.attach = fs_attach; 318 | callbacks.flush = fs_flush; 319 | callbacks.walk = fs_walk; 320 | callbacks.open = fs_open; 321 | callbacks.create = fs_create; 322 | callbacks.read = fs_read; 323 | callbacks.write = fs_write; 324 | callbacks.clunk = fs_clunk; 325 | callbacks.remove = fs_remove; 326 | callbacks.stat = fs_stat; 327 | callbacks.wstat = fs_wstat; 328 | } 329 | 330 | unsigned char msg[MAX_MSG+1]; 331 | unsigned long msglen = 0; 332 | unsigned long r = 0; 333 | 334 | void 335 | loop() 336 | { 337 | unsigned long i; 338 | 339 | while (r < 5) { 340 | while (Serial.available() < 1); 341 | msg[r++] = Serial.read(); 342 | } 343 | 344 | i = 0; 345 | get4(msg, i, msglen); 346 | 347 | // sanity check 348 | if (msg[i] & 1 || msglen > MAX_MSG || msg[i] < TVersion || msg[i] > TWStat) { 349 | sysfatal(3); 350 | } 351 | 352 | while (r < msglen) { 353 | while (Serial.available() < 1); 354 | msg[r++] = Serial.read(); 355 | } 356 | 357 | memset(&ofcall, 0, sizeof(ofcall)); 358 | 359 | // proc9p accepts valid 9P msgs of length msglen, 360 | // processes them using callbacks->various(functions); 361 | // returns variable out's msglen 362 | msglen = proc9p(msg, msglen, &callbacks); 363 | 364 | Serial.write(msg, msglen); 365 | 366 | r = msglen = 0; 367 | } 368 | 369 | -------------------------------------------------------------------------------- /examples/tty9p.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | void 8 | exits(char *arg) 9 | { 10 | fprintf(stderr, "%s\n", arg); 11 | exit(-1); 12 | } 13 | 14 | int 15 | readn(int fd, unsigned char *buf, int len) 16 | { 17 | int r; 18 | int i = 0; 19 | 20 | while ((i < len) && ((r = read(fd, &buf[i], len - i)) >= 0)) { 21 | if (r == 0) 22 | usleep(100); 23 | i += r; 24 | } 25 | 26 | return i; 27 | } 28 | 29 | int 30 | main(int argc, char **argv) 31 | { 32 | int pid; 33 | int fd; 34 | unsigned char in[16384]; 35 | unsigned char out[16384]; 36 | int inlen, outlen; 37 | int r; 38 | struct termios tty; 39 | 40 | if (argc < 2) 41 | exits("args"); 42 | 43 | fd = open(argv[1], O_RDWR); 44 | if (fd < 0) 45 | exits("open"); 46 | 47 | if (tcgetattr(fd, &tty) != 0) 48 | exit(-2); 49 | 50 | cfsetspeed(&tty, B115200); 51 | tty.c_cflag &= ~PARENB; 52 | tty.c_cflag &= ~CSTOPB; 53 | tty.c_cflag &= ~CSIZE; 54 | tty.c_cflag |= CS8; 55 | tty.c_cflag &= ~CRTSCTS; 56 | tty.c_cc[VMIN] = 1; 57 | tty.c_cc[VTIME] = 5; 58 | tty.c_cflag |= CREAD | CLOCAL; 59 | cfmakeraw(&tty); 60 | tcflush(fd, TCIFLUSH); 61 | 62 | if (tcsetattr(fd, TCSANOW, &tty) != 0) 63 | exit(-3); 64 | 65 | pid = fork(); 66 | if (pid < 0) 67 | exits("fork"); 68 | else if (pid == 0) { 69 | while(1) { 70 | if (readn(fd, in, 4) != 4) { 71 | exits("readn in 1"); 72 | } 73 | inlen = in[0] | in[1] << 8 | in[2] << 16 | in[3] << 24; 74 | inlen -= 4; 75 | if (readn(fd, &in[4], inlen) != inlen) 76 | exits("readn in 2"); 77 | inlen += 4; 78 | write(1, in, inlen); 79 | fflush(stdout); 80 | write(2, in, inlen); 81 | fflush(stderr); 82 | } 83 | } 84 | else { 85 | while(1) { 86 | if (readn(0, out, 4) != 4) 87 | exits("readn out 1"); 88 | outlen = out[0] | out[1] << 8 | out[2] << 16 | out[3] << 24; 89 | outlen -= 4; 90 | if (readn(0, &out[4], outlen) != outlen) 91 | exits("readn out 2"); 92 | outlen += 4; 93 | write(fd, out, outlen); 94 | write(2, out, outlen); 95 | fflush(stderr); 96 | } 97 | } 98 | } 99 | 100 | -------------------------------------------------------------------------------- /mkfile: -------------------------------------------------------------------------------- 1 | # C++ as C for -D Plan9 2 |