{"id":6685,"date":"2018-04-24T08:11:09","date_gmt":"2018-04-24T06:11:09","guid":{"rendered":"https:\/\/anexia.com\/stagingblog\/?p=6685"},"modified":"2022-04-21T13:31:05","modified_gmt":"2022-04-21T11:31:05","slug":"filesystem-quota-management-in-go","status":"publish","type":"post","link":"https:\/\/anexia.com\/blog\/en\/filesystem-quota-management-in-go\/","title":{"rendered":"Filesystem quota management in Go"},"content":{"rendered":"<p>Filesystem quotas are a rather simple concept. Instead of allowing every user to use all of the available space on a filesystem (or drive, to be less technical) each user is confined to limits &#8211; so called quotas. These quotas determine how much space an individual user or group may use.<\/p>\n<p>Currently Go does not include support for controlling filesystem quotas in its otherwise vast standard library. Unfortunately, no third-party libraries which would add this functionality exist either. One of our current project at the <a href=\"https:\/\/anexia.com\/en\/software-development\/working-with-anexia\/engineering-rd\/\">Anexia R&amp;D department<\/a> has to control quotas from within Go, therefore, we decided to come up with a solution of our own: a Go-native implementation.<\/p>\n<p>This gives me the opportunity to demonstrate how Go interacts with the underlying operating system at the deepest layers, namely at the <em>syscall<\/em> level. We will go over the steps again taken during the development of our solution. This will explain how the neccessary research was conducted and how we developed our quota library.<\/p>\n<h2>Kernel interface<\/h2>\n<p>In Linux <em>libc<\/em> exposes support for controlling filesystem quotas via its <em>quotactl<\/em> function. <em>musl-libc<\/em> shows that quotactl can be a thin wrapper around the underlying syscall of the same name:<\/p>\n<pre class=\"lang:go decode:true \">int quotactl(int cmd, const char *special, int id, char *addr)\r\n{\r\n  return syscall(SYS_quotactl, cmd, special, id, addr);\r\n}<\/pre>\n<p>This function, along with a few C preprocessor macros which are defined in <em>musl<\/em>&#8217;s <em>include\/sys\/quota.h<\/em>, provides full filesystem quota support. As we can see, we only required a thin wrapper like the one present in <em>musl-libc<\/em> to make Go aware of filesystem quotas &#8211; this is the point where we start out adventure into implementing this functionality on top of Go.<\/p>\n<h2>Preparing a testbed<\/h2>\n<p>Before being able to conduct any tests, we need a testbed. We chose to create a new ext4 filesystem in a file, activate quotas and loop-mount this file:<\/p>\n<pre class=\"lang:go decode:true \">$ truncate -s 1G \/tmp\/test.ext4\r\n$ \/sbin\/mkfs.ext4 \/tmp\/test.ext4\r\n$ sudo mkdir -p \/mnt\/quota_test\r\n$ sudo mount -o usrjquota=aquota.user,grpjquota=aquota.group,jqfmt=vfsv1 \/tmp\/test.ext4 \/mnt\/quota_test\r\n$ sudo quotacheck -vucm \/mnt\/quota_test\r\n$ sudo quotacheck -vgcm \/mnt\/quota_test\r\n$ sudo quotaon -v \/mnt\/quota_test<\/pre>\n<p>You might need to install the quota utilities first. On Debian and Ubuntu the required package is called <em>quota<\/em>. After finishing the operations above we get a new filesystem with user and group quotas enabled, available at <em>\/mnt\/quota_test<\/em>. Take a note of output of the last command. It should be prefixed with <em>\/dev\/loopN<\/em>, whereas <em>N<\/em> is an arbitrary number. This device name will be needed later on.<\/p>\n<h2>Syscalls in Go<\/h2>\n<p>Go&#8217;s standard library provides various functions for working directly with syscalls via its <em>syscall<\/em> package. Fortunately, this package also defines the <em>SYS_QUOTACTL<\/em> constant &#8211; holding the desired syscall number. Additionally the package provides some very handy helper functions &#8211; like <em>BytePtrFromString<\/em> &#8211; to which we will get back to at a later point.<\/p>\n<p>The package further provides us with the <em>Syscall<\/em> and <em>Syscall6<\/em> functions, which issue syscalls. The first variant supports up to three arguments and <em>Syscall6<\/em> &#8211; as its name suggests &#8211; supports up to six. As the <em>quotactl<\/em> syscall requires four arguments, we will need to make use of <em>Syscall6<\/em>.<\/p>\n<h2>Digging through the man page<\/h2>\n<p>The <em>quotactl(2)<\/em> man page explains the interface in detail. However, to start off we only need basic functionality, namely the ability to retrieve and set quotas. These operations are implemented through &#8222;subcommands&#8220;, <em>Q_GETQUOTA<\/em>, which retrieves quota information, is documented to require a pointer to a specific data structure named <em>dqblk<\/em>, which we will have to implement in Go.<\/p>\n<p>Another interesting detail we need to have a closer look at is how the <em>cmd<\/em> argument to the <em>quotactl<\/em> call is built. The man page references the <em>QCMD(subcmd, type)<\/em> macro, which is defined in <em>sys\/quota.h<\/em>, taking a &#8222;subcommand&#8220; and a &#8222;quota type&#8220;, and applies a bitwise shift operation, followed by masking the type with an &#8222;AND&#8220; operation.<\/p>\n<pre class=\"lang:go decode:true \">#define QCMD(cmd, type)  (((cmd) &lt;&lt; SUBCMDSHIFT) | ((type) &amp; SUBCMDMASK))<\/pre>\n<p>As we only want to be able to retrieve quota information, this newly aquired knowledge should suffice.<\/p>\n<h2>Let&#8217;s Go<\/h2>\n<p>With all required information in hand, we define a wrapper similar to the one found in <em>musl-libc<\/em> in Go:<\/p>\n<pre class=\"lang:go decode:true \">func quotactl(cmd int, special string, id int, target unsafe.Pointer) (err error) {\r\n  var deviceNamePtr *byte\r\n  if deviceNamePtr, err = syscall.BytePtrFromString(special); err != nil {\r\n    return\r\n  }\r\n  if _, _, errno := syscall.RawSyscall6(syscall.SYS_QUOTACTL, uintptr(cmd), uintptr(unsafe.Pointer(deviceNamePtr)), uintptr(id), uintptr(target), 0, 0); errno != 0 {\r\n    err = os.NewSyscallError(\"quotactl\", errno)\r\n  }\r\n  return\r\n}<\/pre>\n<p>As <em>unsafe.Pointer<\/em> is used again and again, it is important to have a closer look at it. <em>unsafe.Pointer<\/em> is possibly best compared to a void pointer in C. The type does not matter, we just need a pointer to data.<\/p>\n<p>Also, <em>syscall.BytePtrFromString<\/em> is interesting when interacting at such a low level. This is required as Go&#8217;s string type is not compatible with NULL-terminated strings, as used in the kernel or in C. <em>syscall.BytePtrFromString<\/em> converts a Go string into such a NULL-terminated string, which is represented as the pointer to its first byte.<\/p>\n<p>The <em>target<\/em> argument is intentionally an <em>unsafe.Pointer<\/em>, as <em>quotactl<\/em> is able to fill other data structures, besides the mentioned <em>dqblk<\/em>. Using this approach in the first place allows for easier extension later on. Which brings us right to <em>dqblk<\/em>:<\/p>\n<pre class=\"lang:go decode:true \">type Dqblk struct {\r\n    DqbBHardlimit uint64\r\n    DqbBSoftlimit uint64\r\n    DqbCurSpace   uint64\r\n    DqbIHardlimit uint64\r\n    DqbISoftlimit uint64\r\n    DqbCurInodes  uint64\r\n    DqbBTime      uint64\r\n    DqbITime      uint64\r\n    DqbValid      uint32\r\n}<\/pre>\n<p>As you can see we can freely change the names of all fields, but we ensure the field order is preserved. Defining the required constants and a function implementing the <em>QCMD<\/em> macro in Go is left as an exercise to the reader.<\/p>\n<h2>Retrieving quota information<\/h2>\n<p>Now, having defined the data structures, and having converted the C-style snake-case identifiers to more Go-like camel case identifiers, we can implement the first public function which retrieves quotas:<\/p>\n<pre class=\"lang:go decode:true \">func GetQuota(typ int, special string, id int) (result *Dqblk, err error) {\r\n  result = &amp;Dqblk{}\r\n  if err = quotactl(qCmd(qGetQuota, typ), special, id, unsafe.Pointer(result)); err != nil {\r\n    result = nil\r\n  }\r\n  return\r\n}<\/pre>\n<p>A sample invocation might be <em>GetQuota(UsrQuota, &#8222;\/dev\/loop0&#8220;, 0),<\/em> which retrieves the user quota for UID 0 from &#8222;\/dev\/loop0&#8220;. Substitute <em>\/dev\/loop0<\/em> with the device name, which was returned when the testbed was created and see what happens: the prototype works.<\/p>\n<h2>Summary<\/h2>\n<p>This article has outlined the steps required for bringing low-level Linux kernel functionality to the world of Go. Retrieving filesystem quota information from the operating system was implemented in just a few lines of code. The approach taken \u2013 implementing the functionality natively in Go \u2013 allows creation of statically linked Go binaries without any external dependencies, which also greatly simplifies deployment.<\/p>\n<p>The full implementation of this example can be found at <a href=\"https:\/\/github.com\/anexia-it\/wad2018-quotactl\">https:\/\/github.com\/anexia-it\/wad2018-quotactl<\/a> &#8211; containing the discussed pieces. A more complete Go filesystem quota library, which is based on the research that also led to this article, can be found at <a href=\"https:\/\/github.com\/anexia-it\/fsquota\">https:\/\/github.com\/anexia-it\/fsquota<\/a>.<\/p>\n<p>Hoping you enjoyed this journey into the depths of Go and kernel interfaces as much as I enjoyed working on this article.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Stephan demonstrate here how Go interacts with operating system and how he developed our quota library.<\/p>\n","protected":false},"author":5,"featured_media":4149,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1135],"tags":[1622,1626,1329,1624,1327],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v22.2 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Filesystem quota management in Go - ANEXIA Blog<\/title>\n<meta name=\"description\" content=\"Stephan demonstrate here how Go interacts with operating system and how he developed our quota library.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/anexia.com\/blog\/en\/filesystem-quota-management-in-go\/\" \/>\n<meta property=\"og:locale\" content=\"de_DE\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Filesystem quota management in Go - ANEXIA Blog\" \/>\n<meta property=\"og:description\" content=\"Stephan demonstrate here how Go interacts with operating system and how he developed our quota library.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/anexia.com\/blog\/en\/filesystem-quota-management-in-go\/\" \/>\n<meta property=\"og:site_name\" content=\"ANEXIA Blog\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/anexiagmbh\/\" \/>\n<meta property=\"article:published_time\" content=\"2018-04-24T06:11:09+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2022-04-21T11:31:05+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/anexia.com\/blog\/wp-content\/uploads\/2018\/04\/Stephan-Peijnik.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"1600\" \/>\n\t<meta property=\"og:image:height\" content=\"1066\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"Stephan Peijnik\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@_ANEXIA\" \/>\n<meta name=\"twitter:site\" content=\"@_ANEXIA\" \/>\n<meta name=\"twitter:label1\" content=\"Verfasst von\" \/>\n\t<meta name=\"twitter:data1\" content=\"Stephan Peijnik\" \/>\n\t<meta name=\"twitter:label2\" content=\"Gesch\u00e4tzte Lesezeit\" \/>\n\t<meta name=\"twitter:data2\" content=\"6\u00a0Minuten\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\/\/anexia.com\/blog\/en\/filesystem-quota-management-in-go\/\",\"url\":\"https:\/\/anexia.com\/blog\/en\/filesystem-quota-management-in-go\/\",\"name\":\"Filesystem quota management in Go - ANEXIA Blog\",\"isPartOf\":{\"@id\":\"https:\/\/anexia.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/anexia.com\/blog\/en\/filesystem-quota-management-in-go\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/anexia.com\/blog\/en\/filesystem-quota-management-in-go\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/anexia.com\/blog\/wp-content\/uploads\/2018\/04\/Stephan-Peijnik.jpg\",\"datePublished\":\"2018-04-24T06:11:09+00:00\",\"dateModified\":\"2022-04-21T11:31:05+00:00\",\"author\":{\"@id\":\"https:\/\/anexia.com\/blog\/#\/schema\/person\/8f95147348ae0ed7e4c25999bebf0f1d\"},\"description\":\"Stephan demonstrate here how Go interacts with operating system and how he developed our quota library.\",\"breadcrumb\":{\"@id\":\"https:\/\/anexia.com\/blog\/en\/filesystem-quota-management-in-go\/#breadcrumb\"},\"inLanguage\":\"de\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/anexia.com\/blog\/en\/filesystem-quota-management-in-go\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"de\",\"@id\":\"https:\/\/anexia.com\/blog\/en\/filesystem-quota-management-in-go\/#primaryimage\",\"url\":\"https:\/\/anexia.com\/blog\/wp-content\/uploads\/2018\/04\/Stephan-Peijnik.jpg\",\"contentUrl\":\"https:\/\/anexia.com\/blog\/wp-content\/uploads\/2018\/04\/Stephan-Peijnik.jpg\",\"width\":1600,\"height\":1066,\"caption\":\"Stephan-Peijnik\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/anexia.com\/blog\/en\/filesystem-quota-management-in-go\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/anexia.com\/blog\/de\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Filesystem quota management in Go\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/anexia.com\/blog\/#website\",\"url\":\"https:\/\/anexia.com\/blog\/\",\"name\":\"ANEXIA Blog\",\"description\":\"[:de] ANEXIA Blog - Technischen Themen, Anexia News und Insights [:]\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/anexia.com\/blog\/?s={search_term_string}\"},\"query-input\":\"required name=search_term_string\"}],\"inLanguage\":\"de\"},{\"@type\":\"Person\",\"@id\":\"https:\/\/anexia.com\/blog\/#\/schema\/person\/8f95147348ae0ed7e4c25999bebf0f1d\",\"name\":\"Stephan Peijnik\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"de\",\"@id\":\"https:\/\/anexia.com\/blog\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/0bfcb213a87f6c6c67ec494bc0ae5585?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/0bfcb213a87f6c6c67ec494bc0ae5585?s=96&d=mm&r=g\",\"caption\":\"Stephan Peijnik\"},\"url\":\"https:\/\/anexia.com\/blog\/author\/spe\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Filesystem quota management in Go - ANEXIA Blog","description":"Stephan demonstrate here how Go interacts with operating system and how he developed our quota library.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/anexia.com\/blog\/en\/filesystem-quota-management-in-go\/","og_locale":"de_DE","og_type":"article","og_title":"Filesystem quota management in Go - ANEXIA Blog","og_description":"Stephan demonstrate here how Go interacts with operating system and how he developed our quota library.","og_url":"https:\/\/anexia.com\/blog\/en\/filesystem-quota-management-in-go\/","og_site_name":"ANEXIA Blog","article_publisher":"https:\/\/www.facebook.com\/anexiagmbh\/","article_published_time":"2018-04-24T06:11:09+00:00","article_modified_time":"2022-04-21T11:31:05+00:00","og_image":[{"width":1600,"height":1066,"url":"https:\/\/anexia.com\/blog\/wp-content\/uploads\/2018\/04\/Stephan-Peijnik.jpg","type":"image\/jpeg"}],"author":"Stephan Peijnik","twitter_card":"summary_large_image","twitter_creator":"@_ANEXIA","twitter_site":"@_ANEXIA","twitter_misc":{"Verfasst von":"Stephan Peijnik","Gesch\u00e4tzte Lesezeit":"6\u00a0Minuten"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/anexia.com\/blog\/en\/filesystem-quota-management-in-go\/","url":"https:\/\/anexia.com\/blog\/en\/filesystem-quota-management-in-go\/","name":"Filesystem quota management in Go - ANEXIA Blog","isPartOf":{"@id":"https:\/\/anexia.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/anexia.com\/blog\/en\/filesystem-quota-management-in-go\/#primaryimage"},"image":{"@id":"https:\/\/anexia.com\/blog\/en\/filesystem-quota-management-in-go\/#primaryimage"},"thumbnailUrl":"https:\/\/anexia.com\/blog\/wp-content\/uploads\/2018\/04\/Stephan-Peijnik.jpg","datePublished":"2018-04-24T06:11:09+00:00","dateModified":"2022-04-21T11:31:05+00:00","author":{"@id":"https:\/\/anexia.com\/blog\/#\/schema\/person\/8f95147348ae0ed7e4c25999bebf0f1d"},"description":"Stephan demonstrate here how Go interacts with operating system and how he developed our quota library.","breadcrumb":{"@id":"https:\/\/anexia.com\/blog\/en\/filesystem-quota-management-in-go\/#breadcrumb"},"inLanguage":"de","potentialAction":[{"@type":"ReadAction","target":["https:\/\/anexia.com\/blog\/en\/filesystem-quota-management-in-go\/"]}]},{"@type":"ImageObject","inLanguage":"de","@id":"https:\/\/anexia.com\/blog\/en\/filesystem-quota-management-in-go\/#primaryimage","url":"https:\/\/anexia.com\/blog\/wp-content\/uploads\/2018\/04\/Stephan-Peijnik.jpg","contentUrl":"https:\/\/anexia.com\/blog\/wp-content\/uploads\/2018\/04\/Stephan-Peijnik.jpg","width":1600,"height":1066,"caption":"Stephan-Peijnik"},{"@type":"BreadcrumbList","@id":"https:\/\/anexia.com\/blog\/en\/filesystem-quota-management-in-go\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/anexia.com\/blog\/de\/"},{"@type":"ListItem","position":2,"name":"Filesystem quota management in Go"}]},{"@type":"WebSite","@id":"https:\/\/anexia.com\/blog\/#website","url":"https:\/\/anexia.com\/blog\/","name":"ANEXIA Blog","description":"[:de] ANEXIA Blog - Technischen Themen, Anexia News und Insights [:]","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/anexia.com\/blog\/?s={search_term_string}"},"query-input":"required name=search_term_string"}],"inLanguage":"de"},{"@type":"Person","@id":"https:\/\/anexia.com\/blog\/#\/schema\/person\/8f95147348ae0ed7e4c25999bebf0f1d","name":"Stephan Peijnik","image":{"@type":"ImageObject","inLanguage":"de","@id":"https:\/\/anexia.com\/blog\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/0bfcb213a87f6c6c67ec494bc0ae5585?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/0bfcb213a87f6c6c67ec494bc0ae5585?s=96&d=mm&r=g","caption":"Stephan Peijnik"},"url":"https:\/\/anexia.com\/blog\/author\/spe\/"}]}},"lang":"en","translations":{"en":6685,"de":3447},"amp_enabled":true,"pll_sync_post":[],"_links":{"self":[{"href":"https:\/\/anexia.com\/blog\/wp-json\/wp\/v2\/posts\/6685"}],"collection":[{"href":"https:\/\/anexia.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/anexia.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/anexia.com\/blog\/wp-json\/wp\/v2\/users\/5"}],"replies":[{"embeddable":true,"href":"https:\/\/anexia.com\/blog\/wp-json\/wp\/v2\/comments?post=6685"}],"version-history":[{"count":1,"href":"https:\/\/anexia.com\/blog\/wp-json\/wp\/v2\/posts\/6685\/revisions"}],"predecessor-version":[{"id":6688,"href":"https:\/\/anexia.com\/blog\/wp-json\/wp\/v2\/posts\/6685\/revisions\/6688"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/anexia.com\/blog\/wp-json\/wp\/v2\/media\/4149"}],"wp:attachment":[{"href":"https:\/\/anexia.com\/blog\/wp-json\/wp\/v2\/media?parent=6685"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/anexia.com\/blog\/wp-json\/wp\/v2\/categories?post=6685"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/anexia.com\/blog\/wp-json\/wp\/v2\/tags?post=6685"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}