riofs

package
v0.28.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 4, 2020 License: BSD-3-Clause Imports: 26 Imported by: 4

Documentation

Overview

Package riofs contains the types and low-level functions to deal with opening and creating ROOT files, and decoding the internal structure of ROOT files.

Users should prefer to use the groot package to open or create ROOT files instead of this one.

Example (Mkdir)
package main

import (
	"fmt"
	"log"
	"os"

	"go-hep.org/x/hep/groot"
	"go-hep.org/x/hep/groot/rbase"
	"go-hep.org/x/hep/groot/riofs"
	"go-hep.org/x/hep/groot/root"
)

func main() {
	const fname = "../testdata/subdirs.root"
	defer os.Remove(fname)

	{
		w, err := groot.Create(fname)
		if err != nil {
			log.Fatal(err)
		}
		defer w.Close()

		dir1, err := w.Mkdir("dir1")
		if err != nil {
			log.Fatal(err)
		}

		dir11, err := dir1.Mkdir("dir11")
		if err != nil {
			log.Fatal(err)
		}

		err = dir11.Put("obj1", rbase.NewObjString("data-obj1"))
		if err != nil {
			log.Fatal(err)
		}

		dir2, err := w.Mkdir("dir2")
		if err != nil {
			log.Fatal(err)
		}

		err = dir2.Put("obj2", rbase.NewObjString("data-obj2"))
		if err != nil {
			log.Fatal(err)
		}

		err = w.Close()
		if err != nil {
			log.Fatal(err)
		}
	}

	r, err := groot.Open(fname)
	if err != nil {
		log.Fatal(err)
	}
	defer r.Close()

	err = riofs.Walk(r, func(path string, obj root.Object, err error) error {
		fmt.Printf(">> %v\n", path)
		return err
	})
	if err != nil {
		log.Fatalf("could not walk ROOT file: %+v", err)
	}

}
Output:

>> ../testdata/subdirs.root
>> ../testdata/subdirs.root/dir1
>> ../testdata/subdirs.root/dir1/dir11
>> ../testdata/subdirs.root/dir1/dir11/obj1
>> ../testdata/subdirs.root/dir2
>> ../testdata/subdirs.root/dir2/obj2
Example (RecursiveMkdir)
package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"os"

	stdpath "path"

	"go-hep.org/x/hep/groot"
	"go-hep.org/x/hep/groot/rbase"
	"go-hep.org/x/hep/groot/riofs"
	"go-hep.org/x/hep/groot/root"
)

func main() {
	dir, err := ioutil.TempDir("", "groot-riofs-")
	if err != nil {
		log.Fatal(err)
	}
	defer os.RemoveAll(dir)

	fname := stdpath.Join(dir, "dirs.root")
	f, err := groot.Create(fname)
	if err != nil {
		log.Fatal(err)
	}
	defer f.Close()

	rd := riofs.Dir(f)

	for _, path := range []string{
		"dir1/dir11/dir111",
		"/dir2/dir22/dir000",
		"dir2/dir22/dir222",
	} {
		_, err = rd.Mkdir(path)
		if err != nil {
			log.Fatal(err)
		}
	}

	err = rd.Put("/dir1/dir11/obj-1", rbase.NewObjString("obj-1"))
	if err != nil {
		log.Fatal(err)
	}

	err = riofs.Walk(f, func(path string, obj root.Object, err error) error {
		name := path[len(fname):]
		if name == "" {
			return err
		}
		switch o := obj.(type) {
		case *rbase.ObjString:
			fmt.Printf(">> %v -- value=%q\n", name, o)
		default:
			fmt.Printf(">> %v\n", name)
		}
		return err
	})
	if err != nil {
		log.Fatalf("could not walk ROOT file: %+v", err)
	}

	err = f.Close()
	if err != nil {
		log.Fatalf("could not close ROOT file: %v", err)
	}

}
Output:

>> /dir1
>> /dir1/dir11
>> /dir1/dir11/dir111
>> /dir1/dir11/obj-1 -- value="obj-1"
>> /dir2
>> /dir2/dir22
>> /dir2/dir22/dir000
>> /dir2/dir22/dir222
Example (RecursivePut)
package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"os"

	stdpath "path"

	"go-hep.org/x/hep/groot"
	"go-hep.org/x/hep/groot/rbase"
	"go-hep.org/x/hep/groot/riofs"
	"go-hep.org/x/hep/groot/root"
)

