39

I have seen countless examples and tutorials that show how to create a file and all of them "cheat" by just setting the permission bits of the file. I would like to know / find out how to properly instantiate os.FileMode to provide to a writer during creation / updating of a file.

A crude example is this below:

func FileWrite(path string, r io.Reader, uid, gid int, perms string) (int64, error){
    w, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0664)
    if err != nil {
        if path == "" {
            w = os.Stdout
        } else {
            return 0, err
        }
    }
    defer w.Close()

    size, err := io.Copy(w, r)

    if err != nil {
        return 0, err
    }
    return size, err
}

In the basic function above permission bits 0664 is set and although this may make sense sometimes I prefer to have a proper way of setting the filemode correctly. As seen above a common example would be that the UID / GID is known and already provided as int values and the perms being octal digits that were previously gathered and inserted into a db as a string.

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
Will H
  • 1,408
  • 1
  • 13
  • 20

4 Answers4

70

My fix has been to define my own constants as I couldn't find any in os or syscall:

    const (
        OS_READ = 04
        OS_WRITE = 02
        OS_EX = 01
        OS_USER_SHIFT = 6
        OS_GROUP_SHIFT = 3
        OS_OTH_SHIFT = 0

        OS_USER_R = OS_READ<<OS_USER_SHIFT
        OS_USER_W = OS_WRITE<<OS_USER_SHIFT
        OS_USER_X = OS_EX<<OS_USER_SHIFT
        OS_USER_RW = OS_USER_R | OS_USER_W
        OS_USER_RWX = OS_USER_RW | OS_USER_X

        OS_GROUP_R = OS_READ<<OS_GROUP_SHIFT
        OS_GROUP_W = OS_WRITE<<OS_GROUP_SHIFT
        OS_GROUP_X = OS_EX<<OS_GROUP_SHIFT
        OS_GROUP_RW = OS_GROUP_R | OS_GROUP_W
        OS_GROUP_RWX = OS_GROUP_RW | OS_GROUP_X

        OS_OTH_R = OS_READ<<OS_OTH_SHIFT
        OS_OTH_W = OS_WRITE<<OS_OTH_SHIFT
        OS_OTH_X = OS_EX<<OS_OTH_SHIFT
        OS_OTH_RW = OS_OTH_R | OS_OTH_W
        OS_OTH_RWX = OS_OTH_RW | OS_OTH_X

        OS_ALL_R = OS_USER_R | OS_GROUP_R | OS_OTH_R
        OS_ALL_W = OS_USER_W | OS_GROUP_W | OS_OTH_W
        OS_ALL_X = OS_USER_X | OS_GROUP_X | OS_OTH_X
        OS_ALL_RW = OS_ALL_R | OS_ALL_W
        OS_ALL_RWX = OS_ALL_RW | OS_GROUP_X
)

This then allows me to specify my intent directly:

        // Create any directories needed to put this file in them
        var dir_file_mode os.FileMode
        dir_file_mode = os.ModeDir | (OS_USER_RWX | OS_ALL_R)
        os.MkdirAll(dir_str, dir_file_mode)

I'm sure this could be improved by use of iota, and some more combinations of permissions, but it works for me for now.

Chris Hopkins
  • 961
  • 1
  • 7
  • 15
  • 24
    Chris this is really useful and the fact that this 2 year + old question is still being watched / updated shows the need for constants to be available in the stdlib. – Will H Nov 14 '17 at 20:38
  • I believe the value for `OS_ALL_RWX` is incorrect. It should be `OS_ALL_RWX = OS_ALL_RW | OS_ALL_X` – Sam Herrmann Jul 24 '23 at 17:24
47

FileMode is just a uint32. http://golang.org/pkg/os/#FileMode

Setting via constants isn't "cheating", you use it like other numeric values. If you're not using a constant, you can use a conversion on valid numeric values:

mode := int(0777)
os.FileMode(mode)
JimB
  • 104,193
  • 13
  • 262
  • 255
  • Thanks Jim, always helpful! – Will H Mar 10 '15 at 19:21
  • 5
    Unless I'm much mistaken, this isn't helpful if you're trying to have some files that are writable and some not. One can have or in (for example) 0200 to turn on user writable, but it would aid code readability if there were an os constant to control this. Yes I expect every programmer worth their salt to know octal unix permission but it is still bad programming to use a magic value when a descriptive name could be used. As far as I can tell the standard libraries are missing this and just expect everyone to have memorise Unix file permission constants. – Chris Hopkins Mar 10 '17 at 11:58
  • Whats the difference between 0755 and 755 in go? Without the 0 my permissions failed. – Ben Mar 05 '20 at 19:43
  • 2
    @Ben `0755` is octal, equal to `493` in decimal. – JimB Mar 05 '20 at 20:00
  • I am used to using `chmod 755 *file*` in linux. `0755` is not the same thing? – Ben Mar 06 '20 at 18:54
  • 1
    The leading zero forces a base-8 conversion. A safer way is to use a base-8 conversion explicitly, e.g., if the mode is represented by a string: `modeVal, _ := strconv.ParseUint("0775", 8, 32)` and then `fm := os.FileMode(modeVal)` – SVUser Aug 31 '20 at 20:22
  • I tried to call `os.OpenFile` with 0777 in a simple test go program, but the new file was created with 0755. I ran the test program with sudo so it wasn't due to any system security settings. Does anyone know why? – Zhou Jul 02 '23 at 10:27
  • 1
    @Zhou: the default [`umask`](https://en.wikipedia.org/wiki/Umask) is `022` on most systems – JimB Jul 03 '23 at 14:46
  • @JimB Thanks! Now I see why my 777 became 755. Thanks. – Zhou Jul 18 '23 at 14:36
0

Adding to Chris Hopkins answer

You can convert the value to type fs.fileMode by doing fs.fileMode(dir_file_mode)

Also, there is one typo in the constants defined:

OS_ALL_RWX = OS_ALL_RW | OS_GROUP_X (Incorrect)

OS_ALL_RWX = OS_ALL_RW | OS_ALL_X (Correct)
  • 2
    This would be better served as a comment on Chris' answer (once you have enough reputation); in the meanwhile, it doesn't offer a full answer to the original question. – Jeremy Caney Aug 24 '22 at 04:08
0

Also adding to Chris Hopkins answer, the values you need to compose any of the file permissions using the same naming convention as used in the POSIX C API are found in the syscall package.

So instead of defining your own constants by computing them (which is still trivial as seen in Chris's answer)

OS_USER_R = OS_READ<<OS_USER_SHIFT

You can instead just use

// user read, write, execute bit
syscall.S_IRUSR
syscall.S_IWUSR
syscall.S_IXUSR
// group read, write, execute bit
syscall.S_IRGRP
syscall.S_IWGRP
syscall.S_IXGRP
// world/other read, write, execute bit
syscall.S_IROTH
syscall.S_IWOTH
syscall.S_IXOTH

And you can then compose any others as needed, or use some of the additional pre-defined ones matching the POSIX C API, or shorthands like S_IREAD, S_IWRITE, S_IEXEC, etc.