{"action":"created","comment":{"url":"https://api.github.com/repos/travisg/lk/pulls/comments/44852072","id":44852072,"diff_hunk":"@@ -0,0 +1,1146 @@\n+/*\n+ * Copyright (c) 2015 Gurjant Kalsi <me@gurjantkalsi.com>\n+ *\n+ * Permission is hereby granted, free of charge, to any person obtaining\n+ * a copy of this software and associated documentation files\n+ * (the \"Software\"), to deal in the Software without restriction,\n+ * including without limitation the rights to use, copy, modify, merge,\n+ * publish, distribute, sublicense, and/or sell copies of the Software,\n+ * and to permit persons to whom the Software is furnished to do so,\n+ * subject to the following conditions:\n+ *\n+ * The above copyright notice and this permission notice shall be\n+ * included in all copies or substantial portions of the Software.\n+ *\n+ * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\n+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\n+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n+ */\n+\n+#include <debug.h>\n+#include <err.h>\n+#include <pow2.h>\n+#include <stdint.h>\n+#include <stdlib.h>\n+#include <string.h>\n+#include <sys/types.h>\n+#include <trace.h>\n+\n+#include <kernel/mutex.h>\n+#include <lib/bio.h>\n+#include <lib/cksum.h>\n+#include <lib/console.h>\n+#include <lib/fs.h>\n+#include <lib/fs/spifs.h>\n+#include <list.h>\n+#include <lk/init.h>\n+\n+#define LOCAL_TRACE 0\n+\n+#define FS_VERSION 1\n+#define FS_MAGIC 0x53504653  // SPFS\n+\n+#define SPIFS_ENTRY_LENGTH 32\n+\n+#define TOC_HEADER_RESERVED_BYTES 16\n+#define TOC_FOOTER_RESERVED_BYTES 28\n+#define MAX_FILENAME_LENGTH 20\n+\n+#define CORRUPT_TOC 0\n+#define NO_OPEN_RUNS 0\n+\n+#define FRONT_TOC (1)\n+#define BACK_TOC  (-1)\n+\n+#define FRONT_TOC_LABEL \"front-toc\"\n+#define BACK_TOC_LABEL \"back-toc\"\n+\n+typedef int32_t toc_position_t;\n+\n+typedef struct {\n+    uint8_t* page;\n+    uint32_t page_size;\n+    uint32_t page_count;\n+    uint32_t blocks_per_page;\n+\n+    uint32_t generation;\n+    uint32_t num_entries;\n+    toc_position_t toc_position;\n+\n+    struct list_node files;\n+    struct list_node dcookies;\n+\n+    bdev_t *dev;\n+\n+    mutex_t lock;\n+} spifs_t;\n+\n+typedef struct {\n+    uint32_t magic;\n+    uint32_t version;\n+    uint32_t num_entries;\n+    uint32_t generation;\n+\n+    uint8_t _reserved[TOC_HEADER_RESERVED_BYTES];\n+} toc_header_t;\n+\n+typedef struct {\n+    uint32_t page_idx;\n+    uint32_t length;\n+    uint32_t capacity;\n+    char filename[MAX_FILENAME_LENGTH];\n+} toc_file_t;\n+\n+typedef struct {\n+    uint8_t _reserved[TOC_FOOTER_RESERVED_BYTES];\n+    uint32_t checksum;\n+} toc_footer_t;\n+\n+typedef struct {\n+    struct list_node node;\n+    spifs_t *fs_handle;\n+    toc_file_t metadata;\n+} spifs_file_t;\n+\n+struct dircookie {\n+    struct list_node node;\n+    spifs_t *fs;\n+\n+    spifs_file_t *next_file;\n+};\n+\n+typedef struct {\n+    uint32_t page_id;\n+    int32_t direction;\n+    uint32_t entry_length;\n+    uint8_t *data;\n+    uint8_t *page;\n+    spifs_t *spifs;\n+} cursor_t;\n+\n+static status_t spifs_read_page(spifs_t *spifs, uint32_t page_addr);\n+static status_t spifs_write_page(spifs_t *spifs, uint32_t page_addr);\n+\n+static status_t get_device_page_info(bdev_t* dev, uint32_t* page_size,\n+                                     uint32_t* page_count);\n+\n+\n+static status_t cursor_init(\n+    cursor_t *cursor, spifs_t *spifs, int32_t direction, uint32_t page_id,\n+    uint32_t entry_length\n+)\n+{\n+    // Make sure the cursor can only be advanced an integer number of times\n+    // per page.\n+    DEBUG_ASSERT(ispow2(entry_length));\n+    DEBUG_ASSERT(spifs->page_size % entry_length == 0);\n+    DEBUG_ASSERT(spifs->page);\n+\n+    cursor->page_id = page_id;\n+    cursor->direction = direction;\n+    cursor->entry_length = entry_length;\n+    cursor->data = spifs->page;\n+    cursor->spifs = spifs;\n+\n+\n+    return spifs_read_page(spifs, page_id);\n+}\n+\n+static uint8_t *cursor_get(cursor_t* cursor)\n+{\n+    spifs_t *spifs = cursor->spifs;\n+\n+    uint8_t *page_end = spifs->page + spifs->page_size;\n+    DEBUG_ASSERT(cursor->data < page_end);\n+\n+    return cursor->data;\n+}\n+\n+static status_t cursor_advance(cursor_t *cursor)\n+{\n+    spifs_t *spifs = cursor->spifs;\n+\n+    uint8_t *page_end = spifs->page + spifs->page_size;\n+\n+    cursor->data += cursor->entry_length;\n+\n+    // We have walked past the end of our buffer.\n+    DEBUG_ASSERT(page_end >= cursor->data);\n+\n+    // If we're at the end of this page, read the next page and move the cursor\n+    // to the beginning of it.\n+    if (cursor->data == page_end) {\n+        cursor->page_id += cursor->direction;\n+        cursor->data = spifs->page;\n+\n+        return spifs_read_page(spifs, cursor->page_id);\n+    }\n+\n+    return NO_ERROR;\n+}\n+\n+static spifs_file_t *find_file(spifs_t *spifs, const char* name)\n+{\n+    spifs_file_t *file;\n+\n+    list_for_every_entry(&spifs->files, file, spifs_file_t, node) {\n+        // Skip the ToC Entries\n+        if (file == list_peek_head_type(&spifs->files, spifs_file_t, node) ||\n+                file == list_peek_tail_type(&spifs->files, spifs_file_t, node)) {\n+            continue;\n+        }\n+\n+        if (!strncmp(name, file->metadata.filename, MAX_FILENAME_LENGTH)) {\n+            return file;\n+        }\n+    }\n+\n+    return NULL;\n+}\n+\n+static uint32_t find_open_run(spifs_t *spifs, uint32_t requested_length)\n+{\n+    spifs_file_t *file;\n+    list_for_every_entry(&spifs->files, file, spifs_file_t, node) {\n+        // Number of pages that this file occupies\n+        uint32_t file_page_length =\n+            file->metadata.capacity / file->fs_handle->page_size;\n+\n+        // Index of the page immediately following the last page of this file.\n+        uint32_t file_end_page = file->metadata.page_idx + file_page_length;\n+\n+        // Determine the page that the next file starts at.\n+        spifs_file_t* next =\n+            list_next_type(&spifs->files, &file->node, spifs_file_t, node);\n+\n+        // End of list?\n+        if (next == NULL) {\n+            return NO_OPEN_RUNS;\n+        }\n+\n+        uint32_t available_pages = next->metadata.page_idx - file_end_page;\n+        uint32_t available_bytes = available_pages * file->fs_handle->page_size;\n+        if (available_bytes >= requested_length) {\n+            return file_end_page;\n+        }\n+    }\n+    return NO_OPEN_RUNS;\n+}\n+\n+static uint64_t used_space(spifs_t *spifs)\n+{\n+    uint64_t result = 0;\n+\n+    spifs_file_t *file;\n+    list_for_every_entry(&spifs->files, file, spifs_file_t, node) {\n+        result += file->metadata.capacity;\n+    }\n+\n+    return result;\n+}\n+\n+static bool consistency_check(spifs_t *spifs)\n+{\n+    /* Return true iff the ToC is in a consistent state. */\n+    spifs_file_t *file;\n+    list_for_every_entry(&spifs->files, file, spifs_file_t, node) {\n+        // Number of pages that this file occupies\n+        uint32_t file_page_length =\n+            file->metadata.capacity / file->fs_handle->page_size;\n+\n+        // Index of the last page of this file.\n+        uint32_t file_end_page = file->metadata.page_idx + file_page_length - 1;\n+\n+        // Determine the page that the next file starts at.\n+        spifs_file_t* next =\n+            list_next_type(&spifs->files, &file->node, spifs_file_t, node);\n+\n+        // End of list?\n+        if (next == NULL) {\n+            continue;\n+        }\n+\n+        if (next->metadata.page_idx <= file_end_page) {\n+            return false;\n+        }\n+    }\n+\n+    return true;\n+}\n+\n+static status_t spifs_commit_toc(spifs_t *spifs)\n+{\n+    status_t err;\n+\n+    // Get the next logical ToC.\n+    toc_position_t target_toc =\n+            spifs->toc_position == FRONT_TOC ? BACK_TOC : FRONT_TOC;\n+\n+    // Bump the generation counter.\n+    uint32_t target_generation = spifs->generation + 1;\n+\n+    uint32_t crc = 0;\n+    uint8_t *cursor = spifs->page;\n+    uint32_t toc_page_addr = target_toc == FRONT_TOC ?\n+                             0 : spifs->page_count - 1;\n+\n+    // Setup the ToC Header.\n+    toc_header_t header = {\n+        .magic       = FS_MAGIC,\n+        .version     = FS_VERSION,\n+        .num_entries = spifs->num_entries,\n+        .generation  = target_generation,\n+    };\n+    memset(header._reserved, 0, TOC_HEADER_RESERVED_BYTES);\n+\n+    crc = crc32(crc, (uint8_t*)&header, SPIFS_ENTRY_LENGTH);\n+\n+    memcpy(cursor, (uint8_t*)&header, SPIFS_ENTRY_LENGTH);\n+    cursor += SPIFS_ENTRY_LENGTH;\n+\n+    // Create an empty file to copy into the empty spots in the ToC\n+    toc_file_t empty;\n+    memset(&empty, 0, SPIFS_ENTRY_LENGTH);\n+\n+    spifs_file_t *file = list_peek_head_type(&spifs->files, spifs_file_t, node);\n+    for (uint32_t i = 0; i < spifs->num_entries; i++) {\n+        uint8_t *page_end = spifs->page + spifs->page_size;\n+        DEBUG_ASSERT(cursor <= page_end);\n+\n+        if (cursor == page_end) {\n+            err = spifs_write_page(spifs, toc_page_addr);\n+            if (err != NO_ERROR) {\n+                return err;\n+            }\n+\n+            toc_page_addr += target_toc;\n+            cursor = spifs->page;\n+        }\n+\n+        if (file) {\n+            crc = crc32(crc, (uint8_t*)&file->metadata, SPIFS_ENTRY_LENGTH);\n+            memcpy(cursor, (uint8_t*)&file->metadata, SPIFS_ENTRY_LENGTH);\n+            file = list_next_type(&spifs->files, &file->node, spifs_file_t, node);\n+        } else {\n+            crc = crc32(crc, (uint8_t*)&empty, SPIFS_ENTRY_LENGTH);\n+            memcpy(cursor, (uint8_t*)&empty, SPIFS_ENTRY_LENGTH);\n+        }\n+\n+        cursor += SPIFS_ENTRY_LENGTH;\n+    }\n+\n+    // Sanity check. The cursor should be at the last position in this page\n+    // at this point.\n+    uint8_t* expected_cursor_location =\n+        (spifs->page + spifs->page_size) - SPIFS_ENTRY_LENGTH;\n+    DEBUG_ASSERT(cursor == expected_cursor_location);\n+\n+    toc_footer_t footer;\n+    memset(&footer, 0, SPIFS_ENTRY_LENGTH);\n+    footer.checksum = crc;\n+    memcpy(cursor, (uint8_t*)&footer, SPIFS_ENTRY_LENGTH);\n+\n+    err = spifs_write_page(spifs, toc_page_addr);\n+    if (err != NO_ERROR)\n+        return err;\n+\n+    // Only update this once we're sure that the write went through.\n+    // This way, if the write failed, we'll try writing over the bad ToC again\n+    // rather than potentially corrupting both ToCs.\n+    spifs->generation = target_generation;\n+    spifs->toc_position = target_toc;\n+\n+    return NO_ERROR;\n+}\n+\n+static void spifs_add_ascending(spifs_t *spifs, spifs_file_t *target)\n+{\n+    spifs_file_t *file;\n+    list_for_every_entry(&spifs->files, file, spifs_file_t, node) {\n+        if (file->metadata.page_idx > target->metadata.page_idx) {\n+            list_add_before(&file->node, &target->node);\n+            return;\n+        }\n+    }\n+\n+    list_add_tail(&spifs->files, &target->node);\n+}\n+\n+\n+static status_t spifs_read_page(spifs_t *spifs, uint32_t page_addr)\n+{\n+    off_t block_addr = page_addr * spifs->blocks_per_page;\n+\n+    ssize_t bytes = bio_read_block(spifs->dev, spifs->page, block_addr,\n+                                   spifs->blocks_per_page);\n+\n+    if ((uint32_t)bytes != spifs->page_size) {\n+        return ERR_IO;\n+    }\n+\n+    return NO_ERROR;\n+}\n+\n+static status_t spifs_write_page(spifs_t *spifs, uint32_t page_addr)\n+{\n+    off_t block_addr = page_addr * spifs->blocks_per_page;\n+    off_t device_addr = block_addr * spifs->dev->block_size;\n+\n+    // Device requires erase before write?\n+    if (spifs->dev->geometry_count != 0) {","path":"lib/fs/spifs/spifs.c","position":394,"original_position":394,"commit_id":"e5edfcd3c22cbb419059ee291b589a28b95bdca1","original_commit_id":"e5edfcd3c22cbb419059ee291b589a28b95bdca1","user":{"login":"travisg","id":97708,"avatar_url":"https://avatars.githubusercontent.com/u/97708?v=3","gravatar_id":"","url":"https://api.github.com/users/travisg","html_url":"https://github.com/travisg","followers_url":"https://api.github.com/users/travisg/followers","following_url":"https://api.github.com/users/travisg/following{/other_user}","gists_url":"https://api.github.com/users/travisg/gists{/gist_id}","starred_url":"https://api.github.com/users/travisg/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/travisg/subscriptions","organizations_url":"https://api.github.com/users/travisg/orgs","repos_url":"https://api.github.com/users/travisg/repos","events_url":"https://api.github.com/users/travisg/events{/privacy}","received_events_url":"https://api.github.com/users/travisg/received_events","type":"User","site_admin":false},"body":"didn't we add a flag to the bdev for this, or were we just talking about it and never did?","created_at":"2015-11-14T01:34:19Z","updated_at":"2015-11-14T01:34:19Z","html_url":"https://github.com/travisg/lk/pull/78#discussion_r44852072","pull_request_url":"https://api.github.com/repos/travisg/lk/pulls/78","_links":{"self":{"href":"https://api.github.com/repos/travisg/lk/pulls/comments/44852072"},"html":{"href":"https://github.com/travisg/lk/pull/78#discussion_r44852072"},"pull_request":{"href":"https://api.github.com/repos/travisg/lk/pulls/78"}}},"pull_request":{"url":"https://api.github.com/repos/travisg/lk/pulls/78","id":50569163,"html_url":"https://github.com/travisg/lk/pull/78","diff_url":"https://github.com/travisg/lk/pull/78.diff","patch_url":"https://github.com/travisg/lk/pull/78.patch","issue_url":"https://api.github.com/repos/travisg/lk/issues/78","number":78,"state":"open","locked":false,"title":"Filesystem for SPIFlash","user":{"login":"gkalsi","id":1513257,"avatar_url":"https://avatars.githubusercontent.com/u/1513257?v=3","gravatar_id":"","url":"https://api.github.com/users/gkalsi","html_url":"https://github.com/gkalsi","followers_url":"https://api.github.com/users/gkalsi/followers","following_url":"https://api.github.com/users/gkalsi/following{/other_user}","gists_url":"https://api.github.com/users/gkalsi/gists{/gist_id}","starred_url":"https://api.github.com/users/gkalsi/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gkalsi/subscriptions","organizations_url":"https://api.github.com/users/gkalsi/orgs","repos_url":"https://api.github.com/users/gkalsi/repos","events_url":"https://api.github.com/users/gkalsi/events{/privacy}","received_events_url":"https://api.github.com/users/gkalsi/received_events","type":"User","site_admin":false},"body":"","created_at":"2015-11-12T21:18:13Z","updated_at":"2015-11-14T01:34:19Z","closed_at":null,"merged_at":null,"merge_commit_sha":"eb1fbe336e3c53c4787b184fb9206b2c7374d7ba","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/travisg/lk/pulls/78/commits","review_comments_url":"https://api.github.com/repos/travisg/lk/pulls/78/comments","review_comment_url":"https://api.github.com/repos/travisg/lk/pulls/comments{/number}","comments_url":"https://api.github.com/repos/travisg/lk/issues/78/comments","statuses_url":"https://api.github.com/repos/travisg/lk/statuses/e5edfcd3c22cbb419059ee291b589a28b95bdca1","head":{"label":"travisg:pr_spifs","ref":"pr_spifs","sha":"e5edfcd3c22cbb419059ee291b589a28b95bdca1","user":{"login":"travisg","id":97708,"avatar_url":"https://avatars.githubusercontent.com/u/97708?v=3","gravatar_id":"","url":"https://api.github.com/users/travisg","html_url":"https://github.com/travisg","followers_url":"https://api.github.com/users/travisg/followers","following_url":"https://api.github.com/users/travisg/following{/other_user}","gists_url":"https://api.github.com/users/travisg/gists{/gist_id}","starred_url":"https://api.github.com/users/travisg/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/travisg/subscriptions","organizations_url":"https://api.github.com/users/travisg/orgs","repos_url":"https://api.github.com/users/travisg/repos","events_url":"https://api.github.com/users/travisg/events{/privacy}","received_events_url":"https://api.github.com/users/travisg/received_events","type":"User","site_admin":false},"repo":{"id":3058456,"name":"lk","full_name":"travisg/lk","owner":{"login":"travisg","id":97708,"avatar_url":"https://avatars.githubusercontent.com/u/97708?v=3","gravatar_id":"","url":"https://api.github.com/users/travisg","html_url":"https://github.com/travisg","followers_url":"https://api.github.com/users/travisg/followers","following_url":"https://api.github.com/users/travisg/following{/other_user}","gists_url":"https://api.github.com/users/travisg/gists{/gist_id}","starred_url":"https://api.github.com/users/travisg/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/travisg/subscriptions","organizations_url":"https://api.github.com/users/travisg/orgs","repos_url":"https://api.github.com/users/travisg/repos","events_url":"https://api.github.com/users/travisg/events{/privacy}","received_events_url":"https://api.github.com/users/travisg/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/travisg/lk","description":"LK embedded kernel","fork":false,"url":"https://api.github.com/repos/travisg/lk","forks_url":"https://api.github.com/repos/travisg/lk/forks","keys_url":"https://api.github.com/repos/travisg/lk/keys{/key_id}","collaborators_url":"https://api.github.com/repos/travisg/lk/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/travisg/lk/teams","hooks_url":"https://api.github.com/repos/travisg/lk/hooks","issue_events_url":"https://api.github.com/repos/travisg/lk/issues/events{/number}","events_url":"https://api.github.com/repos/travisg/lk/events","assignees_url":"https://api.github.com/repos/travisg/lk/assignees{/user}","branches_url":"https://api.github.com/repos/travisg/lk/branches{/branch}","tags_url":"https://api.github.com/repos/travisg/lk/tags","blobs_url":"https://api.github.com/repos/travisg/lk/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/travisg/lk/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/travisg/lk/git/refs{/sha}","trees_url":"https://api.github.com/repos/travisg/lk/git/trees{/sha}","statuses_url":"https://api.github.com/repos/travisg/lk/statuses/{sha}","languages_url":"https://api.github.com/repos/travisg/lk/languages","stargazers_url":"https://api.github.com/repos/travisg/lk/stargazers","contributors_url":"https://api.github.com/repos/travisg/lk/contributors","subscribers_url":"https://api.github.com/repos/travisg/lk/subscribers","subscription_url":"https://api.github.com/repos/travisg/lk/subscription","commits_url":"https://api.github.com/repos/travisg/lk/commits{/sha}","git_commits_url":"https://api.github.com/repos/travisg/lk/git/commits{/sha}","comments_url":"https://api.github.com/repos/travisg/lk/comments{/number}","issue_comment_url":"https://api.github.com/repos/travisg/lk/issues/comments{/number}","contents_url":"https://api.github.com/repos/travisg/lk/contents/{+path}","compare_url":"https://api.github.com/repos/travisg/lk/compare/{base}...{head}","merges_url":"https://api.github.com/repos/travisg/lk/merges","archive_url":"https://api.github.com/repos/travisg/lk/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/travisg/lk/downloads","issues_url":"https://api.github.com/repos/travisg/lk/issues{/number}","pulls_url":"https://api.github.com/repos/travisg/lk/pulls{/number}","milestones_url":"https://api.github.com/repos/travisg/lk/milestones{/number}","notifications_url":"https://api.github.com/repos/travisg/lk/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/travisg/lk/labels{/name}","releases_url":"https://api.github.com/repos/travisg/lk/releases{/id}","created_at":"2011-12-27T19:19:36Z","updated_at":"2015-11-13T07:23:01Z","pushed_at":"2015-11-13T21:20:44Z","git_url":"git://github.com/travisg/lk.git","ssh_url":"git@github.com:travisg/lk.git","clone_url":"https://github.com/travisg/lk.git","svn_url":"https://github.com/travisg/lk","homepage":"","size":18377,"stargazers_count":100,"watchers_count":100,"language":"C","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":66,"mirror_url":null,"open_issues_count":11,"forks":66,"open_issues":11,"watchers":100,"default_branch":"master"}},"base":{"label":"travisg:master","ref":"master","sha":"4560219c452f78c7d35d2a8d13634056503594f5","user":{"login":"travisg","id":97708,"avatar_url":"https://avatars.githubusercontent.com/u/97708?v=3","gravatar_id":"","url":"https://api.github.com/users/travisg","html_url":"https://github.com/travisg","followers_url":"https://api.github.com/users/travisg/followers","following_url":"https://api.github.com/users/travisg/following{/other_user}","gists_url":"https://api.github.com/users/travisg/gists{/gist_id}","starred_url":"https://api.github.com/users/travisg/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/travisg/subscriptions","organizations_url":"https://api.github.com/users/travisg/orgs","repos_url":"https://api.github.com/users/travisg/repos","events_url":"https://api.github.com/users/travisg/events{/privacy}","received_events_url":"https://api.github.com/users/travisg/received_events","type":"User","site_admin":false},"repo":{"id":3058456,"name":"lk","full_name":"travisg/lk","owner":{"login":"travisg","id":97708,"avatar_url":"https://avatars.githubusercontent.com/u/97708?v=3","gravatar_id":"","url":"https://api.github.com/users/travisg","html_url":"https://github.com/travisg","followers_url":"https://api.github.com/users/travisg/followers","following_url":"https://api.github.com/users/travisg/following{/other_user}","gists_url":"https://api.github.com/users/travisg/gists{/gist_id}","starred_url":"https://api.github.com/users/travisg/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/travisg/subscriptions","organizations_url":"https://api.github.com/users/travisg/orgs","repos_url":"https://api.github.com/users/travisg/repos","events_url":"https://api.github.com/users/travisg/events{/privacy}","received_events_url":"https://api.github.com/users/travisg/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/travisg/lk","description":"LK embedded kernel","fork":false,"url":"https://api.github.com/repos/travisg/lk","forks_url":"https://api.github.com/repos/travisg/lk/forks","keys_url":"https://api.github.com/repos/travisg/lk/keys{/key_id}","collaborators_url":"https://api.github.com/repos/travisg/lk/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/travisg/lk/teams","hooks_url":"https://api.github.com/repos/travisg/lk/hooks","issue_events_url":"https://api.github.com/repos/travisg/lk/issues/events{/number}","events_url":"https://api.github.com/repos/travisg/lk/events","assignees_url":"https://api.github.com/repos/travisg/lk/assignees{/user}","branches_url":"https://api.github.com/repos/travisg/lk/branches{/branch}","tags_url":"https://api.github.com/repos/travisg/lk/tags","blobs_url":"https://api.github.com/repos/travisg/lk/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/travisg/lk/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/travisg/lk/git/refs{/sha}","trees_url":"https://api.github.com/repos/travisg/lk/git/trees{/sha}","statuses_url":"https://api.github.com/repos/travisg/lk/statuses/{sha}","languages_url":"https://api.github.com/repos/travisg/lk/languages","stargazers_url":"https://api.github.com/repos/travisg/lk/stargazers","contributors_url":"https://api.github.com/repos/travisg/lk/contributors","subscribers_url":"https://api.github.com/repos/travisg/lk/subscribers","subscription_url":"https://api.github.com/repos/travisg/lk/subscription","commits_url":"https://api.github.com/repos/travisg/lk/commits{/sha}","git_commits_url":"https://api.github.com/repos/travisg/lk/git/commits{/sha}","comments_url":"https://api.github.com/repos/travisg/lk/comments{/number}","issue_comment_url":"https://api.github.com/repos/travisg/lk/issues/comments{/number}","contents_url":"https://api.github.com/repos/travisg/lk/contents/{+path}","compare_url":"https://api.github.com/repos/travisg/lk/compare/{base}...{head}","merges_url":"https://api.github.com/repos/travisg/lk/merges","archive_url":"https://api.github.com/repos/travisg/lk/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/travisg/lk/downloads","issues_url":"https://api.github.com/repos/travisg/lk/issues{/number}","pulls_url":"https://api.github.com/repos/travisg/lk/pulls{/number}","milestones_url":"https://api.github.com/repos/travisg/lk/milestones{/number}","notifications_url":"https://api.github.com/repos/travisg/lk/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/travisg/lk/labels{/name}","releases_url":"https://api.github.com/repos/travisg/lk/releases{/id}","created_at":"2011-12-27T19:19:36Z","updated_at":"2015-11-13T07:23:01Z","pushed_at":"2015-11-13T21:20:44Z","git_url":"git://github.com/travisg/lk.git","ssh_url":"git@github.com:travisg/lk.git","clone_url":"https://github.com/travisg/lk.git","svn_url":"https://github.com/travisg/lk","homepage":"","size":18377,"stargazers_count":100,"watchers_count":100,"language":"C","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":66,"mirror_url":null,"open_issues_count":11,"forks":66,"open_issues":11,"watchers":100,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/travisg/lk/pulls/78"},"html":{"href":"https://github.com/travisg/lk/pull/78"},"issue":{"href":"https://api.github.com/repos/travisg/lk/issues/78"},"comments":{"href":"https://api.github.com/repos/travisg/lk/issues/78/comments"},"review_comments":{"href":"https://api.github.com/repos/travisg/lk/pulls/78/comments"},"review_comment":{"href":"https://api.github.com/repos/travisg/lk/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/travisg/lk/pulls/78/commits"},"statuses":{"href":"https://api.github.com/repos/travisg/lk/statuses/e5edfcd3c22cbb419059ee291b589a28b95bdca1"}}}}