func main() {
	dir, err := ioutil.TempDir("", "groot-riofs-")
	if err != nil {
		log.Fatal(err)
	}
	defer os.RemoveAll(dir)

	fname := stdpath.Join(dir, "dirs.root")
	f, err := groot.Create(fname)
	if err != nil {
		log.Fatal(err)
	}
	defer f.Close()

	rd := riofs.Dir(f)

	// create obj-1, put it under dir1/dir11, create all intermediate directories.
	err = rd.Put("dir1/dir11/obj-1", rbase.NewObjString("obj-1"))
	if err != nil {
		log.Fatal(err)
	}

	// create obj-2
	err = rd.Put("/dir2/dir22/dir222/obj-2", rbase.NewObjString("obj-2"))
	if err != nil {
		log.Fatal(err)
	}

	// update obj-1
	err = rd.Put("dir1/dir11/obj-1", rbase.NewObjString("obj-1-1"))
	if err != nil {
		log.Fatal(err)
	}

	o, err := rd.Get("dir1/dir11/obj-1;1")
	if err != nil {
		log.Fatal(err)
	}
	if got, want := o.(*rbase.ObjString).String(), "obj-1"; got != want {
		log.Fatalf("invalid obj-1;1 value. got=%q, want=%q", got, want)
	}

	o, err = rd.Get("dir1/dir11/obj-1;2")
	if err != nil {
		log.Fatal(err)
	}
	if got, want := o.(*rbase.ObjString).String(), "obj-1-1"; got != want {
		log.Fatalf("invalid obj-1;1 value. got=%q, want=%q", got, want)
	}

	o, err = rd.Get("dir1/dir11/obj-1")
	if err != nil {
		log.Fatal(err)
	}
	if got, want := o.(*rbase.ObjString).String(), "obj-1-1"; got != want {
		log.Fatalf("invalid obj-1 value. got=%q, want=%q", got, want)
	}

	err = riofs.Walk(f, func(path string, obj root.Object, err error) error {
		name := path[len(fname):]
		if name == "" {
			return err
		}
		switch o := obj.(type) {
		case *rbase.ObjString:
			fmt.Printf(">> %v -- value=%q\n", name, o)
		default:
			fmt.Printf(">> %v\n", name)
		}
		return err
	})
	if err != nil {
		log.Fatalf("could not walk ROOT file: %+v", err)
	}

	err = f.Close()
	if err != nil {
		log.Fatalf("could not close ROOT file: %v", err)
	}

}
Output:

>> /dir1
>> /dir1/dir11
>> /dir1/dir11/obj-1 -- value="obj-1-1"
>> /dir2
>> /dir2/dir22
>> /dir2/dir22/dir222
>> /dir2/dir22/dir222/obj-2 -- value="obj-2"

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	ErrReadOnly = errors.New("riofs: file read-only")
)
View Source
var SkipDir = errors.New("riofs: skip this directory") //lint:ignore ST1012 EOF-like sentry

SkipDir is used as a return value from WalkFuncs to indicate that the directory named in the call is to be skipped. It is not returned as an error by any function.

Functions

func Drivers added in v0.18.0

func Drivers() []string

Drivers returns a sorted list of the names of the registered plugins to open ROOT files.

func Register added in v0.18.0

func Register(name string, f func(path string) (Reader, error))

Register registers a plugin to open ROOT files. Register panics if it is called twice with the same name of if the plugin function is nil.

func Walk

func Walk(dir Directory, walkFn WalkFunc) error

Walk walks the ROOT file tree rooted at dir, calling walkFn for each ROOT object or Directory in the ROOT file tree, including dir.

If an object exists with multiple cycle values, only the latest one is considered.

Example
package main

import (
	"fmt"
	"log"

	stdpath "path"
	"strings"

	"go-hep.org/x/hep/groot/riofs"
	"go-hep.org/x/hep/groot/root"
)

