Konubinix' opinionated web of thoughts

Edit the Content of a Folder in Ipfs

fleeting

Let’s use an arbitrary folder to play with it.

TMP="$(mktemp -d)"
trap "rm -rf '${TMP}'" 0

echo "the content of a" > "${TMP}/a"
echo "the contant of b" > "${TMP}/b"

HASH="$(ipfs add --recursive --quieter --pin=false --cid-version=1   "${TMP}")"
echo "${HASH}"
bafybeia7so2i5ftkqudrsunpyhdhnpzflz4wtl4gdy2ej2gk42tu7eblai

This hash represent the content of this directory. Using ipfs mount, I can easily navigate in it.

ipfs mount
ls /ipfs/"${HASH}"
cat /ipfs/"${HASH}"/a
cat /ipfs/"${HASH}"/b
IPFS mounted at: /ipfs
IPNS mounted at: /ipns
a  b
the content of a
the contant of b

So far so good. What if I want to add the file c with the content “the content of c” in this directory and get the resulting hash?

getting and adding again

The most simple solution, but it needs to download the whole data.

TMP="$(mktemp -d)"
trap "rm -rf '${TMP}'" 0

ipfs get -o "${TMP}" "${HASH}"
echo "the content of c" > "${TMP}/c"
HASH_GET="$(ipfs add --pin=false --recursive --cid-version=1 --quieter "${TMP}")"
echo "${HASH_GET}"
bafybeicy4ovnichwqcmivgjed6fshw4wclcxmun76zrp62qiqfwuyrpodq

I can check that the new content is indeed there with.

ls /ipfs/"${HASH_GET}"
cat /ipfs/"${HASH_GET}"/a
cat /ipfs/"${HASH_GET}"/b
cat /ipfs/"${HASH_GET}"/c
a  b  c
the content of a
the contant of b
the content of c

using the MFS

To do so, I need to “create the content” in the MFS (say in the directory /tmp), then modify it and then get the resulting hash.

To do this in a reproducible fashion, I need to make sure the temporary MFS directory does not exist prior to doing the command, I can do it with.

ipfs files mkdir -p /tmp
ipfs files rm -r /tmp
C_HASH="$(echo "the content of c" | ipfs add --quieter --cid-version=1 --pin=false -)"
ipfs files cp "/ipfs/${HASH}" /tmp
ipfs files cp "/ipfs/${C_HASH}" /tmp/c
HASH_MFS="$(ipfs files stat --hash /tmp)"
echo "${HASH_MFS}"
bafybeicy4ovnichwqcmivgjed6fshw4wclcxmun76zrp62qiqfwuyrpodq

This is the same hash. So far so good.

using ipfs dag

An ipfs directory is a simple ipfs file with a structure that indicate it points to other ipfs files (see ipld and unixfs).

We can take a look at this object with ipfs dag

ipfs dag get "${HASH}"|jq -M
{
  "Data": {
    "/": {
      "bytes": "CAE"
    }
  },
  "Links": [
    {
      "Hash": {
        "/": "bafkreidk5c6eulzviucvqa55qhb5c3m5uei7dttfarer42tt4m4guohmqa"
      },
      "Name": "a",
      "Tsize": 17
    },
    {
      "Hash": {
        "/": "bafkreibhu5no7laobsvslezwzyoyfgp7ab4x2noa47izc25f72i4d7lmyu"
      },
      "Name": "b",
      "Tsize": 17
    }
  ]
}

I don’t know well unixfs, but chances are that the data “CAE” actually indicates that the content describes a folder and the remaining part are the files or subfolders.

So I could just grab the object, insert the hash of c as a new file and put the object back in ipfs.

HASH_DAG="$(ipfs dag get "${HASH}"|jq -M ".Links += [{\"Name\": \"c\", \"Hash\": {\"/\": \"${C_HASH}\"}, \"Tsize\": $(ipfs files stat --size /ipfs/${C_HASH})}]"|ipfs dag put)"
echo "${HASH_DAG}"
bafyreib2njnykr4f7tzkfol5tmoovesmdmpmrtev2u46umsf7nmd3vltqy
ls /ipfs/"${HASH_DAG}"
cat /ipfs/"${HASH_DAG}"/a
cat /ipfs/"${HASH_DAG}"/b
cat /ipfs/"${HASH_DAG}"/c
ls: cannot access '/ipfs/bafyreib2njnykr4f7tzkfol5tmoovesmdmpmrtev2u46umsf7nmd3vltqy': Operation not supported
cat: https://konubinix.eu/ipfs/bafyreib2njnykr4f7tzkfol5tmoovesmdmpmrtev2u46umsf7nmd3vltqy/a: Operation not supported
cat: https://konubinix.eu/ipfs/bafyreib2njnykr4f7tzkfol5tmoovesmdmpmrtev2u46umsf7nmd3vltqy/b: Operation not supported
cat: https://konubinix.eu/ipfs/bafyreib2njnykr4f7tzkfol5tmoovesmdmpmrtev2u46umsf7nmd3vltqy/c: Operation not supported

Hmmm, the HASH_DAG is not the same as NEW_HASH and is not actually understood as an ipfs directory. I must have misunderstood something.

using ipfs object

ipfs object is supposed to be obsolete and replaced by ipfs dag, but since I don’t know how to make ipfs dag work, let’s give ipfs object a shot.

ipfs object get "${HASH}"|jq -M
{
  "Links": [
    {
      "Name": "a",
      "Hash": "bafkreidk5c6eulzviucvqa55qhb5c3m5uei7dttfarer42tt4m4guohmqa",
      "Size": 17
    },
    {
      "Name": "b",
      "Hash": "bafkreibhu5no7laobsvslezwzyoyfgp7ab4x2noa47izc25f72i4d7lmyu",
      "Size": 17
    }
  ],
  "Data": "\b\u0001"
}

Great, I can get access to the same data, with a slightly different format though. Also, ipfs object appears to only work with CID version 0. That’s a pity but we can use it for now.

Let’s do the same and add some data in this format.

HASH_OBJECT="$(ipfs object get "${HASH}"|jq -M ".Links += [{\"Name\": \"c\", \"Hash\": \"${C_HASH}\", \"Size\": $(ipfs files stat --size /ipfs/${C_HASH})}]"|ipfs object put --quiet)"
echo ${HASH_OBJECT}
QmUKdwJVyRrTPafejK8aAXpFi1Sfv7KUhWTRsFHgWQtTPq

Now, let’s take a look at this content.

ls /ipfs/"${HASH_OBJECT}"
cat /ipfs/"${HASH_OBJECT}"/a
cat /ipfs/"${HASH_OBJECT}"/b
cat /ipfs/"${HASH_OBJECT}"/c
a  b  c
the content of a
the contant of b
the content of c

Great, it works! Let’s try to check that the hash are actually pointing to the same content.

HASH_CONVERTED="$(ipfs cid base32 "${HASH_OBJECT}")"
echo "${HASH_CONVERTED}"
bafybeicy4ovnichwqcmivgjed6fshw4wclcxmun76zrp62qiqfwuyrpodq

This is indeed hash we expected.

ipfs object patch

Actually, ipfs object allows to perform this kind of action easily.

ipfs object patch add-link "${HASH}" c/c "${C_HASH}"
bafybeicy4ovnichwqcmivgjed6fshw4wclcxmun76zrp62qiqfwuyrpodq

And great, it return the correct hash.

Removing the data afterwards is as easy as.

ipfs object patch rm-link "${HASH_GET}" c
bafybeia7so2i5ftkqudrsunpyhdhnpzflz4wtl4gdy2ej2gk42tu7eblai

And we get back to the initial hash.