aknosis.com

webdev && sysadmin quips, tips, articles + more

Twig Cache File Permissions

| Comments

If you are having problems with the permissions of the cache files that Twig compiles this may point you in the right direction.

To bring you up to speed, Twig will compile Twig templates into native php once parsed, this ensures that they are rendered as fast as possible.

My specific use case is that some templates are compiled on the command line as a different user than apache which compiles them from web requests. When generating templates from various users I ran into permissions where the cli user couldn’t create cache files in folders that were previously created by Apache. The cli user and Apache both are in the same group so I just needed to make sure that all folders and files (for clearing the cache) are owned by the common group, which is easier said than done.

Cache Files and setgid

I could never get Twig to work the way I needed expected it to. After much trial and error I found out that my problem lied deep inside Twig. When Twig writes out cache files it creates two sub folders e.g. cachedir/ab/cd/abcdefg.php. I have to ensure that these new folders and files in them get created with the common group owning them. I accomplish this using the setgid bit on the parent cache directory (chmod 2775 or chmod g+s). Now thinking my problems are solved I see what the real results of my perm change are… New directories were still not being created by the common group, everything was owned by the apache group still, and even then the directories weren’t group writable.

Digging into the source

The answer is always in the source right? Taking a look into the
source (extracted below) you have to believe that the directories would be created as 0777 (globally writeable, readable and executable) but that would be too easy. It turns out that Apache was creating directories as 0644 and still owned by apache:apache. It got more strange when seeing that cache files created by my cli user were indeed created as cli:common rather than cli:cli.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php
    protected function writeCacheFile($file, $content)
    {
        $dir = dirname($file);
        if (!is_dir($dir)) {
            if (false === @mkdir($dir, 0777, true) && !is_dir($dir)) {
                throw new RuntimeException(sprintf("Unable to create the cache directory (%s).", $dir));
            }
        } elseif (!is_writable($dir)) {
            throw new RuntimeException(sprintf("Unable to write in the cache directory (%s).", $dir));
        }

        $tmpFile = tempnam(dirname($file), basename($file));
        if (false !== @file_put_contents($tmpFile, $content)) {
            // rename does not work on Win32 before 5.2.6
            if (@rename($tmpFile, $file) || (@copy($tmpFile, $file) && unlink($tmpFile))) {
                @chmod($file, 0644);

                return;
            }
        }

        throw new Twig_Error_Runtime(sprintf('Failed to write cache file "%s".', $file));
    }
?>

umask

After a fair amount of googling it turns out that Apache runs with a umask of 0022 preventing it from creating files and folders that are writable by a group other than its own. The cli user has a umask of 0002, allowing it to create a file that is not owned by itself. So even though Twig tells php to create the directory with the 0777 permission it was effectively creating it as 0644 when running from Apache. If umask is still boggling you, read this good explanation of umask.

Lord love some object oriented programming

After hours of wrapping my brain around the funk that was the umask, setgid, and permissions in general, I came up with this easy patch that allows me to have much greater control of the cache file creation.

The code basically changes the umask to that of the cli user (0002) and proceeds to create the directory before Twig tries to do it. Then I call the function I overrode (the one in the above code block ) that creates my cached template file. I then take that cache file and set it to 775 so that it is readable, writable, and executable by the user and group shared amongst apache and the cli user.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
class Aknosis_Twig_Environment extends Twig_Environment {

  /**
  * This exists so template cache files use the same
  * group between apache and cli
  */
  protected function writeCacheFile($file, $content){
      if (!is_dir(dirname($file))) {
          $old = umask(0002);
          mkdir(dirname($file),0777,true);
          umask($old);
      }
      parent::writeCacheFile($file, $content);
      chmod($file,0775);
  }
}
?>

So now in my code I am calling new Aknosis_Twig_Environment($etc); instead of new Twig_Environment($etc);

Side Note

In the time it has taken me to write this post, there was an issue that was solved in regards to the umask. This is a similar issue to what I am trying to solve here, but not similar enough to solve my problem. Basically this user was saying that creating the file blindly as 0644 is incorrect and that we should apply the umask for the cache file. The fix however, does not solve my problem, it only applies the umask for the cache file writing, not the folder. As it stands now I still have this manual override in place.

I have to assume that my situation isn’t 100% unique, if you need to allow access to compiled template folder by more than one single user the code above should work, or get you pointed in the correct direction.

Comments