func main() {
	f, err := riofs.Open("../testdata/dirs-6.14.00.root")
	if err != nil {
		log.Fatal(err)
	}
	defer f.Close()

	fmt.Printf("visit all ROOT file tree:\n")
	err = riofs.Walk(f, func(path string, obj root.Object, err error) error {
		fmt.Printf("%s (%s)\n", path, obj.Class())
		return nil
	})
	if err != nil {
		log.Fatalf("could not walk through file: %v", err)
	}

	fmt.Printf("visit only dir1:\n")
	err = riofs.Walk(f, func(path string, obj root.Object, err error) error {
		if !(strings.HasPrefix(path, stdpath.Join(f.Name(), "dir1")) || path == f.Name()) {
			return riofs.SkipDir
		}
		fmt.Printf("%s (%s)\n", path, obj.Class())
		return nil
	})
	if err != nil {
		log.Fatalf("could not walk through file: %v", err)
	}

}
Output:

visit all ROOT file tree:
dirs-6.14.00.root (TFile)
dirs-6.14.00.root/dir1 (TDirectoryFile)
dirs-6.14.00.root/dir1/dir11 (TDirectoryFile)
dirs-6.14.00.root/dir1/dir11/h1 (TH1F)
dirs-6.14.00.root/dir2 (TDirectoryFile)
dirs-6.14.00.root/dir3 (TDirectoryFile)
visit only dir1:
dirs-6.14.00.root (TFile)
dirs-6.14.00.root/dir1 (TDirectoryFile)
dirs-6.14.00.root/dir1/dir11 (TDirectoryFile)
dirs-6.14.00.root/dir1/dir11/h1 (TH1F)

Types

type Directory

type Directory interface {
	// Get returns the object identified by namecycle
	//   namecycle has the format name;cycle
	//   name  = * is illegal, cycle = * is illegal
	//   cycle = "" or cycle = 9999 ==> apply to a memory object
	//
	//   examples:
	//     foo   : get object named foo in memory
	//             if object is not in memory, try with highest cycle from file
	//     foo;1 : get cycle 1 of foo on file
	Get(namecycle string) (root.Object, error)

	// Put puts the object v under the key with the given name.
	Put(name string, v root.Object) error

	// Keys returns the list of keys being held by this directory.
	Keys() []Key

	// Mkdir creates a new subdirectory
	Mkdir(name string) (Directory, error)

	// Parent returns the directory holding this directory.
	// Parent returns nil if this is the top-level directory.
	Parent() Directory
}

Directory describes a ROOT directory structure in memory.

func Dir

func Dir(dir Directory) Directory

Dir wraps the given directory to handle fully specified directory names:

rdir := Dir(dir)
obj, err := rdir.Get("some/dir/object/name;1")

type File

type File struct {
	// contains filtered or unexported fields
}

A ROOT file is a suite of consecutive data records (TKey's) with the following format (see also the TKey class). If the key is located past the 32 bit file limit (> 2 GB) then some fields will be 8 instead of 4 bytes:

1->4            Nbytes    = Length of compressed object (in bytes)
5->6            Version   = TKey version identifier
7->10           ObjLen    = Length of uncompressed object
11->14          Datime    = Date and time when object was written to file
15->16          KeyLen    = Length of the key structure (in bytes)
17->18          Cycle     = Cycle of key
19->22 [19->26] SeekKey   = Pointer to record itself (consistency check)
23->26 [27->34] SeekPdir  = Pointer to directory header
27->27 [35->35] lname     = Number of bytes in the class name
28->.. [36->..] ClassName = Object Class Name
..->..          lname     = Number of bytes in the object name
..->..          Name      = lName bytes with the name of the object
..->..          lTitle    = Number of bytes in the object title
..->..          Title     = Title of the object
----->          DATA      = Data bytes associated to the object

The first data record starts at byte fBEGIN (currently set to kBEGIN). Bytes 1->kBEGIN contain the file description, when fVersion >= 1000000 it is a large file (> 2 GB) and the offsets will be 8 bytes long and fUnits will be set to 8:

1->4            "root"      = Root file identifier
5->8            fVersion    = File format version
9->12           fBEGIN      = Pointer to first data record
13->16 [13->20] fEND        = Pointer to first free word at the EOF
17->20 [21->28] fSeekFree   = Pointer to FREE data record
21->24 [29->32] fNbytesFree = Number of bytes in FREE data record
25->28 [33->36] nfree       = Number of free data records
29->32 [37->40] fNbytesName = Number of bytes in TNamed at creation time
33->33 [41->41] fUnits      = Number of bytes for file pointers
34->37 [42->45] fCompress   = Compression level and algorithm
38->41 [46->53] fSeekInfo   = Pointer to TStreamerInfo record
42->45 [54->57] fNbytesInfo = Number of bytes in TStreamerInfo record
46->63 [58->75] fUUID       = Universal Unique ID

func Create

func Create(name string, opts ...FileOption) (*File, error)

Create creates the named ROOT file for writing.

Example
package main

import (
	"fmt"
	"log"
	"os"

	"go-hep.org/x/hep/groot"
	"go-hep.org/x/hep/groot/rbase"
	"go-hep.org/x/hep/groot/root"
)

func main() {
	const fname = "objstring.root"
	defer os.Remove(fname)

	w, err := groot.Create(fname)
	if err != nil {
		log.Fatal(err)
	}
	defer w.Close()

	var (
		k = "my-objstring"
		v = rbase.NewObjString("Hello World from Go-HEP!")
	)

	err = w.Put(k, v)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("wkeys: %d\n", len(w.Keys()))

	err = w.Close()
	if err != nil {
		log.Fatalf("could not close file: %v", err)
	}

	r, err := groot.Open(fname)
	if err != nil {
		log.Fatalf("could not open file: %v", err)
	}
	defer r.Close()

	fmt.Printf("rkeys: %d\n", len(r.Keys()))

	for _, k := range r.Keys() {
		fmt.Printf("key: name=%q, type=%q\n", k.Name(), k.ClassName())
	}

	obj, err := r.Get(k)
	if err != nil {
		log.Fatal(err)
	}
	rv := obj.(root.ObjString)
	fmt.Printf("objstring=%q\n", rv)

}
Output:

wkeys: 1
rkeys: 1
key: name="my-objstring", type="TObjString"
objstring="Hello World from Go-HEP!"
Example (Empty)
package main

import (
	"fmt"
	"log"
	"os"

	"go-hep.org/x/hep/groot"
)

func main() {
	const fname = "empty.root"
	defer os.Remove(fname)

	w, err := groot.Create(fname)
	if err != nil {
		log.Fatal(err)
	}
	defer w.Close()

	// empty file. close it.
	err = w.Close()
	if err != nil {
		log.Fatalf("could not close empty file: %v", err)
	}

	// read back.
	r, err := groot.Open(fname)
	if err != nil {
		log.Fatalf("could not open empty file: %v", err)
	}
	defer r.Close()

	fmt.Printf("file: %q\n", r.Name())

}
Output:

file: "empty.root"
Example (WithZlib)
package main

import (
	"compress/flate"
	"fmt"
	"log"
	"os"

	"go-hep.org/x/hep/groot"
	"go-hep.org/x/hep/groot/rbase"
	"go-hep.org/x/hep/groot/riofs"
	"go-hep.org/x/hep/groot/root"
)

func main() {
	const fname = "objstring-zlib.root"
	defer os.Remove(fname)

	w, err := groot.Create(fname, riofs.WithZlib(flate.BestCompression))
	if err != nil {
		log.Fatal(err)
	}
	defer w.Close()

	var (
		k = "my-objstring"
		v = rbase.NewObjString("Hello World from Go-HEP!")
	)

	err = w.Put(k, v)
	if err != nil {
		log.Fatal(err)
	}

	err = w.Close()
	if err != nil {
		log.Fatalf("could not close writable file: %v", err)
	}

	r, err := groot.Open(fname)
	if err != nil {
		log.Fatalf("could not open file: %v", err)
	}
	defer r.Close()

	for _, k := range r.Keys() {
		fmt.Printf("key: name=%q, type=%q\n", k.Name(), k.ClassName())
	}

	obj, err := r.Get(k)
	if err != nil {
		log.Fatalf("could not get key %q: %v", k, err)
	}
	rv := obj.(root.ObjString)
	fmt.Printf("objstring=%q\n", rv)

}
Output:

key: name="my-objstring", type="TObjString"
objstring="Hello World from Go-HEP!"

func NewReader

func NewReader(r Reader) (*File, error)

NewReader creates a new ROOT file reader.

func Open

func Open(path string) (*File, error)

Open opens the named ROOT file for reading. If successful, methods on the returned file can be used for reading; the associated file descriptor has mode os.O_RDONLY.

func (*File) Class

func (f *File) Class() string

func (*File) Close

func (f *File) Close() error

Close closes the File, rendering it unusable for I/O. It returns an error, if any.

func (*File) Compression added in v0.21.0

func (f *File) Compression() int32

Compression returns the compression-mechanism and compression-level used for this file.

func (*File) Get

func (f *File) Get(namecycle string) (root.Object, error)

Get returns the object identified by namecycle

namecycle has the format name;cycle
name  = * is illegal, cycle = * is illegal
cycle = "" or cycle = 9999 ==> apply to a memory object

examples:
  foo   : get object named foo in memory
          if object is not in memory, try with highest cycle from file
  foo;1 : get cycle 1 of foo on file

func (*File) Keys

func (f *File) Keys() []Key

Keys returns the list of keys this File contains

func (*File) Mkdir added in v0.17.0

func (f *File) Mkdir(name string) (Directory, error)

Mkdir creates a new subdirectory

func (*File) Name

func (f *File) Name() string

func (*File) Parent added in v0.20.0

func (*File) Parent() Directory

Parent returns the directory holding this directory. Parent returns nil if this is the top-level directory.

func (*File) Put

func (f *File) Put(name string, v root.Object) error

Put puts the object v under the key with the given name.

func (*File) Read

func (f *File) Read(p []byte) (int, error)

Read implements io.Reader

func (*File) ReadAt

func (f *File) ReadAt(p []byte, off int64) (int, error)

ReadAt implements io.ReaderAt

func (*File) Records added in v0.20.0

func (f *File) Records(w io.Writer) error

Records writes the records structure of the ROOT file to w.

func (*File) RegisterStreamer added in v0.26.0

func (f *File) RegisterStreamer(streamer rbytes.StreamerInfo)

RegisterStreamer adds the given streamer info to the list of streamers that will be stored in the ROOT file.

func (*File) SegmentMap added in v0.20.0

func (f *File) SegmentMap(w io.Writer) (err error)

SegmentMap displays to w the file's segments map.

func (*File) Stat

func (f *File) Stat() (os.FileInfo, error)

Stat returns the os.FileInfo structure describing this file.

func (*File) StreamerInfo

func (f *File) StreamerInfo(name string, version int) (rbytes.StreamerInfo, error)

StreamerInfo returns the named StreamerInfo. If version is negative, the latest version should be returned.

func (*File) StreamerInfos

func (f *File) StreamerInfos() []rbytes.StreamerInfo

StreamerInfos returns the list of StreamerInfos of this file.

func (*File) Title

func (f *File) Title() string

func (*File) Version

func (f *File) Version() int

Version returns the ROOT version this file was created with.

func (*File) WriteAt added in v0.20.0

func (f *File) WriteAt(p []byte, off int64) (int, error)

WriteAt implements io.WriterAt

type FileOption

type FileOption func(f *File) error

FileOption configures internal states of a ROOT file.

func WithLZ4

func WithLZ4(level int) FileOption

WithLZ4 configures a ROOT file to use LZ4 as a compression mechanism.

func WithLZMA

func WithLZMA(level int) FileOption

WithLZMA configures a ROOT file to use LZMA as a compression mechanism.

func WithZlib

func WithZlib(level int) FileOption

WithZlib configures a ROOT file to use zlib as a compression mechanism.

func WithZstd added in v0.22.0

func WithZstd(level int) FileOption

WithZstd configures a ROOT file to use zstd as a compression mechanism.

func WithoutCompression

func WithoutCompression() FileOption

WithoutCompression configures a ROOT file to not use any compression mechanism.

type Key

type Key struct {
	// contains filtered or unexported fields
}

Key is a key (a label) in a ROOT file

The Key class includes functions to book space on a file,
 to create I/O buffers, to fill these buffers
 to compress/uncompress data buffers.

Before saving (making persistent) an object on a file, a key must
be created. The key structure contains all the information to
uniquely identify a persistent object on a file.
The Key class is used by ROOT:
  - to write an object in the Current Directory
  - to write a new ntuple buffer

func KeyFromDir added in v0.20.0

func KeyFromDir(dir Directory, name, title, class string) Key

KeyFromDir creates a new empty key (with no associated payload object) with provided name and title, and the expected object type name. The key will be held by the provided directory.

func NewKey added in v0.20.0

func NewKey(dir Directory, name, title, class string, cycle int16, obj []byte, f *File) (Key, error)

NewKey creates a new key from the provided serialized object buffer. NewKey puts the key and its payload at the end of the provided file f. Depending on the file configuration, NewKey may compress the provided object buffer.

func NewKeyForBasketInternal added in v0.20.0

func NewKeyForBasketInternal(dir Directory, name, title, class string, cycle int16) Key

NewKeyForBasketInternal creates a new empty key. This is needed for Tree/Branch/Basket persistency.

DO NOT USE.

func (*Key) Buffer added in v0.20.0

func (k *Key) Buffer() []byte

func (*Key) Bytes

func (k *Key) Bytes() ([]byte, error)

Bytes returns the buffer of bytes corresponding to the Key's value

func (*Key) Class

func (*Key) Class() string

func (*Key) ClassName

func (k *Key) ClassName() string

func (*Key) Cycle

func (k *Key) Cycle() int

func (*Key) KeyLen

func (k *Key) KeyLen() int32

func (*Key) Load

func (k *Key) Load(buf []byte) ([]byte, error)

func (*Key) MarshalROOT

func (k *Key) MarshalROOT(w *rbytes.WBuffer) (int, error)

MarshalROOT encodes the key to the provided buffer.

func (*Key) Name

func (k *Key) Name() string

func (*Key) Nbytes added in v0.20.0

func (k *Key) Nbytes() int32

func (*Key) ObjLen

func (k *Key) ObjLen() int32

func (*Key) Object

func (k *Key) Object() (root.Object, error)

Object returns the (ROOT) object corresponding to the Key's value.

func (*Key) ObjectType

func (k *Key) ObjectType() reflect.Type

ObjectType returns the Key's payload type.

ObjectType returns nil if the Key's payload type is not known to the registry of groot.

func (*Key) RVersion

func (k *Key) RVersion() int16

func (*Key) SeekKey added in v0.20.0

func (k *Key) SeekKey() int64

func (*Key) SetBuffer

func (k *Key) SetBuffer(buf []byte)

func (*Key) SetFile

func (k *Key) SetFile(f *File)

func (*Key) Title

func (k *Key) Title() string

func (*Key) UnmarshalROOT

func (k *Key) UnmarshalROOT(r *rbytes.RBuffer) error

UnmarshalROOT decodes the content of data into the Key

func (*Key) Value

func (k *Key) Value() interface{}

Value returns the data corresponding to the Key's value

type Reader

type Reader interface {
	io.Reader
	io.ReaderAt
	io.Closer
}

type SetFiler

type SetFiler interface {
	SetFile(f *File)
}

SetFiler is a simple interface to establish File ownership.

type WalkFunc

type WalkFunc func(path string, obj root.Object, err error) error

WalkFunc is the type of the function called for each object or directory visited by Walk. The path argument contains the argument to Walk as a prefix; that is, if Walk is called with "dir", which is a directory containing the file "a", the walk function will be called with argument "dir/a". The obj argument is the root.Object for the named path.

If there was a problem walking to the file or directory named by path, the incoming error will describe the problem and the function can decide how to handle that error (and Walk will not descend into that directory). In the case of an error, the obj argument will be nil. If an error is returned, processing stops. The sole exception is when the function returns the special value SkipDir. If the function returns SkipDir when invoked on a directory, Walk skips the directory's contents entirely. If the function returns SkipDir when invoked on a non-directory root.Object, Walk skips the remaining keys in the containing directory.

type Writer

type Writer interface {
	io.Writer
	io.WriterAt
	io.Closer
}

Directories

Path Synopsis
plugin
http
Package http is a plugin for riofs.Open to support opening ROOT files over http(s).
Package http is a plugin for riofs.Open to support opening ROOT files over http(s).
xrootd
Package xrootd is a plugin for riofs.Open to support opening ROOT files over xrootd.
Package xrootd is a plugin for riofs.Open to support opening ROOT files over xrootd.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL